edge-ai-client-ts 1.0.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/CHANGELOG.md +72 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/bin/ec-ts.js +18 -0
- package/dist/buffer/disk-queue.d.ts +140 -0
- package/dist/buffer/disk-queue.js +370 -0
- package/dist/cli/devices.d.ts +1 -0
- package/dist/cli/devices.js +61 -0
- package/dist/cli/enroll.d.ts +2 -0
- package/dist/cli/enroll.js +89 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/messages.d.ts +1 -0
- package/dist/cli/messages.js +59 -0
- package/dist/cli/run.d.ts +5 -0
- package/dist/cli/run.js +112 -0
- package/dist/cli/status.d.ts +1 -0
- package/dist/cli/status.js +56 -0
- package/dist/cli/whoami.d.ts +2 -0
- package/dist/cli/whoami.js +41 -0
- package/dist/config/cmd-gate.d.ts +65 -0
- package/dist/config/cmd-gate.js +128 -0
- package/dist/config/settings.d.ts +209 -0
- package/dist/config/settings.js +627 -0
- package/dist/crypto/aes-gcm.d.ts +38 -0
- package/dist/crypto/aes-gcm.js +90 -0
- package/dist/crypto/hmac.d.ts +31 -0
- package/dist/crypto/hmac.js +52 -0
- package/dist/crypto/tls-guard.d.ts +36 -0
- package/dist/crypto/tls-guard.js +54 -0
- package/dist/daemon/manager.d.ts +82 -0
- package/dist/daemon/manager.js +461 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +63 -0
- package/dist/logging/file-logger.d.ts +21 -0
- package/dist/logging/file-logger.js +71 -0
- package/dist/network/ws-client.d.ts +221 -0
- package/dist/network/ws-client.js +1134 -0
- package/dist/session/fail-fast.d.ts +70 -0
- package/dist/session/fail-fast.js +122 -0
- package/dist/session/manager.d.ts +136 -0
- package/dist/session/manager.js +291 -0
- package/dist/session/persistence.d.ts +103 -0
- package/dist/session/persistence.js +194 -0
- package/dist/session/pi-rpc.d.ts +164 -0
- package/dist/session/pi-rpc.js +412 -0
- package/dist/session/sftp.d.ts +64 -0
- package/dist/session/sftp.js +335 -0
- package/dist/session/shell-frame.d.ts +77 -0
- package/dist/session/shell-frame.js +199 -0
- package/dist/session/shell.d.ts +124 -0
- package/dist/session/shell.js +300 -0
- package/docs/CONFIGURATION.md +169 -0
- package/docs/INSTALLATION.md +164 -0
- package/docs/PROTOCOL.md +248 -0
- package/docs/TROUBLESHOOTING.md +177 -0
- package/package.json +79 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
This guide covers installing `edge-ai-client-ts` on Linux, macOS, and
|
|
4
|
+
Windows for **end users** (running the client) and for **contributors**
|
|
5
|
+
(working on the package itself).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## End-user install
|
|
10
|
+
|
|
11
|
+
### 1. Prerequisites
|
|
12
|
+
|
|
13
|
+
- **Node.js >= 20** (we use `crypto.randomUUID`, native `fetch`, modern ESM).
|
|
14
|
+
Check with:
|
|
15
|
+
```bash
|
|
16
|
+
node --version # must print v20.x or newer
|
|
17
|
+
```
|
|
18
|
+
If you need to install or upgrade Node, use [nvm](https://github.com/nvm-sh/nvm)
|
|
19
|
+
(Linux/macOS) or [fnm](https://github.com/Schniz/fnm) (cross-platform).
|
|
20
|
+
|
|
21
|
+
- A **C/C++ toolchain** is only required if `node-pty` does not ship a
|
|
22
|
+
prebuilt binary for your platform. Most users on x86_64 / arm64 Linux,
|
|
23
|
+
x86_64 / arm64 macOS, and x86_64 Windows do **not** need it.
|
|
24
|
+
|
|
25
|
+
### 2. Install via npm
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g edge-ai-client-ts
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Verify the binary is on `PATH`:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
ec-ts --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Configure secrets (NEVER commit them)
|
|
38
|
+
|
|
39
|
+
Set the fleet shared secret and any optional pins via environment
|
|
40
|
+
variables. **Do not paste these into shell history** — use a secrets
|
|
41
|
+
manager or a `.env` file with restrictive permissions.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Linux/macOS — current shell only
|
|
45
|
+
export EDGE_AUTH_TOKEN="<your-fleet-shared-secret>"
|
|
46
|
+
|
|
47
|
+
# Optional: pin the relay's TLS certificate (extract via the openssl
|
|
48
|
+
# one-liner in the upstream docs)
|
|
49
|
+
# export EDGE_RELAY_FINGERPRINT="<sha256:base64>"
|
|
50
|
+
|
|
51
|
+
# Optional: enable offline buffer encryption (32 bytes = 64 hex chars)
|
|
52
|
+
# export EDGE_BUFFER_KEY="<64 hex chars>"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Persist across shells by adding the exports to `~/.bashrc`,
|
|
56
|
+
`~/.zshrc`, or `~/.config/fish/config.fish`.
|
|
57
|
+
|
|
58
|
+
### 4. Enroll against your relay
|
|
59
|
+
|
|
60
|
+
After creating an operator in the Edge AI Agent dashboard, copy the
|
|
61
|
+
**per-operator API key** (format `opk_<base64>`) and enroll:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
ec-ts enroll --key "<your-operator-api-key>"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This persists the credential to the **OS keychain** (Keychain on macOS,
|
|
68
|
+
Credential Manager on Windows, libsecret/Secret Service on Linux). The
|
|
69
|
+
plain key never touches a config file.
|
|
70
|
+
|
|
71
|
+
### 5. Run the daemon
|
|
72
|
+
|
|
73
|
+
Foreground (foreground is the default; useful for development):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
ec-ts run
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
As a system service (auto-start on boot):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ec-ts run --install-service
|
|
83
|
+
# Uninstall later with:
|
|
84
|
+
ec-ts run --uninstall-service
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The service manager installs:
|
|
88
|
+
|
|
89
|
+
| OS | Service unit |
|
|
90
|
+
|----|--------------|
|
|
91
|
+
| Linux | `~/.config/systemd/user/ec-ts.service` |
|
|
92
|
+
| macOS | `~/Library/LaunchAgents/com.edgeai.ec-ts.plist` |
|
|
93
|
+
| Windows | `\LeastPrivilege` scheduled task |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Contributor install
|
|
98
|
+
|
|
99
|
+
### 1. Clone the monorepo
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git clone https://github.com/baphuongna/edge-ai-agent.git
|
|
103
|
+
cd edge-ai-agent/edge-client-ts
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2. Install dependencies
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm install
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 3. Verify
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm run typecheck # tsc --noEmit
|
|
116
|
+
npm test # vitest run
|
|
117
|
+
npm run build # compile to dist/
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4. End-to-end smoke (optional)
|
|
121
|
+
|
|
122
|
+
The smoke test connects a real daemon to a local `ws-service` and
|
|
123
|
+
verifies register → heartbeat → ack → reconnect. See
|
|
124
|
+
[`e2e-smoke/README.md`](../e2e-smoke/README.md).
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run smoke
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Upgrading
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm install -g edge-ai-client-ts@latest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Configuration and OS-keychain credentials are preserved across upgrades.
|
|
139
|
+
To downgrade, pin the version:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm install -g edge-ai-client-ts@1.0.0
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Uninstall
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Remove the daemon + service
|
|
151
|
+
ec-ts run --uninstall-service
|
|
152
|
+
|
|
153
|
+
# Remove the package
|
|
154
|
+
npm uninstall -g edge-ai-client-ts
|
|
155
|
+
|
|
156
|
+
# Optional: wipe the per-user config directory
|
|
157
|
+
rm -rf ~/.config/edge-ai-agent # Linux
|
|
158
|
+
rm -rf ~/Library/Application\ Support/edge-ai-agent # macOS
|
|
159
|
+
# Windows: %APPDATA%\edge-ai-agent
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
OS-keychain entries live in your platform's standard credential store and
|
|
163
|
+
are removed with your platform's credential manager (do not forget to
|
|
164
|
+
revoke the operator API key in the dashboard as well).
|
package/docs/PROTOCOL.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Wire protocol reference
|
|
2
|
+
|
|
3
|
+
`edge-ai-client-ts` speaks the **exact same wire protocol** as the
|
|
4
|
+
Rust `edge-client` (`ec` binary). This document is the authoritative
|
|
5
|
+
spec; both clients must conform.
|
|
6
|
+
|
|
7
|
+
> If you find a discrepancy between the TS and Rust implementations,
|
|
8
|
+
> that is a bug — open an issue.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Transport
|
|
13
|
+
|
|
14
|
+
- **WebSocket** to the relay's `/edge` path (text + binary frames).
|
|
15
|
+
- **Subprotocol** header: `Sec-WebSocket-Protocol: edgeai-v2, <auth_token>[, device-secret=<value>]`.
|
|
16
|
+
- The `<auth_token>` is the **fleet shared secret** (`EDGE_AUTH_TOKEN`).
|
|
17
|
+
- `device-secret=<value>` is sent when the device has been issued a
|
|
18
|
+
per-machine secret (post-enrollment).
|
|
19
|
+
- **TLS** (recommended for production):
|
|
20
|
+
- `wss://` only — `ws://` is refused when `EDGEAI_REQUIRE_TLS=1` or
|
|
21
|
+
`NODE_ENV=production`.
|
|
22
|
+
- Optional **SPKI fingerprint pin** (SHA-256 hex of the DER
|
|
23
|
+
`SubjectPublicKeyInfo`). Pin mismatch → handshake aborted.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## `/edge` JSON connection
|
|
28
|
+
|
|
29
|
+
All messages are JSON, UTF-8 text frames.
|
|
30
|
+
|
|
31
|
+
### Client → Server
|
|
32
|
+
|
|
33
|
+
#### `register`
|
|
34
|
+
|
|
35
|
+
First message after upgrade.
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"type": "register",
|
|
40
|
+
"machine_id": "<uuid-v4>",
|
|
41
|
+
"agent_id": "worker_default",
|
|
42
|
+
"platform": "linux",
|
|
43
|
+
"hostname": "<short-hostname>",
|
|
44
|
+
"agent_version": "0.1.0",
|
|
45
|
+
"operator_id": "op_<your-operator-id>"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Field | Required | Notes |
|
|
50
|
+
|-------|----------|-------|
|
|
51
|
+
| `machine_id` | yes | Stable per-machine UUID. The relay uses this as the device PK. |
|
|
52
|
+
| `agent_id` | yes | Default agent id for dispatch when the dashboard does not pin one. |
|
|
53
|
+
| `platform` | no | `linux` / `darwin` / `win32`. |
|
|
54
|
+
| `hostname` | no | Display only; relay stores on `devices.display_name`. |
|
|
55
|
+
| `agent_version` | no | Display only. |
|
|
56
|
+
| `operator_id` | no | When set, the relay auto-claims ownership on first heartbeat. |
|
|
57
|
+
|
|
58
|
+
#### `heartbeat`
|
|
59
|
+
|
|
60
|
+
Every `heartbeat_interval_s` seconds (default 30).
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"type": "heartbeat",
|
|
65
|
+
"machine_id": "<uuid-v4>",
|
|
66
|
+
"operator_id": "op_<your-operator-id>",
|
|
67
|
+
"system": {
|
|
68
|
+
"cpu_cores": 8,
|
|
69
|
+
"cpu_usage_pct": 12.4,
|
|
70
|
+
"mem_total_mb": 16384,
|
|
71
|
+
"mem_available_mb": 9216,
|
|
72
|
+
"disk_total_gb": 512.0,
|
|
73
|
+
"disk_free_gb": 320.5,
|
|
74
|
+
"load_avg_1m": 0.42,
|
|
75
|
+
"process_count": 312
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### `ack`
|
|
81
|
+
|
|
82
|
+
Acknowledge receipt of a `stream` message from the relay.
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{ "type": "ack", "msg_id": "<relay-msg-id>" }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### `stream` (rare)
|
|
89
|
+
|
|
90
|
+
The TS client sends `stream` messages from the offline buffer drain —
|
|
91
|
+
they have the same shape as relay → client `stream` messages.
|
|
92
|
+
|
|
93
|
+
#### `execute_response` / `agent_list` / `browser_fetch_result`
|
|
94
|
+
|
|
95
|
+
Server-originated RPC responses, forwarded back to the dashboard.
|
|
96
|
+
|
|
97
|
+
### Server → Client
|
|
98
|
+
|
|
99
|
+
#### `registered`
|
|
100
|
+
|
|
101
|
+
Sent once on successful registration.
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{ "type": "registered", "machine_id": "<uuid-v4>", "agent_id": "worker_default" }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If the relay just minted a per-device secret, the next message is
|
|
108
|
+
`device_secret_issued` (one-time).
|
|
109
|
+
|
|
110
|
+
#### `device_secret_issued`
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"type": "device_secret_issued",
|
|
115
|
+
"secret": "<base64-32-bytes>",
|
|
116
|
+
"device_id": "<uuid-v4>"
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### `stream`
|
|
121
|
+
|
|
122
|
+
A message destined for the local agent.
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"type": "stream",
|
|
127
|
+
"msg_id": "<uuid-v4>",
|
|
128
|
+
"machine_id": "<uuid-v4>",
|
|
129
|
+
"agent_id": "worker_default",
|
|
130
|
+
"payload": { "...": "agent-specific JSON" },
|
|
131
|
+
"timestamp": 1721000000000
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The TS client forwards `payload` to the local `agent_id` Pi process and
|
|
136
|
+
replies with `ack`.
|
|
137
|
+
|
|
138
|
+
#### `set_config`
|
|
139
|
+
|
|
140
|
+
Live config push from the relay.
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"type": "set_config",
|
|
145
|
+
"heartbeat_interval_s": 30,
|
|
146
|
+
"network_mode": "lan"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### `execute` / `list_agents` / `get_available_models` / `browser_fetch`
|
|
151
|
+
|
|
152
|
+
RPC commands from the dashboard. The TS client spawns the relevant
|
|
153
|
+
handler and replies via the buffer queue.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## `/shell-edge` binary channel
|
|
158
|
+
|
|
159
|
+
After `/edge` registration succeeds, the TS client opens a second
|
|
160
|
+
WebSocket to `/shell-edge`. This channel carries **binary frames** for
|
|
161
|
+
shell sessions — byte-exact with the Rust client.
|
|
162
|
+
|
|
163
|
+
### Frame layout
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
+----------------+----------------+----------+----------------+
|
|
167
|
+
| session_id | FrameType | payload_len (u32 BE) | payload |
|
|
168
|
+
| 16 bytes (UUID)| 1 byte | 4 bytes | N bytes |
|
|
169
|
+
+----------------+----------------+----------------------+------------+
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Total header size: **17 bytes** (UUID + FrameType + u32 BE length).
|
|
173
|
+
|
|
174
|
+
### `FrameType` enum
|
|
175
|
+
|
|
176
|
+
| Value | Name | Direction | Payload |
|
|
177
|
+
|-------|------|-----------|---------|
|
|
178
|
+
| 0x00 | `Data` | both | PTY output bytes |
|
|
179
|
+
| 0x01 | `Resize` | client → server | `{ cols: u16 BE, rows: u16 BE }` |
|
|
180
|
+
| 0x02 | `Close` | both | empty (graceful shutdown) |
|
|
181
|
+
| 0x03 | `Ping` | both | empty (heartbeat) |
|
|
182
|
+
| 0x04 | `Pong` | both | empty (heartbeat reply) |
|
|
183
|
+
|
|
184
|
+
The first frame after upgrade MUST carry a `session_id` matching the
|
|
185
|
+
one issued by the relay's `share_token` handshake. A mismatch is a
|
|
186
|
+
fatal protocol error (close code 4403).
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Buffer dump encryption
|
|
191
|
+
|
|
192
|
+
When the in-memory queue overflows, the daemon encrypts entries with
|
|
193
|
+
AES-256-GCM and writes them to disk.
|
|
194
|
+
|
|
195
|
+
### Envelope
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
+----------------+----------+----------------------------+
|
|
199
|
+
| KEY_VERSION=1 | nonce | ciphertext + GCM tag |
|
|
200
|
+
| 1 byte | 12 bytes | N bytes |
|
|
201
|
+
+----------------+----------+----------------------------+
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- **KEY_VERSION**: always `0x01` in v1.x. A future major version may
|
|
205
|
+
introduce `0x02`.
|
|
206
|
+
- **nonce**: 12 random bytes (NEVER reuse with the same key).
|
|
207
|
+
- **key**: 32 bytes (256 bits), configured via `EDGE_BUFFER_KEY` (hex, 64 chars).
|
|
208
|
+
- **tag**: 16 bytes (default GCM tag length).
|
|
209
|
+
- **algorithm**: AES-256-GCM (RFC 5116).
|
|
210
|
+
|
|
211
|
+
Key rotation is supported via `EDGE_BUFFER_KEY_PREVIOUS` — the daemon
|
|
212
|
+
can decrypt entries written with the previous key and re-encrypt with
|
|
213
|
+
the new one on next dump.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## TLS pinning
|
|
218
|
+
|
|
219
|
+
When `relay.fingerprint` (or `EDGE_RELAY_FINGERPRINT`) is set, the
|
|
220
|
+
client:
|
|
221
|
+
|
|
222
|
+
1. Completes the standard TLS handshake against the Mozilla CA bundle.
|
|
223
|
+
2. Extracts the leaf certificate's `SubjectPublicKeyInfo`.
|
|
224
|
+
3. Hashes it with SHA-256.
|
|
225
|
+
4. Compares in constant time against the configured pin.
|
|
226
|
+
|
|
227
|
+
**Extract the pin for a relay cert:**
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
openssl s_client -connect <relay-host>:443 < /dev/null 2>/dev/null \
|
|
231
|
+
| openssl x509 -pubkey -noout \
|
|
232
|
+
| openssl pkey -pubin -outform DER \
|
|
233
|
+
| openssl dgst -sha256 -binary \
|
|
234
|
+
| xxd -p -c 64
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
> **Cert-renewal-safe.** The pin is over the **public key**, not the
|
|
238
|
+
> full cert — operators do not need to repin on a cert rotation,
|
|
239
|
+
> only on a keypair rotation.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Versioning
|
|
244
|
+
|
|
245
|
+
The wire protocol is versioned implicitly via the `Sec-WebSocket-Protocol`
|
|
246
|
+
identifier (`edgeai-v2`). Future major versions will use `edgeai-v3`,
|
|
247
|
+
etc. The TS client will refuse to connect if the relay advertises an
|
|
248
|
+
unrecognized protocol version.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
A field guide for the most common `edge-ai-client-ts` errors and how
|
|
4
|
+
to fix them.
|
|
5
|
+
|
|
6
|
+
> **Security note:** when filing an issue or asking for support, **never
|
|
7
|
+
> share your `EDGE_AUTH_TOKEN`, `EDGE_BUFFER_KEY`, `EDGE_RELAY_FINGERPRINT`,
|
|
8
|
+
> or operator API key.** Redact everything matching `<secret>=…` in logs
|
|
9
|
+
> before pasting.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Install / `npm install -g` fails
|
|
14
|
+
|
|
15
|
+
### `EACCES: permission denied`
|
|
16
|
+
|
|
17
|
+
You ran `npm install -g` without sudo. Either:
|
|
18
|
+
|
|
19
|
+
- Re-run with `sudo`:
|
|
20
|
+
```bash
|
|
21
|
+
sudo npm install -g edge-ai-client-ts
|
|
22
|
+
```
|
|
23
|
+
- Or fix npm's global prefix (recommended):
|
|
24
|
+
```bash
|
|
25
|
+
mkdir -p ~/.npm-global
|
|
26
|
+
npm config set prefix '~/.npm-global'
|
|
27
|
+
export PATH="$HOME/.npm-global/bin:$PATH"
|
|
28
|
+
# Persist in ~/.bashrc / ~/.zshrc
|
|
29
|
+
npm install -g edge-ai-client-ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `node-pty` build fails (no prebuilt binary)
|
|
33
|
+
|
|
34
|
+
Most platforms have a prebuilt `.node` artifact. If yours does not:
|
|
35
|
+
|
|
36
|
+
- **Linux**: `apt install build-essential python3` (Debian/Ubuntu) or
|
|
37
|
+
`dnf install gcc-c++ make python3` (Fedora).
|
|
38
|
+
- **macOS**: `xcode-select --install`.
|
|
39
|
+
- **Windows**: install [windows-build-tools](https://github.com/felixrieseberg/windows-build-tools)
|
|
40
|
+
or use the Visual Studio Build Tools.
|
|
41
|
+
|
|
42
|
+
### `ec-ts: command not found` after install
|
|
43
|
+
|
|
44
|
+
The global `bin` dir is not on `PATH`. Find it with:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm config get prefix
|
|
48
|
+
# Add "$prefix/bin" to PATH.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Runtime / the daemon won't start
|
|
54
|
+
|
|
55
|
+
### `WebSocket upgrade rejected (no detail)`
|
|
56
|
+
|
|
57
|
+
The relay rejected the upgrade. Common causes:
|
|
58
|
+
|
|
59
|
+
| Cause | Fix |
|
|
60
|
+
|-------|-----|
|
|
61
|
+
| Missing or wrong `EDGE_AUTH_TOKEN` | Re-export the env var, restart the daemon. |
|
|
62
|
+
| TLS pin mismatch (`relay_fingerprint`) | Re-extract the SPKI hash with the `openssl` one-liner in the upstream docs. |
|
|
63
|
+
| Relay is `wss://` but you configured `ws://` (or vice versa) | Match the scheme; set `EDGEAI_REQUIRE_TLS=1` to force `wss://`. |
|
|
64
|
+
| Operator revoked in the dashboard | Re-enroll with a new key (`ec-ts enroll --key <new-key>`). |
|
|
65
|
+
|
|
66
|
+
### `EAI_AGAIN` / `ENOTFOUND` (DNS resolution)
|
|
67
|
+
|
|
68
|
+
The relay hostname does not resolve. Check:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Replace "relay.example.com" with your ws_url host
|
|
72
|
+
nslookup relay.example.com
|
|
73
|
+
curl -v https://relay.example.com/health
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you are behind a corporate proxy, set `HTTPS_PROXY` (and `NO_PROXY`
|
|
77
|
+
for the local relay) per Node's conventions.
|
|
78
|
+
|
|
79
|
+
### `Buffer full: 16777276 bytes would exceed cap of 16777216 bytes`
|
|
80
|
+
|
|
81
|
+
The in-memory queue hit `buffer.max_memory_mb`. Increase it, or speed up
|
|
82
|
+
flushes:
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
[buffer]
|
|
86
|
+
max_memory_mb = 64 # default 50; raise to 128+ on busy nodes
|
|
87
|
+
flush_interval_ms = 50 # default 100; lower for faster drain
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Also check that `EDGE_BUFFER_KEY` is set — without it, the daemon cannot
|
|
91
|
+
dump the overflow to encrypted disk and starts dropping entries.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Connection / WS flaps
|
|
96
|
+
|
|
97
|
+
### Daemon connects, then immediately disconnects
|
|
98
|
+
|
|
99
|
+
Look in the daemon log (`local_log_dir` from config). Two frequent
|
|
100
|
+
patterns:
|
|
101
|
+
|
|
102
|
+
- **`displaced by duplicate machine_id registration`** — another
|
|
103
|
+
daemon (TypeScript or Rust) is already registered with the same
|
|
104
|
+
`machine_id`. Kill the duplicate or set a unique `machine_id` per
|
|
105
|
+
daemon.
|
|
106
|
+
- **`401 Unauthorized`** — fleet shared secret mismatch. Verify
|
|
107
|
+
`EDGE_AUTH_TOKEN` matches the relay's `RELAY_AUTH_TOKEN`.
|
|
108
|
+
|
|
109
|
+
### `WebSocket protocol error: Handshake not finished`
|
|
110
|
+
|
|
111
|
+
The relay closed the upgrade mid-handshake. Most common cause: an
|
|
112
|
+
intermediating proxy (nginx, Cloudflare Tunnel) is stripping the
|
|
113
|
+
`Sec-WebSocket-Protocol` subprotocol header. The `edgeai-v2` protocol
|
|
114
|
+
identifier carries the auth token, so if the proxy strips it the relay
|
|
115
|
+
sees an unauthenticated request.
|
|
116
|
+
|
|
117
|
+
**Fix (Cloudflare Tunnel):** ensure the tunnel preserves headers. With
|
|
118
|
+
`cloudflared` defaults the `Sec-WebSocket-Protocol` header IS forwarded
|
|
119
|
+
to the upstream; double-check your `config.yml` `originRequest` block.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## `ec-ts enroll` problems
|
|
124
|
+
|
|
125
|
+
### `keychain unavailable` on Linux
|
|
126
|
+
|
|
127
|
+
`ec-ts` requires `libsecret` to store the per-operator API key. Install:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
sudo apt install libsecret-1-0 libsecret-1-dev # Debian/Ubuntu
|
|
131
|
+
sudo dnf install libsecret libsecret-devel # Fedora
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Then make sure a Secret Service provider is running (GNOME Keyring,
|
|
135
|
+
KDE Wallet, KeePassXC with Secret Service integration, etc.).
|
|
136
|
+
|
|
137
|
+
### `enroll` succeeds but `run` still says "no operator"
|
|
138
|
+
|
|
139
|
+
The keychain item may not be visible to the daemon process. Check:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Linux
|
|
143
|
+
secret-tool search service edge-ai-agent
|
|
144
|
+
|
|
145
|
+
# macOS
|
|
146
|
+
security find-generic-password -s edge-ai-agent
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If the item is missing, re-enroll with the same key.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Performance / memory
|
|
154
|
+
|
|
155
|
+
- **`memory_max_mb`** is a SOFT cap. The daemon dumps to disk above it,
|
|
156
|
+
not OOM-kills.
|
|
157
|
+
- **`tasks_max`** caps concurrent sessions + RPCs. Raise it on
|
|
158
|
+
machines running many parallel agents.
|
|
159
|
+
- For long-lived nodes with intermittent connectivity, also raise
|
|
160
|
+
`buffer.max_memory_mb` and `buffer.max_retries`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Getting more help
|
|
165
|
+
|
|
166
|
+
1. Run `ec-ts status --once` and capture the output (redact secrets).
|
|
167
|
+
2. Tail the daemon log (`local_log_dir` from your config).
|
|
168
|
+
3. File an issue at <https://github.com/baphuongna/edge-ai-agent/issues>
|
|
169
|
+
with:
|
|
170
|
+
- `ec-ts --version` and `node --version`
|
|
171
|
+
- OS / architecture
|
|
172
|
+
- Redacted `ec-ts whoami --verbose` output
|
|
173
|
+
- The relevant daemon log lines
|
|
174
|
+
|
|
175
|
+
The maintainers will not ask for your `EDGE_AUTH_TOKEN` or operator API
|
|
176
|
+
key — if you accidentally paste one, **rotate it immediately** in the
|
|
177
|
+
dashboard.
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "edge-ai-client-ts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeScript edge-client for the Edge AI Agent fleet — pure-npm reimplementation of the Rust `ec` binary with byte-compatible wire protocol.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Edge AI Agent Contributors",
|
|
8
|
+
"url": "https://github.com/baphuongna/edge-ai-agent"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/baphuongna/edge-ai-agent/tree/main/edge-client-ts#readme",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/baphuongna/edge-ai-agent.git",
|
|
14
|
+
"directory": "edge-client-ts"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/baphuongna/edge-ai-agent/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"edge",
|
|
21
|
+
"agent",
|
|
22
|
+
"ai",
|
|
23
|
+
"iot",
|
|
24
|
+
"telemetry",
|
|
25
|
+
"relay",
|
|
26
|
+
"websocket",
|
|
27
|
+
"fleet",
|
|
28
|
+
"daemon",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"bin": {
|
|
32
|
+
"ec-ts": "./bin/ec-ts.js"
|
|
33
|
+
},
|
|
34
|
+
"main": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/index.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist/",
|
|
44
|
+
"bin/",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"README.md",
|
|
47
|
+
"CHANGELOG.md",
|
|
48
|
+
"docs/"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"registry": "https://registry.npmjs.org/"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc -p tsconfig.build.json && tsc-alias",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"lint": "tsc --noEmit",
|
|
62
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
63
|
+
"smoke:preflight": "node e2e-smoke/preflight.mjs",
|
|
64
|
+
"smoke": "npm run build && npm run smoke:preflight && node e2e-smoke/smoke.mjs"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"commander": "^12.1.0",
|
|
68
|
+
"node-pty": "^1.0.0",
|
|
69
|
+
"smol-toml": "^1.3.1",
|
|
70
|
+
"ws": "^8.18.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^20.14.0",
|
|
74
|
+
"@types/ws": "^8.5.12",
|
|
75
|
+
"tsc-alias": "^1.9.0",
|
|
76
|
+
"typescript": "^5.5.4",
|
|
77
|
+
"vitest": "^2.1.1"
|
|
78
|
+
}
|
|
79
|
+
}
|