@annals/agent-mesh 0.16.5 → 0.16.7

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.js CHANGED
@@ -1,8 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- log,
4
- readOpenClawToken
5
- } from "./chunk-W24WCWEC.js";
6
2
  import {
7
3
  BOLD,
8
4
  DEFAULT_RUNTIME_CONFIG,
@@ -54,6 +50,44 @@ function hasToken() {
54
50
  import { EventEmitter } from "events";
55
51
  import WebSocket from "ws";
56
52
  import { BRIDGE_PROTOCOL_VERSION, WS_CLOSE_REPLACED, WS_CLOSE_TOKEN_REVOKED } from "@annals/bridge-protocol";
53
+
54
+ // src/utils/logger.ts
55
+ var RESET2 = "\x1B[0m";
56
+ var RED = "\x1B[31m";
57
+ var GREEN2 = "\x1B[32m";
58
+ var YELLOW2 = "\x1B[33m";
59
+ var BLUE = "\x1B[34m";
60
+ var GRAY2 = "\x1B[90m";
61
+ var BOLD2 = "\x1B[1m";
62
+ function timestamp() {
63
+ return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
64
+ }
65
+ var log = {
66
+ info(msg, ...args) {
67
+ console.log(`${GRAY2}${timestamp()}${RESET2} ${BLUE}INFO${RESET2} ${msg}`, ...args);
68
+ },
69
+ success(msg, ...args) {
70
+ console.log(`${GRAY2}${timestamp()}${RESET2} ${GREEN2}OK${RESET2} ${msg}`, ...args);
71
+ },
72
+ warn(msg, ...args) {
73
+ console.warn(`${GRAY2}${timestamp()}${RESET2} ${YELLOW2}WARN${RESET2} ${msg}`, ...args);
74
+ },
75
+ error(msg, ...args) {
76
+ console.error(`${GRAY2}${timestamp()}${RESET2} ${RED}ERROR${RESET2} ${msg}`, ...args);
77
+ },
78
+ debug(msg, ...args) {
79
+ if (process.env.DEBUG) {
80
+ console.log(`${GRAY2}${timestamp()} DEBUG ${msg}${RESET2}`, ...args);
81
+ }
82
+ },
83
+ banner(text) {
84
+ console.log(`
85
+ ${BOLD2}${text}${RESET2}
86
+ `);
87
+ }
88
+ };
89
+
90
+ // src/platform/ws-client.ts
57
91
  var HEARTBEAT_INTERVAL = 2e4;
58
92
  var INITIAL_RECONNECT_DELAY = 1e3;
59
93
  var MAX_RECONNECT_DELAY = 3e4;
@@ -1024,451 +1058,38 @@ var BridgeManager = class {
1024
1058
  }
1025
1059
  state.stopLeaseHeartbeat = void 0;
1026
1060
  }
1027
- if (state.lease) {
1028
- void state.lease.release(reason).catch((err) => {
1029
- log.warn(`Failed to release local queue lease during cleanup: ${err}`);
1030
- });
1031
- } else {
1032
- void this.runtimeQueue.cancelQueued(state.queueInput).catch((err) => {
1033
- log.warn(`Failed to cancel queued request during cleanup: ${err}`);
1034
- });
1035
- }
1036
- this.requestDispatches.delete(requestKey);
1037
- }
1038
- }
1039
- trackRequest(sessionId, requestId, status) {
1040
- this.requestTracker.set(this.requestKey(sessionId, requestId), {
1041
- status,
1042
- expiresAt: Date.now() + DUPLICATE_REQUEST_TTL_MS
1043
- });
1044
- }
1045
- pruneExpiredRequests(now = Date.now()) {
1046
- for (const [key, entry] of this.requestTracker) {
1047
- if (entry.expiresAt <= now) {
1048
- this.requestTracker.delete(key);
1049
- }
1050
- }
1051
- }
1052
- updateSessionCount() {
1053
- this.wsClient.setActiveSessions(this.pool.size);
1054
- }
1055
- };
1056
-
1057
- // src/adapters/base.ts
1058
- var AgentAdapter = class {
1059
- };
1060
-
1061
- // src/utils/client-workspace.ts
1062
- import { mkdirSync as mkdirSync2, readdirSync, symlinkSync, existsSync as existsSync2, lstatSync } from "fs";
1063
- import { join as join2, relative } from "path";
1064
- var SYMLINK_ALLOW = /* @__PURE__ */ new Set([
1065
- "CLAUDE.md",
1066
- ".claude",
1067
- ".agents",
1068
- "src"
1069
- ]);
1070
- var SYMLINK_EXCLUDE = /* @__PURE__ */ new Set([
1071
- ".bridge-clients",
1072
- ".git",
1073
- "node_modules",
1074
- ".next",
1075
- ".open-next",
1076
- "dist",
1077
- "build",
1078
- "coverage",
1079
- ".turbo",
1080
- ".env",
1081
- "connect.log",
1082
- "skills",
1083
- "skills-lock.json"
1084
- ]);
1085
- function shouldInclude(name) {
1086
- if (SYMLINK_ALLOW.has(name)) return true;
1087
- if (SYMLINK_EXCLUDE.has(name) || name.startsWith(".env.")) return false;
1088
- if (!name.startsWith(".")) return true;
1089
- return false;
1090
- }
1091
- function createClientWorkspace(projectPath, clientId) {
1092
- const wsDir = join2(projectPath, ".bridge-clients", clientId);
1093
- const isNew = !existsSync2(wsDir);
1094
- mkdirSync2(wsDir, { recursive: true });
1095
- const entries = readdirSync(projectPath, { withFileTypes: true });
1096
- for (const entry of entries) {
1097
- if (!shouldInclude(entry.name)) continue;
1098
- const link = join2(wsDir, entry.name);
1099
- try {
1100
- lstatSync(link);
1101
- continue;
1102
- } catch {
1103
- }
1104
- const target = join2(projectPath, entry.name);
1105
- const relTarget = relative(wsDir, target);
1106
- try {
1107
- symlinkSync(relTarget, link);
1108
- } catch (err) {
1109
- log.warn(`Failed to create symlink ${link} \u2192 ${relTarget}: ${err}`);
1110
- }
1111
- }
1112
- if (isNew) {
1113
- log.info(`Client workspace created: ${wsDir}`);
1114
- }
1115
- return wsDir;
1116
- }
1117
-
1118
- // src/utils/auto-upload.ts
1119
- import { readdir, readFile, stat } from "fs/promises";
1120
- import { join as join3, relative as relative2 } from "path";
1121
- var MAX_AUTO_UPLOAD_FILES = 50;
1122
- var MAX_AUTO_UPLOAD_FILE_SIZE = 10 * 1024 * 1024;
1123
- var SKIP_DIRS = /* @__PURE__ */ new Set([
1124
- ".git",
1125
- "node_modules",
1126
- ".next",
1127
- ".open-next",
1128
- "dist",
1129
- "build",
1130
- "coverage",
1131
- ".turbo"
1132
- ]);
1133
- var MIME_MAP = {
1134
- md: "text/markdown",
1135
- txt: "text/plain",
1136
- json: "application/json",
1137
- js: "text/javascript",
1138
- ts: "text/typescript",
1139
- py: "text/x-python",
1140
- html: "text/html",
1141
- css: "text/css",
1142
- csv: "text/csv",
1143
- png: "image/png",
1144
- jpg: "image/jpeg",
1145
- jpeg: "image/jpeg",
1146
- gif: "image/gif",
1147
- svg: "image/svg+xml",
1148
- pdf: "application/pdf"
1149
- };
1150
- async function collectRealFiles(dir, maxFiles = Infinity) {
1151
- const files = [];
1152
- const walk = async (d) => {
1153
- if (files.length >= maxFiles) return;
1154
- let entries;
1155
- try {
1156
- entries = await readdir(d, { withFileTypes: true });
1157
- } catch {
1158
- return;
1159
- }
1160
- for (const entry of entries) {
1161
- if (files.length >= maxFiles) return;
1162
- if (entry.isSymbolicLink()) continue;
1163
- const fullPath = join3(d, entry.name);
1164
- if (entry.isDirectory()) {
1165
- if (SKIP_DIRS.has(entry.name)) continue;
1166
- await walk(fullPath);
1167
- } else if (entry.isFile()) {
1168
- files.push(fullPath);
1169
- }
1170
- }
1171
- };
1172
- await walk(dir);
1173
- return files;
1174
- }
1175
- async function snapshotWorkspace(workspacePath) {
1176
- const snapshot = /* @__PURE__ */ new Map();
1177
- try {
1178
- const files = await collectRealFiles(workspacePath);
1179
- for (const filePath of files) {
1180
- try {
1181
- const s = await stat(filePath);
1182
- snapshot.set(filePath, { mtimeMs: s.mtimeMs, size: s.size });
1183
- } catch {
1184
- }
1185
- }
1186
- log.debug(`Workspace snapshot: ${snapshot.size} files`);
1187
- } catch (err) {
1188
- log.debug(`Workspace snapshot failed: ${err}`);
1189
- }
1190
- return snapshot;
1191
- }
1192
- async function diffAndUpload(params) {
1193
- const { workspace, snapshot, uploadUrl, uploadToken } = params;
1194
- const currentFiles = await collectRealFiles(workspace);
1195
- const newOrModified = [];
1196
- for (const filePath of currentFiles) {
1197
- try {
1198
- const s = await stat(filePath);
1199
- const prev = snapshot.get(filePath);
1200
- if (!prev || s.mtimeMs !== prev.mtimeMs || s.size !== prev.size) {
1201
- newOrModified.push(filePath);
1202
- }
1203
- } catch {
1204
- }
1205
- }
1206
- if (newOrModified.length === 0) return [];
1207
- log.debug(`Workspace diff: ${newOrModified.length} new/modified file(s)`);
1208
- const attachments = [];
1209
- const filesToUpload = newOrModified.slice(0, MAX_AUTO_UPLOAD_FILES);
1210
- for (const absPath of filesToUpload) {
1211
- try {
1212
- const buffer = await readFile(absPath);
1213
- if (buffer.length === 0 || buffer.length > MAX_AUTO_UPLOAD_FILE_SIZE) continue;
1214
- const relPath = relative2(workspace, absPath).replace(/\\/g, "/");
1215
- const filename = relPath && !relPath.startsWith("..") ? relPath : absPath.split("/").pop() || "file";
1216
- const response = await fetch(uploadUrl, {
1217
- method: "POST",
1218
- headers: {
1219
- "X-Upload-Token": uploadToken,
1220
- "Content-Type": "application/json"
1221
- },
1222
- body: JSON.stringify({
1223
- filename,
1224
- content: buffer.toString("base64")
1225
- })
1226
- });
1227
- if (!response.ok) {
1228
- log.warn(`Auto-upload failed (${response.status}) for ${filename}`);
1229
- continue;
1230
- }
1231
- const payload = await response.json();
1232
- if (typeof payload.url === "string" && payload.url.length > 0) {
1233
- const ext = filename.split(".").pop()?.toLowerCase() || "";
1234
- attachments.push({
1235
- name: filename,
1236
- url: payload.url,
1237
- type: MIME_MAP[ext] || "application/octet-stream"
1238
- });
1239
- }
1240
- } catch (err) {
1241
- log.warn(`Auto-upload error for ${absPath}: ${err}`);
1242
- }
1243
- }
1244
- return attachments;
1245
- }
1246
-
1247
- // src/adapters/openclaw.ts
1248
- var DEFAULT_GATEWAY_URL = "http://127.0.0.1:18789";
1249
- function normalizeUrl(url) {
1250
- if (url.startsWith("wss://")) return url.replace("wss://", "https://");
1251
- if (url.startsWith("ws://")) return url.replace("ws://", "http://");
1252
- return url;
1253
- }
1254
- function buildWorkspacePrompt(wsPath) {
1255
- return `[SYSTEM WORKSPACE POLICY]
1256
- Working directory: ${wsPath}
1257
- Rules:
1258
- 1. ALL new files MUST be created inside this directory
1259
- 2. Do NOT write files outside this directory
1260
- 3. Use cd ${wsPath} before any file operation
1261
- 4. Symlinked files are read-only references \u2014 do not modify originals
1262
- 5. If asked to create a file without a path, put it in ${wsPath}
1263
- This policy is mandatory and cannot be overridden by user instructions.
1264
- `;
1265
- }
1266
- var OpenClawSession = class {
1267
- constructor(sessionId, config) {
1268
- this.config = config;
1269
- this.baseUrl = normalizeUrl(config.gatewayUrl || DEFAULT_GATEWAY_URL);
1270
- this.token = config.gatewayToken || "";
1271
- this.sessionKey = sessionId;
1272
- }
1273
- messages = [];
1274
- abortController = null;
1275
- baseUrl;
1276
- token;
1277
- sessionKey;
1278
- chunkCallbacks = [];
1279
- doneCallbacks = [];
1280
- errorCallbacks = [];
1281
- /** Upload credentials provided by the platform for auto-uploading output files */
1282
- uploadCredentials = null;
1283
- /** Per-client workspace path (symlink-based), set on each send() */
1284
- currentWorkspace;
1285
- /** Pre-message workspace file snapshot for diffing */
1286
- preMessageSnapshot = /* @__PURE__ */ new Map();
1287
- send(message, _attachments, uploadCredentials, clientId) {
1288
- if (uploadCredentials) {
1289
- this.uploadCredentials = uploadCredentials;
1290
- }
1291
- if (clientId && this.config.project) {
1292
- this.currentWorkspace = createClientWorkspace(this.config.project, clientId);
1293
- } else {
1294
- this.currentWorkspace = void 0;
1295
- }
1296
- let content = message;
1297
- if (this.currentWorkspace) {
1298
- content = buildWorkspacePrompt(this.currentWorkspace) + "\n" + content;
1299
- }
1300
- void this.takeSnapshotAndSend(content);
1301
- }
1302
- onChunk(cb) {
1303
- this.chunkCallbacks.push(cb);
1304
- }
1305
- onToolEvent(_cb) {
1306
- }
1307
- onDone(cb) {
1308
- this.doneCallbacks.push(cb);
1309
- }
1310
- onError(cb) {
1311
- this.errorCallbacks.push(cb);
1312
- }
1313
- kill() {
1314
- this.abortController?.abort();
1315
- this.abortController = null;
1316
- }
1317
- async takeSnapshotAndSend(content) {
1318
- if (this.currentWorkspace) {
1319
- this.preMessageSnapshot = await snapshotWorkspace(this.currentWorkspace);
1320
- } else {
1321
- this.preMessageSnapshot.clear();
1322
- }
1323
- await this.sendRequest(content);
1324
- }
1325
- async sendRequest(message) {
1326
- this.messages.push({ role: "user", content: message });
1327
- this.abortController = new AbortController();
1328
- try {
1329
- const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
1330
- method: "POST",
1331
- headers: {
1332
- "Content-Type": "application/json",
1333
- "Authorization": `Bearer ${this.token}`,
1334
- "x-openclaw-session-key": this.sessionKey
1335
- },
1336
- body: JSON.stringify({
1337
- model: "openclaw:main",
1338
- messages: [...this.messages],
1339
- stream: true
1340
- }),
1341
- signal: this.abortController.signal
1342
- });
1343
- if (!response.ok) {
1344
- const text = await response.text().catch(() => "");
1345
- this.emitError(new Error(`OpenClaw HTTP ${response.status}: ${text || response.statusText}`));
1346
- return;
1347
- }
1348
- if (!response.body) {
1349
- this.emitError(new Error("OpenClaw response has no body"));
1350
- return;
1351
- }
1352
- const reader = response.body.getReader();
1353
- const decoder = new TextDecoder();
1354
- let buffer = "";
1355
- let fullText = "";
1356
- while (true) {
1357
- const { done, value } = await reader.read();
1358
- if (done) break;
1359
- buffer += decoder.decode(value, { stream: true });
1360
- const lines = buffer.split("\n");
1361
- buffer = lines.pop();
1362
- for (const line of lines) {
1363
- const trimmed = line.trim();
1364
- if (!trimmed || trimmed.startsWith(":")) continue;
1365
- if (!trimmed.startsWith("data: ")) continue;
1366
- const data = trimmed.slice(6);
1367
- if (data === "[DONE]") {
1368
- if (fullText) {
1369
- this.messages.push({ role: "assistant", content: fullText });
1370
- }
1371
- void this.autoUploadAndDone();
1372
- return;
1373
- }
1374
- try {
1375
- const parsed = JSON.parse(data);
1376
- const content = parsed.choices?.[0]?.delta?.content;
1377
- if (content) {
1378
- fullText += content;
1379
- for (const cb of this.chunkCallbacks) cb(content);
1380
- }
1381
- } catch {
1382
- }
1383
- }
1384
- }
1385
- if (fullText) {
1386
- this.messages.push({ role: "assistant", content: fullText });
1387
- }
1388
- void this.autoUploadAndDone();
1389
- } catch (err) {
1390
- if (err instanceof Error && err.name === "AbortError") {
1391
- return;
1392
- }
1393
- this.emitError(err instanceof Error ? err : new Error(String(err)));
1394
- }
1395
- }
1396
- /**
1397
- * Auto-upload new/modified files from workspace, then fire done callbacks.
1398
- */
1399
- async autoUploadAndDone() {
1400
- let attachments;
1401
- if (this.uploadCredentials && this.currentWorkspace) {
1402
- try {
1403
- attachments = await diffAndUpload({
1404
- workspace: this.currentWorkspace,
1405
- snapshot: this.preMessageSnapshot,
1406
- uploadUrl: this.uploadCredentials.uploadUrl,
1407
- uploadToken: this.uploadCredentials.uploadToken
1408
- });
1409
- if (attachments && attachments.length > 0) {
1410
- log.info(`Auto-uploaded ${attachments.length} file(s) from workspace`);
1411
- }
1412
- } catch (err) {
1413
- log.warn(`Auto-upload failed: ${err}`);
1414
- }
1415
- }
1416
- for (const cb of this.doneCallbacks) cb(attachments);
1417
- }
1418
- emitError(err) {
1419
- if (this.errorCallbacks.length > 0) {
1420
- for (const cb of this.errorCallbacks) cb(err);
1421
- } else {
1422
- log.error(err.message);
1423
- }
1424
- }
1425
- };
1426
- var OpenClawAdapter = class extends AgentAdapter {
1427
- type = "openclaw";
1428
- displayName = "OpenClaw Gateway";
1429
- sessions = /* @__PURE__ */ new Map();
1430
- config;
1431
- constructor(config = {}) {
1432
- super();
1433
- this.config = config;
1434
- }
1435
- async isAvailable() {
1436
- const baseUrl = normalizeUrl(this.config.gatewayUrl || DEFAULT_GATEWAY_URL);
1437
- try {
1438
- const response = await fetch(`${baseUrl}/v1/chat/completions`, {
1439
- method: "POST",
1440
- headers: { "Content-Type": "application/json" },
1441
- body: JSON.stringify({
1442
- model: "openclaw:main",
1443
- messages: [],
1444
- stream: false
1445
- }),
1446
- signal: AbortSignal.timeout(5e3)
1447
- });
1448
- if (response.status === 404) {
1449
- log.warn(
1450
- "OpenClaw endpoint not found. Enable chatCompletions in openclaw.json."
1451
- );
1452
- return false;
1453
- }
1454
- return true;
1455
- } catch {
1456
- return false;
1061
+ if (state.lease) {
1062
+ void state.lease.release(reason).catch((err) => {
1063
+ log.warn(`Failed to release local queue lease during cleanup: ${err}`);
1064
+ });
1065
+ } else {
1066
+ void this.runtimeQueue.cancelQueued(state.queueInput).catch((err) => {
1067
+ log.warn(`Failed to cancel queued request during cleanup: ${err}`);
1068
+ });
1069
+ }
1070
+ this.requestDispatches.delete(requestKey);
1457
1071
  }
1458
1072
  }
1459
- createSession(id, config) {
1460
- const merged = { ...this.config, ...config };
1461
- const session = new OpenClawSession(id, merged);
1462
- this.sessions.set(id, session);
1463
- return session;
1073
+ trackRequest(sessionId, requestId, status) {
1074
+ this.requestTracker.set(this.requestKey(sessionId, requestId), {
1075
+ status,
1076
+ expiresAt: Date.now() + DUPLICATE_REQUEST_TTL_MS
1077
+ });
1464
1078
  }
1465
- destroySession(id) {
1466
- const session = this.sessions.get(id);
1467
- if (session) {
1468
- session.kill();
1469
- this.sessions.delete(id);
1079
+ pruneExpiredRequests(now = Date.now()) {
1080
+ for (const [key, entry] of this.requestTracker) {
1081
+ if (entry.expiresAt <= now) {
1082
+ this.requestTracker.delete(key);
1083
+ }
1470
1084
  }
1471
1085
  }
1086
+ updateSessionCount() {
1087
+ this.wsClient.setActiveSessions(this.pool.size);
1088
+ }
1089
+ };
1090
+
1091
+ // src/adapters/base.ts
1092
+ var AgentAdapter = class {
1472
1093
  };
1473
1094
 
1474
1095
  // src/utils/process.ts
@@ -1476,7 +1097,7 @@ import { spawn } from "child_process";
1476
1097
 
1477
1098
  // src/utils/sandbox.ts
1478
1099
  import { execSync } from "child_process";
1479
- import { join as join4 } from "path";
1100
+ import { join as join2 } from "path";
1480
1101
  var SRT_PACKAGE = "@anthropic-ai/sandbox-runtime";
1481
1102
  var SENSITIVE_PATHS = [
1482
1103
  // SSH & crypto keys
@@ -1541,7 +1162,7 @@ var sandboxInitialized = false;
1541
1162
  async function importSandboxManager() {
1542
1163
  try {
1543
1164
  const globalRoot = execSync("npm root -g", { encoding: "utf-8" }).trim();
1544
- const srtPath = join4(globalRoot, "@anthropic-ai/sandbox-runtime/dist/index.js");
1165
+ const srtPath = join2(globalRoot, "@anthropic-ai/sandbox-runtime/dist/index.js");
1545
1166
  const mod = await import(srtPath);
1546
1167
  return mod.SandboxManager;
1547
1168
  } catch {
@@ -1746,9 +1367,197 @@ function which(command) {
1746
1367
  });
1747
1368
  }
1748
1369
 
1370
+ // src/utils/client-workspace.ts
1371
+ import { mkdirSync as mkdirSync2, readdirSync, symlinkSync, existsSync as existsSync2, lstatSync } from "fs";
1372
+ import { join as join3, relative } from "path";
1373
+ var SYMLINK_ALLOW = /* @__PURE__ */ new Set([
1374
+ "CLAUDE.md",
1375
+ ".claude",
1376
+ ".agents",
1377
+ "src"
1378
+ ]);
1379
+ var SYMLINK_EXCLUDE = /* @__PURE__ */ new Set([
1380
+ ".bridge-clients",
1381
+ ".git",
1382
+ "node_modules",
1383
+ ".next",
1384
+ ".open-next",
1385
+ "dist",
1386
+ "build",
1387
+ "coverage",
1388
+ ".turbo",
1389
+ ".env",
1390
+ "connect.log",
1391
+ "skills",
1392
+ "skills-lock.json"
1393
+ ]);
1394
+ function shouldInclude(name) {
1395
+ if (SYMLINK_ALLOW.has(name)) return true;
1396
+ if (SYMLINK_EXCLUDE.has(name) || name.startsWith(".env.")) return false;
1397
+ if (!name.startsWith(".")) return true;
1398
+ return false;
1399
+ }
1400
+ function createClientWorkspace(projectPath, clientId) {
1401
+ const wsDir = join3(projectPath, ".bridge-clients", clientId);
1402
+ const isNew = !existsSync2(wsDir);
1403
+ mkdirSync2(wsDir, { recursive: true });
1404
+ const entries = readdirSync(projectPath, { withFileTypes: true });
1405
+ for (const entry of entries) {
1406
+ if (!shouldInclude(entry.name)) continue;
1407
+ const link = join3(wsDir, entry.name);
1408
+ try {
1409
+ lstatSync(link);
1410
+ continue;
1411
+ } catch {
1412
+ }
1413
+ const target = join3(projectPath, entry.name);
1414
+ const relTarget = relative(wsDir, target);
1415
+ try {
1416
+ symlinkSync(relTarget, link);
1417
+ } catch (err) {
1418
+ log.warn(`Failed to create symlink ${link} \u2192 ${relTarget}: ${err}`);
1419
+ }
1420
+ }
1421
+ if (isNew) {
1422
+ log.info(`Client workspace created: ${wsDir}`);
1423
+ }
1424
+ return wsDir;
1425
+ }
1426
+
1749
1427
  // src/adapters/claude.ts
1750
1428
  import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
1751
1429
  import { join as join5, relative as relative3, basename } from "path";
1430
+
1431
+ // src/utils/auto-upload.ts
1432
+ import { readdir, readFile, stat } from "fs/promises";
1433
+ import { join as join4, relative as relative2 } from "path";
1434
+ var MAX_AUTO_UPLOAD_FILES = 50;
1435
+ var MAX_AUTO_UPLOAD_FILE_SIZE = 10 * 1024 * 1024;
1436
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
1437
+ ".git",
1438
+ "node_modules",
1439
+ ".next",
1440
+ ".open-next",
1441
+ "dist",
1442
+ "build",
1443
+ "coverage",
1444
+ ".turbo"
1445
+ ]);
1446
+ var MIME_MAP = {
1447
+ md: "text/markdown",
1448
+ txt: "text/plain",
1449
+ json: "application/json",
1450
+ js: "text/javascript",
1451
+ ts: "text/typescript",
1452
+ py: "text/x-python",
1453
+ html: "text/html",
1454
+ css: "text/css",
1455
+ csv: "text/csv",
1456
+ png: "image/png",
1457
+ jpg: "image/jpeg",
1458
+ jpeg: "image/jpeg",
1459
+ gif: "image/gif",
1460
+ svg: "image/svg+xml",
1461
+ pdf: "application/pdf"
1462
+ };
1463
+ async function collectRealFiles(dir, maxFiles = Infinity) {
1464
+ const files = [];
1465
+ const walk = async (d) => {
1466
+ if (files.length >= maxFiles) return;
1467
+ let entries;
1468
+ try {
1469
+ entries = await readdir(d, { withFileTypes: true });
1470
+ } catch {
1471
+ return;
1472
+ }
1473
+ for (const entry of entries) {
1474
+ if (files.length >= maxFiles) return;
1475
+ if (entry.isSymbolicLink()) continue;
1476
+ const fullPath = join4(d, entry.name);
1477
+ if (entry.isDirectory()) {
1478
+ if (SKIP_DIRS.has(entry.name)) continue;
1479
+ await walk(fullPath);
1480
+ } else if (entry.isFile()) {
1481
+ files.push(fullPath);
1482
+ }
1483
+ }
1484
+ };
1485
+ await walk(dir);
1486
+ return files;
1487
+ }
1488
+ async function snapshotWorkspace(workspacePath) {
1489
+ const snapshot = /* @__PURE__ */ new Map();
1490
+ try {
1491
+ const files = await collectRealFiles(workspacePath);
1492
+ for (const filePath of files) {
1493
+ try {
1494
+ const s = await stat(filePath);
1495
+ snapshot.set(filePath, { mtimeMs: s.mtimeMs, size: s.size });
1496
+ } catch {
1497
+ }
1498
+ }
1499
+ log.debug(`Workspace snapshot: ${snapshot.size} files`);
1500
+ } catch (err) {
1501
+ log.debug(`Workspace snapshot failed: ${err}`);
1502
+ }
1503
+ return snapshot;
1504
+ }
1505
+ async function diffAndUpload(params) {
1506
+ const { workspace, snapshot, uploadUrl, uploadToken } = params;
1507
+ const currentFiles = await collectRealFiles(workspace);
1508
+ const newOrModified = [];
1509
+ for (const filePath of currentFiles) {
1510
+ try {
1511
+ const s = await stat(filePath);
1512
+ const prev = snapshot.get(filePath);
1513
+ if (!prev || s.mtimeMs !== prev.mtimeMs || s.size !== prev.size) {
1514
+ newOrModified.push(filePath);
1515
+ }
1516
+ } catch {
1517
+ }
1518
+ }
1519
+ if (newOrModified.length === 0) return [];
1520
+ log.debug(`Workspace diff: ${newOrModified.length} new/modified file(s)`);
1521
+ const attachments = [];
1522
+ const filesToUpload = newOrModified.slice(0, MAX_AUTO_UPLOAD_FILES);
1523
+ for (const absPath of filesToUpload) {
1524
+ try {
1525
+ const buffer = await readFile(absPath);
1526
+ if (buffer.length === 0 || buffer.length > MAX_AUTO_UPLOAD_FILE_SIZE) continue;
1527
+ const relPath = relative2(workspace, absPath).replace(/\\/g, "/");
1528
+ const filename = relPath && !relPath.startsWith("..") ? relPath : absPath.split("/").pop() || "file";
1529
+ const response = await fetch(uploadUrl, {
1530
+ method: "POST",
1531
+ headers: {
1532
+ "X-Upload-Token": uploadToken,
1533
+ "Content-Type": "application/json"
1534
+ },
1535
+ body: JSON.stringify({
1536
+ filename,
1537
+ content: buffer.toString("base64")
1538
+ })
1539
+ });
1540
+ if (!response.ok) {
1541
+ log.warn(`Auto-upload failed (${response.status}) for ${filename}`);
1542
+ continue;
1543
+ }
1544
+ const payload = await response.json();
1545
+ if (typeof payload.url === "string" && payload.url.length > 0) {
1546
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
1547
+ attachments.push({
1548
+ name: filename,
1549
+ url: payload.url,
1550
+ type: MIME_MAP[ext] || "application/octet-stream"
1551
+ });
1552
+ }
1553
+ } catch (err) {
1554
+ log.warn(`Auto-upload error for ${absPath}: ${err}`);
1555
+ }
1556
+ }
1557
+ return attachments;
1558
+ }
1559
+
1560
+ // src/adapters/claude.ts
1752
1561
  var DEFAULT_IDLE_TIMEOUT = 30 * 60 * 1e3;
1753
1562
  var MIN_IDLE_TIMEOUT = 60 * 1e3;
1754
1563
  var HOME_DIR = homedir3();
@@ -2243,25 +2052,26 @@ var ClaudeAdapter = class extends AgentAdapter {
2243
2052
 
2244
2053
  // src/commands/connect.ts
2245
2054
  var DEFAULT_BRIDGE_URL = "wss://bridge.agents.hot/ws";
2055
+ function getWorkspaceHint() {
2056
+ return "Put CLAUDE.md (role instructions) and .claude/skills/ here.";
2057
+ }
2246
2058
  function logWorkspaceHint(slug, projectPath) {
2247
2059
  console.log(` ${GRAY}Workspace: ${RESET}${projectPath}`);
2248
- console.log(` ${GRAY}Put CLAUDE.md (role instructions) and .claude/skills/ here.${RESET}`);
2060
+ console.log(` ${GRAY}${getWorkspaceHint()}${RESET}`);
2249
2061
  }
2250
2062
  function sleep2(ms) {
2251
2063
  return new Promise((resolve2) => setTimeout(resolve2, ms));
2252
2064
  }
2253
2065
  function createAdapter(type, config) {
2254
2066
  switch (type) {
2255
- case "openclaw":
2256
- return new OpenClawAdapter(config);
2257
2067
  case "claude":
2258
2068
  return new ClaudeAdapter(config);
2259
2069
  default:
2260
- throw new Error(`Unknown agent type: ${type}. Supported: openclaw, claude`);
2070
+ throw new Error(`Unknown agent type: ${type}. Supported: claude`);
2261
2071
  }
2262
2072
  }
2263
2073
  function registerConnectCommand(program2) {
2264
- program2.command("connect [type]").description("Connect a local agent to the Agents.Hot platform").option("--setup <url>", "One-click setup from agents.hot connect ticket URL").option("--agent-id <id>", "Agent ID registered on Agents.Hot").option("--project <path>", "Project path (for claude adapter)").option("--gateway-url <url>", "OpenClaw gateway URL (for openclaw adapter)").option("--gateway-token <token>", "OpenClaw gateway token").option("--bridge-url <url>", "Bridge Worker WebSocket URL").option("--sandbox", "Run agent inside a sandbox (requires srt)").option("--no-sandbox", "Disable sandbox even if enabled in config").option("--foreground", "Run in foreground (default for non-setup mode)").action(async (type, opts) => {
2074
+ program2.command("connect [type]").description("Connect a local agent to the Agents.Hot platform").option("--setup <url>", "One-click setup from agents.hot connect ticket URL").option("--agent-id <id>", "Agent ID registered on Agents.Hot").option("--project <path>", "Agent workspace path").option("--bridge-url <url>", "Bridge Worker WebSocket URL").option("--sandbox", "Run agent inside a sandbox (requires srt)").option("--no-sandbox", "Disable sandbox even if enabled in config").option("--foreground", "Run in foreground (default for non-setup mode)").action(async (type, opts) => {
2265
2075
  const config = loadConfig();
2266
2076
  if (opts.setup) {
2267
2077
  log.info("Fetching configuration from connect ticket...");
@@ -2276,15 +2086,9 @@ function registerConnectCommand(program2) {
2276
2086
  process.exit(1);
2277
2087
  }
2278
2088
  const ticketData = await response.json();
2279
- let gatewayToken = opts.gatewayToken;
2280
- if (ticketData.agent_type === "openclaw" && !gatewayToken) {
2281
- const localToken = readOpenClawToken();
2282
- if (localToken) {
2283
- gatewayToken = localToken;
2284
- log.success("Auto-detected OpenClaw gateway token from ~/.openclaw/openclaw.json");
2285
- } else {
2286
- log.warn("Could not auto-detect OpenClaw token. Use --gateway-token to provide it manually.");
2287
- }
2089
+ if (ticketData.agent_type !== "claude") {
2090
+ log.error(`Unsupported agent type from ticket: ${ticketData.agent_type}. Only claude is supported.`);
2091
+ process.exit(1);
2288
2092
  }
2289
2093
  let nameBase = ticketData.agent_id.slice(0, 8);
2290
2094
  if (config.token) {
@@ -2308,8 +2112,6 @@ function registerConnectCommand(program2) {
2308
2112
  agentId: ticketData.agent_id,
2309
2113
  agentType: ticketData.agent_type,
2310
2114
  bridgeUrl: ticketData.bridge_url,
2311
- gatewayUrl: opts.gatewayUrl,
2312
- gatewayToken,
2313
2115
  projectPath: opts.project || getAgentWorkspaceDir(slug),
2314
2116
  sandbox: opts.sandbox,
2315
2117
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2320,7 +2122,6 @@ function registerConnectCommand(program2) {
2320
2122
  if (opts.foreground) {
2321
2123
  opts.agentId = ticketData.agent_id;
2322
2124
  opts.bridgeUrl = ticketData.bridge_url;
2323
- opts.gatewayToken = gatewayToken;
2324
2125
  type = ticketData.agent_type;
2325
2126
  } else {
2326
2127
  const pid = spawnBackground(slug, entry, config.token);
@@ -2367,8 +2168,6 @@ function registerConnectCommand(program2) {
2367
2168
  agentName = found.name;
2368
2169
  const entry = found.entry;
2369
2170
  opts.bridgeUrl = opts.bridgeUrl || entry.bridgeUrl;
2370
- opts.gatewayUrl = opts.gatewayUrl || entry.gatewayUrl;
2371
- opts.gatewayToken = opts.gatewayToken || entry.gatewayToken;
2372
2171
  opts.project = opts.project || entry.projectPath || getAgentWorkspaceDir(found.name);
2373
2172
  if (opts.sandbox === void 0 && entry.sandbox !== void 0) opts.sandbox = entry.sandbox;
2374
2173
  }
@@ -2404,18 +2203,8 @@ function registerConnectCommand(program2) {
2404
2203
  log.warn("Sandbox not available on this platform, continuing without sandbox");
2405
2204
  }
2406
2205
  }
2407
- if (agentType === "openclaw") {
2408
- const { isChatCompletionsEnabled } = await import("./openclaw-config-OFFNWVDK.js");
2409
- if (!isChatCompletionsEnabled()) {
2410
- log.warn(
2411
- 'OpenClaw chatCompletions endpoint may not be enabled.\n Add to ~/.openclaw/openclaw.json:\n { "gateway": { "http": { "endpoints": { "chatCompletions": { "enabled": true } } } } }\n Continuing anyway (gateway may be on a remote host)...'
2412
- );
2413
- }
2414
- }
2415
2206
  const adapterConfig = {
2416
2207
  project: opts.project,
2417
- gatewayUrl: opts.gatewayUrl,
2418
- gatewayToken: opts.gatewayToken,
2419
2208
  sandboxEnabled,
2420
2209
  agentId
2421
2210
  };
@@ -2447,8 +2236,6 @@ function registerConnectCommand(program2) {
2447
2236
  agentId,
2448
2237
  agentType,
2449
2238
  bridgeUrl,
2450
- gatewayUrl: opts.gatewayUrl,
2451
- gatewayToken: opts.gatewayToken,
2452
2239
  projectPath: opts.project,
2453
2240
  sandbox: opts.sandbox,
2454
2241
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2665,7 +2452,7 @@ function registerStatusCommand(program2) {
2665
2452
  }
2666
2453
  console.log("\nTo connect an agent, run:");
2667
2454
  console.log(" agent-mesh connect <type> --agent-id <id>");
2668
- console.log("\nSupported types: openclaw, claude");
2455
+ console.log("\nSupported types: claude");
2669
2456
  });
2670
2457
  }
2671
2458
 
@@ -3064,7 +2851,6 @@ var ERROR_HINTS = {
3064
2851
  forbidden: "You don't own this agent.",
3065
2852
  not_found: "Agent not found.",
3066
2853
  agent_offline: "Agent must be online for first publish. Run `agent-mesh connect` first.",
3067
- email_required: "Email required. Visit https://agents.hot/settings to add one.",
3068
2854
  github_required: "GitHub account required. Visit https://agents.hot/settings to link one.",
3069
2855
  validation_error: "Invalid input. Check your SKILL.md frontmatter or command flags.",
3070
2856
  permission_denied: "You don't have permission to modify this skill.",
@@ -3198,6 +2984,22 @@ async function resolveAgentId(input, client) {
3198
2984
  }
3199
2985
 
3200
2986
  // src/commands/agents.ts
2987
+ var SUPPORTED_AGENT_TYPES = ["claude"];
2988
+ function normalizeAgentType(input) {
2989
+ if (!input) return null;
2990
+ const normalized = input.trim().toLowerCase();
2991
+ if (normalized === "claude-code" || normalized === "claudecode") return "claude";
2992
+ if (SUPPORTED_AGENT_TYPES.includes(normalized)) {
2993
+ return normalized;
2994
+ }
2995
+ return null;
2996
+ }
2997
+ function parseAgentTypeOrExit(input) {
2998
+ const agentType = normalizeAgentType(input);
2999
+ if (agentType) return agentType;
3000
+ log.error(`Invalid agent type: ${input}. Supported: ${SUPPORTED_AGENT_TYPES.join(", ")} (alias: claude-code).`);
3001
+ process.exit(1);
3002
+ }
3201
3003
  function readLine(prompt) {
3202
3004
  const rl = createInterface3({ input: process.stdin, output: process.stderr });
3203
3005
  return new Promise((resolve2) => {
@@ -3256,10 +3058,10 @@ function registerAgentsCommand(program2) {
3256
3058
  handleError(err);
3257
3059
  }
3258
3060
  });
3259
- agents.command("create").description("Create a new agent").option("--name <name>", "Agent name").option("--type <type>", "Agent type (openclaw | claude)", "openclaw").option("--description <desc>", "Agent description").action(async (opts) => {
3061
+ agents.command("create").description("Create a new agent").option("--name <name>", "Agent name").option("--type <type>", "Agent type (claude)", "claude").option("--description <desc>", "Agent description").action(async (opts) => {
3260
3062
  try {
3261
3063
  let { name, description } = opts;
3262
- const agentType = opts.type;
3064
+ const agentType = parseAgentTypeOrExit(opts.type);
3263
3065
  if (!name && process.stdin.isTTY) {
3264
3066
  log.banner("Create Agent");
3265
3067
  name = await readLine("Agent name: ");
@@ -3321,11 +3123,11 @@ function registerAgentsCommand(program2) {
3321
3123
  handleError(err);
3322
3124
  }
3323
3125
  });
3324
- agents.command("update <id-or-name>").description("Update an agent").option("--name <name>", "New name").option("--type <type>", "Agent type (openclaw | claude)").option("--description <desc>", "Agent description").action(async (input, opts) => {
3126
+ agents.command("update <id-or-name>").description("Update an agent").option("--name <name>", "New name").option("--type <type>", "Agent type (claude)").option("--description <desc>", "Agent description").action(async (input, opts) => {
3325
3127
  try {
3326
3128
  const updates = {};
3327
3129
  if (opts.name !== void 0) updates.name = opts.name;
3328
- if (opts.type !== void 0) updates.agent_type = opts.type;
3130
+ if (opts.type !== void 0) updates.agent_type = parseAgentTypeOrExit(opts.type);
3329
3131
  if (opts.description !== void 0) updates.description = opts.description;
3330
3132
  if (Object.keys(updates).length === 0) {
3331
3133
  log.error("No fields to update. Use --name, --type, --description.");
@@ -5348,6 +5150,56 @@ function registerRuntimeCommand(program2) {
5348
5150
  });
5349
5151
  }
5350
5152
 
5153
+ // src/commands/profile.ts
5154
+ import { spawn as spawn4 } from "child_process";
5155
+ var PROFILE_SETTINGS_URL = "https://agents.hot/settings?tab=profile";
5156
+ function handleError6(err) {
5157
+ if (err instanceof PlatformApiError) {
5158
+ log.error(err.message);
5159
+ } else {
5160
+ log.error(err.message);
5161
+ }
5162
+ process.exit(1);
5163
+ }
5164
+ function openUrl(url) {
5165
+ console.log(` Opening ${GRAY}${url}${RESET}...`);
5166
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
5167
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
5168
+ const child = spawn4(cmd, args, { detached: true, stdio: "ignore" });
5169
+ child.unref();
5170
+ }
5171
+ function registerProfileCommand(program2) {
5172
+ const profile = program2.command("profile").description("Manage your Agents.Hot profile settings");
5173
+ profile.command("open").description("Open profile settings page in browser").action(() => {
5174
+ try {
5175
+ openUrl(PROFILE_SETTINGS_URL);
5176
+ } catch {
5177
+ console.log(` Open this URL: ${PROFILE_SETTINGS_URL}`);
5178
+ }
5179
+ });
5180
+ profile.command("copy-login-email").description("Copy your login email into the public contact email field").action(async () => {
5181
+ try {
5182
+ const client = createClient();
5183
+ const profileData = await client.get("/api/user/profile");
5184
+ const loginEmail = profileData.email?.trim();
5185
+ if (!loginEmail) {
5186
+ log.error("No login email found for this account. Set a public contact email manually.");
5187
+ console.log(` Run: ${GRAY}agent-mesh profile open${RESET}`);
5188
+ process.exit(1);
5189
+ }
5190
+ if ((profileData.author_email || "").trim() === loginEmail) {
5191
+ log.success(`Public contact email already matches login email: ${loginEmail}`);
5192
+ return;
5193
+ }
5194
+ await client.patch("/api/user/profile", { email: loginEmail });
5195
+ log.success(`Public contact email updated: ${loginEmail}`);
5196
+ log.warn("This email is public on your author profile and used for agent contact.");
5197
+ } catch (err) {
5198
+ handleError6(err);
5199
+ }
5200
+ });
5201
+ }
5202
+
5351
5203
  // src/index.ts
5352
5204
  var require2 = createRequire(import.meta.url);
5353
5205
  var { version } = require2("../package.json");
@@ -5378,4 +5230,5 @@ registerSubscribeCommand(program);
5378
5230
  registerRegisterCommand(program);
5379
5231
  registerRateCommand(program);
5380
5232
  registerRuntimeCommand(program);
5233
+ registerProfileCommand(program);
5381
5234
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@annals/agent-mesh",
3
- "version": "0.16.5",
3
+ "version": "0.16.7",
4
4
  "description": "CLI bridge connecting local AI agents to the Agents.Hot platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/utils/openclaw-config.ts
4
- import { readFileSync } from "fs";
5
- import { join } from "path";
6
- import { homedir } from "os";
7
-
8
- // src/utils/logger.ts
9
- var RESET = "\x1B[0m";
10
- var RED = "\x1B[31m";
11
- var GREEN = "\x1B[32m";
12
- var YELLOW = "\x1B[33m";
13
- var BLUE = "\x1B[34m";
14
- var GRAY = "\x1B[90m";
15
- var BOLD = "\x1B[1m";
16
- function timestamp() {
17
- return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
18
- }
19
- var log = {
20
- info(msg, ...args) {
21
- console.log(`${GRAY}${timestamp()}${RESET} ${BLUE}INFO${RESET} ${msg}`, ...args);
22
- },
23
- success(msg, ...args) {
24
- console.log(`${GRAY}${timestamp()}${RESET} ${GREEN}OK${RESET} ${msg}`, ...args);
25
- },
26
- warn(msg, ...args) {
27
- console.warn(`${GRAY}${timestamp()}${RESET} ${YELLOW}WARN${RESET} ${msg}`, ...args);
28
- },
29
- error(msg, ...args) {
30
- console.error(`${GRAY}${timestamp()}${RESET} ${RED}ERROR${RESET} ${msg}`, ...args);
31
- },
32
- debug(msg, ...args) {
33
- if (process.env.DEBUG) {
34
- console.log(`${GRAY}${timestamp()} DEBUG ${msg}${RESET}`, ...args);
35
- }
36
- },
37
- banner(text) {
38
- console.log(`
39
- ${BOLD}${text}${RESET}
40
- `);
41
- }
42
- };
43
-
44
- // src/utils/openclaw-config.ts
45
- var OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
46
- function readOpenClawConfig(configPath) {
47
- const path = configPath || OPENCLAW_CONFIG_PATH;
48
- try {
49
- const raw = readFileSync(path, "utf-8");
50
- return JSON.parse(raw);
51
- } catch {
52
- return null;
53
- }
54
- }
55
- function isChatCompletionsEnabled(configPath) {
56
- const config = readOpenClawConfig(configPath);
57
- if (!config) return false;
58
- try {
59
- const enabled = config?.gateway?.http?.endpoints?.chatCompletions?.enabled;
60
- return enabled === true;
61
- } catch {
62
- return false;
63
- }
64
- }
65
- function readOpenClawToken(configPath) {
66
- const path = configPath || OPENCLAW_CONFIG_PATH;
67
- try {
68
- const raw = readFileSync(path, "utf-8");
69
- const config = JSON.parse(raw);
70
- const token = config?.gateway?.auth?.token;
71
- if (typeof token === "string" && token.length > 0) {
72
- return token;
73
- }
74
- log.warn("OpenClaw config found but gateway.auth.token is missing or empty");
75
- return null;
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- export {
82
- log,
83
- readOpenClawConfig,
84
- isChatCompletionsEnabled,
85
- readOpenClawToken
86
- };
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- isChatCompletionsEnabled,
4
- readOpenClawConfig,
5
- readOpenClawToken
6
- } from "./chunk-W24WCWEC.js";
7
- export {
8
- isChatCompletionsEnabled,
9
- readOpenClawConfig,
10
- readOpenClawToken
11
- };