0xkobold 0.2.0 → 0.3.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/HEARTBEAT.md +40 -58
- package/README.md +387 -337
- package/dist/package.json +4 -2
- package/dist/src/auth/device-auth.js +202 -0
- package/dist/src/auth/device-auth.js.map +1 -0
- package/dist/src/auth/index.js +3 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/channels/index.js +8 -0
- package/dist/src/channels/index.js.map +1 -0
- package/dist/src/channels/slack/webhook.js +128 -0
- package/dist/src/channels/slack/webhook.js.map +1 -0
- package/dist/src/channels/telegram/bot.js +223 -0
- package/dist/src/channels/telegram/bot.js.map +1 -0
- package/dist/src/channels/whatsapp/integration.js +325 -0
- package/dist/src/channels/whatsapp/integration.js.map +1 -0
- package/dist/src/cli/commands/check.js +69 -0
- package/dist/src/cli/commands/check.js.map +1 -0
- package/dist/src/cli/commands/migrate.js +24 -0
- package/dist/src/cli/commands/migrate.js.map +1 -0
- package/dist/src/cli/commands/tailscale.js +81 -0
- package/dist/src/cli/commands/tailscale.js.map +1 -0
- package/dist/src/cli/commands/telegram.js +111 -0
- package/dist/src/cli/commands/telegram.js.map +1 -0
- package/dist/src/cli/commands/tui.js +40 -22
- package/dist/src/cli/commands/tui.js.map +1 -1
- package/dist/src/cli/commands/whatsapp.js +116 -0
- package/dist/src/cli/commands/whatsapp.js.map +1 -0
- package/dist/src/cli/program.js +20 -0
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/index.js +2 -9
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/config/manager.js +222 -0
- package/dist/src/config/manager.js.map +1 -0
- package/dist/src/documents/index.js +3 -0
- package/dist/src/documents/index.js.map +1 -0
- package/dist/src/documents/pdf.js +168 -0
- package/dist/src/documents/pdf.js.map +1 -0
- package/dist/src/gateway/client.js +318 -0
- package/dist/src/gateway/client.js.map +1 -0
- package/dist/src/infra/index.js +3 -0
- package/dist/src/infra/index.js.map +1 -0
- package/dist/src/infra/tailscale.js +163 -0
- package/dist/src/infra/tailscale.js.map +1 -0
- package/dist/src/media/audio.js +130 -0
- package/dist/src/media/audio.js.map +1 -0
- package/dist/src/media/index.js +4 -0
- package/dist/src/media/index.js.map +1 -0
- package/dist/src/media/vision.js +131 -0
- package/dist/src/media/vision.js.map +1 -0
- package/dist/src/migration/openclaw.js +498 -0
- package/dist/src/migration/openclaw.js.map +1 -0
- package/dist/src/sandbox/docker-runner.js +228 -0
- package/dist/src/sandbox/docker-runner.js.map +1 -0
- package/dist/src/sandbox/index.js +3 -0
- package/dist/src/sandbox/index.js.map +1 -0
- package/dist/src/skills/builtin/duplicate-detector.js +469 -0
- package/dist/src/skills/builtin/duplicate-detector.js.map +1 -0
- package/dist/src/skills/index.js +2 -0
- package/dist/src/skills/index.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF Document Support - v0.3.0
|
|
3
|
+
*
|
|
4
|
+
* Text extraction from PDF documents.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "node:fs/promises";
|
|
7
|
+
class PDFExtractor {
|
|
8
|
+
config;
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.config = {
|
|
11
|
+
maxPages: 100,
|
|
12
|
+
extractText: true,
|
|
13
|
+
extractMetadata: true,
|
|
14
|
+
...config,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Extract from file
|
|
19
|
+
*/
|
|
20
|
+
async extractFromFile(filePath) {
|
|
21
|
+
const buffer = await fs.readFile(filePath);
|
|
22
|
+
return this.extractFromBuffer(buffer);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extract from buffer
|
|
26
|
+
*/
|
|
27
|
+
async extractFromBuffer(buffer) {
|
|
28
|
+
// PDF magic number check
|
|
29
|
+
if (buffer.slice(0, 5).toString() !== "%PDF-") {
|
|
30
|
+
throw new Error("Invalid PDF file");
|
|
31
|
+
}
|
|
32
|
+
// Parse basic PDF structure
|
|
33
|
+
const result = {
|
|
34
|
+
text: "",
|
|
35
|
+
metadata: {},
|
|
36
|
+
pages: [],
|
|
37
|
+
};
|
|
38
|
+
// Extract text using regex (basic implementation)
|
|
39
|
+
// In production, use pdf-parse or pdf-lib
|
|
40
|
+
const textContent = this.extractTextFromPDF(buffer);
|
|
41
|
+
result.text = textContent;
|
|
42
|
+
result.pages = [{
|
|
43
|
+
pageNumber: 1,
|
|
44
|
+
text: textContent.slice(0, 5000), // First page preview
|
|
45
|
+
}];
|
|
46
|
+
// Extract metadata
|
|
47
|
+
if (this.config.extractMetadata) {
|
|
48
|
+
result.metadata = this.extractMetadata(buffer);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract text using basic PDF parsing
|
|
54
|
+
*/
|
|
55
|
+
extractTextFromPDF(buffer) {
|
|
56
|
+
const content = buffer.toString("latin1");
|
|
57
|
+
// Find text streams (BT ... ET)
|
|
58
|
+
const textRegex = /BT\s*((?:(?!ET).|\s)*)\s*ET/g;
|
|
59
|
+
const matches = content.match(textRegex) || [];
|
|
60
|
+
const extractedTexts = [];
|
|
61
|
+
for (const match of matches) {
|
|
62
|
+
// Extract Tj and TJ operands
|
|
63
|
+
const text = match
|
|
64
|
+
.replace(/BT|ET/g, "")
|
|
65
|
+
.replace(/\[[^\]]*\]TJ/g, (m) => {
|
|
66
|
+
// Handle TJ arrays
|
|
67
|
+
const inner = m.slice(1, -3);
|
|
68
|
+
return inner
|
|
69
|
+
.split(" ")
|
|
70
|
+
.filter((_, i) => i % 2 === 0)
|
|
71
|
+
.join("");
|
|
72
|
+
})
|
|
73
|
+
.replace(/\([^)]*\)Tj/g, (m) => {
|
|
74
|
+
// Handle Tj strings
|
|
75
|
+
return m.slice(1, -3);
|
|
76
|
+
})
|
|
77
|
+
.replace(/\d+\.?\d*\s+\d+\.?\d*\s+Td/g, " ") // Position operators
|
|
78
|
+
.replace(/\d+\.?\d*\s+TL/g, " ") // Line operators
|
|
79
|
+
.replace(/\s+/g, " ")
|
|
80
|
+
.trim();
|
|
81
|
+
if (text)
|
|
82
|
+
extractedTexts.push(text);
|
|
83
|
+
}
|
|
84
|
+
// Also try obj streams
|
|
85
|
+
const streamRegex = /stream\r?\n([\s\S]*?)endstream/g;
|
|
86
|
+
let streamMatch;
|
|
87
|
+
while ((streamMatch = streamRegex.exec(content)) !== null) {
|
|
88
|
+
const stream = streamMatch[1];
|
|
89
|
+
// Check if it looks like text
|
|
90
|
+
if (stream.includes("(") && stream.includes("Tj")) {
|
|
91
|
+
const text = stream
|
|
92
|
+
.replace(/\([^)]*\)Tj/g, (m) => m.slice(1, -3))
|
|
93
|
+
.replace(/\s+/g, " ")
|
|
94
|
+
.trim();
|
|
95
|
+
if (text.length > 3)
|
|
96
|
+
extractedTexts.push(text);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return extractedTexts.join("\n").slice(0, 50000); // Limit output
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Extract metadata
|
|
103
|
+
*/
|
|
104
|
+
extractMetadata(buffer) {
|
|
105
|
+
const content = buffer.toString("latin1");
|
|
106
|
+
const metadata = {};
|
|
107
|
+
// Title
|
|
108
|
+
const titleMatch = content.match(/\/Title\s*\(([^)]+)\)/);
|
|
109
|
+
if (titleMatch)
|
|
110
|
+
metadata.title = this.unescapePDFString(titleMatch[1]);
|
|
111
|
+
// Author
|
|
112
|
+
const authorMatch = content.match(/\/Author\s*\(([^)]+)\)/);
|
|
113
|
+
if (authorMatch)
|
|
114
|
+
metadata.author = this.unescapePDFString(authorMatch[1]);
|
|
115
|
+
// Subject
|
|
116
|
+
const subjectMatch = content.match(/\/Subject\s*\(([^)]+)\)/);
|
|
117
|
+
if (subjectMatch)
|
|
118
|
+
metadata.subject = this.unescapePDFString(subjectMatch[1]);
|
|
119
|
+
// Creator
|
|
120
|
+
const creatorMatch = content.match(/\/Creator\s*\(([^)]+)\)/);
|
|
121
|
+
if (creatorMatch)
|
|
122
|
+
metadata.creator = this.unescapePDFString(creatorMatch[1]);
|
|
123
|
+
// Producer
|
|
124
|
+
const producerMatch = content.match(/\/Producer\s*\(([^)]+)\)/);
|
|
125
|
+
if (producerMatch)
|
|
126
|
+
metadata.producer = this.unescapePDFString(producerMatch[1]);
|
|
127
|
+
// Page count
|
|
128
|
+
const pagesMatch = content.match(/\/Count\s+(\d+)/);
|
|
129
|
+
if (pagesMatch)
|
|
130
|
+
metadata.pageCount = parseInt(pagesMatch[1], 10);
|
|
131
|
+
// Keywords
|
|
132
|
+
const keywordsMatch = content.match(/\/Keywords\s*\(([^)]+)\)/);
|
|
133
|
+
if (keywordsMatch)
|
|
134
|
+
metadata.keywords = this.unescapePDFString(keywordsMatch[1]);
|
|
135
|
+
return metadata;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Unescape PDF string
|
|
139
|
+
*/
|
|
140
|
+
unescapePDFString(str) {
|
|
141
|
+
return str
|
|
142
|
+
.replace(/\\n/g, "\n")
|
|
143
|
+
.replace(/\\r/g, "\r")
|
|
144
|
+
.replace(/\\t/g, "\t")
|
|
145
|
+
.replace(/\\\\/g, "\\")
|
|
146
|
+
.replace(/\\\(/g, "(")
|
|
147
|
+
.replace(/\\\)/g, ")");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get PDF info
|
|
151
|
+
*/
|
|
152
|
+
async getInfo(filePath) {
|
|
153
|
+
const buffer = await fs.readFile(filePath);
|
|
154
|
+
return this.extractMetadata(buffer);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Helper functions
|
|
158
|
+
export async function extractPDF(filePath, config) {
|
|
159
|
+
const extractor = new PDFExtractor(config);
|
|
160
|
+
return extractor.extractFromFile(filePath);
|
|
161
|
+
}
|
|
162
|
+
export async function extractPDFText(filePath) {
|
|
163
|
+
const result = await extractPDF(filePath, { extractMetadata: false });
|
|
164
|
+
return result.text;
|
|
165
|
+
}
|
|
166
|
+
export { PDFExtractor };
|
|
167
|
+
export default PDFExtractor;
|
|
168
|
+
//# sourceMappingURL=pdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../../src/documents/pdf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway Client - Remote/VPS Support
|
|
3
|
+
*
|
|
4
|
+
* Connect to remote or local gateway server for distributed architecture.
|
|
5
|
+
* Based on kod/OpenClaw client architecture - simplified for 0xKobold.
|
|
6
|
+
*/
|
|
7
|
+
import { EventEmitter } from "events";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import * as fs from "node:fs/promises";
|
|
11
|
+
export const GATEWAY_STATE = {
|
|
12
|
+
DISCONNECTED: "disconnected",
|
|
13
|
+
CONNECTING: "connecting",
|
|
14
|
+
CONNECTED: "connected",
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* GatewayClient - WebSocket client for connecting to local or remote gateway
|
|
18
|
+
*
|
|
19
|
+
* Architecture:
|
|
20
|
+
* - Local mode: Connects to ws://localhost:7777
|
|
21
|
+
* - Remote mode: Connects to wss://your-vps.com:7777
|
|
22
|
+
* - Handles auth tokens (local storage in ~/.0xkobold/)
|
|
23
|
+
* - Auto-reconnect with exponential backoff
|
|
24
|
+
* - Heartbeat/ping-pong for connection health
|
|
25
|
+
*/
|
|
26
|
+
export class GatewayClient extends EventEmitter {
|
|
27
|
+
ws = null;
|
|
28
|
+
config;
|
|
29
|
+
state = GATEWAY_STATE.DISCONNECTED;
|
|
30
|
+
reconnectTimer;
|
|
31
|
+
pingTimer;
|
|
32
|
+
reconnectCount = 0;
|
|
33
|
+
pendingMessages = [];
|
|
34
|
+
messageId = 0;
|
|
35
|
+
constructor(config) {
|
|
36
|
+
super();
|
|
37
|
+
this.config = {
|
|
38
|
+
url: config.url,
|
|
39
|
+
autoReconnect: config.autoReconnect ?? true,
|
|
40
|
+
reconnectDelay: config.reconnectDelay ?? 1000,
|
|
41
|
+
reconnectAttempts: config.reconnectAttempts ?? 10,
|
|
42
|
+
token: config.token,
|
|
43
|
+
deviceToken: config.deviceToken,
|
|
44
|
+
password: config.password,
|
|
45
|
+
clientName: config.clientName ?? "0xKobold-Client",
|
|
46
|
+
clientVersion: config.clientVersion ?? "0.3.0",
|
|
47
|
+
deviceId: config.deviceId ?? "unknown",
|
|
48
|
+
capabilities: config.capabilities ?? ["chat", "files", "heartbeat"],
|
|
49
|
+
onConnect: config.onConnect,
|
|
50
|
+
onDisconnect: config.onDisconnect,
|
|
51
|
+
onMessage: config.onMessage,
|
|
52
|
+
onError: config.onError,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Connect to gateway
|
|
57
|
+
*/
|
|
58
|
+
async connect() {
|
|
59
|
+
if (this.state === GATEWAY_STATE.CONNECTED) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
this.state = GATEWAY_STATE.CONNECTING;
|
|
63
|
+
this.emit("stateChange", this.state);
|
|
64
|
+
try {
|
|
65
|
+
// Determine protocol (ws:// vs wss://)
|
|
66
|
+
const isSecure = this.config.url.startsWith("wss://");
|
|
67
|
+
const wsUrl = this.config.url;
|
|
68
|
+
console.log(`[GatewayClient] Connecting to ${isSecure ? "secure" : "insecure"} WebSocket...`);
|
|
69
|
+
console.log(`[GatewayClient] URL: ${wsUrl}`);
|
|
70
|
+
// Create WebSocket
|
|
71
|
+
this.ws = new WebSocket(wsUrl);
|
|
72
|
+
// Setup handlers
|
|
73
|
+
this.ws.onopen = () => this.handleOpen();
|
|
74
|
+
this.ws.onclose = (event) => this.handleClose(event.code, event.reason);
|
|
75
|
+
this.ws.onerror = (err) => this.handleError(new Error(String(err)));
|
|
76
|
+
this.ws.onmessage = (msg) => this.handleMessage(msg.data);
|
|
77
|
+
// Wait for connection
|
|
78
|
+
await this.waitForConnection();
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
this.handleError(err);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Disconnect from gateway
|
|
88
|
+
*/
|
|
89
|
+
disconnect() {
|
|
90
|
+
this.clearTimers();
|
|
91
|
+
if (this.ws) {
|
|
92
|
+
this.ws.close(1000, "Client disconnect");
|
|
93
|
+
this.ws = null;
|
|
94
|
+
}
|
|
95
|
+
this.state = GATEWAY_STATE.DISCONNECTED;
|
|
96
|
+
this.emit("stateChange", this.state);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Send message to gateway
|
|
100
|
+
*/
|
|
101
|
+
send(message) {
|
|
102
|
+
const fullMessage = {
|
|
103
|
+
...message,
|
|
104
|
+
id: this.generateMessageId(),
|
|
105
|
+
timestamp: Date.now(),
|
|
106
|
+
};
|
|
107
|
+
if (this.state === GATEWAY_STATE.CONNECTED && this.ws) {
|
|
108
|
+
this.ws.send(JSON.stringify(fullMessage));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Queue for later
|
|
112
|
+
this.pendingMessages.push(fullMessage);
|
|
113
|
+
}
|
|
114
|
+
return fullMessage.id;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Send chat request
|
|
118
|
+
*/
|
|
119
|
+
chat(message, context) {
|
|
120
|
+
return this.send({
|
|
121
|
+
type: "request",
|
|
122
|
+
channel: "chat",
|
|
123
|
+
payload: {
|
|
124
|
+
message,
|
|
125
|
+
context,
|
|
126
|
+
capabilities: this.config.capabilities,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Send ping to check connection
|
|
132
|
+
*/
|
|
133
|
+
ping() {
|
|
134
|
+
this.send({
|
|
135
|
+
type: "ping",
|
|
136
|
+
payload: { timestamp: Date.now() },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get current state
|
|
141
|
+
*/
|
|
142
|
+
getState() {
|
|
143
|
+
return this.state;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if connected
|
|
147
|
+
*/
|
|
148
|
+
isConnected() {
|
|
149
|
+
return this.state === GATEWAY_STATE.CONNECTED;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Load cached device token from disk
|
|
153
|
+
*/
|
|
154
|
+
static async loadDeviceToken() {
|
|
155
|
+
const tokenPath = join(homedir(), ".0xkobold", ".device-token");
|
|
156
|
+
try {
|
|
157
|
+
const token = await fs.readFile(tokenPath, "utf-8");
|
|
158
|
+
return token.trim();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Save device token to disk
|
|
166
|
+
*/
|
|
167
|
+
static async saveDeviceToken(token) {
|
|
168
|
+
const tokenPath = join(homedir(), ".0xkobold", ".device-token");
|
|
169
|
+
await fs.mkdir(join(homedir(), ".0xkobold"), { recursive: true });
|
|
170
|
+
await fs.writeFile(tokenPath, token, "utf-8");
|
|
171
|
+
await fs.chmod(tokenPath, 0o600); // Secure permissions
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Clear saved device token
|
|
175
|
+
*/
|
|
176
|
+
static async clearDeviceToken() {
|
|
177
|
+
const tokenPath = join(homedir(), ".0xkobold", ".device-token");
|
|
178
|
+
try {
|
|
179
|
+
await fs.unlink(tokenPath);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Ignore if doesn't exist
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Private methods
|
|
186
|
+
handleOpen() {
|
|
187
|
+
console.log("[GatewayClient] Connected successfully");
|
|
188
|
+
this.state = GATEWAY_STATE.CONNECTED;
|
|
189
|
+
this.reconnectCount = 0;
|
|
190
|
+
// Send authentication
|
|
191
|
+
this.sendAuth();
|
|
192
|
+
// Process queued messages
|
|
193
|
+
this.processPendingMessages();
|
|
194
|
+
// Start heartbeat
|
|
195
|
+
this.startHeartbeat();
|
|
196
|
+
this.emit("connected");
|
|
197
|
+
this.config.onConnect?.();
|
|
198
|
+
}
|
|
199
|
+
handleClose(code, reason) {
|
|
200
|
+
console.log(`[GatewayClient] Disconnected: ${code} - ${reason}`);
|
|
201
|
+
this.state = GATEWAY_STATE.DISCONNECTED;
|
|
202
|
+
this.emit("disconnected", code, reason);
|
|
203
|
+
this.config.onDisconnect?.(code, reason);
|
|
204
|
+
this.clearTimers();
|
|
205
|
+
// Attempt reconnect if enabled
|
|
206
|
+
if (this.config.autoReconnect && this.reconnectCount < this.config.reconnectAttempts) {
|
|
207
|
+
this.scheduleReconnect();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
handleError(err) {
|
|
211
|
+
console.error("[GatewayClient] Error:", err.message);
|
|
212
|
+
this.emit("error", err);
|
|
213
|
+
this.config.onError?.(err);
|
|
214
|
+
}
|
|
215
|
+
handleMessage(data) {
|
|
216
|
+
try {
|
|
217
|
+
const message = JSON.parse(data.toString());
|
|
218
|
+
// Handle ping/pong
|
|
219
|
+
if (message.type === "pong") {
|
|
220
|
+
this.emit("pong", message);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.emit("message", message);
|
|
224
|
+
this.config.onMessage?.(message);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
console.error("[GatewayClient] Failed to parse message:", err);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
sendAuth() {
|
|
231
|
+
const authPayload = {
|
|
232
|
+
client: this.config.clientName,
|
|
233
|
+
version: this.config.clientVersion,
|
|
234
|
+
deviceId: this.config.deviceId,
|
|
235
|
+
capabilities: this.config.capabilities,
|
|
236
|
+
};
|
|
237
|
+
if (this.config.token) {
|
|
238
|
+
authPayload.token = this.config.token;
|
|
239
|
+
}
|
|
240
|
+
if (this.config.deviceToken) {
|
|
241
|
+
authPayload.deviceToken = this.config.deviceToken;
|
|
242
|
+
}
|
|
243
|
+
if (this.config.password) {
|
|
244
|
+
authPayload.password = this.config.password;
|
|
245
|
+
}
|
|
246
|
+
this.send({
|
|
247
|
+
type: "request",
|
|
248
|
+
channel: "auth",
|
|
249
|
+
payload: authPayload,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
waitForConnection() {
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
const timeout = setTimeout(() => {
|
|
255
|
+
reject(new Error("Connection timeout"));
|
|
256
|
+
}, 10000);
|
|
257
|
+
const onConnect = () => {
|
|
258
|
+
clearTimeout(timeout);
|
|
259
|
+
this.off("error", onError);
|
|
260
|
+
resolve();
|
|
261
|
+
};
|
|
262
|
+
const onError = (err) => {
|
|
263
|
+
clearTimeout(timeout);
|
|
264
|
+
this.off("connected", onConnect);
|
|
265
|
+
reject(err);
|
|
266
|
+
};
|
|
267
|
+
this.once("connected", onConnect);
|
|
268
|
+
this.once("error", onError);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
scheduleReconnect() {
|
|
272
|
+
this.reconnectCount++;
|
|
273
|
+
const delay = this.config.reconnectDelay * Math.min(this.reconnectCount, 5);
|
|
274
|
+
console.log(`[GatewayClient] Reconnecting in ${delay}ms (attempt ${this.reconnectCount})`);
|
|
275
|
+
this.reconnectTimer = setTimeout(() => {
|
|
276
|
+
this.connect();
|
|
277
|
+
}, delay);
|
|
278
|
+
}
|
|
279
|
+
startHeartbeat() {
|
|
280
|
+
this.pingTimer = setInterval(() => {
|
|
281
|
+
if (this.isConnected()) {
|
|
282
|
+
this.ping();
|
|
283
|
+
}
|
|
284
|
+
}, 30000); // Ping every 30s
|
|
285
|
+
}
|
|
286
|
+
processPendingMessages() {
|
|
287
|
+
while (this.pendingMessages.length > 0) {
|
|
288
|
+
const msg = this.pendingMessages.shift();
|
|
289
|
+
if (msg && this.ws) {
|
|
290
|
+
this.ws.send(JSON.stringify(msg));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
clearTimers() {
|
|
295
|
+
if (this.reconnectTimer) {
|
|
296
|
+
clearTimeout(this.reconnectTimer);
|
|
297
|
+
this.reconnectTimer = undefined;
|
|
298
|
+
}
|
|
299
|
+
if (this.pingTimer) {
|
|
300
|
+
clearInterval(this.pingTimer);
|
|
301
|
+
this.pingTimer = undefined;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
generateMessageId() {
|
|
305
|
+
return `msg-${Date.now()}-${++this.messageId}`;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Factory for creating client with auto-loaded token
|
|
309
|
+
export async function createGatewayClient(url, options) {
|
|
310
|
+
const deviceToken = await GatewayClient.loadDeviceToken();
|
|
311
|
+
return new GatewayClient({
|
|
312
|
+
url,
|
|
313
|
+
deviceToken: deviceToken ?? undefined,
|
|
314
|
+
...options,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
export default GatewayClient;
|
|
318
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/gateway/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GAGxB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailscale Integration - v0.3.0
|
|
3
|
+
*
|
|
4
|
+
* Zero-config VPN for secure remote gateway access.
|
|
5
|
+
* No port forwarding required, works through NAT/firewalls.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { EventEmitter } from "events";
|
|
9
|
+
class TailscaleIntegration extends EventEmitter {
|
|
10
|
+
config;
|
|
11
|
+
connected = false;
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this.config = {
|
|
15
|
+
enabled: false,
|
|
16
|
+
autoStart: true,
|
|
17
|
+
advertiseExitNode: false,
|
|
18
|
+
...config,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Check if Tailscale is installed
|
|
23
|
+
*/
|
|
24
|
+
async isInstalled() {
|
|
25
|
+
try {
|
|
26
|
+
const result = await this.execCommand("tailscale", ["version"]);
|
|
27
|
+
return result.success;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if Tailscale is running
|
|
35
|
+
*/
|
|
36
|
+
async isRunning() {
|
|
37
|
+
try {
|
|
38
|
+
const result = await this.execCommand("tailscale", ["status", "--json"]);
|
|
39
|
+
if (!result.success)
|
|
40
|
+
return false;
|
|
41
|
+
const status = JSON.parse(result.stdout);
|
|
42
|
+
return status?.Self?.Online || false;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get Tailscale status
|
|
50
|
+
*/
|
|
51
|
+
async getStatus() {
|
|
52
|
+
const installed = await this.isInstalled();
|
|
53
|
+
const running = installed ? await this.isRunning() : false;
|
|
54
|
+
if (running) {
|
|
55
|
+
try {
|
|
56
|
+
const result = await this.execCommand("tailscale", ["status", "--json"]);
|
|
57
|
+
const status = JSON.parse(result.stdout);
|
|
58
|
+
return {
|
|
59
|
+
installed,
|
|
60
|
+
running,
|
|
61
|
+
connected: status?.Self?.Online || false,
|
|
62
|
+
myIP: status?.Self?.TailscaleIPs?.[0],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { installed, running, connected: false };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { installed, running, connected: false };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Start Tailscale daemon
|
|
73
|
+
*/
|
|
74
|
+
async start() {
|
|
75
|
+
const status = await this.getStatus();
|
|
76
|
+
if (!status.installed) {
|
|
77
|
+
console.log("⚠️ Tailscale not installed");
|
|
78
|
+
console.log(" Install: https://tailscale.com/download");
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (status.running) {
|
|
82
|
+
console.log("✅ Tailscale already running");
|
|
83
|
+
this.emit("ready", { ip: status.myIP });
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
console.log("🚀 Starting Tailscale...");
|
|
87
|
+
let result;
|
|
88
|
+
if (process.platform === "darwin") {
|
|
89
|
+
result = await this.execCommand("sudo", ["tailscaled", "--daemon"]);
|
|
90
|
+
}
|
|
91
|
+
else if (process.platform === "linux") {
|
|
92
|
+
result = await this.execCommand("sudo", ["systemctl", "start", "tailscaled"]);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log("⚠️ Windows not yet supported for auto-start");
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (result.success) {
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
100
|
+
const newStatus = await this.getStatus();
|
|
101
|
+
if (newStatus.running) {
|
|
102
|
+
console.log(`✅ Tailscale ready: ${newStatus.myIP}`);
|
|
103
|
+
this.emit("ready", { ip: newStatus.myIP });
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get my Tailscale IP
|
|
111
|
+
*/
|
|
112
|
+
async getMyIP() {
|
|
113
|
+
const result = await this.execCommand("tailscale", ["ip", "-4"]);
|
|
114
|
+
if (result.success) {
|
|
115
|
+
return result.stdout.trim();
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Generate gateway URL for this device
|
|
121
|
+
*/
|
|
122
|
+
async getGatewayURL(port = 7777) {
|
|
123
|
+
const ip = await this.getMyIP();
|
|
124
|
+
if (!ip)
|
|
125
|
+
return undefined;
|
|
126
|
+
return `wss://${ip}:${port}`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Execute command helper
|
|
130
|
+
*/
|
|
131
|
+
execCommand(cmd, args) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
const child = spawn(cmd, args, {
|
|
134
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
135
|
+
});
|
|
136
|
+
let stdout = "";
|
|
137
|
+
let stderr = "";
|
|
138
|
+
child.stdout?.on("data", (data) => {
|
|
139
|
+
stdout += data.toString();
|
|
140
|
+
});
|
|
141
|
+
child.stderr?.on("data", (data) => {
|
|
142
|
+
stderr += data.toString();
|
|
143
|
+
});
|
|
144
|
+
child.on("exit", (code) => {
|
|
145
|
+
resolve({ success: code === 0, stdout, stderr });
|
|
146
|
+
});
|
|
147
|
+
child.on("error", () => {
|
|
148
|
+
resolve({ success: false, stdout, stderr });
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Singleton
|
|
154
|
+
let instance = null;
|
|
155
|
+
export function getTailscaleIntegration(config) {
|
|
156
|
+
if (!instance) {
|
|
157
|
+
instance = new TailscaleIntegration(config);
|
|
158
|
+
}
|
|
159
|
+
return instance;
|
|
160
|
+
}
|
|
161
|
+
export { TailscaleIntegration };
|
|
162
|
+
export default TailscaleIntegration;
|
|
163
|
+
//# sourceMappingURL=tailscale.js.map
|