helloagents 3.0.12 → 3.0.16-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 +6 -4
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +182 -35
- package/README_CN.md +184 -37
- package/bootstrap-lite.md +32 -26
- package/bootstrap.md +35 -29
- package/cli.mjs +119 -11
- package/gemini-extension.json +1 -1
- package/install.ps1 +128 -0
- package/install.sh +121 -0
- package/package.json +23 -4
- package/scripts/advisor-state.mjs +36 -63
- package/scripts/capability-registry.mjs +4 -4
- package/scripts/cli-branch.mjs +84 -0
- package/scripts/cli-codex-config.mjs +14 -20
- package/scripts/cli-codex.mjs +32 -38
- package/scripts/cli-doctor-render.mjs +4 -0
- package/scripts/cli-doctor.mjs +40 -30
- package/scripts/cli-host-detect.mjs +0 -1
- package/scripts/cli-hosts.mjs +16 -8
- package/scripts/cli-lifecycle-hosts.mjs +119 -32
- package/scripts/cli-lifecycle.mjs +24 -13
- package/scripts/cli-messages.mjs +34 -16
- package/scripts/cli-runtime-carrier.mjs +15 -0
- package/scripts/cli-runtime-root.mjs +72 -0
- package/scripts/cli-toml.mjs +0 -79
- package/scripts/cli-utils.mjs +30 -4
- package/scripts/closeout-state.mjs +35 -62
- package/scripts/delivery-gate-messages.mjs +70 -0
- package/scripts/delivery-gate.mjs +9 -75
- package/scripts/guard-rules.mjs +42 -42
- package/scripts/guard.mjs +44 -24
- package/scripts/notify-context.mjs +19 -28
- package/scripts/notify-events.mjs +3 -1
- package/scripts/notify-gates.mjs +2 -0
- package/scripts/notify-route.mjs +9 -7
- package/scripts/notify-ui.mjs +42 -32
- package/scripts/notify.mjs +72 -36
- package/scripts/project-storage.mjs +35 -66
- package/scripts/ralph-loop.mjs +36 -31
- package/scripts/replay-state.mjs +31 -128
- package/scripts/review-state.mjs +34 -61
- package/scripts/runtime-artifacts.mjs +95 -0
- package/scripts/runtime-context.mjs +35 -29
- package/scripts/runtime-scope.mjs +313 -0
- package/scripts/session-capsule.mjs +202 -0
- package/scripts/turn-state-cli.mjs +17 -0
- package/scripts/turn-state.mjs +185 -66
- package/scripts/turn-stop-gate.mjs +24 -6
- package/scripts/verify-state.mjs +34 -85
- package/scripts/visual-state.mjs +38 -65
- package/scripts/workflow-core.mjs +3 -3
- package/scripts/workflow-plan-files.mjs +1 -1
- package/scripts/workflow-recommendation.mjs +17 -13
- package/scripts/workflow-state.mjs +5 -5
- package/skills/commands/build/SKILL.md +1 -1
- package/skills/commands/commit/SKILL.md +1 -1
- package/skills/commands/help/SKILL.md +5 -3
- package/skills/commands/loop/SKILL.md +1 -1
- package/skills/commands/plan/SKILL.md +8 -6
- package/skills/commands/prd/SKILL.md +5 -3
- package/skills/commands/verify/SKILL.md +5 -5
- package/skills/hello-debug/SKILL.md +20 -3
- package/skills/hello-review/SKILL.md +2 -2
- package/skills/hello-subagent/SKILL.md +2 -2
- package/skills/hello-test/SKILL.md +6 -2
- package/skills/hello-ui/SKILL.md +7 -7
- package/skills/hello-verify/SKILL.md +10 -7
- package/skills/helloagents/SKILL.md +14 -9
- package/templates/context.md +6 -0
- package/templates/plans/plan.md +3 -0
- package/templates/plans/tasks.md +8 -3
package/scripts/cli-doctor.mjs
CHANGED
|
@@ -2,14 +2,20 @@ import { existsSync, realpathSync } from 'node:fs'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_CONFIG_HEADER, CODEX_PLUGIN_NAME } from './cli-codex.mjs'
|
|
5
|
+
import {
|
|
6
|
+
CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
7
|
+
CODEX_MANAGED_NOTIFY_VALUE,
|
|
8
|
+
} from './cli-codex-config.mjs'
|
|
5
9
|
import { DEFAULTS } from './cli-config.mjs'
|
|
6
10
|
import { printDoctorText } from './cli-doctor-render.mjs'
|
|
11
|
+
import { buildRuntimeCarrier } from './cli-runtime-carrier.mjs'
|
|
7
12
|
import { readTopLevelTomlLine } from './cli-toml.mjs'
|
|
8
|
-
import {
|
|
13
|
+
import { loadHooksWithCliEntry, safeJson, safeRead } from './cli-utils.mjs'
|
|
9
14
|
|
|
10
15
|
const runtime = {
|
|
11
16
|
home: '',
|
|
12
17
|
pkgRoot: '',
|
|
18
|
+
sourceRoot: '',
|
|
13
19
|
pkgVersion: '',
|
|
14
20
|
msg: (cn, en) => en || cn,
|
|
15
21
|
readSettings: () => ({}),
|
|
@@ -69,15 +75,16 @@ function pickManagedHooks(hooks) {
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
function readExpectedHooks(hooksFile, pathVar) {
|
|
72
|
-
return pickManagedHooks(
|
|
78
|
+
return pickManagedHooks(loadHooksWithCliEntry(runtime.pkgRoot, hooksFile, pathVar)?.hooks || {})
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
function managedHooksMatch(actualHooks, expectedHooks) {
|
|
76
82
|
return stringifySorted(pickManagedHooks(actualHooks || {})) === stringifySorted(expectedHooks || {})
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
function
|
|
80
|
-
|
|
85
|
+
function readExpectedCarrierContent(fileName, settings) {
|
|
86
|
+
const bootstrap = safeRead(join(runtime.pkgRoot, fileName)) || ''
|
|
87
|
+
return normalizeText(buildRuntimeCarrier(bootstrap, settings))
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
function buildDoctorIssue(code, cn, en) {
|
|
@@ -104,7 +111,7 @@ function suggestDoctorFix(host, status, trackedMode) {
|
|
|
104
111
|
return `helloagents update ${host}${trackedMode && trackedMode !== 'none' ? ` --${trackedMode}` : ''}`
|
|
105
112
|
}
|
|
106
113
|
if (status === 'manual-plugin') {
|
|
107
|
-
if (host === 'claude') return '/plugin marketplace add hellowind777/helloagents'
|
|
114
|
+
if (host === 'claude') return '/plugin marketplace add hellowind777/helloagents; /plugin install helloagents@helloagents'
|
|
108
115
|
if (host === 'gemini') return 'gemini extensions install https://github.com/hellowind777/helloagents'
|
|
109
116
|
}
|
|
110
117
|
if (status === 'not-installed') {
|
|
@@ -126,12 +133,13 @@ function inspectClaudeDoctor(settings) {
|
|
|
126
133
|
const expectedHooks = readExpectedHooks('hooks-claude.json', '${CLAUDE_PLUGIN_ROOT}')
|
|
127
134
|
const checks = {
|
|
128
135
|
carrierMarker: (safeRead(join(claudeDir, 'CLAUDE.md')) || '').includes('HELLOAGENTS_START'),
|
|
129
|
-
carrierContentMatch: extractManagedCarrierContent(join(claudeDir, 'CLAUDE.md'))
|
|
136
|
+
carrierContentMatch: extractManagedCarrierContent(join(claudeDir, 'CLAUDE.md'))
|
|
137
|
+
=== readExpectedCarrierContent('bootstrap-lite.md', settings),
|
|
130
138
|
homeLink: safeRealTarget(join(claudeDir, 'helloagents')) === runtime.pkgRoot,
|
|
131
139
|
settingsHooks: JSON.stringify(claudeSettings.hooks || {}).includes('helloagents'),
|
|
132
140
|
settingsHooksMatch: managedHooksMatch(claudeSettings.hooks || {}, expectedHooks),
|
|
133
141
|
settingsPermission: Array.isArray(claudeSettings.permissions?.allow)
|
|
134
|
-
&& claudeSettings.permissions.allow.includes('Read(~/.
|
|
142
|
+
&& claudeSettings.permissions.allow.includes('Read(~/.helloagents/helloagents/**)'),
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
const issues = []
|
|
@@ -142,15 +150,15 @@ function inspectClaudeDoctor(settings) {
|
|
|
142
150
|
if (detectedMode === 'standby') {
|
|
143
151
|
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
144
152
|
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
|
|
153
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向稳定运行根目录', 'Standby home link is missing or points to a different runtime root'))
|
|
146
154
|
if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
|
|
147
155
|
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
156
|
if (!checks.settingsPermission) issues.push(buildDoctorIssue('standby-permission-missing', 'standby Claude 权限注入缺失', 'Standby Claude permission injection is missing'))
|
|
149
157
|
}
|
|
150
158
|
if (trackedMode === 'global') {
|
|
151
159
|
notes.push(runtime.msg(
|
|
152
|
-
'Claude Code 的 global
|
|
153
|
-
'Claude Code global
|
|
160
|
+
'Claude Code 的 global 模式由宿主插件系统管理;doctor 只检查 standby 残留,不直接探测插件状态。',
|
|
161
|
+
'Claude Code global mode is managed by the host plugin system; doctor only checks for standby residue and does not inspect plugin state directly.',
|
|
154
162
|
))
|
|
155
163
|
if (checks.carrierMarker || checks.homeLink || checks.settingsHooks || checks.settingsPermission) {
|
|
156
164
|
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is tracked as global'))
|
|
@@ -176,7 +184,8 @@ function inspectGeminiDoctor(settings) {
|
|
|
176
184
|
const expectedHooks = readExpectedHooks('hooks.json', '${extensionPath}')
|
|
177
185
|
const checks = {
|
|
178
186
|
carrierMarker: (safeRead(join(geminiDir, 'GEMINI.md')) || '').includes('HELLOAGENTS_START'),
|
|
179
|
-
carrierContentMatch: extractManagedCarrierContent(join(geminiDir, 'GEMINI.md'))
|
|
187
|
+
carrierContentMatch: extractManagedCarrierContent(join(geminiDir, 'GEMINI.md'))
|
|
188
|
+
=== readExpectedCarrierContent('bootstrap-lite.md', settings),
|
|
180
189
|
homeLink: safeRealTarget(join(geminiDir, 'helloagents')) === runtime.pkgRoot,
|
|
181
190
|
settingsHooks: JSON.stringify(geminiSettings.hooks || {}).includes('helloagents'),
|
|
182
191
|
settingsHooksMatch: managedHooksMatch(geminiSettings.hooks || {}, expectedHooks),
|
|
@@ -190,14 +199,14 @@ function inspectGeminiDoctor(settings) {
|
|
|
190
199
|
if (detectedMode === 'standby') {
|
|
191
200
|
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
192
201
|
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
|
|
202
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向稳定运行根目录', 'Standby home link is missing or points to a different runtime root'))
|
|
194
203
|
if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
|
|
195
204
|
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
205
|
}
|
|
197
206
|
if (trackedMode === 'global') {
|
|
198
207
|
notes.push(runtime.msg(
|
|
199
|
-
'Gemini CLI 的 global
|
|
200
|
-
'Gemini CLI global
|
|
208
|
+
'Gemini CLI 的 global 模式由宿主扩展系统管理;doctor 只检查 standby 残留,不直接探测扩展状态。',
|
|
209
|
+
'Gemini CLI global mode is managed by the host extension system; doctor only checks for standby residue and does not inspect extension state directly.',
|
|
201
210
|
))
|
|
202
211
|
if (checks.carrierMarker || checks.homeLink || checks.settingsHooks) {
|
|
203
212
|
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is tracked as global'))
|
|
@@ -217,12 +226,12 @@ function inspectGeminiDoctor(settings) {
|
|
|
217
226
|
function appendCodexStandbyIssues(issues, checks) {
|
|
218
227
|
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
219
228
|
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
|
|
229
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向稳定运行根目录', 'Standby home link is missing or points to a different runtime root'))
|
|
221
230
|
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
231
|
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
232
|
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
|
|
225
|
-
if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled
|
|
233
|
+
if (checks.codexNotify && !checks.notifyPathMatch) issues.push(buildDoctorIssue('standby-notify-drift', 'standby notify 未使用受管命令入口', 'Standby notify does not use the managed command entrypoint'))
|
|
234
|
+
if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled) {
|
|
226
235
|
issues.push(buildDoctorIssue('standby-global-residue', 'standby 模式下仍残留 global 插件文件或配置', 'Global plugin artifacts still remain while Codex is in standby mode'))
|
|
227
236
|
}
|
|
228
237
|
}
|
|
@@ -239,8 +248,8 @@ function appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion) {
|
|
|
239
248
|
if (!checks.pluginEnabled) issues.push(buildDoctorIssue('global-plugin-disabled', 'global config 中缺少插件启用段', 'Global plugin enablement block is missing from config'))
|
|
240
249
|
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('global-model-instructions-missing', 'global config 缺少受管 model_instructions_file', 'Global config is missing the managed model_instructions_file'))
|
|
241
250
|
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`'))
|
|
242
|
-
if (!checks.
|
|
243
|
-
if (checks.
|
|
251
|
+
if (!checks.codexNotify) issues.push(buildDoctorIssue('global-notify-missing', 'global notify 配置缺失', 'Global notify configuration is missing'))
|
|
252
|
+
if (checks.codexNotify && !checks.globalNotifyPathMatch) issues.push(buildDoctorIssue('global-notify-drift', 'global notify 未使用受管命令入口', 'Global notify does not use the managed command entrypoint'))
|
|
244
253
|
if (pluginVersion && !checks.pluginVersionMatch) issues.push(buildDoctorIssue('global-plugin-version-drift', 'global 插件根目录版本与当前包版本不一致', 'Global plugin root version does not match the current package version'))
|
|
245
254
|
if (cacheVersion && !checks.pluginCacheVersionMatch) issues.push(buildDoctorIssue('global-plugin-cache-version-drift', 'global 插件缓存版本与当前包版本不一致', 'Global plugin cache version does not match the current package version'))
|
|
246
255
|
if (checks.homeLink) {
|
|
@@ -262,31 +271,31 @@ function inspectCodexDoctor(settings) {
|
|
|
262
271
|
const homeLinkTarget = safeRealTarget(join(codexDir, 'helloagents'))
|
|
263
272
|
const pkgRootTarget = safeRealTarget(runtime.pkgRoot) || normalizePath(runtime.pkgRoot)
|
|
264
273
|
const pluginRootTarget = safeRealTarget(pluginRoot) || normalizePath(pluginRoot)
|
|
265
|
-
const standbyNotifyPath = normalizePath(join(runtime.pkgRoot, 'scripts', 'notify.mjs'))
|
|
266
|
-
const globalNotifyPath = normalizePath(join(pluginRoot, 'scripts', 'notify.mjs'))
|
|
267
|
-
const managedHomeCarrierPath = normalizePath(join(codexDir, 'AGENTS.md'))
|
|
268
274
|
const modelInstructionsLine = readTopLevelTomlLine(codexConfig, 'model_instructions_file')
|
|
269
275
|
const expectedHomeCarrier = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
|
|
270
276
|
? 'bootstrap.md'
|
|
271
277
|
: 'bootstrap-lite.md'
|
|
272
278
|
const checks = {
|
|
273
279
|
carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
274
|
-
carrierContentMatch: extractManagedCarrierContent(join(codexDir, 'AGENTS.md'))
|
|
280
|
+
carrierContentMatch: extractManagedCarrierContent(join(codexDir, 'AGENTS.md'))
|
|
281
|
+
=== readExpectedCarrierContent(expectedHomeCarrier, settings),
|
|
275
282
|
homeLink: homeLinkTarget === pkgRootTarget,
|
|
276
283
|
globalHomeLink: homeLinkTarget === pluginRootTarget,
|
|
277
284
|
modelInstructionsFile: !!modelInstructionsLine,
|
|
278
285
|
modelInstructionsPathMatch: !!modelInstructionsLine
|
|
279
|
-
&& normalizePath(modelInstructionsLine).includes(`"${
|
|
286
|
+
&& normalizePath(modelInstructionsLine).includes(`"${CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH}"`),
|
|
280
287
|
codexNotify: codexConfig.includes('codex-notify'),
|
|
281
|
-
notifyPathMatch: codexConfig.includes(
|
|
288
|
+
notifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
|
|
282
289
|
pluginRoot: existsSync(pluginRoot),
|
|
283
290
|
pluginCache: existsSync(pluginCacheRoot),
|
|
284
|
-
pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '')
|
|
285
|
-
|
|
291
|
+
pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '')
|
|
292
|
+
=== readExpectedCarrierContent('bootstrap.md', settings),
|
|
293
|
+
pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '')
|
|
294
|
+
=== readExpectedCarrierContent('bootstrap.md', settings),
|
|
286
295
|
marketplaceEntry: Array.isArray(marketplace.plugins) && marketplace.plugins.some((plugin) => plugin?.name === CODEX_PLUGIN_NAME),
|
|
287
296
|
pluginEnabled: codexConfig.includes(CODEX_PLUGIN_CONFIG_HEADER) && codexConfig.includes('enabled = true'),
|
|
288
|
-
globalNotifyPath: codexConfig.includes('
|
|
289
|
-
globalNotifyPathMatch: codexConfig.includes(
|
|
297
|
+
globalNotifyPath: codexConfig.includes('codex-notify'),
|
|
298
|
+
globalNotifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
|
|
290
299
|
pluginVersionMatch: pluginVersion ? pluginVersion === runtime.pkgVersion : false,
|
|
291
300
|
pluginCacheVersionMatch: cacheVersion ? cacheVersion === runtime.pkgVersion : false,
|
|
292
301
|
}
|
|
@@ -355,7 +364,8 @@ function buildDoctorReport(host) {
|
|
|
355
364
|
return {
|
|
356
365
|
config: {
|
|
357
366
|
packageVersion: runtime.pkgVersion,
|
|
358
|
-
|
|
367
|
+
runtimeRoot: runtime.pkgRoot,
|
|
368
|
+
packageRoot: runtime.sourceRoot || runtime.pkgRoot,
|
|
359
369
|
installMode: settings.install_mode || DEFAULTS.install_mode,
|
|
360
370
|
trackedHostModes: settings.host_install_modes || {},
|
|
361
371
|
},
|
|
@@ -75,7 +75,6 @@ function detectCodexMode(home) {
|
|
|
75
75
|
|| existsSync(join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME))
|
|
76
76
|
|| marketplace.includes(`"name": "${CODEX_PLUGIN_NAME}"`)
|
|
77
77
|
|| codexConfig.includes(CODEX_PLUGIN_KEY)
|
|
78
|
-
|| codexConfig.includes(`/plugins/${CODEX_PLUGIN_NAME}/scripts/notify.mjs`)
|
|
79
78
|
|| codexHomeLinkTarget === globalPluginRoot
|
|
80
79
|
) {
|
|
81
80
|
return 'global'
|
package/scripts/cli-hosts.mjs
CHANGED
|
@@ -3,15 +3,15 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import {
|
|
4
4
|
ensureDir,
|
|
5
5
|
safeRead,
|
|
6
|
-
removeIfExists,
|
|
7
6
|
createLink,
|
|
8
7
|
removeLink,
|
|
9
8
|
injectMarkedContent,
|
|
10
9
|
removeMarkedContent,
|
|
11
10
|
mergeSettingsHooks,
|
|
12
11
|
cleanSettingsHooks,
|
|
13
|
-
|
|
12
|
+
loadHooksWithCliEntry,
|
|
14
13
|
} from './cli-utils.mjs';
|
|
14
|
+
import { buildRuntimeCarrier, readCarrierSettings } from './cli-runtime-carrier.mjs';
|
|
15
15
|
|
|
16
16
|
export function installClaudeStandby(home, pkgRoot) {
|
|
17
17
|
const claudeDir = join(home, '.claude');
|
|
@@ -19,15 +19,21 @@ export function installClaudeStandby(home, pkgRoot) {
|
|
|
19
19
|
|
|
20
20
|
const bootstrapContent = safeRead(join(pkgRoot, 'bootstrap-lite.md'));
|
|
21
21
|
if (bootstrapContent) {
|
|
22
|
-
injectMarkedContent(
|
|
22
|
+
injectMarkedContent(
|
|
23
|
+
join(claudeDir, 'CLAUDE.md'),
|
|
24
|
+
buildRuntimeCarrier(bootstrapContent, readCarrierSettings(home)).trimEnd(),
|
|
25
|
+
);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
createLink(pkgRoot, join(claudeDir, 'helloagents'));
|
|
26
29
|
|
|
27
30
|
const settingsPath = join(claudeDir, 'settings.json');
|
|
28
|
-
const hooksData =
|
|
31
|
+
const hooksData = loadHooksWithCliEntry(pkgRoot, 'hooks-claude.json', '${CLAUDE_PLUGIN_ROOT}');
|
|
29
32
|
if (hooksData) {
|
|
30
|
-
mergeSettingsHooks(settingsPath, hooksData, [
|
|
33
|
+
mergeSettingsHooks(settingsPath, hooksData, [
|
|
34
|
+
'Read(~/.helloagents/helloagents/**)',
|
|
35
|
+
'Read(~/.claude/helloagents/**)',
|
|
36
|
+
]);
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
return true;
|
|
@@ -50,13 +56,16 @@ export function installGeminiStandby(home, pkgRoot) {
|
|
|
50
56
|
|
|
51
57
|
const bootstrapContent = safeRead(join(pkgRoot, 'bootstrap-lite.md'));
|
|
52
58
|
if (bootstrapContent) {
|
|
53
|
-
injectMarkedContent(
|
|
59
|
+
injectMarkedContent(
|
|
60
|
+
join(geminiDir, 'GEMINI.md'),
|
|
61
|
+
buildRuntimeCarrier(bootstrapContent, readCarrierSettings(home)).trimEnd(),
|
|
62
|
+
);
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
createLink(pkgRoot, join(geminiDir, 'helloagents'));
|
|
57
66
|
|
|
58
67
|
const settingsPath = join(geminiDir, 'settings.json');
|
|
59
|
-
const hooksData =
|
|
68
|
+
const hooksData = loadHooksWithCliEntry(pkgRoot, 'hooks.json', '${extensionPath}');
|
|
60
69
|
if (hooksData) mergeSettingsHooks(settingsPath, hooksData);
|
|
61
70
|
|
|
62
71
|
return true;
|
|
@@ -69,7 +78,6 @@ export function uninstallGeminiStandby(home) {
|
|
|
69
78
|
removeMarkedContent(join(geminiDir, 'GEMINI.md'));
|
|
70
79
|
removeLink(join(geminiDir, 'helloagents'));
|
|
71
80
|
cleanSettingsHooks(join(geminiDir, 'settings.json'));
|
|
72
|
-
removeIfExists(join(geminiDir, 'helloagents-hooks.json'));
|
|
73
81
|
|
|
74
82
|
return true;
|
|
75
83
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
|
|
1
3
|
import { installClaudeStandby, installGeminiStandby, uninstallClaudeStandby, uninstallGeminiStandby } from './cli-hosts.mjs'
|
|
2
4
|
import {
|
|
5
|
+
cleanupCodexGlobalResidueForStandby,
|
|
3
6
|
installCodexGlobal,
|
|
4
7
|
installCodexStandby,
|
|
5
8
|
uninstallCodexGlobal,
|
|
@@ -7,11 +10,63 @@ import {
|
|
|
7
10
|
} from './cli-codex.mjs'
|
|
8
11
|
import { getHostLabel } from './cli-host-detect.mjs'
|
|
9
12
|
|
|
13
|
+
const CLAUDE_COMMAND = process.env.HELLOAGENTS_CLAUDE_CMD || 'claude'
|
|
14
|
+
const GEMINI_COMMAND = process.env.HELLOAGENTS_GEMINI_CMD || 'gemini'
|
|
15
|
+
const CLAUDE_MARKETPLACE = 'hellowind777/helloagents'
|
|
16
|
+
const CLAUDE_PLUGIN = 'helloagents@helloagents'
|
|
17
|
+
const GEMINI_EXTENSION = 'https://github.com/hellowind777/helloagents'
|
|
18
|
+
|
|
19
|
+
function runHostCommand(command, args) {
|
|
20
|
+
const needsShell = process.platform === 'win32' && /\.cmd$/i.test(command)
|
|
21
|
+
const result = spawnSync(command, args, {
|
|
22
|
+
encoding: 'utf-8',
|
|
23
|
+
errors: 'replace',
|
|
24
|
+
shell: needsShell,
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
})
|
|
27
|
+
const errorMessage = result.error?.message || ''
|
|
28
|
+
return {
|
|
29
|
+
ok: result.status === 0,
|
|
30
|
+
missing: result.error?.code === 'ENOENT',
|
|
31
|
+
output: `${result.stdout || ''}${result.stderr || ''}${errorMessage}`.trim(),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildNativeResult(result, successCN, successEN, manualCN, manualEN) {
|
|
36
|
+
if (result.ok) return { ok: true, noteCN: successCN, noteEN: successEN }
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
noteCN: `${manualCN}${result.output ? `;原因:${result.output}` : ''}`,
|
|
40
|
+
noteEN: `${manualEN}${result.output ? `; reason: ${result.output}` : ''}`,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function installClaudeGlobalPlugin() {
|
|
45
|
+
const add = runHostCommand(CLAUDE_COMMAND, ['plugin', 'marketplace', 'add', CLAUDE_MARKETPLACE])
|
|
46
|
+
if (!add.ok && add.missing) return { ok: false, output: '未找到 claude 命令' }
|
|
47
|
+
const install = runHostCommand(CLAUDE_COMMAND, ['plugin', 'install', CLAUDE_PLUGIN, '--scope', 'user'])
|
|
48
|
+
return { ok: install.ok, output: install.output || add.output }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function installGeminiGlobalExtension() {
|
|
52
|
+
return runHostCommand(GEMINI_COMMAND, ['extensions', 'install', GEMINI_EXTENSION])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function removeClaudeGlobalPlugin() {
|
|
56
|
+
return runHostCommand(CLAUDE_COMMAND, ['plugin', 'remove', 'helloagents'])
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function removeGeminiGlobalExtension() {
|
|
60
|
+
return runHostCommand(GEMINI_COMMAND, ['extensions', 'uninstall', 'helloagents'])
|
|
61
|
+
}
|
|
62
|
+
|
|
10
63
|
function reportHostAction(runtime, action, host, mode, result = {}) {
|
|
11
64
|
const label = getHostLabel(host)
|
|
12
65
|
const isCleanup = action === 'cleanup' || action === 'uninstall'
|
|
13
66
|
if (result.skipped) {
|
|
14
67
|
console.log(runtime.msg(` - ${label} 未检测到,跳过`, ` - ${label} not detected, skipped`))
|
|
68
|
+
} else if (result.ok === false && !isCleanup) {
|
|
69
|
+
console.log(runtime.msg(` - ${label} 自动配置未完成`, ` - ${label} automatic setup did not complete`))
|
|
15
70
|
} else if (isCleanup) {
|
|
16
71
|
runtime.ok(runtime.msg(`${label} 已清理(${mode} 模式)`, `${label} cleaned (${mode} mode)`))
|
|
17
72
|
} else if (mode === 'standby') {
|
|
@@ -36,24 +91,31 @@ function installHostStandby(runtime, host) {
|
|
|
36
91
|
installGeminiStandby(runtime.home, runtime.pkgRoot)
|
|
37
92
|
return {}
|
|
38
93
|
}
|
|
39
|
-
|
|
40
|
-
|
|
94
|
+
if (!installCodexStandby(runtime.home, runtime.pkgRoot)) return { skipped: true }
|
|
95
|
+
cleanupCodexGlobalResidueForStandby(runtime.home)
|
|
96
|
+
return {}
|
|
41
97
|
}
|
|
42
98
|
|
|
43
99
|
function installHostGlobal(runtime, host) {
|
|
44
100
|
if (host === 'claude') {
|
|
45
101
|
uninstallClaudeStandby(runtime.home)
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
102
|
+
return buildNativeResult(
|
|
103
|
+
installClaudeGlobalPlugin(),
|
|
104
|
+
'已自动安装 Claude Code 插件;重启 Claude Code 后生效',
|
|
105
|
+
'Claude Code plugin installed automatically; restart Claude Code to apply',
|
|
106
|
+
'Claude Code 插件自动安装失败,请在 Claude Code 中执行: /plugin marketplace add hellowind777/helloagents;/plugin install helloagents@helloagents',
|
|
107
|
+
'Claude Code plugin auto-install failed. Run inside Claude Code: /plugin marketplace add hellowind777/helloagents; /plugin install helloagents@helloagents',
|
|
108
|
+
)
|
|
50
109
|
}
|
|
51
110
|
if (host === 'gemini') {
|
|
52
111
|
uninstallGeminiStandby(runtime.home)
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
112
|
+
return buildNativeResult(
|
|
113
|
+
installGeminiGlobalExtension(),
|
|
114
|
+
'已自动安装 Gemini CLI 扩展;重启 Gemini CLI 后生效',
|
|
115
|
+
'Gemini CLI extension installed automatically; restart Gemini CLI to apply',
|
|
116
|
+
'Gemini CLI 扩展自动安装失败,请手动执行: gemini extensions install https://github.com/hellowind777/helloagents',
|
|
117
|
+
'Gemini CLI extension auto-install failed. Run manually: gemini extensions install https://github.com/hellowind777/helloagents',
|
|
118
|
+
)
|
|
57
119
|
}
|
|
58
120
|
uninstallCodexStandby(runtime.home)
|
|
59
121
|
return installCodexGlobal(runtime.home, runtime.pkgRoot) ? {} : { skipped: true }
|
|
@@ -70,45 +132,70 @@ function cleanupHostStandby(runtime, host) {
|
|
|
70
132
|
function cleanupHostGlobal(runtime, host) {
|
|
71
133
|
if (host === 'claude') {
|
|
72
134
|
uninstallClaudeStandby(runtime.home)
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
135
|
+
return buildNativeResult(
|
|
136
|
+
removeClaudeGlobalPlugin(),
|
|
137
|
+
'已自动移除 Claude Code 插件',
|
|
138
|
+
'Claude Code plugin removed automatically',
|
|
139
|
+
'Claude Code 插件自动移除失败,请手动执行: /plugin remove helloagents',
|
|
140
|
+
'Claude Code plugin auto-remove failed. Run manually: /plugin remove helloagents',
|
|
141
|
+
)
|
|
77
142
|
}
|
|
78
143
|
if (host === 'gemini') {
|
|
79
144
|
uninstallGeminiStandby(runtime.home)
|
|
80
|
-
return
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
145
|
+
return buildNativeResult(
|
|
146
|
+
removeGeminiGlobalExtension(),
|
|
147
|
+
'已自动移除 Gemini CLI 扩展',
|
|
148
|
+
'Gemini CLI extension removed automatically',
|
|
149
|
+
'Gemini CLI 扩展自动移除失败,请手动执行: gemini extensions uninstall helloagents',
|
|
150
|
+
'Gemini CLI extension auto-remove failed. Run manually: gemini extensions uninstall helloagents',
|
|
151
|
+
)
|
|
84
152
|
}
|
|
85
153
|
return { skipped: !uninstallCodexGlobal(runtime.home) }
|
|
86
154
|
}
|
|
87
155
|
|
|
88
156
|
function installStandby(runtime) {
|
|
89
|
-
|
|
90
|
-
if (installClaudeStandby(runtime.home, runtime.pkgRoot))
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
else
|
|
157
|
+
const results = {}
|
|
158
|
+
if (installClaudeStandby(runtime.home, runtime.pkgRoot)) {
|
|
159
|
+
runtime.ok(runtime.msg('Claude Code 已配置(standby 模式)', 'Claude Code configured (standby mode)'))
|
|
160
|
+
results.claude = {}
|
|
161
|
+
} else {
|
|
162
|
+
results.claude = { skipped: true }
|
|
163
|
+
}
|
|
164
|
+
if (installGeminiStandby(runtime.home, runtime.pkgRoot)) {
|
|
165
|
+
runtime.ok(runtime.msg('Gemini CLI 已配置(standby 模式)', 'Gemini CLI configured (standby mode)'))
|
|
166
|
+
results.gemini = {}
|
|
167
|
+
} else {
|
|
168
|
+
results.gemini = { skipped: true }
|
|
169
|
+
}
|
|
170
|
+
if (installCodexStandby(runtime.home, runtime.pkgRoot)) {
|
|
171
|
+
cleanupCodexGlobalResidueForStandby(runtime.home)
|
|
172
|
+
runtime.ok(runtime.msg('Codex CLI 已配置(standby 模式)', 'Codex CLI configured (standby mode)'))
|
|
173
|
+
results.codex = {}
|
|
174
|
+
} else {
|
|
175
|
+
console.log(runtime.msg(' - Codex CLI 未检测到,跳过', ' - Codex CLI not detected, skipped'))
|
|
176
|
+
results.codex = { skipped: true }
|
|
177
|
+
}
|
|
178
|
+
return results
|
|
94
179
|
}
|
|
95
180
|
|
|
96
181
|
function installGlobal(runtime) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
182
|
+
const results = {}
|
|
183
|
+
for (const host of ['claude', 'gemini', 'codex']) {
|
|
184
|
+
const result = installHostGlobal(runtime, host)
|
|
185
|
+
reportHostAction(runtime, 'install', host, 'global', result)
|
|
186
|
+
results[host] = result
|
|
187
|
+
}
|
|
188
|
+
return results
|
|
102
189
|
}
|
|
103
190
|
|
|
104
191
|
export function installAllHosts(runtime, mode) {
|
|
105
|
-
if (mode === 'global') installGlobal(runtime)
|
|
106
|
-
|
|
192
|
+
if (mode === 'global') return installGlobal(runtime)
|
|
193
|
+
return installStandby(runtime)
|
|
107
194
|
}
|
|
108
195
|
|
|
109
196
|
export function uninstallAllHosts(runtime) {
|
|
110
|
-
|
|
111
|
-
|
|
197
|
+
cleanupHostGlobal(runtime, 'claude')
|
|
198
|
+
cleanupHostGlobal(runtime, 'gemini')
|
|
112
199
|
uninstallCodexStandby(runtime.home)
|
|
113
200
|
uninstallCodexGlobal(runtime.home)
|
|
114
201
|
}
|
|
@@ -15,6 +15,7 @@ export const HOSTS = ['claude', 'gemini', 'codex']
|
|
|
15
15
|
const runtime = {
|
|
16
16
|
home: '',
|
|
17
17
|
pkgRoot: '',
|
|
18
|
+
sourceRoot: '',
|
|
18
19
|
helloagentsHome: '',
|
|
19
20
|
configFile: '',
|
|
20
21
|
pkgVersion: '',
|
|
@@ -114,7 +115,6 @@ function resolveHostMode(host, explicitMode, settings) {
|
|
|
114
115
|
if (explicitMode) return explicitMode
|
|
115
116
|
return detectHostMode(host)
|
|
116
117
|
|| getTrackedHostMode(settings, host)
|
|
117
|
-
|| (!hasTrackedHostModes(settings) ? (settings.install_mode || '') : '')
|
|
118
118
|
|| DEFAULTS.install_mode
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -124,10 +124,11 @@ function resolveInstallMode(explicitMode, settings) {
|
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
export function syncVersion() {
|
|
127
|
+
const packageRoot = runtime.sourceRoot || runtime.pkgRoot
|
|
127
128
|
const targets = [
|
|
128
|
-
join(
|
|
129
|
-
join(
|
|
130
|
-
join(
|
|
129
|
+
join(packageRoot, '.claude-plugin', 'plugin.json'),
|
|
130
|
+
join(packageRoot, '.codex-plugin', 'plugin.json'),
|
|
131
|
+
join(packageRoot, 'gemini-extension.json'),
|
|
131
132
|
]
|
|
132
133
|
for (const path of targets) {
|
|
133
134
|
const obj = safeJson(path)
|
|
@@ -135,9 +136,9 @@ export function syncVersion() {
|
|
|
135
136
|
obj.version = runtime.pkgVersion
|
|
136
137
|
safeWrite(path, JSON.stringify(obj, null, 2) + '\n')
|
|
137
138
|
}
|
|
138
|
-
const marketPath = join(
|
|
139
|
+
const marketPath = join(packageRoot, '.claude-plugin', 'marketplace.json')
|
|
139
140
|
const market = safeJson(marketPath)
|
|
140
|
-
if (market?.plugins?.[0]) {
|
|
141
|
+
if (market?.plugins?.[0]?.version) {
|
|
141
142
|
market.plugins[0].version = runtime.pkgVersion
|
|
142
143
|
safeWrite(marketPath, JSON.stringify(market, null, 2) + '\n')
|
|
143
144
|
}
|
|
@@ -173,29 +174,39 @@ function runAllHostsLifecycle(action, explicitMode) {
|
|
|
173
174
|
}
|
|
174
175
|
runtime.ok(runtime.msg('所有 CLI 配置已清理', 'All CLI configurations cleaned'))
|
|
175
176
|
console.log(runtime.msg(
|
|
176
|
-
' ℹ ~/.helloagents/ 已保留(如需彻底清理请手动删除)\n ℹ
|
|
177
|
-
' ℹ ~/.helloagents/ preserved (delete manually if desired)\n ℹ
|
|
177
|
+
' ℹ ~/.helloagents/ 已保留(如需彻底清理请手动删除)\n ℹ 已自动尝试移除 Claude/Gemini 插件或扩展;如宿主命令不可用,请手动执行对应移除命令',
|
|
178
|
+
' ℹ ~/.helloagents/ preserved (delete manually if desired)\n ℹ Claude/Gemini plugin or extension removal was attempted automatically; if host commands are unavailable, remove them manually',
|
|
178
179
|
))
|
|
179
180
|
console.log()
|
|
180
181
|
return
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
const settings = readSettings(true)
|
|
184
|
-
if (
|
|
185
|
+
if (!explicitMode) {
|
|
185
186
|
for (const host of HOSTS) {
|
|
186
187
|
const mode = resolveHostMode(host, '', settings)
|
|
187
188
|
const result = runHostLifecycle(runtime, action, host, mode)
|
|
188
|
-
if (!result.skipped) setTrackedHostMode(settings, host, mode)
|
|
189
|
+
if (!result.skipped && result.ok !== false) setTrackedHostMode(settings, host, mode)
|
|
190
|
+
else clearTrackedHostMode(settings, host)
|
|
189
191
|
}
|
|
190
192
|
writeSettings(settings)
|
|
191
|
-
|
|
193
|
+
const modes = Object.values(settings.host_install_modes || {})
|
|
194
|
+
const displayMode = modes.length && modes.every((mode) => mode === modes[0])
|
|
195
|
+
? modes[0]
|
|
196
|
+
: settings.install_mode || DEFAULTS.install_mode
|
|
197
|
+
runtime.printInstallMsg(displayMode, action === 'update' ? 'refresh' : 'install')
|
|
192
198
|
return
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
const mode = resolveInstallMode(explicitMode, settings)
|
|
196
202
|
if (explicitMode) settings.install_mode = explicitMode
|
|
197
|
-
installAllHosts(runtime, mode)
|
|
198
|
-
|
|
203
|
+
const results = installAllHosts(runtime, mode)
|
|
204
|
+
settings.host_install_modes = {}
|
|
205
|
+
for (const host of HOSTS) {
|
|
206
|
+
if (!results?.[host]?.skipped && results?.[host]?.ok !== false) {
|
|
207
|
+
settings.host_install_modes[host] = mode
|
|
208
|
+
}
|
|
209
|
+
}
|
|
199
210
|
writeSettings(settings)
|
|
200
211
|
runtime.printInstallMsg(mode, action === 'update' ? 'refresh' : 'install')
|
|
201
212
|
}
|