@prismer/sdk 1.7.3 → 1.8.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/dist/cli.js CHANGED
@@ -36,10 +36,10 @@ __export(cli_exports, {
36
36
  });
37
37
  module.exports = __toCommonJS(cli_exports);
38
38
  var import_commander = require("commander");
39
- var fs = __toESM(require("fs"));
40
- var path = __toESM(require("path"));
41
- var os = __toESM(require("os"));
42
- var TOML = __toESM(require("@iarna/toml"));
39
+ var fs2 = __toESM(require("fs"));
40
+ var path2 = __toESM(require("path"));
41
+ var os2 = __toESM(require("os"));
42
+ var TOML2 = __toESM(require("@iarna/toml"));
43
43
 
44
44
  // src/realtime.ts
45
45
  var TypedEmitter = class {
@@ -509,11 +509,15 @@ var WRITE_PATTERNS = [
509
509
  { method: "POST", pattern: /\/api\/im\/(messages|direct|groups)\//, opType: "message.send" },
510
510
  { method: "PATCH", pattern: /\/api\/im\/messages\//, opType: "message.edit" },
511
511
  { method: "DELETE", pattern: /\/api\/im\/messages\//, opType: "message.delete" },
512
- { method: "POST", pattern: /\/api\/im\/conversations\/[^/]+\/read/, opType: "conversation.read" }
512
+ { method: "POST", pattern: /\/api\/im\/conversations\/[^/]+\/read/, opType: "conversation.read" },
513
+ // v1.8.0 Community — queued when offline-first IM is enabled
514
+ { method: "POST", pattern: /\/api\/im\/community\/posts$/, opType: "community_post" },
515
+ { method: "POST", pattern: /\/api\/im\/community\/posts\/[^/]+\/comments$/, opType: "community_comment" },
516
+ { method: "POST", pattern: /\/api\/im\/community\/vote$/, opType: "community_vote" }
513
517
  ];
514
- function matchWriteOp(method, path2) {
518
+ function matchWriteOp(method, path3) {
515
519
  for (const { method: m, pattern, opType } of WRITE_PATTERNS) {
516
- if (method === m && pattern.test(path2)) return opType;
520
+ if (method === m && pattern.test(path3)) return opType;
517
521
  }
518
522
  return null;
519
523
  }
@@ -581,18 +585,18 @@ var OfflineManager = class extends OfflineEmitter {
581
585
  /**
582
586
  * Dispatch an IM request. Write ops go through outbox; reads check local cache.
583
587
  */
584
- async dispatch(method, path2, body, query) {
585
- const opType = matchWriteOp(method, path2);
588
+ async dispatch(method, path3, body, query) {
589
+ const opType = matchWriteOp(method, path3);
586
590
  if (opType) {
587
- return this.dispatchWrite(opType, method, path2, body, query);
591
+ return this.dispatchWrite(opType, method, path3, body, query);
588
592
  }
589
593
  if (method === "GET") {
590
- const cached = await this.readFromCache(path2, query);
594
+ const cached = await this.readFromCache(path3, query);
591
595
  if (cached !== null) return cached;
592
596
  }
593
597
  try {
594
- const result = await this.networkRequest(method, path2, body, query);
595
- if (method === "GET") this.cacheReadResult(path2, query, result);
598
+ const result = await this.networkRequest(method, path3, body, query);
599
+ if (method === "GET") this.cacheReadResult(path3, query, result);
596
600
  return result;
597
601
  } catch {
598
602
  if (!this._isOnline) {
@@ -602,7 +606,7 @@ var OfflineManager = class extends OfflineEmitter {
602
606
  }
603
607
  }
604
608
  // ── Outbox: write operations ──────────────────────────────
605
- async dispatchWrite(opType, method, path2, body, query) {
609
+ async dispatchWrite(opType, method, path3, body, query) {
606
610
  const clientId = generateId();
607
611
  const idempotencyKey = `sdk-${clientId}`;
608
612
  let enrichedBody = body;
@@ -616,7 +620,7 @@ var OfflineManager = class extends OfflineEmitter {
616
620
  let localMessage;
617
621
  if (opType === "message.send" && body && typeof body === "object") {
618
622
  const b = body;
619
- const convIdMatch = path2.match(/\/(?:messages|direct|groups)\/([^/]+)/);
623
+ const convIdMatch = path3.match(/\/(?:messages|direct|groups)\/([^/]+)/);
620
624
  const conversationId = convIdMatch?.[1] ?? "";
621
625
  localMessage = {
622
626
  id: `local-${clientId}`,
@@ -637,7 +641,7 @@ var OfflineManager = class extends OfflineEmitter {
637
641
  id: clientId,
638
642
  type: opType,
639
643
  method,
640
- path: path2,
644
+ path: path3,
641
645
  body: enrichedBody,
642
646
  query,
643
647
  status: "pending",
@@ -957,28 +961,28 @@ var OfflineManager = class extends OfflineEmitter {
957
961
  return 0;
958
962
  }
959
963
  // ── Read cache ────────────────────────────────────────────
960
- async readFromCache(path2, query) {
961
- if (/\/api\/im\/conversations$/.test(path2)) {
964
+ async readFromCache(path3, query) {
965
+ if (/\/api\/im\/conversations$/.test(path3)) {
962
966
  const convos = await this.storage.getConversations({ limit: 50 });
963
967
  if (convos.length > 0) return { ok: true, data: convos };
964
968
  }
965
- const msgMatch = path2.match(/\/api\/im\/messages\/([^/]+)$/);
969
+ const msgMatch = path3.match(/\/api\/im\/messages\/([^/]+)$/);
966
970
  if (msgMatch) {
967
971
  const convId = msgMatch[1];
968
972
  const limit = query?.limit ? parseInt(query.limit) : 50;
969
973
  const messages = await this.storage.getMessages(convId, { limit, before: query?.before });
970
974
  if (messages.length > 0) return { ok: true, data: messages };
971
975
  }
972
- if (/\/api\/im\/contacts$/.test(path2)) {
976
+ if (/\/api\/im\/contacts$/.test(path3)) {
973
977
  const contacts = await this.storage.getContacts();
974
978
  if (contacts.length > 0) return { ok: true, data: contacts };
975
979
  }
976
980
  return null;
977
981
  }
978
- async cacheReadResult(path2, _query, result) {
982
+ async cacheReadResult(path3, _query, result) {
979
983
  if (!result?.ok || !result?.data) return;
980
984
  try {
981
- if (/\/api\/im\/conversations$/.test(path2) && Array.isArray(result.data)) {
985
+ if (/\/api\/im\/conversations$/.test(path3) && Array.isArray(result.data)) {
982
986
  const convos = result.data.map((c) => ({
983
987
  id: c.id,
984
988
  type: c.type ?? "direct",
@@ -992,7 +996,7 @@ var OfflineManager = class extends OfflineEmitter {
992
996
  }));
993
997
  await this.storage.putConversations(convos);
994
998
  }
995
- const msgMatch = path2.match(/\/api\/im\/messages\/([^/]+)$/);
999
+ const msgMatch = path3.match(/\/api\/im\/messages\/([^/]+)$/);
996
1000
  if (msgMatch && Array.isArray(result.data)) {
997
1001
  const messages = result.data.map((m) => ({
998
1002
  id: m.id,
@@ -1007,7 +1011,7 @@ var OfflineManager = class extends OfflineEmitter {
1007
1011
  }));
1008
1012
  await this.storage.putMessages(messages);
1009
1013
  }
1010
- if (/\/api\/im\/contacts$/.test(path2) && Array.isArray(result.data)) {
1014
+ if (/\/api\/im\/contacts$/.test(path3) && Array.isArray(result.data)) {
1011
1015
  await this.storage.putContacts(result.data);
1012
1016
  }
1013
1017
  } catch {
@@ -1120,12 +1124,635 @@ var OfflineManager = class extends OfflineEmitter {
1120
1124
  }
1121
1125
  };
1122
1126
 
1127
+ // src/community-hub.ts
1128
+ var CommunityHub = class {
1129
+ constructor(_r, config) {
1130
+ this._r = _r;
1131
+ this.feedCache = /* @__PURE__ */ new Map();
1132
+ this.statsCache = null;
1133
+ this.notifCountCache = null;
1134
+ this.notifCountTTL = 15e3;
1135
+ this.wsUnsubs = [];
1136
+ this.feedTTL = config?.feedTTLMs ?? 3e5;
1137
+ this.statsTTL = config?.statsTTLMs ?? 6e5;
1138
+ }
1139
+ /** Invalidate cached feeds/stats (e.g. after you posted). */
1140
+ invalidateCache(boardId) {
1141
+ if (boardId) this.feedCache.delete(boardId);
1142
+ else this.feedCache.clear();
1143
+ this.statsCache = null;
1144
+ this.notifCountCache = null;
1145
+ }
1146
+ /**
1147
+ * Subscribe to community.* WebSocket events; updates local notification count hint and invalidates feed.
1148
+ */
1149
+ attachRealtime(ws) {
1150
+ const onReply = () => {
1151
+ this.notifCountCache = null;
1152
+ this.feedCache.clear();
1153
+ };
1154
+ const types = [
1155
+ "community.reply",
1156
+ "community.vote",
1157
+ "community.answer.accepted",
1158
+ "community.mention"
1159
+ ];
1160
+ for (const t of types) {
1161
+ ws.on(t, onReply);
1162
+ this.wsUnsubs.push(() => ws.off(t, onReply));
1163
+ }
1164
+ }
1165
+ detachRealtime() {
1166
+ for (const u of this.wsUnsubs) u();
1167
+ this.wsUnsubs = [];
1168
+ }
1169
+ // ─── Intent (cached reads) ─────────────────────────────────
1170
+ async feed(opts) {
1171
+ const key = opts?.boardId ?? "__all__";
1172
+ const hit = this.feedCache.get(key);
1173
+ if (hit && Date.now() - hit.at < this.feedTTL) {
1174
+ return { ok: true, data: hit.payload };
1175
+ }
1176
+ const res = await this.listPosts({
1177
+ boardId: opts?.boardId,
1178
+ limit: opts?.limit ?? 20,
1179
+ sort: "hot"
1180
+ });
1181
+ if (res.ok && res.data != null) {
1182
+ this.feedCache.set(key, { at: Date.now(), payload: res.data });
1183
+ }
1184
+ return res;
1185
+ }
1186
+ async aggregatedContext(opts) {
1187
+ const [feed, stats, unreadNotifications] = await Promise.all([
1188
+ this.feed({ boardId: opts?.boardId, limit: opts?.feedLimit ?? 15 }),
1189
+ this.statsCached(),
1190
+ this.unreadCountCached()
1191
+ ]);
1192
+ return { feed, stats, unreadNotifications };
1193
+ }
1194
+ async statsCached() {
1195
+ if (this.statsCache && Date.now() - this.statsCache.at < this.statsTTL) {
1196
+ return { ok: true, data: this.statsCache.data };
1197
+ }
1198
+ const res = await this.getStats();
1199
+ if (res.ok && res.data != null) {
1200
+ this.statsCache = { at: Date.now(), data: res.data };
1201
+ }
1202
+ return res;
1203
+ }
1204
+ async unreadCountCached() {
1205
+ if (this.notifCountCache && Date.now() - this.notifCountCache.at < this.notifCountTTL) {
1206
+ return { ok: true, data: { unread: this.notifCountCache.count } };
1207
+ }
1208
+ const res = await this.getNotificationCount();
1209
+ const n = res.data?.unread;
1210
+ if (res.ok && typeof n === "number") {
1211
+ this.notifCountCache = { at: Date.now(), count: n };
1212
+ }
1213
+ return res;
1214
+ }
1215
+ /** Helpdesk question shortcut */
1216
+ async ask(title, content, tags) {
1217
+ const res = await this.createPost({
1218
+ boardId: "helpdesk",
1219
+ title,
1220
+ content,
1221
+ postType: "question",
1222
+ tags
1223
+ });
1224
+ if (res.ok) this.invalidateCache("helpdesk");
1225
+ return res;
1226
+ }
1227
+ /** Showcase battle report shortcut */
1228
+ async reportBattle(input) {
1229
+ const res = await this.createPost({
1230
+ boardId: "showcase",
1231
+ title: input.title,
1232
+ content: input.content,
1233
+ postType: "battleReport",
1234
+ tags: input.tags,
1235
+ linkedGeneIds: input.linkedGeneIds,
1236
+ linkedAgentId: input.linkedAgentId
1237
+ });
1238
+ if (res.ok) this.invalidateCache("showcase");
1239
+ return res;
1240
+ }
1241
+ // ─── Notifications & profile (auth) ────────────────────────
1242
+ async getNotifications(opts) {
1243
+ const q = {};
1244
+ if (opts?.unread) q.unread = "true";
1245
+ if (opts?.limit != null) q.limit = String(opts.limit);
1246
+ if (opts?.offset != null) q.offset = String(opts.offset);
1247
+ return this._r("GET", "/api/im/community/notifications", void 0, q);
1248
+ }
1249
+ async markNotificationsRead(notificationId) {
1250
+ const body = notificationId ? { notificationId } : {};
1251
+ return this._r("POST", "/api/im/community/notifications/read", body);
1252
+ }
1253
+ async getNotificationCount() {
1254
+ return this._r("GET", "/api/im/community/notifications/count");
1255
+ }
1256
+ async listBookmarks(opts) {
1257
+ const q = {};
1258
+ if (opts?.cursor) q.cursor = opts.cursor;
1259
+ if (opts?.limit != null) q.limit = String(opts.limit);
1260
+ return this._r("GET", "/api/im/community/bookmarks", void 0, q);
1261
+ }
1262
+ async followToggle(followingId, followingType) {
1263
+ return this._r("POST", "/api/im/community/follow", { followingId, followingType });
1264
+ }
1265
+ async listFollowing(type) {
1266
+ const q = {};
1267
+ if (type) q.type = type;
1268
+ return this._r("GET", "/api/im/community/following", void 0, q);
1269
+ }
1270
+ async listFollowers(userId) {
1271
+ return this._r("GET", `/api/im/community/followers/${encodeURIComponent(userId)}`);
1272
+ }
1273
+ async getProfile(userId) {
1274
+ return this._r("GET", `/api/im/community/profile/${encodeURIComponent(userId)}`);
1275
+ }
1276
+ // ─── REST (same surface as former CommunityClient) ─────────
1277
+ async createPost(input) {
1278
+ return this._r("POST", "/api/im/community/posts", input);
1279
+ }
1280
+ async listPosts(opts) {
1281
+ const query = {};
1282
+ if (opts?.boardId) query.boardId = opts.boardId;
1283
+ if (opts?.sort) query.sort = opts.sort;
1284
+ if (opts?.period) query.period = opts.period;
1285
+ if (opts?.authorType) query.authorType = opts.authorType;
1286
+ if (opts?.cursor) query.cursor = opts.cursor;
1287
+ if (opts?.limit != null) query.limit = String(opts.limit);
1288
+ return this._r("GET", "/api/im/community/posts", void 0, query);
1289
+ }
1290
+ async getPost(postId) {
1291
+ return this._r("GET", `/api/im/community/posts/${encodeURIComponent(postId)}`);
1292
+ }
1293
+ async updatePost(postId, input) {
1294
+ return this._r("PUT", `/api/im/community/posts/${encodeURIComponent(postId)}`, input);
1295
+ }
1296
+ async deletePost(postId) {
1297
+ return this._r("DELETE", `/api/im/community/posts/${encodeURIComponent(postId)}`);
1298
+ }
1299
+ async createComment(postId, input) {
1300
+ return this._r("POST", `/api/im/community/posts/${encodeURIComponent(postId)}/comments`, input);
1301
+ }
1302
+ async listComments(postId, opts) {
1303
+ const query = {};
1304
+ if (opts?.sort) query.sort = opts.sort;
1305
+ if (opts?.cursor) query.cursor = opts.cursor;
1306
+ if (opts?.limit != null) query.limit = String(opts.limit);
1307
+ return this._r("GET", `/api/im/community/posts/${encodeURIComponent(postId)}/comments`, void 0, query);
1308
+ }
1309
+ async markBestAnswer(commentId) {
1310
+ return this._r("POST", `/api/im/community/comments/${encodeURIComponent(commentId)}/best-answer`);
1311
+ }
1312
+ async vote(targetType, targetId, value) {
1313
+ return this._r("POST", "/api/im/community/vote", { targetType, targetId, value });
1314
+ }
1315
+ async bookmark(postId) {
1316
+ return this._r("POST", "/api/im/community/bookmark", { postId });
1317
+ }
1318
+ async search(query, opts) {
1319
+ const q = { q: query };
1320
+ if (opts?.boardId) q.boardId = opts.boardId;
1321
+ if (opts?.sort) q.sort = opts.sort;
1322
+ if (opts?.limit != null) q.limit = String(opts.limit);
1323
+ return this._r("GET", "/api/im/community/search", void 0, q);
1324
+ }
1325
+ async updateComment(commentId, input) {
1326
+ return this._r("PUT", `/api/im/community/comments/${encodeURIComponent(commentId)}`, input);
1327
+ }
1328
+ async deleteComment(commentId) {
1329
+ return this._r("DELETE", `/api/im/community/comments/${encodeURIComponent(commentId)}`);
1330
+ }
1331
+ async getStats() {
1332
+ return this._r("GET", "/api/im/community/stats");
1333
+ }
1334
+ async getTrendingTags(limit) {
1335
+ const query = {};
1336
+ if (limit != null) query.limit = String(limit);
1337
+ return this._r("GET", "/api/im/community/tags/trending", void 0, query);
1338
+ }
1339
+ async getHotPosts(opts) {
1340
+ const query = {};
1341
+ if (opts?.limit != null) query.limit = String(opts.limit);
1342
+ if (opts?.period) query.period = opts.period;
1343
+ return this._r("GET", "/api/im/community/hot", void 0, query);
1344
+ }
1345
+ async searchSuggest(q) {
1346
+ return this._r("GET", "/api/im/community/search/suggest", void 0, { q });
1347
+ }
1348
+ async autocompleteGenes(q, limit) {
1349
+ const query = { q };
1350
+ if (limit != null) query.limit = String(limit);
1351
+ return this._r("GET", "/api/im/community/autocomplete/genes", void 0, query);
1352
+ }
1353
+ async autocompleteSkills(q, limit) {
1354
+ const query = { q };
1355
+ if (limit != null) query.limit = String(limit);
1356
+ return this._r("GET", "/api/im/community/autocomplete/skills", void 0, query);
1357
+ }
1358
+ async createBattleReport(input) {
1359
+ return this.createPost({
1360
+ boardId: "showcase",
1361
+ title: `Battle Report: ${input.agentId}`,
1362
+ content: input.narrative || "Auto-generated battle report",
1363
+ postType: "battleReport",
1364
+ linkedGeneIds: input.geneIds,
1365
+ linkedAgentId: input.agentId
1366
+ });
1367
+ }
1368
+ async createMilestone(input) {
1369
+ return this.createPost({
1370
+ boardId: "showcase",
1371
+ title: input.title,
1372
+ content: input.content,
1373
+ postType: "milestone",
1374
+ linkedGeneIds: input.geneIds,
1375
+ linkedAgentId: input.agentId,
1376
+ tags: input.tags
1377
+ });
1378
+ }
1379
+ async createGeneRelease(input) {
1380
+ return this.createPost({
1381
+ boardId: "showcase",
1382
+ title: input.title,
1383
+ content: input.content,
1384
+ postType: "geneRelease",
1385
+ linkedGeneIds: [input.geneId],
1386
+ tags: input.tags
1387
+ });
1388
+ }
1389
+ };
1390
+
1391
+ // src/aip.ts
1392
+ var import_aip_sdk = require("@prismer/aip-sdk");
1393
+ var import_aip_sdk2 = require("@prismer/aip-sdk");
1394
+ var import_aip_sdk3 = require("@prismer/aip-sdk");
1395
+ var import_aip_sdk4 = require("@prismer/aip-sdk");
1396
+ var import_aip_sdk5 = require("@prismer/aip-sdk");
1397
+
1123
1398
  // src/types.ts
1124
1399
  var ENVIRONMENTS = {
1125
1400
  production: "https://prismer.cloud"
1126
1401
  };
1127
1402
 
1403
+ // src/encryption.ts
1404
+ function getSubtleCrypto() {
1405
+ if (typeof globalThis.crypto?.subtle !== "undefined") {
1406
+ return globalThis.crypto.subtle;
1407
+ }
1408
+ try {
1409
+ const { webcrypto } = require("crypto");
1410
+ return webcrypto.subtle;
1411
+ } catch {
1412
+ throw new Error("No SubtleCrypto available. Requires browser or Node.js 16+.");
1413
+ }
1414
+ }
1415
+ function getRandomValues(arr) {
1416
+ if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
1417
+ return globalThis.crypto.getRandomValues(arr);
1418
+ }
1419
+ try {
1420
+ const { webcrypto } = require("crypto");
1421
+ return webcrypto.getRandomValues(arr);
1422
+ } catch {
1423
+ throw new Error("No crypto.getRandomValues available.");
1424
+ }
1425
+ }
1426
+ var subtle = () => getSubtleCrypto();
1427
+ var PBKDF2_ITERATIONS = 1e5;
1428
+ var SALT_LENGTH = 16;
1429
+ var IV_LENGTH = 12;
1430
+ var KEY_LENGTH = 256;
1431
+ var _E2EEncryption = class _E2EEncryption {
1432
+ constructor() {
1433
+ this.masterKey = null;
1434
+ this.keyPair = null;
1435
+ this.sessionKeys = /* @__PURE__ */ new Map();
1436
+ // conversationId → AES key
1437
+ this.salt = null;
1438
+ // ─── Pipeline Functions ──────────────────────────────────
1439
+ this.messageCount = 0;
1440
+ this.lastRotation = Date.now();
1441
+ }
1442
+ /**
1443
+ * Initialize encryption with user passphrase.
1444
+ * Derives a master key via PBKDF2 and generates an ECDH key pair.
1445
+ *
1446
+ * @param passphrase - User passphrase for master key derivation
1447
+ * @param salt - Optional Base64-encoded salt. If omitted, a random 16-byte salt is generated.
1448
+ * Store the salt (via exportSalt()) so you can re-derive the same master key later.
1449
+ */
1450
+ async init(passphrase, salt) {
1451
+ this.salt = salt ? new Uint8Array(base64ToArrayBuffer(salt)) : getRandomValues(new Uint8Array(SALT_LENGTH));
1452
+ const passphraseKey = await subtle().importKey(
1453
+ "raw",
1454
+ new TextEncoder().encode(passphrase),
1455
+ "PBKDF2",
1456
+ false,
1457
+ ["deriveKey"]
1458
+ );
1459
+ this.masterKey = await subtle().deriveKey(
1460
+ {
1461
+ name: "PBKDF2",
1462
+ salt: new Uint8Array(this.salt.buffer, this.salt.byteOffset, this.salt.byteLength).buffer,
1463
+ iterations: PBKDF2_ITERATIONS,
1464
+ hash: "SHA-256"
1465
+ },
1466
+ passphraseKey,
1467
+ { name: "AES-GCM", length: KEY_LENGTH },
1468
+ false,
1469
+ ["encrypt", "decrypt"]
1470
+ );
1471
+ this.keyPair = await subtle().generateKey(
1472
+ { name: "ECDH", namedCurve: "P-256" },
1473
+ true,
1474
+ ["deriveKey"]
1475
+ );
1476
+ }
1477
+ /**
1478
+ * Export the salt as Base64 string for persistent storage.
1479
+ * You must store this and pass it back to init() to re-derive the same master key.
1480
+ */
1481
+ exportSalt() {
1482
+ if (!this.salt) throw new Error("E2E not initialized. Call init() first.");
1483
+ return arrayBufferToBase64(this.salt.buffer);
1484
+ }
1485
+ /**
1486
+ * Export public key for sharing with conversation peers.
1487
+ */
1488
+ async exportPublicKey() {
1489
+ if (!this.keyPair) throw new Error("E2E not initialized. Call init() first.");
1490
+ return subtle().exportKey("jwk", this.keyPair.publicKey);
1491
+ }
1492
+ /**
1493
+ * Derive a shared session key for a conversation using ECDH.
1494
+ * Call this with each peer's public key.
1495
+ */
1496
+ async deriveSessionKey(conversationId, peerPublicKey) {
1497
+ if (!this.keyPair) throw new Error("E2E not initialized. Call init() first.");
1498
+ const importedPeerKey = await subtle().importKey(
1499
+ "jwk",
1500
+ peerPublicKey,
1501
+ { name: "ECDH", namedCurve: "P-256" },
1502
+ false,
1503
+ []
1504
+ );
1505
+ const sessionKey = await subtle().deriveKey(
1506
+ { name: "ECDH", public: importedPeerKey },
1507
+ this.keyPair.privateKey,
1508
+ { name: "AES-GCM", length: KEY_LENGTH },
1509
+ false,
1510
+ ["encrypt", "decrypt"]
1511
+ );
1512
+ this.sessionKeys.set(conversationId, sessionKey);
1513
+ }
1514
+ /**
1515
+ * Set a pre-shared session key for a conversation.
1516
+ * Useful when the key is exchanged out-of-band or derived from a group key.
1517
+ */
1518
+ async setSessionKey(conversationId, rawKey) {
1519
+ const key = await subtle().importKey(
1520
+ "raw",
1521
+ rawKey,
1522
+ { name: "AES-GCM", length: KEY_LENGTH },
1523
+ false,
1524
+ ["encrypt", "decrypt"]
1525
+ );
1526
+ this.sessionKeys.set(conversationId, key);
1527
+ }
1528
+ /**
1529
+ * Generate a random session key for a conversation.
1530
+ * Returns the raw key bytes for sharing with peers.
1531
+ */
1532
+ async generateSessionKey(conversationId) {
1533
+ const key = await subtle().generateKey(
1534
+ { name: "AES-GCM", length: KEY_LENGTH },
1535
+ true,
1536
+ ["encrypt", "decrypt"]
1537
+ );
1538
+ this.sessionKeys.set(conversationId, key);
1539
+ return subtle().exportKey("raw", key);
1540
+ }
1541
+ /**
1542
+ * Encrypt plaintext for a conversation.
1543
+ * Returns base64-encoded ciphertext with prepended IV.
1544
+ */
1545
+ async encrypt(conversationId, plaintext) {
1546
+ const key = this.sessionKeys.get(conversationId);
1547
+ if (!key) throw new Error(`No session key for conversation ${conversationId}. Call deriveSessionKey() first.`);
1548
+ const iv = getRandomValues(new Uint8Array(IV_LENGTH));
1549
+ const encoded = new TextEncoder().encode(plaintext);
1550
+ const ciphertext = await subtle().encrypt(
1551
+ { name: "AES-GCM", iv },
1552
+ key,
1553
+ encoded
1554
+ );
1555
+ const combined = new Uint8Array(iv.length + ciphertext.byteLength);
1556
+ combined.set(iv, 0);
1557
+ combined.set(new Uint8Array(ciphertext), iv.length);
1558
+ return arrayBufferToBase64(combined.buffer);
1559
+ }
1560
+ /**
1561
+ * Decrypt ciphertext from a conversation.
1562
+ * Expects base64-encoded data with prepended IV.
1563
+ */
1564
+ async decrypt(conversationId, ciphertext) {
1565
+ const key = this.sessionKeys.get(conversationId);
1566
+ if (!key) throw new Error(`No session key for conversation ${conversationId}. Call deriveSessionKey() first.`);
1567
+ const combined = base64ToArrayBuffer(ciphertext);
1568
+ const iv = combined.slice(0, IV_LENGTH);
1569
+ const data = combined.slice(IV_LENGTH);
1570
+ const decrypted = await subtle().decrypt(
1571
+ { name: "AES-GCM", iv: new Uint8Array(iv) },
1572
+ key,
1573
+ data
1574
+ );
1575
+ return new TextDecoder().decode(decrypted);
1576
+ }
1577
+ /**
1578
+ * Check if a session key exists for a conversation.
1579
+ */
1580
+ hasSessionKey(conversationId) {
1581
+ return this.sessionKeys.has(conversationId);
1582
+ }
1583
+ /**
1584
+ * Remove session key for a conversation.
1585
+ */
1586
+ removeSessionKey(conversationId) {
1587
+ this.sessionKeys.delete(conversationId);
1588
+ }
1589
+ /**
1590
+ * Clear all keys and reset state.
1591
+ */
1592
+ destroy() {
1593
+ this.masterKey = null;
1594
+ this.keyPair = null;
1595
+ this.sessionKeys.clear();
1596
+ this.salt = null;
1597
+ this.messageCount = 0;
1598
+ }
1599
+ /**
1600
+ * High-level encrypt-for-send pipeline.
1601
+ * Encrypts content, builds metadata, and handles key rotation.
1602
+ *
1603
+ * Returns { encryptedContent, metadata } ready to send.
1604
+ */
1605
+ async encryptForSend(conversationId, content) {
1606
+ if (!this.hasSessionKey(conversationId)) {
1607
+ throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
1608
+ }
1609
+ const needsRotation = this.shouldRotateKey();
1610
+ const encryptedContent = await this.encrypt(conversationId, content);
1611
+ this.messageCount++;
1612
+ return {
1613
+ encryptedContent,
1614
+ metadata: {
1615
+ encrypted: true,
1616
+ encryptionVersion: 1,
1617
+ ...needsRotation && { keyRotationRequested: true }
1618
+ }
1619
+ };
1620
+ }
1621
+ /**
1622
+ * High-level decrypt-on-receive pipeline.
1623
+ * Decrypts content and validates metadata.
1624
+ */
1625
+ async decryptOnReceive(conversationId, encryptedContent, metadata) {
1626
+ if (!this.hasSessionKey(conversationId)) {
1627
+ throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
1628
+ }
1629
+ return this.decrypt(conversationId, encryptedContent);
1630
+ }
1631
+ /**
1632
+ * High-level file encryption pipeline.
1633
+ */
1634
+ async encryptFile(conversationId, fileData) {
1635
+ const base64Data = arrayBufferToBase64(fileData);
1636
+ const encryptedData = await this.encrypt(conversationId, base64Data);
1637
+ return {
1638
+ encryptedData,
1639
+ metadata: {
1640
+ encrypted: true,
1641
+ encryptionVersion: 1,
1642
+ fileEncrypted: true
1643
+ }
1644
+ };
1645
+ }
1646
+ /**
1647
+ * High-level file decryption pipeline.
1648
+ */
1649
+ async decryptFile(conversationId, encryptedData) {
1650
+ const base64Data = await this.decrypt(conversationId, encryptedData);
1651
+ return base64ToArrayBuffer(base64Data);
1652
+ }
1653
+ /**
1654
+ * Check if key rotation is needed (1000 messages or 24 hours).
1655
+ */
1656
+ shouldRotateKey() {
1657
+ if (this.messageCount >= _E2EEncryption.KEY_ROTATION_THRESHOLD) {
1658
+ return true;
1659
+ }
1660
+ if (Date.now() - this.lastRotation >= _E2EEncryption.KEY_ROTATION_INTERVAL_MS) {
1661
+ return true;
1662
+ }
1663
+ return false;
1664
+ }
1665
+ /**
1666
+ * Perform key rotation: generate new ECDH keypair and reset counters.
1667
+ * The caller is responsible for re-exchanging keys with peers.
1668
+ */
1669
+ async rotateKeys() {
1670
+ this.keyPair = await subtle().generateKey(
1671
+ { name: "ECDH", namedCurve: "P-256" },
1672
+ false,
1673
+ ["deriveKey"]
1674
+ );
1675
+ this.messageCount = 0;
1676
+ this.lastRotation = Date.now();
1677
+ this.sessionKeys.clear();
1678
+ return this.exportPublicKey();
1679
+ }
1680
+ };
1681
+ _E2EEncryption.KEY_ROTATION_THRESHOLD = 1e3;
1682
+ _E2EEncryption.KEY_ROTATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1683
+ var E2EEncryption = _E2EEncryption;
1684
+ function arrayBufferToBase64(buffer) {
1685
+ if (typeof btoa !== "undefined") {
1686
+ const bytes = new Uint8Array(buffer);
1687
+ let binary = "";
1688
+ for (let i = 0; i < bytes.byteLength; i++) {
1689
+ binary += String.fromCharCode(bytes[i]);
1690
+ }
1691
+ return btoa(binary);
1692
+ }
1693
+ return Buffer.from(buffer).toString("base64");
1694
+ }
1695
+ function base64ToArrayBuffer(base64) {
1696
+ if (typeof atob !== "undefined") {
1697
+ const binary = atob(base64);
1698
+ const bytes = new Uint8Array(binary.length);
1699
+ for (let i = 0; i < binary.length; i++) {
1700
+ bytes[i] = binary.charCodeAt(i);
1701
+ }
1702
+ return bytes.buffer;
1703
+ }
1704
+ const buf = Buffer.from(base64, "base64");
1705
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1706
+ }
1707
+
1128
1708
  // src/index.ts
1709
+ var _fs = null;
1710
+ var _os = null;
1711
+ var _path = null;
1712
+ try {
1713
+ _fs = require("fs");
1714
+ _os = require("os");
1715
+ _path = require("path");
1716
+ } catch {
1717
+ }
1718
+ function resolveApiKey(explicit) {
1719
+ if (explicit) return explicit;
1720
+ try {
1721
+ if (typeof process !== "undefined" && process.env?.PRISMER_API_KEY) {
1722
+ return process.env.PRISMER_API_KEY;
1723
+ }
1724
+ } catch {
1725
+ }
1726
+ if (_fs && _os && _path) {
1727
+ try {
1728
+ const configPath = _path.join(_os.homedir(), ".prismer", "config.toml");
1729
+ const raw = _fs.readFileSync(configPath, "utf-8");
1730
+ const match = raw.match(/^api_key\s*=\s*'([^']+)'/m) || raw.match(/^api_key\s*=\s*"([^"]+)"/m);
1731
+ if (match?.[1]) return match[1];
1732
+ } catch {
1733
+ }
1734
+ }
1735
+ return "";
1736
+ }
1737
+ function resolveBaseUrl(explicit) {
1738
+ if (explicit) return explicit;
1739
+ try {
1740
+ if (typeof process !== "undefined" && process.env?.PRISMER_BASE_URL) {
1741
+ return process.env.PRISMER_BASE_URL;
1742
+ }
1743
+ } catch {
1744
+ }
1745
+ if (_fs && _os && _path) {
1746
+ try {
1747
+ const configPath = _path.join(_os.homedir(), ".prismer", "config.toml");
1748
+ const raw = _fs.readFileSync(configPath, "utf-8");
1749
+ const match = raw.match(/^base_url\s*=\s*'([^']+)'/m) || raw.match(/^base_url\s*=\s*"([^"]+)"/m);
1750
+ if (match?.[1]) return match[1];
1751
+ } catch {
1752
+ }
1753
+ }
1754
+ return void 0;
1755
+ }
1129
1756
  var AccountClient = class {
1130
1757
  constructor(_r) {
1131
1758
  this._r = _r;
@@ -1138,6 +1765,10 @@ var AccountClient = class {
1138
1765
  async me() {
1139
1766
  return this._r("GET", "/api/im/me");
1140
1767
  }
1768
+ /** Update own profile */
1769
+ async updateProfile(options) {
1770
+ return this._r("PATCH", "/api/im/me", options);
1771
+ }
1141
1772
  /** Refresh JWT token */
1142
1773
  async refreshToken() {
1143
1774
  return this._r("POST", "/api/im/token/refresh");
@@ -1228,6 +1859,30 @@ var ConversationsClient = class {
1228
1859
  async markAsRead(conversationId) {
1229
1860
  return this._r("POST", `/api/im/conversations/${conversationId}/read`);
1230
1861
  }
1862
+ /** Archive a conversation */
1863
+ async archive(conversationId) {
1864
+ return this._r("POST", `/api/im/conversations/${conversationId}/archive`);
1865
+ }
1866
+ /** Unarchive a conversation */
1867
+ async unarchive(conversationId) {
1868
+ return this._r("POST", `/api/im/conversations/${conversationId}/unarchive`);
1869
+ }
1870
+ /** Update conversation metadata */
1871
+ async update(conversationId, options) {
1872
+ return this._r("PATCH", `/api/im/conversations/${conversationId}`, options);
1873
+ }
1874
+ /** Pin or unpin a conversation */
1875
+ async pin(conversationId, pinned) {
1876
+ return this._r("PATCH", `/api/im/conversations/${conversationId}/pin`, { pinned });
1877
+ }
1878
+ /** Mute or unmute a conversation */
1879
+ async mute(conversationId, muted) {
1880
+ return this._r("PATCH", `/api/im/conversations/${conversationId}/mute`, { muted });
1881
+ }
1882
+ /** Delete a conversation */
1883
+ async delete(conversationId) {
1884
+ return this._r("DELETE", `/api/im/conversations/${conversationId}`);
1885
+ }
1231
1886
  };
1232
1887
  var MessagesClient = class {
1233
1888
  constructor(_r) {
@@ -1257,6 +1912,10 @@ var MessagesClient = class {
1257
1912
  async delete(conversationId, messageId) {
1258
1913
  return this._r("DELETE", `/api/im/messages/${conversationId}/${messageId}`);
1259
1914
  }
1915
+ /** Mark messages as delivered */
1916
+ async markDelivered(conversationId, messageIds) {
1917
+ return this._r("POST", "/api/im/messages/delivered", { conversationId, messageIds });
1918
+ }
1260
1919
  };
1261
1920
  var ContactsClient = class {
1262
1921
  constructor(_r) {
@@ -1266,6 +1925,18 @@ var ContactsClient = class {
1266
1925
  async list() {
1267
1926
  return this._r("GET", "/api/im/contacts");
1268
1927
  }
1928
+ /** Search users/agents by query */
1929
+ async search(query, options) {
1930
+ const params = { q: query };
1931
+ if (options?.type && options.type !== "all") params.type = options.type;
1932
+ if (options?.limit) params.limit = String(options.limit);
1933
+ if (options?.offset) params.offset = String(options.offset);
1934
+ return this._r("GET", "/api/im/discover", void 0, params);
1935
+ }
1936
+ /** Get a user's public profile */
1937
+ async getProfile(userId) {
1938
+ return this._r("GET", `/api/im/users/${userId}`);
1939
+ }
1269
1940
  /** Discover agents by capability or type */
1270
1941
  async discover(options) {
1271
1942
  const query = {};
@@ -1273,6 +1944,67 @@ var ContactsClient = class {
1273
1944
  if (options?.capability) query.capability = options.capability;
1274
1945
  return this._r("GET", "/api/im/discover", void 0, query);
1275
1946
  }
1947
+ // ─── Friend System (v1.8.0 P9) ─────────────────────────
1948
+ /** Send a friend request */
1949
+ async request(userId, opts) {
1950
+ return this._r("POST", "/api/im/contacts/request", { userId, ...opts });
1951
+ }
1952
+ /** List pending friend requests received */
1953
+ async pendingReceived(opts) {
1954
+ const params = {};
1955
+ if (opts?.limit) params.limit = String(opts.limit);
1956
+ if (opts?.offset) params.offset = String(opts.offset);
1957
+ return this._r("GET", "/api/im/contacts/requests/received", void 0, params);
1958
+ }
1959
+ /** List pending friend requests sent */
1960
+ async pendingSent(opts) {
1961
+ const params = {};
1962
+ if (opts?.limit) params.limit = String(opts.limit);
1963
+ if (opts?.offset) params.offset = String(opts.offset);
1964
+ return this._r("GET", "/api/im/contacts/requests/sent", void 0, params);
1965
+ }
1966
+ /** Accept a friend request */
1967
+ async accept(requestId) {
1968
+ return this._r("POST", `/api/im/contacts/requests/${requestId}/accept`);
1969
+ }
1970
+ /** Reject a friend request */
1971
+ async reject(requestId) {
1972
+ return this._r("POST", `/api/im/contacts/requests/${requestId}/reject`);
1973
+ }
1974
+ /** List friends */
1975
+ async friends(opts) {
1976
+ const params = {};
1977
+ if (opts?.limit) params.limit = String(opts.limit);
1978
+ if (opts?.offset) params.offset = String(opts.offset);
1979
+ return this._r("GET", "/api/im/contacts/friends", void 0, params);
1980
+ }
1981
+ /** Remove a friend */
1982
+ async remove(userId) {
1983
+ return this._r("DELETE", `/api/im/contacts/${userId}/remove`);
1984
+ }
1985
+ /** Set a remark/alias for a contact */
1986
+ async setRemark(userId, remark) {
1987
+ return this._r("PATCH", `/api/im/contacts/${userId}/remark`, { remark });
1988
+ }
1989
+ /** Block a user */
1990
+ async block(userId) {
1991
+ return this._r("POST", `/api/im/contacts/${userId}/block`, {});
1992
+ }
1993
+ /** Unblock a user */
1994
+ async unblock(userId) {
1995
+ return this._r("DELETE", `/api/im/contacts/${userId}/block`);
1996
+ }
1997
+ /** List blocked users */
1998
+ async blocklist(opts) {
1999
+ const params = {};
2000
+ if (opts?.limit) params.limit = String(opts.limit);
2001
+ if (opts?.offset) params.offset = String(opts.offset);
2002
+ return this._r("GET", "/api/im/contacts/blocked", void 0, params);
2003
+ }
2004
+ /** Get presence status for multiple users */
2005
+ async getPresence(userIds) {
2006
+ return this._r("POST", "/api/im/presence/batch", { userIds });
2007
+ }
1276
2008
  };
1277
2009
  var BindingsClient = class {
1278
2010
  constructor(_r) {
@@ -1424,6 +2156,23 @@ var MemoryClient = class {
1424
2156
  if (scope) query.scope = scope;
1425
2157
  return this._r("GET", "/api/im/memory/load", void 0, query);
1426
2158
  }
2159
+ /** Get memory-gene knowledge links for the authenticated user's memory files (v1.8.0) */
2160
+ async getKnowledgeLinks() {
2161
+ return this._r("GET", "/api/im/memory/links");
2162
+ }
2163
+ };
2164
+ var KnowledgeLinkClient = class {
2165
+ constructor(_r) {
2166
+ this._r = _r;
2167
+ }
2168
+ /**
2169
+ * Get all knowledge links for a given entity.
2170
+ * @param entityType - One of: memory, gene, capsule, signal
2171
+ * @param entityId - The entity ID
2172
+ */
2173
+ async getLinks(entityType, entityId) {
2174
+ return this._r("GET", "/api/im/knowledge/links", void 0, { entityType, entityId });
2175
+ }
1427
2176
  };
1428
2177
  var IdentityClient = class {
1429
2178
  constructor(_r) {
@@ -1526,6 +2275,62 @@ var EvolutionClient = class {
1526
2275
  if (limit != null) query.limit = String(limit);
1527
2276
  return this._r("GET", "/api/im/evolution/public/feed", void 0, query);
1528
2277
  }
2278
+ // ── Leaderboard V2 (public, no auth required) ──
2279
+ /** Get hero section global stats (total agents, genes, capsules, savings) */
2280
+ async getLeaderboardHero() {
2281
+ return this._r("GET", "/api/im/evolution/leaderboard/hero");
2282
+ }
2283
+ /** Get rising stars leaderboard */
2284
+ async getLeaderboardRising(period, limit) {
2285
+ const query = {};
2286
+ if (period) query.period = period;
2287
+ if (limit != null) query.limit = String(limit);
2288
+ return this._r("GET", "/api/im/evolution/leaderboard/rising", void 0, query);
2289
+ }
2290
+ /** Get leaderboard summary stats (totalAgentsEvolving, totalGenesCreated, etc.) */
2291
+ async getLeaderboardStats() {
2292
+ return this._r("GET", "/api/im/evolution/leaderboard/stats");
2293
+ }
2294
+ /** Get agent improvement board */
2295
+ async getLeaderboardAgents(period, domain) {
2296
+ const query = {};
2297
+ if (period) query.period = period;
2298
+ if (domain) query.domain = domain;
2299
+ return this._r("GET", "/api/im/evolution/leaderboard/agents", void 0, query);
2300
+ }
2301
+ /** Get gene impact board */
2302
+ async getLeaderboardGenes(period, sort) {
2303
+ const query = {};
2304
+ if (period) query.period = period;
2305
+ if (sort) query.sort = sort;
2306
+ return this._r("GET", "/api/im/evolution/leaderboard/genes", void 0, query);
2307
+ }
2308
+ /** Get contributor board */
2309
+ async getLeaderboardContributors(period) {
2310
+ const query = {};
2311
+ if (period) query.period = period;
2312
+ return this._r("GET", "/api/im/evolution/leaderboard/contributors", void 0, query);
2313
+ }
2314
+ /** Get cross-environment comparison data */
2315
+ async getLeaderboardComparison() {
2316
+ return this._r("GET", "/api/im/evolution/leaderboard/comparison");
2317
+ }
2318
+ /** Get public profile page data for an agent or owner */
2319
+ async getPublicProfile(entityId) {
2320
+ return this._r("GET", `/api/im/evolution/profile/${encodeURIComponent(entityId)}`);
2321
+ }
2322
+ /** Render agent/creator card as PNG */
2323
+ async renderCard(input) {
2324
+ return this._r("POST", "/api/im/evolution/card/render", input);
2325
+ }
2326
+ /** Get benchmark data for profile FOMO section */
2327
+ async getBenchmark() {
2328
+ return this._r("GET", "/api/im/evolution/benchmark");
2329
+ }
2330
+ /** Get gene highlight capsules for profile page */
2331
+ async getHighlights(geneId) {
2332
+ return this._r("GET", `/api/im/evolution/highlights/${encodeURIComponent(geneId)}`);
2333
+ }
1529
2334
  // ── Authenticated endpoints ──
1530
2335
  /** Analyze signals and get gene recommendation */
1531
2336
  async analyze(options) {
@@ -1607,11 +2412,11 @@ var EvolutionClient = class {
1607
2412
  }
1608
2413
  /** Delete a gene */
1609
2414
  async deleteGene(geneId) {
1610
- return this._r("DELETE", `/api/im/evolution/genes/${geneId}`);
2415
+ return this._r("DELETE", `/api/im/evolution/genes/${encodeURIComponent(geneId)}`);
1611
2416
  }
1612
2417
  /** Publish a gene. Pass skipCanary=true to bypass canary validation (MVP/admin). */
1613
2418
  async publishGene(geneId, options) {
1614
- return this._r("POST", `/api/im/evolution/genes/${geneId}/publish`, options?.skipCanary ? { skipCanary: true } : void 0);
2419
+ return this._r("POST", `/api/im/evolution/genes/${encodeURIComponent(geneId)}/publish`, options?.skipCanary ? { skipCanary: true } : void 0);
1615
2420
  }
1616
2421
  /** Import a published gene */
1617
2422
  async importGene(geneId) {
@@ -1682,8 +2487,8 @@ var EvolutionClient = class {
1682
2487
  return this._r("GET", "/api/im/skills/stats");
1683
2488
  }
1684
2489
  /** Install a skill — creates Gene + returns content + install guide */
1685
- async installSkill(slugOrId) {
1686
- return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`);
2490
+ async installSkill(slugOrId, scope) {
2491
+ return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, scope ? { scope } : void 0);
1687
2492
  }
1688
2493
  /** Uninstall a skill */
1689
2494
  async uninstallSkill(slugOrId) {
@@ -1697,6 +2502,14 @@ var EvolutionClient = class {
1697
2502
  async getSkillContent(slugOrId) {
1698
2503
  return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
1699
2504
  }
2505
+ /** Create/submit a community skill */
2506
+ async createSkill(input) {
2507
+ return this._r("POST", "/api/im/skills", input);
2508
+ }
2509
+ /** Star a skill (increment community rating) */
2510
+ async starSkill(skillId) {
2511
+ return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
2512
+ }
1700
2513
  /**
1701
2514
  * Install a skill and write SKILL.md to local filesystem.
1702
2515
  * Combines cloud install + local file sync for Claude Code / OpenClaw / OpenCode.
@@ -1721,30 +2534,30 @@ var EvolutionClient = class {
1721
2534
  }
1722
2535
  const localPaths = [];
1723
2536
  try {
1724
- const fs2 = await import("fs");
1725
- const path2 = await import("path");
1726
- const os2 = await import("os");
1727
- const home = os2.homedir();
1728
- const pluginBase = process.env.PRISMER_PLUGIN_DIR || path2.join(home, ".claude", "plugins", "prismer");
2537
+ const fs3 = await import("fs");
2538
+ const path3 = await import("path");
2539
+ const os3 = await import("os");
2540
+ const home = os3.homedir();
2541
+ const pluginBase = process.env.PRISMER_PLUGIN_DIR || path3.join(home, ".claude", "plugins", "prismer");
1729
2542
  const platformPaths = options?.project ? {
1730
- "claude-code": path2.join(options.projectRoot || ".", ".claude", "skills", slug),
1731
- "openclaw": path2.join(options.projectRoot || ".", "skills", slug),
1732
- "opencode": path2.join(options.projectRoot || ".", ".opencode", "skills", slug),
1733
- "plugin": path2.join(options.projectRoot || ".", ".claude", "plugins", "prismer", "skills", slug)
2543
+ "claude-code": path3.join(options.projectRoot || ".", ".claude", "skills", slug),
2544
+ "openclaw": path3.join(options.projectRoot || ".", "skills", slug),
2545
+ "opencode": path3.join(options.projectRoot || ".", ".opencode", "skills", slug),
2546
+ "plugin": path3.join(options.projectRoot || ".", ".claude", "plugins", "prismer", "skills", slug)
1734
2547
  } : {
1735
- "claude-code": path2.join(home, ".claude", "skills", slug),
1736
- "openclaw": path2.join(home, ".openclaw", "skills", slug),
1737
- "opencode": path2.join(home, ".config", "opencode", "skills", slug),
1738
- "plugin": path2.join(pluginBase, "skills", slug)
2548
+ "claude-code": path3.join(home, ".claude", "skills", slug),
2549
+ "openclaw": path3.join(home, ".openclaw", "skills", slug),
2550
+ "opencode": path3.join(home, ".config", "opencode", "skills", slug),
2551
+ "plugin": path3.join(pluginBase, "skills", slug)
1739
2552
  };
1740
2553
  const targets = options?.platforms || Object.keys(platformPaths);
1741
2554
  for (const platform of targets) {
1742
2555
  const dir = platformPaths[platform];
1743
2556
  if (!dir) continue;
1744
2557
  try {
1745
- fs2.mkdirSync(dir, { recursive: true });
1746
- const filePath = path2.join(dir, "SKILL.md");
1747
- fs2.writeFileSync(filePath, content, "utf-8");
2558
+ fs3.mkdirSync(dir, { recursive: true });
2559
+ const filePath = path3.join(dir, "SKILL.md");
2560
+ fs3.writeFileSync(filePath, content, "utf-8");
1748
2561
  localPaths.push(filePath);
1749
2562
  } catch {
1750
2563
  }
@@ -1759,24 +2572,24 @@ var EvolutionClient = class {
1759
2572
  async uninstallSkillLocal(slugOrId) {
1760
2573
  const result = await this.uninstallSkill(slugOrId);
1761
2574
  const removedPaths = [];
1762
- const safeSlug = slugOrId.replace(/[\/\\]/g, "").replace(/\.\./g, "");
1763
- if (!safeSlug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
2575
+ const slug = safeSlug(slugOrId);
2576
+ if (!slug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
1764
2577
  try {
1765
- const fs2 = await import("fs");
1766
- const path2 = await import("path");
1767
- const os2 = await import("os");
1768
- const home = os2.homedir();
1769
- const pluginBase = process.env.PRISMER_PLUGIN_DIR || path2.join(home, ".claude", "plugins", "prismer");
2578
+ const fs3 = await import("fs");
2579
+ const path3 = await import("path");
2580
+ const os3 = await import("os");
2581
+ const home = os3.homedir();
2582
+ const pluginBase = process.env.PRISMER_PLUGIN_DIR || path3.join(home, ".claude", "plugins", "prismer");
1770
2583
  const dirs = [
1771
- path2.join(home, ".claude", "skills", safeSlug),
1772
- path2.join(home, ".openclaw", "skills", safeSlug),
1773
- path2.join(home, ".config", "opencode", "skills", safeSlug),
1774
- path2.join(pluginBase, "skills", safeSlug)
2584
+ path3.join(home, ".claude", "skills", slug),
2585
+ path3.join(home, ".openclaw", "skills", slug),
2586
+ path3.join(home, ".config", "opencode", "skills", slug),
2587
+ path3.join(pluginBase, "skills", slug)
1775
2588
  ];
1776
2589
  for (const dir of dirs) {
1777
2590
  try {
1778
- if (fs2.existsSync(dir)) {
1779
- fs2.rmSync(dir, { recursive: true });
2591
+ if (fs3.existsSync(dir)) {
2592
+ fs3.rmSync(dir, { recursive: true });
1780
2593
  removedPaths.push(dir);
1781
2594
  }
1782
2595
  } catch {
@@ -1813,25 +2626,25 @@ var EvolutionClient = class {
1813
2626
  failed++;
1814
2627
  continue;
1815
2628
  }
1816
- const fs2 = await import("fs");
1817
- const path2 = await import("path");
1818
- const os2 = await import("os");
1819
- const home = os2.homedir();
1820
- const pluginBase = process.env.PRISMER_PLUGIN_DIR || path2.join(home, ".claude", "plugins", "prismer");
2629
+ const fs3 = await import("fs");
2630
+ const path3 = await import("path");
2631
+ const os3 = await import("os");
2632
+ const home = os3.homedir();
2633
+ const pluginBase = process.env.PRISMER_PLUGIN_DIR || path3.join(home, ".claude", "plugins", "prismer");
1821
2634
  const platformPaths = {
1822
- "claude-code": path2.join(home, ".claude", "skills", slug),
1823
- "openclaw": path2.join(home, ".openclaw", "skills", slug),
1824
- "opencode": path2.join(home, ".config", "opencode", "skills", slug),
1825
- "plugin": path2.join(pluginBase, "skills", slug)
2635
+ "claude-code": path3.join(home, ".claude", "skills", slug),
2636
+ "openclaw": path3.join(home, ".openclaw", "skills", slug),
2637
+ "opencode": path3.join(home, ".config", "opencode", "skills", slug),
2638
+ "plugin": path3.join(pluginBase, "skills", slug)
1826
2639
  };
1827
2640
  const targets = options?.platforms || Object.keys(platformPaths);
1828
2641
  for (const platform of targets) {
1829
2642
  const dir = platformPaths[platform];
1830
2643
  if (!dir) continue;
1831
2644
  try {
1832
- fs2.mkdirSync(dir, { recursive: true });
1833
- const filePath = path2.join(dir, "SKILL.md");
1834
- fs2.writeFileSync(filePath, content, "utf-8");
2645
+ fs3.mkdirSync(dir, { recursive: true });
2646
+ const filePath = path3.join(dir, "SKILL.md");
2647
+ fs3.writeFileSync(filePath, content, "utf-8");
1835
2648
  paths.push(filePath);
1836
2649
  } catch {
1837
2650
  }
@@ -1881,6 +2694,9 @@ var EvolutionClient = class {
1881
2694
  return this._r("POST", "/api/im/evolution/sync", body);
1882
2695
  }
1883
2696
  };
2697
+ function safeSlug(input) {
2698
+ return input.replace(/[\/\\]/g, "").replace(/\.\./g, "").replace(/\0/g, "");
2699
+ }
1884
2700
  function guessMimeType(fileName) {
1885
2701
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
1886
2702
  const map = {
@@ -1968,11 +2784,11 @@ var FilesClient = class {
1968
2784
  let bytes;
1969
2785
  let fileName;
1970
2786
  if (typeof input === "string") {
1971
- const fs2 = await import("fs");
1972
- const path2 = await import("path");
1973
- const buf = await fs2.promises.readFile(input);
2787
+ const fs3 = await import("fs");
2788
+ const path3 = await import("path");
2789
+ const buf = await fs3.promises.readFile(input);
1974
2790
  bytes = new Uint8Array(buf);
1975
- fileName = opts?.fileName || path2.basename(input);
2791
+ fileName = opts?.fileName || path3.basename(input);
1976
2792
  } else if (typeof Blob !== "undefined" && input instanceof Blob) {
1977
2793
  const ab = await input.arrayBuffer();
1978
2794
  bytes = new Uint8Array(ab);
@@ -2109,22 +2925,24 @@ var IMRealtimeClient = class {
2109
2925
  }
2110
2926
  };
2111
2927
  var IMClient = class {
2112
- constructor(request, wsBase, fetchFn, getAuthHeaders, offlineManager) {
2113
- this.account = new AccountClient(request);
2114
- this.direct = new DirectClient(request);
2115
- this.groups = new GroupsClient(request);
2116
- this.conversations = new ConversationsClient(request);
2117
- this.messages = new MessagesClient(request);
2118
- this.contacts = new ContactsClient(request);
2119
- this.bindings = new BindingsClient(request);
2120
- this.credits = new CreditsClient(request);
2121
- this.workspace = new WorkspaceClient(request);
2122
- this.tasks = new TasksClient(request);
2123
- this.memory = new MemoryClient(request);
2124
- this.identity = new IdentityClient(request);
2125
- this.security = new SecurityClient(request);
2126
- this.evolution = new EvolutionClient(request);
2127
- this.files = new FilesClient(request, wsBase, fetchFn, getAuthHeaders);
2928
+ constructor(request2, wsBase, fetchFn, getAuthHeaders, offlineManager, communityHubConfig) {
2929
+ this.account = new AccountClient(request2);
2930
+ this.direct = new DirectClient(request2);
2931
+ this.groups = new GroupsClient(request2);
2932
+ this.conversations = new ConversationsClient(request2);
2933
+ this.messages = new MessagesClient(request2);
2934
+ this.contacts = new ContactsClient(request2);
2935
+ this.bindings = new BindingsClient(request2);
2936
+ this.credits = new CreditsClient(request2);
2937
+ this.workspace = new WorkspaceClient(request2);
2938
+ this.tasks = new TasksClient(request2);
2939
+ this.memory = new MemoryClient(request2);
2940
+ this.knowledge = new KnowledgeLinkClient(request2);
2941
+ this.identity = new IdentityClient(request2);
2942
+ this.security = new SecurityClient(request2);
2943
+ this.evolution = new EvolutionClient(request2);
2944
+ this.community = new CommunityHub(request2, communityHubConfig ?? void 0);
2945
+ this.files = new FilesClient(request2, wsBase, fetchFn, getAuthHeaders);
2128
2946
  this.realtime = new IMRealtimeClient(wsBase);
2129
2947
  this.offline = offlineManager ?? null;
2130
2948
  }
@@ -2132,19 +2950,43 @@ var IMClient = class {
2132
2950
  async health() {
2133
2951
  return this.account["_r"]("GET", "/api/im/health");
2134
2952
  }
2953
+ /** Get workspace superset view with slot filtering */
2954
+ async getWorkspace(scope, slots, includeContent) {
2955
+ const params = new URLSearchParams();
2956
+ if (scope) params.set("scope", scope);
2957
+ if (slots?.length) params.set("slots", slots.join(","));
2958
+ if (includeContent) params.set("includeContent", "true");
2959
+ return this.workspace["_r"]("GET", `/api/im/workspace/view?${params}`);
2960
+ }
2135
2961
  };
2136
2962
  var PrismerClient = class {
2137
2963
  constructor(config = {}) {
2138
2964
  this._offlineManager = null;
2139
- if (config.apiKey && !config.apiKey.startsWith("sk-prismer-") && !config.apiKey.startsWith("eyJ")) {
2965
+ /** AIP identity for auto-signing (v1.8.0 S1) */
2966
+ this._identity = null;
2967
+ this._identityReady = null;
2968
+ const resolvedApiKey = resolveApiKey(config.apiKey);
2969
+ if (resolvedApiKey && !resolvedApiKey.startsWith("sk-prismer-") && !resolvedApiKey.startsWith("eyJ")) {
2140
2970
  console.warn('Warning: API key should start with "sk-prismer-" (or "eyJ" for IM JWT)');
2141
2971
  }
2142
- this.apiKey = config.apiKey || "";
2972
+ this.apiKey = resolvedApiKey;
2143
2973
  const envUrl = ENVIRONMENTS[config.environment || "production"];
2144
- this.baseUrl = (config.baseUrl || envUrl).replace(/\/$/, "");
2974
+ this.baseUrl = (resolveBaseUrl(config.baseUrl) || envUrl).replace(/\/$/, "");
2145
2975
  this.timeout = config.timeout || 3e4;
2146
2976
  this.fetchFn = config.fetch || fetch;
2147
2977
  this.imAgent = config.imAgent;
2978
+ if (config.identity) {
2979
+ if (config.identity === "auto" && this.apiKey) {
2980
+ this._identityReady = import_aip_sdk.AIPIdentity.fromApiKey(this.apiKey).then((id) => {
2981
+ this._identity = id;
2982
+ }).catch((err) => console.warn("[PrismerSDK] Identity init failed:", err));
2983
+ } else if (typeof config.identity === "object" && config.identity.privateKey) {
2984
+ const keyBytes = typeof Buffer !== "undefined" ? new Uint8Array(Buffer.from(config.identity.privateKey, "base64")) : new Uint8Array(atob(config.identity.privateKey).split("").map((c) => c.charCodeAt(0)));
2985
+ this._identityReady = import_aip_sdk.AIPIdentity.fromPrivateKey(keyBytes).then((id) => {
2986
+ this._identity = id;
2987
+ }).catch((err) => console.warn("[PrismerSDK] Identity init failed:", err));
2988
+ }
2989
+ }
2148
2990
  if (config.offline) {
2149
2991
  this._offlineManager = new OfflineManager(
2150
2992
  config.offline.storage,
@@ -2155,15 +2997,61 @@ var PrismerClient = class {
2155
2997
  (err) => console.warn("[PrismerSDK] Offline storage init failed:", err)
2156
2998
  );
2157
2999
  }
2158
- const imRequest = this._offlineManager ? (m, p, b, q) => this._offlineManager.dispatch(m, p, b, q) : (m, p, b, q) => this._request(m, p, b, q);
3000
+ let imRequest = this._offlineManager ? (m, p, b, q) => this._offlineManager.dispatch(m, p, b, q) : (m, p, b, q) => this._request(m, p, b, q);
3001
+ if (config.identity) {
3002
+ const baseRequest = imRequest;
3003
+ imRequest = (method, path3, body, query) => {
3004
+ if (method === "POST" && path3.includes("/messages") && body) {
3005
+ const b = body;
3006
+ if (!b.signature && !b.skipSigning) {
3007
+ const ready = this._identityReady || Promise.resolve();
3008
+ return ready.then(() => {
3009
+ if (this._identity) {
3010
+ return this._signAndSend(baseRequest, method, path3, b, query);
3011
+ }
3012
+ return baseRequest(method, path3, body, query);
3013
+ });
3014
+ }
3015
+ }
3016
+ return baseRequest(method, path3, body, query);
3017
+ };
3018
+ }
2159
3019
  this.im = new IMClient(
2160
3020
  imRequest,
2161
3021
  this.baseUrl,
2162
3022
  this.fetchFn,
2163
3023
  () => this._getAuthHeaders(),
2164
- this._offlineManager
3024
+ this._offlineManager,
3025
+ config.community ?? null
2165
3026
  );
2166
3027
  }
3028
+ /** Wait for identity to be ready (useful for tests or explicit await) */
3029
+ async ensureIdentity() {
3030
+ if (this._identityReady) await this._identityReady;
3031
+ return this._identity;
3032
+ }
3033
+ /** Auto-sign a message body and send (v1.8.0 S1) */
3034
+ async _signAndSend(baseRequest, method, path3, body, query) {
3035
+ if (this._identityReady) await this._identityReady;
3036
+ if (!this._identity) return baseRequest(method, path3, body, query);
3037
+ const content = body.content || "";
3038
+ const contentHashBytes = new Uint8Array(
3039
+ await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content))
3040
+ );
3041
+ const contentHash = Array.from(contentHashBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3042
+ const timestamp = Date.now();
3043
+ const payload = `1|${this._identity.did}|${body.type || "text"}|${timestamp}|${contentHash}`;
3044
+ const payloadBytes = new TextEncoder().encode(payload);
3045
+ const signature = await this._identity.sign(payloadBytes);
3046
+ return baseRequest(method, path3, {
3047
+ ...body,
3048
+ secVersion: 1,
3049
+ senderDid: this._identity.did,
3050
+ contentHash,
3051
+ signature,
3052
+ signedAt: timestamp
3053
+ }, query);
3054
+ }
2167
3055
  /** Build auth headers for raw HTTP requests (used by file upload) */
2168
3056
  _getAuthHeaders() {
2169
3057
  const headers = {};
@@ -2187,11 +3075,11 @@ var PrismerClient = class {
2187
3075
  // --------------------------------------------------------------------------
2188
3076
  // Internal request helper
2189
3077
  // --------------------------------------------------------------------------
2190
- async _request(method, path2, body, query, _isRetry) {
3078
+ async _request(method, path3, body, query, _isRetry) {
2191
3079
  const controller = new AbortController();
2192
3080
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2193
3081
  try {
2194
- let url = `${this.baseUrl}${path2}`;
3082
+ let url = `${this.baseUrl}${path3}`;
2195
3083
  if (query && Object.keys(query).length > 0) {
2196
3084
  url += "?" + new URLSearchParams(query).toString();
2197
3085
  }
@@ -2209,12 +3097,12 @@ var PrismerClient = class {
2209
3097
  }
2210
3098
  const response = await this.fetchFn(url, init);
2211
3099
  const data = await response.json();
2212
- if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path2.includes("/token/refresh")) {
3100
+ if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path3.includes("/token/refresh")) {
2213
3101
  try {
2214
3102
  const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, true);
2215
3103
  if (refreshRes?.ok && refreshRes?.data?.token) {
2216
3104
  this.apiKey = refreshRes.data.token;
2217
- return this._request(method, path2, body, query, true);
3105
+ return this._request(method, path3, body, query, true);
2218
3106
  }
2219
3107
  } catch {
2220
3108
  }
@@ -3285,49 +4173,95 @@ function register3(parent, getIMClient2, _getAPIClient) {
3285
4173
  handleError(err);
3286
4174
  }
3287
4175
  });
3288
- evolve.command("import <gene-id>").description("Import a published gene into your collection").option("--json", "output raw JSON response").action(async (geneId, opts) => {
4176
+ evolve.command("publish <gene-id>").description("Publish a private gene to the evolution network").option("--skip-canary", "skip canary phase and publish directly").option("--json", "output raw JSON response").action(async (geneId, opts) => {
3289
4177
  const client = getIMClient2();
3290
4178
  try {
3291
- const res = await client.im.evolution.importGene(geneId);
4179
+ const res = await client.im.evolution.publishGene(geneId, { skipCanary: opts.skipCanary });
3292
4180
  if (opts.json) {
3293
4181
  process.stdout.write(JSON.stringify(res, null, 2) + "\n");
3294
4182
  return;
3295
4183
  }
3296
- printResult(res, `Gene imported: ${geneId}`);
4184
+ printResult(res, `Gene ${geneId} published${opts.skipCanary ? " (skipped canary)" : " (canary phase)"}`);
3297
4185
  } catch (err) {
3298
4186
  handleError(err);
3299
4187
  }
3300
4188
  });
3301
- evolve.command("distill").description("Trigger gene distillation (consolidate learnings)").option("--dry-run", "preview distillation without applying changes").option("--json", "output raw JSON response").action(async (opts) => {
4189
+ evolve.command("fork <gene-id>").description("Fork a public gene with optional modifications").option("--strategy <steps...>", "override strategy steps").option("--json", "output raw JSON response").action(async (geneId, opts) => {
3302
4190
  const client = getIMClient2();
3303
4191
  try {
3304
- const res = await client.im.evolution.distill(opts.dryRun);
4192
+ const res = await client.im.evolution.forkGene({
4193
+ gene_id: geneId,
4194
+ modifications: opts.strategy ? { strategy: opts.strategy } : void 0
4195
+ });
3305
4196
  if (opts.json) {
3306
4197
  process.stdout.write(JSON.stringify(res, null, 2) + "\n");
3307
4198
  return;
3308
4199
  }
3309
4200
  printResult(res);
3310
4201
  const data = res.data;
3311
- if (opts.dryRun) {
3312
- process.stdout.write("Dry-run distillation preview:\n");
3313
- } else {
3314
- process.stdout.write("Distillation triggered.\n");
3315
- }
3316
- if (data) {
3317
- for (const [key, val] of Object.entries(data)) {
3318
- process.stdout.write(` ${key}: ${JSON.stringify(val)}
4202
+ const newId = data?.id ?? data?.gene_id ?? "unknown";
4203
+ process.stdout.write(`Forked gene ${geneId} \u2192 ${newId}
3319
4204
  `);
3320
- }
3321
- }
3322
4205
  } catch (err) {
3323
4206
  handleError(err);
3324
4207
  }
3325
4208
  });
3326
- }
3327
-
3328
- // src/commands/task.ts
3329
- function register4(parent, getIMClient2, _getAPIClient) {
3330
- const task = parent.command("task").description("Manage tasks in the task marketplace");
4209
+ evolve.command("delete <gene-id>").description("Delete a gene you own").option("--json", "output raw JSON response").action(async (geneId, opts) => {
4210
+ const client = getIMClient2();
4211
+ try {
4212
+ const res = await client.im.evolution.deleteGene(geneId);
4213
+ if (opts.json) {
4214
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
4215
+ return;
4216
+ }
4217
+ printResult(res, `Gene ${geneId} deleted`);
4218
+ } catch (err) {
4219
+ handleError(err);
4220
+ }
4221
+ });
4222
+ evolve.command("import <gene-id>").description("Import a published gene into your collection").option("--json", "output raw JSON response").action(async (geneId, opts) => {
4223
+ const client = getIMClient2();
4224
+ try {
4225
+ const res = await client.im.evolution.importGene(geneId);
4226
+ if (opts.json) {
4227
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
4228
+ return;
4229
+ }
4230
+ printResult(res, `Gene imported: ${geneId}`);
4231
+ } catch (err) {
4232
+ handleError(err);
4233
+ }
4234
+ });
4235
+ evolve.command("distill").description("Trigger gene distillation (consolidate learnings)").option("--dry-run", "preview distillation without applying changes").option("--json", "output raw JSON response").action(async (opts) => {
4236
+ const client = getIMClient2();
4237
+ try {
4238
+ const res = await client.im.evolution.distill(opts.dryRun);
4239
+ if (opts.json) {
4240
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
4241
+ return;
4242
+ }
4243
+ printResult(res);
4244
+ const data = res.data;
4245
+ if (opts.dryRun) {
4246
+ process.stdout.write("Dry-run distillation preview:\n");
4247
+ } else {
4248
+ process.stdout.write("Distillation triggered.\n");
4249
+ }
4250
+ if (data) {
4251
+ for (const [key, val] of Object.entries(data)) {
4252
+ process.stdout.write(` ${key}: ${JSON.stringify(val)}
4253
+ `);
4254
+ }
4255
+ }
4256
+ } catch (err) {
4257
+ handleError(err);
4258
+ }
4259
+ });
4260
+ }
4261
+
4262
+ // src/commands/task.ts
4263
+ function register4(parent, getIMClient2, _getAPIClient) {
4264
+ const task = parent.command("task").description("Manage tasks in the task marketplace");
3331
4265
  task.command("create").description("Create a new task").requiredOption("--title <title>", "task title").option("--description <description>", "task description").option("--priority <priority>", "priority: low, normal, high, urgent").option("--capability <capability>", "required agent capability").option("--budget <budget>", "budget in credits", parseFloat).option("--json", "output raw JSON response").action(async (opts) => {
3332
4266
  const client = getIMClient2();
3333
4267
  try {
@@ -3840,7 +4774,7 @@ function printFileTable(files) {
3840
4774
  const idLen = Math.max(2, ...files.map((f) => f.id.length));
3841
4775
  const scopeLen = Math.max(5, ...files.map((f) => f.scope.length));
3842
4776
  const pathLen = Math.max(4, ...files.map((f) => f.path.length));
3843
- const row = (id, scope, path2) => `${id.padEnd(idLen)} ${scope.padEnd(scopeLen)} ${path2.padEnd(pathLen)}`;
4777
+ const row = (id, scope, path3) => `${id.padEnd(idLen)} ${scope.padEnd(scopeLen)} ${path3.padEnd(pathLen)}`;
3844
4778
  process.stdout.write(row("ID", "SCOPE", "PATH") + "\n");
3845
4779
  process.stdout.write(`${"-".repeat(idLen)} ${"-".repeat(scopeLen)} ${"-".repeat(pathLen)}
3846
4780
  `);
@@ -4597,29 +5531,665 @@ function register9(parent, getIMClient2, _getAPIClient) {
4597
5531
  });
4598
5532
  }
4599
5533
 
5534
+ // src/commands/community.ts
5535
+ var import_node_fs = require("fs");
5536
+ function printJson(res, opts) {
5537
+ if (opts.json) {
5538
+ console.log(JSON.stringify(res, null, 2));
5539
+ return;
5540
+ }
5541
+ if (!res.ok) {
5542
+ const errMsg = res.error && typeof res.error === "object" && "message" in res.error ? res.error.message : JSON.stringify(res.error);
5543
+ console.error("Error:", errMsg || "Unknown");
5544
+ process.exit(1);
5545
+ }
5546
+ console.log(typeof res.data === "string" ? res.data : JSON.stringify(res.data, null, 2));
5547
+ }
5548
+ function formatPostsMarkdown(data) {
5549
+ const d = data;
5550
+ const posts = d?.posts ?? [];
5551
+ let t = "## Feed\n\n";
5552
+ if (posts.length === 0) return t + "_Empty._\n";
5553
+ for (const p of posts) {
5554
+ t += `- **${String(p.title || "")}** (\`${String(p.id)}\`) \u2014 ${String(p.boardId || "")}
5555
+ `;
5556
+ }
5557
+ if (d?.nextCursor) t += `
5558
+ _Next cursor:_ \`${d.nextCursor}\`
5559
+ `;
5560
+ return t;
5561
+ }
5562
+ function register10(parent, getIMClient2, _getAPIClient) {
5563
+ const comm = parent.command("community").description("Evolution community forum \u2014 feed, ask, search, notify");
5564
+ comm.command("feed").description("Browse posts (uses hub cache when fresh)").option("-b, --board <id>", "Board: showcase, genelab, helpdesk, ideas, changelog").option("-n, --limit <n>", "Max posts", "15").option("--json", "JSON output").action(async (opts) => {
5565
+ const c = getIMClient2();
5566
+ const res = await c.im.community.feed({
5567
+ boardId: opts.board,
5568
+ limit: parseInt(opts.limit || "15", 10)
5569
+ });
5570
+ if (opts.json) {
5571
+ printJson(res, opts);
5572
+ return;
5573
+ }
5574
+ if (!res.ok) {
5575
+ printJson(res, { json: false });
5576
+ return;
5577
+ }
5578
+ process.stdout.write(formatPostsMarkdown(res.data));
5579
+ });
5580
+ comm.command("ask").description("Post a helpdesk question").argument("<title>", "Title").argument("[body]", "Body (Markdown); omit if using --file").option("-f, --file <path>", "Read body from file").option("--tags <csv>", "Comma-separated tags").option("--json", "JSON output").action(async (title, body, opts) => {
5581
+ const content = opts.file ? (0, import_node_fs.readFileSync)(opts.file, "utf8") : body || "(no body)";
5582
+ const tags = opts.tags?.split(",").map((s) => s.trim()).filter(Boolean);
5583
+ const c = getIMClient2();
5584
+ const res = await c.im.community.ask(title, content, tags);
5585
+ printJson(res, opts);
5586
+ });
5587
+ comm.command("search").description("Full-text community search").argument("<query>", "Search query").option("-b, --board <id>", "Limit to board").option("-n, --limit <n>", "Max hits", "8").option("--json", "JSON output").action(async (query, opts) => {
5588
+ const c = getIMClient2();
5589
+ const res = await c.im.community.search(query, {
5590
+ boardId: opts.board,
5591
+ limit: parseInt(opts.limit || "8", 10)
5592
+ });
5593
+ printJson(res, opts);
5594
+ });
5595
+ comm.command("check").description("List notifications; optionally mark all read").option("--unread-only", "Unread only").option("--mark-read", "Mark all read after listing").option("--json", "JSON output").action(async (opts) => {
5596
+ const c = getIMClient2();
5597
+ const list = await c.im.community.getNotifications({
5598
+ unread: opts.unreadOnly,
5599
+ limit: 50
5600
+ });
5601
+ if (opts.json) {
5602
+ console.log(JSON.stringify(list, null, 2));
5603
+ } else if (list.ok && list.data) {
5604
+ const payload = list.data;
5605
+ console.log(`## Notifications (${payload.items?.length ?? 0})
5606
+ `);
5607
+ console.log(JSON.stringify(list.data, null, 2));
5608
+ } else {
5609
+ printJson(list, { json: false });
5610
+ }
5611
+ if (opts.markRead) {
5612
+ const mr = await c.im.community.markNotificationsRead();
5613
+ if (opts.json) console.log(JSON.stringify(mr, null, 2));
5614
+ else console.log("\nMarked read:", mr.ok ? "ok" : mr.error);
5615
+ }
5616
+ });
5617
+ comm.command("report").description("Publish a showcase battle-report style post").requiredOption("-t, --title <t>", "Title").option("-c, --content <md>", "Body markdown").option("--genes <csv>", "Linked gene IDs").option("--agent <id>", "linkedAgentId").option("--json", "JSON output").action(async (opts) => {
5618
+ const c = getIMClient2();
5619
+ const geneIds = opts.genes?.split(",").map((s) => s.trim()).filter(Boolean);
5620
+ const res = await c.im.community.reportBattle({
5621
+ title: opts.title,
5622
+ content: opts.content || "_Battle report_",
5623
+ linkedGeneIds: geneIds,
5624
+ linkedAgentId: opts.agent
5625
+ });
5626
+ printJson(res, opts);
5627
+ });
5628
+ comm.command("post").description("Create a post on any board").argument("<board>", "Board id").argument("<title>", "Title").option("-c, --content <md>", "Body", "").option("--tags <csv>", "Tags").option("--json", "JSON output").action(async (board, title, opts) => {
5629
+ const tags = opts.tags?.split(",").map((s) => s.trim()).filter(Boolean);
5630
+ const c = getIMClient2();
5631
+ const res = await c.im.community.createPost({
5632
+ boardId: board,
5633
+ title,
5634
+ content: opts.content || "",
5635
+ tags
5636
+ });
5637
+ if (res.ok) c.im.community.invalidateCache(board);
5638
+ printJson(res, opts);
5639
+ });
5640
+ comm.command("reply").description("Comment on a post").argument("<postId>", "Post ID").argument("<content>", "Comment (markdown)").option("--json", "JSON output").action(async (postId, content, opts) => {
5641
+ const c = getIMClient2();
5642
+ const res = await c.im.community.createComment(postId, { content });
5643
+ printJson(res, opts);
5644
+ });
5645
+ comm.command("vote").description("Vote on post or comment").argument("<type>", "post | comment").argument("<id>", "Target id").argument("<value>", "up | down | cancel").option("--json", "JSON output").action(async (type, id, value, opts) => {
5646
+ const tt = type === "comment" ? "comment" : "post";
5647
+ let v = 0;
5648
+ if (value === "up") v = 1;
5649
+ else if (value === "down") v = -1;
5650
+ const c = getIMClient2();
5651
+ const res = await c.im.community.vote(tt, id, v);
5652
+ printJson(res, opts);
5653
+ });
5654
+ const my = comm.command("my").description("Your bookmarks (auth)");
5655
+ my.command("bookmarks").description("List bookmarked posts").option("--json", "JSON output").action(async (opts) => {
5656
+ const c = getIMClient2();
5657
+ const res = await c.im.community.listBookmarks({ limit: 30 });
5658
+ printJson(res, opts);
5659
+ });
5660
+ }
5661
+
5662
+ // src/daemon.ts
5663
+ var fs = __toESM(require("fs"));
5664
+ var path = __toESM(require("path"));
5665
+ var import_path = require("path");
5666
+ var os = __toESM(require("os"));
5667
+ var import_os = require("os");
5668
+ var http = __toESM(require("http"));
5669
+ var import_http = require("http");
5670
+ var import_child_process = require("child_process");
5671
+ var TOML = __toESM(require("@iarna/toml"));
5672
+ var CONFIG_DIR = path.join(os.homedir(), ".prismer");
5673
+ var CONFIG_PATH = path.join(CONFIG_DIR, "config.toml");
5674
+ var PID_PATH = path.join(CONFIG_DIR, "daemon.pid");
5675
+ var PORT_PATH = path.join(CONFIG_DIR, "daemon.port");
5676
+ var CACHE_DIR = path.join(CONFIG_DIR, "cache");
5677
+ var EVOLUTION_CACHE_PATH = path.join(CACHE_DIR, "evolution.json");
5678
+ var OUTBOX_PATH = path.join(CACHE_DIR, "outbox.json");
5679
+ var SYNC_INTERVAL_MS = 6e4;
5680
+ var FLUSH_INTERVAL_MS = 3e4;
5681
+ var API_TIMEOUT_MS = 1e4;
5682
+ var EVENTS_FILE = (0, import_path.join)(CACHE_DIR, "events.json");
5683
+ var MAX_EVENTS = 1e3;
5684
+ function loadConfig() {
5685
+ if (!fs.existsSync(CONFIG_PATH)) return null;
5686
+ try {
5687
+ const raw = fs.readFileSync(CONFIG_PATH, "utf-8");
5688
+ const parsed = TOML.parse(raw);
5689
+ const apiKey = parsed?.default?.api_key || "";
5690
+ const baseUrl = parsed?.default?.base_url || "https://prismer.cloud";
5691
+ if (!apiKey) return null;
5692
+ return { apiKey, baseUrl };
5693
+ } catch {
5694
+ return null;
5695
+ }
5696
+ }
5697
+ function ensureCacheDir() {
5698
+ if (!fs.existsSync(CACHE_DIR)) {
5699
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
5700
+ }
5701
+ }
5702
+ function loadEvents() {
5703
+ try {
5704
+ return JSON.parse(fs.readFileSync(EVENTS_FILE, "utf-8"));
5705
+ } catch {
5706
+ return [];
5707
+ }
5708
+ }
5709
+ function appendEvent(event) {
5710
+ const events = loadEvents();
5711
+ events.push(event);
5712
+ if (events.length > MAX_EVENTS) events.splice(0, events.length - MAX_EVENTS);
5713
+ fs.writeFileSync(EVENTS_FILE, JSON.stringify(events), { encoding: "utf-8", mode: 384 });
5714
+ }
5715
+ function emitSyncEvent(genesCount) {
5716
+ if (genesCount > 0) {
5717
+ appendEvent({
5718
+ type: "evolution.sync",
5719
+ source: "evolution",
5720
+ priority: "low",
5721
+ title: "Evolution sync complete",
5722
+ body: `${genesCount} genes updated`,
5723
+ timestamp: Date.now()
5724
+ });
5725
+ }
5726
+ }
5727
+ function readPid() {
5728
+ if (!fs.existsSync(PID_PATH)) return null;
5729
+ try {
5730
+ const raw = fs.readFileSync(PID_PATH, "utf-8").trim();
5731
+ const pid = parseInt(raw, 10);
5732
+ return isNaN(pid) ? null : pid;
5733
+ } catch {
5734
+ return null;
5735
+ }
5736
+ }
5737
+ function readPort() {
5738
+ if (!fs.existsSync(PORT_PATH)) return null;
5739
+ try {
5740
+ const raw = fs.readFileSync(PORT_PATH, "utf-8").trim();
5741
+ const port = parseInt(raw, 10);
5742
+ return isNaN(port) ? null : port;
5743
+ } catch {
5744
+ return null;
5745
+ }
5746
+ }
5747
+ function isProcessRunning(pid) {
5748
+ try {
5749
+ process.kill(pid, 0);
5750
+ return true;
5751
+ } catch {
5752
+ return false;
5753
+ }
5754
+ }
5755
+ function writePid(pid) {
5756
+ ensureCacheDir();
5757
+ if (!fs.existsSync(CONFIG_DIR)) {
5758
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
5759
+ }
5760
+ fs.writeFileSync(PID_PATH, String(pid), { encoding: "utf-8", mode: 384 });
5761
+ }
5762
+ function writePort(port) {
5763
+ if (!fs.existsSync(CONFIG_DIR)) {
5764
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
5765
+ }
5766
+ fs.writeFileSync(PORT_PATH, String(port), { encoding: "utf-8", mode: 384 });
5767
+ }
5768
+ function cleanupPidFiles() {
5769
+ try {
5770
+ if (fs.existsSync(PID_PATH)) fs.unlinkSync(PID_PATH);
5771
+ } catch {
5772
+ }
5773
+ try {
5774
+ if (fs.existsSync(PORT_PATH)) fs.unlinkSync(PORT_PATH);
5775
+ } catch {
5776
+ }
5777
+ }
5778
+ async function fetchWithTimeout(url, options, timeoutMs = API_TIMEOUT_MS) {
5779
+ const controller = new AbortController();
5780
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
5781
+ try {
5782
+ return await fetch(url, { ...options, signal: controller.signal });
5783
+ } finally {
5784
+ clearTimeout(timer);
5785
+ }
5786
+ }
5787
+ async function runDaemonProcess() {
5788
+ const cfg = loadConfig();
5789
+ if (!cfg) {
5790
+ process.stderr.write('[prismer-daemon] No config found. Run "prismer setup" first.\n');
5791
+ process.exit(1);
5792
+ }
5793
+ ensureCacheDir();
5794
+ let lastSync = 0;
5795
+ let syncCount = 0;
5796
+ let evolutionCursor = 0;
5797
+ if (fs.existsSync(EVOLUTION_CACHE_PATH)) {
5798
+ try {
5799
+ const cached = JSON.parse(fs.readFileSync(EVOLUTION_CACHE_PATH, "utf-8"));
5800
+ if (typeof cached?.cursor === "number") evolutionCursor = cached.cursor;
5801
+ } catch {
5802
+ }
5803
+ }
5804
+ const server = (0, import_http.createServer)((req, res) => {
5805
+ if (req.method === "GET" && req.url === "/health") {
5806
+ let outboxSize = 0;
5807
+ if (fs.existsSync(OUTBOX_PATH)) {
5808
+ try {
5809
+ const entries = JSON.parse(fs.readFileSync(OUTBOX_PATH, "utf-8"));
5810
+ if (Array.isArray(entries)) outboxSize = entries.length;
5811
+ } catch {
5812
+ }
5813
+ }
5814
+ const body = JSON.stringify({
5815
+ pid: process.pid,
5816
+ uptime: Math.floor(process.uptime()),
5817
+ lastSync,
5818
+ syncCount,
5819
+ outboxSize
5820
+ });
5821
+ res.writeHead(200, { "Content-Type": "application/json" });
5822
+ res.end(body);
5823
+ } else if (req.method === "GET" && req.url === "/events") {
5824
+ res.writeHead(200, { "Content-Type": "application/json" });
5825
+ const events = loadEvents();
5826
+ res.end(JSON.stringify(events.slice(-50)));
5827
+ } else {
5828
+ res.writeHead(404);
5829
+ res.end("Not found");
5830
+ }
5831
+ });
5832
+ server.listen(0, "127.0.0.1", () => {
5833
+ const addr = server.address();
5834
+ const port = addr.port;
5835
+ writePid(process.pid);
5836
+ writePort(port);
5837
+ process.stdout.write(`[prismer-daemon] Started. PID=${process.pid} port=${port}
5838
+ `);
5839
+ });
5840
+ const shutdown = () => {
5841
+ process.stdout.write("[prismer-daemon] Shutting down.\n");
5842
+ cleanupPidFiles();
5843
+ server.close();
5844
+ process.exit(0);
5845
+ };
5846
+ process.on("SIGINT", shutdown);
5847
+ process.on("SIGTERM", shutdown);
5848
+ const doEvolutionSync = async () => {
5849
+ try {
5850
+ const res = await fetchWithTimeout(
5851
+ `${cfg.baseUrl}/api/im/evolution/sync`,
5852
+ {
5853
+ method: "POST",
5854
+ headers: {
5855
+ "Content-Type": "application/json",
5856
+ Authorization: `Bearer ${cfg.apiKey}`
5857
+ },
5858
+ body: JSON.stringify({ pull: { since: evolutionCursor, scope: "global" } })
5859
+ }
5860
+ );
5861
+ if (res.ok) {
5862
+ const data = await res.json();
5863
+ lastSync = Date.now();
5864
+ syncCount++;
5865
+ if (typeof data?.data?.cursor === "number") {
5866
+ evolutionCursor = data.data.cursor;
5867
+ } else if (typeof data?.cursor === "number") {
5868
+ evolutionCursor = data.cursor;
5869
+ }
5870
+ ensureCacheDir();
5871
+ const pulled = data?.data || data;
5872
+ fs.writeFileSync(
5873
+ EVOLUTION_CACHE_PATH,
5874
+ JSON.stringify({ cursor: evolutionCursor, lastSync, data: pulled }, null, 2),
5875
+ { encoding: "utf-8", mode: 384 }
5876
+ );
5877
+ emitSyncEvent(pulled?.genes?.length || 0);
5878
+ }
5879
+ } catch {
5880
+ }
5881
+ };
5882
+ const doOutboxFlush = async () => {
5883
+ if (!fs.existsSync(OUTBOX_PATH)) return;
5884
+ let entries = [];
5885
+ try {
5886
+ entries = JSON.parse(fs.readFileSync(OUTBOX_PATH, "utf-8"));
5887
+ if (!Array.isArray(entries) || entries.length === 0) return;
5888
+ } catch {
5889
+ return;
5890
+ }
5891
+ try {
5892
+ const res = await fetchWithTimeout(
5893
+ `${cfg.baseUrl}/api/im/evolution/sync`,
5894
+ {
5895
+ method: "POST",
5896
+ headers: {
5897
+ "Content-Type": "application/json",
5898
+ Authorization: `Bearer ${cfg.apiKey}`
5899
+ },
5900
+ body: JSON.stringify({
5901
+ push: { outcomes: entries },
5902
+ pull: { since: 0 }
5903
+ })
5904
+ }
5905
+ );
5906
+ if (res.ok) {
5907
+ fs.writeFileSync(OUTBOX_PATH, "[]", { encoding: "utf-8", mode: 384 });
5908
+ }
5909
+ } catch {
5910
+ }
5911
+ };
5912
+ await doEvolutionSync();
5913
+ await doOutboxFlush();
5914
+ const syncTimer = setInterval(doEvolutionSync, SYNC_INTERVAL_MS);
5915
+ const flushTimer = setInterval(doOutboxFlush, FLUSH_INTERVAL_MS);
5916
+ const originalShutdown = shutdown;
5917
+ const fullShutdown = () => {
5918
+ clearInterval(syncTimer);
5919
+ clearInterval(flushTimer);
5920
+ originalShutdown();
5921
+ };
5922
+ process.removeListener("SIGINT", shutdown);
5923
+ process.removeListener("SIGTERM", shutdown);
5924
+ process.on("SIGINT", fullShutdown);
5925
+ process.on("SIGTERM", fullShutdown);
5926
+ }
5927
+ async function startDaemon() {
5928
+ const existingPid = readPid();
5929
+ if (existingPid !== null && isProcessRunning(existingPid)) {
5930
+ const port = readPort();
5931
+ console.log(`Daemon already running. PID=${existingPid}${port ? ` port=${port}` : ""}`);
5932
+ return;
5933
+ }
5934
+ cleanupPidFiles();
5935
+ const cfg = loadConfig();
5936
+ if (!cfg) {
5937
+ console.error('No API key found. Run "prismer setup" first.');
5938
+ process.exit(1);
5939
+ }
5940
+ if (process.env["PRISMER_DAEMON"] === "1") {
5941
+ await runDaemonProcess();
5942
+ return;
5943
+ }
5944
+ const { spawn } = require("child_process");
5945
+ const child = spawn(process.execPath, [process.argv[1], "daemon", "start"], {
5946
+ env: { ...process.env, PRISMER_DAEMON: "1" },
5947
+ detached: true,
5948
+ stdio: "ignore"
5949
+ });
5950
+ child.unref();
5951
+ let waited = 0;
5952
+ while (waited < 3e3) {
5953
+ await new Promise((r) => setTimeout(r, 100));
5954
+ waited += 100;
5955
+ const pid = readPid();
5956
+ const port = readPort();
5957
+ if (pid !== null && port !== null) {
5958
+ console.log(`Daemon started. PID=${pid} port=${port}`);
5959
+ return;
5960
+ }
5961
+ }
5962
+ console.log("Daemon spawned (PID file not yet written \u2014 may take a moment).");
5963
+ }
5964
+ function stopDaemon() {
5965
+ const pid = readPid();
5966
+ if (pid === null || !isProcessRunning(pid)) {
5967
+ console.log("Daemon: not running");
5968
+ cleanupPidFiles();
5969
+ return;
5970
+ }
5971
+ try {
5972
+ process.kill(pid, "SIGTERM");
5973
+ console.log(`Daemon stopped (PID=${pid})`);
5974
+ cleanupPidFiles();
5975
+ } catch (err) {
5976
+ console.error(`Failed to stop daemon: ${err.message}`);
5977
+ }
5978
+ }
5979
+ function daemonStatus() {
5980
+ const pid = readPid();
5981
+ if (pid === null || !isProcessRunning(pid)) {
5982
+ console.log("Daemon: not running");
5983
+ cleanupPidFiles();
5984
+ return;
5985
+ }
5986
+ const port = readPort();
5987
+ if (!port) {
5988
+ console.log(`Daemon: running (PID=${pid}, port unknown)`);
5989
+ return;
5990
+ }
5991
+ const req = http.request(
5992
+ { hostname: "127.0.0.1", port, path: "/health", method: "GET", timeout: 3e3 },
5993
+ (res) => {
5994
+ let body = "";
5995
+ res.on("data", (chunk) => {
5996
+ body += chunk.toString();
5997
+ });
5998
+ res.on("end", () => {
5999
+ try {
6000
+ const health = JSON.parse(body);
6001
+ console.log(`Daemon: running`);
6002
+ console.log(` PID: ${health.pid}`);
6003
+ console.log(` Uptime: ${health.uptime}s`);
6004
+ console.log(` Last sync: ${health.lastSync ? new Date(health.lastSync).toISOString() : "never"}`);
6005
+ console.log(` Sync count: ${health.syncCount}`);
6006
+ console.log(` Outbox: ${health.outboxSize} entries`);
6007
+ console.log(` Port: ${port}`);
6008
+ } catch {
6009
+ console.log(`Daemon: running (PID=${pid} port=${port})`);
6010
+ }
6011
+ });
6012
+ }
6013
+ );
6014
+ req.on("error", () => {
6015
+ console.log(`Daemon: running (PID=${pid} port=${port}, health check failed)`);
6016
+ });
6017
+ req.end();
6018
+ }
6019
+ function resolveNpxPath() {
6020
+ try {
6021
+ return (0, import_child_process.execSync)("which npx", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6022
+ } catch {
6023
+ for (const p of ["/usr/local/bin/npx", "/opt/homebrew/bin/npx", `${(0, import_os.homedir)()}/.nvm/current/bin/npx`]) {
6024
+ try {
6025
+ fs.accessSync(p);
6026
+ return p;
6027
+ } catch {
6028
+ }
6029
+ }
6030
+ return "npx";
6031
+ }
6032
+ }
6033
+ function installLaunchd() {
6034
+ const plistPath = (0, import_path.join)((0, import_os.homedir)(), "Library", "LaunchAgents", "cloud.prismer.daemon.plist");
6035
+ const npxPath = resolveNpxPath();
6036
+ const nodePath = process.execPath;
6037
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
6038
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
6039
+ <plist version="1.0">
6040
+ <dict>
6041
+ <key>Label</key>
6042
+ <string>cloud.prismer.daemon</string>
6043
+ <key>ProgramArguments</key>
6044
+ <array>
6045
+ <string>${npxPath}</string>
6046
+ <string>@prismer/sdk</string>
6047
+ <string>daemon</string>
6048
+ <string>start</string>
6049
+ </array>
6050
+ <key>EnvironmentVariables</key>
6051
+ <dict>
6052
+ <key>PRISMER_DAEMON</key>
6053
+ <string>1</string>
6054
+ <key>PATH</key>
6055
+ <string>${(0, import_path.dirname)(nodePath)}:/usr/local/bin:/usr/bin:/bin</string>
6056
+ </dict>
6057
+ <key>RunAtLoad</key>
6058
+ <true/>
6059
+ <key>KeepAlive</key>
6060
+ <true/>
6061
+ <key>StandardOutPath</key>
6062
+ <string>${(0, import_path.join)((0, import_os.homedir)(), ".prismer", "daemon.stdout.log")}</string>
6063
+ <key>StandardErrorPath</key>
6064
+ <string>${(0, import_path.join)((0, import_os.homedir)(), ".prismer", "daemon.stderr.log")}</string>
6065
+ </dict>
6066
+ </plist>`;
6067
+ fs.mkdirSync((0, import_path.dirname)(plistPath), { recursive: true });
6068
+ fs.writeFileSync(plistPath, plist, { mode: 384 });
6069
+ try {
6070
+ (0, import_child_process.execSync)(`launchctl load ${plistPath}`, { stdio: "pipe" });
6071
+ console.log("[prismer] Daemon service installed and started (launchd)");
6072
+ console.log(` Plist: ${plistPath}`);
6073
+ } catch {
6074
+ console.log("[prismer] Plist written. Load manually: launchctl load " + plistPath);
6075
+ }
6076
+ }
6077
+ function uninstallLaunchd() {
6078
+ const plistPath = (0, import_path.join)((0, import_os.homedir)(), "Library", "LaunchAgents", "cloud.prismer.daemon.plist");
6079
+ try {
6080
+ (0, import_child_process.execSync)(`launchctl unload ${plistPath}`, { stdio: "pipe" });
6081
+ } catch {
6082
+ }
6083
+ try {
6084
+ fs.unlinkSync(plistPath);
6085
+ } catch {
6086
+ }
6087
+ console.log("[prismer] Daemon service uninstalled (launchd)");
6088
+ }
6089
+ function installSystemd() {
6090
+ const serviceDir = (0, import_path.join)((0, import_os.homedir)(), ".config", "systemd", "user");
6091
+ const servicePath = (0, import_path.join)(serviceDir, "prismer-daemon.service");
6092
+ const npxPath = resolveNpxPath();
6093
+ const nodePath = process.execPath;
6094
+ const unit = `[Unit]
6095
+ Description=Prismer Daemon \u2014 background evolution sync
6096
+ After=network-online.target
6097
+
6098
+ [Service]
6099
+ Type=simple
6100
+ Environment=PRISMER_DAEMON=1
6101
+ Environment=PATH=${(0, import_path.dirname)(nodePath)}:/usr/local/bin:/usr/bin:/bin
6102
+ ExecStart=${npxPath} @prismer/sdk daemon start
6103
+ Restart=on-failure
6104
+ RestartSec=10
6105
+
6106
+ [Install]
6107
+ WantedBy=default.target
6108
+ `;
6109
+ fs.mkdirSync(serviceDir, { recursive: true });
6110
+ fs.writeFileSync(servicePath, unit, { mode: 420 });
6111
+ try {
6112
+ (0, import_child_process.execSync)("systemctl --user daemon-reload", { stdio: "pipe" });
6113
+ (0, import_child_process.execSync)("systemctl --user enable prismer-daemon", { stdio: "pipe" });
6114
+ (0, import_child_process.execSync)("systemctl --user start prismer-daemon", { stdio: "pipe" });
6115
+ console.log("[prismer] Daemon service installed and started (systemd)");
6116
+ console.log(` Service: ${servicePath}`);
6117
+ } catch {
6118
+ console.log("[prismer] Service file written. Enable manually:");
6119
+ console.log(" systemctl --user enable --now prismer-daemon");
6120
+ }
6121
+ }
6122
+ function uninstallSystemd() {
6123
+ try {
6124
+ (0, import_child_process.execSync)("systemctl --user stop prismer-daemon", { stdio: "pipe" });
6125
+ } catch {
6126
+ }
6127
+ try {
6128
+ (0, import_child_process.execSync)("systemctl --user disable prismer-daemon", { stdio: "pipe" });
6129
+ } catch {
6130
+ }
6131
+ const servicePath = (0, import_path.join)((0, import_os.homedir)(), ".config", "systemd", "user", "prismer-daemon.service");
6132
+ try {
6133
+ fs.unlinkSync(servicePath);
6134
+ } catch {
6135
+ }
6136
+ try {
6137
+ (0, import_child_process.execSync)("systemctl --user daemon-reload", { stdio: "pipe" });
6138
+ } catch {
6139
+ }
6140
+ console.log("[prismer] Daemon service uninstalled (systemd)");
6141
+ }
6142
+ function installDaemonService() {
6143
+ const platform = process.platform;
6144
+ if (platform === "darwin") {
6145
+ installLaunchd();
6146
+ } else if (platform === "linux") {
6147
+ installSystemd();
6148
+ } else {
6149
+ console.log(`Daemon auto-start not supported on ${platform}. Use: prismer daemon start`);
6150
+ }
6151
+ }
6152
+ function uninstallDaemonService() {
6153
+ const platform = process.platform;
6154
+ if (platform === "darwin") {
6155
+ uninstallLaunchd();
6156
+ } else if (platform === "linux") {
6157
+ uninstallSystemd();
6158
+ } else {
6159
+ console.log(`No daemon service to uninstall on ${platform}.`);
6160
+ }
6161
+ }
6162
+ if (process.env["PRISMER_DAEMON"] === "1") {
6163
+ runDaemonProcess().catch((err) => {
6164
+ process.stderr.write(`[prismer-daemon] Fatal: ${err.message}
6165
+ `);
6166
+ process.exit(1);
6167
+ });
6168
+ }
6169
+
4600
6170
  // src/cli.ts
4601
6171
  var cliVersion = "1.7.2";
4602
6172
  try {
4603
- const pkgPath = path.join(__dirname, "..", "package.json");
4604
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
6173
+ const pkgPath = path2.join(__dirname, "..", "package.json");
6174
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
4605
6175
  cliVersion = pkg.version || cliVersion;
4606
6176
  } catch {
4607
6177
  }
4608
- var CONFIG_DIR = path.join(os.homedir(), ".prismer");
4609
- var CONFIG_PATH = path.join(CONFIG_DIR, "config.toml");
6178
+ var CONFIG_DIR2 = path2.join(os2.homedir(), ".prismer");
6179
+ var CONFIG_PATH2 = path2.join(CONFIG_DIR2, "config.toml");
4610
6180
  function ensureConfigDir() {
4611
- if (!fs.existsSync(CONFIG_DIR)) {
4612
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
6181
+ if (!fs2.existsSync(CONFIG_DIR2)) {
6182
+ fs2.mkdirSync(CONFIG_DIR2, { recursive: true });
4613
6183
  }
4614
6184
  }
4615
6185
  function readConfig() {
4616
- if (!fs.existsSync(CONFIG_PATH)) return {};
4617
- const raw = fs.readFileSync(CONFIG_PATH, "utf-8");
4618
- return TOML.parse(raw);
6186
+ if (!fs2.existsSync(CONFIG_PATH2)) return {};
6187
+ const raw = fs2.readFileSync(CONFIG_PATH2, "utf-8");
6188
+ return TOML2.parse(raw);
4619
6189
  }
4620
6190
  function writeConfig(config) {
4621
6191
  ensureConfigDir();
4622
- fs.writeFileSync(CONFIG_PATH, TOML.stringify(config), "utf-8");
6192
+ fs2.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
4623
6193
  }
4624
6194
  function setNestedValue(obj, dotPath, value) {
4625
6195
  const parts = dotPath.split(".");
@@ -4635,7 +6205,7 @@ function getIMClient() {
4635
6205
  const cfg = readConfig();
4636
6206
  const token = cfg?.auth?.im_token;
4637
6207
  if (!token) {
4638
- console.error('No IM token. Run "prismer register" first.');
6208
+ console.error('No IM token. Run "prismer setup --agent" or "prismer register <username>" first.');
4639
6209
  process.exit(1);
4640
6210
  }
4641
6211
  const env = cfg?.default?.environment || "production";
@@ -4646,7 +6216,7 @@ function getAPIClient() {
4646
6216
  const cfg = readConfig();
4647
6217
  const apiKey = cfg?.default?.api_key;
4648
6218
  if (!apiKey) {
4649
- console.error('No API key. Run "prismer init <api-key>" first.');
6219
+ console.error('No API key. Run "prismer setup" to sign in and get your key.');
4650
6220
  process.exit(1);
4651
6221
  }
4652
6222
  const env = cfg?.default?.environment || "production";
@@ -4655,20 +6225,196 @@ function getAPIClient() {
4655
6225
  }
4656
6226
  var program = new import_commander.Command();
4657
6227
  program.name("prismer").description("Prismer Cloud SDK CLI").version(cliVersion);
4658
- program.command("init <api-key>").description("Store API key in ~/.prismer/config.toml").action((apiKey) => {
4659
- const config = readConfig();
6228
+ async function verifyAndSaveKey(config, apiKey) {
6229
+ if (!apiKey) {
6230
+ console.error("No key provided.");
6231
+ process.exit(1);
6232
+ }
6233
+ if (!apiKey.startsWith("sk-prismer-")) {
6234
+ console.error("Invalid key format. API keys start with sk-prismer-");
6235
+ console.error("Get your key at: https://prismer.cloud/setup");
6236
+ process.exit(1);
6237
+ }
6238
+ const baseUrl = config.default?.base_url || "https://prismer.cloud";
6239
+ try {
6240
+ const res = await fetch(`${baseUrl}/api/version`, {
6241
+ headers: { Authorization: `Bearer ${apiKey}` }
6242
+ });
6243
+ if (res.status === 401) {
6244
+ console.error("API key is invalid or expired.");
6245
+ console.error("Get a new key at: https://prismer.cloud/setup");
6246
+ process.exit(1);
6247
+ }
6248
+ console.log("API key verified \u2713");
6249
+ } catch (err) {
6250
+ console.warn(`Could not verify key (${err.message}). Saving anyway.`);
6251
+ }
4660
6252
  if (!config.default) config.default = {};
4661
6253
  config.default.api_key = apiKey;
4662
6254
  if (!config.default.environment) config.default.environment = "production";
4663
- if (config.default.base_url === void 0) config.default.base_url = "";
4664
6255
  writeConfig(config);
4665
- console.log("API key saved to ~/.prismer/config.toml");
6256
+ console.log("");
6257
+ console.log("Saved to ~/.prismer/config.toml");
6258
+ console.log("You can now use: CLI commands, MCP tools, Claude Code plugin, and all SDKs.");
6259
+ try {
6260
+ installDaemonService();
6261
+ } catch {
6262
+ console.log("Daemon auto-start setup skipped. Run manually: prismer daemon install");
6263
+ }
6264
+ }
6265
+ function openBrowser(url) {
6266
+ const { execFile } = require("child_process");
6267
+ if (process.platform === "darwin") {
6268
+ execFile("open", [url], (err) => {
6269
+ if (err) console.warn("Could not open browser. Please open the URL above manually.");
6270
+ });
6271
+ } else if (process.platform === "win32") {
6272
+ execFile("cmd.exe", ["/c", "start", "", url], (err) => {
6273
+ if (err) console.warn("Could not open browser. Please open the URL above manually.");
6274
+ });
6275
+ } else {
6276
+ execFile("xdg-open", [url], (err) => {
6277
+ if (err) console.warn("Could not open browser. Please open the URL above manually.");
6278
+ });
6279
+ }
6280
+ }
6281
+ async function runSetup(opts, apiKey) {
6282
+ const config = readConfig();
6283
+ if (!config.default) config.default = {};
6284
+ const baseUrl = config.default.base_url || "https://prismer.cloud";
6285
+ if (!opts.force && config.default.api_key?.startsWith("sk-prismer-")) {
6286
+ const masked = config.default.api_key.slice(0, 12) + "..." + config.default.api_key.slice(-4);
6287
+ console.log(`Already configured: ${masked}`);
6288
+ console.log("");
6289
+ console.log("To reconfigure, run: prismer setup --force");
6290
+ console.log("To check status: prismer status");
6291
+ return;
6292
+ }
6293
+ if (apiKey) {
6294
+ await verifyAndSaveKey(config, apiKey);
6295
+ return;
6296
+ }
6297
+ if (opts.agent) {
6298
+ if (!opts.force && config.auth?.im_token) {
6299
+ console.log("Already registered as agent (IM token exists).");
6300
+ console.log("For API key access, run: prismer setup");
6301
+ return;
6302
+ }
6303
+ const username = `agent-${Date.now().toString(36)}`;
6304
+ try {
6305
+ const res = await fetch(`${baseUrl}/api/im/register`, {
6306
+ method: "POST",
6307
+ headers: { "Content-Type": "application/json" },
6308
+ body: JSON.stringify({ username, displayName: username, type: "agent" })
6309
+ });
6310
+ const data = await res.json();
6311
+ if (!data.ok) throw new Error(data.error?.message || "Registration failed");
6312
+ if (!config.auth) config.auth = {};
6313
+ config.auth.im_token = data.data?.token;
6314
+ config.auth.im_user_id = data.data?.imUserId || data.data?.userId;
6315
+ config.auth.im_username = data.data?.username || username;
6316
+ writeConfig(config);
6317
+ console.log("Agent registered with free credits \u2713");
6318
+ console.log(` Username: ${config.auth.im_username}`);
6319
+ console.log(` User ID: ${config.auth.im_user_id}`);
6320
+ console.log("");
6321
+ console.log("For full API access, sign in: prismer setup");
6322
+ } catch (err) {
6323
+ console.error(`Agent registration failed: ${err.message}`);
6324
+ console.error("Try signing in instead: prismer setup");
6325
+ process.exit(1);
6326
+ }
6327
+ return;
6328
+ }
6329
+ if (opts.manual) {
6330
+ const setupUrl = `${baseUrl}/setup?utm_source=cli&utm_medium=manual`;
6331
+ console.log("Opening browser to sign in...");
6332
+ console.log(` ${setupUrl}`);
6333
+ console.log("");
6334
+ openBrowser(setupUrl);
6335
+ console.log("After signing in, copy the API key from the page and paste it below.");
6336
+ console.log("");
6337
+ const readline = require("readline");
6338
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6339
+ rl.question("Paste your API key: ", (key) => {
6340
+ rl.close();
6341
+ verifyAndSaveKey(config, key.trim()).catch((err) => {
6342
+ console.error(`Setup failed: ${err.message}`);
6343
+ process.exit(1);
6344
+ });
6345
+ });
6346
+ return;
6347
+ }
6348
+ const http2 = require("http");
6349
+ const crypto2 = require("crypto");
6350
+ const state = crypto2.randomBytes(16).toString("hex");
6351
+ let resolved = false;
6352
+ const server = http2.createServer((req, res) => {
6353
+ const url = new URL(req.url, `http://localhost`);
6354
+ if (url.pathname === "/callback") {
6355
+ const key = url.searchParams.get("key");
6356
+ const returnedState = url.searchParams.get("state");
6357
+ res.writeHead(200, { "Content-Type": "text/html" });
6358
+ if (!key || !returnedState || returnedState !== state) {
6359
+ res.end('<html><head><meta name="referrer" content="no-referrer"></head><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Setup failed</h2><p>Invalid or missing parameters. Please try again.</p></body></html>');
6360
+ return;
6361
+ }
6362
+ if (!key.startsWith("sk-prismer-")) {
6363
+ res.end('<html><head><meta name="referrer" content="no-referrer"></head><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Invalid key</h2><p>The key format is unexpected. Please try again.</p></body></html>');
6364
+ return;
6365
+ }
6366
+ res.end('<html><head><meta name="referrer" content="no-referrer"></head><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Done!</h2><p>API key received. You can close this tab.</p></body></html>');
6367
+ resolved = true;
6368
+ verifyAndSaveKey(config, key).then(() => {
6369
+ server.close();
6370
+ process.exit(0);
6371
+ }).catch((err) => {
6372
+ console.error(`Setup failed: ${err.message}`);
6373
+ server.close();
6374
+ process.exit(1);
6375
+ });
6376
+ } else {
6377
+ res.writeHead(404);
6378
+ res.end("Not found");
6379
+ }
6380
+ });
6381
+ server.listen(0, "127.0.0.1", () => {
6382
+ const port = server.address().port;
6383
+ const callbackUrl = `http://127.0.0.1:${port}/callback`;
6384
+ const setupUrl = `${baseUrl}/setup?callback=${encodeURIComponent(callbackUrl)}&state=${state}&utm_source=cli&utm_medium=auto`;
6385
+ console.log("Opening browser to sign in...");
6386
+ console.log("");
6387
+ openBrowser(setupUrl);
6388
+ console.log("Waiting for authentication...");
6389
+ console.log("(If the browser didn't open, visit this URL manually:)");
6390
+ console.log(` ${setupUrl}`);
6391
+ console.log("");
6392
+ setTimeout(() => {
6393
+ if (!resolved) {
6394
+ console.error("Timed out waiting for authentication (5 min).");
6395
+ console.error("");
6396
+ console.error("Alternatives:");
6397
+ console.error(" prismer setup --manual Paste key manually");
6398
+ console.error(" prismer setup --agent Register as agent (free credits, no browser)");
6399
+ server.close();
6400
+ process.exit(1);
6401
+ }
6402
+ }, 5 * 60 * 1e3);
6403
+ });
6404
+ }
6405
+ program.command("setup [api-key]").description("Set up Prismer \u2014 sign in via browser, register as agent, or provide your API key").option("--manual", "Paste API key manually instead of browser auto-flow").option("--agent", "Register as agent with free credits (no browser, for CI/scripts)").option("--force", "Reconfigure even if already set up").action(async (apiKey, opts) => {
6406
+ await runSetup(opts, apiKey);
6407
+ });
6408
+ program.command("init [api-key]").description('Alias for "prismer setup" (deprecated, use setup instead)').option("--manual", "Paste API key manually").option("--agent", "Register as agent with free credits").option("--force", "Reconfigure even if already set up").action(async (apiKey, opts) => {
6409
+ console.log('Note: "prismer init" is deprecated. Use "prismer setup" instead.');
6410
+ console.log("");
6411
+ await runSetup(opts, apiKey);
4666
6412
  });
4667
6413
  program.command("register <username>").description("Register an IM identity and store the token").option("--type <type>", "Identity type: agent or human", "agent").option("--display-name <name>", "Display name").option("--agent-type <agentType>", "Agent type: assistant, specialist, orchestrator, tool, bot").option("--capabilities <caps>", "Comma-separated capabilities").option("--endpoint <url>", "Webhook endpoint URL").option("--webhook-secret <secret>", "Webhook HMAC secret").action(async (username, opts) => {
4668
6414
  const config = readConfig();
4669
6415
  const apiKey = config.default?.api_key;
4670
6416
  if (!apiKey) {
4671
- console.error('No API key. Run "prismer init <api-key>" first.');
6417
+ console.error('No API key. Run "prismer setup" first.');
4672
6418
  process.exit(1);
4673
6419
  }
4674
6420
  const client = new PrismerClient({
@@ -4765,11 +6511,11 @@ program.command("status").description("Show current config and live info").actio
4765
6511
  });
4766
6512
  var configCmd = program.command("config").description("Manage config file");
4767
6513
  configCmd.command("show").description("Print config file").action(() => {
4768
- if (!fs.existsSync(CONFIG_PATH)) {
4769
- console.log('No config file. Run "prismer init <api-key>" to create one.');
6514
+ if (!fs2.existsSync(CONFIG_PATH2)) {
6515
+ console.log('No config file. Run "prismer setup" to create one.');
4770
6516
  return;
4771
6517
  }
4772
- console.log(fs.readFileSync(CONFIG_PATH, "utf-8"));
6518
+ console.log(fs2.readFileSync(CONFIG_PATH2, "utf-8"));
4773
6519
  });
4774
6520
  configCmd.command("set <key> <value>").description("Set a config value (e.g. default.base_url)").action((key, value) => {
4775
6521
  const config = readConfig();
@@ -4810,6 +6556,7 @@ register6(program, getIMClient, getAPIClient);
4810
6556
  register7(program, getIMClient, getAPIClient);
4811
6557
  register8(program, getIMClient, getAPIClient);
4812
6558
  register9(program, getIMClient, getAPIClient);
6559
+ register10(program, getIMClient, getAPIClient);
4813
6560
  program.command("send").description("Send a direct message (shortcut for: im send)").argument("<user-id>", "Target user/agent ID").argument("<message>", "Message content").option("-t, --type <type>", "Message type: text, markdown, code, etc.", "text").option("--reply-to <id>", "Reply to a message ID").option("--json", "JSON output").action(async (userId, message, opts) => {
4814
6561
  const client = getIMClient();
4815
6562
  const sendOpts = {};
@@ -4972,6 +6719,28 @@ program.command("discover").description("Discover available agents (shortcut for
4972
6719
  console.log(`${(a.username || "").padEnd(20)}${(a.agentType || "").padEnd(14)}${(a.status || "").padEnd(10)}${a.displayName || ""}`);
4973
6720
  }
4974
6721
  });
6722
+ program.command("daemon <action>").description("Manage background sync daemon (start|stop|status|install|uninstall)").action(async (action) => {
6723
+ switch (action) {
6724
+ case "start":
6725
+ await startDaemon();
6726
+ break;
6727
+ case "stop":
6728
+ stopDaemon();
6729
+ break;
6730
+ case "status":
6731
+ daemonStatus();
6732
+ break;
6733
+ case "install":
6734
+ installDaemonService();
6735
+ break;
6736
+ case "uninstall":
6737
+ uninstallDaemonService();
6738
+ break;
6739
+ default:
6740
+ console.error(`Unknown daemon action: ${action}. Use: start, stop, status, install, uninstall`);
6741
+ process.exit(1);
6742
+ }
6743
+ });
4975
6744
  program.parse(process.argv);
4976
6745
  // Annotate the CommonJS export names for ESM import in node:
4977
6746
  0 && (module.exports = {