@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.
- package/dist/{chunk-7SODRWIG.js → chunk-MO54RNR2.js} +48 -40
- package/dist/cli.js +222 -17
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
66
|
+
path2.join(os.homedir(), ".ahchat")
|
|
60
67
|
);
|
|
61
|
-
const fileConfig = tryReadJsonConfig(
|
|
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 ??
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
909
|
+
const { interval, path: path11, intervalBoundary } = this.options;
|
|
903
910
|
for (let index = 1; index < 1e3; ++index) {
|
|
904
|
-
const filename =
|
|
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:
|
|
941
|
+
const { compress, path: path11, rotate } = this.options;
|
|
935
942
|
let rotatedName = "";
|
|
936
943
|
for (let count = rotate; count > 0; --count) {
|
|
937
|
-
const currName =
|
|
938
|
-
const prevName = count === 1 ? this.filename :
|
|
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 =
|
|
1332
|
-
const filename =
|
|
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 =
|
|
1353
|
-
var LOG_FILE =
|
|
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
|
|
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
|
|
1816
|
+
import path6 from "path";
|
|
1810
1817
|
|
|
1811
1818
|
// ../shared/src/utils/pathSafety.ts
|
|
1812
|
-
import
|
|
1819
|
+
import path5 from "path";
|
|
1813
1820
|
function isPathInside(parent, child) {
|
|
1814
|
-
const resolvedParent =
|
|
1815
|
-
const resolvedChild =
|
|
1821
|
+
const resolvedParent = path5.resolve(parent);
|
|
1822
|
+
const resolvedChild = path5.resolve(child);
|
|
1816
1823
|
if (resolvedParent === resolvedChild) return true;
|
|
1817
|
-
const rel =
|
|
1824
|
+
const rel = path5.relative(resolvedParent, resolvedChild);
|
|
1818
1825
|
if (rel === "") return true;
|
|
1819
|
-
return !rel.startsWith("..") && !
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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 ||
|
|
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 ||
|
|
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
|
|
3692
|
-
return `${base}${
|
|
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
|
|
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 ??
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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-
|
|
18
|
+
} from "./chunk-MO54RNR2.js";
|
|
18
19
|
|
|
19
20
|
// src/cli.ts
|
|
20
21
|
import cac from "cac";
|
|
21
|
-
|
|
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("
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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