opencode-plugin-hub 0.1.1 → 0.1.2

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
@@ -35,7 +35,7 @@ OpenCode 的 GUI 插件 SDK 目前没有提供左侧「主页 / 新建会话」
35
35
  | 命令面板 | 命令面板搜「插件中心」 | 同一个独立弹窗 |
36
36
  | 设置页 | 设置 → 插件中心 | 完整设置页面 |
37
37
 
38
- 弹窗里保留了和设置页一致的紫渐变 Hero:说明文字和配置路径。统计合并到标签页(「已安装」标签带「可更新」数),打开配置 / 重载配置 / 彻底重启合并到标签页右侧的工具栏。只有大标题交给弹窗自己的标题栏,避免重复。标签栏做成 sticky,列表滚动时仍然能切换。弹窗最大约 980×600,内容在弹窗内滚动;想要更宽的空间就用设置页入口。
38
+ 弹窗里保留了和设置页一致的紫渐变 Hero:说明文字和配置路径。统计合并到标签页(「已安装」标签带「可更新」数),打开配置 / 重载配置 / 彻底重启合并到标签页右侧的工具栏。只有大标题交给弹窗自己的标题栏,避免重复。标签栏做成 sticky,列表滚动时仍然能切换。弹窗默认约 1180×780(受窗口大小限制,最小约 720×480),内容在弹窗内滚动;右下角可以拖动调整大小。底部的「在设置中打开」会关闭弹窗并跳到设置里的插件中心页,想要更宽的空间也可以用这个入口。
39
39
 
40
40
  如果以后 SDK 增加侧边栏导航注册表,可以把 Pill 换成真正的侧边栏同级项。
41
41
 
@@ -49,7 +49,7 @@ OpenCode **没有官方插件市场**。官方只提供两种加载方式(配
49
49
 
50
50
  | 来源 | 内容 | 能不能装 |
51
51
  |---|---|---|
52
- | 精选目录(registry.json) | 97 条人工筛选条目,带描述、分类、GitHub 地址 | 75 条可在应用内直接安装,其余 22 条只发布在 GitHub |
52
+ | 精选目录(registry.json) | awesome-opencode 社区维护的 97 条 curated 条目,带描述、分类、GitHub 地址;Hero 里的 awesome-opencode 可点击查看来源 | 75 条可在应用内直接安装,其余 22 条只发布在 GitHub |
53
53
  | npm 搜索 | 带 `opencode-plugin` 标签的包,约 3000 个 | 能,但没有筛选,噪音多 |
54
54
  | 内置快照 | 上面 97 条的构建期副本,断网时兜底 | 同精选目录 |
55
55
 
@@ -57,7 +57,7 @@ OpenCode **没有官方插件市场**。官方只提供两种加载方式(配
57
57
 
58
58
  | 标签 | 说明 |
59
59
  |---|---|
60
- | 精选目录 | 社区 curated 列表,本地筛选(不打网络请求),可按类型过滤,标出可安装/仅 GitHub/已安装/可更新 |
60
+ | 精选目录 | awesome-opencode 社区 curated 列表(Hero 里的来源名可点击),本地筛选(不打网络请求),可按类型过滤,标出可安装/仅 GitHub/已安装/可更新 |
61
61
  | npm 搜索 | 直查 `keywords:opencode-plugin`,覆盖目录之外的长尾;显示周/月下载量、发布者、许可证,可按相关度、下载量或发布时间排序 |
62
62
  | 已安装 | 读配置里的 `plugins` 数组,显示当前版本与最新版本,可查看详情、更新、移除 |
63
63
 
@@ -131,7 +131,7 @@ README 是判断「这个插件具体干啥」最直接的来源。很多包的
131
131
  网络(12 小时 TTL) → 本地缓存 ~/.cache/opencode/plugin-hub/registry.json → 内置快照
132
132
  ```
133
133
 
134
- 断网时页面不会空,只是顶部提示会显示来源是「内置快照」。点「更新目录」可强制跳过缓存。
134
+ 断网时页面不会空,会回退到内置快照,加载时会提示具体错误;Hero 里的 awesome-opencode 仍会链接到实际的 registry 来源。点「更新目录」可强制跳过缓存。
135
135
 
136
136
  ## 为什么装完要重启
137
137
 
@@ -225,7 +225,7 @@ npm publish --access public # publishConfig 会把它发到 registry.
225
225
 
226
226
  ### 后续版本:GitHub Actions + Trusted Publishing
227
227
 
228
- `0.1.0` 已经手动发布。后续版本推荐用 Trusted Publishing,仓库里不存 `NPM_TOKEN`。
228
+ `0.1.0` / `0.1.1` 已发布。后续版本用 **push `v*` tag 自动发布**,仓库里不存 `NPM_TOKEN`。
229
229
 
230
230
  先在 npm 配置 trusted publisher:
231
231
 
@@ -245,13 +245,21 @@ npm version patch
245
245
  git push --follow-tags
246
246
  ```
247
247
 
248
- 然后在 GitHub 上为该 tag 创建 Release(或者在 Actions 里手动运行 **Publish to npm**)。`.github/workflows/publish.yml` 会用 `id-token: write` 和 `npm publish --provenance` 发布,不需要 `NPM_TOKEN`。
248
+ `npm version` 会创建 `v0.1.2` 这样的 annotated tag;`git push --follow-tags` 把 main 和 tag 一起推上去,tag push 会自动触发 **Publish to npm**,不需要创建 Release,也不需要手动点。
249
+
250
+ `.github/workflows/publish.yml` 会:
251
+
252
+ - 校验 tag 和 `package.json` 的 version 一致
253
+ - 用 `id-token: write` 和 `npm publish --provenance` 发布
254
+ - 不需要 `NPM_TOKEN`
249
255
 
250
256
  注意:
251
257
 
252
258
  - `package.json` 的 `version` 必须先 bump,且不能和 npm 上已有版本重复。
253
- - 如果还没配置 Trusted Publishing 就运行 workflow,会发布失败。
254
- - `.github/workflows/ci.yml` 会在 push / PR 时跑 typecheck 和 build。
259
+ - 已发布的 tag 不要强推;要重发就 bump 一个新版本。
260
+ - 如果还没配置 Trusted Publishing 就推 tag,publish workflow 会失败。
261
+ - 如果 tag 没有跟着 push 上去(极少数 `--follow-tags` 情况),补一个 `git push origin v0.1.2`。
262
+ - `.github/workflows/ci.yml` 仍然在 push / PR 时跑 typecheck 和 build;手动 **Publish to npm** 仅作为重试入口保留。
255
263
 
256
264
  ## 已知边界
257
265
 
@@ -1,24 +1,24 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("solid-js/web"),Me=require("@opencode/plugin/gui"),u=require("effect"),Pe=require("node:child_process"),Ce=require("node:os"),E=require("node:path"),q=require("node:fs/promises"),l=require("solid-js"),dn=u.Schema.Struct({spec:u.Schema.String,name:u.Schema.String,kind:u.Schema.String,version:u.Schema.String,removable:u.Schema.Boolean}),Pt=u.Schema.Struct({configPath:u.Schema.String,configKey:u.Schema.String,plugins:u.Schema.Array(dn),error:u.Schema.String}),un=u.Schema.Struct({name:u.Schema.String,version:u.Schema.String,description:u.Schema.String,published:u.Schema.String,updated:u.Schema.String,installed:u.Schema.Boolean,current:u.Schema.String,hasUpdate:u.Schema.Boolean,weekly:u.Schema.Number,monthly:u.Schema.Number,publisher:u.Schema.String,license:u.Schema.String,keywords:u.Schema.Array(u.Schema.String),homepage:u.Schema.String,repo:u.Schema.String,npm:u.Schema.String}),gn=u.Schema.Struct({name:u.Schema.String,version:u.Schema.String,description:u.Schema.String,homepage:u.Schema.String,repo:u.Schema.String,repoDir:u.Schema.String,npm:u.Schema.String,bugs:u.Schema.String,license:u.Schema.String,keywords:u.Schema.Array(u.Schema.String),maintainers:u.Schema.Array(u.Schema.String),weekly:u.Schema.Number,monthly:u.Schema.Number,published:u.Schema.String,created:u.Schema.String,versions:u.Schema.Number,unpacked:u.Schema.Number,files:u.Schema.Number,dependencies:u.Schema.Array(u.Schema.String),deprecated:u.Schema.String,readme:u.Schema.String,readmeTruncated:u.Schema.Boolean,installed:u.Schema.Boolean,current:u.Schema.String,hasUpdate:u.Schema.Boolean,error:u.Schema.String}),hn=u.Schema.Struct({spec:u.Schema.String,name:u.Schema.String,kind:u.Schema.String,current:u.Schema.String,latest:u.Schema.String,hasUpdate:u.Schema.Boolean,managed:u.Schema.Boolean,error:u.Schema.String}),mn=u.Schema.Struct({items:u.Schema.Array(hn),error:u.Schema.String}),fn=u.Schema.Struct({hits:u.Schema.Array(un),error:u.Schema.String}),vn=u.Schema.Struct({name:u.Schema.String,pkg:u.Schema.String,version:u.Schema.String,type:u.Schema.String,desc:u.Schema.String,long:u.Schema.String,repo:u.Schema.String,tags:u.Schema.Array(u.Schema.String),installed:u.Schema.Boolean}),bn=u.Schema.Struct({entries:u.Schema.Array(vn),source:u.Schema.String,fetchedAt:u.Schema.Number,error:u.Schema.String}),yn=u.Schema.Struct({force:u.Schema.Boolean}),wn=u.Schema.Struct({url:u.Schema.String}),Qe=u.Schema.Struct({ok:u.Schema.Boolean}),Xe=u.Schema.Struct({ok:u.Schema.Boolean,message:u.Schema.String,state:Pt}),ht=u.Schema.Struct({spec:u.Schema.String,version:u.Schema.String}),kn=u.Schema.Struct({query:u.Schema.String}),xn=u.Schema.Struct({name:u.Schema.String}),mt=Me.Ipc.define({id:"plugin-hub.admin",methods:{snapshot:{output:Pt},updates:{output:mn},search:{input:kn,output:fn},detail:{input:xn,output:gn},browse:{input:yn,output:bn},openUrl:{input:wn,output:Qe},openConfig:{output:Qe},install:{input:ht,output:Xe},remove:{input:ht,output:Xe},reload:{output:Xe},restart:{output:Qe}}});function zt(e,r=!1){const o=e.length;let n=0,a="",s=0,c=16,p=0,d=0,v=0,h=0,k=0;function $(g,b){let S=0,y=0;for(;S<g;){let f=e.charCodeAt(n);if(f>=48&&f<=57)y=y*16+f-48;else if(f>=65&&f<=70)y=y*16+f-65+10;else if(f>=97&&f<=102)y=y*16+f-97+10;else break;n++,S++}return S<g&&(y=-1),y}function O(g){n=g,a="",s=0,c=16,k=0}function A(){let g=n;if(e.charCodeAt(n)===48)n++;else for(n++;n<e.length&&xe(e.charCodeAt(n));)n++;if(n<e.length&&e.charCodeAt(n)===46)if(n++,n<e.length&&xe(e.charCodeAt(n)))for(n++;n<e.length&&xe(e.charCodeAt(n));)n++;else return k=3,e.substring(g,n);let b=n;if(n<e.length&&(e.charCodeAt(n)===69||e.charCodeAt(n)===101))if(n++,(n<e.length&&e.charCodeAt(n)===43||e.charCodeAt(n)===45)&&n++,n<e.length&&xe(e.charCodeAt(n))){for(n++;n<e.length&&xe(e.charCodeAt(n));)n++;b=n}else k=3;return e.substring(g,b)}function U(){let g="",b=n;for(;;){if(n>=o){g+=e.substring(b,n),k=2;break}const S=e.charCodeAt(n);if(S===34){g+=e.substring(b,n),n++;break}if(S===92){if(g+=e.substring(b,n),n++,n>=o){k=2;break}switch(e.charCodeAt(n++)){case 34:g+='"';break;case 92:g+="\\";break;case 47:g+="/";break;case 98:g+="\b";break;case 102:g+="\f";break;case 110:g+=`
2
- `;break;case 114:g+="\r";break;case 116:g+=" ";break;case 117:const f=$(4);f>=0?g+=String.fromCharCode(f):k=4;break;default:k=5}b=n;continue}if(S>=0&&S<=31)if(Te(S)){g+=e.substring(b,n),k=2;break}else k=6;n++}return g}function L(){if(a="",k=0,s=n,d=p,h=v,n>=o)return s=o,c=17;let g=e.charCodeAt(n);if(Ye(g)){do n++,a+=String.fromCharCode(g),g=e.charCodeAt(n);while(Ye(g));return c=15}if(Te(g))return n++,a+=String.fromCharCode(g),g===13&&e.charCodeAt(n)===10&&(n++,a+=`
3
- `),p++,v=n,c=14;switch(g){case 123:return n++,c=1;case 125:return n++,c=2;case 91:return n++,c=3;case 93:return n++,c=4;case 58:return n++,c=6;case 44:return n++,c=5;case 34:return n++,a=U(),c=10;case 47:const b=n-1;if(e.charCodeAt(n+1)===47){for(n+=2;n<o&&!Te(e.charCodeAt(n));)n++;return a=e.substring(b,n),c=12}if(e.charCodeAt(n+1)===42){n+=2;const S=o-1;let y=!1;for(;n<S;){const f=e.charCodeAt(n);if(f===42&&e.charCodeAt(n+1)===47){n+=2,y=!0;break}n++,Te(f)&&(f===13&&e.charCodeAt(n)===10&&n++,p++,v=n)}return y||(n++,k=1),a=e.substring(b,n),c=13}return a+=String.fromCharCode(g),n++,c=16;case 45:if(a+=String.fromCharCode(g),n++,n===o||!xe(e.charCodeAt(n)))return c=16;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return a+=A(),c=11;default:for(;n<o&&F(g);)n++,g=e.charCodeAt(n);if(s!==n){switch(a=e.substring(s,n),a){case"true":return c=8;case"false":return c=9;case"null":return c=7}return c=16}return a+=String.fromCharCode(g),n++,c=16}}function F(g){if(Ye(g)||Te(g))return!1;switch(g){case 125:case 93:case 123:case 91:case 34:case 58:case 44:case 47:return!1}return!0}function M(){let g;do g=L();while(g>=12&&g<=15);return g}return{setPosition:O,getPosition:()=>n,scan:r?M:L,getToken:()=>c,getTokenValue:()=>a,getTokenOffset:()=>s,getTokenLength:()=>n-s,getTokenStartLine:()=>d,getTokenStartCharacter:()=>s-h,getTokenError:()=>k}}function Ye(e){return e===32||e===9}function Te(e){return e===10||e===13}function xe(e){return e>=48&&e<=57}var ft;(function(e){e[e.lineFeed=10]="lineFeed",e[e.carriageReturn=13]="carriageReturn",e[e.space=32]="space",e[e._0=48]="_0",e[e._1=49]="_1",e[e._2=50]="_2",e[e._3=51]="_3",e[e._4=52]="_4",e[e._5=53]="_5",e[e._6=54]="_6",e[e._7=55]="_7",e[e._8=56]="_8",e[e._9=57]="_9",e[e.a=97]="a",e[e.b=98]="b",e[e.c=99]="c",e[e.d=100]="d",e[e.e=101]="e",e[e.f=102]="f",e[e.g=103]="g",e[e.h=104]="h",e[e.i=105]="i",e[e.j=106]="j",e[e.k=107]="k",e[e.l=108]="l",e[e.m=109]="m",e[e.n=110]="n",e[e.o=111]="o",e[e.p=112]="p",e[e.q=113]="q",e[e.r=114]="r",e[e.s=115]="s",e[e.t=116]="t",e[e.u=117]="u",e[e.v=118]="v",e[e.w=119]="w",e[e.x=120]="x",e[e.y=121]="y",e[e.z=122]="z",e[e.A=65]="A",e[e.B=66]="B",e[e.C=67]="C",e[e.D=68]="D",e[e.E=69]="E",e[e.F=70]="F",e[e.G=71]="G",e[e.H=72]="H",e[e.I=73]="I",e[e.J=74]="J",e[e.K=75]="K",e[e.L=76]="L",e[e.M=77]="M",e[e.N=78]="N",e[e.O=79]="O",e[e.P=80]="P",e[e.Q=81]="Q",e[e.R=82]="R",e[e.S=83]="S",e[e.T=84]="T",e[e.U=85]="U",e[e.V=86]="V",e[e.W=87]="W",e[e.X=88]="X",e[e.Y=89]="Y",e[e.Z=90]="Z",e[e.asterisk=42]="asterisk",e[e.backslash=92]="backslash",e[e.closeBrace=125]="closeBrace",e[e.closeBracket=93]="closeBracket",e[e.colon=58]="colon",e[e.comma=44]="comma",e[e.dot=46]="dot",e[e.doubleQuote=34]="doubleQuote",e[e.minus=45]="minus",e[e.openBrace=123]="openBrace",e[e.openBracket=91]="openBracket",e[e.plus=43]="plus",e[e.slash=47]="slash",e[e.formFeed=12]="formFeed",e[e.tab=9]="tab"})(ft||(ft={}));const ne=new Array(20).fill(0).map((e,r)=>" ".repeat(r)),Se=200,vt={" ":{"\n":new Array(Se).fill(0).map((e,r)=>`
4
- `+" ".repeat(r)),"\r":new Array(Se).fill(0).map((e,r)=>"\r"+" ".repeat(r)),"\r\n":new Array(Se).fill(0).map((e,r)=>`\r
5
- `+" ".repeat(r))}," ":{"\n":new Array(Se).fill(0).map((e,r)=>`
6
- `+" ".repeat(r)),"\r":new Array(Se).fill(0).map((e,r)=>"\r"+" ".repeat(r)),"\r\n":new Array(Se).fill(0).map((e,r)=>`\r
7
- `+" ".repeat(r))}},Sn=[`
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("solid-js/web"),Te=require("@opencode/plugin/gui"),d=require("effect"),Le=require("node:child_process"),$e=require("node:os"),D=require("node:path"),H=require("node:fs/promises"),l=require("solid-js"),pn=d.Schema.Struct({spec:d.Schema.String,name:d.Schema.String,kind:d.Schema.String,version:d.Schema.String,removable:d.Schema.Boolean}),jt=d.Schema.Struct({configPath:d.Schema.String,configKey:d.Schema.String,plugins:d.Schema.Array(pn),error:d.Schema.String}),dn=d.Schema.Struct({name:d.Schema.String,version:d.Schema.String,description:d.Schema.String,published:d.Schema.String,updated:d.Schema.String,installed:d.Schema.Boolean,current:d.Schema.String,hasUpdate:d.Schema.Boolean,weekly:d.Schema.Number,monthly:d.Schema.Number,publisher:d.Schema.String,license:d.Schema.String,keywords:d.Schema.Array(d.Schema.String),homepage:d.Schema.String,repo:d.Schema.String,npm:d.Schema.String}),un=d.Schema.Struct({name:d.Schema.String,version:d.Schema.String,description:d.Schema.String,homepage:d.Schema.String,repo:d.Schema.String,repoDir:d.Schema.String,npm:d.Schema.String,bugs:d.Schema.String,license:d.Schema.String,keywords:d.Schema.Array(d.Schema.String),maintainers:d.Schema.Array(d.Schema.String),weekly:d.Schema.Number,monthly:d.Schema.Number,published:d.Schema.String,created:d.Schema.String,versions:d.Schema.Number,unpacked:d.Schema.Number,files:d.Schema.Number,dependencies:d.Schema.Array(d.Schema.String),deprecated:d.Schema.String,readme:d.Schema.String,readmeTruncated:d.Schema.Boolean,installed:d.Schema.Boolean,current:d.Schema.String,hasUpdate:d.Schema.Boolean,error:d.Schema.String}),gn=d.Schema.Struct({spec:d.Schema.String,name:d.Schema.String,kind:d.Schema.String,current:d.Schema.String,latest:d.Schema.String,hasUpdate:d.Schema.Boolean,managed:d.Schema.Boolean,error:d.Schema.String}),hn=d.Schema.Struct({items:d.Schema.Array(gn),error:d.Schema.String}),mn=d.Schema.Struct({hits:d.Schema.Array(dn),error:d.Schema.String}),fn=d.Schema.Struct({name:d.Schema.String,pkg:d.Schema.String,version:d.Schema.String,type:d.Schema.String,desc:d.Schema.String,long:d.Schema.String,repo:d.Schema.String,tags:d.Schema.Array(d.Schema.String),installed:d.Schema.Boolean}),vn=d.Schema.Struct({entries:d.Schema.Array(fn),source:d.Schema.String,registry:d.Schema.optional(d.Schema.String),fetchedAt:d.Schema.Number,error:d.Schema.String}),bn=d.Schema.Struct({force:d.Schema.Boolean}),yn=d.Schema.Struct({url:d.Schema.String}),Xe=d.Schema.Struct({ok:d.Schema.Boolean}),Ye=d.Schema.Struct({ok:d.Schema.Boolean,message:d.Schema.String,state:jt}),gt=d.Schema.Struct({spec:d.Schema.String,version:d.Schema.String}),wn=d.Schema.Struct({query:d.Schema.String}),kn=d.Schema.Struct({name:d.Schema.String}),ht=Te.Ipc.define({id:"plugin-hub.admin",methods:{snapshot:{output:jt},updates:{output:hn},search:{input:wn,output:mn},detail:{input:kn,output:un},browse:{input:bn,output:vn},openUrl:{input:yn,output:Xe},openConfig:{output:Xe},install:{input:gt,output:Ye},remove:{input:gt,output:Ye},reload:{output:Ye},restart:{output:Xe}}});function zt(e,o=!1){const r=e.length;let n=0,i="",s=0,c=16,p=0,u=0,v=0,h=0,w=0;function S(g,b){let x=0,y=0;for(;x<g;){let f=e.charCodeAt(n);if(f>=48&&f<=57)y=y*16+f-48;else if(f>=65&&f<=70)y=y*16+f-65+10;else if(f>=97&&f<=102)y=y*16+f-97+10;else break;n++,x++}return x<g&&(y=-1),y}function _(g){n=g,i="",s=0,c=16,w=0}function O(){let g=n;if(e.charCodeAt(n)===48)n++;else for(n++;n<e.length&&ke(e.charCodeAt(n));)n++;if(n<e.length&&e.charCodeAt(n)===46)if(n++,n<e.length&&ke(e.charCodeAt(n)))for(n++;n<e.length&&ke(e.charCodeAt(n));)n++;else return w=3,e.substring(g,n);let b=n;if(n<e.length&&(e.charCodeAt(n)===69||e.charCodeAt(n)===101))if(n++,(n<e.length&&e.charCodeAt(n)===43||e.charCodeAt(n)===45)&&n++,n<e.length&&ke(e.charCodeAt(n))){for(n++;n<e.length&&ke(e.charCodeAt(n));)n++;b=n}else w=3;return e.substring(g,b)}function U(){let g="",b=n;for(;;){if(n>=r){g+=e.substring(b,n),w=2;break}const x=e.charCodeAt(n);if(x===34){g+=e.substring(b,n),n++;break}if(x===92){if(g+=e.substring(b,n),n++,n>=r){w=2;break}switch(e.charCodeAt(n++)){case 34:g+='"';break;case 92:g+="\\";break;case 47:g+="/";break;case 98:g+="\b";break;case 102:g+="\f";break;case 110:g+=`
2
+ `;break;case 114:g+="\r";break;case 116:g+=" ";break;case 117:const f=S(4);f>=0?g+=String.fromCharCode(f):w=4;break;default:w=5}b=n;continue}if(x>=0&&x<=31)if(Oe(x)){g+=e.substring(b,n),w=2;break}else w=6;n++}return g}function M(){if(i="",w=0,s=n,u=p,h=v,n>=r)return s=r,c=17;let g=e.charCodeAt(n);if(Ze(g)){do n++,i+=String.fromCharCode(g),g=e.charCodeAt(n);while(Ze(g));return c=15}if(Oe(g))return n++,i+=String.fromCharCode(g),g===13&&e.charCodeAt(n)===10&&(n++,i+=`
3
+ `),p++,v=n,c=14;switch(g){case 123:return n++,c=1;case 125:return n++,c=2;case 91:return n++,c=3;case 93:return n++,c=4;case 58:return n++,c=6;case 44:return n++,c=5;case 34:return n++,i=U(),c=10;case 47:const b=n-1;if(e.charCodeAt(n+1)===47){for(n+=2;n<r&&!Oe(e.charCodeAt(n));)n++;return i=e.substring(b,n),c=12}if(e.charCodeAt(n+1)===42){n+=2;const x=r-1;let y=!1;for(;n<x;){const f=e.charCodeAt(n);if(f===42&&e.charCodeAt(n+1)===47){n+=2,y=!0;break}n++,Oe(f)&&(f===13&&e.charCodeAt(n)===10&&n++,p++,v=n)}return y||(n++,w=1),i=e.substring(b,n),c=13}return i+=String.fromCharCode(g),n++,c=16;case 45:if(i+=String.fromCharCode(g),n++,n===r||!ke(e.charCodeAt(n)))return c=16;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return i+=O(),c=11;default:for(;n<r&&F(g);)n++,g=e.charCodeAt(n);if(s!==n){switch(i=e.substring(s,n),i){case"true":return c=8;case"false":return c=9;case"null":return c=7}return c=16}return i+=String.fromCharCode(g),n++,c=16}}function F(g){if(Ze(g)||Oe(g))return!1;switch(g){case 125:case 93:case 123:case 91:case 34:case 58:case 44:case 47:return!1}return!0}function j(){let g;do g=M();while(g>=12&&g<=15);return g}return{setPosition:_,getPosition:()=>n,scan:o?j:M,getToken:()=>c,getTokenValue:()=>i,getTokenOffset:()=>s,getTokenLength:()=>n-s,getTokenStartLine:()=>u,getTokenStartCharacter:()=>s-h,getTokenError:()=>w}}function Ze(e){return e===32||e===9}function Oe(e){return e===10||e===13}function ke(e){return e>=48&&e<=57}var mt;(function(e){e[e.lineFeed=10]="lineFeed",e[e.carriageReturn=13]="carriageReturn",e[e.space=32]="space",e[e._0=48]="_0",e[e._1=49]="_1",e[e._2=50]="_2",e[e._3=51]="_3",e[e._4=52]="_4",e[e._5=53]="_5",e[e._6=54]="_6",e[e._7=55]="_7",e[e._8=56]="_8",e[e._9=57]="_9",e[e.a=97]="a",e[e.b=98]="b",e[e.c=99]="c",e[e.d=100]="d",e[e.e=101]="e",e[e.f=102]="f",e[e.g=103]="g",e[e.h=104]="h",e[e.i=105]="i",e[e.j=106]="j",e[e.k=107]="k",e[e.l=108]="l",e[e.m=109]="m",e[e.n=110]="n",e[e.o=111]="o",e[e.p=112]="p",e[e.q=113]="q",e[e.r=114]="r",e[e.s=115]="s",e[e.t=116]="t",e[e.u=117]="u",e[e.v=118]="v",e[e.w=119]="w",e[e.x=120]="x",e[e.y=121]="y",e[e.z=122]="z",e[e.A=65]="A",e[e.B=66]="B",e[e.C=67]="C",e[e.D=68]="D",e[e.E=69]="E",e[e.F=70]="F",e[e.G=71]="G",e[e.H=72]="H",e[e.I=73]="I",e[e.J=74]="J",e[e.K=75]="K",e[e.L=76]="L",e[e.M=77]="M",e[e.N=78]="N",e[e.O=79]="O",e[e.P=80]="P",e[e.Q=81]="Q",e[e.R=82]="R",e[e.S=83]="S",e[e.T=84]="T",e[e.U=85]="U",e[e.V=86]="V",e[e.W=87]="W",e[e.X=88]="X",e[e.Y=89]="Y",e[e.Z=90]="Z",e[e.asterisk=42]="asterisk",e[e.backslash=92]="backslash",e[e.closeBrace=125]="closeBrace",e[e.closeBracket=93]="closeBracket",e[e.colon=58]="colon",e[e.comma=44]="comma",e[e.dot=46]="dot",e[e.doubleQuote=34]="doubleQuote",e[e.minus=45]="minus",e[e.openBrace=123]="openBrace",e[e.openBracket=91]="openBracket",e[e.plus=43]="plus",e[e.slash=47]="slash",e[e.formFeed=12]="formFeed",e[e.tab=9]="tab"})(mt||(mt={}));const ne=new Array(20).fill(0).map((e,o)=>" ".repeat(o)),xe=200,ft={" ":{"\n":new Array(xe).fill(0).map((e,o)=>`
4
+ `+" ".repeat(o)),"\r":new Array(xe).fill(0).map((e,o)=>"\r"+" ".repeat(o)),"\r\n":new Array(xe).fill(0).map((e,o)=>`\r
5
+ `+" ".repeat(o))}," ":{"\n":new Array(xe).fill(0).map((e,o)=>`
6
+ `+" ".repeat(o)),"\r":new Array(xe).fill(0).map((e,o)=>"\r"+" ".repeat(o)),"\r\n":new Array(xe).fill(0).map((e,o)=>`\r
7
+ `+" ".repeat(o))}},xn=[`
8
8
  `,"\r",`\r
9
- `];function $n(e,r,o){let n,a,s,c,p;if(r){for(c=r.offset,p=c+r.length,s=c;s>0&&!De(e,s-1);)s--;let S=p;for(;S<e.length&&!De(e,S);)S++;a=e.substring(s,S),n=Cn(a,o)}else a=e,n=0,s=0,c=0,p=e.length;const d=_n(o,e),v=Sn.includes(d);let h=0,k=0,$;o.insertSpaces?$=ne[o.tabSize||4]??$e(ne[1],o.tabSize||4):$=" ";const O=$===" "?" ":" ";let A=zt(a,!1),U=!1;function L(){if(h>1)return $e(d,h)+$e($,n+k);const S=$.length*(n+k);return!v||S>vt[O][d].length?d+$e($,n+k):S<=0?d:vt[O][d][S]}function F(){let S=A.scan();for(h=0;S===15||S===14;)S===14&&o.keepLines?h+=1:S===14&&(h=1),S=A.scan();return U=S===16||A.getTokenError()!==0,S}const M=[];function g(S,y,f){!U&&(!r||y<p&&f>c)&&e.substring(y,f)!==S&&M.push({offset:y,length:f-y,content:S})}let b=F();if(o.keepLines&&h>0&&g($e(d,h),0,0),b!==17){let S=A.getTokenOffset()+s,y=$.length*n<20&&o.insertSpaces?ne[$.length*n]:$e($,n);g(y,s,S)}for(;b!==17;){let S=A.getTokenOffset()+A.getTokenLength()+s,y=F(),f="",H=!1;for(;h===0&&(y===12||y===13);){let pe=A.getTokenOffset()+s;g(ne[1],S,pe),S=A.getTokenOffset()+A.getTokenLength()+s,H=y===12,f=H?L():"",y=F()}if(y===2)b!==1&&k--,o.keepLines&&h>0||!o.keepLines&&b!==1?f=L():o.keepLines&&(f=ne[1]);else if(y===4)b!==3&&k--,o.keepLines&&h>0||!o.keepLines&&b!==3?f=L():o.keepLines&&(f=ne[1]);else{switch(b){case 3:case 1:k++,o.keepLines&&h>0||!o.keepLines?f=L():f=ne[1];break;case 5:o.keepLines&&h>0||!o.keepLines?f=L():f=ne[1];break;case 12:f=L();break;case 13:h>0?f=L():H||(f=ne[1]);break;case 6:o.keepLines&&h>0?f=L():H||(f=ne[1]);break;case 10:o.keepLines&&h>0?f=L():y===6&&!H&&(f="");break;case 7:case 8:case 9:case 11:case 2:case 4:o.keepLines&&h>0?f=L():(y===12||y===13)&&!H?f=ne[1]:y!==5&&y!==17&&(U=!0);break;case 16:U=!0;break}h>0&&(y===12||y===13)&&(f=L())}y===17&&(o.keepLines&&h>0?f=L():f=o.insertFinalNewline?d:"");const re=A.getTokenOffset()+s;g(f,S,re),b=y}return M}function $e(e,r){let o="";for(let n=0;n<r;n++)o+=e;return o}function Cn(e,r){let o=0,n=0;const a=r.tabSize||4;for(;o<e.length;){let s=e.charAt(o);if(s===ne[1])n++;else if(s===" ")n+=a;else break;o++}return Math.floor(n/a)}function _n(e,r){for(let o=0;o<r.length;o++){const n=r.charAt(o);if(n==="\r")return o+1<r.length&&r.charAt(o+1)===`
9
+ `];function Sn(e,o,r){let n,i,s,c,p;if(o){for(c=o.offset,p=c+o.length,s=c;s>0&&!De(e,s-1);)s--;let x=p;for(;x<e.length&&!De(e,x);)x++;i=e.substring(s,x),n=$n(i,r)}else i=e,n=0,s=0,c=0,p=e.length;const u=Cn(r,e),v=xn.includes(u);let h=0,w=0,S;r.insertSpaces?S=ne[r.tabSize||4]??Se(ne[1],r.tabSize||4):S=" ";const _=S===" "?" ":" ";let O=zt(i,!1),U=!1;function M(){if(h>1)return Se(u,h)+Se(S,n+w);const x=S.length*(n+w);return!v||x>ft[_][u].length?u+Se(S,n+w):x<=0?u:ft[_][u][x]}function F(){let x=O.scan();for(h=0;x===15||x===14;)x===14&&r.keepLines?h+=1:x===14&&(h=1),x=O.scan();return U=x===16||O.getTokenError()!==0,x}const j=[];function g(x,y,f){!U&&(!o||y<p&&f>c)&&e.substring(y,f)!==x&&j.push({offset:y,length:f-y,content:x})}let b=F();if(r.keepLines&&h>0&&g(Se(u,h),0,0),b!==17){let x=O.getTokenOffset()+s,y=S.length*n<20&&r.insertSpaces?ne[S.length*n]:Se(S,n);g(y,s,x)}for(;b!==17;){let x=O.getTokenOffset()+O.getTokenLength()+s,y=F(),f="",V=!1;for(;h===0&&(y===12||y===13);){let ce=O.getTokenOffset()+s;g(ne[1],x,ce),x=O.getTokenOffset()+O.getTokenLength()+s,V=y===12,f=V?M():"",y=F()}if(y===2)b!==1&&w--,r.keepLines&&h>0||!r.keepLines&&b!==1?f=M():r.keepLines&&(f=ne[1]);else if(y===4)b!==3&&w--,r.keepLines&&h>0||!r.keepLines&&b!==3?f=M():r.keepLines&&(f=ne[1]);else{switch(b){case 3:case 1:w++,r.keepLines&&h>0||!r.keepLines?f=M():f=ne[1];break;case 5:r.keepLines&&h>0||!r.keepLines?f=M():f=ne[1];break;case 12:f=M();break;case 13:h>0?f=M():V||(f=ne[1]);break;case 6:r.keepLines&&h>0?f=M():V||(f=ne[1]);break;case 10:r.keepLines&&h>0?f=M():y===6&&!V&&(f="");break;case 7:case 8:case 9:case 11:case 2:case 4:r.keepLines&&h>0?f=M():(y===12||y===13)&&!V?f=ne[1]:y!==5&&y!==17&&(U=!0);break;case 16:U=!0;break}h>0&&(y===12||y===13)&&(f=M())}y===17&&(r.keepLines&&h>0?f=M():f=r.insertFinalNewline?u:"");const re=O.getTokenOffset()+s;g(f,x,re),b=y}return j}function Se(e,o){let r="";for(let n=0;n<o;n++)r+=e;return r}function $n(e,o){let r=0,n=0;const i=o.tabSize||4;for(;r<e.length;){let s=e.charAt(r);if(s===ne[1])n++;else if(s===" ")n+=i;else break;r++}return Math.floor(n/i)}function Cn(e,o){for(let r=0;r<o.length;r++){const n=o.charAt(r);if(n==="\r")return r+1<o.length&&o.charAt(r+1)===`
10
10
  `?`\r
11
11
  `:"\r";if(n===`
12
12
  `)return`
13
13
  `}return e&&e.eol||`
14
- `}function De(e,r){return`\r
15
- `.indexOf(e.charAt(r))!==-1}var je;(function(e){e.DEFAULT={allowTrailingComma:!1}})(je||(je={}));function On(e,r=[],o=je.DEFAULT){let n=null,a=[];const s=[];function c(d){Array.isArray(a)?a.push(d):n!==null&&(a[n]=d)}return Bt(e,{onObjectBegin:()=>{const d={};c(d),s.push(a),a=d,n=null},onObjectProperty:d=>{n=d},onObjectEnd:()=>{a=s.pop()},onArrayBegin:()=>{const d=[];c(d),s.push(a),a=d,n=null},onArrayEnd:()=>{a=s.pop()},onLiteralValue:c,onError:(d,v,h)=>{r.push({error:d,offset:v,length:h})}},o),a[0]}function An(e,r=[],o=je.DEFAULT){let n={type:"array",offset:-1,length:-1,children:[],parent:void 0};function a(d){n.type==="property"&&(n.length=d-n.offset,n=n.parent)}function s(d){return n.children.push(d),d}Bt(e,{onObjectBegin:d=>{n=s({type:"object",offset:d,length:-1,parent:n,children:[]})},onObjectProperty:(d,v,h)=>{n=s({type:"property",offset:v,length:-1,parent:n,children:[]}),n.children.push({type:"string",value:d,offset:v,length:h,parent:n})},onObjectEnd:(d,v)=>{a(d+v),n.length=d+v-n.offset,n=n.parent,a(d+v)},onArrayBegin:(d,v)=>{n=s({type:"array",offset:d,length:-1,parent:n,children:[]})},onArrayEnd:(d,v)=>{n.length=d+v-n.offset,n=n.parent,a(d+v)},onLiteralValue:(d,v,h)=>{s({type:In(d),offset:v,length:h,parent:n,value:d}),a(v+h)},onSeparator:(d,v,h)=>{n.type==="property"&&(d===":"?n.colonOffset=v:d===","&&a(v))},onError:(d,v,h)=>{r.push({error:d,offset:v,length:h})}},o);const p=n.children[0];return p&&delete p.parent,p}function bt(e,r){if(!e)return;let o=e;for(let n of r)if(typeof n=="string"){if(o.type!=="object"||!Array.isArray(o.children))return;let a=!1;for(const s of o.children)if(Array.isArray(s.children)&&s.children[0].value===n&&s.children.length===2){o=s.children[1],a=!0;break}if(!a)return}else{const a=n;if(o.type!=="array"||a<0||!Array.isArray(o.children)||a>=o.children.length)return;o=o.children[a]}return o}function Bt(e,r,o=je.DEFAULT){const n=zt(e,!1),a=[];let s=0;function c(_){return _?()=>s===0&&_(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function p(_){return _?B=>s===0&&_(B,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function d(_){return _?B=>s===0&&_(B,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>a.slice()):()=>!0}function v(_){return _?()=>{s>0?s++:_(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>a.slice())===!1&&(s=1)}:()=>!0}function h(_){return _?()=>{s>0&&s--,s===0&&_(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:()=>!0}const k=v(r.onObjectBegin),$=d(r.onObjectProperty),O=h(r.onObjectEnd),A=v(r.onArrayBegin),U=h(r.onArrayEnd),L=d(r.onLiteralValue),F=p(r.onSeparator),M=c(r.onComment),g=p(r.onError),b=o&&o.disallowComments,S=o&&o.allowTrailingComma;function y(){for(;;){const _=n.scan();switch(n.getTokenError()){case 4:f(14);break;case 5:f(15);break;case 3:f(13);break;case 1:b||f(11);break;case 2:f(12);break;case 6:f(16);break}switch(_){case 12:case 13:b?f(10):M();break;case 16:f(1);break;case 15:case 14:break;default:return _}}}function f(_,B=[],R=[]){if(g(_),B.length+R.length>0){let be=n.getToken();for(;be!==17;){if(B.indexOf(be)!==-1){y();break}else if(R.indexOf(be)!==-1)break;be=y()}}}function H(_){const B=n.getTokenValue();return _?L(B):($(B),a.push(B)),y(),!0}function re(){switch(n.getToken()){case 11:const _=n.getTokenValue();let B=Number(_);isNaN(B)&&(f(2),B=0),L(B);break;case 7:L(null);break;case 8:L(!0);break;case 9:L(!1);break;default:return!1}return y(),!0}function pe(){return n.getToken()!==10?(f(3,[],[2,5]),!1):(H(!1),n.getToken()===6?(F(":"),y(),W()||f(4,[],[2,5])):f(5,[],[2,5]),a.pop(),!0)}function ve(){k(),y();let _=!1;for(;n.getToken()!==2&&n.getToken()!==17;){if(n.getToken()===5){if(_||f(4,[],[]),F(","),y(),n.getToken()===2&&S)break}else _&&f(6,[],[]);pe()||f(4,[],[2,5]),_=!0}return O(),n.getToken()!==2?f(7,[2],[]):y(),!0}function Oe(){A(),y();let _=!0,B=!1;for(;n.getToken()!==4&&n.getToken()!==17;){if(n.getToken()===5){if(B||f(4,[],[]),F(","),y(),n.getToken()===4&&S)break}else B&&f(6,[],[]);_?(a.push(0),_=!1):a[a.length-1]++,W()||f(4,[],[4,5]),B=!0}return U(),_||a.pop(),n.getToken()!==4?f(8,[4],[]):y(),!0}function W(){switch(n.getToken()){case 3:return Oe();case 1:return ve();case 10:return H(!0);default:return re()}}return y(),n.getToken()===17?o.allowEmptyContent?!0:(f(4,[],[]),!1):W()?(n.getToken()!==17&&f(9,[],[]),!0):(f(4,[],[]),!1)}function In(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string";case"object":{if(e){if(Array.isArray(e))return"array"}else return"null";return"object"}default:return"null"}}function Tn(e,r,o,n){const a=r.slice(),c=An(e,[]);let p,d;for(;a.length>0&&(d=a.pop(),p=bt(c,a),p===void 0&&o!==void 0);)typeof d=="string"?o={[d]:o}:o=[o];if(p)if(p.type==="object"&&typeof d=="string"&&Array.isArray(p.children)){const v=bt(p,[d]);if(v!==void 0)if(o===void 0){if(!v.parent)throw new Error("Malformed AST");const h=p.children.indexOf(v.parent);let k,$=v.parent.offset+v.parent.length;if(h>0){let O=p.children[h-1];k=O.offset+O.length}else k=p.offset+1,p.children.length>1&&($=p.children[1].offset);return me(e,{offset:k,length:$-k,content:""},n)}else return me(e,{offset:v.offset,length:v.length,content:JSON.stringify(o)},n);else{if(o===void 0)return[];const h=`${JSON.stringify(d)}: ${JSON.stringify(o)}`,k=n.getInsertionIndex?n.getInsertionIndex(p.children.map(O=>O.children[0].value)):p.children.length;let $;if(k>0){let O=p.children[k-1];$={offset:O.offset+O.length,length:0,content:","+h}}else p.children.length===0?$={offset:p.offset+1,length:0,content:h}:$={offset:p.offset+1,length:0,content:h+","};return me(e,$,n)}}else if(p.type==="array"&&typeof d=="number"&&Array.isArray(p.children)){const v=d;if(v===-1){const h=`${JSON.stringify(o)}`;let k;if(p.children.length===0)k={offset:p.offset+1,length:0,content:h};else{const $=p.children[p.children.length-1];k={offset:$.offset+$.length,length:0,content:","+h}}return me(e,k,n)}else if(o===void 0&&p.children.length>=0){const h=d,k=p.children[h];let $;if(p.children.length===1)$={offset:p.offset+1,length:p.length-2,content:""};else if(p.children.length-1===h){let O=p.children[h-1],A=O.offset+O.length,U=p.offset+p.length;$={offset:A,length:U-2-A,content:""}}else $={offset:k.offset,length:p.children[h+1].offset-k.offset,content:""};return me(e,$,n)}else if(o!==void 0){let h;const k=`${JSON.stringify(o)}`;if(!n.isArrayInsertion&&p.children.length>d){const $=p.children[d];h={offset:$.offset,length:$.length,content:k}}else if(p.children.length===0||d===0)h={offset:p.offset+1,length:0,content:p.children.length===0?k:k+","};else{const $=d>p.children.length?p.children.length:d,O=p.children[$-1];h={offset:O.offset+O.length,length:0,content:","+k}}return me(e,h,n)}else throw new Error(`Can not ${o===void 0?"remove":n.isArrayInsertion?"insert":"modify"} Array index ${v} as length is not sufficient`)}else throw new Error(`Can not add ${typeof d!="number"?"index":"property"} to parent of type ${p.type}`);else{if(o===void 0)throw new Error("Can not delete in empty document");return me(e,{offset:c?c.offset:0,length:c?c.length:0,content:JSON.stringify(o)},n)}}function me(e,r,o){if(!o.formattingOptions)return[r];let n=et(e,r),a=r.offset,s=r.offset+r.content.length;if(r.length===0||r.content.length===0){for(;a>0&&!De(n,a-1);)a--;for(;s<n.length&&!De(n,s);)s++}const c=$n(n,{offset:a,length:s-a},{...o.formattingOptions,keepLines:!1});for(let d=c.length-1;d>=0;d--){const v=c[d];n=et(n,v),a=Math.min(a,v.offset),s=Math.max(s,v.offset+v.length),s+=v.content.length-v.length}const p=e.length-(n.length-s)-a;return[{offset:a,length:p,content:n.substring(a,s)}]}function et(e,r){return e.substring(0,r.offset)+r.content+e.substring(r.offset+r.length)}var yt;(function(e){e[e.None=0]="None",e[e.UnexpectedEndOfComment=1]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=2]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=3]="UnexpectedEndOfNumber",e[e.InvalidUnicode=4]="InvalidUnicode",e[e.InvalidEscapeCharacter=5]="InvalidEscapeCharacter",e[e.InvalidCharacter=6]="InvalidCharacter"})(yt||(yt={}));var wt;(function(e){e[e.OpenBraceToken=1]="OpenBraceToken",e[e.CloseBraceToken=2]="CloseBraceToken",e[e.OpenBracketToken=3]="OpenBracketToken",e[e.CloseBracketToken=4]="CloseBracketToken",e[e.CommaToken=5]="CommaToken",e[e.ColonToken=6]="ColonToken",e[e.NullKeyword=7]="NullKeyword",e[e.TrueKeyword=8]="TrueKeyword",e[e.FalseKeyword=9]="FalseKeyword",e[e.StringLiteral=10]="StringLiteral",e[e.NumericLiteral=11]="NumericLiteral",e[e.LineCommentTrivia=12]="LineCommentTrivia",e[e.BlockCommentTrivia=13]="BlockCommentTrivia",e[e.LineBreakTrivia=14]="LineBreakTrivia",e[e.Trivia=15]="Trivia",e[e.Unknown=16]="Unknown",e[e.EOF=17]="EOF"})(wt||(wt={}));const Ln=On;var kt;(function(e){e[e.InvalidSymbol=1]="InvalidSymbol",e[e.InvalidNumberFormat=2]="InvalidNumberFormat",e[e.PropertyNameExpected=3]="PropertyNameExpected",e[e.ValueExpected=4]="ValueExpected",e[e.ColonExpected=5]="ColonExpected",e[e.CommaExpected=6]="CommaExpected",e[e.CloseBraceExpected=7]="CloseBraceExpected",e[e.CloseBracketExpected=8]="CloseBracketExpected",e[e.EndOfFileExpected=9]="EndOfFileExpected",e[e.InvalidCommentToken=10]="InvalidCommentToken",e[e.UnexpectedEndOfComment=11]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=12]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=13]="UnexpectedEndOfNumber",e[e.InvalidUnicode=14]="InvalidUnicode",e[e.InvalidEscapeCharacter=15]="InvalidEscapeCharacter",e[e.InvalidCharacter=16]="InvalidCharacter"})(kt||(kt={}));function Mn(e,r,o,n){return Tn(e,r,o,n)}function jn(e,r){let o=r.slice(0).sort((a,s)=>{const c=a.offset-s.offset;return c===0?a.length-s.length:c}),n=e.length;for(let a=o.length-1;a>=0;a--){let s=o[a];if(s.offset+s.length<=n)e=et(e,s);else throw new Error("Overlapping edit");n=s.offset}return e}const Et="opencode-plugin-hub",xt="opencode-plugin",Pn=300*1e3;function Dt(){const e=process.env.XDG_CONFIG_HOME;return e?E.join(e,"opencode"):E.join(Ce.homedir(),".config","opencode")}function zn(){const e=process.env.XDG_CACHE_HOME;return e?E.join(e,"opencode","npm"):E.join(Ce.homedir(),".cache","opencode","npm")}async function fe(){const e=Dt(),r=[E.join(e,"opencode.json"),E.join(e,"opencode.jsonc")];let o;for(const n of r){let a;try{a=await q.readFile(n,"utf8")}catch{continue}const c=Ln(a,[],{allowTrailingComma:!0,disallowComments:!1}),p=c&&typeof c=="object"?c:{},d=Array.isArray(p.plugins)?"plugins":Array.isArray(p.plugin)?"plugin":"plugins",v=Array.isArray(p[d])?p[d].filter(k=>typeof k=="string"):[],h={path:n,text:a,key:d,plugins:v};if(h.plugins.length>0)return h;o||(o=h)}return o||{path:E.join(e,"opencode.json"),text:`{}
16
- `,key:"plugins",plugins:[]}}function Re(e){if(!/^(@[a-z0-9-_.]+\/)?[a-z0-9-_.]+$/i.test(e)||e.includes(".."))return!1;const r=e.split("/").pop()??e;return r.length>0&&!r.startsWith(".")}function _e(e){const r=e.trim();if(r.startsWith("/")||r.startsWith(".")||r.startsWith("~/"))return{name:r.replace(/\/+$/,"").split("/").pop()??r,range:"",kind:"path"};const o=r.startsWith("@"),n=r.indexOf("@",o?1:0);return n>0?{name:r.slice(0,n),range:r.slice(n+1),kind:"package"}:{name:r,range:"",kind:"package"}}function at(e){const r=e.trim();return r==="~"?Ce.homedir():r.startsWith("~/")?E.join(Ce.homedir(),r.slice(2)):r}async function Ne(e){try{const r=await q.readFile(E.join(e,"package.json"),"utf8"),o=JSON.parse(r);return typeof o=="object"&&o!==null?o:void 0}catch{return}}async function Bn(e,r){const o=r&&r!=="latest"?r:"latest",n=Array.from(new Set([`${e}@${o}`,`${e}@latest`]));for(const a of n){const s=E.join(zn(),a);let c;try{c=await q.readdir(s)}catch{continue}for(const p of c.reverse())try{const d=E.join(s,p,"node_modules",e,"package.json"),v=await q.readFile(d,"utf8"),h=JSON.parse(v);if(h.version)return h.version}catch{}}return""}function En(e){const r=e.trim().replace(/^[\^~=v]+/,"").replace(/^>\=?|^<\=?/,"");return/^\d+(\.\d+)*([-+][\w.-]+)?$/.test(r)?r:""}function St(e){return e.split("-")[0].split(".").map(r=>Number.parseInt(r,10)||0)}function st(e,r){if(!e)return!1;if(!r)return!0;const o=St(e),n=St(r);for(let a=0;a<Math.max(o.length,n.length);a+=1){const s=o[a]??0,c=n[a]??0;if(s!==c)return s>c}return!1}const $t=new Map;async function Dn(e,r=!1){const o=$t.get(e);if(!r&&o&&Date.now()-o.at<Pn)return o.value;const n=`https://registry.npmjs.org/${e.replace("/","%2f")}`;let a;try{a=await fetch(n,{signal:AbortSignal.timeout(1e4),headers:{accept:"application/vnd.npm.install-v1+json"}})}catch{return{version:"",ok:!1}}if(a.status===404)return{version:"",ok:!0};if(!a.ok)return{version:"",ok:!1};try{const s=await a.json(),c=Object.keys(s.versions??{});if(c.length===0)return{version:"",ok:!0};const p=s["dist-tags"]?.latest,d={version:p&&c.includes(p)?p:c[c.length-1]??"",ok:!0};return $t.set(e,{at:Date.now(),value:d}),d}catch{return{version:"",ok:!1}}}async function Nt(e){const r=_e(e);if(r.kind!=="path"){const n=En(r.range)||await Bn(r.name,r.range);return{spec:e,name:r.name,kind:r.kind,version:n}}const o=await Ne(at(e));return{spec:e,name:o?.name&&Re(o.name)?o.name:r.name,kind:"path",version:o?.version??""}}async function Fe(e){return Promise.all(e.plugins.map(r=>Nt(r)))}async function le(){try{const e=await fe(),o=(await Fe(e)).map(n=>({spec:n.spec,name:n.name,kind:n.kind,version:n.version,removable:n.name!==Et}));return{configPath:e.path,configKey:e.key,plugins:o,error:""}}catch(e){return{configPath:E.join(Dt(),"opencode.json"),configKey:"plugins",plugins:[],error:ie(e)}}}async function Nn(){try{const e=await fe(),r=await Fe(e);return{items:await Promise.all(r.map(async n=>{const a=Re(n.name),s=a?await Dn(n.name):{version:"",ok:!1};return{spec:n.spec,name:n.name,kind:n.kind,current:n.version,latest:s.version,hasUpdate:s.ok&&st(s.version,n.version),managed:n.kind!=="path",error:a?s.ok?"":"无法连接 npm registry,本次未检查":"不是 npm 包名,无法查询版本"}})),error:""}}catch(e){return{items:[],error:ie(e)}}}async function tt(e,r,o){const n=e.text.trim().length>0?e.text:`{}
17
- `,s=Mn(n,r,o,{formattingOptions:{insertSpaces:!0,tabSize:2,eol:`
18
- `}});return await q.writeFile(e.path,jn(n,s),"utf8"),le()}async function Un(e,r){for(const[o,n]of e.plugins.entries())if(_e(n).name===r)return o;for(const[o,n]of e.plugins.entries()){if(_e(n).kind!=="path")continue;if((await Ne(at(n)))?.name===r)return o}return-1}async function Gn(e,r){const o=`https://registry.npmjs.org/${e.replace("/","%2f")}`;try{const n=await fetch(o,{signal:AbortSignal.timeout(15e3),headers:{accept:"application/vnd.npm.install-v1+json"}});if(!n.ok)return;const a=await n.json(),s=r||a["dist-tags"]?.latest||"",p=(s?a.versions?.[s]:void 0)?.dist?.tarball;return p?{tarball:p,version:s}:void 0}catch{return}}async function lt(e,r){const o=await q.readdir(e,{withFileTypes:!0});await q.mkdir(r,{recursive:!0});for(const n of o){const a=E.join(e,n.name),s=E.join(r,n.name);n.isDirectory()?await lt(a,s):n.isFile()&&await q.copyFile(a,s)}}async function Rn(e,r){const o=new Set(await q.readdir(e));for(const n of await q.readdir(r,{withFileTypes:!0}))o.has(n.name)||await q.rm(E.join(r,n.name),{recursive:!0,force:!0});await lt(e,r)}function Ct(e,r,o){return new Promise(n=>{const a=Pe.spawn(e,r,{cwd:o,stdio:["ignore","pipe","pipe"]});let s="";a.stdout?.on("data",c=>s+=String(c)),a.stderr?.on("data",c=>s+=String(c)),a.on("error",c=>n({code:-1,output:ie(c)})),a.on("close",c=>n({code:c??-1,output:s}))})}async function Fn(e,r,o){const n=await Gn(r,o);if(!n)return{ok:!1,message:"在 npm 上找不到这个版本"};let a="";try{a=await q.mkdtemp(E.join(Ce.tmpdir(),"plugin-hub-"));const s=E.join(a,"package.tgz"),c=await fetch(n.tarball,{signal:AbortSignal.timeout(6e4)});if(!c.ok)return{ok:!1,message:`下载失败:${c.status}`};await q.writeFile(s,Buffer.from(await c.arrayBuffer()));const p=E.join(a,"unpacked");await q.mkdir(p,{recursive:!0});const d=await Ct("tar",["-xzf",s,"-C",p],a);if(d.code!==0)return{ok:!1,message:`解包失败:${d.output.slice(0,160)}`};const v=E.join(p,"package");if(!await Ne(v))return{ok:!1,message:"下载的包里没有 package.json"};const k=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19),$=`${e}.backup-${k}`;await q.cp(e,$,{recursive:!0});try{await lt(v,e);const O=E.join(e,"scripts","repatch.mjs"),A=await q.stat(O).then(()=>!0,()=>!1);let U="";if(A){const F=await Ct(process.execPath,[O,"--from",v],e);if(F.code!==0)throw new Error(`重打补丁失败:${F.output.slice(0,160)}`);U=",已自动重打补丁"}return{ok:!0,message:`已更新到 ${(await Ne(e))?.version??n.version}${U};旧版本备份在 ${E.basename($)}`}}catch(O){return await Rn($,e).catch(()=>{}),{ok:!1,message:`更新中断,已从备份还原:${ie(O)}`}}}catch(s){return{ok:!1,message:ie(s)}}finally{a&&await q.rm(a,{recursive:!0,force:!0}).catch(()=>{})}}async function qn(e,r){try{const o=await fe(),n=_e(e);if(n.kind!=="package"||!Re(n.name))return{ok:!1,message:"只能按 npm 包名安装",state:await le()};const a=n.name,s=r.trim()?`${a}@${r.trim()}`:a,c=await Un(o,a);if(c>=0&&_e(o.plugins[c]).kind==="path"){const d=at(o.plugins[c]),v=await Fn(d,a,r.trim());return{ok:v.ok,message:v.message,state:await le()}}const p=c>=0?await tt(o,[o.key,c],s):await tt(o,[o.key,o.plugins.length],s);return{ok:!0,message:c>=0?`已更新 ${s}`:`已添加 ${s}`,state:p}}catch(o){return{ok:!1,message:ie(o),state:await le()}}}async function Hn(e){try{const r=await fe(),{name:o}=_e(e),n=r.plugins.findIndex(c=>c.trim()===e.trim());if(n<0)return{ok:!1,message:"配置中没有这一项",state:await le()};const a=await Nt(r.plugins[n]);if(a.name===Et)return{ok:!1,message:"不能移除正在运行的插件中心自身",state:await le()};const s=await tt(r,[r.key,n],void 0);return{ok:!0,message:`已移除 ${a.name||o}`,state:s}}catch(r){return{ok:!1,message:ie(r),state:await le()}}}const Vn=new Set(["opencode","opencode-plugin","opencode-plugins","plugin","plugins","ai"]);function Ut(e){const r=new Set,o=[];for(const n of e??[]){const a=n.trim();if(!a)continue;const s=a.toLowerCase();if(!(Vn.has(s)||r.has(s))&&(r.add(s),o.push(a),o.length>=8))break}return o}function ct(e){const r=(e??"").trim();return r?r.startsWith("//")?`https:${r}`:/^https?:\/\//i.test(r)?r:"":""}function Gt(e){const r=typeof e=="string"?e:e?.url??"",o=typeof e=="object"&&e?e.directory??"":"",n=r.trim().replace(/^git\+/i,"").replace(/^github:/i,"https://github.com/").replace(/^git:\/\//i,"https://").replace(/^ssh:\/\/git@/i,"https://").replace(/^git@([^:]+):/i,"https://$1/").replace(/#.*$/,"").replace(/\.git\/?$/i,"").replace(/\/+$/,"");return/^https?:\/\//i.test(n)?{url:n,directory:o}:{url:"",directory:""}}function Rt(e){return typeof e=="string"?e:e?.type??""}function Wn(e){const r=typeof e=="string"?e:e?.url??"";return ct(r)}const Jn=600*1e3,Ze=3e4,Qn="https://api.npmjs.org",Ft="https://registry.npmjs.org",_t=new Map;function Xn(e){if(e.length<=Ze)return{text:e,truncated:!1};const r=e.slice(0,Ze),o=r.lastIndexOf(`
19
- `);return{text:`${o>Ze*.75?r.slice(0,o):r}
14
+ `}function De(e,o){return`\r
15
+ `.indexOf(e.charAt(o))!==-1}var Ie;(function(e){e.DEFAULT={allowTrailingComma:!1}})(Ie||(Ie={}));function _n(e,o=[],r=Ie.DEFAULT){let n=null,i=[];const s=[];function c(u){Array.isArray(i)?i.push(u):n!==null&&(i[n]=u)}return Pt(e,{onObjectBegin:()=>{const u={};c(u),s.push(i),i=u,n=null},onObjectProperty:u=>{n=u},onObjectEnd:()=>{i=s.pop()},onArrayBegin:()=>{const u=[];c(u),s.push(i),i=u,n=null},onArrayEnd:()=>{i=s.pop()},onLiteralValue:c,onError:(u,v,h)=>{o.push({error:u,offset:v,length:h})}},r),i[0]}function On(e,o=[],r=Ie.DEFAULT){let n={type:"array",offset:-1,length:-1,children:[],parent:void 0};function i(u){n.type==="property"&&(n.length=u-n.offset,n=n.parent)}function s(u){return n.children.push(u),u}Pt(e,{onObjectBegin:u=>{n=s({type:"object",offset:u,length:-1,parent:n,children:[]})},onObjectProperty:(u,v,h)=>{n=s({type:"property",offset:v,length:-1,parent:n,children:[]}),n.children.push({type:"string",value:u,offset:v,length:h,parent:n})},onObjectEnd:(u,v)=>{i(u+v),n.length=u+v-n.offset,n=n.parent,i(u+v)},onArrayBegin:(u,v)=>{n=s({type:"array",offset:u,length:-1,parent:n,children:[]})},onArrayEnd:(u,v)=>{n.length=u+v-n.offset,n=n.parent,i(u+v)},onLiteralValue:(u,v,h)=>{s({type:An(u),offset:v,length:h,parent:n,value:u}),i(v+h)},onSeparator:(u,v,h)=>{n.type==="property"&&(u===":"?n.colonOffset=v:u===","&&i(v))},onError:(u,v,h)=>{o.push({error:u,offset:v,length:h})}},r);const p=n.children[0];return p&&delete p.parent,p}function vt(e,o){if(!e)return;let r=e;for(let n of o)if(typeof n=="string"){if(r.type!=="object"||!Array.isArray(r.children))return;let i=!1;for(const s of r.children)if(Array.isArray(s.children)&&s.children[0].value===n&&s.children.length===2){r=s.children[1],i=!0;break}if(!i)return}else{const i=n;if(r.type!=="array"||i<0||!Array.isArray(r.children)||i>=r.children.length)return;r=r.children[i]}return r}function Pt(e,o,r=Ie.DEFAULT){const n=zt(e,!1),i=[];let s=0;function c(C){return C?()=>s===0&&C(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function p(C){return C?E=>s===0&&C(E,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function u(C){return C?E=>s===0&&C(E,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>i.slice()):()=>!0}function v(C){return C?()=>{s>0?s++:C(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>i.slice())===!1&&(s=1)}:()=>!0}function h(C){return C?()=>{s>0&&s--,s===0&&C(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:()=>!0}const w=v(o.onObjectBegin),S=u(o.onObjectProperty),_=h(o.onObjectEnd),O=v(o.onArrayBegin),U=h(o.onArrayEnd),M=u(o.onLiteralValue),F=p(o.onSeparator),j=c(o.onComment),g=p(o.onError),b=r&&r.disallowComments,x=r&&r.allowTrailingComma;function y(){for(;;){const C=n.scan();switch(n.getTokenError()){case 4:f(14);break;case 5:f(15);break;case 3:f(13);break;case 1:b||f(11);break;case 2:f(12);break;case 6:f(16);break}switch(C){case 12:case 13:b?f(10):j();break;case 16:f(1);break;case 15:case 14:break;default:return C}}}function f(C,E=[],R=[]){if(g(C),E.length+R.length>0){let be=n.getToken();for(;be!==17;){if(E.indexOf(be)!==-1){y();break}else if(R.indexOf(be)!==-1)break;be=y()}}}function V(C){const E=n.getTokenValue();return C?M(E):(S(E),i.push(E)),y(),!0}function re(){switch(n.getToken()){case 11:const C=n.getTokenValue();let E=Number(C);isNaN(E)&&(f(2),E=0),M(E);break;case 7:M(null);break;case 8:M(!0);break;case 9:M(!1);break;default:return!1}return y(),!0}function ce(){return n.getToken()!==10?(f(3,[],[2,5]),!1):(V(!1),n.getToken()===6?(F(":"),y(),J()||f(4,[],[2,5])):f(5,[],[2,5]),i.pop(),!0)}function ve(){w(),y();let C=!1;for(;n.getToken()!==2&&n.getToken()!==17;){if(n.getToken()===5){if(C||f(4,[],[]),F(","),y(),n.getToken()===2&&x)break}else C&&f(6,[],[]);ce()||f(4,[],[2,5]),C=!0}return _(),n.getToken()!==2?f(7,[2],[]):y(),!0}function _e(){O(),y();let C=!0,E=!1;for(;n.getToken()!==4&&n.getToken()!==17;){if(n.getToken()===5){if(E||f(4,[],[]),F(","),y(),n.getToken()===4&&x)break}else E&&f(6,[],[]);C?(i.push(0),C=!1):i[i.length-1]++,J()||f(4,[],[4,5]),E=!0}return U(),C||i.pop(),n.getToken()!==4?f(8,[4],[]):y(),!0}function J(){switch(n.getToken()){case 3:return _e();case 1:return ve();case 10:return V(!0);default:return re()}}return y(),n.getToken()===17?r.allowEmptyContent?!0:(f(4,[],[]),!1):J()?(n.getToken()!==17&&f(9,[],[]),!0):(f(4,[],[]),!1)}function An(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string";case"object":{if(e){if(Array.isArray(e))return"array"}else return"null";return"object"}default:return"null"}}function Tn(e,o,r,n){const i=o.slice(),c=On(e,[]);let p,u;for(;i.length>0&&(u=i.pop(),p=vt(c,i),p===void 0&&r!==void 0);)typeof u=="string"?r={[u]:r}:r=[r];if(p)if(p.type==="object"&&typeof u=="string"&&Array.isArray(p.children)){const v=vt(p,[u]);if(v!==void 0)if(r===void 0){if(!v.parent)throw new Error("Malformed AST");const h=p.children.indexOf(v.parent);let w,S=v.parent.offset+v.parent.length;if(h>0){let _=p.children[h-1];w=_.offset+_.length}else w=p.offset+1,p.children.length>1&&(S=p.children[1].offset);return he(e,{offset:w,length:S-w,content:""},n)}else return he(e,{offset:v.offset,length:v.length,content:JSON.stringify(r)},n);else{if(r===void 0)return[];const h=`${JSON.stringify(u)}: ${JSON.stringify(r)}`,w=n.getInsertionIndex?n.getInsertionIndex(p.children.map(_=>_.children[0].value)):p.children.length;let S;if(w>0){let _=p.children[w-1];S={offset:_.offset+_.length,length:0,content:","+h}}else p.children.length===0?S={offset:p.offset+1,length:0,content:h}:S={offset:p.offset+1,length:0,content:h+","};return he(e,S,n)}}else if(p.type==="array"&&typeof u=="number"&&Array.isArray(p.children)){const v=u;if(v===-1){const h=`${JSON.stringify(r)}`;let w;if(p.children.length===0)w={offset:p.offset+1,length:0,content:h};else{const S=p.children[p.children.length-1];w={offset:S.offset+S.length,length:0,content:","+h}}return he(e,w,n)}else if(r===void 0&&p.children.length>=0){const h=u,w=p.children[h];let S;if(p.children.length===1)S={offset:p.offset+1,length:p.length-2,content:""};else if(p.children.length-1===h){let _=p.children[h-1],O=_.offset+_.length,U=p.offset+p.length;S={offset:O,length:U-2-O,content:""}}else S={offset:w.offset,length:p.children[h+1].offset-w.offset,content:""};return he(e,S,n)}else if(r!==void 0){let h;const w=`${JSON.stringify(r)}`;if(!n.isArrayInsertion&&p.children.length>u){const S=p.children[u];h={offset:S.offset,length:S.length,content:w}}else if(p.children.length===0||u===0)h={offset:p.offset+1,length:0,content:p.children.length===0?w:w+","};else{const S=u>p.children.length?p.children.length:u,_=p.children[S-1];h={offset:_.offset+_.length,length:0,content:","+w}}return he(e,h,n)}else throw new Error(`Can not ${r===void 0?"remove":n.isArrayInsertion?"insert":"modify"} Array index ${v} as length is not sufficient`)}else throw new Error(`Can not add ${typeof u!="number"?"index":"property"} to parent of type ${p.type}`);else{if(r===void 0)throw new Error("Can not delete in empty document");return he(e,{offset:c?c.offset:0,length:c?c.length:0,content:JSON.stringify(r)},n)}}function he(e,o,r){if(!r.formattingOptions)return[o];let n=tt(e,o),i=o.offset,s=o.offset+o.content.length;if(o.length===0||o.content.length===0){for(;i>0&&!De(n,i-1);)i--;for(;s<n.length&&!De(n,s);)s++}const c=Sn(n,{offset:i,length:s-i},{...r.formattingOptions,keepLines:!1});for(let u=c.length-1;u>=0;u--){const v=c[u];n=tt(n,v),i=Math.min(i,v.offset),s=Math.max(s,v.offset+v.length),s+=v.content.length-v.length}const p=e.length-(n.length-s)-i;return[{offset:i,length:p,content:n.substring(i,s)}]}function tt(e,o){return e.substring(0,o.offset)+o.content+e.substring(o.offset+o.length)}var bt;(function(e){e[e.None=0]="None",e[e.UnexpectedEndOfComment=1]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=2]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=3]="UnexpectedEndOfNumber",e[e.InvalidUnicode=4]="InvalidUnicode",e[e.InvalidEscapeCharacter=5]="InvalidEscapeCharacter",e[e.InvalidCharacter=6]="InvalidCharacter"})(bt||(bt={}));var yt;(function(e){e[e.OpenBraceToken=1]="OpenBraceToken",e[e.CloseBraceToken=2]="CloseBraceToken",e[e.OpenBracketToken=3]="OpenBracketToken",e[e.CloseBracketToken=4]="CloseBracketToken",e[e.CommaToken=5]="CommaToken",e[e.ColonToken=6]="ColonToken",e[e.NullKeyword=7]="NullKeyword",e[e.TrueKeyword=8]="TrueKeyword",e[e.FalseKeyword=9]="FalseKeyword",e[e.StringLiteral=10]="StringLiteral",e[e.NumericLiteral=11]="NumericLiteral",e[e.LineCommentTrivia=12]="LineCommentTrivia",e[e.BlockCommentTrivia=13]="BlockCommentTrivia",e[e.LineBreakTrivia=14]="LineBreakTrivia",e[e.Trivia=15]="Trivia",e[e.Unknown=16]="Unknown",e[e.EOF=17]="EOF"})(yt||(yt={}));const In=_n;var wt;(function(e){e[e.InvalidSymbol=1]="InvalidSymbol",e[e.InvalidNumberFormat=2]="InvalidNumberFormat",e[e.PropertyNameExpected=3]="PropertyNameExpected",e[e.ValueExpected=4]="ValueExpected",e[e.ColonExpected=5]="ColonExpected",e[e.CommaExpected=6]="CommaExpected",e[e.CloseBraceExpected=7]="CloseBraceExpected",e[e.CloseBracketExpected=8]="CloseBracketExpected",e[e.EndOfFileExpected=9]="EndOfFileExpected",e[e.InvalidCommentToken=10]="InvalidCommentToken",e[e.UnexpectedEndOfComment=11]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=12]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=13]="UnexpectedEndOfNumber",e[e.InvalidUnicode=14]="InvalidUnicode",e[e.InvalidEscapeCharacter=15]="InvalidEscapeCharacter",e[e.InvalidCharacter=16]="InvalidCharacter"})(wt||(wt={}));function Ln(e,o,r,n){return Tn(e,o,r,n)}function Mn(e,o){let r=o.slice(0).sort((i,s)=>{const c=i.offset-s.offset;return c===0?i.length-s.length:c}),n=e.length;for(let i=r.length-1;i>=0;i--){let s=r[i];if(s.offset+s.length<=n)e=tt(e,s);else throw new Error("Overlapping edit");n=s.offset}return e}const Bt="opencode-plugin-hub",kt="opencode-plugin",jn=300*1e3;function Et(){const e=process.env.XDG_CONFIG_HOME;return e?D.join(e,"opencode"):D.join($e.homedir(),".config","opencode")}function zn(){const e=process.env.XDG_CACHE_HOME;return e?D.join(e,"opencode","npm"):D.join($e.homedir(),".cache","opencode","npm")}async function fe(){const e=Et(),o=[D.join(e,"opencode.json"),D.join(e,"opencode.jsonc")];let r;for(const n of o){let i;try{i=await H.readFile(n,"utf8")}catch{continue}const c=In(i,[],{allowTrailingComma:!0,disallowComments:!1}),p=c&&typeof c=="object"?c:{},u=Array.isArray(p.plugins)?"plugins":Array.isArray(p.plugin)?"plugin":"plugins",v=Array.isArray(p[u])?p[u].filter(w=>typeof w=="string"):[],h={path:n,text:i,key:u,plugins:v};if(h.plugins.length>0)return h;r||(r=h)}return r||{path:D.join(e,"opencode.json"),text:`{}
16
+ `,key:"plugins",plugins:[]}}function Re(e){if(!/^(@[a-z0-9-_.]+\/)?[a-z0-9-_.]+$/i.test(e)||e.includes(".."))return!1;const o=e.split("/").pop()??e;return o.length>0&&!o.startsWith(".")}function Ce(e){const o=e.trim();if(o.startsWith("/")||o.startsWith(".")||o.startsWith("~/"))return{name:o.replace(/\/+$/,"").split("/").pop()??o,range:"",kind:"path"};const r=o.startsWith("@"),n=o.indexOf("@",r?1:0);return n>0?{name:o.slice(0,n),range:o.slice(n+1),kind:"package"}:{name:o,range:"",kind:"package"}}function st(e){const o=e.trim();return o==="~"?$e.homedir():o.startsWith("~/")?D.join($e.homedir(),o.slice(2)):o}async function Ne(e){try{const o=await H.readFile(D.join(e,"package.json"),"utf8"),r=JSON.parse(o);return typeof r=="object"&&r!==null?r:void 0}catch{return}}async function Pn(e,o){const r=o&&o!=="latest"?o:"latest",n=Array.from(new Set([`${e}@${r}`,`${e}@latest`]));for(const i of n){const s=D.join(zn(),i);let c;try{c=await H.readdir(s)}catch{continue}for(const p of c.reverse())try{const u=D.join(s,p,"node_modules",e,"package.json"),v=await H.readFile(u,"utf8"),h=JSON.parse(v);if(h.version)return h.version}catch{}}return""}function Bn(e){const o=e.trim().replace(/^[\^~=v]+/,"").replace(/^>\=?|^<\=?/,"");return/^\d+(\.\d+)*([-+][\w.-]+)?$/.test(o)?o:""}function xt(e){return e.split("-")[0].split(".").map(o=>Number.parseInt(o,10)||0)}function lt(e,o){if(!e)return!1;if(!o)return!0;const r=xt(e),n=xt(o);for(let i=0;i<Math.max(r.length,n.length);i+=1){const s=r[i]??0,c=n[i]??0;if(s!==c)return s>c}return!1}const St=new Map;async function En(e,o=!1){const r=St.get(e);if(!o&&r&&Date.now()-r.at<jn)return r.value;const n=`https://registry.npmjs.org/${e.replace("/","%2f")}`;let i;try{i=await fetch(n,{signal:AbortSignal.timeout(1e4),headers:{accept:"application/vnd.npm.install-v1+json"}})}catch{return{version:"",ok:!1}}if(i.status===404)return{version:"",ok:!0};if(!i.ok)return{version:"",ok:!1};try{const s=await i.json(),c=Object.keys(s.versions??{});if(c.length===0)return{version:"",ok:!0};const p=s["dist-tags"]?.latest,u={version:p&&c.includes(p)?p:c[c.length-1]??"",ok:!0};return St.set(e,{at:Date.now(),value:u}),u}catch{return{version:"",ok:!1}}}async function Dt(e){const o=Ce(e);if(o.kind!=="path"){const n=Bn(o.range)||await Pn(o.name,o.range);return{spec:e,name:o.name,kind:o.kind,version:n}}const r=await Ne(st(e));return{spec:e,name:r?.name&&Re(r.name)?r.name:o.name,kind:"path",version:r?.version??""}}async function Fe(e){return Promise.all(e.plugins.map(o=>Dt(o)))}async function se(){try{const e=await fe(),r=(await Fe(e)).map(n=>({spec:n.spec,name:n.name,kind:n.kind,version:n.version,removable:n.name!==Bt}));return{configPath:e.path,configKey:e.key,plugins:r,error:""}}catch(e){return{configPath:D.join(Et(),"opencode.json"),configKey:"plugins",plugins:[],error:oe(e)}}}async function Dn(){try{const e=await fe(),o=await Fe(e);return{items:await Promise.all(o.map(async n=>{const i=Re(n.name),s=i?await En(n.name):{version:"",ok:!1};return{spec:n.spec,name:n.name,kind:n.kind,current:n.version,latest:s.version,hasUpdate:s.ok&&lt(s.version,n.version),managed:n.kind!=="path",error:i?s.ok?"":"无法连接 npm registry,本次未检查":"不是 npm 包名,无法查询版本"}})),error:""}}catch(e){return{items:[],error:oe(e)}}}async function nt(e,o,r){const n=e.text.trim().length>0?e.text:`{}
17
+ `,s=Ln(n,o,r,{formattingOptions:{insertSpaces:!0,tabSize:2,eol:`
18
+ `}});return await H.writeFile(e.path,Mn(n,s),"utf8"),se()}async function Nn(e,o){for(const[r,n]of e.plugins.entries())if(Ce(n).name===o)return r;for(const[r,n]of e.plugins.entries()){if(Ce(n).kind!=="path")continue;if((await Ne(st(n)))?.name===o)return r}return-1}async function Un(e,o){const r=`https://registry.npmjs.org/${e.replace("/","%2f")}`;try{const n=await fetch(r,{signal:AbortSignal.timeout(15e3),headers:{accept:"application/vnd.npm.install-v1+json"}});if(!n.ok)return;const i=await n.json(),s=o||i["dist-tags"]?.latest||"",p=(s?i.versions?.[s]:void 0)?.dist?.tarball;return p?{tarball:p,version:s}:void 0}catch{return}}async function ct(e,o){const r=await H.readdir(e,{withFileTypes:!0});await H.mkdir(o,{recursive:!0});for(const n of r){const i=D.join(e,n.name),s=D.join(o,n.name);n.isDirectory()?await ct(i,s):n.isFile()&&await H.copyFile(i,s)}}async function Gn(e,o){const r=new Set(await H.readdir(e));for(const n of await H.readdir(o,{withFileTypes:!0}))r.has(n.name)||await H.rm(D.join(o,n.name),{recursive:!0,force:!0});await ct(e,o)}function $t(e,o,r){return new Promise(n=>{const i=Le.spawn(e,o,{cwd:r,stdio:["ignore","pipe","pipe"]});let s="";i.stdout?.on("data",c=>s+=String(c)),i.stderr?.on("data",c=>s+=String(c)),i.on("error",c=>n({code:-1,output:oe(c)})),i.on("close",c=>n({code:c??-1,output:s}))})}async function Rn(e,o,r){const n=await Un(o,r);if(!n)return{ok:!1,message:"在 npm 上找不到这个版本"};let i="";try{i=await H.mkdtemp(D.join($e.tmpdir(),"plugin-hub-"));const s=D.join(i,"package.tgz"),c=await fetch(n.tarball,{signal:AbortSignal.timeout(6e4)});if(!c.ok)return{ok:!1,message:`下载失败:${c.status}`};await H.writeFile(s,Buffer.from(await c.arrayBuffer()));const p=D.join(i,"unpacked");await H.mkdir(p,{recursive:!0});const u=await $t("tar",["-xzf",s,"-C",p],i);if(u.code!==0)return{ok:!1,message:`解包失败:${u.output.slice(0,160)}`};const v=D.join(p,"package");if(!await Ne(v))return{ok:!1,message:"下载的包里没有 package.json"};const w=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19),S=`${e}.backup-${w}`;await H.cp(e,S,{recursive:!0});try{await ct(v,e);const _=D.join(e,"scripts","repatch.mjs"),O=await H.stat(_).then(()=>!0,()=>!1);let U="";if(O){const F=await $t(process.execPath,[_,"--from",v],e);if(F.code!==0)throw new Error(`重打补丁失败:${F.output.slice(0,160)}`);U=",已自动重打补丁"}return{ok:!0,message:`已更新到 ${(await Ne(e))?.version??n.version}${U};旧版本备份在 ${D.basename(S)}`}}catch(_){return await Gn(S,e).catch(()=>{}),{ok:!1,message:`更新中断,已从备份还原:${oe(_)}`}}}catch(s){return{ok:!1,message:oe(s)}}finally{i&&await H.rm(i,{recursive:!0,force:!0}).catch(()=>{})}}async function Fn(e,o){try{const r=await fe(),n=Ce(e);if(n.kind!=="package"||!Re(n.name))return{ok:!1,message:"只能按 npm 包名安装",state:await se()};const i=n.name,s=o.trim()?`${i}@${o.trim()}`:i,c=await Nn(r,i);if(c>=0&&Ce(r.plugins[c]).kind==="path"){const u=st(r.plugins[c]),v=await Rn(u,i,o.trim());return{ok:v.ok,message:v.message,state:await se()}}const p=c>=0?await nt(r,[r.key,c],s):await nt(r,[r.key,r.plugins.length],s);return{ok:!0,message:c>=0?`已更新 ${s}`:`已添加 ${s}`,state:p}}catch(r){return{ok:!1,message:oe(r),state:await se()}}}async function qn(e){try{const o=await fe(),{name:r}=Ce(e),n=o.plugins.findIndex(c=>c.trim()===e.trim());if(n<0)return{ok:!1,message:"配置中没有这一项",state:await se()};const i=await Dt(o.plugins[n]);if(i.name===Bt)return{ok:!1,message:"不能移除正在运行的插件中心自身",state:await se()};const s=await nt(o,[o.key,n],void 0);return{ok:!0,message:`已移除 ${i.name||r}`,state:s}}catch(o){return{ok:!1,message:oe(o),state:await se()}}}const Hn=new Set(["opencode","opencode-plugin","opencode-plugins","plugin","plugins","ai"]);function Nt(e){const o=new Set,r=[];for(const n of e??[]){const i=n.trim();if(!i)continue;const s=i.toLowerCase();if(!(Hn.has(s)||o.has(s))&&(o.add(s),r.push(i),r.length>=8))break}return r}function pt(e){const o=(e??"").trim();return o?o.startsWith("//")?`https:${o}`:/^https?:\/\//i.test(o)?o:"":""}function Ut(e){const o=typeof e=="string"?e:e?.url??"",r=typeof e=="object"&&e?e.directory??"":"",n=o.trim().replace(/^git\+/i,"").replace(/^github:/i,"https://github.com/").replace(/^git:\/\//i,"https://").replace(/^ssh:\/\/git@/i,"https://").replace(/^git@([^:]+):/i,"https://$1/").replace(/#.*$/,"").replace(/\.git\/?$/i,"").replace(/\/+$/,"");return/^https?:\/\//i.test(n)?{url:n,directory:r}:{url:"",directory:""}}function Gt(e){return typeof e=="string"?e:e?.type??""}function Vn(e){const o=typeof e=="string"?e:e?.url??"";return pt(o)}const Wn=600*1e3,Ke=3e4,Jn="https://api.npmjs.org",Rt="https://registry.npmjs.org",Ct=new Map;function Qn(e){if(e.length<=Ke)return{text:e,truncated:!1};const o=e.slice(0,Ke),r=o.lastIndexOf(`
19
+ `);return{text:`${r>Ke*.75?o.slice(0,r):o}
20
20
 
21
- …`,truncated:!0}}async function Ot(e,r){try{const o=await fetch(`${Qn}/downloads/point/${r}/${encodeURIComponent(e)}`,{signal:AbortSignal.timeout(8e3),headers:{accept:"application/json"}});if(!o.ok)return 0;const n=await o.json();return Number(n.downloads??0)||0}catch{return 0}}async function Yn(e){try{const r=await fetch(`${Ft}/${encodeURIComponent(e)}`,{signal:AbortSignal.timeout(2e4),headers:{accept:"application/json"}});if(r.status===404)return{error:"npm 上找不到这个包"};if(!r.ok)return{error:`npm registry 返回 ${r.status}`};if(Number(r.headers.get("content-length")??0)>12*1024*1024)return At(e,"包元数据过大,只读取了最新版本");let n;try{n=await r.json()}catch{return At(e,"npm 返回的数据无法解析")}return!n||typeof n!="object"?{error:"npm 返回的数据不完整"}:{body:n,error:""}}catch(r){return{error:ie(r)}}}async function At(e,r){try{const o=await fetch(`${Ft}/${encodeURIComponent(e)}/latest`,{signal:AbortSignal.timeout(1e4),headers:{accept:"application/json"}});if(!o.ok)return{error:r||`npm registry 返回 ${o.status}`};const n=await o.json();return n?.version?{body:{...n,readme:typeof n.readme=="string"?n.readme:"","dist-tags":{latest:n.version},versions:{[n.version]:n},time:n.time},error:""}:{error:r||"npm 返回的数据不完整"}}catch(o){return{error:r||ie(o)}}}async function Zn(e){const r=await Yn(e);if(!r.body)return{error:r.error};const o=r.body,n=o.versions??{},a=Object.keys(n);if(a.length===0)return{error:"npm 上只有占位条目,还没有发布过版本"};const s=o["dist-tags"]?.latest??"",c=s&&n[s]?s:a[a.length-1]??o.version??"",p=c?n[c]??{}:o,d=Gt(p.repository??o.repository),v=Xn(typeof o.readme=="string"?o.readme:""),[h,k]=await Promise.all([Ot(e,"last-week"),Ot(e,"last-month")]),$=(o.maintainers??[]).map(A=>A.name??A.username??"").filter(A=>A!==""),O=Object.entries(p.dependencies??{}).map(([A,U])=>U?`${A}@${U}`:A);return{name:e,version:c,description:p.description??o.description??"",homepage:ct(p.homepage??o.homepage)||d.url,repo:d.url,repoDir:d.directory,bugs:Wn(p.bugs??o.bugs),license:Rt(p.license??o.license),keywords:Ut(p.keywords??o.keywords),maintainers:$,weekly:h,monthly:k,published:o.time?.[c]??"",created:o.time?.created??"",versions:Object.keys(n).length,unpacked:p.dist?.unpackedSize??0,files:p.dist?.fileCount??0,dependencies:O,deprecated:typeof p.deprecated=="string"?p.deprecated:"",readme:v.text,readmeTruncated:v.truncated}}async function Kn(e){const r=_t.get(e);if(r&&Date.now()-r.at<Jn)return r.value;const o=await Zn(e);return"error"in o||_t.set(e,{at:Date.now(),value:o}),o}async function er(e){const r=()=>({name:e,version:"",description:"",homepage:"",repo:"",repoDir:"",npm:`https://www.npmjs.com/package/${e}`,bugs:"",license:"",keywords:[],maintainers:[],weekly:0,monthly:0,published:"",created:"",versions:0,unpacked:0,files:0,dependencies:[],deprecated:"",readme:"",readmeTruncated:!1,installed:!1,current:"",hasUpdate:!1,error:""});if(!Re(e))return{...r(),error:"不是 npm 包名,无法查询详情"};let o=!1,n="";try{const s=await fe(),p=(await Fe(s)).find(d=>d.name===e);o=p!==void 0,n=p?.version??""}catch{}const a=await Kn(e);return"error"in a?{...r(),installed:o,current:n,error:a.error}:{...a,npm:r().npm,installed:o,current:n,hasUpdate:o&&st(a.version,n),error:""}}async function tr(e){const r=e.trim(),o=r?`${r} keywords:${xt}`:`keywords:${xt}`;try{const n=await fetch(`https://registry.npmjs.org/-/v1/search?size=50&text=${encodeURIComponent(o)}`,{signal:AbortSignal.timeout(12e3),headers:{accept:"application/json"}});if(!n.ok)return{hits:[],error:`npm registry 返回 ${n.status}`};const a=await n.json(),s=await fe(),c=await Fe(s),p=new Map(c.map(v=>[v.name,v.version])),d=[];for(const v of a.objects??[]){const h=v.package;if(!h?.name)continue;const k=h.name,$=p.has(k),O=$?p.get(k)??"":"",A=Gt(h.links?.repository);d.push({name:k,version:h.version??"",description:h.description??"",published:h.date??"",updated:h.date??"",installed:$,current:O,hasUpdate:$&&st(h.version??"",O),weekly:Number(v.downloads?.weekly??0)||0,monthly:Number(v.downloads?.monthly??0)||0,publisher:h.maintainers?.[0]?.username??h.publisher?.username??"",license:Rt(h.license),keywords:Ut(h.keywords),homepage:ct(h.links?.homepage),repo:A.url,npm:h.links?.npm||`https://www.npmjs.com/package/${k}`})}return{hits:d,error:""}}catch(n){return{hits:[],error:ie(n)}}}function qt(){return process.platform==="darwin"?"open":process.platform==="win32"?"explorer":"xdg-open"}function nr(e){if(!/^https?:\/\//i.test(e))return!1;try{return Pe.spawn(qt(),[e],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function rr(){try{const e=await fe();return Pe.spawn(qt(),[e.path],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}function or(){if(process.platform!=="darwin")return!1;const e=`
21
+ …`,truncated:!0}}async function _t(e,o){try{const r=await fetch(`${Jn}/downloads/point/${o}/${encodeURIComponent(e)}`,{signal:AbortSignal.timeout(8e3),headers:{accept:"application/json"}});if(!r.ok)return 0;const n=await r.json();return Number(n.downloads??0)||0}catch{return 0}}async function Xn(e){try{const o=await fetch(`${Rt}/${encodeURIComponent(e)}`,{signal:AbortSignal.timeout(2e4),headers:{accept:"application/json"}});if(o.status===404)return{error:"npm 上找不到这个包"};if(!o.ok)return{error:`npm registry 返回 ${o.status}`};if(Number(o.headers.get("content-length")??0)>12*1024*1024)return Ot(e,"包元数据过大,只读取了最新版本");let n;try{n=await o.json()}catch{return Ot(e,"npm 返回的数据无法解析")}return!n||typeof n!="object"?{error:"npm 返回的数据不完整"}:{body:n,error:""}}catch(o){return{error:oe(o)}}}async function Ot(e,o){try{const r=await fetch(`${Rt}/${encodeURIComponent(e)}/latest`,{signal:AbortSignal.timeout(1e4),headers:{accept:"application/json"}});if(!r.ok)return{error:o||`npm registry 返回 ${r.status}`};const n=await r.json();return n?.version?{body:{...n,readme:typeof n.readme=="string"?n.readme:"","dist-tags":{latest:n.version},versions:{[n.version]:n},time:n.time},error:""}:{error:o||"npm 返回的数据不完整"}}catch(r){return{error:o||oe(r)}}}async function Yn(e){const o=await Xn(e);if(!o.body)return{error:o.error};const r=o.body,n=r.versions??{},i=Object.keys(n);if(i.length===0)return{error:"npm 上只有占位条目,还没有发布过版本"};const s=r["dist-tags"]?.latest??"",c=s&&n[s]?s:i[i.length-1]??r.version??"",p=c?n[c]??{}:r,u=Ut(p.repository??r.repository),v=Qn(typeof r.readme=="string"?r.readme:""),[h,w]=await Promise.all([_t(e,"last-week"),_t(e,"last-month")]),S=(r.maintainers??[]).map(O=>O.name??O.username??"").filter(O=>O!==""),_=Object.entries(p.dependencies??{}).map(([O,U])=>U?`${O}@${U}`:O);return{name:e,version:c,description:p.description??r.description??"",homepage:pt(p.homepage??r.homepage)||u.url,repo:u.url,repoDir:u.directory,bugs:Vn(p.bugs??r.bugs),license:Gt(p.license??r.license),keywords:Nt(p.keywords??r.keywords),maintainers:S,weekly:h,monthly:w,published:r.time?.[c]??"",created:r.time?.created??"",versions:Object.keys(n).length,unpacked:p.dist?.unpackedSize??0,files:p.dist?.fileCount??0,dependencies:_,deprecated:typeof p.deprecated=="string"?p.deprecated:"",readme:v.text,readmeTruncated:v.truncated}}async function Zn(e){const o=Ct.get(e);if(o&&Date.now()-o.at<Wn)return o.value;const r=await Yn(e);return"error"in r||Ct.set(e,{at:Date.now(),value:r}),r}async function Kn(e){const o=()=>({name:e,version:"",description:"",homepage:"",repo:"",repoDir:"",npm:`https://www.npmjs.com/package/${e}`,bugs:"",license:"",keywords:[],maintainers:[],weekly:0,monthly:0,published:"",created:"",versions:0,unpacked:0,files:0,dependencies:[],deprecated:"",readme:"",readmeTruncated:!1,installed:!1,current:"",hasUpdate:!1,error:""});if(!Re(e))return{...o(),error:"不是 npm 包名,无法查询详情"};let r=!1,n="";try{const s=await fe(),p=(await Fe(s)).find(u=>u.name===e);r=p!==void 0,n=p?.version??""}catch{}const i=await Zn(e);return"error"in i?{...o(),installed:r,current:n,error:i.error}:{...i,npm:o().npm,installed:r,current:n,hasUpdate:r&&lt(i.version,n),error:""}}async function er(e){const o=e.trim(),r=o?`${o} keywords:${kt}`:`keywords:${kt}`;try{const n=await fetch(`https://registry.npmjs.org/-/v1/search?size=50&text=${encodeURIComponent(r)}`,{signal:AbortSignal.timeout(12e3),headers:{accept:"application/json"}});if(!n.ok)return{hits:[],error:`npm registry 返回 ${n.status}`};const i=await n.json(),s=await fe(),c=await Fe(s),p=new Map(c.map(v=>[v.name,v.version])),u=[];for(const v of i.objects??[]){const h=v.package;if(!h?.name)continue;const w=h.name,S=p.has(w),_=S?p.get(w)??"":"",O=Ut(h.links?.repository);u.push({name:w,version:h.version??"",description:h.description??"",published:h.date??"",updated:h.date??"",installed:S,current:_,hasUpdate:S&&lt(h.version??"",_),weekly:Number(v.downloads?.weekly??0)||0,monthly:Number(v.downloads?.monthly??0)||0,publisher:h.maintainers?.[0]?.username??h.publisher?.username??"",license:Gt(h.license),keywords:Nt(h.keywords),homepage:pt(h.links?.homepage),repo:O.url,npm:h.links?.npm||`https://www.npmjs.com/package/${w}`})}return{hits:u,error:""}}catch(n){return{hits:[],error:oe(n)}}}function Ft(){return process.platform==="darwin"?"open":process.platform==="win32"?"explorer":"xdg-open"}function tr(e){if(!/^https?:\/\//i.test(e))return!1;try{return Le.spawn(Ft(),[e],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function nr(){try{const e=await fe();return Le.spawn(Ft(),[e.path],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}function rr(){if(process.platform!=="darwin")return!1;const e=`
22
22
  export PATH=/usr/bin:/bin:/usr/sbin:/sbin
23
23
  log="$HOME/.cache/opencode/plugin-hub/restart.log"
24
24
  mkdir -p "$HOME/.cache/opencode/plugin-hub" 2>/dev/null
@@ -52,7 +52,7 @@ done
52
52
  sleep 1
53
53
  say "opening app"
54
54
  open -b ai.opencode.desktop 2>/dev/null || open -a OpenCode
55
- `;try{return Pe.spawn("/bin/sh",["-c",e],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function ir(e){const r=await le();try{return e.length===0?{ok:!1,message:"找不到 opencode CLI",state:r}:(Pe.spawn(e[0],[...e.slice(1),"reload"],{detached:!0,stdio:"ignore"}).unref(),{ok:!0,message:"已请求重载配置;插件列表变化建议完全退出应用再打开",state:r})}catch(o){return{ok:!1,message:ie(o),state:r}}}function ie(e){return e instanceof Error?e.message:String(e)}const ar=JSON.parse(`[{"name":"@aerovato/operator-memory","pkg":"","ver":"","type":"plugins","desc":"Durable context for agent-driven development","long":"Operator Memory gives the agent durable memory made of ordinary Markdown: specs, decisions, standards, and research, kept across private, shared, and user partitions. Every file is yours to read, correct, or remove, and new sessions start from that knowledge. No embeddings, no vector database, no ho","repo":"https://github.com/aerovato/operator-memory","tags":["memory","context","documentation","markdown"]},{"name":"Agent Memory","pkg":"opencode-agent-memory","ver":"0.3.1","type":"plugins","desc":"Letta-inspired memory","long":"Gives the agent persistent, self-editable memory blocks inspired by Letta agents.","repo":"https://github.com/joshuadavidthomas/opencode-agent-memory","tags":[]},{"name":"Agent Notifications","pkg":"","ver":"","type":"plugins","desc":"Desktop and webhook alerts for OpenCode sessions","long":"Desktop and optional webhook notifications for OpenCode root-session completion, questions, permissions, and errors on macOS, Linux, and Windows.","repo":"https://github.com/777genius/agent-notifications","tags":[]},{"name":"Agent Skills (JDT)","pkg":"opencode-agent-skills","ver":"0.7.0","type":"plugins","desc":"Dynamic skills loader","long":"Dynamic skills loader that discovers skills from project, user, and plugin directories.","repo":"https://github.com/joshuadavidthomas/opencode-agent-skills","tags":[]},{"name":"OpenCode Antigravity CLI Auth","pkg":"opencode-agy-auth","ver":"2.1.1","type":"plugins","desc":"Google Antigravity account auth","long":"Authenticate the Opencode CLI with your Google account so you can use your existing Antigravity plan.","repo":"https://github.com/anthonyhaussman/opencode-agy-auth","tags":[]},{"name":"AIDevOps","pkg":"aidevops","ver":"3.39.125","type":"plugins","desc":"AI DevOps framework and OpenCode plugin","long":"OpenCode-first AI DevOps framework with agents, skills, workflows, safety hooks, memory, GitHub worker dispatch, and integrations for code, infrastructure, content, SEO, and operations.","repo":"https://github.com/marcusquinn/aidevops","tags":[]},{"name":"Antigravity Auth","pkg":"opencode-antigravity-auth","ver":"2.1.2","type":"plugins","desc":"Google Antigravity models","long":"Use Gemini and Anthropic models for free via Google Antigravity IDE authentication.","repo":"https://github.com/NoeFabris/opencode-antigravity-auth","tags":[]},{"name":"basemind","pkg":"basemind","ver":"0.31.1","type":"plugins","desc":"Full AI context layer over MCP — code-map, document RAG, agent memory, web crawl, git history","long":"Full AI context layer over MCP — tree-sitter code-map, document RAG, shared agent memory, web crawl, and git history. 300+ languages. Published to npm as basemind-opencode (https://www.npmjs.com/package/basemind-opencode).","repo":"https://github.com/Goldziher/basemind","tags":[]},{"name":"Beads Plugin","pkg":"opencode-beads","ver":"0.9.0","type":"plugins","desc":"Beads issue tracker integration","long":"Integration for Steve Yegge's beads issue tracker with /bd-* commands.","repo":"https://github.com/joshuadavidthomas/opencode-beads","tags":[]},{"name":"BRHP","pkg":"","ver":"","type":"plugins","desc":"Persistent planning state","long":"Structured, persistent planning for OpenCode with local session state, /brhp commands, bounded planner history, and a TUI sidebar.","repo":"https://github.com/ZanzyTHEbar/brhp","tags":[]},{"name":"bro","pkg":"bro","ver":"1.0.7","type":"plugins","desc":"Re-explains the last reply in plain language","long":"One-file Agent Skill that re-explains the assistant's last reply (or pasted text) in plain language when it was too dense. The installer adds a \`/bro\` command for OpenCode (\`~/.config/opencode/skills/bro/SKILL.md\` plus a command wrapper); also works in Claude Code, Codex, Cursor and Hermes.","repo":"https://github.com/luchasarie/bro-skill","tags":[]},{"name":"cache-stats","pkg":"","ver":"","type":"plugins","desc":"Showing the active session's cache hit rate in a popup dialog","long":"This plugin shows the overall cache hit rate, a per-model breakdown, a per subagent breakdown and the associated cost, making it easy to understand cache performance at a glance.","repo":"https://github.com/nmdra/opencode-cache-stats","tags":[]},{"name":"Caspian","pkg":"caspian-opencode-plugin","ver":"0.1.7","type":"plugins","desc":"Give your OpenCode agent an inbox — email, Telegram, and Discord","long":"Official Caspian plugin for OpenCode. Gives the agent a bidirectional inbox — email, Telegram, and Discord — with slash commands, session threading back to Gmail, and desktop notifications. No Caspian account required; on first start the plugin mints a sandbox project and provisions an agent email. ","repo":"https://github.com/TryCaspian/caspian-sdk/tree/main/packages/opencode","tags":["email","telegram","discord","inbox","messaging","notifications"]},{"name":"CC Safety Net","pkg":"","ver":"","type":"plugins","desc":"Safety net catching destructive commands","long":"A Claude Code plugin that acts as a safety net, catching destructive git and filesystem commands before they execute.","repo":"https://github.com/kenryu42/claude-code-safety-net","tags":[]},{"name":"Chisle","pkg":"chisle","ver":"3.8.1","type":"plugins","desc":"Cuts token use with terse answers, smallest-code-that-works rules and tool-output compression","long":"Installs a ruleset for terse answers that keep every decisive fact and a \\"smallest code that works\\" ladder, Chisle skills, and a compression plugin that trims oversized tool output before it re-enters the context. The same ruleset ships for Claude Code, Cursor, Codex and Gemini CLI. Install with \`np","repo":"https://github.com/JayPokale/Chisle","tags":["tokens","cost","context","productivity"]},{"name":"Subagent Reporter","pkg":"","ver":"","type":"plugins","desc":"See exactly what your subagents are up to (in the terminal) when invoked during an \`opencode run _____\` session.","long":"When opencode run <prompt> is invoked normally, the actions of the primary agent are visible in the terminal, but subagent actions are not. This makes it difficult to follow progress on the prompt, and makes it very difficult to use \`opencode run\` as part of an unattended process or script. This plu","repo":"https://github.com/raisbecka/opencode-subagent-output","tags":[]},{"name":"codemem","pkg":"codemem","ver":"0.47.3","type":"plugins","desc":"Persistent coding memory across sessions, machines, and teammates","long":"Captures coding decisions, discoveries, and fixes, with automatic context injection in OpenCode, Claude Code, and Codex. Includes MCP tools for searching and managing memories, a viewer, optional cross-machine sync, and sharing of selected projects with teammates.","repo":"https://github.com/kunickiaj/codemem","tags":[]},{"name":"Command Inject","pkg":"opencode-command-inject","ver":"1.3.1","type":"plugins","desc":"Auto-inject project commands into OpenCode","long":"Automatically discovers and injects Makefile targets, npm/pnpm/yarn/bun scripts, and local skills at startup. Type / to see and run all project commands.","repo":"https://github.com/shihyuho/opencode-command-inject","tags":[]},{"name":"ContextStream","pkg":"mcp-server","ver":"0.0.9","type":"plugins","desc":"Shared project context for OpenCode, Cursor, Claude Code, Codex, and Grok via MCP","long":"Hosted MCP server that keeps coding agents on shared project context across sessions and teammates. Works with OpenCode and other MCP-compatible clients. Endpoint https://mcp.contextstream.io/mcp — Intelligence isn’t the bottleneck. Context is.","repo":"https://github.com/contextstream/mcp-server","tags":["mcp","context","productivity","collaboration"]},{"name":"deja","pkg":"opencode-deja","ver":"0.23.5","type":"plugins","desc":"Recall what you already did in your other coding agents","long":"opencode remembers its own sessions. deja answers the other question: what you did in Claude Code, Codex, Cursor, Gemini CLI, Zed and fifteen more agents on this machine — including the months before you installed anything.\\n\\nA local Go binary indexes the transcripts those agents already wrote to dis","repo":"https://github.com/vshulcz/deja-vu","tags":["memory","recall","search","cross-agent","session-history"]},{"name":"Ditto","pkg":"ditto","ver":"0.0.11","type":"plugins","desc":"Mines your local AI coding session logs into a personal profile your agent loads before every task","long":"Mines local AI coding session logs (OpenCode, Claude Code, Codex, Copilot CLI) into a personal working profile. The OpenCode adapter reads OpenCode's SQLite session store and installs the profile into OpenCode's global rules so the agent loads it before every task. Single stdlib-only Python file, ru","repo":"https://github.com/ohad6k/ditto","tags":[]},{"name":"Dodo Payments","pkg":"dodo-agent-plugin","ver":"0.0.1","type":"plugins","desc":"Payments, subscriptions, webhooks, and billing for OpenCode agents","long":"Official Dodo Payments plugin for OpenCode (also Claude Code, Codex, Cursor). Bundles eight integration skills (checkout, subscriptions, webhooks, usage-based billing, credits, license keys, BillingSDK, best practices) plus two MCP servers — a live API server with browser OAuth and a documentation s","repo":"https://github.com/dodopayments/dodo-agent-plugin","tags":[]},{"name":"Dynamic Context Pruning","pkg":"","ver":"","type":"plugins","desc":"Optimize token usage","long":"Plugin that optimises token usage by pruning obsolete tool outputs from conversation context.","repo":"https://github.com/Tarquinen/opencode-dynamic-context-pruning","tags":[]},{"name":"Envsitter Guard","pkg":"envsitter-guard","ver":"0.1.1","type":"plugins","desc":"Prevent .env leaks","long":"OpenCode plugin that prevents agents/tools from reading or editing sensitive .env* files, while still allowing safe inspection via EnvSitter (keys + deterministic fingerprints; never values).","repo":"https://github.com/boxpositron/envsitter-guard","tags":[]},{"name":"FlowDeck","pkg":"flowdeck","ver":"0.16.1","type":"plugins","desc":"AI-powered multi-agent workflow orchestration with built-in safety intelligence","long":"FlowDeck adds a structured, multi-agent development workflow to OpenCode. It coordinates 25 specialist agents through a four-phase cycle — discuss, plan, execute, review — with persistent state that survives session restarts.\\n\\nKey features:\\n- 25 specialist agents (architect, planner, coder, reviewer","repo":"https://github.com/DVNghiem/FlowDeck","tags":["workflow","orchestration","multi-agent","planning","security","TDD"]},{"name":"ForLoop Planner","pkg":"@forloop-cc/forloop-opencode-plugin-planner","ver":"2.2.25","type":"plugins","desc":"Autopilot development. Plan with ForLoop agent. Build, Test, Deploy on cloud in one go.","long":"ForLoop offers an all-in-one platform that covers the entire development lifecycle, from initial planning and coding to automated testing and cloud deployment.","repo":"https://github.com/forloop-cc/forloop-opencode-plugin-planner","tags":[]},{"name":"Froggy","pkg":"froggy","ver":"1.1.6","type":"plugins","desc":"Hooks and specialized agents","long":"Plugin providing Claude Code-style hooks, specialized agents, and tools like gitingest.","repo":"https://github.com/smartfrog/opencode-froggy","tags":[]},{"name":"Gemini Auth","pkg":"opencode-gemini-auth","ver":"2.0.1","type":"plugins","desc":"Google account auth","long":"Authenticate the Opencode CLI with your Google account so you can use your existing Gemini plan.","repo":"https://github.com/jenslys/opencode-gemini-auth","tags":[]},{"name":"Godmode","pkg":"godmode","ver":"0.0.3","type":"plugins","desc":"Discipline layer: measure → modify → verify → keep/revert","long":"Skill plugin with 135 skills and 7 subagents that adds a measure → modify → verify → keep/revert discipline loop to AI coding agents. Works with OpenCode alongside Claude Code, Codex, Cursor, and Gemini CLI.","repo":"https://github.com/arbazkhan971/godmode","tags":[]},{"name":"Handoff","pkg":"handoff","ver":"1.3.2","type":"plugins","desc":"Session handoff prompts","long":"Creates focused handoff prompts for continuing work in a new session.","repo":"https://github.com/joshuadavidthomas/opencode-handoff","tags":[]},{"name":"Honcho","pkg":"honcho","ver":"0.4.13","type":"plugins","desc":"AI-native long-term memory for OpenCode","long":"Give OpenCode persistent memory that survives context wipes, session restarts, and fresh chats. Honcho remembers what you're working on, durable preferences, and prior context across projects. Supports cloud and self-hosted deployments with configurable session strategies.","repo":"https://github.com/plastic-labs/opencode-honcho","tags":[]},{"name":"Internet Court","pkg":"","ver":"","type":"plugins","desc":"Trust layer for agent-to-agent commerce: mandates, payments, escrow, dispute resolution.","long":"The trust layer for agent-to-agent commerce — natural-language mandates, delegated permissions, x402 payments, escrow, and dispute resolution as one open, catch-all skill. By the Internet Court Consortium (internetcourt.org). MIT licensed.","repo":"https://github.com/internet-court/internet-court-skill","tags":["agent-payments","x402","erc-7710","erc-8004","escrow","genlayer"]},{"name":"kibi-opencode","pkg":"kibi-opencode","ver":"3.2.0","type":"plugins","desc":"Repo-local, branch-scoped knowledge and traceability for OpenCode","long":"Plugin-first entry point into the Kibi stack for OpenCode users. It adds context-aware\\nguidance, routes durable code comments toward Kibi artifacts, runs non-blocking background\\nsync and targeted validation checks, and helps keep repo knowledge branch-local and queryable.","repo":"https://github.com/Looted/kibi","tags":["opencode","plugin","mcp","traceability"]},{"name":"Kilo Gateway Auth","pkg":"opencode-kilo-auth","ver":"1.1.4","type":"plugins","desc":"Kilo Gateway provider","long":"Adds Kilo Gateway provider support to OpenCode.","repo":"https://github.com/JungHoonGhae/opencode-kilo-auth","tags":[]},{"name":"Lemma","pkg":"lemma","ver":"0.10.2","type":"plugins","desc":"Persistent memory layer for LLMs via MCP - local, zero-dependency, works on all clients","long":"Biological memory model for AI coding agents. Features confidence decay/boost, Fuse.js fuzzy dedup, guide system with usage tracking, cross-references, cumulative backup, virtual session tracking, and universal memory injection via tool descriptions (works on Claude Desktop, Cursor, VS Code, Gemini ","repo":"https://github.com/xenitV1/lemma","tags":["memory","mcp","persistence","context","claude","cursor"]},{"name":"opencode-llm-proxy","pkg":"opencode-llm-proxy","ver":"1.11.2","type":"plugins","desc":"OpenCode-backed API gateway with tool calling","long":"Local OpenCode plugin that exposes OpenAI Chat Completions, OpenAI Responses, Anthropic Messages, and Gemini-compatible endpoints for any model configured in OpenCode. Supports streaming, model listing, and real tool/function calling including parallel tool calls, so tools like n8n, Open WebUI, Lang","repo":"https://github.com/KochC/opencode-llm-proxy","tags":["openai","anthropic","gemini","responses-api","llm-gateway","tool-calling"]},{"name":"Magic Context","pkg":"@cortexkit/opencode-magic-context","ver":"0.49.35","type":"plugins","desc":"Lossless context management with background compression","long":"Cache-aware context management that keeps long sessions productive. Background historian compresses old conversation into structured compartments while you keep working. Includes cross-session project memory, unified search across history/memories/facts, overnight dreamer for memory maintenance, and","repo":"https://github.com/cortexkit/opencode-magic-context","tags":[]},{"name":"Mem0 OpenCode Plugin","pkg":"","ver":"","type":"plugins","desc":"Persistent memory plugin so your agent remembers across sessions","long":"OpenCode plugin that gives your agent persistent memory powered by Mem0. Includes 9 MCP memory tools, 16 slash commands, and lifecycle hooks that auto-capture decisions, search prior context at session start, and pre-fetch error memories on failures. Install with \`opencode plugin @mem0/opencode-plug","repo":"https://github.com/mem0ai/mem0","tags":[]},{"name":"Micode","pkg":"micode","ver":"0.11.0","type":"plugins","desc":"Brainstorm-Plan-Implement workflow","long":"Structured workflow with session continuity, subagent orchestration, git worktree isolation, and AST-aware tools.","repo":"https://github.com/vtemian/micode","tags":[]},{"name":"Morph Fast Apply","pkg":"","ver":"","type":"plugins","desc":"10,500+ tokens/sec code editing","long":"Integrates Morph's Fast Apply API for faster code editing with lazy edit markers and unified diff output.","repo":"https://github.com/JRedeker/opencode-morph-fast-apply","tags":[]},{"name":"Neuralyzer","pkg":"neuralyzer","ver":"1.1.5","type":"plugins","desc":"Easier Ralph loop inside harness","long":"Allow agent to wipe its own session context and re-run the first message. This allows easier and more ergonomic Ralph loop engineering.","repo":"https://github.com/gintasz/neuralyzer","tags":[]},{"name":"Oh My Cassette","pkg":"","ver":"","type":"plugins","desc":"Chat your raw clips into a finished cut","long":"Adds a local stdio MCP server that turns a chat message into an edited video: beat-synced cuts, auto-matched background music, subtitles, transitions, and picture-in-picture. Ingest local clips, then drive a persistent multi-turn editing thread — every turn returns a timeline delta and a contact-she","repo":"https://github.com/Cassette-Editor/oh-my-cassette","tags":["mcp","video","video-editing","media"]},{"name":"Oh My Opencode","pkg":"oh-my-opencode","ver":"5.1.60","type":"plugins","desc":"Agents & Pre-built tools","long":"Background agents, pre-built tools (LSP/AST/MCP), curated agents, and a Claude Code compatible layer.","repo":"https://github.com/code-yeongyu/oh-my-opencode","tags":[]},{"name":"Oh My Opencode Slim","pkg":"oh-my-opencode-slim","ver":"3.0.7","type":"plugins","desc":"Lightweight agent orchestration with reduced token usage","long":"Slimmed-down fork of oh-my-opencode focused on core agent orchestration. Features specialized sub-agents (Explorer, Oracle, Librarian, Designer, etc.), background task management, LSP/AST tools, tmux integration for live agent visibility, and MCP servers. Optimized to consume significantly fewer tok","repo":"https://github.com/alvinunreal/oh-my-opencode-slim","tags":["agents","orchestration","background-tasks","lsp","mcp"]},{"name":"open-plan-annotator","pkg":"open-plan-annotator","ver":"1.13.2","type":"plugins","desc":"Annotate LLM plans like a Google Doc!","long":"A fully local agentic coding plugin that intercepts plan mode and opens an annotation UI in your browser. Select text to strikethrough, replace, insert, or comment — then approve the plan or request changes","repo":"https://github.com/ndom91/open-plan-annotator","tags":[]},{"name":"Opencode Agents Sidebar","pkg":"opencode-agents-sidebar","ver":"3.1.0","type":"plugins","desc":"Browse configured OhMyOpenAgent agents in the TUI","long":"OpenCode sidebar plugin that displays configured OhMyOpenAgent agents with lifecycle-based categories, collapsible sections, descriptions, and model information.","repo":"https://github.com/Mark1708/opencode-agents-sidebar","tags":[]},{"name":"Boffin","pkg":"","ver":"","type":"plugins","desc":"Routed architectural guardrails for AI coding agents in OpenCode","long":"Staff-engineer layer for AI coding agents: routes per-edit architectural constraints and requires verification. Not another AGENTS.md.","repo":"https://github.com/MicSm/boffin","tags":["opencode","architecture","guardrails","code-review","cplusplus","python"]},{"name":"OpenCode Claude Code Plugin","pkg":"opencode-claude-code-plugin","ver":"0.1.2","type":"plugins","desc":"Claude models through the official Claude Code CLI, using the login you already have","long":"Runs Anthropic's Claude models in opencode through the official claude CLI as a subprocess, so opencode uses whatever the CLI is logged in as and no token is ever read or replayed. Bash, Edit and Write are proxied back to opencode's own permissions, with several accounts, per-subagent models, and on","repo":"https://github.com/khalilgharbaoui/opencode-claude-code-plugin","tags":[]},{"name":"OpenCode Claude Memory","pkg":"opencode-claude-memory","ver":"2.3.0","type":"plugins","desc":"Claude Code-compatible memory","long":"Share persistent Markdown memory between OpenCode and Claude Code using Claude Code-compatible paths and file formats.","repo":"https://github.com/kuitos/opencode-claude-memory","tags":[]},{"name":"Opencode CMD Provider","pkg":"opencode-cmd-provider","ver":"2.3.4","type":"plugins","desc":"Use CommandCode plans (Go, GOAT, Pro, Max) as an OpenCode provider.","long":"OpenCode provider plugin for the CommandCode Provider API. Authenticate via\\n/connect browser flow or COMMANDCODE_API_KEY, auto-registers all CommandCode\\nmodels ([CMD] prefix) with offline snapshot, pricing display, and per-plan\\ndeals/allowance intelligence in the sidebar and cmd_plan_summary tool.","repo":"https://github.com/rashidrazak/opencode-cmd-provider","tags":["provider","commandcode","model-discovery","auth"]},{"name":"Cursor Auth","pkg":"opencode-cursor","ver":"0.0.1","type":"plugins","desc":"Use your Cursor subscription models in OpenCode via OAuth","long":"Connects Cursor's API to OpenCode via OAuth login, automatic model discovery, and a local OpenAI-compatible proxy — use the models included in your Cursor subscription directly from OpenCode.","repo":"https://github.com/ephraimduncan/opencode-cursor","tags":[]},{"name":"opencode-dir-tree-tui","pkg":"opencode-dir-tree-tui","ver":"1.0.6","type":"plugins","desc":"File tree sidebar with Git status and quick file actions","long":"Browse project files in the OpenCode V2 sidebar with Git status indicators, custom ignore rules, path copying, and quick file and folder opening.","repo":"https://github.com/aihaipeng/opencode-dir-tree-tui","tags":[]},{"name":"OpenCode Ensemble","pkg":"@hueyexe/opencode-ensemble","ver":"0.20.0","type":"plugins","desc":"Parallel agent teams for OpenCode","long":"Coordinate parallel OpenCode agents with peer messaging, a shared task board, git worktree isolation, and a live dashboard.","repo":"https://github.com/hueyexe/opencode-ensemble","tags":["agents","multi-agent","orchestration","worktrees","dashboard"]},{"name":"opencode-failover","pkg":"opencode-failover","ver":"2.0.7","type":"plugins","desc":"Automatic API-key failover and rotation across multiple provider keys","long":"OpenCode plugin that automatically rotates API keys across multiple provider credentials. When a key hits a rate limit, the plugin quarantines it and switches to the next available key with zero downtime. Features weighted round-robin distribution, exponential backoff quarantine, retry-after header ","repo":"https://github.com/bulutmuf/opencode-failover","tags":[]},{"name":"GitHub Release","pkg":"opencode-github-release","ver":"0.6.0","type":"plugins","desc":"Automated GitHub releases","long":"Create and publish GitHub releases with semantic versioning, tag management, and auto-generated release notes.","repo":"https://github.com/amestsantim/opencode-github-release","tags":[]},{"name":"GoopSpec","pkg":"goopspec","ver":"0.2.9","type":"plugins","desc":"Spec-driven development workflow","long":"Transforms AI-assisted coding with spec-driven workflows. Features 5-phase workflow (Plan, Research, Specify, Execute, Accept), contract gates for user confirmation, 12 specialized subagents, persistent memory system, wave-based execution with atomic commits, and deviation rules for handling unexpec","repo":"https://github.com/hffmnnj/opencode-goopspec","tags":[]},{"name":"GPT Imagegen","pkg":"opencode-gpt-imagegen","ver":"1.2.2","type":"plugins","desc":"gpt-image-2 in OpenCode — no API cost when using your ChatGPT subscription","long":"Brings gpt-image-2 (ChatGPT Images 2) image generation to OpenCode. When you sign into OpenCode with your ChatGPT account, generations are billed against your existing Plus / Pro / Business plan — no per-image API cost. An OpenAI API key path is also planned. Supports reference images for style guid","repo":"https://github.com/yuji-hatakeyama/opencode-gpt-imagegen","tags":["openai","gpt-image-2","gpt-image","image-generation","image-editing","text-to-image"]},{"name":"Opencode LiteLLM","pkg":"opencode-litellm","ver":"0.1.0","type":"plugins","desc":"Auto-discover models from a LiteLLM proxy","long":"Drop-in LiteLLM provider for OpenCode with zero configuration. Auto-detects a running\\nLiteLLM proxy on common ports (4000, 8000, 8080), pulls every model from /v1/models,\\nand registers them in OpenCode automatically — no model lists to hand-maintain. Smart\\nname formatting, modality categorization (c","repo":"https://github.com/yuseferi/opencode-litellm","tags":["litellm","provider","openai-compatible","llm-proxy","model-discovery"]},{"name":"Opencode Mem","pkg":"opencode-mem","ver":"2.41.0","type":"plugins","desc":"Persistent memory with vector database","long":"A persistent memory system for AI coding agents that enables long-term context retention across sessions using local vector database technology. Features dual memory scopes, web interface, auto-capture system, and multi-provider AI support.","repo":"https://github.com/tickernelz/opencode-mem","tags":[]},{"name":"OpenCode Mission Control","pkg":"opencode-mission-control","ver":"1.6.0","type":"plugins","desc":"Command center for parallel agents — worktree isolation, DAG plans, merge train, PRs","long":"Orchestrates parallel OpenCode agents in tmux-isolated git worktrees with live inspection (capture/attach/diff), a dashboard overview, agent status reporting, and in-chat notifications. Supports DAG-based plans with autopilot/copilot/supervisor modes and a merge train with test gating and automatic ","repo":"https://github.com/nigel-dev/opencode-mission-control","tags":[]},{"name":"Opencode Models Discovery","pkg":"opencode-models-discovery","ver":"2.1.1","type":"plugins","desc":"Configurable model discovery and filtering without long manual config","long":"OpenCode plugin for discovering models from OpenAI-compatible providers and API gateways, with provider and model filtering so users can avoid maintaining large configs or loading every model at once.","repo":"https://github.com/yuhp/opencode-models-discovery","tags":[]},{"name":"OpenCode ntfy.sh","pkg":"opencode-ntfy.sh","ver":"1.1.0","type":"plugins","desc":"Push notifications to keep you in the know, even when you're on the go.","long":"An OpenCode plugin that adds push notifications through ntfy.sh.","repo":"https://github.com/lannuttia/opencode-ntfy.sh","tags":[]},{"name":"OpenAI Native Compaction","pkg":"opencode-openai-compact","ver":"1.4.0","type":"plugins","desc":"Use OpenAI's official Responses API compaction in OpenCode","long":"Routes OpenCode session compaction for OpenAI providers through \`/responses/compact\`, stores the returned compacted output locally, and injects it into the next Responses request so long sessions use OpenAI's native compaction instead of a prompt-written summary.","repo":"https://github.com/partment/opencode-openai-compact","tags":[]},{"name":"OpenWork for OpenCode","pkg":"","ver":"","type":"plugins","desc":"Official OpenWork plugin to deploy AI models, skills, and MCPs to your team's OpenCode","long":"The official plugin from OpenWork. Centrally deploy AI models, skills, and MCP servers to your whole team. Admins add a model provider, skill, or MCP connection once and choose who gets it; everyone receives it in OpenCode after a single sign-in. No shared API keys, no copying MCP configs between ma","repo":"https://github.com/different-ai/openwork/tree/dev/packages/opencode-plugin","tags":[]},{"name":"opencode-plugin-otel","pkg":"@devtheops/opencode-plugin-otel","ver":"2.0.1","type":"plugins","desc":"OpenTelemetry telemetry exporter for opencode sessions, mirroring Claude Code monitoring signals","long":"Exports metrics, logs, and traces from opencode sessions via OTLP/gRPC to any OpenTelemetry-compatible backend (Datadog, Honeycomb, Grafana Cloud, etc.). Instruments session lifecycle, token usage, cost, tool durations, and git commits — mirroring the same signals as Claude Code's monitoring.","repo":"https://github.com/DEVtheOPS/opencode-plugin-otel","tags":[]},{"name":"OpenCode Pollinations Plugin","pkg":"opencode-pollinations-plugin","ver":"6.5.5","type":"plugins","desc":"Full Pollinations.ai integration — free unlimited AI models, 17+ multimodal tools, hourly free tiers & Safety Net.","long":"The ultimate bridge between OpenCode and the Pollinations.ai ecosystem. Access 30+ free and enterprise AI models (OpenAI, Gemini, Mistral, Claude...) with hourly Pollen resets. Includes 17+ agent tools (image, video, audio, music generation, background removal, web search, transcription, QR codes, d","repo":"https://github.com/fkom13/opencode-pollinations-plugin","tags":[]},{"name":"PR Auto-Signature","pkg":"opencode-pr-signature","ver":"1.2.2","type":"plugins","desc":"Automatically adds AI model signature to PRs, Issues, and Commits","long":"Plugin that automatically appends AI model signature to Pull Requests, Issues, and git commit messages. Supports GitHub MCP tools, git CLI, and gh CLI. Recognizes 90+ AI models including Claude, GPT, Gemini, DeepSeek, Llama, Mistral, and more.","repo":"https://github.com/arttttt/opencode-pr-signature","tags":[]},{"name":"Opencode Quota","pkg":"opencode-quota","ver":"0.0.1-dev","type":"plugins","desc":"Quota toasts and token tracking","long":"Track quota and token usage across providers via automatic toasts and slash commands.","repo":"https://github.com/slkiser/opencode-quota","tags":[]},{"name":"OpenCodeRAG","pkg":"","ver":"","type":"plugins","desc":"Local-first RAG plugin for semantic code search with tree-sitter chunking and LanceDB","long":"A local-first Retrieval-Augmented Generation plugin for OpenCode that enables semantic code\\nsearch across your workspace. Features AST-aware chunking via tree-sitter (19 languages),\\nincremental indexing with file-change detection, watch mode for live re-indexing, and\\nconfigurable embeddings via Olla","repo":"https://github.com/MrDoe/OpenCodeRAG","tags":[]},{"name":"opencode-review","pkg":"","ver":"","type":"plugins","desc":"Automatic structured code review with configurable dimensions and auto-fix","long":"An automatic code review plugin for OpenCode CLI. Reviews staged changes when session goes idle, with configurable cooldown, multi-dimension analysis (code quality, security, performance, testing, documentation), severity levels, and auto-fix chain support.","repo":"https://github.com/sun-praise/opencode-review","tags":[]},{"name":"opencode-short-term-memory","pkg":"","ver":"","type":"plugins","desc":"Maintain user instructions and preferences throughout long sessions. No more repeating yourself.","long":"Automatically summarizes conversation context into structured session memory and injects it back into the system prompt every few turns — preserving user instructions, project context, decisions, and active references across long chats and compactions.","repo":"https://github.com/andrejtonev/opencode-short-term-memory","tags":[]},{"name":"opencode-skills-tui","pkg":"opencode-skills-tui","ver":"1.2.3","type":"plugins","desc":"Browse skills, track loaded state, and preview SKILL.md","long":"OpenCode V2 skills sidebar with loaded-state tracking, SKILL.md previews, token estimates, and skill mentions in prompts.","repo":"https://github.com/aihaipeng/opencode-skills-tui","tags":[]},{"name":"opencode-snip","pkg":"opencode-snip","ver":"3.0.0","type":"plugins","desc":"OpenCode plugin that prefixes shell commands with snip to reduce LLM token consumption by 60-90%","long":"Automatically prefixes supported shell commands (git, go, cargo, npm, docker, etc.) with snip to filter output before it reaches your LLM context window.","repo":"https://github.com/VincentHardouin/opencode-snip","tags":[]},{"name":"Opencode Snippets","pkg":"opencode-snippets","ver":"3.4.0","type":"plugins","desc":"Instant inline text expansion","long":"Instant inline text expansion for OpenCode. Type #snippet anywhere in your message and watch it transform. Brings DRY principles to prompt engineering with composable, shell-enabled snippets.","repo":"https://github.com/JosXa/opencode-snippets","tags":[]},{"name":"OpenCode Swarm","pkg":"opencode-swarm","ver":"7.201.2","type":"plugins","desc":"Verification-gated swarm with architect, review, test, and security agents","long":"Verification-gated OpenCode swarm with architect planning, independent review, test engineering, security checks, and resumable evidence.","repo":"https://github.com/zaxbysauce/opencode-swarm","tags":[]},{"name":"Opencode Synced","pkg":"opencode-synced","ver":"0.11.0","type":"plugins","desc":"Sync configs across machines","long":"Enables syncing global opencode configurations across machines with public/private visibility options.","repo":"https://github.com/iHildy/opencode-synced","tags":[]},{"name":"System Prompt Logger","pkg":"opencode-system-prompt-logger","ver":"1.0.0","type":"plugins","desc":"System prompt logger","long":"OpenCode plugin that intercepts the system prompt before it's sent to the LLM and logs it.","repo":"https://github.com/tlinhart/opencode-system-prompt-logger","tags":[]},{"name":"Opencode Telemetry","pkg":"opencode-telemetry","ver":"0.3.4","type":"plugins","desc":"Passive cross-session telemetry to local SQLite; cost rollups across agent orchestration chains","long":"Logs every opencode session to a local SQLite database automatically — tokens, tool calls, skills, per-turn cost. Aggregates costs across orchestration chains (conductor + child sessions), offers turn-by-turn forensic inspection, and ships an \`octm\` CLI for reports without spending model tokens. Zer","repo":"https://github.com/agostinilabsrl/opencode-telemetry","tags":["telemetry","observability","cost-tracking","sqlite","local-first","orchestration"]},{"name":"Opencode update notifier","pkg":"opencode-update-notifier","ver":"0.4.0","type":"plugins","desc":"Notify about plugin updates.","long":"Checks if your pinned plugins have newer versions available and shows a notification.","repo":"https://github.com/tim-hilde/opencode-update-notifier","tags":[]},{"name":"Opencode Usage Monitor","pkg":"opencode-usage-monitor","ver":"3.1.0","type":"plugins","desc":"Monitor OpenAI and Z.AI usage quotas in the TUI","long":"OpenCode sidebar plugin that displays API usage quotas for OpenAI and Z.AI providers with per-model breakdowns, spend tracking, and configurable refresh controls.","repo":"https://github.com/Mark1708/opencode-usage-monitor","tags":[]},{"name":"Opencode Visualizer","pkg":"","ver":"","type":"plugins","desc":"2D pixel-art office for AI agents","long":"Turning raw OpenCode terminal logs into cozy 2D pixel office chaos. Watch your agents work, idle, and celebrate success in a bustling virtual office.","repo":"https://github.com/psinetron/opencode-visualiser","tags":[]},{"name":"OrgX OpenCode Plugin","pkg":"@useorgx/orgx-opencode-plugin","ver":"0.1.0-alpha.7","type":"plugins","desc":"OrgX Work Graph peer for task dispatch, receipts, deviations, and passive reconciliation","long":"OrgX OpenCode Plugin connects OpenCode sessions to the OrgX execution control plane. It receives OrgX task dispatches, drives the user's local OpenCode session, posts execution receipts and deviations back to OrgX, and writes compact Work Graph events for later audit-first reconciliation.","repo":"https://github.com/useorgx/orgx-opencode-plugin","tags":["agent-orchestration","work-graph","task-dispatch","receipts","mcp"]},{"name":"Plannotator","pkg":"","ver":"","type":"plugins","desc":"Interactive plan review UI","long":"Plan review UI with visual annotation, private/offline sharing, and Obsidian/Bear integration.","repo":"https://github.com/backnotprop/plannotator","tags":[]},{"name":"Rehydra","pkg":"rehydra","ver":"0.16.5","type":"plugins","desc":"Prevent your coding agent from leaking secrets to LLM providers","long":"Intercepts the conversation between OpenCode and the LLM, replacing secrets from .env files and detected API keys, tokens, and credentials with placeholders before they leave your machine. Secrets are transparently restored before any tool executes locally. Supports pattern-based detection for Strip","repo":"https://github.com/rehydra-ai/rehydra-sdk","tags":[]},{"name":"Ring a Bell Example","pkg":"","ver":"","type":"plugins","desc":"Simple terminal bell plugin","long":"A simple plugin to ring the terminal bell once a request is complete.","repo":"https://gist.github.com/ahosker/267f375a65378bcb9a867fd9a195db1e","tags":[]},{"name":"Simple Memory","pkg":"","ver":"","type":"plugins","desc":"Git-based memory","long":"Simple plugin to manage memory inside a git repo that can be committed and reviewed by team members.","repo":"https://github.com/cnicolov/opencode-plugin-simple-memory","tags":[]},{"name":"SonarQube","pkg":"sonarqube","ver":"0.0.1","type":"plugins","desc":"Automatically enforce SonarQube code quality and security in the agent coding loop","long":"SonarQube is the AI code quality and security verification platform used by millions of developers to catch bugs, vulnerabilities, and leaked secrets. This plugin enforces those standards in the agent coding loop: 7,500+ distinct issue types, secrets scanning, agentic analysis, and quality gates acr","repo":"https://github.com/SonarSource/sonarqube-agent-plugins","tags":["sonarqube","code-quality","static-analysis","security","secrets"]},{"name":"Superpowers","pkg":"superpowers","ver":"0.0.2","type":"plugins","desc":"Complete software development methodology with composable skills for coding agents","long":"Gives your coding agent a structured methodology - brainstorm specs, write plans, run TDD, dispatch subagents, and review work. Skills trigger automatically across Claude Code, OpenCode, Codex, Gemini CLI, Cursor, and GitHub Copilot CLI.","repo":"https://github.com/obra/superpowers","tags":[]},{"name":"Systematic","pkg":"systematic","ver":"3.1.1","type":"plugins","desc":"Compound-engineering workflows for OpenCode, Pi, and Claude Code","long":"Provides brainstorm, plan, work, and review workflows as bundled skills and agents for OpenCode, Pi, and Claude Code.","repo":"https://github.com/marcusrbrown/systematic","tags":[]},{"name":"@telemetry-dev/opencode","pkg":"@telemetry-dev/opencode","ver":"0.2.4","type":"plugins","desc":"OpenTelemetry traces for OpenCode agent turns, model calls, and tools","long":"Records OpenCode agent turns, model calls, tool executions, and session events with OpenTelemetry for telemetry.dev. Available as the @telemetry-dev/opencode npm package; source and setup instructions are in packages/opencode of the linked SDK repository.","repo":"https://github.com/telemetry-dev/telemetry","tags":[]},{"name":"toon-config","pkg":"toon-config","ver":"1.0.2","type":"plugins","desc":"Loads TOON based, AGENTS.toon rather than Markdown","long":"Use TOON based agent instructions. Command to analyize and improve agent instructions.","repo":"https://github.com/mmynsted/opencode-toon-config-plugin","tags":[]},{"name":"TypeUI","pkg":"","ver":"","type":"plugins","desc":"Design systems, UI prompts, and layout variation guidance","long":"Open-source design layer for OpenCode that provides curated design skills, UI prompts, and layout variation guidance so agents can generate more consistent interfaces. Includes an <a href=\\"https://www.typeui.sh/docs/guides/opencode\\">OpenCode setup guide</a>.","repo":"https://github.com/bergside/typeui","tags":[]},{"name":"ul-opencode-event","pkg":"@ulthon/ul-opencode-event","ver":"0.1.58","type":"plugins","desc":"Multi-channel notifications (SMTP, DingTalk, Feishu) and token usage analytics","long":"Get notified when OpenCode sessions complete, error, or need your attention - via SMTP email, DingTalk robot, or Feishu robot.\\n- Multi-channel: SMTP, DingTalk, Feishu, local JSONL file\\n- Multi-level token analytics: message / session / total / project scopes\\n- 74+ template variables with i18n suppor","repo":"https://gitee.com/ulthon/ul-opencode-event","tags":["notifications","email","dingtalk","feishu","token-tracking","usage-analytics"]},{"name":"UNMOJI","pkg":"@bastiangx/opencode-unmoji","ver":"1.0.0","type":"plugins","desc":"Strip emojis from output","long":"A simple plugin that strips ALL emojis from agent outputs in Opencode.","repo":"https://codeberg.org/bastiangx/opencode-unmoji","tags":[]},{"name":"WakaTime","pkg":"wakatime","ver":"1.2.0","type":"plugins","desc":"WakaTime integration","long":"WakaTime integration plugin for tracking coding activity in opencode sessions.","repo":"https://github.com/angristan/opencode-wakatime","tags":[]},{"name":"Xquik","pkg":"x-twitter-scraper","ver":"0.20.0","type":"plugins","desc":"X/Twitter data skill & MCP server","long":"X/Twitter data skill — MCP server, REST API, 20 extraction tools. Works with Claude Code, Cursor, Codex, and 40+ agents.","repo":"https://github.com/Xquik-dev/x-twitter-scraper","tags":[]},{"name":"OpenCode Light Themes","pkg":"","ver":"","type":"themes","desc":"A curated collection of 21 light color themes for OpenCode","long":"The largest collection of light themes for OpenCode. Includes 21 carefully crafted light themes converted from popular VS Code themes — scaefy-light (Atom One Light), Solarized Light, Vivid Light, Coffee Cream, GitHub Light, Quiet Light, Bluloco Light, Brackets Light Pro, NetBeans Light, Ysgrifennwr","repo":"https://github.com/fatihtoprakk/opencode-light-themes","tags":[]}]`),sr=["https://raw.githubusercontent.com/awesome-opencode/awesome-opencode/main/dist/registry.json","https://raw.githubusercontent.com/Gracdjd/awesome-opencode/main/dist/registry.json"],lr=720*60*1e3,Ht=ar;function Vt(){const e=process.env.XDG_CACHE_HOME;return e?E.join(e,"opencode","plugin-hub"):E.join(Ce.homedir(),".cache","opencode","plugin-hub")}function Wt(){return E.join(Vt(),"registry.json")}const cr=/"plugin"\s*:\s*\[\s*"([^"]+)"/,It=/^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;function Tt(e){const r=cr.exec(e.installation??"");if(r?.[1])return r[1];const o=(e.displayName??"").trim();if(It.test(o)&&!o.includes(" "))return o;const a=(e.repoUrl??"").replace(/\/+$/,"").split("/").pop()?.replace(".git","")??"";return It.test(a)?a:""}const Ue=new Map;for(const e of Ht)Ue.set(e.repo,e),e.pkg&&Ue.set(`pkg:${e.pkg}`,e);function pr(e){return e.replace(/\/+$/,"").replace(/\.git$/,"")}function Be(e,r){const o=[],n=new Set;for(const a of e){const s=a.type??"plugins";if(s!=="plugins"&&s!=="mcp"&&s!=="themes")continue;const c=pr(a.repoUrl??"");if(!c||n.has(c))continue;n.add(c);const p=Ue.get(c)??Ue.get(`pkg:${Tt(a)}`),d=p?.pkg??Tt(a);o.push({name:a.displayName??d??c,pkg:d,version:p?.ver??"",type:s,desc:(a.tagline??"").trim(),long:(a.description??"").trim().slice(0,300),repo:c,tags:(a.tags??[]).slice(0,6),installed:d?r.has(d):!1})}return o}async function dr(){let e="";for(const r of sr)try{const o=await fetch(r,{signal:AbortSignal.timeout(12e3),headers:{accept:"application/json"}});if(!o.ok){e=`${new URL(r).host} 返回 ${o.status}`;continue}const n=await o.json();if(!Array.isArray(n)){e="registry 格式不是数组";continue}return n}catch(o){e=nt(o)}throw new Error(e||"无法连接插件目录")}async function ur(){try{const e=await q.readFile(Wt(),"utf8"),r=JSON.parse(e);return Array.isArray(r.entries)?r:void 0}catch{return}}async function gr(e){const r=Date.now();try{await q.mkdir(Vt(),{recursive:!0}),await q.writeFile(Wt(),JSON.stringify({fetchedAt:r,entries:e}),"utf8")}catch{}return r}async function hr(e,r){const o=await ur(),n=o!==void 0&&Date.now()-o.fetchedAt<lr;if(o&&n&&!r)return{entries:Be(o.entries,e),source:"cache",fetchedAt:o.fetchedAt,error:""};try{const a=await dr(),s=await gr(a);return{entries:Be(a,e),source:"network",fetchedAt:s,error:""}}catch(a){return o?{entries:Be(o.entries,e),source:"cache",fetchedAt:o.fetchedAt,error:nt(a)}:{entries:Be(Ht.map(mr),e),source:"builtin",fetchedAt:0,error:nt(a)}}}function mr(e){return{displayName:e.name,type:e.type,tagline:e.desc,description:e.long,repoUrl:e.repo,tags:e.tags}}function nt(e){return e instanceof Error?e.message:String(e)}var fr=t.template("<div class=ph-detail-loading><span>正在读取 npm 上的 README 与统计…"),vr=t.template("<span class=ph-badge data-tone=accent>v"),rt=t.template("<span class=ph-badge data-tone=success>已安装"),br=t.template("<span class=ph-badge data-tone=warning>可更新"),yr=t.template("<span class=ph-badge data-tone=danger>已废弃"),wr=t.template("<div class=ph-detail-head><div class=ph-name-row></div><div class=ph-detail-desc>"),kr=t.template("<div class=ph-detail-stats><div class=ph-detail-stat><b></b><span>周下载</span></div><div class=ph-detail-stat><b></b><span>月下载</span></div><div class=ph-detail-stat><b></b><span>已发布版本</span></div><div class=ph-detail-stat><b></b><span>"),xr=t.template("<div class=ph-warning><span>"),Sr=t.template("<div class=ph-warning data-tone=danger><span>"),$r=t.template("<span class=ph-chip>许可证 "),Cr=t.template("<span class=ph-chip>发布于 "),_r=t.template("<span class=ph-chip>首次发布 "),Or=t.template("<span class=ph-chip>维护者 "),Ar=t.template("<div class=ph-detail-meta>"),Jt=t.template("<div class=ph-chips>"),Ir=t.template("<div class=ph-detail-links>"),Tr=t.template("<details class=ph-details><summary>依赖 <!> 个</summary><div class=ph-chips>"),Lr=t.template("<div class=ph-readme-note>README 较长,这里只显示前一部分。完整内容见 npm 页面。"),Mr=t.template("<section class=ph-section><div class=ph-section-title>README"),jr=t.template("<div class=ph-readme-empty>"),Pr=t.template("<div class=ph-detail>"),zr=t.template("<span class=ph-detail-error>"),Br=t.template("<div class=ph-dialog-footer><span class=ph-spacer>"),ot=t.template("<span class=ph-chip>"),Er=t.template("<div class=ph-readme-empty>这个包没有附带 README。可以打开 npm 页面或项目主页进一步了解。"),Dr=t.template("<span class=ph-count>"),Nr=t.template('<span class="ph-count ph-count-alert"> 可更新'),Ur=t.template("<button class=ph-tab>"),Gr=t.template("<span class=ph-version-arrow> → v"),Rr=t.template("<span class=ph-version>"),Fr=t.template("<span class=ph-pkg>"),Ke=t.template("<span class=ph-badge data-tone=accent>可更新 → v"),ue=t.template("<span class=ph-dot>"),qr=t.template("<span>v"),Hr=t.template("<span>仅 GitHub 发布"),Lt=t.template("<span>"),Vr=t.template("<span class=ph-badge>需手动"),Wr=t.template('<article class=ph-card><div class=ph-tile></div><div class=ph-card-body role=button tabindex=0><div class=ph-name-row><span class=ph-name></span></div><div class="ph-desc ph-clamp"></div><div class=ph-meta><span class=ph-type></span></div></div><div class=ph-actions>'),Jr=t.template("<span>by "),Qr=t.template("<span>更新"),Xr=t.template('<article class=ph-card><div class=ph-tile></div><div class=ph-card-body role=button tabindex=0><div class=ph-name-row><span class=ph-name></span><span class=ph-pkg>v</span></div><div class="ph-desc ph-clamp"></div><div class=ph-meta><span>/周</span><span class=ph-dot></span><span>/月</span></div></div><div class=ph-actions>'),Yr=t.template("<span class=ph-badge>本地目录"),Zr=t.template("<span class=ph-badge data-tone=warning>未能检查"),Kr=t.template("<span>更新会覆盖目录内容,原文件自动备份"),eo=t.template("<article class=ph-card><div class=ph-tile></div><div class=ph-card-body><div class=ph-name-row><span class=ph-name></span></div><div class=ph-meta><span class=ph-pkg></span></div></div><div class=ph-actions>"),to=t.template("<h2 class=ph-title>插件中心"),no=t.template("<div class=ph-notice><span>"),ro=t.template("<div class=ph-notice data-tone=error><span>"),oo=t.template("<span>更新于 "),Le=t.template("<div class=ph-list>"),io=t.template("<section class=ph-panel><div class=ph-filters><div class=ph-filter-row><div class=ph-grow></div><div class=ph-chips></div></div><div class=ph-meta><span></span><span class=ph-dot></span><span> 个条目 · <!> 个可安装"),ao=t.template("<div class=ph-empty><strong>搜索 npm 上的 OpenCode 插件</strong><span>按名称或用途搜索,例如 auth、notify、memory。点开任意结果可以看 README、下载量和主页。</span><div class=ph-chips>"),so=t.template("<div class=ph-resultbar><h3 class=ph-h3>搜索结果 <span class=ph-count></span></h3><div class=ph-sort>"),lo=t.template("<section class=ph-panel><div class=ph-searchbar><div class=ph-grow></div></div><div class=ph-meta><span>直查 npm 上带 opencode-plugin 标签的包,覆盖精选目录之外的长尾;结果未经人工筛选。"),co=t.template("<span class=ph-meta>检查过 npm registry"),po=t.template("<section class=ph-panel><div class=ph-resultbar><h3 class=ph-h3>已安装 <span class=ph-count>"),uo=t.template(`<div class=ph-root><style>
55
+ `;try{return Le.spawn("/bin/sh",["-c",e],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function or(e){const o=await se();try{return e.length===0?{ok:!1,message:"找不到 opencode CLI",state:o}:(Le.spawn(e[0],[...e.slice(1),"reload"],{detached:!0,stdio:"ignore"}).unref(),{ok:!0,message:"已请求重载配置;插件列表变化建议完全退出应用再打开",state:o})}catch(r){return{ok:!1,message:oe(r),state:o}}}function oe(e){return e instanceof Error?e.message:String(e)}const ir=JSON.parse(`[{"name":"@aerovato/operator-memory","pkg":"","ver":"","type":"plugins","desc":"Durable context for agent-driven development","long":"Operator Memory gives the agent durable memory made of ordinary Markdown: specs, decisions, standards, and research, kept across private, shared, and user partitions. Every file is yours to read, correct, or remove, and new sessions start from that knowledge. No embeddings, no vector database, no ho","repo":"https://github.com/aerovato/operator-memory","tags":["memory","context","documentation","markdown"]},{"name":"Agent Memory","pkg":"opencode-agent-memory","ver":"0.3.1","type":"plugins","desc":"Letta-inspired memory","long":"Gives the agent persistent, self-editable memory blocks inspired by Letta agents.","repo":"https://github.com/joshuadavidthomas/opencode-agent-memory","tags":[]},{"name":"Agent Notifications","pkg":"","ver":"","type":"plugins","desc":"Desktop and webhook alerts for OpenCode sessions","long":"Desktop and optional webhook notifications for OpenCode root-session completion, questions, permissions, and errors on macOS, Linux, and Windows.","repo":"https://github.com/777genius/agent-notifications","tags":[]},{"name":"Agent Skills (JDT)","pkg":"opencode-agent-skills","ver":"0.7.0","type":"plugins","desc":"Dynamic skills loader","long":"Dynamic skills loader that discovers skills from project, user, and plugin directories.","repo":"https://github.com/joshuadavidthomas/opencode-agent-skills","tags":[]},{"name":"OpenCode Antigravity CLI Auth","pkg":"opencode-agy-auth","ver":"2.1.1","type":"plugins","desc":"Google Antigravity account auth","long":"Authenticate the Opencode CLI with your Google account so you can use your existing Antigravity plan.","repo":"https://github.com/anthonyhaussman/opencode-agy-auth","tags":[]},{"name":"AIDevOps","pkg":"aidevops","ver":"3.39.125","type":"plugins","desc":"AI DevOps framework and OpenCode plugin","long":"OpenCode-first AI DevOps framework with agents, skills, workflows, safety hooks, memory, GitHub worker dispatch, and integrations for code, infrastructure, content, SEO, and operations.","repo":"https://github.com/marcusquinn/aidevops","tags":[]},{"name":"Antigravity Auth","pkg":"opencode-antigravity-auth","ver":"2.1.2","type":"plugins","desc":"Google Antigravity models","long":"Use Gemini and Anthropic models for free via Google Antigravity IDE authentication.","repo":"https://github.com/NoeFabris/opencode-antigravity-auth","tags":[]},{"name":"basemind","pkg":"basemind","ver":"0.31.1","type":"plugins","desc":"Full AI context layer over MCP — code-map, document RAG, agent memory, web crawl, git history","long":"Full AI context layer over MCP — tree-sitter code-map, document RAG, shared agent memory, web crawl, and git history. 300+ languages. Published to npm as basemind-opencode (https://www.npmjs.com/package/basemind-opencode).","repo":"https://github.com/Goldziher/basemind","tags":[]},{"name":"Beads Plugin","pkg":"opencode-beads","ver":"0.9.0","type":"plugins","desc":"Beads issue tracker integration","long":"Integration for Steve Yegge's beads issue tracker with /bd-* commands.","repo":"https://github.com/joshuadavidthomas/opencode-beads","tags":[]},{"name":"BRHP","pkg":"","ver":"","type":"plugins","desc":"Persistent planning state","long":"Structured, persistent planning for OpenCode with local session state, /brhp commands, bounded planner history, and a TUI sidebar.","repo":"https://github.com/ZanzyTHEbar/brhp","tags":[]},{"name":"bro","pkg":"bro","ver":"1.0.7","type":"plugins","desc":"Re-explains the last reply in plain language","long":"One-file Agent Skill that re-explains the assistant's last reply (or pasted text) in plain language when it was too dense. The installer adds a \`/bro\` command for OpenCode (\`~/.config/opencode/skills/bro/SKILL.md\` plus a command wrapper); also works in Claude Code, Codex, Cursor and Hermes.","repo":"https://github.com/luchasarie/bro-skill","tags":[]},{"name":"cache-stats","pkg":"","ver":"","type":"plugins","desc":"Showing the active session's cache hit rate in a popup dialog","long":"This plugin shows the overall cache hit rate, a per-model breakdown, a per subagent breakdown and the associated cost, making it easy to understand cache performance at a glance.","repo":"https://github.com/nmdra/opencode-cache-stats","tags":[]},{"name":"Caspian","pkg":"caspian-opencode-plugin","ver":"0.1.7","type":"plugins","desc":"Give your OpenCode agent an inbox — email, Telegram, and Discord","long":"Official Caspian plugin for OpenCode. Gives the agent a bidirectional inbox — email, Telegram, and Discord — with slash commands, session threading back to Gmail, and desktop notifications. No Caspian account required; on first start the plugin mints a sandbox project and provisions an agent email. ","repo":"https://github.com/TryCaspian/caspian-sdk/tree/main/packages/opencode","tags":["email","telegram","discord","inbox","messaging","notifications"]},{"name":"CC Safety Net","pkg":"","ver":"","type":"plugins","desc":"Safety net catching destructive commands","long":"A Claude Code plugin that acts as a safety net, catching destructive git and filesystem commands before they execute.","repo":"https://github.com/kenryu42/claude-code-safety-net","tags":[]},{"name":"Chisle","pkg":"chisle","ver":"3.8.1","type":"plugins","desc":"Cuts token use with terse answers, smallest-code-that-works rules and tool-output compression","long":"Installs a ruleset for terse answers that keep every decisive fact and a \\"smallest code that works\\" ladder, Chisle skills, and a compression plugin that trims oversized tool output before it re-enters the context. The same ruleset ships for Claude Code, Cursor, Codex and Gemini CLI. Install with \`np","repo":"https://github.com/JayPokale/Chisle","tags":["tokens","cost","context","productivity"]},{"name":"Subagent Reporter","pkg":"","ver":"","type":"plugins","desc":"See exactly what your subagents are up to (in the terminal) when invoked during an \`opencode run _____\` session.","long":"When opencode run <prompt> is invoked normally, the actions of the primary agent are visible in the terminal, but subagent actions are not. This makes it difficult to follow progress on the prompt, and makes it very difficult to use \`opencode run\` as part of an unattended process or script. This plu","repo":"https://github.com/raisbecka/opencode-subagent-output","tags":[]},{"name":"codemem","pkg":"codemem","ver":"0.47.3","type":"plugins","desc":"Persistent coding memory across sessions, machines, and teammates","long":"Captures coding decisions, discoveries, and fixes, with automatic context injection in OpenCode, Claude Code, and Codex. Includes MCP tools for searching and managing memories, a viewer, optional cross-machine sync, and sharing of selected projects with teammates.","repo":"https://github.com/kunickiaj/codemem","tags":[]},{"name":"Command Inject","pkg":"opencode-command-inject","ver":"1.3.1","type":"plugins","desc":"Auto-inject project commands into OpenCode","long":"Automatically discovers and injects Makefile targets, npm/pnpm/yarn/bun scripts, and local skills at startup. Type / to see and run all project commands.","repo":"https://github.com/shihyuho/opencode-command-inject","tags":[]},{"name":"ContextStream","pkg":"mcp-server","ver":"0.0.9","type":"plugins","desc":"Shared project context for OpenCode, Cursor, Claude Code, Codex, and Grok via MCP","long":"Hosted MCP server that keeps coding agents on shared project context across sessions and teammates. Works with OpenCode and other MCP-compatible clients. Endpoint https://mcp.contextstream.io/mcp — Intelligence isn’t the bottleneck. Context is.","repo":"https://github.com/contextstream/mcp-server","tags":["mcp","context","productivity","collaboration"]},{"name":"deja","pkg":"opencode-deja","ver":"0.23.5","type":"plugins","desc":"Recall what you already did in your other coding agents","long":"opencode remembers its own sessions. deja answers the other question: what you did in Claude Code, Codex, Cursor, Gemini CLI, Zed and fifteen more agents on this machine — including the months before you installed anything.\\n\\nA local Go binary indexes the transcripts those agents already wrote to dis","repo":"https://github.com/vshulcz/deja-vu","tags":["memory","recall","search","cross-agent","session-history"]},{"name":"Ditto","pkg":"ditto","ver":"0.0.11","type":"plugins","desc":"Mines your local AI coding session logs into a personal profile your agent loads before every task","long":"Mines local AI coding session logs (OpenCode, Claude Code, Codex, Copilot CLI) into a personal working profile. The OpenCode adapter reads OpenCode's SQLite session store and installs the profile into OpenCode's global rules so the agent loads it before every task. Single stdlib-only Python file, ru","repo":"https://github.com/ohad6k/ditto","tags":[]},{"name":"Dodo Payments","pkg":"dodo-agent-plugin","ver":"0.0.1","type":"plugins","desc":"Payments, subscriptions, webhooks, and billing for OpenCode agents","long":"Official Dodo Payments plugin for OpenCode (also Claude Code, Codex, Cursor). Bundles eight integration skills (checkout, subscriptions, webhooks, usage-based billing, credits, license keys, BillingSDK, best practices) plus two MCP servers — a live API server with browser OAuth and a documentation s","repo":"https://github.com/dodopayments/dodo-agent-plugin","tags":[]},{"name":"Dynamic Context Pruning","pkg":"","ver":"","type":"plugins","desc":"Optimize token usage","long":"Plugin that optimises token usage by pruning obsolete tool outputs from conversation context.","repo":"https://github.com/Tarquinen/opencode-dynamic-context-pruning","tags":[]},{"name":"Envsitter Guard","pkg":"envsitter-guard","ver":"0.1.1","type":"plugins","desc":"Prevent .env leaks","long":"OpenCode plugin that prevents agents/tools from reading or editing sensitive .env* files, while still allowing safe inspection via EnvSitter (keys + deterministic fingerprints; never values).","repo":"https://github.com/boxpositron/envsitter-guard","tags":[]},{"name":"FlowDeck","pkg":"flowdeck","ver":"0.16.1","type":"plugins","desc":"AI-powered multi-agent workflow orchestration with built-in safety intelligence","long":"FlowDeck adds a structured, multi-agent development workflow to OpenCode. It coordinates 25 specialist agents through a four-phase cycle — discuss, plan, execute, review — with persistent state that survives session restarts.\\n\\nKey features:\\n- 25 specialist agents (architect, planner, coder, reviewer","repo":"https://github.com/DVNghiem/FlowDeck","tags":["workflow","orchestration","multi-agent","planning","security","TDD"]},{"name":"ForLoop Planner","pkg":"@forloop-cc/forloop-opencode-plugin-planner","ver":"2.2.25","type":"plugins","desc":"Autopilot development. Plan with ForLoop agent. Build, Test, Deploy on cloud in one go.","long":"ForLoop offers an all-in-one platform that covers the entire development lifecycle, from initial planning and coding to automated testing and cloud deployment.","repo":"https://github.com/forloop-cc/forloop-opencode-plugin-planner","tags":[]},{"name":"Froggy","pkg":"froggy","ver":"1.1.6","type":"plugins","desc":"Hooks and specialized agents","long":"Plugin providing Claude Code-style hooks, specialized agents, and tools like gitingest.","repo":"https://github.com/smartfrog/opencode-froggy","tags":[]},{"name":"Gemini Auth","pkg":"opencode-gemini-auth","ver":"2.0.1","type":"plugins","desc":"Google account auth","long":"Authenticate the Opencode CLI with your Google account so you can use your existing Gemini plan.","repo":"https://github.com/jenslys/opencode-gemini-auth","tags":[]},{"name":"Godmode","pkg":"godmode","ver":"0.0.3","type":"plugins","desc":"Discipline layer: measure → modify → verify → keep/revert","long":"Skill plugin with 135 skills and 7 subagents that adds a measure → modify → verify → keep/revert discipline loop to AI coding agents. Works with OpenCode alongside Claude Code, Codex, Cursor, and Gemini CLI.","repo":"https://github.com/arbazkhan971/godmode","tags":[]},{"name":"Handoff","pkg":"handoff","ver":"1.3.2","type":"plugins","desc":"Session handoff prompts","long":"Creates focused handoff prompts for continuing work in a new session.","repo":"https://github.com/joshuadavidthomas/opencode-handoff","tags":[]},{"name":"Honcho","pkg":"honcho","ver":"0.4.13","type":"plugins","desc":"AI-native long-term memory for OpenCode","long":"Give OpenCode persistent memory that survives context wipes, session restarts, and fresh chats. Honcho remembers what you're working on, durable preferences, and prior context across projects. Supports cloud and self-hosted deployments with configurable session strategies.","repo":"https://github.com/plastic-labs/opencode-honcho","tags":[]},{"name":"Internet Court","pkg":"","ver":"","type":"plugins","desc":"Trust layer for agent-to-agent commerce: mandates, payments, escrow, dispute resolution.","long":"The trust layer for agent-to-agent commerce — natural-language mandates, delegated permissions, x402 payments, escrow, and dispute resolution as one open, catch-all skill. By the Internet Court Consortium (internetcourt.org). MIT licensed.","repo":"https://github.com/internet-court/internet-court-skill","tags":["agent-payments","x402","erc-7710","erc-8004","escrow","genlayer"]},{"name":"kibi-opencode","pkg":"kibi-opencode","ver":"3.2.0","type":"plugins","desc":"Repo-local, branch-scoped knowledge and traceability for OpenCode","long":"Plugin-first entry point into the Kibi stack for OpenCode users. It adds context-aware\\nguidance, routes durable code comments toward Kibi artifacts, runs non-blocking background\\nsync and targeted validation checks, and helps keep repo knowledge branch-local and queryable.","repo":"https://github.com/Looted/kibi","tags":["opencode","plugin","mcp","traceability"]},{"name":"Kilo Gateway Auth","pkg":"opencode-kilo-auth","ver":"1.1.4","type":"plugins","desc":"Kilo Gateway provider","long":"Adds Kilo Gateway provider support to OpenCode.","repo":"https://github.com/JungHoonGhae/opencode-kilo-auth","tags":[]},{"name":"Lemma","pkg":"lemma","ver":"0.10.2","type":"plugins","desc":"Persistent memory layer for LLMs via MCP - local, zero-dependency, works on all clients","long":"Biological memory model for AI coding agents. Features confidence decay/boost, Fuse.js fuzzy dedup, guide system with usage tracking, cross-references, cumulative backup, virtual session tracking, and universal memory injection via tool descriptions (works on Claude Desktop, Cursor, VS Code, Gemini ","repo":"https://github.com/xenitV1/lemma","tags":["memory","mcp","persistence","context","claude","cursor"]},{"name":"opencode-llm-proxy","pkg":"opencode-llm-proxy","ver":"1.11.2","type":"plugins","desc":"OpenCode-backed API gateway with tool calling","long":"Local OpenCode plugin that exposes OpenAI Chat Completions, OpenAI Responses, Anthropic Messages, and Gemini-compatible endpoints for any model configured in OpenCode. Supports streaming, model listing, and real tool/function calling including parallel tool calls, so tools like n8n, Open WebUI, Lang","repo":"https://github.com/KochC/opencode-llm-proxy","tags":["openai","anthropic","gemini","responses-api","llm-gateway","tool-calling"]},{"name":"Magic Context","pkg":"@cortexkit/opencode-magic-context","ver":"0.49.35","type":"plugins","desc":"Lossless context management with background compression","long":"Cache-aware context management that keeps long sessions productive. Background historian compresses old conversation into structured compartments while you keep working. Includes cross-session project memory, unified search across history/memories/facts, overnight dreamer for memory maintenance, and","repo":"https://github.com/cortexkit/opencode-magic-context","tags":[]},{"name":"Mem0 OpenCode Plugin","pkg":"","ver":"","type":"plugins","desc":"Persistent memory plugin so your agent remembers across sessions","long":"OpenCode plugin that gives your agent persistent memory powered by Mem0. Includes 9 MCP memory tools, 16 slash commands, and lifecycle hooks that auto-capture decisions, search prior context at session start, and pre-fetch error memories on failures. Install with \`opencode plugin @mem0/opencode-plug","repo":"https://github.com/mem0ai/mem0","tags":[]},{"name":"Micode","pkg":"micode","ver":"0.11.0","type":"plugins","desc":"Brainstorm-Plan-Implement workflow","long":"Structured workflow with session continuity, subagent orchestration, git worktree isolation, and AST-aware tools.","repo":"https://github.com/vtemian/micode","tags":[]},{"name":"Morph Fast Apply","pkg":"","ver":"","type":"plugins","desc":"10,500+ tokens/sec code editing","long":"Integrates Morph's Fast Apply API for faster code editing with lazy edit markers and unified diff output.","repo":"https://github.com/JRedeker/opencode-morph-fast-apply","tags":[]},{"name":"Neuralyzer","pkg":"neuralyzer","ver":"1.1.5","type":"plugins","desc":"Easier Ralph loop inside harness","long":"Allow agent to wipe its own session context and re-run the first message. This allows easier and more ergonomic Ralph loop engineering.","repo":"https://github.com/gintasz/neuralyzer","tags":[]},{"name":"Oh My Cassette","pkg":"","ver":"","type":"plugins","desc":"Chat your raw clips into a finished cut","long":"Adds a local stdio MCP server that turns a chat message into an edited video: beat-synced cuts, auto-matched background music, subtitles, transitions, and picture-in-picture. Ingest local clips, then drive a persistent multi-turn editing thread — every turn returns a timeline delta and a contact-she","repo":"https://github.com/Cassette-Editor/oh-my-cassette","tags":["mcp","video","video-editing","media"]},{"name":"Oh My Opencode","pkg":"oh-my-opencode","ver":"5.1.60","type":"plugins","desc":"Agents & Pre-built tools","long":"Background agents, pre-built tools (LSP/AST/MCP), curated agents, and a Claude Code compatible layer.","repo":"https://github.com/code-yeongyu/oh-my-opencode","tags":[]},{"name":"Oh My Opencode Slim","pkg":"oh-my-opencode-slim","ver":"3.0.7","type":"plugins","desc":"Lightweight agent orchestration with reduced token usage","long":"Slimmed-down fork of oh-my-opencode focused on core agent orchestration. Features specialized sub-agents (Explorer, Oracle, Librarian, Designer, etc.), background task management, LSP/AST tools, tmux integration for live agent visibility, and MCP servers. Optimized to consume significantly fewer tok","repo":"https://github.com/alvinunreal/oh-my-opencode-slim","tags":["agents","orchestration","background-tasks","lsp","mcp"]},{"name":"open-plan-annotator","pkg":"open-plan-annotator","ver":"1.13.2","type":"plugins","desc":"Annotate LLM plans like a Google Doc!","long":"A fully local agentic coding plugin that intercepts plan mode and opens an annotation UI in your browser. Select text to strikethrough, replace, insert, or comment — then approve the plan or request changes","repo":"https://github.com/ndom91/open-plan-annotator","tags":[]},{"name":"Opencode Agents Sidebar","pkg":"opencode-agents-sidebar","ver":"3.1.0","type":"plugins","desc":"Browse configured OhMyOpenAgent agents in the TUI","long":"OpenCode sidebar plugin that displays configured OhMyOpenAgent agents with lifecycle-based categories, collapsible sections, descriptions, and model information.","repo":"https://github.com/Mark1708/opencode-agents-sidebar","tags":[]},{"name":"Boffin","pkg":"","ver":"","type":"plugins","desc":"Routed architectural guardrails for AI coding agents in OpenCode","long":"Staff-engineer layer for AI coding agents: routes per-edit architectural constraints and requires verification. Not another AGENTS.md.","repo":"https://github.com/MicSm/boffin","tags":["opencode","architecture","guardrails","code-review","cplusplus","python"]},{"name":"OpenCode Claude Code Plugin","pkg":"opencode-claude-code-plugin","ver":"0.1.2","type":"plugins","desc":"Claude models through the official Claude Code CLI, using the login you already have","long":"Runs Anthropic's Claude models in opencode through the official claude CLI as a subprocess, so opencode uses whatever the CLI is logged in as and no token is ever read or replayed. Bash, Edit and Write are proxied back to opencode's own permissions, with several accounts, per-subagent models, and on","repo":"https://github.com/khalilgharbaoui/opencode-claude-code-plugin","tags":[]},{"name":"OpenCode Claude Memory","pkg":"opencode-claude-memory","ver":"2.3.0","type":"plugins","desc":"Claude Code-compatible memory","long":"Share persistent Markdown memory between OpenCode and Claude Code using Claude Code-compatible paths and file formats.","repo":"https://github.com/kuitos/opencode-claude-memory","tags":[]},{"name":"Opencode CMD Provider","pkg":"opencode-cmd-provider","ver":"2.3.4","type":"plugins","desc":"Use CommandCode plans (Go, GOAT, Pro, Max) as an OpenCode provider.","long":"OpenCode provider plugin for the CommandCode Provider API. Authenticate via\\n/connect browser flow or COMMANDCODE_API_KEY, auto-registers all CommandCode\\nmodels ([CMD] prefix) with offline snapshot, pricing display, and per-plan\\ndeals/allowance intelligence in the sidebar and cmd_plan_summary tool.","repo":"https://github.com/rashidrazak/opencode-cmd-provider","tags":["provider","commandcode","model-discovery","auth"]},{"name":"Cursor Auth","pkg":"opencode-cursor","ver":"0.0.1","type":"plugins","desc":"Use your Cursor subscription models in OpenCode via OAuth","long":"Connects Cursor's API to OpenCode via OAuth login, automatic model discovery, and a local OpenAI-compatible proxy — use the models included in your Cursor subscription directly from OpenCode.","repo":"https://github.com/ephraimduncan/opencode-cursor","tags":[]},{"name":"opencode-dir-tree-tui","pkg":"opencode-dir-tree-tui","ver":"1.0.6","type":"plugins","desc":"File tree sidebar with Git status and quick file actions","long":"Browse project files in the OpenCode V2 sidebar with Git status indicators, custom ignore rules, path copying, and quick file and folder opening.","repo":"https://github.com/aihaipeng/opencode-dir-tree-tui","tags":[]},{"name":"OpenCode Ensemble","pkg":"@hueyexe/opencode-ensemble","ver":"0.20.0","type":"plugins","desc":"Parallel agent teams for OpenCode","long":"Coordinate parallel OpenCode agents with peer messaging, a shared task board, git worktree isolation, and a live dashboard.","repo":"https://github.com/hueyexe/opencode-ensemble","tags":["agents","multi-agent","orchestration","worktrees","dashboard"]},{"name":"opencode-failover","pkg":"opencode-failover","ver":"2.0.7","type":"plugins","desc":"Automatic API-key failover and rotation across multiple provider keys","long":"OpenCode plugin that automatically rotates API keys across multiple provider credentials. When a key hits a rate limit, the plugin quarantines it and switches to the next available key with zero downtime. Features weighted round-robin distribution, exponential backoff quarantine, retry-after header ","repo":"https://github.com/bulutmuf/opencode-failover","tags":[]},{"name":"GitHub Release","pkg":"opencode-github-release","ver":"0.6.0","type":"plugins","desc":"Automated GitHub releases","long":"Create and publish GitHub releases with semantic versioning, tag management, and auto-generated release notes.","repo":"https://github.com/amestsantim/opencode-github-release","tags":[]},{"name":"GoopSpec","pkg":"goopspec","ver":"0.2.9","type":"plugins","desc":"Spec-driven development workflow","long":"Transforms AI-assisted coding with spec-driven workflows. Features 5-phase workflow (Plan, Research, Specify, Execute, Accept), contract gates for user confirmation, 12 specialized subagents, persistent memory system, wave-based execution with atomic commits, and deviation rules for handling unexpec","repo":"https://github.com/hffmnnj/opencode-goopspec","tags":[]},{"name":"GPT Imagegen","pkg":"opencode-gpt-imagegen","ver":"1.2.2","type":"plugins","desc":"gpt-image-2 in OpenCode — no API cost when using your ChatGPT subscription","long":"Brings gpt-image-2 (ChatGPT Images 2) image generation to OpenCode. When you sign into OpenCode with your ChatGPT account, generations are billed against your existing Plus / Pro / Business plan — no per-image API cost. An OpenAI API key path is also planned. Supports reference images for style guid","repo":"https://github.com/yuji-hatakeyama/opencode-gpt-imagegen","tags":["openai","gpt-image-2","gpt-image","image-generation","image-editing","text-to-image"]},{"name":"Opencode LiteLLM","pkg":"opencode-litellm","ver":"0.1.0","type":"plugins","desc":"Auto-discover models from a LiteLLM proxy","long":"Drop-in LiteLLM provider for OpenCode with zero configuration. Auto-detects a running\\nLiteLLM proxy on common ports (4000, 8000, 8080), pulls every model from /v1/models,\\nand registers them in OpenCode automatically — no model lists to hand-maintain. Smart\\nname formatting, modality categorization (c","repo":"https://github.com/yuseferi/opencode-litellm","tags":["litellm","provider","openai-compatible","llm-proxy","model-discovery"]},{"name":"Opencode Mem","pkg":"opencode-mem","ver":"2.41.0","type":"plugins","desc":"Persistent memory with vector database","long":"A persistent memory system for AI coding agents that enables long-term context retention across sessions using local vector database technology. Features dual memory scopes, web interface, auto-capture system, and multi-provider AI support.","repo":"https://github.com/tickernelz/opencode-mem","tags":[]},{"name":"OpenCode Mission Control","pkg":"opencode-mission-control","ver":"1.6.0","type":"plugins","desc":"Command center for parallel agents — worktree isolation, DAG plans, merge train, PRs","long":"Orchestrates parallel OpenCode agents in tmux-isolated git worktrees with live inspection (capture/attach/diff), a dashboard overview, agent status reporting, and in-chat notifications. Supports DAG-based plans with autopilot/copilot/supervisor modes and a merge train with test gating and automatic ","repo":"https://github.com/nigel-dev/opencode-mission-control","tags":[]},{"name":"Opencode Models Discovery","pkg":"opencode-models-discovery","ver":"2.1.1","type":"plugins","desc":"Configurable model discovery and filtering without long manual config","long":"OpenCode plugin for discovering models from OpenAI-compatible providers and API gateways, with provider and model filtering so users can avoid maintaining large configs or loading every model at once.","repo":"https://github.com/yuhp/opencode-models-discovery","tags":[]},{"name":"OpenCode ntfy.sh","pkg":"opencode-ntfy.sh","ver":"1.1.0","type":"plugins","desc":"Push notifications to keep you in the know, even when you're on the go.","long":"An OpenCode plugin that adds push notifications through ntfy.sh.","repo":"https://github.com/lannuttia/opencode-ntfy.sh","tags":[]},{"name":"OpenAI Native Compaction","pkg":"opencode-openai-compact","ver":"1.4.0","type":"plugins","desc":"Use OpenAI's official Responses API compaction in OpenCode","long":"Routes OpenCode session compaction for OpenAI providers through \`/responses/compact\`, stores the returned compacted output locally, and injects it into the next Responses request so long sessions use OpenAI's native compaction instead of a prompt-written summary.","repo":"https://github.com/partment/opencode-openai-compact","tags":[]},{"name":"OpenWork for OpenCode","pkg":"","ver":"","type":"plugins","desc":"Official OpenWork plugin to deploy AI models, skills, and MCPs to your team's OpenCode","long":"The official plugin from OpenWork. Centrally deploy AI models, skills, and MCP servers to your whole team. Admins add a model provider, skill, or MCP connection once and choose who gets it; everyone receives it in OpenCode after a single sign-in. No shared API keys, no copying MCP configs between ma","repo":"https://github.com/different-ai/openwork/tree/dev/packages/opencode-plugin","tags":[]},{"name":"opencode-plugin-otel","pkg":"@devtheops/opencode-plugin-otel","ver":"2.0.1","type":"plugins","desc":"OpenTelemetry telemetry exporter for opencode sessions, mirroring Claude Code monitoring signals","long":"Exports metrics, logs, and traces from opencode sessions via OTLP/gRPC to any OpenTelemetry-compatible backend (Datadog, Honeycomb, Grafana Cloud, etc.). Instruments session lifecycle, token usage, cost, tool durations, and git commits — mirroring the same signals as Claude Code's monitoring.","repo":"https://github.com/DEVtheOPS/opencode-plugin-otel","tags":[]},{"name":"OpenCode Pollinations Plugin","pkg":"opencode-pollinations-plugin","ver":"6.5.5","type":"plugins","desc":"Full Pollinations.ai integration — free unlimited AI models, 17+ multimodal tools, hourly free tiers & Safety Net.","long":"The ultimate bridge between OpenCode and the Pollinations.ai ecosystem. Access 30+ free and enterprise AI models (OpenAI, Gemini, Mistral, Claude...) with hourly Pollen resets. Includes 17+ agent tools (image, video, audio, music generation, background removal, web search, transcription, QR codes, d","repo":"https://github.com/fkom13/opencode-pollinations-plugin","tags":[]},{"name":"PR Auto-Signature","pkg":"opencode-pr-signature","ver":"1.2.2","type":"plugins","desc":"Automatically adds AI model signature to PRs, Issues, and Commits","long":"Plugin that automatically appends AI model signature to Pull Requests, Issues, and git commit messages. Supports GitHub MCP tools, git CLI, and gh CLI. Recognizes 90+ AI models including Claude, GPT, Gemini, DeepSeek, Llama, Mistral, and more.","repo":"https://github.com/arttttt/opencode-pr-signature","tags":[]},{"name":"Opencode Quota","pkg":"opencode-quota","ver":"0.0.1-dev","type":"plugins","desc":"Quota toasts and token tracking","long":"Track quota and token usage across providers via automatic toasts and slash commands.","repo":"https://github.com/slkiser/opencode-quota","tags":[]},{"name":"OpenCodeRAG","pkg":"","ver":"","type":"plugins","desc":"Local-first RAG plugin for semantic code search with tree-sitter chunking and LanceDB","long":"A local-first Retrieval-Augmented Generation plugin for OpenCode that enables semantic code\\nsearch across your workspace. Features AST-aware chunking via tree-sitter (19 languages),\\nincremental indexing with file-change detection, watch mode for live re-indexing, and\\nconfigurable embeddings via Olla","repo":"https://github.com/MrDoe/OpenCodeRAG","tags":[]},{"name":"opencode-review","pkg":"","ver":"","type":"plugins","desc":"Automatic structured code review with configurable dimensions and auto-fix","long":"An automatic code review plugin for OpenCode CLI. Reviews staged changes when session goes idle, with configurable cooldown, multi-dimension analysis (code quality, security, performance, testing, documentation), severity levels, and auto-fix chain support.","repo":"https://github.com/sun-praise/opencode-review","tags":[]},{"name":"opencode-short-term-memory","pkg":"","ver":"","type":"plugins","desc":"Maintain user instructions and preferences throughout long sessions. No more repeating yourself.","long":"Automatically summarizes conversation context into structured session memory and injects it back into the system prompt every few turns — preserving user instructions, project context, decisions, and active references across long chats and compactions.","repo":"https://github.com/andrejtonev/opencode-short-term-memory","tags":[]},{"name":"opencode-skills-tui","pkg":"opencode-skills-tui","ver":"1.2.3","type":"plugins","desc":"Browse skills, track loaded state, and preview SKILL.md","long":"OpenCode V2 skills sidebar with loaded-state tracking, SKILL.md previews, token estimates, and skill mentions in prompts.","repo":"https://github.com/aihaipeng/opencode-skills-tui","tags":[]},{"name":"opencode-snip","pkg":"opencode-snip","ver":"3.0.0","type":"plugins","desc":"OpenCode plugin that prefixes shell commands with snip to reduce LLM token consumption by 60-90%","long":"Automatically prefixes supported shell commands (git, go, cargo, npm, docker, etc.) with snip to filter output before it reaches your LLM context window.","repo":"https://github.com/VincentHardouin/opencode-snip","tags":[]},{"name":"Opencode Snippets","pkg":"opencode-snippets","ver":"3.4.0","type":"plugins","desc":"Instant inline text expansion","long":"Instant inline text expansion for OpenCode. Type #snippet anywhere in your message and watch it transform. Brings DRY principles to prompt engineering with composable, shell-enabled snippets.","repo":"https://github.com/JosXa/opencode-snippets","tags":[]},{"name":"OpenCode Swarm","pkg":"opencode-swarm","ver":"7.201.2","type":"plugins","desc":"Verification-gated swarm with architect, review, test, and security agents","long":"Verification-gated OpenCode swarm with architect planning, independent review, test engineering, security checks, and resumable evidence.","repo":"https://github.com/zaxbysauce/opencode-swarm","tags":[]},{"name":"Opencode Synced","pkg":"opencode-synced","ver":"0.11.0","type":"plugins","desc":"Sync configs across machines","long":"Enables syncing global opencode configurations across machines with public/private visibility options.","repo":"https://github.com/iHildy/opencode-synced","tags":[]},{"name":"System Prompt Logger","pkg":"opencode-system-prompt-logger","ver":"1.0.0","type":"plugins","desc":"System prompt logger","long":"OpenCode plugin that intercepts the system prompt before it's sent to the LLM and logs it.","repo":"https://github.com/tlinhart/opencode-system-prompt-logger","tags":[]},{"name":"Opencode Telemetry","pkg":"opencode-telemetry","ver":"0.3.4","type":"plugins","desc":"Passive cross-session telemetry to local SQLite; cost rollups across agent orchestration chains","long":"Logs every opencode session to a local SQLite database automatically — tokens, tool calls, skills, per-turn cost. Aggregates costs across orchestration chains (conductor + child sessions), offers turn-by-turn forensic inspection, and ships an \`octm\` CLI for reports without spending model tokens. Zer","repo":"https://github.com/agostinilabsrl/opencode-telemetry","tags":["telemetry","observability","cost-tracking","sqlite","local-first","orchestration"]},{"name":"Opencode update notifier","pkg":"opencode-update-notifier","ver":"0.4.0","type":"plugins","desc":"Notify about plugin updates.","long":"Checks if your pinned plugins have newer versions available and shows a notification.","repo":"https://github.com/tim-hilde/opencode-update-notifier","tags":[]},{"name":"Opencode Usage Monitor","pkg":"opencode-usage-monitor","ver":"3.1.0","type":"plugins","desc":"Monitor OpenAI and Z.AI usage quotas in the TUI","long":"OpenCode sidebar plugin that displays API usage quotas for OpenAI and Z.AI providers with per-model breakdowns, spend tracking, and configurable refresh controls.","repo":"https://github.com/Mark1708/opencode-usage-monitor","tags":[]},{"name":"Opencode Visualizer","pkg":"","ver":"","type":"plugins","desc":"2D pixel-art office for AI agents","long":"Turning raw OpenCode terminal logs into cozy 2D pixel office chaos. Watch your agents work, idle, and celebrate success in a bustling virtual office.","repo":"https://github.com/psinetron/opencode-visualiser","tags":[]},{"name":"OrgX OpenCode Plugin","pkg":"@useorgx/orgx-opencode-plugin","ver":"0.1.0-alpha.7","type":"plugins","desc":"OrgX Work Graph peer for task dispatch, receipts, deviations, and passive reconciliation","long":"OrgX OpenCode Plugin connects OpenCode sessions to the OrgX execution control plane. It receives OrgX task dispatches, drives the user's local OpenCode session, posts execution receipts and deviations back to OrgX, and writes compact Work Graph events for later audit-first reconciliation.","repo":"https://github.com/useorgx/orgx-opencode-plugin","tags":["agent-orchestration","work-graph","task-dispatch","receipts","mcp"]},{"name":"Plannotator","pkg":"","ver":"","type":"plugins","desc":"Interactive plan review UI","long":"Plan review UI with visual annotation, private/offline sharing, and Obsidian/Bear integration.","repo":"https://github.com/backnotprop/plannotator","tags":[]},{"name":"Rehydra","pkg":"rehydra","ver":"0.16.5","type":"plugins","desc":"Prevent your coding agent from leaking secrets to LLM providers","long":"Intercepts the conversation between OpenCode and the LLM, replacing secrets from .env files and detected API keys, tokens, and credentials with placeholders before they leave your machine. Secrets are transparently restored before any tool executes locally. Supports pattern-based detection for Strip","repo":"https://github.com/rehydra-ai/rehydra-sdk","tags":[]},{"name":"Ring a Bell Example","pkg":"","ver":"","type":"plugins","desc":"Simple terminal bell plugin","long":"A simple plugin to ring the terminal bell once a request is complete.","repo":"https://gist.github.com/ahosker/267f375a65378bcb9a867fd9a195db1e","tags":[]},{"name":"Simple Memory","pkg":"","ver":"","type":"plugins","desc":"Git-based memory","long":"Simple plugin to manage memory inside a git repo that can be committed and reviewed by team members.","repo":"https://github.com/cnicolov/opencode-plugin-simple-memory","tags":[]},{"name":"SonarQube","pkg":"sonarqube","ver":"0.0.1","type":"plugins","desc":"Automatically enforce SonarQube code quality and security in the agent coding loop","long":"SonarQube is the AI code quality and security verification platform used by millions of developers to catch bugs, vulnerabilities, and leaked secrets. This plugin enforces those standards in the agent coding loop: 7,500+ distinct issue types, secrets scanning, agentic analysis, and quality gates acr","repo":"https://github.com/SonarSource/sonarqube-agent-plugins","tags":["sonarqube","code-quality","static-analysis","security","secrets"]},{"name":"Superpowers","pkg":"superpowers","ver":"0.0.2","type":"plugins","desc":"Complete software development methodology with composable skills for coding agents","long":"Gives your coding agent a structured methodology - brainstorm specs, write plans, run TDD, dispatch subagents, and review work. Skills trigger automatically across Claude Code, OpenCode, Codex, Gemini CLI, Cursor, and GitHub Copilot CLI.","repo":"https://github.com/obra/superpowers","tags":[]},{"name":"Systematic","pkg":"systematic","ver":"3.1.1","type":"plugins","desc":"Compound-engineering workflows for OpenCode, Pi, and Claude Code","long":"Provides brainstorm, plan, work, and review workflows as bundled skills and agents for OpenCode, Pi, and Claude Code.","repo":"https://github.com/marcusrbrown/systematic","tags":[]},{"name":"@telemetry-dev/opencode","pkg":"@telemetry-dev/opencode","ver":"0.2.4","type":"plugins","desc":"OpenTelemetry traces for OpenCode agent turns, model calls, and tools","long":"Records OpenCode agent turns, model calls, tool executions, and session events with OpenTelemetry for telemetry.dev. Available as the @telemetry-dev/opencode npm package; source and setup instructions are in packages/opencode of the linked SDK repository.","repo":"https://github.com/telemetry-dev/telemetry","tags":[]},{"name":"toon-config","pkg":"toon-config","ver":"1.0.2","type":"plugins","desc":"Loads TOON based, AGENTS.toon rather than Markdown","long":"Use TOON based agent instructions. Command to analyize and improve agent instructions.","repo":"https://github.com/mmynsted/opencode-toon-config-plugin","tags":[]},{"name":"TypeUI","pkg":"","ver":"","type":"plugins","desc":"Design systems, UI prompts, and layout variation guidance","long":"Open-source design layer for OpenCode that provides curated design skills, UI prompts, and layout variation guidance so agents can generate more consistent interfaces. Includes an <a href=\\"https://www.typeui.sh/docs/guides/opencode\\">OpenCode setup guide</a>.","repo":"https://github.com/bergside/typeui","tags":[]},{"name":"ul-opencode-event","pkg":"@ulthon/ul-opencode-event","ver":"0.1.58","type":"plugins","desc":"Multi-channel notifications (SMTP, DingTalk, Feishu) and token usage analytics","long":"Get notified when OpenCode sessions complete, error, or need your attention - via SMTP email, DingTalk robot, or Feishu robot.\\n- Multi-channel: SMTP, DingTalk, Feishu, local JSONL file\\n- Multi-level token analytics: message / session / total / project scopes\\n- 74+ template variables with i18n suppor","repo":"https://gitee.com/ulthon/ul-opencode-event","tags":["notifications","email","dingtalk","feishu","token-tracking","usage-analytics"]},{"name":"UNMOJI","pkg":"@bastiangx/opencode-unmoji","ver":"1.0.0","type":"plugins","desc":"Strip emojis from output","long":"A simple plugin that strips ALL emojis from agent outputs in Opencode.","repo":"https://codeberg.org/bastiangx/opencode-unmoji","tags":[]},{"name":"WakaTime","pkg":"wakatime","ver":"1.2.0","type":"plugins","desc":"WakaTime integration","long":"WakaTime integration plugin for tracking coding activity in opencode sessions.","repo":"https://github.com/angristan/opencode-wakatime","tags":[]},{"name":"Xquik","pkg":"x-twitter-scraper","ver":"0.20.0","type":"plugins","desc":"X/Twitter data skill & MCP server","long":"X/Twitter data skill — MCP server, REST API, 20 extraction tools. Works with Claude Code, Cursor, Codex, and 40+ agents.","repo":"https://github.com/Xquik-dev/x-twitter-scraper","tags":[]},{"name":"OpenCode Light Themes","pkg":"","ver":"","type":"themes","desc":"A curated collection of 21 light color themes for OpenCode","long":"The largest collection of light themes for OpenCode. Includes 21 carefully crafted light themes converted from popular VS Code themes — scaefy-light (Atom One Light), Solarized Light, Vivid Light, Coffee Cream, GitHub Light, Quiet Light, Bluloco Light, Brackets Light Pro, NetBeans Light, Ysgrifennwr","repo":"https://github.com/fatihtoprakk/opencode-light-themes","tags":[]}]`),Ee=["https://raw.githubusercontent.com/awesome-opencode/awesome-opencode/main/dist/registry.json","https://raw.githubusercontent.com/Gracdjd/awesome-opencode/main/dist/registry.json"],ar=720*60*1e3,qt=ir;function Ht(){const e=process.env.XDG_CACHE_HOME;return e?D.join(e,"opencode","plugin-hub"):D.join($e.homedir(),".cache","opencode","plugin-hub")}function Vt(){return D.join(Ht(),"registry.json")}const sr=/"plugin"\s*:\s*\[\s*"([^"]+)"/,At=/^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;function Tt(e){const o=sr.exec(e.installation??"");if(o?.[1])return o[1];const r=(e.displayName??"").trim();if(At.test(r)&&!r.includes(" "))return r;const i=(e.repoUrl??"").replace(/\/+$/,"").split("/").pop()?.replace(".git","")??"";return At.test(i)?i:""}const Ue=new Map;for(const e of qt)Ue.set(e.repo,e),e.pkg&&Ue.set(`pkg:${e.pkg}`,e);function lr(e){return e.replace(/\/+$/,"").replace(/\.git$/,"")}function Pe(e,o){const r=[],n=new Set;for(const i of e){const s=i.type??"plugins";if(s!=="plugins"&&s!=="mcp"&&s!=="themes")continue;const c=lr(i.repoUrl??"");if(!c||n.has(c))continue;n.add(c);const p=Ue.get(c)??Ue.get(`pkg:${Tt(i)}`),u=p?.pkg??Tt(i);r.push({name:i.displayName??u??c,pkg:u,version:p?.ver??"",type:s,desc:(i.tagline??"").trim(),long:(i.description??"").trim().slice(0,300),repo:c,tags:(i.tags??[]).slice(0,6),installed:u?o.has(u):!1})}return r}async function cr(){let e="";for(const o of Ee)try{const r=await fetch(o,{signal:AbortSignal.timeout(12e3),headers:{accept:"application/json"}});if(!r.ok){e=`${new URL(o).host} 返回 ${r.status}`;continue}const n=await r.json();if(!Array.isArray(n)){e="registry 格式不是数组";continue}return{entries:n,url:o}}catch(r){e=rt(r)}throw new Error(e||"无法连接插件目录")}async function pr(){try{const e=await H.readFile(Vt(),"utf8"),o=JSON.parse(e);return Array.isArray(o.entries)?o:void 0}catch{return}}async function dr(e,o){const r=Date.now();try{await H.mkdir(Ht(),{recursive:!0}),await H.writeFile(Vt(),JSON.stringify({fetchedAt:r,url:o,entries:e}),"utf8")}catch{}return r}async function ur(e,o){const r=await pr(),n=r!==void 0&&Date.now()-r.fetchedAt<ar;if(r&&n&&!o)return{entries:Pe(r.entries,e),source:"cache",registry:r.url??Ee[0],fetchedAt:r.fetchedAt,error:""};try{const i=await cr(),s=await dr(i.entries,i.url);return{entries:Pe(i.entries,e),source:"network",registry:i.url,fetchedAt:s,error:""}}catch(i){return r?{entries:Pe(r.entries,e),source:"cache",registry:r.url??Ee[0],fetchedAt:r.fetchedAt,error:rt(i)}:{entries:Pe(qt.map(gr),e),source:"builtin",registry:Ee[0],fetchedAt:0,error:rt(i)}}}function gr(e){return{displayName:e.name,type:e.type,tagline:e.desc,description:e.long,repoUrl:e.repo,tags:e.tags}}function rt(e){return e instanceof Error?e.message:String(e)}var hr=t.template("<div class=ph-detail-loading><span>正在读取 npm 上的 README 与统计…"),mr=t.template("<span class=ph-badge data-tone=accent>v"),ot=t.template("<span class=ph-badge data-tone=success>已安装"),fr=t.template("<span class=ph-badge data-tone=warning>可更新"),vr=t.template("<span class=ph-badge data-tone=danger>已废弃"),br=t.template("<div class=ph-detail-head><div class=ph-name-row></div><div class=ph-detail-desc>"),yr=t.template("<div class=ph-detail-stats><div class=ph-detail-stat><b></b><span>周下载</span></div><div class=ph-detail-stat><b></b><span>月下载</span></div><div class=ph-detail-stat><b></b><span>已发布版本</span></div><div class=ph-detail-stat><b></b><span>"),wr=t.template("<div class=ph-warning><span>"),kr=t.template("<div class=ph-warning data-tone=danger><span>"),xr=t.template("<span class=ph-chip>许可证 "),Sr=t.template("<span class=ph-chip>发布于 "),$r=t.template("<span class=ph-chip>首次发布 "),Cr=t.template("<span class=ph-chip>维护者 "),_r=t.template("<div class=ph-detail-meta>"),Wt=t.template("<div class=ph-chips>"),Or=t.template("<div class=ph-detail-links>"),Ar=t.template("<details class=ph-details><summary>依赖 <!> 个</summary><div class=ph-chips>"),Tr=t.template("<div class=ph-readme-note>README 较长,这里只显示前一部分。完整内容见 npm 页面。"),Ir=t.template("<section class=ph-section><div class=ph-section-title>README"),Lr=t.template("<div class=ph-readme-empty>"),Mr=t.template("<div class=ph-detail>"),jr=t.template("<span class=ph-detail-error>"),zr=t.template("<div class=ph-dialog-footer><span class=ph-spacer>"),it=t.template("<span class=ph-chip>"),Pr=t.template("<div class=ph-readme-empty>这个包没有附带 README。可以打开 npm 页面或项目主页进一步了解。"),Br=t.template("<span class=ph-count>"),Er=t.template('<span class="ph-count ph-count-alert"> 可更新'),Dr=t.template("<button class=ph-tab>"),Nr=t.template("<span class=ph-version-arrow> → v"),Ur=t.template("<span class=ph-version>"),Gr=t.template("<span class=ph-pkg>"),et=t.template("<span class=ph-badge data-tone=accent>可更新 → v"),me=t.template("<span class=ph-dot>"),Rr=t.template("<span>v"),Fr=t.template("<span>仅 GitHub 发布"),It=t.template("<span>"),qr=t.template("<span class=ph-badge>需手动"),Hr=t.template('<article class=ph-card><div class=ph-tile></div><div class=ph-card-body role=button tabindex=0><div class=ph-name-row><span class=ph-name></span></div><div class="ph-desc ph-clamp"></div><div class=ph-meta><span class=ph-type></span></div></div><div class=ph-actions>'),Vr=t.template("<span>by "),Wr=t.template("<span>更新"),Jr=t.template('<article class=ph-card><div class=ph-tile></div><div class=ph-card-body role=button tabindex=0><div class=ph-name-row><span class=ph-name></span><span class=ph-pkg>v</span></div><div class="ph-desc ph-clamp"></div><div class=ph-meta><span>/周</span><span class=ph-dot></span><span>/月</span></div></div><div class=ph-actions>'),Qr=t.template("<span class=ph-badge>本地目录"),Xr=t.template("<span class=ph-badge data-tone=warning>未能检查"),Yr=t.template("<span>更新会覆盖目录内容,原文件自动备份"),Zr=t.template("<article class=ph-card><div class=ph-tile></div><div class=ph-card-body><div class=ph-name-row><span class=ph-name></span></div><div class=ph-meta><span class=ph-pkg></span></div></div><div class=ph-actions>"),Kr=t.template("<h2 class=ph-title>插件中心"),eo=t.template("<div class=ph-notice><span>"),to=t.template("<div class=ph-notice data-tone=error><span>"),Ae=t.template("<div class=ph-list>"),no=t.template("<section class=ph-panel><div class=ph-filters><div class=ph-filter-row><div class=ph-grow></div><div class=ph-chips>"),ro=t.template("<div class=ph-empty><strong>搜索 npm 上的 OpenCode 插件</strong><span>按名称或用途搜索,例如 auth、notify、memory。点开任意结果可以看 README、下载量和主页。</span><div class=ph-chips>"),oo=t.template("<div class=ph-resultbar><h3 class=ph-h3>搜索结果 <span class=ph-count></span></h3><div class=ph-sort>"),io=t.template("<section class=ph-panel><div class=ph-searchbar><div class=ph-grow></div></div><div class=ph-meta><span>直查 npm 上带 opencode-plugin 标签的包,覆盖精选目录之外的长尾;结果未经人工筛选。"),ao=t.template("<span class=ph-meta>检查过 npm registry"),so=t.template("<section class=ph-panel><div class=ph-resultbar><h3 class=ph-h3>已安装 <span class=ph-count>"),lo=t.template(`<div class=ph-root><style>
56
56
  :root {
57
57
  --ph-strong: var(--color-v2-text-text-contrast, var(--color-text-strong, currentColor));
58
58
  --ph-muted: var(--color-v2-text-text-muted, var(--color-text-weak, color-mix(in srgb, currentColor 58%, transparent)));
@@ -114,7 +114,22 @@ open -b ai.opencode.desktop 2>/dev/null || open -a OpenCode
114
114
  font-size: 12px;
115
115
  line-height: 1.6;
116
116
  color: var(--ph-muted);
117
- max-width: 54ch;
117
+ max-width: 72ch;
118
+ }
119
+ .ph-inline-source {
120
+ padding: 0;
121
+ border: 0;
122
+ background: none;
123
+ color: var(--ph-accent);
124
+ font: inherit;
125
+ line-height: inherit;
126
+ cursor: pointer;
127
+ }
128
+ .ph-inline-source:hover { text-decoration: underline; }
129
+ .ph-inline-source:focus-visible {
130
+ outline: 2px solid color-mix(in srgb, var(--ph-accent) 55%, transparent);
131
+ outline-offset: 2px;
132
+ border-radius: 2px;
118
133
  }
119
134
  .ph-path {
120
135
  overflow: hidden;
@@ -514,6 +529,26 @@ button.ph-chip:focus-visible,
514
529
  max-width: none;
515
530
  padding: 0 0 8px;
516
531
  }
532
+ /* The host caps "x-large" at 980x600. Give the standalone hub more room and
533
+ let the dialog be resized from its bottom-right corner, within the window. */
534
+ [data-component="dialog-v2"][data-size="x-large"]:has(.ph-root[data-embedded="true"]) [data-slot="dialog-container"] {
535
+ width: min(100vw - 48px, 1180px);
536
+ height: min(100vh - 88px, 780px);
537
+ min-width: min(100vw - 48px, 720px);
538
+ min-height: min(100vh - 88px, 480px);
539
+ max-width: calc(100vw - 24px);
540
+ max-height: calc(100vh - 24px);
541
+ resize: both;
542
+ }
543
+ [data-component="dialog-v2"][data-size="x-large"]:has(.ph-detail) [data-slot="dialog-container"] {
544
+ width: min(100vw - 48px, 1180px);
545
+ height: min(100vh - 88px, 780px);
546
+ min-width: min(100vw - 48px, 720px);
547
+ min-height: min(100vh - 88px, 480px);
548
+ max-width: calc(100vw - 24px);
549
+ max-height: calc(100vh - 24px);
550
+ resize: both;
551
+ }
517
552
  .ph-root[data-embedded="true"] .ph-toolbar {
518
553
  position: sticky;
519
554
  top: 0;
@@ -541,4 +576,4 @@ button.ph-chip:focus-visible,
541
576
  .ph-actions { width: 100%; justify-content: flex-start; padding-left: 50px; }
542
577
  .ph-detail-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
543
578
  }
544
- </style><section class=ph-hero><div class=ph-hero-copy><div class=ph-kicker>PLUGIN HUB</div><p class=ph-sub>浏览社区精选、搜索 npm 上的 OpenCode 插件。安装前先看下载量、README 和主页,确认它到底能做什么。</p><div class=ph-path>配置:<code></code> · <code>[]</code></div></div></section><div class=ph-toolbar><div class=ph-tabs role=tablist></div><span class=ph-spacer></span></div><footer class=ph-footer><div class=ph-footer-note>安装和移除只改配置,不会立即生效:插件与模型目录都是启动时快照的。改完点上方工具栏的「彻底重启」,应用会自动退出并重新打开。`),go=t.template("<button class=ph-chip><span class=ph-chip-count>"),Mt=t.template("<div class=ph-skeleton>"),ho=t.template("<div class=ph-empty><strong>没有匹配的插件</strong><span>试试别的关键词,或者清除筛选条件。"),mo=t.template("<button class=ph-chip>"),fo=t.template("<button>"),vo=t.template("<div class=ph-empty><strong>没有找到匹配的插件</strong><span>换个关键词试试,或者清空搜索查看建议。"),bo=t.template("<div class=ph-empty><strong>还没有安装插件</strong><span>去「精选目录」看看社区推荐,或者在「npm 搜索」里找长尾插件。");const Ee=24,yo={network:"社区目录 · 已更新",cache:"社区目录 · 本地缓存",builtin:"社区目录 · 内置快照"},wo={plugins:"插件",mcp:"MCP",themes:"主题"},ko=[{value:"all",label:"全部"},{value:"plugins",label:"插件"},{value:"mcp",label:"MCP"},{value:"themes",label:"主题"}],xo=[{value:"relevance",label:"相关度"},{value:"downloads",label:"周下载"},{value:"recent",label:"最近发布"}],So=["qoder","notify","memory","auth","usage","theme","mcp"],$o=new Intl.NumberFormat("zh-CN",{notation:"compact",maximumFractionDigits:1});function Ge(e){return!Number.isFinite(e)||e<=0?"0":$o.format(e)}function Co(e){return!Number.isFinite(e)||e<=0?"—":e>=1024*1024?`${(e/1024/1024).toFixed(1)} MB`:e>=1024?`${Math.round(e/1024)} KB`:`${e} B`}function it(e){if(!e)return"";const r=Date.parse(e);if(!Number.isFinite(r))return e.slice(0,10);const o=Math.floor((Date.now()-r)/864e5);return o<=0?"今天":o===1?"昨天":o<30?`${o} 天前`:o<365?`${Math.floor(o/30)} 个月前`:`${Math.floor(o/365)} 年前`}function _o(e){return e?new Date(e).toLocaleString("zh-CN",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}):""}function jt(e){const r=e.replace(/^@/,"").split("/").pop()??e;return(r.replace(/^opencode[-_]?/i,"")||r).slice(0,1).toUpperCase()}function Oo(e){return e==="mcp"?"mcp":e==="themes"?"appearance":"extensions"}function Ao(e,r){return!e||!r||!/^https:\/\/github\.com\//i.test(e)?e:`${e}/tree/HEAD/${r.replace(/^\/+/,"")}`}function Io(e){return e==="error"?"circle-exclamation":e==="success"?"circle-check":e==="warning"?"warning":"info"}function se(e){const r=e instanceof Error?e.message:String(e);return/SchemaError|Missing key at/i.test(r)?"插件窗口与后台服务版本不一致。点上方工具栏的「彻底重启」让两边换成同一版本。":/unknown method|not implemented|no such method|method.*not.*found/i.test(r)?"后台服务还是旧版本,暂时没有「彻底重启」能力。请先手动完全退出 OpenCode(并结束 serve --service)再打开一次。":r}const To=/^(@[a-z0-9-_.]+\/)?[a-z0-9-_.]+$/i;function Lo(e){const r=e.ctx.ui,[o,n]=l.createSignal(),[a,s]=l.createSignal(""),[c,p]=l.createSignal(!0),d=async()=>{try{const M=await e.admin.detail({name:e.name});n(M),M.error&&s(M.error)}catch(M){s(se(M))}finally{p(!1)}};l.onMount(()=>{d()});const v=M=>{M&&e.admin.openUrl({url:M})},h=()=>o()?.version||e.fallback?.version||"",k=()=>o()?.description||e.fallback?.description||"",$=()=>o()?.homepage||e.fallback?.homepage||"",O=()=>o()?.repo||e.fallback?.repo||"",A=()=>o()?.npm||e.fallback?.npm||`https://www.npmjs.com/package/${e.name}`,U=()=>!c()&&o()!==void 0&&!a()&&(o()?.version??"")!=="",L=()=>o()?.hasUpdate===!0,F=()=>o()?.installed===!0;return t.createComponent(r.Dialog,{get title(){return e.name},size:"x-large",get footer(){return(()=>{var M=Br(),g=M.firstChild;return t.insert(M,t.createComponent(l.Show,{get when(){return $()},get children(){return t.createComponent(r.Button,{size:"small",variant:"ghost",icon:"globe",onClick:()=>v($()),children:"主页"})}}),g),t.insert(M,t.createComponent(l.Show,{get when(){return O()},get children(){return t.createComponent(r.Button,{size:"small",variant:"ghost",icon:"github",onClick:()=>v(Ao(O(),o()?.repoDir??"")),children:"源码"})}}),g),t.insert(M,t.createComponent(l.Show,{get when(){return A()},get children(){return t.createComponent(r.Button,{size:"small",variant:"ghost",icon:"cube",onClick:()=>v(A()),children:"npm"})}}),g),t.insert(M,t.createComponent(l.Show,{get when(){return a()},get children(){var b=zr();return t.insert(b,a),t.effect(()=>t.setAttribute(b,"title",a())),b}}),null),t.insert(M,t.createComponent(r.Button,{size:"small",get onClick(){return e.onClose},children:"关闭"}),null),t.insert(M,t.createComponent(l.Show,{get when(){return U()},get children(){return t.createComponent(l.Show,{get when(){return L()||!F()},get fallback(){return t.createComponent(r.Button,{size:"small",disabled:!0,children:"已安装"})},get children(){return t.createComponent(r.Button,{variant:"contrast",size:"small",icon:"download",get busy(){return e.busy},onClick:()=>e.onInstall(o().version),get children(){return t.memo(()=>!!L())()?`更新到 v${h()}`:`安装 v${h()}`}})}})}}),null),M})()},get children(){var M=Pr();return t.insert(M,t.createComponent(l.Show,{get when(){return c()},get children(){var g=fr(),b=g.firstChild;return t.insert(g,t.createComponent(r.Loader,{size:"large"}),b),g}}),null),t.insert(M,t.createComponent(l.Show,{get when(){return t.memo(()=>!c())()&&o()},get children(){return[(()=>{var g=wr(),b=g.firstChild,S=b.nextSibling;return t.insert(b,t.createComponent(l.Show,{get when(){return h()},get children(){var y=vr();return y.firstChild,t.insert(y,h,null),y}}),null),t.insert(b,t.createComponent(l.Show,{get when(){return F()},get children(){var y=rt();return y.firstChild,t.insert(y,(()=>{var f=t.memo(()=>!!o().current);return()=>f()?` v${o().current}`:""})(),null),y}}),null),t.insert(b,t.createComponent(l.Show,{get when(){return L()},get children(){return br()}}),null),t.insert(b,t.createComponent(l.Show,{get when(){return o().deprecated},get children(){return yr()}}),null),t.insert(S,()=>k()||"这个包没有提供描述。"),g})(),t.createComponent(l.Show,{get when(){return o().version},get children(){var g=kr(),b=g.firstChild,S=b.firstChild,y=b.nextSibling,f=y.firstChild,H=y.nextSibling,re=H.firstChild,pe=H.nextSibling,ve=pe.firstChild,Oe=ve.nextSibling;return t.insert(S,()=>Ge(o().weekly)),t.insert(f,()=>Ge(o().monthly)),t.insert(re,()=>o().versions||"—"),t.insert(ve,(()=>{var W=t.memo(()=>!!o().unpacked);return()=>W()?Co(o().unpacked):"—"})()),t.insert(Oe,(()=>{var W=t.memo(()=>!!o().files);return()=>W()?`解包体积 · ${o().files} 文件`:"解包体积"})()),t.effect(W=>{var _=`${o().weekly.toLocaleString()} 次`,B=`${o().monthly.toLocaleString()} 次`;return _!==W.e&&t.setAttribute(S,"title",W.e=_),B!==W.t&&t.setAttribute(f,"title",W.t=B),W},{e:void 0,t:void 0}),g}}),t.createComponent(l.Show,{get when(){return o().deprecated},get children(){var g=xr(),b=g.firstChild;return t.insert(g,t.createComponent(r.Icon,{name:"warning",size:"small"}),b),t.insert(b,()=>o().deprecated),g}}),t.createComponent(l.Show,{get when(){return a()},get children(){var g=Sr(),b=g.firstChild;return t.insert(g,t.createComponent(r.Icon,{name:"circle-exclamation",size:"small"}),b),t.insert(b,a),g}}),(()=>{var g=Ar();return t.insert(g,t.createComponent(l.Show,{get when(){return o().license},get children(){var b=$r();return b.firstChild,t.insert(b,()=>o().license,null),b}}),null),t.insert(g,t.createComponent(l.Show,{get when(){return o().published},get children(){var b=Cr();return b.firstChild,t.insert(b,()=>it(o().published),null),b}}),null),t.insert(g,t.createComponent(l.Show,{get when(){return o().created},get children(){var b=_r();return b.firstChild,t.insert(b,()=>it(o().created),null),b}}),null),t.insert(g,t.createComponent(l.Show,{get when(){return o().maintainers.length>0},get children(){var b=Or();return b.firstChild,t.insert(b,()=>o().maintainers.join(", "),null),b}}),null),g})(),t.createComponent(l.Show,{get when(){return o().keywords.length>0},get children(){var g=Jt();return t.insert(g,t.createComponent(l.For,{get each(){return o().keywords},children:b=>(()=>{var S=ot();return t.insert(S,b),S})()})),g}}),t.createComponent(l.Show,{get when(){return o().bugs},get children(){var g=Ir();return t.insert(g,t.createComponent(r.Button,{size:"small",variant:"ghost-muted",icon:"bubble-5",onClick:()=>v(o().bugs),children:"问题反馈"})),g}}),t.createComponent(l.Show,{get when(){return o().dependencies.length>0},get children(){var g=Tr(),b=g.firstChild,S=b.firstChild,y=S.nextSibling;y.nextSibling;var f=b.nextSibling;return t.insert(b,()=>o().dependencies.length,y),t.insert(f,t.createComponent(l.For,{get each(){return o().dependencies},children:H=>(()=>{var re=ot();return t.insert(re,H),re})()})),g}}),(()=>{var g=Mr();return g.firstChild,t.insert(g,t.createComponent(l.Show,{get when(){return o().readme},get fallback(){return Er()},get children(){return[t.createComponent(r.Markdown,{get text(){return o().readme},class:"ph-readme"}),t.createComponent(l.Show,{get when(){return o().readmeTruncated},get children(){return Lr()}})]}}),null),g})()]}}),null),t.insert(M,t.createComponent(l.Show,{get when(){return t.memo(()=>!c())()&&!o()},get children(){var g=jr();return t.insert(g,()=>a()||"未能读取这个包的详情。"),g}}),null),M}})}function Qt(e){const r=e.ctx.ui,[o,n]=l.createSignal(),[a,s]=l.createSignal("catalog"),[c,p]=l.createSignal([]),[d,v]=l.createSignal(),[h,k]=l.createSignal(""),[$,O]=l.createSignal("all"),[A,U]=l.createSignal(Ee),[L,F]=l.createSignal([]),[M,g]=l.createSignal(""),[b,S]=l.createSignal(!1),[y,f]=l.createSignal("relevance"),[H,re]=l.createSignal(new Map),[pe,ve]=l.createSignal(!1),[Oe,W]=l.createSignal(new Set),[_,B]=l.createSignal(),R=i=>Oe().has(i),be=i=>W(m=>{const x=new Set(m);return x.add(i),x}),Xt=i=>W(m=>{const x=new Set(m);return x.delete(i),x}),de=async(i,m)=>{be(i);try{return await m()}finally{Xt(i)}},G=(i,m="info")=>B(i?{text:i,tone:m}:void 0),pt=async()=>{try{n(await e.admin.snapshot())}catch(i){G(se(i),"error")}},ze=async(i=!1)=>{await de("updates",async()=>{try{const m=await e.admin.updates(),x=new Map;for(const P of m.items)x.set(P.spec,P);if(re(x),ve(!0),i)return;const I=m.items.filter(P=>P.hasUpdate).length,C=m.items.filter(P=>P.error).length,j=m.error?m.error:C>0?`${C} 个插件未能检查(registry 连接失败),其余${I>0?`有 ${I} 个可更新`:"已是最新"}`:I>0?`发现 ${I} 个可更新的插件`:"全部已是最新版本";G(j,m.error?"error":C>0?"warning":I>0?"info":"success")}catch(m){i||G(se(m),"error")}})},dt=async i=>{await de("catalog",async()=>{try{const m=await e.admin.browse({force:i});p([...m.entries]),v(m),m.error&&G(m.error,"error"),await pt()}catch(m){G(se(m),"error")}})},qe=async i=>{g(i),await de("search",async()=>{try{const m=await e.admin.search({query:i});F(m.hits),S(!0),m.error&&G(m.error,"error")}catch(m){G(se(m),"error")}})},ye=async(i,m="")=>{await de(`install:${i}`,async()=>{try{const x=await e.admin.install({spec:i,version:m});if(n(x.state),!x.ok){G(x.message,"error");return}G(`${x.message}。点上方工具栏的「彻底重启」后生效`,"success");const I=m||L().find(C=>C.name===i)?.version||"";p(C=>C.map(j=>j.pkg===i?{...j,installed:!0}:j)),F(C=>C.map(j=>j.name===i?{...j,installed:!0,current:I,hasUpdate:!1}:j)),await ze(!0)}catch(x){G(se(x),"error")}})},Yt=async i=>{await e.ctx.dialogs.confirm({title:`移除 ${i.name}?`,description:"只从 opencode.json 里删掉这一项,已安装的文件不会删除。",confirm:"移除",danger:!0})&&await de(`install:${i.name}`,async()=>{try{const x=await e.admin.remove({spec:i.spec,version:""});if(n(x.state),!x.ok){G(x.message,"error");return}G(`${x.message}。点上方工具栏的「彻底重启」后生效`,"success"),p(I=>I.map(C=>C.pkg===i.name?{...C,installed:!1}:C)),F(I=>I.map(C=>C.name===i.name?{...C,installed:!1,current:"",hasUpdate:!1}:C)),re(I=>{const C=new Map(I);return C.delete(i.spec),C})}catch(x){G(se(x),"error")}})},Zt=async()=>{await de("reload",async()=>{try{const i=await e.admin.reload();n(i.state),G(i.message,i.ok?"success":"error")}catch(i){G(se(i),"error")}})},Kt=async()=>{await e.ctx.dialogs.confirm({title:"彻底重启 OpenCode?",description:"应用会退出并自动重新打开,让插件代码和后台 serve --service 一起换成最新版本。当前会话通常会自动恢复。",confirm:"重启"})&&await de("restart",async()=>{try{if(!(await e.admin.restart()).ok){G("无法自动重启,请手动完全退出 OpenCode 再打开。","error");return}G("正在重启 OpenCode…如果没有自动打开,请手动启动。","info")}catch{G("自动重启调用失败,后台服务可能还是旧版本。请先手动完全退出 OpenCode(并结束 serve --service)再打开一次。","error")}})},Ae=async i=>{if(i)try{(await e.admin.openUrl({url:i})).ok||G(`无法打开链接:${i}`,"error")}catch(m){G(se(m),"error")}},en=async()=>{await de("config",async()=>{try{(await e.admin.openConfig()).ok||G("无法打开配置文件","error")}catch(i){G(se(i),"error")}})},He=(i,m)=>{e.ctx.dialogs.open(x=>t.createComponent(Lo,{get ctx(){return e.ctx},get admin(){return e.admin},name:i,fallback:m,get busy(){return R(`install:${i}`)},onInstall:I=>{x.close(),ye(i,I)},onClose:()=>x.close()}))},Ie=l.createMemo(()=>{const i=h().trim().toLowerCase(),m=$();return c().filter(x=>m!=="all"&&x.type!==m?!1:i?[x.name,x.pkg,x.desc,x.long,...x.tags].join(" ").toLowerCase().includes(i):!0)}),tn=l.createMemo(()=>{const i=c();return{all:i.length,plugins:i.filter(m=>m.type==="plugins").length,mcp:i.filter(m=>m.type==="mcp").length,themes:i.filter(m=>m.type==="themes").length}}),Ve=l.createMemo(()=>{const i=[...L()];return y()==="downloads"?i.sort((m,x)=>x.weekly-m.weekly):y()==="recent"&&i.sort((m,x)=>(x.published||"").localeCompare(m.published||"")),i}),nn=l.createMemo(()=>Ie().filter(i=>i.pkg).length),We=l.createMemo(()=>[...H().values()].filter(i=>i.hasUpdate).length),rn=i=>H().get(i),on=l.createMemo(()=>{const i=new Map;for(const m of H().values())i.set(m.name,m);return i}),an=i=>{s(i),i!=="catalog"&&!pe()&&!R("updates")&&ze(!0)};l.onMount(()=>{pt(),dt(!1),ze(!0)});const Je=(i,m,x,I,C)=>(()=>{var j=Ur();return j.$$click=()=>an(i),t.insert(j,t.createComponent(r.Icon,{name:x,size:"small"}),null),t.insert(j,m,null),t.insert(j,t.createComponent(l.Show,{when:I!==void 0&&I!=="",get children(){var P=Dr();return t.insert(P,I),P}}),null),t.insert(j,t.createComponent(l.Show,{when:C!==void 0&&C>0,get children(){var P=Nr(),J=P.firstChild;return t.setAttribute(P,"title",`${C} 个可更新`),t.insert(P,C,J),P}}),null),t.effect(()=>t.setAttribute(j,"data-active",a()===i)),j})(),sn=(i,m,x)=>(()=>{var I=Rr();return t.insert(I,i?`v${i}`:"",null),t.insert(I,t.createComponent(l.Show,{when:x&&m,get children(){var C=Gr();return C.firstChild,t.insert(C,m,null),C}}),null),I})(),ln=i=>{const m=()=>on().get(i.pkg),x=()=>m()?.hasUpdate===!0,I=()=>He(i.pkg,{version:i.version,description:i.desc||i.long,repo:i.repo});return(()=>{var C=Wr(),j=C.firstChild,P=j.nextSibling,J=P.firstChild,Y=J.firstChild,ee=J.nextSibling,Q=ee.nextSibling,ce=Q.firstChild,Z=P.nextSibling;return t.insert(j,t.createComponent(r.Icon,{get name(){return Oo(i.type)},size:"normal"})),P.$$keydown=T=>{(T.key==="Enter"||T.key===" ")&&(T.preventDefault(),i.pkg?I():Ae(i.repo))},P.$$click=()=>i.pkg?I():void Ae(i.repo),t.insert(Y,()=>i.name),t.insert(J,t.createComponent(l.Show,{get when(){return i.pkg},get children(){var T=Fr();return t.insert(T,()=>i.pkg),T}}),null),t.insert(J,t.createComponent(l.Show,{get when(){return i.installed},get children(){return rt()}}),null),t.insert(J,t.createComponent(l.Show,{get when(){return x()},get children(){var T=Ke();return T.firstChild,t.insert(T,()=>m().latest,null),T}}),null),t.insert(ee,()=>i.desc||i.long||"—"),t.insert(ce,()=>wo[i.type]??i.type),t.insert(Q,t.createComponent(l.Show,{get when(){return i.version},get children(){return[ue(),(()=>{var T=qr();return T.firstChild,t.insert(T,()=>i.version,null),T})()]}}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return!i.pkg},get children(){return[ue(),Hr()]}}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return i.tags.length>0},get children(){return[ue(),(()=>{var T=Lt();return t.insert(T,()=>i.tags.slice(0,4).join(" / ")),T})()]}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return i.pkg},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"eye",onClick:I,children:"详情"})}}),null),t.insert(Z,t.createComponent(r.Button,{variant:"ghost-muted",size:"small",icon:"square-arrow-top-right",onClick:()=>{Ae(i.repo)},children:"源码"}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return x()},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"download",get busy(){return R(`install:${i.pkg}`)},onClick:()=>{ye(i.pkg,m().latest)},children:"更新"})}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return t.memo(()=>!x()&&!i.installed)()&&i.pkg},get children(){return t.createComponent(r.Button,{variant:"neutral",size:"small",icon:"download",get busy(){return R(`install:${i.pkg}`)},onClick:()=>{ye(i.pkg)},children:"安装"})}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return t.memo(()=>!x()&&!i.installed)()&&!i.pkg},get children(){return Vr()}}),null),C})()},cn=i=>{const m=()=>He(i.name,{version:i.version,description:i.description,homepage:i.homepage,repo:i.repo,npm:i.npm});return(()=>{var x=Xr(),I=x.firstChild,C=I.nextSibling,j=C.firstChild,P=j.firstChild,J=P.nextSibling;J.firstChild;var Y=j.nextSibling,ee=Y.nextSibling,Q=ee.firstChild,ce=Q.firstChild,Z=Q.nextSibling,T=Z.nextSibling,we=T.firstChild,ge=C.nextSibling;return t.insert(I,()=>jt(i.name)),C.$$keydown=w=>{(w.key==="Enter"||w.key===" ")&&(w.preventDefault(),m())},C.$$click=m,t.insert(P,()=>i.name),t.insert(J,()=>i.version,null),t.insert(j,t.createComponent(l.Show,{get when(){return i.installed},get children(){var w=rt();return w.firstChild,t.insert(w,(()=>{var z=t.memo(()=>!!i.current);return()=>z()?` v${i.current}`:""})(),null),w}}),null),t.insert(j,t.createComponent(l.Show,{get when(){return i.hasUpdate},get children(){var w=Ke();return w.firstChild,t.insert(w,()=>i.version,null),w}}),null),t.insert(Y,()=>i.description||"—"),t.insert(Q,t.createComponent(r.Icon,{name:"download",size:"small"}),ce),t.insert(Q,()=>Ge(i.weekly),ce),t.insert(T,()=>Ge(i.monthly),we),t.insert(ee,t.createComponent(l.Show,{get when(){return i.publisher},get children(){return[ue(),(()=>{var w=Jr();return w.firstChild,t.insert(w,()=>i.publisher,null),w})()]}}),null),t.insert(ee,t.createComponent(l.Show,{get when(){return i.license},get children(){return[ue(),(()=>{var w=Lt();return t.insert(w,()=>i.license),w})()]}}),null),t.insert(ee,t.createComponent(l.Show,{get when(){return i.published},get children(){return[ue(),(()=>{var w=Qr(),z=w.firstChild;return t.insert(w,()=>it(i.published),z),w})()]}}),null),t.insert(C,t.createComponent(l.Show,{get when(){return i.keywords.length>0},get children(){var w=Jt();return t.insert(w,t.createComponent(l.For,{get each(){return i.keywords},children:z=>(()=>{var X=ot();return t.insert(X,z),X})()})),w}}),null),t.insert(ge,t.createComponent(l.Show,{get when(){return i.homepage},get children(){return t.createComponent(r.Button,{variant:"ghost-muted",size:"small",icon:"globe",onClick:()=>{Ae(i.homepage)},children:"主页"})}}),null),t.insert(ge,t.createComponent(l.Show,{get when(){return i.repo},get children(){return t.createComponent(r.Tooltip,{value:"源码仓库",get children(){return t.createComponent(r.IconButton,{icon:"github",size:"small",label:"源码仓库",onClick:()=>{Ae(i.repo)}})}})}}),null),t.insert(ge,t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"eye",onClick:m,children:"详情"}),null),t.insert(ge,t.createComponent(l.Show,{get when(){return i.hasUpdate},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"download",get busy(){return R(`install:${i.name}`)},onClick:()=>{ye(i.name,i.version)},children:"更新"})}}),null),t.insert(ge,t.createComponent(l.Show,{get when(){return t.memo(()=>!i.hasUpdate)()&&!i.installed},get children(){return t.createComponent(r.Button,{variant:"neutral",size:"small",icon:"download",get busy(){return R(`install:${i.name}`)},onClick:()=>{ye(i.name)},children:"安装"})}}),null),t.effect(w=>{var z=`周下载 ${i.weekly.toLocaleString()} 次`,X=`月下载 ${i.monthly.toLocaleString()} 次`;return z!==w.e&&t.setAttribute(Q,"title",w.e=z),X!==w.t&&t.setAttribute(T,"title",w.t=X),w},{e:void 0,t:void 0}),x})()},pn=i=>{const m=()=>rn(i.spec),x=()=>m()?.hasUpdate===!0,I=()=>m()?.current||i.version,C=()=>(m()?.error??"")!=="";return(()=>{var j=eo(),P=j.firstChild,J=P.nextSibling,Y=J.firstChild,ee=Y.firstChild,Q=Y.nextSibling,ce=Q.firstChild,Z=J.nextSibling;return t.insert(P,()=>jt(i.name)),t.insert(ee,()=>i.name),t.insert(Y,()=>sn(I(),m()?.latest??"",x()),null),t.insert(Y,t.createComponent(l.Show,{get when(){return i.kind==="path"},get children(){return Yr()}}),null),t.insert(Y,t.createComponent(l.Show,{get when(){return x()},get children(){var T=Ke();return T.firstChild,t.insert(T,()=>m().latest,null),T}}),null),t.insert(Y,t.createComponent(l.Show,{get when(){return C()},get children(){return Zr()}}),null),t.insert(ce,()=>i.spec),t.insert(Q,t.createComponent(l.Show,{get when(){return t.memo(()=>!!x())()&&i.kind==="path"},get children(){return[ue(),Kr()]}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return To.test(i.name)},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"eye",onClick:()=>He(i.name,{version:I()}),children:"详情"})}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return x()},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"download",get busy(){return R(`install:${i.name}`)},onClick:()=>{ye(m().name,m().latest)},children:"更新"})}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return i.removable},get children(){return t.createComponent(r.Button,{variant:"ghost-muted",size:"small",icon:"trash",get busy(){return R(`install:${i.name}`)},onClick:()=>{Yt(i)},children:"移除"})}}),null),j})()};return(()=>{var i=uo(),m=i.firstChild,x=m.nextSibling,I=x.firstChild,C=I.firstChild,j=C.firstChild,P=C.nextSibling,J=P.nextSibling,Y=J.firstChild,ee=Y.nextSibling,Q=ee.nextSibling,ce=Q.nextSibling,Z=ce.firstChild,T=x.nextSibling,we=T.firstChild;we.nextSibling;var ge=T.nextSibling;return t.insert(C,t.createComponent(r.Icon,{name:"extensions",size:"small"}),j),t.insert(I,t.createComponent(l.Show,{get when(){return!e.embedded},get children(){return to()}}),P),t.insert(ee,()=>o()?.configPath??"读取中…"),t.insert(ce,()=>o()?.configKey??"plugins",Z),t.insert(i,t.createComponent(l.Show,{get when(){return _()},get children(){var w=no(),z=w.firstChild;return t.insert(w,t.createComponent(r.Icon,{get name(){return Io(_().tone)},size:"small"}),z),t.insert(z,()=>_().text),t.effect(()=>t.setAttribute(w,"data-tone",_().tone)),w}}),T),t.insert(i,t.createComponent(l.Show,{get when(){return o()?.error},get children(){var w=ro(),z=w.firstChild;return t.insert(w,t.createComponent(r.Icon,{name:"circle-exclamation",size:"small"}),z),t.insert(z,()=>o().error),w}}),T),t.insert(we,()=>Je("catalog","精选目录","extensions",d()?c().length:void 0),null),t.insert(we,()=>Je("search","npm 搜索","magnifying-glass",b()?L().length:void 0),null),t.insert(we,()=>Je("installed","已安装","checklist",o()?.plugins.length??0,We()),null),t.insert(T,t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"open-file",get busy(){return R("config")},onClick:()=>{en()},children:"打开配置"}),null),t.insert(T,t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"refresh",get busy(){return R("reload")},onClick:()=>{Zt()},children:"重载配置"}),null),t.insert(T,t.createComponent(r.Button,{variant:"neutral",size:"small",icon:"outline-rotate-clockwise",get busy(){return R("restart")},onClick:()=>{Kt()},children:"彻底重启"}),null),t.insert(T,t.createComponent(l.Show,{get when(){return a()==="catalog"},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"refresh",get busy(){return R("catalog")},onClick:()=>{dt(!0)},children:"更新目录"})}}),null),t.insert(T,t.createComponent(l.Show,{get when(){return a()==="search"},get children(){return t.createComponent(l.Show,{get when(){return b()},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"close",onClick:()=>{F([]),S(!1),g(""),B(void 0)},children:"清空"})}})}}),null),t.insert(T,t.createComponent(l.Show,{get when(){return a()==="installed"},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",icon:"refresh",get busy(){return R("updates")},onClick:()=>{ze()},children:"检查更新"})}}),null),t.insert(i,t.createComponent(l.Switch,{get children(){return[t.createComponent(l.Match,{get when(){return a()==="catalog"},get children(){var w=io(),z=w.firstChild,X=z.firstChild,D=X.firstChild,K=D.nextSibling,V=X.nextSibling,te=V.firstChild,ke=te.nextSibling,oe=ke.nextSibling,he=oe.firstChild,ut=he.nextSibling;return ut.nextSibling,t.insert(D,t.createComponent(r.TextInput,{label:"筛选社区精选插件",placeholder:"按名称、描述或标签筛选,例如 memory / auth / token",type:"search",get value(){return h()},onInput:N=>{k(N),U(Ee)}})),t.insert(K,t.createComponent(l.For,{each:ko,children:N=>(()=>{var ae=go(),gt=ae.firstChild;return ae.$$click=()=>{O(N.value),U(Ee)},t.insert(ae,()=>N.label,gt),t.insert(gt,()=>tn()[N.value]),t.effect(()=>t.setAttribute(ae,"data-active",$()===N.value)),ae})()})),t.insert(te,()=>yo[d()?.source??""]??"加载中…"),t.insert(oe,()=>c().length,he),t.insert(oe,nn,ut),t.insert(V,t.createComponent(l.Show,{get when(){return d()?.fetchedAt},get children(){return[ue(),(()=>{var N=oo();return N.firstChild,t.insert(N,()=>_o(d().fetchedAt),null),N})()]}}),null),t.insert(w,t.createComponent(l.Show,{get when(){return t.memo(()=>!!R("catalog"))()&&c().length===0},get children(){var N=Le();return t.insert(N,t.createComponent(l.For,{each:[0,1,2,3],children:()=>Mt()})),N}}),null),t.insert(w,t.createComponent(l.Show,{get when(){return!R("catalog")||c().length>0},get children(){return t.createComponent(l.Show,{get when(){return Ie().length>0},get fallback(){return(()=>{var N=ho(),ae=N.firstChild;return ae.nextSibling,t.insert(N,t.createComponent(r.Icon,{name:"magnifying-glass",size:"large"}),ae),t.insert(N,t.createComponent(r.Button,{variant:"ghost",size:"small",onClick:()=>{k(""),O("all")},children:"清除筛选"}),null),N})()},get children(){return[(()=>{var N=Le();return t.insert(N,t.createComponent(l.For,{get each(){return Ie().slice(0,A())},children:ae=>ln(ae)})),N})(),t.createComponent(l.Show,{get when(){return Ie().length>A()},get children(){return t.createComponent(r.Button,{variant:"ghost",size:"small",onClick:()=>U(N=>N+Ee),get children(){return["显示更多(还有 ",t.memo(()=>Ie().length-A())," 个)"]}})}})]}})}}),null),w}}),t.createComponent(l.Match,{get when(){return a()==="search"},get children(){var w=lo(),z=w.firstChild,X=z.firstChild;return z.nextSibling,t.insert(X,t.createComponent(r.TextInput,{label:"搜索 npm 上的 OpenCode 插件",placeholder:"搜索插件,例如 qoder / notify / usage",type:"search",get value(){return M()},onInput:g,onSubmit:D=>{qe(D)}})),t.insert(z,t.createComponent(r.Button,{variant:"contrast",icon:"magnifying-glass",get busy(){return R("search")},onClick:()=>{qe(M())},children:"搜索"}),null),t.insert(w,t.createComponent(l.Show,{get when(){return!b()},get children(){var D=ao(),K=D.firstChild,V=K.nextSibling,te=V.nextSibling;return t.insert(D,t.createComponent(r.Icon,{name:"magnifying-glass",size:"large"}),K),t.insert(te,t.createComponent(l.For,{each:So,children:ke=>(()=>{var oe=mo();return oe.$$click=()=>{qe(ke)},t.insert(oe,ke),oe})()})),D}}),null),t.insert(w,t.createComponent(l.Show,{get when(){return b()},get children(){return[(()=>{var D=so(),K=D.firstChild,V=K.firstChild,te=V.nextSibling,ke=K.nextSibling;return t.insert(te,()=>Ve().length),t.insert(ke,t.createComponent(l.For,{each:xo,children:oe=>(()=>{var he=fo();return he.$$click=()=>f(oe.value),t.insert(he,()=>oe.label),t.effect(()=>t.setAttribute(he,"data-active",y()===oe.value)),he})()})),D})(),t.createComponent(l.Show,{get when(){return t.memo(()=>!!R("search"))()&&L().length===0},get children(){var D=Le();return t.insert(D,t.createComponent(l.For,{each:[0,1,2,3],children:()=>Mt()})),D}}),t.createComponent(l.Show,{get when(){return!R("search")||L().length>0},get children(){return t.createComponent(l.Show,{get when(){return Ve().length>0},get fallback(){return(()=>{var D=vo(),K=D.firstChild;return t.insert(D,t.createComponent(r.Icon,{name:"magnifying-glass",size:"large"}),K),D})()},get children(){var D=Le();return t.insert(D,t.createComponent(l.For,{get each(){return Ve()},children:K=>cn(K)})),D}})}})]}}),null),w}}),t.createComponent(l.Match,{get when(){return a()==="installed"},get children(){var w=po(),z=w.firstChild,X=z.firstChild,D=X.firstChild,K=D.nextSibling;return t.insert(K,()=>o()?.plugins.length??0),t.insert(z,t.createComponent(l.Show,{get when(){return pe()},get children(){var V=co();return V.firstChild,t.insert(V,(()=>{var te=t.memo(()=>We()>0);return()=>te()?` · ${We()} 个可更新`:" · 全部已是最新"})(),null),V}}),null),t.insert(w,t.createComponent(l.Show,{get when(){return(o()?.plugins.length??0)>0},get fallback(){return(()=>{var V=bo(),te=V.firstChild;return t.insert(V,t.createComponent(r.Icon,{name:"extensions",size:"large"}),te),V})()},get children(){var V=Le();return t.insert(V,t.createComponent(l.For,{get each(){return o()?.plugins??[]},children:te=>pn(te)})),V}}),null),w}})]}}),ge),t.effect(w=>{var z=e.embedded?"true":void 0,X=o()?.configPath;return z!==w.e&&t.setAttribute(i,"data-embedded",w.e=z),X!==w.t&&t.setAttribute(J,"title",w.t=X),w},{e:void 0,t:void 0}),i})()}function Mo(e){const r=e.ctx.ui;return t.createComponent(r.Dialog,{title:"插件中心",size:"x-large",get footer(){return t.createComponent(r.Button,{get onClick(){return e.onClose},children:"关闭"})},get children(){return t.createComponent(Qt,{get ctx(){return e.ctx},get admin(){return e.admin},embedded:!0})}})}t.delegateEvents(["click","keydown"]);const jo=Me.Plugin.define({setup(e){const r=e.main(mt),o=()=>{e.dialogs.active()||e.dialogs.open(n=>t.createComponent(Mo,{ctx:e,admin:r,onClose:()=>n.close()}))};e.add(Me.SettingsPage,{id:"hub",title:"插件中心",icon:"extensions",entries:[{id:"hub",title:"插件中心",description:"浏览社区精选插件,按 npm 搜索,安装或移除",keywords:"plugin npm install remove hub catalog marketplace"}],render:()=>t.createComponent(Qt,{ctx:e,admin:r})}),e.add(Me.TitlebarItem,()=>({id:"hub",label:"插件中心",title:"打开插件中心",icon:"extensions",run:o})),e.add(Me.Command,()=>({id:"open",title:"插件中心",description:"浏览、搜索、安装与更新 OpenCode 插件",keywords:"plugin hub install browse npm catalog marketplace",group:"设置",section:"navigation",bind:"mod+alt+p",featured:!0,run:o}))},main(e){e.provide(mt,{snapshot:()=>le(),updates:()=>Nn(),search:r=>tr(r.query),browse:async r=>{const o=await le();return hr(new Set(o.plugins.map(n=>n.name)),r.force)},detail:r=>er(r.name),openUrl:r=>({ok:nr(r.url)}),openConfig:async()=>({ok:await rr()}),install:r=>qn(r.spec,r.version),remove:r=>Hn(r.spec),reload:()=>ir(e.cli.command),restart:()=>({ok:or()})})}});exports.default=jo;
579
+ </style><section class=ph-hero><div class=ph-hero-copy><div class=ph-kicker>PLUGIN HUB</div><p class=ph-sub>浏览 <button type=button class=ph-inline-source title="精选目录来自 awesome-opencode 社区维护的 registry.json,不是 OpenCode 官方推荐">awesome-opencode</button> 社区维护的精选目录、搜索 npm 上的 OpenCode 插件。安装前先看下载量、README 和主页,确认它到底能做什么。</p><div class=ph-path>配置:<code></code> · <code>[]</code></div></div></section><div class=ph-toolbar><div class=ph-tabs role=tablist></div><span class=ph-spacer></span></div><footer class=ph-footer><div class=ph-footer-note>安装和移除只改配置,不会立即生效:插件与模型目录都是启动时快照的。改完点上方工具栏的「彻底重启」,应用会自动退出并重新打开。`),co=t.template("<button class=ph-chip><span class=ph-chip-count>"),Lt=t.template("<div class=ph-skeleton>"),po=t.template("<div class=ph-empty><strong>没有匹配的插件</strong><span>试试别的关键词,或者清除筛选条件。"),uo=t.template("<button class=ph-chip>"),go=t.template("<button>"),ho=t.template("<div class=ph-empty><strong>没有找到匹配的插件</strong><span>换个关键词试试,或者清空搜索查看建议。"),mo=t.template("<div class=ph-empty><strong>还没有安装插件</strong><span>去「精选目录」看看 awesome-opencode 的推荐,或者在「npm 搜索」里找长尾插件。"),fo=t.template("<div class=ph-dialog-footer><span class=ph-footer-note>拖动右下角可调整大小");const Be=24,vo="https://github.com/awesome-opencode/awesome-opencode",bo={plugins:"插件",mcp:"MCP",themes:"主题"},yo=[{value:"all",label:"全部"},{value:"plugins",label:"插件"},{value:"mcp",label:"MCP"},{value:"themes",label:"主题"}],wo=[{value:"relevance",label:"相关度"},{value:"downloads",label:"周下载"},{value:"recent",label:"最近发布"}],ko=["qoder","notify","memory","auth","usage","theme","mcp"],xo=new Intl.NumberFormat("zh-CN",{notation:"compact",maximumFractionDigits:1});function Ge(e){return!Number.isFinite(e)||e<=0?"0":xo.format(e)}function So(e){return!Number.isFinite(e)||e<=0?"—":e>=1024*1024?`${(e/1024/1024).toFixed(1)} MB`:e>=1024?`${Math.round(e/1024)} KB`:`${e} B`}function at(e){if(!e)return"";const o=Date.parse(e);if(!Number.isFinite(o))return e.slice(0,10);const r=Math.floor((Date.now()-o)/864e5);return r<=0?"今天":r===1?"昨天":r<30?`${r} 天前`:r<365?`${Math.floor(r/30)} 个月前`:`${Math.floor(r/365)} 年前`}function Mt(e){const o=e.replace(/^@/,"").split("/").pop()??e;return(o.replace(/^opencode[-_]?/i,"")||o).slice(0,1).toUpperCase()}function $o(e){return e==="mcp"?"mcp":e==="themes"?"appearance":"extensions"}function Co(e,o){return!e||!o||!/^https:\/\/github\.com\//i.test(e)?e:`${e}/tree/HEAD/${o.replace(/^\/+/,"")}`}function _o(e){return e==="error"?"circle-exclamation":e==="success"?"circle-check":e==="warning"?"warning":"info"}function ae(e){const o=e instanceof Error?e.message:String(e);return/SchemaError|Missing key at/i.test(o)?"插件窗口与后台服务版本不一致。点上方工具栏的「彻底重启」让两边换成同一版本。":/unknown method|not implemented|no such method|method.*not.*found/i.test(o)?"后台服务还是旧版本,暂时没有「彻底重启」能力。请先手动完全退出 OpenCode(并结束 serve --service)再打开一次。":o}const Oo=/^(@[a-z0-9-_.]+\/)?[a-z0-9-_.]+$/i;function Ao(e){const o=e.ctx.ui,[r,n]=l.createSignal(),[i,s]=l.createSignal(""),[c,p]=l.createSignal(!0),u=async()=>{try{const j=await e.admin.detail({name:e.name});n(j),j.error&&s(j.error)}catch(j){s(ae(j))}finally{p(!1)}};l.onMount(()=>{u()});const v=j=>{j&&e.admin.openUrl({url:j})},h=()=>r()?.version||e.fallback?.version||"",w=()=>r()?.description||e.fallback?.description||"",S=()=>r()?.homepage||e.fallback?.homepage||"",_=()=>r()?.repo||e.fallback?.repo||"",O=()=>r()?.npm||e.fallback?.npm||`https://www.npmjs.com/package/${e.name}`,U=()=>!c()&&r()!==void 0&&!i()&&(r()?.version??"")!=="",M=()=>r()?.hasUpdate===!0,F=()=>r()?.installed===!0;return t.createComponent(o.Dialog,{get title(){return e.name},size:"x-large",get footer(){return(()=>{var j=zr(),g=j.firstChild;return t.insert(j,t.createComponent(l.Show,{get when(){return S()},get children(){return t.createComponent(o.Button,{size:"small",variant:"ghost",icon:"globe",onClick:()=>v(S()),children:"主页"})}}),g),t.insert(j,t.createComponent(l.Show,{get when(){return _()},get children(){return t.createComponent(o.Button,{size:"small",variant:"ghost",icon:"github",onClick:()=>v(Co(_(),r()?.repoDir??"")),children:"源码"})}}),g),t.insert(j,t.createComponent(l.Show,{get when(){return O()},get children(){return t.createComponent(o.Button,{size:"small",variant:"ghost",icon:"cube",onClick:()=>v(O()),children:"npm"})}}),g),t.insert(j,t.createComponent(l.Show,{get when(){return i()},get children(){var b=jr();return t.insert(b,i),t.effect(()=>t.setAttribute(b,"title",i())),b}}),null),t.insert(j,t.createComponent(o.Button,{size:"small",get onClick(){return e.onClose},children:"关闭"}),null),t.insert(j,t.createComponent(l.Show,{get when(){return U()},get children(){return t.createComponent(l.Show,{get when(){return M()||!F()},get fallback(){return t.createComponent(o.Button,{size:"small",disabled:!0,children:"已安装"})},get children(){return t.createComponent(o.Button,{variant:"contrast",size:"small",icon:"download",get busy(){return e.busy},onClick:()=>e.onInstall(r().version),get children(){return t.memo(()=>!!M())()?`更新到 v${h()}`:`安装 v${h()}`}})}})}}),null),j})()},get children(){var j=Mr();return t.insert(j,t.createComponent(l.Show,{get when(){return c()},get children(){var g=hr(),b=g.firstChild;return t.insert(g,t.createComponent(o.Loader,{size:"large"}),b),g}}),null),t.insert(j,t.createComponent(l.Show,{get when(){return t.memo(()=>!c())()&&r()},get children(){return[(()=>{var g=br(),b=g.firstChild,x=b.nextSibling;return t.insert(b,t.createComponent(l.Show,{get when(){return h()},get children(){var y=mr();return y.firstChild,t.insert(y,h,null),y}}),null),t.insert(b,t.createComponent(l.Show,{get when(){return F()},get children(){var y=ot();return y.firstChild,t.insert(y,(()=>{var f=t.memo(()=>!!r().current);return()=>f()?` v${r().current}`:""})(),null),y}}),null),t.insert(b,t.createComponent(l.Show,{get when(){return M()},get children(){return fr()}}),null),t.insert(b,t.createComponent(l.Show,{get when(){return r().deprecated},get children(){return vr()}}),null),t.insert(x,()=>w()||"这个包没有提供描述。"),g})(),t.createComponent(l.Show,{get when(){return r().version},get children(){var g=yr(),b=g.firstChild,x=b.firstChild,y=b.nextSibling,f=y.firstChild,V=y.nextSibling,re=V.firstChild,ce=V.nextSibling,ve=ce.firstChild,_e=ve.nextSibling;return t.insert(x,()=>Ge(r().weekly)),t.insert(f,()=>Ge(r().monthly)),t.insert(re,()=>r().versions||"—"),t.insert(ve,(()=>{var J=t.memo(()=>!!r().unpacked);return()=>J()?So(r().unpacked):"—"})()),t.insert(_e,(()=>{var J=t.memo(()=>!!r().files);return()=>J()?`解包体积 · ${r().files} 文件`:"解包体积"})()),t.effect(J=>{var C=`${r().weekly.toLocaleString()} 次`,E=`${r().monthly.toLocaleString()} 次`;return C!==J.e&&t.setAttribute(x,"title",J.e=C),E!==J.t&&t.setAttribute(f,"title",J.t=E),J},{e:void 0,t:void 0}),g}}),t.createComponent(l.Show,{get when(){return r().deprecated},get children(){var g=wr(),b=g.firstChild;return t.insert(g,t.createComponent(o.Icon,{name:"warning",size:"small"}),b),t.insert(b,()=>r().deprecated),g}}),t.createComponent(l.Show,{get when(){return i()},get children(){var g=kr(),b=g.firstChild;return t.insert(g,t.createComponent(o.Icon,{name:"circle-exclamation",size:"small"}),b),t.insert(b,i),g}}),(()=>{var g=_r();return t.insert(g,t.createComponent(l.Show,{get when(){return r().license},get children(){var b=xr();return b.firstChild,t.insert(b,()=>r().license,null),b}}),null),t.insert(g,t.createComponent(l.Show,{get when(){return r().published},get children(){var b=Sr();return b.firstChild,t.insert(b,()=>at(r().published),null),b}}),null),t.insert(g,t.createComponent(l.Show,{get when(){return r().created},get children(){var b=$r();return b.firstChild,t.insert(b,()=>at(r().created),null),b}}),null),t.insert(g,t.createComponent(l.Show,{get when(){return r().maintainers.length>0},get children(){var b=Cr();return b.firstChild,t.insert(b,()=>r().maintainers.join(", "),null),b}}),null),g})(),t.createComponent(l.Show,{get when(){return r().keywords.length>0},get children(){var g=Wt();return t.insert(g,t.createComponent(l.For,{get each(){return r().keywords},children:b=>(()=>{var x=it();return t.insert(x,b),x})()})),g}}),t.createComponent(l.Show,{get when(){return r().bugs},get children(){var g=Or();return t.insert(g,t.createComponent(o.Button,{size:"small",variant:"ghost-muted",icon:"bubble-5",onClick:()=>v(r().bugs),children:"问题反馈"})),g}}),t.createComponent(l.Show,{get when(){return r().dependencies.length>0},get children(){var g=Ar(),b=g.firstChild,x=b.firstChild,y=x.nextSibling;y.nextSibling;var f=b.nextSibling;return t.insert(b,()=>r().dependencies.length,y),t.insert(f,t.createComponent(l.For,{get each(){return r().dependencies},children:V=>(()=>{var re=it();return t.insert(re,V),re})()})),g}}),(()=>{var g=Ir();return g.firstChild,t.insert(g,t.createComponent(l.Show,{get when(){return r().readme},get fallback(){return Pr()},get children(){return[t.createComponent(o.Markdown,{get text(){return r().readme},class:"ph-readme"}),t.createComponent(l.Show,{get when(){return r().readmeTruncated},get children(){return Tr()}})]}}),null),g})()]}}),null),t.insert(j,t.createComponent(l.Show,{get when(){return t.memo(()=>!c())()&&!r()},get children(){var g=Lr();return t.insert(g,()=>i()||"未能读取这个包的详情。"),g}}),null),j}})}function Jt(e){const o=e.ctx.ui,[r,n]=l.createSignal(),[i,s]=l.createSignal("catalog"),[c,p]=l.createSignal([]),[u,v]=l.createSignal(),[h,w]=l.createSignal(""),[S,_]=l.createSignal("all"),[O,U]=l.createSignal(Be),[M,F]=l.createSignal([]),[j,g]=l.createSignal(""),[b,x]=l.createSignal(!1),[y,f]=l.createSignal("relevance"),[V,re]=l.createSignal(new Map),[ce,ve]=l.createSignal(!1),[_e,J]=l.createSignal(new Set),[C,E]=l.createSignal(),R=a=>_e().has(a),be=a=>J(m=>{const k=new Set(m);return k.add(a),k}),Qt=a=>J(m=>{const k=new Set(m);return k.delete(a),k}),pe=async(a,m)=>{be(a);try{return await m()}finally{Qt(a)}},G=(a,m="info")=>E(a?{text:a,tone:m}:void 0),dt=async()=>{try{n(await e.admin.snapshot())}catch(a){G(ae(a),"error")}},Me=async(a=!1)=>{await pe("updates",async()=>{try{const m=await e.admin.updates(),k=new Map;for(const P of m.items)k.set(P.spec,P);if(re(k),ve(!0),a)return;const A=m.items.filter(P=>P.hasUpdate).length,$=m.items.filter(P=>P.error).length,z=m.error?m.error:$>0?`${$} 个插件未能检查(registry 连接失败),其余${A>0?`有 ${A} 个可更新`:"已是最新"}`:A>0?`发现 ${A} 个可更新的插件`:"全部已是最新版本";G(z,m.error?"error":$>0?"warning":A>0?"info":"success")}catch(m){a||G(ae(m),"error")}})},ut=async a=>{await pe("catalog",async()=>{try{const m=await e.admin.browse({force:a});p([...m.entries]),v(m),m.error&&G(m.error,"error"),await dt()}catch(m){G(ae(m),"error")}})},qe=async a=>{g(a),await pe("search",async()=>{try{const m=await e.admin.search({query:a});F(m.hits),x(!0),m.error&&G(m.error,"error")}catch(m){G(ae(m),"error")}})},ye=async(a,m="")=>{await pe(`install:${a}`,async()=>{try{const k=await e.admin.install({spec:a,version:m});if(n(k.state),!k.ok){G(k.message,"error");return}G(`${k.message}。点上方工具栏的「彻底重启」后生效`,"success");const A=m||M().find($=>$.name===a)?.version||"";p($=>$.map(z=>z.pkg===a?{...z,installed:!0}:z)),F($=>$.map(z=>z.name===a?{...z,installed:!0,current:A,hasUpdate:!1}:z)),await Me(!0)}catch(k){G(ae(k),"error")}})},Xt=async a=>{await e.ctx.dialogs.confirm({title:`移除 ${a.name}?`,description:"只从 opencode.json 里删掉这一项,已安装的文件不会删除。",confirm:"移除",danger:!0})&&await pe(`install:${a.name}`,async()=>{try{const k=await e.admin.remove({spec:a.spec,version:""});if(n(k.state),!k.ok){G(k.message,"error");return}G(`${k.message}。点上方工具栏的「彻底重启」后生效`,"success"),p(A=>A.map($=>$.pkg===a.name?{...$,installed:!1}:$)),F(A=>A.map($=>$.name===a.name?{...$,installed:!1,current:"",hasUpdate:!1}:$)),re(A=>{const $=new Map(A);return $.delete(a.spec),$})}catch(k){G(ae(k),"error")}})},Yt=async()=>{await pe("reload",async()=>{try{const a=await e.admin.reload();n(a.state),G(a.message,a.ok?"success":"error")}catch(a){G(ae(a),"error")}})},Zt=async()=>{await e.ctx.dialogs.confirm({title:"彻底重启 OpenCode?",description:"应用会退出并自动重新打开,让插件代码和后台 serve --service 一起换成最新版本。当前会话通常会自动恢复。",confirm:"重启"})&&await pe("restart",async()=>{try{if(!(await e.admin.restart()).ok){G("无法自动重启,请手动完全退出 OpenCode 再打开。","error");return}G("正在重启 OpenCode…如果没有自动打开,请手动启动。","info")}catch{G("自动重启调用失败,后台服务可能还是旧版本。请先手动完全退出 OpenCode(并结束 serve --service)再打开一次。","error")}})},we=async a=>{if(a)try{(await e.admin.openUrl({url:a})).ok||G(`无法打开链接:${a}`,"error")}catch(m){G(ae(m),"error")}},Kt=async()=>{await pe("config",async()=>{try{(await e.admin.openConfig()).ok||G("无法打开配置文件","error")}catch(a){G(ae(a),"error")}})},He=(a,m)=>{e.ctx.dialogs.open(k=>t.createComponent(Ao,{get ctx(){return e.ctx},get admin(){return e.admin},name:a,fallback:m,get busy(){return R(`install:${a}`)},onInstall:A=>{k.close(),ye(a,A)},onClose:()=>k.close()}))},je=l.createMemo(()=>{const a=h().trim().toLowerCase(),m=S();return c().filter(k=>m!=="all"&&k.type!==m?!1:a?[k.name,k.pkg,k.desc,k.long,...k.tags].join(" ").toLowerCase().includes(a):!0)}),en=l.createMemo(()=>{const a=c();return{all:a.length,plugins:a.filter(m=>m.type==="plugins").length,mcp:a.filter(m=>m.type==="mcp").length,themes:a.filter(m=>m.type==="themes").length}}),Ve=l.createMemo(()=>{const a=[...M()];return y()==="downloads"?a.sort((m,k)=>k.weekly-m.weekly):y()==="recent"&&a.sort((m,k)=>(k.published||"").localeCompare(m.published||"")),a}),tn=()=>u()?.registry||vo,We=l.createMemo(()=>[...V().values()].filter(a=>a.hasUpdate).length),nn=a=>V().get(a),rn=l.createMemo(()=>{const a=new Map;for(const m of V().values())a.set(m.name,m);return a}),on=a=>{s(a),a!=="catalog"&&!ce()&&!R("updates")&&Me(!0)};l.onMount(()=>{dt(),ut(!1),Me(!0)});const Je=(a,m,k,A,$)=>(()=>{var z=Dr();return z.$$click=()=>on(a),t.insert(z,t.createComponent(o.Icon,{name:k,size:"small"}),null),t.insert(z,m,null),t.insert(z,t.createComponent(l.Show,{when:A!==void 0&&A!=="",get children(){var P=Br();return t.insert(P,A),P}}),null),t.insert(z,t.createComponent(l.Show,{when:$!==void 0&&$>0,get children(){var P=Er(),X=P.firstChild;return t.setAttribute(P,"title",`${$} 个可更新`),t.insert(P,$,X),P}}),null),t.effect(()=>t.setAttribute(z,"data-active",i()===a)),z})(),an=(a,m,k)=>(()=>{var A=Ur();return t.insert(A,a?`v${a}`:"",null),t.insert(A,t.createComponent(l.Show,{when:k&&m,get children(){var $=Nr();return $.firstChild,t.insert($,m,null),$}}),null),A})(),sn=a=>{const m=()=>rn().get(a.pkg),k=()=>m()?.hasUpdate===!0,A=()=>He(a.pkg,{version:a.version,description:a.desc||a.long,repo:a.repo});return(()=>{var $=Hr(),z=$.firstChild,P=z.nextSibling,X=P.firstChild,Z=X.firstChild,te=X.nextSibling,Y=te.nextSibling,le=Y.firstChild,K=P.nextSibling;return t.insert(z,t.createComponent(o.Icon,{get name(){return $o(a.type)},size:"normal"})),P.$$keydown=B=>{(B.key==="Enter"||B.key===" ")&&(B.preventDefault(),a.pkg?A():we(a.repo))},P.$$click=()=>a.pkg?A():void we(a.repo),t.insert(Z,()=>a.name),t.insert(X,t.createComponent(l.Show,{get when(){return a.pkg},get children(){var B=Gr();return t.insert(B,()=>a.pkg),B}}),null),t.insert(X,t.createComponent(l.Show,{get when(){return a.installed},get children(){return ot()}}),null),t.insert(X,t.createComponent(l.Show,{get when(){return k()},get children(){var B=et();return B.firstChild,t.insert(B,()=>m().latest,null),B}}),null),t.insert(te,()=>a.desc||a.long||"—"),t.insert(le,()=>bo[a.type]??a.type),t.insert(Y,t.createComponent(l.Show,{get when(){return a.version},get children(){return[me(),(()=>{var B=Rr();return B.firstChild,t.insert(B,()=>a.version,null),B})()]}}),null),t.insert(Y,t.createComponent(l.Show,{get when(){return!a.pkg},get children(){return[me(),Fr()]}}),null),t.insert(Y,t.createComponent(l.Show,{get when(){return a.tags.length>0},get children(){return[me(),(()=>{var B=It();return t.insert(B,()=>a.tags.slice(0,4).join(" / ")),B})()]}}),null),t.insert(K,t.createComponent(l.Show,{get when(){return a.pkg},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"eye",onClick:A,children:"详情"})}}),null),t.insert(K,t.createComponent(o.Button,{variant:"ghost-muted",size:"small",icon:"square-arrow-top-right",onClick:()=>{we(a.repo)},children:"源码"}),null),t.insert(K,t.createComponent(l.Show,{get when(){return k()},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"download",get busy(){return R(`install:${a.pkg}`)},onClick:()=>{ye(a.pkg,m().latest)},children:"更新"})}}),null),t.insert(K,t.createComponent(l.Show,{get when(){return t.memo(()=>!k()&&!a.installed)()&&a.pkg},get children(){return t.createComponent(o.Button,{variant:"neutral",size:"small",icon:"download",get busy(){return R(`install:${a.pkg}`)},onClick:()=>{ye(a.pkg)},children:"安装"})}}),null),t.insert(K,t.createComponent(l.Show,{get when(){return t.memo(()=>!k()&&!a.installed)()&&!a.pkg},get children(){return qr()}}),null),$})()},ln=a=>{const m=()=>He(a.name,{version:a.version,description:a.description,homepage:a.homepage,repo:a.repo,npm:a.npm});return(()=>{var k=Jr(),A=k.firstChild,$=A.nextSibling,z=$.firstChild,P=z.firstChild,X=P.nextSibling;X.firstChild;var Z=z.nextSibling,te=Z.nextSibling,Y=te.firstChild,le=Y.firstChild,K=Y.nextSibling,B=K.nextSibling,Qe=B.firstChild,Q=$.nextSibling;return t.insert(A,()=>Mt(a.name)),$.$$keydown=T=>{(T.key==="Enter"||T.key===" ")&&(T.preventDefault(),m())},$.$$click=m,t.insert(P,()=>a.name),t.insert(X,()=>a.version,null),t.insert(z,t.createComponent(l.Show,{get when(){return a.installed},get children(){var T=ot();return T.firstChild,t.insert(T,(()=>{var ie=t.memo(()=>!!a.current);return()=>ie()?` v${a.current}`:""})(),null),T}}),null),t.insert(z,t.createComponent(l.Show,{get when(){return a.hasUpdate},get children(){var T=et();return T.firstChild,t.insert(T,()=>a.version,null),T}}),null),t.insert(Z,()=>a.description||"—"),t.insert(Y,t.createComponent(o.Icon,{name:"download",size:"small"}),le),t.insert(Y,()=>Ge(a.weekly),le),t.insert(B,()=>Ge(a.monthly),Qe),t.insert(te,t.createComponent(l.Show,{get when(){return a.publisher},get children(){return[me(),(()=>{var T=Vr();return T.firstChild,t.insert(T,()=>a.publisher,null),T})()]}}),null),t.insert(te,t.createComponent(l.Show,{get when(){return a.license},get children(){return[me(),(()=>{var T=It();return t.insert(T,()=>a.license),T})()]}}),null),t.insert(te,t.createComponent(l.Show,{get when(){return a.published},get children(){return[me(),(()=>{var T=Wr(),ie=T.firstChild;return t.insert(T,()=>at(a.published),ie),T})()]}}),null),t.insert($,t.createComponent(l.Show,{get when(){return a.keywords.length>0},get children(){var T=Wt();return t.insert(T,t.createComponent(l.For,{get each(){return a.keywords},children:ie=>(()=>{var I=it();return t.insert(I,ie),I})()})),T}}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return a.homepage},get children(){return t.createComponent(o.Button,{variant:"ghost-muted",size:"small",icon:"globe",onClick:()=>{we(a.homepage)},children:"主页"})}}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return a.repo},get children(){return t.createComponent(o.Tooltip,{value:"源码仓库",get children(){return t.createComponent(o.IconButton,{icon:"github",size:"small",label:"源码仓库",onClick:()=>{we(a.repo)}})}})}}),null),t.insert(Q,t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"eye",onClick:m,children:"详情"}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return a.hasUpdate},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"download",get busy(){return R(`install:${a.name}`)},onClick:()=>{ye(a.name,a.version)},children:"更新"})}}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return t.memo(()=>!a.hasUpdate)()&&!a.installed},get children(){return t.createComponent(o.Button,{variant:"neutral",size:"small",icon:"download",get busy(){return R(`install:${a.name}`)},onClick:()=>{ye(a.name)},children:"安装"})}}),null),t.effect(T=>{var ie=`周下载 ${a.weekly.toLocaleString()} 次`,I=`月下载 ${a.monthly.toLocaleString()} 次`;return ie!==T.e&&t.setAttribute(Y,"title",T.e=ie),I!==T.t&&t.setAttribute(B,"title",T.t=I),T},{e:void 0,t:void 0}),k})()},cn=a=>{const m=()=>nn(a.spec),k=()=>m()?.hasUpdate===!0,A=()=>m()?.current||a.version,$=()=>(m()?.error??"")!=="";return(()=>{var z=Zr(),P=z.firstChild,X=P.nextSibling,Z=X.firstChild,te=Z.firstChild,Y=Z.nextSibling,le=Y.firstChild,K=X.nextSibling;return t.insert(P,()=>Mt(a.name)),t.insert(te,()=>a.name),t.insert(Z,()=>an(A(),m()?.latest??"",k()),null),t.insert(Z,t.createComponent(l.Show,{get when(){return a.kind==="path"},get children(){return Qr()}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return k()},get children(){var B=et();return B.firstChild,t.insert(B,()=>m().latest,null),B}}),null),t.insert(Z,t.createComponent(l.Show,{get when(){return $()},get children(){return Xr()}}),null),t.insert(le,()=>a.spec),t.insert(Y,t.createComponent(l.Show,{get when(){return t.memo(()=>!!k())()&&a.kind==="path"},get children(){return[me(),Yr()]}}),null),t.insert(K,t.createComponent(l.Show,{get when(){return Oo.test(a.name)},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"eye",onClick:()=>He(a.name,{version:A()}),children:"详情"})}}),null),t.insert(K,t.createComponent(l.Show,{get when(){return k()},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"download",get busy(){return R(`install:${a.name}`)},onClick:()=>{ye(m().name,m().latest)},children:"更新"})}}),null),t.insert(K,t.createComponent(l.Show,{get when(){return a.removable},get children(){return t.createComponent(o.Button,{variant:"ghost-muted",size:"small",icon:"trash",get busy(){return R(`install:${a.name}`)},onClick:()=>{Xt(a)},children:"移除"})}}),null),z})()};return(()=>{var a=lo(),m=a.firstChild,k=m.nextSibling,A=k.firstChild,$=A.firstChild,z=$.firstChild,P=$.nextSibling,X=P.firstChild,Z=X.nextSibling,te=P.nextSibling,Y=te.firstChild,le=Y.nextSibling,K=le.nextSibling,B=K.nextSibling,Qe=B.firstChild,Q=k.nextSibling,T=Q.firstChild;T.nextSibling;var ie=Q.nextSibling;return t.insert($,t.createComponent(o.Icon,{name:"extensions",size:"small"}),z),t.insert(A,t.createComponent(l.Show,{get when(){return!e.embedded},get children(){return Kr()}}),P),Z.$$click=()=>{we(tn())},t.insert(le,()=>r()?.configPath??"读取中…"),t.insert(B,()=>r()?.configKey??"plugins",Qe),t.insert(a,t.createComponent(l.Show,{get when(){return C()},get children(){var I=eo(),W=I.firstChild;return t.insert(I,t.createComponent(o.Icon,{get name(){return _o(C().tone)},size:"small"}),W),t.insert(W,()=>C().text),t.effect(()=>t.setAttribute(I,"data-tone",C().tone)),I}}),Q),t.insert(a,t.createComponent(l.Show,{get when(){return r()?.error},get children(){var I=to(),W=I.firstChild;return t.insert(I,t.createComponent(o.Icon,{name:"circle-exclamation",size:"small"}),W),t.insert(W,()=>r().error),I}}),Q),t.insert(T,()=>Je("catalog","精选目录","extensions",u()?c().length:void 0),null),t.insert(T,()=>Je("search","npm 搜索","magnifying-glass",b()?M().length:void 0),null),t.insert(T,()=>Je("installed","已安装","checklist",r()?.plugins.length??0,We()),null),t.insert(Q,t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"open-file",get busy(){return R("config")},onClick:()=>{Kt()},children:"打开配置"}),null),t.insert(Q,t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"refresh",get busy(){return R("reload")},onClick:()=>{Yt()},children:"重载配置"}),null),t.insert(Q,t.createComponent(o.Button,{variant:"neutral",size:"small",icon:"outline-rotate-clockwise",get busy(){return R("restart")},onClick:()=>{Zt()},children:"彻底重启"}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return i()==="catalog"},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"refresh",get busy(){return R("catalog")},onClick:()=>{ut(!0)},children:"更新目录"})}}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return i()==="search"},get children(){return t.createComponent(l.Show,{get when(){return b()},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"close",onClick:()=>{F([]),x(!1),g(""),E(void 0)},children:"清空"})}})}}),null),t.insert(Q,t.createComponent(l.Show,{get when(){return i()==="installed"},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",icon:"refresh",get busy(){return R("updates")},onClick:()=>{Me()},children:"检查更新"})}}),null),t.insert(a,t.createComponent(l.Switch,{get children(){return[t.createComponent(l.Match,{get when(){return i()==="catalog"},get children(){var I=no(),W=I.firstChild,de=W.firstChild,N=de.firstChild,ee=N.nextSibling;return t.insert(N,t.createComponent(o.TextInput,{label:"筛选 awesome-opencode 精选插件",placeholder:"按名称、描述或标签筛选,例如 memory / auth / token",type:"search",get value(){return h()},onInput:L=>{w(L),U(Be)}})),t.insert(ee,t.createComponent(l.For,{each:yo,children:L=>(()=>{var q=co(),ue=q.firstChild;return q.$$click=()=>{_(L.value),U(Be)},t.insert(q,()=>L.label,ue),t.insert(ue,()=>en()[L.value]),t.effect(()=>t.setAttribute(q,"data-active",S()===L.value)),q})()})),t.insert(I,t.createComponent(l.Show,{get when(){return t.memo(()=>!!R("catalog"))()&&c().length===0},get children(){var L=Ae();return t.insert(L,t.createComponent(l.For,{each:[0,1,2,3],children:()=>Lt()})),L}}),null),t.insert(I,t.createComponent(l.Show,{get when(){return!R("catalog")||c().length>0},get children(){return t.createComponent(l.Show,{get when(){return je().length>0},get fallback(){return(()=>{var L=po(),q=L.firstChild;return q.nextSibling,t.insert(L,t.createComponent(o.Icon,{name:"magnifying-glass",size:"large"}),q),t.insert(L,t.createComponent(o.Button,{variant:"ghost",size:"small",onClick:()=>{w(""),_("all")},children:"清除筛选"}),null),L})()},get children(){return[(()=>{var L=Ae();return t.insert(L,t.createComponent(l.For,{get each(){return je().slice(0,O())},children:q=>sn(q)})),L})(),t.createComponent(l.Show,{get when(){return je().length>O()},get children(){return t.createComponent(o.Button,{variant:"ghost",size:"small",onClick:()=>U(L=>L+Be),get children(){return["显示更多(还有 ",t.memo(()=>je().length-O())," 个)"]}})}})]}})}}),null),I}}),t.createComponent(l.Match,{get when(){return i()==="search"},get children(){var I=io(),W=I.firstChild,de=W.firstChild;return W.nextSibling,t.insert(de,t.createComponent(o.TextInput,{label:"搜索 npm 上的 OpenCode 插件",placeholder:"搜索插件,例如 qoder / notify / usage",type:"search",get value(){return j()},onInput:g,onSubmit:N=>{qe(N)}})),t.insert(W,t.createComponent(o.Button,{variant:"contrast",icon:"magnifying-glass",get busy(){return R("search")},onClick:()=>{qe(j())},children:"搜索"}),null),t.insert(I,t.createComponent(l.Show,{get when(){return!b()},get children(){var N=ro(),ee=N.firstChild,L=ee.nextSibling,q=L.nextSibling;return t.insert(N,t.createComponent(o.Icon,{name:"magnifying-glass",size:"large"}),ee),t.insert(q,t.createComponent(l.For,{each:ko,children:ue=>(()=>{var ge=uo();return ge.$$click=()=>{qe(ue)},t.insert(ge,ue),ge})()})),N}}),null),t.insert(I,t.createComponent(l.Show,{get when(){return b()},get children(){return[(()=>{var N=oo(),ee=N.firstChild,L=ee.firstChild,q=L.nextSibling,ue=ee.nextSibling;return t.insert(q,()=>Ve().length),t.insert(ue,t.createComponent(l.For,{each:wo,children:ge=>(()=>{var ze=go();return ze.$$click=()=>f(ge.value),t.insert(ze,()=>ge.label),t.effect(()=>t.setAttribute(ze,"data-active",y()===ge.value)),ze})()})),N})(),t.createComponent(l.Show,{get when(){return t.memo(()=>!!R("search"))()&&M().length===0},get children(){var N=Ae();return t.insert(N,t.createComponent(l.For,{each:[0,1,2,3],children:()=>Lt()})),N}}),t.createComponent(l.Show,{get when(){return!R("search")||M().length>0},get children(){return t.createComponent(l.Show,{get when(){return Ve().length>0},get fallback(){return(()=>{var N=ho(),ee=N.firstChild;return t.insert(N,t.createComponent(o.Icon,{name:"magnifying-glass",size:"large"}),ee),N})()},get children(){var N=Ae();return t.insert(N,t.createComponent(l.For,{get each(){return Ve()},children:ee=>ln(ee)})),N}})}})]}}),null),I}}),t.createComponent(l.Match,{get when(){return i()==="installed"},get children(){var I=so(),W=I.firstChild,de=W.firstChild,N=de.firstChild,ee=N.nextSibling;return t.insert(ee,()=>r()?.plugins.length??0),t.insert(W,t.createComponent(l.Show,{get when(){return ce()},get children(){var L=ao();return L.firstChild,t.insert(L,(()=>{var q=t.memo(()=>We()>0);return()=>q()?` · ${We()} 个可更新`:" · 全部已是最新"})(),null),L}}),null),t.insert(I,t.createComponent(l.Show,{get when(){return(r()?.plugins.length??0)>0},get fallback(){return(()=>{var L=mo(),q=L.firstChild;return t.insert(L,t.createComponent(o.Icon,{name:"extensions",size:"large"}),q),L})()},get children(){var L=Ae();return t.insert(L,t.createComponent(l.For,{get each(){return r()?.plugins??[]},children:q=>cn(q)})),L}}),null),I}})]}}),ie),t.effect(I=>{var W=e.embedded?"true":void 0,de=r()?.configPath;return W!==I.e&&t.setAttribute(a,"data-embedded",I.e=W),de!==I.t&&t.setAttribute(te,"title",I.t=de),I},{e:void 0,t:void 0}),a})()}function To(e){const o=e.ctx.ui,r=()=>{e.onClose(),setTimeout(()=>e.ctx.layout.settings("hub"),0)};return t.createComponent(o.Dialog,{title:"插件中心",size:"x-large",get footer(){return(()=>{var n=fo();return n.firstChild,t.insert(n,t.createComponent(o.Button,{size:"small",variant:"ghost",icon:"settings-gear",onClick:r,children:"在设置中打开"}),null),t.insert(n,t.createComponent(o.Button,{size:"small",get onClick(){return e.onClose},children:"关闭"}),null),n})()},get children(){return t.createComponent(Jt,{get ctx(){return e.ctx},get admin(){return e.admin},embedded:!0})}})}t.delegateEvents(["click","keydown"]);const Io=Te.Plugin.define({setup(e){const o=e.main(ht),r=()=>{e.dialogs.active()||e.dialogs.open(n=>t.createComponent(To,{ctx:e,admin:o,onClose:()=>n.close()}))};e.add(Te.SettingsPage,{id:"hub",title:"插件中心",icon:"extensions",entries:[{id:"hub",title:"插件中心",description:"浏览社区精选插件,按 npm 搜索,安装或移除",keywords:"plugin npm install remove hub catalog marketplace"}],render:()=>t.createComponent(Jt,{ctx:e,admin:o})}),e.add(Te.TitlebarItem,()=>({id:"hub",label:"插件中心",title:"打开插件中心",icon:"extensions",run:r})),e.add(Te.Command,()=>({id:"open",title:"插件中心",description:"浏览、搜索、安装与更新 OpenCode 插件",keywords:"plugin hub install browse npm catalog marketplace",group:"设置",section:"navigation",bind:"mod+alt+p",featured:!0,run:r}))},main(e){e.provide(ht,{snapshot:()=>se(),updates:()=>Dn(),search:o=>er(o.query),browse:async o=>{const r=await se();return ur(new Set(r.plugins.map(n=>n.name)),o.force)},detail:o=>Kn(o.name),openUrl:o=>({ok:tr(o.url)}),openConfig:async()=>({ok:await nr()}),install:o=>Fn(o.spec,o.version),remove:o=>qn(o.spec),reload:()=>or(e.cli.command),restart:()=>({ok:rr()})})}});exports.default=Io;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-hub",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Plugin manager for OpenCode Desktop: browse community plugins, search npm, inspect README and downloads, install and update without editing JSON.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/contract.ts CHANGED
@@ -131,6 +131,8 @@ export const CatalogResult = Schema.Struct({
131
131
  entries: Schema.Array(CatalogEntry),
132
132
  /** `network`, `cache` or `builtin`. */
133
133
  source: Schema.String,
134
+ /** The registry URL the entries came from. Optional for older cached results. */
135
+ registry: Schema.optional(Schema.String),
134
136
  fetchedAt: Schema.Number,
135
137
  error: Schema.String,
136
138
  })
@@ -27,6 +27,8 @@ export interface CatalogResultValue {
27
27
  entries: CatalogEntryValue[]
28
28
  /** Where the list came from: `network`, `cache` or `builtin`. */
29
29
  source: string
30
+ /** The registry URL the entries came from. */
31
+ registry: string
30
32
  /** Epoch milliseconds of the data, 0 for the built-in snapshot. */
31
33
  fetchedAt: number
32
34
  error: string
@@ -129,7 +131,7 @@ function toEntries(raw: RawEntry[], installed: Set<string>): CatalogEntryValue[]
129
131
  return out
130
132
  }
131
133
 
132
- async function fetchRemote(): Promise<RawEntry[]> {
134
+ async function fetchRemote(): Promise<{ entries: RawEntry[]; url: string }> {
133
135
  let lastError = ""
134
136
  for (const url of SOURCES) {
135
137
  try {
@@ -143,7 +145,7 @@ async function fetchRemote(): Promise<RawEntry[]> {
143
145
  lastError = "registry 格式不是数组"
144
146
  continue
145
147
  }
146
- return body as RawEntry[]
148
+ return { entries: body as RawEntry[], url }
147
149
  } catch (error) {
148
150
  lastError = describe(error)
149
151
  }
@@ -153,6 +155,8 @@ async function fetchRemote(): Promise<RawEntry[]> {
153
155
 
154
156
  interface CacheFile {
155
157
  fetchedAt: number
158
+ /** The source URL that produced this cache. Older caches do not have it. */
159
+ url?: string
156
160
  entries: RawEntry[]
157
161
  }
158
162
 
@@ -167,11 +171,11 @@ async function readCache(): Promise<CacheFile | undefined> {
167
171
  }
168
172
  }
169
173
 
170
- async function writeCache(entries: RawEntry[]): Promise<number> {
174
+ async function writeCache(entries: RawEntry[], url: string): Promise<number> {
171
175
  const fetchedAt = Date.now()
172
176
  try {
173
177
  await mkdir(cacheDir(), { recursive: true })
174
- await writeFile(cacheFile(), JSON.stringify({ fetchedAt, entries }), "utf8")
178
+ await writeFile(cacheFile(), JSON.stringify({ fetchedAt, url, entries }), "utf8")
175
179
  } catch {
176
180
  // A failed cache write still leaves the in-memory result usable.
177
181
  }
@@ -186,17 +190,30 @@ export async function browse(installed: Set<string>, force: boolean): Promise<Ca
186
190
  const cache = await readCache()
187
191
  const fresh = cache !== undefined && Date.now() - cache.fetchedAt < TTL
188
192
  if (cache && fresh && !force) {
189
- return { entries: toEntries(cache.entries, installed), source: "cache", fetchedAt: cache.fetchedAt, error: "" }
193
+ return {
194
+ entries: toEntries(cache.entries, installed),
195
+ source: "cache",
196
+ registry: cache.url ?? SOURCES[0]!,
197
+ fetchedAt: cache.fetchedAt,
198
+ error: "",
199
+ }
190
200
  }
191
201
  try {
192
202
  const remote = await fetchRemote()
193
- const fetchedAt = await writeCache(remote)
194
- return { entries: toEntries(remote, installed), source: "network", fetchedAt, error: "" }
203
+ const fetchedAt = await writeCache(remote.entries, remote.url)
204
+ return {
205
+ entries: toEntries(remote.entries, installed),
206
+ source: "network",
207
+ registry: remote.url,
208
+ fetchedAt,
209
+ error: "",
210
+ }
195
211
  } catch (error) {
196
212
  if (cache) {
197
213
  return {
198
214
  entries: toEntries(cache.entries, installed),
199
215
  source: "cache",
216
+ registry: cache.url ?? SOURCES[0]!,
200
217
  fetchedAt: cache.fetchedAt,
201
218
  error: describe(error),
202
219
  }
@@ -204,6 +221,7 @@ export async function browse(installed: Set<string>, force: boolean): Promise<Ca
204
221
  return {
205
222
  entries: toEntries(SNAPSHOT.map(toRaw), installed),
206
223
  source: "builtin",
224
+ registry: SOURCES[0]!,
207
225
  fetchedAt: 0,
208
226
  error: describe(error),
209
227
  }
package/src/gui/page.tsx CHANGED
@@ -17,11 +17,8 @@ type Note = { text: string; tone: Tone }
17
17
 
18
18
  const PAGE = 24
19
19
 
20
- const SOURCE_LABEL: Record<string, string> = {
21
- network: "社区目录 · 已更新",
22
- cache: "社区目录 · 本地缓存",
23
- builtin: "社区目录 · 内置快照",
24
- }
20
+ const CATALOG_SOURCE_FALLBACK = "https://github.com/awesome-opencode/awesome-opencode"
21
+ const CATALOG_SOURCE_TITLE = "精选目录来自 awesome-opencode 社区维护的 registry.json,不是 OpenCode 官方推荐"
25
22
 
26
23
  const TYPE_LABEL: Record<string, string> = {
27
24
  plugins: "插件",
@@ -72,16 +69,6 @@ function relativeDate(value: string): string {
72
69
  return `${Math.floor(days / 365)} 年前`
73
70
  }
74
71
 
75
- function formatDate(epoch: number): string {
76
- if (!epoch) return ""
77
- return new Date(epoch).toLocaleString("zh-CN", {
78
- month: "2-digit",
79
- day: "2-digit",
80
- hour: "2-digit",
81
- minute: "2-digit",
82
- })
83
- }
84
-
85
72
  function initial(name: string): string {
86
73
  const base = name.replace(/^@/, "").split("/").pop() ?? name
87
74
  const cleaned = base.replace(/^opencode[-_]?/i, "")
@@ -195,7 +182,22 @@ const CSS = `
195
182
  font-size: 12px;
196
183
  line-height: 1.6;
197
184
  color: var(--ph-muted);
198
- max-width: 54ch;
185
+ max-width: 72ch;
186
+ }
187
+ .ph-inline-source {
188
+ padding: 0;
189
+ border: 0;
190
+ background: none;
191
+ color: var(--ph-accent);
192
+ font: inherit;
193
+ line-height: inherit;
194
+ cursor: pointer;
195
+ }
196
+ .ph-inline-source:hover { text-decoration: underline; }
197
+ .ph-inline-source:focus-visible {
198
+ outline: 2px solid color-mix(in srgb, var(--ph-accent) 55%, transparent);
199
+ outline-offset: 2px;
200
+ border-radius: 2px;
199
201
  }
200
202
  .ph-path {
201
203
  overflow: hidden;
@@ -595,6 +597,26 @@ button.ph-chip:focus-visible,
595
597
  max-width: none;
596
598
  padding: 0 0 8px;
597
599
  }
600
+ /* The host caps "x-large" at 980x600. Give the standalone hub more room and
601
+ let the dialog be resized from its bottom-right corner, within the window. */
602
+ [data-component="dialog-v2"][data-size="x-large"]:has(.ph-root[data-embedded="true"]) [data-slot="dialog-container"] {
603
+ width: min(100vw - 48px, 1180px);
604
+ height: min(100vh - 88px, 780px);
605
+ min-width: min(100vw - 48px, 720px);
606
+ min-height: min(100vh - 88px, 480px);
607
+ max-width: calc(100vw - 24px);
608
+ max-height: calc(100vh - 24px);
609
+ resize: both;
610
+ }
611
+ [data-component="dialog-v2"][data-size="x-large"]:has(.ph-detail) [data-slot="dialog-container"] {
612
+ width: min(100vw - 48px, 1180px);
613
+ height: min(100vh - 88px, 780px);
614
+ min-width: min(100vw - 48px, 720px);
615
+ min-height: min(100vh - 88px, 480px);
616
+ max-width: calc(100vw - 24px);
617
+ max-height: calc(100vh - 24px);
618
+ resize: both;
619
+ }
598
620
  .ph-root[data-embedded="true"] .ph-toolbar {
599
621
  position: sticky;
600
622
  top: 0;
@@ -1145,7 +1167,7 @@ export function PluginHubPage(props: { ctx: Context; admin: AdminClient; embedde
1145
1167
  return list
1146
1168
  })
1147
1169
 
1148
- const installable = createMemo(() => visible().filter((entry) => entry.pkg).length)
1170
+ const registryUrl = () => meta()?.registry || CATALOG_SOURCE_FALLBACK
1149
1171
  const outdated = createMemo(() => [...updates().values()].filter((item) => item.hasUpdate).length)
1150
1172
  const bySpec = (spec: string) => updates().get(spec)
1151
1173
  const byPackage = createMemo(() => {
@@ -1496,7 +1518,16 @@ export function PluginHubPage(props: { ctx: Context; admin: AdminClient; embedde
1496
1518
  <h2 class="ph-title">插件中心</h2>
1497
1519
  </Show>
1498
1520
  <p class="ph-sub">
1499
- 浏览社区精选、搜索 npm 上的 OpenCode 插件。安装前先看下载量、README 和主页,确认它到底能做什么。
1521
+ 浏览{" "}
1522
+ <button
1523
+ type="button"
1524
+ class="ph-inline-source"
1525
+ title={CATALOG_SOURCE_TITLE}
1526
+ onClick={() => void openUrl(registryUrl())}
1527
+ >
1528
+ awesome-opencode
1529
+ </button>{" "}
1530
+ 社区维护的精选目录、搜索 npm 上的 OpenCode 插件。安装前先看下载量、README 和主页,确认它到底能做什么。
1500
1531
  </p>
1501
1532
  <div class="ph-path" title={state()?.configPath}>
1502
1533
  配置:<code>{state()?.configPath ?? "读取中…"}</code> · <code>{state()?.configKey ?? "plugins"}[]</code>
@@ -1600,7 +1631,7 @@ export function PluginHubPage(props: { ctx: Context; admin: AdminClient; embedde
1600
1631
  <div class="ph-filter-row">
1601
1632
  <div class="ph-grow">
1602
1633
  <ui.TextInput
1603
- label="筛选社区精选插件"
1634
+ label="筛选 awesome-opencode 精选插件"
1604
1635
  placeholder="按名称、描述或标签筛选,例如 memory / auth / token"
1605
1636
  type="search"
1606
1637
  value={filter()}
@@ -1628,17 +1659,6 @@ export function PluginHubPage(props: { ctx: Context; admin: AdminClient; embedde
1628
1659
  </For>
1629
1660
  </div>
1630
1661
  </div>
1631
- <div class="ph-meta">
1632
- <span>{SOURCE_LABEL[meta()?.source ?? ""] ?? "加载中…"}</span>
1633
- <span class="ph-dot" />
1634
- <span>
1635
- {catalog().length} 个条目 · {installable()} 个可安装
1636
- </span>
1637
- <Show when={meta()?.fetchedAt}>
1638
- <span class="ph-dot" />
1639
- <span>更新于 {formatDate(meta()!.fetchedAt)}</span>
1640
- </Show>
1641
- </div>
1642
1662
  </div>
1643
1663
 
1644
1664
  <Show when={isBusy("catalog") && catalog().length === 0}>
@@ -1785,7 +1805,7 @@ export function PluginHubPage(props: { ctx: Context; admin: AdminClient; embedde
1785
1805
  <div class="ph-empty">
1786
1806
  <ui.Icon name="extensions" size="large" />
1787
1807
  <strong>还没有安装插件</strong>
1788
- <span>去「精选目录」看看社区推荐,或者在「npm 搜索」里找长尾插件。</span>
1808
+ <span>去「精选目录」看看 awesome-opencode 的推荐,或者在「npm 搜索」里找长尾插件。</span>
1789
1809
  </div>
1790
1810
  }
1791
1811
  >
@@ -1809,11 +1829,24 @@ export function PluginHubPage(props: { ctx: Context; admin: AdminClient; embedde
1809
1829
  /** The standalone x-large dialog opened from the titlebar pill and the ⌘⌥P shortcut. */
1810
1830
  export function PluginHubDialog(props: { ctx: Context; admin: AdminClient; onClose: () => void }) {
1811
1831
  const ui = props.ctx.ui
1832
+ const openSettings = () => {
1833
+ props.onClose()
1834
+ // Let the dialog unmount before the host swaps in the Settings view.
1835
+ setTimeout(() => props.ctx.layout.settings("hub"), 0)
1836
+ }
1812
1837
  return (
1813
1838
  <ui.Dialog
1814
1839
  title="插件中心"
1815
1840
  size="x-large"
1816
- footer={<ui.Button onClick={props.onClose}>关闭</ui.Button>}
1841
+ footer={
1842
+ <div class="ph-dialog-footer">
1843
+ <span class="ph-footer-note">拖动右下角可调整大小</span>
1844
+ <ui.Button size="small" variant="ghost" icon="settings-gear" onClick={openSettings}>
1845
+ 在设置中打开
1846
+ </ui.Button>
1847
+ <ui.Button size="small" onClick={props.onClose}>关闭</ui.Button>
1848
+ </div>
1849
+ }
1817
1850
  >
1818
1851
  <PluginHubPage ctx={props.ctx} admin={props.admin} embedded />
1819
1852
  </ui.Dialog>