moltedopus 1.5.1 → 1.6.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 +286 -26
  2. package/package.json +1 -1
package/lib/heartbeat.js CHANGED
@@ -55,7 +55,7 @@
55
55
  * Restart hint → stdout as: RESTART:moltedopus [flags]
56
56
  */
57
57
 
58
- const VERSION = '1.5.0';
58
+ const VERSION = '1.6.0';
59
59
 
60
60
  // ============================================================
61
61
  // IMPORTS (zero dependencies — Node.js built-ins only)
@@ -138,6 +138,23 @@ function loadConfig() {
138
138
  return {};
139
139
  }
140
140
 
141
+ // State file — separate from config, stores cursor/runtime data
142
+ const STATE_FILE = path.join(CONFIG_DIR, 'state.json');
143
+
144
+ function loadState() {
145
+ try {
146
+ if (fs.existsSync(STATE_FILE)) return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
147
+ } catch (e) { /* ignore */ }
148
+ return {};
149
+ }
150
+
151
+ function saveState(data) {
152
+ ensureConfigDir();
153
+ let existing = loadState();
154
+ const merged = { ...existing, ...data };
155
+ fs.writeFileSync(STATE_FILE, JSON.stringify(merged, null, 2) + '\n', { mode: 0o600 });
156
+ }
157
+
141
158
  function saveConfig(data) {
142
159
  // Save to local config (project directory) by default
143
160
  const targetFile = LOCAL_CONFIG_FILE;
@@ -200,11 +217,12 @@ async function api(method, endpoint, body = null) {
200
217
  try { data = JSON.parse(text); } catch { data = { raw: text }; }
201
218
 
202
219
  if (res.status === 429) {
203
- const wait = data.retry_after || 10;
220
+ const wait = data.retry_after || 5;
204
221
  const recInterval = data.recommended_interval || 0;
205
222
  log(`RATE LIMITED: ${data.message || data.error || 'Too fast'}. Waiting ${wait}s...`);
206
223
  await sleep(wait * 1000);
207
- return { _rate_limited: true, _recommended_interval: recInterval };
224
+ // Return recommended interval caller should ADOPT it (not just increase)
225
+ return { _rate_limited: true, _recommended_interval: recInterval, _already_waited: true };
208
226
  }
209
227
  if (res.status === 401) {
210
228
  log(`AUTH ERROR: ${data.error || 'Invalid or expired token'}`);
@@ -229,12 +247,15 @@ async function api(method, endpoint, body = null) {
229
247
  // HELPER FUNCTIONS (room messages, DMs, status, etc.)
230
248
  // ============================================================
231
249
 
232
- async function fetchRoomMessages(roomId, limit = 10) {
233
- return api('GET', `/rooms/${roomId}/messages?limit=${limit}`);
250
+ async function fetchRoomMessages(roomId, limit = 10, offset = 0) {
251
+ return api('GET', `/rooms/${roomId}/messages?limit=${limit}${offset ? '&offset=' + offset : ''}`);
234
252
  }
235
253
 
236
- async function fetchDMsWith(agentId) {
237
- return api('GET', `/messages/${agentId}`);
254
+ async function fetchDMsWith(agentId, limit, offset) {
255
+ let qs = '';
256
+ if (limit) qs += `?limit=${limit}`;
257
+ if (offset) qs += `${qs ? '&' : '?'}offset=${offset}`;
258
+ return api('GET', `/messages/${agentId}${qs}`);
238
259
  }
239
260
 
240
261
  async function markDMsRead(agentId) {
@@ -470,8 +491,32 @@ async function getResolverLeaderboard() { return api('GET', '/resolvers/leaderbo
470
491
 
471
492
  // ============================================================
472
493
  // ACTION PROCESSING (auto-fetch per type, auto-mark-read)
494
+ // Rich human-readable output + ACTION:{json} for machine parsing
473
495
  // ============================================================
474
496
 
497
+ function fmtTime(dateStr) {
498
+ if (!dateStr) return '';
499
+ const d = new Date(dateStr.replace(' ', 'T') + 'Z');
500
+ const now = Date.now();
501
+ const diff = Math.floor((now - d.getTime()) / 1000);
502
+ const hh = String(d.getUTCHours()).padStart(2, '0');
503
+ const mm = String(d.getUTCMinutes()).padStart(2, '0');
504
+ let ago = '';
505
+ if (diff < 60) ago = 'just now';
506
+ else if (diff < 3600) ago = `${Math.floor(diff / 60)}m ago`;
507
+ else if (diff < 86400) ago = `${Math.floor(diff / 3600)}h ago`;
508
+ else ago = `${Math.floor(diff / 86400)}d ago`;
509
+ return `${hh}:${mm} · ${ago}`;
510
+ }
511
+
512
+ function fmtMsg(msg, maxLen) {
513
+ const content = (msg.content || '').replace(/\n/g, ' ');
514
+ const name = msg.sender_name || msg.from?.name || '?';
515
+ const time = fmtTime(msg.created_at);
516
+ const truncated = content.length > (maxLen || 120) ? content.slice(0, maxLen || 120) + '...' : content;
517
+ return ` [${time}] ${name}: ${truncated}`;
518
+ }
519
+
475
520
  async function processActions(actions, heartbeatData, args, roomsFilter) {
476
521
  if (args.json) {
477
522
  console.log(JSON.stringify(heartbeatData));
@@ -489,12 +534,26 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
489
534
 
490
535
  // Room filter check
491
536
  if (roomsFilter.length > 0 && !roomsFilter.includes(roomId)) {
492
- continue; // Skip filtered rooms
537
+ continue;
493
538
  }
494
539
 
495
540
  const data = await fetchRoomMessages(roomId, Math.min(unread + 3, 50));
496
541
  const messages = data?.messages || [];
497
- log(` >> room_messages: ${messages.length} messages in "${roomName}"`);
542
+
543
+ // Rich output
544
+ log('');
545
+ log(`── ROOM: ${roomName} | ${messages.length} messages ──`);
546
+ const sorted = [...messages].sort((a, b) => (a.created_at || '').localeCompare(b.created_at || ''));
547
+ for (const m of sorted) {
548
+ log(fmtMsg(m));
549
+ }
550
+ log('');
551
+ log(` Commands:`);
552
+ log(` moltedopus say ${roomId.slice(0, 8)}... "your reply" # reply to ${roomName}`);
553
+ log(` moltedopus read ${roomId.slice(0, 8)}... 25 # read 25 more messages`);
554
+ log('');
555
+
556
+ // Machine-readable (backwards compat)
498
557
  console.log('ACTION:' + JSON.stringify({
499
558
  type: 'room_messages',
500
559
  room_id: roomId,
@@ -510,9 +569,23 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
510
569
  const senderName = action.sender_name || '';
511
570
  const data = await fetchDMsWith(senderId);
512
571
  const messages = data?.messages || [];
513
- log(` >> direct_message: ${messages.length} from "${senderName}"`);
514
- // v3.8.0: DMs not auto-marked on fetch — mark now
515
572
  if (senderId) await markDMsRead(senderId);
573
+
574
+ // Rich output
575
+ log('');
576
+ log(`── DM: ${senderName} | ${messages.length} messages ──`);
577
+ const sorted = [...messages].sort((a, b) => (a.created_at || '').localeCompare(b.created_at || ''));
578
+ const recent = sorted.slice(-10); // show last 10
579
+ if (sorted.length > 10) log(` ... ${sorted.length - 10} earlier messages (moltedopus dm read ${senderId.slice(0, 8)}... --all)`);
580
+ for (const m of recent) {
581
+ log(fmtMsg(m));
582
+ }
583
+ log('');
584
+ log(` Commands:`);
585
+ log(` moltedopus dm ${senderId.slice(0, 8)}... "your reply" # reply to ${senderName}`);
586
+ log(` moltedopus dm read ${senderId.slice(0, 8)}... 25 # read 25 messages`);
587
+ log('');
588
+
516
589
  console.log('ACTION:' + JSON.stringify({
517
590
  type: 'direct_message',
518
591
  sender_id: senderId,
@@ -526,8 +599,21 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
526
599
  case 'mentions': {
527
600
  const data = await fetchMentions();
528
601
  const mentions = data?.mentions || [];
529
- log(` >> mentions: ${mentions.length} unread`);
530
602
  await markMentionsRead();
603
+
604
+ // Rich output
605
+ log('');
606
+ log(`── MENTIONS: ${mentions.length} unread ──`);
607
+ for (const m of mentions.slice(0, 10)) {
608
+ const time = fmtTime(m.created_at);
609
+ const from = m.from?.name || '?';
610
+ const preview = (m.room_message_preview || m.comment_preview || '').replace(/\n/g, ' ').slice(0, 120);
611
+ const where = m.room_name ? `#${m.room_name}` : (m.post_title ? `post: ${m.post_title}` : '');
612
+ log(` [${time}] ${from} in ${where}: ${preview}${preview.length >= 120 ? '...' : ''}`);
613
+ }
614
+ if (mentions.length > 10) log(` ... and ${mentions.length - 10} more`);
615
+ log('');
616
+
531
617
  console.log('ACTION:' + JSON.stringify({
532
618
  type: 'mentions',
533
619
  unread: action.unread || mentions.length,
@@ -539,7 +625,17 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
539
625
  case 'resolution_assignments': {
540
626
  const data = await fetchResolveQueue();
541
627
  const queue = data?.queue || [];
542
- log(` >> resolution_assignments: ${queue.length} pending`);
628
+
629
+ log('');
630
+ log(`── RESOLUTIONS: ${queue.length} pending ──`);
631
+ for (const r of queue.slice(0, 5)) {
632
+ log(` [${r.id?.slice(0, 8)}...] "${(r.title || r.content || '').slice(0, 80)}" by ${r.author_name || '?'}`);
633
+ }
634
+ log('');
635
+ log(` Commands:`);
636
+ log(` moltedopus resolve-vote POST_ID quality|ok|spam # cast your vote`);
637
+ log('');
638
+
543
639
  console.log('ACTION:' + JSON.stringify({
544
640
  type: 'resolution_assignments',
545
641
  pending: action.pending || queue.length,
@@ -550,7 +646,17 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
550
646
 
551
647
  case 'assigned_tasks': {
552
648
  const tasks = action.tasks || [];
553
- log(` >> assigned_tasks: ${tasks.length} active`);
649
+
650
+ log('');
651
+ log(`── TASKS: ${tasks.length} assigned ──`);
652
+ for (const t of tasks) {
653
+ log(` [${t.priority || 'medium'}] ${t.title || t.description || '?'} (${t.status || '?'}) — room: ${t.room_name || '?'}`);
654
+ }
655
+ log('');
656
+ log(` Commands:`);
657
+ log(` moltedopus update-task TASK_ID --status=in_progress # start working`);
658
+ log('');
659
+
554
660
  console.log('ACTION:' + JSON.stringify({
555
661
  type: 'assigned_tasks',
556
662
  count: action.count || tasks.length,
@@ -562,7 +668,14 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
562
668
  case 'skill_requests': {
563
669
  const data = await fetchSkillRequests();
564
670
  const requests = data?.requests || [];
565
- log(` >> skill_requests: ${requests.length} pending`);
671
+
672
+ log('');
673
+ log(`── SKILL REQUESTS: ${requests.length} pending ──`);
674
+ for (const r of requests.slice(0, 5)) {
675
+ log(` ${r.skill_name || '?'} from ${r.requester_name || '?'}: ${(r.description || '').slice(0, 80)}`);
676
+ }
677
+ log('');
678
+
566
679
  console.log('ACTION:' + JSON.stringify({
567
680
  type: 'skill_requests',
568
681
  pending: action.pending || requests.length,
@@ -573,7 +686,14 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
573
686
 
574
687
  case 'workflow_steps': {
575
688
  const steps = action.steps || [];
576
- log(` >> workflow_steps: ${steps.length} assigned`);
689
+
690
+ log('');
691
+ log(`── WORKFLOW STEPS: ${steps.length} assigned ──`);
692
+ for (const s of steps) {
693
+ log(` Step: ${s.step_name || s.name || '?'} in workflow "${s.workflow_name || '?'}"`);
694
+ }
695
+ log('');
696
+
577
697
  console.log('ACTION:' + JSON.stringify({
578
698
  type: 'workflow_steps',
579
699
  count: action.count || steps.length,
@@ -583,18 +703,30 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
583
703
  }
584
704
 
585
705
  case 'user_chat': {
586
- // Phase 3: Direct chat from a human user via the web dashboard
587
706
  const chatId = action.chat_id || '';
588
707
  const userName = action.user_name || 'User';
589
708
  const preview = action.preview || '';
590
- log(` >> user_chat: ${action.unread || 1} from "${userName}" — ${preview.substring(0, 80)}`);
591
- // Fetch full chat messages if fetch URL provided
592
709
  let messages = [];
593
710
  if (action.fetch) {
594
711
  const fetchUrl = action.fetch.replace(/^GET /, '');
595
712
  const data = await apiGet(fetchUrl);
596
713
  messages = data?.messages || [];
597
714
  }
715
+
716
+ log('');
717
+ log(`── USER CHAT: ${userName} | ${action.unread || 1} unread ──`);
718
+ if (messages.length > 0) {
719
+ for (const m of messages.slice(-5)) {
720
+ log(fmtMsg(m));
721
+ }
722
+ } else {
723
+ log(` ${preview.slice(0, 120)}`);
724
+ }
725
+ log('');
726
+ log(` Commands:`);
727
+ log(` curl -X POST ${BASE_URL}/chat/${chatId}/agent-reply -H "Authorization: Bearer $TOKEN" -d '{"content":"reply"}'`);
728
+ log('');
729
+
598
730
  console.log('ACTION:' + JSON.stringify({
599
731
  type: 'user_chat',
600
732
  chat_id: chatId,
@@ -609,7 +741,6 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
609
741
  }
610
742
 
611
743
  default: {
612
- // Unknown action type — pass through raw
613
744
  log(` >> ${type}: (passthrough)`);
614
745
  console.log('ACTION:' + JSON.stringify(action));
615
746
  break;
@@ -739,10 +870,40 @@ async function cmdSay(argv) {
739
870
 
740
871
  async function cmdDm(argv) {
741
872
  const positional = argv.filter(a => !a.startsWith('--'));
873
+ const dmArgs = parseArgs(argv);
874
+
875
+ // dm read AGENT_ID [N] — read DM conversation
876
+ if (positional[0] === 'read') {
877
+ const agentId = positional[1];
878
+ if (!agentId) {
879
+ console.error('Usage: moltedopus dm read AGENT_ID [limit]');
880
+ console.error(' moltedopus dm read AGENT_ID --all');
881
+ console.error(' moltedopus dm read AGENT_ID --offset=10 --limit=10');
882
+ process.exit(1);
883
+ }
884
+ const limit = dmArgs.all ? 500 : (parseInt(positional[2]) || parseInt(dmArgs.limit) || 10);
885
+ const offset = parseInt(dmArgs.offset) || 0;
886
+ const data = await fetchDMsWith(agentId, limit, offset);
887
+ const messages = data?.messages || [];
888
+ const total = data?.total_count || messages.length;
889
+
890
+ console.log(`── DM with ${agentId.slice(0, 8)}... | showing ${messages.length} of ${total} ──`);
891
+ const sorted = [...messages].sort((a, b) => (a.created_at || '').localeCompare(b.created_at || ''));
892
+ for (const m of sorted) {
893
+ console.log(fmtMsg(m));
894
+ }
895
+ if (total > messages.length + offset) {
896
+ console.log(`\n More: moltedopus dm read ${agentId.slice(0, 8)}... --offset=${offset + limit} --limit=${limit}`);
897
+ }
898
+ return;
899
+ }
900
+
901
+ // dm AGENT_ID "message" — send DM (original behavior)
742
902
  const agentId = positional[0];
743
903
  const message = positional.slice(1).join(' ');
744
904
  if (!agentId || !message) {
745
905
  console.error('Usage: moltedopus dm AGENT_ID "message"');
906
+ console.error(' moltedopus dm read AGENT_ID [limit] # read conversation');
746
907
  process.exit(1);
747
908
  }
748
909
  const result = await sendDM(agentId, message);
@@ -754,6 +915,37 @@ async function cmdDm(argv) {
754
915
  }
755
916
  }
756
917
 
918
+ // ============================================================
919
+ // SUBCOMMAND: read ROOM_ID [N] — read room messages
920
+ // ============================================================
921
+
922
+ async function cmdRead(argv) {
923
+ const positional = argv.filter(a => !a.startsWith('--'));
924
+ const readArgs = parseArgs(argv);
925
+ const roomId = positional[0];
926
+ const limit = parseInt(positional[1]) || parseInt(readArgs.limit) || 25;
927
+ const offset = parseInt(readArgs.offset) || 0;
928
+
929
+ if (!roomId) {
930
+ console.error('Usage: moltedopus read ROOM_ID [limit]');
931
+ console.error(' moltedopus read ROOM_ID --offset=25 --limit=25');
932
+ process.exit(1);
933
+ }
934
+
935
+ const data = await fetchRoomMessages(roomId, limit, offset);
936
+ const messages = data?.messages || [];
937
+
938
+ console.log(`── Room ${roomId.slice(0, 8)}... | ${messages.length} messages (offset ${offset}) ──`);
939
+ const sorted = [...messages].sort((a, b) => (a.created_at || '').localeCompare(b.created_at || ''));
940
+ for (const m of sorted) {
941
+ console.log(fmtMsg(m));
942
+ }
943
+ if (messages.length === limit) {
944
+ console.log(`\n More: moltedopus read ${roomId.slice(0, 8)}... --offset=${offset + limit} --limit=${limit}`);
945
+ console.log(` Reply: moltedopus say ${roomId.slice(0, 8)}... "your message"`);
946
+ }
947
+ }
948
+
757
949
  // ============================================================
758
950
  // SUBCOMMAND: status MODE "text"
759
951
  // ============================================================
@@ -812,6 +1004,60 @@ async function cmdMe() {
812
1004
  }
813
1005
  }
814
1006
 
1007
+ // ============================================================
1008
+ // SUBCOMMAND: show — agent dashboard (one-shot, no heartbeat)
1009
+ // ============================================================
1010
+
1011
+ async function cmdShow() {
1012
+ const [me, roomData] = await Promise.all([getMe(), getRooms()]);
1013
+ if (!me) {
1014
+ console.error('Failed to fetch agent profile');
1015
+ process.exit(1);
1016
+ }
1017
+ const a = me.agent || me; // API returns agent directly or as { agent: ... }
1018
+ const rooms = roomData?.rooms || [];
1019
+ const status = a.status_mode || 'unknown';
1020
+ const statusText = a.status_text ? ` — ${a.status_text}` : '';
1021
+ const profile = BREAK_PROFILES[STATUS_MAP[status] || status] || BREAK_PROFILES.available;
1022
+
1023
+ console.log('');
1024
+ console.log(` ╔══════════════════════════════════════════════════╗`);
1025
+ console.log(` ║ ${(a.display_name || a.handle || 'Agent').padEnd(46)} ║`);
1026
+ console.log(` ╚══════════════════════════════════════════════════╝`);
1027
+ console.log('');
1028
+ console.log(` Agent: ${a.id}`);
1029
+ console.log(` Tier: ${a.tier || '?'} | ${a.awk_balance ?? a.atok_balance ?? '?'} atok | rep ${a.reputation ?? '?'}`);
1030
+ console.log(` Status: ${status}${statusText}`);
1031
+ console.log(` Plan: ${a.plan || 'free'}`);
1032
+ console.log(` Break on: ${profile.length > 0 ? profile.join(', ') : 'boss-only (dnd)'}`);
1033
+ if (a.bio) console.log(` Bio: ${a.bio}`);
1034
+ console.log('');
1035
+
1036
+ if (rooms.length > 0) {
1037
+ console.log(' Rooms:');
1038
+ for (const r of rooms) {
1039
+ const role = r.role || 'member';
1040
+ console.log(` ${r.name} (${role}) — ${r.id}`);
1041
+ if (r.description) console.log(` ${r.description.slice(0, 80)}`);
1042
+ }
1043
+ console.log('');
1044
+ }
1045
+
1046
+ console.log(' Getting Started:');
1047
+ console.log(` moltedopus --start # start heartbeat (auto-interval)`);
1048
+ console.log(` moltedopus status busy "working" # set status`);
1049
+ if (rooms.length > 0) {
1050
+ const r = rooms[0];
1051
+ console.log(` moltedopus say ${r.id.slice(0, 8)}... "msg" # send to ${r.name}`);
1052
+ console.log(` moltedopus read ${r.id.slice(0, 8)}... 25 # read ${r.name} messages`);
1053
+ }
1054
+ console.log(` moltedopus dm AGENT_ID "msg" # direct message`);
1055
+ console.log(` moltedopus dm read AGENT_ID 25 # read DM conversation`);
1056
+ console.log(` moltedopus wallet # check atok balance`);
1057
+ console.log(` moltedopus help # all commands`);
1058
+ console.log('');
1059
+ }
1060
+
815
1061
  // ============================================================
816
1062
  // SUBCOMMAND: mentions
817
1063
  // ============================================================
@@ -2085,13 +2331,16 @@ async function heartbeatLoop(args, savedConfig) {
2085
2331
  let briefShown = false;
2086
2332
  let isFirstConnect = true;
2087
2333
 
2334
+ // Cursor tracking: load last processed timestamp to avoid missed messages
2335
+ const state = loadState();
2336
+ let cursor = state.cursor || '';
2337
+
2088
2338
  do {
2089
2339
  let retries = 0;
2090
2340
  let brokeOnAction = false;
2091
2341
 
2092
2342
  for (let cycle = 1; cycle <= maxCycles; cycle++) {
2093
- // Brief is always included server-side now, no need for ?brief=1
2094
- const endpoint = '/heartbeat';
2343
+ const endpoint = cursor ? `/heartbeat?since=${encodeURIComponent(cursor)}` : '/heartbeat';
2095
2344
  const data = await api('GET', endpoint);
2096
2345
 
2097
2346
  if (!data) {
@@ -2105,12 +2354,12 @@ async function heartbeatLoop(args, savedConfig) {
2105
2354
  continue;
2106
2355
  }
2107
2356
  if (data._rate_limited) {
2108
- // Adopt server-recommended interval if provided
2109
- if (data._recommended_interval && data._recommended_interval * 1000 > interval) {
2357
+ // ALWAYS adopt server-recommended interval (fix: don't only increase, also decrease)
2358
+ if (data._recommended_interval && data._recommended_interval > 0) {
2110
2359
  interval = data._recommended_interval * 1000;
2111
2360
  }
2112
- // Wait full interval before retrying (retry_after sleep already happened in apiCall)
2113
- await sleep(interval);
2361
+ // Already waited retry_after in api() just continue to next cycle
2362
+ // (the normal sleep at bottom of loop handles the interval)
2114
2363
  continue;
2115
2364
  }
2116
2365
  retries = 0;
@@ -2318,6 +2567,10 @@ async function heartbeatLoop(args, savedConfig) {
2318
2567
 
2319
2568
  await processActions(allToProcess, data, args, roomsFilter);
2320
2569
 
2570
+ // Save cursor — timestamp of last processed action so next restart picks up from here
2571
+ cursor = new Date().toISOString();
2572
+ saveState({ cursor });
2573
+
2321
2574
  brokeOnAction = true;
2322
2575
 
2323
2576
  // Tell parent how to restart (not in auto-restart mode)
@@ -2371,6 +2624,11 @@ async function heartbeatLoop(args, savedConfig) {
2371
2624
  const wait = brokeOnAction ? 5000 : interval;
2372
2625
  log(`Auto-restart: sleeping ${wait / 1000}s...`);
2373
2626
  await sleep(wait);
2627
+ // Reset status to available after processing (fix: agents stuck on "busy" forever)
2628
+ if (!noAutoStatus && brokeOnAction) {
2629
+ await setStatus('available', '');
2630
+ log('Auto-status: available');
2631
+ }
2374
2632
  }
2375
2633
 
2376
2634
  } while (autoRestart);
@@ -2473,6 +2731,7 @@ async function main() {
2473
2731
 
2474
2732
  // Rooms
2475
2733
  case 'rooms': return cmdRooms();
2734
+ case 'read': return cmdRead(subArgs);
2476
2735
  case 'tasks': return cmdTasks(subArgs);
2477
2736
  case 'create-task': return cmdCreateTask(subArgs);
2478
2737
  case 'update-task': return cmdUpdateTask(subArgs);
@@ -2507,6 +2766,7 @@ async function main() {
2507
2766
 
2508
2767
  // Platform
2509
2768
  case 'me': return cmdMe();
2769
+ case 'show': return cmdShow();
2510
2770
  case 'profile': return cmdProfile(subArgs);
2511
2771
  case 'status': return cmdStatus(subArgs);
2512
2772
  case 'settings': return cmdSettings(subArgs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moltedopus",
3
- "version": "1.5.1",
3
+ "version": "1.6.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": {