@clawchatsai/connector 0.0.87 → 0.0.89
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/gateway-bridge.d.ts +4 -0
- package/dist/index.js +216 -104
- package/package.json +3 -4
- package/prebuilds/darwin-arm64/node_datachannel.node +0 -0
- package/prebuilds/darwin-x64/node_datachannel.node +0 -0
- package/prebuilds/linux-arm/node_datachannel.node +0 -0
- package/prebuilds/linux-arm64/node_datachannel.node +0 -0
- package/prebuilds/linux-x64/node_datachannel.node +0 -0
- package/prebuilds/linuxmusl-arm64/node_datachannel.node +0 -0
- package/prebuilds/linuxmusl-x64/node_datachannel.node +0 -0
- package/prebuilds/win32-arm64/node_datachannel.node +0 -0
- package/prebuilds/win32-x64/node_datachannel.node +0 -0
- package/server/config.js +5 -4
- package/server/controllers/agents.js +20 -0
- package/server/controllers/settings.js +28 -0
- package/server/controllers/static.js +56 -0
- package/server/controllers/transcribe.js +3 -10
- package/server/index.js +24 -49
- package/server/providers/memory-config.js +52 -0
- package/server/providers/memory.js +3 -39
- package/server/store/workspace-store.js +31 -0
- package/dist/updater.d.ts +0 -21
- package/dist/updater.js +0 -64
- package/server.js +0 -2392
package/dist/gateway-bridge.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as http from 'node:http';
|
|
14
|
+
import * as os from 'node:os';
|
|
14
15
|
import * as path from 'node:path';
|
|
15
16
|
import { SignalingClient } from './signaling-client.js';
|
|
16
17
|
import { dispatchRpc } from './shim.js';
|
|
17
|
-
import { checkForUpdates, performUpdate } from './updater.js';
|
|
18
18
|
import { initAuth, handleAuthMessage, cleanupAuth } from './auth-handler.js';
|
|
19
19
|
import { generateTotpSecret, verifyTotp, generateBackupCodes, buildOtpauthUri } from './totp.js';
|
|
20
20
|
import { generateSessionSecret } from './session-token.js';
|
|
@@ -66,63 +66,58 @@ function saveConfig(config) {
|
|
|
66
66
|
// ---------------------------------------------------------------------------
|
|
67
67
|
// Service lifecycle
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Detect musl libc (Alpine Linux) vs glibc.
|
|
71
|
+
* prebuildify/prebuild-install distinguishes linux vs linuxmusl.
|
|
72
|
+
*/
|
|
73
|
+
function detectLinuxLibc() {
|
|
74
|
+
try {
|
|
75
|
+
const ldd = fs.readFileSync('/usr/bin/ldd', 'utf8');
|
|
76
|
+
if (ldd.includes('musl'))
|
|
77
|
+
return 'musl';
|
|
78
|
+
}
|
|
79
|
+
catch { /* not found */ }
|
|
80
|
+
try {
|
|
81
|
+
if (fs.readdirSync('/lib').some((f) => f.startsWith('libc.musl')))
|
|
82
|
+
return 'musl';
|
|
83
|
+
}
|
|
84
|
+
catch { /* not found */ }
|
|
85
|
+
return 'glibc';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the prebuild key for the current platform, matching the directory
|
|
89
|
+
* names we bundle under prebuilds/ (e.g. "linux-x64", "linuxmusl-arm64").
|
|
90
|
+
*/
|
|
91
|
+
function getPrebuildKey() {
|
|
92
|
+
const platform = process.platform;
|
|
93
|
+
const arch = process.arch;
|
|
94
|
+
if (platform === 'linux' && detectLinuxLibc() === 'musl')
|
|
95
|
+
return `linuxmusl-${arch}`;
|
|
96
|
+
return `${platform}-${arch}`;
|
|
97
|
+
}
|
|
69
98
|
async function ensureNativeModules(ctx) {
|
|
70
|
-
// OpenClaw installs plugins with --ignore-scripts, which skips native module compilation.
|
|
71
|
-
// Check if native modules are usable; if not, rebuild them automatically.
|
|
72
99
|
const pluginDir = path.resolve(__dirname, '..');
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
path.join(modDir, 'node_modules', '.bin'),
|
|
95
|
-
path.join(pluginDir, 'node_modules', '.bin'),
|
|
96
|
-
].join(':');
|
|
97
|
-
execFileSync('sh', ['-c', installCmd], {
|
|
98
|
-
cwd: modDir,
|
|
99
|
-
stdio: 'pipe',
|
|
100
|
-
timeout: 120_000,
|
|
101
|
-
env: { ...process.env, PATH: `${localBin}:${process.env.PATH ?? ''}`, npm_config_node_gyp: '' },
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
// Fallback to rebuild if no install script found
|
|
106
|
-
execFileSync('npm', ['rebuild', mod.name], {
|
|
107
|
-
cwd: pluginDir,
|
|
108
|
-
stdio: 'pipe',
|
|
109
|
-
timeout: 120_000,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
execFileSync('npm', ['rebuild', mod.name], {
|
|
115
|
-
cwd: pluginDir,
|
|
116
|
-
stdio: 'pipe',
|
|
117
|
-
timeout: 120_000,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
ctx.logger.info(`${mod.name} ready.`);
|
|
121
|
-
}
|
|
122
|
-
catch (e) {
|
|
123
|
-
ctx.logger.error(`Failed to build ${mod.name}: ${e.message}`);
|
|
124
|
-
ctx.logger.error(`Try manually: cd ~/.openclaw/extensions/connector && npm rebuild ${mod.name}`);
|
|
125
|
-
}
|
|
100
|
+
const targetPath = path.join(pluginDir, 'node_modules', 'node-datachannel', 'build', 'Release', 'node_datachannel.node');
|
|
101
|
+
// Already built — nothing to do.
|
|
102
|
+
if (fs.existsSync(targetPath))
|
|
103
|
+
return;
|
|
104
|
+
// Find the bundled prebuilt for this platform (shipped inside the npm package).
|
|
105
|
+
const prebuildKey = getPrebuildKey();
|
|
106
|
+
const prebuiltPath = path.join(pluginDir, 'prebuilds', prebuildKey, 'node_datachannel.node');
|
|
107
|
+
if (!fs.existsSync(prebuiltPath)) {
|
|
108
|
+
ctx.logger.error(`[clawchats] No prebuilt binary for ${prebuildKey}. ` +
|
|
109
|
+
`WebRTC will be unavailable. To fix manually: ` +
|
|
110
|
+
`cd ~/.openclaw/extensions/connector && npm rebuild node-datachannel`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
ctx.logger.info(`[clawchats] Installing node-datachannel prebuilt for ${prebuildKey}...`);
|
|
114
|
+
try {
|
|
115
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
116
|
+
fs.copyFileSync(prebuiltPath, targetPath);
|
|
117
|
+
ctx.logger.info('[clawchats] node-datachannel ready.');
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
ctx.logger.error(`[clawchats] Failed to install prebuilt: ${e.message}`);
|
|
126
121
|
}
|
|
127
122
|
}
|
|
128
123
|
const CLAWCHATS_MD_CONTENT = `# ClawChats — Inline File Delivery
|
|
@@ -171,23 +166,7 @@ async function startClawChats(ctx, api, mediaStash) {
|
|
|
171
166
|
return;
|
|
172
167
|
ctx.logger.info('Setup detected — connecting to ClawChats...');
|
|
173
168
|
}
|
|
174
|
-
// 1.
|
|
175
|
-
const update = await checkForUpdates();
|
|
176
|
-
if (update) {
|
|
177
|
-
ctx.logger.info(`Update available: ${update.current} → ${update.latest}`);
|
|
178
|
-
if (ctx._forceUpdate) {
|
|
179
|
-
try {
|
|
180
|
-
await performUpdate();
|
|
181
|
-
ctx.logger.info(`Updated to ${update.latest}. Requesting graceful restart...`);
|
|
182
|
-
api.runtime.requestRestart?.('clawchats update');
|
|
183
|
-
return; // will restart with new version
|
|
184
|
-
}
|
|
185
|
-
catch (e) {
|
|
186
|
-
ctx.logger.error(`Auto-update failed: ${e.message}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// 2. Resolve gateway token: runtime API → config file → error
|
|
169
|
+
// 1. Resolve gateway token: runtime API → config file → error
|
|
191
170
|
const gwCfg = api.config;
|
|
192
171
|
const gwAuth = gwCfg?.['gateway']?.['auth'];
|
|
193
172
|
const gatewayToken = gwAuth?.['token'] || config.gatewayToken || '';
|
|
@@ -222,14 +201,38 @@ async function startClawChats(ctx, api, mediaStash) {
|
|
|
222
201
|
const uploadsDir = path.join(ctx.stateDir, 'clawchats', 'uploads');
|
|
223
202
|
_uploadsDir = uploadsDir;
|
|
224
203
|
// Dynamic import of server.js (plain JS, no type declarations)
|
|
225
|
-
// @ts-expect-error — server.js is plain JS with no .d.ts
|
|
226
|
-
const serverModule = await import('../server.js');
|
|
204
|
+
// @ts-expect-error — server/index.js is plain JS with no .d.ts
|
|
205
|
+
const serverModule = await import('../server/index.js');
|
|
206
|
+
// Read env vars here (plugin host) so server/ bundle stays process.env-free.
|
|
207
|
+
const memoryEnv = {
|
|
208
|
+
provider: process.env.MEMORY_PROVIDER,
|
|
209
|
+
host: process.env.MEMORY_HOST || process.env.QDRANT_HOST,
|
|
210
|
+
port: process.env.MEMORY_PORT || process.env.QDRANT_PORT,
|
|
211
|
+
collection: process.env.MEMORY_COLLECTION || process.env.QDRANT_COLLECTION,
|
|
212
|
+
pgUrl: process.env.MEMORY_PG_URL,
|
|
213
|
+
qdrantUrl: process.env.QDRANT_URL,
|
|
214
|
+
};
|
|
215
|
+
// Filter out undefined values so discoverMemoryConfig only overrides what's set.
|
|
216
|
+
const memoryEnvFiltered = Object.fromEntries(Object.entries(memoryEnv).filter(([, v]) => v !== undefined && v !== ''));
|
|
227
217
|
app = serverModule.createApp({
|
|
228
218
|
dataDir,
|
|
229
219
|
uploadsDir,
|
|
230
|
-
|
|
231
|
-
|
|
220
|
+
port: parseInt(process.env.PORT || '3001', 10),
|
|
221
|
+
gatewayUrl: process.env.GATEWAY_WS_URL || 'ws://localhost:18789',
|
|
222
|
+
authToken: process.env.CLAWCHATS_AUTH_TOKEN || '', // P2P: DataChannel is the auth boundary
|
|
232
223
|
gatewayToken, // For WS auth to local OpenClaw gateway
|
|
224
|
+
openaiApiKey: (() => {
|
|
225
|
+
// Resolve OpenAI API key: openclaw config → env var
|
|
226
|
+
try {
|
|
227
|
+
const oc = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw', 'openclaw.json'), 'utf8'));
|
|
228
|
+
const fromConfig = oc?.skills?.entries?.['openai-whisper-api']?.apiKey;
|
|
229
|
+
if (fromConfig)
|
|
230
|
+
return fromConfig;
|
|
231
|
+
}
|
|
232
|
+
catch { /* ok */ }
|
|
233
|
+
return process.env.OPENAI_API_KEY || null;
|
|
234
|
+
})(),
|
|
235
|
+
memoryEnv: memoryEnvFiltered,
|
|
233
236
|
mediaStash, // Shared Map populated by after_tool_call hook (captures MEDIA: paths from exec)
|
|
234
237
|
});
|
|
235
238
|
// 4. Connect createApp's gateway client (handles persistence + event relay)
|
|
@@ -263,17 +266,6 @@ async function startClawChats(ctx, api, mediaStash) {
|
|
|
263
266
|
ctx.logger.error(`Signaling auth rejected: ${reason}`);
|
|
264
267
|
});
|
|
265
268
|
// version-rejected listener removed — version check is now client-side
|
|
266
|
-
signaling.on('force-update', async (targetVersion) => {
|
|
267
|
-
ctx.logger.info(`Force update to ${targetVersion} requested`);
|
|
268
|
-
try {
|
|
269
|
-
await performUpdate();
|
|
270
|
-
ctx.logger.info('Update complete, requesting restart');
|
|
271
|
-
api.runtime.requestRestart?.('forced update');
|
|
272
|
-
}
|
|
273
|
-
catch (e) {
|
|
274
|
-
ctx.logger.error(`Force update failed: ${e.message}`);
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
269
|
signaling.on('account-suspended', (reason) => {
|
|
278
270
|
ctx.logger.error(`Account suspended: ${reason}`);
|
|
279
271
|
broadcastToClients({ type: 'account-suspended', reason });
|
|
@@ -760,7 +752,7 @@ function formatStatus() {
|
|
|
760
752
|
// ---------------------------------------------------------------------------
|
|
761
753
|
// CLI handlers
|
|
762
754
|
// ---------------------------------------------------------------------------
|
|
763
|
-
async function handleSetup(token) {
|
|
755
|
+
async function handleSetup(token, options = {}) {
|
|
764
756
|
// Decode base64 token
|
|
765
757
|
let tokenData;
|
|
766
758
|
try {
|
|
@@ -850,21 +842,36 @@ async function handleSetup(token) {
|
|
|
850
842
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
851
843
|
fs.mkdirSync(uploadsDir, { recursive: true });
|
|
852
844
|
ws.close();
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
845
|
+
if (options.skipTotp) {
|
|
846
|
+
// Agent-driven flow: skip interactive TOTP enrollment.
|
|
847
|
+
// User will run setup-totp + verify-totp separately.
|
|
848
|
+
console.log('');
|
|
849
|
+
console.log(' ✅ Setup complete!');
|
|
850
|
+
console.log('');
|
|
851
|
+
console.log(' 2FA setup pending. Run these commands to enable it:');
|
|
852
|
+
console.log(' openclaw clawchats setup-totp');
|
|
853
|
+
console.log(' openclaw clawchats verify-totp <6-digit-code>');
|
|
854
|
+
console.log('');
|
|
855
|
+
console.log(' Then restart: openclaw gateway restart');
|
|
856
|
+
console.log('');
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
// Interactive (human) flow: enroll TOTP now.
|
|
860
|
+
const totpOk = await enrollTotp(config);
|
|
861
|
+
if (!totpOk) {
|
|
862
|
+
console.log('');
|
|
863
|
+
console.log(' ⚠️ TOTP not configured. You can set it up later with: openclaw clawchats reauth');
|
|
864
|
+
console.log(' ClawChats will not allow browser connections until 2FA is enabled.');
|
|
865
|
+
}
|
|
866
|
+
console.log(' ✅ Setup complete!');
|
|
856
867
|
console.log('');
|
|
857
|
-
console.log('
|
|
858
|
-
console.log('
|
|
868
|
+
console.log(' Next steps:');
|
|
869
|
+
console.log(' 1. Restart your gateway: openclaw gateway restart');
|
|
870
|
+
console.log(' (or: systemctl --user restart openclaw-gateway)');
|
|
871
|
+
console.log(' 2. Open ClawChats: https://app.clawchats.ai');
|
|
872
|
+
console.log('');
|
|
873
|
+
console.log(' The gateway will connect automatically after restart.');
|
|
859
874
|
}
|
|
860
|
-
console.log(' ✅ Setup complete!');
|
|
861
|
-
console.log('');
|
|
862
|
-
console.log(' Next steps:');
|
|
863
|
-
console.log(' 1. Restart your gateway: openclaw gateway restart');
|
|
864
|
-
console.log(' (or: systemctl --user restart openclaw-gateway)');
|
|
865
|
-
console.log(' 2. Open ClawChats: https://app.clawchats.ai');
|
|
866
|
-
console.log('');
|
|
867
|
-
console.log(' The gateway will connect automatically after restart.');
|
|
868
875
|
resolve();
|
|
869
876
|
}
|
|
870
877
|
else if (msg.type === 'setup-error') {
|
|
@@ -993,6 +1000,105 @@ async function handleShowTotp() {
|
|
|
993
1000
|
console.log(' 3. When prompted for a TOTP secret, paste the value above');
|
|
994
1001
|
console.log('');
|
|
995
1002
|
}
|
|
1003
|
+
// ---------------------------------------------------------------------------
|
|
1004
|
+
// Agent-driven TOTP setup (setup-totp + verify-totp)
|
|
1005
|
+
// ---------------------------------------------------------------------------
|
|
1006
|
+
async function handleSetupTotp() {
|
|
1007
|
+
const config = loadConfig();
|
|
1008
|
+
if (!config) {
|
|
1009
|
+
console.error('ClawChats not configured. Run: openclaw clawchats setup <token> --skip-totp');
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (config.schemaVersion >= 2 && config.totp) {
|
|
1013
|
+
console.error('TOTP already active. Use: openclaw clawchats reauth to reset.');
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
// Idempotency: reuse pending secret if generated within the last 24 hours.
|
|
1017
|
+
// Prevents stale-secret issues when the agent retries or the user reruns the command.
|
|
1018
|
+
const PENDING_TTL_MS = 24 * 60 * 60 * 1000;
|
|
1019
|
+
let totpSecret;
|
|
1020
|
+
const existing = config.totpPending;
|
|
1021
|
+
if (existing?.secret && existing.generatedAt) {
|
|
1022
|
+
const age = Date.now() - new Date(existing.generatedAt).getTime();
|
|
1023
|
+
if (age < PENDING_TTL_MS) {
|
|
1024
|
+
totpSecret = existing.secret;
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
// Expired — generate a fresh one
|
|
1028
|
+
totpSecret = generateTotpSecret();
|
|
1029
|
+
config.totpPending = { secret: totpSecret, generatedAt: new Date().toISOString() };
|
|
1030
|
+
saveConfig(config);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
totpSecret = generateTotpSecret();
|
|
1035
|
+
config.totpPending = { secret: totpSecret, generatedAt: new Date().toISOString() };
|
|
1036
|
+
saveConfig(config);
|
|
1037
|
+
}
|
|
1038
|
+
const formatted = totpSecret.match(/.{1,4}/g)?.join(' ') || totpSecret;
|
|
1039
|
+
const setupUrl = `${config.serverUrl.replace('wss://', 'https://').replace(/\/ws\/?$/, '')}/totp-setup#${totpSecret}`;
|
|
1040
|
+
console.log('');
|
|
1041
|
+
console.log(' 🔐 ClawChats Two-Factor Authentication Setup');
|
|
1042
|
+
console.log('');
|
|
1043
|
+
console.log(' Open this URL to scan the QR code with your authenticator app:');
|
|
1044
|
+
console.log(` ${setupUrl}`);
|
|
1045
|
+
console.log('');
|
|
1046
|
+
console.log(` Or enter manually: ${formatted}`);
|
|
1047
|
+
console.log('');
|
|
1048
|
+
console.log(' Once added, verify with:');
|
|
1049
|
+
console.log(' openclaw clawchats verify-totp <6-digit-code>');
|
|
1050
|
+
console.log('');
|
|
1051
|
+
}
|
|
1052
|
+
async function handleVerifyTotp(code) {
|
|
1053
|
+
const config = loadConfig();
|
|
1054
|
+
if (!config) {
|
|
1055
|
+
console.error('ClawChats not configured. Run: openclaw clawchats setup <token> --skip-totp');
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (config.schemaVersion >= 2 && config.totp) {
|
|
1059
|
+
console.error('TOTP already active. Use: openclaw clawchats reauth to reset.');
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (!config.totpPending?.secret) {
|
|
1063
|
+
console.error('No pending TOTP secret. Run: openclaw clawchats setup-totp first.');
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const step = verifyTotp(code.trim(), config.totpPending.secret, 0);
|
|
1067
|
+
if (step < 0) {
|
|
1068
|
+
console.error(' ❌ Invalid code. Make sure you scanned the correct QR code and try again.');
|
|
1069
|
+
console.error(' Run: openclaw clawchats verify-totp <new-code>');
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
}
|
|
1072
|
+
// Generate backup codes
|
|
1073
|
+
const { codes, hashes } = generateBackupCodes();
|
|
1074
|
+
console.log('');
|
|
1075
|
+
console.log(' 🔑 Backup codes (save these somewhere safe — one-time use):');
|
|
1076
|
+
for (const backupCode of codes) {
|
|
1077
|
+
console.log(` ${backupCode}`);
|
|
1078
|
+
}
|
|
1079
|
+
console.log('');
|
|
1080
|
+
console.log(' ⚠️ These codes will NOT be shown again.');
|
|
1081
|
+
// Generate session secret and finalize config
|
|
1082
|
+
const sessionSecret = generateSessionSecret();
|
|
1083
|
+
config.totp = {
|
|
1084
|
+
secret: config.totpPending.secret,
|
|
1085
|
+
algorithm: 'SHA1',
|
|
1086
|
+
digits: 6,
|
|
1087
|
+
period: 30,
|
|
1088
|
+
enabledAt: new Date().toISOString(),
|
|
1089
|
+
};
|
|
1090
|
+
config.sessionSecret = sessionSecret;
|
|
1091
|
+
config.backupCodeHashes = hashes;
|
|
1092
|
+
config.schemaVersion = 2;
|
|
1093
|
+
delete config.totpPending;
|
|
1094
|
+
saveConfig(config);
|
|
1095
|
+
console.log('');
|
|
1096
|
+
console.log(' ✅ Two-factor authentication enabled!');
|
|
1097
|
+
console.log('');
|
|
1098
|
+
console.log(' Restart the gateway to apply:');
|
|
1099
|
+
console.log(' openclaw gateway restart');
|
|
1100
|
+
console.log('');
|
|
1101
|
+
}
|
|
996
1102
|
async function handleReauth() {
|
|
997
1103
|
const config = loadConfig();
|
|
998
1104
|
if (!config) {
|
|
@@ -1178,11 +1284,17 @@ const plugin = {
|
|
|
1178
1284
|
api.registerCli((ctx) => {
|
|
1179
1285
|
const cmd = ctx.program.command('clawchats');
|
|
1180
1286
|
cmd.command('setup <token>')
|
|
1181
|
-
.description('Set up ClawChats with a setup token')
|
|
1182
|
-
.action((token) => handleSetup(String(token)));
|
|
1287
|
+
.description('Set up ClawChats with a setup token (use --skip-totp for agent-driven installs)')
|
|
1288
|
+
.action((token) => handleSetup(String(token), { skipTotp: process.argv.includes('--skip-totp') }));
|
|
1183
1289
|
cmd.command('status')
|
|
1184
1290
|
.description('Show ClawChats connection status')
|
|
1185
1291
|
.action(() => handleStatus());
|
|
1292
|
+
cmd.command('setup-totp')
|
|
1293
|
+
.description('Generate TOTP QR code for agent-driven 2FA setup (run after setup --skip-totp)')
|
|
1294
|
+
.action(() => handleSetupTotp());
|
|
1295
|
+
cmd.command('verify-totp <code>')
|
|
1296
|
+
.description('Verify TOTP code and finalize 2FA setup (agent-driven flow)')
|
|
1297
|
+
.action((code) => handleVerifyTotp(String(code)));
|
|
1186
1298
|
cmd.command('reauth')
|
|
1187
1299
|
.description('Reset two-factor authentication (new TOTP secret + invalidate sessions)')
|
|
1188
1300
|
.action(() => handleReauth());
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawchatsai/connector",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.89",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ClawChats OpenClaw plugin — P2P tunnel + local API bridge",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
|
-
"server.js",
|
|
11
10
|
"server/",
|
|
12
|
-
"openclaw.plugin.json"
|
|
11
|
+
"openclaw.plugin.json",
|
|
12
|
+
"prebuilds/"
|
|
13
13
|
],
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"node": ">=22.5.0"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"prebuild": "node esbuild.config.mjs",
|
|
22
21
|
"build": "tsc",
|
|
23
22
|
"dev": "tsc --watch",
|
|
24
23
|
"prepublishOnly": "npm run build"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/server/config.js
CHANGED
|
@@ -23,13 +23,14 @@ export function parseConfigField(field) {
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// Auth token:
|
|
27
|
-
|
|
26
|
+
// Auth token: config.js → empty (open/unauthenticated mode)
|
|
27
|
+
// Note: CLAWCHATS_AUTH_TOKEN env var is read by the plugin host (src/index.ts) and passed via createApp().
|
|
28
|
+
export const AUTH_TOKEN = parseConfigField('authToken') || '';
|
|
28
29
|
|
|
29
30
|
// Gateway WebSocket URL — uses the internal/local gateway address, NOT config.js gatewayUrl
|
|
30
31
|
// (that's the browser's external-facing URL and would cause a routing loop through Caddy)
|
|
32
|
+
// Note: GATEWAY_WS_URL env var is read by the plugin host (src/index.ts) and passed via createApp().
|
|
31
33
|
export function discoverGatewayWsUrl() {
|
|
32
|
-
if (process.env.GATEWAY_WS_URL) return process.env.GATEWAY_WS_URL;
|
|
33
34
|
for (const cfgPath of [path.join(HOME, '.openclaw', 'openclaw.json'), '/etc/openclaw/openclaw.json']) {
|
|
34
35
|
try {
|
|
35
36
|
const raw = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
@@ -43,8 +44,8 @@ export function discoverGatewayWsUrl() {
|
|
|
43
44
|
export const GATEWAY_WS_URL = discoverGatewayWsUrl();
|
|
44
45
|
|
|
45
46
|
// Sessions directory — where OpenClaw stores session .jsonl files
|
|
47
|
+
// Note: OPENCLAW_SESSIONS_DIR env var is read by the plugin host (src/index.ts) and passed via createApp().
|
|
46
48
|
export const OPENCLAW_SESSIONS_DIR =
|
|
47
|
-
process.env.OPENCLAW_SESSIONS_DIR ||
|
|
48
49
|
parseConfigField('sessionsDir') ||
|
|
49
50
|
path.join(HOME, '.openclaw', 'agents', 'main', 'sessions');
|
|
50
51
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { send } from '../util/http.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* List available OpenClaw agents.
|
|
8
|
+
* Keeps fs.readdirSync out of the HTTP router (server/index.js).
|
|
9
|
+
*/
|
|
10
|
+
export function handleAgents(req, res) {
|
|
11
|
+
try {
|
|
12
|
+
const agentsDir = path.join(os.homedir(), '.openclaw', 'agents');
|
|
13
|
+
const agents = fs.readdirSync(agentsDir, { withFileTypes: true })
|
|
14
|
+
.filter(e => e.isDirectory())
|
|
15
|
+
.map(e => e.name);
|
|
16
|
+
send(res, 200, { agents });
|
|
17
|
+
} catch {
|
|
18
|
+
send(res, 200, { agents: ['main'] });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { send } from '../util/http.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory that returns settings GET/PUT handlers bound to a specific settings file path.
|
|
7
|
+
* Keeps file I/O out of the HTTP router (server/index.js).
|
|
8
|
+
*/
|
|
9
|
+
export function createSettingsHandlers(settingsFile) {
|
|
10
|
+
function handleGetSettings(req, res) {
|
|
11
|
+
try {
|
|
12
|
+
send(res, 200, fs.existsSync(settingsFile)
|
|
13
|
+
? JSON.parse(fs.readFileSync(settingsFile, 'utf8'))
|
|
14
|
+
: {});
|
|
15
|
+
} catch {
|
|
16
|
+
send(res, 200, {});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function handleSaveSettings(req, res, parseBody) {
|
|
21
|
+
const body = await parseBody(req);
|
|
22
|
+
fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
|
|
23
|
+
fs.writeFileSync(settingsFile, JSON.stringify(body, null, 2));
|
|
24
|
+
send(res, 200, { ok: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { handleGetSettings, handleSaveSettings };
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const MIME = {
|
|
5
|
+
'.html': 'text/html',
|
|
6
|
+
'.js': 'text/javascript',
|
|
7
|
+
'.css': 'text/css',
|
|
8
|
+
'.json': 'application/json',
|
|
9
|
+
'.ico': 'image/x-icon',
|
|
10
|
+
'.png': 'image/png',
|
|
11
|
+
'.svg': 'image/svg+xml',
|
|
12
|
+
'.gif': 'image/gif',
|
|
13
|
+
'.webp': 'image/webp',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const STATIC_MAP = {
|
|
17
|
+
'/': 'index.html',
|
|
18
|
+
'/index.html': 'index.html',
|
|
19
|
+
'/app.js': 'app.js',
|
|
20
|
+
'/style.css': 'style.css',
|
|
21
|
+
'/error-handler.js': 'error-handler.js',
|
|
22
|
+
'/manifest.json': 'manifest.json',
|
|
23
|
+
'/favicon.ico': 'favicon.ico',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Serve static files from pluginDir.
|
|
28
|
+
* Returns true if the request was handled, false if it should fall through.
|
|
29
|
+
* Keeps fs.createReadStream out of the HTTP router (server/index.js).
|
|
30
|
+
*/
|
|
31
|
+
export function handleStatic(req, res, pluginDir) {
|
|
32
|
+
const urlPath = (req.url || '/').split('?')[0];
|
|
33
|
+
const fileName = STATIC_MAP[urlPath];
|
|
34
|
+
const isAllowed =
|
|
35
|
+
fileName ||
|
|
36
|
+
urlPath.startsWith('/icons/') ||
|
|
37
|
+
urlPath.startsWith('/lib/') ||
|
|
38
|
+
urlPath.startsWith('/frontend/') ||
|
|
39
|
+
urlPath.startsWith('/emoji/') ||
|
|
40
|
+
urlPath === '/config.js';
|
|
41
|
+
|
|
42
|
+
if (!isAllowed) return false;
|
|
43
|
+
|
|
44
|
+
const staticPath = path.join(pluginDir, fileName || urlPath.slice(1));
|
|
45
|
+
if (!fs.existsSync(staticPath) || !fs.statSync(staticPath).isFile()) return false;
|
|
46
|
+
|
|
47
|
+
const ext = path.extname(staticPath).toLowerCase();
|
|
48
|
+
const stat = fs.statSync(staticPath);
|
|
49
|
+
res.writeHead(200, {
|
|
50
|
+
'Content-Type': MIME[ext] || 'application/octet-stream',
|
|
51
|
+
'Content-Length': stat.size,
|
|
52
|
+
'Cache-Control': ext === '.html' ? 'no-cache' : 'public, max-age=3600',
|
|
53
|
+
});
|
|
54
|
+
fs.createReadStream(staticPath).pipe(res);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
1
|
import { send } from '../util/http.js';
|
|
5
2
|
|
|
6
|
-
export async function handleTranscribe(req, res) {
|
|
3
|
+
export async function handleTranscribe(req, res, opts = {}) {
|
|
7
4
|
try {
|
|
8
5
|
const chunks = [];
|
|
9
6
|
for await (const chunk of req) chunks.push(chunk);
|
|
@@ -12,12 +9,8 @@ export async function handleTranscribe(req, res) {
|
|
|
12
9
|
if (audioBuffer.length === 0) return send(res, 400, { error: 'No audio data' });
|
|
13
10
|
if (audioBuffer.length > 25 * 1024 * 1024) return send(res, 400, { error: 'Audio too large (max 25MB)' });
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const ocConfig = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw', 'openclaw.json'), 'utf8'));
|
|
18
|
-
apiKey = ocConfig?.skills?.entries?.['openai-whisper-api']?.apiKey;
|
|
19
|
-
} catch { /* ok */ }
|
|
20
|
-
if (!apiKey) apiKey = process.env.OPENAI_API_KEY;
|
|
12
|
+
// API key is resolved by the plugin host (src/index.ts) and passed via opts.openaiApiKey.
|
|
13
|
+
const apiKey = opts.openaiApiKey;
|
|
21
14
|
if (!apiKey) return send(res, 500, { error: 'No OpenAI API key configured' });
|
|
22
15
|
|
|
23
16
|
const contentType = req.headers['content-type'] || 'audio/webm';
|