@hasna/bridge 0.1.0 → 0.1.2
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/LICENSE +168 -1
- package/README.md +85 -3
- package/dist/cli/index.js +865 -95
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +787 -73
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/daemon.d.ts +123 -0
- package/dist/lib/daemon.d.ts.map +1 -0
- package/dist/lib/doctor.d.ts +1 -1
- package/dist/lib/doctor.d.ts.map +1 -1
- package/dist/lib/router.d.ts.map +1 -1
- package/dist/lib/telegram.d.ts +6 -0
- package/dist/lib/telegram.d.ts.map +1 -1
- package/dist/mcp/index.js +327 -38
- package/docs/architecture.md +35 -0
- package/package.json +2 -2
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AgentConfig, type BridgeConfig, type ChannelConfig, type ProfileConfig, type RouteConfig } from "../types.js";
|
|
2
2
|
export declare function emptyConfig(): BridgeConfig;
|
|
3
3
|
export declare function parseConfig(value: unknown): BridgeConfig;
|
|
4
|
+
export declare function redactConfig(config: BridgeConfig): BridgeConfig;
|
|
4
5
|
export declare function loadConfig(configPath?: string): Promise<BridgeConfig>;
|
|
5
6
|
export declare function saveConfig(config: BridgeConfig, configPath?: string): Promise<void>;
|
|
6
7
|
export declare function ensureConfig(configPath?: string): Promise<BridgeConfig>;
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAqF5I,wBAAgB,WAAW,IAAI,YAAY,CAQ1C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAGxD;AAeD,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAW/D;AAED,wBAAsB,UAAU,CAAC,UAAU,SAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAUxF;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,SAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKtG;AAED,wBAAsB,YAAY,CAAC,UAAU,SAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAI1F;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,UAAU,SAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKnH;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,UAAU,SAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKnH;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,SAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAK7G;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,SAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAK7G"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { BridgeConfig } from "../types.js";
|
|
2
|
+
export type DaemonSupervisor = "process" | "launchd" | "systemd";
|
|
3
|
+
export type DaemonSupervisorOption = DaemonSupervisor | "auto";
|
|
4
|
+
export interface DaemonPaths {
|
|
5
|
+
dir: string;
|
|
6
|
+
lockDir: string;
|
|
7
|
+
metadataFile: string;
|
|
8
|
+
stdoutLog: string;
|
|
9
|
+
stderrLog: string;
|
|
10
|
+
launchdPlist: string;
|
|
11
|
+
systemdUnit: string;
|
|
12
|
+
}
|
|
13
|
+
export interface DaemonMetadata {
|
|
14
|
+
version: 1;
|
|
15
|
+
supervisor: "process";
|
|
16
|
+
pid: number;
|
|
17
|
+
pgid?: number;
|
|
18
|
+
startedAt: string;
|
|
19
|
+
identity: {
|
|
20
|
+
command: string;
|
|
21
|
+
cwd: string;
|
|
22
|
+
configPath: string;
|
|
23
|
+
statePath: string;
|
|
24
|
+
daemonDir: string;
|
|
25
|
+
bridgeHome: string;
|
|
26
|
+
};
|
|
27
|
+
command: string[];
|
|
28
|
+
cwd: string;
|
|
29
|
+
configPath: string;
|
|
30
|
+
statePath: string;
|
|
31
|
+
intervalMs: number;
|
|
32
|
+
serveJson: boolean;
|
|
33
|
+
daemonDir: string;
|
|
34
|
+
bridgeHome: string;
|
|
35
|
+
stdoutLog: string;
|
|
36
|
+
stderrLog: string;
|
|
37
|
+
}
|
|
38
|
+
export interface DaemonStatus {
|
|
39
|
+
running: boolean;
|
|
40
|
+
stale: boolean;
|
|
41
|
+
supervisor: DaemonSupervisor;
|
|
42
|
+
pid?: number;
|
|
43
|
+
startedAt?: string;
|
|
44
|
+
uptimeSeconds?: number;
|
|
45
|
+
detail?: string;
|
|
46
|
+
installedDetail?: string;
|
|
47
|
+
metadata?: DaemonMetadata;
|
|
48
|
+
paths: DaemonPaths;
|
|
49
|
+
installed: {
|
|
50
|
+
launchd: boolean;
|
|
51
|
+
systemd: boolean;
|
|
52
|
+
};
|
|
53
|
+
telegramApiBase: {
|
|
54
|
+
overridden: boolean;
|
|
55
|
+
origin: string;
|
|
56
|
+
pathname: string;
|
|
57
|
+
error?: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export interface DaemonStartOptions {
|
|
61
|
+
supervisor?: DaemonSupervisorOption;
|
|
62
|
+
daemonDir?: string;
|
|
63
|
+
configPath?: string;
|
|
64
|
+
statePath?: string;
|
|
65
|
+
intervalMs?: number;
|
|
66
|
+
serveJson?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface DaemonStopOptions {
|
|
69
|
+
supervisor?: DaemonSupervisorOption;
|
|
70
|
+
daemonDir?: string;
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
force?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface DaemonInstallOptions {
|
|
75
|
+
supervisor?: DaemonSupervisorOption;
|
|
76
|
+
daemonDir?: string;
|
|
77
|
+
configPath?: string;
|
|
78
|
+
statePath?: string;
|
|
79
|
+
intervalMs?: number;
|
|
80
|
+
serveJson?: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface DaemonInstallResult {
|
|
83
|
+
supervisor: DaemonSupervisor;
|
|
84
|
+
path: string;
|
|
85
|
+
command: string[];
|
|
86
|
+
requiredEnv: string[];
|
|
87
|
+
warning?: string;
|
|
88
|
+
}
|
|
89
|
+
export declare function resolveSupervisor(supervisor?: DaemonSupervisorOption): DaemonSupervisor;
|
|
90
|
+
export declare function defaultDaemonDir(): string;
|
|
91
|
+
export declare function daemonPaths(daemonDir?: string): DaemonPaths;
|
|
92
|
+
export declare function ensureDaemonDir(dir?: string): Promise<DaemonPaths>;
|
|
93
|
+
export declare function requiredTelegramEnvVars(config: BridgeConfig): string[];
|
|
94
|
+
export declare function daemonStatus(options?: {
|
|
95
|
+
daemonDir?: string;
|
|
96
|
+
supervisor?: DaemonSupervisorOption;
|
|
97
|
+
}): Promise<DaemonStatus>;
|
|
98
|
+
export declare function startProcessDaemon(options?: DaemonStartOptions): Promise<DaemonStatus>;
|
|
99
|
+
export declare function stopProcessDaemon(options?: DaemonStopOptions): Promise<DaemonStatus>;
|
|
100
|
+
export declare function restartProcessDaemon(options?: DaemonStartOptions & DaemonStopOptions): Promise<DaemonStatus>;
|
|
101
|
+
export declare function renderLaunchdPlist(command: string[], paths: DaemonPaths): string;
|
|
102
|
+
export declare function renderSystemdUnit(command: string[], paths: DaemonPaths): string;
|
|
103
|
+
export declare function installDaemon(options?: DaemonInstallOptions): Promise<DaemonInstallResult>;
|
|
104
|
+
export declare function startInstalledDaemon(options?: DaemonInstallOptions): Promise<DaemonInstallResult>;
|
|
105
|
+
export declare function stopInstalledDaemon(options?: DaemonStopOptions): Promise<void>;
|
|
106
|
+
export declare function restartInstalledDaemon(options?: DaemonInstallOptions & DaemonStopOptions): Promise<DaemonInstallResult | DaemonStatus>;
|
|
107
|
+
export declare function uninstallDaemon(options?: {
|
|
108
|
+
supervisor?: DaemonSupervisorOption;
|
|
109
|
+
daemonDir?: string;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
supervisor: DaemonSupervisor;
|
|
112
|
+
removed: string[];
|
|
113
|
+
}>;
|
|
114
|
+
export declare function tailFile(path: string, lines: number): Promise<string>;
|
|
115
|
+
export declare function daemonLogs(options?: {
|
|
116
|
+
daemonDir?: string;
|
|
117
|
+
lines?: number;
|
|
118
|
+
}): Promise<{
|
|
119
|
+
stdout: string;
|
|
120
|
+
stderr: string;
|
|
121
|
+
paths: DaemonPaths;
|
|
122
|
+
}>;
|
|
123
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/lib/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/doctor.d.ts
CHANGED
package/dist/lib/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,aAAa,CAAC;AAsC7D,wBAAsB,MAAM,CAAC,UAAU,SAAsB,EAAE,SAAS,SAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CA+DpH"}
|
package/dist/lib/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/lib/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAiB,mBAAmB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAChH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAsC,MAAM,eAAe,CAAC;AAExF,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,OAAO,QAAQ,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAC1C,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC;CACjD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/lib/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAiB,mBAAmB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAChH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAsC,MAAM,eAAe,CAAC;AAExF,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,OAAO,QAAQ,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAC1C,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC;CACjD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,WAAW,EAAE,CAc1F;AAMD,wBAAsB,YAAY,CAChC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAgChC"}
|
package/dist/lib/telegram.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export interface TelegramUpdate {
|
|
|
17
17
|
date?: number;
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
export interface TelegramApiBaseInfo {
|
|
21
|
+
overridden: boolean;
|
|
22
|
+
origin: string;
|
|
23
|
+
pathname: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function telegramApiBaseInfo(): TelegramApiBaseInfo;
|
|
20
26
|
export declare function telegramToken(channel: TelegramChannelConfig): string;
|
|
21
27
|
export declare function telegramChatAllowed(channel: TelegramChannelConfig, chatId: string | undefined): boolean;
|
|
22
28
|
export declare function sendTelegramMessage(token: string, chatId: string, text: string): Promise<unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/lib/telegram.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,EAAE;YAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAChE,IAAI,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACvE,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/lib/telegram.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,EAAE;YAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAChE,IAAI,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACvE,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAID,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAgB,mBAAmB,IAAI,mBAAmB,CAOzD;AAUD,wBAAgB,aAAa,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAKpE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAIvG;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASvG;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACzD,OAAO,CAAC,cAAc,EAAE,CAAC,CAU3B;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,aAAa,GAAG,SAAS,CAa5G"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -4101,6 +4101,7 @@ function defaultConfigPath() {
|
|
|
4101
4101
|
}
|
|
4102
4102
|
|
|
4103
4103
|
// src/lib/config.ts
|
|
4104
|
+
var REDACTED_VALUE = "[redacted]";
|
|
4104
4105
|
var channelSchema = exports_external.discriminatedUnion("kind", [
|
|
4105
4106
|
exports_external.object({
|
|
4106
4107
|
id: exports_external.string().min(1),
|
|
@@ -4188,6 +4189,31 @@ function parseConfig(value) {
|
|
|
4188
4189
|
const parsed = configSchema.parse(value);
|
|
4189
4190
|
return parsed;
|
|
4190
4191
|
}
|
|
4192
|
+
function redactEnv(env) {
|
|
4193
|
+
if (!env)
|
|
4194
|
+
return;
|
|
4195
|
+
return Object.fromEntries(Object.keys(env).map((key) => [key, REDACTED_VALUE]));
|
|
4196
|
+
}
|
|
4197
|
+
function redactEnvRecord(items) {
|
|
4198
|
+
return Object.fromEntries(Object.entries(items).map(([id, item]) => {
|
|
4199
|
+
const clone = { ...item };
|
|
4200
|
+
if (item.env)
|
|
4201
|
+
clone.env = redactEnv(item.env);
|
|
4202
|
+
return [id, clone];
|
|
4203
|
+
}));
|
|
4204
|
+
}
|
|
4205
|
+
function redactConfig(config) {
|
|
4206
|
+
return {
|
|
4207
|
+
...config,
|
|
4208
|
+
channels: Object.fromEntries(Object.entries(config.channels).map(([id, channel]) => [id, { ...channel }])),
|
|
4209
|
+
profiles: redactEnvRecord(config.profiles),
|
|
4210
|
+
agents: redactEnvRecord(config.agents),
|
|
4211
|
+
routes: config.routes.map((route) => ({
|
|
4212
|
+
...route,
|
|
4213
|
+
match: route.match ? { ...route.match, chatIds: route.match.chatIds ? [...route.match.chatIds] : undefined } : undefined
|
|
4214
|
+
}))
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4191
4217
|
async function loadConfig(configPath = defaultConfigPath()) {
|
|
4192
4218
|
try {
|
|
4193
4219
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -4199,8 +4225,275 @@ async function loadConfig(configPath = defaultConfigPath()) {
|
|
|
4199
4225
|
throw err;
|
|
4200
4226
|
}
|
|
4201
4227
|
}
|
|
4228
|
+
// src/lib/daemon.ts
|
|
4229
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename, rm, rmdir, stat, writeFile as writeFile2 } from "fs/promises";
|
|
4230
|
+
import { dirname as dirname2, join as join3, resolve } from "path";
|
|
4231
|
+
|
|
4232
|
+
// src/lib/state.ts
|
|
4233
|
+
import { dirname, join as join2 } from "path";
|
|
4234
|
+
function defaultStatePath() {
|
|
4235
|
+
return process.env["BRIDGE_STATE"] || join2(bridgeHome(), "state.json");
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
// src/lib/telegram.ts
|
|
4239
|
+
var DEFAULT_TELEGRAM_API_BASE = "https://api.telegram.org";
|
|
4240
|
+
function telegramApiBase() {
|
|
4241
|
+
const raw = process.env["BRIDGE_TELEGRAM_API_BASE"] || DEFAULT_TELEGRAM_API_BASE;
|
|
4242
|
+
const parsed = new URL(raw);
|
|
4243
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
4244
|
+
throw new Error("BRIDGE_TELEGRAM_API_BASE must use http or https");
|
|
4245
|
+
}
|
|
4246
|
+
if (parsed.username || parsed.password) {
|
|
4247
|
+
throw new Error("BRIDGE_TELEGRAM_API_BASE must not contain credentials");
|
|
4248
|
+
}
|
|
4249
|
+
if (parsed.search || parsed.hash) {
|
|
4250
|
+
throw new Error("BRIDGE_TELEGRAM_API_BASE must not contain query strings or fragments");
|
|
4251
|
+
}
|
|
4252
|
+
return parsed;
|
|
4253
|
+
}
|
|
4254
|
+
function telegramApiBaseInfo() {
|
|
4255
|
+
const parsed = telegramApiBase();
|
|
4256
|
+
return {
|
|
4257
|
+
overridden: parsed.href.replace(/\/$/, "") !== DEFAULT_TELEGRAM_API_BASE,
|
|
4258
|
+
origin: parsed.origin,
|
|
4259
|
+
pathname: parsed.pathname
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
function telegramMethodUrl(token, method) {
|
|
4263
|
+
const base = telegramApiBase();
|
|
4264
|
+
const prefix = base.pathname.replace(/\/$/, "");
|
|
4265
|
+
base.pathname = `${prefix}/bot${token}/${method}`;
|
|
4266
|
+
base.search = "";
|
|
4267
|
+
return base.toString();
|
|
4268
|
+
}
|
|
4269
|
+
function telegramToken(channel) {
|
|
4270
|
+
const envName = channel.botTokenEnv || "TELEGRAM_BOT_TOKEN";
|
|
4271
|
+
const token = process.env[envName];
|
|
4272
|
+
if (!token)
|
|
4273
|
+
throw new Error(`Missing Telegram bot token env var: ${envName}`);
|
|
4274
|
+
return token;
|
|
4275
|
+
}
|
|
4276
|
+
function telegramChatAllowed(channel, chatId) {
|
|
4277
|
+
if (channel.allowAllChats)
|
|
4278
|
+
return true;
|
|
4279
|
+
if (!channel.allowedChatIds?.length)
|
|
4280
|
+
return false;
|
|
4281
|
+
return Boolean(chatId && channel.allowedChatIds.includes(chatId));
|
|
4282
|
+
}
|
|
4283
|
+
async function sendTelegramMessage(token, chatId, text) {
|
|
4284
|
+
const response = await fetch(telegramMethodUrl(token, "sendMessage"), {
|
|
4285
|
+
method: "POST",
|
|
4286
|
+
headers: { "content-type": "application/json" },
|
|
4287
|
+
body: JSON.stringify({ chat_id: chatId, text })
|
|
4288
|
+
});
|
|
4289
|
+
const body = await response.json().catch(() => {
|
|
4290
|
+
return;
|
|
4291
|
+
});
|
|
4292
|
+
if (!response.ok)
|
|
4293
|
+
throw new Error(`Telegram sendMessage failed (${response.status}): ${JSON.stringify(body)}`);
|
|
4294
|
+
return body;
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
// src/lib/daemon.ts
|
|
4298
|
+
function isNotFound(err) {
|
|
4299
|
+
return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
|
|
4300
|
+
}
|
|
4301
|
+
function currentPlatformSupervisor() {
|
|
4302
|
+
if (process.platform === "darwin")
|
|
4303
|
+
return "launchd";
|
|
4304
|
+
if (process.platform === "linux")
|
|
4305
|
+
return "systemd";
|
|
4306
|
+
return "process";
|
|
4307
|
+
}
|
|
4308
|
+
function resolveSupervisor(supervisor = "process") {
|
|
4309
|
+
return supervisor === "auto" ? currentPlatformSupervisor() : supervisor;
|
|
4310
|
+
}
|
|
4311
|
+
function defaultDaemonDir() {
|
|
4312
|
+
return join3(bridgeHome(), "daemon");
|
|
4313
|
+
}
|
|
4314
|
+
function daemonPaths(daemonDir = defaultDaemonDir()) {
|
|
4315
|
+
const dir = resolve(daemonDir);
|
|
4316
|
+
return {
|
|
4317
|
+
dir,
|
|
4318
|
+
lockDir: join3(dir, "lock"),
|
|
4319
|
+
metadataFile: join3(dir, "bridge-daemon.json"),
|
|
4320
|
+
stdoutLog: join3(dir, "bridge.out.log"),
|
|
4321
|
+
stderrLog: join3(dir, "bridge.err.log"),
|
|
4322
|
+
launchdPlist: join3(process.env["HOME"] || process.cwd(), "Library", "LaunchAgents", "com.hasna.bridge.plist"),
|
|
4323
|
+
systemdUnit: join3(process.env["HOME"] || process.cwd(), ".config", "systemd", "user", "hasna-bridge.service")
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
async function fileExists(path) {
|
|
4327
|
+
try {
|
|
4328
|
+
await stat(path);
|
|
4329
|
+
return true;
|
|
4330
|
+
} catch (err) {
|
|
4331
|
+
if (isNotFound(err))
|
|
4332
|
+
return false;
|
|
4333
|
+
throw err;
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
async function readMetadata(paths) {
|
|
4337
|
+
try {
|
|
4338
|
+
return JSON.parse(await readFile2(paths.metadataFile, "utf-8"));
|
|
4339
|
+
} catch (err) {
|
|
4340
|
+
if (isNotFound(err))
|
|
4341
|
+
return;
|
|
4342
|
+
throw err;
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
function pidAlive(pid) {
|
|
4346
|
+
try {
|
|
4347
|
+
process.kill(pid, 0);
|
|
4348
|
+
return true;
|
|
4349
|
+
} catch {
|
|
4350
|
+
return false;
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
async function processCommand(pid) {
|
|
4354
|
+
const proc = Bun.spawn(["ps", "-p", String(pid), "-o", "command="], {
|
|
4355
|
+
stdout: "pipe",
|
|
4356
|
+
stderr: "ignore"
|
|
4357
|
+
});
|
|
4358
|
+
if (await proc.exited !== 0)
|
|
4359
|
+
return;
|
|
4360
|
+
return (await new Response(proc.stdout).text()).trim();
|
|
4361
|
+
}
|
|
4362
|
+
async function processPgid(pid) {
|
|
4363
|
+
const proc = Bun.spawn(["ps", "-p", String(pid), "-o", "pgid="], {
|
|
4364
|
+
stdout: "pipe",
|
|
4365
|
+
stderr: "ignore"
|
|
4366
|
+
});
|
|
4367
|
+
if (await proc.exited !== 0)
|
|
4368
|
+
return;
|
|
4369
|
+
const parsed = Number.parseInt((await new Response(proc.stdout).text()).trim(), 10);
|
|
4370
|
+
return Number.isInteger(parsed) ? parsed : undefined;
|
|
4371
|
+
}
|
|
4372
|
+
async function processMatches(metadata) {
|
|
4373
|
+
if (!pidAlive(metadata.pid))
|
|
4374
|
+
return false;
|
|
4375
|
+
const command = await processCommand(metadata.pid);
|
|
4376
|
+
if (!command)
|
|
4377
|
+
return false;
|
|
4378
|
+
if (!metadata.pgid)
|
|
4379
|
+
return false;
|
|
4380
|
+
const pgid = await processPgid(metadata.pid);
|
|
4381
|
+
if (pgid !== metadata.pgid)
|
|
4382
|
+
return false;
|
|
4383
|
+
const requiredArgs = [
|
|
4384
|
+
metadata.command[1],
|
|
4385
|
+
"serve",
|
|
4386
|
+
"--config",
|
|
4387
|
+
metadata.configPath,
|
|
4388
|
+
"--state",
|
|
4389
|
+
metadata.statePath,
|
|
4390
|
+
"--interval",
|
|
4391
|
+
String(metadata.intervalMs)
|
|
4392
|
+
].filter((arg) => Boolean(arg));
|
|
4393
|
+
if (metadata.serveJson)
|
|
4394
|
+
requiredArgs.push("--json");
|
|
4395
|
+
return requiredArgs.every((arg) => command.includes(arg));
|
|
4396
|
+
}
|
|
4397
|
+
function safeTelegramApiBaseInfo() {
|
|
4398
|
+
try {
|
|
4399
|
+
return telegramApiBaseInfo();
|
|
4400
|
+
} catch (err) {
|
|
4401
|
+
return {
|
|
4402
|
+
overridden: true,
|
|
4403
|
+
origin: "",
|
|
4404
|
+
pathname: "",
|
|
4405
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
async function runCapture(command) {
|
|
4410
|
+
const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
|
|
4411
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
4412
|
+
proc.exited,
|
|
4413
|
+
new Response(proc.stdout).text(),
|
|
4414
|
+
new Response(proc.stderr).text()
|
|
4415
|
+
]);
|
|
4416
|
+
return { exitCode, stdout, stderr };
|
|
4417
|
+
}
|
|
4418
|
+
async function installedSupervisorStatus(supervisor, paths) {
|
|
4419
|
+
if (supervisor === "launchd") {
|
|
4420
|
+
if (!await fileExists(paths.launchdPlist))
|
|
4421
|
+
return { running: false, detail: "launchd plist not installed" };
|
|
4422
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
4423
|
+
if (uid === undefined)
|
|
4424
|
+
return { running: false, detail: "launchd status requires a numeric uid" };
|
|
4425
|
+
const result = await runCapture(["launchctl", "print", `gui/${uid}/com.hasna.bridge`]);
|
|
4426
|
+
if (result.exitCode !== 0)
|
|
4427
|
+
return { running: false, detail: result.stderr.trim() || result.stdout.trim() || "launchd service not loaded" };
|
|
4428
|
+
const running = /state\s*=\s*running/.test(result.stdout);
|
|
4429
|
+
return { running, detail: running ? "launchd running" : "launchd loaded but not running" };
|
|
4430
|
+
}
|
|
4431
|
+
if (supervisor === "systemd") {
|
|
4432
|
+
if (!await fileExists(paths.systemdUnit))
|
|
4433
|
+
return { running: false, detail: "systemd unit not installed" };
|
|
4434
|
+
const result = await runCapture(["systemctl", "--user", "is-active", "hasna-bridge.service"]);
|
|
4435
|
+
const state = result.stdout.trim() || result.stderr.trim() || "unknown";
|
|
4436
|
+
return { running: result.exitCode === 0 && state === "active", detail: `systemd ${state}` };
|
|
4437
|
+
}
|
|
4438
|
+
return { running: false, detail: "process supervisor has no installed status" };
|
|
4439
|
+
}
|
|
4440
|
+
async function daemonStatus(options = {}) {
|
|
4441
|
+
const supervisor = resolveSupervisor(options.supervisor);
|
|
4442
|
+
const paths = daemonPaths(options.daemonDir);
|
|
4443
|
+
const metadata = await readMetadata(paths);
|
|
4444
|
+
const live = metadata ? await processMatches(metadata) : false;
|
|
4445
|
+
const stale = Boolean(metadata && !live);
|
|
4446
|
+
const startedAt = metadata?.startedAt;
|
|
4447
|
+
const uptimeSeconds = live && startedAt ? Math.max(0, Math.floor((Date.now() - Date.parse(startedAt)) / 1000)) : undefined;
|
|
4448
|
+
const installed = {
|
|
4449
|
+
launchd: await fileExists(paths.launchdPlist),
|
|
4450
|
+
systemd: await fileExists(paths.systemdUnit)
|
|
4451
|
+
};
|
|
4452
|
+
const installedRuntime = supervisor === "process" ? undefined : await installedSupervisorStatus(supervisor, paths);
|
|
4453
|
+
return {
|
|
4454
|
+
running: installedRuntime ? installedRuntime.running : live,
|
|
4455
|
+
stale: installedRuntime ? false : stale,
|
|
4456
|
+
supervisor,
|
|
4457
|
+
pid: metadata?.pid,
|
|
4458
|
+
startedAt,
|
|
4459
|
+
uptimeSeconds,
|
|
4460
|
+
detail: installedRuntime?.detail || (stale ? "stale process metadata" : live ? "running" : "not running"),
|
|
4461
|
+
installedDetail: installedRuntime?.detail,
|
|
4462
|
+
metadata,
|
|
4463
|
+
paths,
|
|
4464
|
+
installed,
|
|
4465
|
+
telegramApiBase: safeTelegramApiBaseInfo()
|
|
4466
|
+
};
|
|
4467
|
+
}
|
|
4202
4468
|
// src/lib/doctor.ts
|
|
4203
|
-
import {
|
|
4469
|
+
import { stat as stat2 } from "fs/promises";
|
|
4470
|
+
function isNotFound2(err) {
|
|
4471
|
+
return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
|
|
4472
|
+
}
|
|
4473
|
+
async function privateFileCheck(name, path) {
|
|
4474
|
+
try {
|
|
4475
|
+
const info = await stat2(path);
|
|
4476
|
+
const mode = info.mode & 511;
|
|
4477
|
+
const ok = (mode & 63) === 0;
|
|
4478
|
+
return { name, ok, detail: `${path} mode=${mode.toString(8)}` };
|
|
4479
|
+
} catch (err) {
|
|
4480
|
+
if (isNotFound2(err))
|
|
4481
|
+
return { name, ok: true, detail: `not created yet: ${path}` };
|
|
4482
|
+
return { name, ok: false, detail: `${path}: ${err instanceof Error ? err.message : String(err)}` };
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
async function privateDirCheck(name, path) {
|
|
4486
|
+
try {
|
|
4487
|
+
const info = await stat2(path);
|
|
4488
|
+
const mode = info.mode & 511;
|
|
4489
|
+
const ok = (mode & 63) === 0;
|
|
4490
|
+
return { name, ok, detail: `${path} mode=${mode.toString(8)}` };
|
|
4491
|
+
} catch (err) {
|
|
4492
|
+
if (isNotFound2(err))
|
|
4493
|
+
return { name, ok: true, detail: `not created yet: ${path}` };
|
|
4494
|
+
return { name, ok: false, detail: `${path}: ${err instanceof Error ? err.message : String(err)}` };
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4204
4497
|
async function commandExists(command) {
|
|
4205
4498
|
const proc = Bun.spawn(["sh", "-lc", `command -v ${command} >/dev/null 2>&1`], {
|
|
4206
4499
|
stdout: "ignore",
|
|
@@ -4208,14 +4501,33 @@ async function commandExists(command) {
|
|
|
4208
4501
|
});
|
|
4209
4502
|
return await proc.exited === 0;
|
|
4210
4503
|
}
|
|
4211
|
-
async function doctor(configPath = defaultConfigPath()) {
|
|
4504
|
+
async function doctor(configPath = defaultConfigPath(), statePath = defaultStatePath()) {
|
|
4212
4505
|
const checks = [];
|
|
4213
|
-
|
|
4506
|
+
const config = await loadConfig(configPath);
|
|
4507
|
+
const daemon = await daemonStatus();
|
|
4508
|
+
const paths = daemonPaths();
|
|
4509
|
+
checks.push(await privateFileCheck("config", configPath));
|
|
4510
|
+
checks.push(await privateFileCheck("state", statePath));
|
|
4511
|
+
checks.push(await privateDirCheck("daemon-dir", paths.dir));
|
|
4512
|
+
checks.push(await privateFileCheck("daemon-metadata", paths.metadataFile));
|
|
4513
|
+
checks.push({
|
|
4514
|
+
name: "daemon-status",
|
|
4515
|
+
ok: !daemon.stale,
|
|
4516
|
+
detail: daemon.running ? `running pid=${daemon.pid}` : daemon.stale ? `stale pid=${daemon.pid}` : "not running"
|
|
4517
|
+
});
|
|
4214
4518
|
try {
|
|
4215
|
-
|
|
4216
|
-
checks.push({
|
|
4217
|
-
|
|
4218
|
-
|
|
4519
|
+
const apiBase = telegramApiBaseInfo();
|
|
4520
|
+
checks.push({
|
|
4521
|
+
name: "telegram-api-base",
|
|
4522
|
+
ok: true,
|
|
4523
|
+
detail: apiBase.overridden ? `overridden: ${apiBase.origin}${apiBase.pathname}` : apiBase.origin
|
|
4524
|
+
});
|
|
4525
|
+
} catch (err) {
|
|
4526
|
+
checks.push({
|
|
4527
|
+
name: "telegram-api-base",
|
|
4528
|
+
ok: false,
|
|
4529
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
4530
|
+
});
|
|
4219
4531
|
}
|
|
4220
4532
|
for (const command of ["bridge", "codewith", "claude", "aicopilot"]) {
|
|
4221
4533
|
checks.push({
|
|
@@ -4247,38 +4559,11 @@ async function doctor(configPath = defaultConfigPath()) {
|
|
|
4247
4559
|
}
|
|
4248
4560
|
return { ok: checks.every((check) => check.ok), configPath, checks };
|
|
4249
4561
|
}
|
|
4250
|
-
// src/lib/telegram.ts
|
|
4251
|
-
function telegramToken(channel) {
|
|
4252
|
-
const envName = channel.botTokenEnv || "TELEGRAM_BOT_TOKEN";
|
|
4253
|
-
const token = process.env[envName];
|
|
4254
|
-
if (!token)
|
|
4255
|
-
throw new Error(`Missing Telegram bot token env var: ${envName}`);
|
|
4256
|
-
return token;
|
|
4257
|
-
}
|
|
4258
|
-
function telegramChatAllowed(channel, chatId) {
|
|
4259
|
-
if (channel.allowAllChats)
|
|
4260
|
-
return true;
|
|
4261
|
-
if (!channel.allowedChatIds?.length)
|
|
4262
|
-
return false;
|
|
4263
|
-
return Boolean(chatId && channel.allowedChatIds.includes(chatId));
|
|
4264
|
-
}
|
|
4265
|
-
async function sendTelegramMessage(token, chatId, text) {
|
|
4266
|
-
const response = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
4267
|
-
method: "POST",
|
|
4268
|
-
headers: { "content-type": "application/json" },
|
|
4269
|
-
body: JSON.stringify({ chat_id: chatId, text })
|
|
4270
|
-
});
|
|
4271
|
-
const body = await response.json().catch(() => {
|
|
4272
|
-
return;
|
|
4273
|
-
});
|
|
4274
|
-
if (!response.ok)
|
|
4275
|
-
throw new Error(`Telegram sendMessage failed (${response.status}): ${JSON.stringify(body)}`);
|
|
4276
|
-
return body;
|
|
4277
|
-
}
|
|
4278
|
-
|
|
4279
4562
|
// src/lib/router.ts
|
|
4280
4563
|
function matchingRoutes(config, message) {
|
|
4281
4564
|
const channel = config.channels[message.channelId];
|
|
4565
|
+
if (!channel || channel.enabled === false)
|
|
4566
|
+
return [];
|
|
4282
4567
|
if (channel?.kind === "telegram" && !telegramChatAllowed(channel, message.chatId)) {
|
|
4283
4568
|
return [];
|
|
4284
4569
|
}
|
|
@@ -4306,6 +4591,10 @@ async function routeMessage(config, message, options = {}) {
|
|
|
4306
4591
|
let deliveredResponse = false;
|
|
4307
4592
|
const channel = responseChannel(config, route, message);
|
|
4308
4593
|
const responseText = agent.stdout.trim();
|
|
4594
|
+
if (channel?.enabled === false) {
|
|
4595
|
+
results.push({ route, agent, deliveredResponse });
|
|
4596
|
+
continue;
|
|
4597
|
+
}
|
|
4309
4598
|
if (responseText && channel?.kind === "telegram" && message.chatId) {
|
|
4310
4599
|
if (!telegramChatAllowed(channel, message.chatId)) {
|
|
4311
4600
|
results.push({ route, agent, deliveredResponse });
|
|
@@ -4327,9 +4616,9 @@ function text(value) {
|
|
|
4327
4616
|
return { content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }] };
|
|
4328
4617
|
}
|
|
4329
4618
|
function buildServer() {
|
|
4330
|
-
const server = new McpServer({ name: "bridge", version: "0.1.
|
|
4619
|
+
const server = new McpServer({ name: "bridge", version: "0.1.1" });
|
|
4331
4620
|
server.tool("bridge_status", {}, async () => text(await doctor()));
|
|
4332
|
-
server.tool("bridge_config", {}, async () => text(await loadConfig()));
|
|
4621
|
+
server.tool("bridge_config", {}, async () => text(redactConfig(await loadConfig())));
|
|
4333
4622
|
server.tool("bridge_route_message", {
|
|
4334
4623
|
channelId: exports_external.string(),
|
|
4335
4624
|
text: exports_external.string(),
|
package/docs/architecture.md
CHANGED
|
@@ -43,5 +43,40 @@ Telegram channels fail closed unless `allowedChatIds` are configured or
|
|
|
43
43
|
before route matching. Routes can also add narrower `match.chatIds` filters, but
|
|
44
44
|
they cannot expand beyond the channel allowlist.
|
|
45
45
|
|
|
46
|
+
Disabled channels do not match inbound routes and do not deliver responses.
|
|
47
|
+
MCP config inspection redacts profile and agent environment values so local
|
|
48
|
+
secrets are not exposed through `bridge_config`.
|
|
49
|
+
|
|
46
50
|
Long-poll offsets are persisted in a private state file so process restarts do
|
|
47
51
|
not replay already-seen updates and re-run agents.
|
|
52
|
+
|
|
53
|
+
`BRIDGE_TELEGRAM_API_BASE` can override the Telegram API origin for local tests.
|
|
54
|
+
The override accepts only `http` or `https` URLs without credentials. It is not
|
|
55
|
+
intended for normal production use because bot tokens are part of Telegram API
|
|
56
|
+
request paths.
|
|
57
|
+
|
|
58
|
+
## Daemon Model
|
|
59
|
+
|
|
60
|
+
The foreground runtime remains `bridge serve`. Daemon commands are lifecycle
|
|
61
|
+
wrappers around that same runtime:
|
|
62
|
+
|
|
63
|
+
- `bridge daemon start` uses a local process supervisor by default.
|
|
64
|
+
- `bridge daemon status` reads private metadata and verifies the recorded
|
|
65
|
+
process still looks like `bridge serve`.
|
|
66
|
+
- `bridge daemon stop` terminates the process group and removes stale metadata.
|
|
67
|
+
- `bridge daemon logs` reads private stdout/stderr logs.
|
|
68
|
+
- `bridge daemon install` writes user `launchd` or user `systemd` files.
|
|
69
|
+
|
|
70
|
+
The process supervisor is the quickest local testing path because it inherits
|
|
71
|
+
the shell environment, including Telegram token env vars. Installed launchd and
|
|
72
|
+
systemd services do not store token values by default; operators must make token
|
|
73
|
+
env vars available through their service manager.
|
|
74
|
+
|
|
75
|
+
Daemon files live under `~/.hasna/bridge/daemon` by default. The directory is
|
|
76
|
+
`0700`; metadata and log files are `0600`. Logs are considered sensitive because
|
|
77
|
+
they can contain prompts, Telegram text, agent stdout/stderr, and routing
|
|
78
|
+
errors.
|
|
79
|
+
|
|
80
|
+
`bridge serve` handles per-channel poll errors without exiting in long-running
|
|
81
|
+
mode. `serve --once` still fails fast so health checks and tests can catch
|
|
82
|
+
misconfiguration.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Agent messaging bridge for Telegram and other channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"dev:cli": "bun run src/cli/index.ts",
|
|
30
30
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
31
31
|
"prepublishOnly": "bun run build",
|
|
32
|
-
"postinstall": "
|
|
32
|
+
"postinstall": "node -e \"const fs=require('node:fs');const path=require('node:path');const dir=path.join(process.env.HOME||process.cwd(),'.hasna','bridge');fs.mkdirSync(dir,{recursive:true,mode:0o700});try{fs.chmodSync(dir,0o700)}catch{}\""
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"bridge",
|