helloagents 2.3.8 → 3.0.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +21 -0
  3. package/.codex-plugin/plugin.json +46 -0
  4. package/README.md +494 -636
  5. package/README_CN.md +778 -0
  6. package/assets/dogdoing/complete.wav +0 -0
  7. package/assets/dogdoing/confirm.wav +0 -0
  8. package/assets/dogdoing/error.wav +0 -0
  9. package/assets/dogdoing/idle.wav +0 -0
  10. package/assets/dogdoing/warning.wav +0 -0
  11. package/assets/icons/icon-large.png +0 -0
  12. package/assets/icons/icon.png +0 -0
  13. package/assets/sounds/complete.wav +0 -0
  14. package/assets/sounds/confirm.wav +0 -0
  15. package/assets/sounds/error.wav +0 -0
  16. package/assets/sounds/idle.wav +0 -0
  17. package/assets/sounds/warning.wav +0 -0
  18. package/bootstrap-lite.md +199 -0
  19. package/bootstrap.md +296 -0
  20. package/cli.mjs +453 -0
  21. package/gemini-extension.json +8 -0
  22. package/hooks/hooks-claude.json +88 -0
  23. package/hooks/hooks.json +76 -0
  24. package/package.json +36 -6
  25. package/scripts/cli-codex.mjs +428 -0
  26. package/scripts/cli-config.mjs +37 -0
  27. package/scripts/cli-hosts.mjs +75 -0
  28. package/scripts/cli-messages.mjs +92 -0
  29. package/scripts/cli-toml.mjs +251 -0
  30. package/scripts/cli-utils.mjs +139 -0
  31. package/scripts/guard.mjs +217 -0
  32. package/scripts/notify-context.mjs +123 -0
  33. package/scripts/notify-events.mjs +11 -0
  34. package/scripts/notify-shared.mjs +47 -0
  35. package/scripts/notify-ui.mjs +92 -0
  36. package/scripts/notify.mjs +219 -0
  37. package/scripts/ralph-loop.mjs +246 -0
  38. package/skills/_meta/SKILL.md +19 -0
  39. package/skills/commands/auto/SKILL.md +91 -0
  40. package/skills/commands/clean/SKILL.md +21 -0
  41. package/skills/commands/commit/SKILL.md +26 -0
  42. package/skills/commands/design/SKILL.md +108 -0
  43. package/skills/commands/help/SKILL.md +45 -0
  44. package/skills/commands/init/SKILL.md +60 -0
  45. package/skills/commands/loop/SKILL.md +98 -0
  46. package/skills/commands/prd/SKILL.md +151 -0
  47. package/skills/commands/review/SKILL.md +16 -0
  48. package/skills/commands/test/SKILL.md +16 -0
  49. package/skills/commands/verify/SKILL.md +21 -0
  50. package/skills/hello-api/SKILL.md +40 -0
  51. package/skills/hello-arch/SKILL.md +38 -0
  52. package/skills/hello-data/SKILL.md +39 -0
  53. package/skills/hello-debug/SKILL.md +58 -0
  54. package/skills/hello-errors/SKILL.md +39 -0
  55. package/skills/hello-perf/SKILL.md +40 -0
  56. package/skills/hello-reflect/SKILL.md +34 -0
  57. package/skills/hello-review/SKILL.md +33 -0
  58. package/skills/hello-security/SKILL.md +40 -0
  59. package/skills/hello-subagent/SKILL.md +32 -0
  60. package/skills/hello-test/SKILL.md +55 -0
  61. package/skills/hello-ui/SKILL.md +197 -0
  62. package/skills/hello-verify/SKILL.md +132 -0
  63. package/skills/hello-write/SKILL.md +33 -0
  64. package/skills/helloagents/SKILL.md +107 -0
  65. package/templates/CHANGELOG.md +5 -0
  66. package/templates/DESIGN.md +19 -0
  67. package/templates/STATE.md +19 -0
  68. package/templates/archive/_index.md +4 -0
  69. package/templates/context.md +19 -0
  70. package/templates/guidelines.md +15 -0
  71. package/templates/modules/module.md +13 -0
  72. package/templates/plans/decisions.md +10 -0
  73. package/templates/plans/design.md +14 -0
  74. package/templates/plans/prd/00-overview.md +23 -0
  75. package/templates/plans/prd/01-user-stories.md +19 -0
  76. package/templates/plans/prd/02-functional.md +30 -0
  77. package/templates/plans/prd/03-ui-design.md +31 -0
  78. package/templates/plans/prd/04-technical.md +25 -0
  79. package/templates/plans/prd/05-nonfunctional.md +28 -0
  80. package/templates/plans/prd/06-i18n-l10n.md +23 -0
  81. package/templates/plans/prd/07-accessibility.md +20 -0
  82. package/templates/plans/prd/08-content.md +20 -0
  83. package/templates/plans/prd/09-testing.md +22 -0
  84. package/templates/plans/prd/10-deployment.md +23 -0
  85. package/templates/plans/prd/11-legal-privacy.md +18 -0
  86. package/templates/plans/prd/12-timeline.md +21 -0
  87. package/templates/plans/requirements.md +18 -0
  88. package/templates/plans/tasks.md +10 -0
  89. package/templates/verify.yaml +9 -0
  90. package/bin/cli.mjs +0 -106
@@ -0,0 +1,123 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+
5
+ function buildPackageRootBlock(pkgRoot) {
6
+ if (!pkgRoot) return '';
7
+ return `## 当前 HelloAGENTS 包根目录\n\`\`\`text\n${pkgRoot}\n\`\`\``;
8
+ }
9
+
10
+ function resolveStandbyHostRoot(host) {
11
+ const home = homedir();
12
+ const map = {
13
+ claude: join(home, '.claude', 'helloagents'),
14
+ codex: join(home, '.codex', 'helloagents'),
15
+ gemini: join(home, '.gemini', 'helloagents'),
16
+ };
17
+ return map[host] || '';
18
+ }
19
+
20
+ function resolveReadRoot({ cwd, pkgRoot, host, settings }) {
21
+ const projectRoot = join(cwd, 'skills', 'helloagents');
22
+ if (existsSync(projectRoot)) {
23
+ return { source: 'project', root: projectRoot };
24
+ }
25
+
26
+ if (settings.install_mode === 'standby') {
27
+ const standbyRoot = resolveStandbyHostRoot(host);
28
+ if (standbyRoot && existsSync(standbyRoot)) {
29
+ return { source: 'standby-home', root: standbyRoot };
30
+ }
31
+ }
32
+
33
+ return { source: 'package', root: pkgRoot };
34
+ }
35
+
36
+ function buildReadRootBlock(readRoot) {
37
+ if (!readRoot?.root) return '';
38
+ return `## 当前 HelloAGENTS 读取根目录\n\`\`\`json\n${JSON.stringify(readRoot, null, 2)}\n\`\`\``;
39
+ }
40
+
41
+ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFile, host }) {
42
+ const summaryParts = [];
43
+ summaryParts.push('## HelloAGENTS 压缩摘要');
44
+ summaryParts.push('以下信息在上下文压缩前保存,确保压缩后不丢失关键状态。');
45
+
46
+ const cwd = payload.cwd || process.cwd();
47
+ const statePath = join(cwd, '.helloagents', 'STATE.md');
48
+ if (existsSync(statePath)) {
49
+ try {
50
+ const stateContent = readFileSync(statePath, 'utf-8');
51
+ summaryParts.push('');
52
+ summaryParts.push('## 恢复快照(从 STATE.md 读取,读完即可接上工作)');
53
+ summaryParts.push(stateContent);
54
+ } catch {}
55
+ }
56
+
57
+ let bootstrap = '';
58
+ try {
59
+ bootstrap = readFileSync(join(pkgRoot, bootstrapFile), 'utf-8');
60
+ } catch {}
61
+ if (bootstrap) {
62
+ summaryParts.push('');
63
+ summaryParts.push('## 核心规则(从 bootstrap 重新注入)');
64
+ summaryParts.push(bootstrap);
65
+ }
66
+
67
+ const packageRootBlock = buildPackageRootBlock(pkgRoot);
68
+ if (packageRootBlock) {
69
+ summaryParts.push('');
70
+ summaryParts.push(packageRootBlock);
71
+ }
72
+
73
+ const readRootBlock = buildReadRootBlock(resolveReadRoot({ cwd, pkgRoot, host, settings }));
74
+ if (readRootBlock) {
75
+ summaryParts.push('');
76
+ summaryParts.push(readRootBlock);
77
+ }
78
+
79
+ if (Object.keys(settings).length) {
80
+ summaryParts.push('');
81
+ summaryParts.push(`## 当前用户设置\n\`\`\`json\n${JSON.stringify(settings, null, 2)}\n\`\`\``);
82
+ }
83
+
84
+ return summaryParts.join('\n');
85
+ }
86
+
87
+ export function buildInjectContext({ source, bootstrap, settings, pkgRoot, host, cwd }) {
88
+ const packageRootBlock = buildPackageRootBlock(pkgRoot);
89
+ const readRootBlock = buildReadRootBlock(resolveReadRoot({ cwd, pkgRoot, host, settings }));
90
+ const settingsBlock = Object.keys(settings).length
91
+ ? `\n\n## 当前用户设置\n\`\`\`json\n${JSON.stringify(settings, null, 2)}\n\`\`\``
92
+ : '';
93
+
94
+ let context = bootstrap;
95
+ if (packageRootBlock) context += `\n\n${packageRootBlock}`;
96
+ if (readRootBlock) context += `\n\n${readRootBlock}`;
97
+ context += settingsBlock;
98
+ if (source === 'resume' || source === 'compact') {
99
+ context += '\n\n> ⚠️ 会话已恢复/压缩,请先读取 .helloagents/STATE.md 恢复工作状态。';
100
+ }
101
+ return context;
102
+ }
103
+
104
+ export function buildRouteInstruction({ skillName, extraRules = '', cwd, pkgRoot, host, settings }) {
105
+ const readRoot = resolveReadRoot({ cwd, pkgRoot, host, settings });
106
+ const skillPath = join(readRoot.root, 'skills', 'commands', skillName, 'SKILL.md');
107
+ return `用户使用了 ~${skillName} 命令。当前命令技能文件已解析为:${skillPath}。请直接读取这个 SKILL.md;本轮不要再为同一个命令 skill 重复 Test-Path / Get-Content,也不要探测其他 helloagents 路径。${extraRules}`;
108
+ }
109
+
110
+ export function detectNewProjectRoute(prompt) {
111
+ const newProjectPatterns = [
112
+ /(?:创建|新建|从零|搭建).*(?:项目|应用|系统|网站|游戏|工具|平台|小程序|APP)/,
113
+ /(?:做|写|开发|实现)[一个]*.*(?:项目|应用|系统|网站|游戏|工具|平台|小程序|APP)/,
114
+ /\b(build|create|design|make|new|start|init)\b.*\b(app|game|project|site|website|tool|system|platform)\b/i,
115
+ ];
116
+
117
+ for (const pattern of newProjectPatterns) {
118
+ if (pattern.test(prompt)) {
119
+ return '检测到可能是新项目/新应用任务。根据 HelloAGENTS 路由规则,新项目必须进入 ~design 设计流程。请引导用户进入 ~design。';
120
+ }
121
+ }
122
+ return '';
123
+ }
@@ -0,0 +1,11 @@
1
+ export function shouldIgnoreCodexNotifyClient(client) {
2
+ return !!client && client !== 'codex-tui';
3
+ }
4
+
5
+ export function shouldIgnoreFormattedSubagent(lastMsg, outputFormatEnabled) {
6
+ return outputFormatEnabled && !lastMsg.includes('【HelloAGENTS】');
7
+ }
8
+
9
+ export function claimsTaskComplete(lastMsg) {
10
+ return /✅|完成|已修复|done|fixed|completed|finished/i.test(lastMsg);
11
+ }
@@ -0,0 +1,47 @@
1
+ import { readFileSync, readSync } from 'node:fs';
2
+
3
+ export function readSettings(configFile) {
4
+ try {
5
+ return JSON.parse(readFileSync(configFile, 'utf-8'));
6
+ } catch {
7
+ return {};
8
+ }
9
+ }
10
+
11
+ export function readStdinJson() {
12
+ try {
13
+ const chunks = [];
14
+ const buf = Buffer.alloc(4096);
15
+ let n;
16
+ const fd = process.stdin.fd;
17
+ try {
18
+ while ((n = readSync(fd, buf, 0, buf.length)) > 0) {
19
+ chunks.push(buf.slice(0, n));
20
+ }
21
+ } catch {}
22
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
23
+ return raw ? JSON.parse(raw) : {};
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ export function output(obj) {
30
+ process.stdout.write(JSON.stringify(obj));
31
+ }
32
+
33
+ export function suppressedOutput(hookEventName, additionalContext) {
34
+ output({
35
+ hookSpecificOutput: {
36
+ hookEventName,
37
+ ...(additionalContext != null ? { additionalContext } : {}),
38
+ },
39
+ suppressOutput: true,
40
+ });
41
+ }
42
+
43
+ export function emptySuppress() {
44
+ output({ suppressOutput: true });
45
+ }
46
+
47
+ export function versionCheckBackground() {}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Sound playback and desktop notification for HelloAGENTS.
3
+ * Cross-platform: Windows (PowerShell), macOS (afplay/osascript), Linux (aplay/notify-send).
4
+ */
5
+ import { platform } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { existsSync } from 'node:fs';
8
+ import { spawnSync } from 'node:child_process';
9
+
10
+ const PLAT = platform();
11
+
12
+ const NOTIFY_MESSAGES = {
13
+ complete: '任务完成',
14
+ confirm: '需要确认',
15
+ warning: '出现问题',
16
+ error: '执行出错',
17
+ idle: '等待输入',
18
+ };
19
+
20
+ const WIN_APPID = 'HelloAgents.Notification';
21
+
22
+ function resolveWav(pkgRoot, event) {
23
+ const p = join(pkgRoot, 'assets', 'sounds', `${event}.wav`);
24
+ return existsSync(p) ? p : null;
25
+ }
26
+
27
+ export function playSound(pkgRoot, event) {
28
+ const wav = resolveWav(pkgRoot, event);
29
+ if (!wav) { process.stderr.write('\x07'); return; }
30
+ try {
31
+ if (PLAT === 'win32') {
32
+ spawnSync('powershell', ['-NoProfile', '-c',
33
+ `(New-Object Media.SoundPlayer '${wav.replace(/'/g, "''")}').PlaySync()`],
34
+ { stdio: 'ignore', windowsHide: true });
35
+ } else if (PLAT === 'darwin') {
36
+ spawnSync('afplay', [wav], { stdio: 'ignore' });
37
+ } else {
38
+ const result = spawnSync('aplay', ['-q', wav], { stdio: 'ignore' });
39
+ if (result.status !== 0) {
40
+ const pa = spawnSync('paplay', [wav], { stdio: 'ignore' });
41
+ if (pa.status !== 0) process.stderr.write('\x07');
42
+ }
43
+ }
44
+ } catch { process.stderr.write('\x07'); }
45
+ }
46
+
47
+ function ensureWinAppId(pkgRoot) {
48
+ if (PLAT !== 'win32') return;
49
+ const regKey = `HKCU:\\Software\\Classes\\AppUserModelId\\${WIN_APPID}`;
50
+ spawnSync('powershell', ['-NoProfile', '-c',
51
+ `if (-not (Test-Path '${regKey}')) { New-Item -Path '${regKey}' -Force | Out-Null; Set-ItemProperty -Path '${regKey}' -Name 'DisplayName' -Value 'HelloAgents 通知' -Force }`],
52
+ { stdio: 'ignore', windowsHide: true });
53
+ }
54
+
55
+ export function desktopNotify(pkgRoot, event, extra) {
56
+ const msg = extra || NOTIFY_MESSAGES[event] || event;
57
+ const title = 'HelloAgents 通知';
58
+ try {
59
+ if (PLAT === 'win32') {
60
+ ensureWinAppId(pkgRoot);
61
+ const safeMsg = msg.replace(/'/g, "''");
62
+ const iconPath = join(pkgRoot, 'assets', 'icons', 'icon.png').replace(/\//g, '\\');
63
+ const iconXml = existsSync(iconPath) ? `<image placement="appLogoOverride" src="${iconPath}" />` : '';
64
+ const ps = `
65
+ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
66
+ [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null
67
+ $xml = @"
68
+ <toast>
69
+ <visual>
70
+ <binding template="ToastGeneric">
71
+ ${iconXml}
72
+ <text>${safeMsg}</text>
73
+ </binding>
74
+ </visual>
75
+ </toast>
76
+ "@
77
+ $doc = New-Object Windows.Data.Xml.Dom.XmlDocument
78
+ $doc.LoadXml($xml)
79
+ $toast = [Windows.UI.Notifications.ToastNotification]::new($doc)
80
+ [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('${WIN_APPID}').Show($toast)
81
+ `.trim();
82
+ spawnSync('powershell', ['-NoProfile', '-c', ps], { stdio: 'ignore', windowsHide: true });
83
+ } else if (PLAT === 'darwin') {
84
+ spawnSync('osascript', ['-e',
85
+ `display notification "${msg.replace(/"/g, '\\"')}" with title "${title}"`],
86
+ { stdio: 'ignore' });
87
+ } else {
88
+ const result = spawnSync('notify-send', [title, msg], { stdio: 'ignore' });
89
+ if (result.status !== 0) process.stderr.write('\x07');
90
+ }
91
+ } catch { process.stderr.write('\x07'); }
92
+ }
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env node
2
+ // notify.mjs — Unified notification & bootstrap injection for HelloAGENTS
3
+ // Zero external dependencies, ES module, cross-platform
4
+
5
+ import { join, dirname } from 'node:path';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { homedir } from 'node:os';
10
+ import { playSound as _playSound, desktopNotify as _desktopNotify } from './notify-ui.mjs';
11
+ import { buildCompactionContext, buildInjectContext, buildRouteInstruction, detectNewProjectRoute } from './notify-context.mjs';
12
+ import { claimsTaskComplete, shouldIgnoreCodexNotifyClient, shouldIgnoreFormattedSubagent } from './notify-events.mjs';
13
+ import { readSettings, readStdinJson, output, suppressedOutput, emptySuppress, versionCheckBackground } from './notify-shared.mjs';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const PKG_ROOT = join(__dirname, '..');
18
+ const CONFIG_FILE = join(homedir(), '.helloagents', 'helloagents.json');
19
+ const HOST = process.argv.includes('--gemini')
20
+ ? 'gemini'
21
+ : process.argv.includes('--codex')
22
+ ? 'codex'
23
+ : 'claude';
24
+ const IS_GEMINI = HOST === 'gemini';
25
+ const EVENT_NAME = {
26
+ SessionStart: 'SessionStart',
27
+ UserPromptSubmit: IS_GEMINI ? 'BeforeAgent' : 'UserPromptSubmit',
28
+ PreCompact: IS_GEMINI ? 'BeforeAgent' : 'PreCompact',
29
+ };
30
+
31
+ const playSound = (event) => _playSound(PKG_ROOT, event);
32
+ const desktopNotify = (event, extra) => _desktopNotify(PKG_ROOT, event, extra);
33
+
34
+ function getSettings() {
35
+ return readSettings(CONFIG_FILE);
36
+ }
37
+
38
+ function runRalphLoop(payload) {
39
+ const settings = getSettings();
40
+ if (settings.ralph_loop_enabled === false) return false;
41
+ try {
42
+ const rlPath = join(__dirname, 'ralph-loop.mjs');
43
+ if (!existsSync(rlPath)) return false;
44
+ const result = spawnSync(process.execPath, [rlPath, ...(IS_GEMINI ? ['--gemini'] : [])], {
45
+ input: JSON.stringify(payload),
46
+ encoding: 'utf-8',
47
+ timeout: 120_000,
48
+ });
49
+ if (result.stdout) {
50
+ const rlOut = JSON.parse(result.stdout);
51
+ if (rlOut.decision === 'block') {
52
+ output(rlOut);
53
+ return true;
54
+ }
55
+ }
56
+ } catch {}
57
+ return false;
58
+ }
59
+
60
+ function resolveBootstrapFile(cwd, settings) {
61
+ const isGlobal = settings.install_mode === 'global';
62
+ const isActivated = existsSync(join(cwd, '.helloagents'));
63
+ return (isGlobal || isActivated) ? 'bootstrap.md' : 'bootstrap-lite.md';
64
+ }
65
+
66
+ function cmdPreCompact() {
67
+ const payload = readStdinJson();
68
+ const cwd = payload.cwd || process.cwd();
69
+ const settings = getSettings();
70
+ const bootstrapFile = resolveBootstrapFile(cwd, settings);
71
+ const context = buildCompactionContext({
72
+ payload,
73
+ pkgRoot: PKG_ROOT,
74
+ settings,
75
+ bootstrapFile,
76
+ host: HOST,
77
+ });
78
+ suppressedOutput(EVENT_NAME.PreCompact, context);
79
+ }
80
+
81
+ function cmdRoute() {
82
+ const payload = readStdinJson();
83
+ const prompt = (payload.prompt || '').trim();
84
+ const cwd = payload.cwd || process.cwd();
85
+ const settings = getSettings();
86
+ if (!prompt) {
87
+ emptySuppress();
88
+ return;
89
+ }
90
+ if (/^\[子代理任务\]/.test(prompt)) {
91
+ emptySuppress();
92
+ return;
93
+ }
94
+
95
+ const cmdMatch = prompt.match(/^~(\w+)/);
96
+ if (cmdMatch) {
97
+ const skillName = cmdMatch[1];
98
+ const extraRules = skillName === 'help'
99
+ ? ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。适用条件:仅显示 HelloAGENTS 的帮助和当前设置;优先使用当前上下文中已注入的“当前用户设置”,只有上下文不存在该信息时才尝试读取 ~/.helloagents/helloagents.json;自动激活技能说明仅在全局模式或当前项目已通过 ~init 激活后生效。排除条件:不要调用宿主 CLI 的帮助工具(如 cli_help 或 /help),不要使用子代理,不要读取项目文件;若受工作区限制无法读取配置,必须明确说明并按已知默认值或已注入设置展示;纯标准模式未激活项目不会自动触发。'
100
+ : '';
101
+ suppressedOutput(EVENT_NAME.UserPromptSubmit, buildRouteInstruction({
102
+ skillName,
103
+ extraRules,
104
+ cwd,
105
+ pkgRoot: PKG_ROOT,
106
+ host: HOST,
107
+ settings,
108
+ }));
109
+ return;
110
+ }
111
+
112
+ const bootstrapFile = resolveBootstrapFile(cwd, settings);
113
+ if (bootstrapFile === 'bootstrap.md') {
114
+ const routeMessage = detectNewProjectRoute(prompt);
115
+ if (routeMessage) {
116
+ suppressedOutput(EVENT_NAME.UserPromptSubmit, routeMessage);
117
+ return;
118
+ }
119
+ }
120
+
121
+ emptySuppress();
122
+ }
123
+
124
+ function cmdInject() {
125
+ const payload = readStdinJson();
126
+ const source = payload.source || 'startup';
127
+ const cwd = payload.cwd || process.cwd();
128
+ const settings = getSettings();
129
+ const bootstrapFile = resolveBootstrapFile(cwd, settings);
130
+
131
+ let bootstrap = '';
132
+ try {
133
+ bootstrap = readFileSync(join(PKG_ROOT, bootstrapFile), 'utf-8');
134
+ } catch {}
135
+
136
+ const context = buildInjectContext({
137
+ source,
138
+ bootstrap,
139
+ settings,
140
+ pkgRoot: PKG_ROOT,
141
+ host: HOST,
142
+ cwd,
143
+ });
144
+ suppressedOutput(EVENT_NAME.SessionStart, context || undefined);
145
+ versionCheckBackground();
146
+ }
147
+
148
+ function cmdStop() {
149
+ const payload = readStdinJson();
150
+ if (runRalphLoop(payload)) {
151
+ playSound('warning');
152
+ desktopNotify('warning');
153
+ return;
154
+ }
155
+
156
+ const settings = getSettings();
157
+ const level = settings.notify_level ?? 0;
158
+ if (level === 2 || level === 3) playSound('complete');
159
+ if (level === 1 || level === 3) desktopNotify('complete');
160
+ emptySuppress();
161
+ }
162
+
163
+ function cmdSound() {
164
+ playSound(process.argv[3] || 'complete');
165
+ }
166
+
167
+ function cmdDesktop() {
168
+ desktopNotify(process.argv[3] || 'complete');
169
+ }
170
+
171
+ function cmdCodexNotify() {
172
+ let data = {};
173
+ try { data = JSON.parse(process.argv[3] || '{}'); } catch {}
174
+
175
+ const type = data.type || '';
176
+ const client = data.client || '';
177
+ if (shouldIgnoreCodexNotifyClient(client)) return;
178
+
179
+ if (type === 'approval-requested') {
180
+ playSound('confirm');
181
+ desktopNotify('confirm');
182
+ return;
183
+ }
184
+ if (type !== 'agent-turn-complete') return;
185
+
186
+ const lastMsg = data['last-assistant-message'] || '';
187
+ const settings = getSettings();
188
+ if (shouldIgnoreFormattedSubagent(lastMsg, settings.output_format !== false)) return;
189
+
190
+ const cwd = data.cwd || process.cwd();
191
+ if (claimsTaskComplete(lastMsg) && runRalphLoop({ cwd })) {
192
+ playSound('warning');
193
+ desktopNotify('warning');
194
+ return;
195
+ }
196
+
197
+ const level = settings.notify_level ?? 0;
198
+ if (level === 2 || level === 3) playSound('complete');
199
+ if (level === 1 || level === 3) desktopNotify('complete');
200
+ versionCheckBackground();
201
+ }
202
+
203
+ function cmdVersionCheck() {}
204
+
205
+ const cmd = process.argv[2] || '';
206
+
207
+ switch (cmd) {
208
+ case 'inject': cmdInject(); break;
209
+ case 'stop': cmdStop(); break;
210
+ case 'pre-compact': cmdPreCompact(); break;
211
+ case 'route': cmdRoute(); break;
212
+ case 'sound': cmdSound(); break;
213
+ case 'desktop': cmdDesktop(); break;
214
+ case 'codex-notify': cmdCodexNotify(); break;
215
+ case 'version-check': cmdVersionCheck(); break;
216
+ default:
217
+ process.stderr.write(`notify.mjs: unknown command "${cmd}"\n`);
218
+ process.exit(1);
219
+ }