dsh-tg-bridge 1.3.5 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,9 +23,29 @@ blocks.
23
23
  ## Requirements
24
24
 
25
25
  - Node.js ≥ 20
26
+ - DeepSeek Harness `0.2.1-rc.2` or newer in the `0.2.x` line (`>=0.2.1-rc.2 <0.3.0`)
26
27
  - DeepSeek Harness installed (`~/.dsh/profiles/headless`)
27
28
  - A Telegram bot token from [@BotFather](https://t.me/BotFather)
28
29
 
30
+ The supported harness range is declared in `engines.dsh`; that field decides
31
+ compatibility on its own (DSH ignores `peerDependencies` when it is present).
32
+ The range includes an explicit prerelease branch, because a plain `<0.3.0`
33
+ silently excludes `0.2.2-*` builds. A future `0.3.0` needs an explicit bump:
34
+ the bridge relies on the `0.2.x` CLI flags and profile layout.
35
+
36
+ ## Ecosystem
37
+
38
+ The Chinese DSH community maintains the largest plugin catalogs and the
39
+ in-app markets that read them; this bridge follows their packaging rules so it
40
+ can be listed next to them:
41
+
42
+ - [awesome-dsh-plugin](https://awesome-dsh-plugin.com) — bilingual (English /
43
+ 中文) plugin list and the `dsh-plugin-catalog` npm package
44
+ - [dsh-market](https://github.com/dsh-market/dsh-market) — the Chinese-built
45
+ in-app market with one-click install and themes
46
+ - [dsh-find-plugin](https://github.com/awesome-dsh-plugin/dsh-find-plugin) —
47
+ finds a plugin from chat instead of browsing a catalog
48
+
29
49
  ## Languages
30
50
 
31
51
  - Menus (`/` in chat): three sets, `ru` by default, plus `en` and `zh`.
@@ -39,26 +59,26 @@ blocks.
39
59
 
40
60
  One command: your bot token and your numeric id. Nothing to edit by hand.
41
61
 
42
- **Windows (PowerShell)**
62
+ **Any platform with Node.js ≥ 20**
43
63
 
44
- ```powershell
64
+ ```bash
45
65
  npx dsh-tg-bridge@latest init --token 123456789:AA... --user 123456789
46
66
  npx dsh-tg-bridge@latest setup
47
67
  ```
48
68
 
49
- **macOS / Linux**
69
+ `init` writes the token, your id and the working directory; `setup` registers
70
+ autostart and starts the bridge.
71
+
72
+ **As a DSH plugin** (the package carries a `dsh.bundle`; the catalog entry is
73
+ pending review):
50
74
 
51
75
  ```bash
52
- DSH_TG_TOKEN=123456789:AA... DSH_TG_USER=123456789 \
53
- bash -c "$(curl -fsSL https://raw.githubusercontent.com/aleksvega/dsh-tg-bridge/main/install.sh)"
76
+ dsh plugin --profile desktop add dsh-tg-bridge
54
77
  ```
55
78
 
56
- **Windows, one line** (same thing, installs and starts):
57
-
58
- ```powershell
59
- $env:DSH_TG_TOKEN='123456789:AA...'; $env:DSH_TG_USER='123456789'
60
- irm https://raw.githubusercontent.com/aleksvega/dsh-tg-bridge/main/install.ps1 | iex
61
- ```
79
+ The plugin starts one bridge when DSH boots and stops it on shutdown; if a
80
+ bridge is already running (for example from the scheduled task), it leaves it
81
+ alone. Run `npx dsh-tg-bridge@latest init ...` first so a config exists.
62
82
 
63
83
  What `init` does:
64
84
 
@@ -90,17 +110,17 @@ in Russian, English and Chinese at once. The full settings table, permissions, c
90
110
  common errors are in [docs/TELEGRAM-SETUP.md](docs/TELEGRAM-SETUP.md).
91
111
 
92
112
  **Autostart** is installed by the setup: `launchd` on macOS, `systemd --user` on
93
- Linux. On Windows `setup` registers the `DSH-TG-Bridge` task and a one-minute watchdog
94
- `DSH-Bridge-Kick` that brings the bridge back after a crash.
113
+ Linux. On Windows `setup` registers the `DSH-TG-Bridge` task, which runs the
114
+ guarded supervisor (the same launcher on logon and every five minutes) so a
115
+ crashed bridge comes back without a second poller.
95
116
 
96
117
  **Manual install** (if you want full control):
97
118
 
98
119
  ```bash
99
- git clone https://github.com/aleksvega/dsh-tg-bridge.git ~/.dsh/tg-bridge
100
- cd ~/.dsh/tg-bridge
101
- node cli.mjs init --token 123456789:AA... --user 123456789
102
- node cli.mjs check # report only, changes nothing
103
- node cli.mjs setup # stop the old bridge and start the new one
120
+ npm install -g dsh-tg-bridge
121
+ dsh-tg init --token 123456789:AA... --user 123456789
122
+ dsh-tg check # report only, changes nothing
123
+ dsh-tg setup # stop the old bridge and start the new one
104
124
  ```
105
125
 
106
126
  If the bridge is already installed, `setup` restarts it on the new code and reinstalls
@@ -0,0 +1,6 @@
1
+ # DSH bundle: load the Telegram bridge with the harness. The plugin starts one
2
+ # bridge process (skipped when a bridge is already running) and stops it on
3
+ # shutdown. Install with: dsh plugin --profile desktop add dsh-tg-bridge
4
+ - insert:
5
+ - id: tg-bridge
6
+ name: dsh-tg-bridge
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "dsh-tg-bridge",
3
- "version": "1.3.5",
3
+ "version": "1.4.1",
4
4
  "description": "Telegram bridge for DeepSeek Harness: text, voice, photos and project-per-topic sessions without opening the desktop app",
5
5
  "type": "module",
6
- "main": "bridge.mjs",
6
+ "main": "plugin-bridge.mjs",
7
7
  "bin": {
8
8
  "dsh-tg": "./cli.mjs"
9
9
  },
@@ -16,7 +16,17 @@
16
16
  "test": "node --test"
17
17
  },
18
18
  "engines": {
19
- "node": ">=20"
19
+ "node": ">=20",
20
+ "dsh": ">=0.2.1-rc.2 <0.3.0-0 || >=0.2.2-rc.1 <0.3.0-0"
21
+ },
22
+ "dsh": {
23
+ "bundle": {
24
+ "patch": "./cordis.patch.yml"
25
+ }
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/aleksvega/dsh-tg-bridge.git"
20
30
  },
21
31
  "keywords": [
22
32
  "deepseek-harness",
@@ -31,6 +41,8 @@
31
41
  "dependencies": {},
32
42
  "files": [
33
43
  "bridge.mjs",
44
+ "plugin-bridge.mjs",
45
+ "cordis.patch.yml",
34
46
  "control.mjs",
35
47
  "i18n.mjs",
36
48
  "prices.mjs",
@@ -0,0 +1,83 @@
1
+ // DSH plugin entry: run the Telegram bridge alongside the harness.
2
+ //
3
+ // The bridge is a separate long-polling process, not a tool inside the agent,
4
+ // so the plugin starts it once when the host boots and stops it on shutdown.
5
+ // It never starts a second copy: a live `bridge.pid` wins. That keeps the
6
+ // scheduled-task install and this plugin from polling the same bot twice.
7
+ import { spawn } from 'node:child_process';
8
+ import { closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { dirname, join } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ export const name = 'tg-bridge';
14
+ export const inject = [];
15
+
16
+ const HERE = dirname(fileURLToPath(import.meta.url));
17
+ const DSH_HOME = process.env.DSH_HOME || join(homedir(), '.dsh');
18
+ const DIR = join(DSH_HOME, 'tg-bridge');
19
+
20
+ function alive(pid) {
21
+ try {
22
+ process.kill(pid, 0);
23
+ return true;
24
+ } catch (error) {
25
+ return error.code === 'EPERM';
26
+ }
27
+ }
28
+
29
+ function runningPid() {
30
+ try {
31
+ const pid = Number(readFileSync(join(DIR, 'bridge.pid'), 'utf8').trim());
32
+ return pid > 0 && alive(pid) ? pid : 0;
33
+ } catch {
34
+ return 0;
35
+ }
36
+ }
37
+
38
+ export function apply(ctx, config = {}) {
39
+ if (config.autoStart === false) return;
40
+ // Nothing to start until `dsh-tg init` wrote a token and a working directory.
41
+ if (!existsSync(join(DIR, 'config.json'))) return;
42
+ if (runningPid()) return;
43
+
44
+ // Two profiles loading at once must not both start a poller.
45
+ const lock = join(DIR, 'plugin-start.lock');
46
+ try {
47
+ const fd = openSync(lock, 'wx');
48
+ writeFileSync(fd, String(process.pid));
49
+ closeSync(fd);
50
+ } catch {
51
+ return;
52
+ }
53
+
54
+ let child = null;
55
+ try {
56
+ child = spawn(process.execPath, [join(HERE, 'bridge.mjs')], {
57
+ cwd: DIR,
58
+ detached: true,
59
+ stdio: 'ignore',
60
+ windowsHide: true,
61
+ env: {
62
+ ...process.env,
63
+ // Inside DSH desktop, process.execPath is Electron; this makes it run
64
+ // as Node without opening a window.
65
+ ...(process.versions.electron ? { ELECTRON_RUN_AS_NODE: '1' } : {}),
66
+ },
67
+ });
68
+ child.unref();
69
+ } catch {
70
+ child = null;
71
+ } finally {
72
+ try {
73
+ unlinkSync(lock);
74
+ } catch {}
75
+ }
76
+
77
+ if (!child?.pid || typeof ctx.on !== 'function') return;
78
+ ctx.on('dispose', () => {
79
+ try {
80
+ if (alive(child.pid)) process.kill(child.pid);
81
+ } catch {}
82
+ });
83
+ }