talon-agent 5.6.0 → 5.8.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/README.md +11 -1
- package/package.json +1 -1
- package/src/core/config/index.ts +10 -0
- package/src/core/mesh/links/bridge-links.ts +13 -3
- package/src/frontend/native/index.ts +3 -0
package/README.md
CHANGED
|
@@ -78,6 +78,9 @@ brew install dylanneve1/talon/talon
|
|
|
78
78
|
# Debian / Ubuntu — download the .deb for your arch from the release, then:
|
|
79
79
|
sudo apt install ./talon_<version>_amd64.deb # or _arm64.deb
|
|
80
80
|
|
|
81
|
+
# Fedora / RHEL — download the .rpm for your arch from the release, then:
|
|
82
|
+
sudo dnf install ./talon-<version>-1.x86_64.rpm # or .aarch64.rpm
|
|
83
|
+
|
|
81
84
|
# Direct download — grab talon-<os>-<arch> from the release, verify, run:
|
|
82
85
|
chmod +x talon-linux-x64 && ./talon-linux-x64 --version
|
|
83
86
|
# macOS, if Gatekeeper blocks an unsigned binary:
|
|
@@ -87,6 +90,10 @@ xattr -d com.apple.quarantine ./talon-darwin-arm64
|
|
|
87
90
|
Verify a direct download against the release `SHA256SUMS`:
|
|
88
91
|
`sha256sum -c SHA256SUMS --ignore-missing`.
|
|
89
92
|
|
|
93
|
+
**Server only, no Telegram?** Run the daemon with just the client bridge,
|
|
94
|
+
reached by the companion app and talon-node: see
|
|
95
|
+
[docs/server-install.md](docs/server-install.md).
|
|
96
|
+
|
|
90
97
|
> The binary runs the full interactive/agent CLI (`setup`, `start`, `chat`,
|
|
91
98
|
> `doctor`, …) and supervises MCP children like any other install shape. The one
|
|
92
99
|
> gap: a plugin shipping its MCP server as TypeScript source (`mcpServerPath`)
|
|
@@ -249,6 +256,7 @@ The `native` frontend turns the daemon into a **client bridge** — a versioned
|
|
|
249
256
|
- **Local (desktop):** the app connects to a Talon on the same machine and launches one if needed (`TALON_FRONTEND_OVERRIDE=desktop`).
|
|
250
257
|
- **Remote (mobile/LAN):** point the app at `host:port` + token; the bridge requires `Authorization: Bearer …` (or `?token=` on the SSE stream) whenever a token is set.
|
|
251
258
|
- **Encryption:** off-loopback binds serve **HTTPS by default** with a persistent self-signed certificate (`~/.talon/keys/`); the companion pins its SHA-256 fingerprint on first connect and refuses any change afterwards. The daemon logs the fingerprint at startup and `/health` advertises it. Opt out (or in, on loopback) with `"tls": false` / `true` in the `native` section.
|
|
259
|
+
- **From anywhere, certificate-only (Immich-style):** put your reverse proxy (Caddy, nginx, Traefik, Cloudflare Tunnel) in front with client-certificate auth; import the `.p12` in the companion, which also switches to your home-network address whenever it answers. Talon still authenticates with its token — see [docs/mtls.md](docs/mtls.md).
|
|
252
260
|
|
|
253
261
|
The app **keeps itself up to date** — it watches this repo's releases and installs the next one itself (silently on a rooted/Shizuku phone, swap-and-relaunch on desktop; see [docs/companion-updates.md](docs/companion-updates.md)). It provides multi-chat history, live streaming with reasoning + tool-call visibility, per-chat model/effort/reset, and **settings sync** — read and change the daemon's own config (default model, display name, timezone, pulse/heartbeat/dream) and restart it. See [apps/companion/README.md](apps/companion/README.md).
|
|
254
262
|
|
|
@@ -529,7 +537,7 @@ Commands: `/model`, `/effort`, `/context`, `/status`, `/reset`, `/rename`, `/res
|
|
|
529
537
|
|
|
530
538
|
## Production
|
|
531
539
|
|
|
532
|
-
**Docker:** the image runs the daemon on Bun (`bun src/index.ts`); `~/.talon` and `~/.claude` are bind-mounted from the host into the container's `HOME=/home/bun`.
|
|
540
|
+
**Docker:** the image runs the daemon on Bun (`bun src/index.ts`); `~/.talon` and `~/.claude` are bind-mounted from the host into the container's `HOME=/home/bun`. Prebuilt images are on GHCR (`ghcr.io/dylanneve1/talon:latest`), and a first boot can be configured entirely from `TALON_*` environment variables — see **[docs/docker.md](docs/docker.md)** for the quick install, and **[docs/truenas.md](docs/truenas.md)** for a step-by-step TrueNAS SCALE install.
|
|
533
541
|
|
|
534
542
|
```bash
|
|
535
543
|
docker compose up -d
|
|
@@ -537,6 +545,8 @@ docker compose up -d
|
|
|
537
545
|
|
|
538
546
|
A Node 24 + tsx image is kept as a fallback for one release cycle: `docker build --build-arg RUNTIME=node -t talon .` (or set `build.args.RUNTIME` in `docker-compose.yml`). Mount paths are the same for both. See [`packaging/README.md`](packaging/README.md#docker-image) for the build's details.
|
|
539
547
|
|
|
548
|
+
The image is also ready for the **Antigravity (`agy`) backend**: add `-f docker-compose.agy.yml` to mount `~/.gemini` and the `agy` binary (or bake the binary in with `AGY_DOWNLOAD_URL` + `AGY_SHA256`). See [docs/docker.md](docs/docker.md#antigravity-agy-backend).
|
|
549
|
+
|
|
540
550
|
**Systemd:** unit file at `packaging/systemd/talon.service` — copy to `/etc/systemd/system/`, set `User=` and `WorkingDirectory=`, then `systemctl enable --now talon`.
|
|
541
551
|
|
|
542
552
|
**Health endpoint:** `GET http://localhost:19876/health` returns JSON with uptime, memory, queue depth, active sessions, and last activity timestamp.
|
package/package.json
CHANGED
package/src/core/config/index.ts
CHANGED
|
@@ -192,6 +192,16 @@ const nativeConfigSchema = z
|
|
|
192
192
|
* working zero-config).
|
|
193
193
|
*/
|
|
194
194
|
tls: z.boolean().optional(),
|
|
195
|
+
/**
|
|
196
|
+
* The URL other devices should dial, when it isn't the bind address —
|
|
197
|
+
* a container (Docker, TrueNAS) or anything behind NAT or a proxy,
|
|
198
|
+
* where the bridge only sees its internal IP. Used for pairing links,
|
|
199
|
+
* node installers and `/mesh`. e.g. "https://truenas.lan:19880".
|
|
200
|
+
*/
|
|
201
|
+
publicUrl: z
|
|
202
|
+
.string()
|
|
203
|
+
.regex(/^https?:\/\/\S+$/, "native.publicUrl must be an http(s) URL")
|
|
204
|
+
.optional(),
|
|
195
205
|
})
|
|
196
206
|
.strict();
|
|
197
207
|
|
|
@@ -40,6 +40,11 @@ export type MeshBridgeInfo = {
|
|
|
40
40
|
token?: string;
|
|
41
41
|
/** TLS certificate SHA-256 (absent over plain HTTP). */
|
|
42
42
|
fingerprint?: string;
|
|
43
|
+
/**
|
|
44
|
+
* The operator's `native.publicUrl`: what devices dial when the bind
|
|
45
|
+
* address isn't reachable as-is (containers, NAT, proxies).
|
|
46
|
+
*/
|
|
47
|
+
publicUrl?: string;
|
|
43
48
|
};
|
|
44
49
|
|
|
45
50
|
export class BridgeLinks {
|
|
@@ -261,9 +266,10 @@ export class BridgeLinks {
|
|
|
261
266
|
}
|
|
262
267
|
|
|
263
268
|
/**
|
|
264
|
-
* The bridge base URL a NEW host should dial: an explicit override wins
|
|
265
|
-
*
|
|
266
|
-
*
|
|
269
|
+
* The bridge base URL a NEW host should dial: an explicit override wins,
|
|
270
|
+
* then the configured `native.publicUrl`; otherwise derive from the
|
|
271
|
+
* bridge's bind. A wildcard bind maps to this host's first external IPv4;
|
|
272
|
+
* a loopback bind is unreachable from other
|
|
267
273
|
* machines, so it's an error rather than a link that can't work.
|
|
268
274
|
*/
|
|
269
275
|
private bridgeBaseUrl(
|
|
@@ -277,6 +283,10 @@ export class BridgeLinks {
|
|
|
277
283
|
}
|
|
278
284
|
return url;
|
|
279
285
|
}
|
|
286
|
+
// Inside a container the "first external IPv4" is the container's own
|
|
287
|
+
// bridge-network address, which no phone can reach — the operator's
|
|
288
|
+
// public URL is the only right answer there.
|
|
289
|
+
if (info.publicUrl) return info.publicUrl.trim().replace(/\/+$/, "");
|
|
280
290
|
let host = info.host;
|
|
281
291
|
if (host === "0.0.0.0" || host === "::") {
|
|
282
292
|
const external = firstExternalIPv4();
|
|
@@ -185,6 +185,9 @@ export function createNativeFrontend(
|
|
|
185
185
|
port: server.getPort(),
|
|
186
186
|
...(listen.token ? { token: listen.token } : {}),
|
|
187
187
|
...(fingerprint ? { fingerprint } : {}),
|
|
188
|
+
...(config.native?.publicUrl
|
|
189
|
+
? { publicUrl: config.native.publicUrl }
|
|
190
|
+
: {}),
|
|
188
191
|
});
|
|
189
192
|
await writeBridgeDiscovery({
|
|
190
193
|
port: server.getPort(),
|