shine-code-submit 0.2.7 → 0.2.9
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/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/bin/launcher.cjs +54 -22
- package/dist/install.cjs +1 -1
- package/package.json +1 -1
- package/src/shared/config.ts +4 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ npx shine-code-submit install
|
|
|
72
72
|
|
|
73
73
|
### 方式二:`/plugin marketplace add`(从 GitHub)
|
|
74
74
|
|
|
75
|
-
源码直跑,需要 Bun 运行时——**没装也行**:首次 SessionStart 时 `launcher.cjs`
|
|
75
|
+
源码直跑,需要 Bun 运行时——**没装也行**:首次 SessionStart 时 `launcher.cjs` 会自动装(`npm i -g bun`,失败回退官方脚本,约 10-30s;SessionStart 已配 200s 超时兜底,进度见 `~/.local/share/shine-code-submit/log/bun-install.log`)。想首次更快可先手装 `npm install -g bun`,或官方脚本——Windows `powershell -c "irm bun.sh/install.ps1 | iex"`,macOS/Linux `curl -fsSL https://bun.sh/install | bash`。
|
|
76
76
|
|
|
77
77
|
**从 GitHub:**
|
|
78
78
|
|
package/bin/launcher.cjs
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
// shine-code-submit hook 平台分发器(.cjs 强制 CommonJS,兼容所有 node,不依赖 package.json)。
|
|
3
3
|
// Claude Code 经 hooks.json 以 `node launcher.cjs <Event>` 调用(exec form,不经 shell)。
|
|
4
4
|
// 优先 spawn 同目录 bin/<plat>-<arch>/hook[.exe](二进制模式,本机 build 产物);
|
|
5
|
-
// 不存在则 bun run src/hook/main.ts
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// 不存在则 bun run src/hook/main.ts(源码模式)。源码模式需要 Bun——若没装,首次自动安装
|
|
6
|
+
// (npm i -g bun → 失败回退官方脚本)。退出码恒 0——绝不影响 Claude Code 主进程。
|
|
7
|
+
//
|
|
8
|
+
// ⚠️ Claude Code 的 SessionStart hook 把 stdout 当【单个 JSON 对象】解析(提取 systemMessage 展示)。
|
|
9
|
+
// 所以:① 进度/提示绝不能写 stdout(混入纯文本会让整个 stdout JSON 解析失败、链接也不显示)——走 stderr + 日志。
|
|
10
|
+
// ② 装完 Bun 后,把「安装完成」提示与 hook 产出的 Dashboard 链接【合并成一条 systemMessage】发 stdout,
|
|
11
|
+
// 确保交互式 claude 里一定能看到(systemMessage 字段已被验证会显示)。
|
|
8
12
|
const { spawn, spawnSync } = require("node:child_process");
|
|
9
13
|
const readline = require("node:readline");
|
|
10
14
|
const { existsSync, mkdirSync, appendFileSync } = require("node:fs");
|
|
@@ -24,7 +28,7 @@ const SHELL = process.platform === "win32";
|
|
|
24
28
|
|
|
25
29
|
/** 找 bun:先 PATH,再常见安装位置(官方脚本装到 ~/.bun/bin,npm -g 装到全局 bin)。 */
|
|
26
30
|
function findBun() {
|
|
27
|
-
// shell 模式用单字符串(避免 Node 的 "args + shell:true" 弃用警告污染
|
|
31
|
+
// shell 模式用单字符串(避免 Node 的 "args + shell:true" 弃用警告污染 stderr)
|
|
28
32
|
const r = SHELL
|
|
29
33
|
? spawnSync("bun --version", { shell: true, encoding: "utf8" })
|
|
30
34
|
: spawnSync("bun", ["--version"], { encoding: "utf8" });
|
|
@@ -44,8 +48,8 @@ function logFile() {
|
|
|
44
48
|
return join(dir, "bun-install.log");
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
/** 跑一条 shell 命令,stdout/stderr 逐行流式:写日志 +(仅 SessionStart)转发到 hook
|
|
48
|
-
function streamCmd(cmd, file,
|
|
51
|
+
/** 跑一条 shell 命令,stdout/stderr 逐行流式:写日志 +(仅 SessionStart)转发到 hook stderr。 */
|
|
52
|
+
function streamCmd(cmd, file, toStderr) {
|
|
49
53
|
return new Promise((resolve) => {
|
|
50
54
|
const w = (s) => { try { appendFileSync(file, s); } catch {} };
|
|
51
55
|
w(`\n[${new Date().toISOString()}] $ ${cmd}\n`);
|
|
@@ -61,7 +65,7 @@ function streamCmd(cmd, file, toStdout) {
|
|
|
61
65
|
if (!stream) continue;
|
|
62
66
|
readline.createInterface({ input: stream, crlfDelay: Infinity }).on("line", (line) => {
|
|
63
67
|
w(line + "\n");
|
|
64
|
-
if (
|
|
68
|
+
if (toStderr) process.stderr.write(line + "\n");
|
|
65
69
|
});
|
|
66
70
|
}
|
|
67
71
|
child.on("error", () => { w("[child error]\n"); resolve(1); });
|
|
@@ -69,10 +73,10 @@ function streamCmd(cmd, file, toStdout) {
|
|
|
69
73
|
});
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
/** 装 bun:npm i -g bun →
|
|
73
|
-
async function installBun(
|
|
76
|
+
/** 装 bun:npm i -g bun → 失败回退官方脚本。每步逐行流式(stderr + 日志)。返回 bun 路径或 null。 */
|
|
77
|
+
async function installBun(toStderr) {
|
|
74
78
|
const file = logFile();
|
|
75
|
-
const step = async (cmd) => { await streamCmd(cmd, file,
|
|
79
|
+
const step = async (cmd) => { await streamCmd(cmd, file, toStderr); return findBun(); };
|
|
76
80
|
let b = await step("npm install -g bun");
|
|
77
81
|
if (b) return b;
|
|
78
82
|
const official = process.platform === "win32"
|
|
@@ -81,34 +85,62 @@ async function installBun(toStdout) {
|
|
|
81
85
|
return step(official);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
/** 只发一条 systemMessage 到 stdout(Claude Code 把 systemMessage 显示给用户)。 */
|
|
89
|
+
function tellUser(msg) {
|
|
90
|
+
process.stdout.write(JSON.stringify({ systemMessage: msg }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** spawn 子进程、inherit stdio、子退出即本进程退出(二进制模式 / 有 Bun 的源码模式用)。 */
|
|
94
|
+
function runInherit(cmd, args) {
|
|
95
|
+
const child = spawn(cmd, args, { stdio: "inherit", shell: SHELL && cmd !== hookBin });
|
|
86
96
|
child.on("error", () => process.exit(0));
|
|
87
97
|
child.on("exit", () => process.exit(0));
|
|
88
98
|
}
|
|
89
99
|
|
|
100
|
+
/**
|
|
101
|
+
* 刚装完 Bun 时跑 hook:捕获 hook 的 stdout(其中的 {systemMessage: Dashboard 链接}),与「安装完成」
|
|
102
|
+
* 提示合并成【一条】systemMessage 再发 stdout。为什么不直接 inherit:若额外打一行纯文本提示会污染
|
|
103
|
+
* stdout、让 JSON 解析失败;所以合并成单个 JSON,确保用户一定能看到「装好了 + 链接」。
|
|
104
|
+
*/
|
|
105
|
+
function runHookMerged(bun, installNote) {
|
|
106
|
+
const child = spawn(bun, ["run", hookSrc, ...argv], { stdio: ["inherit", "pipe", "inherit"], shell: SHELL });
|
|
107
|
+
let out = "";
|
|
108
|
+
child.stdout.on("data", (d) => { out += d.toString(); });
|
|
109
|
+
child.on("error", () => { tellUser(installNote); process.exit(0); });
|
|
110
|
+
child.on("exit", () => {
|
|
111
|
+
let linkMsg = "";
|
|
112
|
+
try { linkMsg = JSON.parse((out || "").trim()).systemMessage || ""; } catch {}
|
|
113
|
+
tellUser(linkMsg ? `${installNote}\n${linkMsg}` : installNote);
|
|
114
|
+
process.exit(0);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
90
118
|
(async () => {
|
|
91
119
|
try {
|
|
92
120
|
if (existsSync(hookBin)) {
|
|
93
|
-
// 二进制模式:spawn 本地已 build 的 hook
|
|
94
|
-
|
|
121
|
+
// 二进制模式:spawn 本地已 build 的 hook(不经 bun、不需 shell)
|
|
122
|
+
runInherit(hookBin, argv);
|
|
95
123
|
return;
|
|
96
124
|
}
|
|
97
|
-
// 源码模式:bun run src/hook/main.ts
|
|
125
|
+
// 源码模式:bun run src/hook/main.ts
|
|
98
126
|
let bun = findBun();
|
|
99
127
|
if (!bun) {
|
|
100
|
-
// 只在 SessionStart 把进度打到 stdout(其它 hook 的 stdout 可能被 Claude Code 按 JSON 解析)
|
|
101
128
|
const show = event === "SessionStart";
|
|
102
129
|
if (show) {
|
|
103
|
-
console.
|
|
104
|
-
console.
|
|
105
|
-
console.
|
|
130
|
+
console.error("");
|
|
131
|
+
console.error("⏳ shine-code-submit: 未检测到 Bun 运行时,首次自动安装中(约 10-30s)");
|
|
132
|
+
console.error(" 实时进度可另开终端: tail -f " + logFile());
|
|
106
133
|
}
|
|
107
134
|
bun = await installBun(show);
|
|
108
|
-
if (
|
|
135
|
+
if (!bun) {
|
|
136
|
+
// 失败也走 systemMessage,确保用户看到(而不是静默)
|
|
137
|
+
if (show) tellUser("❌ shine-code-submit: Bun 自动安装失败。请手动装 Bun(https://bun.sh)后重开会话;事件不丢。");
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
// 装好了:合并「安装完成 + Dashboard 链接」为一条 systemMessage,确保用户看到
|
|
141
|
+
if (show) { runHookMerged(bun, "✅ shine-code-submit: 已自动安装 Bun 运行时,继续启动。"); return; }
|
|
109
142
|
}
|
|
110
|
-
|
|
111
|
-
runChild(bun, ["run", hookSrc, ...argv], { stdio: "inherit", shell: SHELL });
|
|
143
|
+
runInherit(bun, ["run", hookSrc, ...argv]);
|
|
112
144
|
} catch {
|
|
113
145
|
process.exit(0);
|
|
114
146
|
}
|
package/dist/install.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var w=require("node:fs"),Xb=require("node:path"),Yb=require("node:child_process");var f=require("node:fs"),m=require("node:os"),y=require("node:path"),j=require("node:child_process"),Gb=[1,1,0],Hb=300000;function D(){let b=j.spawnSync("bun",["--version"],{shell:process.platform==="win32",encoding:"utf8"});if(b.status===0&&(b.stdout??"").trim())return"bun";let q=m.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(f.existsSync(Q))return Q;return null}function Wb(b){return b.trim().split(".").map((q)=>parseInt(q,10)||0)}function Jb(b,q){let z=Wb(b);for(let Q=0;Q<q.length;Q++){let X=z[Q]??0,J=q[Q]??0;if(X>J)return!0;if(X<J)return!1}return!0}function Kb(){let b=process.env.npm_config_registry??"";return/npmmirror|taobao/i.test(b)}function M(b){return j.spawnSync(b,{shell:!0,encoding:"utf8",timeout:Hb,stdio:"inherit"}).status??1}async function E(){let b=D();if(b){let z=j.spawnSync(b,["--version"],{shell:process.platform==="win32",encoding:"utf8"}).stdout?.trim()??"";if(z&&Jb(z,Gb))return console.log(`[shine-code-submit] bun ${z} detected`),b}if(console.log("[shine-code-submit] bun 未找到或版本过低,开始自动安装..."),Kb()){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 Z=require("node:fs"),Y=require("node:path"),g=require("node:child_process");var S=require("node:os"),V=require("node:path");function p(){return process.env.CLAUDE_CONFIG_DIR||V.join(S.homedir(),".claude")}function u(){return V.join(p(),"plugins")}function x(){return V.join(u(),"known_marketplaces.json")}function O(){return V.join(u(),"installed_plugins.json")}function v(){return V.join(p(),"settings.json")}var A={name:"shine-code-submit",version:"0.2.7",private:!1,type:"module",description:"Claude Code Hook -> 本地常驻 Daemon 的状态/持久化底座",author:"renguifeng",license:"MIT",repository:{type:"git",url:"https://github.com/renguifeng/shine-code-submit.git"},homepage:"https://github.com/renguifeng/shine-code-submit",bugs:{url:"https://github.com/renguifeng/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 P=require("node:os"),h="shine-code-submit",W=A.version,Rb=process.env.SHINE_CODE_SUBMIT_HOST??"0.0.0.0",Vb="127.0.0.1",c=36666,B=`http://${Vb}:${c}`;function Ub(){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=P.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 N=`http://${Ub()}:${c}`;var G="shine-code-submit",I="shine-code-submit";function L(b=W){return Y.join(u(),"cache",G,I,b)}function Cb(){let b=process.argv[1],q=process.cwd();if(b)try{q=Y.dirname(Z.realpathSync(Y.resolve(b)))}catch{q=Y.dirname(Y.resolve(b))}let z=q;for(let Q=0;Q<12;Q++){if(Z.existsSync(Y.join(z,"package.json"))&&Z.existsSync(Y.join(z,".claude-plugin")))return z;let X=Y.dirname(z);if(X===z)break;z=X}return Y.dirname(q)}var jb=[".claude-plugin","hooks","bin","src","ui","package.json","bun.lock","README.md"];function l(b){let q=L();if(Z.existsSync(q))Z.rmSync(q,{recursive:!0,force:!0});Z.mkdirSync(q,{recursive:!0});let z=Cb();console.log(`[shine-code-submit] 部署源:${z}`);for(let X of jb){let J=Y.join(z,X);if(!Z.existsSync(J))continue;Z.cpSync(J,Y.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 Z.writeFileSync(Y.join(q,".install-version"),JSON.stringify({version:W,installedAt:Date.now()}),"utf8"),console.log(`[shine-code-submit] 已部署到 ${q}`),q}var $=require("node:fs"),d=require("node:path");function K(b,q){if(!$.existsSync(b))return q;let z;try{z=$.readFileSync(b,"utf8")}catch{return q}try{return JSON.parse(z)}catch{let Q=`${b}.bak-corrupt-${Date.now()}`;try{$.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($.existsSync(b)&&!$.existsSync(z))try{$.copyFileSync(b,z)}catch{}$.mkdirSync(d.dirname(b),{recursive:!0});let Q=`${b}.tmp-${process.pid}`;$.writeFileSync(Q,JSON.stringify(q,null,2),"utf8"),$.renameSync(Q,b)}function R(){return`${I}@${G}`}function o(b){let q=x(),z=K(q,{}),Q=z[G];if(Q?.source&&Q.source.source!=="directory")console.log(`[shine-code-submit] WARNING: marketplace "${G}" 已存在(source=${Q.source.source}),将覆盖为 directory 源(原文件已备份)`);z[G]={source:{source:"directory",path:b},installLocation:b,lastUpdated:new Date().toISOString(),autoUpdate:!1},F(q,z),console.log(`[shine-code-submit] marketplace 已注册 → ${q}`)}function s(b){let q=O(),z=K(q,{version:2,plugins:{}});if(!z.version)z.version=2;if(!z.plugins)z.plugins={};let Q=R(),X=new Date().toISOString(),J=z.plugins[Q]?.[0];z.plugins[Q]=[{scope:"user",installPath:b,version:W,installedAt:J?.installedAt??X,lastUpdated:X}],F(q,z),console.log(`[shine-code-submit] plugin 已注册 → ${q}`)}function r(b){let q=v(),z=K(q,{});if(!z.enabledPlugins)z.enabledPlugins={};let Q=R();if(z.enabledPlugins[Q]=!0,!z.extraKnownMarketplaces)z.extraKnownMarketplaces={};if(!z.extraKnownMarketplaces[G])z.extraKnownMarketplaces[G]={source:{source:"directory",path:b}};F(q,z),console.log(`[shine-code-submit] 已启用(enabledPlugins)→ ${q}`)}function i(){let b=R(),q=K(x(),{});if(q[G])delete q[G],F(x(),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[G])delete Q.extraKnownMarketplaces[G],X=!0;if(X)F(v(),Q);console.log("[shine-code-submit] 已从三处 JSON 移除注册")}var t=require("node:child_process"),_=require("node:fs");async function U(b=400){try{let q=await fetch(`${B}/api/health`,{signal:AbortSignal.timeout(b)});if(!q.ok)return!1;return(await q.json())?.service===h}catch{return!1}}function n(b){let q=process.platform,z,Q;if(q==="win32")z="cmd",Q=["/c","start","",b];else if(q==="darwin")z="open",Q=[b];else if(ub())z="cmd.exe",Q=["/c","start","",b];else z="xdg-open",Q=[b];try{t.spawn(z,Q,{detached:!0,stdio:"ignore"}).unref()}catch{}}function ub(){if(process.platform!=="linux")return!1;try{return _.readFileSync("/proc/version","utf8").toLowerCase().includes("microsoft")}catch{return!1}}var H=require("node:path"),a=require("node:fs"),e=require("node:os"),xb=process.env.LOCALAPPDATA??H.join(e.homedir(),".local","share"),C=H.join(xb,"shine-code-submit"),Ob=H.join(C,"spool"),bb=H.join(C,"log"),qb=H.join(C,"db"),zb=H.join(C,"daemon.pid"),rb=H.join(bb,"daemon.log"),ib=H.join(qb,"events.sqlite");function Qb(){for(let b of[C,Ob,bb,qb])a.mkdirSync(b,{recursive:!0})}var k=require("node:fs");function T(){try{let b=k.readFileSync(zb,"utf8");return JSON.parse(b)}catch{return null}}var[,,Zb]=process.argv;vb().catch((b)=>{console.error(`[shine-code-submit] ${b instanceof Error?b.message:String(b)}`),process.exit(1)});async function vb(){switch(Zb){case void 0:case"install":await Bb();break;case"uninstall":await Nb();break;case"status":await kb();break;case"--version":case"-v":console.log(W);break;default:Mb()}}async function Bb(){console.log(`=== shine-code-submit installer v${W} ===`);let b=await E(),q=l(b);o(q),s(q),r(q),Qb(),await Tb(b,q),Db(),console.log(""),console.log("✓ 安装完成。"),console.log(" · 重启 Claude Code 后,/plugin 列表会显示 shine-code-submit(已启用)。"),console.log(" · 开新会话即触发 SessionStart hook,事件出现在 dashboard。")}async function Nb(){console.log("=== shine-code-submit uninstaller ==="),await wb(),i();let b=L();if(w.existsSync(b))w.rmSync(b,{recursive:!0,force:!0}),console.log(`[shine-code-submit] 已删除 ${b}`);console.log("✓ 已卸载。重启 Claude Code 后 /plugin 不再显示。")}async function kb(){let b=await U(),q=T();if(b&&q)console.log(`daemon: running pid=${q.pid} ${N}`);else console.log("daemon: not running")}async function Tb(b,q){if(await U()){console.log("[shine-code-submit] daemon 已在运行,跳过启动");return}let z=Xb.join(q,"src","daemon","main.ts");console.log("[shine-code-submit] 启动 daemon...");try{Yb.spawn(b,["run",z],{detached:!0,stdio:"ignore",windowsHide:!0,cwd:q,shell:process.platform==="win32"}).unref()}catch(X){console.error(`[shine-code-submit] 启动 daemon 失败:${X instanceof Error?X.message:X}`),console.error(" plugin 已注册,Claude Code 重启后 hook 会自动拉起 daemon");return}let Q=Date.now()+1e4;while(Date.now()<Q)if(await $b(200),await U()){console.log("[shine-code-submit] daemon 已就绪");return}console.error("[shine-code-submit] daemon 启动超时(10s)。plugin 已注册,可稍后手动 `shine-code-submit start` 或重启 claude。")}async function wb(){let b=T();if(!b){console.log("[shine-code-submit] daemon 未运行(无 pid 文件)");return}if(await U()){try{await fetch(`${B}/api/shutdown`,{method:"POST",headers:{authorization:`Bearer ${b.token}`}})}catch{}if(await $b(1000),await U())try{process.kill(b.pid)}catch{}}console.log("[shine-code-submit] daemon 已停止")}function Db(){let b=T(),q=b?`${N}/ui?t=${b.token}`:`${N}/ui`;console.log(`[shine-code-submit] Dashboard: ${q}`);try{n(q)}catch{}}function $b(b){return new Promise((q)=>setTimeout(q,b))}function Mb(){console.log(`shine-code-submit <command>
|
|
2
|
+
var w=require("node:fs"),Xb=require("node:path"),Yb=require("node:child_process");var f=require("node:fs"),m=require("node:os"),y=require("node:path"),j=require("node:child_process"),Gb=[1,1,0],Hb=300000;function D(){let b=j.spawnSync("bun",["--version"],{shell:process.platform==="win32",encoding:"utf8"});if(b.status===0&&(b.stdout??"").trim())return"bun";let q=m.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(f.existsSync(Q))return Q;return null}function Wb(b){return b.trim().split(".").map((q)=>parseInt(q,10)||0)}function Jb(b,q){let z=Wb(b);for(let Q=0;Q<q.length;Q++){let X=z[Q]??0,J=q[Q]??0;if(X>J)return!0;if(X<J)return!1}return!0}function Kb(){let b=process.env.npm_config_registry??"";return/npmmirror|taobao/i.test(b)}function M(b){return j.spawnSync(b,{shell:!0,encoding:"utf8",timeout:Hb,stdio:"inherit"}).status??1}async function E(){let b=D();if(b){let z=j.spawnSync(b,["--version"],{shell:process.platform==="win32",encoding:"utf8"}).stdout?.trim()??"";if(z&&Jb(z,Gb))return console.log(`[shine-code-submit] bun ${z} detected`),b}if(console.log("[shine-code-submit] bun 未找到或版本过低,开始自动安装..."),Kb()){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 Z=require("node:fs"),Y=require("node:path"),g=require("node:child_process");var S=require("node:os"),V=require("node:path");function p(){return process.env.CLAUDE_CONFIG_DIR||V.join(S.homedir(),".claude")}function u(){return V.join(p(),"plugins")}function x(){return V.join(u(),"known_marketplaces.json")}function O(){return V.join(u(),"installed_plugins.json")}function v(){return V.join(p(),"settings.json")}var A={name:"shine-code-submit",version:"0.2.9",private:!1,type:"module",description:"Claude Code Hook -> 本地常驻 Daemon 的状态/持久化底座",author:"renguifeng",license:"MIT",repository:{type:"git",url:"https://github.com/renguifeng/shine-code-submit.git"},homepage:"https://github.com/renguifeng/shine-code-submit",bugs:{url:"https://github.com/renguifeng/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 P=require("node:os"),h="shine-code-submit",W=A.version,Rb=process.env.SHINE_CODE_SUBMIT_HOST??"0.0.0.0",Vb="127.0.0.1",c=36666,B=`http://${Vb}:${c}`;function Ub(){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=P.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 N=`http://${Ub()}:${c}`;var G="shine-code-submit",I="shine-code-submit";function L(b=W){return Y.join(u(),"cache",G,I,b)}function Cb(){let b=process.argv[1],q=process.cwd();if(b)try{q=Y.dirname(Z.realpathSync(Y.resolve(b)))}catch{q=Y.dirname(Y.resolve(b))}let z=q;for(let Q=0;Q<12;Q++){if(Z.existsSync(Y.join(z,"package.json"))&&Z.existsSync(Y.join(z,".claude-plugin")))return z;let X=Y.dirname(z);if(X===z)break;z=X}return Y.dirname(q)}var jb=[".claude-plugin","hooks","bin","src","ui","package.json","bun.lock","README.md"];function l(b){let q=L();if(Z.existsSync(q))Z.rmSync(q,{recursive:!0,force:!0});Z.mkdirSync(q,{recursive:!0});let z=Cb();console.log(`[shine-code-submit] 部署源:${z}`);for(let X of jb){let J=Y.join(z,X);if(!Z.existsSync(J))continue;Z.cpSync(J,Y.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 Z.writeFileSync(Y.join(q,".install-version"),JSON.stringify({version:W,installedAt:Date.now()}),"utf8"),console.log(`[shine-code-submit] 已部署到 ${q}`),q}var $=require("node:fs"),d=require("node:path");function K(b,q){if(!$.existsSync(b))return q;let z;try{z=$.readFileSync(b,"utf8")}catch{return q}try{return JSON.parse(z)}catch{let Q=`${b}.bak-corrupt-${Date.now()}`;try{$.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($.existsSync(b)&&!$.existsSync(z))try{$.copyFileSync(b,z)}catch{}$.mkdirSync(d.dirname(b),{recursive:!0});let Q=`${b}.tmp-${process.pid}`;$.writeFileSync(Q,JSON.stringify(q,null,2),"utf8"),$.renameSync(Q,b)}function R(){return`${I}@${G}`}function o(b){let q=x(),z=K(q,{}),Q=z[G];if(Q?.source&&Q.source.source!=="directory")console.log(`[shine-code-submit] WARNING: marketplace "${G}" 已存在(source=${Q.source.source}),将覆盖为 directory 源(原文件已备份)`);z[G]={source:{source:"directory",path:b},installLocation:b,lastUpdated:new Date().toISOString(),autoUpdate:!1},F(q,z),console.log(`[shine-code-submit] marketplace 已注册 → ${q}`)}function s(b){let q=O(),z=K(q,{version:2,plugins:{}});if(!z.version)z.version=2;if(!z.plugins)z.plugins={};let Q=R(),X=new Date().toISOString(),J=z.plugins[Q]?.[0];z.plugins[Q]=[{scope:"user",installPath:b,version:W,installedAt:J?.installedAt??X,lastUpdated:X}],F(q,z),console.log(`[shine-code-submit] plugin 已注册 → ${q}`)}function r(b){let q=v(),z=K(q,{});if(!z.enabledPlugins)z.enabledPlugins={};let Q=R();if(z.enabledPlugins[Q]=!0,!z.extraKnownMarketplaces)z.extraKnownMarketplaces={};if(!z.extraKnownMarketplaces[G])z.extraKnownMarketplaces[G]={source:{source:"directory",path:b}};F(q,z),console.log(`[shine-code-submit] 已启用(enabledPlugins)→ ${q}`)}function i(){let b=R(),q=K(x(),{});if(q[G])delete q[G],F(x(),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[G])delete Q.extraKnownMarketplaces[G],X=!0;if(X)F(v(),Q);console.log("[shine-code-submit] 已从三处 JSON 移除注册")}var t=require("node:child_process"),_=require("node:fs");async function U(b=400){try{let q=await fetch(`${B}/api/health`,{signal:AbortSignal.timeout(b)});if(!q.ok)return!1;return(await q.json())?.service===h}catch{return!1}}function n(b){let q=process.platform,z,Q;if(q==="win32")z="cmd",Q=["/c","start","",b];else if(q==="darwin")z="open",Q=[b];else if(ub())z="cmd.exe",Q=["/c","start","",b];else z="xdg-open",Q=[b];try{t.spawn(z,Q,{detached:!0,stdio:"ignore"}).unref()}catch{}}function ub(){if(process.platform!=="linux")return!1;try{return _.readFileSync("/proc/version","utf8").toLowerCase().includes("microsoft")}catch{return!1}}var H=require("node:path"),a=require("node:fs"),e=require("node:os"),xb=process.env.LOCALAPPDATA??H.join(e.homedir(),".local","share"),C=H.join(xb,"shine-code-submit"),Ob=H.join(C,"spool"),bb=H.join(C,"log"),qb=H.join(C,"db"),zb=H.join(C,"daemon.pid"),rb=H.join(bb,"daemon.log"),ib=H.join(qb,"events.sqlite");function Qb(){for(let b of[C,Ob,bb,qb])a.mkdirSync(b,{recursive:!0})}var k=require("node:fs");function T(){try{let b=k.readFileSync(zb,"utf8");return JSON.parse(b)}catch{return null}}var[,,Zb]=process.argv;vb().catch((b)=>{console.error(`[shine-code-submit] ${b instanceof Error?b.message:String(b)}`),process.exit(1)});async function vb(){switch(Zb){case void 0:case"install":await Bb();break;case"uninstall":await Nb();break;case"status":await kb();break;case"--version":case"-v":console.log(W);break;default:Mb()}}async function Bb(){console.log(`=== shine-code-submit installer v${W} ===`);let b=await E(),q=l(b);o(q),s(q),r(q),Qb(),await Tb(b,q),Db(),console.log(""),console.log("✓ 安装完成。"),console.log(" · 重启 Claude Code 后,/plugin 列表会显示 shine-code-submit(已启用)。"),console.log(" · 开新会话即触发 SessionStart hook,事件出现在 dashboard。")}async function Nb(){console.log("=== shine-code-submit uninstaller ==="),await wb(),i();let b=L();if(w.existsSync(b))w.rmSync(b,{recursive:!0,force:!0}),console.log(`[shine-code-submit] 已删除 ${b}`);console.log("✓ 已卸载。重启 Claude Code 后 /plugin 不再显示。")}async function kb(){let b=await U(),q=T();if(b&&q)console.log(`daemon: running pid=${q.pid} ${N}`);else console.log("daemon: not running")}async function Tb(b,q){if(await U()){console.log("[shine-code-submit] daemon 已在运行,跳过启动");return}let z=Xb.join(q,"src","daemon","main.ts");console.log("[shine-code-submit] 启动 daemon...");try{Yb.spawn(b,["run",z],{detached:!0,stdio:"ignore",windowsHide:!0,cwd:q,shell:process.platform==="win32"}).unref()}catch(X){console.error(`[shine-code-submit] 启动 daemon 失败:${X instanceof Error?X.message:X}`),console.error(" plugin 已注册,Claude Code 重启后 hook 会自动拉起 daemon");return}let Q=Date.now()+1e4;while(Date.now()<Q)if(await $b(200),await U()){console.log("[shine-code-submit] daemon 已就绪");return}console.error("[shine-code-submit] daemon 启动超时(10s)。plugin 已注册,可稍后手动 `shine-code-submit start` 或重启 claude。")}async function wb(){let b=T();if(!b){console.log("[shine-code-submit] daemon 未运行(无 pid 文件)");return}if(await U()){try{await fetch(`${B}/api/shutdown`,{method:"POST",headers:{authorization:`Bearer ${b.token}`}})}catch{}if(await $b(1000),await U())try{process.kill(b.pid)}catch{}}console.log("[shine-code-submit] daemon 已停止")}function Db(){let b=T(),q=b?`${N}/ui?t=${b.token}`:`${N}/ui`;console.log(`[shine-code-submit] Dashboard: ${q}`);try{n(q)}catch{}}function $b(b){return new Promise((q)=>setTimeout(q,b))}function Mb(){console.log(`shine-code-submit <command>
|
|
3
3
|
|
|
4
4
|
install 安装插件(自动装 bun + 部署 + 注册 + 启 daemon + 开 dashboard)
|
|
5
5
|
uninstall 卸载(停 daemon + 反注册 + 删文件)
|
package/package.json
CHANGED
package/src/shared/config.ts
CHANGED
|
@@ -55,7 +55,10 @@ export const PUBLIC_BASE_URL = `http://${getPrimaryIpv4()}:${PORT}`; // 打印
|
|
|
55
55
|
export const HOOK_POST_TIMEOUT_MS = 500;
|
|
56
56
|
|
|
57
57
|
// 故障路径:detached 拉起 Daemon 后轮询 /api/health 的总预算与间隔。
|
|
58
|
-
|
|
58
|
+
// 源码模式下首次 SessionStart 冷启动 daemon(bun run 首次 transpile TS + 加载 react/sqlite)
|
|
59
|
+
// 可能 >5s;预算太短会等不到 ready → readToken 空 → 首次会话不打印 Dashboard 链接(得重启才出)。
|
|
60
|
+
// 15s 覆盖冷启动;warm 启动 isOursAlive 立即命中,不会真等满。
|
|
61
|
+
export const HEALTH_POLL_TIMEOUT_MS = 15000;
|
|
59
62
|
export const HEALTH_POLL_INTERVAL_MS = 100;
|
|
60
63
|
|
|
61
64
|
// Daemon 回捞 spool 的扫描间隔。
|