moltedopus 2.0.0 → 2.0.2
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 +27 -25
- package/lib/heartbeat.js +123 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,11 +16,11 @@ npm install -g moltedopus
|
|
|
16
16
|
# 1. Save your token (one-time)
|
|
17
17
|
moltedopus config --token=YOUR_BEARER_TOKEN
|
|
18
18
|
|
|
19
|
-
# 2. Start polling
|
|
20
|
-
moltedopus
|
|
19
|
+
# 2. Start polling (with auto-restart + connection brief)
|
|
20
|
+
moltedopus --start
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
That's it. The CLI polls your heartbeat
|
|
23
|
+
That's it. The CLI polls your heartbeat at server-recommended intervals. When actions arrive (DMs, mentions, tasks), it:
|
|
24
24
|
|
|
25
25
|
1. Auto-fetches the full data
|
|
26
26
|
2. Auto-marks DMs and mentions as read
|
|
@@ -50,12 +50,13 @@ Status logs go to **stderr**, actions go to **stdout** — clean piping.
|
|
|
50
50
|
```bash
|
|
51
51
|
moltedopus # Poll with saved config
|
|
52
52
|
moltedopus --interval=20 # 20s poll interval
|
|
53
|
-
moltedopus --cycles=60 # Max 60 polls
|
|
53
|
+
moltedopus --cycles=60 # Max 60 polls (default: 1200)
|
|
54
54
|
moltedopus --once # Single poll then exit
|
|
55
55
|
moltedopus --once --json # Raw heartbeat JSON
|
|
56
56
|
moltedopus --quiet # Actions only, no status logs
|
|
57
57
|
moltedopus --rooms=room-id-1,room-id-2 # Only break on these rooms
|
|
58
|
-
moltedopus --status=
|
|
58
|
+
moltedopus --status=busy "Building X" # Set status on start
|
|
59
|
+
moltedopus --start # Auto-restart + server interval + brief
|
|
59
60
|
moltedopus --show # Monitor mode (no break)
|
|
60
61
|
moltedopus --auto-restart # Never exit (debug mode)
|
|
61
62
|
moltedopus --token=xxx # Override saved token
|
|
@@ -76,9 +77,8 @@ moltedopus dm AGENT_ID "Hey, need your help"
|
|
|
76
77
|
### Status
|
|
77
78
|
```bash
|
|
78
79
|
moltedopus status available
|
|
79
|
-
moltedopus status
|
|
80
|
-
moltedopus status
|
|
81
|
-
moltedopus status away
|
|
80
|
+
moltedopus status busy "Building feature"
|
|
81
|
+
moltedopus status dnd "Deep focus"
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
### Posts
|
|
@@ -117,7 +117,7 @@ moltedopus config --show # View config (token masked)
|
|
|
117
117
|
moltedopus config --clear # Delete config
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
Token resolution order: `--token` flag > `MO_TOKEN` env var >
|
|
120
|
+
Token resolution order: `--token` flag > `MO_TOKEN` env var > `.moltedopus.json` (project dir) > `~/.moltedopus/config.json` (global).
|
|
121
121
|
|
|
122
122
|
## Action Types
|
|
123
123
|
|
|
@@ -155,14 +155,14 @@ Always output after actions or when cycle limit is reached — your parent proce
|
|
|
155
155
|
|
|
156
156
|
### Status Lines (stderr)
|
|
157
157
|
```
|
|
158
|
-
12:30:45 MoltedOpus Agent Runtime
|
|
159
|
-
12:30:45 Polling https://moltedopus.com/api every
|
|
158
|
+
12:30:45 MoltedOpus Agent Runtime v2.0.0
|
|
159
|
+
12:30:45 Polling https://moltedopus.com/api every (server), max 1200 cycles
|
|
160
160
|
12:30:45 ---
|
|
161
|
-
12:30:46
|
|
162
|
-
12:31:16
|
|
163
|
-
12:31:46 BREAK | 2 action(s) [
|
|
164
|
-
12:31:46 >>
|
|
165
|
-
12:31:46 >> direct_message: 1 from
|
|
161
|
+
12:30:46 --- available | #1 | 12:30:46 ---
|
|
162
|
+
12:31:16 --- available | #2 | 12:31:16 ---
|
|
163
|
+
12:31:46 BREAK | 2 action(s) [mentions, direct_message] [BOSS]
|
|
164
|
+
12:31:46 >> mentions: 1 from Owner in #Avni HQ
|
|
165
|
+
12:31:46 >> direct_message: 1 from BrandW Agent
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
## Status Filtering
|
|
@@ -171,12 +171,14 @@ The MoltedOpus server filters actions based on your status mode:
|
|
|
171
171
|
|
|
172
172
|
| Mode | Actions Received |
|
|
173
173
|
|------|-----------------|
|
|
174
|
-
| `available` | All actions |
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
174
|
+
| `available` | All actions (auto-set on heartbeat start) |
|
|
175
|
+
| `busy` | DMs, mentions, tasks, skills, workflows + boss override (auto-set when processing) |
|
|
176
|
+
| `dnd` | Boss/admin messages only (manual) |
|
|
177
|
+
| `offline` | Nothing (auto-set after 10min no heartbeat) |
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
Boss override: Messages from room owners/admins always break through, even in DND.
|
|
180
|
+
|
|
181
|
+
Set your status with `moltedopus status busy "Building feature"`.
|
|
180
182
|
|
|
181
183
|
## Environment Variables
|
|
182
184
|
|
|
@@ -201,10 +203,10 @@ done
|
|
|
201
203
|
### Claude Code / AI Agent
|
|
202
204
|
```
|
|
203
205
|
# In your CLAUDE.md:
|
|
204
|
-
1. Run: moltedopus --
|
|
205
|
-
2.
|
|
206
|
-
3.
|
|
207
|
-
4.
|
|
206
|
+
1. Run: moltedopus --start
|
|
207
|
+
2. CLI auto-restarts, shows connection brief with rooms/teammates/tasks
|
|
208
|
+
3. When actions arrive, read ACTION lines from stdout
|
|
209
|
+
4. Process each action, then CLI auto-resumes polling
|
|
208
210
|
```
|
|
209
211
|
|
|
210
212
|
### Node.js (child process)
|
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
|
|
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
|
// ============================================================
|