herdr-remote 0.2.0
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 +21 -0
- package/README.md +155 -0
- package/bin/herdr-remote.js +225 -0
- package/config.example.json +19 -0
- package/dist/tui.mjs +3982 -0
- package/herdr-plugin.toml +61 -0
- package/package.json +60 -0
- package/src/config.js +411 -0
- package/src/exit-codes.js +14 -0
- package/src/herdr-command.js +12 -0
- package/src/herdr-plugin.js +122 -0
- package/src/host-connector.js +280 -0
- package/src/i18n/en.js +219 -0
- package/src/i18n/index.js +56 -0
- package/src/i18n/zh.js +218 -0
- package/src/keepalive.js +404 -0
- package/src/lifecycle.js +58 -0
- package/src/net-interfaces.js +76 -0
- package/src/pty-session.js +92 -0
- package/src/service.js +492 -0
- package/src/settings-model.js +265 -0
- package/src/socket-discovery.js +55 -0
- package/src/state.js +47 -0
- package/src/supervisor.js +242 -0
- package/src/terminal-palette.js +325 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dibin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# herdr-remote
|
|
2
|
+
|
|
3
|
+
Use your [Herdr](https://herdr.dev) terminal workspaces from a phone or any
|
|
4
|
+
browser. A mobile-first web terminal with low-latency ANSI streaming, one-time
|
|
5
|
+
pairing codes, and a bilingual configuration TUI.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g herdr-remote
|
|
9
|
+
herdr-remote
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That opens the setup wizard: pick a language, pick how you want to reach the
|
|
13
|
+
machine, and it starts the services for you.
|
|
14
|
+
|
|
15
|
+
## Two packages
|
|
16
|
+
|
|
17
|
+
| Package | Runs on | Contains |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **`herdr-remote`** | your workstation | Herdr plugin, host connector, configuration TUI |
|
|
20
|
+
| **`herdr-remote-relay`** | wherever you want | the relay and the web terminal |
|
|
21
|
+
|
|
22
|
+
The relay is completely separate: its only dependency is `ws`, so a server
|
|
23
|
+
running one needs no compiler, no Herdr and no plugin. You do not have to
|
|
24
|
+
install it yourself — `herdr-remote` starts one locally unless you point it at
|
|
25
|
+
your own.
|
|
26
|
+
|
|
27
|
+
## Three ways to connect
|
|
28
|
+
|
|
29
|
+
| Mode | Who can reach it | Needs a server? |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| **This machine only** *(default)* | a browser on the workstation | no |
|
|
32
|
+
| **Local network / Tailscale** | phones on your LAN or tailnet | no |
|
|
33
|
+
| **Self-hosted relay** | anywhere, over the internet | yes — see [`herdr-remote-relay`](https://www.npmjs.com/package/herdr-remote-relay) |
|
|
34
|
+
|
|
35
|
+
Without a relay configured, the web UI is served from a local address only.
|
|
36
|
+
This already covers being away from home if you run Tailscale or WireGuard.
|
|
37
|
+
Only a self-hosted relay opens access from outside your network.
|
|
38
|
+
|
|
39
|
+
## The TUI
|
|
40
|
+
|
|
41
|
+
`herdr-remote` with no arguments opens the interface. It is bilingual —
|
|
42
|
+
Chinese or English, following `$LANG` unless you choose otherwise — and works
|
|
43
|
+
with the keyboard or the mouse.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
Herdr Remote Remote browser access to your Herdr workspaces
|
|
47
|
+
|
|
48
|
+
1 Overview 2 Pair a device 3 Services 4 Relay 5 Keep-alive 6 Herdr 7 Language & about
|
|
49
|
+
╭──────────────────────────────────────────────────────────────────────╮
|
|
50
|
+
│ Status │
|
|
51
|
+
│ │
|
|
52
|
+
│ Access mode This machine only │
|
|
53
|
+
│ Relay ● local, 127.0.0.1:8787 pid 1239815 │
|
|
54
|
+
│ Host connector ● running pid 1239816 │
|
|
55
|
+
│ Herdr socket ● /home/you/.config/herdr/herdr.sock │
|
|
56
|
+
│ Web UI http://127.0.0.1:8787 │
|
|
57
|
+
│ Keep-alive ● systemd — running │
|
|
58
|
+
│ │
|
|
59
|
+
│ Workstations 1 │
|
|
60
|
+
│ Browsers 0 │
|
|
61
|
+
│ Relay uptime 4m 2s │
|
|
62
|
+
╰──────────────────────────────────────────────────────────────────────╯
|
|
63
|
+
↑↓ move · ↵ select · ← → switch tab · m mouse off · q quit
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| Tab | |
|
|
67
|
+
|---|---|
|
|
68
|
+
| **Overview** | live service, socket and keep-alive status |
|
|
69
|
+
| **Pair a device** | one-time code with a QR you can scan |
|
|
70
|
+
| **Services** | start, stop, restart, recent logs |
|
|
71
|
+
| **Relay** | access mode, port, listen address, browser access address, relay URL and password |
|
|
72
|
+
| **Keep-alive** | install or remove the background service |
|
|
73
|
+
| **Herdr** | socket path, arguments, plugin registration |
|
|
74
|
+
| **Language & about** | Chinese / English / follow the system |
|
|
75
|
+
|
|
76
|
+
Keys: `↑↓` move · `↵` select or edit · `←→` or `1`–`7` switch tab · `s` save ·
|
|
77
|
+
`r` refresh · `m` toggle mouse · `q` quit.
|
|
78
|
+
|
|
79
|
+
Mouse tracking turns itself on where the terminal supports it. It takes over
|
|
80
|
+
text selection while active, so press `m` when you want to copy something.
|
|
81
|
+
|
|
82
|
+
## Pairing
|
|
83
|
+
|
|
84
|
+
Open **Pair a device** and press Enter. Scan the QR code, or enter the six
|
|
85
|
+
characters on the page. The browser gets a long-lived token; the code is burned
|
|
86
|
+
on first use and expires after ten minutes.
|
|
87
|
+
|
|
88
|
+
## Keeping it running
|
|
89
|
+
|
|
90
|
+
The **Keep-alive** tab installs a service that starts the relay and host
|
|
91
|
+
connector at login and restarts them if they die — a systemd user unit on
|
|
92
|
+
Linux, a LaunchAgent on macOS, and a supervised background process where
|
|
93
|
+
neither is available.
|
|
94
|
+
|
|
95
|
+
On Linux, "Enable start at boot" runs `loginctl enable-linger` so it also comes
|
|
96
|
+
up before you log in.
|
|
97
|
+
|
|
98
|
+
## Command line
|
|
99
|
+
|
|
100
|
+
Everything the TUI does is scriptable:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
herdr-remote start | stop | restart
|
|
104
|
+
herdr-remote status [--json]
|
|
105
|
+
herdr-remote pair [--json]
|
|
106
|
+
herdr-remote url
|
|
107
|
+
herdr-remote keepalive install | uninstall | restart | status
|
|
108
|
+
herdr-remote plugin link | unlink | status
|
|
109
|
+
herdr-remote --lang zh|en
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Herdr plugin
|
|
113
|
+
|
|
114
|
+
The package is also a Herdr plugin. Register it from the **Herdr** tab, or:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
herdr-remote plugin link
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Nothing is compiled at registration time — the npm package ships prebuilt.
|
|
121
|
+
|
|
122
|
+
> Upgrading from a source checkout? Unlink the old one first:
|
|
123
|
+
> `herdr plugin unlink herdr.remote.web`. Your settings are migrated
|
|
124
|
+
> automatically on first run.
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
`~/.config/herdr-remote/config.json`, edited through the TUI:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"ui": { "language": "auto" },
|
|
133
|
+
"relay": { "mode": "local", "port": 8787, "lanHost": "", "publicUrl": "", "remoteUrl": "" },
|
|
134
|
+
"herdr": { "socketPath": null, "args": [] },
|
|
135
|
+
"keepalive": { "manager": "auto" }
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Secrets are never stored here. The workstation's host token and the relay
|
|
140
|
+
password live in `~/.local/state/herdr-remote/runtime.json`, mode 0600.
|
|
141
|
+
|
|
142
|
+
## Security
|
|
143
|
+
|
|
144
|
+
- The relay never runs a shell and never sees your Herdr socket; only the host
|
|
145
|
+
connector on your machine does.
|
|
146
|
+
- Tokens are stored as SHA-256 hashes. Terminal content is never written to disk.
|
|
147
|
+
- Pairing codes are single-use, expire in ten minutes, and are rate limited.
|
|
148
|
+
- Secrets never appear in URLs or shell history; the web UI strips them from the
|
|
149
|
+
address bar after pairing.
|
|
150
|
+
- One writable controller at a time, with explicit takeover; other devices watch
|
|
151
|
+
read-only.
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// herdr-remote command line entry point.
|
|
5
|
+
//
|
|
6
|
+
// With no arguments (and a real terminal) this opens the configuration TUI;
|
|
7
|
+
// the subcommands exist for scripting and for the actions declared in
|
|
8
|
+
// herdr-plugin.toml, which Herdr runs without a terminal attached.
|
|
9
|
+
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { pathToFileURL } = require('node:url');
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
LANGUAGES,
|
|
16
|
+
configPath,
|
|
17
|
+
loadConfig,
|
|
18
|
+
migrateLegacyConfig,
|
|
19
|
+
resolvePublicUrl,
|
|
20
|
+
stateDir,
|
|
21
|
+
} = require('../src/config');
|
|
22
|
+
const { createTranslator, detectLocale } = require('../src/i18n');
|
|
23
|
+
const { preferredLanAddress } = require('../src/net-interfaces');
|
|
24
|
+
|
|
25
|
+
const VERSION = require('../package.json').version;
|
|
26
|
+
|
|
27
|
+
const USAGE = `herdr-remote ${VERSION} — remote browser access to your Herdr workspaces
|
|
28
|
+
|
|
29
|
+
Usage: herdr-remote [command] [options]
|
|
30
|
+
|
|
31
|
+
Commands:
|
|
32
|
+
(none) Open the configuration TUI
|
|
33
|
+
start Start the relay and host connector
|
|
34
|
+
stop Stop them
|
|
35
|
+
restart Restart them
|
|
36
|
+
status [--json] Show service status
|
|
37
|
+
pair [--json] Create a one-time pairing code
|
|
38
|
+
url Print the URL to open in a browser
|
|
39
|
+
run [--daemon] Run the services in the foreground (used by keep-alive)
|
|
40
|
+
keepalive <action> install | uninstall | restart | status
|
|
41
|
+
plugin <action> link | unlink | status (Herdr plugin registration)
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
--lang <zh|en> Force the interface language
|
|
45
|
+
--json Machine readable output where supported
|
|
46
|
+
-h, --help Show this help
|
|
47
|
+
-v, --version Show the version
|
|
48
|
+
|
|
49
|
+
Self-hosting a relay: docs/self-hosted-relay.md
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
function parseArgs(argv) {
|
|
53
|
+
const positional = [];
|
|
54
|
+
const flags = {};
|
|
55
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
56
|
+
const arg = argv[index];
|
|
57
|
+
if (arg === '--json') { flags.json = true; continue; }
|
|
58
|
+
if (arg === '--daemon') { flags.daemon = true; continue; }
|
|
59
|
+
if (arg === '--help' || arg === '-h') { flags.help = true; continue; }
|
|
60
|
+
if (arg === '--version' || arg === '-v') { flags.version = true; continue; }
|
|
61
|
+
if (arg === '--lang') { flags.lang = argv[index + 1]; index += 1; continue; }
|
|
62
|
+
if (arg.startsWith('--lang=')) { flags.lang = arg.slice('--lang='.length); continue; }
|
|
63
|
+
if (arg.startsWith('-')) { flags.unknown = arg; continue; }
|
|
64
|
+
positional.push(arg);
|
|
65
|
+
}
|
|
66
|
+
return { positional, flags };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function printJson(value) {
|
|
70
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function describeStatus(status, t) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
lines.push(`${t('overview.mode')}: ${t(`mode.${status.mode}`)}`);
|
|
76
|
+
if (status.relay.local) {
|
|
77
|
+
lines.push(`${t('overview.relay')}: ${status.relay.alive ? t('common.running') : t('common.stopped')} (${status.relay.bind}:${status.relay.port})`);
|
|
78
|
+
} else {
|
|
79
|
+
lines.push(`${t('overview.relay')}: ${status.relay.remoteUrl}`);
|
|
80
|
+
}
|
|
81
|
+
lines.push(`${t('overview.host')}: ${status.host.alive ? t('common.running') : t('common.stopped')}`);
|
|
82
|
+
lines.push(`${t('overview.socket')}: ${status.host.socketPath}${status.host.socketExists ? '' : ` (${t('overview.socketMissing')})`}`);
|
|
83
|
+
lines.push(`${t('overview.webUrl')}: ${status.publicUrl}`);
|
|
84
|
+
if (status.keepalive) {
|
|
85
|
+
const state = status.keepalive.active ? t('common.running') : status.keepalive.installed ? t('common.installed') : t('common.notInstalled');
|
|
86
|
+
lines.push(`${t('overview.keepalive')}: ${status.keepalive.manager} — ${state}`);
|
|
87
|
+
}
|
|
88
|
+
if (status.relay.health && status.relay.health.ok === false) {
|
|
89
|
+
lines.push(`${t('overview.relay')}: ${t('overview.unreachable', { message: status.relay.health.message })}`);
|
|
90
|
+
}
|
|
91
|
+
return lines.join('\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function runTui(options) {
|
|
95
|
+
// The TUI is an ESM bundle; import() keeps this entry point loadable on any
|
|
96
|
+
// Node 22 without relying on require(esm).
|
|
97
|
+
const bundle = path.join(__dirname, '..', 'dist', 'tui.mjs');
|
|
98
|
+
if (!fs.existsSync(bundle)) {
|
|
99
|
+
process.stderr.write(
|
|
100
|
+
'herdr-remote: the TUI bundle is missing.\n'
|
|
101
|
+
+ 'If you are running from a source checkout, build it first:\n'
|
|
102
|
+
+ ' npm run build -w herdr-remote\n',
|
|
103
|
+
);
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const module = await import(pathToFileURL(bundle).href);
|
|
108
|
+
await module.startTui(options);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function main(argv = process.argv.slice(2)) {
|
|
112
|
+
const { positional, flags } = parseArgs(argv);
|
|
113
|
+
const command = positional[0] || null;
|
|
114
|
+
|
|
115
|
+
migrateLegacyConfig();
|
|
116
|
+
|
|
117
|
+
// Ask the terminal for its colors while it is still a plain terminal: the
|
|
118
|
+
// TUI takes the screen a few lines below, and the services this command
|
|
119
|
+
// starts usually run detached with nothing to ask. Everything downstream
|
|
120
|
+
// inherits the answer through the environment.
|
|
121
|
+
require('../src/terminal-palette').captureTerminalPalette();
|
|
122
|
+
|
|
123
|
+
const config = loadConfig();
|
|
124
|
+
const preference = flags.lang && LANGUAGES.includes(flags.lang) ? flags.lang : config.ui.language;
|
|
125
|
+
const t = createTranslator(detectLocale({ preference }));
|
|
126
|
+
|
|
127
|
+
if (flags.help) { process.stdout.write(USAGE); return; }
|
|
128
|
+
if (flags.version) { process.stdout.write(`${VERSION}\n`); return; }
|
|
129
|
+
|
|
130
|
+
// Lazily required so that `--help` and the TUI do not pay for loading the
|
|
131
|
+
// service layer (which pulls in the relay package and node-pty).
|
|
132
|
+
const lifecycle = require('../src/lifecycle');
|
|
133
|
+
const service = require('../src/service');
|
|
134
|
+
|
|
135
|
+
switch (command) {
|
|
136
|
+
case null: {
|
|
137
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
138
|
+
await runTui({ language: flags.lang || null });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Herdr runs plugin panes and actions without a TTY; emit status instead
|
|
142
|
+
// of trying to paint an interface into a pipe.
|
|
143
|
+
printJson(await lifecycle.fullStatus(config));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
case 'start': {
|
|
147
|
+
const result = lifecycle.startAll(config);
|
|
148
|
+
if (flags.json) printJson(result);
|
|
149
|
+
else process.stdout.write(`${t('services.started')}\n${describeStatus(await lifecycle.fullStatus(config), t)}\n`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
case 'stop': {
|
|
153
|
+
const result = lifecycle.stopAll(config);
|
|
154
|
+
if (flags.json) printJson(result);
|
|
155
|
+
else process.stdout.write(`${t('services.stopped')}\n`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
case 'restart': {
|
|
159
|
+
const result = lifecycle.restartAll(config);
|
|
160
|
+
if (flags.json) printJson(result);
|
|
161
|
+
else process.stdout.write(`${t('services.restarted')}\n`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
case 'status': {
|
|
165
|
+
const status = await lifecycle.fullStatus(config);
|
|
166
|
+
if (flags.json) printJson(status);
|
|
167
|
+
else process.stdout.write(`${describeStatus(status, t)}\n`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
case 'pair': {
|
|
171
|
+
const pairing = await service.pair();
|
|
172
|
+
if (flags.json) { printJson(pairing); return; }
|
|
173
|
+
const code = service.extractPairingCode(pairing);
|
|
174
|
+
const url = pairing.pairUrl || `${resolvePublicUrl(config, preferredLanAddress())}/?pairCode=${encodeURIComponent(code)}`;
|
|
175
|
+
const minutes = `${Math.round((config.auth.pairingTtlMs || 600000) / 60000)}m`;
|
|
176
|
+
process.stdout.write(`${t('pair.code')}: ${code}\n${t('pair.url')}: ${url}\n${t('pair.expires', { minutes, time: new Date(pairing.expiresAt).toLocaleTimeString() })}\n`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
case 'url': {
|
|
180
|
+
process.stdout.write(`${resolvePublicUrl(config, preferredLanAddress())}\n`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
case 'run': {
|
|
184
|
+
const { runForeground } = require('../src/supervisor');
|
|
185
|
+
await runForeground({ logToFiles: Boolean(flags.daemon) });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
case 'keepalive': {
|
|
189
|
+
const keepalive = require('../src/keepalive');
|
|
190
|
+
const action = positional[1] || 'status';
|
|
191
|
+
if (action === 'install') printJson(keepalive.install(config));
|
|
192
|
+
else if (action === 'uninstall') printJson(keepalive.uninstall(config));
|
|
193
|
+
else if (action === 'restart') printJson(keepalive.restart(config));
|
|
194
|
+
else if (action === 'status') printJson(keepalive.status(config));
|
|
195
|
+
else throw new Error(`unknown keepalive action: ${action}`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
case 'plugin': {
|
|
199
|
+
const plugin = require('../src/herdr-plugin');
|
|
200
|
+
const action = positional[1] || 'status';
|
|
201
|
+
if (action === 'link') printJson(plugin.register());
|
|
202
|
+
else if (action === 'unlink') printJson(plugin.unregister());
|
|
203
|
+
else if (action === 'status') printJson(plugin.registrationStatus());
|
|
204
|
+
else throw new Error(`unknown plugin action: ${action}`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
case 'config': {
|
|
208
|
+
// Handy when reporting a problem: where does this install read from?
|
|
209
|
+
printJson({ configPath: configPath(), stateDir: stateDir(), config });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
default:
|
|
213
|
+
process.stderr.write(`herdr-remote: unknown command "${command}"\n\n${USAGE}`);
|
|
214
|
+
process.exitCode = 2;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (require.main === module) {
|
|
219
|
+
main().catch((error) => {
|
|
220
|
+
process.stderr.write(`herdr-remote: ${error.message}\n`);
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = { main, parseArgs, describeStatus, USAGE, VERSION };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ui": {
|
|
3
|
+
"language": "auto"
|
|
4
|
+
},
|
|
5
|
+
"relay": {
|
|
6
|
+
"mode": "local",
|
|
7
|
+
"port": 8787,
|
|
8
|
+
"lanHost": "",
|
|
9
|
+
"publicUrl": "",
|
|
10
|
+
"remoteUrl": ""
|
|
11
|
+
},
|
|
12
|
+
"herdr": {
|
|
13
|
+
"socketPath": null,
|
|
14
|
+
"args": []
|
|
15
|
+
},
|
|
16
|
+
"keepalive": {
|
|
17
|
+
"manager": "auto"
|
|
18
|
+
}
|
|
19
|
+
}
|