opencode-telegram-link 1.0.0-rc.3 → 1.0.0-rc.5

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,14 +2,14 @@
2
2
 
3
3
  OpenCode Telegram Notifier is a privacy-first OpenCode plugin and multi-host management gateway for asynchronous notifications, interactive inline buttons, safe remote replies, and proactive remote task dispatching. It is designed for developers who run OpenCode across multiple projects and multiple computers (dev machine, laptop, VPS) while routing every Telegram and Web interaction to the exact originating host, process, and workspace window.
4
4
 
5
- > Version: **v1.0.0-rc.3** (OpenCode Commander Web Dashboard, Proactive Remote Dispatch, Multi-Engine Voice STT, Multi-Host Gateway & Node Agent Architecture).
6
- > Status: Release Candidate 3. Install from a release tarball or source checkout until the first public npm package is published.
5
+ > Version: **v1.0.0-rc.5** (OpenCode Commander Web Dashboard, Proactive Remote Dispatch, Multi-Engine Voice STT, Multi-Host Gateway & Node Agent Architecture).
6
+ > Status: Release Candidate 5. Install from a release tarball or source checkout until the first public npm package is published.
7
7
 
8
8
  [繁體中文總覽 (Traditional Chinese)](docs/README.zh-TW.md)
9
9
 
10
10
  ---
11
11
 
12
- ## Features & Highlights (v1.0.0-rc.3)
12
+ ## Features & Highlights (v1.0.0-rc.5)
13
13
 
14
14
  - 🖥️ **OpenCode Commander (Web GUI Dashboard)**: Built-in modern Glassmorphism Web Console (`http://<gateway-ip>:42617/dashboard`) providing real-time visibility, live metrics, and centralized cluster management.
15
15
  - 🌐 **Cluster Topology & 1-to-1 Window Mapping**: Live cards for all connected computers and OpenCode workspace windows with real-time session indicators and task counters.
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/broker/commands.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,kBAAkB,EAExB,MAAM,UAAU,CAAC;AAElB,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D,CAAC;AAEF,wBAAsB,YAAY,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAqC9F;AA2DD,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,cAAc,EACvB,mBAAmB,GAAE,OAAO,KAAa,GACxC,OAAO,CAAC,MAAM,CAAC,CAejB"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/broker/commands.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,kBAAkB,EAExB,MAAM,UAAU,CAAC;AAElB,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D,CAAC;AAEF,wBAAsB,YAAY,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAsC9F;AA2DD,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,cAAc,EACvB,mBAAmB,GAAE,OAAO,KAAa,GACxC,OAAO,CAAC,MAAM,CAAC,CAejB"}
@@ -14518,9 +14518,9 @@ var DiagnosticSchema = exports_external.object({
14518
14518
 
14519
14519
  // src/setup.ts
14520
14520
  import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
14521
- import { existsSync, readFileSync } from "fs";
14522
- import { chmod as chmod3, lstat as lstat5, mkdir as mkdir3, open as open2, readFile as readFile5, rename as rename4, rm as rm2, writeFile as writeFile3 } from "fs/promises";
14523
- import { hostname as hostname3, platform as platform4 } from "os";
14521
+ import { existsSync as existsSync2, readFileSync } from "fs";
14522
+ import { chmod as chmod3, lstat as lstat5, mkdir as mkdir3, open as open2, readFile as readFile5, rename as rename4, rm as rm2, writeFile as writeFile4 } from "fs/promises";
14523
+ import { hostname as hostname3, platform as platform5 } from "os";
14524
14524
  import { dirname as dirname2, join as join5, resolve as resolve2 } from "path";
14525
14525
 
14526
14526
  // src/config.ts
@@ -15781,14 +15781,11 @@ async function injectOpenCodeConfig(targetPath, config2) {
15781
15781
  chatId: config2.telegram.chatId
15782
15782
  };
15783
15783
  }
15784
+ delete existingJson["opencode-telegram-link"];
15785
+ delete existingJson["telegram-link"];
15784
15786
  const isMatch = (entry) => {
15785
- if (typeof entry === "string") {
15786
- return entry === "opencode-telegram-link" || entry.endsWith("opencode-telegram-link");
15787
- }
15788
- if (Array.isArray(entry) && typeof entry[0] === "string") {
15789
- return entry[0] === "opencode-telegram-link" || entry[0].endsWith("opencode-telegram-link");
15790
- }
15791
- return false;
15787
+ const name = typeof entry === "string" ? entry : Array.isArray(entry) && typeof entry[0] === "string" ? entry[0] : "";
15788
+ return name === "opencode-telegram-link" || name.startsWith("opencode-telegram-link") || name.includes("opencode-telegram-link") || name.includes("opencode_notification") || name.includes("telegram-link");
15792
15789
  };
15793
15790
  function mergePluginOptions(existing, updated) {
15794
15791
  const existingObj = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
@@ -15811,19 +15808,15 @@ async function injectOpenCodeConfig(targetPath, config2) {
15811
15808
  return merged;
15812
15809
  }
15813
15810
  if (Array.isArray(existingJson.plugin)) {
15814
- const index = existingJson.plugin.findIndex(isMatch);
15815
- if (index >= 0) {
15816
- const existingEntry = existingJson.plugin[index];
15817
- const existingOptions = Array.isArray(existingEntry) ? existingEntry[1] : undefined;
15818
- const pluginName = Array.isArray(existingEntry) && typeof existingEntry[0] === "string" ? existingEntry[0] : "opencode-telegram-link";
15819
- existingJson.plugin[index] = [pluginName, mergePluginOptions(existingOptions, pluginConfig)];
15820
- } else {
15821
- existingJson.plugin.push(["opencode-telegram-link", pluginConfig]);
15822
- }
15811
+ const existingEntry = existingJson.plugin.find(isMatch);
15812
+ const existingOptions = Array.isArray(existingEntry) ? existingEntry[1] : undefined;
15813
+ const mergedOptions = mergePluginOptions(existingOptions, pluginConfig);
15814
+ const cleanedPlugins = existingJson.plugin.filter((p) => !isMatch(p));
15815
+ cleanedPlugins.push(["opencode-telegram-link", mergedOptions]);
15816
+ existingJson.plugin = cleanedPlugins;
15823
15817
  } else if (Array.isArray(existingJson.plugins)) {
15824
- if (!existingJson.plugins.includes("opencode-telegram-link")) {
15825
- existingJson.plugins.push("opencode-telegram-link");
15826
- }
15818
+ const plugins = existingJson.plugins;
15819
+ existingJson.plugins = [...plugins.filter((p) => !isMatch(p)), "opencode-telegram-link"];
15827
15820
  const existingPluginMap = existingJson.plugin && typeof existingJson.plugin === "object" ? existingJson.plugin : {};
15828
15821
  existingPluginMap["opencode-telegram-link"] = mergePluginOptions(existingPluginMap["opencode-telegram-link"], pluginConfig);
15829
15822
  existingJson.plugin = existingPluginMap;
@@ -15845,14 +15838,17 @@ async function removeOpenCodeConfig(targetPath) {
15845
15838
  const existingJson = parseJsonc(existingContent);
15846
15839
  let modified = false;
15847
15840
  const isPluginMatch = (entry) => {
15848
- if (typeof entry === "string") {
15849
- return entry === "opencode-telegram-link" || entry.endsWith("opencode_notification") || entry.endsWith("opencode-telegram-link");
15850
- }
15851
- if (Array.isArray(entry) && typeof entry[0] === "string") {
15852
- return isPluginMatch(entry[0]);
15853
- }
15854
- return false;
15841
+ const name = typeof entry === "string" ? entry : Array.isArray(entry) && typeof entry[0] === "string" ? entry[0] : "";
15842
+ return name === "opencode-telegram-link" || name.startsWith("opencode-telegram-link") || name.includes("opencode-telegram-link") || name.includes("opencode_notification") || name.includes("telegram-link");
15855
15843
  };
15844
+ if ("opencode-telegram-link" in existingJson) {
15845
+ delete existingJson["opencode-telegram-link"];
15846
+ modified = true;
15847
+ }
15848
+ if ("telegram-link" in existingJson) {
15849
+ delete existingJson["telegram-link"];
15850
+ modified = true;
15851
+ }
15856
15852
  if (Array.isArray(existingJson.plugin)) {
15857
15853
  const originalLength = existingJson.plugin.length;
15858
15854
  const filtered = existingJson.plugin.filter((p) => !isPluginMatch(p));
@@ -15892,6 +15888,93 @@ async function removeOpenCodeConfig(targetPath) {
15892
15888
  return { modified: false };
15893
15889
  }
15894
15890
  }
15891
+ // src/service.ts
15892
+ import { existsSync } from "fs";
15893
+ import { writeFile as writeFile2 } from "fs/promises";
15894
+ import { homedir as homedir3, platform as platform4 } from "os";
15895
+ function isSystemdAvailable() {
15896
+ if (platform4() !== "linux")
15897
+ return false;
15898
+ try {
15899
+ const result = Bun.spawnSync(["systemctl", "--version"]);
15900
+ return result.exitCode === 0;
15901
+ } catch {
15902
+ return false;
15903
+ }
15904
+ }
15905
+ function generateSystemdService(options = {}) {
15906
+ const user = options.user || process.env.USER || "root";
15907
+ const home = options.home || process.env.HOME || homedir3();
15908
+ const envPath = options.envPath || process.env.PATH || "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
15909
+ const execPath = options.execPath || process.execPath;
15910
+ const binScript = options.binScript || process.argv[1] || "opencode-telegram-link";
15911
+ return `[Unit]
15912
+ Description=OpenCode Telegram Gateway & Commander
15913
+ After=network.target
15914
+
15915
+ [Service]
15916
+ Type=simple
15917
+ User=${user}
15918
+ WorkingDirectory=${home}
15919
+ Environment=HOME=${home}
15920
+ Environment="PATH=${envPath}"
15921
+ Environment="OPENCODE_TELEGRAM_BROKER_BIND_HOST=0.0.0.0"
15922
+ ExecStart=${execPath} ${binScript} start --bind-host 0.0.0.0
15923
+ Restart=always
15924
+ RestartSec=5
15925
+
15926
+ [Install]
15927
+ WantedBy=multi-user.target
15928
+ `;
15929
+ }
15930
+ async function installSystemdService(options = {}) {
15931
+ try {
15932
+ const serviceContent = generateSystemdService(options);
15933
+ const tmpServicePath = `/tmp/opencode-gateway.${process.pid}.service`;
15934
+ await writeFile2(tmpServicePath, serviceContent, "utf8");
15935
+ const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
15936
+ const cpCmd = isRoot ? ["cp", tmpServicePath, "/etc/systemd/system/opencode-gateway.service"] : ["sudo", "cp", tmpServicePath, "/etc/systemd/system/opencode-gateway.service"];
15937
+ const cpResult = Bun.spawnSync(cpCmd);
15938
+ if (cpResult.exitCode !== 0) {
15939
+ const err = cpResult.stderr ? new TextDecoder().decode(cpResult.stderr) : "Failed to copy service file";
15940
+ return { success: false, error: err };
15941
+ }
15942
+ const reloadCmd = isRoot ? ["systemctl", "daemon-reload"] : ["sudo", "systemctl", "daemon-reload"];
15943
+ Bun.spawnSync(reloadCmd);
15944
+ const enableCmd = isRoot ? ["systemctl", "enable", "--now", "opencode-gateway"] : ["sudo", "systemctl", "enable", "--now", "opencode-gateway"];
15945
+ const enableResult = Bun.spawnSync(enableCmd);
15946
+ if (enableResult.exitCode !== 0) {
15947
+ const err = enableResult.stderr ? new TextDecoder().decode(enableResult.stderr) : "Failed to enable service";
15948
+ return { success: false, error: err };
15949
+ }
15950
+ return { success: true };
15951
+ } catch (err) {
15952
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
15953
+ }
15954
+ }
15955
+ async function uninstallSystemdService() {
15956
+ const serviceFile = "/etc/systemd/system/opencode-gateway.service";
15957
+ if (!existsSync(serviceFile)) {
15958
+ return { success: true, removed: false };
15959
+ }
15960
+ try {
15961
+ const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
15962
+ const disableCmd = isRoot ? ["systemctl", "disable", "--now", "opencode-gateway"] : ["sudo", "systemctl", "disable", "--now", "opencode-gateway"];
15963
+ Bun.spawnSync(disableCmd);
15964
+ const rmCmd = isRoot ? ["rm", "-f", serviceFile] : ["sudo", "rm", "-f", serviceFile];
15965
+ Bun.spawnSync(rmCmd);
15966
+ const reloadCmd = isRoot ? ["systemctl", "daemon-reload"] : ["sudo", "systemctl", "daemon-reload"];
15967
+ Bun.spawnSync(reloadCmd);
15968
+ return { success: true, removed: true };
15969
+ } catch (err) {
15970
+ return {
15971
+ success: false,
15972
+ removed: false,
15973
+ error: err instanceof Error ? err.message : String(err)
15974
+ };
15975
+ }
15976
+ }
15977
+
15895
15978
  // src/state/database.ts
15896
15979
  import { Database } from "bun:sqlite";
15897
15980
  import { chmod as chmod2, copyFile as copyFile2, lstat as lstat4, rename as rename2 } from "fs/promises";
@@ -16389,7 +16472,7 @@ var SCHEMA_VERSION_2 = `
16389
16472
  `;
16390
16473
  // src/state/discovery.ts
16391
16474
  import { randomUUID as randomUUID2 } from "crypto";
16392
- import { readFile as readFile4, rename as rename3, rm, writeFile as writeFile2 } from "fs/promises";
16475
+ import { readFile as readFile4, rename as rename3, rm, writeFile as writeFile3 } from "fs/promises";
16393
16476
  import { join as join4 } from "path";
16394
16477
  var DiscoveryRecordSchema = exports_external.object({
16395
16478
  port: exports_external.number().int().min(1).max(65535),
@@ -16411,7 +16494,7 @@ async function writeDiscoveryRecord(stateDirectory, port) {
16411
16494
  };
16412
16495
  const destination = discoveryRecordPath(stateDirectory);
16413
16496
  const temporary = `${destination}.${record2.nonce}.tmp`;
16414
- await writeFile2(temporary, `${JSON.stringify(record2)}
16497
+ await writeFile3(temporary, `${JSON.stringify(record2)}
16415
16498
  `, { encoding: "utf8", mode: 384 });
16416
16499
  await rename3(temporary, destination);
16417
16500
  return record2;
@@ -16658,7 +16741,7 @@ class TelegramApiError extends Error {
16658
16741
 
16659
16742
  // src/setup.ts
16660
16743
  function resolveDockerComposeContext(cwd = process.cwd(), explicitComposePath) {
16661
- if (explicitComposePath && existsSync(explicitComposePath)) {
16744
+ if (explicitComposePath && existsSync2(explicitComposePath)) {
16662
16745
  return {
16663
16746
  composeFile: explicitComposePath,
16664
16747
  projectDir: dirname2(explicitComposePath)
@@ -16671,7 +16754,7 @@ function resolveDockerComposeContext(cwd = process.cwd(), explicitComposePath) {
16671
16754
  ];
16672
16755
  for (const file2 of packageCandidates) {
16673
16756
  try {
16674
- if (existsSync(file2)) {
16757
+ if (existsSync2(file2)) {
16675
16758
  return {
16676
16759
  composeFile: file2,
16677
16760
  projectDir: dirname2(file2)
@@ -16681,7 +16764,7 @@ function resolveDockerComposeContext(cwd = process.cwd(), explicitComposePath) {
16681
16764
  }
16682
16765
  const cwdCompose = join5(cwd, "docker-compose.yml");
16683
16766
  try {
16684
- if (existsSync(cwdCompose)) {
16767
+ if (existsSync2(cwdCompose)) {
16685
16768
  const content = readFileSync(cwdCompose, "utf8");
16686
16769
  if (content.includes("opencode-telegram-broker") || content.includes("broker.Dockerfile")) {
16687
16770
  return {
@@ -17015,7 +17098,7 @@ async function runInteractiveSetup(options = {}) {
17015
17098
  const tokenFile = join5(stateDirectory, "telegram-bot-token");
17016
17099
  await writePrivateTokenFile(stateDirectory, tokenFile, botToken);
17017
17100
  const identityFile = join5(stateDirectory, "telegram-identity.json");
17018
- await writeFile3(identityFile, `${JSON.stringify({ userId: pairing.userId, chatId: pairing.chatId, tokenFile, locale }, null, 2)}
17101
+ await writeFile4(identityFile, `${JSON.stringify({ userId: pairing.userId, chatId: pairing.chatId, tokenFile, locale }, null, 2)}
17019
17102
  `, "utf8");
17020
17103
  stdout.write(isZh ? `\u2502 \u2714 \u5B89\u5168 Token \u6A94\u6848\u5DF2\u5132\u5B58 (${tokenFile})
17021
17104
  ` : `\u2502 \u2714 Secure token file saved (${tokenFile})
@@ -17146,9 +17229,32 @@ ${generatePluginConfigSnippet(configData)}
17146
17229
  } else {
17147
17230
  stdout.write(`\u2502
17148
17231
  `);
17149
- stdout.write(isZh ? `\u2502 \u2714 \u672C\u6A5F\u539F\u751F\u6A21\u5F0F\u5DF2\u8A2D\u5B9A\u5B8C\u6210\uFF01\u7576 OpenCode \u555F\u52D5\u6642\u5C07\u81EA\u52D5\u5728\u80CC\u666F\u63A5\u7BA1 Broker\u3002
17232
+ if (!isNode && isSystemdAvailable()) {
17233
+ const autoSystemd = await reader.ask(isZh ? "\u25C7 \u662F\u5426\u5C07 Gateway \u8A3B\u518A\u70BA Systemd \u7CFB\u7D71\u958B\u6A5F\u81EA\u52D5\u555F\u52D5\u670D\u52D9\uFF1F [Y/n]: " : "\u25C7 Register Gateway as a systemd background service (auto-start on boot)? [Y/n]: ", stdout, "Y");
17234
+ if (autoSystemd.toLowerCase() !== "n" && autoSystemd.toLowerCase() !== "no") {
17235
+ stdout.write(isZh ? `\u2502 \u280B \u6B63\u5728\u8A3B\u518A\u4E26\u555F\u7528 Systemd \u670D\u52D9 (opencode-gateway.service)...
17236
+ ` : `\u2502 \u280B Registering and enabling systemd service (opencode-gateway.service)...
17237
+ `);
17238
+ const serviceResult = await installSystemdService();
17239
+ if (serviceResult.success) {
17240
+ stdout.write(isZh ? `\u2502 \u2714 Systemd \u670D\u52D9\u5DF2\u6210\u529F\u5EFA\u7ACB\u4E26\u555F\u52D5\uFF01\u91CD\u958B\u6A5F\u6642\u5C07\u81EA\u52D5\u5728\u80CC\u666F\u57F7\u884C\u3002
17241
+ ` : `\u2502 \u2714 Systemd service created and enabled! Will auto-start on boot.
17242
+ `);
17243
+ } else {
17244
+ stdout.write(isZh ? `\u2502 \u2716 Systemd \u670D\u52D9\u8A3B\u518A\u5931\u6557: ${serviceResult.error}\uFF08\u60A8\u53EF\u65BC\u7A0D\u5F8C\u624B\u52D5\u555F\u52D5: npx opencode-telegram-link start\uFF09
17245
+ ` : `\u2502 \u2716 Failed to register systemd service: ${serviceResult.error}
17246
+ `);
17247
+ }
17248
+ } else {
17249
+ stdout.write(isZh ? `\u2502 \u2714 \u672C\u6A5F\u539F\u751F\u6A21\u5F0F\u5DF2\u8A2D\u5B9A\u5B8C\u6210\uFF01\u7576 OpenCode \u555F\u52D5\u6642\u5C07\u81EA\u52D5\u5728\u80CC\u666F\u63A5\u7BA1 Broker\u3002
17250
+ ` : `\u2502 \u2714 Native mode configured! OpenCode will automatically manage Broker in background.
17251
+ `);
17252
+ }
17253
+ } else {
17254
+ stdout.write(isZh ? `\u2502 \u2714 \u672C\u6A5F\u539F\u751F\u6A21\u5F0F\u5DF2\u8A2D\u5B9A\u5B8C\u6210\uFF01\u7576 OpenCode \u555F\u52D5\u6642\u5C07\u81EA\u52D5\u5728\u80CC\u666F\u63A5\u7BA1 Broker\u3002
17150
17255
  ` : `\u2502 \u2714 Native mode configured! OpenCode will automatically manage Broker in background.
17151
17256
  `);
17257
+ }
17152
17258
  }
17153
17259
  if (!isNode && pairing && api2) {
17154
17260
  stdout.write(`\u2502
@@ -17333,7 +17439,7 @@ async function assertPrivateFile(path) {
17333
17439
  await assertPrivateMode(path, stats.mode, 384);
17334
17440
  }
17335
17441
  async function assertPrivateMode(path, mode, expectedMode) {
17336
- if (platform4() === "win32")
17442
+ if (platform5() === "win32")
17337
17443
  return;
17338
17444
  if ((mode & 63) !== 0) {
17339
17445
  throw new SetupError("SETUP_PERMISSIONS_UNSAFE", "setup state must not allow group or other access");
@@ -17341,7 +17447,7 @@ async function assertPrivateMode(path, mode, expectedMode) {
17341
17447
  await chmodPrivate(path, expectedMode);
17342
17448
  }
17343
17449
  async function chmodPrivate(path, mode) {
17344
- if (platform4() !== "win32")
17450
+ if (platform5() !== "win32")
17345
17451
  await chmod3(path, mode);
17346
17452
  }
17347
17453
  function parseSetupArgs(argv) {
@@ -17442,7 +17548,7 @@ function publicBot(bot) {
17442
17548
 
17443
17549
  // src/uninstall.ts
17444
17550
  import { rm as rm4 } from "fs/promises";
17445
- import { homedir as homedir3, tmpdir } from "os";
17551
+ import { homedir as homedir4, tmpdir } from "os";
17446
17552
  import { join as join8 } from "path";
17447
17553
 
17448
17554
  // src/broker/registry.ts
@@ -17751,7 +17857,7 @@ function sameSessionRoute(left, right) {
17751
17857
  }
17752
17858
  // src/broker/server.ts
17753
17859
  import { createHash as createHash5, randomUUID as randomUUID6, timingSafeEqual } from "crypto";
17754
- import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
17860
+ import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
17755
17861
  import { join as join6 } from "path";
17756
17862
 
17757
17863
  // src/i18n/catalogs.ts
@@ -17986,7 +18092,7 @@ function rejectionHash(updateId, reason) {
17986
18092
  import { randomUUID as randomUUID4 } from "crypto";
17987
18093
 
17988
18094
  // src/version.ts
17989
- var PACKAGE_VERSION = "1.0.0-rc.3";
18095
+ var PACKAGE_VERSION = "1.0.0-rc.5";
17990
18096
 
17991
18097
  // src/telegram/commands.ts
17992
18098
  function isSlashCommand(text) {
@@ -20053,7 +20159,7 @@ async function loadDashboardSettings(stateDirectory) {
20053
20159
  }
20054
20160
  async function saveDashboardSettings(stateDirectory, settings) {
20055
20161
  const filePath = join6(stateDirectory, "dashboard-settings.json");
20056
- await writeFile4(filePath, JSON.stringify(settings, null, 2), "utf-8");
20162
+ await writeFile5(filePath, JSON.stringify(settings, null, 2), "utf-8");
20057
20163
  }
20058
20164
  async function readJsonBody(request) {
20059
20165
  const text = await request.text();
@@ -21459,10 +21565,11 @@ async function runBrokerCli(options = {}) {
21459
21565
  stdout: options.stdout ?? process.stdout,
21460
21566
  stderr: options.stderr ?? process.stderr
21461
21567
  };
21462
- const command = argv[0];
21463
- const flags = parseFlags(argv.slice(1));
21568
+ const normalizedArgv = argv[0] === "broker" ? argv.slice(1) : argv;
21569
+ const command = normalizedArgv[0];
21570
+ const flags = parseFlags(normalizedArgv.slice(1));
21464
21571
  const brokerOptions = brokerOptionsFrom(flags, env);
21465
- if (!command || command === "start") {
21572
+ if (!command || command === "start" || command === "run") {
21466
21573
  return await runStartCommand(brokerOptions, streams, options.onStarted);
21467
21574
  }
21468
21575
  try {
@@ -21740,12 +21847,25 @@ async function runInteractiveUninstall(options = {}) {
21740
21847
  stdout.write(isZh ? `\u25C7 [1/4] \u6B63\u5728\u6AA2\u67E5\u4E26\u505C\u6B62\u57F7\u884C\u4E2D\u7684 Broker (\u672C\u6A5F\u7A0B\u5E8F\u6216 Docker \u5BB9\u5668)...
21741
21848
  ` : `\u25C7 [1/4] Checking and stopping running Broker (native process or Docker container)...
21742
21849
  `);
21743
- try {
21744
- const dockerCmd = process.platform === "win32" ? "docker.exe" : "docker";
21745
- const dockerDown = Bun.spawnSync([dockerCmd, "compose", "down"], { cwd });
21746
- if (dockerDown.exitCode === 0) {
21747
- stdout.write(isZh ? `\u2502 \u2714 Docker Broker \u5BB9\u5668\u5DF2\u505C\u6B62\u4E26\u6E05\u7406\u3002
21850
+ const composeContext = resolveDockerComposeContext(cwd);
21851
+ if (composeContext) {
21852
+ try {
21853
+ const dockerCmd = process.platform === "win32" ? "docker.exe" : "docker";
21854
+ const dockerDown = Bun.spawnSync([dockerCmd, "compose", "down"], {
21855
+ cwd: composeContext.projectDir
21856
+ });
21857
+ if (dockerDown.exitCode === 0) {
21858
+ stdout.write(isZh ? `\u2502 \u2714 Docker Broker \u5BB9\u5668\u5DF2\u505C\u6B62\u4E26\u6E05\u7406\u3002
21748
21859
  ` : `\u2502 \u2714 Docker Broker container stopped and cleaned up.
21860
+ `);
21861
+ }
21862
+ } catch {}
21863
+ }
21864
+ try {
21865
+ const systemdResult = await uninstallSystemdService();
21866
+ if (systemdResult.removed) {
21867
+ stdout.write(isZh ? `\u2502 \u2714 Systemd \u670D\u52D9 (opencode-gateway.service) \u5DF2\u505C\u7528\u4E26\u79FB\u9664\u3002
21868
+ ` : `\u2502 \u2714 Systemd service (opencode-gateway.service) stopped and removed.
21749
21869
  `);
21750
21870
  }
21751
21871
  } catch {}
@@ -21792,8 +21912,8 @@ async function runInteractiveUninstall(options = {}) {
21792
21912
  }
21793
21913
  const symlinkLocations = [
21794
21914
  join8(cwd, "node_modules", "opencode-telegram-link"),
21795
- join8(homedir3(), ".config", "opencode", "node_modules", "opencode-telegram-link"),
21796
- join8(homedir3(), ".opencode", "node_modules", "opencode-telegram-link")
21915
+ join8(homedir4(), ".config", "opencode", "node_modules", "opencode-telegram-link"),
21916
+ join8(homedir4(), ".opencode", "node_modules", "opencode-telegram-link")
21797
21917
  ];
21798
21918
  for (const symlink of symlinkLocations) {
21799
21919
  try {
@@ -21913,4 +22033,4 @@ export {
21913
22033
  runBroker
21914
22034
  };
21915
22035
 
21916
- //# debugId=4DA37A84D8B98D2764756E2164756E21
22036
+ //# debugId=6769A77FD917233264756E2164756E21