helloagents 3.1.7 → 3.1.9
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/.cursor-plugin/plugin.json +24 -0
- package/.grok-plugin/plugin.json +24 -0
- package/README.md +37 -9
- package/README_CN.md +39 -11
- package/bootstrap-lite.md +4 -5
- package/bootstrap.md +4 -5
- package/cli.mjs +2 -0
- package/gemini-extension.json +1 -1
- package/hooks/hooks-cursor.json +41 -0
- package/hooks/hooks-grok.json +98 -0
- package/install.ps1 +3 -3
- package/install.sh +3 -3
- package/package.json +5 -2
- package/scripts/advisor-state.mjs +1 -1
- package/scripts/cli-config.mjs +1 -1
- package/scripts/cli-cursor.mjs +42 -0
- package/scripts/cli-doctor.mjs +173 -3
- package/scripts/cli-grok.mjs +54 -0
- package/scripts/cli-host-detect.mjs +67 -1
- package/scripts/cli-hosts.mjs +5 -0
- package/scripts/cli-lifecycle-hosts.mjs +197 -1
- package/scripts/cli-lifecycle.mjs +3 -1
- package/scripts/cli-messages.mjs +28 -14
- package/scripts/cli-runtime-root.mjs +284 -1
- package/scripts/cli-utils.mjs +36 -0
- package/scripts/cursor-hook.mjs +193 -0
- package/scripts/guard.mjs +6 -4
- package/scripts/notify-events.mjs +2 -0
- package/scripts/notify-source.mjs +2 -0
- package/scripts/notify.mjs +3 -2
- package/scripts/plan-contract.mjs +1 -1
- package/scripts/runtime-scope.mjs +1 -1
- package/skills/commands/help/SKILL.md +11 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helloagents",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "HelloAGENTS — The orchestration kernel that makes any AI CLI smarter. Adds intelligent routing, unified QA gates, safety guards, and notifications.",
|
|
6
6
|
"author": "HelloWind",
|
|
@@ -43,14 +43,17 @@
|
|
|
43
43
|
"install.sh",
|
|
44
44
|
"install.ps1",
|
|
45
45
|
"gemini-extension.json",
|
|
46
|
+
".cursor-plugin/",
|
|
46
47
|
".claude-plugin/",
|
|
47
|
-
".codex-plugin/"
|
|
48
|
+
".codex-plugin/",
|
|
49
|
+
".grok-plugin/"
|
|
48
50
|
],
|
|
49
51
|
"keywords": [
|
|
50
52
|
"ai",
|
|
51
53
|
"agent",
|
|
52
54
|
"claude",
|
|
53
55
|
"codex",
|
|
56
|
+
"cursor",
|
|
54
57
|
"cli",
|
|
55
58
|
"orchestration",
|
|
56
59
|
"subagent",
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
|
|
16
16
|
export const ADVISOR_EVIDENCE_FILE_NAME = 'advisor.json'
|
|
17
17
|
const VALID_ADVISOR_OUTCOMES = new Set(['clean', 'findings'])
|
|
18
|
-
const VALID_SOURCES = new Set(['claude', 'codex', 'gemini'])
|
|
18
|
+
const VALID_SOURCES = new Set(['claude', 'codex', 'cursor', 'gemini'])
|
|
19
19
|
|
|
20
20
|
function normalizeStringArray(values) {
|
|
21
21
|
if (!Array.isArray(values)) return []
|
package/scripts/cli-config.mjs
CHANGED
|
@@ -39,7 +39,7 @@ export function ensureConfig(helloagentsHome, configFile, safeJson, ensureDir) {
|
|
|
39
39
|
} else {
|
|
40
40
|
reconciled.host_install_modes = Object.fromEntries(
|
|
41
41
|
Object.entries(reconciled.host_install_modes)
|
|
42
|
-
.filter(([host, mode]) => ['claude', 'gemini', 'codex'].includes(host) && typeof mode === 'string' && mode),
|
|
42
|
+
.filter(([host, mode]) => ['claude', 'gemini', 'grok', 'cursor', 'codex'].includes(host) && typeof mode === 'string' && mode),
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
if (JSON.stringify(reconciled) !== JSON.stringify(existing)) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
cleanHooksConfig,
|
|
6
|
+
createLink,
|
|
7
|
+
ensureDir,
|
|
8
|
+
mergeHooksConfig,
|
|
9
|
+
removeLink,
|
|
10
|
+
safeJson,
|
|
11
|
+
} from './cli-utils.mjs'
|
|
12
|
+
|
|
13
|
+
function readCursorHooks(pkgRoot) {
|
|
14
|
+
return safeJson(join(pkgRoot, 'hooks', 'hooks-cursor.json')) || null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function writeCursorStandbyHooks(home, pkgRoot) {
|
|
18
|
+
const hooksData = readCursorHooks(pkgRoot)
|
|
19
|
+
if (!hooksData) return false
|
|
20
|
+
|
|
21
|
+
const cursorDir = join(home, '.cursor')
|
|
22
|
+
ensureDir(cursorDir)
|
|
23
|
+
mergeHooksConfig(join(cursorDir, 'hooks.json'), hooksData)
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function installCursorStandby(home, pkgRoot) {
|
|
28
|
+
const cursorDir = join(home, '.cursor')
|
|
29
|
+
ensureDir(cursorDir)
|
|
30
|
+
createLink(pkgRoot, join(cursorDir, 'helloagents'))
|
|
31
|
+
writeCursorStandbyHooks(home, pkgRoot)
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function uninstallCursorStandby(home) {
|
|
36
|
+
const cursorDir = join(home, '.cursor')
|
|
37
|
+
if (!existsSync(cursorDir)) return false
|
|
38
|
+
|
|
39
|
+
removeLink(join(cursorDir, 'helloagents'))
|
|
40
|
+
cleanHooksConfig(join(cursorDir, 'hooks.json'))
|
|
41
|
+
return true
|
|
42
|
+
}
|
package/scripts/cli-doctor.mjs
CHANGED
|
@@ -5,11 +5,19 @@ 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 {
|
|
8
|
+
import {
|
|
9
|
+
GROK_PLUGIN_NAME,
|
|
10
|
+
getClaudeMarketplaceRoot,
|
|
11
|
+
getCursorInstallRoot,
|
|
12
|
+
getCursorPluginRoot,
|
|
13
|
+
getGeminiExtensionRoot,
|
|
14
|
+
getGrokMarketplaceRoot,
|
|
15
|
+
} from './cli-runtime-root.mjs'
|
|
9
16
|
import { loadHooksWithCliEntry, safeJson, safeRead } from './cli-utils.mjs'
|
|
10
17
|
|
|
11
18
|
const CLAUDE_PLUGIN = 'helloagents@helloagents'
|
|
12
19
|
const GEMINI_EXTENSION = 'helloagents'
|
|
20
|
+
const GROK_STANDBY_HOOK_FILE = 'helloagents.json'
|
|
13
21
|
|
|
14
22
|
const runtime = {
|
|
15
23
|
home: '',
|
|
@@ -37,7 +45,13 @@ function normalizeText(text = '') {
|
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
function normalizePath(value = '') {
|
|
40
|
-
return String(value || '').replace(/\\/g, '/')
|
|
48
|
+
return String(value || '').replace(/\\/g, '/').toLowerCase()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function textIncludesNormalizedPath(text = '', targetPath = '') {
|
|
52
|
+
const normalizedText = normalizePath(text)
|
|
53
|
+
const normalizedTargetPath = normalizePath(targetPath)
|
|
54
|
+
return Boolean(normalizedTargetPath) && normalizedText.includes(normalizedTargetPath)
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
function extractManagedCarrierContent(filePath) {
|
|
@@ -107,6 +121,15 @@ function hasEnabledPlugin(enabledPlugins, pluginName) {
|
|
|
107
121
|
return false
|
|
108
122
|
}
|
|
109
123
|
|
|
124
|
+
function hasRegistryPlugin(registry = {}, pluginName, expectedSource = '') {
|
|
125
|
+
const normalizedExpectedSource = normalizePath(expectedSource)
|
|
126
|
+
return Object.values(registry?.repos || {}).some((repo) => {
|
|
127
|
+
if (!repo?.plugins?.[pluginName]) return false
|
|
128
|
+
if (!normalizedExpectedSource) return true
|
|
129
|
+
return normalizePath(repo?.kind?.source_path || '') === normalizedExpectedSource
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
function summarizeDoctorStatus(issues, { host, trackedMode, detectedMode } = {}) {
|
|
111
134
|
if (issues.length > 0) return 'drift'
|
|
112
135
|
if (detectedMode !== 'none') return 'ok'
|
|
@@ -193,6 +216,151 @@ function inspectClaudeDoctor(settings) {
|
|
|
193
216
|
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
194
217
|
}
|
|
195
218
|
|
|
219
|
+
function inspectCursorDoctor(settings) {
|
|
220
|
+
const host = 'cursor'
|
|
221
|
+
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
222
|
+
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
223
|
+
const cursorDir = join(runtime.home, '.cursor')
|
|
224
|
+
const actualHooks = safeJson(join(cursorDir, 'hooks.json')) || {}
|
|
225
|
+
const expectedHooks = readExpectedHooks('hooks-cursor.json', '')
|
|
226
|
+
const projectionRoot = getCursorPluginRoot(runtime.home)
|
|
227
|
+
const installRoot = getCursorInstallRoot(runtime.home)
|
|
228
|
+
const projectionManifestPath = join(projectionRoot, '.cursor-plugin', 'plugin.json')
|
|
229
|
+
const projectionHooksPath = join(projectionRoot, 'hooks', 'hooks-cursor.json')
|
|
230
|
+
const installManifestPath = join(installRoot, '.cursor-plugin', 'plugin.json')
|
|
231
|
+
const installHooksPath = join(installRoot, 'hooks', 'hooks-cursor.json')
|
|
232
|
+
const installedPlugin = safeJson(installManifestPath) || {}
|
|
233
|
+
const projectionManifest = normalizeText(safeRead(projectionManifestPath) || '')
|
|
234
|
+
const projectionHooks = normalizeText(safeRead(projectionHooksPath) || '')
|
|
235
|
+
const installManifest = normalizeText(safeRead(installManifestPath) || '')
|
|
236
|
+
const installHooks = normalizeText(safeRead(installHooksPath) || '')
|
|
237
|
+
const checks = {
|
|
238
|
+
homeLink: safeRealTarget(join(cursorDir, 'helloagents')) === runtime.pkgRoot,
|
|
239
|
+
standbyHooksFile: JSON.stringify(actualHooks).includes('helloagents'),
|
|
240
|
+
standbyHooksMatch: managedHooksMatch(actualHooks.hooks || actualHooks, expectedHooks),
|
|
241
|
+
globalPluginRoot: existsSync(projectionRoot),
|
|
242
|
+
globalPluginManifest: existsSync(projectionManifestPath),
|
|
243
|
+
globalPluginHooks: existsSync(projectionHooksPath),
|
|
244
|
+
globalPluginInstall: existsSync(installRoot),
|
|
245
|
+
globalPluginInstallManifest: existsSync(installManifestPath),
|
|
246
|
+
globalPluginInstallHooks: existsSync(installHooksPath),
|
|
247
|
+
globalPluginInstalled: installedPlugin.name === 'helloagents' || existsSync(installManifestPath),
|
|
248
|
+
globalPluginSyncMatch: Boolean(projectionManifest && projectionHooks)
|
|
249
|
+
&& projectionManifest === installManifest
|
|
250
|
+
&& projectionHooks === installHooks,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const issues = []
|
|
254
|
+
const notes = []
|
|
255
|
+
if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
|
|
256
|
+
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
257
|
+
}
|
|
258
|
+
if (detectedMode === 'standby') {
|
|
259
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby Cursor home 链接缺失或未指向稳定运行根目录', 'Standby Cursor home link is missing or points to a different runtime root'))
|
|
260
|
+
if (!checks.standbyHooksFile) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby Cursor hooks.json 缺少 HelloAGENTS hooks', 'Standby Cursor hooks.json is missing HelloAGENTS hooks'))
|
|
261
|
+
if (checks.standbyHooksFile && !checks.standbyHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby Cursor hooks 与当前 hooks 配置不一致', 'Standby Cursor hooks differ from the current hook configuration'))
|
|
262
|
+
}
|
|
263
|
+
if (detectedMode === 'global') {
|
|
264
|
+
if (!checks.globalPluginRoot) issues.push(buildDoctorIssue('global-plugin-root-missing', 'global Cursor 插件投影缺失', 'Global Cursor plugin projection is missing'))
|
|
265
|
+
if (!checks.globalPluginManifest) issues.push(buildDoctorIssue('global-plugin-manifest-missing', 'global Cursor .cursor-plugin/plugin.json 缺失', 'Global Cursor .cursor-plugin/plugin.json is missing'))
|
|
266
|
+
if (!checks.globalPluginHooks) issues.push(buildDoctorIssue('global-plugin-hooks-missing', 'global Cursor hooks-cursor.json 缺失', 'Global Cursor hooks-cursor.json is missing'))
|
|
267
|
+
if (!checks.globalPluginInstall) issues.push(buildDoctorIssue('global-plugin-install-missing', 'global Cursor 本地插件安装目录缺失', 'Global Cursor local plugin install directory is missing'))
|
|
268
|
+
if (!checks.globalPluginInstallManifest) issues.push(buildDoctorIssue('global-plugin-install-manifest-missing', 'global Cursor 安装目录缺少 .cursor-plugin/plugin.json', 'Global Cursor install directory is missing .cursor-plugin/plugin.json'))
|
|
269
|
+
if (!checks.globalPluginInstallHooks) issues.push(buildDoctorIssue('global-plugin-install-hooks-missing', 'global Cursor 安装目录缺少 hooks-cursor.json', 'Global Cursor install directory is missing hooks-cursor.json'))
|
|
270
|
+
if (!checks.globalPluginInstalled) issues.push(buildDoctorIssue('global-plugin-missing', 'global Cursor 本地插件未安装', 'Global Cursor local plugin is not installed'))
|
|
271
|
+
if (checks.globalPluginInstall && checks.globalPluginInstallManifest && checks.globalPluginInstallHooks && !checks.globalPluginSyncMatch) {
|
|
272
|
+
issues.push(buildDoctorIssue('global-plugin-sync-drift', 'global Cursor 安装目录内容与受管投影不一致', 'Global Cursor install directory content differs from the managed projection'))
|
|
273
|
+
}
|
|
274
|
+
if (checks.homeLink || checks.standbyHooksFile) {
|
|
275
|
+
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is detected as global'))
|
|
276
|
+
}
|
|
277
|
+
} else if (trackedMode === 'global') {
|
|
278
|
+
notes.push(runtime.msg(
|
|
279
|
+
'Cursor 的 global 模式使用本地插件目录 `~/.cursor/plugins/local/helloagents`;doctor 会检查受管投影、安装目录内容同步情况与 standby 残留。',
|
|
280
|
+
'Cursor global mode uses the local plugin directory `~/.cursor/plugins/local/helloagents`; doctor checks the managed projection, install-directory sync, and standby residue.',
|
|
281
|
+
))
|
|
282
|
+
}
|
|
283
|
+
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
284
|
+
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
285
|
+
}
|
|
286
|
+
if (trackedMode !== 'none' && detectedMode === 'none' && trackedMode !== 'global') {
|
|
287
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
291
|
+
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function inspectGrokDoctor(settings) {
|
|
295
|
+
const host = 'grok'
|
|
296
|
+
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
297
|
+
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
298
|
+
const grokDir = join(runtime.home, '.grok')
|
|
299
|
+
const registry = safeJson(join(grokDir, 'installed-plugins', 'registry.json')) || {}
|
|
300
|
+
const configText = safeRead(join(grokDir, 'config.toml')) || ''
|
|
301
|
+
const expectedHooks = readExpectedHooks('hooks-grok.json', '${GROK_PLUGIN_ROOT}')
|
|
302
|
+
const marketplaceRoot = getGrokMarketplaceRoot(runtime.home)
|
|
303
|
+
const marketplacePluginRoot = join(marketplaceRoot, 'plugins', GROK_PLUGIN_NAME)
|
|
304
|
+
const actualHooks = safeJson(join(grokDir, 'hooks', GROK_STANDBY_HOOK_FILE)) || {}
|
|
305
|
+
const checks = {
|
|
306
|
+
carrierMarker: (safeRead(join(grokDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
307
|
+
carrierContentMatch: extractManagedCarrierContent(join(grokDir, 'AGENTS.md'))
|
|
308
|
+
=== readExpectedCarrierContent('bootstrap-lite.md', settings),
|
|
309
|
+
homeLink: safeRealTarget(join(grokDir, 'helloagents')) === runtime.pkgRoot,
|
|
310
|
+
standbyHooksFile: JSON.stringify(actualHooks).includes('helloagents'),
|
|
311
|
+
standbyHooksMatch: managedHooksMatch(actualHooks.hooks || actualHooks, expectedHooks),
|
|
312
|
+
globalMarketplaceRoot: existsSync(marketplaceRoot),
|
|
313
|
+
globalMarketplaceCatalog: existsSync(join(marketplaceRoot, '.grok-plugin', 'marketplace.json')),
|
|
314
|
+
globalMarketplaceIndex: existsSync(join(marketplaceRoot, '.grok-plugin', 'plugin-index.json')),
|
|
315
|
+
globalPluginPayload: existsSync(marketplacePluginRoot),
|
|
316
|
+
globalPluginHooks: existsSync(join(marketplacePluginRoot, 'hooks', 'hooks.json')),
|
|
317
|
+
globalMarketplaceConfigured: textIncludesNormalizedPath(configText, marketplaceRoot),
|
|
318
|
+
globalPluginInstalled: hasRegistryPlugin(registry, GROK_PLUGIN_NAME, marketplacePluginRoot),
|
|
319
|
+
globalPluginEnabled: /\[plugins\][\s\S]*enabled\s*=\s*\[[^\]]*["']helloagents["']/i.test(configText),
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const issues = []
|
|
323
|
+
const notes = []
|
|
324
|
+
if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
|
|
325
|
+
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
326
|
+
}
|
|
327
|
+
if (detectedMode === 'standby') {
|
|
328
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
329
|
+
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前标准模式规则不一致', 'Standby carrier content differs from the current standby rules'))
|
|
330
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向稳定运行根目录', 'Standby home link is missing or points to a different runtime root'))
|
|
331
|
+
if (!checks.standbyHooksFile) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby Grok hooks 文件缺失', 'Standby Grok hooks file is missing'))
|
|
332
|
+
if (checks.standbyHooksFile && !checks.standbyHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby Grok hooks 与当前 hooks 配置不一致', 'Standby Grok hooks differ from the current hook configuration'))
|
|
333
|
+
}
|
|
334
|
+
if (detectedMode === 'global') {
|
|
335
|
+
if (!checks.globalMarketplaceRoot) issues.push(buildDoctorIssue('global-marketplace-root-missing', 'global marketplace 投影缺失', 'Global marketplace projection is missing'))
|
|
336
|
+
if (!checks.globalMarketplaceCatalog) issues.push(buildDoctorIssue('global-marketplace-catalog-missing', 'global marketplace catalog 缺失', 'Global marketplace catalog is missing'))
|
|
337
|
+
if (!checks.globalMarketplaceIndex) issues.push(buildDoctorIssue('global-marketplace-index-missing', 'global marketplace plugin-index 缺失', 'Global marketplace plugin-index is missing'))
|
|
338
|
+
if (!checks.globalPluginPayload) issues.push(buildDoctorIssue('global-plugin-payload-missing', 'global Grok 插件投影缺失', 'Global Grok plugin payload is missing'))
|
|
339
|
+
if (!checks.globalPluginHooks) issues.push(buildDoctorIssue('global-plugin-hooks-missing', 'global Grok hooks.json 缺失', 'Global Grok hooks.json is missing'))
|
|
340
|
+
if (!checks.globalPluginInstalled) issues.push(buildDoctorIssue('global-plugin-missing', 'global Grok 插件未安装', 'Global Grok plugin is not installed'))
|
|
341
|
+
if (!checks.globalMarketplaceConfigured) issues.push(buildDoctorIssue('global-marketplace-config-missing', 'global marketplace 来源未写入 ~/.grok/config.toml', 'Global marketplace source is not written to ~/.grok/config.toml'))
|
|
342
|
+
if (!checks.globalPluginEnabled) issues.push(buildDoctorIssue('global-plugin-disabled', 'global Grok 插件未在 ~/.grok/config.toml 中启用', 'Global Grok plugin is not enabled in ~/.grok/config.toml'))
|
|
343
|
+
if (checks.carrierMarker || checks.homeLink || checks.standbyHooksFile) {
|
|
344
|
+
issues.push(buildDoctorIssue('global-standby-residue', 'global 模式下仍残留 standby 注入/链接', 'Standby injections or links still remain while the host is detected as global'))
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (runtime.detectHostMode('claude') !== 'none') {
|
|
348
|
+
notes.push(runtime.msg(
|
|
349
|
+
'检测到 Claude Code 侧也存在受管配置。Grok 默认会扫描 Claude 兼容载体,若两边同时开启,可能出现规则重复加载。',
|
|
350
|
+
'Managed Claude Code artifacts were also detected. Grok scans Claude compatibility carriers by default, so enabling both sides can lead to duplicated rule loading.',
|
|
351
|
+
))
|
|
352
|
+
}
|
|
353
|
+
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
354
|
+
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
355
|
+
}
|
|
356
|
+
if (trackedMode !== 'none' && detectedMode === 'none') {
|
|
357
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
361
|
+
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
362
|
+
}
|
|
363
|
+
|
|
196
364
|
function inspectGeminiDoctor(settings) {
|
|
197
365
|
const host = 'gemini'
|
|
198
366
|
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
@@ -270,13 +438,15 @@ function parseDoctorArgs(args) {
|
|
|
270
438
|
|
|
271
439
|
function inspectDoctorHost(host, settings) {
|
|
272
440
|
if (host === 'claude') return inspectClaudeDoctor(settings)
|
|
441
|
+
if (host === 'cursor') return inspectCursorDoctor(settings)
|
|
273
442
|
if (host === 'gemini') return inspectGeminiDoctor(settings)
|
|
443
|
+
if (host === 'grok') return inspectGrokDoctor(settings)
|
|
274
444
|
return inspectCodexDoctorImpl(runtime, settings)
|
|
275
445
|
}
|
|
276
446
|
|
|
277
447
|
function buildDoctorReport(host) {
|
|
278
448
|
const settings = runtime.readSettings(true)
|
|
279
|
-
const hosts = host === 'all' ? ['claude', 'gemini', 'codex'] : [host]
|
|
449
|
+
const hosts = host === 'all' ? ['claude', 'gemini', 'grok', 'cursor', 'codex'] : [host]
|
|
280
450
|
const reports = hosts.map((target) => inspectDoctorHost(target, settings))
|
|
281
451
|
const summary = reports.reduce((acc, report) => {
|
|
282
452
|
acc[report.status] = (acc[report.status] || 0) + 1
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createLink,
|
|
6
|
+
ensureDir,
|
|
7
|
+
injectMarkedContent,
|
|
8
|
+
loadHooksWithCliEntry,
|
|
9
|
+
removeIfExists,
|
|
10
|
+
removeLink,
|
|
11
|
+
removeMarkedContent,
|
|
12
|
+
safeRead,
|
|
13
|
+
safeWrite,
|
|
14
|
+
} from './cli-utils.mjs'
|
|
15
|
+
import { buildRuntimeCarrier, readCarrierSettings } from './cli-runtime-carrier.mjs'
|
|
16
|
+
|
|
17
|
+
export const GROK_STANDBY_HOOK_FILE = 'helloagents.json'
|
|
18
|
+
|
|
19
|
+
function writeStandbyHooks(home, pkgRoot) {
|
|
20
|
+
const hooksData = loadHooksWithCliEntry(pkgRoot, 'hooks-grok.json', '${GROK_PLUGIN_ROOT}')
|
|
21
|
+
if (!hooksData) return false
|
|
22
|
+
|
|
23
|
+
const hooksDir = join(home, '.grok', 'hooks')
|
|
24
|
+
ensureDir(hooksDir)
|
|
25
|
+
safeWrite(join(hooksDir, GROK_STANDBY_HOOK_FILE), `${JSON.stringify(hooksData, null, 2)}\n`)
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function installGrokStandby(home, pkgRoot) {
|
|
30
|
+
const grokDir = join(home, '.grok')
|
|
31
|
+
ensureDir(grokDir)
|
|
32
|
+
|
|
33
|
+
const bootstrapContent = safeRead(join(pkgRoot, 'bootstrap-lite.md'))
|
|
34
|
+
if (bootstrapContent) {
|
|
35
|
+
injectMarkedContent(
|
|
36
|
+
join(grokDir, 'AGENTS.md'),
|
|
37
|
+
buildRuntimeCarrier(bootstrapContent, readCarrierSettings(home)).trimEnd(),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
createLink(pkgRoot, join(grokDir, 'helloagents'))
|
|
42
|
+
writeStandbyHooks(home, pkgRoot)
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function uninstallGrokStandby(home) {
|
|
47
|
+
const grokDir = join(home, '.grok')
|
|
48
|
+
if (!existsSync(grokDir)) return false
|
|
49
|
+
|
|
50
|
+
removeMarkedContent(join(grokDir, 'AGENTS.md'))
|
|
51
|
+
removeLink(join(grokDir, 'helloagents'))
|
|
52
|
+
removeIfExists(join(grokDir, 'hooks', GROK_STANDBY_HOOK_FILE))
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
@@ -6,11 +6,19 @@ import {
|
|
|
6
6
|
CODEX_PLUGIN_KEY,
|
|
7
7
|
CODEX_PLUGIN_NAME,
|
|
8
8
|
} from './cli-codex.mjs'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getCursorInstallRoot,
|
|
11
|
+
getCursorPluginRoot,
|
|
12
|
+
GROK_PLUGIN_NAME,
|
|
13
|
+
getGeminiExtensionRoot,
|
|
14
|
+
getGrokMarketplaceRoot,
|
|
15
|
+
getStableRuntimeRoot,
|
|
16
|
+
} from './cli-runtime-root.mjs'
|
|
10
17
|
import { safeJson, safeRead } from './cli-utils.mjs'
|
|
11
18
|
|
|
12
19
|
const CLAUDE_PLUGIN = 'helloagents@helloagents'
|
|
13
20
|
const GEMINI_EXTENSION = 'helloagents'
|
|
21
|
+
const GROK_STANDBY_HOOK_FILE = 'helloagents.json'
|
|
14
22
|
|
|
15
23
|
const HOST_ALIASES = new Map([
|
|
16
24
|
['all', 'all'],
|
|
@@ -19,6 +27,10 @@ const HOST_ALIASES = new Map([
|
|
|
19
27
|
['claude-code', 'claude'],
|
|
20
28
|
['gemini', 'gemini'],
|
|
21
29
|
['gemini-cli', 'gemini'],
|
|
30
|
+
['grok', 'grok'],
|
|
31
|
+
['grok-build', 'grok'],
|
|
32
|
+
['cursor', 'cursor'],
|
|
33
|
+
['cursor-cli', 'cursor'],
|
|
22
34
|
['codex', 'codex'],
|
|
23
35
|
['codex-cli', 'codex'],
|
|
24
36
|
])
|
|
@@ -59,6 +71,19 @@ function hasEnabledPlugin(enabledPlugins, pluginName) {
|
|
|
59
71
|
return false
|
|
60
72
|
}
|
|
61
73
|
|
|
74
|
+
function hasHelloagentsHookFile(filePath) {
|
|
75
|
+
return JSON.stringify(safeJson(filePath) || {}).includes('helloagents')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function grokRegistryHasManagedPlugin(registry = {}, expectedSource = '') {
|
|
79
|
+
const normalizedExpectedSource = normalizePath(expectedSource)
|
|
80
|
+
return Object.values(registry?.repos || {}).some((repo) => {
|
|
81
|
+
if (!repo?.plugins?.[GROK_PLUGIN_NAME]) return false
|
|
82
|
+
if (!normalizedExpectedSource) return true
|
|
83
|
+
return normalizePath(repo?.kind?.source_path || '') === normalizedExpectedSource
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
62
87
|
function detectClaudeMode(home) {
|
|
63
88
|
const claudeDir = join(home, '.claude')
|
|
64
89
|
const settings = safeJson(join(claudeDir, 'settings.json')) || {}
|
|
@@ -123,6 +148,43 @@ function detectCodexMode(home) {
|
|
|
123
148
|
return ''
|
|
124
149
|
}
|
|
125
150
|
|
|
151
|
+
function detectCursorMode(home) {
|
|
152
|
+
const cursorDir = join(home, '.cursor')
|
|
153
|
+
const projectionRoot = safeRealTarget(getCursorPluginRoot(home)) || normalizePath(getCursorPluginRoot(home))
|
|
154
|
+
const installRoot = getCursorInstallRoot(home)
|
|
155
|
+
const installedPlugin = safeJson(join(installRoot, '.cursor-plugin', 'plugin.json')) || {}
|
|
156
|
+
if (
|
|
157
|
+
existsSync(installRoot)
|
|
158
|
+
&& (safeRealTarget(installRoot) === projectionRoot || installedPlugin.name === 'helloagents')
|
|
159
|
+
) {
|
|
160
|
+
return 'global'
|
|
161
|
+
}
|
|
162
|
+
if (
|
|
163
|
+
existsSync(join(cursorDir, 'helloagents'))
|
|
164
|
+
|| hasHelloagentsHookFile(join(cursorDir, 'hooks.json'))
|
|
165
|
+
) {
|
|
166
|
+
return 'standby'
|
|
167
|
+
}
|
|
168
|
+
return ''
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function detectGrokMode(home) {
|
|
172
|
+
const grokDir = join(home, '.grok')
|
|
173
|
+
const registry = safeJson(join(grokDir, 'installed-plugins', 'registry.json')) || {}
|
|
174
|
+
const marketplacePluginRoot = join(getGrokMarketplaceRoot(home), 'plugins', GROK_PLUGIN_NAME)
|
|
175
|
+
if (grokRegistryHasManagedPlugin(registry, marketplacePluginRoot)) {
|
|
176
|
+
return 'global'
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
existsSync(join(grokDir, 'helloagents'))
|
|
180
|
+
|| hasHelloagentsMarker(join(grokDir, 'AGENTS.md'))
|
|
181
|
+
|| hasHelloagentsHookFile(join(grokDir, 'hooks', GROK_STANDBY_HOOK_FILE))
|
|
182
|
+
) {
|
|
183
|
+
return 'standby'
|
|
184
|
+
}
|
|
185
|
+
return ''
|
|
186
|
+
}
|
|
187
|
+
|
|
126
188
|
export function normalizeHost(value = '') {
|
|
127
189
|
return HOST_ALIASES.get(String(value || '').toLowerCase()) || ''
|
|
128
190
|
}
|
|
@@ -130,6 +192,8 @@ export function normalizeHost(value = '') {
|
|
|
130
192
|
export function getHostLabel(host) {
|
|
131
193
|
if (host === 'claude') return 'Claude Code'
|
|
132
194
|
if (host === 'gemini') return 'Gemini CLI'
|
|
195
|
+
if (host === 'grok') return 'Grok Build'
|
|
196
|
+
if (host === 'cursor') return 'Cursor'
|
|
133
197
|
if (host === 'codex') return 'Codex CLI'
|
|
134
198
|
return 'All CLIs'
|
|
135
199
|
}
|
|
@@ -137,6 +201,8 @@ export function getHostLabel(host) {
|
|
|
137
201
|
export function detectHostMode(host, runtime) {
|
|
138
202
|
if (host === 'claude') return detectClaudeMode(runtime.home)
|
|
139
203
|
if (host === 'gemini') return detectGeminiMode(runtime.home)
|
|
204
|
+
if (host === 'grok') return detectGrokMode(runtime.home)
|
|
205
|
+
if (host === 'cursor') return detectCursorMode(runtime.home)
|
|
140
206
|
if (host === 'codex') return detectCodexMode(runtime.home)
|
|
141
207
|
return ''
|
|
142
208
|
}
|
package/scripts/cli-hosts.mjs
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
loadHooksWithCliEntry,
|
|
13
13
|
} from './cli-utils.mjs';
|
|
14
14
|
import { buildRuntimeCarrier, readCarrierSettings } from './cli-runtime-carrier.mjs';
|
|
15
|
+
import { installCursorStandby, uninstallCursorStandby } from './cli-cursor.mjs';
|
|
16
|
+
import { installGrokStandby, uninstallGrokStandby } from './cli-grok.mjs';
|
|
15
17
|
|
|
16
18
|
export function installClaudeStandby(home, pkgRoot) {
|
|
17
19
|
const claudeDir = join(home, '.claude');
|
|
@@ -81,3 +83,6 @@ export function uninstallGeminiStandby(home) {
|
|
|
81
83
|
|
|
82
84
|
return true;
|
|
83
85
|
}
|
|
86
|
+
|
|
87
|
+
export { installGrokStandby, uninstallGrokStandby };
|
|
88
|
+
export { installCursorStandby, uninstallCursorStandby };
|