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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.1.7",
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 []
@@ -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
+ }
@@ -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 { getClaudeMarketplaceRoot, getGeminiExtensionRoot } from './cli-runtime-root.mjs'
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 { getGeminiExtensionRoot, getStableRuntimeRoot } from './cli-runtime-root.mjs'
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
  }
@@ -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 };