@micsushi/agent-hotline 1.1.0 → 1.1.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Agent Hotline is a local Windows tray app that reads useful parts of Codex, Claude Code, and Antigravity replies aloud.
4
4
 
5
- You keep using your coding tool like normal. Agent Hotline listens for finished responses through local hooks, skips code-heavy bits, and reads the useful prose through the desktop WebView. The full reply stays in the original chat.
5
+ You keep using your coding tool like normal. Agent Hotline listens for finished responses through local hooks, skips code-heavy bits, and reads the useful prose through the browser or desktop UI. The full reply stays in the original chat.
6
6
 
7
7
  ## What It Does
8
8
 
@@ -15,40 +15,57 @@ You keep using your coding tool like normal. Agent Hotline listens for finished
15
15
 
16
16
  ## Status
17
17
 
18
- Good enough for local development use.
18
+ Usable from npm or the Windows desktop installer.
19
19
 
20
20
  Still missing:
21
21
 
22
- - A normal packaged installer.
23
- - A bundled backend sidecar for production installs.
24
22
  - Voice input owned by Agent Hotline.
25
23
 
26
24
  ## Requirements
27
25
 
28
26
  - Windows 10 or 11.
29
- - Node.js 22 or newer.
30
- - For terminal install only: no repo clone is needed.
27
+ - Node.js 22 or newer for the npm/npx commands.
28
+ - No repo clone is needed for normal install.
31
29
  - For desktop app development: Rust plus the usual Tauri Windows prerequisites.
32
30
 
33
31
  ## Install
34
32
 
33
+ ### Recommended: desktop app plus hook setup
34
+
35
+ 1. Download and run the latest Windows installer:
36
+
37
+ ```text
38
+ https://github.com/Micsushi/agent-hotline/releases/latest
39
+ ```
40
+
41
+ 2. Install the hooks and spoken instructions:
42
+
43
+ ```powershell
44
+ npx --yes @micsushi/agent-hotline install --harness all --skill all
45
+ ```
46
+
47
+ 3. Restart Codex, Claude Code, or Antigravity so it reloads its hook files.
48
+
49
+ After that, open Agent Hotline from the Start Menu. The desktop app starts its own bundled backend and opens the panel. No separate `ah run` command is needed for the desktop installer.
50
+
35
51
  ### Terminal install from npm
36
52
 
37
- Use this on a normal machine that should not clone this repo:
53
+ Use this when you want the browser control panel instead of the native tray app:
38
54
 
39
55
  ```powershell
40
56
  npx --yes @micsushi/agent-hotline install --harness all --skill all
57
+ npx --yes @micsushi/agent-hotline run
41
58
  ```
42
59
 
43
- That one command downloads the Agent Hotline npm package and installs both parts:
60
+ The install command downloads the Agent Hotline npm package and installs both parts:
44
61
 
45
62
  - the hook/tool command used by Codex, Claude Code, and Antigravity
46
63
  - the spoken-output skill or managed instructions
47
64
 
48
- Start the local backend:
65
+ The run command starts the backend and opens the browser control panel:
49
66
 
50
- ```powershell
51
- npx --yes @micsushi/agent-hotline run
67
+ ```text
68
+ http://127.0.0.1:4777
52
69
  ```
53
70
 
54
71
  For one repo only:
@@ -65,12 +82,20 @@ agent-hotline install --harness all --skill all
65
82
  agent-hotline run
66
83
  ```
67
84
 
85
+ If `ah` or `agent-hotline` is not found after a global install, run:
86
+
87
+ ```powershell
88
+ npx --yes @micsushi/agent-hotline doctor
89
+ npx --yes @micsushi/agent-hotline doctor --fix-path
90
+ ```
91
+
68
92
  Useful separate commands:
69
93
 
70
94
  ```powershell
71
95
  npx --yes @micsushi/agent-hotline install-hooks --harness all
72
96
  npx --yes @micsushi/agent-hotline install-skill --target all
73
97
  npx --yes @micsushi/agent-hotline hook
98
+ npx --yes @micsushi/agent-hotline run --no-open
74
99
  ```
75
100
 
76
101
  ### Local checkout install
@@ -90,9 +115,9 @@ npm run install:tts
90
115
 
91
116
  ## What Gets Installed
92
117
 
93
- The npm package includes the CLI/backend hook tool and the spoken skill/instructions. Users do not download those separately.
118
+ The npm package includes the CLI/backend hook tool, the browser control panel, and the spoken skill/instructions. Users do not download those separately.
94
119
 
95
- The polished desktop installer is not finished yet. Until then, the desktop control panel is run from a local checkout.
120
+ The GitHub release includes the Windows desktop installer for the native tray/WebView app and a bundled backend. The installer does not yet write Codex, Claude Code, or Antigravity hook files, so run the npm setup command once after installing the desktop app.
96
121
 
97
122
  ## Run Locally
98
123
 
@@ -108,6 +133,18 @@ From `npx`, start the backend:
108
133
  npx --yes @micsushi/agent-hotline run
109
134
  ```
110
135
 
136
+ This also opens the browser control panel:
137
+
138
+ ```text
139
+ http://127.0.0.1:4777
140
+ ```
141
+
142
+ To start only the backend:
143
+
144
+ ```powershell
145
+ npx --yes @micsushi/agent-hotline run --no-open
146
+ ```
147
+
111
148
  From a local checkout, run it in the foreground while developing:
112
149
 
113
150
  ```powershell
@@ -142,6 +179,47 @@ From a local checkout, test the local hook:
142
179
 
143
180
  The sentence should show up in the Agent Hotline queue. If the backend is not running, the hook exits quietly.
144
181
 
182
+ ## Troubleshooting
183
+
184
+ ### The desktop app says Backend unavailable
185
+
186
+ Install the latest GitHub release. The current desktop app starts its bundled backend automatically. If you are using the npm/browser version instead, run:
187
+
188
+ ```powershell
189
+ npx --yes @micsushi/agent-hotline run
190
+ ```
191
+
192
+ ### I ran ah run but no tray icon appeared
193
+
194
+ `ah run` starts the npm/browser version. It opens `http://127.0.0.1:4777` and does not start the native tray app. For the tray app, install and launch the GitHub `.exe`.
195
+
196
+ ### ah is not recognized
197
+
198
+ Use `npx` directly, or install globally:
199
+
200
+ ```powershell
201
+ npm install -g @micsushi/agent-hotline
202
+ npx --yes @micsushi/agent-hotline doctor
203
+ ```
204
+
205
+ If npm's global command folder is missing from PATH on Windows:
206
+
207
+ ```powershell
208
+ npx --yes @micsushi/agent-hotline doctor --fix-path
209
+ ```
210
+
211
+ Restart the terminal after changing PATH.
212
+
213
+ ### Agent replies are not spoken
214
+
215
+ Make sure the hook and spoken instructions are installed, then restart the coding tool:
216
+
217
+ ```powershell
218
+ npx --yes @micsushi/agent-hotline install --harness all --skill all
219
+ ```
220
+
221
+ Then say `hotline on` or `read aloud on` in Codex, Claude Code, or Antigravity.
222
+
145
223
  ## Checks
146
224
 
147
225
  ```powershell
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micsushi/agent-hotline",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Local read-aloud hooks and tray app for AI coding agents.",
5
5
  "bin": {
6
6
  "ah": "packages/backend/bin/agent-hotline.js",
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { main: hookMain } = require("../src/hook-command");
4
+ const { createDoctorReport, fixWindowsUserPath, formatDoctorReport } = require("../src/doctor");
4
5
  const { installHooks, installSkills, npxHookCommand, parseArgs } = require("../src/installer");
5
- const { launchBackend } = require("../src/run-command");
6
+ const { launchBackend, openUrl } = require("../src/run-command");
6
7
 
7
8
  function printHelp() {
8
9
  console.log(`Agent Hotline
@@ -10,6 +11,8 @@ function printHelp() {
10
11
  Usage:
11
12
  agent-hotline run
12
13
  agent-hotline hook
14
+ agent-hotline doctor
15
+ agent-hotline doctor --fix-path
13
16
  agent-hotline install --harness codex --skill codex
14
17
  agent-hotline install-hooks --harness all
15
18
  agent-hotline install-skill --target all
@@ -24,6 +27,8 @@ Options:
24
27
  --home <path>
25
28
  --hook-command <command>
26
29
  --port <port> Backend port for "run" (default: 4777)
30
+ --no-open Start only the backend; do not open the browser panel
31
+ --fix-path With "doctor", add npm global bin to the Windows user PATH
27
32
  --use-npx-hook Write hooks that call "npx --yes @micsushi/agent-hotline hook"
28
33
  `);
29
34
  }
@@ -65,12 +70,42 @@ async function main(argv = process.argv.slice(2), options = {}) {
65
70
  if (command === "run" || command === "start") {
66
71
  const args = parseArgs([subcommand, ...rest].filter(Boolean));
67
72
  const launcher = options.launchBackend || launchBackend;
73
+ const opener = options.openUrl || openUrl;
68
74
  const result = launcher({ port: args.port });
69
75
  console.log(`Agent Hotline backend started in the background on ${result.url}.`);
70
76
  if (result.pid) {
71
77
  console.log(`Process id: ${result.pid}`);
72
78
  }
73
- console.log("You can close this terminal; the backend will keep running.");
79
+ if (!args["no-open"]) {
80
+ opener(result.url);
81
+ console.log(`Opening the Agent Hotline browser panel: ${result.url}`);
82
+ } else {
83
+ console.log(`Open the Agent Hotline browser panel: ${result.url}`);
84
+ }
85
+ console.log("You can close this terminal; Agent Hotline will keep running.");
86
+ return 0;
87
+ }
88
+
89
+ if (command === "doctor") {
90
+ const args = parseArgs([subcommand, ...rest].filter(Boolean));
91
+ const report = createDoctorReport();
92
+ console.log(formatDoctorReport(report));
93
+ if (args["fix-path"]) {
94
+ if (report.platform !== "win32") {
95
+ console.log("--fix-path is only supported on Windows.");
96
+ } else if (!report.binDir) {
97
+ console.log("Could not find npm global bin directory.");
98
+ } else if (report.onPath) {
99
+ console.log("npm global bin is already on PATH.");
100
+ } else {
101
+ const result = fixWindowsUserPath(report.binDir);
102
+ console.log(
103
+ result.ok
104
+ ? "Added npm global bin to your user PATH. Restart your terminal."
105
+ : `Could not update PATH: ${result.error || "unknown error"}`
106
+ );
107
+ }
108
+ }
74
109
  return 0;
75
110
  }
76
111
 
@@ -13,7 +13,7 @@
13
13
  "scripts": {
14
14
  "start": "node src/server.js",
15
15
  "test": "node --test test/*.test.js test/*.test.mjs",
16
- "check": "node --check src/server.js && node --check src/settings-store.js && node --check src/speech-queue-store.js && node --check src/audio-cache-store.js && node --check src/installer.js && node --check src/hook-input-parser.js && node --check src/hook-command.js && node --check src/run-command.js && node --check src/speakable-filter.js && node --check src/mcp-server.mjs && node --check bin/agent-hotline.js && node --check bin/agent-hotline-hook.js && node --check bin/agent-hotline-mcp.js && node --check test/settings-store.test.js && node --check test/speech-queue-store.test.js && node --check test/audio-cache-store.test.js && node --check test/audio-endpoints.test.js && node --check test/installer.test.js && node --check test/run-command.test.js && node --check test/speakable-filter.test.js && node --check test/hook-input-parser.test.js && node --check test/hook-command.test.js && node --check test/server-endpoints.test.js && node --check test/mcp-server.test.mjs"
16
+ "check": "node --check src/server.js && node --check src/settings-store.js && node --check src/speech-queue-store.js && node --check src/audio-cache-store.js && node --check src/doctor.js && node --check src/installer.js && node --check src/hook-input-parser.js && node --check src/hook-command.js && node --check src/run-command.js && node --check src/speakable-filter.js && node --check src/mcp-server.mjs && node --check bin/agent-hotline.js && node --check bin/agent-hotline-hook.js && node --check bin/agent-hotline-mcp.js && node --check test/settings-store.test.js && node --check test/speech-queue-store.test.js && node --check test/audio-cache-store.test.js && node --check test/audio-endpoints.test.js && node --check test/doctor.test.js && node --check test/installer.test.js && node --check test/run-command.test.js && node --check test/speakable-filter.test.js && node --check test/hook-input-parser.test.js && node --check test/hook-command.test.js && node --check test/server-endpoints.test.js && node --check test/mcp-server.test.mjs"
17
17
  },
18
18
  "engines": {
19
19
  "node": ">=18"
@@ -0,0 +1,129 @@
1
+ const os = require("os");
2
+ const path = require("path");
3
+ const { spawnSync } = require("child_process");
4
+
5
+ function globalBinFromPrefix(prefix, platform = process.platform) {
6
+ const trimmed = String(prefix || "").trim();
7
+ if (!trimmed) return "";
8
+ return platform === "win32" ? trimmed : path.join(trimmed, "bin");
9
+ }
10
+
11
+ function npmGlobalPrefix(options = {}) {
12
+ const spawnSyncImpl = options.spawnSync || spawnSync;
13
+ const platform = options.platform || process.platform;
14
+ const result = spawnSyncImpl("npm", ["config", "get", "prefix"], {
15
+ encoding: "utf8",
16
+ windowsHide: true
17
+ });
18
+ if (result.error || result.status !== 0) {
19
+ if (platform === "win32" && process.env.APPDATA) {
20
+ return path.join(process.env.APPDATA, "npm");
21
+ }
22
+ return "";
23
+ }
24
+ return String(result.stdout || "").trim();
25
+ }
26
+
27
+ function pathEntries(value = process.env.PATH || "") {
28
+ return String(value)
29
+ .split(path.delimiter)
30
+ .map((entry) => entry.trim())
31
+ .filter(Boolean);
32
+ }
33
+
34
+ function pathContains(dir, value = process.env.PATH || "", platform = process.platform) {
35
+ const normalize =
36
+ platform === "win32" ? (entry) => entry.toLowerCase().replace(/[\\/]+$/, "") : (entry) => entry;
37
+ const target = normalize(dir);
38
+ return pathEntries(value).some((entry) => normalize(entry) === target);
39
+ }
40
+
41
+ function windowsPathFixCommand(binDir) {
42
+ const escaped = String(binDir).replace(/'/g, "''");
43
+ return [
44
+ "$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')",
45
+ `$bin = '${escaped}'`,
46
+ "if ($null -eq $userPath) { $userPath = '' }",
47
+ "if (($userPath -split ';') -notcontains $bin) {",
48
+ " $nextPath = if ([string]::IsNullOrWhiteSpace($userPath)) { $bin } else { $userPath.TrimEnd(';') + ';' + $bin }",
49
+ " [Environment]::SetEnvironmentVariable('Path', $nextPath, 'User')",
50
+ "}",
51
+ "Write-Host 'Restart your terminal after updating PATH.'"
52
+ ].join("; ");
53
+ }
54
+
55
+ function fixWindowsUserPath(binDir, options = {}) {
56
+ const spawnSyncImpl = options.spawnSync || spawnSync;
57
+ const result = spawnSyncImpl(
58
+ "powershell.exe",
59
+ ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", windowsPathFixCommand(binDir)],
60
+ {
61
+ encoding: "utf8",
62
+ windowsHide: true
63
+ }
64
+ );
65
+ return {
66
+ ok: !result.error && result.status === 0,
67
+ error: result.error ? result.error.message : String(result.stderr || "").trim()
68
+ };
69
+ }
70
+
71
+ function createDoctorReport(options = {}) {
72
+ const platform = options.platform || process.platform;
73
+ const prefix = options.prefix || npmGlobalPrefix(options);
74
+ const binDir = globalBinFromPrefix(prefix, platform);
75
+ const onPath = binDir
76
+ ? pathContains(binDir, options.pathValue || process.env.PATH || "", platform)
77
+ : false;
78
+ const hasGlobalInstall = !!options.hasGlobalInstall;
79
+
80
+ return {
81
+ platform,
82
+ node: process.version,
83
+ prefix,
84
+ binDir,
85
+ onPath,
86
+ hasGlobalInstall,
87
+ pathFixCommand: platform === "win32" && binDir ? windowsPathFixCommand(binDir) : ""
88
+ };
89
+ }
90
+
91
+ function formatDoctorReport(report) {
92
+ const lines = [
93
+ "Agent Hotline doctor",
94
+ `Node: ${report.node}`,
95
+ `npm global bin: ${report.binDir || "not found"}`,
96
+ `npm global bin on PATH: ${report.onPath ? "yes" : "no"}`,
97
+ ""
98
+ ];
99
+
100
+ if (!report.onPath && report.platform === "win32" && report.pathFixCommand) {
101
+ lines.push("To add npm global commands like ah to PATH:");
102
+ lines.push(
103
+ `powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "${report.pathFixCommand.replace(/"/g, '\\"')}"`
104
+ );
105
+ lines.push("");
106
+ }
107
+
108
+ lines.push("Recommended first run:");
109
+ lines.push(" npx --yes @micsushi/agent-hotline install --harness all --skill all");
110
+ lines.push(" npx --yes @micsushi/agent-hotline run");
111
+
112
+ if (report.platform === "win32") {
113
+ lines.push("");
114
+ lines.push("Windows desktop installer:");
115
+ lines.push(" https://github.com/Micsushi/agent-hotline/releases/latest");
116
+ }
117
+
118
+ return lines.join(os.EOL);
119
+ }
120
+
121
+ module.exports = {
122
+ createDoctorReport,
123
+ fixWindowsUserPath,
124
+ formatDoctorReport,
125
+ globalBinFromPrefix,
126
+ npmGlobalPrefix,
127
+ pathContains,
128
+ windowsPathFixCommand
129
+ };
@@ -34,7 +34,38 @@ function launchBackend(options = {}) {
34
34
  };
35
35
  }
36
36
 
37
+ function openUrl(url, options = {}) {
38
+ const spawnImpl = options.spawn || spawn;
39
+ const platform = options.platform || process.platform;
40
+ let command;
41
+ let args;
42
+
43
+ if (platform === "win32") {
44
+ command = "cmd";
45
+ args = ["/c", "start", "", url];
46
+ } else if (platform === "darwin") {
47
+ command = "open";
48
+ args = [url];
49
+ } else {
50
+ command = "xdg-open";
51
+ args = [url];
52
+ }
53
+
54
+ const child = spawnImpl(command, args, {
55
+ detached: true,
56
+ stdio: "ignore",
57
+ windowsHide: true
58
+ });
59
+
60
+ if (typeof child.unref === "function") {
61
+ child.unref();
62
+ }
63
+
64
+ return { command, args };
65
+ }
66
+
37
67
  module.exports = {
38
68
  backendServerPath,
39
- launchBackend
69
+ launchBackend,
70
+ openUrl
40
71
  };
@@ -8,14 +8,13 @@ const {
8
8
  TTS_ENGINES,
9
9
  createSettingsStore
10
10
  } = require("./settings-store");
11
- const { createSpeechQueueStore } = require("./speech-queue-store");
11
+ const { createSpeechQueueStore, defaultDataDir } = require("./speech-queue-store");
12
12
  const { createSpoolStore } = require("./spool-store");
13
13
  const { createAudioCacheStore } = require("./audio-cache-store");
14
14
 
15
15
  const PORT = Number(process.env.AGENT_HOTLINE_PORT || process.env.VOICE_QUESTION_LOOP_PORT || 4777);
16
16
  const HOST = "127.0.0.1";
17
- const ROOT = path.resolve(__dirname, "..");
18
- const DATA_DIR = process.env.AGENT_HOTLINE_DATA_DIR || path.join(ROOT, "data");
17
+ const DATA_DIR = process.env.AGENT_HOTLINE_DATA_DIR || defaultDataDir();
19
18
  const QUESTIONS_FILE = process.env.QUESTION_FILE || path.join(DATA_DIR, "questions.json");
20
19
  const REQUEST_LIMIT_BYTES = 1_000_000;
21
20
  const AUDIO_BODY_LIMIT_BYTES = 96 * 1024 * 1024;
@@ -1029,6 +1028,7 @@ if (require.main === module) {
1029
1028
  }
1030
1029
 
1031
1030
  module.exports = {
1031
+ DATA_DIR,
1032
1032
  HOST,
1033
1033
  PORT,
1034
1034
  createServer,