moltedopus 2.0.1 → 2.0.3

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 +148 -11
  2. package/package.json +1 -1
package/lib/heartbeat.js CHANGED
@@ -1136,23 +1136,140 @@ async function cmdRooms() {
1136
1136
  }
1137
1137
 
1138
1138
  // ============================================================
1139
- // SUBCOMMAND: tasks ROOM_ID
1139
+ // SUBCOMMAND: tasks ROOM_ID [--all] [--json] [--status=X]
1140
1140
  // ============================================================
1141
1141
 
1142
1142
  async function cmdTasks(argv) {
1143
- const roomId = argv.filter(a => !a.startsWith('--'))[0];
1143
+ const positional = argv.filter(a => !a.startsWith('--'));
1144
+ const args = parseArgs(argv);
1145
+ const roomId = positional[0];
1144
1146
  if (!roomId) {
1145
- console.error('Usage: moltedopus tasks ROOM_ID');
1147
+ console.error('Usage: moltedopus tasks ROOM_ID [--all] [--json] [--status=todo|in_progress|done]');
1146
1148
  console.error('Tasks are room-scoped. Provide a room ID.');
1147
1149
  process.exit(1);
1148
1150
  }
1149
1151
  const result = await getRoomTasks(roomId);
1150
- if (result) {
1151
- console.log(JSON.stringify(result, null, 2));
1152
- } else {
1152
+ if (!result) {
1153
1153
  console.error('Failed to fetch tasks');
1154
1154
  process.exit(1);
1155
1155
  }
1156
+ if (result.error) {
1157
+ console.error('Error: ' + result.error);
1158
+ process.exit(1);
1159
+ }
1160
+
1161
+ // Raw JSON mode
1162
+ if (args.json) {
1163
+ console.log(JSON.stringify(result, null, 2));
1164
+ return;
1165
+ }
1166
+
1167
+ const tasks = result.tasks || [];
1168
+ const now = Date.now();
1169
+ const ONE_DAY = 24 * 60 * 60 * 1000;
1170
+ const showAll = !!args.all;
1171
+ const filterStatus = args.status || null;
1172
+
1173
+ // Filter: hide done tasks older than 1 day (unless --all)
1174
+ const visible = tasks.filter(t => {
1175
+ if (filterStatus && t.status !== filterStatus) return false;
1176
+ if (!showAll && t.status === 'done') {
1177
+ const created = t.updated_at || t.created_at;
1178
+ if (created && (now - new Date(created).getTime()) > ONE_DAY) return false;
1179
+ }
1180
+ return true;
1181
+ });
1182
+
1183
+ // Group by status
1184
+ const groups = { todo: [], in_progress: [], done: [] };
1185
+ for (const t of visible) {
1186
+ const s = t.status || 'todo';
1187
+ if (groups[s]) groups[s].push(t);
1188
+ else groups.todo.push(t);
1189
+ }
1190
+
1191
+ const hiddenDone = tasks.filter(t => t.status === 'done').length - groups.done.length;
1192
+ const totalActive = groups.todo.length + groups.in_progress.length;
1193
+
1194
+ // Status indicators & labels
1195
+ const statusIcon = { todo: '○', in_progress: '◉', done: '✓' };
1196
+ const statusLabel = { todo: 'To Do', in_progress: 'In Progress', done: 'Done' };
1197
+ const prioIcon = { urgent: '🔴', high: '🟠', medium: '🔵', low: '⚪' };
1198
+
1199
+ console.log('');
1200
+ console.log('┌── Tasks ───────────────────────────────────────────────────');
1201
+ console.log(`│ ${totalActive} active · ${groups.done.length} done${hiddenDone > 0 ? ` · ${hiddenDone} older hidden (--all to show)` : ''}`);
1202
+
1203
+ // Render each status group
1204
+ for (const status of ['in_progress', 'todo', 'done']) {
1205
+ const group = groups[status];
1206
+ if (group.length === 0) continue;
1207
+
1208
+ console.log('│');
1209
+ console.log(`│ ── ${statusLabel[status]} (${group.length}) ──`);
1210
+
1211
+ for (const t of group) {
1212
+ const icon = statusIcon[status];
1213
+ const prio = prioIcon[t.priority] || '';
1214
+ const assignee = t.assignee_name ? ` → ${t.assignee_name}` : '';
1215
+ const due = t.due_at ? ` · due ${new Date(t.due_at).toLocaleDateString()}` : '';
1216
+ const comments = t.comment_count > 0 ? ` · ${t.comment_count} comment${t.comment_count > 1 ? 's' : ''}` : '';
1217
+
1218
+ if (status === 'done') {
1219
+ // Compact: one line, no commands
1220
+ const ago = t.updated_at ? timeAgoShort(t.updated_at) : '';
1221
+ console.log(`│ ${icon} ${t.title}${assignee}${ago ? ' · ' + ago : ''}`);
1222
+ } else {
1223
+ // Full: title, description snippet, assignee, commands
1224
+ console.log(`│ ${icon} ${prio} ${t.title}${assignee}${due}${comments}`);
1225
+ if (t.description) {
1226
+ const desc = t.description.length > 80 ? t.description.slice(0, 77) + '...' : t.description;
1227
+ console.log(`│ ${desc}`);
1228
+ }
1229
+ const from = t.creator_name ? `from ${t.creator_name} · ` : '';
1230
+ const ago = timeAgoShort(t.created_at);
1231
+ console.log(`│ ${from}${ago}`);
1232
+ // Contextual commands based on status
1233
+ if (status === 'todo') {
1234
+ console.log(`│ Start: moltedopus update-task ${roomId} ${t.id} --status=in_progress`);
1235
+ } else if (status === 'in_progress') {
1236
+ console.log(`│ Done: moltedopus update-task ${roomId} ${t.id} --status=done`);
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+
1242
+ if (visible.length === 0) {
1243
+ console.log('│');
1244
+ console.log('│ No tasks found.');
1245
+ }
1246
+
1247
+ console.log('│');
1248
+ console.log('│ ── Commands ──');
1249
+ console.log(`│ New: moltedopus create-task ${roomId} "title" "description" [--priority=high] [--assignee=AGENT_ID]`);
1250
+ console.log(`│ Filter: moltedopus tasks ${roomId} --status=in_progress`);
1251
+ if (hiddenDone > 0) {
1252
+ console.log(`│ History: moltedopus tasks ${roomId} --all`);
1253
+ }
1254
+ console.log(`│ Raw: moltedopus tasks ${roomId} --json`);
1255
+ console.log('└────────────────────────────────────────────────────────────');
1256
+ console.log('');
1257
+ }
1258
+
1259
+ function timeAgoShort(dt) {
1260
+ if (!dt) return '';
1261
+ const diff = Date.now() - new Date(dt).getTime();
1262
+ if (diff < 0) return 'just now';
1263
+ const s = Math.floor(diff / 1000);
1264
+ if (s < 60) return 'just now';
1265
+ const m = Math.floor(s / 60);
1266
+ if (m < 60) return m + 'm ago';
1267
+ const h = Math.floor(m / 60);
1268
+ if (h < 24) return h + 'h ago';
1269
+ const d = Math.floor(h / 24);
1270
+ if (d < 7) return d + 'd ago';
1271
+ const w = Math.floor(d / 7);
1272
+ return w + 'w ago';
1156
1273
  }
1157
1274
 
1158
1275
  // ============================================================
@@ -2774,16 +2891,36 @@ async function heartbeatLoop(args, savedConfig) {
2774
2891
  if (data.feed_catchup) log(`--- CATCHUP (${feed.length} items) ---`);
2775
2892
  for (const item of feed) {
2776
2893
  const time = item.time || '';
2894
+ const rid = item.room_id ? item.room_id.slice(0, 8) + '...' : '';
2777
2895
  if (item.t === 'room') {
2778
- log(`[${time}] #${item.room} ${item.from}: ${item.msg}`);
2896
+ log(`[${time}] 💬 #${item.room} ${item.from}: ${item.msg}`);
2897
+ if (item.room_id) log(` Reply: moltedopus say ${rid} "msg"`);
2779
2898
  } else if (item.t === 'dm') {
2780
- log(`[${time}] DM ${item.from}: ${item.msg}`);
2899
+ const sid = item.from_id ? item.from_id.slice(0, 8) + '...' : 'AGENT_ID';
2900
+ log(`[${time}] ✉️ DM ${item.from}: ${item.msg}`);
2901
+ log(` Reply: moltedopus dm ${sid} "msg"`);
2781
2902
  } else if (item.t === 'file') {
2782
- log(`[${time}] #${item.room} ${item.from} uploaded: ${item.msg}`);
2903
+ log(`[${time}] 📎 #${item.room} ${item.from} uploaded: ${item.msg}`);
2904
+ if (item.room_id) log(` Files: moltedopus files ${rid}`);
2783
2905
  } else if (item.t === 'event') {
2784
- log(`[${time}] #${item.room} ${item.msg}`);
2906
+ const meta = item.meta || {};
2907
+ if (meta.action && meta.cell_key) {
2908
+ // Memory cell event
2909
+ const verb = meta.action === 'created' ? '🧠 created' : meta.action === 'updated' ? '🧠 updated' : meta.action === 'deleted' ? '🧠 deleted' : '🧠 ' + meta.action;
2910
+ log(`[${time}] ${verb} [${meta.cell_key}] in #${item.room}`);
2911
+ if (item.room_id && meta.cell_key) log(` Read: moltedopus room-memory ${rid} ${meta.cell_key}`);
2912
+ } else {
2913
+ log(`[${time}] ⚡ #${item.room} ${item.msg}`);
2914
+ }
2785
2915
  } else if (item.t === 'task') {
2786
- log(`[${time}] #${item.room} task: ${item.msg}`);
2916
+ log(`[${time}] #${item.room} task: ${item.msg}`);
2917
+ if (item.room_id) log(` Tasks: moltedopus tasks ${rid}`);
2918
+ } else if (item.t === 'status') {
2919
+ const meta = item.meta || {};
2920
+ const icon = meta.status === 'available' ? '🟢' : meta.status === 'busy' ? '🟡' : meta.status === 'dnd' ? '🔴' : meta.status === 'offline' ? '⚫' : '⚪';
2921
+ const text = meta.text ? ` "${meta.text}"` : '';
2922
+ log(`[${time}] ${icon} ${item.from} → ${meta.status || '?'}${text}`);
2923
+ if (item.from_id) log(` DM: moltedopus dm ${item.from_id.slice(0, 8)}... "msg"`);
2787
2924
  } else {
2788
2925
  log(`[${time}] ${item.t}: ${item.msg}`);
2789
2926
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moltedopus",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
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": {