agent-lookout 0.2.6 → 0.2.7
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/CHANGELOG.md +15 -0
- package/PRIVACY.md +6 -6
- package/README.md +187 -160
- package/dist/assets/{HistoryPanel-Cwmn7YtJ.js → HistoryPanel-oSXRXEdv.js} +1 -1
- package/dist/assets/{SessionPanel-BJ5t_CPi.js → SessionPanel-DcJvw0F8.js} +1 -1
- package/dist/assets/index-DLPmOXDe.js +3 -0
- package/dist/cli/agent-lookout.js +1 -1
- package/dist/cli/{startCommand-PR5JPWCT.js → startCommand-3LYUYPG4.js} +91 -30
- package/dist/index.html +1 -1
- package/docs/API.md +3 -2
- package/docs/GUIDE.md +11 -58
- package/docs/INSTALL.md +175 -0
- package/package.json +2 -1
- package/dist/assets/index-QSfCI-L_.js +0 -3
|
@@ -167,7 +167,7 @@ async function runCommand(options) {
|
|
|
167
167
|
return EXIT.notKnown;
|
|
168
168
|
}
|
|
169
169
|
if (parsed.kind === "start") {
|
|
170
|
-
const { startCommand } = await import("./startCommand-
|
|
170
|
+
const { startCommand } = await import("./startCommand-3LYUYPG4.js");
|
|
171
171
|
const url = await startCommand({
|
|
172
172
|
...parsed.options,
|
|
173
173
|
distDir: options.distDir ?? fileURLToPath(new URL("../../dist/", import.meta.url)),
|
|
@@ -254,10 +254,30 @@ function createQuietHold() {
|
|
|
254
254
|
const last = lastWaitOf(session.id);
|
|
255
255
|
if (last?.begunAt === begunAt) {
|
|
256
256
|
last.endedAt = null;
|
|
257
|
+
last.reminderOnly = false;
|
|
257
258
|
return;
|
|
258
259
|
}
|
|
259
260
|
if (last && last.endedAt === null) last.endedAt = begunAt;
|
|
260
|
-
waits.push({
|
|
261
|
+
waits.push({
|
|
262
|
+
session: withoutWaitingText(session),
|
|
263
|
+
begunAt,
|
|
264
|
+
endedAt: null,
|
|
265
|
+
reminderOnly: false
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
holdReminder(session, begunAt) {
|
|
269
|
+
const last = lastWaitOf(session.id);
|
|
270
|
+
if (last?.begunAt === begunAt) {
|
|
271
|
+
last.endedAt = null;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (last && last.endedAt === null) last.endedAt = begunAt;
|
|
275
|
+
waits.push({
|
|
276
|
+
session: withoutWaitingText(session),
|
|
277
|
+
begunAt,
|
|
278
|
+
endedAt: null,
|
|
279
|
+
reminderOnly: true
|
|
280
|
+
});
|
|
261
281
|
},
|
|
262
282
|
holdOver(event, session, at) {
|
|
263
283
|
over.push({ event, session: withoutWaitingText(session), at });
|
|
@@ -279,6 +299,7 @@ function createQuietHold() {
|
|
|
279
299
|
}
|
|
280
300
|
const open2 = [];
|
|
281
301
|
for (const [id, wait] of stillOpen) {
|
|
302
|
+
if (wait.reminderOnly) continue;
|
|
282
303
|
open2.push({ session: listed.get(id), begunAt: wait.begunAt });
|
|
283
304
|
}
|
|
284
305
|
const answered = /* @__PURE__ */ new Map();
|
|
@@ -336,6 +357,7 @@ var DAY_MS = 24 * HOUR_MS;
|
|
|
336
357
|
var WEEKDAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
|
|
337
358
|
var TIME_RULE_NAMES = ["longWait", "idle", "quietHours"];
|
|
338
359
|
var LONG_WAIT_MINUTES = { min: 1, max: 1440, default: 10 };
|
|
360
|
+
var REPEAT_MINUTES = { min: 5, max: 1440, default: 30 };
|
|
339
361
|
var IDLE_HOURS = { min: 1, max: 720, default: 48 };
|
|
340
362
|
var DEFAULT_TIME_RULES = {
|
|
341
363
|
longWait: { on: false, minutes: LONG_WAIT_MINUTES.default },
|
|
@@ -365,12 +387,22 @@ function holds(value, keys, exact2) {
|
|
|
365
387
|
function wholeNumberIn(value, range) {
|
|
366
388
|
return Number.isSafeInteger(value) && value >= range.min && value <= range.max;
|
|
367
389
|
}
|
|
368
|
-
function
|
|
390
|
+
function readRepeat(value, exact2) {
|
|
369
391
|
if (!isRecord(value) || !holds(value, ["on", "minutes"], exact2)) return null;
|
|
370
392
|
const { on, minutes } = value;
|
|
371
|
-
if (typeof on !== "boolean" || !wholeNumberIn(minutes,
|
|
393
|
+
if (typeof on !== "boolean" || !wholeNumberIn(minutes, REPEAT_MINUTES)) return null;
|
|
372
394
|
return { on, minutes };
|
|
373
395
|
}
|
|
396
|
+
function readLongWait(value, exact2) {
|
|
397
|
+
if (!isRecord(value)) return null;
|
|
398
|
+
const keys = value.repeat === void 0 ? ["on", "minutes"] : ["on", "minutes", "repeat"];
|
|
399
|
+
if (!holds(value, keys, exact2)) return null;
|
|
400
|
+
const { on, minutes } = value;
|
|
401
|
+
if (typeof on !== "boolean" || !wholeNumberIn(minutes, LONG_WAIT_MINUTES)) return null;
|
|
402
|
+
if (value.repeat === void 0) return { on, minutes };
|
|
403
|
+
const repeat = readRepeat(value.repeat, exact2);
|
|
404
|
+
return repeat === null ? null : { on, minutes, repeat };
|
|
405
|
+
}
|
|
374
406
|
function readIdle(value, exact2) {
|
|
375
407
|
if (!isRecord(value) || !holds(value, ["on", "hours"], exact2)) return null;
|
|
376
408
|
const { on, hours } = value;
|
|
@@ -402,7 +434,7 @@ var READERS = {
|
|
|
402
434
|
quietHours: readQuietHours
|
|
403
435
|
};
|
|
404
436
|
var SHAPES = {
|
|
405
|
-
longWait: `longWait must be {"on", "minutes"}, with minutes a whole number from ${LONG_WAIT_MINUTES.min} to ${LONG_WAIT_MINUTES.max}.`,
|
|
437
|
+
longWait: `longWait must be {"on", "minutes"}, with minutes a whole number from ${LONG_WAIT_MINUTES.min} to ${LONG_WAIT_MINUTES.max}, and may have "repeat", {"on", "minutes"} with minutes a whole number from ${REPEAT_MINUTES.min} to ${REPEAT_MINUTES.max}.`,
|
|
406
438
|
idle: `idle must be {"on", "hours"}, with hours a whole number from ${IDLE_HOURS.min} to ${IDLE_HOURS.max}.`,
|
|
407
439
|
quietHours: 'quietHours must be {"on", "from", "to", "days", "leaveOutAnswered"}, with from and to two different times written as 22:00 is, and days any of mon, tue, wed, thu, fri, sat and sun, each once.'
|
|
408
440
|
};
|
|
@@ -467,6 +499,18 @@ function quietOf(snapshot) {
|
|
|
467
499
|
}
|
|
468
500
|
|
|
469
501
|
// src/core/time-rules/reminders.ts
|
|
502
|
+
var MINUTE_MS = 6e4;
|
|
503
|
+
function reminderSchedule(rule) {
|
|
504
|
+
return {
|
|
505
|
+
thresholdMs: rule.minutes * MINUTE_MS,
|
|
506
|
+
everyMs: rule.repeat?.on ? rule.repeat.minutes * MINUTE_MS : null
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function reminderNumber(waitedMs, schedule) {
|
|
510
|
+
if (waitedMs < schedule.thresholdMs) return -1;
|
|
511
|
+
if (schedule.everyMs === null) return 0;
|
|
512
|
+
return Math.floor((waitedMs - schedule.thresholdMs) / schedule.everyMs);
|
|
513
|
+
}
|
|
470
514
|
function readable(snapshot) {
|
|
471
515
|
return new Set(
|
|
472
516
|
snapshot.sources.filter((source) => source.state === "ok" || source.state === "not-set-up").map((source) => source.id)
|
|
@@ -485,29 +529,33 @@ function createReminderWatch() {
|
|
|
485
529
|
if (known && (since === null || since === known.begunAt)) continue;
|
|
486
530
|
watched.set(session.id, {
|
|
487
531
|
begunAt: since ?? at,
|
|
488
|
-
|
|
489
|
-
|
|
532
|
+
seenAfterMs: since === null ? 0 : at - since,
|
|
533
|
+
toldAfterMs: null
|
|
490
534
|
});
|
|
491
535
|
}
|
|
492
536
|
},
|
|
493
|
-
due(snapshot, at,
|
|
537
|
+
due(snapshot, at, schedule, eligible = () => true) {
|
|
494
538
|
const found = [];
|
|
495
539
|
for (const session of snapshot.sessions) {
|
|
496
540
|
if (!needsYou(session)) continue;
|
|
497
541
|
const wait = watched.get(session.id);
|
|
498
|
-
if (!wait ||
|
|
542
|
+
if (!wait || !eligible(session.id)) continue;
|
|
499
543
|
const waitedMs = at - wait.begunAt;
|
|
500
|
-
|
|
501
|
-
|
|
544
|
+
const repeat = reminderNumber(waitedMs, schedule);
|
|
545
|
+
if (repeat < 0 || repeat <= reminderNumber(wait.seenAfterMs, schedule)) continue;
|
|
546
|
+
const told = wait.toldAfterMs;
|
|
547
|
+
if (told !== null && repeat <= reminderNumber(told, schedule)) continue;
|
|
548
|
+
const { thresholdMs, everyMs } = schedule;
|
|
549
|
+
if (told !== null && everyMs !== null && repeat > 0) {
|
|
550
|
+
if (thresholdMs + repeat * everyMs - told < everyMs / 2) continue;
|
|
551
|
+
}
|
|
552
|
+
found.push({ session, begunAt: wait.begunAt, waitedMs, repeat });
|
|
502
553
|
}
|
|
503
554
|
return found;
|
|
504
555
|
},
|
|
505
|
-
|
|
506
|
-
watched.get(sessionId)?.reminded.add(thresholdMs);
|
|
507
|
-
},
|
|
508
|
-
toldLate(sessionId, at, thresholdMs) {
|
|
556
|
+
told(sessionId, at) {
|
|
509
557
|
const wait = watched.get(sessionId);
|
|
510
|
-
if (wait
|
|
558
|
+
if (wait) wait.toldAfterMs = Math.max(wait.toldAfterMs ?? 0, at - wait.begunAt);
|
|
511
559
|
},
|
|
512
560
|
clear() {
|
|
513
561
|
watched.clear();
|
|
@@ -609,7 +657,7 @@ function createOutboundChannel(options) {
|
|
|
609
657
|
sentAt.push(at);
|
|
610
658
|
send2(write(at));
|
|
611
659
|
}
|
|
612
|
-
const
|
|
660
|
+
const schedule = reminderSchedule(rules.longWait);
|
|
613
661
|
for (const [id, begunAt] of open2) {
|
|
614
662
|
const session = snapshot.sessions.find(
|
|
615
663
|
(candidate) => candidate.id === id && needsYou(candidate)
|
|
@@ -635,13 +683,17 @@ function createOutboundChannel(options) {
|
|
|
635
683
|
now: at
|
|
636
684
|
})
|
|
637
685
|
);
|
|
638
|
-
if (rules.longWait.on) reminders.
|
|
686
|
+
if (rules.longWait.on) reminders.told(id, at);
|
|
639
687
|
}
|
|
640
|
-
if (
|
|
641
|
-
for (const due of reminders.due(snapshot, at,
|
|
688
|
+
if (rules.longWait.on && wanted.has("needs-you")) {
|
|
689
|
+
for (const due of reminders.due(snapshot, at, schedule, (id) => !open2.has(id))) {
|
|
690
|
+
if (quiet) {
|
|
691
|
+
hold.holdReminder(due.session, due.begunAt);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
642
694
|
if (limitLiftsAt(sentAt, at) !== null) break;
|
|
643
695
|
sentAt.push(at);
|
|
644
|
-
reminders.
|
|
696
|
+
reminders.told(due.session.id, at);
|
|
645
697
|
send2(
|
|
646
698
|
reminderMessage({
|
|
647
699
|
session: withoutWaitingText(due.session),
|
|
@@ -649,7 +701,9 @@ function createOutboundChannel(options) {
|
|
|
649
701
|
asking: asking ? waitingText(due.session.waitingText) ?? null : null,
|
|
650
702
|
begunAt: due.begunAt,
|
|
651
703
|
now: at,
|
|
652
|
-
thresholdMs
|
|
704
|
+
thresholdMs: schedule.thresholdMs,
|
|
705
|
+
everyMs: schedule.everyMs,
|
|
706
|
+
repeat: due.repeat
|
|
653
707
|
})
|
|
654
708
|
);
|
|
655
709
|
}
|
|
@@ -669,9 +723,9 @@ function createOutboundChannel(options) {
|
|
|
669
723
|
}
|
|
670
724
|
|
|
671
725
|
// src/core/time-rules/timeRulesWords.ts
|
|
672
|
-
var
|
|
726
|
+
var MINUTE_MS2 = 6e4;
|
|
673
727
|
function waitedInWords(ms) {
|
|
674
|
-
if (ms <
|
|
728
|
+
if (ms < MINUTE_MS2) return "under a minute";
|
|
675
729
|
return partsInWords(durationParts(ms).filter((part) => part.unit !== "s"));
|
|
676
730
|
}
|
|
677
731
|
var WAITED_FOR = {
|
|
@@ -873,9 +927,10 @@ function waitEmail(facts) {
|
|
|
873
927
|
}
|
|
874
928
|
function reminderEmail(facts) {
|
|
875
929
|
const { session, begunAt, now } = facts;
|
|
930
|
+
const again = facts.everyMs === null ? "" : `, and again every ${waitedInWords(facts.everyMs)} while it goes on`;
|
|
876
931
|
return emailOf(
|
|
877
932
|
reminderSentence(session, now - begunAt),
|
|
878
|
-
`It has waited since ${clockAt(begunAt, now)}. Agent Lookout reminds you once a wait lasts ${waitedInWords(facts.thresholdMs)}, as Time rules in Settings says.`,
|
|
933
|
+
`It has waited since ${clockAt(begunAt, now)}. Agent Lookout reminds you once a wait lasts ${waitedInWords(facts.thresholdMs)}${again}, as Time rules in Settings says.`,
|
|
879
934
|
session,
|
|
880
935
|
facts.agent,
|
|
881
936
|
facts.asking
|
|
@@ -1634,7 +1689,9 @@ function reminderPost(facts) {
|
|
|
1634
1689
|
post.session.app,
|
|
1635
1690
|
post.session.agent
|
|
1636
1691
|
]),
|
|
1637
|
-
reminder: true
|
|
1692
|
+
reminder: true,
|
|
1693
|
+
// Left out on the first, so its post is the post it always was.
|
|
1694
|
+
...facts.repeat > 0 && { repeat: facts.repeat }
|
|
1638
1695
|
};
|
|
1639
1696
|
}
|
|
1640
1697
|
function summaryPost(facts) {
|
|
@@ -9217,7 +9274,7 @@ function createServerNotifications(options) {
|
|
|
9217
9274
|
const at = now();
|
|
9218
9275
|
const rules = rulesOf(snapshot);
|
|
9219
9276
|
const quietNow = quietOf(snapshot);
|
|
9220
|
-
const
|
|
9277
|
+
const schedule = reminderSchedule(rules.longWait);
|
|
9221
9278
|
const result = sessionChanges(memory, snapshot);
|
|
9222
9279
|
memory = result.memory;
|
|
9223
9280
|
for (const id of result.stopped) {
|
|
@@ -9243,7 +9300,7 @@ function createServerNotifications(options) {
|
|
|
9243
9300
|
change: { event: "needs-you", session },
|
|
9244
9301
|
since: at
|
|
9245
9302
|
});
|
|
9246
|
-
if (rules.longWait.on) reminders.
|
|
9303
|
+
if (rules.longWait.on) reminders.told(session.id, at);
|
|
9247
9304
|
}
|
|
9248
9305
|
}
|
|
9249
9306
|
if (quietNow) quiet.begin(at);
|
|
@@ -9253,9 +9310,13 @@ function createServerNotifications(options) {
|
|
|
9253
9310
|
else
|
|
9254
9311
|
held.set(heldKey(change.event, change.session.id), { kind: "change", change, since: at });
|
|
9255
9312
|
}
|
|
9256
|
-
if (
|
|
9257
|
-
for (const reminder of reminders.due(snapshot, at,
|
|
9258
|
-
|
|
9313
|
+
if (rules.longWait.on && wants("needs-you")) {
|
|
9314
|
+
for (const reminder of reminders.due(snapshot, at, schedule)) {
|
|
9315
|
+
if (quietNow) {
|
|
9316
|
+
quiet.holdReminder(reminder.session, reminder.begunAt);
|
|
9317
|
+
continue;
|
|
9318
|
+
}
|
|
9319
|
+
reminders.told(reminder.session.id, at);
|
|
9259
9320
|
held.set(reminderKey(reminder.session.id), { kind: "reminder", reminder, since: at });
|
|
9260
9321
|
}
|
|
9261
9322
|
}
|
package/dist/index.html
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
document.documentElement.setAttribute("data-theme", theme);
|
|
29
29
|
})();
|
|
30
30
|
</script>
|
|
31
|
-
<script type="module" crossorigin src="/assets/index-
|
|
31
|
+
<script type="module" crossorigin src="/assets/index-DLPmOXDe.js"></script>
|
|
32
32
|
<link rel="modulepreload" crossorigin href="/assets/react-C2DiJaR6.js">
|
|
33
33
|
<link rel="modulepreload" crossorigin href="/assets/motion-BAhdRuCw.js">
|
|
34
34
|
<link rel="modulepreload" crossorigin href="/assets/radix-BH0JH8UU.js">
|
package/docs/API.md
CHANGED
|
@@ -335,7 +335,7 @@ The settings Agent Lookout keeps itself, in a file on this computer, so they hol
|
|
|
335
335
|
```json
|
|
336
336
|
{
|
|
337
337
|
"timeRules": {
|
|
338
|
-
"longWait": { "on": true, "minutes": 10 },
|
|
338
|
+
"longWait": { "on": true, "minutes": 10, "repeat": { "on": true, "minutes": 30 } },
|
|
339
339
|
"idle": { "on": true, "hours": 48 },
|
|
340
340
|
"quietHours": {
|
|
341
341
|
"on": true,
|
|
@@ -370,6 +370,7 @@ The settings Agent Lookout keeps itself, in a file on this computer, so they hol
|
|
|
370
370
|
| Field | Holds |
|
|
371
371
|
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
372
372
|
| `timeRules.longWait` | The long wait reminder: `on`, and `minutes`, a whole number from 1 to 1,440, how long a wait lasts before it is reminded of. 10 unless set |
|
|
373
|
+
| `longWait.repeat` | Whether the reminder goes again while the wait goes on: `on`, and `minutes`, a whole number from 5 to 1,440, how long from one reminder to the next. Left out until it is first set, which is the same as off, so a file from before it reads as it did. The Time rules card sets 30 when it is first turned on |
|
|
373
374
|
| `timeRules.idle` | The idle rule: `on`, and `hours`, a whole number from 1 to 720, how long a session is idle before it is stale. 48 unless set. While it is off a session is stale after 24 hours |
|
|
374
375
|
| `timeRules.quietHours` | Quiet hours: `on`, `from` and `to` on the 24-hour clock as `HH:MM`, two different times, which run past midnight when `to` is the earlier, and `days`, the days they begin on, any of `mon` to `sun`. 22:00 to 08:00 every day unless set |
|
|
375
376
|
| `quietHours.leaveOutAnswered` | Whether the summary sent when quiet hours end leaves out the waits that were answered while they held |
|
|
@@ -391,7 +392,7 @@ A route that acts. It puts the time rules in force and saves them in the setting
|
|
|
391
392
|
- is marked `same-origin` in `Sec-Fetch-Site`, when that header is sent, or gets 403.
|
|
392
393
|
- carries `X-Agent-Lookout-Action: time-rules`, or gets 403.
|
|
393
394
|
- has `Content-Type: application/json`, or gets 415.
|
|
394
|
-
- has a body of 2,048 bytes or less, or gets 413, that is exactly the three rules, `longWait`, `idle` and `quietHours`, each whole, with exactly the fields `GET /api/settings` gives and every value in range, or gets 400.
|
|
395
|
+
- has a body of 2,048 bytes or less, or gets 413, that is exactly the three rules, `longWait`, `idle` and `quietHours`, each whole, with exactly the fields `GET /api/settings` gives and every value in range, or gets 400. `longWait.repeat` may be left out, which is no repeat, and when it is there it is `{ "on", "minutes" }` whole.
|
|
395
396
|
|
|
396
397
|
The rules are in force once they are saved, and not before: the file is written whole, with mode 600, in a folder made with mode 700, and never through a link. Nothing in the request names a file.
|
|
397
398
|
|
package/docs/GUIDE.md
CHANGED
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Requirements
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Node.js 22.12 or newer. The [Mac app](#desktop-app) needs no Node.js.
|
|
7
|
-
- Claude Code, Codex or both. Neither needs any setup. Any other agent can appear too, by writing a status file: see [Your own agents](#your-own-agents).
|
|
8
|
-
- For Claude Code, a version that has the `claude agents` command. `claude agents --help` should print `Usage: claude agents`. Without that command Agent Lookout still reads the session files, but cannot list background jobs that have finished or failed.
|
|
9
|
-
- For Codex, version 0.155 or later, so that Agent Lookout can tell a session that has ended from one that is idle.
|
|
10
|
-
|
|
11
|
-
The [README](../README.md#install) has the steps to install and start it.
|
|
5
|
+
Node.js 22.12 or newer, or the Mac app, which needs none, and Claude Code, Codex or both. [INSTALL.md](INSTALL.md) has the versions each needs, how to check them, and the steps to install and start Agent Lookout.
|
|
12
6
|
|
|
13
7
|
## The screen
|
|
14
8
|
|
|
@@ -320,15 +314,17 @@ The browser gives its permission to the address, not to Agent Lookout. Another p
|
|
|
320
314
|
|
|
321
315
|
The Time rules card has three rules, each with an Off and On switch, and all three are off until you turn them on. Agent Lookout keeps them in `~/.agent-lookout/settings.json`, so they hold with no dashboard tab open and after a restart. A change takes effect at once. The card shows what Agent Lookout saved: if a change could not be saved, a note says why and the switch stays as it was. A field is read when you leave it or press Enter, and Escape puts back what it held.
|
|
322
316
|
|
|
323
|
-
Remind me of a long wait sends a reminder once a session has waited the minutes you set: 10 unless you change them, from 1 to 1,440. It goes once for each wait, the way the wait's first notice went: as a notification while Needs you is switched on, as an email or a webhook post while `needs-you` is among the events those send. It says how long the session has waited. The notification reads "Has waited 10 minutes for permission" under the session's name, takes the place of the wait's own, and is cleared when the session moves on. The email's subject and the post's line say "checkout-flow has waited 10 minutes for permission", and the post has `reminder: true`. A wait that had already gone on longer when the page opened, or when Agent Lookout started, is not reminded of, and nor is one whose first email or post went only once it had waited that long, since that one says how long already. Set more minutes while a session waits, and it is reminded of again once it has waited those.
|
|
317
|
+
Remind me of a long wait sends a reminder once a session has waited the minutes you set: 10 unless you change them, from 1 to 1,440. It goes once for each wait, the way the wait's first notice went: as a notification while Needs you is switched on, as an email or a webhook post while `needs-you` is among the events those send. Remind again, below, sends it again while the wait goes on. It says how long the session has waited. The notification reads "Has waited 10 minutes for permission" under the session's name, takes the place of the wait's own, and is cleared when the session moves on. The email's subject and the post's line say "checkout-flow has waited 10 minutes for permission", and the post has `reminder: true`. A wait that had already gone on longer when the page opened, or when Agent Lookout started, is not reminded of at those minutes, and nor is one whose first email or post went only once it had waited that long, since that one says how long already. Set more minutes while a session waits, and it is reminded of again once it has waited those.
|
|
318
|
+
|
|
319
|
+
Under it, Remind again while it waits sends the reminder again while the session still waits: every 30 minutes unless you change them, from 5 to 1,440. It is off until you turn it on. With a reminder after 10 minutes and again every 30, a session that keeps waiting is reminded of at 10 minutes, 40 minutes, 1 hour 10 minutes and so on, each time by the same channels as the first, saying how long it has waited by then, as in "checkout-flow has waited 40 minutes for permission". On the page each notification takes the place of the last. The ones Agent Lookout shows itself, with no dashboard tab open, cannot be taken down, so each stays in Notification Centre until you clear it: with Remind again set to 5 minutes, that can be 12 an hour for one wait. The email's subject is worded as the first reminder's, and the post has `reminder: true` and `repeat`, which counts the intervals since the first reminder's time: 1 at 40 minutes, 2 at 1 hour 10 minutes. After a sleep it can skip numbers. No reminder comes once the wait is answered, from the session itself, with Allow or Deny or by a permission rule, or once the session ends. The times go by how long the wait has lasted, so each channel, and a dashboard tab opened later, reminds at the same moments. A wait has one reminder at a time: after the computer has slept, or when quiet hours end, one comes, saying how long the session has waited by then, and none for the moments missed, and a moment that falls less than half the interval after it is skipped. A wait that had already passed its minutes when Agent Lookout started has its next reminder at the next of its moments, not at once.
|
|
324
320
|
|
|
325
321
|
Mark idle sessions stale sets how long a session is idle before it is stale, in hours or days, from an hour to 30 days: 2 days unless you change them. A stale session is counted under Stale rather than Idle, its idle line in the Timeline turns to dots where it became stale, and a Claude Code session among them whose process still runs is listed under [Left running](#sessions-left-running), where it can be ended. With the rule off, a session is stale after a day.
|
|
326
322
|
|
|
327
|
-
Quiet hours hold back every notification, email and post between two times on your computer's clock: 22:00 to 08:00 unless you change them. When the end comes before the start, they run past midnight, and the days you tick are the days they begin on: with Monday to Friday ticked, Friday night is quiet until Saturday morning, and Saturday night is not. With no day ticked they never begin. When they end, a session still waiting is notified as usual, and everything else held comes as one summary on each channel, such as "While quiet: checkout-flow waited 25 minutes, docs-site waited 1 hour 5 minutes over 2 waits and billing-webhooks finished": one notification headed While quiet, which stays until you clear it, one email and one webhook post. A session that waited and then finished, failed or ended is one item, as in "checkout-flow waited 25 minutes then ended". Each summary holds what its channel would have sent, so a wait answered before the email's delay was up is in no email. Turn on Leave answered waits out of the summary and it names only the sessions that finished, failed or ended. With nothing to say, there is no summary. Turning quiet hours off while they hold ends them at once. While they hold, a line under the times says so: "Quiet now, until 08:00. Notifications, emails and posts are held." In the Mac app, the notice of a new version waits for them to end too.
|
|
323
|
+
Quiet hours hold back every notification, email and post between two times on your computer's clock: 22:00 to 08:00 unless you change them. When the end comes before the start, they run past midnight, and the days you tick are the days they begin on: with Monday to Friday ticked, Friday night is quiet until Saturday morning, and Saturday night is not. With no day ticked they never begin. When they end, a session still waiting is notified as usual, or reminded of if it was told of before they began and its reminder came due in them, and everything else held comes as one summary on each channel, such as "While quiet: checkout-flow waited 25 minutes, docs-site waited 1 hour 5 minutes over 2 waits and billing-webhooks finished": one notification headed While quiet, which stays until you clear it, one email and one webhook post. A session that waited and then finished, failed or ended is one item, as in "checkout-flow waited 25 minutes then ended". A wait whose reminders came due in quiet hours and that was answered before they ended is one item too, however many came due. Each summary holds what its channel would have sent, so a wait answered before the email's delay was up is in no email. Turn on Leave answered waits out of the summary and it names only the sessions that finished, failed or ended. With nothing to say, there is no summary. Turning quiet hours off while they hold ends them at once. While they hold, a line under the times says so: "Quiet now, until 08:00. Notifications, emails and posts are held." In the Mac app, the notice of a new version waits for them to end too.
|
|
328
324
|
|
|
329
325
|
The rules apply on top of the switches under Notifications, and of the settings for [email](#email) and the [webhook](#webhook), which still choose what goes where. Quiet hours go by the clock and time zone of the computer Agent Lookout runs on, and a dashboard tab follows what Agent Lookout says, so a tab in another time zone holds and sums up at the same moments. A dashboard tab opened during quiet hours sums up what happened after it opened. With no tab open, Agent Lookout shows its own summary on a Mac, and it covers all of the quiet hours. The rules set here cover the sessions of [another machine](#another-machine-over-ssh) too, as they cover this computer's.
|
|
330
326
|
|
|
331
|
-
What quiet hours hold is kept in memory only. If Agent Lookout is stopped or restarted while they hold, what it held so far is forgotten: no summary comes of those hours, and a session that began waiting before the restart and still waits is not notified when they end. With the long wait reminder on, it is still reminded of, if it had not yet waited the reminder's minutes at the restart.
|
|
327
|
+
What quiet hours hold is kept in memory only. If Agent Lookout is stopped or restarted while they hold, what it held so far is forgotten: no summary comes of those hours, and a session that began waiting before the restart and still waits is not notified when they end. With the long wait reminder on, it is still reminded of, if it had not yet waited the reminder's minutes at the restart, or with Remind again on, at its next moment.
|
|
332
328
|
|
|
333
329
|
`AGENT_LOOKOUT_SETTINGS_FILE` keeps the rules in another file. Agent Lookout reads the file as it starts, and again just before each change, which it makes to what the file holds then. It writes it whole when you change a rule, with mode 600, in a folder it makes with mode 700, and never through a link. If it cannot read the file, the rules it holds are off, the card says why in a note, and the terminal you started Agent Lookout in prints the same line. A file that is not JSON is written again by your first change. One it could not read at all, such as one larger than 64 KB, is never written over: a change is not saved until you mend or remove the file. Two copies running at once, such as the Mac app and `npx agent-lookout`, share the file. Each looks at it at every poll and reads it again when the other has saved a change, so a change made in one is in force in the other by its next poll, usually within 2 seconds. A change to the permission rules is made to the list the file holds just then, so it keeps the other copy's. The Time rules card saves all three rules at once, so a time rule changed in one copy before the other copy's change has reached it puts that change back. After changing the time rules in one copy, wait until the other copy's card shows the change before changing them there. [PRIVACY.md](../PRIVACY.md#the-settings-file) says what the file holds.
|
|
334
330
|
|
|
@@ -931,7 +927,7 @@ In a clone without `npm link`, give the path to yours:
|
|
|
931
927
|
claude mcp add agent-lookout -- /path/to/agent-lookout/bin/agent-lookout.mjs mcp
|
|
932
928
|
```
|
|
933
929
|
|
|
934
|
-
Claude Code adds it for the folder you run that in. Put `--scope user` after `add` to have it in every folder. Any other app that takes MCP servers starts it with the same command: the program `npx` with the arguments `-y`, `agent-lookout` and `mcp`, or `agent-lookout` with the one argument `mcp`. Many take it as JSON in this shape:
|
|
930
|
+
Claude Code adds it for the folder you run that in. Put `--scope user` after `add` to have it in every folder. `claude mcp list` then lists `agent-lookout`. Any other app that takes MCP servers starts it with the same command: the program `npx` with the arguments `-y`, `agent-lookout` and `mcp`, or `agent-lookout` with the one argument `mcp`. Many take it as JSON in this shape:
|
|
935
931
|
|
|
936
932
|
```json
|
|
937
933
|
{
|
|
@@ -1314,60 +1310,17 @@ npm run dev -- --port 5180
|
|
|
1314
1310
|
|
|
1315
1311
|
## Run the built version
|
|
1316
1312
|
|
|
1317
|
-
In a clone, `npm run
|
|
1318
|
-
|
|
1319
|
-
```sh
|
|
1320
|
-
npm run build
|
|
1321
|
-
npm start
|
|
1322
|
-
```
|
|
1323
|
-
|
|
1324
|
-
`npm start` prints `Agent Lookout is running at http://127.0.0.1:4777`. Open that address. To check it from a terminal:
|
|
1325
|
-
|
|
1326
|
-
```sh
|
|
1327
|
-
curl -s http://127.0.0.1:4777/api/health
|
|
1328
|
-
```
|
|
1329
|
-
|
|
1330
|
-
It prints `{"ok":true,"version":"0.2.1"}`, or a later version number. If you run `npm start` before `npm run build`, it stops and tells you to build first. After you pull new code, run `npm run build` again.
|
|
1313
|
+
In a clone, `npm run build` and then `npm start` run Agent Lookout from built files, at `http://127.0.0.1:4777`. [From the repository](INSTALL.md#from-the-repository) has the steps.
|
|
1331
1314
|
|
|
1332
1315
|
## Start it with one command
|
|
1333
1316
|
|
|
1334
|
-
`agent-lookout`
|
|
1335
|
-
|
|
1336
|
-
| Option | What it does |
|
|
1337
|
-
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1338
|
-
| `--port <number>` | Listens on this port in place of 4777, such as `--port 4778`. `AGENT_LOOKOUT_PORT` does the same, and `--port` wins when both are given |
|
|
1339
|
-
| `--open` | Opens the address in your default browser once it is listening, with `open` on macOS, `xdg-open` on Linux or `rundll32.exe` on Windows. If that cannot be done, it prints one line with the address and keeps running |
|
|
1340
|
-
| `--help` | Prints the options of `agent-lookout`, `agent-lookout status` and `agent-lookout mcp` |
|
|
1341
|
-
| `--version` | Prints the version of Agent Lookout, such as `0.2.1`, and nothing else |
|
|
1342
|
-
|
|
1343
|
-
Every other setting under [Settings you can change](#settings-you-can-change) works with it as with `npm start`. It ends with 0 when you press Ctrl+C, and with 1 when it cannot start, such as when the port is in use. Under `npx agent-lookout`, the shell reports the interrupt instead, as exit code 130.
|
|
1344
|
-
|
|
1345
|
-
Agent Lookout is on npm, so `npx agent-lookout` starts it with nothing to clone or install first, and `npx agent-lookout --open` opens it in the browser as well. npm downloads the package the first time, under 2 MB with what it needs and about 4 MB once installed, and keeps it in its cache. The package holds the built dashboard and the collector as plain JavaScript, so it needs Node.js and nothing else. Each time, npx asks npm's registry whether a newer version is out, as it does for any package it runs. To start it without that, install it once with `npm install -g agent-lookout` and run `agent-lookout`.
|
|
1346
|
-
|
|
1347
|
-
In a clone, run `npm run build` and `npm link` once in the `agent-lookout` folder, then `agent-lookout` from any folder.
|
|
1317
|
+
`npx agent-lookout`, or `agent-lookout` once it is installed, starts Agent Lookout at `http://127.0.0.1:4777`, on this computer only. [With npx](INSTALL.md#with-npx) has its options, and how to run it in the background, stop it and update it.
|
|
1348
1318
|
|
|
1349
1319
|
## Desktop app
|
|
1350
1320
|
|
|
1351
1321
|
Agent Lookout can also run as a Mac app: the same dashboard in a window of its own, with the collector running inside the app. The app opens no port, so nothing else on this computer can reach it. It keeps its rules in the same [settings file](#time-rules) as `npx agent-lookout`, though, and takes up a change to that file at its next poll, so a program running as you can change them by writing the file. It needs no Node.js.
|
|
1352
1322
|
|
|
1353
|
-
To install it, download
|
|
1354
|
-
|
|
1355
|
-
The app is not yet signed with an Apple Developer ID, so the first time a downloaded copy is opened, macOS does not open it. Its message is titled “Agent Lookout” Not Opened and says that Apple could not verify “Agent Lookout” is free of malware. To open it anyway:
|
|
1356
|
-
|
|
1357
|
-
1. Press Done. Do not press Move to Bin, or Move to Trash, which deletes the app.
|
|
1358
|
-
2. Open System Settings › Privacy & Security.
|
|
1359
|
-
3. Under Security, next to the line that says Agent Lookout was blocked, press Open Anyway, and confirm with your password or Touch ID. The button is there for about an hour after macOS stopped the app. If it has gone, open the app again, press Done, and go back to Privacy & Security.
|
|
1360
|
-
|
|
1361
|
-
macOS remembers the choice.
|
|
1362
|
-
|
|
1363
|
-
To build the app yourself instead, from a clone, on a Mac:
|
|
1364
|
-
|
|
1365
|
-
```sh
|
|
1366
|
-
npm install
|
|
1367
|
-
npm run dist:mac
|
|
1368
|
-
```
|
|
1369
|
-
|
|
1370
|
-
It takes a minute or two, and the first time it downloads Electron for each kind of Mac. It puts a disk image and a zip for each in `release/`, with the same names as on the release. Open the disk image for your Mac and drag Agent Lookout to Applications. A copy you built yourself on this Mac opens without the steps above.
|
|
1323
|
+
To install it, download it from the [latest release](https://github.com/Olanetsoft/agent-lookout/releases/latest) and drag it to Applications, or build it yourself: [Mac app](INSTALL.md#mac-app) has the steps, and what to do the first time macOS will not open it. Keep it in Applications: the app [updates itself](#updates) only from a folder it can change.
|
|
1371
1324
|
|
|
1372
1325
|
It behaves as a Mac app does:
|
|
1373
1326
|
|
|
@@ -1526,7 +1479,7 @@ The program serving the page has stopped. The page keeps the last thing it saw,
|
|
|
1526
1479
|
|
|
1527
1480
|
## How it finds sessions
|
|
1528
1481
|
|
|
1529
|
-
For Claude Code, Agent Lookout reads the small file Claude Code keeps for each running session in `~/.claude/sessions/`, every 2 seconds. That starts no program and uses no network. When it starts, and every 30 seconds after that, it also runs `claude agents --json --all`, the command Claude Code [documents](https://code.claude.com/docs/en/agent-view) for listing its sessions. That answer decides which sessions exist, and it adds background jobs that have finished or failed. If the command cannot be found or fails, Agent Lookout uses the files alone. While a session is waiting for you, Agent Lookout also reads the end of its transcript, in `~/.claude/projects/`, to say what it is asking, and forgets it when the wait ends. Claude Code does not document its transcripts, so a new version can change them, and then the reason shows alone.
|
|
1482
|
+
For Claude Code, Agent Lookout reads the small file Claude Code keeps for each running session in `~/.claude/sessions/`, every 2 seconds, whether the session runs in a terminal, in VS Code or in the desktop app. That starts no program and uses no network. When it starts, and every 30 seconds after that, it also runs `claude agents --json --all`, the command Claude Code [documents](https://code.claude.com/docs/en/agent-view) for listing its sessions. That answer decides which sessions exist, and it adds background jobs that have finished or failed. If the command cannot be found or fails, Agent Lookout uses the files alone. While a session is waiting for you, Agent Lookout also reads the end of its transcript, in `~/.claude/projects/`, to say what it is asking, and forgets it when the wait ends. Claude Code does not document its transcripts, so a new version can change them, and then the reason shows alone.
|
|
1530
1483
|
|
|
1531
1484
|
For Codex it runs nothing. Every 2 seconds it reads what Codex has added to the session files under `~/.codex/sessions/` and finds the last line that says a turn started or ended: a session with a turn under way is working, and one whose last turn ended is idle. A session that no Codex program has open is finished. Those files hold your conversations with Codex. Agent Lookout keeps only when each turn started and ended and a few details, such as the session's folder, and when each file was last changed, which says how long a working session has been [quiet](#quiet-for). Codex documents none of these files, so a new Codex version can change them.
|
|
1532
1485
|
|
package/docs/INSTALL.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Install
|
|
2
|
+
|
|
3
|
+
Agent Lookout runs with npx, as a Mac app, or from a clone of the repository. The Claude Code plugin adds Allow and Deny to any of them. [The README](../README.md) says what it does.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- macOS, Linux or Windows. Agent Lookout is developed and used on macOS. On Linux and Windows, CI runs the tests and starts it, and no one has used it on those desktops yet. [On Linux](GUIDE.md#on-linux) and [On Windows](GUIDE.md#on-windows) say what differs there.
|
|
8
|
+
- Node.js 22.12 or newer. The [Mac app](#mac-app) needs no Node.js.
|
|
9
|
+
- Claude Code, Codex or both. Neither needs any setup. Any other agent can appear too, by writing a [status file](GUIDE.md#your-own-agents).
|
|
10
|
+
- For Claude Code, a version that has the `claude agents` command. Without it, Agent Lookout still reads the session files, but cannot list background jobs that have finished or failed.
|
|
11
|
+
- For Codex, version 0.155 or later, so that Agent Lookout can tell a session that has ended from one that is idle.
|
|
12
|
+
|
|
13
|
+
Check what you have:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
node --version
|
|
17
|
+
claude --version
|
|
18
|
+
claude agents --help
|
|
19
|
+
codex --version
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`node --version` prints `v22.12.0` or later. `claude --version` prints a version number followed by `(Claude Code)`, and `claude agents --help` prints a line that starts with `Usage: claude agents`. `codex --version` prints `codex-cli` followed by a version number. A command for a program you do not use prints `command not found`: carry on. Agent Lookout never runs `codex`, so the Codex desktop app needs no CLI.
|
|
23
|
+
|
|
24
|
+
## With npx
|
|
25
|
+
|
|
26
|
+
### Start it
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx agent-lookout
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The first time, npm names the package and asks `Ok to proceed? (y)`. Press Enter. npm downloads under 2 MB, about 4 MB once installed, and keeps it in its cache. The package holds the built dashboard and the collector as plain JavaScript, so it needs Node.js and nothing else. Then Agent Lookout prints:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Agent Lookout is running at http://127.0.0.1:4777
|
|
36
|
+
It listens on this machine only. Press Ctrl+C to stop.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Open <http://127.0.0.1:4777>. Your sessions appear within a few seconds. If none do, see [No sessions appear](GUIDE.md#no-sessions-appear). It keeps running until you press Ctrl+C, so leave that terminal open. If you used `npm run dev` before, turn notifications on again here: the browser keeps them for each address.
|
|
40
|
+
|
|
41
|
+
| Option | What it does |
|
|
42
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `--port <number>` | Listens on this port in place of 4777, such as `--port 4778`. `AGENT_LOOKOUT_PORT` does the same, and `--port` wins when both are given |
|
|
44
|
+
| `--open` | Opens the address in your default browser once it is listening, with `open` on macOS, `xdg-open` on Linux or `rundll32.exe` on Windows. If that cannot be done, it prints one line with the address and keeps running |
|
|
45
|
+
| `--help` | Prints the options of `agent-lookout`, `agent-lookout status` and `agent-lookout mcp` |
|
|
46
|
+
| `--version` | Prints the version of Agent Lookout and nothing else |
|
|
47
|
+
|
|
48
|
+
`agent-lookout start` does the same as `agent-lookout`. Every setting under [Settings you can change](GUIDE.md#settings-you-can-change) works with it. It ends with 0 when you press Ctrl+C, and with 1 when it cannot start, such as when the port is in use. Under `npx agent-lookout`, the shell reports the interrupt instead, as exit code 130.
|
|
49
|
+
|
|
50
|
+
### Run it in the background
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
nohup npx --yes agent-lookout > agent-lookout.log 2>&1 &
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`--yes` stops npm asking `Ok to proceed?`, since nothing would answer. What Agent Lookout prints goes to `agent-lookout.log`.
|
|
57
|
+
|
|
58
|
+
### Another port
|
|
59
|
+
|
|
60
|
+
If it prints `Port 4777 is already in use`, another program has that port. Choose another:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
npx agent-lookout --port 4778
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use the new port in the address you open and in the check below. `agent-lookout status` and `agent-lookout mcp` then need `--url http://127.0.0.1:4778`, or `AGENT_LOOKOUT_URL` set to that address, as [In the terminal](GUIDE.md#in-the-terminal) says. [The port is in use](GUIDE.md#the-port-is-in-use) shows how to find which program holds a port.
|
|
67
|
+
|
|
68
|
+
### Check that it answers
|
|
69
|
+
|
|
70
|
+
If `npx agent-lookout` holds your terminal, use a second one:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
curl -s http://127.0.0.1:4777/api/health
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
It prints `{"ok":true,"version":"…"}`, with the version you are running. If a step prints something else, [When something goes wrong](GUIDE.md#when-something-goes-wrong) says what to do for the common problems.
|
|
77
|
+
|
|
78
|
+
### Stop it
|
|
79
|
+
|
|
80
|
+
Press Ctrl+C in its terminal. If it runs in the background, find its process ID and end it. On macOS, or on Linux with `lsof` installed, `lsof -nP -iTCP:4777 -sTCP:LISTEN` shows the process ID under `PID`, and `kill` followed by that number stops it. On Windows, `netstat -ano | findstr :4777` shows the process ID in its last column, and `taskkill /F /PID` followed by that number stops it. Put your port in place of 4777 if you chose another.
|
|
81
|
+
|
|
82
|
+
### Update it
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
npx agent-lookout@latest
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This also makes sure npx does not reuse an older copy it keeps. Each time npx starts it, npx asks npm's registry whether a newer version is out, as it does for any package it runs.
|
|
89
|
+
|
|
90
|
+
### Install it once
|
|
91
|
+
|
|
92
|
+
To start it without npx asking the registry each time, install it once and run it by name:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
npm install -g agent-lookout
|
|
96
|
+
agent-lookout
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`agent-lookout status` and `agent-lookout mcp` then start without npm asking its registry each time, which suits a tmux status line or a shell prompt that runs them every few seconds. Run `npm install -g agent-lookout` again to update it, and `npm uninstall -g agent-lookout` to take it away.
|
|
100
|
+
|
|
101
|
+
## Mac app
|
|
102
|
+
|
|
103
|
+
Download the disk image for your Mac from the [latest release](https://github.com/Olanetsoft/agent-lookout/releases/latest): `Agent-Lookout-<version>-mac-arm64.dmg` for Apple silicon, or `Agent-Lookout-<version>-mac-x64.dmg` for Intel. About This Mac, in the Apple menu, shows which you have: a chip such as Apple M1 is Apple silicon, and a processor named Intel is Intel. Open the disk image and drag Agent Lookout to Applications. Keep it there: the app updates itself only from a folder it can change. It needs no Node.js.
|
|
104
|
+
|
|
105
|
+
The app is not yet signed with an Apple Developer ID, so the first time a downloaded copy is opened, macOS does not open it. Its message is titled “Agent Lookout” Not Opened and says that Apple could not verify “Agent Lookout” is free of malware. To open it anyway:
|
|
106
|
+
|
|
107
|
+
1. Press Done. Do not press Move to Bin, or Move to Trash, which deletes the app.
|
|
108
|
+
2. Open System Settings › Privacy & Security.
|
|
109
|
+
3. Under Security, next to the line that says Agent Lookout was blocked, press Open Anyway, and confirm with your password or Touch ID. The button is there for about an hour after macOS stopped the app. If it has gone, open the app again, press Done, and go back to Privacy & Security.
|
|
110
|
+
|
|
111
|
+
macOS remembers the choice. [Desktop app](GUIDE.md#desktop-app) says how the app behaves, [Menu bar](GUIDE.md#menu-bar) what its icon in the menu bar lists, and [Updates](GUIDE.md#updates) how it checks for a newer version and installs it.
|
|
112
|
+
|
|
113
|
+
### Build it yourself
|
|
114
|
+
|
|
115
|
+
From a clone, on a Mac:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
npm install
|
|
119
|
+
npm run dist:mac
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
It takes a minute or two, and the first time it downloads Electron for each kind of Mac. It puts a disk image and a zip for each in `release/`, with the same names as on the release. Open the disk image for your Mac and drag Agent Lookout to Applications. A copy you built yourself on this Mac opens without the steps above.
|
|
123
|
+
|
|
124
|
+
### Mac app and npx together
|
|
125
|
+
|
|
126
|
+
The app opens no port, so `agent-lookout status`, `agent-lookout mcp` and a [tmux status line](GUIDE.md#in-a-tmux-status-line) cannot reach it. For those, run `npx agent-lookout` as well. The two keep their history in the same folder, so the Events log and the charts carry over, and their time rules and permission rules in the same settings file. Turn notifications on in only one of the two, or you get each one twice.
|
|
127
|
+
|
|
128
|
+
## Claude Code plugin
|
|
129
|
+
|
|
130
|
+
With Agent Lookout running, the plugin lets you answer a Claude Code permission prompt from the page, with Allow or Deny. In Claude Code, run:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
/plugin marketplace add Olanetsoft/agent-lookout
|
|
134
|
+
/plugin install agent-lookout@agent-lookout
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
From a shell, `claude plugin marketplace add Olanetsoft/agent-lookout` and `claude plugin install agent-lookout@agent-lookout` do the same. To remove it, run `/plugin uninstall agent-lookout@agent-lookout`. The hook needs `curl`, which macOS has, and most Linux systems do. On Windows the plugin does nothing, and Claude Code asks as usual. [plugins/agent-lookout/README.md](../plugins/agent-lookout/README.md) says what the hook does, and [Answer a permission prompt](GUIDE.md#answer-a-permission-prompt) what the page shows.
|
|
138
|
+
|
|
139
|
+
## From the repository
|
|
140
|
+
|
|
141
|
+
To run it from a clone, as you would to work on its code:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
git clone https://github.com/Olanetsoft/agent-lookout.git
|
|
145
|
+
cd agent-lookout
|
|
146
|
+
npm install
|
|
147
|
+
npm run build
|
|
148
|
+
npm start
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`npm start` prints the same two lines as `npx agent-lookout`, at the same address, and takes another port as `AGENT_LOOKOUT_PORT=4778 npm start`. Run before `npm run build`, it stops and tells you to build first. After a `git pull`, run `npm install` and `npm run build` again.
|
|
152
|
+
|
|
153
|
+
`npm link`, run once in the folder after `npm run build`, puts `agent-lookout` on your `PATH`, pointing at the clone, so it starts from any folder. It serves the same built page, so a pull needs the same `npm install` and `npm run build`.
|
|
154
|
+
|
|
155
|
+
`npm run dev` starts it at <http://localhost:5173> with the development tools used to work on its code, as [CONTRIBUTING.md](../CONTRIBUTING.md) describes.
|
|
156
|
+
|
|
157
|
+
## With your coding agent
|
|
158
|
+
|
|
159
|
+
A coding agent such as Claude Code or Codex can start it for you. Give it this prompt:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
Start Agent Lookout on this computer. First check that node --version
|
|
163
|
+
prints v22.12.0 or later. Then run npx --yes agent-lookout in the
|
|
164
|
+
background and leave it running.
|
|
165
|
+
It should print "Agent Lookout is running at http://127.0.0.1:4777".
|
|
166
|
+
If it prints anything else, or says the port is in use, stop and tell
|
|
167
|
+
me what it printed.
|
|
168
|
+
|
|
169
|
+
Do not use sudo, and do not set up email, a webhook, pull requests
|
|
170
|
+
or other machines. You are done when
|
|
171
|
+
curl -s http://127.0.0.1:4777/api/health prints {"ok":true and a
|
|
172
|
+
version number. Tell me what it printed.
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
An agent's background command can end when its session does. If the page then says Agent Lookout has stopped updating, run `npx agent-lookout` yourself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-lookout",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Shows the AI agent sessions on your computer in one browser page, and which need you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"dist/",
|
|
33
33
|
"docs/API.md",
|
|
34
34
|
"docs/GUIDE.md",
|
|
35
|
+
"docs/INSTALL.md",
|
|
35
36
|
"CHANGELOG.md",
|
|
36
37
|
"DISCLAIMER.md",
|
|
37
38
|
"LICENSE",
|