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.
Files changed (73) hide show
  1. package/.claude-plugin/marketplace.json +6 -4
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +182 -35
  5. package/README_CN.md +184 -37
  6. package/bootstrap-lite.md +32 -26
  7. package/bootstrap.md +35 -29
  8. package/cli.mjs +119 -11
  9. package/gemini-extension.json +1 -1
  10. package/install.ps1 +128 -0
  11. package/install.sh +121 -0
  12. package/package.json +23 -4
  13. package/scripts/advisor-state.mjs +36 -63
  14. package/scripts/capability-registry.mjs +4 -4
  15. package/scripts/cli-branch.mjs +84 -0
  16. package/scripts/cli-codex-config.mjs +14 -20
  17. package/scripts/cli-codex.mjs +32 -38
  18. package/scripts/cli-doctor-render.mjs +4 -0
  19. package/scripts/cli-doctor.mjs +40 -30
  20. package/scripts/cli-host-detect.mjs +0 -1
  21. package/scripts/cli-hosts.mjs +16 -8
  22. package/scripts/cli-lifecycle-hosts.mjs +119 -32
  23. package/scripts/cli-lifecycle.mjs +24 -13
  24. package/scripts/cli-messages.mjs +34 -16
  25. package/scripts/cli-runtime-carrier.mjs +15 -0
  26. package/scripts/cli-runtime-root.mjs +72 -0
  27. package/scripts/cli-toml.mjs +0 -79
  28. package/scripts/cli-utils.mjs +30 -4
  29. package/scripts/closeout-state.mjs +35 -62
  30. package/scripts/delivery-gate-messages.mjs +70 -0
  31. package/scripts/delivery-gate.mjs +9 -75
  32. package/scripts/guard-rules.mjs +42 -42
  33. package/scripts/guard.mjs +44 -24
  34. package/scripts/notify-context.mjs +19 -28
  35. package/scripts/notify-events.mjs +3 -1
  36. package/scripts/notify-gates.mjs +2 -0
  37. package/scripts/notify-route.mjs +9 -7
  38. package/scripts/notify-ui.mjs +42 -32
  39. package/scripts/notify.mjs +72 -36
  40. package/scripts/project-storage.mjs +35 -66
  41. package/scripts/ralph-loop.mjs +36 -31
  42. package/scripts/replay-state.mjs +31 -128
  43. package/scripts/review-state.mjs +34 -61
  44. package/scripts/runtime-artifacts.mjs +95 -0
  45. package/scripts/runtime-context.mjs +35 -29
  46. package/scripts/runtime-scope.mjs +313 -0
  47. package/scripts/session-capsule.mjs +202 -0
  48. package/scripts/turn-state-cli.mjs +17 -0
  49. package/scripts/turn-state.mjs +185 -66
  50. package/scripts/turn-stop-gate.mjs +24 -6
  51. package/scripts/verify-state.mjs +34 -85
  52. package/scripts/visual-state.mjs +38 -65
  53. package/scripts/workflow-core.mjs +3 -3
  54. package/scripts/workflow-plan-files.mjs +1 -1
  55. package/scripts/workflow-recommendation.mjs +17 -13
  56. package/scripts/workflow-state.mjs +5 -5
  57. package/skills/commands/build/SKILL.md +1 -1
  58. package/skills/commands/commit/SKILL.md +1 -1
  59. package/skills/commands/help/SKILL.md +5 -3
  60. package/skills/commands/loop/SKILL.md +1 -1
  61. package/skills/commands/plan/SKILL.md +8 -6
  62. package/skills/commands/prd/SKILL.md +5 -3
  63. package/skills/commands/verify/SKILL.md +5 -5
  64. package/skills/hello-debug/SKILL.md +20 -3
  65. package/skills/hello-review/SKILL.md +2 -2
  66. package/skills/hello-subagent/SKILL.md +2 -2
  67. package/skills/hello-test/SKILL.md +6 -2
  68. package/skills/hello-ui/SKILL.md +7 -7
  69. package/skills/hello-verify/SKILL.md +10 -7
  70. package/skills/helloagents/SKILL.md +14 -9
  71. package/templates/context.md +6 -0
  72. package/templates/plans/plan.md +3 -0
  73. package/templates/plans/tasks.md +8 -3
@@ -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 { loadHooksWithAbsPath, safeJson, safeRead } from './cli-utils.mjs'
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(loadHooksWithAbsPath(runtime.pkgRoot, hooksFile, pathVar)?.hooks || {})
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 readBootstrapContent(fileName) {
80
- return normalizeText(safeRead(join(runtime.pkgRoot, fileName)) || '')
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')) === readBootstrapContent('bootstrap-lite.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(~/.claude/helloagents/**)'),
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 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
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 模式插件需手动安装;doctor 只检查 standby 残留,不直接探测插件状态。',
153
- 'Claude Code global-mode plugins are manual; doctor only checks for standby residue and does not inspect plugin state directly.',
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')) === readBootstrapContent('bootstrap-lite.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 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
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 模式扩展需手动安装;doctor 只检查 standby 残留,不直接探测扩展状态。',
200
- 'Gemini CLI global-mode extensions are manual; doctor only checks for standby residue and does not inspect extension state directly.',
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 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
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 路径未指向当前包根目录', 'Standby notify path does not point to the current package root'))
225
- if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled || checks.globalNotifyPath) {
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.globalNotifyPath) issues.push(buildDoctorIssue('global-notify-missing', 'global notify 路径缺失', 'Global notify path is missing'))
243
- if (checks.globalNotifyPath && !checks.globalNotifyPathMatch) issues.push(buildDoctorIssue('global-notify-drift', 'global notify 路径未指向当前插件根目录', 'Global notify path does not point to the current plugin root'))
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')) === readBootstrapContent(expectedHomeCarrier),
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(`"${managedHomeCarrierPath}"`),
286
+ && normalizePath(modelInstructionsLine).includes(`"${CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH}"`),
280
287
  codexNotify: codexConfig.includes('codex-notify'),
281
- notifyPathMatch: codexConfig.includes(standbyNotifyPath),
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')) || '') === readBootstrapContent('bootstrap.md'),
285
- pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '') === readBootstrapContent('bootstrap.md'),
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('/plugins/helloagents/scripts/notify.mjs'),
289
- globalNotifyPathMatch: codexConfig.includes(globalNotifyPath),
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
- packageRoot: runtime.pkgRoot,
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'
@@ -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
- loadHooksWithAbsPath,
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(join(claudeDir, 'CLAUDE.md'), bootstrapContent);
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 = loadHooksWithAbsPath(pkgRoot, 'hooks-claude.json', '${CLAUDE_PLUGIN_ROOT}');
31
+ const hooksData = loadHooksWithCliEntry(pkgRoot, 'hooks-claude.json', '${CLAUDE_PLUGIN_ROOT}');
29
32
  if (hooksData) {
30
- mergeSettingsHooks(settingsPath, hooksData, ['Read(~/.claude/helloagents/**)']);
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(join(geminiDir, 'GEMINI.md'), bootstrapContent);
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 = loadHooksWithAbsPath(pkgRoot, 'hooks.json', '${extensionPath}');
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
- uninstallCodexGlobal(runtime.home)
40
- return installCodexStandby(runtime.home, runtime.pkgRoot) ? {} : { skipped: true }
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
- 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
- }
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
- 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
- }
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
- noteCN: '如已安装 Claude Code 插件,请手动执行: /plugin remove helloagents',
75
- noteEN: 'If the Claude Code plugin is installed, remove it manually: /plugin remove helloagents',
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
- noteCN: '如已安装 Gemini CLI 扩展,请手动执行: gemini extensions uninstall helloagents',
82
- noteEN: 'If the Gemini CLI extension is installed, remove it manually: gemini extensions uninstall helloagents',
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
- 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'))
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
- 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'))
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
- else installStandby(runtime)
192
+ if (mode === 'global') return installGlobal(runtime)
193
+ return installStandby(runtime)
107
194
  }
108
195
 
109
196
  export function uninstallAllHosts(runtime) {
110
- uninstallClaudeStandby(runtime.home)
111
- uninstallGeminiStandby(runtime.home)
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(runtime.pkgRoot, '.claude-plugin', 'plugin.json'),
129
- join(runtime.pkgRoot, '.codex-plugin', 'plugin.json'),
130
- join(runtime.pkgRoot, 'gemini-extension.json'),
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(runtime.pkgRoot, '.claude-plugin', 'marketplace.json')
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 ℹ 如已安装 Claude Code 插件,请手动执行: /plugin remove helloagents\n ℹ 如已安装 Gemini CLI 扩展,请手动执行: gemini extensions uninstall helloagents',
177
- ' ℹ ~/.helloagents/ preserved (delete manually if desired)\n ℹ If Claude Code plugin installed, run: /plugin remove helloagents\n ℹ If Gemini CLI extension installed, run: gemini extensions uninstall helloagents',
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 (action === 'update' && !explicitMode) {
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
- runtime.printInstallMsg(settings.install_mode || DEFAULTS.install_mode, 'refresh')
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
- setAllTrackedHostModes(settings, mode)
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
  }