@fangyb/ahchat-bridge 0.1.2 → 0.1.3

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.
@@ -1,8 +1,15 @@
1
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ var getFilename = () => fileURLToPath(import.meta.url);
5
+ var getDirname = () => path.dirname(getFilename());
6
+ var __dirname = /* @__PURE__ */ getDirname();
7
+
1
8
  // src/config.ts
2
9
  import crypto from "crypto";
3
10
  import fs from "fs";
4
11
  import os from "os";
5
- import path from "path";
12
+ import path2 from "path";
6
13
  var DEFAULT_QUERY_CONFIG = {
7
14
  maxActive: 50,
8
15
  idleTimeoutMs: 6e4,
@@ -56,9 +63,9 @@ function mergeQueryConfig(file) {
56
63
  function loadBridgeConfig() {
57
64
  const dataDir = readEnvString(
58
65
  "AHCHAT_DATA_DIR",
59
- path.join(os.homedir(), ".ahchat")
66
+ path2.join(os.homedir(), ".ahchat")
60
67
  );
61
- const fileConfig = tryReadJsonConfig(path.join(dataDir, "bridge.json"));
68
+ const fileConfig = tryReadJsonConfig(path2.join(dataDir, "bridge.json"));
62
69
  return {
63
70
  serverUrl: readEnvString(
64
71
  "AHCHAT_BRIDGE_SERVER_URL",
@@ -75,7 +82,7 @@ function loadBridgeConfig() {
75
82
  dataDir,
76
83
  dbPath: readEnvString(
77
84
  "AHCHAT_DB_PATH",
78
- fileConfig.dbPath ?? path.join(dataDir, "data.db")
85
+ fileConfig.dbPath ?? path2.join(dataDir, "data.db")
79
86
  ),
80
87
  serverApiUrl: readEnvString(
81
88
  "AHCHAT_SERVER_API_URL",
@@ -90,7 +97,7 @@ function ensureDir(dirPath) {
90
97
 
91
98
  // src/logger.ts
92
99
  import os3 from "os";
93
- import path3 from "path";
100
+ import path4 from "path";
94
101
 
95
102
  // ../logger/src/types.ts
96
103
  var LOG_LEVEL_VALUE = {
@@ -710,7 +717,7 @@ function consoleTransport(opts) {
710
717
  }
711
718
 
712
719
  // ../logger/src/transports/file.ts
713
- import path2 from "path";
720
+ import path3 from "path";
714
721
 
715
722
  // ../../node_modules/.pnpm/rotating-file-stream@3.2.9/node_modules/rotating-file-stream/dist/esm/index.js
716
723
  import { exec } from "child_process";
@@ -755,11 +762,11 @@ var RotatingFileStream = class extends Writable {
755
762
  timeout;
756
763
  timeoutPromise;
757
764
  constructor(generator, options) {
758
- const { encoding, history, maxFiles, maxSize, path: path10 } = options;
765
+ const { encoding, history, maxFiles, maxSize, path: path11 } = options;
759
766
  super({ decodeStrings: true, defaultEncoding: encoding });
760
767
  this.createGzip = createGzip;
761
768
  this.exec = exec;
762
- this.filename = path10 + generator(null);
769
+ this.filename = path11 + generator(null);
763
770
  this.fsCreateReadStream = createReadStream;
764
771
  this.fsCreateWriteStream = createWriteStream;
765
772
  this.fsOpen = open;
@@ -771,7 +778,7 @@ var RotatingFileStream = class extends Writable {
771
778
  this.options = options;
772
779
  this.stdout = process.stdout;
773
780
  if (maxFiles || maxSize)
774
- options.history = path10 + (history ? history : this.generator(null) + ".txt");
781
+ options.history = path11 + (history ? history : this.generator(null) + ".txt");
775
782
  this.on("close", () => this.finished ? null : this.emit("finish"));
776
783
  this.on("finish", () => this.finished = this.clear());
777
784
  (async () => {
@@ -899,9 +906,9 @@ var RotatingFileStream = class extends Writable {
899
906
  return this.move();
900
907
  }
901
908
  async findName() {
902
- const { interval, path: path10, intervalBoundary } = this.options;
909
+ const { interval, path: path11, intervalBoundary } = this.options;
903
910
  for (let index = 1; index < 1e3; ++index) {
904
- const filename = path10 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
911
+ const filename = path11 + this.generator(interval && intervalBoundary ? new Date(this.prev) : this.rotation, index);
905
912
  if (!await exists(filename))
906
913
  return filename;
907
914
  }
@@ -931,11 +938,11 @@ var RotatingFileStream = class extends Writable {
931
938
  return this.unlink(filename);
932
939
  }
933
940
  async classical() {
934
- const { compress, path: path10, rotate } = this.options;
941
+ const { compress, path: path11, rotate } = this.options;
935
942
  let rotatedName = "";
936
943
  for (let count = rotate; count > 0; --count) {
937
- const currName = path10 + this.generator(count);
938
- const prevName = count === 1 ? this.filename : path10 + this.generator(count - 1);
944
+ const currName = path11 + this.generator(count);
945
+ const prevName = count === 1 ? this.filename : path11 + this.generator(count - 1);
939
946
  if (!await exists(prevName))
940
947
  continue;
941
948
  if (!rotatedName)
@@ -1328,8 +1335,8 @@ function parseSize(maxSize) {
1328
1335
  }
1329
1336
  function fileTransport(opts) {
1330
1337
  const fmt = opts.formatter ?? jsonFormatter;
1331
- const dir = path2.dirname(opts.path);
1332
- const filename = path2.basename(opts.path);
1338
+ const dir = path3.dirname(opts.path);
1339
+ const filename = path3.basename(opts.path);
1333
1340
  const stream = createStream(filename, {
1334
1341
  path: dir,
1335
1342
  size: opts.rotate?.maxSize ? parseSize(opts.rotate.maxSize) : "50M",
@@ -1349,8 +1356,8 @@ function createLogger(config) {
1349
1356
  // src/logger.ts
1350
1357
  var bridgeConfig = loadBridgeConfig();
1351
1358
  var isTest = !!process.env["VITEST"];
1352
- var LOG_DIR = path3.join(os3.homedir(), ".ahchat", "logs");
1353
- var LOG_FILE = path3.join(LOG_DIR, "bridge.log");
1359
+ var LOG_DIR = path4.join(os3.homedir(), ".ahchat", "logs");
1360
+ var LOG_FILE = path4.join(LOG_DIR, "bridge.log");
1354
1361
  if (!isTest) ensureDir(LOG_DIR);
1355
1362
  function createModuleLogger(module) {
1356
1363
  const transports = [consoleTransport({ formatter: prettyFormatter })];
@@ -1748,7 +1755,7 @@ var wsMetrics = new WsMetrics();
1748
1755
  // src/agentManager.ts
1749
1756
  import fs2 from "fs/promises";
1750
1757
  import os4 from "os";
1751
- import path6 from "path";
1758
+ import path7 from "path";
1752
1759
 
1753
1760
  // src/inputController.ts
1754
1761
  var InputController = class {
@@ -1806,17 +1813,17 @@ var InputController = class {
1806
1813
  };
1807
1814
 
1808
1815
  // src/permissionGuard.ts
1809
- import path5 from "path";
1816
+ import path6 from "path";
1810
1817
 
1811
1818
  // ../shared/src/utils/pathSafety.ts
1812
- import path4 from "path";
1819
+ import path5 from "path";
1813
1820
  function isPathInside(parent, child) {
1814
- const resolvedParent = path4.resolve(parent);
1815
- const resolvedChild = path4.resolve(child);
1821
+ const resolvedParent = path5.resolve(parent);
1822
+ const resolvedChild = path5.resolve(child);
1816
1823
  if (resolvedParent === resolvedChild) return true;
1817
- const rel = path4.relative(resolvedParent, resolvedChild);
1824
+ const rel = path5.relative(resolvedParent, resolvedChild);
1818
1825
  if (rel === "") return true;
1819
- return !rel.startsWith("..") && !path4.isAbsolute(rel);
1826
+ return !rel.startsWith("..") && !path5.isAbsolute(rel);
1820
1827
  }
1821
1828
 
1822
1829
  // src/permissionGuard.ts
@@ -1831,7 +1838,7 @@ function makeCwdPermissionGuard(cwd, agentId, scope, log) {
1831
1838
  if (typeof raw !== "string" || raw.length === 0) {
1832
1839
  return { behavior: "allow" };
1833
1840
  }
1834
- const abs = path5.isAbsolute(raw) ? raw : path5.resolve(cwd, raw);
1841
+ const abs = path6.isAbsolute(raw) ? raw : path6.resolve(cwd, raw);
1835
1842
  if (isPathInside(cwd, abs)) {
1836
1843
  return { behavior: "allow" };
1837
1844
  }
@@ -2881,14 +2888,14 @@ var AgentManager = class {
2881
2888
  this.emit = emit;
2882
2889
  if (typeof options === "function") {
2883
2890
  this.queryFn = options;
2884
- this.workspacesDir = path6.join(os4.homedir(), ".ahchat", "workspaces");
2891
+ this.workspacesDir = path7.join(os4.homedir(), ".ahchat", "workspaces");
2885
2892
  this.queryConfig = DEFAULT_QUERY_CONFIG;
2886
2893
  this.askQuestionRegistry = new AskQuestionRegistry();
2887
2894
  this.neuralBusManager = new NeuralBusManager();
2888
2895
  this.groupRegistry = null;
2889
2896
  } else {
2890
2897
  this.queryFn = options?.queryFn ?? null;
2891
- this.workspacesDir = options?.workspacesDir ?? path6.join(os4.homedir(), ".ahchat", "workspaces");
2898
+ this.workspacesDir = options?.workspacesDir ?? path7.join(os4.homedir(), ".ahchat", "workspaces");
2892
2899
  this.queryConfig = options?.queryConfig ?? DEFAULT_QUERY_CONFIG;
2893
2900
  this.askQuestionRegistry = options?.askQuestionRegistry ?? new AskQuestionRegistry();
2894
2901
  this.neuralBusManager = options?.neuralBusManager ?? new NeuralBusManager();
@@ -3357,7 +3364,7 @@ ${relay.message}`,
3357
3364
  status: existingProc.status
3358
3365
  });
3359
3366
  } else {
3360
- const cwd = agentConfig.workingDirectory || path6.join(this.workspacesDir, agentConfig.id);
3367
+ const cwd = agentConfig.workingDirectory || path7.join(this.workspacesDir, agentConfig.id);
3361
3368
  void this.acquire(agentConfig, scope, cwd).then((proc) => {
3362
3369
  if (suppressEmit) proc.internalRelayId = suppressEmit;
3363
3370
  return this.sendMessage({ ...task, agentId: agentConfig.id, scope });
@@ -3492,7 +3499,7 @@ ${relay.message}`,
3492
3499
  break;
3493
3500
  }
3494
3501
  try {
3495
- const cwd = agent.workingDirectory || path6.join(this.workspacesDir, agent.id);
3502
+ const cwd = agent.workingDirectory || path7.join(this.workspacesDir, agent.id);
3496
3503
  await this.acquire(agent, { kind: "single" }, cwd);
3497
3504
  warmed++;
3498
3505
  logger8.info("Agent process pre-created for recovery", { agentId: agent.id });
@@ -3688,8 +3695,8 @@ var HttpAgentRegistry = class {
3688
3695
  agents = /* @__PURE__ */ new Map();
3689
3696
  apiUrl(suffix) {
3690
3697
  const base = this.serverApiUrl.replace(/\/$/, "");
3691
- const path10 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3692
- return `${base}${path10}`;
3698
+ const path11 = suffix.startsWith("/") ? suffix : `/${suffix}`;
3699
+ return `${base}${path11}`;
3693
3700
  }
3694
3701
  async refresh() {
3695
3702
  try {
@@ -4074,11 +4081,11 @@ var ServerConnector = class {
4074
4081
  // src/modelQuerier.ts
4075
4082
  import fs3 from "fs/promises";
4076
4083
  import os5 from "os";
4077
- import path7 from "path";
4084
+ import path8 from "path";
4078
4085
  var logger12 = createModuleLogger("bridge.modelQuerier");
4079
4086
  async function listModels(queryFn, opts = {}) {
4080
4087
  const t0 = Date.now();
4081
- const cwd = opts.cwd ?? path7.join(os5.homedir(), ".ahchat", "workspaces", "_list_models");
4088
+ const cwd = opts.cwd ?? path8.join(os5.homedir(), ".ahchat", "workspaces", "_list_models");
4082
4089
  await fs3.mkdir(cwd, { recursive: true });
4083
4090
  const fn = queryFn ?? (await import("@anthropic-ai/claude-agent-sdk")).query;
4084
4091
  const ic = new InputController();
@@ -4137,7 +4144,7 @@ async function listModels(queryFn, opts = {}) {
4137
4144
 
4138
4145
  // src/lockfile.ts
4139
4146
  import fs4 from "fs";
4140
- import path8 from "path";
4147
+ import path9 from "path";
4141
4148
  var logger13 = createModuleLogger("bridge.lockfile");
4142
4149
  var lockPath = null;
4143
4150
  function isProcessAlive(pid) {
@@ -4151,7 +4158,7 @@ function isProcessAlive(pid) {
4151
4158
  }
4152
4159
  }
4153
4160
  function acquireLock(dataDir) {
4154
- const file = path8.join(dataDir, "bridge.lock");
4161
+ const file = path9.join(dataDir, "bridge.lock");
4155
4162
  lockPath = file;
4156
4163
  if (fs4.existsSync(file)) {
4157
4164
  const raw = fs4.readFileSync(file, "utf-8").trim();
@@ -4163,7 +4170,7 @@ function acquireLock(dataDir) {
4163
4170
  logger13.warn("Removing stale bridge.lock (process not found)", { pid, path: file });
4164
4171
  }
4165
4172
  }
4166
- fs4.mkdirSync(path8.dirname(file), { recursive: true });
4173
+ fs4.mkdirSync(path9.dirname(file), { recursive: true });
4167
4174
  fs4.writeFileSync(file, String(process.pid), "utf-8");
4168
4175
  logger13.info("Acquired bridge lock", { path: file, pid: process.pid });
4169
4176
  const release = () => {
@@ -4431,13 +4438,13 @@ function createGroupTaskDispatchHandler(agentManager, agentRegistry, emit) {
4431
4438
 
4432
4439
  // src/sessionStore.ts
4433
4440
  import fs5 from "fs";
4434
- import path9 from "path";
4441
+ import path10 from "path";
4435
4442
  var logger15 = createModuleLogger("session.store");
4436
4443
  var SessionStore = class {
4437
4444
  filePath;
4438
4445
  cache;
4439
4446
  constructor(dataDir) {
4440
- this.filePath = path9.join(dataDir, "sessions.json");
4447
+ this.filePath = path10.join(dataDir, "sessions.json");
4441
4448
  this.cache = this.loadFromDisk();
4442
4449
  }
4443
4450
  cacheKey(agentId, scope) {
@@ -4497,7 +4504,7 @@ var SessionStore = class {
4497
4504
  }
4498
4505
  saveToDisk() {
4499
4506
  try {
4500
- const dir = path9.dirname(this.filePath);
4507
+ const dir = path10.dirname(this.filePath);
4501
4508
  fs5.mkdirSync(dir, { recursive: true });
4502
4509
  fs5.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
4503
4510
  } catch (e) {
@@ -4507,6 +4514,7 @@ var SessionStore = class {
4507
4514
  };
4508
4515
 
4509
4516
  export {
4517
+ __dirname,
4510
4518
  loadBridgeConfig,
4511
4519
  ensureDir,
4512
4520
  createModuleLogger,
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  HttpAgentRegistry,
6
6
  ServerConnector,
7
7
  SessionStore,
8
+ __dirname,
8
9
  acquireLock,
9
10
  createGroupTaskDispatchHandler,
10
11
  createModuleLogger,
@@ -14,19 +15,199 @@ import {
14
15
  listModels,
15
16
  loadBridgeConfig,
16
17
  wsMetrics
17
- } from "./chunk-7SODRWIG.js";
18
+ } from "./chunk-MO54RNR2.js";
18
19
 
19
20
  // src/cli.ts
20
21
  import cac from "cac";
21
- var logger = createModuleLogger("bridge");
22
+
23
+ // src/protocol.ts
24
+ import { execSync } from "child_process";
25
+ import fs from "fs";
26
+ import os from "os";
27
+ import path from "path";
28
+ var logger = createModuleLogger("bridge.protocol");
29
+ function getBridgeExePath() {
30
+ const pkgDir = path.resolve(__dirname, "..");
31
+ return path.join(pkgDir, "dist", "cli.js");
32
+ }
33
+ function registerProtocolHandler() {
34
+ const platform = os.platform();
35
+ if (platform === "win32") {
36
+ registerWindows();
37
+ } else if (platform === "darwin") {
38
+ registerMacOS();
39
+ } else {
40
+ registerLinux();
41
+ }
42
+ logger.info("Protocol handler registered", { platform });
43
+ }
44
+ function registerWindows() {
45
+ const exe = getBridgeExePath();
46
+ const handler = `"${process.execPath}" "${exe}" launch --url "%1"`;
47
+ const iconPath = process.execPath;
48
+ const regCommands = [
49
+ `REG ADD "HKCU\\Software\\Classes\\ahchat" /ve /d "URL:ahchat" /f`,
50
+ `REG ADD "HKCU\\Software\\Classes\\ahchat" /v "URL Protocol" /d "" /f`,
51
+ `REG ADD "HKCU\\Software\\Classes\\ahchat\\DefaultIcon" /ve /d "${iconPath}" /f`,
52
+ `REG ADD "HKCU\\Software\\Classes\\ahchat\\shell\\open\\command" /ve /d ${handler} /f`
53
+ ];
54
+ for (const cmd of regCommands) {
55
+ try {
56
+ execSync(cmd, { stdio: "pipe" });
57
+ } catch (e) {
58
+ logger.error("Failed to register Windows protocol handler", { error: e, cmd });
59
+ throw new Error(`Failed to register protocol handler: ${cmd}`);
60
+ }
61
+ }
62
+ logger.info("Windows protocol handler registered");
63
+ }
64
+ function registerMacOS() {
65
+ const appDir = path.join(os.homedir(), "Applications", "AHChatBridge.app");
66
+ const contentsDir = path.join(appDir, "Contents");
67
+ const macosDir = path.join(contentsDir, "MacOS");
68
+ const resourcesDir = path.join(contentsDir, "Resources");
69
+ fs.mkdirSync(macosDir, { recursive: true });
70
+ fs.mkdirSync(resourcesDir, { recursive: true });
71
+ const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
72
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
73
+ <plist version="1.0">
74
+ <dict>
75
+ <key>CFBundleName</key>
76
+ <string>AHChatBridge</string>
77
+ <key>CFBundleDisplayName</key>
78
+ <string>AHChat Bridge</string>
79
+ <key>CFBundleIdentifier</key>
80
+ <string>com.fangyb.ahchat-bridge</string>
81
+ <key>CFBundleVersion</key>
82
+ <string>0.1.0</string>
83
+ <key>CFBundlePackageType</key>
84
+ <string>APPL</string>
85
+ <key>CFBundleExecutable</key>
86
+ <string>launch.sh</string>
87
+ <key>CFBundleURLTypes</key>
88
+ <array>
89
+ <dict>
90
+ <key>CFBundleURLName</key>
91
+ <string>AHChat Bridge</string>
92
+ <key>CFBundleURLSchemes</key>
93
+ <array>
94
+ <string>ahchat</string>
95
+ </array>
96
+ </dict>
97
+ </array>
98
+ </dict>
99
+ </plist>`;
100
+ const launchScript = `#!/bin/bash
101
+ URL="$1"
102
+ exec "${process.execPath}" "${getBridgeExePath()}" launch --url "$URL"`;
103
+ fs.writeFileSync(path.join(contentsDir, "Info.plist"), infoPlist);
104
+ fs.writeFileSync(path.join(macosDir, "launch.sh"), launchScript);
105
+ fs.chmodSync(path.join(macosDir, "launch.sh"), 493);
106
+ logger.info("macOS protocol handler registered", { appDir });
107
+ }
108
+ function registerLinux() {
109
+ const desktopFile = `[Desktop Entry]
110
+ Name=AHChat Bridge
111
+ Exec=${process.execPath} ${getBridgeExePath()} launch --url %u
112
+ Type=Application
113
+ NoDisplay=true
114
+ MimeType=x-scheme-handler/ahchat;`;
115
+ const desktopPath = path.join(
116
+ os.homedir(),
117
+ ".local",
118
+ "share",
119
+ "applications",
120
+ "ahchat-bridge.desktop"
121
+ );
122
+ fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
123
+ fs.writeFileSync(desktopPath, desktopFile);
124
+ try {
125
+ execSync("update-desktop-database ~/.local/share/applications/", { stdio: "pipe" });
126
+ } catch {
127
+ }
128
+ logger.info("Linux protocol handler registered", { desktopPath });
129
+ }
130
+ function unregisterProtocolHandler() {
131
+ const platform = os.platform();
132
+ if (platform === "win32") {
133
+ try {
134
+ execSync('REG DELETE "HKCU\\Software\\Classes\\ahchat" /f', { stdio: "pipe" });
135
+ logger.info("Windows protocol handler unregistered");
136
+ } catch (e) {
137
+ logger.warn("Failed to unregister Windows protocol handler", { error: e });
138
+ }
139
+ } else if (platform === "darwin") {
140
+ const appDir = path.join(os.homedir(), "Applications", "AHChatBridge.app");
141
+ try {
142
+ fs.rmSync(appDir, { recursive: true, force: true });
143
+ logger.info("macOS protocol handler unregistered");
144
+ } catch (e) {
145
+ logger.warn("Failed to unregister macOS protocol handler", { error: e });
146
+ }
147
+ } else {
148
+ const desktopPath = path.join(
149
+ os.homedir(),
150
+ ".local",
151
+ "share",
152
+ "applications",
153
+ "ahchat-bridge.desktop"
154
+ );
155
+ try {
156
+ fs.unlinkSync(desktopPath);
157
+ logger.info("Linux protocol handler unregistered");
158
+ } catch (e) {
159
+ logger.warn("Failed to unregister Linux protocol handler", { error: e });
160
+ }
161
+ }
162
+ }
163
+ function isProtocolRegistered() {
164
+ const platform = os.platform();
165
+ if (platform === "win32") {
166
+ try {
167
+ execSync('REG QUERY "HKCU\\Software\\Classes\\ahchat" /ve', { stdio: "pipe" });
168
+ return true;
169
+ } catch {
170
+ return false;
171
+ }
172
+ } else if (platform === "darwin") {
173
+ const appDir = path.join(os.homedir(), "Applications", "AHChatBridge.app");
174
+ return fs.existsSync(path.join(appDir, "Contents", "Info.plist"));
175
+ } else {
176
+ const desktopPath = path.join(
177
+ os.homedir(),
178
+ ".local",
179
+ "share",
180
+ "applications",
181
+ "ahchat-bridge.desktop"
182
+ );
183
+ return fs.existsSync(desktopPath);
184
+ }
185
+ }
186
+
187
+ // src/cli.ts
188
+ var logger2 = createModuleLogger("bridge");
22
189
  var cli = cac("ahchat-bridge");
23
- cli.command("[command]", "Start the bridge (default: run)").option("--server-url <url>", "WebSocket URL of the AHChat server").option("--token <token>", "Auth token for server registration").option("--data-dir <dir>", "Data directory (default: ~/.ahchat)").option("--log-level <level>", "Log level (default: INFO)").action((args) => {
24
- void runBridge({
25
- serverUrl: args["server-url"],
26
- token: args.token,
27
- dataDir: args["data-dir"],
28
- logLevel: args["log-level"]
29
- });
190
+ cli.command("install", "Register ahchat:// protocol handler (one-time setup)").action(() => {
191
+ registerProtocolHandler();
192
+ console.log("ahchat:// protocol handler registered successfully.");
193
+ console.log("You can now launch the bridge from your browser with one click.");
194
+ });
195
+ cli.command("uninstall", "Remove ahchat:// protocol handler").action(() => {
196
+ unregisterProtocolHandler();
197
+ console.log("ahchat:// protocol handler removed.");
198
+ });
199
+ cli.command("status", "Check if protocol handler is registered").action(() => {
200
+ const registered = isProtocolRegistered();
201
+ console.log(registered ? "ahchat:// protocol is registered." : "ahchat:// protocol is NOT registered.");
202
+ console.log('Run "npx @fangyb/ahchat-bridge install" to register it.');
203
+ });
204
+ cli.command("launch", "Launch bridge from ahchat:// URL (called by OS)").option("--url <url>", "ahchat:// URL with server and token params").action((args) => {
205
+ const url = args.url;
206
+ if (!url) {
207
+ console.error("Error: --url is required");
208
+ process.exit(1);
209
+ }
210
+ void runBridgeFromUrl(url);
30
211
  });
31
212
  cli.command("run", "Start the bridge and connect to server").option("--server-url <url>", "WebSocket URL of the AHChat server").option("--token <token>", "Auth token for server registration").option("--data-dir <dir>", "Data directory (default: ~/.ahchat)").option("--log-level <level>", "Log level (default: INFO)").action((args) => {
32
213
  void runBridge({
@@ -42,6 +223,30 @@ cli.command("version", "Show bridge version").action(() => {
42
223
  cli.help();
43
224
  cli.version("0.1.0");
44
225
  cli.parse();
226
+ function parseAhchatUrl(url) {
227
+ try {
228
+ const parsed = new URL(url);
229
+ if (parsed.protocol !== "ahchat:") return null;
230
+ const serverUrl = parsed.searchParams.get("server");
231
+ const token = parsed.searchParams.get("token");
232
+ if (!serverUrl || !token) return null;
233
+ return { serverUrl, token };
234
+ } catch {
235
+ return null;
236
+ }
237
+ }
238
+ async function runBridgeFromUrl(url) {
239
+ const parsed = parseAhchatUrl(url);
240
+ if (!parsed) {
241
+ console.error("Invalid ahchat:// URL:", url);
242
+ console.error("Expected format: ahchat://bridge?server=ws://host:port/ws/bridge&token=xxx");
243
+ process.exit(1);
244
+ }
245
+ await runBridge({
246
+ serverUrl: parsed.serverUrl,
247
+ token: parsed.token
248
+ });
249
+ }
45
250
  async function runBridge(args) {
46
251
  let config = loadBridgeConfig();
47
252
  if (args.serverUrl) {
@@ -60,7 +265,7 @@ async function runBridge(args) {
60
265
  }
61
266
  ensureDir(config.dataDir);
62
267
  acquireLock(config.dataDir);
63
- logger.info("Bridge starting", {
268
+ logger2.info("Bridge starting", {
64
269
  bridgeId: config.bridgeId,
65
270
  serverUrl: config.serverUrl,
66
271
  serverApiUrl: config.serverApiUrl
@@ -101,21 +306,21 @@ async function runBridge(args) {
101
306
  switch (msg.type) {
102
307
  case "bridge:list_models_request": {
103
308
  const { requestId } = msg.payload;
104
- logger.info("list_models request received", { requestId });
309
+ logger2.info("list_models request received", { requestId });
105
310
  try {
106
311
  const models = await listModels();
107
312
  connector?.send({
108
313
  type: "bridge:list_models_response",
109
314
  payload: { requestId, models }
110
315
  });
111
- logger.info("list_models response sent", { requestId, count: models.length });
316
+ logger2.info("list_models response sent", { requestId, count: models.length });
112
317
  } catch (e) {
113
318
  const err = e instanceof Error ? e.message : String(e);
114
319
  connector?.send({
115
320
  type: "bridge:list_models_response",
116
321
  payload: { requestId, error: err }
117
322
  });
118
- logger.error("list_models failed", { requestId, error: e });
323
+ logger2.error("list_models failed", { requestId, error: e });
119
324
  }
120
325
  break;
121
326
  }
@@ -123,7 +328,7 @@ async function runBridge(args) {
123
328
  await agentManager.terminate(msg.payload.agentId);
124
329
  break;
125
330
  case "agent:terminate_scope":
126
- logger.info("agent:terminate_scope received", {
331
+ logger2.info("agent:terminate_scope received", {
127
332
  agentId: msg.payload.agentId,
128
333
  scope: msg.payload.scope
129
334
  });
@@ -140,7 +345,7 @@ async function runBridge(args) {
140
345
  const p = msg.payload;
141
346
  const answerText = formatAnswerForSDK(p);
142
347
  const ok = askQuestionRegistry.resolve(p.questionId, answerText);
143
- logger.info("user:answer_question handled", {
348
+ logger2.info("user:answer_question handled", {
144
349
  questionId: p.questionId,
145
350
  agentId: p.agentId,
146
351
  resolved: ok,
@@ -161,7 +366,7 @@ async function runBridge(args) {
161
366
  });
162
367
  }, config.queryConfig.statusReportIntervalMs);
163
368
  const shutdown = async (signal) => {
164
- logger.info("Shutdown signal received", { signal });
369
+ logger2.info("Shutdown signal received", { signal });
165
370
  if (statusInterval) {
166
371
  clearInterval(statusInterval);
167
372
  statusInterval = null;
@@ -169,7 +374,7 @@ async function runBridge(args) {
169
374
  wsMetrics.stop();
170
375
  connector?.close();
171
376
  await agentManager.shutdownAll();
172
- logger.info("Bridge stopped");
377
+ logger2.info("Bridge stopped");
173
378
  process.exit(0);
174
379
  };
175
380
  process.on("SIGINT", () => void shutdown("SIGINT"));
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  listModels,
15
15
  loadBridgeConfig,
16
16
  wsMetrics
17
- } from "./chunk-7SODRWIG.js";
17
+ } from "./chunk-MO54RNR2.js";
18
18
 
19
19
  // src/index.ts
20
20
  var logger = createModuleLogger("bridge");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fangyb/ahchat-bridge",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "AHChat Bridge CLI — connect your local Claude Code agents to an AHChat server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",