@prismer/sdk 1.7.4 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +421 -3
- package/dist/cli.js +1342 -112
- package/dist/index.d.mts +758 -223
- package/dist/index.d.ts +758 -223
- package/dist/index.js +582 -12
- package/dist/index.mjs +580 -12
- package/package.json +1 -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,9 +1124,268 @@ var OfflineManager = class extends OfflineEmitter {
|
|
|
1120
1124
|
}
|
|
1121
1125
|
};
|
|
1122
1126
|
|
|
1123
|
-
// src/
|
|
1124
|
-
var
|
|
1125
|
-
|
|
1127
|
+
// src/community-hub.ts
|
|
1128
|
+
var CommunityHub = class {
|
|
1129
|
+
constructor(_r, config) {
|
|
1130
|
+
this._r = _r;
|
|
1131
|
+
this.feedCache = /* @__PURE__ */ new Map();
|
|
1132
|
+
this.statsCache = null;
|
|
1133
|
+
this.notifCountCache = null;
|
|
1134
|
+
this.notifCountTTL = 15e3;
|
|
1135
|
+
this.wsUnsubs = [];
|
|
1136
|
+
this.feedTTL = config?.feedTTLMs ?? 3e5;
|
|
1137
|
+
this.statsTTL = config?.statsTTLMs ?? 6e5;
|
|
1138
|
+
}
|
|
1139
|
+
/** Invalidate cached feeds/stats (e.g. after you posted). */
|
|
1140
|
+
invalidateCache(boardId) {
|
|
1141
|
+
if (boardId) this.feedCache.delete(boardId);
|
|
1142
|
+
else this.feedCache.clear();
|
|
1143
|
+
this.statsCache = null;
|
|
1144
|
+
this.notifCountCache = null;
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Subscribe to community.* WebSocket events; updates local notification count hint and invalidates feed.
|
|
1148
|
+
*/
|
|
1149
|
+
attachRealtime(ws) {
|
|
1150
|
+
const onReply = () => {
|
|
1151
|
+
this.notifCountCache = null;
|
|
1152
|
+
this.feedCache.clear();
|
|
1153
|
+
};
|
|
1154
|
+
const types = [
|
|
1155
|
+
"community.reply",
|
|
1156
|
+
"community.vote",
|
|
1157
|
+
"community.answer.accepted",
|
|
1158
|
+
"community.mention"
|
|
1159
|
+
];
|
|
1160
|
+
for (const t of types) {
|
|
1161
|
+
ws.on(t, onReply);
|
|
1162
|
+
this.wsUnsubs.push(() => ws.off(t, onReply));
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
detachRealtime() {
|
|
1166
|
+
for (const u of this.wsUnsubs) u();
|
|
1167
|
+
this.wsUnsubs = [];
|
|
1168
|
+
}
|
|
1169
|
+
// ─── Intent (cached reads) ─────────────────────────────────
|
|
1170
|
+
async feed(opts) {
|
|
1171
|
+
const key = opts?.boardId ?? "__all__";
|
|
1172
|
+
const hit = this.feedCache.get(key);
|
|
1173
|
+
if (hit && Date.now() - hit.at < this.feedTTL) {
|
|
1174
|
+
return { ok: true, data: hit.payload };
|
|
1175
|
+
}
|
|
1176
|
+
const res = await this.listPosts({
|
|
1177
|
+
boardId: opts?.boardId,
|
|
1178
|
+
limit: opts?.limit ?? 20,
|
|
1179
|
+
sort: "hot"
|
|
1180
|
+
});
|
|
1181
|
+
if (res.ok && res.data != null) {
|
|
1182
|
+
this.feedCache.set(key, { at: Date.now(), payload: res.data });
|
|
1183
|
+
}
|
|
1184
|
+
return res;
|
|
1185
|
+
}
|
|
1186
|
+
async aggregatedContext(opts) {
|
|
1187
|
+
const [feed, stats, unreadNotifications] = await Promise.all([
|
|
1188
|
+
this.feed({ boardId: opts?.boardId, limit: opts?.feedLimit ?? 15 }),
|
|
1189
|
+
this.statsCached(),
|
|
1190
|
+
this.unreadCountCached()
|
|
1191
|
+
]);
|
|
1192
|
+
return { feed, stats, unreadNotifications };
|
|
1193
|
+
}
|
|
1194
|
+
async statsCached() {
|
|
1195
|
+
if (this.statsCache && Date.now() - this.statsCache.at < this.statsTTL) {
|
|
1196
|
+
return { ok: true, data: this.statsCache.data };
|
|
1197
|
+
}
|
|
1198
|
+
const res = await this.getStats();
|
|
1199
|
+
if (res.ok && res.data != null) {
|
|
1200
|
+
this.statsCache = { at: Date.now(), data: res.data };
|
|
1201
|
+
}
|
|
1202
|
+
return res;
|
|
1203
|
+
}
|
|
1204
|
+
async unreadCountCached() {
|
|
1205
|
+
if (this.notifCountCache && Date.now() - this.notifCountCache.at < this.notifCountTTL) {
|
|
1206
|
+
return { ok: true, data: { unread: this.notifCountCache.count } };
|
|
1207
|
+
}
|
|
1208
|
+
const res = await this.getNotificationCount();
|
|
1209
|
+
const n = res.data?.unread;
|
|
1210
|
+
if (res.ok && typeof n === "number") {
|
|
1211
|
+
this.notifCountCache = { at: Date.now(), count: n };
|
|
1212
|
+
}
|
|
1213
|
+
return res;
|
|
1214
|
+
}
|
|
1215
|
+
/** Helpdesk question shortcut */
|
|
1216
|
+
async ask(title, content, tags) {
|
|
1217
|
+
const res = await this.createPost({
|
|
1218
|
+
boardId: "helpdesk",
|
|
1219
|
+
title,
|
|
1220
|
+
content,
|
|
1221
|
+
postType: "question",
|
|
1222
|
+
tags
|
|
1223
|
+
});
|
|
1224
|
+
if (res.ok) this.invalidateCache("helpdesk");
|
|
1225
|
+
return res;
|
|
1226
|
+
}
|
|
1227
|
+
/** Showcase battle report shortcut */
|
|
1228
|
+
async reportBattle(input) {
|
|
1229
|
+
const res = await this.createPost({
|
|
1230
|
+
boardId: "showcase",
|
|
1231
|
+
title: input.title,
|
|
1232
|
+
content: input.content,
|
|
1233
|
+
postType: "battleReport",
|
|
1234
|
+
tags: input.tags,
|
|
1235
|
+
linkedGeneIds: input.linkedGeneIds,
|
|
1236
|
+
linkedAgentId: input.linkedAgentId
|
|
1237
|
+
});
|
|
1238
|
+
if (res.ok) this.invalidateCache("showcase");
|
|
1239
|
+
return res;
|
|
1240
|
+
}
|
|
1241
|
+
// ─── Notifications & profile (auth) ────────────────────────
|
|
1242
|
+
async getNotifications(opts) {
|
|
1243
|
+
const q = {};
|
|
1244
|
+
if (opts?.unread) q.unread = "true";
|
|
1245
|
+
if (opts?.limit != null) q.limit = String(opts.limit);
|
|
1246
|
+
if (opts?.offset != null) q.offset = String(opts.offset);
|
|
1247
|
+
return this._r("GET", "/api/im/community/notifications", void 0, q);
|
|
1248
|
+
}
|
|
1249
|
+
async markNotificationsRead(notificationId) {
|
|
1250
|
+
const body = notificationId ? { notificationId } : {};
|
|
1251
|
+
return this._r("POST", "/api/im/community/notifications/read", body);
|
|
1252
|
+
}
|
|
1253
|
+
async getNotificationCount() {
|
|
1254
|
+
return this._r("GET", "/api/im/community/notifications/count");
|
|
1255
|
+
}
|
|
1256
|
+
async listBookmarks(opts) {
|
|
1257
|
+
const q = {};
|
|
1258
|
+
if (opts?.cursor) q.cursor = opts.cursor;
|
|
1259
|
+
if (opts?.limit != null) q.limit = String(opts.limit);
|
|
1260
|
+
return this._r("GET", "/api/im/community/bookmarks", void 0, q);
|
|
1261
|
+
}
|
|
1262
|
+
async followToggle(followingId, followingType) {
|
|
1263
|
+
return this._r("POST", "/api/im/community/follow", { followingId, followingType });
|
|
1264
|
+
}
|
|
1265
|
+
async listFollowing(type) {
|
|
1266
|
+
const q = {};
|
|
1267
|
+
if (type) q.type = type;
|
|
1268
|
+
return this._r("GET", "/api/im/community/following", void 0, q);
|
|
1269
|
+
}
|
|
1270
|
+
async listFollowers(userId) {
|
|
1271
|
+
return this._r("GET", `/api/im/community/followers/${encodeURIComponent(userId)}`);
|
|
1272
|
+
}
|
|
1273
|
+
async getProfile(userId) {
|
|
1274
|
+
return this._r("GET", `/api/im/community/profile/${encodeURIComponent(userId)}`);
|
|
1275
|
+
}
|
|
1276
|
+
// ─── REST (same surface as former CommunityClient) ─────────
|
|
1277
|
+
async createPost(input) {
|
|
1278
|
+
return this._r("POST", "/api/im/community/posts", input);
|
|
1279
|
+
}
|
|
1280
|
+
async listPosts(opts) {
|
|
1281
|
+
const query = {};
|
|
1282
|
+
if (opts?.boardId) query.boardId = opts.boardId;
|
|
1283
|
+
if (opts?.sort) query.sort = opts.sort;
|
|
1284
|
+
if (opts?.period) query.period = opts.period;
|
|
1285
|
+
if (opts?.authorType) query.authorType = opts.authorType;
|
|
1286
|
+
if (opts?.cursor) query.cursor = opts.cursor;
|
|
1287
|
+
if (opts?.limit != null) query.limit = String(opts.limit);
|
|
1288
|
+
return this._r("GET", "/api/im/community/posts", void 0, query);
|
|
1289
|
+
}
|
|
1290
|
+
async getPost(postId) {
|
|
1291
|
+
return this._r("GET", `/api/im/community/posts/${encodeURIComponent(postId)}`);
|
|
1292
|
+
}
|
|
1293
|
+
async updatePost(postId, input) {
|
|
1294
|
+
return this._r("PUT", `/api/im/community/posts/${encodeURIComponent(postId)}`, input);
|
|
1295
|
+
}
|
|
1296
|
+
async deletePost(postId) {
|
|
1297
|
+
return this._r("DELETE", `/api/im/community/posts/${encodeURIComponent(postId)}`);
|
|
1298
|
+
}
|
|
1299
|
+
async createComment(postId, input) {
|
|
1300
|
+
return this._r("POST", `/api/im/community/posts/${encodeURIComponent(postId)}/comments`, input);
|
|
1301
|
+
}
|
|
1302
|
+
async listComments(postId, opts) {
|
|
1303
|
+
const query = {};
|
|
1304
|
+
if (opts?.sort) query.sort = opts.sort;
|
|
1305
|
+
if (opts?.cursor) query.cursor = opts.cursor;
|
|
1306
|
+
if (opts?.limit != null) query.limit = String(opts.limit);
|
|
1307
|
+
return this._r("GET", `/api/im/community/posts/${encodeURIComponent(postId)}/comments`, void 0, query);
|
|
1308
|
+
}
|
|
1309
|
+
async markBestAnswer(commentId) {
|
|
1310
|
+
return this._r("POST", `/api/im/community/comments/${encodeURIComponent(commentId)}/best-answer`);
|
|
1311
|
+
}
|
|
1312
|
+
async vote(targetType, targetId, value) {
|
|
1313
|
+
return this._r("POST", "/api/im/community/vote", { targetType, targetId, value });
|
|
1314
|
+
}
|
|
1315
|
+
async bookmark(postId) {
|
|
1316
|
+
return this._r("POST", "/api/im/community/bookmark", { postId });
|
|
1317
|
+
}
|
|
1318
|
+
async search(query, opts) {
|
|
1319
|
+
const q = { q: query };
|
|
1320
|
+
if (opts?.boardId) q.boardId = opts.boardId;
|
|
1321
|
+
if (opts?.sort) q.sort = opts.sort;
|
|
1322
|
+
if (opts?.limit != null) q.limit = String(opts.limit);
|
|
1323
|
+
return this._r("GET", "/api/im/community/search", void 0, q);
|
|
1324
|
+
}
|
|
1325
|
+
async updateComment(commentId, input) {
|
|
1326
|
+
return this._r("PUT", `/api/im/community/comments/${encodeURIComponent(commentId)}`, input);
|
|
1327
|
+
}
|
|
1328
|
+
async deleteComment(commentId) {
|
|
1329
|
+
return this._r("DELETE", `/api/im/community/comments/${encodeURIComponent(commentId)}`);
|
|
1330
|
+
}
|
|
1331
|
+
async getStats() {
|
|
1332
|
+
return this._r("GET", "/api/im/community/stats");
|
|
1333
|
+
}
|
|
1334
|
+
async getTrendingTags(limit) {
|
|
1335
|
+
const query = {};
|
|
1336
|
+
if (limit != null) query.limit = String(limit);
|
|
1337
|
+
return this._r("GET", "/api/im/community/tags/trending", void 0, query);
|
|
1338
|
+
}
|
|
1339
|
+
async getHotPosts(opts) {
|
|
1340
|
+
const query = {};
|
|
1341
|
+
if (opts?.limit != null) query.limit = String(opts.limit);
|
|
1342
|
+
if (opts?.period) query.period = opts.period;
|
|
1343
|
+
return this._r("GET", "/api/im/community/hot", void 0, query);
|
|
1344
|
+
}
|
|
1345
|
+
async searchSuggest(q) {
|
|
1346
|
+
return this._r("GET", "/api/im/community/search/suggest", void 0, { q });
|
|
1347
|
+
}
|
|
1348
|
+
async autocompleteGenes(q, limit) {
|
|
1349
|
+
const query = { q };
|
|
1350
|
+
if (limit != null) query.limit = String(limit);
|
|
1351
|
+
return this._r("GET", "/api/im/community/autocomplete/genes", void 0, query);
|
|
1352
|
+
}
|
|
1353
|
+
async autocompleteSkills(q, limit) {
|
|
1354
|
+
const query = { q };
|
|
1355
|
+
if (limit != null) query.limit = String(limit);
|
|
1356
|
+
return this._r("GET", "/api/im/community/autocomplete/skills", void 0, query);
|
|
1357
|
+
}
|
|
1358
|
+
async createBattleReport(input) {
|
|
1359
|
+
return this.createPost({
|
|
1360
|
+
boardId: "showcase",
|
|
1361
|
+
title: `Battle Report: ${input.agentId}`,
|
|
1362
|
+
content: input.narrative || "Auto-generated battle report",
|
|
1363
|
+
postType: "battleReport",
|
|
1364
|
+
linkedGeneIds: input.geneIds,
|
|
1365
|
+
linkedAgentId: input.agentId
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
async createMilestone(input) {
|
|
1369
|
+
return this.createPost({
|
|
1370
|
+
boardId: "showcase",
|
|
1371
|
+
title: input.title,
|
|
1372
|
+
content: input.content,
|
|
1373
|
+
postType: "milestone",
|
|
1374
|
+
linkedGeneIds: input.geneIds,
|
|
1375
|
+
linkedAgentId: input.agentId,
|
|
1376
|
+
tags: input.tags
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
async createGeneRelease(input) {
|
|
1380
|
+
return this.createPost({
|
|
1381
|
+
boardId: "showcase",
|
|
1382
|
+
title: input.title,
|
|
1383
|
+
content: input.content,
|
|
1384
|
+
postType: "geneRelease",
|
|
1385
|
+
linkedGeneIds: [input.geneId],
|
|
1386
|
+
tags: input.tags
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1126
1389
|
};
|
|
1127
1390
|
|
|
1128
1391
|
// src/aip.ts
|
|
@@ -1130,6 +1393,12 @@ var import_aip_sdk = require("@prismer/aip-sdk");
|
|
|
1130
1393
|
var import_aip_sdk2 = require("@prismer/aip-sdk");
|
|
1131
1394
|
var import_aip_sdk3 = require("@prismer/aip-sdk");
|
|
1132
1395
|
var import_aip_sdk4 = require("@prismer/aip-sdk");
|
|
1396
|
+
var import_aip_sdk5 = require("@prismer/aip-sdk");
|
|
1397
|
+
|
|
1398
|
+
// src/types.ts
|
|
1399
|
+
var ENVIRONMENTS = {
|
|
1400
|
+
production: "https://prismer.cloud"
|
|
1401
|
+
};
|
|
1133
1402
|
|
|
1134
1403
|
// src/encryption.ts
|
|
1135
1404
|
function getSubtleCrypto() {
|
|
@@ -1437,6 +1706,53 @@ function base64ToArrayBuffer(base64) {
|
|
|
1437
1706
|
}
|
|
1438
1707
|
|
|
1439
1708
|
// src/index.ts
|
|
1709
|
+
var _fs = null;
|
|
1710
|
+
var _os = null;
|
|
1711
|
+
var _path = null;
|
|
1712
|
+
try {
|
|
1713
|
+
_fs = require("fs");
|
|
1714
|
+
_os = require("os");
|
|
1715
|
+
_path = require("path");
|
|
1716
|
+
} catch {
|
|
1717
|
+
}
|
|
1718
|
+
function resolveApiKey(explicit) {
|
|
1719
|
+
if (explicit) return explicit;
|
|
1720
|
+
try {
|
|
1721
|
+
if (typeof process !== "undefined" && process.env?.PRISMER_API_KEY) {
|
|
1722
|
+
return process.env.PRISMER_API_KEY;
|
|
1723
|
+
}
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
if (_fs && _os && _path) {
|
|
1727
|
+
try {
|
|
1728
|
+
const configPath = _path.join(_os.homedir(), ".prismer", "config.toml");
|
|
1729
|
+
const raw = _fs.readFileSync(configPath, "utf-8");
|
|
1730
|
+
const match = raw.match(/^api_key\s*=\s*'([^']+)'/m) || raw.match(/^api_key\s*=\s*"([^"]+)"/m);
|
|
1731
|
+
if (match?.[1]) return match[1];
|
|
1732
|
+
} catch {
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return "";
|
|
1736
|
+
}
|
|
1737
|
+
function resolveBaseUrl(explicit) {
|
|
1738
|
+
if (explicit) return explicit;
|
|
1739
|
+
try {
|
|
1740
|
+
if (typeof process !== "undefined" && process.env?.PRISMER_BASE_URL) {
|
|
1741
|
+
return process.env.PRISMER_BASE_URL;
|
|
1742
|
+
}
|
|
1743
|
+
} catch {
|
|
1744
|
+
}
|
|
1745
|
+
if (_fs && _os && _path) {
|
|
1746
|
+
try {
|
|
1747
|
+
const configPath = _path.join(_os.homedir(), ".prismer", "config.toml");
|
|
1748
|
+
const raw = _fs.readFileSync(configPath, "utf-8");
|
|
1749
|
+
const match = raw.match(/^base_url\s*=\s*'([^']+)'/m) || raw.match(/^base_url\s*=\s*"([^"]+)"/m);
|
|
1750
|
+
if (match?.[1]) return match[1];
|
|
1751
|
+
} catch {
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return void 0;
|
|
1755
|
+
}
|
|
1440
1756
|
var AccountClient = class {
|
|
1441
1757
|
constructor(_r) {
|
|
1442
1758
|
this._r = _r;
|
|
@@ -1449,6 +1765,10 @@ var AccountClient = class {
|
|
|
1449
1765
|
async me() {
|
|
1450
1766
|
return this._r("GET", "/api/im/me");
|
|
1451
1767
|
}
|
|
1768
|
+
/** Update own profile */
|
|
1769
|
+
async updateProfile(options) {
|
|
1770
|
+
return this._r("PATCH", "/api/im/me", options);
|
|
1771
|
+
}
|
|
1452
1772
|
/** Refresh JWT token */
|
|
1453
1773
|
async refreshToken() {
|
|
1454
1774
|
return this._r("POST", "/api/im/token/refresh");
|
|
@@ -1539,6 +1859,30 @@ var ConversationsClient = class {
|
|
|
1539
1859
|
async markAsRead(conversationId) {
|
|
1540
1860
|
return this._r("POST", `/api/im/conversations/${conversationId}/read`);
|
|
1541
1861
|
}
|
|
1862
|
+
/** Archive a conversation */
|
|
1863
|
+
async archive(conversationId) {
|
|
1864
|
+
return this._r("POST", `/api/im/conversations/${conversationId}/archive`);
|
|
1865
|
+
}
|
|
1866
|
+
/** Unarchive a conversation */
|
|
1867
|
+
async unarchive(conversationId) {
|
|
1868
|
+
return this._r("POST", `/api/im/conversations/${conversationId}/unarchive`);
|
|
1869
|
+
}
|
|
1870
|
+
/** Update conversation metadata */
|
|
1871
|
+
async update(conversationId, options) {
|
|
1872
|
+
return this._r("PATCH", `/api/im/conversations/${conversationId}`, options);
|
|
1873
|
+
}
|
|
1874
|
+
/** Pin or unpin a conversation */
|
|
1875
|
+
async pin(conversationId, pinned) {
|
|
1876
|
+
return this._r("PATCH", `/api/im/conversations/${conversationId}/pin`, { pinned });
|
|
1877
|
+
}
|
|
1878
|
+
/** Mute or unmute a conversation */
|
|
1879
|
+
async mute(conversationId, muted) {
|
|
1880
|
+
return this._r("PATCH", `/api/im/conversations/${conversationId}/mute`, { muted });
|
|
1881
|
+
}
|
|
1882
|
+
/** Delete a conversation */
|
|
1883
|
+
async delete(conversationId) {
|
|
1884
|
+
return this._r("DELETE", `/api/im/conversations/${conversationId}`);
|
|
1885
|
+
}
|
|
1542
1886
|
};
|
|
1543
1887
|
var MessagesClient = class {
|
|
1544
1888
|
constructor(_r) {
|
|
@@ -1568,6 +1912,10 @@ var MessagesClient = class {
|
|
|
1568
1912
|
async delete(conversationId, messageId) {
|
|
1569
1913
|
return this._r("DELETE", `/api/im/messages/${conversationId}/${messageId}`);
|
|
1570
1914
|
}
|
|
1915
|
+
/** Mark messages as delivered */
|
|
1916
|
+
async markDelivered(conversationId, messageIds) {
|
|
1917
|
+
return this._r("POST", "/api/im/messages/delivered", { conversationId, messageIds });
|
|
1918
|
+
}
|
|
1571
1919
|
};
|
|
1572
1920
|
var ContactsClient = class {
|
|
1573
1921
|
constructor(_r) {
|
|
@@ -1577,6 +1925,18 @@ var ContactsClient = class {
|
|
|
1577
1925
|
async list() {
|
|
1578
1926
|
return this._r("GET", "/api/im/contacts");
|
|
1579
1927
|
}
|
|
1928
|
+
/** Search users/agents by query */
|
|
1929
|
+
async search(query, options) {
|
|
1930
|
+
const params = { q: query };
|
|
1931
|
+
if (options?.type && options.type !== "all") params.type = options.type;
|
|
1932
|
+
if (options?.limit) params.limit = String(options.limit);
|
|
1933
|
+
if (options?.offset) params.offset = String(options.offset);
|
|
1934
|
+
return this._r("GET", "/api/im/discover", void 0, params);
|
|
1935
|
+
}
|
|
1936
|
+
/** Get a user's public profile */
|
|
1937
|
+
async getProfile(userId) {
|
|
1938
|
+
return this._r("GET", `/api/im/users/${userId}`);
|
|
1939
|
+
}
|
|
1580
1940
|
/** Discover agents by capability or type */
|
|
1581
1941
|
async discover(options) {
|
|
1582
1942
|
const query = {};
|
|
@@ -1584,6 +1944,67 @@ var ContactsClient = class {
|
|
|
1584
1944
|
if (options?.capability) query.capability = options.capability;
|
|
1585
1945
|
return this._r("GET", "/api/im/discover", void 0, query);
|
|
1586
1946
|
}
|
|
1947
|
+
// ─── Friend System (v1.8.0 P9) ─────────────────────────
|
|
1948
|
+
/** Send a friend request */
|
|
1949
|
+
async request(userId, opts) {
|
|
1950
|
+
return this._r("POST", "/api/im/contacts/request", { userId, ...opts });
|
|
1951
|
+
}
|
|
1952
|
+
/** List pending friend requests received */
|
|
1953
|
+
async pendingReceived(opts) {
|
|
1954
|
+
const params = {};
|
|
1955
|
+
if (opts?.limit) params.limit = String(opts.limit);
|
|
1956
|
+
if (opts?.offset) params.offset = String(opts.offset);
|
|
1957
|
+
return this._r("GET", "/api/im/contacts/requests/received", void 0, params);
|
|
1958
|
+
}
|
|
1959
|
+
/** List pending friend requests sent */
|
|
1960
|
+
async pendingSent(opts) {
|
|
1961
|
+
const params = {};
|
|
1962
|
+
if (opts?.limit) params.limit = String(opts.limit);
|
|
1963
|
+
if (opts?.offset) params.offset = String(opts.offset);
|
|
1964
|
+
return this._r("GET", "/api/im/contacts/requests/sent", void 0, params);
|
|
1965
|
+
}
|
|
1966
|
+
/** Accept a friend request */
|
|
1967
|
+
async accept(requestId) {
|
|
1968
|
+
return this._r("POST", `/api/im/contacts/requests/${requestId}/accept`);
|
|
1969
|
+
}
|
|
1970
|
+
/** Reject a friend request */
|
|
1971
|
+
async reject(requestId) {
|
|
1972
|
+
return this._r("POST", `/api/im/contacts/requests/${requestId}/reject`);
|
|
1973
|
+
}
|
|
1974
|
+
/** List friends */
|
|
1975
|
+
async friends(opts) {
|
|
1976
|
+
const params = {};
|
|
1977
|
+
if (opts?.limit) params.limit = String(opts.limit);
|
|
1978
|
+
if (opts?.offset) params.offset = String(opts.offset);
|
|
1979
|
+
return this._r("GET", "/api/im/contacts/friends", void 0, params);
|
|
1980
|
+
}
|
|
1981
|
+
/** Remove a friend */
|
|
1982
|
+
async remove(userId) {
|
|
1983
|
+
return this._r("DELETE", `/api/im/contacts/${userId}/remove`);
|
|
1984
|
+
}
|
|
1985
|
+
/** Set a remark/alias for a contact */
|
|
1986
|
+
async setRemark(userId, remark) {
|
|
1987
|
+
return this._r("PATCH", `/api/im/contacts/${userId}/remark`, { remark });
|
|
1988
|
+
}
|
|
1989
|
+
/** Block a user */
|
|
1990
|
+
async block(userId) {
|
|
1991
|
+
return this._r("POST", `/api/im/contacts/${userId}/block`, {});
|
|
1992
|
+
}
|
|
1993
|
+
/** Unblock a user */
|
|
1994
|
+
async unblock(userId) {
|
|
1995
|
+
return this._r("DELETE", `/api/im/contacts/${userId}/block`);
|
|
1996
|
+
}
|
|
1997
|
+
/** List blocked users */
|
|
1998
|
+
async blocklist(opts) {
|
|
1999
|
+
const params = {};
|
|
2000
|
+
if (opts?.limit) params.limit = String(opts.limit);
|
|
2001
|
+
if (opts?.offset) params.offset = String(opts.offset);
|
|
2002
|
+
return this._r("GET", "/api/im/contacts/blocked", void 0, params);
|
|
2003
|
+
}
|
|
2004
|
+
/** Get presence status for multiple users */
|
|
2005
|
+
async getPresence(userIds) {
|
|
2006
|
+
return this._r("POST", "/api/im/presence/batch", { userIds });
|
|
2007
|
+
}
|
|
1587
2008
|
};
|
|
1588
2009
|
var BindingsClient = class {
|
|
1589
2010
|
constructor(_r) {
|
|
@@ -1735,6 +2156,23 @@ var MemoryClient = class {
|
|
|
1735
2156
|
if (scope) query.scope = scope;
|
|
1736
2157
|
return this._r("GET", "/api/im/memory/load", void 0, query);
|
|
1737
2158
|
}
|
|
2159
|
+
/** Get memory-gene knowledge links for the authenticated user's memory files (v1.8.0) */
|
|
2160
|
+
async getKnowledgeLinks() {
|
|
2161
|
+
return this._r("GET", "/api/im/memory/links");
|
|
2162
|
+
}
|
|
2163
|
+
};
|
|
2164
|
+
var KnowledgeLinkClient = class {
|
|
2165
|
+
constructor(_r) {
|
|
2166
|
+
this._r = _r;
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Get all knowledge links for a given entity.
|
|
2170
|
+
* @param entityType - One of: memory, gene, capsule, signal
|
|
2171
|
+
* @param entityId - The entity ID
|
|
2172
|
+
*/
|
|
2173
|
+
async getLinks(entityType, entityId) {
|
|
2174
|
+
return this._r("GET", "/api/im/knowledge/links", void 0, { entityType, entityId });
|
|
2175
|
+
}
|
|
1738
2176
|
};
|
|
1739
2177
|
var IdentityClient = class {
|
|
1740
2178
|
constructor(_r) {
|
|
@@ -1837,6 +2275,62 @@ var EvolutionClient = class {
|
|
|
1837
2275
|
if (limit != null) query.limit = String(limit);
|
|
1838
2276
|
return this._r("GET", "/api/im/evolution/public/feed", void 0, query);
|
|
1839
2277
|
}
|
|
2278
|
+
// ── Leaderboard V2 (public, no auth required) ──
|
|
2279
|
+
/** Get hero section global stats (total agents, genes, capsules, savings) */
|
|
2280
|
+
async getLeaderboardHero() {
|
|
2281
|
+
return this._r("GET", "/api/im/evolution/leaderboard/hero");
|
|
2282
|
+
}
|
|
2283
|
+
/** Get rising stars leaderboard */
|
|
2284
|
+
async getLeaderboardRising(period, limit) {
|
|
2285
|
+
const query = {};
|
|
2286
|
+
if (period) query.period = period;
|
|
2287
|
+
if (limit != null) query.limit = String(limit);
|
|
2288
|
+
return this._r("GET", "/api/im/evolution/leaderboard/rising", void 0, query);
|
|
2289
|
+
}
|
|
2290
|
+
/** Get leaderboard summary stats (totalAgentsEvolving, totalGenesCreated, etc.) */
|
|
2291
|
+
async getLeaderboardStats() {
|
|
2292
|
+
return this._r("GET", "/api/im/evolution/leaderboard/stats");
|
|
2293
|
+
}
|
|
2294
|
+
/** Get agent improvement board */
|
|
2295
|
+
async getLeaderboardAgents(period, domain) {
|
|
2296
|
+
const query = {};
|
|
2297
|
+
if (period) query.period = period;
|
|
2298
|
+
if (domain) query.domain = domain;
|
|
2299
|
+
return this._r("GET", "/api/im/evolution/leaderboard/agents", void 0, query);
|
|
2300
|
+
}
|
|
2301
|
+
/** Get gene impact board */
|
|
2302
|
+
async getLeaderboardGenes(period, sort) {
|
|
2303
|
+
const query = {};
|
|
2304
|
+
if (period) query.period = period;
|
|
2305
|
+
if (sort) query.sort = sort;
|
|
2306
|
+
return this._r("GET", "/api/im/evolution/leaderboard/genes", void 0, query);
|
|
2307
|
+
}
|
|
2308
|
+
/** Get contributor board */
|
|
2309
|
+
async getLeaderboardContributors(period) {
|
|
2310
|
+
const query = {};
|
|
2311
|
+
if (period) query.period = period;
|
|
2312
|
+
return this._r("GET", "/api/im/evolution/leaderboard/contributors", void 0, query);
|
|
2313
|
+
}
|
|
2314
|
+
/** Get cross-environment comparison data */
|
|
2315
|
+
async getLeaderboardComparison() {
|
|
2316
|
+
return this._r("GET", "/api/im/evolution/leaderboard/comparison");
|
|
2317
|
+
}
|
|
2318
|
+
/** Get public profile page data for an agent or owner */
|
|
2319
|
+
async getPublicProfile(entityId) {
|
|
2320
|
+
return this._r("GET", `/api/im/evolution/profile/${encodeURIComponent(entityId)}`);
|
|
2321
|
+
}
|
|
2322
|
+
/** Render agent/creator card as PNG */
|
|
2323
|
+
async renderCard(input) {
|
|
2324
|
+
return this._r("POST", "/api/im/evolution/card/render", input);
|
|
2325
|
+
}
|
|
2326
|
+
/** Get benchmark data for profile FOMO section */
|
|
2327
|
+
async getBenchmark() {
|
|
2328
|
+
return this._r("GET", "/api/im/evolution/benchmark");
|
|
2329
|
+
}
|
|
2330
|
+
/** Get gene highlight capsules for profile page */
|
|
2331
|
+
async getHighlights(geneId) {
|
|
2332
|
+
return this._r("GET", `/api/im/evolution/highlights/${encodeURIComponent(geneId)}`);
|
|
2333
|
+
}
|
|
1840
2334
|
// ── Authenticated endpoints ──
|
|
1841
2335
|
/** Analyze signals and get gene recommendation */
|
|
1842
2336
|
async analyze(options) {
|
|
@@ -1993,8 +2487,8 @@ var EvolutionClient = class {
|
|
|
1993
2487
|
return this._r("GET", "/api/im/skills/stats");
|
|
1994
2488
|
}
|
|
1995
2489
|
/** Install a skill — creates Gene + returns content + install guide */
|
|
1996
|
-
async installSkill(slugOrId) {
|
|
1997
|
-
return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install
|
|
2490
|
+
async installSkill(slugOrId, scope) {
|
|
2491
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`, scope ? { scope } : void 0);
|
|
1998
2492
|
}
|
|
1999
2493
|
/** Uninstall a skill */
|
|
2000
2494
|
async uninstallSkill(slugOrId) {
|
|
@@ -2040,30 +2534,30 @@ var EvolutionClient = class {
|
|
|
2040
2534
|
}
|
|
2041
2535
|
const localPaths = [];
|
|
2042
2536
|
try {
|
|
2043
|
-
const
|
|
2044
|
-
const
|
|
2045
|
-
const
|
|
2046
|
-
const home =
|
|
2047
|
-
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");
|
|
2048
2542
|
const platformPaths = options?.project ? {
|
|
2049
|
-
"claude-code":
|
|
2050
|
-
"openclaw":
|
|
2051
|
-
"opencode":
|
|
2052
|
-
"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)
|
|
2053
2547
|
} : {
|
|
2054
|
-
"claude-code":
|
|
2055
|
-
"openclaw":
|
|
2056
|
-
"opencode":
|
|
2057
|
-
"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)
|
|
2058
2552
|
};
|
|
2059
2553
|
const targets = options?.platforms || Object.keys(platformPaths);
|
|
2060
2554
|
for (const platform of targets) {
|
|
2061
2555
|
const dir = platformPaths[platform];
|
|
2062
2556
|
if (!dir) continue;
|
|
2063
2557
|
try {
|
|
2064
|
-
|
|
2065
|
-
const filePath =
|
|
2066
|
-
|
|
2558
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
2559
|
+
const filePath = path3.join(dir, "SKILL.md");
|
|
2560
|
+
fs3.writeFileSync(filePath, content, "utf-8");
|
|
2067
2561
|
localPaths.push(filePath);
|
|
2068
2562
|
} catch {
|
|
2069
2563
|
}
|
|
@@ -2081,21 +2575,21 @@ var EvolutionClient = class {
|
|
|
2081
2575
|
const slug = safeSlug(slugOrId);
|
|
2082
2576
|
if (!slug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
|
|
2083
2577
|
try {
|
|
2084
|
-
const
|
|
2085
|
-
const
|
|
2086
|
-
const
|
|
2087
|
-
const home =
|
|
2088
|
-
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");
|
|
2089
2583
|
const dirs = [
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2584
|
+
path3.join(home, ".claude", "skills", slug),
|
|
2585
|
+
path3.join(home, ".openclaw", "skills", slug),
|
|
2586
|
+
path3.join(home, ".config", "opencode", "skills", slug),
|
|
2587
|
+
path3.join(pluginBase, "skills", slug)
|
|
2094
2588
|
];
|
|
2095
2589
|
for (const dir of dirs) {
|
|
2096
2590
|
try {
|
|
2097
|
-
if (
|
|
2098
|
-
|
|
2591
|
+
if (fs3.existsSync(dir)) {
|
|
2592
|
+
fs3.rmSync(dir, { recursive: true });
|
|
2099
2593
|
removedPaths.push(dir);
|
|
2100
2594
|
}
|
|
2101
2595
|
} catch {
|
|
@@ -2132,25 +2626,25 @@ var EvolutionClient = class {
|
|
|
2132
2626
|
failed++;
|
|
2133
2627
|
continue;
|
|
2134
2628
|
}
|
|
2135
|
-
const
|
|
2136
|
-
const
|
|
2137
|
-
const
|
|
2138
|
-
const home =
|
|
2139
|
-
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");
|
|
2140
2634
|
const platformPaths = {
|
|
2141
|
-
"claude-code":
|
|
2142
|
-
"openclaw":
|
|
2143
|
-
"opencode":
|
|
2144
|
-
"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)
|
|
2145
2639
|
};
|
|
2146
2640
|
const targets = options?.platforms || Object.keys(platformPaths);
|
|
2147
2641
|
for (const platform of targets) {
|
|
2148
2642
|
const dir = platformPaths[platform];
|
|
2149
2643
|
if (!dir) continue;
|
|
2150
2644
|
try {
|
|
2151
|
-
|
|
2152
|
-
const filePath =
|
|
2153
|
-
|
|
2645
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
2646
|
+
const filePath = path3.join(dir, "SKILL.md");
|
|
2647
|
+
fs3.writeFileSync(filePath, content, "utf-8");
|
|
2154
2648
|
paths.push(filePath);
|
|
2155
2649
|
} catch {
|
|
2156
2650
|
}
|
|
@@ -2290,11 +2784,11 @@ var FilesClient = class {
|
|
|
2290
2784
|
let bytes;
|
|
2291
2785
|
let fileName;
|
|
2292
2786
|
if (typeof input === "string") {
|
|
2293
|
-
const
|
|
2294
|
-
const
|
|
2295
|
-
const buf = await
|
|
2787
|
+
const fs3 = await import("fs");
|
|
2788
|
+
const path3 = await import("path");
|
|
2789
|
+
const buf = await fs3.promises.readFile(input);
|
|
2296
2790
|
bytes = new Uint8Array(buf);
|
|
2297
|
-
fileName = opts?.fileName ||
|
|
2791
|
+
fileName = opts?.fileName || path3.basename(input);
|
|
2298
2792
|
} else if (typeof Blob !== "undefined" && input instanceof Blob) {
|
|
2299
2793
|
const ab = await input.arrayBuffer();
|
|
2300
2794
|
bytes = new Uint8Array(ab);
|
|
@@ -2431,22 +2925,24 @@ var IMRealtimeClient = class {
|
|
|
2431
2925
|
}
|
|
2432
2926
|
};
|
|
2433
2927
|
var IMClient = class {
|
|
2434
|
-
constructor(
|
|
2435
|
-
this.account = new AccountClient(
|
|
2436
|
-
this.direct = new DirectClient(
|
|
2437
|
-
this.groups = new GroupsClient(
|
|
2438
|
-
this.conversations = new ConversationsClient(
|
|
2439
|
-
this.messages = new MessagesClient(
|
|
2440
|
-
this.contacts = new ContactsClient(
|
|
2441
|
-
this.bindings = new BindingsClient(
|
|
2442
|
-
this.credits = new CreditsClient(
|
|
2443
|
-
this.workspace = new WorkspaceClient(
|
|
2444
|
-
this.tasks = new TasksClient(
|
|
2445
|
-
this.memory = new MemoryClient(
|
|
2446
|
-
this.
|
|
2447
|
-
this.
|
|
2448
|
-
this.
|
|
2449
|
-
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);
|
|
2450
2946
|
this.realtime = new IMRealtimeClient(wsBase);
|
|
2451
2947
|
this.offline = offlineManager ?? null;
|
|
2452
2948
|
}
|
|
@@ -2454,19 +2950,43 @@ var IMClient = class {
|
|
|
2454
2950
|
async health() {
|
|
2455
2951
|
return this.account["_r"]("GET", "/api/im/health");
|
|
2456
2952
|
}
|
|
2953
|
+
/** Get workspace superset view with slot filtering */
|
|
2954
|
+
async getWorkspace(scope, slots, includeContent) {
|
|
2955
|
+
const params = new URLSearchParams();
|
|
2956
|
+
if (scope) params.set("scope", scope);
|
|
2957
|
+
if (slots?.length) params.set("slots", slots.join(","));
|
|
2958
|
+
if (includeContent) params.set("includeContent", "true");
|
|
2959
|
+
return this.workspace["_r"]("GET", `/api/im/workspace/view?${params}`);
|
|
2960
|
+
}
|
|
2457
2961
|
};
|
|
2458
2962
|
var PrismerClient = class {
|
|
2459
2963
|
constructor(config = {}) {
|
|
2460
2964
|
this._offlineManager = null;
|
|
2461
|
-
|
|
2965
|
+
/** AIP identity for auto-signing (v1.8.0 S1) */
|
|
2966
|
+
this._identity = null;
|
|
2967
|
+
this._identityReady = null;
|
|
2968
|
+
const resolvedApiKey = resolveApiKey(config.apiKey);
|
|
2969
|
+
if (resolvedApiKey && !resolvedApiKey.startsWith("sk-prismer-") && !resolvedApiKey.startsWith("eyJ")) {
|
|
2462
2970
|
console.warn('Warning: API key should start with "sk-prismer-" (or "eyJ" for IM JWT)');
|
|
2463
2971
|
}
|
|
2464
|
-
this.apiKey =
|
|
2972
|
+
this.apiKey = resolvedApiKey;
|
|
2465
2973
|
const envUrl = ENVIRONMENTS[config.environment || "production"];
|
|
2466
|
-
this.baseUrl = (config.baseUrl || envUrl).replace(/\/$/, "");
|
|
2974
|
+
this.baseUrl = (resolveBaseUrl(config.baseUrl) || envUrl).replace(/\/$/, "");
|
|
2467
2975
|
this.timeout = config.timeout || 3e4;
|
|
2468
2976
|
this.fetchFn = config.fetch || fetch;
|
|
2469
2977
|
this.imAgent = config.imAgent;
|
|
2978
|
+
if (config.identity) {
|
|
2979
|
+
if (config.identity === "auto" && this.apiKey) {
|
|
2980
|
+
this._identityReady = import_aip_sdk.AIPIdentity.fromApiKey(this.apiKey).then((id) => {
|
|
2981
|
+
this._identity = id;
|
|
2982
|
+
}).catch((err) => console.warn("[PrismerSDK] Identity init failed:", err));
|
|
2983
|
+
} else if (typeof config.identity === "object" && config.identity.privateKey) {
|
|
2984
|
+
const keyBytes = typeof Buffer !== "undefined" ? new Uint8Array(Buffer.from(config.identity.privateKey, "base64")) : new Uint8Array(atob(config.identity.privateKey).split("").map((c) => c.charCodeAt(0)));
|
|
2985
|
+
this._identityReady = import_aip_sdk.AIPIdentity.fromPrivateKey(keyBytes).then((id) => {
|
|
2986
|
+
this._identity = id;
|
|
2987
|
+
}).catch((err) => console.warn("[PrismerSDK] Identity init failed:", err));
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2470
2990
|
if (config.offline) {
|
|
2471
2991
|
this._offlineManager = new OfflineManager(
|
|
2472
2992
|
config.offline.storage,
|
|
@@ -2477,15 +2997,61 @@ var PrismerClient = class {
|
|
|
2477
2997
|
(err) => console.warn("[PrismerSDK] Offline storage init failed:", err)
|
|
2478
2998
|
);
|
|
2479
2999
|
}
|
|
2480
|
-
|
|
3000
|
+
let imRequest = this._offlineManager ? (m, p, b, q) => this._offlineManager.dispatch(m, p, b, q) : (m, p, b, q) => this._request(m, p, b, q);
|
|
3001
|
+
if (config.identity) {
|
|
3002
|
+
const baseRequest = imRequest;
|
|
3003
|
+
imRequest = (method, path3, body, query) => {
|
|
3004
|
+
if (method === "POST" && path3.includes("/messages") && body) {
|
|
3005
|
+
const b = body;
|
|
3006
|
+
if (!b.signature && !b.skipSigning) {
|
|
3007
|
+
const ready = this._identityReady || Promise.resolve();
|
|
3008
|
+
return ready.then(() => {
|
|
3009
|
+
if (this._identity) {
|
|
3010
|
+
return this._signAndSend(baseRequest, method, path3, b, query);
|
|
3011
|
+
}
|
|
3012
|
+
return baseRequest(method, path3, body, query);
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return baseRequest(method, path3, body, query);
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
2481
3019
|
this.im = new IMClient(
|
|
2482
3020
|
imRequest,
|
|
2483
3021
|
this.baseUrl,
|
|
2484
3022
|
this.fetchFn,
|
|
2485
3023
|
() => this._getAuthHeaders(),
|
|
2486
|
-
this._offlineManager
|
|
3024
|
+
this._offlineManager,
|
|
3025
|
+
config.community ?? null
|
|
2487
3026
|
);
|
|
2488
3027
|
}
|
|
3028
|
+
/** Wait for identity to be ready (useful for tests or explicit await) */
|
|
3029
|
+
async ensureIdentity() {
|
|
3030
|
+
if (this._identityReady) await this._identityReady;
|
|
3031
|
+
return this._identity;
|
|
3032
|
+
}
|
|
3033
|
+
/** Auto-sign a message body and send (v1.8.0 S1) */
|
|
3034
|
+
async _signAndSend(baseRequest, method, path3, body, query) {
|
|
3035
|
+
if (this._identityReady) await this._identityReady;
|
|
3036
|
+
if (!this._identity) return baseRequest(method, path3, body, query);
|
|
3037
|
+
const content = body.content || "";
|
|
3038
|
+
const contentHashBytes = new Uint8Array(
|
|
3039
|
+
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content))
|
|
3040
|
+
);
|
|
3041
|
+
const contentHash = Array.from(contentHashBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3042
|
+
const timestamp = Date.now();
|
|
3043
|
+
const payload = `1|${this._identity.did}|${body.type || "text"}|${timestamp}|${contentHash}`;
|
|
3044
|
+
const payloadBytes = new TextEncoder().encode(payload);
|
|
3045
|
+
const signature = await this._identity.sign(payloadBytes);
|
|
3046
|
+
return baseRequest(method, path3, {
|
|
3047
|
+
...body,
|
|
3048
|
+
secVersion: 1,
|
|
3049
|
+
senderDid: this._identity.did,
|
|
3050
|
+
contentHash,
|
|
3051
|
+
signature,
|
|
3052
|
+
signedAt: timestamp
|
|
3053
|
+
}, query);
|
|
3054
|
+
}
|
|
2489
3055
|
/** Build auth headers for raw HTTP requests (used by file upload) */
|
|
2490
3056
|
_getAuthHeaders() {
|
|
2491
3057
|
const headers = {};
|
|
@@ -2509,11 +3075,11 @@ var PrismerClient = class {
|
|
|
2509
3075
|
// --------------------------------------------------------------------------
|
|
2510
3076
|
// Internal request helper
|
|
2511
3077
|
// --------------------------------------------------------------------------
|
|
2512
|
-
async _request(method,
|
|
3078
|
+
async _request(method, path3, body, query, _isRetry) {
|
|
2513
3079
|
const controller = new AbortController();
|
|
2514
3080
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2515
3081
|
try {
|
|
2516
|
-
let url = `${this.baseUrl}${
|
|
3082
|
+
let url = `${this.baseUrl}${path3}`;
|
|
2517
3083
|
if (query && Object.keys(query).length > 0) {
|
|
2518
3084
|
url += "?" + new URLSearchParams(query).toString();
|
|
2519
3085
|
}
|
|
@@ -2531,12 +3097,12 @@ var PrismerClient = class {
|
|
|
2531
3097
|
}
|
|
2532
3098
|
const response = await this.fetchFn(url, init);
|
|
2533
3099
|
const data = await response.json();
|
|
2534
|
-
if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !
|
|
3100
|
+
if (response.status === 401 && this.apiKey.startsWith("eyJ") && !_isRetry && !path3.includes("/token/refresh")) {
|
|
2535
3101
|
try {
|
|
2536
3102
|
const refreshRes = await this._request("POST", "/api/im/token/refresh", void 0, void 0, true);
|
|
2537
3103
|
if (refreshRes?.ok && refreshRes?.data?.token) {
|
|
2538
3104
|
this.apiKey = refreshRes.data.token;
|
|
2539
|
-
return this._request(method,
|
|
3105
|
+
return this._request(method, path3, body, query, true);
|
|
2540
3106
|
}
|
|
2541
3107
|
} catch {
|
|
2542
3108
|
}
|
|
@@ -4208,7 +4774,7 @@ function printFileTable(files) {
|
|
|
4208
4774
|
const idLen = Math.max(2, ...files.map((f) => f.id.length));
|
|
4209
4775
|
const scopeLen = Math.max(5, ...files.map((f) => f.scope.length));
|
|
4210
4776
|
const pathLen = Math.max(4, ...files.map((f) => f.path.length));
|
|
4211
|
-
const row = (id, scope,
|
|
4777
|
+
const row = (id, scope, path3) => `${id.padEnd(idLen)} ${scope.padEnd(scopeLen)} ${path3.padEnd(pathLen)}`;
|
|
4212
4778
|
process.stdout.write(row("ID", "SCOPE", "PATH") + "\n");
|
|
4213
4779
|
process.stdout.write(`${"-".repeat(idLen)} ${"-".repeat(scopeLen)} ${"-".repeat(pathLen)}
|
|
4214
4780
|
`);
|
|
@@ -4965,29 +5531,665 @@ function register9(parent, getIMClient2, _getAPIClient) {
|
|
|
4965
5531
|
});
|
|
4966
5532
|
}
|
|
4967
5533
|
|
|
5534
|
+
// src/commands/community.ts
|
|
5535
|
+
var import_node_fs = require("fs");
|
|
5536
|
+
function printJson(res, opts) {
|
|
5537
|
+
if (opts.json) {
|
|
5538
|
+
console.log(JSON.stringify(res, null, 2));
|
|
5539
|
+
return;
|
|
5540
|
+
}
|
|
5541
|
+
if (!res.ok) {
|
|
5542
|
+
const errMsg = res.error && typeof res.error === "object" && "message" in res.error ? res.error.message : JSON.stringify(res.error);
|
|
5543
|
+
console.error("Error:", errMsg || "Unknown");
|
|
5544
|
+
process.exit(1);
|
|
5545
|
+
}
|
|
5546
|
+
console.log(typeof res.data === "string" ? res.data : JSON.stringify(res.data, null, 2));
|
|
5547
|
+
}
|
|
5548
|
+
function formatPostsMarkdown(data) {
|
|
5549
|
+
const d = data;
|
|
5550
|
+
const posts = d?.posts ?? [];
|
|
5551
|
+
let t = "## Feed\n\n";
|
|
5552
|
+
if (posts.length === 0) return t + "_Empty._\n";
|
|
5553
|
+
for (const p of posts) {
|
|
5554
|
+
t += `- **${String(p.title || "")}** (\`${String(p.id)}\`) \u2014 ${String(p.boardId || "")}
|
|
5555
|
+
`;
|
|
5556
|
+
}
|
|
5557
|
+
if (d?.nextCursor) t += `
|
|
5558
|
+
_Next cursor:_ \`${d.nextCursor}\`
|
|
5559
|
+
`;
|
|
5560
|
+
return t;
|
|
5561
|
+
}
|
|
5562
|
+
function register10(parent, getIMClient2, _getAPIClient) {
|
|
5563
|
+
const comm = parent.command("community").description("Evolution community forum \u2014 feed, ask, search, notify");
|
|
5564
|
+
comm.command("feed").description("Browse posts (uses hub cache when fresh)").option("-b, --board <id>", "Board: showcase, genelab, helpdesk, ideas, changelog").option("-n, --limit <n>", "Max posts", "15").option("--json", "JSON output").action(async (opts) => {
|
|
5565
|
+
const c = getIMClient2();
|
|
5566
|
+
const res = await c.im.community.feed({
|
|
5567
|
+
boardId: opts.board,
|
|
5568
|
+
limit: parseInt(opts.limit || "15", 10)
|
|
5569
|
+
});
|
|
5570
|
+
if (opts.json) {
|
|
5571
|
+
printJson(res, opts);
|
|
5572
|
+
return;
|
|
5573
|
+
}
|
|
5574
|
+
if (!res.ok) {
|
|
5575
|
+
printJson(res, { json: false });
|
|
5576
|
+
return;
|
|
5577
|
+
}
|
|
5578
|
+
process.stdout.write(formatPostsMarkdown(res.data));
|
|
5579
|
+
});
|
|
5580
|
+
comm.command("ask").description("Post a helpdesk question").argument("<title>", "Title").argument("[body]", "Body (Markdown); omit if using --file").option("-f, --file <path>", "Read body from file").option("--tags <csv>", "Comma-separated tags").option("--json", "JSON output").action(async (title, body, opts) => {
|
|
5581
|
+
const content = opts.file ? (0, import_node_fs.readFileSync)(opts.file, "utf8") : body || "(no body)";
|
|
5582
|
+
const tags = opts.tags?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
5583
|
+
const c = getIMClient2();
|
|
5584
|
+
const res = await c.im.community.ask(title, content, tags);
|
|
5585
|
+
printJson(res, opts);
|
|
5586
|
+
});
|
|
5587
|
+
comm.command("search").description("Full-text community search").argument("<query>", "Search query").option("-b, --board <id>", "Limit to board").option("-n, --limit <n>", "Max hits", "8").option("--json", "JSON output").action(async (query, opts) => {
|
|
5588
|
+
const c = getIMClient2();
|
|
5589
|
+
const res = await c.im.community.search(query, {
|
|
5590
|
+
boardId: opts.board,
|
|
5591
|
+
limit: parseInt(opts.limit || "8", 10)
|
|
5592
|
+
});
|
|
5593
|
+
printJson(res, opts);
|
|
5594
|
+
});
|
|
5595
|
+
comm.command("check").description("List notifications; optionally mark all read").option("--unread-only", "Unread only").option("--mark-read", "Mark all read after listing").option("--json", "JSON output").action(async (opts) => {
|
|
5596
|
+
const c = getIMClient2();
|
|
5597
|
+
const list = await c.im.community.getNotifications({
|
|
5598
|
+
unread: opts.unreadOnly,
|
|
5599
|
+
limit: 50
|
|
5600
|
+
});
|
|
5601
|
+
if (opts.json) {
|
|
5602
|
+
console.log(JSON.stringify(list, null, 2));
|
|
5603
|
+
} else if (list.ok && list.data) {
|
|
5604
|
+
const payload = list.data;
|
|
5605
|
+
console.log(`## Notifications (${payload.items?.length ?? 0})
|
|
5606
|
+
`);
|
|
5607
|
+
console.log(JSON.stringify(list.data, null, 2));
|
|
5608
|
+
} else {
|
|
5609
|
+
printJson(list, { json: false });
|
|
5610
|
+
}
|
|
5611
|
+
if (opts.markRead) {
|
|
5612
|
+
const mr = await c.im.community.markNotificationsRead();
|
|
5613
|
+
if (opts.json) console.log(JSON.stringify(mr, null, 2));
|
|
5614
|
+
else console.log("\nMarked read:", mr.ok ? "ok" : mr.error);
|
|
5615
|
+
}
|
|
5616
|
+
});
|
|
5617
|
+
comm.command("report").description("Publish a showcase battle-report style post").requiredOption("-t, --title <t>", "Title").option("-c, --content <md>", "Body markdown").option("--genes <csv>", "Linked gene IDs").option("--agent <id>", "linkedAgentId").option("--json", "JSON output").action(async (opts) => {
|
|
5618
|
+
const c = getIMClient2();
|
|
5619
|
+
const geneIds = opts.genes?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
5620
|
+
const res = await c.im.community.reportBattle({
|
|
5621
|
+
title: opts.title,
|
|
5622
|
+
content: opts.content || "_Battle report_",
|
|
5623
|
+
linkedGeneIds: geneIds,
|
|
5624
|
+
linkedAgentId: opts.agent
|
|
5625
|
+
});
|
|
5626
|
+
printJson(res, opts);
|
|
5627
|
+
});
|
|
5628
|
+
comm.command("post").description("Create a post on any board").argument("<board>", "Board id").argument("<title>", "Title").option("-c, --content <md>", "Body", "").option("--tags <csv>", "Tags").option("--json", "JSON output").action(async (board, title, opts) => {
|
|
5629
|
+
const tags = opts.tags?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
5630
|
+
const c = getIMClient2();
|
|
5631
|
+
const res = await c.im.community.createPost({
|
|
5632
|
+
boardId: board,
|
|
5633
|
+
title,
|
|
5634
|
+
content: opts.content || "",
|
|
5635
|
+
tags
|
|
5636
|
+
});
|
|
5637
|
+
if (res.ok) c.im.community.invalidateCache(board);
|
|
5638
|
+
printJson(res, opts);
|
|
5639
|
+
});
|
|
5640
|
+
comm.command("reply").description("Comment on a post").argument("<postId>", "Post ID").argument("<content>", "Comment (markdown)").option("--json", "JSON output").action(async (postId, content, opts) => {
|
|
5641
|
+
const c = getIMClient2();
|
|
5642
|
+
const res = await c.im.community.createComment(postId, { content });
|
|
5643
|
+
printJson(res, opts);
|
|
5644
|
+
});
|
|
5645
|
+
comm.command("vote").description("Vote on post or comment").argument("<type>", "post | comment").argument("<id>", "Target id").argument("<value>", "up | down | cancel").option("--json", "JSON output").action(async (type, id, value, opts) => {
|
|
5646
|
+
const tt = type === "comment" ? "comment" : "post";
|
|
5647
|
+
let v = 0;
|
|
5648
|
+
if (value === "up") v = 1;
|
|
5649
|
+
else if (value === "down") v = -1;
|
|
5650
|
+
const c = getIMClient2();
|
|
5651
|
+
const res = await c.im.community.vote(tt, id, v);
|
|
5652
|
+
printJson(res, opts);
|
|
5653
|
+
});
|
|
5654
|
+
const my = comm.command("my").description("Your bookmarks (auth)");
|
|
5655
|
+
my.command("bookmarks").description("List bookmarked posts").option("--json", "JSON output").action(async (opts) => {
|
|
5656
|
+
const c = getIMClient2();
|
|
5657
|
+
const res = await c.im.community.listBookmarks({ limit: 30 });
|
|
5658
|
+
printJson(res, opts);
|
|
5659
|
+
});
|
|
5660
|
+
}
|
|
5661
|
+
|
|
5662
|
+
// src/daemon.ts
|
|
5663
|
+
var fs = __toESM(require("fs"));
|
|
5664
|
+
var path = __toESM(require("path"));
|
|
5665
|
+
var import_path = require("path");
|
|
5666
|
+
var os = __toESM(require("os"));
|
|
5667
|
+
var import_os = require("os");
|
|
5668
|
+
var http = __toESM(require("http"));
|
|
5669
|
+
var import_http = require("http");
|
|
5670
|
+
var import_child_process = require("child_process");
|
|
5671
|
+
var TOML = __toESM(require("@iarna/toml"));
|
|
5672
|
+
var CONFIG_DIR = path.join(os.homedir(), ".prismer");
|
|
5673
|
+
var CONFIG_PATH = path.join(CONFIG_DIR, "config.toml");
|
|
5674
|
+
var PID_PATH = path.join(CONFIG_DIR, "daemon.pid");
|
|
5675
|
+
var PORT_PATH = path.join(CONFIG_DIR, "daemon.port");
|
|
5676
|
+
var CACHE_DIR = path.join(CONFIG_DIR, "cache");
|
|
5677
|
+
var EVOLUTION_CACHE_PATH = path.join(CACHE_DIR, "evolution.json");
|
|
5678
|
+
var OUTBOX_PATH = path.join(CACHE_DIR, "outbox.json");
|
|
5679
|
+
var SYNC_INTERVAL_MS = 6e4;
|
|
5680
|
+
var FLUSH_INTERVAL_MS = 3e4;
|
|
5681
|
+
var API_TIMEOUT_MS = 1e4;
|
|
5682
|
+
var EVENTS_FILE = (0, import_path.join)(CACHE_DIR, "events.json");
|
|
5683
|
+
var MAX_EVENTS = 1e3;
|
|
5684
|
+
function loadConfig() {
|
|
5685
|
+
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
5686
|
+
try {
|
|
5687
|
+
const raw = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
5688
|
+
const parsed = TOML.parse(raw);
|
|
5689
|
+
const apiKey = parsed?.default?.api_key || "";
|
|
5690
|
+
const baseUrl = parsed?.default?.base_url || "https://prismer.cloud";
|
|
5691
|
+
if (!apiKey) return null;
|
|
5692
|
+
return { apiKey, baseUrl };
|
|
5693
|
+
} catch {
|
|
5694
|
+
return null;
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
function ensureCacheDir() {
|
|
5698
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
5699
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
function loadEvents() {
|
|
5703
|
+
try {
|
|
5704
|
+
return JSON.parse(fs.readFileSync(EVENTS_FILE, "utf-8"));
|
|
5705
|
+
} catch {
|
|
5706
|
+
return [];
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
function appendEvent(event) {
|
|
5710
|
+
const events = loadEvents();
|
|
5711
|
+
events.push(event);
|
|
5712
|
+
if (events.length > MAX_EVENTS) events.splice(0, events.length - MAX_EVENTS);
|
|
5713
|
+
fs.writeFileSync(EVENTS_FILE, JSON.stringify(events), { encoding: "utf-8", mode: 384 });
|
|
5714
|
+
}
|
|
5715
|
+
function emitSyncEvent(genesCount) {
|
|
5716
|
+
if (genesCount > 0) {
|
|
5717
|
+
appendEvent({
|
|
5718
|
+
type: "evolution.sync",
|
|
5719
|
+
source: "evolution",
|
|
5720
|
+
priority: "low",
|
|
5721
|
+
title: "Evolution sync complete",
|
|
5722
|
+
body: `${genesCount} genes updated`,
|
|
5723
|
+
timestamp: Date.now()
|
|
5724
|
+
});
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
function readPid() {
|
|
5728
|
+
if (!fs.existsSync(PID_PATH)) return null;
|
|
5729
|
+
try {
|
|
5730
|
+
const raw = fs.readFileSync(PID_PATH, "utf-8").trim();
|
|
5731
|
+
const pid = parseInt(raw, 10);
|
|
5732
|
+
return isNaN(pid) ? null : pid;
|
|
5733
|
+
} catch {
|
|
5734
|
+
return null;
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
function readPort() {
|
|
5738
|
+
if (!fs.existsSync(PORT_PATH)) return null;
|
|
5739
|
+
try {
|
|
5740
|
+
const raw = fs.readFileSync(PORT_PATH, "utf-8").trim();
|
|
5741
|
+
const port = parseInt(raw, 10);
|
|
5742
|
+
return isNaN(port) ? null : port;
|
|
5743
|
+
} catch {
|
|
5744
|
+
return null;
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
function isProcessRunning(pid) {
|
|
5748
|
+
try {
|
|
5749
|
+
process.kill(pid, 0);
|
|
5750
|
+
return true;
|
|
5751
|
+
} catch {
|
|
5752
|
+
return false;
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
function writePid(pid) {
|
|
5756
|
+
ensureCacheDir();
|
|
5757
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
5758
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
5759
|
+
}
|
|
5760
|
+
fs.writeFileSync(PID_PATH, String(pid), { encoding: "utf-8", mode: 384 });
|
|
5761
|
+
}
|
|
5762
|
+
function writePort(port) {
|
|
5763
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
5764
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
5765
|
+
}
|
|
5766
|
+
fs.writeFileSync(PORT_PATH, String(port), { encoding: "utf-8", mode: 384 });
|
|
5767
|
+
}
|
|
5768
|
+
function cleanupPidFiles() {
|
|
5769
|
+
try {
|
|
5770
|
+
if (fs.existsSync(PID_PATH)) fs.unlinkSync(PID_PATH);
|
|
5771
|
+
} catch {
|
|
5772
|
+
}
|
|
5773
|
+
try {
|
|
5774
|
+
if (fs.existsSync(PORT_PATH)) fs.unlinkSync(PORT_PATH);
|
|
5775
|
+
} catch {
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
async function fetchWithTimeout(url, options, timeoutMs = API_TIMEOUT_MS) {
|
|
5779
|
+
const controller = new AbortController();
|
|
5780
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5781
|
+
try {
|
|
5782
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
5783
|
+
} finally {
|
|
5784
|
+
clearTimeout(timer);
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
async function runDaemonProcess() {
|
|
5788
|
+
const cfg = loadConfig();
|
|
5789
|
+
if (!cfg) {
|
|
5790
|
+
process.stderr.write('[prismer-daemon] No config found. Run "prismer setup" first.\n');
|
|
5791
|
+
process.exit(1);
|
|
5792
|
+
}
|
|
5793
|
+
ensureCacheDir();
|
|
5794
|
+
let lastSync = 0;
|
|
5795
|
+
let syncCount = 0;
|
|
5796
|
+
let evolutionCursor = 0;
|
|
5797
|
+
if (fs.existsSync(EVOLUTION_CACHE_PATH)) {
|
|
5798
|
+
try {
|
|
5799
|
+
const cached = JSON.parse(fs.readFileSync(EVOLUTION_CACHE_PATH, "utf-8"));
|
|
5800
|
+
if (typeof cached?.cursor === "number") evolutionCursor = cached.cursor;
|
|
5801
|
+
} catch {
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
const server = (0, import_http.createServer)((req, res) => {
|
|
5805
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
5806
|
+
let outboxSize = 0;
|
|
5807
|
+
if (fs.existsSync(OUTBOX_PATH)) {
|
|
5808
|
+
try {
|
|
5809
|
+
const entries = JSON.parse(fs.readFileSync(OUTBOX_PATH, "utf-8"));
|
|
5810
|
+
if (Array.isArray(entries)) outboxSize = entries.length;
|
|
5811
|
+
} catch {
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
const body = JSON.stringify({
|
|
5815
|
+
pid: process.pid,
|
|
5816
|
+
uptime: Math.floor(process.uptime()),
|
|
5817
|
+
lastSync,
|
|
5818
|
+
syncCount,
|
|
5819
|
+
outboxSize
|
|
5820
|
+
});
|
|
5821
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5822
|
+
res.end(body);
|
|
5823
|
+
} else if (req.method === "GET" && req.url === "/events") {
|
|
5824
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5825
|
+
const events = loadEvents();
|
|
5826
|
+
res.end(JSON.stringify(events.slice(-50)));
|
|
5827
|
+
} else {
|
|
5828
|
+
res.writeHead(404);
|
|
5829
|
+
res.end("Not found");
|
|
5830
|
+
}
|
|
5831
|
+
});
|
|
5832
|
+
server.listen(0, "127.0.0.1", () => {
|
|
5833
|
+
const addr = server.address();
|
|
5834
|
+
const port = addr.port;
|
|
5835
|
+
writePid(process.pid);
|
|
5836
|
+
writePort(port);
|
|
5837
|
+
process.stdout.write(`[prismer-daemon] Started. PID=${process.pid} port=${port}
|
|
5838
|
+
`);
|
|
5839
|
+
});
|
|
5840
|
+
const shutdown = () => {
|
|
5841
|
+
process.stdout.write("[prismer-daemon] Shutting down.\n");
|
|
5842
|
+
cleanupPidFiles();
|
|
5843
|
+
server.close();
|
|
5844
|
+
process.exit(0);
|
|
5845
|
+
};
|
|
5846
|
+
process.on("SIGINT", shutdown);
|
|
5847
|
+
process.on("SIGTERM", shutdown);
|
|
5848
|
+
const doEvolutionSync = async () => {
|
|
5849
|
+
try {
|
|
5850
|
+
const res = await fetchWithTimeout(
|
|
5851
|
+
`${cfg.baseUrl}/api/im/evolution/sync`,
|
|
5852
|
+
{
|
|
5853
|
+
method: "POST",
|
|
5854
|
+
headers: {
|
|
5855
|
+
"Content-Type": "application/json",
|
|
5856
|
+
Authorization: `Bearer ${cfg.apiKey}`
|
|
5857
|
+
},
|
|
5858
|
+
body: JSON.stringify({ pull: { since: evolutionCursor, scope: "global" } })
|
|
5859
|
+
}
|
|
5860
|
+
);
|
|
5861
|
+
if (res.ok) {
|
|
5862
|
+
const data = await res.json();
|
|
5863
|
+
lastSync = Date.now();
|
|
5864
|
+
syncCount++;
|
|
5865
|
+
if (typeof data?.data?.cursor === "number") {
|
|
5866
|
+
evolutionCursor = data.data.cursor;
|
|
5867
|
+
} else if (typeof data?.cursor === "number") {
|
|
5868
|
+
evolutionCursor = data.cursor;
|
|
5869
|
+
}
|
|
5870
|
+
ensureCacheDir();
|
|
5871
|
+
const pulled = data?.data || data;
|
|
5872
|
+
fs.writeFileSync(
|
|
5873
|
+
EVOLUTION_CACHE_PATH,
|
|
5874
|
+
JSON.stringify({ cursor: evolutionCursor, lastSync, data: pulled }, null, 2),
|
|
5875
|
+
{ encoding: "utf-8", mode: 384 }
|
|
5876
|
+
);
|
|
5877
|
+
emitSyncEvent(pulled?.genes?.length || 0);
|
|
5878
|
+
}
|
|
5879
|
+
} catch {
|
|
5880
|
+
}
|
|
5881
|
+
};
|
|
5882
|
+
const doOutboxFlush = async () => {
|
|
5883
|
+
if (!fs.existsSync(OUTBOX_PATH)) return;
|
|
5884
|
+
let entries = [];
|
|
5885
|
+
try {
|
|
5886
|
+
entries = JSON.parse(fs.readFileSync(OUTBOX_PATH, "utf-8"));
|
|
5887
|
+
if (!Array.isArray(entries) || entries.length === 0) return;
|
|
5888
|
+
} catch {
|
|
5889
|
+
return;
|
|
5890
|
+
}
|
|
5891
|
+
try {
|
|
5892
|
+
const res = await fetchWithTimeout(
|
|
5893
|
+
`${cfg.baseUrl}/api/im/evolution/sync`,
|
|
5894
|
+
{
|
|
5895
|
+
method: "POST",
|
|
5896
|
+
headers: {
|
|
5897
|
+
"Content-Type": "application/json",
|
|
5898
|
+
Authorization: `Bearer ${cfg.apiKey}`
|
|
5899
|
+
},
|
|
5900
|
+
body: JSON.stringify({
|
|
5901
|
+
push: { outcomes: entries },
|
|
5902
|
+
pull: { since: 0 }
|
|
5903
|
+
})
|
|
5904
|
+
}
|
|
5905
|
+
);
|
|
5906
|
+
if (res.ok) {
|
|
5907
|
+
fs.writeFileSync(OUTBOX_PATH, "[]", { encoding: "utf-8", mode: 384 });
|
|
5908
|
+
}
|
|
5909
|
+
} catch {
|
|
5910
|
+
}
|
|
5911
|
+
};
|
|
5912
|
+
await doEvolutionSync();
|
|
5913
|
+
await doOutboxFlush();
|
|
5914
|
+
const syncTimer = setInterval(doEvolutionSync, SYNC_INTERVAL_MS);
|
|
5915
|
+
const flushTimer = setInterval(doOutboxFlush, FLUSH_INTERVAL_MS);
|
|
5916
|
+
const originalShutdown = shutdown;
|
|
5917
|
+
const fullShutdown = () => {
|
|
5918
|
+
clearInterval(syncTimer);
|
|
5919
|
+
clearInterval(flushTimer);
|
|
5920
|
+
originalShutdown();
|
|
5921
|
+
};
|
|
5922
|
+
process.removeListener("SIGINT", shutdown);
|
|
5923
|
+
process.removeListener("SIGTERM", shutdown);
|
|
5924
|
+
process.on("SIGINT", fullShutdown);
|
|
5925
|
+
process.on("SIGTERM", fullShutdown);
|
|
5926
|
+
}
|
|
5927
|
+
async function startDaemon() {
|
|
5928
|
+
const existingPid = readPid();
|
|
5929
|
+
if (existingPid !== null && isProcessRunning(existingPid)) {
|
|
5930
|
+
const port = readPort();
|
|
5931
|
+
console.log(`Daemon already running. PID=${existingPid}${port ? ` port=${port}` : ""}`);
|
|
5932
|
+
return;
|
|
5933
|
+
}
|
|
5934
|
+
cleanupPidFiles();
|
|
5935
|
+
const cfg = loadConfig();
|
|
5936
|
+
if (!cfg) {
|
|
5937
|
+
console.error('No API key found. Run "prismer setup" first.');
|
|
5938
|
+
process.exit(1);
|
|
5939
|
+
}
|
|
5940
|
+
if (process.env["PRISMER_DAEMON"] === "1") {
|
|
5941
|
+
await runDaemonProcess();
|
|
5942
|
+
return;
|
|
5943
|
+
}
|
|
5944
|
+
const { spawn } = require("child_process");
|
|
5945
|
+
const child = spawn(process.execPath, [process.argv[1], "daemon", "start"], {
|
|
5946
|
+
env: { ...process.env, PRISMER_DAEMON: "1" },
|
|
5947
|
+
detached: true,
|
|
5948
|
+
stdio: "ignore"
|
|
5949
|
+
});
|
|
5950
|
+
child.unref();
|
|
5951
|
+
let waited = 0;
|
|
5952
|
+
while (waited < 3e3) {
|
|
5953
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
5954
|
+
waited += 100;
|
|
5955
|
+
const pid = readPid();
|
|
5956
|
+
const port = readPort();
|
|
5957
|
+
if (pid !== null && port !== null) {
|
|
5958
|
+
console.log(`Daemon started. PID=${pid} port=${port}`);
|
|
5959
|
+
return;
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
console.log("Daemon spawned (PID file not yet written \u2014 may take a moment).");
|
|
5963
|
+
}
|
|
5964
|
+
function stopDaemon() {
|
|
5965
|
+
const pid = readPid();
|
|
5966
|
+
if (pid === null || !isProcessRunning(pid)) {
|
|
5967
|
+
console.log("Daemon: not running");
|
|
5968
|
+
cleanupPidFiles();
|
|
5969
|
+
return;
|
|
5970
|
+
}
|
|
5971
|
+
try {
|
|
5972
|
+
process.kill(pid, "SIGTERM");
|
|
5973
|
+
console.log(`Daemon stopped (PID=${pid})`);
|
|
5974
|
+
cleanupPidFiles();
|
|
5975
|
+
} catch (err) {
|
|
5976
|
+
console.error(`Failed to stop daemon: ${err.message}`);
|
|
5977
|
+
}
|
|
5978
|
+
}
|
|
5979
|
+
function daemonStatus() {
|
|
5980
|
+
const pid = readPid();
|
|
5981
|
+
if (pid === null || !isProcessRunning(pid)) {
|
|
5982
|
+
console.log("Daemon: not running");
|
|
5983
|
+
cleanupPidFiles();
|
|
5984
|
+
return;
|
|
5985
|
+
}
|
|
5986
|
+
const port = readPort();
|
|
5987
|
+
if (!port) {
|
|
5988
|
+
console.log(`Daemon: running (PID=${pid}, port unknown)`);
|
|
5989
|
+
return;
|
|
5990
|
+
}
|
|
5991
|
+
const req = http.request(
|
|
5992
|
+
{ hostname: "127.0.0.1", port, path: "/health", method: "GET", timeout: 3e3 },
|
|
5993
|
+
(res) => {
|
|
5994
|
+
let body = "";
|
|
5995
|
+
res.on("data", (chunk) => {
|
|
5996
|
+
body += chunk.toString();
|
|
5997
|
+
});
|
|
5998
|
+
res.on("end", () => {
|
|
5999
|
+
try {
|
|
6000
|
+
const health = JSON.parse(body);
|
|
6001
|
+
console.log(`Daemon: running`);
|
|
6002
|
+
console.log(` PID: ${health.pid}`);
|
|
6003
|
+
console.log(` Uptime: ${health.uptime}s`);
|
|
6004
|
+
console.log(` Last sync: ${health.lastSync ? new Date(health.lastSync).toISOString() : "never"}`);
|
|
6005
|
+
console.log(` Sync count: ${health.syncCount}`);
|
|
6006
|
+
console.log(` Outbox: ${health.outboxSize} entries`);
|
|
6007
|
+
console.log(` Port: ${port}`);
|
|
6008
|
+
} catch {
|
|
6009
|
+
console.log(`Daemon: running (PID=${pid} port=${port})`);
|
|
6010
|
+
}
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
6013
|
+
);
|
|
6014
|
+
req.on("error", () => {
|
|
6015
|
+
console.log(`Daemon: running (PID=${pid} port=${port}, health check failed)`);
|
|
6016
|
+
});
|
|
6017
|
+
req.end();
|
|
6018
|
+
}
|
|
6019
|
+
function resolveNpxPath() {
|
|
6020
|
+
try {
|
|
6021
|
+
return (0, import_child_process.execSync)("which npx", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6022
|
+
} catch {
|
|
6023
|
+
for (const p of ["/usr/local/bin/npx", "/opt/homebrew/bin/npx", `${(0, import_os.homedir)()}/.nvm/current/bin/npx`]) {
|
|
6024
|
+
try {
|
|
6025
|
+
fs.accessSync(p);
|
|
6026
|
+
return p;
|
|
6027
|
+
} catch {
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
return "npx";
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
function installLaunchd() {
|
|
6034
|
+
const plistPath = (0, import_path.join)((0, import_os.homedir)(), "Library", "LaunchAgents", "cloud.prismer.daemon.plist");
|
|
6035
|
+
const npxPath = resolveNpxPath();
|
|
6036
|
+
const nodePath = process.execPath;
|
|
6037
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
6038
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
6039
|
+
<plist version="1.0">
|
|
6040
|
+
<dict>
|
|
6041
|
+
<key>Label</key>
|
|
6042
|
+
<string>cloud.prismer.daemon</string>
|
|
6043
|
+
<key>ProgramArguments</key>
|
|
6044
|
+
<array>
|
|
6045
|
+
<string>${npxPath}</string>
|
|
6046
|
+
<string>@prismer/sdk</string>
|
|
6047
|
+
<string>daemon</string>
|
|
6048
|
+
<string>start</string>
|
|
6049
|
+
</array>
|
|
6050
|
+
<key>EnvironmentVariables</key>
|
|
6051
|
+
<dict>
|
|
6052
|
+
<key>PRISMER_DAEMON</key>
|
|
6053
|
+
<string>1</string>
|
|
6054
|
+
<key>PATH</key>
|
|
6055
|
+
<string>${(0, import_path.dirname)(nodePath)}:/usr/local/bin:/usr/bin:/bin</string>
|
|
6056
|
+
</dict>
|
|
6057
|
+
<key>RunAtLoad</key>
|
|
6058
|
+
<true/>
|
|
6059
|
+
<key>KeepAlive</key>
|
|
6060
|
+
<true/>
|
|
6061
|
+
<key>StandardOutPath</key>
|
|
6062
|
+
<string>${(0, import_path.join)((0, import_os.homedir)(), ".prismer", "daemon.stdout.log")}</string>
|
|
6063
|
+
<key>StandardErrorPath</key>
|
|
6064
|
+
<string>${(0, import_path.join)((0, import_os.homedir)(), ".prismer", "daemon.stderr.log")}</string>
|
|
6065
|
+
</dict>
|
|
6066
|
+
</plist>`;
|
|
6067
|
+
fs.mkdirSync((0, import_path.dirname)(plistPath), { recursive: true });
|
|
6068
|
+
fs.writeFileSync(plistPath, plist, { mode: 384 });
|
|
6069
|
+
try {
|
|
6070
|
+
(0, import_child_process.execSync)(`launchctl load ${plistPath}`, { stdio: "pipe" });
|
|
6071
|
+
console.log("[prismer] Daemon service installed and started (launchd)");
|
|
6072
|
+
console.log(` Plist: ${plistPath}`);
|
|
6073
|
+
} catch {
|
|
6074
|
+
console.log("[prismer] Plist written. Load manually: launchctl load " + plistPath);
|
|
6075
|
+
}
|
|
6076
|
+
}
|
|
6077
|
+
function uninstallLaunchd() {
|
|
6078
|
+
const plistPath = (0, import_path.join)((0, import_os.homedir)(), "Library", "LaunchAgents", "cloud.prismer.daemon.plist");
|
|
6079
|
+
try {
|
|
6080
|
+
(0, import_child_process.execSync)(`launchctl unload ${plistPath}`, { stdio: "pipe" });
|
|
6081
|
+
} catch {
|
|
6082
|
+
}
|
|
6083
|
+
try {
|
|
6084
|
+
fs.unlinkSync(plistPath);
|
|
6085
|
+
} catch {
|
|
6086
|
+
}
|
|
6087
|
+
console.log("[prismer] Daemon service uninstalled (launchd)");
|
|
6088
|
+
}
|
|
6089
|
+
function installSystemd() {
|
|
6090
|
+
const serviceDir = (0, import_path.join)((0, import_os.homedir)(), ".config", "systemd", "user");
|
|
6091
|
+
const servicePath = (0, import_path.join)(serviceDir, "prismer-daemon.service");
|
|
6092
|
+
const npxPath = resolveNpxPath();
|
|
6093
|
+
const nodePath = process.execPath;
|
|
6094
|
+
const unit = `[Unit]
|
|
6095
|
+
Description=Prismer Daemon \u2014 background evolution sync
|
|
6096
|
+
After=network-online.target
|
|
6097
|
+
|
|
6098
|
+
[Service]
|
|
6099
|
+
Type=simple
|
|
6100
|
+
Environment=PRISMER_DAEMON=1
|
|
6101
|
+
Environment=PATH=${(0, import_path.dirname)(nodePath)}:/usr/local/bin:/usr/bin:/bin
|
|
6102
|
+
ExecStart=${npxPath} @prismer/sdk daemon start
|
|
6103
|
+
Restart=on-failure
|
|
6104
|
+
RestartSec=10
|
|
6105
|
+
|
|
6106
|
+
[Install]
|
|
6107
|
+
WantedBy=default.target
|
|
6108
|
+
`;
|
|
6109
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
6110
|
+
fs.writeFileSync(servicePath, unit, { mode: 420 });
|
|
6111
|
+
try {
|
|
6112
|
+
(0, import_child_process.execSync)("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
6113
|
+
(0, import_child_process.execSync)("systemctl --user enable prismer-daemon", { stdio: "pipe" });
|
|
6114
|
+
(0, import_child_process.execSync)("systemctl --user start prismer-daemon", { stdio: "pipe" });
|
|
6115
|
+
console.log("[prismer] Daemon service installed and started (systemd)");
|
|
6116
|
+
console.log(` Service: ${servicePath}`);
|
|
6117
|
+
} catch {
|
|
6118
|
+
console.log("[prismer] Service file written. Enable manually:");
|
|
6119
|
+
console.log(" systemctl --user enable --now prismer-daemon");
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
function uninstallSystemd() {
|
|
6123
|
+
try {
|
|
6124
|
+
(0, import_child_process.execSync)("systemctl --user stop prismer-daemon", { stdio: "pipe" });
|
|
6125
|
+
} catch {
|
|
6126
|
+
}
|
|
6127
|
+
try {
|
|
6128
|
+
(0, import_child_process.execSync)("systemctl --user disable prismer-daemon", { stdio: "pipe" });
|
|
6129
|
+
} catch {
|
|
6130
|
+
}
|
|
6131
|
+
const servicePath = (0, import_path.join)((0, import_os.homedir)(), ".config", "systemd", "user", "prismer-daemon.service");
|
|
6132
|
+
try {
|
|
6133
|
+
fs.unlinkSync(servicePath);
|
|
6134
|
+
} catch {
|
|
6135
|
+
}
|
|
6136
|
+
try {
|
|
6137
|
+
(0, import_child_process.execSync)("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
6138
|
+
} catch {
|
|
6139
|
+
}
|
|
6140
|
+
console.log("[prismer] Daemon service uninstalled (systemd)");
|
|
6141
|
+
}
|
|
6142
|
+
function installDaemonService() {
|
|
6143
|
+
const platform = process.platform;
|
|
6144
|
+
if (platform === "darwin") {
|
|
6145
|
+
installLaunchd();
|
|
6146
|
+
} else if (platform === "linux") {
|
|
6147
|
+
installSystemd();
|
|
6148
|
+
} else {
|
|
6149
|
+
console.log(`Daemon auto-start not supported on ${platform}. Use: prismer daemon start`);
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
function uninstallDaemonService() {
|
|
6153
|
+
const platform = process.platform;
|
|
6154
|
+
if (platform === "darwin") {
|
|
6155
|
+
uninstallLaunchd();
|
|
6156
|
+
} else if (platform === "linux") {
|
|
6157
|
+
uninstallSystemd();
|
|
6158
|
+
} else {
|
|
6159
|
+
console.log(`No daemon service to uninstall on ${platform}.`);
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
if (process.env["PRISMER_DAEMON"] === "1") {
|
|
6163
|
+
runDaemonProcess().catch((err) => {
|
|
6164
|
+
process.stderr.write(`[prismer-daemon] Fatal: ${err.message}
|
|
6165
|
+
`);
|
|
6166
|
+
process.exit(1);
|
|
6167
|
+
});
|
|
6168
|
+
}
|
|
6169
|
+
|
|
4968
6170
|
// src/cli.ts
|
|
4969
6171
|
var cliVersion = "1.7.2";
|
|
4970
6172
|
try {
|
|
4971
|
-
const pkgPath =
|
|
4972
|
-
const pkg = JSON.parse(
|
|
6173
|
+
const pkgPath = path2.join(__dirname, "..", "package.json");
|
|
6174
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
4973
6175
|
cliVersion = pkg.version || cliVersion;
|
|
4974
6176
|
} catch {
|
|
4975
6177
|
}
|
|
4976
|
-
var
|
|
4977
|
-
var
|
|
6178
|
+
var CONFIG_DIR2 = path2.join(os2.homedir(), ".prismer");
|
|
6179
|
+
var CONFIG_PATH2 = path2.join(CONFIG_DIR2, "config.toml");
|
|
4978
6180
|
function ensureConfigDir() {
|
|
4979
|
-
if (!
|
|
4980
|
-
|
|
6181
|
+
if (!fs2.existsSync(CONFIG_DIR2)) {
|
|
6182
|
+
fs2.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
4981
6183
|
}
|
|
4982
6184
|
}
|
|
4983
6185
|
function readConfig() {
|
|
4984
|
-
if (!
|
|
4985
|
-
const raw =
|
|
4986
|
-
return
|
|
6186
|
+
if (!fs2.existsSync(CONFIG_PATH2)) return {};
|
|
6187
|
+
const raw = fs2.readFileSync(CONFIG_PATH2, "utf-8");
|
|
6188
|
+
return TOML2.parse(raw);
|
|
4987
6189
|
}
|
|
4988
6190
|
function writeConfig(config) {
|
|
4989
6191
|
ensureConfigDir();
|
|
4990
|
-
|
|
6192
|
+
fs2.writeFileSync(CONFIG_PATH2, TOML2.stringify(config), { encoding: "utf-8", mode: 384 });
|
|
4991
6193
|
}
|
|
4992
6194
|
function setNestedValue(obj, dotPath, value) {
|
|
4993
6195
|
const parts = dotPath.split(".");
|
|
@@ -5054,6 +6256,11 @@ async function verifyAndSaveKey(config, apiKey) {
|
|
|
5054
6256
|
console.log("");
|
|
5055
6257
|
console.log("Saved to ~/.prismer/config.toml");
|
|
5056
6258
|
console.log("You can now use: CLI commands, MCP tools, Claude Code plugin, and all SDKs.");
|
|
6259
|
+
try {
|
|
6260
|
+
installDaemonService();
|
|
6261
|
+
} catch {
|
|
6262
|
+
console.log("Daemon auto-start setup skipped. Run manually: prismer daemon install");
|
|
6263
|
+
}
|
|
5057
6264
|
}
|
|
5058
6265
|
function openBrowser(url) {
|
|
5059
6266
|
const { execFile } = require("child_process");
|
|
@@ -5138,11 +6345,11 @@ async function runSetup(opts, apiKey) {
|
|
|
5138
6345
|
});
|
|
5139
6346
|
return;
|
|
5140
6347
|
}
|
|
5141
|
-
const
|
|
6348
|
+
const http2 = require("http");
|
|
5142
6349
|
const crypto2 = require("crypto");
|
|
5143
6350
|
const state = crypto2.randomBytes(16).toString("hex");
|
|
5144
6351
|
let resolved = false;
|
|
5145
|
-
const server =
|
|
6352
|
+
const server = http2.createServer((req, res) => {
|
|
5146
6353
|
const url = new URL(req.url, `http://localhost`);
|
|
5147
6354
|
if (url.pathname === "/callback") {
|
|
5148
6355
|
const key = url.searchParams.get("key");
|
|
@@ -5304,11 +6511,11 @@ program.command("status").description("Show current config and live info").actio
|
|
|
5304
6511
|
});
|
|
5305
6512
|
var configCmd = program.command("config").description("Manage config file");
|
|
5306
6513
|
configCmd.command("show").description("Print config file").action(() => {
|
|
5307
|
-
if (!
|
|
6514
|
+
if (!fs2.existsSync(CONFIG_PATH2)) {
|
|
5308
6515
|
console.log('No config file. Run "prismer setup" to create one.');
|
|
5309
6516
|
return;
|
|
5310
6517
|
}
|
|
5311
|
-
console.log(
|
|
6518
|
+
console.log(fs2.readFileSync(CONFIG_PATH2, "utf-8"));
|
|
5312
6519
|
});
|
|
5313
6520
|
configCmd.command("set <key> <value>").description("Set a config value (e.g. default.base_url)").action((key, value) => {
|
|
5314
6521
|
const config = readConfig();
|
|
@@ -5349,6 +6556,7 @@ register6(program, getIMClient, getAPIClient);
|
|
|
5349
6556
|
register7(program, getIMClient, getAPIClient);
|
|
5350
6557
|
register8(program, getIMClient, getAPIClient);
|
|
5351
6558
|
register9(program, getIMClient, getAPIClient);
|
|
6559
|
+
register10(program, getIMClient, getAPIClient);
|
|
5352
6560
|
program.command("send").description("Send a direct message (shortcut for: im send)").argument("<user-id>", "Target user/agent ID").argument("<message>", "Message content").option("-t, --type <type>", "Message type: text, markdown, code, etc.", "text").option("--reply-to <id>", "Reply to a message ID").option("--json", "JSON output").action(async (userId, message, opts) => {
|
|
5353
6561
|
const client = getIMClient();
|
|
5354
6562
|
const sendOpts = {};
|
|
@@ -5511,6 +6719,28 @@ program.command("discover").description("Discover available agents (shortcut for
|
|
|
5511
6719
|
console.log(`${(a.username || "").padEnd(20)}${(a.agentType || "").padEnd(14)}${(a.status || "").padEnd(10)}${a.displayName || ""}`);
|
|
5512
6720
|
}
|
|
5513
6721
|
});
|
|
6722
|
+
program.command("daemon <action>").description("Manage background sync daemon (start|stop|status|install|uninstall)").action(async (action) => {
|
|
6723
|
+
switch (action) {
|
|
6724
|
+
case "start":
|
|
6725
|
+
await startDaemon();
|
|
6726
|
+
break;
|
|
6727
|
+
case "stop":
|
|
6728
|
+
stopDaemon();
|
|
6729
|
+
break;
|
|
6730
|
+
case "status":
|
|
6731
|
+
daemonStatus();
|
|
6732
|
+
break;
|
|
6733
|
+
case "install":
|
|
6734
|
+
installDaemonService();
|
|
6735
|
+
break;
|
|
6736
|
+
case "uninstall":
|
|
6737
|
+
uninstallDaemonService();
|
|
6738
|
+
break;
|
|
6739
|
+
default:
|
|
6740
|
+
console.error(`Unknown daemon action: ${action}. Use: start, stop, status, install, uninstall`);
|
|
6741
|
+
process.exit(1);
|
|
6742
|
+
}
|
|
6743
|
+
});
|
|
5514
6744
|
program.parse(process.argv);
|
|
5515
6745
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5516
6746
|
0 && (module.exports = {
|