@kmmao/happy-agent 0.3.7 → 0.3.8
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/dist/index.cjs +17 -4
- package/dist/index.d.cts +64 -1
- package/dist/index.d.mts +64 -1
- package/dist/index.mjs +17 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -18,7 +18,7 @@ var path = require('path');
|
|
|
18
18
|
var fs = require('fs');
|
|
19
19
|
var os = require('os');
|
|
20
20
|
|
|
21
|
-
var version = "0.3.
|
|
21
|
+
var version = "0.3.8";
|
|
22
22
|
|
|
23
23
|
function loadConfig() {
|
|
24
24
|
const serverUrl = (process.env.HAPPY_SERVER_URL ?? "https://happyserve.xycloud.info").replace(/\/+$/, "");
|
|
@@ -1460,6 +1460,7 @@ class MachineClient {
|
|
|
1460
1460
|
keepAliveInterval = null;
|
|
1461
1461
|
tailscaleRefreshInterval = null;
|
|
1462
1462
|
lastTailscaleInfo = null;
|
|
1463
|
+
tunnelManager = null;
|
|
1463
1464
|
token;
|
|
1464
1465
|
serverUrl;
|
|
1465
1466
|
onEphemeral;
|
|
@@ -1629,6 +1630,10 @@ class MachineClient {
|
|
|
1629
1630
|
setTailscaleInfo(info) {
|
|
1630
1631
|
this.lastTailscaleInfo = info;
|
|
1631
1632
|
}
|
|
1633
|
+
/** Attach a TunnelManager for periodic tunnel state refresh. */
|
|
1634
|
+
setTunnelManager(manager) {
|
|
1635
|
+
this.tunnelManager = manager;
|
|
1636
|
+
}
|
|
1632
1637
|
shutdown() {
|
|
1633
1638
|
logger.debug("[MACHINE] Shutting down");
|
|
1634
1639
|
this.stopKeepAlive();
|
|
@@ -1660,14 +1665,22 @@ class MachineClient {
|
|
|
1660
1665
|
const info = await detectTailscale();
|
|
1661
1666
|
const serves = info.status === "connected" ? await detectTailscaleServe() : [];
|
|
1662
1667
|
const fullInfo = { ...info, serves };
|
|
1663
|
-
|
|
1668
|
+
const tsChanged = tailscaleChanged(this.lastTailscaleInfo, fullInfo);
|
|
1669
|
+
if (tsChanged) {
|
|
1664
1670
|
logger.debug(
|
|
1665
1671
|
`[MACHINE] Tailscale changed: ${this.lastTailscaleInfo?.status} \u2192 ${fullInfo.status}, serves: ${serves.length}`
|
|
1666
1672
|
);
|
|
1667
1673
|
this.lastTailscaleInfo = fullInfo;
|
|
1674
|
+
}
|
|
1675
|
+
const tunnels = this.tunnelManager ? await this.tunnelManager.detectAll() : void 0;
|
|
1676
|
+
if (tsChanged || tunnels) {
|
|
1668
1677
|
this.updateDaemonState((state) => {
|
|
1669
|
-
if (!state) return { status: "running", tailscale: fullInfo };
|
|
1670
|
-
return {
|
|
1678
|
+
if (!state) return { status: "running", tailscale: fullInfo, tunnels };
|
|
1679
|
+
return {
|
|
1680
|
+
...state,
|
|
1681
|
+
...tsChanged ? { tailscale: fullInfo } : {},
|
|
1682
|
+
...tunnels ? { tunnels } : {}
|
|
1683
|
+
};
|
|
1671
1684
|
});
|
|
1672
1685
|
}
|
|
1673
1686
|
}, TAILSCALE_REFRESH_MS);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { MachineMetadata, DaemonState } from '@kmmao/happy-wire';
|
|
2
|
+
import { MachineMetadata, DaemonState, TunnelProviderInfo, TunnelEntry, TunnelState } from '@kmmao/happy-wire';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { Socket } from 'socket.io-client';
|
|
5
5
|
|
|
@@ -278,6 +278,66 @@ type TailscaleInfo = {
|
|
|
278
278
|
serves?: TailscaleServeEntry[];
|
|
279
279
|
};
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Tunnel Provider abstraction — unified interface for all tunnel backends.
|
|
283
|
+
*
|
|
284
|
+
* Each provider (Tailscale, UPnP, Cloudflare, FRP, etc.) implements this
|
|
285
|
+
* interface. TunnelManager aggregates all providers and exposes a single API.
|
|
286
|
+
*/
|
|
287
|
+
|
|
288
|
+
interface TunnelProvider {
|
|
289
|
+
/** Unique provider name: "tailscale" | "upnp" | "cloudflare" | "frp" */
|
|
290
|
+
readonly name: string;
|
|
291
|
+
/** Detect provider availability and list current tunnel entries. Never throws. */
|
|
292
|
+
detect(): Promise<TunnelProviderInfo>;
|
|
293
|
+
/** Add a tunnel mapping */
|
|
294
|
+
add(params: TunnelAddParams): Promise<TunnelOpResult>;
|
|
295
|
+
/** Remove a tunnel mapping */
|
|
296
|
+
remove(params: TunnelRemoveParams): Promise<TunnelOpResult>;
|
|
297
|
+
/** Toggle public/private access (not all providers support this) */
|
|
298
|
+
toggleAccess?(entry: TunnelEntry, publicAccess: boolean): Promise<TunnelOpResult>;
|
|
299
|
+
}
|
|
300
|
+
interface TunnelAddParams {
|
|
301
|
+
localPort: number;
|
|
302
|
+
remotePort?: number;
|
|
303
|
+
protocol?: string;
|
|
304
|
+
path?: string;
|
|
305
|
+
publicAccess?: boolean;
|
|
306
|
+
hostname?: string;
|
|
307
|
+
}
|
|
308
|
+
interface TunnelRemoveParams {
|
|
309
|
+
localPort?: number;
|
|
310
|
+
remotePort?: number;
|
|
311
|
+
path?: string;
|
|
312
|
+
hostname?: string;
|
|
313
|
+
/** Remove entire site (all routes for this hostname) */
|
|
314
|
+
removeEntireSite?: boolean;
|
|
315
|
+
}
|
|
316
|
+
interface TunnelOpResult {
|
|
317
|
+
success: boolean;
|
|
318
|
+
error?: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* TunnelManager for happy-agent — aggregates all tunnel providers.
|
|
323
|
+
*/
|
|
324
|
+
|
|
325
|
+
declare class TunnelManager {
|
|
326
|
+
private readonly providers;
|
|
327
|
+
private refreshTimer;
|
|
328
|
+
private leaseRenewalTimer;
|
|
329
|
+
private lastState;
|
|
330
|
+
constructor(providers: TunnelProvider[]);
|
|
331
|
+
detectAll(): Promise<TunnelState>;
|
|
332
|
+
getLastState(): TunnelState;
|
|
333
|
+
getProvider(name: string): TunnelProvider | undefined;
|
|
334
|
+
add(providerName: string, params: TunnelAddParams): Promise<TunnelOpResult>;
|
|
335
|
+
remove(providerName: string, params: TunnelRemoveParams): Promise<TunnelOpResult>;
|
|
336
|
+
toggleAccess(providerName: string, entry: TunnelEntry, publicAccess: boolean): Promise<TunnelOpResult>;
|
|
337
|
+
startRefresh(onChange: (state: TunnelState) => void, intervalMs?: number): void;
|
|
338
|
+
stopRefresh(): void;
|
|
339
|
+
}
|
|
340
|
+
|
|
281
341
|
/**
|
|
282
342
|
* Machine WebSocket client — trimmed from CLI's ApiMachineClient.
|
|
283
343
|
*
|
|
@@ -313,6 +373,7 @@ declare class MachineClient {
|
|
|
313
373
|
private keepAliveInterval;
|
|
314
374
|
private tailscaleRefreshInterval;
|
|
315
375
|
private lastTailscaleInfo;
|
|
376
|
+
private tunnelManager;
|
|
316
377
|
private readonly token;
|
|
317
378
|
private readonly serverUrl;
|
|
318
379
|
private readonly onEphemeral?;
|
|
@@ -322,6 +383,8 @@ declare class MachineClient {
|
|
|
322
383
|
updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
|
|
323
384
|
/** Seed initial Tailscale info detected before connect. */
|
|
324
385
|
setTailscaleInfo(info: TailscaleInfo): void;
|
|
386
|
+
/** Attach a TunnelManager for periodic tunnel state refresh. */
|
|
387
|
+
setTunnelManager(manager: TunnelManager): void;
|
|
325
388
|
shutdown(): void;
|
|
326
389
|
private startKeepAlive;
|
|
327
390
|
private stopKeepAlive;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { MachineMetadata, DaemonState } from '@kmmao/happy-wire';
|
|
2
|
+
import { MachineMetadata, DaemonState, TunnelProviderInfo, TunnelEntry, TunnelState } from '@kmmao/happy-wire';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { Socket } from 'socket.io-client';
|
|
5
5
|
|
|
@@ -278,6 +278,66 @@ type TailscaleInfo = {
|
|
|
278
278
|
serves?: TailscaleServeEntry[];
|
|
279
279
|
};
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Tunnel Provider abstraction — unified interface for all tunnel backends.
|
|
283
|
+
*
|
|
284
|
+
* Each provider (Tailscale, UPnP, Cloudflare, FRP, etc.) implements this
|
|
285
|
+
* interface. TunnelManager aggregates all providers and exposes a single API.
|
|
286
|
+
*/
|
|
287
|
+
|
|
288
|
+
interface TunnelProvider {
|
|
289
|
+
/** Unique provider name: "tailscale" | "upnp" | "cloudflare" | "frp" */
|
|
290
|
+
readonly name: string;
|
|
291
|
+
/** Detect provider availability and list current tunnel entries. Never throws. */
|
|
292
|
+
detect(): Promise<TunnelProviderInfo>;
|
|
293
|
+
/** Add a tunnel mapping */
|
|
294
|
+
add(params: TunnelAddParams): Promise<TunnelOpResult>;
|
|
295
|
+
/** Remove a tunnel mapping */
|
|
296
|
+
remove(params: TunnelRemoveParams): Promise<TunnelOpResult>;
|
|
297
|
+
/** Toggle public/private access (not all providers support this) */
|
|
298
|
+
toggleAccess?(entry: TunnelEntry, publicAccess: boolean): Promise<TunnelOpResult>;
|
|
299
|
+
}
|
|
300
|
+
interface TunnelAddParams {
|
|
301
|
+
localPort: number;
|
|
302
|
+
remotePort?: number;
|
|
303
|
+
protocol?: string;
|
|
304
|
+
path?: string;
|
|
305
|
+
publicAccess?: boolean;
|
|
306
|
+
hostname?: string;
|
|
307
|
+
}
|
|
308
|
+
interface TunnelRemoveParams {
|
|
309
|
+
localPort?: number;
|
|
310
|
+
remotePort?: number;
|
|
311
|
+
path?: string;
|
|
312
|
+
hostname?: string;
|
|
313
|
+
/** Remove entire site (all routes for this hostname) */
|
|
314
|
+
removeEntireSite?: boolean;
|
|
315
|
+
}
|
|
316
|
+
interface TunnelOpResult {
|
|
317
|
+
success: boolean;
|
|
318
|
+
error?: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* TunnelManager for happy-agent — aggregates all tunnel providers.
|
|
323
|
+
*/
|
|
324
|
+
|
|
325
|
+
declare class TunnelManager {
|
|
326
|
+
private readonly providers;
|
|
327
|
+
private refreshTimer;
|
|
328
|
+
private leaseRenewalTimer;
|
|
329
|
+
private lastState;
|
|
330
|
+
constructor(providers: TunnelProvider[]);
|
|
331
|
+
detectAll(): Promise<TunnelState>;
|
|
332
|
+
getLastState(): TunnelState;
|
|
333
|
+
getProvider(name: string): TunnelProvider | undefined;
|
|
334
|
+
add(providerName: string, params: TunnelAddParams): Promise<TunnelOpResult>;
|
|
335
|
+
remove(providerName: string, params: TunnelRemoveParams): Promise<TunnelOpResult>;
|
|
336
|
+
toggleAccess(providerName: string, entry: TunnelEntry, publicAccess: boolean): Promise<TunnelOpResult>;
|
|
337
|
+
startRefresh(onChange: (state: TunnelState) => void, intervalMs?: number): void;
|
|
338
|
+
stopRefresh(): void;
|
|
339
|
+
}
|
|
340
|
+
|
|
281
341
|
/**
|
|
282
342
|
* Machine WebSocket client — trimmed from CLI's ApiMachineClient.
|
|
283
343
|
*
|
|
@@ -313,6 +373,7 @@ declare class MachineClient {
|
|
|
313
373
|
private keepAliveInterval;
|
|
314
374
|
private tailscaleRefreshInterval;
|
|
315
375
|
private lastTailscaleInfo;
|
|
376
|
+
private tunnelManager;
|
|
316
377
|
private readonly token;
|
|
317
378
|
private readonly serverUrl;
|
|
318
379
|
private readonly onEphemeral?;
|
|
@@ -322,6 +383,8 @@ declare class MachineClient {
|
|
|
322
383
|
updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
|
|
323
384
|
/** Seed initial Tailscale info detected before connect. */
|
|
324
385
|
setTailscaleInfo(info: TailscaleInfo): void;
|
|
386
|
+
/** Attach a TunnelManager for periodic tunnel state refresh. */
|
|
387
|
+
setTunnelManager(manager: TunnelManager): void;
|
|
325
388
|
shutdown(): void;
|
|
326
389
|
private startKeepAlive;
|
|
327
390
|
private stopKeepAlive;
|
package/dist/index.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { join as join$1, resolve } from 'path';
|
|
|
16
16
|
import { realpathSync } from 'fs';
|
|
17
17
|
import { tmpdir } from 'os';
|
|
18
18
|
|
|
19
|
-
var version = "0.3.
|
|
19
|
+
var version = "0.3.8";
|
|
20
20
|
|
|
21
21
|
function loadConfig() {
|
|
22
22
|
const serverUrl = (process.env.HAPPY_SERVER_URL ?? "https://happyserve.xycloud.info").replace(/\/+$/, "");
|
|
@@ -1458,6 +1458,7 @@ class MachineClient {
|
|
|
1458
1458
|
keepAliveInterval = null;
|
|
1459
1459
|
tailscaleRefreshInterval = null;
|
|
1460
1460
|
lastTailscaleInfo = null;
|
|
1461
|
+
tunnelManager = null;
|
|
1461
1462
|
token;
|
|
1462
1463
|
serverUrl;
|
|
1463
1464
|
onEphemeral;
|
|
@@ -1627,6 +1628,10 @@ class MachineClient {
|
|
|
1627
1628
|
setTailscaleInfo(info) {
|
|
1628
1629
|
this.lastTailscaleInfo = info;
|
|
1629
1630
|
}
|
|
1631
|
+
/** Attach a TunnelManager for periodic tunnel state refresh. */
|
|
1632
|
+
setTunnelManager(manager) {
|
|
1633
|
+
this.tunnelManager = manager;
|
|
1634
|
+
}
|
|
1630
1635
|
shutdown() {
|
|
1631
1636
|
logger.debug("[MACHINE] Shutting down");
|
|
1632
1637
|
this.stopKeepAlive();
|
|
@@ -1658,14 +1663,22 @@ class MachineClient {
|
|
|
1658
1663
|
const info = await detectTailscale();
|
|
1659
1664
|
const serves = info.status === "connected" ? await detectTailscaleServe() : [];
|
|
1660
1665
|
const fullInfo = { ...info, serves };
|
|
1661
|
-
|
|
1666
|
+
const tsChanged = tailscaleChanged(this.lastTailscaleInfo, fullInfo);
|
|
1667
|
+
if (tsChanged) {
|
|
1662
1668
|
logger.debug(
|
|
1663
1669
|
`[MACHINE] Tailscale changed: ${this.lastTailscaleInfo?.status} \u2192 ${fullInfo.status}, serves: ${serves.length}`
|
|
1664
1670
|
);
|
|
1665
1671
|
this.lastTailscaleInfo = fullInfo;
|
|
1672
|
+
}
|
|
1673
|
+
const tunnels = this.tunnelManager ? await this.tunnelManager.detectAll() : void 0;
|
|
1674
|
+
if (tsChanged || tunnels) {
|
|
1666
1675
|
this.updateDaemonState((state) => {
|
|
1667
|
-
if (!state) return { status: "running", tailscale: fullInfo };
|
|
1668
|
-
return {
|
|
1676
|
+
if (!state) return { status: "running", tailscale: fullInfo, tunnels };
|
|
1677
|
+
return {
|
|
1678
|
+
...state,
|
|
1679
|
+
...tsChanged ? { tailscale: fullInfo } : {},
|
|
1680
|
+
...tunnels ? { tunnels } : {}
|
|
1681
|
+
};
|
|
1669
1682
|
});
|
|
1670
1683
|
}
|
|
1671
1684
|
}, TAILSCALE_REFRESH_MS);
|