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