@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/index.mjs CHANGED
@@ -470,7 +470,11 @@ var WRITE_PATTERNS = [
470
470
  { method: "POST", pattern: /\/api\/im\/(messages|direct|groups)\//, opType: "message.send" },
471
471
  { method: "PATCH", pattern: /\/api\/im\/messages\//, opType: "message.edit" },
472
472
  { method: "DELETE", pattern: /\/api\/im\/messages\//, opType: "message.delete" },
473
- { method: "POST", pattern: /\/api\/im\/conversations\/[^/]+\/read/, opType: "conversation.read" }
473
+ { method: "POST", pattern: /\/api\/im\/conversations\/[^/]+\/read/, opType: "conversation.read" },
474
+ // v1.8.0 Community — queued when offline-first IM is enabled
475
+ { method: "POST", pattern: /\/api\/im\/community\/posts$/, opType: "community_post" },
476
+ { method: "POST", pattern: /\/api\/im\/community\/posts\/[^/]+\/comments$/, opType: "community_comment" },
477
+ { method: "POST", pattern: /\/api\/im\/community\/vote$/, opType: "community_vote" }
474
478
  ];
475
479
  function matchWriteOp(method, path) {
476
480
  for (const { method: m, pattern, opType } of WRITE_PATTERNS) {
@@ -1202,6 +1206,293 @@ var AttachmentQueue = class {
1202
1206
  }
1203
1207
  };
1204
1208
 
1209
+ // src/community-hub.ts
1210
+ var CommunityHub = class {
1211
+ constructor(_r, config) {
1212
+ this._r = _r;
1213
+ this.feedCache = /* @__PURE__ */ new Map();
1214
+ this.statsCache = null;
1215
+ this.notifCountCache = null;
1216
+ this.notifCountTTL = 15e3;
1217
+ this.wsUnsubs = [];
1218
+ this.feedTTL = config?.feedTTLMs ?? 3e5;
1219
+ this.statsTTL = config?.statsTTLMs ?? 6e5;
1220
+ }
1221
+ /** Invalidate cached feeds/stats (e.g. after you posted). */
1222
+ invalidateCache(boardId) {
1223
+ if (boardId) this.feedCache.delete(boardId);
1224
+ else this.feedCache.clear();
1225
+ this.statsCache = null;
1226
+ this.notifCountCache = null;
1227
+ }
1228
+ /**
1229
+ * Subscribe to community.* WebSocket events; updates local notification count hint and invalidates feed.
1230
+ */
1231
+ attachRealtime(ws) {
1232
+ const onReply = () => {
1233
+ this.notifCountCache = null;
1234
+ this.feedCache.clear();
1235
+ };
1236
+ const types = [
1237
+ "community.reply",
1238
+ "community.vote",
1239
+ "community.answer.accepted",
1240
+ "community.mention"
1241
+ ];
1242
+ for (const t of types) {
1243
+ ws.on(t, onReply);
1244
+ this.wsUnsubs.push(() => ws.off(t, onReply));
1245
+ }
1246
+ }
1247
+ detachRealtime() {
1248
+ for (const u of this.wsUnsubs) u();
1249
+ this.wsUnsubs = [];
1250
+ }
1251
+ // ─── Intent (cached reads) ─────────────────────────────────
1252
+ async feed(opts) {
1253
+ const key = opts?.boardId ?? "__all__";
1254
+ const hit = this.feedCache.get(key);
1255
+ if (hit && Date.now() - hit.at < this.feedTTL) {
1256
+ return { ok: true, data: hit.payload };
1257
+ }
1258
+ const res = await this.listPosts({
1259
+ boardId: opts?.boardId,
1260
+ limit: opts?.limit ?? 20,
1261
+ sort: "hot"
1262
+ });
1263
+ if (res.ok && res.data != null) {
1264
+ this.feedCache.set(key, { at: Date.now(), payload: res.data });
1265
+ }
1266
+ return res;
1267
+ }
1268
+ async aggregatedContext(opts) {
1269
+ const [feed, stats, unreadNotifications] = await Promise.all([
1270
+ this.feed({ boardId: opts?.boardId, limit: opts?.feedLimit ?? 15 }),
1271
+ this.statsCached(),
1272
+ this.unreadCountCached()
1273
+ ]);
1274
+ return { feed, stats, unreadNotifications };
1275
+ }
1276
+ async statsCached() {
1277
+ if (this.statsCache && Date.now() - this.statsCache.at < this.statsTTL) {
1278
+ return { ok: true, data: this.statsCache.data };
1279
+ }
1280
+ const res = await this.getStats();
1281
+ if (res.ok && res.data != null) {
1282
+ this.statsCache = { at: Date.now(), data: res.data };
1283
+ }
1284
+ return res;
1285
+ }
1286
+ async unreadCountCached() {
1287
+ if (this.notifCountCache && Date.now() - this.notifCountCache.at < this.notifCountTTL) {
1288
+ return { ok: true, data: { unread: this.notifCountCache.count } };
1289
+ }
1290
+ const res = await this.getNotificationCount();
1291
+ const n = res.data?.unread;
1292
+ if (res.ok && typeof n === "number") {
1293
+ this.notifCountCache = { at: Date.now(), count: n };
1294
+ }
1295
+ return res;
1296
+ }
1297
+ /** Helpdesk question shortcut */
1298
+ async ask(title, content, tags) {
1299
+ const res = await this.createPost({
1300
+ boardId: "helpdesk",
1301
+ title,
1302
+ content,
1303
+ postType: "question",
1304
+ tags
1305
+ });
1306
+ if (res.ok) this.invalidateCache("helpdesk");
1307
+ return res;
1308
+ }
1309
+ /** Showcase battle report shortcut */
1310
+ async reportBattle(input) {
1311
+ const res = await this.createPost({
1312
+ boardId: "showcase",
1313
+ title: input.title,
1314
+ content: input.content,
1315
+ postType: "battleReport",
1316
+ tags: input.tags,
1317
+ linkedGeneIds: input.linkedGeneIds,
1318
+ linkedAgentId: input.linkedAgentId
1319
+ });
1320
+ if (res.ok) this.invalidateCache("showcase");
1321
+ return res;
1322
+ }
1323
+ // ─── Notifications & profile (auth) ────────────────────────
1324
+ async getNotifications(opts) {
1325
+ const q = {};
1326
+ if (opts?.unread) q.unread = "true";
1327
+ if (opts?.limit != null) q.limit = String(opts.limit);
1328
+ if (opts?.offset != null) q.offset = String(opts.offset);
1329
+ return this._r("GET", "/api/im/community/notifications", void 0, q);
1330
+ }
1331
+ async markNotificationsRead(notificationId) {
1332
+ const body = notificationId ? { notificationId } : {};
1333
+ return this._r("POST", "/api/im/community/notifications/read", body);
1334
+ }
1335
+ async getNotificationCount() {
1336
+ return this._r("GET", "/api/im/community/notifications/count");
1337
+ }
1338
+ async listBookmarks(opts) {
1339
+ const q = {};
1340
+ if (opts?.cursor) q.cursor = opts.cursor;
1341
+ if (opts?.limit != null) q.limit = String(opts.limit);
1342
+ return this._r("GET", "/api/im/community/bookmarks", void 0, q);
1343
+ }
1344
+ async followToggle(followingId, followingType) {
1345
+ return this._r("POST", "/api/im/community/follow", { followingId, followingType });
1346
+ }
1347
+ async listFollowing(type) {
1348
+ const q = {};
1349
+ if (type) q.type = type;
1350
+ return this._r("GET", "/api/im/community/following", void 0, q);
1351
+ }
1352
+ async listFollowers(userId) {
1353
+ return this._r("GET", `/api/im/community/followers/${encodeURIComponent(userId)}`);
1354
+ }
1355
+ async getProfile(userId) {
1356
+ return this._r("GET", `/api/im/community/profile/${encodeURIComponent(userId)}`);
1357
+ }
1358
+ // ─── REST (same surface as former CommunityClient) ─────────
1359
+ async createPost(input) {
1360
+ return this._r("POST", "/api/im/community/posts", input);
1361
+ }
1362
+ async listPosts(opts) {
1363
+ const query = {};
1364
+ if (opts?.boardId) query.boardId = opts.boardId;
1365
+ if (opts?.sort) query.sort = opts.sort;
1366
+ if (opts?.period) query.period = opts.period;
1367
+ if (opts?.authorType) query.authorType = opts.authorType;
1368
+ if (opts?.cursor) query.cursor = opts.cursor;
1369
+ if (opts?.limit != null) query.limit = String(opts.limit);
1370
+ return this._r("GET", "/api/im/community/posts", void 0, query);
1371
+ }
1372
+ async getPost(postId) {
1373
+ return this._r("GET", `/api/im/community/posts/${encodeURIComponent(postId)}`);
1374
+ }
1375
+ async updatePost(postId, input) {
1376
+ return this._r("PUT", `/api/im/community/posts/${encodeURIComponent(postId)}`, input);
1377
+ }
1378
+ async deletePost(postId) {
1379
+ return this._r("DELETE", `/api/im/community/posts/${encodeURIComponent(postId)}`);
1380
+ }
1381
+ async createComment(postId, input) {
1382
+ return this._r("POST", `/api/im/community/posts/${encodeURIComponent(postId)}/comments`, input);
1383
+ }
1384
+ async listComments(postId, opts) {
1385
+ const query = {};
1386
+ if (opts?.sort) query.sort = opts.sort;
1387
+ if (opts?.cursor) query.cursor = opts.cursor;
1388
+ if (opts?.limit != null) query.limit = String(opts.limit);
1389
+ return this._r("GET", `/api/im/community/posts/${encodeURIComponent(postId)}/comments`, void 0, query);
1390
+ }
1391
+ async markBestAnswer(commentId) {
1392
+ return this._r("POST", `/api/im/community/comments/${encodeURIComponent(commentId)}/best-answer`);
1393
+ }
1394
+ async vote(targetType, targetId, value) {
1395
+ return this._r("POST", "/api/im/community/vote", { targetType, targetId, value });
1396
+ }
1397
+ async bookmark(postId) {
1398
+ return this._r("POST", "/api/im/community/bookmark", { postId });
1399
+ }
1400
+ async search(query, opts) {
1401
+ const q = { q: query };
1402
+ if (opts?.boardId) q.boardId = opts.boardId;
1403
+ if (opts?.sort) q.sort = opts.sort;
1404
+ if (opts?.limit != null) q.limit = String(opts.limit);
1405
+ return this._r("GET", "/api/im/community/search", void 0, q);
1406
+ }
1407
+ async updateComment(commentId, input) {
1408
+ return this._r("PUT", `/api/im/community/comments/${encodeURIComponent(commentId)}`, input);
1409
+ }
1410
+ async deleteComment(commentId) {
1411
+ return this._r("DELETE", `/api/im/community/comments/${encodeURIComponent(commentId)}`);
1412
+ }
1413
+ async getStats() {
1414
+ return this._r("GET", "/api/im/community/stats");
1415
+ }
1416
+ async getTrendingTags(limit) {
1417
+ const query = {};
1418
+ if (limit != null) query.limit = String(limit);
1419
+ return this._r("GET", "/api/im/community/tags/trending", void 0, query);
1420
+ }
1421
+ async getHotPosts(opts) {
1422
+ const query = {};
1423
+ if (opts?.limit != null) query.limit = String(opts.limit);
1424
+ if (opts?.period) query.period = opts.period;
1425
+ return this._r("GET", "/api/im/community/hot", void 0, query);
1426
+ }
1427
+ async searchSuggest(q) {
1428
+ return this._r("GET", "/api/im/community/search/suggest", void 0, { q });
1429
+ }
1430
+ async autocompleteGenes(q, limit) {
1431
+ const query = { q };
1432
+ if (limit != null) query.limit = String(limit);
1433
+ return this._r("GET", "/api/im/community/autocomplete/genes", void 0, query);
1434
+ }
1435
+ async autocompleteSkills(q, limit) {
1436
+ const query = { q };
1437
+ if (limit != null) query.limit = String(limit);
1438
+ return this._r("GET", "/api/im/community/autocomplete/skills", void 0, query);
1439
+ }
1440
+ async createBattleReport(input) {
1441
+ return this.createPost({
1442
+ boardId: "showcase",
1443
+ title: `Battle Report: ${input.agentId}`,
1444
+ content: input.narrative || "Auto-generated battle report",
1445
+ postType: "battleReport",
1446
+ linkedGeneIds: input.geneIds,
1447
+ linkedAgentId: input.agentId
1448
+ });
1449
+ }
1450
+ async createMilestone(input) {
1451
+ return this.createPost({
1452
+ boardId: "showcase",
1453
+ title: input.title,
1454
+ content: input.content,
1455
+ postType: "milestone",
1456
+ linkedGeneIds: input.geneIds,
1457
+ linkedAgentId: input.agentId,
1458
+ tags: input.tags
1459
+ });
1460
+ }
1461
+ async createGeneRelease(input) {
1462
+ return this.createPost({
1463
+ boardId: "showcase",
1464
+ title: input.title,
1465
+ content: input.content,
1466
+ postType: "geneRelease",
1467
+ linkedGeneIds: [input.geneId],
1468
+ tags: input.tags
1469
+ });
1470
+ }
1471
+ };
1472
+
1473
+ // src/aip.ts
1474
+ import {
1475
+ AIPIdentity
1476
+ } from "@prismer/aip-sdk";
1477
+ import {
1478
+ publicKeyToDIDKey,
1479
+ didKeyToPublicKey,
1480
+ validateDIDKey
1481
+ } from "@prismer/aip-sdk";
1482
+ import {
1483
+ buildCredential,
1484
+ buildPresentation,
1485
+ verifyCredential,
1486
+ verifyPresentation
1487
+ } from "@prismer/aip-sdk";
1488
+ import {
1489
+ buildDelegation,
1490
+ buildEphemeralDelegation,
1491
+ verifyDelegation,
1492
+ verifyEphemeralDelegation
1493
+ } from "@prismer/aip-sdk";
1494
+ import { AIPIdentity as AIPIdentity2 } from "@prismer/aip-sdk";
1495
+
1205
1496
  // src/types.ts
1206
1497
  var ENVIRONMENTS = {
1207
1498
  production: "https://prismer.cloud"
@@ -2091,20 +2382,27 @@ var PBKDF2_ITERATIONS = 1e5;
2091
2382
  var SALT_LENGTH = 16;
2092
2383
  var IV_LENGTH = 12;
2093
2384
  var KEY_LENGTH = 256;
2094
- var E2EEncryption = class {
2385
+ var _E2EEncryption = class _E2EEncryption {
2095
2386
  constructor() {
2096
2387
  this.masterKey = null;
2097
2388
  this.keyPair = null;
2098
2389
  this.sessionKeys = /* @__PURE__ */ new Map();
2099
2390
  // conversationId → AES key
2100
2391
  this.salt = null;
2392
+ // ─── Pipeline Functions ──────────────────────────────────
2393
+ this.messageCount = 0;
2394
+ this.lastRotation = Date.now();
2101
2395
  }
2102
2396
  /**
2103
2397
  * Initialize encryption with user passphrase.
2104
2398
  * Derives a master key via PBKDF2 and generates an ECDH key pair.
2399
+ *
2400
+ * @param passphrase - User passphrase for master key derivation
2401
+ * @param salt - Optional Base64-encoded salt. If omitted, a random 16-byte salt is generated.
2402
+ * Store the salt (via exportSalt()) so you can re-derive the same master key later.
2105
2403
  */
2106
- async init(passphrase) {
2107
- this.salt = getRandomValues(new Uint8Array(SALT_LENGTH));
2404
+ async init(passphrase, salt) {
2405
+ this.salt = salt ? new Uint8Array(base64ToArrayBuffer(salt)) : getRandomValues(new Uint8Array(SALT_LENGTH));
2108
2406
  const passphraseKey = await subtle().importKey(
2109
2407
  "raw",
2110
2408
  new TextEncoder().encode(passphrase),
@@ -2115,7 +2413,7 @@ var E2EEncryption = class {
2115
2413
  this.masterKey = await subtle().deriveKey(
2116
2414
  {
2117
2415
  name: "PBKDF2",
2118
- salt: this.salt,
2416
+ salt: new Uint8Array(this.salt.buffer, this.salt.byteOffset, this.salt.byteLength).buffer,
2119
2417
  iterations: PBKDF2_ITERATIONS,
2120
2418
  hash: "SHA-256"
2121
2419
  },
@@ -2130,6 +2428,14 @@ var E2EEncryption = class {
2130
2428
  ["deriveKey"]
2131
2429
  );
2132
2430
  }
2431
+ /**
2432
+ * Export the salt as Base64 string for persistent storage.
2433
+ * You must store this and pass it back to init() to re-derive the same master key.
2434
+ */
2435
+ exportSalt() {
2436
+ if (!this.salt) throw new Error("E2E not initialized. Call init() first.");
2437
+ return arrayBufferToBase64(this.salt.buffer);
2438
+ }
2133
2439
  /**
2134
2440
  * Export public key for sharing with conversation peers.
2135
2441
  */
@@ -2242,8 +2548,93 @@ var E2EEncryption = class {
2242
2548
  this.keyPair = null;
2243
2549
  this.sessionKeys.clear();
2244
2550
  this.salt = null;
2551
+ this.messageCount = 0;
2552
+ }
2553
+ /**
2554
+ * High-level encrypt-for-send pipeline.
2555
+ * Encrypts content, builds metadata, and handles key rotation.
2556
+ *
2557
+ * Returns { encryptedContent, metadata } ready to send.
2558
+ */
2559
+ async encryptForSend(conversationId, content) {
2560
+ if (!this.hasSessionKey(conversationId)) {
2561
+ throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
2562
+ }
2563
+ const needsRotation = this.shouldRotateKey();
2564
+ const encryptedContent = await this.encrypt(conversationId, content);
2565
+ this.messageCount++;
2566
+ return {
2567
+ encryptedContent,
2568
+ metadata: {
2569
+ encrypted: true,
2570
+ encryptionVersion: 1,
2571
+ ...needsRotation && { keyRotationRequested: true }
2572
+ }
2573
+ };
2574
+ }
2575
+ /**
2576
+ * High-level decrypt-on-receive pipeline.
2577
+ * Decrypts content and validates metadata.
2578
+ */
2579
+ async decryptOnReceive(conversationId, encryptedContent, metadata) {
2580
+ if (!this.hasSessionKey(conversationId)) {
2581
+ throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
2582
+ }
2583
+ return this.decrypt(conversationId, encryptedContent);
2584
+ }
2585
+ /**
2586
+ * High-level file encryption pipeline.
2587
+ */
2588
+ async encryptFile(conversationId, fileData) {
2589
+ const base64Data = arrayBufferToBase64(fileData);
2590
+ const encryptedData = await this.encrypt(conversationId, base64Data);
2591
+ return {
2592
+ encryptedData,
2593
+ metadata: {
2594
+ encrypted: true,
2595
+ encryptionVersion: 1,
2596
+ fileEncrypted: true
2597
+ }
2598
+ };
2599
+ }
2600
+ /**
2601
+ * High-level file decryption pipeline.
2602
+ */
2603
+ async decryptFile(conversationId, encryptedData) {
2604
+ const base64Data = await this.decrypt(conversationId, encryptedData);
2605
+ return base64ToArrayBuffer(base64Data);
2606
+ }
2607
+ /**
2608
+ * Check if key rotation is needed (1000 messages or 24 hours).
2609
+ */
2610
+ shouldRotateKey() {
2611
+ if (this.messageCount >= _E2EEncryption.KEY_ROTATION_THRESHOLD) {
2612
+ return true;
2613
+ }
2614
+ if (Date.now() - this.lastRotation >= _E2EEncryption.KEY_ROTATION_INTERVAL_MS) {
2615
+ return true;
2616
+ }
2617
+ return false;
2618
+ }
2619
+ /**
2620
+ * Perform key rotation: generate new ECDH keypair and reset counters.
2621
+ * The caller is responsible for re-exchanging keys with peers.
2622
+ */
2623
+ async rotateKeys() {
2624
+ this.keyPair = await subtle().generateKey(
2625
+ { name: "ECDH", namedCurve: "P-256" },
2626
+ false,
2627
+ ["deriveKey"]
2628
+ );
2629
+ this.messageCount = 0;
2630
+ this.lastRotation = Date.now();
2631
+ this.sessionKeys.clear();
2632
+ return this.exportPublicKey();
2245
2633
  }
2246
2634
  };
2635
+ _E2EEncryption.KEY_ROTATION_THRESHOLD = 1e3;
2636
+ _E2EEncryption.KEY_ROTATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2637
+ var E2EEncryption = _E2EEncryption;
2247
2638
  function arrayBufferToBase64(buffer) {
2248
2639
  if (typeof btoa !== "undefined") {
2249
2640
  const bytes = new Uint8Array(buffer);
@@ -2811,6 +3202,53 @@ var EvolutionRuntime = class {
2811
3202
  };
2812
3203
 
2813
3204
  // src/index.ts
3205
+ var _fs = null;
3206
+ var _os = null;
3207
+ var _path = null;
3208
+ try {
3209
+ _fs = __require("fs");
3210
+ _os = __require("os");
3211
+ _path = __require("path");
3212
+ } catch {
3213
+ }
3214
+ function resolveApiKey(explicit) {
3215
+ if (explicit) return explicit;
3216
+ try {
3217
+ if (typeof process !== "undefined" && process.env?.PRISMER_API_KEY) {
3218
+ return process.env.PRISMER_API_KEY;
3219
+ }
3220
+ } catch {
3221
+ }
3222
+ if (_fs && _os && _path) {
3223
+ try {
3224
+ const configPath = _path.join(_os.homedir(), ".prismer", "config.toml");
3225
+ const raw = _fs.readFileSync(configPath, "utf-8");
3226
+ const match = raw.match(/^api_key\s*=\s*'([^']+)'/m) || raw.match(/^api_key\s*=\s*"([^"]+)"/m);
3227
+ if (match?.[1]) return match[1];
3228
+ } catch {
3229
+ }
3230
+ }
3231
+ return "";
3232
+ }
3233
+ function resolveBaseUrl(explicit) {
3234
+ if (explicit) return explicit;
3235
+ try {
3236
+ if (typeof process !== "undefined" && process.env?.PRISMER_BASE_URL) {
3237
+ return process.env.PRISMER_BASE_URL;
3238
+ }
3239
+ } catch {
3240
+ }
3241
+ if (_fs && _os && _path) {
3242
+ try {
3243
+ const configPath = _path.join(_os.homedir(), ".prismer", "config.toml");
3244
+ const raw = _fs.readFileSync(configPath, "utf-8");
3245
+ const match = raw.match(/^base_url\s*=\s*'([^']+)'/m) || raw.match(/^base_url\s*=\s*"([^"]+)"/m);
3246
+ if (match?.[1]) return match[1];
3247
+ } catch {
3248
+ }
3249
+ }
3250
+ return void 0;
3251
+ }
2814
3252
  var AccountClient = class {
2815
3253
  constructor(_r) {
2816
3254
  this._r = _r;
@@ -2823,6 +3261,10 @@ var AccountClient = class {
2823
3261
  async me() {
2824
3262
  return this._r("GET", "/api/im/me");
2825
3263
  }
3264
+ /** Update own profile */
3265
+ async updateProfile(options) {
3266
+ return this._r("PATCH", "/api/im/me", options);
3267
+ }
2826
3268
  /** Refresh JWT token */
2827
3269
  async refreshToken() {
2828
3270
  return this._r("POST", "/api/im/token/refresh");
@@ -2913,6 +3355,30 @@ var ConversationsClient = class {
2913
3355
  async markAsRead(conversationId) {
2914
3356
  return this._r("POST", `/api/im/conversations/${conversationId}/read`);
2915
3357
  }
3358
+ /** Archive a conversation */
3359
+ async archive(conversationId) {
3360
+ return this._r("POST", `/api/im/conversations/${conversationId}/archive`);
3361
+ }
3362
+ /** Unarchive a conversation */
3363
+ async unarchive(conversationId) {
3364
+ return this._r("POST", `/api/im/conversations/${conversationId}/unarchive`);
3365
+ }
3366
+ /** Update conversation metadata */
3367
+ async update(conversationId, options) {
3368
+ return this._r("PATCH", `/api/im/conversations/${conversationId}`, options);
3369
+ }
3370
+ /** Pin or unpin a conversation */
3371
+ async pin(conversationId, pinned) {
3372
+ return this._r("PATCH", `/api/im/conversations/${conversationId}/pin`, { pinned });
3373
+ }
3374
+ /** Mute or unmute a conversation */
3375
+ async mute(conversationId, muted) {
3376
+ return this._r("PATCH", `/api/im/conversations/${conversationId}/mute`, { muted });
3377
+ }
3378
+ /** Delete a conversation */
3379
+ async delete(conversationId) {
3380
+ return this._r("DELETE", `/api/im/conversations/${conversationId}`);
3381
+ }
2916
3382
  };
2917
3383
  var MessagesClient = class {
2918
3384
  constructor(_r) {
@@ -2942,6 +3408,10 @@ var MessagesClient = class {
2942
3408
  async delete(conversationId, messageId) {
2943
3409
  return this._r("DELETE", `/api/im/messages/${conversationId}/${messageId}`);
2944
3410
  }
3411
+ /** Mark messages as delivered */
3412
+ async markDelivered(conversationId, messageIds) {
3413
+ return this._r("POST", "/api/im/messages/delivered", { conversationId, messageIds });
3414
+ }
2945
3415
  };
2946
3416
  var ContactsClient = class {
2947
3417
  constructor(_r) {
@@ -2951,6 +3421,18 @@ var ContactsClient = class {
2951
3421
  async list() {
2952
3422
  return this._r("GET", "/api/im/contacts");
2953
3423
  }
3424
+ /** Search users/agents by query */
3425
+ async search(query, options) {
3426
+ const params = { q: query };
3427
+ if (options?.type && options.type !== "all") params.type = options.type;
3428
+ if (options?.limit) params.limit = String(options.limit);
3429
+ if (options?.offset) params.offset = String(options.offset);
3430
+ return this._r("GET", "/api/im/discover", void 0, params);
3431
+ }
3432
+ /** Get a user's public profile */
3433
+ async getProfile(userId) {
3434
+ return this._r("GET", `/api/im/users/${userId}`);
3435
+ }
2954
3436
  /** Discover agents by capability or type */
2955
3437
  async discover(options) {
2956
3438
  const query = {};
@@ -2958,6 +3440,67 @@ var ContactsClient = class {
2958
3440
  if (options?.capability) query.capability = options.capability;
2959
3441
  return this._r("GET", "/api/im/discover", void 0, query);
2960
3442
  }
3443
+ // ─── Friend System (v1.8.0 P9) ─────────────────────────
3444
+ /** Send a friend request */
3445
+ async request(userId, opts) {
3446
+ return this._r("POST", "/api/im/contacts/request", { userId, ...opts });
3447
+ }
3448
+ /** List pending friend requests received */
3449
+ async pendingReceived(opts) {
3450
+ const params = {};
3451
+ if (opts?.limit) params.limit = String(opts.limit);
3452
+ if (opts?.offset) params.offset = String(opts.offset);
3453
+ return this._r("GET", "/api/im/contacts/requests/received", void 0, params);
3454
+ }
3455
+ /** List pending friend requests sent */
3456
+ async pendingSent(opts) {
3457
+ const params = {};
3458
+ if (opts?.limit) params.limit = String(opts.limit);
3459
+ if (opts?.offset) params.offset = String(opts.offset);
3460
+ return this._r("GET", "/api/im/contacts/requests/sent", void 0, params);
3461
+ }
3462
+ /** Accept a friend request */
3463
+ async accept(requestId) {
3464
+ return this._r("POST", `/api/im/contacts/requests/${requestId}/accept`);
3465
+ }
3466
+ /** Reject a friend request */
3467
+ async reject(requestId) {
3468
+ return this._r("POST", `/api/im/contacts/requests/${requestId}/reject`);
3469
+ }
3470
+ /** List friends */
3471
+ async friends(opts) {
3472
+ const params = {};
3473
+ if (opts?.limit) params.limit = String(opts.limit);
3474
+ if (opts?.offset) params.offset = String(opts.offset);
3475
+ return this._r("GET", "/api/im/contacts/friends", void 0, params);
3476
+ }
3477
+ /** Remove a friend */
3478
+ async remove(userId) {
3479
+ return this._r("DELETE", `/api/im/contacts/${userId}/remove`);
3480
+ }
3481
+ /** Set a remark/alias for a contact */
3482
+ async setRemark(userId, remark) {
3483
+ return this._r("PATCH", `/api/im/contacts/${userId}/remark`, { remark });
3484
+ }
3485
+ /** Block a user */
3486
+ async block(userId) {
3487
+ return this._r("POST", `/api/im/contacts/${userId}/block`, {});
3488
+ }
3489
+ /** Unblock a user */
3490
+ async unblock(userId) {
3491
+ return this._r("DELETE", `/api/im/contacts/${userId}/block`);
3492
+ }
3493
+ /** List blocked users */
3494
+ async blocklist(opts) {
3495
+ const params = {};
3496
+ if (opts?.limit) params.limit = String(opts.limit);
3497
+ if (opts?.offset) params.offset = String(opts.offset);
3498
+ return this._r("GET", "/api/im/contacts/blocked", void 0, params);
3499
+ }
3500
+ /** Get presence status for multiple users */
3501
+ async getPresence(userIds) {
3502
+ return this._r("POST", "/api/im/presence/batch", { userIds });
3503
+ }
2961
3504
  };
2962
3505
  var BindingsClient = class {
2963
3506
  constructor(_r) {
@@ -3109,6 +3652,23 @@ var MemoryClient = class {
3109
3652
  if (scope) query.scope = scope;
3110
3653
  return this._r("GET", "/api/im/memory/load", void 0, query);
3111
3654
  }
3655
+ /** Get memory-gene knowledge links for the authenticated user's memory files (v1.8.0) */
3656
+ async getKnowledgeLinks() {
3657
+ return this._r("GET", "/api/im/memory/links");
3658
+ }
3659
+ };
3660
+ var KnowledgeLinkClient = class {
3661
+ constructor(_r) {
3662
+ this._r = _r;
3663
+ }
3664
+ /**
3665
+ * Get all knowledge links for a given entity.
3666
+ * @param entityType - One of: memory, gene, capsule, signal
3667
+ * @param entityId - The entity ID
3668
+ */
3669
+ async getLinks(entityType, entityId) {
3670
+ return this._r("GET", "/api/im/knowledge/links", void 0, { entityType, entityId });
3671
+ }
3112
3672
  };
3113
3673
  var IdentityClient = class {
3114
3674
  constructor(_r) {
@@ -3211,6 +3771,62 @@ var EvolutionClient = class {
3211
3771
  if (limit != null) query.limit = String(limit);
3212
3772
  return this._r("GET", "/api/im/evolution/public/feed", void 0, query);
3213
3773
  }
3774
+ // ── Leaderboard V2 (public, no auth required) ──
3775
+ /** Get hero section global stats (total agents, genes, capsules, savings) */
3776
+ async getLeaderboardHero() {
3777
+ return this._r("GET", "/api/im/evolution/leaderboard/hero");
3778
+ }
3779
+ /** Get rising stars leaderboard */
3780
+ async getLeaderboardRising(period, limit) {
3781
+ const query = {};
3782
+ if (period) query.period = period;
3783
+ if (limit != null) query.limit = String(limit);
3784
+ return this._r("GET", "/api/im/evolution/leaderboard/rising", void 0, query);
3785
+ }
3786
+ /** Get leaderboard summary stats (totalAgentsEvolving, totalGenesCreated, etc.) */
3787
+ async getLeaderboardStats() {
3788
+ return this._r("GET", "/api/im/evolution/leaderboard/stats");
3789
+ }
3790
+ /** Get agent improvement board */
3791
+ async getLeaderboardAgents(period, domain) {
3792
+ const query = {};
3793
+ if (period) query.period = period;
3794
+ if (domain) query.domain = domain;
3795
+ return this._r("GET", "/api/im/evolution/leaderboard/agents", void 0, query);
3796
+ }
3797
+ /** Get gene impact board */
3798
+ async getLeaderboardGenes(period, sort) {
3799
+ const query = {};
3800
+ if (period) query.period = period;
3801
+ if (sort) query.sort = sort;
3802
+ return this._r("GET", "/api/im/evolution/leaderboard/genes", void 0, query);
3803
+ }
3804
+ /** Get contributor board */
3805
+ async getLeaderboardContributors(period) {
3806
+ const query = {};
3807
+ if (period) query.period = period;
3808
+ return this._r("GET", "/api/im/evolution/leaderboard/contributors", void 0, query);
3809
+ }
3810
+ /** Get cross-environment comparison data */
3811
+ async getLeaderboardComparison() {
3812
+ return this._r("GET", "/api/im/evolution/leaderboard/comparison");
3813
+ }
3814
+ /** Get public profile page data for an agent or owner */
3815
+ async getPublicProfile(entityId) {
3816
+ return this._r("GET", `/api/im/evolution/profile/${encodeURIComponent(entityId)}`);
3817
+ }
3818
+ /** Render agent/creator card as PNG */
3819
+ async renderCard(input) {
3820
+ return this._r("POST", "/api/im/evolution/card/render", input);
3821
+ }
3822
+ /** Get benchmark data for profile FOMO section */
3823
+ async getBenchmark() {
3824
+ return this._r("GET", "/api/im/evolution/benchmark");
3825
+ }
3826
+ /** Get gene highlight capsules for profile page */
3827
+ async getHighlights(geneId) {
3828
+ return this._r("GET", `/api/im/evolution/highlights/${encodeURIComponent(geneId)}`);
3829
+ }
3214
3830
  // ── Authenticated endpoints ──
3215
3831
  /** Analyze signals and get gene recommendation */
3216
3832
  async analyze(options) {
@@ -3292,11 +3908,11 @@ var EvolutionClient = class {
3292
3908
  }
3293
3909
  /** Delete a gene */
3294
3910
  async deleteGene(geneId) {
3295
- return this._r("DELETE", `/api/im/evolution/genes/${geneId}`);
3911
+ return this._r("DELETE", `/api/im/evolution/genes/${encodeURIComponent(geneId)}`);
3296
3912
  }
3297
3913
  /** Publish a gene. Pass skipCanary=true to bypass canary validation (MVP/admin). */
3298
3914
  async publishGene(geneId, options) {
3299
- return this._r("POST", `/api/im/evolution/genes/${geneId}/publish`, options?.skipCanary ? { skipCanary: true } : void 0);
3915
+ return this._r("POST", `/api/im/evolution/genes/${encodeURIComponent(geneId)}/publish`, options?.skipCanary ? { skipCanary: true } : void 0);
3300
3916
  }
3301
3917
  /** Import a published gene */
3302
3918
  async importGene(geneId) {
@@ -3367,8 +3983,8 @@ var EvolutionClient = class {
3367
3983
  return this._r("GET", "/api/im/skills/stats");
3368
3984
  }
3369
3985
  /** Install a skill — creates Gene + returns content + install guide */
3370
- async installSkill(slugOrId) {
3371
- return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`);
3986
+ async installSkill(slugOrId, scope) {
3987
+ return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, scope ? { scope } : void 0);
3372
3988
  }
3373
3989
  /** Uninstall a skill */
3374
3990
  async uninstallSkill(slugOrId) {
@@ -3382,6 +3998,14 @@ var EvolutionClient = class {
3382
3998
  async getSkillContent(slugOrId) {
3383
3999
  return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
3384
4000
  }
4001
+ /** Create/submit a community skill */
4002
+ async createSkill(input) {
4003
+ return this._r("POST", "/api/im/skills", input);
4004
+ }
4005
+ /** Star a skill (increment community rating) */
4006
+ async starSkill(skillId) {
4007
+ return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
4008
+ }
3385
4009
  /**
3386
4010
  * Install a skill and write SKILL.md to local filesystem.
3387
4011
  * Combines cloud install + local file sync for Claude Code / OpenClaw / OpenCode.
@@ -3444,8 +4068,8 @@ var EvolutionClient = class {
3444
4068
  async uninstallSkillLocal(slugOrId) {
3445
4069
  const result = await this.uninstallSkill(slugOrId);
3446
4070
  const removedPaths = [];
3447
- const safeSlug = slugOrId.replace(/[\/\\]/g, "").replace(/\.\./g, "");
3448
- if (!safeSlug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
4071
+ const slug = safeSlug(slugOrId);
4072
+ if (!slug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
3449
4073
  try {
3450
4074
  const fs = await import("fs");
3451
4075
  const path = await import("path");
@@ -3453,10 +4077,10 @@ var EvolutionClient = class {
3453
4077
  const home = os.homedir();
3454
4078
  const pluginBase = process.env.PRISMER_PLUGIN_DIR || path.join(home, ".claude", "plugins", "prismer");
3455
4079
  const dirs = [
3456
- path.join(home, ".claude", "skills", safeSlug),
3457
- path.join(home, ".openclaw", "skills", safeSlug),
3458
- path.join(home, ".config", "opencode", "skills", safeSlug),
3459
- path.join(pluginBase, "skills", safeSlug)
4080
+ path.join(home, ".claude", "skills", slug),
4081
+ path.join(home, ".openclaw", "skills", slug),
4082
+ path.join(home, ".config", "opencode", "skills", slug),
4083
+ path.join(pluginBase, "skills", slug)
3460
4084
  ];
3461
4085
  for (const dir of dirs) {
3462
4086
  try {
@@ -3566,6 +4190,9 @@ var EvolutionClient = class {
3566
4190
  return this._r("POST", "/api/im/evolution/sync", body);
3567
4191
  }
3568
4192
  };
4193
+ function safeSlug(input) {
4194
+ return input.replace(/[\/\\]/g, "").replace(/\.\./g, "").replace(/\0/g, "");
4195
+ }
3569
4196
  function guessMimeType(fileName) {
3570
4197
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
3571
4198
  const map = {
@@ -3794,7 +4421,7 @@ var IMRealtimeClient = class {
3794
4421
  }
3795
4422
  };
3796
4423
  var IMClient = class {
3797
- constructor(request, wsBase, fetchFn, getAuthHeaders, offlineManager) {
4424
+ constructor(request, wsBase, fetchFn, getAuthHeaders, offlineManager, communityHubConfig) {
3798
4425
  this.account = new AccountClient(request);
3799
4426
  this.direct = new DirectClient(request);
3800
4427
  this.groups = new GroupsClient(request);
@@ -3806,9 +4433,11 @@ var IMClient = class {
3806
4433
  this.workspace = new WorkspaceClient(request);
3807
4434
  this.tasks = new TasksClient(request);
3808
4435
  this.memory = new MemoryClient(request);
4436
+ this.knowledge = new KnowledgeLinkClient(request);
3809
4437
  this.identity = new IdentityClient(request);
3810
4438
  this.security = new SecurityClient(request);
3811
4439
  this.evolution = new EvolutionClient(request);
4440
+ this.community = new CommunityHub(request, communityHubConfig ?? void 0);
3812
4441
  this.files = new FilesClient(request, wsBase, fetchFn, getAuthHeaders);
3813
4442
  this.realtime = new IMRealtimeClient(wsBase);
3814
4443
  this.offline = offlineManager ?? null;
@@ -3817,19 +4446,43 @@ var IMClient = class {
3817
4446
  async health() {
3818
4447
  return this.account["_r"]("GET", "/api/im/health");
3819
4448
  }
4449
+ /** Get workspace superset view with slot filtering */
4450
+ async getWorkspace(scope, slots, includeContent) {
4451
+ const params = new URLSearchParams();
4452
+ if (scope) params.set("scope", scope);
4453
+ if (slots?.length) params.set("slots", slots.join(","));
4454
+ if (includeContent) params.set("includeContent", "true");
4455
+ return this.workspace["_r"]("GET", `/api/im/workspace/view?${params}`);
4456
+ }
3820
4457
  };
3821
4458
  var PrismerClient = class {
3822
4459
  constructor(config = {}) {
3823
4460
  this._offlineManager = null;
3824
- if (config.apiKey && !config.apiKey.startsWith("sk-prismer-") && !config.apiKey.startsWith("eyJ")) {
4461
+ /** AIP identity for auto-signing (v1.8.0 S1) */
4462
+ this._identity = null;
4463
+ this._identityReady = null;
4464
+ const resolvedApiKey = resolveApiKey(config.apiKey);
4465
+ if (resolvedApiKey && !resolvedApiKey.startsWith("sk-prismer-") && !resolvedApiKey.startsWith("eyJ")) {
3825
4466
  console.warn('Warning: API key should start with "sk-prismer-" (or "eyJ" for IM JWT)');
3826
4467
  }
3827
- this.apiKey = config.apiKey || "";
4468
+ this.apiKey = resolvedApiKey;
3828
4469
  const envUrl = ENVIRONMENTS[config.environment || "production"];
3829
- this.baseUrl = (config.baseUrl || envUrl).replace(/\/$/, "");
4470
+ this.baseUrl = (resolveBaseUrl(config.baseUrl) || envUrl).replace(/\/$/, "");
3830
4471
  this.timeout = config.timeout || 3e4;
3831
4472
  this.fetchFn = config.fetch || fetch;
3832
4473
  this.imAgent = config.imAgent;
4474
+ if (config.identity) {
4475
+ if (config.identity === "auto" && this.apiKey) {
4476
+ this._identityReady = AIPIdentity.fromApiKey(this.apiKey).then((id) => {
4477
+ this._identity = id;
4478
+ }).catch((err) => console.warn("[PrismerSDK] Identity init failed:", err));
4479
+ } else if (typeof config.identity === "object" && config.identity.privateKey) {
4480
+ 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)));
4481
+ this._identityReady = AIPIdentity.fromPrivateKey(keyBytes).then((id) => {
4482
+ this._identity = id;
4483
+ }).catch((err) => console.warn("[PrismerSDK] Identity init failed:", err));
4484
+ }
4485
+ }
3833
4486
  if (config.offline) {
3834
4487
  this._offlineManager = new OfflineManager(
3835
4488
  config.offline.storage,
@@ -3840,14 +4493,60 @@ var PrismerClient = class {
3840
4493
  (err) => console.warn("[PrismerSDK] Offline storage init failed:", err)
3841
4494
  );
3842
4495
  }
3843
- 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);
4496
+ 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);
4497
+ if (config.identity) {
4498
+ const baseRequest = imRequest;
4499
+ imRequest = (method, path, body, query) => {
4500
+ if (method === "POST" && path.includes("/messages") && body) {
4501
+ const b = body;
4502
+ if (!b.signature && !b.skipSigning) {
4503
+ const ready = this._identityReady || Promise.resolve();
4504
+ return ready.then(() => {
4505
+ if (this._identity) {
4506
+ return this._signAndSend(baseRequest, method, path, b, query);
4507
+ }
4508
+ return baseRequest(method, path, body, query);
4509
+ });
4510
+ }
4511
+ }
4512
+ return baseRequest(method, path, body, query);
4513
+ };
4514
+ }
3844
4515
  this.im = new IMClient(
3845
4516
  imRequest,
3846
4517
  this.baseUrl,
3847
4518
  this.fetchFn,
3848
4519
  () => this._getAuthHeaders(),
3849
- this._offlineManager
4520
+ this._offlineManager,
4521
+ config.community ?? null
4522
+ );
4523
+ }
4524
+ /** Wait for identity to be ready (useful for tests or explicit await) */
4525
+ async ensureIdentity() {
4526
+ if (this._identityReady) await this._identityReady;
4527
+ return this._identity;
4528
+ }
4529
+ /** Auto-sign a message body and send (v1.8.0 S1) */
4530
+ async _signAndSend(baseRequest, method, path, body, query) {
4531
+ if (this._identityReady) await this._identityReady;
4532
+ if (!this._identity) return baseRequest(method, path, body, query);
4533
+ const content = body.content || "";
4534
+ const contentHashBytes = new Uint8Array(
4535
+ await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content))
3850
4536
  );
4537
+ const contentHash = Array.from(contentHashBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
4538
+ const timestamp = Date.now();
4539
+ const payload = `1|${this._identity.did}|${body.type || "text"}|${timestamp}|${contentHash}`;
4540
+ const payloadBytes = new TextEncoder().encode(payload);
4541
+ const signature = await this._identity.sign(payloadBytes);
4542
+ return baseRequest(method, path, {
4543
+ ...body,
4544
+ secVersion: 1,
4545
+ senderDid: this._identity.did,
4546
+ contentHash,
4547
+ signature,
4548
+ signedAt: timestamp
4549
+ }, query);
3851
4550
  }
3852
4551
  /** Build auth headers for raw HTTP requests (used by file upload) */
3853
4552
  _getAuthHeaders() {
@@ -3982,9 +4681,11 @@ function createClient(config) {
3982
4681
  return new PrismerClient(config);
3983
4682
  }
3984
4683
  export {
4684
+ AIPIdentity,
3985
4685
  AccountClient,
3986
4686
  AttachmentQueue,
3987
4687
  BindingsClient,
4688
+ CommunityHub,
3988
4689
  ContactsClient,
3989
4690
  ConversationsClient,
3990
4691
  CreditsClient,
@@ -4000,6 +4701,7 @@ export {
4000
4701
  IMRealtimeClient,
4001
4702
  IdentityClient,
4002
4703
  IndexedDBStorage,
4704
+ KnowledgeLinkClient,
4003
4705
  MemoryClient,
4004
4706
  MemoryStorage,
4005
4707
  MessagesClient,
@@ -4022,5 +4724,7 @@ export {
4022
4724
  encryptContext,
4023
4725
  encryptFile,
4024
4726
  encryptForSend,
4025
- extractSignals
4727
+ extractSignals,
4728
+ guessMimeType,
4729
+ safeSlug
4026
4730
  };