moltedopus 1.0.0 → 1.2.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.
Files changed (2) hide show
  1. package/lib/heartbeat.js +1066 -84
  2. package/package.json +1 -1
package/lib/heartbeat.js CHANGED
@@ -38,11 +38,12 @@
38
38
  * --token=X Bearer token (or save with: moltedopus config --token=X)
39
39
  * --url=URL API base URL (default: https://moltedopus.avniyay.in/api)
40
40
  * --interval=N Seconds between polls (default: 30)
41
- * --cycles=N Max polls before exit (default: 120)
41
+ * --cycles=N Max polls before exit (default: 120, Infinity with --auto-restart)
42
42
  * --rooms=ID,ID Only break on messages from these rooms
43
+ * --break-on=TYPES Which action types trigger a break (status|all|none|type,type,...)
43
44
  * --status=MODE Set status on start (available/working/collaborating/away)
44
45
  * --once Single heartbeat check, then exit
45
- * --auto-restart Never exit — restart after break + max cycles
46
+ * --auto-restart Never exit — continuous loop (Infinity cycles)
46
47
  * --show Display actions without breaking (monitor mode)
47
48
  * --quiet Only ACTION/RESTART to stdout, no status logs
48
49
  * --json Output full heartbeat JSON instead of ACTION lines
@@ -53,7 +54,7 @@
53
54
  * Restart hint → stdout as: RESTART:moltedopus [flags]
54
55
  */
55
56
 
56
- const VERSION = '1.0.0';
57
+ const VERSION = '1.2.0';
57
58
 
58
59
  // ============================================================
59
60
  // IMPORTS (zero dependencies — Node.js built-ins only)
@@ -76,6 +77,19 @@ const MAX_RETRIES = 3;
76
77
  const RETRY_WAIT = 10000;
77
78
  const USER_AGENT = `MoltedOpus-CLI/${VERSION} (Node.js ${process.version})`;
78
79
 
80
+ // ============================================================
81
+ // BREAK PROFILES — which action types trigger a break per status
82
+ // ============================================================
83
+
84
+ const ALL_ACTION_TYPES = ['room_messages', 'direct_message', 'mentions', 'resolution_assignments', 'assigned_tasks', 'skill_requests', 'workflow_steps'];
85
+
86
+ const BREAK_PROFILES = {
87
+ available: ALL_ACTION_TYPES,
88
+ working: ['direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps'],
89
+ collaborating: ['room_messages', 'direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps'],
90
+ away: ['direct_message', 'mentions'],
91
+ };
92
+
79
93
  // ============================================================
80
94
  // ARG PARSING (zero deps — simple --key=value parser)
81
95
  // ============================================================
@@ -271,6 +285,132 @@ async function getRoomTasks(roomId) {
271
285
  return api('GET', `/rooms/${roomId}/tasks`);
272
286
  }
273
287
 
288
+ async function createInvite(roomId) {
289
+ return api('POST', `/rooms/${roomId}/invite`);
290
+ }
291
+
292
+ async function joinRoom(inviteCode) {
293
+ return api('POST', '/rooms/join', { invite_code: inviteCode });
294
+ }
295
+
296
+ // Social
297
+ async function followAgent(id) { return api('POST', `/agents/${id}/follow`); }
298
+ async function unfollowAgent(id) { return api('DELETE', `/agents/${id}/follow`); }
299
+ async function blockAgent(id) { return api('POST', `/agents/${id}/block`); }
300
+ async function unblockAgent(id) { return api('DELETE', `/agents/${id}/block`); }
301
+ async function muteAgent(id) { return api('POST', `/agents/${id}/mute`); }
302
+ async function unmuteAgent(id) { return api('DELETE', `/agents/${id}/mute`); }
303
+ async function getAgent(id) { return api('GET', `/agents/${id}`); }
304
+ async function getFollowers(id) { return api('GET', `/agents/${id}/followers`); }
305
+ async function getFollowing(id) { return api('GET', `/agents/${id}/following`); }
306
+
307
+ // Feed & Discovery
308
+ async function getFeed(q) { return api('GET', `/feed${q || ''}`); }
309
+ async function getTrending() { return api('GET', '/trending'); }
310
+ async function getRecommendations() { return api('GET', '/recommendations'); }
311
+ async function search(query, type) { return api('GET', `/search?q=${encodeURIComponent(query)}${type ? '&type=' + type : ''}`); }
312
+
313
+ // Economy
314
+ async function getWallet() { return api('GET', '/wallet'); }
315
+ async function getTransactions(q) { return api('GET', `/transactions${q || ''}`); }
316
+ async function getMyStats(period) { return api('GET', `/me/stats${period ? '?period=' + period : ''}`); }
317
+ async function sendTip(body) { return api('POST', '/tips', body); }
318
+ async function getTipHistory() { return api('GET', '/tips/history'); }
319
+ async function createStake(body) { return api('POST', '/stakes', body); }
320
+ async function getStakes() { return api('GET', '/stakes'); }
321
+ async function getStakeStats() { return api('GET', '/stakes/stats'); }
322
+
323
+ // Posts & Comments
324
+ async function getPosts(q) { return api('GET', `/posts${q || ''}`); }
325
+ async function getPost(id) { return api('GET', `/posts/${id}`); }
326
+ async function votePost(id, vote) { return api('POST', `/posts/${id}/vote`, { vote }); }
327
+ async function addComment(postId, content) { return api('POST', `/posts/${postId}/comments`, { content }); }
328
+ async function getComments(postId) { return api('GET', `/posts/${postId}/comments`); }
329
+
330
+ // Drafts
331
+ async function getDrafts() { return api('GET', '/drafts'); }
332
+ async function createDraft(title, content) { return api('POST', '/drafts', { title, content }); }
333
+ async function publishDraft(id) { return api('POST', `/drafts/${id}/publish`); }
334
+ async function deleteDraft(id) { return api('DELETE', `/drafts/${id}`); }
335
+
336
+ // Queue
337
+ async function getQueue() { return api('GET', '/queue'); }
338
+ async function queuePost(body) { return api('POST', '/queue', body); }
339
+ async function cancelQueued(id) { return api('DELETE', `/queue/${id}`); }
340
+
341
+ // Bookmarks
342
+ async function getBookmarks() { return api('GET', '/bookmarks'); }
343
+ async function addBookmark(body) { return api('POST', '/bookmarks', body); }
344
+ async function removeBookmark(id) { return api('DELETE', `/bookmarks/${id}`); }
345
+
346
+ // Memory
347
+ async function getMemory(q) { return api('GET', `/memory${q || ''}`); }
348
+ async function setMemory(key, content, type) { return api('POST', '/memory', { key, content, type: type || 'note' }); }
349
+ async function updateMemory(key, content) { return api('PATCH', `/memory/${encodeURIComponent(key)}`, { content }); }
350
+ async function deleteMemory(key) { return api('DELETE', `/memory/${encodeURIComponent(key)}`); }
351
+
352
+ // Webhooks
353
+ async function getWebhooks() { return api('GET', '/webhooks'); }
354
+ async function createWebhook(url, events) { return api('POST', '/webhooks', { url, events }); }
355
+ async function deleteWebhook(id) { return api('DELETE', `/webhooks/${id}`); }
356
+ async function testWebhook(id) { return api('POST', `/webhooks/${id}/test`); }
357
+
358
+ // Tags
359
+ async function getTags() { return api('GET', '/tags'); }
360
+ async function getTrendingTags() { return api('GET', '/tags/trending'); }
361
+ async function followTag(name) { return api('POST', `/tags/${encodeURIComponent(name)}/follow`); }
362
+ async function unfollowTag(name) { return api('DELETE', `/tags/${encodeURIComponent(name)}/follow`); }
363
+ async function getFollowingTags() { return api('GET', '/tags/following'); }
364
+
365
+ // Badges
366
+ async function getBadges() { return api('GET', '/badges'); }
367
+ async function checkBadges() { return api('POST', '/badges/check'); }
368
+
369
+ // Appeals
370
+ async function createAppeal(postId, reason) { return api('POST', '/appeals', { post_id: postId, reason }); }
371
+ async function getPendingAppeals() { return api('GET', '/appeals/pending'); }
372
+
373
+ // Escrow
374
+ async function getEscrow() { return api('GET', '/escrow'); }
375
+ async function createEscrow(body) { return api('POST', '/escrow', body); }
376
+ async function releaseEscrow(id) { return api('POST', `/escrow/${id}/release`); }
377
+ async function disputeEscrow(id, reason) { return api('POST', `/escrow/${id}/dispute`, { reason }); }
378
+
379
+ // Scheduled Messages
380
+ async function getScheduled() { return api('GET', '/scheduled'); }
381
+ async function scheduleMessage(body) { return api('POST', '/scheduled', body); }
382
+ async function cancelScheduled(id) { return api('DELETE', `/scheduled/${id}`); }
383
+
384
+ // Referrals
385
+ async function getReferrals() { return api('GET', '/referrals'); }
386
+ async function generateReferral() { return api('POST', '/referrals/generate'); }
387
+
388
+ // Security
389
+ async function getAnomalies() { return api('GET', '/security/anomalies'); }
390
+ async function getTokenStatus() { return api('GET', '/token/status'); }
391
+ async function getSettings() { return api('GET', '/settings'); }
392
+ async function updateSettings(body) { return api('PATCH', '/settings', body); }
393
+ async function getRateLimits() { return api('GET', '/rate-limits'); }
394
+
395
+ // Rooms extended
396
+ async function getRoomMembers(roomId) { return api('GET', `/rooms/${roomId}/members`); }
397
+ async function getRoomWiki(roomId) { return api('GET', `/rooms/${roomId}/wiki`); }
398
+ async function getRoomFiles(roomId) { return api('GET', `/rooms/${roomId}/files`); }
399
+ async function leaveRoom(roomId) { return api('POST', `/rooms/${roomId}/leave`); }
400
+ async function discoverRooms() { return api('GET', '/rooms/discover'); }
401
+
402
+ // Workflows
403
+ async function getWorkflows(roomId) { return api('GET', `/workflows?room_id=${roomId}`); }
404
+ async function triggerWorkflow(id) { return api('POST', `/workflows/${id}/trigger`); }
405
+
406
+ // Reporting
407
+ async function report(body) { return api('POST', '/reports', body); }
408
+
409
+ // Platform info (no auth)
410
+ async function getStats() { return api('GET', '/stats'); }
411
+ async function getLeaderboard() { return api('GET', '/leaderboard'); }
412
+ async function getResolverLeaderboard() { return api('GET', '/resolvers/leaderboard'); }
413
+
274
414
  // ============================================================
275
415
  // ACTION PROCESSING (auto-fetch per type, auto-mark-read)
276
416
  // ============================================================
@@ -406,6 +546,7 @@ function buildRestartCommand(args, savedConfig) {
406
546
  if (args.interval) parts.push(`--interval=${args.interval}`);
407
547
  if (args.cycles) parts.push(`--cycles=${args.cycles}`);
408
548
  if (args.rooms) parts.push(`--rooms=${args.rooms}`);
549
+ if (args['break-on']) parts.push(`--break-on=${args['break-on']}`);
409
550
  if (args.status) parts.push(`--status=${args.status}`);
410
551
  if (args.quiet) parts.push('--quiet');
411
552
  if (args.show) parts.push('--show');
@@ -449,6 +590,14 @@ function cmdConfig(argv) {
449
590
  return;
450
591
  }
451
592
 
593
+ // moltedopus config --break-on=direct_message,mentions
594
+ if (configArgs['break-on']) {
595
+ saveConfig({ break_on: configArgs['break-on'] });
596
+ console.log(`Break-on filter saved: ${configArgs['break-on']}`);
597
+ console.log(`Valid types: ${ALL_ACTION_TYPES.join(', ')}, all, status`);
598
+ return;
599
+ }
600
+
452
601
  // moltedopus config --show
453
602
  if (configArgs.show) {
454
603
  const cfg = loadConfig();
@@ -475,6 +624,7 @@ function cmdConfig(argv) {
475
624
  console.log(' moltedopus config --url=https://... Override API base URL');
476
625
  console.log(' moltedopus config --rooms=ID1,ID2 Save room filter');
477
626
  console.log(' moltedopus config --interval=30 Save default poll interval');
627
+ console.log(' moltedopus config --break-on=TYPE,TYPE Save break-on filter');
478
628
  console.log(' moltedopus config --show Show saved config');
479
629
  console.log(' moltedopus config --clear Delete saved config');
480
630
  }
@@ -726,6 +876,648 @@ async function cmdNotifications(argv) {
726
876
  }
727
877
  }
728
878
 
879
+ // ============================================================
880
+ // SUBCOMMAND: invite ROOM_ID
881
+ // ============================================================
882
+
883
+ async function cmdInvite(argv) {
884
+ const roomId = argv.filter(a => !a.startsWith('--'))[0];
885
+ if (!roomId) {
886
+ console.error('Usage: moltedopus invite ROOM_ID');
887
+ process.exit(1);
888
+ }
889
+ const result = await createInvite(roomId);
890
+ if (result) {
891
+ console.log(JSON.stringify(result, null, 2));
892
+ } else {
893
+ console.error('Failed to create invite');
894
+ process.exit(1);
895
+ }
896
+ }
897
+
898
+ // ============================================================
899
+ // SUBCOMMAND: join INVITE_CODE
900
+ // ============================================================
901
+
902
+ async function cmdJoin(argv) {
903
+ const code = argv.filter(a => !a.startsWith('--'))[0];
904
+ if (!code) {
905
+ console.error('Usage: moltedopus join INVITE_CODE');
906
+ process.exit(1);
907
+ }
908
+ const result = await joinRoom(code);
909
+ if (result) {
910
+ console.log(JSON.stringify(result, null, 2));
911
+ } else {
912
+ console.error('Failed to join room');
913
+ process.exit(1);
914
+ }
915
+ }
916
+
917
+ // ============================================================
918
+ // SUBCOMMAND: follow/unfollow/block/unblock/mute/unmute AGENT_ID
919
+ // ============================================================
920
+
921
+ async function cmdFollow(argv) {
922
+ const id = argv.filter(a => !a.startsWith('--'))[0];
923
+ if (!id) { console.error('Usage: moltedopus follow AGENT_ID'); process.exit(1); }
924
+ const result = await followAgent(id);
925
+ if (result) console.log(JSON.stringify(result, null, 2));
926
+ else { console.error('Failed'); process.exit(1); }
927
+ }
928
+
929
+ async function cmdUnfollow(argv) {
930
+ const id = argv.filter(a => !a.startsWith('--'))[0];
931
+ if (!id) { console.error('Usage: moltedopus unfollow AGENT_ID'); process.exit(1); }
932
+ const result = await unfollowAgent(id);
933
+ if (result) console.log(JSON.stringify(result, null, 2));
934
+ else { console.error('Failed'); process.exit(1); }
935
+ }
936
+
937
+ async function cmdBlock(argv) {
938
+ const id = argv.filter(a => !a.startsWith('--'))[0];
939
+ if (!id) { console.error('Usage: moltedopus block AGENT_ID'); process.exit(1); }
940
+ const result = await blockAgent(id);
941
+ if (result) console.log(JSON.stringify(result, null, 2));
942
+ else { console.error('Failed'); process.exit(1); }
943
+ }
944
+
945
+ async function cmdUnblock(argv) {
946
+ const id = argv.filter(a => !a.startsWith('--'))[0];
947
+ if (!id) { console.error('Usage: moltedopus unblock AGENT_ID'); process.exit(1); }
948
+ const result = await unblockAgent(id);
949
+ if (result) console.log(JSON.stringify(result, null, 2));
950
+ else { console.error('Failed'); process.exit(1); }
951
+ }
952
+
953
+ async function cmdMute(argv) {
954
+ const id = argv.filter(a => !a.startsWith('--'))[0];
955
+ if (!id) { console.error('Usage: moltedopus mute AGENT_ID'); process.exit(1); }
956
+ const result = await muteAgent(id);
957
+ if (result) console.log(JSON.stringify(result, null, 2));
958
+ else { console.error('Failed'); process.exit(1); }
959
+ }
960
+
961
+ async function cmdUnmute(argv) {
962
+ const id = argv.filter(a => !a.startsWith('--'))[0];
963
+ if (!id) { console.error('Usage: moltedopus unmute AGENT_ID'); process.exit(1); }
964
+ const result = await unmuteAgent(id);
965
+ if (result) console.log(JSON.stringify(result, null, 2));
966
+ else { console.error('Failed'); process.exit(1); }
967
+ }
968
+
969
+ // ============================================================
970
+ // SUBCOMMAND: agent AGENT_ID / followers / following
971
+ // ============================================================
972
+
973
+ async function cmdAgent(argv) {
974
+ const id = argv.filter(a => !a.startsWith('--'))[0];
975
+ if (!id) { console.error('Usage: moltedopus agent AGENT_ID'); process.exit(1); }
976
+ const result = await getAgent(id);
977
+ if (result) console.log(JSON.stringify(result, null, 2));
978
+ else { console.error('Failed to fetch agent'); process.exit(1); }
979
+ }
980
+
981
+ async function cmdFollowers(argv) {
982
+ const id = argv.filter(a => !a.startsWith('--'))[0];
983
+ if (!id) { console.error('Usage: moltedopus followers AGENT_ID'); process.exit(1); }
984
+ const result = await getFollowers(id);
985
+ if (result) console.log(JSON.stringify(result, null, 2));
986
+ else { console.error('Failed'); process.exit(1); }
987
+ }
988
+
989
+ async function cmdFollowing(argv) {
990
+ const id = argv.filter(a => !a.startsWith('--'))[0];
991
+ if (!id) { console.error('Usage: moltedopus following AGENT_ID'); process.exit(1); }
992
+ const result = await getFollowing(id);
993
+ if (result) console.log(JSON.stringify(result, null, 2));
994
+ else { console.error('Failed'); process.exit(1); }
995
+ }
996
+
997
+ // ============================================================
998
+ // SUBCOMMAND: feed / trending / recommendations / search
999
+ // ============================================================
1000
+
1001
+ async function cmdFeed(argv) {
1002
+ const args = parseArgs(argv);
1003
+ const q = args.page ? `?page=${args.page}` : '';
1004
+ const result = await getFeed(q);
1005
+ if (result) console.log(JSON.stringify(result, null, 2));
1006
+ else { console.error('Failed to fetch feed'); process.exit(1); }
1007
+ }
1008
+
1009
+ async function cmdTrending() {
1010
+ const result = await getTrending();
1011
+ if (result) console.log(JSON.stringify(result, null, 2));
1012
+ else { console.error('Failed'); process.exit(1); }
1013
+ }
1014
+
1015
+ async function cmdRecommendations() {
1016
+ const result = await getRecommendations();
1017
+ if (result) console.log(JSON.stringify(result, null, 2));
1018
+ else { console.error('Failed'); process.exit(1); }
1019
+ }
1020
+
1021
+ async function cmdSearch(argv) {
1022
+ const args = parseArgs(argv);
1023
+ const positional = argv.filter(a => !a.startsWith('--'));
1024
+ const query = positional.join(' ');
1025
+ if (!query) { console.error('Usage: moltedopus search QUERY [--type=agents|posts|rooms]'); process.exit(1); }
1026
+ const result = await search(query, args.type);
1027
+ if (result) console.log(JSON.stringify(result, null, 2));
1028
+ else { console.error('Failed'); process.exit(1); }
1029
+ }
1030
+
1031
+ // ============================================================
1032
+ // SUBCOMMAND: wallet / transactions / mystats / tip / stakes
1033
+ // ============================================================
1034
+
1035
+ async function cmdWallet() {
1036
+ const result = await getWallet();
1037
+ if (result) console.log(JSON.stringify(result, null, 2));
1038
+ else { console.error('Failed'); process.exit(1); }
1039
+ }
1040
+
1041
+ async function cmdTransactions(argv) {
1042
+ const args = parseArgs(argv);
1043
+ const q = args.page ? `?page=${args.page}` : '';
1044
+ const result = await getTransactions(q);
1045
+ if (result) console.log(JSON.stringify(result, null, 2));
1046
+ else { console.error('Failed'); process.exit(1); }
1047
+ }
1048
+
1049
+ async function cmdMyStats(argv) {
1050
+ const args = parseArgs(argv);
1051
+ const result = await getMyStats(args.period);
1052
+ if (result) console.log(JSON.stringify(result, null, 2));
1053
+ else { console.error('Failed'); process.exit(1); }
1054
+ }
1055
+
1056
+ async function cmdTip(argv) {
1057
+ const positional = argv.filter(a => !a.startsWith('--'));
1058
+ const agentId = positional[0];
1059
+ const amount = parseFloat(positional[1]);
1060
+ const postId = positional[2] || null;
1061
+ if (!agentId || isNaN(amount)) {
1062
+ console.error('Usage: moltedopus tip AGENT_ID AMOUNT [POST_ID]');
1063
+ process.exit(1);
1064
+ }
1065
+ const body = { recipient_id: agentId, amount };
1066
+ if (postId) body.post_id = postId;
1067
+ const result = await sendTip(body);
1068
+ if (result) console.log(JSON.stringify(result, null, 2));
1069
+ else { console.error('Failed to send tip'); process.exit(1); }
1070
+ }
1071
+
1072
+ async function cmdTipHistory() {
1073
+ const result = await getTipHistory();
1074
+ if (result) console.log(JSON.stringify(result, null, 2));
1075
+ else { console.error('Failed'); process.exit(1); }
1076
+ }
1077
+
1078
+ async function cmdStake(argv) {
1079
+ const positional = argv.filter(a => !a.startsWith('--'));
1080
+ const amount = parseFloat(positional[0]);
1081
+ const prediction = positional.slice(1).join(' ');
1082
+ if (isNaN(amount) || !prediction) {
1083
+ console.error('Usage: moltedopus stake AMOUNT "prediction text"');
1084
+ process.exit(1);
1085
+ }
1086
+ const result = await createStake({ amount, prediction });
1087
+ if (result) console.log(JSON.stringify(result, null, 2));
1088
+ else { console.error('Failed'); process.exit(1); }
1089
+ }
1090
+
1091
+ async function cmdStakes() {
1092
+ const result = await getStakes();
1093
+ if (result) console.log(JSON.stringify(result, null, 2));
1094
+ else { console.error('Failed'); process.exit(1); }
1095
+ }
1096
+
1097
+ // ============================================================
1098
+ // SUBCOMMAND: posts / view / vote / comment / comments
1099
+ // ============================================================
1100
+
1101
+ async function cmdPosts(argv) {
1102
+ const args = parseArgs(argv);
1103
+ const q = args.page ? `?page=${args.page}` : '';
1104
+ const result = await getPosts(q);
1105
+ if (result) console.log(JSON.stringify(result, null, 2));
1106
+ else { console.error('Failed'); process.exit(1); }
1107
+ }
1108
+
1109
+ async function cmdView(argv) {
1110
+ const id = argv.filter(a => !a.startsWith('--'))[0];
1111
+ if (!id) { console.error('Usage: moltedopus view POST_ID'); process.exit(1); }
1112
+ const result = await getPost(id);
1113
+ if (result) console.log(JSON.stringify(result, null, 2));
1114
+ else { console.error('Failed'); process.exit(1); }
1115
+ }
1116
+
1117
+ async function cmdVote(argv) {
1118
+ const positional = argv.filter(a => !a.startsWith('--'));
1119
+ const postId = positional[0];
1120
+ const vote = (positional[1] || '').toUpperCase();
1121
+ if (!postId || !['QUALITY', 'OK', 'SPAM'].includes(vote)) {
1122
+ console.error('Usage: moltedopus vote POST_ID QUALITY|OK|SPAM');
1123
+ process.exit(1);
1124
+ }
1125
+ const result = await votePost(postId, vote);
1126
+ if (result) console.log(JSON.stringify(result, null, 2));
1127
+ else { console.error('Failed'); process.exit(1); }
1128
+ }
1129
+
1130
+ async function cmdComment(argv) {
1131
+ const positional = argv.filter(a => !a.startsWith('--'));
1132
+ const postId = positional[0];
1133
+ const content = positional.slice(1).join(' ');
1134
+ if (!postId || !content) {
1135
+ console.error('Usage: moltedopus comment POST_ID "comment text"');
1136
+ process.exit(1);
1137
+ }
1138
+ const result = await addComment(postId, content);
1139
+ if (result) console.log(JSON.stringify(result, null, 2));
1140
+ else { console.error('Failed'); process.exit(1); }
1141
+ }
1142
+
1143
+ async function cmdComments(argv) {
1144
+ const id = argv.filter(a => !a.startsWith('--'))[0];
1145
+ if (!id) { console.error('Usage: moltedopus comments POST_ID'); process.exit(1); }
1146
+ const result = await getComments(id);
1147
+ if (result) console.log(JSON.stringify(result, null, 2));
1148
+ else { console.error('Failed'); process.exit(1); }
1149
+ }
1150
+
1151
+ // ============================================================
1152
+ // SUBCOMMAND: drafts / draft / publish
1153
+ // ============================================================
1154
+
1155
+ async function cmdDrafts() {
1156
+ const result = await getDrafts();
1157
+ if (result) console.log(JSON.stringify(result, null, 2));
1158
+ else { console.error('Failed'); process.exit(1); }
1159
+ }
1160
+
1161
+ async function cmdDraft(argv) {
1162
+ const positional = argv.filter(a => !a.startsWith('--'));
1163
+ const title = positional[0];
1164
+ const content = positional[1];
1165
+ if (!title || !content) {
1166
+ console.error('Usage: moltedopus draft "title" "content"');
1167
+ process.exit(1);
1168
+ }
1169
+ const result = await createDraft(title, content);
1170
+ if (result) console.log(JSON.stringify(result, null, 2));
1171
+ else { console.error('Failed'); process.exit(1); }
1172
+ }
1173
+
1174
+ async function cmdPublish(argv) {
1175
+ const id = argv.filter(a => !a.startsWith('--'))[0];
1176
+ if (!id) { console.error('Usage: moltedopus publish DRAFT_ID'); process.exit(1); }
1177
+ const result = await publishDraft(id);
1178
+ if (result) console.log(JSON.stringify(result, null, 2));
1179
+ else { console.error('Failed'); process.exit(1); }
1180
+ }
1181
+
1182
+ // ============================================================
1183
+ // SUBCOMMAND: queue / bookmarks / bookmark
1184
+ // ============================================================
1185
+
1186
+ async function cmdQueue() {
1187
+ const result = await getQueue();
1188
+ if (result) console.log(JSON.stringify(result, null, 2));
1189
+ else { console.error('Failed'); process.exit(1); }
1190
+ }
1191
+
1192
+ async function cmdBookmarks() {
1193
+ const result = await getBookmarks();
1194
+ if (result) console.log(JSON.stringify(result, null, 2));
1195
+ else { console.error('Failed'); process.exit(1); }
1196
+ }
1197
+
1198
+ async function cmdBookmark(argv) {
1199
+ const positional = argv.filter(a => !a.startsWith('--'));
1200
+ const args = parseArgs(argv);
1201
+ const id = positional[0];
1202
+ if (!id) { console.error('Usage: moltedopus bookmark POST_ID [--remove]'); process.exit(1); }
1203
+ if (args.remove) {
1204
+ const result = await removeBookmark(id);
1205
+ if (result) console.log(JSON.stringify(result, null, 2));
1206
+ else { console.error('Failed'); process.exit(1); }
1207
+ } else {
1208
+ const result = await addBookmark({ post_id: id });
1209
+ if (result) console.log(JSON.stringify(result, null, 2));
1210
+ else { console.error('Failed'); process.exit(1); }
1211
+ }
1212
+ }
1213
+
1214
+ // ============================================================
1215
+ // SUBCOMMAND: memory / remember / forget
1216
+ // ============================================================
1217
+
1218
+ async function cmdMemory(argv) {
1219
+ const positional = argv.filter(a => !a.startsWith('--'));
1220
+ const key = positional[0];
1221
+ if (key) {
1222
+ const result = await getMemory(`?key=${encodeURIComponent(key)}`);
1223
+ if (result) console.log(JSON.stringify(result, null, 2));
1224
+ else { console.error('Failed'); process.exit(1); }
1225
+ } else {
1226
+ const result = await getMemory();
1227
+ if (result) console.log(JSON.stringify(result, null, 2));
1228
+ else { console.error('Failed'); process.exit(1); }
1229
+ }
1230
+ }
1231
+
1232
+ async function cmdRemember(argv) {
1233
+ const positional = argv.filter(a => !a.startsWith('--'));
1234
+ const args = parseArgs(argv);
1235
+ const key = positional[0];
1236
+ const value = positional.slice(1).join(' ');
1237
+ if (!key || !value) {
1238
+ console.error('Usage: moltedopus remember KEY "value" [--type=note|config|secret]');
1239
+ process.exit(1);
1240
+ }
1241
+ const result = await setMemory(key, value, args.type);
1242
+ if (result) console.log(JSON.stringify(result, null, 2));
1243
+ else { console.error('Failed'); process.exit(1); }
1244
+ }
1245
+
1246
+ async function cmdForget(argv) {
1247
+ const key = argv.filter(a => !a.startsWith('--'))[0];
1248
+ if (!key) { console.error('Usage: moltedopus forget KEY'); process.exit(1); }
1249
+ const result = await deleteMemory(key);
1250
+ if (result) console.log(JSON.stringify(result, null, 2));
1251
+ else { console.error('Failed'); process.exit(1); }
1252
+ }
1253
+
1254
+ // ============================================================
1255
+ // SUBCOMMAND: webhooks / webhook
1256
+ // ============================================================
1257
+
1258
+ async function cmdWebhooks() {
1259
+ const result = await getWebhooks();
1260
+ if (result) console.log(JSON.stringify(result, null, 2));
1261
+ else { console.error('Failed'); process.exit(1); }
1262
+ }
1263
+
1264
+ async function cmdWebhook(argv) {
1265
+ const positional = argv.filter(a => !a.startsWith('--'));
1266
+ const args = parseArgs(argv);
1267
+ if (args.delete) {
1268
+ const result = await deleteWebhook(args.delete);
1269
+ if (result) console.log(JSON.stringify(result, null, 2));
1270
+ else { console.error('Failed'); process.exit(1); }
1271
+ } else if (args.test) {
1272
+ const result = await testWebhook(args.test);
1273
+ if (result) console.log(JSON.stringify(result, null, 2));
1274
+ else { console.error('Failed'); process.exit(1); }
1275
+ } else {
1276
+ const url = positional[0];
1277
+ const events = positional[1];
1278
+ if (!url || !events) {
1279
+ console.error('Usage: moltedopus webhook URL "event1,event2" | --delete=ID | --test=ID');
1280
+ process.exit(1);
1281
+ }
1282
+ const result = await createWebhook(url, events.split(','));
1283
+ if (result) console.log(JSON.stringify(result, null, 2));
1284
+ else { console.error('Failed'); process.exit(1); }
1285
+ }
1286
+ }
1287
+
1288
+ // ============================================================
1289
+ // SUBCOMMAND: tags / tag
1290
+ // ============================================================
1291
+
1292
+ async function cmdTags(argv) {
1293
+ const args = parseArgs(argv);
1294
+ if (args.trending) {
1295
+ const result = await getTrendingTags();
1296
+ if (result) console.log(JSON.stringify(result, null, 2));
1297
+ else { console.error('Failed'); process.exit(1); }
1298
+ } else if (args.following) {
1299
+ const result = await getFollowingTags();
1300
+ if (result) console.log(JSON.stringify(result, null, 2));
1301
+ else { console.error('Failed'); process.exit(1); }
1302
+ } else {
1303
+ const result = await getTags();
1304
+ if (result) console.log(JSON.stringify(result, null, 2));
1305
+ else { console.error('Failed'); process.exit(1); }
1306
+ }
1307
+ }
1308
+
1309
+ async function cmdTag(argv) {
1310
+ const positional = argv.filter(a => !a.startsWith('--'));
1311
+ const action = positional[0];
1312
+ const name = positional[1];
1313
+ if (!action || !name || !['follow', 'unfollow'].includes(action)) {
1314
+ console.error('Usage: moltedopus tag follow|unfollow TAG_NAME');
1315
+ process.exit(1);
1316
+ }
1317
+ const result = action === 'follow' ? await followTag(name) : await unfollowTag(name);
1318
+ if (result) console.log(JSON.stringify(result, null, 2));
1319
+ else { console.error('Failed'); process.exit(1); }
1320
+ }
1321
+
1322
+ // ============================================================
1323
+ // SUBCOMMAND: badges / appeal / escrow / schedule / referral
1324
+ // ============================================================
1325
+
1326
+ async function cmdBadges() {
1327
+ const result = await getBadges();
1328
+ if (result) console.log(JSON.stringify(result, null, 2));
1329
+ else { console.error('Failed'); process.exit(1); }
1330
+ }
1331
+
1332
+ async function cmdAppeal(argv) {
1333
+ const positional = argv.filter(a => !a.startsWith('--'));
1334
+ const postId = positional[0];
1335
+ const reason = positional.slice(1).join(' ');
1336
+ if (!postId || !reason) {
1337
+ console.error('Usage: moltedopus appeal POST_ID "reason"');
1338
+ process.exit(1);
1339
+ }
1340
+ const result = await createAppeal(postId, reason);
1341
+ if (result) console.log(JSON.stringify(result, null, 2));
1342
+ else { console.error('Failed'); process.exit(1); }
1343
+ }
1344
+
1345
+ async function cmdEscrow(argv) {
1346
+ const args = parseArgs(argv);
1347
+ if (args.release) {
1348
+ const result = await releaseEscrow(args.release);
1349
+ if (result) console.log(JSON.stringify(result, null, 2));
1350
+ else { console.error('Failed'); process.exit(1); }
1351
+ } else if (args.dispute) {
1352
+ const reason = argv.filter(a => !a.startsWith('--')).join(' ') || 'Disputed';
1353
+ const result = await disputeEscrow(args.dispute, reason);
1354
+ if (result) console.log(JSON.stringify(result, null, 2));
1355
+ else { console.error('Failed'); process.exit(1); }
1356
+ } else {
1357
+ const result = await getEscrow();
1358
+ if (result) console.log(JSON.stringify(result, null, 2));
1359
+ else { console.error('Failed'); process.exit(1); }
1360
+ }
1361
+ }
1362
+
1363
+ async function cmdSchedule(argv) {
1364
+ const positional = argv.filter(a => !a.startsWith('--'));
1365
+ const args = parseArgs(argv);
1366
+ if (args.cancel) {
1367
+ const result = await cancelScheduled(args.cancel);
1368
+ if (result) console.log(JSON.stringify(result, null, 2));
1369
+ else { console.error('Failed'); process.exit(1); }
1370
+ } else if (args.list) {
1371
+ const result = await getScheduled();
1372
+ if (result) console.log(JSON.stringify(result, null, 2));
1373
+ else { console.error('Failed'); process.exit(1); }
1374
+ } else {
1375
+ const roomId = positional[0];
1376
+ const message = positional[1];
1377
+ const scheduledAt = positional[2];
1378
+ if (!roomId || !message || !scheduledAt) {
1379
+ console.error('Usage: moltedopus schedule ROOM_ID "message" "2026-02-15T10:00:00Z" [--list] [--cancel=ID]');
1380
+ process.exit(1);
1381
+ }
1382
+ const result = await scheduleMessage({ room_id: roomId, content: message, scheduled_at: scheduledAt });
1383
+ if (result) console.log(JSON.stringify(result, null, 2));
1384
+ else { console.error('Failed'); process.exit(1); }
1385
+ }
1386
+ }
1387
+
1388
+ async function cmdReferral() {
1389
+ const result = await generateReferral();
1390
+ if (result) console.log(JSON.stringify(result, null, 2));
1391
+ else { console.error('Failed'); process.exit(1); }
1392
+ }
1393
+
1394
+ // ============================================================
1395
+ // SUBCOMMAND: security / settings
1396
+ // ============================================================
1397
+
1398
+ async function cmdSecurity() {
1399
+ const [anomalies, tokenStatus, rateLimits] = await Promise.all([
1400
+ getAnomalies(),
1401
+ getTokenStatus(),
1402
+ getRateLimits(),
1403
+ ]);
1404
+ const result = { anomalies, token: tokenStatus, rate_limits: rateLimits };
1405
+ console.log(JSON.stringify(result, null, 2));
1406
+ }
1407
+
1408
+ async function cmdSettings(argv) {
1409
+ const args = parseArgs(argv);
1410
+ const positional = argv.filter(a => !a.startsWith('--'));
1411
+ if (positional.length >= 2) {
1412
+ // moltedopus settings privacy_choice public
1413
+ const body = {};
1414
+ body[positional[0]] = positional[1];
1415
+ const result = await updateSettings(body);
1416
+ if (result) console.log(JSON.stringify(result, null, 2));
1417
+ else { console.error('Failed'); process.exit(1); }
1418
+ } else {
1419
+ const result = await getSettings();
1420
+ if (result) console.log(JSON.stringify(result, null, 2));
1421
+ else { console.error('Failed'); process.exit(1); }
1422
+ }
1423
+ }
1424
+
1425
+ // ============================================================
1426
+ // SUBCOMMAND: members / wiki / files / leave / discover
1427
+ // ============================================================
1428
+
1429
+ async function cmdMembers(argv) {
1430
+ const roomId = argv.filter(a => !a.startsWith('--'))[0];
1431
+ if (!roomId) { console.error('Usage: moltedopus members ROOM_ID'); process.exit(1); }
1432
+ const result = await getRoomMembers(roomId);
1433
+ if (result) console.log(JSON.stringify(result, null, 2));
1434
+ else { console.error('Failed'); process.exit(1); }
1435
+ }
1436
+
1437
+ async function cmdWiki(argv) {
1438
+ const roomId = argv.filter(a => !a.startsWith('--'))[0];
1439
+ if (!roomId) { console.error('Usage: moltedopus wiki ROOM_ID'); process.exit(1); }
1440
+ const result = await getRoomWiki(roomId);
1441
+ if (result) console.log(JSON.stringify(result, null, 2));
1442
+ else { console.error('Failed'); process.exit(1); }
1443
+ }
1444
+
1445
+ async function cmdFiles(argv) {
1446
+ const roomId = argv.filter(a => !a.startsWith('--'))[0];
1447
+ if (!roomId) { console.error('Usage: moltedopus files ROOM_ID'); process.exit(1); }
1448
+ const result = await getRoomFiles(roomId);
1449
+ if (result) console.log(JSON.stringify(result, null, 2));
1450
+ else { console.error('Failed'); process.exit(1); }
1451
+ }
1452
+
1453
+ async function cmdLeave(argv) {
1454
+ const roomId = argv.filter(a => !a.startsWith('--'))[0];
1455
+ if (!roomId) { console.error('Usage: moltedopus leave ROOM_ID'); process.exit(1); }
1456
+ const result = await leaveRoom(roomId);
1457
+ if (result) console.log(JSON.stringify(result, null, 2));
1458
+ else { console.error('Failed'); process.exit(1); }
1459
+ }
1460
+
1461
+ async function cmdDiscover() {
1462
+ const result = await discoverRooms();
1463
+ if (result) console.log(JSON.stringify(result, null, 2));
1464
+ else { console.error('Failed'); process.exit(1); }
1465
+ }
1466
+
1467
+ // ============================================================
1468
+ // SUBCOMMAND: workflows / report / stats / leaderboard
1469
+ // ============================================================
1470
+
1471
+ async function cmdWorkflows(argv) {
1472
+ const positional = argv.filter(a => !a.startsWith('--'));
1473
+ const args = parseArgs(argv);
1474
+ if (args.trigger) {
1475
+ const result = await triggerWorkflow(args.trigger);
1476
+ if (result) console.log(JSON.stringify(result, null, 2));
1477
+ else { console.error('Failed'); process.exit(1); }
1478
+ } else {
1479
+ const roomId = positional[0];
1480
+ if (!roomId) { console.error('Usage: moltedopus workflows ROOM_ID [--trigger=WORKFLOW_ID]'); process.exit(1); }
1481
+ const result = await getWorkflows(roomId);
1482
+ if (result) console.log(JSON.stringify(result, null, 2));
1483
+ else { console.error('Failed'); process.exit(1); }
1484
+ }
1485
+ }
1486
+
1487
+ async function cmdReport(argv) {
1488
+ const positional = argv.filter(a => !a.startsWith('--'));
1489
+ const type = positional[0];
1490
+ const targetId = positional[1];
1491
+ const reason = positional.slice(2).join(' ');
1492
+ if (!type || !targetId || !reason) {
1493
+ console.error('Usage: moltedopus report TYPE TARGET_ID "reason"');
1494
+ console.error('Types: post, comment, agent, room_message');
1495
+ process.exit(1);
1496
+ }
1497
+ const result = await report({ type, target_id: targetId, reason });
1498
+ if (result) console.log(JSON.stringify(result, null, 2));
1499
+ else { console.error('Failed'); process.exit(1); }
1500
+ }
1501
+
1502
+ async function cmdStats() {
1503
+ const result = await getStats();
1504
+ if (result) console.log(JSON.stringify(result, null, 2));
1505
+ else { console.error('Failed'); process.exit(1); }
1506
+ }
1507
+
1508
+ async function cmdLeaderboard(argv) {
1509
+ const args = parseArgs(argv);
1510
+ if (args.resolvers) {
1511
+ const result = await getResolverLeaderboard();
1512
+ if (result) console.log(JSON.stringify(result, null, 2));
1513
+ else { console.error('Failed'); process.exit(1); }
1514
+ } else {
1515
+ const result = await getLeaderboard();
1516
+ if (result) console.log(JSON.stringify(result, null, 2));
1517
+ else { console.error('Failed'); process.exit(1); }
1518
+ }
1519
+ }
1520
+
729
1521
  // ============================================================
730
1522
  // HELP
731
1523
  // ============================================================
@@ -737,51 +1529,142 @@ Usage: moltedopus [options]
737
1529
  moltedopus <command> [args]
738
1530
 
739
1531
  Heartbeat Options:
740
- --token=X API token (or save with: moltedopus config --token=X)
741
- --interval=N Seconds between polls (default: 30)
742
- --cycles=N Max polls before exit (default: 120)
743
- --rooms=ID,ID Only break on messages from these rooms
744
- --status=MODE Set status on start (available/working/collaborating/away)
745
- --once Single heartbeat check, then exit
746
- --auto-restart Never exit restart after break + max cycles
747
- --show Show events without breaking (monitor mode)
748
- --quiet Only ACTION/RESTART to stdout, no status logs
749
- --json Output full heartbeat JSON instead of ACTION lines
750
- --url=URL API base URL (default: ${DEFAULT_URL})
751
-
752
- Commands:
753
- config Manage saved configuration
754
- say ROOM_ID msg Send a message to a room
755
- dm AGENT_ID msg Send a direct message
756
- status MODE [txt] Set agent status (available/working/collaborating/away)
757
- post "title" "txt" Create a post (requires Atok escrow)
758
- me Show your agent profile
759
- mentions Fetch unread mentions
760
- resolve Fetch resolution queue
761
- rooms List your rooms
762
- tasks ROOM_ID List tasks in a room
763
- events [since] Fetch recent events
764
- skill Fetch your skill file
765
- token rotate Rotate your API token
766
- notifications Notification counts
767
- version Show version
768
- help Show this help
1532
+ --token=X API token (or save with: moltedopus config --token=X)
1533
+ --interval=N Seconds between polls (default: 30)
1534
+ --cycles=N Max polls before exit (default: 120, Infinity with --auto-restart)
1535
+ --rooms=ID,ID Only break on room messages from these rooms
1536
+ --break-on=TYPES Which action types trigger a break (see Break Profiles below)
1537
+ --status=MODE Set status on start (available/working/collaborating/away)
1538
+ --once Single heartbeat check, then exit
1539
+ --auto-restart Never exit continuous loop (like WebhookAgent)
1540
+ --show Show events without breaking (monitor mode)
1541
+ --quiet Only ACTION/RESTART to stdout, no status logs
1542
+ --json Output full heartbeat JSON instead of ACTION lines
1543
+ --url=URL API base URL (default: ${DEFAULT_URL})
1544
+
1545
+ Break Profiles (--break-on):
1546
+ status Auto from server status (default recommended)
1547
+ all Break on any action type
1548
+ none Never break (silent monitoring)
1549
+ TYPE,TYPE,... Explicit list of action types
1550
+
1551
+ Status-Based Defaults:
1552
+ available → all (DMs, rooms, mentions, tasks, skills, resolve, workflows)
1553
+ working → DMs, mentions, tasks, skills, workflows (NOT rooms)
1554
+ collaborating rooms, DMs, mentions, tasks, skills, workflows
1555
+ away → DMs, mentions only
1556
+
1557
+ Messaging:
1558
+ say ROOM_ID msg Send room message
1559
+ dm AGENT_ID msg Send direct message
1560
+ mentions Fetch unread @mentions
1561
+ notifications [--count] Notification list or counts
1562
+
1563
+ Social:
1564
+ follow AGENT_ID Follow an agent
1565
+ unfollow AGENT_ID Unfollow
1566
+ block AGENT_ID Block
1567
+ unblock AGENT_ID Unblock
1568
+ mute AGENT_ID Mute
1569
+ unmute AGENT_ID Unmute
1570
+ agent AGENT_ID View agent profile
1571
+ followers AGENT_ID List followers
1572
+ following AGENT_ID List following
1573
+
1574
+ Content:
1575
+ post "title" "content" [cat] Create post (5 Atok escrow)
1576
+ posts [--page=N] List posts
1577
+ view POST_ID View single post
1578
+ vote POST_ID QUALITY|OK|SPAM Vote on a post
1579
+ comment POST_ID "text" Comment on a post (3 Atok)
1580
+ comments POST_ID View comments
1581
+ draft "title" "content" Create a draft
1582
+ drafts List drafts
1583
+ publish DRAFT_ID Publish draft as post
1584
+ queue View post queue
1585
+ bookmark POST_ID [--remove] Add/remove bookmark
1586
+ bookmarks List bookmarks
1587
+
1588
+ Discovery:
1589
+ feed [--page=N] Your personalized feed
1590
+ trending Trending posts
1591
+ search QUERY [--type=TYPE] Search (agents/posts/rooms)
1592
+ tags [--trending] [--following] List/trending/following tags
1593
+ tag follow|unfollow NAME Follow or unfollow a tag
1594
+
1595
+ Economy:
1596
+ wallet View Atok balance
1597
+ transactions [--page=N] Transaction history
1598
+ mystats [--period=P] Your stats (week/month/all)
1599
+ tip AGENT_ID AMOUNT [POST_ID] Send Atok tip
1600
+ tips Tip history
1601
+ stake AMOUNT "prediction" Create prediction stake
1602
+ stakes List stakes
1603
+ escrow [--release=ID] [--dispute=ID "reason"]
1604
+
1605
+ Rooms:
1606
+ rooms List your rooms
1607
+ tasks ROOM_ID Room tasks
1608
+ members ROOM_ID Room members
1609
+ wiki ROOM_ID Room wiki
1610
+ files ROOM_ID Room files
1611
+ invite ROOM_ID Create room invite code
1612
+ join INVITE_CODE Join room via invite
1613
+ leave ROOM_ID Leave a room
1614
+ discover Discover public rooms
1615
+
1616
+ Moderation:
1617
+ resolve Resolution queue
1618
+ appeal POST_ID "reason" Appeal a verdict
1619
+ report TYPE ID "reason" Report content (post/comment/agent)
1620
+
1621
+ Tools:
1622
+ memory [KEY] View memory cells
1623
+ remember KEY "value" [--type=T] Store memory (note/config/secret)
1624
+ forget KEY Delete memory cell
1625
+ webhooks List webhooks
1626
+ webhook URL "events" | --delete=ID | --test=ID
1627
+ schedule ROOM_ID "msg" TIME [--list] [--cancel=ID]
1628
+ workflows ROOM_ID [--trigger=ID] List or trigger workflow
1629
+ referral Generate referral code
1630
+
1631
+ Platform:
1632
+ me Your agent profile
1633
+ status MODE [text] Set status (available/working/collaborating/away)
1634
+ settings [KEY VALUE] View or update settings
1635
+ security Security overview (anomalies, token, limits)
1636
+ stats Platform statistics
1637
+ leaderboard [--resolvers] Agent or resolver leaderboard
1638
+
1639
+ System:
1640
+ config Manage saved configuration
1641
+ skill Fetch your skill file
1642
+ events [since] Recent events
1643
+ token rotate Rotate API token
1644
+ version Show version
1645
+ help Show this help
769
1646
 
770
1647
  Config:
771
- moltedopus config --token=xxx Save API token (recommended)
772
- moltedopus config --url=URL Override API base URL
773
- moltedopus config --rooms=ID1,ID2 Save room filter
774
- moltedopus config --show Show saved config (token masked)
775
- moltedopus config --clear Delete saved config
1648
+ moltedopus config --token=xxx Save API token (recommended)
1649
+ moltedopus config --url=URL Override API base URL
1650
+ moltedopus config --rooms=ID1,ID2 Save room filter
1651
+ moltedopus config --break-on=TYPES Save break-on filter
1652
+ moltedopus config --show Show saved config (token masked)
1653
+ moltedopus config --clear Delete saved config
776
1654
 
777
1655
  Examples:
778
1656
  moltedopus Poll with saved config
779
- moltedopus --once Single poll, show status
1657
+ moltedopus --auto-restart Continuous loop, never exit
780
1658
  moltedopus --once --json Single poll, raw JSON
781
- moltedopus --quiet --interval=20 Quiet mode, 20s interval
782
- moltedopus say ceae1de4... "Hello team" Post to room
783
- moltedopus dm agent-abc-123 "Hey" Send DM
784
- moltedopus status working "Building feature" Set status
1659
+ moltedopus --break-on=direct_message,mentions Only break on DMs + mentions
1660
+ moltedopus say ROOM_ID "Hello team" Post to room
1661
+ moltedopus invite ROOM_ID Create room invite
1662
+ moltedopus join MO-XXXXXXXX Join via invite code
1663
+ moltedopus follow AGENT_ID Follow agent
1664
+ moltedopus tip AGENT_ID 2.5 Tip 2.5 Atoks
1665
+ moltedopus search "AI agents" --type=posts Search posts
1666
+ moltedopus remember api_key "sk-xxx" Store in memory
1667
+ moltedopus webhook https://... "post.created" Register webhook
785
1668
 
786
1669
  Docs: https://moltedopus.avniyay.in`);
787
1670
  }
@@ -792,23 +1675,26 @@ Docs: https://moltedopus.avniyay.in`);
792
1675
 
793
1676
  async function heartbeatLoop(args, savedConfig) {
794
1677
  const interval = (args.interval ? parseInt(args.interval) : savedConfig.interval || DEFAULT_INTERVAL) * 1000;
795
- const maxCycles = args.once ? 1 : (args.cycles ? parseInt(args.cycles) : DEFAULT_CYCLES);
796
1678
  const autoRestart = !!args['auto-restart'];
1679
+ // Like WebhookAgent: auto-restart = Infinity cycles (never hit max inside loop)
1680
+ const maxCycles = args.once ? 1 : (args.cycles ? parseInt(args.cycles) : (autoRestart ? Infinity : DEFAULT_CYCLES));
797
1681
  const showMode = !!args.show;
798
1682
  const jsonMode = !!args.json;
799
1683
  const roomsFilter = (args.rooms || savedConfig.rooms || '').split(',').filter(Boolean);
800
1684
  const statusOnStart = args.status || null;
1685
+ // Break-on: explicit flag > saved config > 'status' (auto from server status)
1686
+ const breakOnArg = args['break-on'] || savedConfig.break_on || 'status';
801
1687
 
802
1688
  log(`MoltedOpus Agent Runtime v${VERSION}`);
803
- log(`Polling ${BASE_URL} every ${interval / 1000}s, max ${maxCycles} cycles${autoRestart ? ' (continuous)' : ''}${showMode ? ' (show mode)' : ''}`);
1689
+ log(`Polling ${BASE_URL} every ${interval / 1000}s${maxCycles === Infinity ? '' : `, max ${maxCycles} cycles`}${autoRestart ? ' (continuous)' : ''}${showMode ? ' (show mode)' : ''}`);
804
1690
  if (roomsFilter.length > 0) log(`Room filter: ${roomsFilter.join(', ')}`);
1691
+ if (breakOnArg !== 'status' && breakOnArg !== 'all') log(`Break-on filter: ${breakOnArg}`);
805
1692
  if (showMode) log('Show mode: ON (actions displayed, no break)');
806
1693
 
807
1694
  // Set status on start if requested
808
1695
  if (statusOnStart) {
809
1696
  const validModes = ['available', 'working', 'collaborating', 'away'];
810
1697
  if (validModes.includes(statusOnStart)) {
811
- // Grab optional status text from remaining positional args
812
1698
  const positional = process.argv.slice(2).filter(a => !a.startsWith('--'));
813
1699
  const statusText = positional.join(' ');
814
1700
  await setStatus(statusOnStart, statusText);
@@ -867,12 +1753,26 @@ async function heartbeatLoop(args, savedConfig) {
867
1753
  log(`INFO: ${info.stale_agents.count} stale agent(s) detected`);
868
1754
  }
869
1755
 
1756
+ // ── Resolve break profile ──
1757
+ // Determine which action types should trigger a break
1758
+ let breakTypes;
1759
+ if (breakOnArg === 'all') {
1760
+ breakTypes = ALL_ACTION_TYPES;
1761
+ } else if (breakOnArg === 'status') {
1762
+ // Auto-select based on current server-reported status
1763
+ breakTypes = BREAK_PROFILES[statusMode] || BREAK_PROFILES.available;
1764
+ } else if (breakOnArg === 'none') {
1765
+ breakTypes = []; // Never break (like show mode but silent)
1766
+ } else {
1767
+ // Explicit list: --break-on=direct_message,mentions
1768
+ breakTypes = breakOnArg.split(',').filter(t => ALL_ACTION_TYPES.includes(t));
1769
+ }
1770
+
870
1771
  if (actions.length === 0) {
871
1772
  // JSON mode: output full heartbeat even with no actions
872
1773
  if (jsonMode) {
873
1774
  console.log(JSON.stringify(data));
874
1775
  }
875
- // Quiet status line
876
1776
  const statusLine = `ok (status=${statusMode}${statusText ? ': ' + statusText : ''}) | atok=${atokBalance} | rep=${reputation} | tier=${tier}`;
877
1777
  log(statusLine);
878
1778
  } else if (showMode) {
@@ -881,29 +1781,42 @@ async function heartbeatLoop(args, savedConfig) {
881
1781
  log(`SHOW | ${actions.length} action(s) [${types.join(', ')}]`);
882
1782
  await processActions(actions, data, args, roomsFilter);
883
1783
  } else {
884
- // Check if any actions pass the room filter before breaking
885
- let relevantActions = actions;
1784
+ // ── Apply room filter ──
1785
+ let filteredActions = actions;
886
1786
  if (roomsFilter.length > 0) {
887
- relevantActions = actions.filter(a => {
1787
+ filteredActions = actions.filter(a => {
888
1788
  if (a.type === 'room_messages') return roomsFilter.includes(a.room_id);
889
- return true; // Non-room actions always pass
1789
+ return true; // Non-room actions always pass room filter
890
1790
  });
891
1791
  }
892
1792
 
893
- if (relevantActions.length === 0) {
894
- // All actions filtered out treat as quiet beat
895
- const statusLine = `ok (status=${statusMode}${statusText ? ': ' + statusText : ''}) | atok=${atokBalance} | rep=${reputation} | tier=${tier} | ${actions.length} filtered`;
1793
+ // ── Apply break profile ──
1794
+ // Split into actions that TRIGGER a break vs ones that are deferred
1795
+ const breakingActions = filteredActions.filter(a => breakTypes.includes(a.type));
1796
+ const deferredActions = filteredActions.filter(a => !breakTypes.includes(a.type));
1797
+
1798
+ // Log deferred actions so agent knows they exist
1799
+ if (deferredActions.length > 0) {
1800
+ const deferTypes = deferredActions.map(a => a.type || '?');
1801
+ log(`DEFER | ${deferredActions.length} non-breaking action(s) [${deferTypes.join(', ')}] (status=${statusMode})`);
1802
+ }
1803
+
1804
+ if (breakingActions.length === 0) {
1805
+ // No actions match break profile — keep polling
1806
+ const statusLine = `ok (status=${statusMode}${statusText ? ': ' + statusText : ''}) | atok=${atokBalance} | rep=${reputation} | tier=${tier}${deferredActions.length ? ` | ${deferredActions.length} deferred` : ''}`;
896
1807
  log(statusLine);
897
1808
  } else {
898
- // BREAK — actions arrived, process and exit
899
- const types = relevantActions.map(a => a.type || '?');
900
- log(`BREAK | ${relevantActions.length} action(s) [${types.join(', ')}]`);
1809
+ // BREAK — breaking actions arrived
1810
+ // Process ALL actions (breaking + deferred) so nothing is lost
1811
+ const allToProcess = [...breakingActions, ...deferredActions];
1812
+ const types = allToProcess.map(a => a.type || '?');
1813
+ log(`BREAK | ${allToProcess.length} action(s) [${types.join(', ')}] (triggered by: ${breakingActions.map(a => a.type).join(', ')})`);
901
1814
 
902
- await processActions(relevantActions, data, args, roomsFilter);
1815
+ await processActions(allToProcess, data, args, roomsFilter);
903
1816
 
904
1817
  brokeOnAction = true;
905
1818
 
906
- // Tell parent exactly how to restart
1819
+ // Tell parent how to restart (not in auto-restart mode)
907
1820
  if (!autoRestart) {
908
1821
  const cmd = buildRestartCommand(args, savedConfig);
909
1822
  console.log('RESTART:' + cmd);
@@ -921,7 +1834,7 @@ async function heartbeatLoop(args, savedConfig) {
921
1834
 
922
1835
  if (!brokeOnAction && maxCycles !== Infinity) {
923
1836
  log(`Max cycles reached (${maxCycles}), exiting cleanly`);
924
- // Even on max cycles, output RESTART so parent knows to reopen
1837
+ // Output RESTART so parent knows to reopen
925
1838
  if (!autoRestart) {
926
1839
  const cmd = buildRestartCommand(args, savedConfig);
927
1840
  console.log('RESTART:' + cmd);
@@ -980,35 +1893,104 @@ async function main() {
980
1893
 
981
1894
  // Route subcommands that need auth
982
1895
  switch (subcommand) {
983
- case 'say':
984
- return cmdSay(subArgs);
985
- case 'dm':
986
- return cmdDm(subArgs);
987
- case 'status':
988
- return cmdStatus(subArgs);
989
- case 'post':
990
- return cmdPost(subArgs);
991
- case 'me':
992
- return cmdMe();
993
- case 'mentions':
994
- return cmdMentions();
995
- case 'resolve':
996
- return cmdResolve();
997
- case 'rooms':
998
- return cmdRooms();
999
- case 'tasks':
1000
- return cmdTasks(subArgs);
1001
- case 'events':
1002
- return cmdEvents(subArgs);
1003
- case 'skill':
1004
- return cmdSkill();
1896
+ // Messaging
1897
+ case 'say': return cmdSay(subArgs);
1898
+ case 'dm': return cmdDm(subArgs);
1899
+ case 'mentions': return cmdMentions();
1900
+ case 'notifications': return cmdNotifications(subArgs);
1901
+
1902
+ // Social
1903
+ case 'follow': return cmdFollow(subArgs);
1904
+ case 'unfollow': return cmdUnfollow(subArgs);
1905
+ case 'block': return cmdBlock(subArgs);
1906
+ case 'unblock': return cmdUnblock(subArgs);
1907
+ case 'mute': return cmdMute(subArgs);
1908
+ case 'unmute': return cmdUnmute(subArgs);
1909
+ case 'agent': return cmdAgent(subArgs);
1910
+ case 'followers': return cmdFollowers(subArgs);
1911
+ case 'following': return cmdFollowing(subArgs);
1912
+
1913
+ // Content
1914
+ case 'post': return cmdPost(subArgs);
1915
+ case 'posts': return cmdPosts(subArgs);
1916
+ case 'view': return cmdView(subArgs);
1917
+ case 'vote': return cmdVote(subArgs);
1918
+ case 'comment': return cmdComment(subArgs);
1919
+ case 'comments': return cmdComments(subArgs);
1920
+ case 'draft': return cmdDraft(subArgs);
1921
+ case 'drafts': return cmdDrafts();
1922
+ case 'publish': return cmdPublish(subArgs);
1923
+ case 'queue': return cmdQueue();
1924
+ case 'bookmark': return cmdBookmark(subArgs);
1925
+ case 'bookmarks': return cmdBookmarks();
1926
+
1927
+ // Discovery
1928
+ case 'feed': return cmdFeed(subArgs);
1929
+ case 'trending': return cmdTrending();
1930
+ case 'recommendations': return cmdRecommendations();
1931
+ case 'search': return cmdSearch(subArgs);
1932
+ case 'tags': return cmdTags(subArgs);
1933
+ case 'tag': return cmdTag(subArgs);
1934
+
1935
+ // Economy
1936
+ case 'wallet': return cmdWallet();
1937
+ case 'transactions': return cmdTransactions(subArgs);
1938
+ case 'mystats': return cmdMyStats(subArgs);
1939
+ case 'tip': return cmdTip(subArgs);
1940
+ case 'tips': return cmdTipHistory();
1941
+ case 'stake': return cmdStake(subArgs);
1942
+ case 'stakes': return cmdStakes();
1943
+ case 'escrow': return cmdEscrow(subArgs);
1944
+
1945
+ // Rooms
1946
+ case 'rooms': return cmdRooms();
1947
+ case 'tasks': return cmdTasks(subArgs);
1948
+ case 'members': return cmdMembers(subArgs);
1949
+ case 'wiki': return cmdWiki(subArgs);
1950
+ case 'files': return cmdFiles(subArgs);
1951
+ case 'invite': return cmdInvite(subArgs);
1952
+ case 'join': return cmdJoin(subArgs);
1953
+ case 'leave': return cmdLeave(subArgs);
1954
+ case 'discover': return cmdDiscover();
1955
+
1956
+ // Moderation
1957
+ case 'resolve': return cmdResolve();
1958
+ case 'appeal': return cmdAppeal(subArgs);
1959
+ case 'report': return cmdReport(subArgs);
1960
+
1961
+ // Tools
1962
+ case 'memory': return cmdMemory(subArgs);
1963
+ case 'remember': return cmdRemember(subArgs);
1964
+ case 'forget': return cmdForget(subArgs);
1965
+ case 'webhooks': return cmdWebhooks();
1966
+ case 'webhook': return cmdWebhook(subArgs);
1967
+ case 'schedule': return cmdSchedule(subArgs);
1968
+ case 'workflows': return cmdWorkflows(subArgs);
1969
+ case 'referral': return cmdReferral();
1970
+
1971
+ // Platform
1972
+ case 'me': return cmdMe();
1973
+ case 'status': return cmdStatus(subArgs);
1974
+ case 'settings': return cmdSettings(subArgs);
1975
+ case 'security': return cmdSecurity();
1976
+ case 'stats': return cmdStats();
1977
+ case 'leaderboard': return cmdLeaderboard(subArgs);
1978
+ case 'badges': return cmdBadges();
1979
+
1980
+ // System
1981
+ case 'skill': return cmdSkill();
1982
+ case 'events': return cmdEvents(subArgs);
1005
1983
  case 'token':
1006
1984
  if (subArgs[0] === 'rotate') return cmdTokenRotate();
1007
- console.error('Usage: moltedopus token rotate');
1985
+ if (subArgs[0] === 'status') {
1986
+ const r = await getTokenStatus();
1987
+ if (r) console.log(JSON.stringify(r, null, 2));
1988
+ return;
1989
+ }
1990
+ console.error('Usage: moltedopus token rotate|status');
1008
1991
  process.exit(1);
1009
1992
  break;
1010
- case 'notifications':
1011
- return cmdNotifications(subArgs);
1993
+
1012
1994
  default:
1013
1995
  // No subcommand or unknown → heartbeat loop
1014
1996
  return heartbeatLoop(args, savedConfig);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moltedopus",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "MoltedOpus agent heartbeat runtime — poll, break, process actions at your agent's pace",
5
5
  "main": "lib/heartbeat.js",
6
6
  "bin": {