helloagents 3.0.3-beta.1 → 3.0.8-beta.1
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +157 -57
- package/README_CN.md +157 -57
- package/bootstrap-lite.md +125 -50
- package/bootstrap.md +169 -123
- package/cli.mjs +80 -427
- package/gemini-extension.json +1 -1
- package/hooks/hooks-claude.json +10 -0
- package/hooks/hooks.json +10 -0
- package/package.json +1 -1
- package/scripts/advisor-state.mjs +222 -0
- package/scripts/capability-registry.mjs +59 -0
- package/scripts/cli-codex-backup.mjs +59 -0
- package/scripts/cli-codex-config.mjs +94 -0
- package/scripts/cli-codex.mjs +90 -222
- package/scripts/cli-config.mjs +1 -0
- package/scripts/cli-doctor-render.mjs +28 -0
- package/scripts/cli-doctor.mjs +370 -0
- package/scripts/cli-host-detect.mjs +94 -0
- package/scripts/cli-lifecycle-hosts.mjs +123 -0
- package/scripts/cli-lifecycle.mjs +213 -0
- package/scripts/cli-messages.mjs +76 -52
- package/scripts/cli-toml.mjs +30 -0
- package/scripts/closeout-state.mjs +213 -0
- package/scripts/delivery-gate.mjs +256 -0
- package/scripts/guard-rules.mjs +147 -0
- package/scripts/guard.mjs +218 -168
- package/scripts/notify-context.mjs +78 -23
- package/scripts/notify-events.mjs +5 -1
- package/scripts/notify-route.mjs +111 -0
- package/scripts/notify-shared.mjs +0 -2
- package/scripts/notify-source.mjs +113 -0
- package/scripts/notify-ui.mjs +40 -6
- package/scripts/notify.mjs +137 -65
- package/scripts/plan-contract.mjs +210 -0
- package/scripts/project-storage.mjs +235 -0
- package/scripts/ralph-loop.mjs +9 -58
- package/scripts/replay-state.mjs +210 -0
- package/scripts/review-state.mjs +220 -0
- package/scripts/runtime-context.mjs +74 -0
- package/scripts/turn-state.mjs +173 -0
- package/scripts/verify-state.mjs +226 -0
- package/scripts/visual-state.mjs +244 -0
- package/scripts/workflow-core.mjs +165 -0
- package/scripts/workflow-plan-files.mjs +249 -0
- package/scripts/workflow-recommendation.mjs +335 -0
- package/scripts/workflow-state.mjs +113 -0
- package/skills/_meta/SKILL.md +1 -1
- package/skills/commands/auto/SKILL.md +48 -67
- package/skills/commands/build/SKILL.md +67 -0
- package/skills/commands/clean/SKILL.md +10 -8
- package/skills/commands/commit/SKILL.md +8 -4
- package/skills/commands/help/SKILL.md +18 -11
- package/skills/commands/idea/SKILL.md +55 -0
- package/skills/commands/init/SKILL.md +16 -8
- package/skills/commands/loop/SKILL.md +6 -5
- package/skills/commands/plan/SKILL.md +118 -0
- package/skills/commands/prd/SKILL.md +22 -15
- package/skills/commands/verify/SKILL.md +32 -9
- package/skills/commands/wiki/SKILL.md +11 -11
- package/skills/hello-review/SKILL.md +9 -0
- package/skills/hello-subagent/SKILL.md +5 -3
- package/skills/hello-ui/SKILL.md +36 -8
- package/skills/hello-verify/SKILL.md +12 -3
- package/skills/helloagents/SKILL.md +36 -20
- package/templates/DESIGN.md +25 -4
- package/templates/STATE.md +3 -0
- package/templates/plans/contract.json +48 -0
- package/templates/plans/plan.md +23 -0
- package/templates/plans/tasks.md +3 -3
- package/skills/commands/design/SKILL.md +0 -108
- package/skills/commands/review/SKILL.md +0 -16
- package/templates/plans/design.md +0 -14
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_CONFIG_HEADER, CODEX_PLUGIN_NAME } from './cli-codex.mjs'
|
|
5
|
+
import { DEFAULTS } from './cli-config.mjs'
|
|
6
|
+
import { printDoctorText } from './cli-doctor-render.mjs'
|
|
7
|
+
import { readTopLevelTomlLine } from './cli-toml.mjs'
|
|
8
|
+
import { loadHooksWithAbsPath, safeJson, safeRead } from './cli-utils.mjs'
|
|
9
|
+
|
|
10
|
+
const runtime = {
|
|
11
|
+
home: '',
|
|
12
|
+
pkgRoot: '',
|
|
13
|
+
pkgVersion: '',
|
|
14
|
+
msg: (cn, en) => en || cn,
|
|
15
|
+
readSettings: () => ({}),
|
|
16
|
+
getTrackedHostMode: () => '',
|
|
17
|
+
normalizeHost: (value) => value,
|
|
18
|
+
detectHostMode: () => '',
|
|
19
|
+
getHostLabel: (host) => host,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function safeRealTarget(linkPath) {
|
|
23
|
+
try {
|
|
24
|
+
return realpathSync(linkPath)
|
|
25
|
+
} catch {
|
|
26
|
+
return ''
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeText(text = '') {
|
|
31
|
+
return String(text || '').replace(/\r\n/g, '\n').trim()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizePath(value = '') {
|
|
35
|
+
return String(value || '').replace(/\\/g, '/')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractManagedCarrierContent(filePath) {
|
|
39
|
+
const text = safeRead(filePath) || ''
|
|
40
|
+
const match = text.match(/<!-- HELLOAGENTS_START -->([\s\S]*?)<!-- HELLOAGENTS_END -->/)
|
|
41
|
+
return normalizeText(match?.[1] || '')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sortJson(value) {
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.map(sortJson)
|
|
47
|
+
}
|
|
48
|
+
if (value && typeof value === 'object') {
|
|
49
|
+
return Object.keys(value).sort().reduce((acc, key) => {
|
|
50
|
+
acc[key] = sortJson(value[key])
|
|
51
|
+
return acc
|
|
52
|
+
}, {})
|
|
53
|
+
}
|
|
54
|
+
return value
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function stringifySorted(value) {
|
|
58
|
+
return JSON.stringify(sortJson(value))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function pickManagedHooks(hooks) {
|
|
62
|
+
const next = {}
|
|
63
|
+
for (const [event, entries] of Object.entries(hooks || {})) {
|
|
64
|
+
if (!Array.isArray(entries)) continue
|
|
65
|
+
const managedEntries = entries.filter((entry) => JSON.stringify(entry).includes('helloagents'))
|
|
66
|
+
if (managedEntries.length > 0) next[event] = managedEntries
|
|
67
|
+
}
|
|
68
|
+
return next
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readExpectedHooks(hooksFile, pathVar) {
|
|
72
|
+
return pickManagedHooks(loadHooksWithAbsPath(runtime.pkgRoot, hooksFile, pathVar)?.hooks || {})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function managedHooksMatch(actualHooks, expectedHooks) {
|
|
76
|
+
return stringifySorted(pickManagedHooks(actualHooks || {})) === stringifySorted(expectedHooks || {})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readBootstrapContent(fileName) {
|
|
80
|
+
return normalizeText(safeRead(join(runtime.pkgRoot, fileName)) || '')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildDoctorIssue(code, cn, en) {
|
|
84
|
+
return {
|
|
85
|
+
code,
|
|
86
|
+
message: runtime.msg(cn, en),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeDoctorMode(mode = '') {
|
|
91
|
+
return mode || 'none'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function summarizeDoctorStatus(issues, { host, trackedMode, detectedMode } = {}) {
|
|
95
|
+
if (issues.length > 0) return 'drift'
|
|
96
|
+
if (detectedMode !== 'none') return 'ok'
|
|
97
|
+
if (trackedMode === 'global' && ['claude', 'gemini'].includes(host)) return 'manual-plugin'
|
|
98
|
+
if (trackedMode !== 'none') return 'drift'
|
|
99
|
+
return 'not-installed'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function suggestDoctorFix(host, status, trackedMode) {
|
|
103
|
+
if (status === 'drift') {
|
|
104
|
+
return `helloagents update ${host}${trackedMode && trackedMode !== 'none' ? ` --${trackedMode}` : ''}`
|
|
105
|
+
}
|
|
106
|
+
if (status === 'manual-plugin') {
|
|
107
|
+
if (host === 'claude') return '/plugin marketplace add hellowind777/helloagents'
|
|
108
|
+
if (host === 'gemini') return 'gemini extensions install https://github.com/hellowind777/helloagents'
|
|
109
|
+
}
|
|
110
|
+
if (status === 'not-installed') {
|
|
111
|
+
return `helloagents install ${host} --standby`
|
|
112
|
+
}
|
|
113
|
+
return ''
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function initCliDoctor(options) {
|
|
117
|
+
Object.assign(runtime, options)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function inspectClaudeDoctor(settings) {
|
|
121
|
+
const host = 'claude'
|
|
122
|
+
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
123
|
+
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
124
|
+
const claudeDir = join(runtime.home, '.claude')
|
|
125
|
+
const claudeSettings = safeJson(join(claudeDir, 'settings.json')) || {}
|
|
126
|
+
const expectedHooks = readExpectedHooks('hooks-claude.json', '${CLAUDE_PLUGIN_ROOT}')
|
|
127
|
+
const checks = {
|
|
128
|
+
carrierMarker: (safeRead(join(claudeDir, 'CLAUDE.md')) || '').includes('HELLOAGENTS_START'),
|
|
129
|
+
carrierContentMatch: extractManagedCarrierContent(join(claudeDir, 'CLAUDE.md')) === readBootstrapContent('bootstrap-lite.md'),
|
|
130
|
+
homeLink: safeRealTarget(join(claudeDir, 'helloagents')) === runtime.pkgRoot,
|
|
131
|
+
settingsHooks: JSON.stringify(claudeSettings.hooks || {}).includes('helloagents'),
|
|
132
|
+
settingsHooksMatch: managedHooksMatch(claudeSettings.hooks || {}, expectedHooks),
|
|
133
|
+
settingsPermission: Array.isArray(claudeSettings.permissions?.allow)
|
|
134
|
+
&& claudeSettings.permissions.allow.includes('Read(~/.claude/helloagents/**)'),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const issues = []
|
|
138
|
+
const notes = []
|
|
139
|
+
if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
|
|
140
|
+
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
141
|
+
}
|
|
142
|
+
if (detectedMode === 'standby') {
|
|
143
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
144
|
+
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
|
|
145
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
|
|
146
|
+
if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
|
|
147
|
+
if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
|
|
148
|
+
if (!checks.settingsPermission) issues.push(buildDoctorIssue('standby-permission-missing', 'standby Claude 权限注入缺失', 'Standby Claude permission injection is missing'))
|
|
149
|
+
}
|
|
150
|
+
if (trackedMode === 'global') {
|
|
151
|
+
notes.push(runtime.msg(
|
|
152
|
+
'Claude Code 的 global 模式插件需手动安装;doctor 只检查 standby 残留,不直接探测插件状态。',
|
|
153
|
+
'Claude Code global-mode plugins are manual; doctor only checks for standby residue and does not inspect plugin state directly.',
|
|
154
|
+
))
|
|
155
|
+
if (checks.carrierMarker || checks.homeLink || checks.settingsHooks || checks.settingsPermission) {
|
|
156
|
+
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is tracked as global'))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
160
|
+
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
161
|
+
}
|
|
162
|
+
if (trackedMode !== 'none' && detectedMode === 'none' && trackedMode !== 'global') {
|
|
163
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
167
|
+
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function inspectGeminiDoctor(settings) {
|
|
171
|
+
const host = 'gemini'
|
|
172
|
+
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
173
|
+
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
174
|
+
const geminiDir = join(runtime.home, '.gemini')
|
|
175
|
+
const geminiSettings = safeJson(join(geminiDir, 'settings.json')) || {}
|
|
176
|
+
const expectedHooks = readExpectedHooks('hooks.json', '${extensionPath}')
|
|
177
|
+
const checks = {
|
|
178
|
+
carrierMarker: (safeRead(join(geminiDir, 'GEMINI.md')) || '').includes('HELLOAGENTS_START'),
|
|
179
|
+
carrierContentMatch: extractManagedCarrierContent(join(geminiDir, 'GEMINI.md')) === readBootstrapContent('bootstrap-lite.md'),
|
|
180
|
+
homeLink: safeRealTarget(join(geminiDir, 'helloagents')) === runtime.pkgRoot,
|
|
181
|
+
settingsHooks: JSON.stringify(geminiSettings.hooks || {}).includes('helloagents'),
|
|
182
|
+
settingsHooksMatch: managedHooksMatch(geminiSettings.hooks || {}, expectedHooks),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const issues = []
|
|
186
|
+
const notes = []
|
|
187
|
+
if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
|
|
188
|
+
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
189
|
+
}
|
|
190
|
+
if (detectedMode === 'standby') {
|
|
191
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
192
|
+
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
|
|
193
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
|
|
194
|
+
if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
|
|
195
|
+
if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
|
|
196
|
+
}
|
|
197
|
+
if (trackedMode === 'global') {
|
|
198
|
+
notes.push(runtime.msg(
|
|
199
|
+
'Gemini CLI 的 global 模式扩展需手动安装;doctor 只检查 standby 残留,不直接探测扩展状态。',
|
|
200
|
+
'Gemini CLI global-mode extensions are manual; doctor only checks for standby residue and does not inspect extension state directly.',
|
|
201
|
+
))
|
|
202
|
+
if (checks.carrierMarker || checks.homeLink || checks.settingsHooks) {
|
|
203
|
+
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is tracked as global'))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
207
|
+
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
208
|
+
}
|
|
209
|
+
if (trackedMode !== 'none' && detectedMode === 'none' && trackedMode !== 'global') {
|
|
210
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
214
|
+
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function appendCodexStandbyIssues(issues, checks) {
|
|
218
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
219
|
+
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
|
|
220
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
|
|
221
|
+
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('standby-model-instructions-missing', 'standby config 缺少受管 model_instructions_file', 'Standby config is missing the managed model_instructions_file'))
|
|
222
|
+
if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue('standby-model-instructions-drift', 'standby model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Standby model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
|
|
223
|
+
if (!checks.codexNotify) issues.push(buildDoctorIssue('standby-notify-missing', 'standby notify 配置缺失', 'Standby notify configuration is missing'))
|
|
224
|
+
if (checks.codexNotify && !checks.notifyPathMatch) issues.push(buildDoctorIssue('standby-notify-drift', 'standby notify 路径未指向当前包根目录', 'Standby notify path does not point to the current package root'))
|
|
225
|
+
if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled || checks.globalNotifyPath) {
|
|
226
|
+
issues.push(buildDoctorIssue('standby-global-residue', 'standby 模式下仍残留 global 插件文件或配置', 'Global plugin artifacts still remain while Codex is in standby mode'))
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion) {
|
|
231
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('global-home-carrier-missing', 'global `~/.codex/AGENTS.md` 缺少 HelloAGENTS 规则内容', 'Global `~/.codex/AGENTS.md` is missing the HelloAGENTS carrier'))
|
|
232
|
+
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('global-home-carrier-drift', 'global `~/.codex/AGENTS.md` 与当前 bootstrap.md 不一致', 'Global `~/.codex/AGENTS.md` differs from the current bootstrap.md'))
|
|
233
|
+
if (!checks.pluginRoot) issues.push(buildDoctorIssue('global-plugin-root-missing', 'global 插件根目录缺失', 'Global plugin root is missing'))
|
|
234
|
+
if (!checks.pluginCache) issues.push(buildDoctorIssue('global-plugin-cache-missing', 'global 插件缓存目录缺失', 'Global plugin cache directory is missing'))
|
|
235
|
+
if (checks.pluginRoot && !checks.pluginCarrierMatch) issues.push(buildDoctorIssue('global-plugin-carrier-drift', 'global 插件根目录中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin AGENTS.md differs from the current bootstrap.md'))
|
|
236
|
+
if (checks.pluginCache && !checks.pluginCacheCarrierMatch) issues.push(buildDoctorIssue('global-plugin-cache-carrier-drift', 'global 插件缓存中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin cache AGENTS.md differs from the current bootstrap.md'))
|
|
237
|
+
if (!checks.marketplaceEntry) issues.push(buildDoctorIssue('global-marketplace-missing', 'global marketplace 条目缺失', 'Global marketplace entry is missing'))
|
|
238
|
+
if (!checks.pluginEnabled) issues.push(buildDoctorIssue('global-plugin-disabled', 'global config 中缺少插件启用段', 'Global plugin enablement block is missing from config'))
|
|
239
|
+
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('global-model-instructions-missing', 'global config 缺少受管 model_instructions_file', 'Global config is missing the managed model_instructions_file'))
|
|
240
|
+
if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue('global-model-instructions-drift', 'global model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Global model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
|
|
241
|
+
if (!checks.globalNotifyPath) issues.push(buildDoctorIssue('global-notify-missing', 'global notify 路径缺失', 'Global notify path is missing'))
|
|
242
|
+
if (checks.globalNotifyPath && !checks.globalNotifyPathMatch) issues.push(buildDoctorIssue('global-notify-drift', 'global notify 路径未指向当前插件根目录', 'Global notify path does not point to the current plugin root'))
|
|
243
|
+
if (pluginVersion && !checks.pluginVersionMatch) issues.push(buildDoctorIssue('global-plugin-version-drift', 'global 插件根目录版本与当前包版本不一致', 'Global plugin root version does not match the current package version'))
|
|
244
|
+
if (cacheVersion && !checks.pluginCacheVersionMatch) issues.push(buildDoctorIssue('global-plugin-cache-version-drift', 'global 插件缓存版本与当前包版本不一致', 'Global plugin cache version does not match the current package version'))
|
|
245
|
+
if (checks.homeLink) {
|
|
246
|
+
issues.push(buildDoctorIssue('global-standby-link-residue', 'global 模式下仍残留 standby home 链接', 'Standby home link still remains while Codex is in global mode'))
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function inspectCodexDoctor(settings) {
|
|
251
|
+
const host = 'codex'
|
|
252
|
+
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
253
|
+
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
254
|
+
const codexDir = join(runtime.home, '.codex')
|
|
255
|
+
const codexConfig = safeRead(join(codexDir, 'config.toml')) || ''
|
|
256
|
+
const pluginRoot = join(runtime.home, 'plugins', CODEX_PLUGIN_NAME)
|
|
257
|
+
const pluginCacheRoot = join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME, 'local')
|
|
258
|
+
const marketplace = safeJson(join(runtime.home, '.agents', 'plugins', 'marketplace.json')) || {}
|
|
259
|
+
const pluginVersion = safeJson(join(pluginRoot, 'package.json'))?.version || ''
|
|
260
|
+
const cacheVersion = safeJson(join(pluginCacheRoot, 'package.json'))?.version || ''
|
|
261
|
+
const standbyNotifyPath = normalizePath(join(runtime.pkgRoot, 'scripts', 'notify.mjs'))
|
|
262
|
+
const globalNotifyPath = normalizePath(join(pluginRoot, 'scripts', 'notify.mjs'))
|
|
263
|
+
const managedHomeCarrierPath = normalizePath(join(codexDir, 'AGENTS.md'))
|
|
264
|
+
const modelInstructionsLine = readTopLevelTomlLine(codexConfig, 'model_instructions_file')
|
|
265
|
+
const expectedHomeCarrier = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
|
|
266
|
+
? 'bootstrap.md'
|
|
267
|
+
: 'bootstrap-lite.md'
|
|
268
|
+
const checks = {
|
|
269
|
+
carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
270
|
+
carrierContentMatch: extractManagedCarrierContent(join(codexDir, 'AGENTS.md')) === readBootstrapContent(expectedHomeCarrier),
|
|
271
|
+
homeLink: safeRealTarget(join(codexDir, 'helloagents')) === runtime.pkgRoot,
|
|
272
|
+
modelInstructionsFile: !!modelInstructionsLine,
|
|
273
|
+
modelInstructionsPathMatch: !!modelInstructionsLine
|
|
274
|
+
&& normalizePath(modelInstructionsLine).includes(`"${managedHomeCarrierPath}"`),
|
|
275
|
+
codexNotify: codexConfig.includes('codex-notify'),
|
|
276
|
+
notifyPathMatch: codexConfig.includes(standbyNotifyPath),
|
|
277
|
+
pluginRoot: existsSync(pluginRoot),
|
|
278
|
+
pluginCache: existsSync(pluginCacheRoot),
|
|
279
|
+
pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '') === readBootstrapContent('bootstrap.md'),
|
|
280
|
+
pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '') === readBootstrapContent('bootstrap.md'),
|
|
281
|
+
marketplaceEntry: Array.isArray(marketplace.plugins) && marketplace.plugins.some((plugin) => plugin?.name === CODEX_PLUGIN_NAME),
|
|
282
|
+
pluginEnabled: codexConfig.includes(CODEX_PLUGIN_CONFIG_HEADER) && codexConfig.includes('enabled = true'),
|
|
283
|
+
globalNotifyPath: codexConfig.includes('/plugins/helloagents/scripts/notify.mjs'),
|
|
284
|
+
globalNotifyPathMatch: codexConfig.includes(globalNotifyPath),
|
|
285
|
+
pluginVersionMatch: pluginVersion ? pluginVersion === runtime.pkgVersion : false,
|
|
286
|
+
pluginCacheVersionMatch: cacheVersion ? cacheVersion === runtime.pkgVersion : false,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const issues = []
|
|
290
|
+
const notes = []
|
|
291
|
+
if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
|
|
292
|
+
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
293
|
+
}
|
|
294
|
+
if (detectedMode === 'standby') {
|
|
295
|
+
appendCodexStandbyIssues(issues, checks)
|
|
296
|
+
}
|
|
297
|
+
if (detectedMode === 'global') {
|
|
298
|
+
appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion)
|
|
299
|
+
}
|
|
300
|
+
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
301
|
+
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
302
|
+
}
|
|
303
|
+
if (trackedMode !== 'none' && detectedMode === 'none') {
|
|
304
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
305
|
+
}
|
|
306
|
+
if (!checks.pluginVersionMatch && !pluginVersion && detectedMode === 'global') {
|
|
307
|
+
notes.push(runtime.msg('未读到 global 插件根目录版本信息', 'Global plugin root version was not readable'))
|
|
308
|
+
}
|
|
309
|
+
if (!checks.pluginCacheVersionMatch && !cacheVersion && detectedMode === 'global') {
|
|
310
|
+
notes.push(runtime.msg('未读到 global 插件缓存版本信息', 'Global plugin cache version was not readable'))
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
314
|
+
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function parseDoctorArgs(args) {
|
|
318
|
+
const wantsJson = args.includes('--json')
|
|
319
|
+
const unknownFlags = args.filter((arg) => arg.startsWith('--') && arg !== '--json' && arg !== '--all')
|
|
320
|
+
if (unknownFlags.length) {
|
|
321
|
+
throw new Error(runtime.msg(`未知参数: ${unknownFlags.join(', ')}`, `Unknown flags: ${unknownFlags.join(', ')}`))
|
|
322
|
+
}
|
|
323
|
+
const positionals = args.filter((arg) => !arg.startsWith('--'))
|
|
324
|
+
if (positionals.length > 1) {
|
|
325
|
+
throw new Error(runtime.msg(`参数过多: ${positionals.join(' ')}`, `Too many arguments: ${positionals.join(' ')}`))
|
|
326
|
+
}
|
|
327
|
+
const host = runtime.normalizeHost(args.includes('--all') ? 'all' : (positionals[0] || 'all'))
|
|
328
|
+
if (!host) {
|
|
329
|
+
throw new Error(runtime.msg(`不支持的 CLI: ${positionals[0]}`, `Unsupported CLI: ${positionals[0]}`))
|
|
330
|
+
}
|
|
331
|
+
return { host, wantsJson }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function inspectDoctorHost(host, settings) {
|
|
335
|
+
if (host === 'claude') return inspectClaudeDoctor(settings)
|
|
336
|
+
if (host === 'gemini') return inspectGeminiDoctor(settings)
|
|
337
|
+
return inspectCodexDoctor(settings)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function buildDoctorReport(host) {
|
|
341
|
+
const settings = runtime.readSettings(true)
|
|
342
|
+
const hosts = host === 'all' ? ['claude', 'gemini', 'codex'] : [host]
|
|
343
|
+
const reports = hosts.map((target) => inspectDoctorHost(target, settings))
|
|
344
|
+
const summary = reports.reduce((acc, report) => {
|
|
345
|
+
acc[report.status] = (acc[report.status] || 0) + 1
|
|
346
|
+
acc.issueCount += report.issues.length
|
|
347
|
+
return acc
|
|
348
|
+
}, { ok: 0, drift: 0, 'manual-plugin': 0, 'not-installed': 0, issueCount: 0 })
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
config: {
|
|
352
|
+
packageVersion: runtime.pkgVersion,
|
|
353
|
+
packageRoot: runtime.pkgRoot,
|
|
354
|
+
installMode: settings.install_mode || DEFAULTS.install_mode,
|
|
355
|
+
trackedHostModes: settings.host_install_modes || {},
|
|
356
|
+
},
|
|
357
|
+
hosts: reports,
|
|
358
|
+
summary,
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function runDoctor(rawArgs) {
|
|
363
|
+
const { host, wantsJson } = parseDoctorArgs(rawArgs)
|
|
364
|
+
const report = buildDoctorReport(host)
|
|
365
|
+
if (wantsJson) {
|
|
366
|
+
console.log(JSON.stringify(report, null, 2))
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
printDoctorText(runtime, report)
|
|
370
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CODEX_MARKETPLACE_NAME,
|
|
6
|
+
CODEX_PLUGIN_KEY,
|
|
7
|
+
CODEX_PLUGIN_NAME,
|
|
8
|
+
} from './cli-codex.mjs'
|
|
9
|
+
import { safeJson, safeRead } from './cli-utils.mjs'
|
|
10
|
+
|
|
11
|
+
const HOST_ALIASES = new Map([
|
|
12
|
+
['all', 'all'],
|
|
13
|
+
['*', 'all'],
|
|
14
|
+
['claude', 'claude'],
|
|
15
|
+
['claude-code', 'claude'],
|
|
16
|
+
['gemini', 'gemini'],
|
|
17
|
+
['gemini-cli', 'gemini'],
|
|
18
|
+
['codex', 'codex'],
|
|
19
|
+
['codex-cli', 'codex'],
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
function hasHelloagentsMarker(filePath) {
|
|
23
|
+
return (safeRead(filePath) || '').includes('HELLOAGENTS_START')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function hasHelloagentsSettings(filePath) {
|
|
27
|
+
return JSON.stringify(safeJson(filePath) || {}).includes('helloagents')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function detectClaudeMode(home) {
|
|
31
|
+
const claudeDir = join(home, '.claude')
|
|
32
|
+
if (
|
|
33
|
+
existsSync(join(claudeDir, 'helloagents'))
|
|
34
|
+
|| hasHelloagentsMarker(join(claudeDir, 'CLAUDE.md'))
|
|
35
|
+
|| hasHelloagentsSettings(join(claudeDir, 'settings.json'))
|
|
36
|
+
) {
|
|
37
|
+
return 'standby'
|
|
38
|
+
}
|
|
39
|
+
return ''
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function detectGeminiMode(home) {
|
|
43
|
+
const geminiDir = join(home, '.gemini')
|
|
44
|
+
if (
|
|
45
|
+
existsSync(join(geminiDir, 'helloagents'))
|
|
46
|
+
|| hasHelloagentsMarker(join(geminiDir, 'GEMINI.md'))
|
|
47
|
+
|| hasHelloagentsSettings(join(geminiDir, 'settings.json'))
|
|
48
|
+
) {
|
|
49
|
+
return 'standby'
|
|
50
|
+
}
|
|
51
|
+
return ''
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function detectCodexMode(home) {
|
|
55
|
+
const codexDir = join(home, '.codex')
|
|
56
|
+
const codexConfig = safeRead(join(codexDir, 'config.toml')) || ''
|
|
57
|
+
const marketplace = safeRead(join(home, '.agents', 'plugins', 'marketplace.json')) || ''
|
|
58
|
+
if (
|
|
59
|
+
existsSync(join(home, 'plugins', CODEX_PLUGIN_NAME))
|
|
60
|
+
|| existsSync(join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME))
|
|
61
|
+
|| marketplace.includes(`"name": "${CODEX_PLUGIN_NAME}"`)
|
|
62
|
+
|| codexConfig.includes(CODEX_PLUGIN_KEY)
|
|
63
|
+
|| codexConfig.includes(`/plugins/${CODEX_PLUGIN_NAME}/scripts/notify.mjs`)
|
|
64
|
+
) {
|
|
65
|
+
return 'global'
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
existsSync(join(codexDir, 'helloagents'))
|
|
69
|
+
|| hasHelloagentsMarker(join(codexDir, 'AGENTS.md'))
|
|
70
|
+
|| codexConfig.includes('codex-notify')
|
|
71
|
+
|| codexConfig.includes('HelloAGENTS')
|
|
72
|
+
) {
|
|
73
|
+
return 'standby'
|
|
74
|
+
}
|
|
75
|
+
return ''
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function normalizeHost(value = '') {
|
|
79
|
+
return HOST_ALIASES.get(String(value || '').toLowerCase()) || ''
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getHostLabel(host) {
|
|
83
|
+
if (host === 'claude') return 'Claude Code'
|
|
84
|
+
if (host === 'gemini') return 'Gemini CLI'
|
|
85
|
+
if (host === 'codex') return 'Codex CLI'
|
|
86
|
+
return 'All CLIs'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function detectHostMode(host, runtime) {
|
|
90
|
+
if (host === 'claude') return detectClaudeMode(runtime.home)
|
|
91
|
+
if (host === 'gemini') return detectGeminiMode(runtime.home)
|
|
92
|
+
if (host === 'codex') return detectCodexMode(runtime.home)
|
|
93
|
+
return ''
|
|
94
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { installClaudeStandby, installGeminiStandby, uninstallClaudeStandby, uninstallGeminiStandby } from './cli-hosts.mjs'
|
|
2
|
+
import {
|
|
3
|
+
installCodexGlobal,
|
|
4
|
+
installCodexStandby,
|
|
5
|
+
uninstallCodexGlobal,
|
|
6
|
+
uninstallCodexStandby,
|
|
7
|
+
} from './cli-codex.mjs'
|
|
8
|
+
import { getHostLabel } from './cli-host-detect.mjs'
|
|
9
|
+
|
|
10
|
+
function reportHostAction(runtime, action, host, mode, result = {}) {
|
|
11
|
+
const label = getHostLabel(host)
|
|
12
|
+
const isCleanup = action === 'cleanup' || action === 'uninstall'
|
|
13
|
+
if (result.skipped) {
|
|
14
|
+
console.log(runtime.msg(` - ${label} 未检测到,跳过`, ` - ${label} not detected, skipped`))
|
|
15
|
+
} else if (isCleanup) {
|
|
16
|
+
runtime.ok(runtime.msg(`${label} 已清理(${mode} 模式)`, `${label} cleaned (${mode} mode)`))
|
|
17
|
+
} else if (mode === 'standby') {
|
|
18
|
+
runtime.ok(runtime.msg(`${label} 已配置(standby 模式)`, `${label} configured (standby mode)`))
|
|
19
|
+
} else if (host === 'codex') {
|
|
20
|
+
runtime.ok(runtime.msg(`${label} 已安装原生本地插件(global 模式)`, `${label} native local plugin installed (global mode)`))
|
|
21
|
+
} else {
|
|
22
|
+
runtime.ok(runtime.msg(`${label} 已切到 global 模式`, `${label} switched to global mode`))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (result.noteCN || result.noteEN) {
|
|
26
|
+
console.log(runtime.msg(` ℹ ${result.noteCN}`, ` ℹ ${result.noteEN}`))
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function installHostStandby(runtime, host) {
|
|
31
|
+
if (host === 'claude') {
|
|
32
|
+
installClaudeStandby(runtime.home, runtime.pkgRoot)
|
|
33
|
+
return {}
|
|
34
|
+
}
|
|
35
|
+
if (host === 'gemini') {
|
|
36
|
+
installGeminiStandby(runtime.home, runtime.pkgRoot)
|
|
37
|
+
return {}
|
|
38
|
+
}
|
|
39
|
+
uninstallCodexGlobal(runtime.home)
|
|
40
|
+
return installCodexStandby(runtime.home, runtime.pkgRoot) ? {} : { skipped: true }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function installHostGlobal(runtime, host) {
|
|
44
|
+
if (host === 'claude') {
|
|
45
|
+
uninstallClaudeStandby(runtime.home)
|
|
46
|
+
return {
|
|
47
|
+
noteCN: 'Claude Code 的 global 模式需手动安装插件: /plugin marketplace add hellowind777/helloagents',
|
|
48
|
+
noteEN: 'Claude Code global mode still needs a manual plugin install: /plugin marketplace add hellowind777/helloagents',
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (host === 'gemini') {
|
|
52
|
+
uninstallGeminiStandby(runtime.home)
|
|
53
|
+
return {
|
|
54
|
+
noteCN: 'Gemini CLI 的 global 模式需手动安装扩展: gemini extensions install https://github.com/hellowind777/helloagents',
|
|
55
|
+
noteEN: 'Gemini CLI global mode still needs a manual extension install: gemini extensions install https://github.com/hellowind777/helloagents',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
uninstallCodexStandby(runtime.home)
|
|
59
|
+
return installCodexGlobal(runtime.home, runtime.pkgRoot) ? {} : { skipped: true }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cleanupHostStandby(runtime, host) {
|
|
63
|
+
if (host === 'claude') return { skipped: !uninstallClaudeStandby(runtime.home) }
|
|
64
|
+
if (host === 'gemini') return { skipped: !uninstallGeminiStandby(runtime.home) }
|
|
65
|
+
const standbyCleaned = uninstallCodexStandby(runtime.home)
|
|
66
|
+
const globalResidueCleaned = uninstallCodexGlobal(runtime.home)
|
|
67
|
+
return { skipped: !(standbyCleaned || globalResidueCleaned) }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function cleanupHostGlobal(runtime, host) {
|
|
71
|
+
if (host === 'claude') {
|
|
72
|
+
uninstallClaudeStandby(runtime.home)
|
|
73
|
+
return {
|
|
74
|
+
noteCN: '如已安装 Claude Code 插件,请手动执行: /plugin remove helloagents',
|
|
75
|
+
noteEN: 'If the Claude Code plugin is installed, remove it manually: /plugin remove helloagents',
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (host === 'gemini') {
|
|
79
|
+
uninstallGeminiStandby(runtime.home)
|
|
80
|
+
return {
|
|
81
|
+
noteCN: '如已安装 Gemini CLI 扩展,请手动执行: gemini extensions uninstall helloagents',
|
|
82
|
+
noteEN: 'If the Gemini CLI extension is installed, remove it manually: gemini extensions uninstall helloagents',
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { skipped: !uninstallCodexGlobal(runtime.home) }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function installStandby(runtime) {
|
|
89
|
+
uninstallCodexGlobal(runtime.home)
|
|
90
|
+
if (installClaudeStandby(runtime.home, runtime.pkgRoot)) runtime.ok(runtime.msg('Claude Code 已配置(standby 模式)', 'Claude Code configured (standby mode)'))
|
|
91
|
+
if (installGeminiStandby(runtime.home, runtime.pkgRoot)) runtime.ok(runtime.msg('Gemini CLI 已配置(standby 模式)', 'Gemini CLI configured (standby mode)'))
|
|
92
|
+
if (installCodexStandby(runtime.home, runtime.pkgRoot)) runtime.ok(runtime.msg('Codex CLI 已配置(standby 模式)', 'Codex CLI configured (standby mode)'))
|
|
93
|
+
else console.log(runtime.msg(' - Codex CLI 未检测到,跳过', ' - Codex CLI not detected, skipped'))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function installGlobal(runtime) {
|
|
97
|
+
uninstallClaudeStandby(runtime.home)
|
|
98
|
+
uninstallGeminiStandby(runtime.home)
|
|
99
|
+
uninstallCodexStandby(runtime.home)
|
|
100
|
+
if (installCodexGlobal(runtime.home, runtime.pkgRoot)) runtime.ok(runtime.msg('Codex CLI 已安装原生本地插件(global 模式)', 'Codex CLI native local plugin installed (global mode)'))
|
|
101
|
+
else console.log(runtime.msg(' - Codex CLI 未检测到,跳过', ' - Codex CLI not detected, skipped'))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function installAllHosts(runtime, mode) {
|
|
105
|
+
if (mode === 'global') installGlobal(runtime)
|
|
106
|
+
else installStandby(runtime)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function uninstallAllHosts(runtime) {
|
|
110
|
+
uninstallClaudeStandby(runtime.home)
|
|
111
|
+
uninstallGeminiStandby(runtime.home)
|
|
112
|
+
uninstallCodexStandby(runtime.home)
|
|
113
|
+
uninstallCodexGlobal(runtime.home)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function runHostLifecycle(runtime, action, host, mode) {
|
|
117
|
+
const result = (action === 'cleanup' || action === 'uninstall')
|
|
118
|
+
? (mode === 'global' ? cleanupHostGlobal(runtime, host) : cleanupHostStandby(runtime, host))
|
|
119
|
+
: (mode === 'global' ? installHostGlobal(runtime, host) : installHostStandby(runtime, host))
|
|
120
|
+
|
|
121
|
+
reportHostAction(runtime, action, host, mode, result)
|
|
122
|
+
return result
|
|
123
|
+
}
|