@prismer/sdk 1.7.4 → 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,9 +1124,268 @@ var OfflineManager = class extends OfflineEmitter {
1120
1124
  }
1121
1125
  };
1122
1126
 
1123
- // src/types.ts
1124
- var ENVIRONMENTS = {
1125
- production: "https://prismer.cloud"
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
+ }
1126
1389
  };
1127
1390
 
1128
1391
  // src/aip.ts
@@ -1130,6 +1393,12 @@ var import_aip_sdk = require("@prismer/aip-sdk");
1130
1393
  var import_aip_sdk2 = require("@prismer/aip-sdk");
1131
1394
  var import_aip_sdk3 = require("@prismer/aip-sdk");
1132
1395
  var import_aip_sdk4 = require("@prismer/aip-sdk");
1396
+ var import_aip_sdk5 = require("@prismer/aip-sdk");
1397
+
1398
+ // src/types.ts
1399
+ var ENVIRONMENTS = {
1400
+ production: "https://prismer.cloud"
1401
+ };
1133
1402
 
1134
1403
  // src/encryption.ts
1135
1404
  function getSubtleCrypto() {
@@ -1437,6 +1706,53 @@ function base64ToArrayBuffer(base64) {
1437
1706
  }
1438
1707
 
1439
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
+ }
1440
1756
  var AccountClient = class {
1441
1757
  constructor(_r) {
1442
1758
  this._r = _r;
@@ -1449,6 +1765,10 @@ var AccountClient = class {
1449
1765
  async me() {
1450
1766
  return this._r("GET", "/api/im/me");
1451
1767
  }
1768
+ /** Update own profile */
1769
+ async updateProfile(options) {
1770
+ return this._r("PATCH", "/api/im/me", options);
1771
+ }
1452
1772
  /** Refresh JWT token */
1453
1773
  async refreshToken() {
1454
1774
  return this._r("POST", "/api/im/token/refresh");
@@ -1539,6 +1859,30 @@ var ConversationsClient = class {
1539
1859
  async markAsRead(conversationId) {
1540
1860
  return this._r("POST", `/api/im/conversations/${conversationId}/read`);
1541
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
+ }
1542
1886
  };
1543
1887
  var MessagesClient = class {
1544
1888
  constructor(_r) {
@@ -1568,6 +1912,10 @@ var MessagesClient = class {
1568
1912
  async delete(conversationId, messageId) {
1569
1913
  return this._r("DELETE", `/api/im/messages/${conversationId}/${messageId}`);
1570
1914
  }
1915
+ /** Mark messages as delivered */
1916
+ async markDelivered(conversationId, messageIds) {
1917
+ return this._r("POST", "/api/im/messages/delivered", { conversationId, messageIds });
1918
+ }
1571
1919
  };
1572
1920
  var ContactsClient = class {
1573
1921
  constructor(_r) {
@@ -1577,6 +1925,18 @@ var ContactsClient = class {
1577
1925
  async list() {
1578
1926
  return this._r("GET", "/api/im/contacts");
1579
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
+ }
1580
1940
  /** Discover agents by capability or type */
1581
1941
  async discover(options) {
1582
1942
  const query = {};
@@ -1584,6 +1944,67 @@ var ContactsClient = class {
1584
1944
  if (options?.capability) query.capability = options.capability;
1585
1945
  return this._r("GET", "/api/im/discover", void 0, query);
1586
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
+ }
1587
2008
  };
1588
2009
  var BindingsClient = class {
1589
2010
  constructor(_r) {
@@ -1735,6 +2156,23 @@ var MemoryClient = class {
1735
2156
  if (scope) query.scope = scope;
1736
2157
  return this._r("GET", "/api/im/memory/load", void 0, query);
1737
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
+ }
1738
2176
  };
1739
2177
  var IdentityClient = class {
1740
2178
  constructor(_r) {
@@ -1837,6 +2275,62 @@ var EvolutionClient = class {
1837
2275
  if (limit != null) query.limit = String(limit);
1838
2276
  return this._r("GET", "/api/im/evolution/public/feed", void 0, query);
1839
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
+ }
1840
2334
  // ── Authenticated endpoints ──
1841
2335
  /** Analyze signals and get gene recommendation */
1842
2336
  async analyze(options) {
@@ -1993,8 +2487,8 @@ var EvolutionClient = class {
1993
2487
  return this._r("GET", "/api/im/skills/stats");
1994
2488
  }
1995
2489
  /** Install a skill — creates Gene + returns content + install guide */
1996
- async installSkill(slugOrId) {
1997
- 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);
1998
2492
  }
1999
2493
  /** Uninstall a skill */
2000
2494
  async uninstallSkill(slugOrId) {
@@ -2040,30 +2534,30 @@ var EvolutionClient = class {
2040
2534
  }
2041
2535
  const localPaths = [];
2042
2536
  try {
2043
- const fs2 = await import("fs");
2044
- const path2 = await import("path");
2045
- const os2 = await import("os");
2046
- const home = os2.homedir();
2047
- 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");
2048
2542
  const platformPaths = options?.project ? {
2049
- "claude-code": path2.join(options.projectRoot || ".", ".claude", "skills", slug),
2050
- "openclaw": path2.join(options.projectRoot || ".", "skills", slug),
2051
- "opencode": path2.join(options.projectRoot || ".", ".opencode", "skills", slug),
2052
- "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)
2053
2547
  } : {
2054
- "claude-code": path2.join(home, ".claude", "skills", slug),
2055
- "openclaw": path2.join(home, ".openclaw", "skills", slug),
2056
- "opencode": path2.join(home, ".config", "opencode", "skills", slug),
2057
- "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)
2058
2552
  };
2059
2553
  const targets = options?.platforms || Object.keys(platformPaths);
2060
2554
  for (const platform of targets) {
2061
2555
  const dir = platformPaths[platform];
2062
2556
  if (!dir) continue;
2063
2557
  try {
2064
- fs2.mkdirSync(dir, { recursive: true });
2065
- const filePath = path2.join(dir, "SKILL.md");
2066
- 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");
2067
2561
  localPaths.push(filePath);
2068
2562
  } catch {
2069
2563
  }
@@ -2081,21 +2575,21 @@ var EvolutionClient = class {
2081
2575
  const slug = safeSlug(slugOrId);
2082
2576
  if (!slug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
2083
2577
  try {
2084
- const fs2 = await import("fs");
2085
- const path2 = await import("path");
2086
- const os2 = await import("os");
2087
- const home = os2.homedir();
2088
- 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");
2089
2583
  const dirs = [
2090
- path2.join(home, ".claude", "skills", slug),
2091
- path2.join(home, ".openclaw", "skills", slug),
2092
- path2.join(home, ".config", "opencode", "skills", slug),
2093
- path2.join(pluginBase, "skills", slug)
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)
2094
2588
  ];
2095
2589
  for (const dir of dirs) {
2096
2590
  try {
2097
- if (fs2.existsSync(dir)) {
2098
- fs2.rmSync(dir, { recursive: true });
2591
+ if (fs3.existsSync(dir)) {
2592
+ fs3.rmSync(dir, { recursive: true });
2099
2593
  removedPaths.push(dir);
2100
2594
  }
2101
2595
  } catch {
@@ -2132,25 +2626,25 @@ var EvolutionClient = class {
2132
2626
  failed++;
2133
2627
  continue;
2134
2628
  }
2135
- const fs2 = await import("fs");
2136
- const path2 = await import("path");
2137
- const os2 = await import("os");
2138
- const home = os2.homedir();
2139
- 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");
2140
2634
  const platformPaths = {
2141
- "claude-code": path2.join(home, ".claude", "skills", slug),
2142
- "openclaw": path2.join(home, ".openclaw", "skills", slug),
2143
- "opencode": path2.join(home, ".config", "opencode", "skills", slug),
2144
- "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)
2145
2639
  };
2146
2640
  const targets = options?.platforms || Object.keys(platformPaths);
2147
2641
  for (const platform of targets) {
2148
2642
  const dir = platformPaths[platform];
2149
2643
  if (!dir) continue;
2150
2644
  try {
2151
- fs2.mkdirSync(dir, { recursive: true });
2152
- const filePath = path2.join(dir, "SKILL.md");
2153
- 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");
2154
2648
  paths.push(filePath);
2155
2649
  } catch {
2156
2650
  }
@@ -2290,11 +2784,11 @@ var FilesClient = class {
2290
2784
  let bytes;
2291
2785
  let fileName;
2292
2786
  if (typeof input === "string") {
2293
- const fs2 = await import("fs");
2294
- const path2 = await import("path");
2295
- 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);
2296
2790
  bytes = new Uint8Array(buf);
2297
- fileName = opts?.fileName || path2.basename(input);
2791
+ fileName = opts?.fileName || path3.basename(input);
2298
2792
  } else if (typeof Blob !== "undefined" && input instanceof Blob) {
2299
2793
  const ab = await input.arrayBuffer();
2300
2794
  bytes = new Uint8Array(ab);
@@ -2431,22 +2925,24 @@ var IMRealtimeClient = class {
2431
2925
  }
2432
2926
  };
2433
2927
  var IMClient = class {
2434
- constructor(request, wsBase, fetchFn, getAuthHeaders, offlineManager) {
2435
- this.account = new AccountClient(request);
2436
- this.direct = new DirectClient(request);
2437
- this.groups = new GroupsClient(request);
2438
- this.conversations = new ConversationsClient(request);
2439
- this.messages = new MessagesClient(request);
2440
- this.contacts = new ContactsClient(request);
2441
- this.bindings = new BindingsClient(request);
2442
- this.credits = new CreditsClient(request);
2443
- this.workspace = new WorkspaceClient(request);
2444
- this.tasks = new TasksClient(request);
2445
- this.memory = new MemoryClient(request);
2446
- this.identity = new IdentityClient(request);
2447
- this.security = new SecurityClient(request);
2448
- this.evolution = new EvolutionClient(request);
2449
- 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);
2450
2946
  this.realtime = new IMRealtimeClient(wsBase);
2451
2947
  this.offline = offlineManager ?? null;
2452
2948
  }
@@ -2454,19 +2950,43 @@ var IMClient = class {
2454
2950
  async health() {
2455
2951
  return this.account["_r"]("GET", "/api/im/health");
2456
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
+ }
2457
2961
  };
2458
2962
  var PrismerClient = class {
2459
2963
  constructor(config = {}) {
2460
2964
  this._offlineManager = null;
2461
- 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")) {
2462
2970
  console.warn('Warning: API key should start with "sk-prismer-" (or "eyJ" for IM JWT)');
2463
2971
  }
2464
- this.apiKey = config.apiKey || "";
2972
+ this.apiKey = resolvedApiKey;
2465
2973
  const envUrl = ENVIRONMENTS[config.environment || "production"];
2466
- this.baseUrl = (config.baseUrl || envUrl).replace(/\/$/, "");
2974
+ this.baseUrl = (resolveBaseUrl(config.baseUrl) || envUrl).replace(/\/$/, "");
2467
2975
  this.timeout = config.timeout || 3e4;
2468
2976
  this.fetchFn = config.fetch || fetch;
2469
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
+ }
2470
2990
  if (config.offline) {
2471
2991
  this._offlineManager = new OfflineManager(
2472
2992
  config.offline.storage,
@@ -2477,15 +2997,61 @@ var PrismerClient = class {
2477
2997
  (err) => console.warn("[PrismerSDK] Offline storage init failed:", err)
2478
2998
  );
2479
2999
  }
2480
- 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
+ }
2481
3019
  this.im = new IMClient(
2482
3020
  imRequest,
2483
3021
  this.baseUrl,
2484
3022
  this.fetchFn,
2485
3023
  () => this._getAuthHeaders(),
2486
- this._offlineManager
3024
+ this._offlineManager,
3025
+ config.community ?? null
2487
3026
  );
2488
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
+ }
2489
3055
  /** Build auth headers for raw HTTP requests (used by file upload) */
2490
3056
  _getAuthHeaders() {
2491
3057
  const headers = {};
@@ -2509,11 +3075,11 @@ var PrismerClient = class {
2509
3075
  // --------------------------------------------------------------------------
2510
3076
  // Internal request helper
2511
3077
  // --------------------------------------------------------------------------
2512
- async _request(method, path2, body, query, _isRetry) {
3078
+ async _request(method, path3, body, query, _isRetry) {
2513
3079
  const controller = new AbortController();
2514
3080
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2515
3081
  try {
2516
- let url = `${this.baseUrl}${path2}`;
3082
+ let url = `${this.baseUrl}${path3}`;
2517
3083
  if (query && Object.keys(query).length > 0) {
2518
3084
  url += "?" + new URLSearchParams(query).toString();
2519
3085
  }
@@ -2531,12 +3097,12 @@ var PrismerClient = class {
2531
3097
  }
2532
3098
  const response = await this.fetchFn(url, init);
2533
3099
  const data = await response.json();
2534
- 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")) {
2535
3101
  try {
2536
3102
  const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, true);
2537
3103
  if (refreshRes?.ok && refreshRes?.data?.token) {
2538
3104
  this.apiKey = refreshRes.data.token;
2539
- return this._request(method, path2, body, query, true);
3105
+ return this._request(method, path3, body, query, true);
2540
3106
  }
2541
3107
  } catch {
2542
3108
  }
@@ -4208,7 +4774,7 @@ function printFileTable(files) {
4208
4774
  const idLen = Math.max(2, ...files.map((f) => f.id.length));
4209
4775
  const scopeLen = Math.max(5, ...files.map((f) => f.scope.length));
4210
4776
  const pathLen = Math.max(4, ...files.map((f) => f.path.length));
4211
- 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)}`;
4212
4778
  process.stdout.write(row("ID", "SCOPE", "PATH") + "\n");
4213
4779
  process.stdout.write(`${"-".repeat(idLen)} ${"-".repeat(scopeLen)} ${"-".repeat(pathLen)}
4214
4780
  `);
@@ -4965,29 +5531,665 @@ function register9(parent, getIMClient2, _getAPIClient) {
4965
5531
  });
4966
5532
  }
4967
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
+
4968
6170
  // src/cli.ts
4969
6171
  var cliVersion = "1.7.2";
4970
6172
  try {
4971
- const pkgPath = path.join(__dirname, "..", "package.json");
4972
- 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"));
4973
6175
  cliVersion = pkg.version || cliVersion;
4974
6176
  } catch {
4975
6177
  }
4976
- var CONFIG_DIR = path.join(os.homedir(), ".prismer");
4977
- 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");
4978
6180
  function ensureConfigDir() {
4979
- if (!fs.existsSync(CONFIG_DIR)) {
4980
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
6181
+ if (!fs2.existsSync(CONFIG_DIR2)) {
6182
+ fs2.mkdirSync(CONFIG_DIR2, { recursive: true });
4981
6183
  }
4982
6184
  }
4983
6185
  function readConfig() {
4984
- if (!fs.existsSync(CONFIG_PATH)) return {};
4985
- const raw = fs.readFileSync(CONFIG_PATH, "utf-8");
4986
- 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);
4987
6189
  }
4988
6190
  function writeConfig(config) {
4989
6191
  ensureConfigDir();
4990
- fs.writeFileSync(CONFIG_PATH, TOML.stringify(config), { encoding: "utf-8", mode: 384 });
6192
+ fs2.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
4991
6193
  }
4992
6194
  function setNestedValue(obj, dotPath, value) {
4993
6195
  const parts = dotPath.split(".");
@@ -5054,6 +6256,11 @@ async function verifyAndSaveKey(config, apiKey) {
5054
6256
  console.log("");
5055
6257
  console.log("Saved to ~/.prismer/config.toml");
5056
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
+ }
5057
6264
  }
5058
6265
  function openBrowser(url) {
5059
6266
  const { execFile } = require("child_process");
@@ -5138,11 +6345,11 @@ async function runSetup(opts, apiKey) {
5138
6345
  });
5139
6346
  return;
5140
6347
  }
5141
- const http = require("http");
6348
+ const http2 = require("http");
5142
6349
  const crypto2 = require("crypto");
5143
6350
  const state = crypto2.randomBytes(16).toString("hex");
5144
6351
  let resolved = false;
5145
- const server = http.createServer((req, res) => {
6352
+ const server = http2.createServer((req, res) => {
5146
6353
  const url = new URL(req.url, `http://localhost`);
5147
6354
  if (url.pathname === "/callback") {
5148
6355
  const key = url.searchParams.get("key");
@@ -5304,11 +6511,11 @@ program.command("status").description("Show current config and live info").actio
5304
6511
  });
5305
6512
  var configCmd = program.command("config").description("Manage config file");
5306
6513
  configCmd.command("show").description("Print config file").action(() => {
5307
- if (!fs.existsSync(CONFIG_PATH)) {
6514
+ if (!fs2.existsSync(CONFIG_PATH2)) {
5308
6515
  console.log('No config file. Run "prismer setup" to create one.');
5309
6516
  return;
5310
6517
  }
5311
- console.log(fs.readFileSync(CONFIG_PATH, "utf-8"));
6518
+ console.log(fs2.readFileSync(CONFIG_PATH2, "utf-8"));
5312
6519
  });
5313
6520
  configCmd.command("set <key> <value>").description("Set a config value (e.g. default.base_url)").action((key, value) => {
5314
6521
  const config = readConfig();
@@ -5349,6 +6556,7 @@ register6(program, getIMClient, getAPIClient);
5349
6556
  register7(program, getIMClient, getAPIClient);
5350
6557
  register8(program, getIMClient, getAPIClient);
5351
6558
  register9(program, getIMClient, getAPIClient);
6559
+ register10(program, getIMClient, getAPIClient);
5352
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) => {
5353
6561
  const client = getIMClient();
5354
6562
  const sendOpts = {};
@@ -5511,6 +6719,28 @@ program.command("discover").description("Discover available agents (shortcut for
5511
6719
  console.log(`${(a.username || "").padEnd(20)}${(a.agentType || "").padEnd(14)}${(a.status || "").padEnd(10)}${a.displayName || ""}`);
5512
6720
  }
5513
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
+ });
5514
6744
  program.parse(process.argv);
5515
6745
  // Annotate the CommonJS export names for ESM import in node:
5516
6746
  0 && (module.exports = {