agentquad 0.3.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/LICENSE +21 -0
- package/README.md +318 -0
- package/dist-web/assets/index-CMaXwixo.js +1234 -0
- package/dist-web/assets/index-DBHApzV1.css +32 -0
- package/dist-web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/dist-web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
- package/dist-web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
- package/dist-web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
- package/dist-web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/dist-web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
- package/dist-web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
- package/dist-web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
- package/dist-web/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
- package/dist-web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/dist-web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/dist-web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
- package/dist-web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
- package/dist-web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
- package/dist-web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
- package/dist-web/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
- package/dist-web/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
- package/dist-web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/dist-web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/dist-web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
- package/dist-web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
- package/dist-web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
- package/dist-web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
- package/dist-web/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
- package/dist-web/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
- package/dist-web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist-web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist-web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist-web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist-web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/dist-web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/dist-web/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
- package/dist-web/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/dist-web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/dist-web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/dist-web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
- package/dist-web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
- package/dist-web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
- package/dist-web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
- package/dist-web/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
- package/dist-web/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
- package/dist-web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/dist-web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/dist-web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- package/dist-web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
- package/dist-web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
- package/dist-web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- package/dist-web/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
- package/dist-web/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist-web/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
- package/dist-web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist-web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
- package/dist-web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist-web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
- package/dist-web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist-web/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
- package/dist-web/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
- package/dist-web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist-web/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
- package/dist-web/assets/logo-D4DDtU-r.png +0 -0
- package/dist-web/favicon.png +0 -0
- package/dist-web/index.html +14 -0
- package/package.json +88 -0
- package/src/ask-user-buttons.js +142 -0
- package/src/claude-transcript.js +203 -0
- package/src/cli.js +1040 -0
- package/src/codex-event-emitter.js +111 -0
- package/src/codex-prompt-detector.js +53 -0
- package/src/codex-sidecar.js +52 -0
- package/src/codex-transcript.js +74 -0
- package/src/config.js +692 -0
- package/src/data/claude-code-commands.json +52 -0
- package/src/db.js +1503 -0
- package/src/dispatch.js +13 -0
- package/src/export/todoMarkdown.js +246 -0
- package/src/first-run-wizard.js +82 -0
- package/src/git/gitStatus.js +139 -0
- package/src/lark-api-client.js +205 -0
- package/src/lark-bot.js +510 -0
- package/src/lark-card.js +88 -0
- package/src/lark-config-service.js +16 -0
- package/src/lark-event-client.js +107 -0
- package/src/lark-image.js +99 -0
- package/src/lark-markdown.js +51 -0
- package/src/lark-video.js +163 -0
- package/src/mcp/audit.js +34 -0
- package/src/mcp/server.js +83 -0
- package/src/mcp/tools/destructive/index.js +252 -0
- package/src/mcp/tools/openclaw/index.js +405 -0
- package/src/mcp/tools/read/index.js +269 -0
- package/src/mcp/tools/write/index.js +157 -0
- package/src/openclaw-bridge.js +566 -0
- package/src/openclaw-hook-installer.js +338 -0
- package/src/openclaw-hook.js +908 -0
- package/src/openclaw-wizard.js +2442 -0
- package/src/pending-questions.js +297 -0
- package/src/pricing.js +45 -0
- package/src/prompt-render.js +36 -0
- package/src/pty.js +992 -0
- package/src/routes/ai-terminal.js +1228 -0
- package/src/routes/git.js +89 -0
- package/src/routes/openclaw-hook.js +67 -0
- package/src/routes/openclaw-inbound.js +36 -0
- package/src/routes/recurringRules.js +80 -0
- package/src/routes/reports.js +50 -0
- package/src/routes/search.js +46 -0
- package/src/routes/stats.js +31 -0
- package/src/routes/telegram-config.js +152 -0
- package/src/routes/telegram-sync.js +221 -0
- package/src/routes/templates.js +63 -0
- package/src/routes/todos.js +649 -0
- package/src/routes/transcripts.js +75 -0
- package/src/routes/uploads.js +107 -0
- package/src/routes/wiki.js +142 -0
- package/src/search/fts.js +209 -0
- package/src/search/index.js +199 -0
- package/src/search/transcripts.js +148 -0
- package/src/server.js +1791 -0
- package/src/session-input-dispatcher.js +256 -0
- package/src/stats/markdown.js +42 -0
- package/src/stats/report.js +207 -0
- package/src/summarize.js +84 -0
- package/src/system-rules.js +52 -0
- package/src/telegram-bot.js +875 -0
- package/src/telegram-commands.js +149 -0
- package/src/telegram-config-service.js +84 -0
- package/src/telegram-image.js +95 -0
- package/src/telegram-loading-status.js +112 -0
- package/src/telegram-markdown.js +82 -0
- package/src/telegram-reaction-tracker.js +69 -0
- package/src/telegram-video.js +75 -0
- package/src/templates/claude-hooks/notify.js +103 -0
- package/src/transcript.js +305 -0
- package/src/transcripts/blocks.js +56 -0
- package/src/transcripts/index.js +222 -0
- package/src/transcripts/indexer.js +34 -0
- package/src/transcripts/matcher.js +70 -0
- package/src/transcripts/scanner.js +259 -0
- package/src/usage-footer.js +170 -0
- package/src/usage-parser.js +132 -0
- package/src/wiki/guide.js +44 -0
- package/src/wiki/index.js +232 -0
- package/src/wiki/redact.js +34 -0
- package/src/wiki/sources.js +122 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code hooks 安装器:把 AgentQuad 的 3 个 hook entry 合并写入
|
|
3
|
+
* `~/.claude/settings.json`,不破坏用户现有 hooks 配置。
|
|
4
|
+
*
|
|
5
|
+
* 合并策略:
|
|
6
|
+
* - 已有 hooks.<event> 数组 → append;不删除已有 entry
|
|
7
|
+
* - AgentQuad 加的 entry 用 `_quadtodoManaged: true` 字段标记,方便 uninstall
|
|
8
|
+
* - 卸载时仅删带这个标记的 entry,其他保留不动
|
|
9
|
+
* - settings.json 不存在 → 创建
|
|
10
|
+
* - settings.json 损坏 → 抛错让用户修,绝不擅自覆盖
|
|
11
|
+
*
|
|
12
|
+
* 启动期 bootstrap(bootstrapHooks):
|
|
13
|
+
* - 部署/升级 ~/.agentquad/claude-hooks/notify.js(带版本号比对)
|
|
14
|
+
* - 合并 hooks 到 settings.json(已装则 noop,避免 .bak 刷屏)
|
|
15
|
+
* - 用户跑过 uninstall-hook → 留 .uninstalled marker,bootstrap 默认尊重;
|
|
16
|
+
* `agentquad openclaw bootstrap` 显式忽略 marker 强制装回
|
|
17
|
+
* - settings.json 损坏 → warn-skip,不让 agentquad start 挂掉
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, unlinkSync } from 'node:fs'
|
|
20
|
+
import { dirname, join } from 'node:path'
|
|
21
|
+
import { homedir } from 'node:os'
|
|
22
|
+
import { fileURLToPath } from 'node:url'
|
|
23
|
+
import { DEFAULT_ROOT_DIR } from './config.js'
|
|
24
|
+
|
|
25
|
+
const QUADTODO_MANAGED_KEY = '_quadtodoManaged'
|
|
26
|
+
const HOOK_EVENTS = ['Stop', 'Notification', 'SessionEnd']
|
|
27
|
+
const HOOK_VERSION_RE = /quadtodo-hook-version:\s*(\d+)/
|
|
28
|
+
|
|
29
|
+
function defaultHookScriptPath() {
|
|
30
|
+
return join(DEFAULT_ROOT_DIR, 'claude-hooks', 'notify.js')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function defaultSettingsPath() {
|
|
34
|
+
return join(homedir(), '.claude', 'settings.json')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function defaultTemplatePath() {
|
|
38
|
+
return fileURLToPath(new URL('./templates/claude-hooks/notify.js', import.meta.url))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function defaultUninstallMarkerPath() {
|
|
42
|
+
return join(DEFAULT_ROOT_DIR, 'claude-hooks', '.uninstalled')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseHookVersion(content) {
|
|
46
|
+
if (!content) return null
|
|
47
|
+
const m = content.match(HOOK_VERSION_RE)
|
|
48
|
+
return m ? Number(m[1]) : 0 // 0 = unversioned legacy script
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildHookEntry(event, hookScriptPath) {
|
|
52
|
+
// Claude Code hook 格式(参考其文档):matchers 数组里每项有 type+command
|
|
53
|
+
const eventLower = event === 'SessionEnd' ? 'session-end'
|
|
54
|
+
: event === 'Notification' ? 'notification'
|
|
55
|
+
: 'stop'
|
|
56
|
+
return {
|
|
57
|
+
matcher: '*',
|
|
58
|
+
hooks: [
|
|
59
|
+
{
|
|
60
|
+
type: 'command',
|
|
61
|
+
command: `node ${hookScriptPath} ${eventLower}`,
|
|
62
|
+
[QUADTODO_MANAGED_KEY]: true,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
[QUADTODO_MANAGED_KEY]: true,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function loadSettings(path) {
|
|
70
|
+
if (!existsSync(path)) return {}
|
|
71
|
+
const raw = readFileSync(path, 'utf8')
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(raw)
|
|
74
|
+
} catch (e) {
|
|
75
|
+
const err = new Error(`settings.json malformed: ${e.message}`)
|
|
76
|
+
err.code = 'malformed_settings'
|
|
77
|
+
err.path = path
|
|
78
|
+
throw err
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function saveSettings(path, data) {
|
|
83
|
+
const dir = dirname(path)
|
|
84
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
85
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + '\n')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function backupSettings(path) {
|
|
89
|
+
if (!existsSync(path)) return null
|
|
90
|
+
const bak = `${path}.bak.${Date.now()}`
|
|
91
|
+
copyFileSync(path, bak)
|
|
92
|
+
return bak
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 把 AgentQuad 的 3 个 hook entry 合并到 settings.json。
|
|
97
|
+
* 重复安装是幂等的:如果已经有 quadtodo-managed 同名 entry,先清掉再加,避免重复 fire。
|
|
98
|
+
*
|
|
99
|
+
* 返回 { settingsPath, backup, added: [event...], skipped: [event...] }
|
|
100
|
+
*/
|
|
101
|
+
export function installHooks({
|
|
102
|
+
settingsPath = defaultSettingsPath(),
|
|
103
|
+
hookScriptPath = defaultHookScriptPath(),
|
|
104
|
+
events = HOOK_EVENTS,
|
|
105
|
+
uninstallMarkerPath = defaultUninstallMarkerPath(),
|
|
106
|
+
clearUninstallMarker = true,
|
|
107
|
+
} = {}) {
|
|
108
|
+
if (!existsSync(hookScriptPath)) {
|
|
109
|
+
const err = new Error(`hook script not found: ${hookScriptPath}`)
|
|
110
|
+
err.code = 'hook_script_missing'
|
|
111
|
+
throw err
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const data = loadSettings(settingsPath)
|
|
115
|
+
const backup = backupSettings(settingsPath)
|
|
116
|
+
if (!data.hooks || typeof data.hooks !== 'object') data.hooks = {}
|
|
117
|
+
|
|
118
|
+
const added = []
|
|
119
|
+
for (const event of events) {
|
|
120
|
+
if (!Array.isArray(data.hooks[event])) data.hooks[event] = []
|
|
121
|
+
// 移除旧的 _quadtodoManaged entry(避免重复 install 累加)
|
|
122
|
+
data.hooks[event] = data.hooks[event].filter((entry) => !entry?.[QUADTODO_MANAGED_KEY])
|
|
123
|
+
data.hooks[event].push(buildHookEntry(event, hookScriptPath))
|
|
124
|
+
added.push(event)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
saveSettings(settingsPath, data)
|
|
128
|
+
// 用户重新装 = 收回之前的 uninstall 拒绝;下次 start 不再被 marker 拦
|
|
129
|
+
let markerCleared = false
|
|
130
|
+
if (clearUninstallMarker && existsSync(uninstallMarkerPath)) {
|
|
131
|
+
try { unlinkSync(uninstallMarkerPath); markerCleared = true } catch { /* 不阻塞 */ }
|
|
132
|
+
}
|
|
133
|
+
return { settingsPath, backup, added, skipped: [], markerCleared }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 移除 AgentQuad 加的所有 hook entry(按 _quadtodoManaged 标记)。
|
|
138
|
+
* 不动其他 entry。默认会写一个 .uninstalled marker,让后续 `agentquad start` 自检不再回写。
|
|
139
|
+
*/
|
|
140
|
+
export function uninstallHooks({
|
|
141
|
+
settingsPath = defaultSettingsPath(),
|
|
142
|
+
uninstallMarkerPath = defaultUninstallMarkerPath(),
|
|
143
|
+
writeUninstallMarker = true,
|
|
144
|
+
} = {}) {
|
|
145
|
+
let markerWritten = false
|
|
146
|
+
const writeMarker = () => {
|
|
147
|
+
if (!writeUninstallMarker) return
|
|
148
|
+
try {
|
|
149
|
+
const dir = dirname(uninstallMarkerPath)
|
|
150
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
151
|
+
writeFileSync(uninstallMarkerPath, `${new Date().toISOString()}\n`)
|
|
152
|
+
markerWritten = true
|
|
153
|
+
} catch { /* 不阻塞 */ }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!existsSync(settingsPath)) {
|
|
157
|
+
writeMarker()
|
|
158
|
+
return { settingsPath, removed: [], backup: null, markerWritten }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const data = loadSettings(settingsPath)
|
|
162
|
+
const backup = backupSettings(settingsPath)
|
|
163
|
+
const removed = []
|
|
164
|
+
|
|
165
|
+
if (data.hooks && typeof data.hooks === 'object') {
|
|
166
|
+
for (const event of Object.keys(data.hooks)) {
|
|
167
|
+
if (!Array.isArray(data.hooks[event])) continue
|
|
168
|
+
const before = data.hooks[event].length
|
|
169
|
+
data.hooks[event] = data.hooks[event].filter((entry) => !entry?.[QUADTODO_MANAGED_KEY])
|
|
170
|
+
if (data.hooks[event].length !== before) {
|
|
171
|
+
removed.push({ event, removedCount: before - data.hooks[event].length })
|
|
172
|
+
}
|
|
173
|
+
// 空数组干净掉
|
|
174
|
+
if (data.hooks[event].length === 0) delete data.hooks[event]
|
|
175
|
+
}
|
|
176
|
+
if (Object.keys(data.hooks).length === 0) delete data.hooks
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
saveSettings(settingsPath, data)
|
|
180
|
+
writeMarker()
|
|
181
|
+
return { settingsPath, removed, backup, markerWritten }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 查询当前 AgentQuad hook 安装状态。返回 {installed: bool, eventsInstalled: [], settingsPath, scriptExists}
|
|
186
|
+
*/
|
|
187
|
+
export function inspectHooks({
|
|
188
|
+
settingsPath = defaultSettingsPath(),
|
|
189
|
+
hookScriptPath = defaultHookScriptPath(),
|
|
190
|
+
} = {}) {
|
|
191
|
+
const scriptExists = existsSync(hookScriptPath)
|
|
192
|
+
if (!existsSync(settingsPath)) {
|
|
193
|
+
return { installed: false, eventsInstalled: [], settingsPath, hookScriptPath, scriptExists }
|
|
194
|
+
}
|
|
195
|
+
let data
|
|
196
|
+
try {
|
|
197
|
+
data = loadSettings(settingsPath)
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return { installed: false, eventsInstalled: [], settingsPath, hookScriptPath, scriptExists, error: e.code }
|
|
200
|
+
}
|
|
201
|
+
const eventsInstalled = []
|
|
202
|
+
for (const event of HOOK_EVENTS) {
|
|
203
|
+
const arr = data?.hooks?.[event]
|
|
204
|
+
if (!Array.isArray(arr)) continue
|
|
205
|
+
if (arr.some((entry) => entry?.[QUADTODO_MANAGED_KEY])) eventsInstalled.push(event)
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
installed: eventsInstalled.length === HOOK_EVENTS.length,
|
|
209
|
+
eventsInstalled,
|
|
210
|
+
settingsPath,
|
|
211
|
+
hookScriptPath,
|
|
212
|
+
scriptExists,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 把仓库内置的 notify.js 模板部署到 ~/.agentquad/claude-hooks/notify.js。
|
|
218
|
+
*
|
|
219
|
+
* 行为:
|
|
220
|
+
* - 目标不存在 → 直接写(action: 'installed')
|
|
221
|
+
* - 目标版本 < 模板版本 → 备份旧脚本,覆盖(action: 'upgraded')
|
|
222
|
+
* - 目标版本 = 模板版本 → 不动(action: 'unchanged')
|
|
223
|
+
* - 目标存在但解析不出版本号 → 视为 v0,按升级路径处理(保护用户改动)
|
|
224
|
+
*
|
|
225
|
+
* 返回 { action, version, previousVersion, scriptPath, backup }
|
|
226
|
+
*/
|
|
227
|
+
export function deployHookScript({
|
|
228
|
+
scriptPath = defaultHookScriptPath(),
|
|
229
|
+
templatePath = defaultTemplatePath(),
|
|
230
|
+
} = {}) {
|
|
231
|
+
if (!existsSync(templatePath)) {
|
|
232
|
+
const err = new Error(`hook template not found: ${templatePath}`)
|
|
233
|
+
err.code = 'hook_template_missing'
|
|
234
|
+
throw err
|
|
235
|
+
}
|
|
236
|
+
const templateContent = readFileSync(templatePath, 'utf8')
|
|
237
|
+
const templateVersion = parseHookVersion(templateContent)
|
|
238
|
+
|
|
239
|
+
const dir = dirname(scriptPath)
|
|
240
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
241
|
+
|
|
242
|
+
const previousVersion = existsSync(scriptPath)
|
|
243
|
+
? parseHookVersion(readFileSync(scriptPath, 'utf8'))
|
|
244
|
+
: null
|
|
245
|
+
|
|
246
|
+
if (previousVersion !== null && previousVersion === templateVersion) {
|
|
247
|
+
return { action: 'unchanged', version: templateVersion, previousVersion, scriptPath, backup: null }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let backup = null
|
|
251
|
+
if (previousVersion !== null) {
|
|
252
|
+
backup = `${scriptPath}.bak.${Date.now()}`
|
|
253
|
+
copyFileSync(scriptPath, backup)
|
|
254
|
+
}
|
|
255
|
+
writeFileSync(scriptPath, templateContent)
|
|
256
|
+
return {
|
|
257
|
+
action: previousVersion === null ? 'installed' : 'upgraded',
|
|
258
|
+
version: templateVersion,
|
|
259
|
+
previousVersion,
|
|
260
|
+
scriptPath,
|
|
261
|
+
backup,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* `agentquad start` 启动时调用:部署 notify.js + 合入 settings.json hook entry。
|
|
267
|
+
*
|
|
268
|
+
* 设计要点:
|
|
269
|
+
* - 已经装好(inspectHooks.installed === true)→ 不重写 settings.json,避免每次 start 都生成 .bak
|
|
270
|
+
* - settings.json 损坏 → skip + 返回 reason,让调用方 warn 而不是让启动挂掉
|
|
271
|
+
* - 用户跑过 uninstall-hook 留下 marker → respectUninstallMarker=true 时 skip
|
|
272
|
+
* `agentquad openclaw bootstrap` 子命令传 false 强装回(同时清掉 marker)
|
|
273
|
+
*
|
|
274
|
+
* 返回 { skipped, reason?, scriptResult, hookResult, alreadyInstalled }
|
|
275
|
+
*/
|
|
276
|
+
export function bootstrapHooks({
|
|
277
|
+
settingsPath = defaultSettingsPath(),
|
|
278
|
+
scriptPath = defaultHookScriptPath(),
|
|
279
|
+
templatePath = defaultTemplatePath(),
|
|
280
|
+
uninstallMarkerPath = defaultUninstallMarkerPath(),
|
|
281
|
+
respectUninstallMarker = true,
|
|
282
|
+
} = {}) {
|
|
283
|
+
if (respectUninstallMarker && existsSync(uninstallMarkerPath)) {
|
|
284
|
+
return { skipped: true, reason: 'uninstall_marker', uninstallMarkerPath }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 显式 bootstrap 时清掉 marker(即便文件不存在也安全)
|
|
288
|
+
let markerCleared = false
|
|
289
|
+
if (!respectUninstallMarker && existsSync(uninstallMarkerPath)) {
|
|
290
|
+
try { unlinkSync(uninstallMarkerPath); markerCleared = true } catch { /* 不阻塞 */ }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const scriptResult = deployHookScript({ scriptPath, templatePath })
|
|
294
|
+
|
|
295
|
+
const inspect = inspectHooks({ settingsPath, hookScriptPath: scriptPath })
|
|
296
|
+
if (inspect.error === 'malformed_settings') {
|
|
297
|
+
return {
|
|
298
|
+
skipped: true,
|
|
299
|
+
reason: 'malformed_settings',
|
|
300
|
+
settingsPath,
|
|
301
|
+
scriptResult,
|
|
302
|
+
markerCleared,
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (inspect.installed) {
|
|
307
|
+
return {
|
|
308
|
+
skipped: false,
|
|
309
|
+
alreadyInstalled: true,
|
|
310
|
+
scriptResult,
|
|
311
|
+
hookResult: null,
|
|
312
|
+
markerCleared,
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const hookResult = installHooks({
|
|
317
|
+
settingsPath,
|
|
318
|
+
hookScriptPath: scriptPath,
|
|
319
|
+
uninstallMarkerPath,
|
|
320
|
+
clearUninstallMarker: false, // 已在上面处理
|
|
321
|
+
})
|
|
322
|
+
return {
|
|
323
|
+
skipped: false,
|
|
324
|
+
alreadyInstalled: false,
|
|
325
|
+
scriptResult,
|
|
326
|
+
hookResult,
|
|
327
|
+
markerCleared,
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export const __test__ = {
|
|
332
|
+
buildHookEntry,
|
|
333
|
+
QUADTODO_MANAGED_KEY,
|
|
334
|
+
HOOK_EVENTS,
|
|
335
|
+
parseHookVersion,
|
|
336
|
+
defaultTemplatePath,
|
|
337
|
+
defaultUninstallMarkerPath,
|
|
338
|
+
}
|