agent-lookout 0.2.7 → 0.2.9
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 +37 -0
- package/PRIVACY.md +164 -25
- package/README.md +46 -43
- package/SECURITY.md +29 -10
- package/dist/assets/{HistoryPanel-oSXRXEdv.js → HistoryPanel-Cr6VQlz-.js} +1 -1
- package/dist/assets/SessionPanel-YVGY4vbo.js +1 -0
- package/dist/assets/index-BRNSMV9A.css +1 -0
- package/dist/assets/index-DlDz48ci.js +9 -0
- package/dist/assets/motion-DlYOyClL.js +9 -0
- package/dist/cli/agent-lookout.js +4 -4
- package/dist/cli/{chunk-LZ62RW3N.js → chunk-AHCLJEYU.js} +1 -1
- package/dist/cli/{chunk-JSBQMY3Q.js → chunk-CMDNRNYP.js} +1 -1
- package/dist/cli/{chunk-I6Q35DHC.js → chunk-XV7BYWVY.js} +17 -0
- package/dist/cli/{mcpServer-HJSH3WIS.js → mcpServer-33QB6COH.js} +5 -5
- package/dist/cli/{startCommand-3LYUYPG4.js → startCommand-XUZHHWZG.js} +2881 -623
- package/dist/index.html +3 -3
- package/docs/API.md +150 -68
- package/docs/GUIDE.md +248 -94
- package/docs/INSTALL.md +8 -6
- package/package.json +5 -1
- package/dist/assets/SessionPanel-DcJvw0F8.js +0 -1
- package/dist/assets/index-B_1Its0R.css +0 -1
- package/dist/assets/index-DLPmOXDe.js +0 -3
- package/dist/assets/motion-BAhdRuCw.js +0 -9
package/dist/index.html
CHANGED
|
@@ -28,11 +28,11 @@
|
|
|
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-DlDz48ci.js"></script>
|
|
32
32
|
<link rel="modulepreload" crossorigin href="/assets/react-C2DiJaR6.js">
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/motion-
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/motion-DlYOyClL.js">
|
|
34
34
|
<link rel="modulepreload" crossorigin href="/assets/radix-BH0JH8UU.js">
|
|
35
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
35
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BRNSMV9A.css">
|
|
36
36
|
</head>
|
|
37
37
|
<body>
|
|
38
38
|
<div id="root"></div>
|
package/docs/API.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# API
|
|
2
2
|
|
|
3
|
-
Agent Lookout's server answers a small HTTP API under `/api/`. The dashboard reads it, and so do `agent-lookout status` and `agent-lookout mcp`. It is meant for this computer only: it listens on a loopback address, and it answers only requests made to a loopback name. Every route returns JSON, and every route but
|
|
3
|
+
Agent Lookout's server answers a small HTTP API under `/api/`. The dashboard reads it, and so do `agent-lookout status` and `agent-lookout mcp`. It is meant for this computer only: it listens on a loopback address, and it answers only requests made to a loopback name. Every route returns JSON, and every route but eight only reads.
|
|
4
4
|
|
|
5
5
|
The answers hold your session names and folder paths. Check one before you share it.
|
|
6
6
|
|
|
@@ -31,34 +31,38 @@ curl -s http://127.0.0.1:4777/api/sessions
|
|
|
31
31
|
|
|
32
32
|
Every answer has `Content-Type: application/json; charset=utf-8`, `Cache-Control: no-store`, `X-Content-Type-Options: nosniff` and `Cross-Origin-Resource-Policy: same-origin`. Every answer that is not a 200 has the body `{ "error": "<one sentence>" }`.
|
|
33
33
|
|
|
34
|
-
| Status | When
|
|
35
|
-
| ------ |
|
|
36
|
-
| 400 | The address, `since` or `
|
|
37
|
-
| 403 | The `Host`, the `Origin` or `Sec-Fetch-Site` above
|
|
38
|
-
| 404 | There is no route at that path
|
|
39
|
-
| 405 | Any method but `GET`, with `Allow: GET`. The
|
|
40
|
-
| 500 | Something went wrong that the server did not expect
|
|
34
|
+
| Status | When |
|
|
35
|
+
| ------ | ------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| 400 | The address, `since`, `windowMs` or the `id` of `GET /api/sessions/last-message` could not be read |
|
|
37
|
+
| 403 | The `Host`, the `Origin` or `Sec-Fetch-Site` above |
|
|
38
|
+
| 404 | There is no route at that path, or no session listed with the `id` `GET /api/sessions/last-message` was given |
|
|
39
|
+
| 405 | Any method but `GET`, with `Allow: GET`. The eight routes that act take only `POST`, with `Allow: POST` |
|
|
40
|
+
| 500 | Something went wrong that the server did not expect |
|
|
41
41
|
|
|
42
42
|
## Routes
|
|
43
43
|
|
|
44
|
-
| Route
|
|
45
|
-
|
|
|
46
|
-
| `GET /api/health`
|
|
47
|
-
| `GET /api/sessions`
|
|
48
|
-
| `GET /api/
|
|
49
|
-
| `GET /api/
|
|
50
|
-
| `GET /api/
|
|
51
|
-
| `GET /api/
|
|
52
|
-
| `GET /api/
|
|
53
|
-
| `GET /api/
|
|
54
|
-
| `GET /api/
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `POST /api/
|
|
59
|
-
| `POST /api/
|
|
60
|
-
| `POST /api/
|
|
61
|
-
| `POST /api/
|
|
44
|
+
| Route | Answers |
|
|
45
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `GET /api/health` | `{ ok: true, version }` |
|
|
47
|
+
| `GET /api/sessions` | The latest snapshot: `{ generatedAt, sources, sessions, answering, timeRules, quiet }` |
|
|
48
|
+
| `GET /api/sessions/last-message?id=<id>` | `{ message: { text, cut } }`, what one session last said, or `{ message: null, reason }` |
|
|
49
|
+
| `GET /api/events?since=<epoch ms>` | `{ events }`, newest first, at most 200 |
|
|
50
|
+
| `GET /api/history?windowMs=<ms>` | `{ points, startedAt, since, kept, restarts }`, one point for each poll, oldest first |
|
|
51
|
+
| `GET /api/email` | `{ on, to, events, afterMs, asking, problem, last, limitedUntil }` |
|
|
52
|
+
| `GET /api/webhook` | `{ on, host, events, afterMs, asking, problem, last, limitedUntil }` |
|
|
53
|
+
| `GET /api/ntfy` | `{ on, host, tokenSet, events, afterMs, asking, problem, last, limitedUntil }` |
|
|
54
|
+
| `GET /api/pushover` | `{ on, events, afterMs, asking, problem, last, limitedUntil }` |
|
|
55
|
+
| `GET /api/pull-requests` | `{ on, problem, gh, last }`: whether each branch's pull request is shown |
|
|
56
|
+
| `GET /api/waits` | `{ at, today, sevenDays, since, where }`: how long sessions waited on you |
|
|
57
|
+
| `GET /api/settings` | `{ timeRules, file, problem, permissionRules, permissionRulesProblem, ruleAnswers, ruleAnswersSince }`: the time rules, the permission rules, the file they are kept in, and what the rules answered |
|
|
58
|
+
| `POST /api/jump` | `{ ok: true, kind, place }`, with `app` for a terminal tab. One of eight routes that act |
|
|
59
|
+
| `POST /api/history/clear` | `{ ok: true, clearedAt }`. A route that acts |
|
|
60
|
+
| `POST /api/sessions/stop` | `{ ok: true }` once the session's process has ended. A route that acts |
|
|
61
|
+
| `POST /api/sessions/clean-up` | `{ results }`, what became of each session left running. A route that acts |
|
|
62
|
+
| `POST /api/permission/answer` | `{ ok: true, decision }` once a permission prompt was answered. A route that acts |
|
|
63
|
+
| `POST /api/settings/time-rules` | `{ ok: true, timeRules }` once the time rules are saved. A route that acts |
|
|
64
|
+
| `POST /api/settings/permission-rules` | `{ ok: true, permissionRules }` once one change to the permission rules is saved. A route that acts |
|
|
65
|
+
| `POST /api/phone/test` | `{ ok: true, channel, sentAt }` once a test push went through ntfy or Pushover. A route that acts |
|
|
62
66
|
|
|
63
67
|
Times are milliseconds since 1970, and lengths of time are milliseconds.
|
|
64
68
|
|
|
@@ -122,46 +126,46 @@ The snapshot the collector made at its last poll, two seconds apart at most:
|
|
|
122
126
|
|
|
123
127
|
Sessions come in this order: those that need you first, longest wait first, then working, idle, finished, failed and unknown, each with the most recent change first.
|
|
124
128
|
|
|
125
|
-
| Session field | Holds
|
|
126
|
-
| ----------------- |
|
|
127
|
-
| `id` | Stable across polls: the source, a colon, and the agent's own id for the session. A session on another machine has that machine's own id for it after its source: `remote:devbox:claude-code:…`
|
|
128
|
-
| `source` | `claude-code`, `codex` or `status-files`, or for a session on another machine `remote:` and the machine's name, such as `remote:devbox`
|
|
129
|
-
| `agent` | The agent's own name, for a session from a status file, and for a session on another machine, such as `Claude Code`. Left out for the others, whose agent is the source's `label`
|
|
130
|
-
| `machine` | The other machine it runs on, as `AGENT_LOOKOUT_REMOTES` names it: letters, digits and dashes, such as `devbox`. Left out for a session on this computer. Such a session has no `pid`, `links.open`, `jump`, `stop`, `ask` or `git.pullRequest`, since those act on this computer or are read from its folders
|
|
131
|
-
| `surface` | `terminal`, `vscode`, `desktop`, `cloud`, `browser` or `unknown`
|
|
132
|
-
| `name` | What the dashboard calls it
|
|
133
|
-
| `cwd` | The folder it works in, or `null`
|
|
134
|
-
| `project` | The last part of `cwd`, or `null`
|
|
135
|
-
| `git` | `{ branch }`, or `{ commit }` with seven characters when no branch is checked out, each with `repository`. Left out when the folder is in no repository that could be read
|
|
136
|
-
| `git.repository` | `{ id, name }`: the repository the folder belongs to, the same for its main folder and every worktree of it. `name` is the main folder's name. `id` is 16 hexadecimal characters, a hash of the path of the repository's own git folder, which its worktrees share, for telling repositories apart. Left out for a repository with no name, at the root, or a worktree whose `commondir` cannot be read
|
|
137
|
-
| `git.pullRequest` | With `AGENT_LOOKOUT_PULL_REQUESTS=on` only, for a branch whose pull request on github.com your own `gh` found: `{ number, title, state, checks, url }`. Described below. Left out with the setting off, for a commit, for the default branch, for a branch named with digits alone, such as `51`, for a repository with no remote on github.com, for a branch with no pull request, and for a session on another machine
|
|
138
|
-
| `status` | `needs-you`, `working`, `idle`, `finished`, `failed` or `unknown`
|
|
139
|
-
| `waitingReason` | For `needs-you` only: `permission`, `question` or `other`
|
|
140
|
-
| `waitingDetail` | The agent's own words for the wait, when it gives any
|
|
141
|
-
| `waitingText` | For `needs-you` Claude Code sessions only, and sessions on another machine whose Agent Lookout sent it: what the session is asking, in a line or two, read from the last message of its transcript, such as `Run: npm test`, `Edit: src/app.ts` or the question it put. Plain text of at most 200 characters. Left out when the transcript does not say, or with `AGENT_LOOKOUT_WAITING_TEXT=off`. No event in `/api/events`, and no history point, holds it. An email
|
|
142
|
-
| `startedAt` | When it started, or `null`
|
|
143
|
-
| `statusSince` | When its status last changed, or `null`. It does not move while a wait goes on
|
|
144
|
-
| `lastWriteAt` | When its agent last wrote the file it is read from, for Codex and status files
|
|
145
|
-
| `pid`, `alive` | Its process and whether that still runs, when known
|
|
146
|
-
| `links.open` | A `vscode://` address that opens it, for a session in VS Code
|
|
147
|
-
| `jump` | `{ kind: "tmux", place }` or `{ kind: "terminal", app, place }` when `POST /api/jump` can take you to it
|
|
148
|
-
| `stop` | `{ how: "signal" }` or `{ how: "background" }` when `POST /api/sessions/stop` can stop it: a Claude Code session in a terminal or VS Code, or a background job. It holds nothing else
|
|
149
|
-
| `ask` | For `needs-you` Claude Code sessions with the Agent Lookout plugin only, while Agent Lookout holds the session's permission request: `{ requestId, tool, command, description, inputs, allow, denyOnly, subagent, until }`. `command` is the whole command for `Bash`, every line, and `inputs` is `[{ name, value }]`, each input as text, for any other tool or the rest of a command's. `allow` says whether `POST /api/permission/answer` takes `allow`, which it does only when the whole of what is allowed is here: never for `Edit`, `Write`, `MultiEdit` or `NotebookEdit`, and not for `ExitPlanMode` or `AskUserQuestion`, past 4,000 characters or 40 lines, with a character that cannot be shown as it is, with more than two blank lines in a row, or for a command holding right-to-left letters, with `denyOnly` saying why: `edit`, `too-long`, `hidden-characters`, `not-yes-or-no`, `right-to-left` or `blank-lines`. `until` is when the request is let go. Kept in memory only while it is held: no event, history point, email, webhook post or answer of `agent-lookout mcp` holds it |
|
|
150
|
-
| `answered` | `true` for a `needs-you` Claude Code session whose permission request Agent Lookout has answered, from Allow, Deny or a [permission rule](GUIDE.md#permission-rules), or is answering by a rule, while its registry file still says it waits in that wait, as it can for a second or two after the answer, and for 10 seconds after the answer at most. A session on another machine has it when Agent Lookout there sent it, for an answer given there. The wait is over: the dashboard's notifications and Agent Lookout's own, email
|
|
151
|
-
| `stale` | `true` once it has been idle for 24 hours, or for as long as the idle rule in `timeRules` says while it is on. For a session on another machine too, by this computer's rule, whatever that machine said
|
|
152
|
-
|
|
153
|
-
| Source field | Holds
|
|
154
|
-
| -------------- |
|
|
155
|
-
| `id`, `label` | `claude-code` and `Claude Code`, `codex` and `Codex`, or `status-files` and `Status files`, or for another machine `remote:devbox` and `devbox`
|
|
156
|
-
| `state` | `ok`, `searching`, `unavailable`, `not-set-up` or `error`
|
|
157
|
-
| `detail` | One plain sentence: how it is being read, or what went wrong
|
|
158
|
-
| `advice` | One plain sentence saying what you can do about a problem, when the collector knows
|
|
159
|
-
| `watching` | `[{ label, value }]`: what it reads and runs, and how often
|
|
160
|
-
| `basis` | Which way the sessions were read, for a source that has more than one
|
|
161
|
-
| `capabilities` | What its agent can report, and whether its sessions can be stopped and answered: for each of nine things, `{ level: "yes" }`, or `{ level: "no" }` or `"partly"` with a `reason`
|
|
162
|
-
| `checkedAt` | When it was last read
|
|
163
|
-
| `machine` | For another machine: its name, which its sessions carry as their `machine`
|
|
164
|
-
| `agents` | For another machine: `[{ label, capabilities }]`, what each agent found there can report, as last read from it, with `jump`, `stop` and `answer` always `no`
|
|
129
|
+
| Session field | Holds |
|
|
130
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
131
|
+
| `id` | Stable across polls: the source, a colon, and the agent's own id for the session. A session on another machine has that machine's own id for it after its source: `remote:devbox:claude-code:…` |
|
|
132
|
+
| `source` | `claude-code`, `codex`, `antigravity-cli` or `status-files`, or for a session on another machine `remote:` and the machine's name, such as `remote:devbox` |
|
|
133
|
+
| `agent` | The agent's own name, for a session from a status file, and for a session on another machine, such as `Claude Code`. Left out for the others, whose agent is the source's `label` |
|
|
134
|
+
| `machine` | The other machine it runs on, as `AGENT_LOOKOUT_REMOTES` names it: letters, digits and dashes, such as `devbox`. Left out for a session on this computer. Such a session has no `pid`, `links.open`, `jump`, `stop`, `ask` or `git.pullRequest`, since those act on this computer or are read from its folders |
|
|
135
|
+
| `surface` | `terminal`, `vscode`, `desktop`, `cloud`, `browser` or `unknown` |
|
|
136
|
+
| `name` | What the dashboard calls it |
|
|
137
|
+
| `cwd` | The folder it works in, or `null` |
|
|
138
|
+
| `project` | The last part of `cwd`, or `null` |
|
|
139
|
+
| `git` | `{ branch }`, or `{ commit }` with seven characters when no branch is checked out, each with `repository`. Left out when the folder is in no repository that could be read |
|
|
140
|
+
| `git.repository` | `{ id, name }`: the repository the folder belongs to, the same for its main folder and every worktree of it. `name` is the main folder's name. `id` is 16 hexadecimal characters, a hash of the path of the repository's own git folder, which its worktrees share, for telling repositories apart. Left out for a repository with no name, at the root, or a worktree whose `commondir` cannot be read |
|
|
141
|
+
| `git.pullRequest` | With `AGENT_LOOKOUT_PULL_REQUESTS=on` only, for a branch whose pull request on github.com your own `gh` found: `{ number, title, state, checks, url }`. Described below. Left out with the setting off, for a commit, for the default branch, for a branch named with digits alone, such as `51`, for a repository with no remote on github.com, for a branch with no pull request, and for a session on another machine |
|
|
142
|
+
| `status` | `needs-you`, `working`, `idle`, `finished`, `failed` or `unknown` |
|
|
143
|
+
| `waitingReason` | For `needs-you` only: `permission`, `question` or `other` |
|
|
144
|
+
| `waitingDetail` | The agent's own words for the wait, when it gives any |
|
|
145
|
+
| `waitingText` | For `needs-you` Claude Code sessions only, and sessions on another machine whose Agent Lookout sent it: what the session is asking, in a line or two, read from the last message of its transcript, such as `Run: npm test`, `Edit: src/app.ts` or the question it put. Plain text of at most 200 characters. Left out when the transcript does not say, or with `AGENT_LOOKOUT_WAITING_TEXT=off`. No event in `/api/events`, and no history point, holds it. An email, a webhook post or a push holds it only with `AGENT_LOOKOUT_EMAIL_ASKING=on`, `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on` |
|
|
146
|
+
| `startedAt` | When it started, or `null` |
|
|
147
|
+
| `statusSince` | When its status last changed, or `null`. It does not move while a wait goes on |
|
|
148
|
+
| `lastWriteAt` | When its agent last wrote the file it is read from, for Codex, the Antigravity CLI and status files |
|
|
149
|
+
| `pid`, `alive` | Its process and whether that still runs, when known |
|
|
150
|
+
| `links.open` | A `vscode://` address that opens it, for a session in VS Code |
|
|
151
|
+
| `jump` | `{ kind: "tmux", place }` or `{ kind: "terminal", app, place }` when `POST /api/jump` can take you to it |
|
|
152
|
+
| `stop` | `{ how: "signal" }` or `{ how: "background" }` when `POST /api/sessions/stop` can stop it: a Claude Code session in a terminal or VS Code, or a background job. It holds nothing else |
|
|
153
|
+
| `ask` | For `needs-you` Claude Code sessions with the Agent Lookout plugin only, while Agent Lookout holds the session's permission request: `{ requestId, tool, command, description, inputs, allow, denyOnly, subagent, until }`. `command` is the whole command for `Bash`, every line, and `inputs` is `[{ name, value }]`, each input as text, for any other tool or the rest of a command's. `allow` says whether `POST /api/permission/answer` takes `allow`, which it does only when the whole of what is allowed is here: never for `Edit`, `Write`, `MultiEdit` or `NotebookEdit`, and not for `ExitPlanMode` or `AskUserQuestion`, past 4,000 characters or 40 lines, with a character that cannot be shown as it is, with more than two blank lines in a row, or for a command holding right-to-left letters, with `denyOnly` saying why: `edit`, `too-long`, `hidden-characters`, `not-yes-or-no`, `right-to-left` or `blank-lines`. `until` is when the request is let go. Kept in memory only while it is held: no event, history point, email, webhook post, push or answer of `agent-lookout mcp` holds it |
|
|
154
|
+
| `answered` | `true` for a `needs-you` Claude Code session whose permission request Agent Lookout has answered, from Allow, Deny or a [permission rule](GUIDE.md#permission-rules), or is answering by a rule, while its registry file still says it waits in that wait, as it can for a second or two after the answer, and for 10 seconds after the answer at most. A session on another machine has it when Agent Lookout there sent it, for an answer given there. The wait is over: the dashboard's notifications and Agent Lookout's own, email, the webhook, ntfy and Pushover send nothing more for it, no reminder comes, `/api/waits` does not count it as waiting, `/api/history` counts it as working, not as needing you, and the dashboard, the Mac app's Dock badge and menu bar, `agent-lookout status` and the MCP tools count it as working too. Left out otherwise |
|
|
155
|
+
| `stale` | `true` once it has been idle for 24 hours, or for as long as the idle rule in `timeRules` says while it is on. For a session on another machine too, by this computer's rule, whatever that machine said |
|
|
156
|
+
|
|
157
|
+
| Source field | Holds |
|
|
158
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| `id`, `label` | `claude-code` and `Claude Code`, `codex` and `Codex`, `antigravity-cli` and `Antigravity CLI`, or `status-files` and `Status files`, or for another machine `remote:devbox` and `devbox` |
|
|
160
|
+
| `state` | `ok`, `searching`, `unavailable`, `not-set-up` or `error` |
|
|
161
|
+
| `detail` | One plain sentence: how it is being read, or what went wrong |
|
|
162
|
+
| `advice` | One plain sentence saying what you can do about a problem, when the collector knows |
|
|
163
|
+
| `watching` | `[{ label, value }]`: what it reads and runs, and how often |
|
|
164
|
+
| `basis` | Which way the sessions were read, for a source that has more than one |
|
|
165
|
+
| `capabilities` | What its agent can report, and whether its sessions can be stopped and answered: for each of nine things, `{ level: "yes" }`, or `{ level: "no" }` or `"partly"` with a `reason` |
|
|
166
|
+
| `checkedAt` | When it was last read |
|
|
167
|
+
| `machine` | For another machine: its name, which its sessions carry as their `machine` |
|
|
168
|
+
| `agents` | For another machine: `[{ label, capabilities }]`, what each agent found there can report, as last read from it, with `jump`, `stop` and `answer` always `no` |
|
|
165
169
|
|
|
166
170
|
The [guide](GUIDE.md#what-each-agent-can-report) has the table `capabilities` holds.
|
|
167
171
|
|
|
@@ -197,6 +201,32 @@ With `AGENT_LOOKOUT_PULL_REQUESTS=on`, a session on a branch that has a pull req
|
|
|
197
201
|
It is what `gh` last said, asked at most once every 2 minutes for each repository and branch, so it can be up to about 2 minutes old. [Pull requests](GUIDE.md#pull-requests) in the guide says when it is asked for.
|
|
198
202
|
`answering` says whether permission prompts can be answered from the dashboard: `{ state, plugin, holdMs, problem }`. `state` is `on` while Agent Lookout listens for the plugin's requests, `off` with `AGENT_LOOKOUT_ANSWER=off`, and `unavailable` when it could not listen or does not, as on Windows, which has no Unix socket for the plugin's hook, with `problem` saying why in one sentence. `plugin` is `seen` once a request has arrived since the last permission prompt it missed, `missed` when a Claude Code session waited for permission and no request arrived, as for a session without the plugin, and `unknown` before either. `holdMs` is how long a request is held.
|
|
199
203
|
|
|
204
|
+
### `GET /api/sessions/last-message?id=<id>`
|
|
205
|
+
|
|
206
|
+
What one session on this computer last said, read from the end of its transcript when it is asked for. The dashboard asks for it only while that session's details are open. Only Claude Code sessions are read for now. It only reads.
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{ "message": { "text": "The tests pass now.\n\nI changed two files in src/app.", "cut": false } }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| Field | Holds |
|
|
213
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
214
|
+
| `message.text` | The text of the session's newest reply, its parts joined with a blank line between. Line breaks are kept, a tab is two spaces, any other control character is a space, and the characters that change the direction of text are taken out. At most its last 2,000 characters. Plain text, with Markdown left as it was written |
|
|
215
|
+
| `message.cut` | `true` when the start of a longer message is left out |
|
|
216
|
+
|
|
217
|
+
When there is nothing to show, `message` is `null` and `reason` says why:
|
|
218
|
+
|
|
219
|
+
| `reason` | When |
|
|
220
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `off` | `AGENT_LOOKOUT_LAST_MESSAGE=off`, or `AGENT_LOOKOUT_WAITING_TEXT=off` for a Claude Code session. `setting` names the one that is off |
|
|
222
|
+
| `not-read` | The session is not a Claude Code session on this computer: a Codex, Antigravity CLI or status-file session, or one on another machine |
|
|
223
|
+
| `not-found` | Its transcript was not found. A link or a pipe where it should be does not count, and a background job known only by its job ID has none to look for |
|
|
224
|
+
| `unreadable` | Its transcript could not be read, as when a link or a pipe has taken its place since it was found |
|
|
225
|
+
| `nothing-yet` | The whole transcript was read, and the session has said nothing yet |
|
|
226
|
+
| `too-far-back` | Its last message is further back than the last 256 KB of the transcript, which is all that is read |
|
|
227
|
+
|
|
228
|
+
The query is one `id` of 1 to 300 characters, a session's `id` from `GET /api/sessions` written with `encodeURIComponent`, and nothing else, or the answer is 400. A request a browser marks with any `Sec-Fetch-Site` but `same-origin` gets 403, so a page at another port of this computer, or a link opened from anywhere, reads nothing. A session that is not listed gets 404. At most four transcripts are read in a second, for every session together, and above that the answer is 429 with `Retry-After: 1`. An answer is given again from memory for a second. With `AGENT_LOOKOUT_LAST_MESSAGE=off` every `id` is answered `off`, and nothing is looked up. [A session's last message](../PRIVACY.md#a-sessions-last-message) in PRIVACY.md says what is read and kept.
|
|
229
|
+
|
|
200
230
|
### `GET /api/events?since=<epoch ms>`
|
|
201
231
|
|
|
202
232
|
`{ "events": [...] }`: what changed, newest first, at most 200, and with `since` only those after it. Each event is `{ id, at, sessionId, sessionName, kind, from, to, severity }`, where `kind` is `appeared`, `status-changed`, `ended`, `stopped` or `answered`, `from` and `to` are statuses, and `severity` is `advisory`, `warning` for a change to `needs-you` or `critical` for a change to `failed`. A `stopped` event says that Agent Lookout stopped the session, when you pressed Stop or ended the sessions left running: it has `from`, the status the session had, no `to`, and `by: "agent-lookout"`. Its leaving the list is an `ended` event of its own. An `answered` event says that Agent Lookout answered a permission prompt of the session's, when you pressed Allow or Deny, or by a permission rule: it has `from`, `by: "agent-lookout"` and `decision`, `allow` or `deny`, and nothing of what was asked. One a rule gave also has `tool`, the tool's name as Claude Code gave it, and `rule`, `{ tool, command }`, the rule as you wrote it, with `command` only for a Bash rule that names one; its decision is the event's. Neither holds the command the session asked to run. A reader should pass over a `kind` it does not know: later versions may add others. The collector keeps the last 1,000 in memory, and with history kept on disk it reads them back when it starts again.
|
|
@@ -311,6 +341,35 @@ Whether [email](GUIDE.md#email) or a [webhook](GUIDE.md#webhook) is set up, and
|
|
|
311
341
|
|
|
312
342
|
Neither ever holds the mail server's address, its user name or its password, the webhook's path, or anything a session was asking.
|
|
313
343
|
|
|
344
|
+
### `GET /api/ntfy` and `GET /api/pushover`
|
|
345
|
+
|
|
346
|
+
Whether pushes to a phone go through [ntfy](GUIDE.md#ntfy) or [Pushover](GUIDE.md#pushover), and how the last one went. They only read: both are set up in the environment Agent Lookout starts with.
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{
|
|
350
|
+
"on": true,
|
|
351
|
+
"host": "ntfy.sh",
|
|
352
|
+
"tokenSet": false,
|
|
353
|
+
"events": ["needs-you"],
|
|
354
|
+
"afterMs": 60000,
|
|
355
|
+
"asking": false,
|
|
356
|
+
"problem": null,
|
|
357
|
+
"last": { "at": 1791205920000, "sent": true },
|
|
358
|
+
"limitedUntil": null
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Each has the fields of `GET /api/webhook`, ntfy's with one more, `tokenSet`, and Pushover's without `host`. These differ:
|
|
363
|
+
|
|
364
|
+
| Field | Holds |
|
|
365
|
+
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
366
|
+
| `host` | ntfy only: the host of the server the topic is on, such as `ntfy.sh`, and never the topic |
|
|
367
|
+
| `tokenSet` | ntfy only: whether `AGENT_LOOKOUT_NTFY_TOKEN` is set, `true` or `false`, and `null` while ntfy is off. Never the token |
|
|
368
|
+
| `asking` | `true` with `AGENT_LOOKOUT_NTFY_ASKING=on` or `AGENT_LOOKOUT_PUSHOVER_ASKING=on`, `false` when that setting is `off` or left out |
|
|
369
|
+
| `last` | The last push of a notice that was tried. A test push from `POST /api/phone/test` is not counted here, though it counts toward the 20 an hour |
|
|
370
|
+
|
|
371
|
+
Neither ever holds the ntfy topic or its token, Pushover's application token or the user key, or anything a session was asking.
|
|
372
|
+
|
|
314
373
|
### `GET /api/pull-requests`
|
|
315
374
|
|
|
316
375
|
Whether Agent Lookout shows each session's [pull request](GUIDE.md#pull-requests), with `AGENT_LOOKOUT_PULL_REQUESTS=on`, and whether the GitHub CLI, `gh`, can be asked for them. It only reads.
|
|
@@ -425,9 +484,32 @@ A new rule goes to the end of the list, with an ID the server makes. A move at e
|
|
|
425
484
|
|
|
426
485
|
A held permission request is put to the rules as its session's registry file confirms its wait, and a deny or an allow rule answers it through the same path as `POST /api/permission/answer`, below, with every check it makes. [Permission rules](GUIDE.md#permission-rules) says what each rule matches.
|
|
427
486
|
|
|
487
|
+
### `POST /api/phone/test`
|
|
488
|
+
|
|
489
|
+
A route that acts. It sends one test push through ntfy or Pushover, as Send a test on the card in Settings does. The push is titled "Agent Lookout test", says "Pushes from Agent Lookout reach this device.", and holds nothing of any session. It goes during quiet hours too, since the person asked, and counts toward the channel's 20 an hour. It makes the same checks as `POST /api/jump`, with its own action, in `src/collector/outbound/testSendRoute.ts`, and answers only a request that:
|
|
490
|
+
|
|
491
|
+
- is a `POST`. Any other method gets 405, with `Allow: POST`.
|
|
492
|
+
- has an `Origin` that names this computer, or gets 403.
|
|
493
|
+
- is marked `same-origin` in `Sec-Fetch-Site`, when that header is sent, or gets 403.
|
|
494
|
+
- carries `X-Agent-Lookout-Action: phone-test`, or gets 403.
|
|
495
|
+
- has `Content-Type: application/json`, or gets 415.
|
|
496
|
+
- has a body of 64 bytes or less, or gets 413, that is exactly `{ "channel": "ntfy" }` or `{ "channel": "pushover" }`, or gets 400.
|
|
497
|
+
|
|
498
|
+
Nothing in the request reaches the push or says where it goes: that is the channel's own setting. It sends one test a second, and one at a time.
|
|
499
|
+
|
|
500
|
+
| Status | Body |
|
|
501
|
+
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
502
|
+
| 200 | `{ "ok": true, "channel": "ntfy", "sentAt": 1791205920000 }`: the service took it |
|
|
503
|
+
| 409 | `reason: "off"`: the channel is not set up |
|
|
504
|
+
| 429 | `reason: "too-soon"`, with `Retry-After: 1` |
|
|
505
|
+
| 429 | `reason: "limited"`, with `limitedUntil` and `Retry-After` in seconds: 20 pushes were tried through that channel in the last hour |
|
|
506
|
+
| 502 | `reason: "not-sent"`: it was tried, and the service did not take it. The `error` says why, such as `Pushover had a problem (status 503).` |
|
|
507
|
+
|
|
508
|
+
Each of these has an `error` sentence beside its `reason`, and none holds the topic, a token or a key.
|
|
509
|
+
|
|
428
510
|
### `POST /api/jump`
|
|
429
511
|
|
|
430
|
-
One of the
|
|
512
|
+
One of the eight routes that act. It selects the tmux pane a session runs in, or brings its tab of Terminal or iTerm2 to the front, as the Jump button does. Its checks, on top of the ones every request passes, are `jumpRefusalFor` in `src/collector/jumpRoute.ts`, which makes those `actionRefusalFor` in `src/collector/handler.ts` makes for every route that acts. It answers only a request that:
|
|
431
513
|
|
|
432
514
|
- is a `POST`. Any other method gets 405, with `Allow: POST`.
|
|
433
515
|
- has an `Origin` that names this computer. A request with no `Origin` gets 403 here, though a read may go without one.
|
|
@@ -574,7 +656,7 @@ A program that only reads should not send it. `agent-lookout status` and `agent-
|
|
|
574
656
|
|
|
575
657
|
## For this computer only
|
|
576
658
|
|
|
577
|
-
The API has no token or password. The `Host` and `Origin` rules keep websites, and other computers on the network, away from it: it listens on loopback, and a browser cannot be made to read it from another site. What they do not stop is another program on this computer, which can read it as `curl` does, and on a shared computer that includes other user accounts. Such a program can also send the headers a route that acts asks for, since no browser is involved, so another account can ask the server to stop your Claude Code sessions, which it could not do itself. [SECURITY.md](../SECURITY.md) lists that as a known limit. The Mac app opens no port,
|
|
659
|
+
The API has no token or password. The `Host` and `Origin` rules keep websites, and other computers on the network, away from it: it listens on loopback, and a browser cannot be made to read it from another site. What they do not stop is another program on this computer, which can read it as `curl` does, and on a shared computer that includes other user accounts. Such a program can also send the headers a route that acts asks for, since no browser is involved, so another account can ask the server to stop your Claude Code sessions, which it could not do itself. It can also read up to 2,000 characters of what a listed Claude Code session last said, from `GET /api/sessions/last-message`. [SECURITY.md](../SECURITY.md) lists that as a known limit. The Mac app opens no port, `AGENT_LOOKOUT_STOP=off` takes stopping away, and `AGENT_LOOKOUT_LAST_MESSAGE=off` takes last messages away.
|
|
578
660
|
|
|
579
661
|
A token would not keep out programs running as you, which could read it as easily as the API. It could keep out other accounts on a shared computer, stopping your sessions included, the known limit above, and is left for a later version. The MCP server changes none of this: it opens no port of its own, speaks to the app that started it over stdin and stdout, and reads the same loopback API the dashboard and `agent-lookout status` read without one. A token is also for an API that can be reached some other way, such as from another computer, and Agent Lookout has none.
|
|
580
662
|
|