helloagents 3.0.39 → 3.1.2
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/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +18 -10
- package/README_CN.md +18 -10
- package/bootstrap-lite.md +2 -2
- package/bootstrap.md +4 -2
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/scripts/cli-branch.mjs +2 -5
- package/scripts/cli-codex-config.mjs +186 -0
- package/scripts/cli-codex.mjs +10 -4
- package/scripts/cli-doctor-codex.mjs +17 -8
- package/scripts/cli-doctor.mjs +48 -15
- package/scripts/cli-host-detect.mjs +38 -5
- package/scripts/cli-lifecycle-hosts.mjs +48 -20
- package/scripts/cli-messages.mjs +9 -8
- package/scripts/cli-process.mjs +16 -0
- package/scripts/cli-runtime-root.mjs +55 -12
- package/scripts/cli-toml.mjs +4 -0
- package/scripts/guard.mjs +3 -3
- package/scripts/notify-context.mjs +1 -1
- package/scripts/runtime-context.mjs +2 -2
- package/skills/commands/auto/SKILL.md +6 -3
- package/skills/commands/help/SKILL.md +1 -0
- package/skills/commands/idea/SKILL.md +1 -0
- package/skills/commands/office/SKILL.md +86 -0
- package/skills/helloagents/SKILL.md +1 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
1
|
import { existsSync, realpathSync } from 'node:fs'
|
|
3
2
|
import { platform } from 'node:os'
|
|
4
3
|
import { join } from 'node:path'
|
|
5
4
|
|
|
6
5
|
import { CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_CONFIG_HEADER, CODEX_PLUGIN_NAME } from './cli-codex.mjs'
|
|
7
6
|
import {
|
|
7
|
+
analyzeCodexNotifyBlock,
|
|
8
8
|
CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
9
|
-
CODEX_MANAGED_NOTIFY_VALUE,
|
|
10
9
|
readCodexGoalsFeatureLine,
|
|
11
10
|
readCodexHooksFeatureLine,
|
|
12
11
|
} from './cli-codex-config.mjs'
|
|
@@ -16,7 +15,8 @@ import {
|
|
|
16
15
|
} from './cli-codex-hooks-state.mjs'
|
|
17
16
|
import { getStableRuntimeRoot } from './cli-runtime-root.mjs'
|
|
18
17
|
import { buildRuntimeCarrier } from './cli-runtime-carrier.mjs'
|
|
19
|
-
import { readTopLevelTomlLine } from './cli-toml.mjs'
|
|
18
|
+
import { readTopLevelTomlBlock, readTopLevelTomlLine } from './cli-toml.mjs'
|
|
19
|
+
import { spawnCommandSync } from './cli-process.mjs'
|
|
20
20
|
import { loadHooksWithCliEntry, safeJson, safeRead } from './cli-utils.mjs'
|
|
21
21
|
|
|
22
22
|
function safeRealTarget(linkPath) {
|
|
@@ -149,7 +149,7 @@ function summarizeNativeCodexDoctorOutput(payload = {}) {
|
|
|
149
149
|
function inspectNativeCodexDoctor(runtime) {
|
|
150
150
|
const command = platform() === 'win32' ? 'codex.cmd' : 'codex'
|
|
151
151
|
try {
|
|
152
|
-
const result =
|
|
152
|
+
const result = spawnCommandSync(command, ['doctor', '--json'], {
|
|
153
153
|
cwd: process.cwd(),
|
|
154
154
|
env: {
|
|
155
155
|
...process.env,
|
|
@@ -159,7 +159,6 @@ function inspectNativeCodexDoctor(runtime) {
|
|
|
159
159
|
},
|
|
160
160
|
encoding: 'utf-8',
|
|
161
161
|
timeout: 20_000,
|
|
162
|
-
shell: platform() === 'win32',
|
|
163
162
|
windowsHide: true,
|
|
164
163
|
})
|
|
165
164
|
|
|
@@ -259,6 +258,8 @@ function appendCodexGlobalIssues(runtime, issues, checks, pluginVersion, cacheVe
|
|
|
259
258
|
if (!checks.pluginCache) issues.push(buildDoctorIssue(runtime, 'global-plugin-cache-missing', 'global 插件缓存目录缺失', 'Global plugin cache directory is missing'))
|
|
260
259
|
if (checks.pluginRoot && !checks.pluginRootLink) issues.push(buildDoctorIssue(runtime, 'global-plugin-root-link-drift', 'global 插件根目录未链接到稳定运行根目录', 'Global plugin root does not link to the stable runtime root'))
|
|
261
260
|
if (checks.pluginCache && !checks.pluginCacheLink) issues.push(buildDoctorIssue(runtime, 'global-plugin-cache-link-drift', 'global 插件缓存未链接到稳定运行根目录', 'Global plugin cache does not link to the stable runtime root'))
|
|
261
|
+
if (checks.pluginGenericHooks) issues.push(buildDoctorIssue(runtime, 'global-plugin-generic-hooks-present', 'global 插件根目录中意外存在通用 `hooks/hooks.json`,可能污染 Codex 本地插件 hook 加载', 'Global plugin root unexpectedly contains a generic `hooks/hooks.json`, which can pollute Codex local-plugin hook loading'))
|
|
262
|
+
if (checks.pluginCacheGenericHooks) issues.push(buildDoctorIssue(runtime, 'global-plugin-cache-generic-hooks-present', 'global 插件缓存中意外存在通用 `hooks/hooks.json`,可能污染 Codex 本地插件 hook 加载', 'Global plugin cache unexpectedly contains a generic `hooks/hooks.json`, which can pollute Codex local-plugin hook loading'))
|
|
262
263
|
if (checks.pluginRoot && !checks.pluginCarrierMatch) issues.push(buildDoctorIssue(runtime, 'global-plugin-carrier-drift', 'global 插件根目录中的 AGENTS.md 与当前全局模式规则不一致', 'Global plugin AGENTS.md differs from the current global rules'))
|
|
263
264
|
if (checks.pluginCache && !checks.pluginCacheCarrierMatch) issues.push(buildDoctorIssue(runtime, 'global-plugin-cache-carrier-drift', 'global 插件缓存中的 AGENTS.md 与当前全局模式规则不一致', 'Global plugin cache AGENTS.md differs from the current global rules'))
|
|
264
265
|
if (!checks.marketplaceEntry) issues.push(buildDoctorIssue(runtime, 'global-marketplace-missing', 'global marketplace 条目缺失', 'Global marketplace entry is missing'))
|
|
@@ -285,12 +286,16 @@ function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
|
|
|
285
286
|
const homeLinkTarget = safeRealTarget(join(codexDir, 'helloagents'))
|
|
286
287
|
const pluginRootTarget = safeRealTarget(pluginRoot)
|
|
287
288
|
const pluginCacheTarget = safeRealTarget(pluginCacheRoot)
|
|
289
|
+
const pluginGenericHooks = !!safeRead(join(pluginRoot, 'hooks', 'hooks.json'))
|
|
290
|
+
const pluginCacheGenericHooks = !!safeRead(join(pluginCacheRoot, 'hooks', 'hooks.json'))
|
|
288
291
|
const expectedHomeCarrier = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
|
|
289
292
|
? 'bootstrap.md'
|
|
290
293
|
: 'bootstrap-lite.md'
|
|
291
294
|
const codexHooks = safeJson(join(codexDir, 'hooks.json')) || {}
|
|
292
295
|
const marketplace = safeJson(join(runtime.home, '.agents', 'plugins', 'marketplace.json')) || {}
|
|
293
296
|
const modelInstructionsLine = readTopLevelTomlLine(codexConfig, 'model_instructions_file')
|
|
297
|
+
const notifyBlock = readTopLevelTomlBlock(codexConfig, 'notify')
|
|
298
|
+
const notifyAnalysis = analyzeCodexNotifyBlock(notifyBlock)
|
|
294
299
|
const expectedHooks = readExpectedHooks(runtime, 'hooks-codex.json', '${PLUGIN_ROOT}')
|
|
295
300
|
const expectedHookTrust = buildManagedCodexHookTrustEntries(join(codexDir, 'hooks.json'), codexHooks)
|
|
296
301
|
const managedHookTrust = new Map(
|
|
@@ -314,8 +319,9 @@ function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
|
|
|
314
319
|
globalHomeLink: homeLinkTarget === runtimeRoot,
|
|
315
320
|
modelInstructionsFile: !!modelInstructionsLine,
|
|
316
321
|
modelInstructionsPathMatch: !!modelInstructionsLine && normalizePath(modelInstructionsLine).includes(`"${CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH}"`),
|
|
317
|
-
codexNotify:
|
|
318
|
-
notifyPathMatch:
|
|
322
|
+
codexNotify: notifyAnalysis.containsCodexNotify,
|
|
323
|
+
notifyPathMatch: notifyAnalysis.managed,
|
|
324
|
+
notifyShape: notifyAnalysis.shape,
|
|
319
325
|
codexHooksFeature: !/^\s*hooks\s*=\s*false\b/.test(hooksFeatureLine),
|
|
320
326
|
codexGoalsFeature: /^\s*goals\s*=\s*true\b/.test(goalsFeatureLine),
|
|
321
327
|
standaloneHooks: JSON.stringify(codexHooks.hooks || {}).includes('helloagents'),
|
|
@@ -326,11 +332,13 @@ function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
|
|
|
326
332
|
pluginCache: existsSync(pluginCacheRoot),
|
|
327
333
|
pluginRootLink: pluginRootTarget === runtimeRoot,
|
|
328
334
|
pluginCacheLink: pluginCacheTarget === runtimeRoot,
|
|
335
|
+
pluginGenericHooks,
|
|
336
|
+
pluginCacheGenericHooks,
|
|
329
337
|
pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings, { profile: 'full' }),
|
|
330
338
|
pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings, { profile: 'full' }),
|
|
331
339
|
marketplaceEntry: Array.isArray(marketplace.plugins) && marketplace.plugins.some((plugin) => plugin?.name === CODEX_PLUGIN_NAME),
|
|
332
340
|
pluginEnabled: codexConfig.includes(CODEX_PLUGIN_CONFIG_HEADER) && codexConfig.includes('enabled = true'),
|
|
333
|
-
globalNotifyPathMatch:
|
|
341
|
+
globalNotifyPathMatch: notifyAnalysis.managed,
|
|
334
342
|
pluginVersionMatch: false,
|
|
335
343
|
pluginCacheVersionMatch: false,
|
|
336
344
|
},
|
|
@@ -364,6 +372,7 @@ export function inspectCodexDoctor(runtime, settings) {
|
|
|
364
372
|
if (!checks.pluginVersionMatch && !pluginVersion && detectedMode === 'global') notes.push(runtime.msg('未读到 global 插件根目录版本信息', 'Global plugin root version was not readable'))
|
|
365
373
|
if (!checks.pluginCacheVersionMatch && !cacheVersion && detectedMode === 'global') notes.push(runtime.msg('未读到 global 插件缓存版本信息', 'Global plugin cache version was not readable'))
|
|
366
374
|
if (detectedMode !== 'none' && !checks.codexGoalsFeature) notes.push(runtime.msg('Codex /goal 未启用;如需长程执行,可运行 `helloagents codex goals enable`。', 'Codex /goal is not enabled; run `helloagents codex goals enable` if you need long-running goals.'))
|
|
375
|
+
if (checks.notifyShape === 'chained') notes.push(runtime.msg('HelloAGENTS notify 当前通过 Codex Computer Use / wrapper 链式转发,仍视为有效。', 'HelloAGENTS notify is currently chained through Codex Computer Use / a wrapper and is still treated as valid.'))
|
|
367
376
|
if (!nativeDoctor.available) notes.push(runtime.msg('未检测到原生 `codex doctor`;当前仅检查 HelloAGENTS 受管覆盖层。', 'Native `codex doctor` was not available; only the HelloAGENTS managed overlay was checked.'))
|
|
368
377
|
|
|
369
378
|
const status = summarizeDoctorStatus(issues, { trackedMode, detectedMode })
|
package/scripts/cli-doctor.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { realpathSync } from 'node:fs'
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { DEFAULTS } from './cli-config.mjs'
|
|
5
5
|
import { inspectCodexDoctor as inspectCodexDoctorImpl } from './cli-doctor-codex.mjs'
|
|
6
6
|
import { printDoctorText } from './cli-doctor-render.mjs'
|
|
7
7
|
import { buildRuntimeCarrier } from './cli-runtime-carrier.mjs'
|
|
8
|
+
import { getClaudeMarketplaceRoot, getGeminiExtensionRoot } from './cli-runtime-root.mjs'
|
|
8
9
|
import { loadHooksWithCliEntry, safeJson, safeRead } from './cli-utils.mjs'
|
|
9
10
|
|
|
11
|
+
const CLAUDE_PLUGIN = 'helloagents@helloagents'
|
|
12
|
+
const GEMINI_EXTENSION = 'helloagents'
|
|
13
|
+
|
|
10
14
|
const runtime = {
|
|
11
15
|
home: '',
|
|
12
16
|
pkgRoot: '',
|
|
@@ -93,6 +97,16 @@ function normalizeDoctorMode(mode = '') {
|
|
|
93
97
|
return mode || 'none'
|
|
94
98
|
}
|
|
95
99
|
|
|
100
|
+
function hasEnabledPlugin(enabledPlugins, pluginName) {
|
|
101
|
+
if (Array.isArray(enabledPlugins)) {
|
|
102
|
+
return enabledPlugins.includes(pluginName)
|
|
103
|
+
}
|
|
104
|
+
if (enabledPlugins && typeof enabledPlugins === 'object') {
|
|
105
|
+
return Boolean(enabledPlugins[pluginName])
|
|
106
|
+
}
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
|
|
96
110
|
function summarizeDoctorStatus(issues, { host, trackedMode, detectedMode } = {}) {
|
|
97
111
|
if (issues.length > 0) return 'drift'
|
|
98
112
|
if (detectedMode !== 'none') return 'ok'
|
|
@@ -106,8 +120,8 @@ function suggestDoctorFix(host, status, trackedMode) {
|
|
|
106
120
|
return `helloagents update ${host}${trackedMode && trackedMode !== 'none' ? ` --${trackedMode}` : ''}`
|
|
107
121
|
}
|
|
108
122
|
if (status === 'manual-plugin') {
|
|
109
|
-
if (host === 'claude') return
|
|
110
|
-
if (host === 'gemini') return
|
|
123
|
+
if (host === 'claude') return `/plugin marketplace add "${getClaudeMarketplaceRoot(runtime.home)}"; /plugin install helloagents@helloagents`
|
|
124
|
+
if (host === 'gemini') return `gemini extensions link "${getGeminiExtensionRoot(runtime.home)}"`
|
|
111
125
|
}
|
|
112
126
|
if (status === 'not-installed') {
|
|
113
127
|
return `helloagents install ${host} --standby`
|
|
@@ -125,12 +139,18 @@ function inspectClaudeDoctor(settings) {
|
|
|
125
139
|
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
126
140
|
const claudeDir = join(runtime.home, '.claude')
|
|
127
141
|
const claudeSettings = safeJson(join(claudeDir, 'settings.json')) || {}
|
|
142
|
+
const claudePlugins = safeJson(join(claudeDir, 'plugins', 'installed_plugins.json')) || {}
|
|
128
143
|
const expectedHooks = readExpectedHooks('hooks-claude.json', '${CLAUDE_PLUGIN_ROOT}')
|
|
144
|
+
const marketplaceRoot = getClaudeMarketplaceRoot(runtime.home)
|
|
145
|
+
const globalPluginInstalled = Boolean(claudePlugins.plugins?.[CLAUDE_PLUGIN]?.length)
|
|
146
|
+
|| hasEnabledPlugin(claudeSettings.enabledPlugins, CLAUDE_PLUGIN)
|
|
129
147
|
const checks = {
|
|
130
148
|
carrierMarker: (safeRead(join(claudeDir, 'CLAUDE.md')) || '').includes('HELLOAGENTS_START'),
|
|
131
149
|
carrierContentMatch: extractManagedCarrierContent(join(claudeDir, 'CLAUDE.md'))
|
|
132
150
|
=== readExpectedCarrierContent('bootstrap-lite.md', settings),
|
|
133
151
|
homeLink: safeRealTarget(join(claudeDir, 'helloagents')) === runtime.pkgRoot,
|
|
152
|
+
globalMarketplaceRoot: existsSync(marketplaceRoot),
|
|
153
|
+
globalPluginInstalled,
|
|
134
154
|
settingsHooks: JSON.stringify(claudeSettings.hooks || {}).includes('helloagents'),
|
|
135
155
|
settingsHooksMatch: managedHooksMatch(claudeSettings.hooks || {}, expectedHooks),
|
|
136
156
|
settingsPermission: Array.isArray(claudeSettings.permissions?.allow)
|
|
@@ -150,14 +170,17 @@ function inspectClaudeDoctor(settings) {
|
|
|
150
170
|
if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
|
|
151
171
|
if (!checks.settingsPermission) issues.push(buildDoctorIssue('standby-permission-missing', 'standby Claude 权限注入缺失', 'Standby Claude permission injection is missing'))
|
|
152
172
|
}
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
'Claude Code global mode is managed by the host plugin system; doctor only checks for standby residue and does not inspect plugin state directly.',
|
|
157
|
-
))
|
|
173
|
+
if (detectedMode === 'global') {
|
|
174
|
+
if (!checks.globalMarketplaceRoot) issues.push(buildDoctorIssue('global-marketplace-root-missing', 'global marketplace 投影缺失', 'Global marketplace projection is missing'))
|
|
175
|
+
if (!checks.globalPluginInstalled) issues.push(buildDoctorIssue('global-plugin-missing', 'global Claude 插件未安装', 'Global Claude plugin is not installed'))
|
|
158
176
|
if (checks.carrierMarker || checks.homeLink || checks.settingsHooks || checks.settingsPermission) {
|
|
159
|
-
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is
|
|
177
|
+
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is detected as global'))
|
|
160
178
|
}
|
|
179
|
+
} else if (trackedMode === 'global') {
|
|
180
|
+
notes.push(runtime.msg(
|
|
181
|
+
'Claude Code 的 global 模式由宿主插件系统管理;doctor 会检查本地 marketplace 投影、已安装插件记录与 standby 残留。',
|
|
182
|
+
'Claude Code global mode is managed by the host plugin system; doctor checks the local marketplace projection, installed-plugin records, and standby residue.',
|
|
183
|
+
))
|
|
161
184
|
}
|
|
162
185
|
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
163
186
|
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
@@ -177,11 +200,17 @@ function inspectGeminiDoctor(settings) {
|
|
|
177
200
|
const geminiDir = join(runtime.home, '.gemini')
|
|
178
201
|
const geminiSettings = safeJson(join(geminiDir, 'settings.json')) || {}
|
|
179
202
|
const expectedHooks = readExpectedHooks('hooks-gemini.json', '${extensionPath}')
|
|
203
|
+
const extensionRoot = getGeminiExtensionRoot(runtime.home)
|
|
204
|
+
const extensionInstallRoot = join(geminiDir, 'extensions', GEMINI_EXTENSION)
|
|
205
|
+
const expectedExtensionTarget = safeRealTarget(extensionRoot) || normalizePath(extensionRoot)
|
|
180
206
|
const checks = {
|
|
181
207
|
carrierMarker: (safeRead(join(geminiDir, 'GEMINI.md')) || '').includes('HELLOAGENTS_START'),
|
|
182
208
|
carrierContentMatch: extractManagedCarrierContent(join(geminiDir, 'GEMINI.md'))
|
|
183
209
|
=== readExpectedCarrierContent('bootstrap-lite.md', settings),
|
|
184
210
|
homeLink: safeRealTarget(join(geminiDir, 'helloagents')) === runtime.pkgRoot,
|
|
211
|
+
globalExtensionRoot: existsSync(extensionRoot),
|
|
212
|
+
globalExtensionLink: safeRealTarget(extensionInstallRoot) === expectedExtensionTarget,
|
|
213
|
+
globalExtensionInstall: existsSync(extensionInstallRoot),
|
|
185
214
|
settingsHooks: JSON.stringify(geminiSettings.hooks || {}).includes('helloagents'),
|
|
186
215
|
settingsHooksMatch: managedHooksMatch(geminiSettings.hooks || {}, expectedHooks),
|
|
187
216
|
}
|
|
@@ -198,14 +227,18 @@ function inspectGeminiDoctor(settings) {
|
|
|
198
227
|
if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
|
|
199
228
|
if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
|
|
200
229
|
}
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
))
|
|
230
|
+
if (detectedMode === 'global') {
|
|
231
|
+
if (!checks.globalExtensionRoot) issues.push(buildDoctorIssue('global-extension-root-missing', 'global extension 投影缺失', 'Global extension projection is missing'))
|
|
232
|
+
if (!checks.globalExtensionInstall) issues.push(buildDoctorIssue('global-extension-missing', 'global Gemini 扩展未安装', 'Global Gemini extension is not installed'))
|
|
233
|
+
if (!checks.globalExtensionLink) issues.push(buildDoctorIssue('global-extension-link-missing', 'global Gemini 扩展链接未指向投影目录', 'Global Gemini extension link does not point to the projection root'))
|
|
206
234
|
if (checks.carrierMarker || checks.homeLink || checks.settingsHooks) {
|
|
207
|
-
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is
|
|
235
|
+
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is detected as global'))
|
|
208
236
|
}
|
|
237
|
+
} else if (trackedMode === 'global') {
|
|
238
|
+
notes.push(runtime.msg(
|
|
239
|
+
'Gemini CLI 的 global 模式由宿主扩展系统管理;doctor 会检查本地扩展投影、已安装链接与 standby 残留。',
|
|
240
|
+
'Gemini CLI global mode is managed by the host extension system; doctor checks the local extension projection, installed link, and standby residue.',
|
|
241
|
+
))
|
|
209
242
|
}
|
|
210
243
|
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
211
244
|
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
@@ -6,9 +6,12 @@ import {
|
|
|
6
6
|
CODEX_PLUGIN_KEY,
|
|
7
7
|
CODEX_PLUGIN_NAME,
|
|
8
8
|
} from './cli-codex.mjs'
|
|
9
|
-
import { getStableRuntimeRoot } from './cli-runtime-root.mjs'
|
|
9
|
+
import { getGeminiExtensionRoot, getStableRuntimeRoot } from './cli-runtime-root.mjs'
|
|
10
10
|
import { safeJson, safeRead } from './cli-utils.mjs'
|
|
11
11
|
|
|
12
|
+
const CLAUDE_PLUGIN = 'helloagents@helloagents'
|
|
13
|
+
const GEMINI_EXTENSION = 'helloagents'
|
|
14
|
+
|
|
12
15
|
const HOST_ALIASES = new Map([
|
|
13
16
|
['all', 'all'],
|
|
14
17
|
['*', 'all'],
|
|
@@ -24,8 +27,14 @@ function hasHelloagentsMarker(filePath) {
|
|
|
24
27
|
return (safeRead(filePath) || '').includes('HELLOAGENTS_START')
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
function hasHelloagentsSettings(filePath) {
|
|
28
|
-
|
|
30
|
+
function hasHelloagentsSettings(filePath, host = '') {
|
|
31
|
+
const settings = safeJson(filePath) || {}
|
|
32
|
+
const hooksText = JSON.stringify(settings.hooks || {})
|
|
33
|
+
if (hooksText.includes('helloagents')) return true
|
|
34
|
+
if (host === 'claude') {
|
|
35
|
+
return JSON.stringify(settings.permissions?.allow || []).includes('~/.helloagents/helloagents')
|
|
36
|
+
}
|
|
37
|
+
return false
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
function normalizePath(value = '') {
|
|
@@ -40,12 +49,27 @@ function safeRealTarget(linkPath) {
|
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
|
|
52
|
+
function hasEnabledPlugin(enabledPlugins, pluginName) {
|
|
53
|
+
if (Array.isArray(enabledPlugins)) {
|
|
54
|
+
return enabledPlugins.includes(pluginName)
|
|
55
|
+
}
|
|
56
|
+
if (enabledPlugins && typeof enabledPlugins === 'object') {
|
|
57
|
+
return Boolean(enabledPlugins[pluginName])
|
|
58
|
+
}
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
function detectClaudeMode(home) {
|
|
44
63
|
const claudeDir = join(home, '.claude')
|
|
64
|
+
const settings = safeJson(join(claudeDir, 'settings.json')) || {}
|
|
65
|
+
const installedPlugins = safeJson(join(claudeDir, 'plugins', 'installed_plugins.json')) || {}
|
|
66
|
+
if (hasEnabledPlugin(settings.enabledPlugins, CLAUDE_PLUGIN) || installedPlugins.plugins?.[CLAUDE_PLUGIN]?.length) {
|
|
67
|
+
return 'global'
|
|
68
|
+
}
|
|
45
69
|
if (
|
|
46
70
|
existsSync(join(claudeDir, 'helloagents'))
|
|
47
71
|
|| hasHelloagentsMarker(join(claudeDir, 'CLAUDE.md'))
|
|
48
|
-
|| hasHelloagentsSettings(join(claudeDir, 'settings.json'))
|
|
72
|
+
|| hasHelloagentsSettings(join(claudeDir, 'settings.json'), 'claude')
|
|
49
73
|
) {
|
|
50
74
|
return 'standby'
|
|
51
75
|
}
|
|
@@ -54,10 +78,19 @@ function detectClaudeMode(home) {
|
|
|
54
78
|
|
|
55
79
|
function detectGeminiMode(home) {
|
|
56
80
|
const geminiDir = join(home, '.gemini')
|
|
81
|
+
const extensionRoot = safeRealTarget(getGeminiExtensionRoot(home)) || normalizePath(getGeminiExtensionRoot(home))
|
|
82
|
+
const installedExtensionRoot = join(geminiDir, 'extensions', GEMINI_EXTENSION)
|
|
83
|
+
const installedExtension = safeJson(join(installedExtensionRoot, 'gemini-extension.json')) || {}
|
|
84
|
+
if (
|
|
85
|
+
existsSync(installedExtensionRoot)
|
|
86
|
+
&& (safeRealTarget(installedExtensionRoot) === extensionRoot || installedExtension.name === GEMINI_EXTENSION)
|
|
87
|
+
) {
|
|
88
|
+
return 'global'
|
|
89
|
+
}
|
|
57
90
|
if (
|
|
58
91
|
existsSync(join(geminiDir, 'helloagents'))
|
|
59
92
|
|| hasHelloagentsMarker(join(geminiDir, 'GEMINI.md'))
|
|
60
|
-
|| hasHelloagentsSettings(join(geminiDir, 'settings.json'))
|
|
93
|
+
|| hasHelloagentsSettings(join(geminiDir, 'settings.json'), 'gemini')
|
|
61
94
|
) {
|
|
62
95
|
return 'standby'
|
|
63
96
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
1
|
import { platform } from 'node:os'
|
|
3
2
|
|
|
4
3
|
import {
|
|
@@ -14,11 +13,22 @@ import {
|
|
|
14
13
|
uninstallCodexGlobal,
|
|
15
14
|
uninstallCodexStandby,
|
|
16
15
|
} from './cli-codex.mjs'
|
|
17
|
-
import {
|
|
16
|
+
import { spawnCommandSync } from './cli-process.mjs'
|
|
17
|
+
import {
|
|
18
|
+
detectHostMode as detectRuntimeHostMode,
|
|
19
|
+
getHostLabel,
|
|
20
|
+
} from './cli-host-detect.mjs'
|
|
21
|
+
import {
|
|
22
|
+
getClaudeMarketplaceRoot,
|
|
23
|
+
getGeminiExtensionRoot,
|
|
24
|
+
removeClaudeMarketplaceRoot,
|
|
25
|
+
removeGeminiExtensionRoot,
|
|
26
|
+
syncClaudeMarketplaceRoot,
|
|
27
|
+
syncGeminiExtensionRoot,
|
|
28
|
+
} from './cli-runtime-root.mjs'
|
|
18
29
|
|
|
19
30
|
const CLAUDE_COMMAND = process.env.HELLOAGENTS_CLAUDE_CMD || 'claude'
|
|
20
31
|
const GEMINI_COMMAND = process.env.HELLOAGENTS_GEMINI_CMD || 'gemini'
|
|
21
|
-
const CLAUDE_MARKETPLACE = 'https://github.com/hellowind777/helloagents.git'
|
|
22
32
|
const CLAUDE_PLUGIN = 'helloagents@helloagents'
|
|
23
33
|
|
|
24
34
|
function normalizeCommand(command = '') {
|
|
@@ -44,11 +54,9 @@ function runHostCommand(command, args) {
|
|
|
44
54
|
let lastResult = null
|
|
45
55
|
|
|
46
56
|
for (const candidate of attempts) {
|
|
47
|
-
const
|
|
48
|
-
const result = spawnSync(candidate, args, {
|
|
57
|
+
const result = spawnCommandSync(candidate, args, {
|
|
49
58
|
encoding: 'utf-8',
|
|
50
59
|
errors: 'replace',
|
|
51
|
-
shell: needsShell,
|
|
52
60
|
windowsHide: true,
|
|
53
61
|
})
|
|
54
62
|
lastResult = result
|
|
@@ -81,8 +89,8 @@ function preserveTrackedModeOnFailure(result = {}, trackedMode = '') {
|
|
|
81
89
|
return result
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
function installClaudeGlobalPlugin() {
|
|
85
|
-
const add = runHostCommand(CLAUDE_COMMAND, ['plugin', 'marketplace', 'add',
|
|
92
|
+
function installClaudeGlobalPlugin(marketplaceRoot) {
|
|
93
|
+
const add = runHostCommand(CLAUDE_COMMAND, ['plugin', 'marketplace', 'add', marketplaceRoot])
|
|
86
94
|
if (!add.ok && add.missing) return { ok: false, output: '未找到 claude 命令' }
|
|
87
95
|
const install = runHostCommand(CLAUDE_COMMAND, ['plugin', 'install', CLAUDE_PLUGIN, '--scope', 'user'])
|
|
88
96
|
return { ok: install.ok, output: install.output || add.output }
|
|
@@ -158,12 +166,14 @@ function installHostStandby(runtime, host, { previousMode = '' } = {}) {
|
|
|
158
166
|
const cleanupResult = prepareClaudeStandby(previousMode)
|
|
159
167
|
if (cleanupResult.ok === false) return cleanupResult
|
|
160
168
|
installClaudeStandby(runtime.home, runtime.pkgRoot)
|
|
169
|
+
if (detectRuntimeHostMode('claude', runtime) !== 'global') removeClaudeMarketplaceRoot(runtime.home)
|
|
161
170
|
return cleanupResult
|
|
162
171
|
}
|
|
163
172
|
if (host === 'gemini') {
|
|
164
173
|
const cleanupResult = prepareGeminiStandby(previousMode)
|
|
165
174
|
if (cleanupResult.ok === false) return cleanupResult
|
|
166
175
|
installGeminiStandby(runtime.home, runtime.pkgRoot)
|
|
176
|
+
if (detectRuntimeHostMode('gemini', runtime) !== 'global') removeGeminiExtensionRoot(runtime.home)
|
|
167
177
|
return cleanupResult
|
|
168
178
|
}
|
|
169
179
|
if (!installCodexStandby(runtime.home, runtime.pkgRoot)) return { skipped: true }
|
|
@@ -174,31 +184,45 @@ function installHostStandby(runtime, host, { previousMode = '' } = {}) {
|
|
|
174
184
|
function installHostGlobal(runtime, host) {
|
|
175
185
|
if (host === 'claude') {
|
|
176
186
|
uninstallClaudeStandby(runtime.home)
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
const marketplaceRoot = getClaudeMarketplaceRoot(runtime.home)
|
|
188
|
+
syncClaudeMarketplaceRoot(runtime.pkgRoot, marketplaceRoot)
|
|
189
|
+
const result = buildNativeResult(
|
|
190
|
+
installClaudeGlobalPlugin(marketplaceRoot),
|
|
179
191
|
'已自动安装 Claude Code 插件;重启 Claude Code 后生效',
|
|
180
192
|
'Claude Code plugin installed automatically; restart Claude Code to apply',
|
|
181
|
-
|
|
182
|
-
|
|
193
|
+
`Claude Code 插件自动安装失败,请在 Claude Code 中执行: /plugin marketplace add "${marketplaceRoot}";/plugin install helloagents@helloagents`,
|
|
194
|
+
`Claude Code plugin auto-install failed. Run inside Claude Code: /plugin marketplace add "${marketplaceRoot}"; /plugin install helloagents@helloagents`,
|
|
183
195
|
)
|
|
196
|
+
return result
|
|
184
197
|
}
|
|
185
198
|
if (host === 'gemini') {
|
|
186
199
|
uninstallGeminiStandby(runtime.home)
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
const extensionRoot = getGeminiExtensionRoot(runtime.home)
|
|
201
|
+
syncGeminiExtensionRoot(runtime.pkgRoot, extensionRoot)
|
|
202
|
+
const result = buildNativeResult(
|
|
203
|
+
installGeminiGlobalExtension(extensionRoot),
|
|
189
204
|
'已自动安装 Gemini CLI 扩展;重启 Gemini CLI 后生效',
|
|
190
205
|
'Gemini CLI extension installed automatically; restart Gemini CLI to apply',
|
|
191
|
-
`Gemini CLI 扩展自动安装失败,请手动执行: gemini extensions link ${
|
|
192
|
-
`Gemini CLI extension auto-install failed. Run manually: gemini extensions link ${
|
|
206
|
+
`Gemini CLI 扩展自动安装失败,请手动执行: gemini extensions link "${extensionRoot}"`,
|
|
207
|
+
`Gemini CLI extension auto-install failed. Run manually: gemini extensions link "${extensionRoot}"`,
|
|
193
208
|
)
|
|
209
|
+
return result
|
|
194
210
|
}
|
|
195
211
|
uninstallCodexStandby(runtime.home)
|
|
196
212
|
return installCodexGlobal(runtime.home, runtime.pkgRoot) ? {} : { skipped: true }
|
|
197
213
|
}
|
|
198
214
|
|
|
199
215
|
function cleanupHostStandby(runtime, host) {
|
|
200
|
-
if (host === 'claude')
|
|
201
|
-
|
|
216
|
+
if (host === 'claude') {
|
|
217
|
+
const skipped = !uninstallClaudeStandby(runtime.home)
|
|
218
|
+
if (detectRuntimeHostMode('claude', runtime) !== 'global') removeClaudeMarketplaceRoot(runtime.home)
|
|
219
|
+
return { skipped }
|
|
220
|
+
}
|
|
221
|
+
if (host === 'gemini') {
|
|
222
|
+
const skipped = !uninstallGeminiStandby(runtime.home)
|
|
223
|
+
if (detectRuntimeHostMode('gemini', runtime) !== 'global') removeGeminiExtensionRoot(runtime.home)
|
|
224
|
+
return { skipped }
|
|
225
|
+
}
|
|
202
226
|
const standbyCleaned = uninstallCodexStandby(runtime.home)
|
|
203
227
|
const globalResidueCleaned = uninstallCodexGlobal(runtime.home)
|
|
204
228
|
return { skipped: !(standbyCleaned || globalResidueCleaned) }
|
|
@@ -207,7 +231,7 @@ function cleanupHostStandby(runtime, host) {
|
|
|
207
231
|
function cleanupHostGlobal(runtime, host) {
|
|
208
232
|
if (host === 'claude') {
|
|
209
233
|
uninstallClaudeStandby(runtime.home)
|
|
210
|
-
|
|
234
|
+
const result = preserveTrackedModeOnFailure(
|
|
211
235
|
buildNativeResult(
|
|
212
236
|
removeClaudeGlobalPlugin(),
|
|
213
237
|
'已自动移除 Claude Code 插件',
|
|
@@ -217,10 +241,12 @@ function cleanupHostGlobal(runtime, host) {
|
|
|
217
241
|
),
|
|
218
242
|
'global',
|
|
219
243
|
)
|
|
244
|
+
if (result.ok) removeClaudeMarketplaceRoot(runtime.home)
|
|
245
|
+
return result
|
|
220
246
|
}
|
|
221
247
|
if (host === 'gemini') {
|
|
222
248
|
uninstallGeminiStandby(runtime.home)
|
|
223
|
-
|
|
249
|
+
const result = preserveTrackedModeOnFailure(
|
|
224
250
|
buildNativeResult(
|
|
225
251
|
removeGeminiGlobalExtension(),
|
|
226
252
|
'已自动移除 Gemini CLI 扩展',
|
|
@@ -230,6 +256,8 @@ function cleanupHostGlobal(runtime, host) {
|
|
|
230
256
|
),
|
|
231
257
|
'global',
|
|
232
258
|
)
|
|
259
|
+
if (result.ok) removeGeminiExtensionRoot(runtime.home)
|
|
260
|
+
return result
|
|
233
261
|
}
|
|
234
262
|
return { skipped: !uninstallCodexGlobal(runtime.home) }
|
|
235
263
|
}
|
package/scripts/cli-messages.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
+
import { getClaudeMarketplaceRoot, getGeminiExtensionRoot } from './cli-runtime-root.mjs'
|
|
3
4
|
|
|
4
5
|
export function createMessageHelpers(isCN) {
|
|
5
6
|
const msg = (cn, en) => (isCN ? cn : en)
|
|
@@ -19,11 +20,11 @@ function codexGlobalStatus({ home, msg }) {
|
|
|
19
20
|
: msg('安装 Codex CLI 后重新运行 npm install -g helloagents', 'Install Codex CLI then re-run npm install -g helloagents')
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function pluginCommands() {
|
|
23
|
+
function pluginCommands(home) {
|
|
23
24
|
return [
|
|
24
|
-
|
|
25
|
+
` Claude Code: /plugin marketplace add "${getClaudeMarketplaceRoot(home)}"`,
|
|
25
26
|
' /plugin install helloagents@helloagents',
|
|
26
|
-
|
|
27
|
+
` Gemini CLI: gemini extensions link "${getGeminiExtensionRoot(home)}"`,
|
|
27
28
|
].join('\n')
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -42,24 +43,24 @@ function restartHint(msg) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
function renderInstallMessage(context, mode, state) {
|
|
45
|
-
const { msg } = context
|
|
46
|
+
const { home, msg } = context
|
|
46
47
|
const install = state === 'install'
|
|
47
48
|
const refresh = state === 'refresh'
|
|
48
49
|
|
|
49
50
|
if (mode === 'global') {
|
|
50
51
|
if (install) {
|
|
51
52
|
return msg(
|
|
52
|
-
`\n ✅ HelloAGENTS 已安装(global 模式)!\n\n Claude Code / Gemini CLI: 已自动尝试宿主原生插件/扩展安装\n Codex: ${codexGlobalStatus(context)}(~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n ${restartHint(msg)}\n\n 若宿主命令不可用,请手动执行:\n${pluginCommands()}\n\n 切换模式:\n helloagents --standby 标准模式(默认,非插件安装)`,
|
|
53
|
-
`\n ✅ HelloAGENTS installed (global mode)!\n\n Claude Code / Gemini CLI: native plugin/extension install attempted automatically\n Codex: ${codexGlobalStatus(context)} (~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n ${restartHint(msg)}\n\n If a host command is unavailable, run manually:\n${pluginCommands()}\n\n Switch modes:\n helloagents --standby Standby mode (default, non-plugin install)`,
|
|
53
|
+
`\n ✅ HelloAGENTS 已安装(global 模式)!\n\n Claude Code / Gemini CLI: 已自动尝试宿主原生插件/扩展安装\n Codex: ${codexGlobalStatus(context)}(~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n ${restartHint(msg)}\n\n 若宿主命令不可用,请手动执行:\n${pluginCommands(home)}\n\n 切换模式:\n helloagents --standby 标准模式(默认,非插件安装)`,
|
|
54
|
+
`\n ✅ HelloAGENTS installed (global mode)!\n\n Claude Code / Gemini CLI: native plugin/extension install attempted automatically\n Codex: ${codexGlobalStatus(context)} (~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n ${restartHint(msg)}\n\n If a host command is unavailable, run manually:\n${pluginCommands(home)}\n\n Switch modes:\n helloagents --standby Standby mode (default, non-plugin install)`,
|
|
54
55
|
)
|
|
55
56
|
}
|
|
56
57
|
return msg(
|
|
57
58
|
refresh
|
|
58
59
|
? ` global 模式已刷新。\n Claude Code / Gemini 已自动尝试刷新宿主插件/扩展;Codex 原生本地插件已重装并同步最新文件。\n ${restartHint(msg)}`
|
|
59
|
-
: ` 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 已自动尝试安装宿主插件/扩展;Codex 已自动安装原生本地插件。\n ${restartHint(msg)}\n\n若宿主命令不可用,请手动执行:\n${pluginCommands()}`,
|
|
60
|
+
: ` 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 已自动尝试安装宿主插件/扩展;Codex 已自动安装原生本地插件。\n ${restartHint(msg)}\n\n若宿主命令不可用,请手动执行:\n${pluginCommands(home)}`,
|
|
60
61
|
refresh
|
|
61
62
|
? ` Global mode refreshed.\n Claude Code / Gemini native plugin/extension refresh was attempted automatically; Codex native local-plugin files were reinstalled and synced.\n ${restartHint(msg)}`
|
|
62
|
-
: ` All projects will use full HelloAGENTS rules.\n Claude Code / Gemini native plugin/extension install was attempted automatically; Codex now uses the native local-plugin path automatically.\n ${restartHint(msg)}\n\nIf a host command is unavailable, run manually:\n${pluginCommands()}`,
|
|
63
|
+
: ` All projects will use full HelloAGENTS rules.\n Claude Code / Gemini native plugin/extension install was attempted automatically; Codex now uses the native local-plugin path automatically.\n ${restartHint(msg)}\n\nIf a host command is unavailable, run manually:\n${pluginCommands(home)}`,
|
|
63
64
|
)
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Run a command on all platforms, including Windows .cmd/.bat files, without
|
|
5
|
+
* relying on child_process shell=true argument concatenation.
|
|
6
|
+
*/
|
|
7
|
+
export function spawnCommandSync(command, args = [], options = {}) {
|
|
8
|
+
const normalizedArgs = Array.isArray(args) ? args.map((arg) => String(arg)) : []
|
|
9
|
+
const isWindowsShellScript = process.platform === 'win32' && /\.(cmd|bat)$/i.test(String(command || ''))
|
|
10
|
+
if (!isWindowsShellScript) {
|
|
11
|
+
return spawnSync(command, normalizedArgs, options)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const comspec = process.env.ComSpec || 'cmd.exe'
|
|
15
|
+
return spawnSync(comspec, ['/d', '/s', '/c', String(command || ''), ...normalizedArgs], options)
|
|
16
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdtempSync, realpathSync, renameSync } from 'node:fs'
|
|
2
2
|
import { dirname, join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import { copyEntries, ensureDir, removeIfExists } from './cli-utils.mjs'
|
|
4
|
+
import { copyEntries, createLink, ensureDir, removeIfExists } from './cli-utils.mjs'
|
|
5
5
|
|
|
6
6
|
export const RUNTIME_ROOT_ENTRIES = [
|
|
7
7
|
'.claude-plugin',
|
|
@@ -28,6 +28,16 @@ export function getStableRuntimeRoot(home) {
|
|
|
28
28
|
return join(home, '.helloagents', 'helloagents')
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** Return the Claude local marketplace projection root derived from the shared runtime copy. */
|
|
32
|
+
export function getClaudeMarketplaceRoot(home) {
|
|
33
|
+
return join(home, '.helloagents', 'host-projections', 'claude-marketplace')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Return the Gemini extension projection root derived from the shared runtime copy. */
|
|
37
|
+
export function getGeminiExtensionRoot(home) {
|
|
38
|
+
return join(home, '.helloagents', 'host-projections', 'gemini')
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
function normalizePath(path) {
|
|
32
42
|
const resolved = resolve(path)
|
|
33
43
|
try {
|
|
@@ -63,17 +73,9 @@ function retryTransientFs(operation) {
|
|
|
63
73
|
throw lastError
|
|
64
74
|
}
|
|
65
75
|
|
|
66
|
-
function materializeGeminiHooks
|
|
67
|
-
const source = join(root, 'hooks', 'hooks-gemini.json')
|
|
68
|
-
const target = join(root, 'hooks', 'hooks.json')
|
|
69
|
-
if (!existsSync(source)) return
|
|
70
|
-
copyFileSync(source, target)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Sync package runtime files into the stable root without copying repo-only files. */
|
|
74
|
-
export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
76
|
+
function syncRuntimeTree(sourceRoot, targetRoot, { materializeGeminiHooks = false } = {}) {
|
|
75
77
|
const source = resolve(sourceRoot)
|
|
76
|
-
const target = resolve(
|
|
78
|
+
const target = resolve(targetRoot)
|
|
77
79
|
if (samePath(source, target)) {
|
|
78
80
|
return { synced: false, root: target }
|
|
79
81
|
}
|
|
@@ -84,7 +86,13 @@ export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
|
84
86
|
|
|
85
87
|
try {
|
|
86
88
|
copyEntries(source, staging, RUNTIME_ROOT_ENTRIES)
|
|
87
|
-
materializeGeminiHooks
|
|
89
|
+
if (materializeGeminiHooks) {
|
|
90
|
+
const sourceHooks = join(staging, 'hooks', 'hooks-gemini.json')
|
|
91
|
+
const targetHooks = join(staging, 'hooks', 'hooks.json')
|
|
92
|
+
if (existsSync(sourceHooks)) {
|
|
93
|
+
copyFileSync(sourceHooks, targetHooks)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
88
96
|
retryTransientFs(() => {
|
|
89
97
|
removeIfExists(target)
|
|
90
98
|
renameSync(staging, target)
|
|
@@ -96,7 +104,42 @@ export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
|
|
107
|
+
/** Sync package runtime files into the stable root without copying repo-only files. */
|
|
108
|
+
export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
|
|
109
|
+
return syncRuntimeTree(sourceRoot, runtimeRoot)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Sync a Claude local marketplace root that resolves to the stable runtime copy. */
|
|
113
|
+
export function syncClaudeMarketplaceRoot(sourceRoot, marketplaceRoot) {
|
|
114
|
+
const source = resolve(sourceRoot)
|
|
115
|
+
const target = resolve(marketplaceRoot)
|
|
116
|
+
if (samePath(source, target)) {
|
|
117
|
+
return { synced: false, root: target }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
removeIfExists(target)
|
|
121
|
+
if (createLink(source, target)) {
|
|
122
|
+
return { synced: true, root: target }
|
|
123
|
+
}
|
|
124
|
+
return syncRuntimeTree(source, target)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Sync a host-specific extension root derived from the stable runtime copy. */
|
|
128
|
+
export function syncGeminiExtensionRoot(sourceRoot, extensionRoot) {
|
|
129
|
+
return syncRuntimeTree(sourceRoot, extensionRoot, { materializeGeminiHooks: true })
|
|
130
|
+
}
|
|
131
|
+
|
|
99
132
|
/** Remove the stable runtime copy while leaving user settings under ~/.helloagents intact. */
|
|
100
133
|
export function removeRuntimeRoot(runtimeRoot) {
|
|
101
134
|
removeIfExists(runtimeRoot)
|
|
102
135
|
}
|
|
136
|
+
|
|
137
|
+
/** Remove the Claude marketplace projection root. */
|
|
138
|
+
export function removeClaudeMarketplaceRoot(home) {
|
|
139
|
+
removeIfExists(getClaudeMarketplaceRoot(home))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Remove the Gemini extension projection root. */
|
|
143
|
+
export function removeGeminiExtensionRoot(home) {
|
|
144
|
+
removeIfExists(getGeminiExtensionRoot(home))
|
|
145
|
+
}
|