shine-code-submit 0.2.0
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/marketplace.json +13 -0
- package/.claude-plugin/plugin.json +10 -0
- package/LICENSE +21 -0
- package/README.md +217 -0
- package/bin/launcher.cjs +35 -0
- package/bun.lock +45 -0
- package/dist/install.cjs +8 -0
- package/hooks/hooks.json +81 -0
- package/package.json +68 -0
- package/src/cli/main.ts +128 -0
- package/src/daemon/auth.ts +7 -0
- package/src/daemon/bus.ts +25 -0
- package/src/daemon/git.ts +118 -0
- package/src/daemon/logger.ts +66 -0
- package/src/daemon/main.ts +95 -0
- package/src/daemon/server.ts +208 -0
- package/src/daemon/spool-consumer.ts +52 -0
- package/src/daemon/stats.ts +33 -0
- package/src/daemon/store.ts +147 -0
- package/src/daemon/token-cache.ts +35 -0
- package/src/daemon/transcript.ts +130 -0
- package/src/daemon/ui-assets.ts +4 -0
- package/src/daemon/ui.ts +34 -0
- package/src/daemon/ws.ts +54 -0
- package/src/hook/main.ts +154 -0
- package/src/install/bun.ts +97 -0
- package/src/install/deploy.ts +78 -0
- package/src/install/json-safe.ts +43 -0
- package/src/install/main.ts +167 -0
- package/src/install/paths.ts +28 -0
- package/src/install/register.ts +108 -0
- package/src/shared/config.ts +77 -0
- package/src/shared/daemonctl.ts +110 -0
- package/src/shared/id.ts +19 -0
- package/src/shared/paths.ts +23 -0
- package/src/shared/pidfile.ts +32 -0
- package/src/shared/spool.ts +60 -0
- package/src/shared/types.ts +116 -0
- package/ui/.build/app.js +76 -0
- package/ui/app.tsx +31 -0
- package/ui/components/App.tsx +55 -0
- package/ui/components/CommitsModule.tsx +25 -0
- package/ui/components/CommitsView.tsx +84 -0
- package/ui/components/Conversation.tsx +101 -0
- package/ui/components/DiffBlock.tsx +64 -0
- package/ui/components/EventDetail.tsx +8 -0
- package/ui/components/EventItem.tsx +24 -0
- package/ui/components/EventList.tsx +22 -0
- package/ui/components/EventsModule.tsx +123 -0
- package/ui/components/Header.tsx +15 -0
- package/ui/components/Icon.tsx +114 -0
- package/ui/components/Markdown.tsx +13 -0
- package/ui/components/Message.tsx +60 -0
- package/ui/components/OverviewModule.tsx +86 -0
- package/ui/components/SessionDetail.tsx +51 -0
- package/ui/components/SessionTree.tsx +94 -0
- package/ui/components/SessionsModule.tsx +39 -0
- package/ui/components/SessionsPanel.tsx +15 -0
- package/ui/components/SideNav.tsx +46 -0
- package/ui/components/Splitter.tsx +23 -0
- package/ui/components/StatsModule.tsx +133 -0
- package/ui/components/Status.tsx +66 -0
- package/ui/components/SummaryView.tsx +83 -0
- package/ui/components/SystemModule.tsx +38 -0
- package/ui/components/ToolCard.tsx +57 -0
- package/ui/hooks/useAllCommits.ts +59 -0
- package/ui/hooks/useApi.ts +13 -0
- package/ui/hooks/useCommits.ts +45 -0
- package/ui/hooks/useConversation.ts +43 -0
- package/ui/hooks/useEvents.ts +45 -0
- package/ui/hooks/useSplitter.ts +43 -0
- package/ui/hooks/useStatsPolling.ts +35 -0
- package/ui/hooks/useWebSocket.ts +38 -0
- package/ui/index.html +13 -0
- package/ui/lib/aggregate.ts +81 -0
- package/ui/lib/diff.ts +45 -0
- package/ui/lib/export.ts +45 -0
- package/ui/lib/format.ts +100 -0
- package/ui/lib/util.ts +106 -0
- package/ui/state/AppContext.tsx +65 -0
- package/ui/style.css +849 -0
- package/ui/types.ts +46 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shine-code-submit",
|
|
3
|
+
"version": "0.1.13",
|
|
4
|
+
"description": "Claude Code Hook → 本地常驻 Daemon 的状态/持久化底座。Hook 只做采集+落盘+转发立即退出,重活交给后台 Daemon 异步处理,不拖慢 Claude Code。",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "renguifeng"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "http://8.130.168.121:9001/AI/shine-code-submit",
|
|
9
|
+
"repository": "http://8.130.168.121:9001/AI/shine-code-submit.git"
|
|
10
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 renguifeng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Shine Code Submit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
登录npm官方地址: npm login --registry=https://registry.npmjs.org/
|
|
5
|
+
|
|
6
|
+
npm config set //registry.npmjs.org/:_authToken "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" 粘贴你的新token
|
|
7
|
+
Claude Code Hook → 本地常驻 Daemon 的状态/持久化底座。Hook 只做「采集 + 落盘 + 转发」立即退出,重活交给后台 Daemon 异步处理,不拖慢 Claude Code。详见 [`设计文档.md`](./设计文档.md)。
|
|
8
|
+
|
|
9
|
+
以 **Claude Code Plugin** 形式分发——`/plugin install` 一键安装,跨平台(Windows/macOS/Linux × x64/arm64)。
|
|
10
|
+
|
|
11
|
+
claude --resume 964154b8-3a68-4b4d-ae43-10205ff50978
|
|
12
|
+
## 架构
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Claude Code ──事件──▶ node launcher.js ──spawn──▶ hook(短命) ──┬── POST(热路径) ──▶ Daemon(常驻)
|
|
16
|
+
└── spool 落盘 ──▶ (回捞兜底)
|
|
17
|
+
├── SQLite(幂等)
|
|
18
|
+
├── WS 推送
|
|
19
|
+
└── 查看页 /ui
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
可靠性:异步 ≠ 可丢,但允许重放。Hook 先原子落盘 spool 再转发;Daemon 崩溃自愈;事件不丢、处理幂等。
|
|
23
|
+
|
|
24
|
+
### hook / daemon / cli 分工
|
|
25
|
+
|
|
26
|
+
| 二进制 | 生命周期 | 职责 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| **hook** | 短命(每次事件 spawn 后立即退出) | Claude Code 经 hooks.json 调它;采集事件 → POST 给 daemon(热路径)+ spool 落盘(兜底)→ 退出。绝不拖慢 Claude Code |
|
|
29
|
+
| **daemon** | 常驻后台(首次被 hook 拉起,自愈) | 收事件存 SQLite(幂等去重)、WebSocket 推送查看页、提供 HTTP API、内嵌并服务查看页 UI |
|
|
30
|
+
| **cli** | 按需(用户手动跑) | 管理命令:`status` / `start` / `stop` / `restart` / `ui`。读 pid 文件取 token → 调 daemon API |
|
|
31
|
+
|
|
32
|
+
三者同目录,hook 用 `process.execPath` 定位 daemon,零配置。
|
|
33
|
+
|
|
34
|
+
### 二进制为何这么大
|
|
35
|
+
|
|
36
|
+
每个二进制 ~94MB(Windows x64),`bin/` 共 ~1.5GB(6 平台)。`bun build --compile` 把**整个 Bun 运行时**(JavaScriptCore + sqlite + 系统 API)静态链入每个二进制——源码本身很小(hook 9KB / daemon 33KB / cli 6KB),运行时占 ~90MB 且三个二进制各自独立含一份、不共享。这是 `--compile` 的固有代价,减重只能换方案 B(Release 下载)或 C(源码跑,运行时全局只装一份)。
|
|
37
|
+
|
|
38
|
+
## Hook 事件覆盖
|
|
39
|
+
|
|
40
|
+
Claude Code 共 9 个 hook 事件([官方清单](https://docs.claude.com/en/docs/claude-code/hooks))。本插件注册其中 7 个**只读观测**事件;所有 hook 退出码恒 0,绝不阻断或改写 Claude Code 主进程。
|
|
41
|
+
|
|
42
|
+
| 事件 | 注册 | 触发时机 |
|
|
43
|
+
| --- | :---: | --- |
|
|
44
|
+
| `SessionStart` | ✅ | 会话开始 / resume / clear / compact(兼做 daemon 首次拉起) |
|
|
45
|
+
| `UserPromptSubmit` | ✅ | 用户提交提示词前 |
|
|
46
|
+
| `PostToolUse` | ✅ | 工具调用完成后 |
|
|
47
|
+
| `Stop` | ✅ | 主 agent 结束响应 |
|
|
48
|
+
| `SubagentStop` | ✅ | 子 agent(Task 工具)结束响应 |
|
|
49
|
+
| `PreCompact` | ✅ | 上下文压缩前(手动 `/compact` 或自动) |
|
|
50
|
+
| `SessionEnd` | ✅ | 会话结束(clear / logout / exit) |
|
|
51
|
+
| `PreToolUse` | ❌ | 工具调用前——**故意不启用**:其 exit2/JSON 会阻断或改写工具调用,与「Hook 不影响主进程」冲突;需拦截时再单独设计同步返回逻辑 |
|
|
52
|
+
| `Notification` | ❌ | 权限请求 / 闲置通知——噪音大、观测价值低,默认不收 |
|
|
53
|
+
|
|
54
|
+
> `SessionResume` 在部分资料里被列为独立事件;官方文档里 resume 是 `SessionStart` 的一个 `source` matcher,非独立事件。
|
|
55
|
+
|
|
56
|
+
## 安装(用户)
|
|
57
|
+
|
|
58
|
+
本仓库既是 plugin 又兼作 marketplace,采用**方案 C(源码直跑)**——仓库只含源码、无二进制,因此**用户机器需先装 [Bun](https://bun.sh) 1.3+**。
|
|
59
|
+
|
|
60
|
+
### 前置:安装 Bun
|
|
61
|
+
|
|
62
|
+
机器上已有 node/npm 的话最省事(国内可复用 npm mirror):
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
npm install -g bun
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
或用官方脚本——Windows(PowerShell):
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
macOS / Linux:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
curl -fsSL https://bun.sh/install | bash
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
装完重开终端,`bun --version` 确认可用。
|
|
81
|
+
|
|
82
|
+
### 从 Gitea 安装
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
/plugin marketplace add http://8.130.168.121:9001/AI/shine-code-submit.git
|
|
86
|
+
/plugin install shine-code-submit@shine-code-submit
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
clone 后无二进制;首次 hook 事件时 `bin/launcher.js`(node)自动 `bun run src/hook/main.ts`,daemon 同理 `bun run src/daemon/main.ts`。
|
|
90
|
+
|
|
91
|
+
> ⚠️ Windows 系统代理会让 git 连不上 localhost。先 `setx NO_PROXY "localhost,127.0.0.1"` 并**重启 Claude Code**,再 `marketplace add`。
|
|
92
|
+
|
|
93
|
+
### 从本地目录安装(开发自测)
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
/plugin marketplace add <本仓库本地路径>
|
|
97
|
+
/plugin install shine-code-submit@shine-code-submit
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
直接读本机源码,改完即时生效(无需 build)。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 查看页(Dashboard)
|
|
105
|
+
|
|
106
|
+
装完**开新会话**即生效。两种打开方式:
|
|
107
|
+
|
|
108
|
+
- **自动**:每次真·新开会话(`source=startup`,非 `resume/clear/compact`),hook 会在会话顶部打印一行 Dashboard 链接(走 Claude Code 的 `systemMessage` 机制,直接显示给你;裸 stdout 只注入 assistant 当 context,用户不可见)。复制到浏览器即开。
|
|
109
|
+
- **手动**:`bun run src/cli/main.ts ui` —— 打印带 token 的链接并尝试打开浏览器。
|
|
110
|
+
|
|
111
|
+
> daemon 没起来也不报错:SessionStart hook 会先拉起 daemon 再读 token 打印;万一拉起失败则静默跳过(退出码恒 0,绝不阻断 Claude Code)。
|
|
112
|
+
|
|
113
|
+
### 局域网访问(其他设备看 Dashboard)
|
|
114
|
+
|
|
115
|
+
daemon 默认绑 `0.0.0.0`(所有网卡),打印的 Dashboard 链接自动用**第一个真实网卡的局域网 IP**(`getPrimaryIpv4` 跳过 vEthernet/VMware/docker 等虚拟网卡)。开新会话时链接形如 `http://192.168.x.x:36666/ui?t=...`,手机/平板/局域网其他设备直接能用。仅本机回环用时设 `SHINE_CODE_SUBMIT_HOST=127.0.0.1` 再 restart daemon。
|
|
116
|
+
|
|
117
|
+
端口对外可达性:
|
|
118
|
+
|
|
119
|
+
- **裸机 / Windows 原生跑 daemon**:绑 `0.0.0.0` 即对局域网可见,放行防火墙 36666 入站即可。
|
|
120
|
+
- **WSL2**:daemon 在 NAT 后,链接取到的是 WSL eth0 的 `172.x`(局域网外不可达);要让局域网设备真访问到,需 `networkingMode=mirrored`(`.wslconfig`,推荐)或 `netsh portproxy` 端口转发。
|
|
121
|
+
|
|
122
|
+
> ⚠️ 绑非回环后,`token`(UI 链接 `?t=` 里明文)成为数据接口唯一防线。仅可信网络下如此配,勿外泄带 token 的链接。
|
|
123
|
+
|
|
124
|
+
## 分发方案:三种取舍
|
|
125
|
+
|
|
126
|
+
本项目支持三种分发方案(A、B 为二进制,C 为源码直跑):
|
|
127
|
+
|
|
128
|
+
### 方案 A:二进制 commit 进 `bin/`(备选)
|
|
129
|
+
|
|
130
|
+
18 个二进制 commit 进 `bin/<plat>-<arch>/`,经 Git LFS 纳管。clone/安装即自带,launcher 直接 spawn 本地 hook,**装即用、无需联网**。
|
|
131
|
+
|
|
132
|
+
- ✅ 安装最简单(装即用,无首次下载)
|
|
133
|
+
- ✅ 本地/自建 git 仓库无 LFS 配额限制
|
|
134
|
+
- ⚠️ GitHub 公开时占 LFS 配额(免费约 1GB 存储 + 1GB/月流量;776MB 二进制几人 clone 即超额)
|
|
135
|
+
- ⚠️ clone 需拉 LFS 对象(首次稍慢)
|
|
136
|
+
|
|
137
|
+
### 方案 B:GitHub Releases 下载(仓库更小,备选)
|
|
138
|
+
|
|
139
|
+
二进制移出 git,作为 GitHub Release 资产。plugin 仓库只留源码 + `launcher.js`(几 MB,clone 快、零 LFS)。launcher 首次运行(SessionStart)按平台从 Release 下载 hook/cli/daemon 到 `%LOCALAPPDATA%/shine-code-submit/bin/<plat>-<arch>/`。
|
|
140
|
+
|
|
141
|
+
- ✅ 仓库小、clone 快、GitHub 无 LFS 配额压力
|
|
142
|
+
- ⚠️ 首次安装需联网下载约 300MB(SessionStart 的 `timeout` 调到 300s)
|
|
143
|
+
- ⚠️ 需额外发 Release(`gh release create`)+ launcher 下载自举逻辑
|
|
144
|
+
|
|
145
|
+
> 切换到方案 B 的实现见 git 历史 commit `fc30665`(launcher 下载自举 + `scripts/release.sh`)。
|
|
146
|
+
claude --resume 61a9beba-9bc9-4407-a41e-270e3d2efed3
|
|
147
|
+
|
|
148
|
+
### 方案 C:源码直跑(**本仓库采用** ✅,需 Bun)
|
|
149
|
+
|
|
150
|
+
不产出/不分发二进制,launcher 与 daemon 直接用 `bun run src/...` 跑源码。plugin 仓库只留源码 + `launcher.js`(几 MB,无 LFS、无 Release)。
|
|
151
|
+
|
|
152
|
+
- ✅ 仓库最小、零 LFS 配额、无需发 Release
|
|
153
|
+
- ✅ 改源码即时生效(无 build/commit 二进制步骤)
|
|
154
|
+
- ⚠️ 用户机器须装 Bun 1.3+(开发者向,非通用)
|
|
155
|
+
- ⚠️ hook/daemon 启动比二进制慢几百 ms(bun run 源码)
|
|
156
|
+
|
|
157
|
+
> 实施需:`bin/launcher.js` 与 `src/shared/daemonctl.ts` 加「二进制优先、否则 `bun run src/...`」分支;`bin/<plat>-<arch>/` 移除并加入 `.gitignore`;cli 用 `bun run src/cli/main.ts`。
|
|
158
|
+
|
|
159
|
+
## 开发(贡献者)
|
|
160
|
+
|
|
161
|
+
依赖 [Bun](https://bun.sh) 1.3+:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
bun install
|
|
165
|
+
bun run build # 产本机平台到 bin/<本机plat>-<arch>/(开发自测)
|
|
166
|
+
bun run build:all # 6 平台交叉编译到 bin/(方案 A 需 commit 这些)
|
|
167
|
+
bun run typecheck
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
方案 A 下,`build:all` 产出的 18 个二进制需 `git add bin/` commit(LFS 自动纳管)。本仓库 `.claude/settings.json` 指向 `bin/windows-x64/hook.exe`,本机 build 后即可自测。Daemon 默认由同目录二进制拉起;开发期可用 env 覆盖跑 `bun run`。
|
|
171
|
+
|
|
172
|
+
## 目录
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
.claude-plugin/ plugin.json、marketplace.json(plugin 元信息 + 自托管市场)
|
|
176
|
+
hooks/ hooks.json(plugin hook 注册,exec form 调 node launcher)
|
|
177
|
+
bin/ launcher.js + <plat>-<arch>/{hook,cli,daemon}(编译产物,commit 入库,Git LFS)
|
|
178
|
+
src/ shared/ daemon/ hook/ cli/(三端共用源码)
|
|
179
|
+
ui/ 查看页(原生 JS,编译期嵌入 daemon 二进制)
|
|
180
|
+
scripts/ build.ts
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 环境变量
|
|
184
|
+
|
|
185
|
+
| 变量 | 作用 | 默认 |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `SHINE_CODE_SUBMIT_DAEMON_CMD` | 拉起 daemon 的完整命令(开发期,如 `bun run src/daemon/main.ts`) | 同目录 daemon 二进制 |
|
|
188
|
+
| `SHINE_CODE_SUBMIT_DAEMON` | 仅 `bun run` 入口路径 | 无 |
|
|
189
|
+
| `SHINE_CODE_SUBMIT_DEBUG` | 开启 daemon DEBUG 日志 | 无 |
|
|
190
|
+
| `SHINE_CODE_SUBMIT_HOST` | daemon 监听地址。默认 `0.0.0.0`(绑所有网卡,局域网可访问);仅本机回环用时设 `127.0.0.1` | `0.0.0.0` |
|
|
191
|
+
|
|
192
|
+
## 数据位置
|
|
193
|
+
|
|
194
|
+
`%LOCALAPPDATA%/shine-code-submit/`:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
daemon.pid pid/port/token/startedAt
|
|
198
|
+
spool/*.json 待消费事件(每事件一文件,原子写)
|
|
199
|
+
log/daemon.log 日志(按大小轮转)
|
|
200
|
+
db/events.sqlite 事件库(按 cwd 隔离,幂等去重)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
二进制在 plugin 安装目录的 `bin/<plat>-<arch>/`(不在数据目录)。
|
|
204
|
+
|
|
205
|
+
> 旧版本(livesetting)的数据目录 `%LOCALAPPDATA%/livesetting/` 不会自动迁移,可手动删除。
|
|
206
|
+
|
|
207
|
+
## 关键设计点
|
|
208
|
+
|
|
209
|
+
- **目录式 spool + 原子 rename**:每事件一文件,规避多进程并发 append 损坏。
|
|
210
|
+
- **幂等**:`(sessionId, eventId)` 唯一约束 + `INSERT OR IGNORE`,热路径与回捞共享,允许重放。
|
|
211
|
+
- **热路径优先**:直接 POST,连接失败才探测/拉起(健康路径单次往返)。
|
|
212
|
+
- **认自己人**:`/api/health` 返回 `service` 字段,Hook 校验后才认端口归属。
|
|
213
|
+
- **默认绑 0.0.0.0 + token**:健康端点外都鉴权;默认暴露给局域网(方便其他设备访问),仅本机回环用时设 `SHINE_CODE_SUBMIT_HOST=127.0.0.1`(见「局域网访问」)。
|
|
214
|
+
- **监听/连接地址分离**:daemon 监听用 `LISTEN_HOST`(默认 0.0.0.0,env 可配);hook POST / cli / 探活 连接 daemon 固定走 `127.0.0.1` 回环(daemon 即使绑 0.0.0.0 也含回环),最快最稳。
|
|
215
|
+
- **打印链接用真实网卡 IP**:`PUBLIC_BASE_URL` 取第一个非虚拟网卡的 IPv4(跳过 vEthernet/VMware/docker),局域网设备可直接访问;本机打开浏览器则用 `localhost`(WSL2 转发友好)。
|
|
216
|
+
- **自启动 + 自愈**:任意事件故障路径都能拉起;重复实例启动时自检退出,crash 只删属于自己的 pid。
|
|
217
|
+
- **跨平台分发(方案 A)**:plugin 的 hooks.json 静态、无平台变量,靠 `bin/launcher.js` 按 `process.platform/arch` 选 `bin/<plat>-<arch>/` 里的二进制;hook/cli/daemon 同目录,hook 用 `process.execPath` 定位 daemon,零额外配置、装即用。
|
package/bin/launcher.cjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// shine-code-submit hook 平台分发器(.cjs 强制 CommonJS,兼容所有 node,不依赖 package.json)。
|
|
3
|
+
// Claude Code 经 hooks.json 以 `node launcher.cjs <Event>` 调用(exec form,不经 shell)。
|
|
4
|
+
// 优先 spawn 同目录 bin/<plat>-<arch>/hook[.exe](二进制模式,已 build);
|
|
5
|
+
// 不存在则 bun run src/hook/main.ts(源码模式,需用户装 Bun)。
|
|
6
|
+
// 退出码恒 0——绝不影响 Claude Code 主进程。
|
|
7
|
+
const { spawn } = require("node:child_process");
|
|
8
|
+
const { existsSync } = require("node:fs");
|
|
9
|
+
const { join, dirname } = require("node:path");
|
|
10
|
+
|
|
11
|
+
const here = __dirname; // .../bin/
|
|
12
|
+
const plat = process.platform === "win32" ? "windows" : process.platform; // darwin | linux | windows
|
|
13
|
+
const arch = process.arch; // x64 | arm64
|
|
14
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
15
|
+
const hookBin = join(here, `${plat}-${arch}`, `hook${ext}`);
|
|
16
|
+
const hookSrc = join(here, "..", "src", "hook", "main.ts"); // 源码模式入口
|
|
17
|
+
|
|
18
|
+
const argv = process.argv.slice(2);
|
|
19
|
+
try {
|
|
20
|
+
let child;
|
|
21
|
+
if (existsSync(hookBin)) {
|
|
22
|
+
// 二进制模式:spawn 本地已 build 的 hook
|
|
23
|
+
child = spawn(hookBin, argv, { stdio: "inherit" });
|
|
24
|
+
} else {
|
|
25
|
+
// 源码模式:bun run src/hook/main.ts(Windows 需 shell 找 bun.exe)
|
|
26
|
+
child = spawn("bun", ["run", hookSrc, ...argv], {
|
|
27
|
+
stdio: "inherit",
|
|
28
|
+
shell: process.platform === "win32",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
child.on("error", () => process.exit(0));
|
|
32
|
+
child.on("exit", () => process.exit(0));
|
|
33
|
+
} catch {
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
package/bun.lock
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "shine-code-submit",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"marked": "^18.0.5",
|
|
9
|
+
"react": "^19.2.7",
|
|
10
|
+
"react-dom": "^19.2.7",
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/bun": "^1.3.14",
|
|
14
|
+
"@types/react": "^19.2.17",
|
|
15
|
+
"@types/react-dom": "^19.2.3",
|
|
16
|
+
"typescript": "^6.0.3",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
"packages": {
|
|
21
|
+
"@types/bun": ["@types/bun@1.3.14", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.14.tgz", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
|
22
|
+
|
|
23
|
+
"@types/node": ["@types/node@26.1.0", "https://registry.npmmirror.com/@types/node/-/node-26.1.0.tgz", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="],
|
|
24
|
+
|
|
25
|
+
"@types/react": ["@types/react@19.2.17", "https://registry.npmmirror.com/@types/react/-/react-19.2.17.tgz", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
|
|
26
|
+
|
|
27
|
+
"@types/react-dom": ["@types/react-dom@19.2.3", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
|
28
|
+
|
|
29
|
+
"bun-types": ["bun-types@1.3.14", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
|
30
|
+
|
|
31
|
+
"csstype": ["csstype@3.2.3", "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
32
|
+
|
|
33
|
+
"marked": ["marked@18.0.5", "https://registry.npmmirror.com/marked/-/marked-18.0.5.tgz", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="],
|
|
34
|
+
|
|
35
|
+
"react": ["react@19.2.7", "https://registry.npmmirror.com/react/-/react-19.2.7.tgz", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
|
|
36
|
+
|
|
37
|
+
"react-dom": ["react-dom@19.2.7", "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.7.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="],
|
|
38
|
+
|
|
39
|
+
"scheduler": ["scheduler@0.27.0", "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
|
40
|
+
|
|
41
|
+
"typescript": ["typescript@6.0.3", "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
|
42
|
+
|
|
43
|
+
"undici-types": ["undici-types@8.3.0", "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/install.cjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var T=require("node:fs"),Z1=require("node:path"),$1=require("node:child_process");var m=require("node:fs"),S=require("node:os"),I=require("node:path"),v=require("node:child_process"),W1=[1,1,0],J1=300000;function y(){let b=v.spawnSync("bun",["--version"],{shell:process.platform==="win32",encoding:"utf8"});if(b.status===0&&(b.stdout??"").trim())return"bun";let q=S.homedir(),z=process.platform==="win32"?[I.join(q,".bun","bin","bun.exe")]:[I.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 F1(b){return b.trim().split(".").map((q)=>parseInt(q,10)||0)}function K1(b,q){let z=F1(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 V1(){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:J1,stdio:"inherit"}).status??1}async function p(){let b=y();if(b){let z=v.spawnSync(b,["--version"],{shell:process.platform==="win32",encoding:"utf8"}).stdout?.trim()??"";if(z&&K1(z,W1))return console.log(`[shine-code-submit] bun ${z} detected`),b}if(console.log("[shine-code-submit] bun 未找到或版本过低,开始自动安装..."),V1()){if(console.log("[shine-code-submit] 检测到国内 npm 镜像,先尝试 npm install -g bun"),M("npm install -g bun")===0){let z=y();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=y();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"),G=require("node:path"),d=require("node:url"),L=require("node:child_process");var E=require("node:os"),V=require("node:path");function A(){return process.env.CLAUDE_CONFIG_DIR||V.join(E.homedir(),".claude")}function U(){return V.join(A(),"plugins")}function j(){return V.join(U(),"known_marketplaces.json")}function O(){return V.join(U(),"installed_plugins.json")}function B(){return V.join(A(),"settings.json")}var P={name:"shine-code-submit",version:"0.2.0",private:!1,type:"module",description:"Claude Code Hook -> 本地常驻 Daemon 的状态/持久化底座",author:"renguifeng",license:"MIT",repository:{type:"git",url:"http://8.130.168.121:9001/AI/shine-code-submit.git"},homepage:"http://8.130.168.121:9001/AI/shine-code-submit",bugs:{url:"http://8.130.168.121:9001/AI/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 h=require("node:os"),c="shine-code-submit",W=P.version,f1=process.env.SHINE_CODE_SUBMIT_HOST??"0.0.0.0",C1="127.0.0.1",l=36666,N=`http://${C1}:${l}`;function x1(){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=h.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 u=`http://${x1()}:${l}`;var $="shine-code-submit",g="shine-code-submit";function R(b=W){return G.join(U(),"cache",$,g,b)}function v1(){let b=G.dirname(d.fileURLToPath("file:///C:/Users/ren/Desktop/workspace/livesetting/src/install/deploy.ts")),q=b;for(let z=0;z<10;z++){if(Z.existsSync(G.join(q,"package.json"))&&Z.existsSync(G.join(q,".claude-plugin")))return q;let Q=G.dirname(q);if(Q===q)break;q=Q}return b}var j1=[".claude-plugin","hooks","bin","src","ui","package.json","bun.lock","README.md"];function s(b){let q=R();if(Z.existsSync(q))Z.rmSync(q,{recursive:!0,force:!0});Z.mkdirSync(q,{recursive:!0});let z=v1();console.log(`[shine-code-submit] 部署源:${z}`);for(let X of j1){let J=G.join(z,X);if(!Z.existsSync(J))continue;Z.cpSync(J,G.join(q,X),{recursive:!0})}console.log("[shine-code-submit] 安装运行时依赖(bun install)...");let Q=L.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=L.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(G.join(q,".install-version"),JSON.stringify({version:W,installedAt:Date.now()}),"utf8"),console.log(`[shine-code-submit] 已部署到 ${q}`),q}var k=require("node:path");var Y=require("node:fs"),o=require("node:path");function F(b,q){if(!Y.existsSync(b))return q;let z;try{z=Y.readFileSync(b,"utf8")}catch{return q}try{return JSON.parse(z)}catch{let Q=`${b}.bak-corrupt-${Date.now()}`;try{Y.copyFileSync(b,Q),console.error(`[shine-code-submit] WARNING: ${b} JSON 损坏,已备份到 ${Q},用默认值继续`)}catch{}return q}}function K(b,q){let z=`${b}.bak-pre-install`;if(Y.existsSync(b)&&!Y.existsSync(z))try{Y.copyFileSync(b,z)}catch{}Y.mkdirSync(o.dirname(b),{recursive:!0});let Q=`${b}.tmp-${process.pid}`;Y.writeFileSync(Q,JSON.stringify(q,null,2),"utf8"),Y.renameSync(Q,b)}function _(){return`${g}@${$}`}function r(b){let q=j(),z=F(q,{}),Q=k.join(b,".claude-plugin"),X=z[$];if(X?.source&&X.source.source!=="directory")console.log(`[shine-code-submit] WARNING: marketplace "${$}" 已存在(source=${X.source.source}),将覆盖为 directory 源(原文件已备份)`);z[$]={source:{source:"directory",path:Q},installLocation:k.join(U(),"marketplaces",$),lastUpdated:new Date().toISOString(),autoUpdate:!1},K(q,z),console.log(`[shine-code-submit] marketplace 已注册 → ${q}`)}function t(b){let q=O(),z=F(q,{version:2,plugins:{}});if(!z.version)z.version=2;if(!z.plugins)z.plugins={};let Q=_(),X=new Date().toISOString(),J=z.plugins[Q]?.[0];z.plugins[Q]=[{scope:"user",installPath:b,version:W,installedAt:J?.installedAt??X,lastUpdated:X}],K(q,z),console.log(`[shine-code-submit] plugin 已注册 → ${q}`)}function i(b){let q=B(),z=F(q,{});if(!z.enabledPlugins)z.enabledPlugins={};let Q=_();if(z.enabledPlugins[Q]=!0,!z.extraKnownMarketplaces)z.extraKnownMarketplaces={};if(!z.extraKnownMarketplaces[$])z.extraKnownMarketplaces[$]={source:{source:"directory",path:k.join(b,".claude-plugin")}};K(q,z),console.log(`[shine-code-submit] 已启用(enabledPlugins)→ ${q}`)}function n(){let b=_(),q=F(j(),{});if(q[$])delete q[$],K(j(),q);let z=F(O(),{plugins:{}});if(z.plugins&&z.plugins[b])delete z.plugins[b],K(O(),z);let Q=F(B(),{}),X=!1;if(Q.enabledPlugins&&Q.enabledPlugins[b])delete Q.enabledPlugins[b],X=!0;if(Q.extraKnownMarketplaces&&Q.extraKnownMarketplaces[$])delete Q.extraKnownMarketplaces[$],X=!0;if(X)K(B(),Q);console.log("[shine-code-submit] 已从三处 JSON 移除注册")}var a=require("node:child_process"),f=require("node:fs");async function C(b=400){try{let q=await fetch(`${N}/api/health`,{signal:AbortSignal.timeout(b)});if(!q.ok)return!1;return(await q.json())?.service===c}catch{return!1}}function e(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(O1())z="cmd.exe",Q=["/c","start","",b];else z="xdg-open",Q=[b];try{a.spawn(z,Q,{detached:!0,stdio:"ignore"}).unref()}catch{}}function O1(){if(process.platform!=="linux")return!1;try{return f.readFileSync("/proc/version","utf8").toLowerCase().includes("microsoft")}catch{return!1}}var H=require("node:path"),b1=require("node:fs"),q1=require("node:os"),B1=process.env.LOCALAPPDATA??H.join(q1.homedir(),".local","share"),x=H.join(B1,"shine-code-submit"),N1=H.join(x,"spool"),z1=H.join(x,"log"),Q1=H.join(x,"db"),X1=H.join(x,"daemon.pid"),i1=H.join(z1,"daemon.log"),n1=H.join(Q1,"events.sqlite");function Y1(){for(let b of[x,N1,z1,Q1])b1.mkdirSync(b,{recursive:!0})}var w=require("node:fs");function D(){try{let b=w.readFileSync(X1,"utf8");return JSON.parse(b)}catch{return null}}var[,,G1]=process.argv;u1().catch((b)=>{console.error(`[shine-code-submit] ${b instanceof Error?b.message:String(b)}`),process.exit(1)});async function u1(){switch(G1){case void 0:case"install":await k1();break;case"uninstall":await w1();break;case"status":await D1();break;case"--version":case"-v":console.log(W);break;default:I1()}}async function k1(){console.log(`=== shine-code-submit installer v${W} ===`);let b=await p(),q=s(b);r(q),t(q),i(q),Y1(),await T1(b,q),M1(),console.log(""),console.log("✓ 安装完成。"),console.log(" · 重启 Claude Code 后,/plugin 列表会显示 shine-code-submit(已启用)。"),console.log(" · 开新会话即触发 SessionStart hook,事件出现在 dashboard。")}async function w1(){console.log("=== shine-code-submit uninstaller ==="),await y1(),n();let b=R();if(T.existsSync(b))T.rmSync(b,{recursive:!0,force:!0}),console.log(`[shine-code-submit] 已删除 ${b}`);console.log("✓ 已卸载。重启 Claude Code 后 /plugin 不再显示。")}async function D1(){let b=await C(),q=D();if(b&&q)console.log(`daemon: running pid=${q.pid} ${u}`);else console.log("daemon: not running")}async function T1(b,q){if(await C()){console.log("[shine-code-submit] daemon 已在运行,跳过启动");return}let z=Z1.join(q,"src","daemon","main.ts");console.log("[shine-code-submit] 启动 daemon...");try{$1.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 H1(200),await C()){console.log("[shine-code-submit] daemon 已就绪");return}console.error("[shine-code-submit] daemon 启动超时(10s)。plugin 已注册,可稍后手动 `shine-code-submit start` 或重启 claude。")}async function y1(){let b=D();if(!b){console.log("[shine-code-submit] daemon 未运行(无 pid 文件)");return}if(await C()){try{await fetch(`${N}/api/shutdown`,{method:"POST",headers:{authorization:`Bearer ${b.token}`}})}catch{}if(await H1(1000),await C())try{process.kill(b.pid)}catch{}}console.log("[shine-code-submit] daemon 已停止")}function M1(){let b=D(),q=b?`${u}/ui?t=${b.token}`:`${u}/ui`;console.log(`[shine-code-submit] Dashboard: ${q}`);try{e(q)}catch{}}function H1(b){return new Promise((q)=>setTimeout(q,b))}function I1(){console.log(`shine-code-submit <command>
|
|
3
|
+
|
|
4
|
+
install 安装插件(自动装 bun + 部署 + 注册 + 启 daemon + 开 dashboard)
|
|
5
|
+
uninstall 卸载(停 daemon + 反注册 + 删文件)
|
|
6
|
+
status 显示 daemon 状态
|
|
7
|
+
|
|
8
|
+
通常通过 npx 跑:npx shine-code-submit install`),process.exit(G1?1:0)}
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "node",
|
|
9
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/launcher.cjs", "SessionStart"]
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "node",
|
|
20
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/launcher.cjs", "UserPromptSubmit"]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PostToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "node",
|
|
31
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/launcher.cjs", "PostToolUse"]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"Stop": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "node",
|
|
42
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/launcher.cjs", "Stop"]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"SubagentStop": [
|
|
48
|
+
{
|
|
49
|
+
"hooks": [
|
|
50
|
+
{
|
|
51
|
+
"type": "command",
|
|
52
|
+
"command": "node",
|
|
53
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/launcher.cjs", "SubagentStop"]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"PreCompact": [
|
|
59
|
+
{
|
|
60
|
+
"hooks": [
|
|
61
|
+
{
|
|
62
|
+
"type": "command",
|
|
63
|
+
"command": "node",
|
|
64
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/launcher.cjs", "PreCompact"]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"SessionEnd": [
|
|
70
|
+
{
|
|
71
|
+
"hooks": [
|
|
72
|
+
{
|
|
73
|
+
"type": "command",
|
|
74
|
+
"command": "node",
|
|
75
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/launcher.cjs", "SessionEnd"]
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shine-code-submit",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Claude Code Hook -> 本地常驻 Daemon 的状态/持久化底座",
|
|
7
|
+
"author": "renguifeng",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "http://8.130.168.121:9001/AI/shine-code-submit.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "http://8.130.168.121:9001/AI/shine-code-submit",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "http://8.130.168.121:9001/AI/shine-code-submit/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"plugin",
|
|
21
|
+
"hooks",
|
|
22
|
+
"daemon",
|
|
23
|
+
"dashboard"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"shine-code-submit": "dist/install.cjs"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist/install.cjs",
|
|
30
|
+
".claude-plugin",
|
|
31
|
+
"hooks/hooks.json",
|
|
32
|
+
"bin/launcher.cjs",
|
|
33
|
+
"src",
|
|
34
|
+
"ui",
|
|
35
|
+
"package.json",
|
|
36
|
+
"bun.lock",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public",
|
|
44
|
+
"registry": "https://registry.npmjs.org/"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"daemon": "bun run src/daemon/main.ts",
|
|
48
|
+
"hook": "bun run src/hook/main.ts",
|
|
49
|
+
"cli": "bun run src/cli/main.ts",
|
|
50
|
+
"build": "bun run scripts/build.ts",
|
|
51
|
+
"build:all": "bun run scripts/build.ts --all",
|
|
52
|
+
"build:install": "bun run scripts/build-install.ts",
|
|
53
|
+
"build:dist": "bun run scripts/build.ts && bun run scripts/build-install.ts",
|
|
54
|
+
"prepublishOnly": "bun run build:dist",
|
|
55
|
+
"typecheck": "tsc --noEmit"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/bun": "^1.3.14",
|
|
59
|
+
"@types/react": "^19.2.17",
|
|
60
|
+
"@types/react-dom": "^19.2.3",
|
|
61
|
+
"typescript": "^6.0.3"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"marked": "^18.0.5",
|
|
65
|
+
"react": "^19.2.7",
|
|
66
|
+
"react-dom": "^19.2.7"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/cli/main.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// shine-code-submit CLI:status / start / stop / restart / ui。
|
|
2
|
+
// 用户侧管理命令。token 从 pid 文件读取。
|
|
3
|
+
import { BASE_URL, PUBLIC_BASE_URL } from "../shared/config";
|
|
4
|
+
import { readPidFile, removePidFile } from "../shared/pidfile";
|
|
5
|
+
import { ensureDaemon, isOursAlive, openBrowser, spawnDaemon } from "../shared/daemonctl";
|
|
6
|
+
|
|
7
|
+
const [cmd] = process.argv.slice(2);
|
|
8
|
+
|
|
9
|
+
switch (cmd) {
|
|
10
|
+
case "status":
|
|
11
|
+
void cmdStatus();
|
|
12
|
+
break;
|
|
13
|
+
case "start":
|
|
14
|
+
void cmdStart();
|
|
15
|
+
break;
|
|
16
|
+
case "stop":
|
|
17
|
+
void cmdStop();
|
|
18
|
+
break;
|
|
19
|
+
case "restart":
|
|
20
|
+
void cmdRestart();
|
|
21
|
+
break;
|
|
22
|
+
case "ui":
|
|
23
|
+
void cmdUi();
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
printHelp();
|
|
27
|
+
process.exit(cmd ? 1 : 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function cmdStatus(): Promise<void> {
|
|
31
|
+
if (!(await isOursAlive())) {
|
|
32
|
+
console.log("daemon: not running");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const res = await fetch(`${BASE_URL}/api/health`);
|
|
36
|
+
const h = (await res.json()) as { pid: number; uptime: number; version: string };
|
|
37
|
+
console.log(`daemon: running pid=${h.pid} uptime=${Math.floor(h.uptime / 1000)}s v${h.version}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function cmdStart(): Promise<void> {
|
|
41
|
+
if (await isOursAlive()) {
|
|
42
|
+
console.log("daemon: already running");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
spawnDaemon();
|
|
46
|
+
const ok = await waitReady();
|
|
47
|
+
console.log(ok ? "daemon: started" : "daemon: start failed (check %LOCALAPPDATA%/shine-code-submit/log/daemon.log)");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function cmdStop(): Promise<void> {
|
|
51
|
+
const pid = readPidFile();
|
|
52
|
+
if (!pid) {
|
|
53
|
+
console.log("daemon: not running (no pid file)");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!(await isOursAlive())) {
|
|
57
|
+
console.log("daemon: not running (stale pid file)");
|
|
58
|
+
removePidFile();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// 优雅停止:调用 /api/shutdown
|
|
62
|
+
try {
|
|
63
|
+
await fetch(`${BASE_URL}/api/shutdown`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { authorization: `Bearer ${pid.token}` },
|
|
66
|
+
});
|
|
67
|
+
} catch {
|
|
68
|
+
/* ignore */
|
|
69
|
+
}
|
|
70
|
+
await sleep(1000);
|
|
71
|
+
if (await isOursAlive()) {
|
|
72
|
+
try {
|
|
73
|
+
process.kill(pid.pid); // 兜底强杀
|
|
74
|
+
} catch {
|
|
75
|
+
/* ignore */
|
|
76
|
+
}
|
|
77
|
+
removePidFile();
|
|
78
|
+
console.log("daemon: force-stopped");
|
|
79
|
+
} else {
|
|
80
|
+
console.log("daemon: stopped");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function cmdRestart(): Promise<void> {
|
|
85
|
+
await cmdStop();
|
|
86
|
+
spawnDaemon();
|
|
87
|
+
const ok = await waitReady();
|
|
88
|
+
console.log(ok ? "daemon: restarted" : "daemon: restart failed");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function cmdUi(): Promise<void> {
|
|
92
|
+
let pid = readPidFile();
|
|
93
|
+
if (!(await isOursAlive())) {
|
|
94
|
+
spawnDaemon();
|
|
95
|
+
await waitReady();
|
|
96
|
+
pid = readPidFile();
|
|
97
|
+
}
|
|
98
|
+
if (!pid) {
|
|
99
|
+
console.error("daemon: failed to start");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const url = `${PUBLIC_BASE_URL}/ui?t=${pid.token}`;
|
|
103
|
+
console.log("opening:", url);
|
|
104
|
+
openBrowser(url);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function waitReady(): Promise<boolean> {
|
|
108
|
+
return ensureDaemon();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function sleep(ms: number): Promise<void> {
|
|
112
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function printHelp(): void {
|
|
116
|
+
console.log(`shine-code-submit <command>
|
|
117
|
+
|
|
118
|
+
status 显示 daemon 运行状态
|
|
119
|
+
start 启动 daemon(已在跑则跳过)
|
|
120
|
+
stop 优雅停止 daemon
|
|
121
|
+
restart 重启 daemon
|
|
122
|
+
ui 打开查看页(必要时先启动 daemon)
|
|
123
|
+
|
|
124
|
+
发布态下 hook/cli/daemon 同目录,daemon 由同目录二进制拉起;
|
|
125
|
+
开发期可用环境变量覆盖:
|
|
126
|
+
SHINE_CODE_SUBMIT_DAEMON_CMD 启动 daemon 的完整命令(如 bun run src/daemon/main.ts)
|
|
127
|
+
SHINE_CODE_SUBMIT_DAEMON bun run 的入口(默认 src/daemon/main.ts)`);
|
|
128
|
+
}
|