@kortix/agent-tunnel 0.1.1 → 0.1.3
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/agent/index.d.ts +140 -0
- package/dist/agent/index.js +21 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent-cli.js +4061 -0
- package/dist/chunk-7N7GSU6K.js +34 -0
- package/dist/client/index.d.ts +183 -0
- package/dist/client/index.js +8 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client-cli.js +752 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +89 -0
- package/dist/server/index.js +14 -0
- package/dist/server/index.js.map +1 -0
- package/dist/shared/index.d.ts +10 -0
- package/dist/shared/index.js +20 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/types-Dpwrd8Ai.d.ts +194 -0
- package/package.json +32 -12
- package/src/agent/agent.ts +19 -9
- package/src/agent/capabilities/desktop/atspi-helper.ts +1 -1
- package/src/agent/capabilities/desktop/csharp-helper.ts +1 -1
- package/src/agent/capabilities/desktop/linux-driver.ts +1 -1
- package/src/agent/capabilities/desktop/macos-driver.ts +1 -1
- package/src/agent/capabilities/desktop/swift-helper.ts +1 -1
- package/src/agent/cli.ts +157 -23
- package/src/client/cli.test.ts +10 -10
- package/src/client/cli.ts +5 -6
- package/src/node-ws-polyfill.ts +17 -0
- package/src/server/ws-handler.ts +5 -0
- package/src/shared/crypto.ts +2 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { deriveSigningKey, generateToken, hashToken, signMessage, timingSafeStringEqual, verifyMessageSignature } from './shared/index.js';
|
|
2
|
+
export { A as AgentInfo, a as AuthResult, H as HeartbeatConfig, J as JsonRpcError, b as JsonRpcErrorResponse, c as JsonRpcMessage, d as JsonRpcNotification, e as JsonRpcRequest, f as JsonRpcResponse, g as JsonRpcSuccessResponse, P as PendingRPC, R as RelayRpcOptions, S as SignedJsonRpcNotification, h as SignedJsonRpcRequest, T as TunnelAuthMessage, i as TunnelCapability, j as TunnelErrorCode, k as TunnelErrorCodeValue, l as TunnelMethod, m as TunnelMethods, n as TunnelRelayConfig, o as TunnelRelayEvents, p as TunnelRpcParams, q as TunnelServerConfig } from './types-Dpwrd8Ai.js';
|
|
3
|
+
export { HeartbeatManager, TunnelRelay, TunnelRelayError, TunnelServer, WsHandlerOptions, WsHandlers, createTunnelRouter, createWsHandlers, startTunnelServer } from './server/index.js';
|
|
4
|
+
export { AXElement, TunnelClient, TunnelClientConfig, TunnelClientError, TunnelToolDefinition, TunnelToolParameter, createTunnelTools } from './client/index.js';
|
|
5
|
+
export { Capability, CapabilityRegistry, LocalPermission, PermissionGuard, RpcHandler, TunnelAgent, TunnelConfig, createDesktopCapability, createFilesystemCapability, createShellCapability, loadConfig, validateCommand, validatePath } from './agent/index.js';
|
|
6
|
+
import 'events';
|
|
7
|
+
import 'hono';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveSigningKey,
|
|
3
|
+
signMessage,
|
|
4
|
+
verifyMessageSignature,
|
|
5
|
+
generateToken,
|
|
6
|
+
hashToken,
|
|
7
|
+
timingSafeStringEqual,
|
|
8
|
+
TunnelErrorCode,
|
|
9
|
+
TunnelMethods
|
|
10
|
+
} from "./shared";
|
|
11
|
+
import { TunnelRelay, TunnelRelayError } from "./server";
|
|
12
|
+
import { HeartbeatManager } from "./server";
|
|
13
|
+
import { createWsHandlers } from "./server";
|
|
14
|
+
import { createTunnelRouter } from "./server";
|
|
15
|
+
import { startTunnelServer } from "./server";
|
|
16
|
+
import { TunnelClient, TunnelClientError } from "./client";
|
|
17
|
+
import { createTunnelTools } from "./client";
|
|
18
|
+
import { TunnelAgent } from "./agent";
|
|
19
|
+
import { loadConfig } from "./agent";
|
|
20
|
+
import { CapabilityRegistry } from "./agent";
|
|
21
|
+
import { createFilesystemCapability } from "./agent";
|
|
22
|
+
import { createShellCapability } from "./agent";
|
|
23
|
+
import { createDesktopCapability } from "./agent";
|
|
24
|
+
import { PermissionGuard } from "./agent";
|
|
25
|
+
import { validateCommand } from "./agent";
|
|
26
|
+
import { validatePath } from "./agent";
|
|
27
|
+
export {
|
|
28
|
+
CapabilityRegistry,
|
|
29
|
+
HeartbeatManager,
|
|
30
|
+
PermissionGuard,
|
|
31
|
+
TunnelAgent,
|
|
32
|
+
TunnelClient,
|
|
33
|
+
TunnelClientError,
|
|
34
|
+
TunnelErrorCode,
|
|
35
|
+
TunnelMethods,
|
|
36
|
+
TunnelRelay,
|
|
37
|
+
TunnelRelayError,
|
|
38
|
+
createDesktopCapability,
|
|
39
|
+
createFilesystemCapability,
|
|
40
|
+
createShellCapability,
|
|
41
|
+
createTunnelRouter,
|
|
42
|
+
createTunnelTools,
|
|
43
|
+
createWsHandlers,
|
|
44
|
+
deriveSigningKey,
|
|
45
|
+
generateToken,
|
|
46
|
+
hashToken,
|
|
47
|
+
loadConfig,
|
|
48
|
+
signMessage,
|
|
49
|
+
startTunnelServer,
|
|
50
|
+
timingSafeStringEqual,
|
|
51
|
+
validateCommand,
|
|
52
|
+
validatePath,
|
|
53
|
+
verifyMessageSignature
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// ─── Shared: Types + Crypto ─────────────────────────────────────────────────\nexport {\n deriveSigningKey,\n signMessage,\n verifyMessageSignature,\n generateToken,\n hashToken,\n timingSafeStringEqual,\n TunnelErrorCode,\n TunnelMethods,\n} from './shared';\n\nexport type {\n JsonRpcRequest,\n JsonRpcSuccessResponse,\n JsonRpcErrorResponse,\n JsonRpcError,\n JsonRpcResponse,\n JsonRpcNotification,\n JsonRpcMessage,\n SignedJsonRpcRequest,\n SignedJsonRpcNotification,\n PendingRPC,\n TunnelRpcParams,\n RelayRpcOptions,\n AgentInfo,\n TunnelRelayEvents,\n TunnelRelayConfig,\n HeartbeatConfig,\n TunnelServerConfig,\n TunnelAuthMessage,\n AuthResult,\n TunnelCapability,\n TunnelMethod,\n TunnelErrorCodeValue,\n} from './shared';\n\n// ─── Server: Relay ──────────────────────────────────────────────────────────\nexport { TunnelRelay, TunnelRelayError } from './server';\nexport { HeartbeatManager } from './server';\nexport { createWsHandlers } from './server';\nexport type { WsHandlers, WsHandlerOptions } from './server';\nexport { createTunnelRouter } from './server';\nexport { startTunnelServer } from './server';\nexport type { TunnelServer } from './server';\n\n// ─── Client: SDK ────────────────────────────────────────────────────────────\nexport { TunnelClient, TunnelClientError } from './client';\nexport type { TunnelClientConfig, AXElement } from './client';\nexport { createTunnelTools } from './client';\nexport type { TunnelToolDefinition, TunnelToolParameter } from './client';\n\n// ─── Agent: Local Machine ───────────────────────────────────────────────────\nexport { TunnelAgent } from './agent';\nexport { loadConfig, type TunnelConfig } from './agent';\nexport { CapabilityRegistry } from './agent';\nexport type { Capability, RpcHandler } from './agent';\nexport { createFilesystemCapability } from './agent';\nexport { createShellCapability } from './agent';\nexport { createDesktopCapability } from './agent';\nexport { PermissionGuard } from './agent';\nexport type { LocalPermission } from './agent';\nexport { validateCommand } from './agent';\nexport { validatePath } from './agent';\n"],"mappings":"AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA4BP,SAAS,aAAa,wBAAwB;AAC9C,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AAEjC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAIlC,SAAS,cAAc,yBAAyB;AAEhD,SAAS,yBAAyB;AAIlC,SAAS,mBAAmB;AAC5B,SAAS,kBAAqC;AAC9C,SAAS,0BAA0B;AAEnC,SAAS,kCAAkC;AAC3C,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AAEhC,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;","names":[]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { n as TunnelRelayConfig, o as TunnelRelayEvents, A as AgentInfo, R as RelayRpcOptions, H as HeartbeatConfig, a as AuthResult, q as TunnelServerConfig } from '../types-Dpwrd8Ai.js';
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
|
|
5
|
+
declare class TunnelRelay extends EventEmitter {
|
|
6
|
+
private agents;
|
|
7
|
+
private pendingRPCs;
|
|
8
|
+
private config;
|
|
9
|
+
onAuthorizeRPC?: (tunnelId: string, method: string, params: Record<string, unknown>) => Promise<boolean>;
|
|
10
|
+
constructor(config?: TunnelRelayConfig);
|
|
11
|
+
emitEvent<K extends keyof TunnelRelayEvents>(event: K, data: TunnelRelayEvents[K]): boolean;
|
|
12
|
+
registerAgent(tunnelId: string, ws: WebSocket, signingKey: string, metadata?: Record<string, unknown>): void;
|
|
13
|
+
unregisterAgent(tunnelId: string): void;
|
|
14
|
+
isConnected(tunnelId: string): boolean;
|
|
15
|
+
getConnectedCount(): number;
|
|
16
|
+
getConnectedAgents(): Map<string, AgentInfo>;
|
|
17
|
+
getAgentMetadata(tunnelId: string): Record<string, unknown> | undefined;
|
|
18
|
+
handleAgentMessage(tunnelId: string, raw: string | Buffer): void;
|
|
19
|
+
relayRPC(tunnelId: string, method: string, params: Record<string, unknown>, options?: RelayRpcOptions): Promise<unknown>;
|
|
20
|
+
sendNotification(tunnelId: string, method: string, params?: Record<string, unknown>): boolean;
|
|
21
|
+
shutdown(): void;
|
|
22
|
+
}
|
|
23
|
+
declare class TunnelRelayError extends Error {
|
|
24
|
+
readonly code: number;
|
|
25
|
+
readonly data?: unknown | undefined;
|
|
26
|
+
constructor(code: number, message: string, data?: unknown | undefined);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class HeartbeatManager {
|
|
30
|
+
private states;
|
|
31
|
+
private intervalHandle;
|
|
32
|
+
private relay;
|
|
33
|
+
private intervalMs;
|
|
34
|
+
private maxMissed;
|
|
35
|
+
constructor(relay: TunnelRelay, config?: HeartbeatConfig);
|
|
36
|
+
start(): void;
|
|
37
|
+
stop(): void;
|
|
38
|
+
register(tunnelId: string): void;
|
|
39
|
+
unregister(tunnelId: string): void;
|
|
40
|
+
recordPong(tunnelId: string): void;
|
|
41
|
+
private tick;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface WsHandlerOptions {
|
|
45
|
+
heartbeat?: HeartbeatManager;
|
|
46
|
+
maxMessageSize?: number;
|
|
47
|
+
onAuthenticate?: (tunnelId: string, token: string) => Promise<AuthResult | null>;
|
|
48
|
+
authTimeoutMs?: number;
|
|
49
|
+
}
|
|
50
|
+
interface WsHandlers {
|
|
51
|
+
onOpen(tunnelId: string, ws: WebSocket): void;
|
|
52
|
+
onMessage(tunnelId: string, message: string | Buffer): void;
|
|
53
|
+
onClose(tunnelId: string): void;
|
|
54
|
+
}
|
|
55
|
+
declare function createWsHandlers(relay: TunnelRelay, opts?: WsHandlerOptions): WsHandlers;
|
|
56
|
+
|
|
57
|
+
declare function createTunnelRouter(relay: TunnelRelay, onAuthorizeHTTP?: (req: Request) => Promise<boolean>): Hono;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Standalone tunnel server — one function call to get a working relay.
|
|
61
|
+
*
|
|
62
|
+
* Usage:
|
|
63
|
+
* import { startTunnelServer } from 'agent-tunnel';
|
|
64
|
+
* const server = startTunnelServer({ port: 8080, onAuthenticate: ... });
|
|
65
|
+
*
|
|
66
|
+
* Agents connect via: ws://localhost:8080/ws?tunnelId=yyy
|
|
67
|
+
* Then send { type: "auth", token: "tnl_xxx" } as the first message.
|
|
68
|
+
* Your app calls relay.relayRPC(tunnelId, method, params) to reach the agent.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
interface TunnelServer {
|
|
72
|
+
app: Hono;
|
|
73
|
+
relay: TunnelRelay;
|
|
74
|
+
heartbeat: HeartbeatManager;
|
|
75
|
+
wsHandlers: ReturnType<typeof createWsHandlers>;
|
|
76
|
+
stop: () => void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Start a tunnel relay server. Handles everything:
|
|
80
|
+
* - HTTP routes for connections list + RPC
|
|
81
|
+
* - WebSocket upgrades on /ws for agent connections
|
|
82
|
+
* - Heartbeat ping/pong
|
|
83
|
+
* - HMAC signing key derivation via onAuthenticate hook
|
|
84
|
+
*
|
|
85
|
+
* Requires Bun runtime for WebSocket server support.
|
|
86
|
+
*/
|
|
87
|
+
declare function startTunnelServer(config?: TunnelServerConfig): TunnelServer;
|
|
88
|
+
|
|
89
|
+
export { HeartbeatManager, TunnelRelay, TunnelRelayError, type TunnelServer, type WsHandlerOptions, type WsHandlers, createTunnelRouter, createWsHandlers, startTunnelServer };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TunnelRelay, TunnelRelayError } from "./relay";
|
|
2
|
+
import { HeartbeatManager } from "./heartbeat";
|
|
3
|
+
import { createWsHandlers } from "./ws-handler";
|
|
4
|
+
import { createTunnelRouter } from "./routes";
|
|
5
|
+
import { startTunnelServer } from "./server";
|
|
6
|
+
export {
|
|
7
|
+
HeartbeatManager,
|
|
8
|
+
TunnelRelay,
|
|
9
|
+
TunnelRelayError,
|
|
10
|
+
createTunnelRouter,
|
|
11
|
+
createWsHandlers,
|
|
12
|
+
startTunnelServer
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["export { TunnelRelay, TunnelRelayError } from './relay';\nexport { HeartbeatManager } from './heartbeat';\nexport { createWsHandlers } from './ws-handler';\nexport type { WsHandlers, WsHandlerOptions } from './ws-handler';\nexport { createTunnelRouter } from './routes';\nexport { startTunnelServer } from './server';\nexport type { TunnelServer } from './server';\n"],"mappings":"AAAA,SAAS,aAAa,wBAAwB;AAC9C,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AAEjC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { A as AgentInfo, a as AuthResult, H as HeartbeatConfig, J as JsonRpcError, b as JsonRpcErrorResponse, c as JsonRpcMessage, d as JsonRpcNotification, e as JsonRpcRequest, f as JsonRpcResponse, g as JsonRpcSuccessResponse, P as PendingRPC, R as RelayRpcOptions, S as SignedJsonRpcNotification, h as SignedJsonRpcRequest, T as TunnelAuthMessage, i as TunnelCapability, j as TunnelErrorCode, k as TunnelErrorCodeValue, l as TunnelMethod, m as TunnelMethods, n as TunnelRelayConfig, o as TunnelRelayEvents, p as TunnelRpcParams, q as TunnelServerConfig } from '../types-Dpwrd8Ai.js';
|
|
2
|
+
|
|
3
|
+
declare function deriveSigningKey(token: string, secret: string): string;
|
|
4
|
+
declare function signMessage(signingKey: string, payload: string, nonce: number): string;
|
|
5
|
+
declare function verifyMessageSignature(signingKey: string, payload: string, nonce: number, signature: string): boolean;
|
|
6
|
+
declare function generateToken(prefix?: string): string;
|
|
7
|
+
declare function hashToken(token: string, secret: string): string;
|
|
8
|
+
declare function timingSafeStringEqual(a: string, b: string): boolean;
|
|
9
|
+
|
|
10
|
+
export { deriveSigningKey, generateToken, hashToken, signMessage, timingSafeStringEqual, verifyMessageSignature };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveSigningKey,
|
|
3
|
+
signMessage,
|
|
4
|
+
verifyMessageSignature,
|
|
5
|
+
generateToken,
|
|
6
|
+
hashToken,
|
|
7
|
+
timingSafeStringEqual
|
|
8
|
+
} from "./crypto";
|
|
9
|
+
import { TunnelErrorCode, TunnelMethods } from "./types";
|
|
10
|
+
export {
|
|
11
|
+
TunnelErrorCode,
|
|
12
|
+
TunnelMethods,
|
|
13
|
+
deriveSigningKey,
|
|
14
|
+
generateToken,
|
|
15
|
+
hashToken,
|
|
16
|
+
signMessage,
|
|
17
|
+
timingSafeStringEqual,
|
|
18
|
+
verifyMessageSignature
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/shared/index.ts"],"sourcesContent":["export {\n deriveSigningKey,\n signMessage,\n verifyMessageSignature,\n generateToken,\n hashToken,\n timingSafeStringEqual,\n} from './crypto';\n\nexport type {\n JsonRpcRequest,\n JsonRpcSuccessResponse,\n JsonRpcErrorResponse,\n JsonRpcError,\n JsonRpcResponse,\n JsonRpcNotification,\n JsonRpcMessage,\n SignedJsonRpcRequest,\n SignedJsonRpcNotification,\n PendingRPC,\n TunnelRpcParams,\n RelayRpcOptions,\n AgentInfo,\n TunnelRelayEvents,\n TunnelRelayConfig,\n HeartbeatConfig,\n TunnelServerConfig,\n TunnelAuthMessage,\n AuthResult,\n TunnelCapability,\n TunnelMethod,\n TunnelErrorCodeValue,\n} from './types';\n\nexport { TunnelErrorCode, TunnelMethods } from './types';\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA2BP,SAAS,iBAAiB,qBAAqB;","names":[]}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
interface JsonRpcRequest {
|
|
2
|
+
jsonrpc: '2.0';
|
|
3
|
+
id: string;
|
|
4
|
+
method: string;
|
|
5
|
+
params?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
interface JsonRpcSuccessResponse {
|
|
8
|
+
jsonrpc: '2.0';
|
|
9
|
+
id: string;
|
|
10
|
+
result: unknown;
|
|
11
|
+
}
|
|
12
|
+
interface JsonRpcErrorResponse {
|
|
13
|
+
jsonrpc: '2.0';
|
|
14
|
+
id: string;
|
|
15
|
+
error: JsonRpcError;
|
|
16
|
+
}
|
|
17
|
+
interface JsonRpcError {
|
|
18
|
+
code: number;
|
|
19
|
+
message: string;
|
|
20
|
+
data?: unknown;
|
|
21
|
+
}
|
|
22
|
+
type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
|
23
|
+
interface JsonRpcNotification {
|
|
24
|
+
jsonrpc: '2.0';
|
|
25
|
+
method: string;
|
|
26
|
+
params?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
|
29
|
+
declare const TunnelErrorCode: {
|
|
30
|
+
readonly PERMISSION_DENIED: -32000;
|
|
31
|
+
readonly CAPABILITY_NOT_REGISTERED: -32001;
|
|
32
|
+
readonly TIMEOUT: -32002;
|
|
33
|
+
readonly LOCAL_ERROR: -32003;
|
|
34
|
+
readonly NOT_CONNECTED: -32004;
|
|
35
|
+
readonly EXPIRED: -32005;
|
|
36
|
+
readonly RATE_LIMITED: -32006;
|
|
37
|
+
readonly AUTH_FAILED: -32007;
|
|
38
|
+
};
|
|
39
|
+
type TunnelErrorCodeValue = (typeof TunnelErrorCode)[keyof typeof TunnelErrorCode];
|
|
40
|
+
type TunnelCapability = 'filesystem' | 'shell' | 'network' | 'apps' | 'hardware' | 'desktop' | 'gpu';
|
|
41
|
+
declare const TunnelMethods: {
|
|
42
|
+
readonly 'fs.read': "filesystem";
|
|
43
|
+
readonly 'fs.write': "filesystem";
|
|
44
|
+
readonly 'fs.list': "filesystem";
|
|
45
|
+
readonly 'fs.stat': "filesystem";
|
|
46
|
+
readonly 'fs.delete': "filesystem";
|
|
47
|
+
readonly 'shell.exec': "shell";
|
|
48
|
+
readonly 'desktop.screenshot': "desktop";
|
|
49
|
+
readonly 'desktop.mouse.click': "desktop";
|
|
50
|
+
readonly 'desktop.mouse.move': "desktop";
|
|
51
|
+
readonly 'desktop.mouse.drag': "desktop";
|
|
52
|
+
readonly 'desktop.mouse.scroll': "desktop";
|
|
53
|
+
readonly 'desktop.mouse.position': "desktop";
|
|
54
|
+
readonly 'desktop.keyboard.type': "desktop";
|
|
55
|
+
readonly 'desktop.keyboard.key': "desktop";
|
|
56
|
+
readonly 'desktop.window.list': "desktop";
|
|
57
|
+
readonly 'desktop.window.focus': "desktop";
|
|
58
|
+
readonly 'desktop.window.resize': "desktop";
|
|
59
|
+
readonly 'desktop.window.close': "desktop";
|
|
60
|
+
readonly 'desktop.window.minimize': "desktop";
|
|
61
|
+
readonly 'desktop.app.launch': "desktop";
|
|
62
|
+
readonly 'desktop.app.quit': "desktop";
|
|
63
|
+
readonly 'desktop.app.list': "desktop";
|
|
64
|
+
readonly 'desktop.clipboard.read': "desktop";
|
|
65
|
+
readonly 'desktop.clipboard.write': "desktop";
|
|
66
|
+
readonly 'desktop.screen.info': "desktop";
|
|
67
|
+
readonly 'desktop.cursor.image': "desktop";
|
|
68
|
+
readonly 'desktop.ax.tree': "desktop";
|
|
69
|
+
readonly 'desktop.ax.action': "desktop";
|
|
70
|
+
readonly 'desktop.ax.set_value': "desktop";
|
|
71
|
+
readonly 'desktop.ax.focus': "desktop";
|
|
72
|
+
readonly 'desktop.ax.search': "desktop";
|
|
73
|
+
readonly 'net.request': "network";
|
|
74
|
+
readonly 'net.port_forward.start': "network";
|
|
75
|
+
readonly 'net.port_forward.stop': "network";
|
|
76
|
+
readonly 'tunnel.ping': null;
|
|
77
|
+
readonly 'tunnel.pong': null;
|
|
78
|
+
readonly 'tunnel.permission.revoked': null;
|
|
79
|
+
readonly 'tunnel.permissions.sync': null;
|
|
80
|
+
readonly 'tunnel.token.rotated': null;
|
|
81
|
+
};
|
|
82
|
+
type TunnelMethod = keyof typeof TunnelMethods;
|
|
83
|
+
interface PendingRPC {
|
|
84
|
+
resolve: (value: unknown) => void;
|
|
85
|
+
reject: (error: Error) => void;
|
|
86
|
+
timer: ReturnType<typeof setTimeout>;
|
|
87
|
+
method: string;
|
|
88
|
+
tunnelId: string;
|
|
89
|
+
startedAt: number;
|
|
90
|
+
}
|
|
91
|
+
interface SignedJsonRpcRequest extends JsonRpcRequest {
|
|
92
|
+
_sig: string;
|
|
93
|
+
_nonce: number;
|
|
94
|
+
}
|
|
95
|
+
interface SignedJsonRpcNotification extends JsonRpcNotification {
|
|
96
|
+
_sig: string;
|
|
97
|
+
_nonce: number;
|
|
98
|
+
}
|
|
99
|
+
interface TunnelRpcParams {
|
|
100
|
+
capability: TunnelCapability;
|
|
101
|
+
operation: string;
|
|
102
|
+
args: Record<string, unknown>;
|
|
103
|
+
permissionId?: string;
|
|
104
|
+
}
|
|
105
|
+
interface RelayRpcOptions {
|
|
106
|
+
timeoutMs?: number;
|
|
107
|
+
}
|
|
108
|
+
/** Public agent info — does NOT expose signing key. */
|
|
109
|
+
interface AgentInfo {
|
|
110
|
+
tunnelId: string;
|
|
111
|
+
connectedAt: number;
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
}
|
|
114
|
+
interface TunnelRelayEvents {
|
|
115
|
+
'agent:connect': {
|
|
116
|
+
tunnelId: string;
|
|
117
|
+
metadata?: Record<string, unknown>;
|
|
118
|
+
};
|
|
119
|
+
'agent:disconnect': {
|
|
120
|
+
tunnelId: string;
|
|
121
|
+
};
|
|
122
|
+
'agent:timeout': {
|
|
123
|
+
tunnelId: string;
|
|
124
|
+
};
|
|
125
|
+
'rpc:request': {
|
|
126
|
+
tunnelId: string;
|
|
127
|
+
method: string;
|
|
128
|
+
requestId: string;
|
|
129
|
+
};
|
|
130
|
+
'rpc:response': {
|
|
131
|
+
tunnelId: string;
|
|
132
|
+
method: string;
|
|
133
|
+
requestId: string;
|
|
134
|
+
durationMs: number;
|
|
135
|
+
};
|
|
136
|
+
'rpc:error': {
|
|
137
|
+
tunnelId: string;
|
|
138
|
+
method: string;
|
|
139
|
+
requestId: string;
|
|
140
|
+
error: Error;
|
|
141
|
+
};
|
|
142
|
+
'connection:replaced': {
|
|
143
|
+
tunnelId: string;
|
|
144
|
+
};
|
|
145
|
+
'message:pong': {
|
|
146
|
+
tunnelId: string;
|
|
147
|
+
params?: Record<string, unknown>;
|
|
148
|
+
};
|
|
149
|
+
'message:raw': {
|
|
150
|
+
tunnelId: string;
|
|
151
|
+
message: unknown;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
interface TunnelRelayConfig {
|
|
155
|
+
rpcTimeoutMs?: number;
|
|
156
|
+
maxWsMessageSize?: number;
|
|
157
|
+
}
|
|
158
|
+
interface HeartbeatConfig {
|
|
159
|
+
intervalMs?: number;
|
|
160
|
+
maxMissed?: number;
|
|
161
|
+
}
|
|
162
|
+
/** Auth handshake message sent by agent as first WS message. */
|
|
163
|
+
interface TunnelAuthMessage {
|
|
164
|
+
type: 'auth';
|
|
165
|
+
token: string;
|
|
166
|
+
}
|
|
167
|
+
/** Result returned by onAuthenticate hook on success. */
|
|
168
|
+
interface AuthResult {
|
|
169
|
+
signingKey: string;
|
|
170
|
+
metadata?: Record<string, unknown>;
|
|
171
|
+
}
|
|
172
|
+
interface TunnelServerConfig {
|
|
173
|
+
port?: number;
|
|
174
|
+
relay?: TunnelRelayConfig;
|
|
175
|
+
heartbeat?: HeartbeatConfig;
|
|
176
|
+
/**
|
|
177
|
+
* Called when an agent sends its auth handshake.
|
|
178
|
+
* Return { signingKey, metadata } to accept, or null to reject.
|
|
179
|
+
* If not provided, all connections are rejected.
|
|
180
|
+
*/
|
|
181
|
+
onAuthenticate?: (tunnelId: string, token: string) => Promise<AuthResult | null>;
|
|
182
|
+
/**
|
|
183
|
+
* Called before relaying an RPC to the agent.
|
|
184
|
+
* Return false to deny. If not provided, all RPCs are allowed.
|
|
185
|
+
*/
|
|
186
|
+
onAuthorizeRPC?: (tunnelId: string, method: string, params: Record<string, unknown>) => Promise<boolean>;
|
|
187
|
+
/**
|
|
188
|
+
* Called before handling HTTP requests to relay routes (/connections, /rpc).
|
|
189
|
+
* Return false to deny. If not provided, routes are open.
|
|
190
|
+
*/
|
|
191
|
+
onAuthorizeHTTP?: (req: Request) => Promise<boolean>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { type AgentInfo as A, type HeartbeatConfig as H, type JsonRpcError as J, type PendingRPC as P, type RelayRpcOptions as R, type SignedJsonRpcNotification as S, type TunnelAuthMessage as T, type AuthResult as a, type JsonRpcErrorResponse as b, type JsonRpcMessage as c, type JsonRpcNotification as d, type JsonRpcRequest as e, type JsonRpcResponse as f, type JsonRpcSuccessResponse as g, type SignedJsonRpcRequest as h, type TunnelCapability as i, TunnelErrorCode as j, type TunnelErrorCodeValue as k, type TunnelMethod as l, TunnelMethods as m, type TunnelRelayConfig as n, type TunnelRelayEvents as o, type TunnelRpcParams as p, type TunnelServerConfig as q };
|
package/package.json
CHANGED
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kortix/agent-tunnel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Tunnel relay between cloud AI agents and local machines — server relay, local agent, client SDK, JSON-RPC, HMAC signing",
|
|
7
|
-
"main": "
|
|
8
|
-
"types": "
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
|
-
".":
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"./
|
|
15
|
-
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./shared": {
|
|
15
|
+
"import": "./dist/shared/index.js",
|
|
16
|
+
"types": "./dist/shared/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./server": {
|
|
19
|
+
"import": "./dist/server/index.js",
|
|
20
|
+
"types": "./dist/server/index.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./client": {
|
|
23
|
+
"import": "./dist/client/index.js",
|
|
24
|
+
"types": "./dist/client/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./agent": {
|
|
27
|
+
"import": "./dist/agent/index.js",
|
|
28
|
+
"types": "./dist/agent/index.d.ts"
|
|
29
|
+
}
|
|
16
30
|
},
|
|
17
31
|
"bin": {
|
|
18
|
-
"agent-tunnel": "
|
|
19
|
-
"agent-tunnel-cli": "
|
|
32
|
+
"agent-tunnel": "dist/agent-cli.js",
|
|
33
|
+
"agent-tunnel-cli": "dist/client-cli.js"
|
|
20
34
|
},
|
|
21
35
|
"files": [
|
|
36
|
+
"dist/",
|
|
22
37
|
"src/"
|
|
23
38
|
],
|
|
24
39
|
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
25
42
|
"typecheck": "tsc --noEmit"
|
|
26
43
|
},
|
|
27
44
|
"dependencies": {
|
|
28
|
-
"hono": "^4.4.0"
|
|
45
|
+
"hono": "^4.4.0",
|
|
46
|
+
"ws": "^8.18.0"
|
|
29
47
|
},
|
|
30
48
|
"devDependencies": {
|
|
31
49
|
"@types/bun": "^1.3.9",
|
|
50
|
+
"@types/ws": "^8.5.13",
|
|
51
|
+
"tsup": "^8.4.0",
|
|
32
52
|
"typescript": "^5.4.0"
|
|
33
53
|
},
|
|
34
54
|
"keywords": ["tunnel", "relay", "websocket", "json-rpc", "hmac", "agent", "sdk"],
|
package/src/agent/agent.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { TunnelConfig } from './config';
|
|
|
3
3
|
import { CapabilityRegistry, type RpcHandler } from './capabilities/index';
|
|
4
4
|
import { PermissionGuard } from './security/permission-guard';
|
|
5
5
|
import type { LocalPermission } from './security/permission-guard';
|
|
6
|
-
import {
|
|
6
|
+
import { signMessage, verifyMessageSignature } from '../shared/crypto';
|
|
7
7
|
|
|
8
8
|
interface JsonRpcRequest {
|
|
9
9
|
jsonrpc: '2.0';
|
|
@@ -54,7 +54,7 @@ export class TunnelAgent {
|
|
|
54
54
|
private uptimeInterval: ReturnType<typeof setInterval> | null = null;
|
|
55
55
|
|
|
56
56
|
// HMAC signature verification
|
|
57
|
-
private signingKey: string;
|
|
57
|
+
private signingKey: string | null = null;
|
|
58
58
|
private lastNonce = 0;
|
|
59
59
|
private responseNonce = 0;
|
|
60
60
|
|
|
@@ -62,7 +62,6 @@ export class TunnelAgent {
|
|
|
62
62
|
this.config = config;
|
|
63
63
|
this.registry = registry;
|
|
64
64
|
this.permissionGuard = new PermissionGuard();
|
|
65
|
-
this.signingKey = deriveSigningKey(config.token);
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
connect(): void {
|
|
@@ -116,12 +115,11 @@ export class TunnelAgent {
|
|
|
116
115
|
this.uptime = 0;
|
|
117
116
|
this.lastNonce = 0;
|
|
118
117
|
this.responseNonce = 0;
|
|
118
|
+
this.signingKey = null;
|
|
119
119
|
this.uptimeInterval = setInterval(() => { this.uptime++; }, 1000);
|
|
120
120
|
|
|
121
121
|
// Send auth handshake as first message (token never in URL)
|
|
122
122
|
this.send({ type: 'auth', token: this.config.token });
|
|
123
|
-
|
|
124
|
-
log(`${c.green}●${c.reset}`, `Connected ${c.reset}${c.gray}(${this.registry.getCapabilityNames().join(', ')})${c.reset}`);
|
|
125
123
|
});
|
|
126
124
|
|
|
127
125
|
this.ws.addEventListener('message', (event) => {
|
|
@@ -150,7 +148,7 @@ export class TunnelAgent {
|
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
private async handleMessage(raw: string): Promise<void> {
|
|
153
|
-
let msg:
|
|
151
|
+
let msg: any;
|
|
154
152
|
try {
|
|
155
153
|
msg = JSON.parse(raw);
|
|
156
154
|
} catch {
|
|
@@ -158,6 +156,18 @@ export class TunnelAgent {
|
|
|
158
156
|
return;
|
|
159
157
|
}
|
|
160
158
|
|
|
159
|
+
// Handle auth_ok — server sends signing key after successful auth
|
|
160
|
+
if (msg.type === 'auth_ok' && msg.signingKey) {
|
|
161
|
+
this.signingKey = msg.signingKey;
|
|
162
|
+
log(`${c.green}●${c.reset}`, `Connected ${c.reset}${c.gray}(${this.registry.getCapabilityNames().join(', ')})${c.reset}`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!this.signingKey) {
|
|
167
|
+
log(`${c.yellow}!${c.reset}`, `Message received before auth completed`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
161
171
|
if (!this.verifyIncomingSignature(msg, raw)) {
|
|
162
172
|
if ('id' in msg && msg.id) {
|
|
163
173
|
this.sendSignedError(msg.id, -32000, 'Invalid message signature');
|
|
@@ -232,7 +242,7 @@ export class TunnelAgent {
|
|
|
232
242
|
const { _sig, _nonce, ...payloadObj } = msg as any;
|
|
233
243
|
const payload = JSON.stringify(payloadObj);
|
|
234
244
|
|
|
235
|
-
if (!verifyMessageSignature(this.signingKey
|
|
245
|
+
if (!verifyMessageSignature(this.signingKey!, payload, nonce, sig)) {
|
|
236
246
|
log(`${c.red}✗${c.reset}`, `Invalid HMAC signature`);
|
|
237
247
|
return false;
|
|
238
248
|
}
|
|
@@ -278,7 +288,7 @@ export class TunnelAgent {
|
|
|
278
288
|
}
|
|
279
289
|
|
|
280
290
|
private sendSigned(data: Record<string, unknown>): void {
|
|
281
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
291
|
+
if (this.ws?.readyState === WebSocket.OPEN && this.signingKey) {
|
|
282
292
|
const nonce = ++this.responseNonce;
|
|
283
293
|
const payload = JSON.stringify(data);
|
|
284
294
|
const sig = signMessage(this.signingKey, payload, nonce);
|
|
@@ -313,7 +323,7 @@ export class TunnelAgent {
|
|
|
313
323
|
platform: platform(),
|
|
314
324
|
arch: arch(),
|
|
315
325
|
osVersion: release(),
|
|
316
|
-
agentVersion: '0.1.
|
|
326
|
+
agentVersion: '0.1.2',
|
|
317
327
|
},
|
|
318
328
|
},
|
|
319
329
|
});
|
|
@@ -4,7 +4,7 @@ import { join } from 'path';
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
|
|
6
6
|
const HELPER_VERSION = 'v1';
|
|
7
|
-
const BIN_DIR = join(homedir(), '.
|
|
7
|
+
const BIN_DIR = join(homedir(), '.agent-tunnel', 'bin');
|
|
8
8
|
const HELPER_PATH = join(BIN_DIR, `atspi-helper-${HELPER_VERSION}.py`);
|
|
9
9
|
|
|
10
10
|
const PYTHON_SOURCE = `#!/usr/bin/env python3
|
|
@@ -4,7 +4,7 @@ import { join } from 'path';
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
|
|
6
6
|
const HELPER_VERSION = 'v1';
|
|
7
|
-
const BIN_DIR = join(homedir(), '.
|
|
7
|
+
const BIN_DIR = join(homedir(), '.agent-tunnel', 'bin');
|
|
8
8
|
const HELPER_PATH = join(BIN_DIR, `desktop-helper-win-${HELPER_VERSION}.exe`);
|
|
9
9
|
|
|
10
10
|
const CSHARP_SOURCE = `
|
|
@@ -32,7 +32,7 @@ import type {
|
|
|
32
32
|
} from './types';
|
|
33
33
|
|
|
34
34
|
function tmpPath(): string {
|
|
35
|
-
return join(tmpdir(), `
|
|
35
|
+
return join(tmpdir(), `tunnel-ss-${randomBytes(6).toString('hex')}.png`);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function exec(cmd: string, args: string[]): Promise<string> {
|
|
@@ -32,7 +32,7 @@ import type {
|
|
|
32
32
|
} from './types';
|
|
33
33
|
|
|
34
34
|
function tmpPath(ext: string = '.png'): string {
|
|
35
|
-
return join(tmpdir(), `
|
|
35
|
+
return join(tmpdir(), `tunnel-ss-${randomBytes(6).toString('hex')}${ext}`);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function exec(cmd: string, args: string[], timeoutMs = 15000): Promise<string> {
|
|
@@ -4,7 +4,7 @@ import { join } from 'path';
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
|
|
6
6
|
const HELPER_VERSION = 'v4';
|
|
7
|
-
const BIN_DIR = join(homedir(), '.
|
|
7
|
+
const BIN_DIR = join(homedir(), '.agent-tunnel', 'bin');
|
|
8
8
|
const HELPER_PATH = join(BIN_DIR, `desktop-helper-${HELPER_VERSION}`);
|
|
9
9
|
|
|
10
10
|
const SWIFT_SOURCE = `
|