shine-code-submit 1.0.19 → 1.0.21

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shine-code-submit",
3
- "version": "1.0.18",
3
+ "version": "1.0.21",
4
4
  "description": "Claude Code Hook → 本地常驻 Daemon 的状态/持久化底座。Hook 只做采集+落盘+转发立即退出,重活交给后台 Daemon 异步处理,不拖慢 Claude Code。",
5
5
  "author": {
6
6
  "name": "renguifeng"
package/dist/install.cjs CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- var N=require("node:fs"),X1=require("node:path"),Y1=require("node:child_process");var m=require("node:fs"),h=require("node:os"),y=require("node:path"),v=require("node:child_process"),$1=[1,1,0],G1=300000;function D(){let b=v.spawnSync("bun",["--version"],{shell:process.platform==="win32",encoding:"utf8"});if(b.status===0&&(b.stdout??"").trim())return"bun";let q=h.homedir(),z=process.platform==="win32"?[y.join(q,".bun","bin","bun.exe")]:[y.join(q,".bun","bin","bun"),"/usr/local/bin/bun","/opt/homebrew/bin/bun"];for(let Q of z)if(m.existsSync(Q))return Q;return null}function H1(b){return b.trim().split(".").map((q)=>parseInt(q,10)||0)}function W1(b,q){let z=H1(b);for(let Q=0;Q<q.length;Q++){let X=z[Q]??0,Y=q[Q]??0;if(X>Y)return!0;if(X<Y)return!1}return!0}function J1(){let b=process.env.npm_config_registry??"";return/npmmirror|taobao/i.test(b)}function M(b){return v.spawnSync(b,{shell:!0,encoding:"utf8",timeout:G1,stdio:"inherit"}).status??1}async function P(){let b=D();if(b){let z=v.spawnSync(b,["--version"],{shell:process.platform==="win32",encoding:"utf8"}).stdout?.trim()??"";if(z&&W1(z,$1))return console.log(`[shine-code-submit] bun ${z} detected`),b}if(console.log("[shine-code-submit] bun 未找到或版本过低,开始自动安装..."),J1()){if(console.log("[shine-code-submit] 检测到国内 npm 镜像,先尝试 npm install -g bun"),M("npm install -g bun")===0){let z=D();if(z)return console.log("[shine-code-submit] ✓ bun 安装成功(via npm 镜像)"),z}console.log("[shine-code-submit] npm 镜像方式失败,回退官方脚本")}if(process.platform==="win32")M('powershell -c "irm bun.sh/install.ps1 | iex"');else M("curl -fsSL https://bun.sh/install | bash");let q=D();if(!q)throw console.error("[shine-code-submit] bun 自动安装失败。请手动安装后重试:"),console.error(" Windows: winget install Oven-sh.Bun 或 npm install -g bun"),console.error(" macOS: brew install oven-sh/bun/bun"),console.error(" Linux: curl -fsSL https://bun.sh/install | bash"),Error("bun installation failed");return console.log("[shine-code-submit] ✓ bun 安装成功"),q}var $=require("node:fs"),Z=require("node:path"),g=require("node:child_process");var S=require("node:os"),U=require("node:path");function p(){return process.env.CLAUDE_CONFIG_DIR||U.join(S.homedir(),".claude")}function B(){return U.join(p(),"plugins")}function C(){return U.join(B(),"known_marketplaces.json")}function O(){return U.join(B(),"installed_plugins.json")}function V(){return U.join(p(),"settings.json")}var c={name:"shine-code-submit",version:"1.0.19",private:!1,type:"module",description:"Claude Code Hook -> 本地常驻 Daemon 的状态/持久化底座",author:"renguifeng",license:"MIT",repository:{type:"git",url:"https://github.com/china-shine/shine-code-submit.git"},homepage:"https://github.com/china-shine/shine-code-submit",bugs:{url:"https://github.com/china-shine/shine-code-submit/issues"},keywords:["claude","claude-code","plugin","hooks","daemon","dashboard"],bin:{"shine-code-submit":"dist/install.cjs"},files:["dist/install.cjs",".claude-plugin","hooks/hooks.json","bin/launcher.cjs","src","ui","package.json","bun.lock","README.md"],engines:{node:">=18"},publishConfig:{access:"public",registry:"https://registry.npmjs.org/"},scripts:{daemon:"bun run src/daemon/main.ts",hook:"bun run src/hook/main.ts",cli:"bun run src/cli/main.ts",build:"bun run scripts/build.ts","build:all":"bun run scripts/build.ts --all","build:install":"bun run scripts/build-install.ts","build:dist":"bun run scripts/build.ts && bun run scripts/build-install.ts",prepublishOnly:"bun run build:dist",typecheck:"tsc --noEmit"},devDependencies:{"@types/bun":"^1.3.14","@types/react":"^19.2.17","@types/react-dom":"^19.2.3",typescript:"^6.0.3"},dependencies:{marked:"^18.0.5",react:"^19.2.7","react-dom":"^19.2.7"}};var E=require("node:os"),l="shine-code-submit",H=c.version,I1=process.env.SHINE_CODE_SUBMIT_HOST??"0.0.0.0",F1="127.0.0.1",d=36666,L=`http://${F1}:${d}`;function U1(){let b=["vethernet","vmware","virtualbox","docker","veth","br-","virbr","vnet","utun"],q=(z)=>{let Q=z.toLowerCase();return b.some((X)=>Q.includes(X))};try{let z=E.networkInterfaces();for(let Q of Object.keys(z)){if(q(Q))continue;for(let X of z[Q]??[])if(X.family==="IPv4"&&!X.internal)return X.address}for(let Q of Object.keys(z))for(let X of z[Q]??[])if(X.family==="IPv4"&&!X.internal)return X.address}catch{}return"localhost"}var w=`http://${U1()}:${d}`;var W="shine-code-submit",I="shine-code-submit";function _(b=H){return Z.join(B(),"cache",W,I,b)}function u1(){let b=process.argv[1],q=process.cwd();if(b)try{q=Z.dirname($.realpathSync(Z.resolve(b)))}catch{q=Z.dirname(Z.resolve(b))}let z=q;for(let Q=0;Q<12;Q++){if($.existsSync(Z.join(z,"package.json"))&&$.existsSync(Z.join(z,".claude-plugin")))return z;let X=Z.dirname(z);if(X===z)break;z=X}return Z.dirname(q)}var x1=[".claude-plugin","hooks","bin","src","ui","package.json","bun.lock","README.md"];function s(b){let q=_();if($.existsSync(q))$.rmSync(q,{recursive:!0,force:!0});$.mkdirSync(q,{recursive:!0});let z=u1();console.log(`[shine-code-submit] 部署源:${z}`);for(let X of x1){let Y=Z.join(z,X);if(!$.existsSync(Y))continue;$.cpSync(Y,Z.join(q,X),{recursive:!0})}console.log("[shine-code-submit] 安装运行时依赖(bun install)...");let Q=g.spawnSync(b,["install","--frozen-lockfile"],{cwd:q,shell:process.platform==="win32",encoding:"utf8",stdio:"inherit"}).status;if(Q!==0){if(console.log("[shine-code-submit] --frozen-lockfile 失败,重试普通 bun install"),Q=g.spawnSync(b,["install"],{cwd:q,shell:process.platform==="win32",encoding:"utf8",stdio:"inherit"}).status,Q!==0)throw Error(`bun install 失败(exit ${Q})。请手动在 ${q} 跑 bun install`)}return $.writeFileSync(Z.join(q,".install-version"),JSON.stringify({version:H,installedAt:Date.now()}),"utf8"),console.log(`[shine-code-submit] 已部署到 ${q}`),q}var G=require("node:fs"),o=require("node:path");function K(b,q){if(!G.existsSync(b))return q;let z;try{z=G.readFileSync(b,"utf8")}catch{return q}try{return JSON.parse(z)}catch{let Q=`${b}.bak-corrupt-${Date.now()}`;try{G.copyFileSync(b,Q),console.error(`[shine-code-submit] WARNING: ${b} JSON 损坏,已备份到 ${Q},用默认值继续`)}catch{}return q}}function F(b,q){let z=`${b}.bak-pre-install`;if(G.existsSync(b)&&!G.existsSync(z))try{G.copyFileSync(b,z)}catch{}G.mkdirSync(o.dirname(b),{recursive:!0});let Q=`${b}.tmp-${process.pid}`;G.writeFileSync(Q,JSON.stringify(q,null,2),"utf8"),G.renameSync(Q,b)}function f(){return`${I}@${W}`}function r(b){let q=C(),z=K(q,{}),Q=z[W];if(Q?.source&&Q.source.source!=="directory")console.log(`[shine-code-submit] WARNING: marketplace "${W}" 已存在(source=${Q.source.source}),将覆盖为 directory 源(原文件已备份)`);z[W]={source:{source:"directory",path:b},installLocation:b,lastUpdated:new Date().toISOString(),autoUpdate:!1},F(q,z),console.log(`[shine-code-submit] marketplace 已注册 → ${q}`)}function i(b){let q=O(),z=K(q,{version:2,plugins:{}});if(!z.version)z.version=2;if(!z.plugins)z.plugins={};let Q=f(),X=new Date().toISOString(),Y=z.plugins[Q]?.[0];z.plugins[Q]=[{scope:"user",installPath:b,version:H,installedAt:Y?.installedAt??X,lastUpdated:X}],F(q,z),console.log(`[shine-code-submit] plugin 已注册 → ${q}`)}function t(b){let q=V(),z=K(q,{});if(!z.enabledPlugins)z.enabledPlugins={};let Q=f();if(z.enabledPlugins[Q]=!0,!z.extraKnownMarketplaces)z.extraKnownMarketplaces={};if(!z.extraKnownMarketplaces[W])z.extraKnownMarketplaces[W]={source:{source:"directory",path:b}};F(q,z),console.log(`[shine-code-submit] 已启用(enabledPlugins)→ ${q}`)}function n(){let b=f(),q=K(C(),{});if(q[W])delete q[W],F(C(),q);let z=K(O(),{plugins:{}});if(z.plugins&&z.plugins[b])delete z.plugins[b],F(O(),z);let Q=K(V(),{}),X=!1;if(Q.enabledPlugins&&Q.enabledPlugins[b])delete Q.enabledPlugins[b],X=!0;if(Q.extraKnownMarketplaces&&Q.extraKnownMarketplaces[W])delete Q.extraKnownMarketplaces[W],X=!0;if(X)F(V(),Q);console.log("[shine-code-submit] 已从三处 JSON 移除注册")}var x=require("node:fs");var J=require("node:path"),a=require("node:fs"),e=require("node:os"),j1=process.env.LOCALAPPDATA??J.join(e.homedir(),".local","share"),u=J.join(j1,"shine-code-submit"),v1=J.join(u,"spool"),b1=J.join(u,"log"),q1=J.join(u,"db"),R=J.join(u,"daemon.pid"),E1=J.join(b1,"daemon.log"),l1=J.join(q1,"events.sqlite");function z1(){for(let b of[u,v1,b1,q1])a.mkdirSync(b,{recursive:!0})}function j(){try{let b=x.readFileSync(R,"utf8");return JSON.parse(b)}catch{return null}}function Q1(){try{x.unlinkSync(R)}catch{}}async function T(b=400){try{let q=await fetch(`${L}/api/health`,{signal:AbortSignal.timeout(b)});if(!q.ok)return{alive:!1};let z=await q.json();if(z?.service!==l)return{alive:!1};return{alive:!0,version:z.version}}catch{return{alive:!1}}}async function k(b=400){return(await T(b)).alive}async function A(){let b=j();if(!b)return;if(await k()){try{await fetch(`${L}/api/shutdown`,{method:"POST",headers:{authorization:`Bearer ${b.token}`}})}catch{}if(await B1(1000),await k())try{process.kill(b.pid)}catch{}}Q1()}function B1(b){return new Promise((q)=>setTimeout(q,b))}var[,,Z1]=process.argv;C1().catch((b)=>{console.error(`[shine-code-submit] ${b instanceof Error?b.message:String(b)}`),process.exit(1)});async function C1(){switch(Z1){case void 0:case"install":await O1();break;case"uninstall":await V1();break;case"status":await w1();break;case"--version":case"-v":console.log(H);break;default:D1()}}async function O1(){console.log(`=== shine-code-submit installer v${H} ===`);let b=await P(),q=s(b);r(q),i(q),t(q),z1(),await k1(b,q),T1(),console.log(""),console.log("✓ 安装完成。"),console.log(" · 重启 Claude Code 后,/plugin 列表会显示 shine-code-submit(已启用)。"),console.log(" · 开新会话即触发 SessionStart hook,事件出现在 dashboard。")}async function V1(){console.log("=== shine-code-submit uninstaller ==="),await A(),n();let b=_();if(N.existsSync(b))N.rmSync(b,{recursive:!0,force:!0}),console.log(`[shine-code-submit] 已删除 ${b}`);console.log("✓ 已卸载。重启 Claude Code 后 /plugin 不再显示。")}async function w1(){let b=await k(),q=j();if(b&&q)console.log(`daemon: running pid=${q.pid} ${w}`);else console.log("daemon: not running")}async function k1(b,q){let z=await T();if(z.alive&&z.version===H){console.log(`[shine-code-submit] daemon 已是最新 v${H},跳过启动`);return}if(z.alive)console.log(`[shine-code-submit] daemon 旧版 v${z.version} 运行中,重启到 v${H}...`),await A();else console.log("[shine-code-submit] 启动 daemon...");let Q=X1.join(q,"src","daemon","main.ts");try{Y1.spawn(b,["run",Q],{detached:!0,stdio:"ignore",windowsHide:!0,cwd:q,shell:process.platform==="win32"}).unref()}catch(Y){console.error(`[shine-code-submit] 启动 daemon 失败:${Y instanceof Error?Y.message:Y}`),console.error(" plugin 已注册,Claude Code 重启后 hook 会自动拉起 daemon");return}let X=Date.now()+1e4;while(Date.now()<X){await N1(200);let Y=await T();if(Y.alive&&Y.version===H){console.log("[shine-code-submit] daemon 已就绪");return}}console.error("[shine-code-submit] daemon 启动超时(10s)。plugin 已注册,可稍后手动 `shine-code-submit start` 或重启 claude。")}function T1(){let b=j(),q=b?`${w}/ui?t=${b.token}`:`${w}/ui`;console.log(`[shine-code-submit] Dashboard: ${q}`)}function N1(b){return new Promise((q)=>setTimeout(q,b))}function D1(){console.log(`shine-code-submit <command>
2
+ var A=require("node:fs"),F1=require("node:path"),u1=require("node:child_process");var a=require("node:fs"),e=require("node:os"),P=require("node:path"),T=require("node:child_process");var O=require("node:fs"),i=require("node:path");var U=require("node:path"),s=require("node:fs"),o=require("node:os"),j1=process.env.LOCALAPPDATA??U.join(o.homedir(),".local","share"),j=U.join(j1,"shine-code-submit"),v1=U.join(j,"spool"),v=U.join(j,"log"),r=U.join(j,"db"),g=U.join(j,"daemon.pid"),S1=U.join(v,"daemon.log"),f1=U.join(r,"events.sqlite");function t(){for(let b of[j,v1,v,r])s.mkdirSync(b,{recursive:!0})}var k=!1;function n(b){k=b}function V(){return k}function X(b){if(k)return;console.log(b)}function W(b){if(!k){console.error(b);return}try{O.mkdirSync(v,{recursive:!0}),O.appendFileSync(i.join(v,"install.log"),`[${new Date().toISOString()}] ${b}
3
+ `,"utf8")}catch{}}var B1=[1,1,0],C1=300000;function R(){let b=T.spawnSync("bun",["--version"],{shell:process.platform==="win32",encoding:"utf8"});if(b.status===0&&(b.stdout??"").trim())return"bun";let z=e.homedir(),q=process.platform==="win32"?[P.join(z,".bun","bin","bun.exe")]:[P.join(z,".bun","bin","bun"),"/usr/local/bin/bun","/opt/homebrew/bin/bun"];for(let Q of q)if(a.existsSync(Q))return Q;return null}function O1(b){return b.trim().split(".").map((z)=>parseInt(z,10)||0)}function k1(b,z){let q=O1(b);for(let Q=0;Q<z.length;Q++){let Y=q[Q]??0,Z=z[Q]??0;if(Y>Z)return!0;if(Y<Z)return!1}return!0}function V1(){let b=process.env.npm_config_registry??"";return/npmmirror|taobao/i.test(b)}function h(b){return T.spawnSync(b,{shell:!0,encoding:"utf8",timeout:C1,stdio:V()?"ignore":"inherit"}).status??1}async function b1(){let b=R();if(b){let q=T.spawnSync(b,["--version"],{shell:process.platform==="win32",encoding:"utf8"}).stdout?.trim()??"";if(q&&k1(q,B1))return X(`[shine-code-submit] bun ${q} detected`),b}if(X("[shine-code-submit] bun 未找到或版本过低,开始自动安装..."),V1()){if(X("[shine-code-submit] 检测到国内 npm 镜像,先尝试 npm install -g bun"),h("npm install -g bun")===0){let q=R();if(q)return X("[shine-code-submit] ✓ bun 安装成功(via npm 镜像)"),q}X("[shine-code-submit] npm 镜像方式失败,回退官方脚本")}if(process.platform==="win32")h('powershell -c "irm bun.sh/install.ps1 | iex"');else h("curl -fsSL https://bun.sh/install | bash");let z=R();if(!z)throw W("[shine-code-submit] bun 自动安装失败。请手动安装后重试:"),W(" Windows: winget install Oven-sh.Bun 或 npm install -g bun"),W(" macOS: brew install oven-sh/bun/bun"),W(" Linux: curl -fsSL https://bun.sh/install | bash"),Error("bun installation failed");return X("[shine-code-submit] ✓ bun 安装成功"),z}var $=require("node:fs"),G=require("node:path"),c=require("node:child_process");var q1=require("node:os"),x=require("node:path");function z1(){return process.env.CLAUDE_CONFIG_DIR||x.join(q1.homedir(),".claude")}function M(){return x.join(z1(),"plugins")}function N(){return x.join(M(),"known_marketplaces.json")}function w(){return x.join(M(),"installed_plugins.json")}function D(){return x.join(z1(),"settings.json")}var Q1={name:"shine-code-submit",version:"1.0.21",private:!1,type:"module",description:"Claude Code Hook -> 本地常驻 Daemon 的状态/持久化底座",author:"renguifeng",license:"MIT",repository:{type:"git",url:"https://github.com/china-shine/shine-code-submit.git"},homepage:"https://github.com/china-shine/shine-code-submit",bugs:{url:"https://github.com/china-shine/shine-code-submit/issues"},keywords:["claude","claude-code","plugin","hooks","daemon","dashboard"],bin:{"shine-code-submit":"dist/install.cjs"},files:["dist/install.cjs",".claude-plugin","hooks/hooks.json","bin/launcher.cjs","src","ui","package.json","bun.lock","README.md"],engines:{node:">=18"},publishConfig:{access:"public",registry:"https://registry.npmjs.org/"},scripts:{daemon:"bun run src/daemon/main.ts",hook:"bun run src/hook/main.ts",cli:"bun run src/cli/main.ts",build:"bun run scripts/build.ts","build:all":"bun run scripts/build.ts --all","build:install":"bun run scripts/build-install.ts","build:dist":"bun run scripts/build.ts && bun run scripts/build-install.ts",prepublishOnly:"bun run build:dist",typecheck:"tsc --noEmit"},devDependencies:{"@types/bun":"^1.3.14","@types/react":"^19.2.17","@types/react-dom":"^19.2.3",typescript:"^6.0.3"},dependencies:{marked:"^18.0.5",react:"^19.2.7","react-dom":"^19.2.7"}};var X1=require("node:os"),Y1="shine-code-submit",J=Q1.version,n1=process.env.SHINE_CODE_SUBMIT_HOST??"0.0.0.0",M1="127.0.0.1",Z1=36666,m=`http://${M1}:${Z1}`;function N1(){let b=["vethernet","vmware","virtualbox","docker","veth","br-","virbr","vnet","utun"],z=(q)=>{let Q=q.toLowerCase();return b.some((Y)=>Q.includes(Y))};try{let q=X1.networkInterfaces();for(let Q of Object.keys(q)){if(z(Q))continue;for(let Y of q[Q]??[])if(Y.family==="IPv4"&&!Y.internal)return Y.address}for(let Q of Object.keys(q))for(let Y of q[Q]??[])if(Y.family==="IPv4"&&!Y.internal)return Y.address}catch{}return"localhost"}var y=`http://${N1()}:${Z1}`;var K="shine-code-submit",p="shine-code-submit";function S(b=J){return G.join(M(),"cache",K,p,b)}function w1(){let b=process.argv[1],z=process.cwd();if(b)try{z=G.dirname($.realpathSync(G.resolve(b)))}catch{z=G.dirname(G.resolve(b))}let q=z;for(let Q=0;Q<12;Q++){if($.existsSync(G.join(q,"package.json"))&&$.existsSync(G.join(q,".claude-plugin")))return q;let Y=G.dirname(q);if(Y===q)break;q=Y}return G.dirname(z)}var D1=[".claude-plugin","hooks","bin","src","ui","package.json","bun.lock","README.md"];function $1(b,z={}){let q=S();if(!z.force&&y1(q))return X("[shine-code-submit] 同版本已部署,跳过(用 --force 强制重装)"),q;if($.existsSync(q))$.rmSync(q,{recursive:!0,force:!0});$.mkdirSync(q,{recursive:!0});let Q=w1();X(`[shine-code-submit] 部署源:${Q}`);for(let l of D1){let d=G.join(Q,l);if(!$.existsSync(d))continue;$.cpSync(d,G.join(q,l),{recursive:!0})}X("[shine-code-submit] 安装运行时依赖(bun install)...");let Y=V()?"ignore":"inherit",Z=c.spawnSync(b,["install","--frozen-lockfile"],{cwd:q,shell:process.platform==="win32",encoding:"utf8",stdio:Y}).status;if(Z!==0){if(X("[shine-code-submit] --frozen-lockfile 失败,重试普通 bun install"),Z=c.spawnSync(b,["install"],{cwd:q,shell:process.platform==="win32",encoding:"utf8",stdio:Y}).status,Z!==0)throw Error(`bun install 失败(exit ${Z})。请手动在 ${q} 跑 bun install`)}return $.writeFileSync(G.join(q,".install-version"),JSON.stringify({version:J,installedAt:Date.now()}),"utf8"),X(`[shine-code-submit] 已部署到 ${q}`),q}function y1(b){try{return JSON.parse($.readFileSync(G.join(b,".install-version"),"utf8")).version===J}catch{return!1}}var H=require("node:fs"),G1=require("node:path");function F(b,z){if(!H.existsSync(b))return z;let q;try{q=H.readFileSync(b,"utf8")}catch{return z}try{return JSON.parse(q)}catch{let Q=`${b}.bak-corrupt-${Date.now()}`;try{H.copyFileSync(b,Q),W(`[shine-code-submit] WARNING: ${b} JSON 损坏,已备份到 ${Q},用默认值继续`)}catch{}return z}}function u(b,z){let q=`${b}.bak-pre-install`;if(H.existsSync(b)&&!H.existsSync(q))try{H.copyFileSync(b,q)}catch{}H.mkdirSync(G1.dirname(b),{recursive:!0});let Q=`${b}.tmp-${process.pid}`;H.writeFileSync(Q,JSON.stringify(z,null,2),"utf8"),H.renameSync(Q,b)}function f(){return`${p}@${K}`}function H1(b){let z=N(),q=F(z,{}),Q=q[K];if(Q?.source&&Q.source.source!=="directory")W(`[shine-code-submit] WARNING: marketplace "${K}" 已存在(source=${Q.source.source}),将覆盖为 directory 源(原文件已备份)`);q[K]={source:{source:"directory",path:b},installLocation:b,lastUpdated:new Date().toISOString(),autoUpdate:!1},u(z,q),X(`[shine-code-submit] marketplace 已注册 → ${z}`)}function W1(b){let z=w(),q=F(z,{version:2,plugins:{}});if(!q.version)q.version=2;if(!q.plugins)q.plugins={};let Q=f(),Y=new Date().toISOString(),Z=q.plugins[Q]?.[0];q.plugins[Q]=[{scope:"user",installPath:b,version:J,installedAt:Z?.installedAt??Y,lastUpdated:Y}],u(z,q),X(`[shine-code-submit] plugin 已注册 → ${z}`)}function J1(b){let z=D(),q=F(z,{});if(!q.enabledPlugins)q.enabledPlugins={};let Q=f();if(q.enabledPlugins[Q]=!0,!q.extraKnownMarketplaces)q.extraKnownMarketplaces={};if(!q.extraKnownMarketplaces[K])q.extraKnownMarketplaces[K]={source:{source:"directory",path:b}};u(z,q),X(`[shine-code-submit] 已启用(enabledPlugins)→ ${z}`)}function K1(){let b=f(),z=F(N(),{});if(z[K])delete z[K],u(N(),z);let q=F(w(),{plugins:{}});if(q.plugins&&q.plugins[b])delete q.plugins[b],u(w(),q);let Q=F(D(),{}),Y=!1;if(Q.enabledPlugins&&Q.enabledPlugins[b])delete Q.enabledPlugins[b],Y=!0;if(Q.extraKnownMarketplaces&&Q.extraKnownMarketplaces[K])delete Q.extraKnownMarketplaces[K],Y=!0;if(Y)u(D(),Q);X("[shine-code-submit] 已从三处 JSON 移除注册")}var B=require("node:fs");function C(){try{let b=B.readFileSync(g,"utf8");return JSON.parse(b)}catch{return null}}function U1(){try{B.unlinkSync(g)}catch{}}async function _(b=400){try{let z=await fetch(`${m}/api/health`,{signal:AbortSignal.timeout(b)});if(!z.ok)return{alive:!1};let q=await z.json();if(q?.service!==Y1)return{alive:!1};return{alive:!0,version:q.version}}catch{return{alive:!1}}}async function L(b=400){return(await _(b)).alive}async function E(){let b=C();if(!b)return;if(await L()){try{await fetch(`${m}/api/shutdown`,{method:"POST",headers:{authorization:`Bearer ${b.token}`}})}catch{}if(await L1(1000),await L())try{process.kill(b.pid)}catch{}}U1()}function L1(b){return new Promise((z)=>setTimeout(z,b))}process.noDeprecation=!0;var I=process.argv.slice(2),_1=I.includes("--silent")||I.includes("-s")||process.env.SHINE_SILENT==="1",I1=I.includes("--force");n(_1);var x1=I.find((b)=>b!=="--silent"&&b!=="-s"&&b!=="--force");A1().catch((b)=>{W(`[shine-code-submit] ${b instanceof Error?b.message:String(b)}`),process.exit(1)});async function A1(){switch(x1){case void 0:case"install":await g1();break;case"uninstall":await R1();break;case"status":await h1();break;case"--version":case"-v":console.log(J);break;default:p1()}}async function g1(){X(`=== shine-code-submit installer v${J} ===`);let b=await b1(),z=$1(b,{force:I1});H1(z),W1(z),J1(z),t(),await P1(b,z),m1(),X(""),X("✓ 安装完成。"),X(" · 重启 Claude Code 后,/plugin 列表会显示 shine-code-submit(已启用)。"),X(" · 开新会话即触发 SessionStart hook,事件出现在 dashboard。")}async function R1(){X("=== shine-code-submit uninstaller ==="),await E(),K1();let b=S();if(A.existsSync(b))A.rmSync(b,{recursive:!0,force:!0}),X(`[shine-code-submit] 已删除 ${b}`);X("✓ 已卸载。重启 Claude Code 后 /plugin 不再显示。")}async function h1(){let b=await L(),z=C();if(b&&z)console.log(`daemon: running pid=${z.pid} ${y}`);else console.log("daemon: not running")}async function P1(b,z){let q=await _();if(q.alive&&q.version===J){X(`[shine-code-submit] daemon 已是最新 v${J},跳过启动`);return}if(q.alive)X(`[shine-code-submit] daemon 旧版 v${q.version} 运行中,重启到 v${J}...`),await E();else X("[shine-code-submit] 启动 daemon...");let Q=F1.join(z,"src","daemon","main.ts");try{u1.spawn(b,["run",Q],{detached:!0,stdio:"ignore",windowsHide:!0,cwd:z,shell:process.platform==="win32"}).unref()}catch(Z){W(`[shine-code-submit] 启动 daemon 失败:${Z instanceof Error?Z.message:Z}`),W(" plugin 已注册,Claude Code 重启后 hook 会自动拉起 daemon");return}let Y=Date.now()+1e4;while(Date.now()<Y){await c1(200);let Z=await _();if(Z.alive&&Z.version===J){X("[shine-code-submit] daemon 已就绪");return}}W("[shine-code-submit] daemon 启动超时(10s)。plugin 已注册,可稍后手动 `shine-code-submit start` 或重启 claude。")}function m1(){let b=C(),z=b?`${y}/ui?t=${b.token}`:`${y}/ui`;X(`[shine-code-submit] Dashboard: ${z}`)}function c1(b){return new Promise((z)=>setTimeout(z,b))}function p1(){console.log(`shine-code-submit <command>
3
4
 
4
5
  install 安装插件(自动装 bun + 部署 + 注册 + 启 daemon + 开 dashboard)
6
+ flags: --silent(静默,自动更新用) --force(强制重装,绕过同版本幂等)
5
7
  uninstall 卸载(停 daemon + 反注册 + 删文件)
6
8
  status 显示 daemon 状态
7
9
 
8
- 通常通过 npx 跑:npx shine-code-submit install`),process.exit(Z1?1:0)}
10
+ 通常通过 npx 跑:npx shine-code-submit install`),process.exit(x1?1:0)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shine-code-submit",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Claude Code Hook -> 本地常驻 Daemon 的状态/持久化底座",
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
+ import { info, isSilent, warn } from "./log";
6
7
 
7
8
  const MIN_BUN = [1, 1, 0];
8
9
  const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
@@ -44,7 +45,12 @@ function isCnRegistry(): boolean {
44
45
  }
45
46
 
46
47
  function runShell(cmd: string): number {
47
- return spawnSync(cmd, { shell: true, encoding: "utf8", timeout: INSTALL_TIMEOUT_MS, stdio: "inherit" }).status ?? 1;
48
+ return spawnSync(cmd, {
49
+ shell: true,
50
+ encoding: "utf8",
51
+ timeout: INSTALL_TIMEOUT_MS,
52
+ stdio: isSilent() ? "ignore" : "inherit",
53
+ }).status ?? 1;
48
54
  }
49
55
 
50
56
  /**
@@ -57,24 +63,24 @@ export async function ensureBun(): Promise<string> {
57
63
  const v =
58
64
  spawnSync(existing, ["--version"], { shell: process.platform === "win32", encoding: "utf8" }).stdout?.trim() ?? "";
59
65
  if (v && versionGte(v, MIN_BUN)) {
60
- console.log(`[shine-code-submit] bun ${v} detected`);
66
+ info(`[shine-code-submit] bun ${v} detected`);
61
67
  return existing;
62
68
  }
63
69
  }
64
70
 
65
- console.log("[shine-code-submit] bun 未找到或版本过低,开始自动安装...");
71
+ info("[shine-code-submit] bun 未找到或版本过低,开始自动安装...");
66
72
 
67
73
  // 国内镜像优先:npm i -g bun(走 npmmirror,比官方脚本快且稳)
68
74
  if (isCnRegistry()) {
69
- console.log("[shine-code-submit] 检测到国内 npm 镜像,先尝试 npm install -g bun");
75
+ info("[shine-code-submit] 检测到国内 npm 镜像,先尝试 npm install -g bun");
70
76
  if (runShell("npm install -g bun") === 0) {
71
77
  const p = getBunPath();
72
78
  if (p) {
73
- console.log("[shine-code-submit] ✓ bun 安装成功(via npm 镜像)");
79
+ info("[shine-code-submit] ✓ bun 安装成功(via npm 镜像)");
74
80
  return p;
75
81
  }
76
82
  }
77
- console.log("[shine-code-submit] npm 镜像方式失败,回退官方脚本");
83
+ info("[shine-code-submit] npm 镜像方式失败,回退官方脚本");
78
84
  }
79
85
 
80
86
  // 官方脚本
@@ -86,12 +92,12 @@ export async function ensureBun(): Promise<string> {
86
92
 
87
93
  const p = getBunPath();
88
94
  if (!p) {
89
- console.error("[shine-code-submit] bun 自动安装失败。请手动安装后重试:");
90
- console.error(" Windows: winget install Oven-sh.Bun 或 npm install -g bun");
91
- console.error(" macOS: brew install oven-sh/bun/bun");
92
- console.error(" Linux: curl -fsSL https://bun.sh/install | bash");
95
+ warn("[shine-code-submit] bun 自动安装失败。请手动安装后重试:");
96
+ warn(" Windows: winget install Oven-sh.Bun 或 npm install -g bun");
97
+ warn(" macOS: brew install oven-sh/bun/bun");
98
+ warn(" Linux: curl -fsSL https://bun.sh/install | bash");
93
99
  throw new Error("bun installation failed");
94
100
  }
95
- console.log("[shine-code-submit] ✓ bun 安装成功");
101
+ info("[shine-code-submit] ✓ bun 安装成功");
96
102
  return p;
97
103
  }
@@ -1,9 +1,10 @@
1
1
  // 部署 plugin 文件到 claude cache 目录,并跑 bun install 装运行时依赖(marked/react)。
2
- import { cpSync, existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
2
+ import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { pluginsRoot } from "./paths";
6
6
  import { SERVICE_VERSION } from "../shared/config";
7
+ import { info, isSilent } from "./log";
7
8
 
8
9
  export const MARKETPLACE_NAME = "shine-code-submit";
9
10
  export const PLUGIN_NAME = "shine-code-submit";
@@ -47,35 +48,43 @@ const WHITELIST = [".claude-plugin", "hooks", "bin", "src", "ui", "package.json"
47
48
  /**
48
49
  * 部署 plugin:清同版本目录 → 拷白名单 → bun install 装依赖 → 写版本标记。
49
50
  * 返回 cache 目录绝对路径。
51
+ *
52
+ * 幂等:同版本已部署(且非 --force)时直接复用,不 rmSync/不拷贝/不 bun install。
53
+ * 自动更新可能反复触发(60min tick、或旧 daemon 没杀干净导致循环),没有幂等就会每次满屏日志 + 慢装。
50
54
  */
51
- export function deployPlugin(bunPath: string): string {
55
+ export function deployPlugin(bunPath: string, opts: { force?: boolean } = {}): string {
52
56
  const target = cacheDir();
57
+ if (!opts.force && sameVersionDeployed(target)) {
58
+ info("[shine-code-submit] 同版本已部署,跳过(用 --force 强制重装)");
59
+ return target;
60
+ }
53
61
  if (existsSync(target)) rmSync(target, { recursive: true, force: true });
54
62
  mkdirSync(target, { recursive: true });
55
63
 
56
64
  const srcRoot = findPackageRoot();
57
- console.log(`[shine-code-submit] 部署源:${srcRoot}`);
65
+ info(`[shine-code-submit] 部署源:${srcRoot}`);
58
66
  for (const item of WHITELIST) {
59
67
  const from = join(srcRoot, item);
60
68
  if (!existsSync(from)) continue; // 缺(如 bun.lock 未入库)跳过
61
69
  cpSync(from, join(target, item), { recursive: true });
62
70
  }
63
71
 
64
- // bun install 装运行时依赖
65
- console.log("[shine-code-submit] 安装运行时依赖(bun install)...");
72
+ // bun install 装运行时依赖。silent(--silent) 时 stdio:ignore,不喷日志到可能的控制台。
73
+ info("[shine-code-submit] 安装运行时依赖(bun install)...");
74
+ const stdio = isSilent() ? "ignore" : "inherit";
66
75
  let status = spawnSync(bunPath, ["install", "--frozen-lockfile"], {
67
76
  cwd: target,
68
77
  shell: process.platform === "win32",
69
78
  encoding: "utf8",
70
- stdio: "inherit",
79
+ stdio,
71
80
  }).status;
72
81
  if (status !== 0) {
73
- console.log("[shine-code-submit] --frozen-lockfile 失败,重试普通 bun install");
82
+ info("[shine-code-submit] --frozen-lockfile 失败,重试普通 bun install");
74
83
  status = spawnSync(bunPath, ["install"], {
75
84
  cwd: target,
76
85
  shell: process.platform === "win32",
77
86
  encoding: "utf8",
78
- stdio: "inherit",
87
+ stdio,
79
88
  }).status;
80
89
  if (status !== 0) {
81
90
  throw new Error(`bun install 失败(exit ${status})。请手动在 ${target} 跑 bun install`);
@@ -87,6 +96,18 @@ export function deployPlugin(bunPath: string): string {
87
96
  JSON.stringify({ version: SERVICE_VERSION, installedAt: Date.now() }),
88
97
  "utf8",
89
98
  );
90
- console.log(`[shine-code-submit] 已部署到 ${target}`);
99
+ info(`[shine-code-submit] 已部署到 ${target}`);
91
100
  return target;
92
101
  }
102
+
103
+ /** 同版本已部署?.install-version 的 version === SERVICE_VERSION 即视为已部署。 */
104
+ function sameVersionDeployed(target: string): boolean {
105
+ try {
106
+ const meta = JSON.parse(readFileSync(join(target, ".install-version"), "utf8")) as {
107
+ version?: string;
108
+ };
109
+ return meta.version === SERVICE_VERSION;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
@@ -2,6 +2,7 @@
2
2
  // 改用户 ~/.claude 下的 JSON 全走这套,防止 install 脚本 bug 损坏 claude 配置。
3
3
  import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
4
4
  import { dirname } from "node:path";
5
+ import { warn } from "./log";
5
6
 
6
7
  /** 读 JSON;文件不存在/读失败返回默认;JSON 损坏则备份后返回默认(绝不覆盖原文件)。 */
7
8
  export function readJsonDefault<T>(file: string, def: T): T {
@@ -18,7 +19,7 @@ export function readJsonDefault<T>(file: string, def: T): T {
18
19
  const bak = `${file}.bak-corrupt-${Date.now()}`;
19
20
  try {
20
21
  copyFileSync(file, bak);
21
- console.error(`[shine-code-submit] WARNING: ${file} JSON 损坏,已备份到 ${bak},用默认值继续`);
22
+ warn(`[shine-code-submit] WARNING: ${file} JSON 损坏,已备份到 ${bak},用默认值继续`);
22
23
  } catch {
23
24
  /* 备份失败也继续 */
24
25
  }
@@ -0,0 +1,38 @@
1
+ // 安装器统一日志:支持 --silent(自动更新后台升级用)。
2
+ // info = 进度提示,silent 时整体丢弃(不进窗、不进文件)。
3
+ // warn = 诊断信息(非致命警告 / 致命错误),silent 时改写 LOG_DIR/install.log 保留诊断、不抛窗;
4
+ // 非 silent 时正常打 stderr/stdout。
5
+ // 设计目的:自动更新 spawn 起的安装器即使意外拿到控制台,也不会再喷一坨日志(error.png 那样)。
6
+ import { appendFileSync, mkdirSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { LOG_DIR } from "../shared/paths";
9
+
10
+ let SILENT = false;
11
+
12
+ export function setSilent(b: boolean): void {
13
+ SILENT = b;
14
+ }
15
+
16
+ export function isSilent(): boolean {
17
+ return SILENT;
18
+ }
19
+
20
+ /** 进度提示。silent 时直接丢弃。 */
21
+ export function info(msg: string): void {
22
+ if (SILENT) return;
23
+ console.log(msg);
24
+ }
25
+
26
+ /** 诊断(警告 / 致命错误)。silent 时落 install.log,否则打 stderr。 */
27
+ export function warn(msg: string): void {
28
+ if (!SILENT) {
29
+ console.error(msg);
30
+ return;
31
+ }
32
+ try {
33
+ mkdirSync(LOG_DIR, { recursive: true });
34
+ appendFileSync(join(LOG_DIR, "install.log"), `[${new Date().toISOString()}] ${msg}\n`, "utf8");
35
+ } catch {
36
+ /* 连日志都写不进去就彻底静默——绝不抛窗 */
37
+ }
38
+ }
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // install CLI 入口:install / uninstall / status。由 node 跑(编译成 dist/install.cjs)。
3
3
  // npx shine-code-submit install → 自动装 bun + 部署 plugin + 注册 + 启 daemon + 开 dashboard。
4
+ //
5
+ // --silent:自动更新后台升级用(由 daemon 的 updater.ts 传入)。静默所有进度输出,
6
+ // 诊断/致命错误改落 LOG_DIR/install.log,绝不在用户屏幕弹控制台喷日志(见 error.png 的教训)。
7
+ // --force:强制重新部署 + bun install(绕过同版本幂等短路),排错/手动重装用。
4
8
  import { existsSync, rmSync } from "node:fs";
5
9
  import { join } from "node:path";
6
10
  import { spawn } from "node:child_process";
@@ -11,11 +15,24 @@ import { PUBLIC_BASE_URL, SERVICE_VERSION } from "../shared/config";
11
15
  import { isOursAlive, openBrowser, probeDaemon, stopDaemon } from "../shared/daemonctl";
12
16
  import { ensureDirs } from "../shared/paths";
13
17
  import { readPidFile } from "../shared/pidfile";
18
+ import { info, setSilent, warn } from "./log";
14
19
 
15
- const [, , cmd] = process.argv;
20
+ // 静默 node 自身的 DEP0190 噪音:bun install / 起 daemon 用 spawn(bun,[...],{shell:true})(Windows
21
+ // 下 bun 经 shell 调用更稳),命中 "Passing args to a child process with shell option" 弃用警告。
22
+ // 该警告对功能零影响,但在 --silent 自动更新路径下会污染输出,统一关掉。
23
+ process.noDeprecation = true;
24
+
25
+ const args = process.argv.slice(2);
26
+ // --silent/-s 显式 flag,或 daemon 自动更新经 env SHINE_SILENT=1 触发(env 经 npx/wscript 链无解析歧义,
27
+ // 不依赖 npx 是否吞掉包名后的 --silent flag,跨 npm 版本都稳)。
28
+ const silent = args.includes("--silent") || args.includes("-s") || process.env.SHINE_SILENT === "1";
29
+ const force = args.includes("--force");
30
+ setSilent(silent);
31
+ // cmd = 首个非 flag 参数(跳过 --silent/-s/--force),保留 --version/-v 当命令用。
32
+ const cmd = args.find((a) => a !== "--silent" && a !== "-s" && a !== "--force");
16
33
 
17
34
  main().catch((err) => {
18
- console.error(`[shine-code-submit] ${err instanceof Error ? err.message : String(err)}`);
35
+ warn(`[shine-code-submit] ${err instanceof Error ? err.message : String(err)}`);
19
36
  process.exit(1);
20
37
  });
21
38
 
@@ -41,31 +58,31 @@ async function main(): Promise<void> {
41
58
  }
42
59
 
43
60
  async function runInstall(): Promise<void> {
44
- console.log(`=== shine-code-submit installer v${SERVICE_VERSION} ===`);
61
+ info(`=== shine-code-submit installer v${SERVICE_VERSION} ===`);
45
62
  const bunPath = await ensureBun();
46
- const cachePath = deployPlugin(bunPath);
63
+ const cachePath = deployPlugin(bunPath, { force });
47
64
  registerMarketplace(cachePath);
48
65
  registerPlugin(cachePath);
49
66
  enablePlugin(cachePath);
50
67
  ensureDirs();
51
68
  await startDaemonWithBun(bunPath, cachePath);
52
69
  openDashboard();
53
- console.log("");
54
- console.log("✓ 安装完成。");
55
- console.log(" · 重启 Claude Code 后,/plugin 列表会显示 shine-code-submit(已启用)。");
56
- console.log(" · 开新会话即触发 SessionStart hook,事件出现在 dashboard。");
70
+ info("");
71
+ info("✓ 安装完成。");
72
+ info(" · 重启 Claude Code 后,/plugin 列表会显示 shine-code-submit(已启用)。");
73
+ info(" · 开新会话即触发 SessionStart hook,事件出现在 dashboard。");
57
74
  }
58
75
 
59
76
  async function runUninstall(): Promise<void> {
60
- console.log("=== shine-code-submit uninstaller ===");
77
+ info("=== shine-code-submit uninstaller ===");
61
78
  await stopDaemon();
62
79
  unregisterAll();
63
80
  const target = cacheDir();
64
81
  if (existsSync(target)) {
65
82
  rmSync(target, { recursive: true, force: true });
66
- console.log(`[shine-code-submit] 已删除 ${target}`);
83
+ info(`[shine-code-submit] 已删除 ${target}`);
67
84
  }
68
- console.log("✓ 已卸载。重启 Claude Code 后 /plugin 不再显示。");
85
+ info("✓ 已卸载。重启 Claude Code 后 /plugin 不再显示。");
69
86
  }
70
87
 
71
88
  async function runStatus(): Promise<void> {
@@ -83,14 +100,14 @@ async function runStatus(): Promise<void> {
83
100
  async function startDaemonWithBun(bunPath: string, cachePath: string): Promise<void> {
84
101
  const probe = await probeDaemon();
85
102
  if (probe.alive && probe.version === SERVICE_VERSION) {
86
- console.log(`[shine-code-submit] daemon 已是最新 v${SERVICE_VERSION},跳过启动`);
103
+ info(`[shine-code-submit] daemon 已是最新 v${SERVICE_VERSION},跳过启动`);
87
104
  return;
88
105
  }
89
106
  if (probe.alive) {
90
- console.log(`[shine-code-submit] daemon 旧版 v${probe.version} 运行中,重启到 v${SERVICE_VERSION}...`);
107
+ info(`[shine-code-submit] daemon 旧版 v${probe.version} 运行中,重启到 v${SERVICE_VERSION}...`);
91
108
  await stopDaemon();
92
109
  } else {
93
- console.log("[shine-code-submit] 启动 daemon...");
110
+ info("[shine-code-submit] 启动 daemon...");
94
111
  }
95
112
  const daemonSrc = join(cachePath, "src", "daemon", "main.ts");
96
113
  try {
@@ -103,8 +120,8 @@ async function startDaemonWithBun(bunPath: string, cachePath: string): Promise<v
103
120
  });
104
121
  child.unref();
105
122
  } catch (err) {
106
- console.error(`[shine-code-submit] 启动 daemon 失败:${err instanceof Error ? err.message : err}`);
107
- console.error(" plugin 已注册,Claude Code 重启后 hook 会自动拉起 daemon");
123
+ warn(`[shine-code-submit] 启动 daemon 失败:${err instanceof Error ? err.message : err}`);
124
+ warn(" plugin 已注册,Claude Code 重启后 hook 会自动拉起 daemon");
108
125
  return;
109
126
  }
110
127
  const deadline = Date.now() + 10_000;
@@ -112,11 +129,11 @@ async function startDaemonWithBun(bunPath: string, cachePath: string): Promise<v
112
129
  await sleep(200);
113
130
  const p = await probeDaemon();
114
131
  if (p.alive && p.version === SERVICE_VERSION) {
115
- console.log("[shine-code-submit] daemon 已就绪");
132
+ info("[shine-code-submit] daemon 已就绪");
116
133
  return;
117
134
  }
118
135
  }
119
- console.error(
136
+ warn(
120
137
  "[shine-code-submit] daemon 启动超时(10s)。plugin 已注册,可稍后手动 `shine-code-submit start` 或重启 claude。",
121
138
  );
122
139
  }
@@ -124,7 +141,7 @@ async function startDaemonWithBun(bunPath: string, cachePath: string): Promise<v
124
141
  function openDashboard(): void {
125
142
  const pid = readPidFile();
126
143
  const url = pid ? `${PUBLIC_BASE_URL}/ui?t=${pid.token}` : `${PUBLIC_BASE_URL}/ui`;
127
- console.log(`[shine-code-submit] Dashboard: ${url}`);
144
+ info(`[shine-code-submit] Dashboard: ${url}`);
128
145
  // 自动弹浏览器暂时关闭——Dashboard 链接仍打印在上一行,用户可自行点开。
129
146
  // 想恢复:把下面 try/catch 取消注释(openBrowser(url))。
130
147
  // try {
@@ -139,9 +156,11 @@ function sleep(ms: number): Promise<void> {
139
156
  }
140
157
 
141
158
  function printHelp(): void {
159
+ // help 走 console.log 不受 silent 影响:用户显式打错命令时应看到帮助。
142
160
  console.log(`shine-code-submit <command>
143
161
 
144
162
  install 安装插件(自动装 bun + 部署 + 注册 + 启 daemon + 开 dashboard)
163
+ flags: --silent(静默,自动更新用) --force(强制重装,绕过同版本幂等)
145
164
  uninstall 卸载(停 daemon + 反注册 + 删文件)
146
165
  status 显示 daemon 状态
147
166
 
@@ -5,6 +5,7 @@ import { readJsonDefault, writeJsonAtomicWithBackup } from "./json-safe";
5
5
  import { installedPluginsPath, knownMarketplacesPath, settingsPath } from "./paths";
6
6
  import { MARKETPLACE_NAME, PLUGIN_NAME } from "./deploy";
7
7
  import { SERVICE_VERSION } from "../shared/config";
8
+ import { info, warn } from "./log";
8
9
 
9
10
  /* eslint-disable @typescript-eslint/no-explicit-any -- claude 的 JSON 结构是动态的,用 any 最直接 */
10
11
 
@@ -22,7 +23,7 @@ export function registerMarketplace(cachePath: string): void {
22
23
  const data = readJsonDefault<Record<string, any>>(file, {});
23
24
  const existing = data[MARKETPLACE_NAME];
24
25
  if (existing?.source && existing.source.source !== "directory") {
25
- console.log(
26
+ warn(
26
27
  `[shine-code-submit] WARNING: marketplace "${MARKETPLACE_NAME}" 已存在(source=${existing.source.source}),将覆盖为 directory 源(原文件已备份)`,
27
28
  );
28
29
  }
@@ -33,7 +34,7 @@ export function registerMarketplace(cachePath: string): void {
33
34
  autoUpdate: false,
34
35
  };
35
36
  writeJsonAtomicWithBackup(file, data);
36
- console.log(`[shine-code-submit] marketplace 已注册 → ${file}`);
37
+ info(`[shine-code-submit] marketplace 已注册 → ${file}`);
37
38
  }
38
39
 
39
40
  /** 注册 plugin 到 installed_plugins.json(version 2 结构)。幂等。 */
@@ -58,7 +59,7 @@ export function registerPlugin(cachePath: string): void {
58
59
  },
59
60
  ];
60
61
  writeJsonAtomicWithBackup(file, data);
61
- console.log(`[shine-code-submit] plugin 已注册 → ${file}`);
62
+ info(`[shine-code-submit] plugin 已注册 → ${file}`);
62
63
  }
63
64
 
64
65
  /** 启用 plugin:写 settings.json 的 enabledPlugins + extraKnownMarketplaces。幂等。解 #17832。 */
@@ -76,7 +77,7 @@ export function enablePlugin(cachePath: string): void {
76
77
  };
77
78
  }
78
79
  writeJsonAtomicWithBackup(file, data);
79
- console.log(`[shine-code-submit] 已启用(enabledPlugins)→ ${file}`);
80
+ info(`[shine-code-submit] 已启用(enabledPlugins)→ ${file}`);
80
81
  }
81
82
 
82
83
  /** 反注册:从三处 JSON 移除条目。幂等。 */
@@ -106,5 +107,5 @@ export function unregisterAll(): void {
106
107
  changed = true;
107
108
  }
108
109
  if (changed) writeJsonAtomicWithBackup(settingsPath(), s);
109
- console.log("[shine-code-submit] 已从三处 JSON 移除注册");
110
+ info("[shine-code-submit] 已从三处 JSON 移除注册");
110
111
  }
@@ -4,12 +4,56 @@
4
4
  // 升级链路:npx 拉 latest 包 → install CLI 部署新版到 cache + 注册 → startDaemonWithBun(1.0.5)
5
5
  // 检测旧 daemon 版本不匹配 → stopDaemon 停旧 → 启新 daemon。daemon 退出不影响 npx install(detached)。
6
6
  import { spawn } from "node:child_process";
7
+ import { mkdirSync, writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
7
9
  import { SERVICE_NAME, SERVICE_VERSION } from "./config";
10
+ import { DATA_DIR } from "./paths";
8
11
  import { readSettings, writeSettings } from "../daemon/settings";
9
12
 
10
13
  const REGISTRY_LATEST = `https://registry.npmjs.org/${SERVICE_NAME}/latest`;
11
14
  const NPM_REGISTRY = "https://registry.npmjs.org/";
12
15
 
16
+ /**
17
+ * 后台拉起一次静默自动更新(spawn detached,不阻塞 daemon)。
18
+ * - Windows:wscript VBS 隐藏包装(Run cmd,0 = SW_HIDE),彻底无窗口。直接 spawn("npx",…,{shell:true})
19
+ * 会因 cmd→npx.cmd→孙进程 node 链分配新控制台而在用户屏幕弹安装日志(error.png),windowsHide 管不到孙进程。
20
+ * - mac/linux:npx 有 shebang,直接 spawn(不 shell)即可,顺带消 DEP0190。
21
+ * install 一律带 --silent:安装器自身也不喷日志(幂等短路 + 诊断落 install.log)。
22
+ */
23
+ function spawnSilentInstall(): void {
24
+ const npxArgs = ["--yes", `--registry=${NPM_REGISTRY}`, `${SERVICE_NAME}@latest`, "install", "--silent"];
25
+ // env 经 wscript→cmd→npx→node 链逐级继承,让安装器进 --silent 模式;不依赖 npx 是否吞掉包名后的
26
+ // --silent flag(--silent 是 npx 自身 flag,跨 npm 版本对"包名后已知 flag 是否透传"行为不一)。
27
+ const env = { ...process.env, SHINE_SILENT: "1" };
28
+ if (process.platform === "win32") {
29
+ // 稳定路径,不每次新建;DATA_DIR 即 %LOCALAPPDATA%/shine-code-submit。
30
+ const vbsPath = join(DATA_DIR, "update-hidden.vbs");
31
+ const vbs = [
32
+ `Set s = CreateObject("Wscript.Shell")`,
33
+ `s.Run "cmd /c npx ${npxArgs.join(" ")}", 0, False`,
34
+ ].join("\r\n");
35
+ try {
36
+ mkdirSync(DATA_DIR, { recursive: true });
37
+ writeFileSync(vbsPath, vbs, "utf8");
38
+ } catch {
39
+ /* 写不了 vbs 就放弃本次静默升级,下次 tick 再试——绝不影响 daemon */
40
+ return;
41
+ }
42
+ const wscript = join(process.env.SystemRoot || "C:\\Windows", "system32", "wscript.exe");
43
+ const child = spawn(wscript, [vbsPath], { detached: true, stdio: "ignore", windowsHide: true, env });
44
+ child.on("error", () => {
45
+ /* wscript 缺失/被策略禁用等启动失败:静默放弃本次,下次 tick 再试,绝不拖崩 daemon */
46
+ });
47
+ child.unref();
48
+ return;
49
+ }
50
+ const child = spawn("npx", npxArgs, { detached: true, stdio: "ignore", windowsHide: true, env });
51
+ child.on("error", () => {
52
+ /* npx 不在 PATH 等启动失败:静默放弃本次,下次 tick 再试 */
53
+ });
54
+ child.unref();
55
+ }
56
+
13
57
  /** semver 大于:a > b 才 true(相等 false)。只升级不降级,避免本地比 npm 新(如发版前 build)时误降级。 */
14
58
  function versionGt(a: string, b: string): boolean {
15
59
  const pa = a.split(".").map((x) => parseInt(x, 10) || 0);
@@ -60,12 +104,9 @@ export async function autoUpdateIfNeeded(force = false): Promise<{ updated: bool
60
104
  writeSettings({ ...s, latestVersion: check.latest });
61
105
  }
62
106
  if (!check.hasUpdate) return { updated: false, latest: check.latest };
63
- // 有新版:spawn detached npx install(官方 registry 确保 latest,npmmirror 有同步延迟)
64
- spawn(
65
- "npx",
66
- ["--yes", `--registry=${NPM_REGISTRY}`, `${SERVICE_NAME}@latest`, "install"],
67
- { detached: true, stdio: "ignore", windowsHide: true, shell: true },
68
- ).unref();
107
+ // 有新版:静默 spawn npx install(官方 registry 确保 latest,npmmirror 有同步延迟)
108
+ // Windows 走 wscript VBS 隐藏包装避免弹窗,见 spawnSilentInstall。
109
+ spawnSilentInstall();
69
110
  return { updated: true, latest: check.latest };
70
111
  } catch {
71
112
  return { updated: false };