notemend 0.1.6 → 0.1.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/.claude-plugin/plugin.json +1 -1
- package/README.md +191 -176
- package/bin/notemend.mjs +1 -1
- package/package.json +1 -1
- package/src/client/run.js +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,162 @@
|
|
|
1
|
-
# notemend
|
|
1
|
+
# notemend
|
|
2
2
|
|
|
3
|
-
Note it. Mend it.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Note it. Mend it. Pin a comment on any element of a live site, at the screen width it
|
|
4
|
+
breaks; your coding agent — Claude Code, Codex, Cursor, Gemini CLI, Copilot or any other —
|
|
5
|
+
takes the comment, fixes the code and says what it changed; a person confirms the fix with
|
|
6
|
+
a tick. For design studios, their teams and their clients.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
This package is the bar people comment with and the `notemend` CLI and MCP server agents
|
|
9
|
+
work the queue with. The threads live in the Notemend cabinet at
|
|
10
|
+
[app.notemend.com](https://app.notemend.com), shared by everyone in a project — or, with
|
|
11
|
+
no account, in `feedback/comments.json` in your repo. Docs:
|
|
12
|
+
[app.notemend.com/docs](https://app.notemend.com/docs).
|
|
13
|
+
|
|
14
|
+
## Start
|
|
15
|
+
|
|
16
|
+
1. Make a project at [app.notemend.com](https://app.notemend.com).
|
|
17
|
+
2. Put the bar on the site — one line, any site (below) — open it with Notemend, and pin
|
|
18
|
+
comments. Teammates and clients do the same.
|
|
19
|
+
3. In the site's code folder, connect your agent:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npx notemend init # asks which agents you use and wires each one
|
|
23
|
+
npx notemend login # shows a code; allow it in the cabinet
|
|
24
|
+
npx notemend link <project-id>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then ask your agent for the open comments. It takes one (`notemend start`), fixes it and
|
|
28
|
+
resolves it with a note (`notemend done`); the comment waits, marked, until a person
|
|
29
|
+
confirms it on the site or in the cabinet.
|
|
30
|
+
|
|
31
|
+
## On any site — one line, no build
|
|
32
|
+
|
|
33
|
+
Webflow, Tilda, WordPress, Framer, a live Next or Astro site: the cabinet's **Install**
|
|
34
|
+
tab hands out one line for the site's custom code,
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script src="https://app.notemend.com/bar.js" data-project="<project-id>" defer></script>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
and the whole bar — comments, grid, spacing, size and type inspectors, breakpoints,
|
|
41
|
+
devices — runs on the live site. A visitor sees nothing: the bar wakes only for people in
|
|
42
|
+
the project, once they open the site with `?notemend` (the cabinet's "Open site", the
|
|
43
|
+
link you send a client) and sign in once on that browser. Their session stays with the
|
|
44
|
+
cabinet, in a hidden frame of its own — the site's scripts never hold a token. The
|
|
45
|
+
cabinet's **Preview** tab shows the site at desktop, tablet or mobile width with the bar
|
|
46
|
+
in it. The pages menu comes from the site's `sitemap.xml` and the pages that already have
|
|
47
|
+
threads.
|
|
48
|
+
|
|
49
|
+
`bar.js` is `src/hosted.ts` bundled by the cabinet's build (`web/scripts/build-bar.mjs`);
|
|
50
|
+
its endpoints are the dev server's, served by the cabinet (`src/hosting.mjs`) as whoever
|
|
51
|
+
is signed in, so the database's rules decide what they may read and write.
|
|
52
|
+
|
|
53
|
+
## Your coding agent
|
|
54
|
+
|
|
55
|
+
The queue is a CLI any agent that runs commands can work, an MCP server for agents that
|
|
56
|
+
take tools, and a Claude Code plugin. Whichever you use, a comment reaches the agent with
|
|
57
|
+
its page, element, width, device, thread, screenshots and what the page logged (console
|
|
58
|
+
errors and failed requests, secrets masked), quoted as what people wrote — never as
|
|
59
|
+
instructions — and only the comments meant for the agent.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
npx notemend open comments for the agent
|
|
63
|
+
npx notemend all including resolved ones and those given to people
|
|
64
|
+
npx notemend start <id> take it before fixing: the thread shows the agent is on it,
|
|
65
|
+
and another agent is refused
|
|
66
|
+
npx notemend reply <id> "..." answer in the thread
|
|
67
|
+
npx notemend done <id> "..." resolve as the agent, with a note (stays marked until a person confirms)
|
|
68
|
+
npx notemend reopen <id>
|
|
69
|
+
npx notemend rm <id> delete the thread and its screenshots
|
|
70
|
+
npx notemend shot <id> [--before] a picture of the element at the comment's width (Playwright)
|
|
71
|
+
npx notemend prune [--dry] local store: move resolved threads to feedback/archive/
|
|
72
|
+
npx notemend digest | count short forms for the agents' hooks
|
|
73
|
+
npx notemend mcp the same queue as an MCP server (stdio)
|
|
74
|
+
npx notemend init [--yes] set a project up
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The root is the nearest directory with a `package.json` above the working directory, or
|
|
78
|
+
`--root <dir>`. The panel numbers threads per page and breakpoint (`#4`); the CLI prints
|
|
79
|
+
both that and the file-wide id: `#4 (id 10)`. `prune` keeps a local store readable:
|
|
80
|
+
resolved threads and their screenshots move to `feedback/archive/`, and the numbering
|
|
81
|
+
carries on. Nothing is deleted.
|
|
82
|
+
|
|
83
|
+
### Signing in
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
npx notemend login # shows a code and opens the cabinet — allow the same code there
|
|
87
|
+
npx notemend link # a new project named after the site (package.json name and homepage)
|
|
88
|
+
npx notemend link <project-id> # or an existing one — the id is under Install in the cabinet
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`login` works over SSH and in containers too — nothing listens on your computer; open the
|
|
92
|
+
link on any machine where you are signed in. `login --code` signs in with the six-digit
|
|
93
|
+
code the cabinet emails instead. The terminal gets a session of its own, so signing out in
|
|
94
|
+
one never signs out the other. `link` writes `.notemend.json`; commit it and the whole
|
|
95
|
+
team files into the same project. `whoami` says who you are and where comments go;
|
|
96
|
+
`unlink` and `logout` undo.
|
|
97
|
+
|
|
98
|
+
### Wiring each agent
|
|
99
|
+
|
|
100
|
+
`init` asks which agents you use (or `--agents=claude,codex,cursor`) and wires each the
|
|
101
|
+
way it reads context:
|
|
102
|
+
|
|
103
|
+
| Agent | What `init` writes | What it reads |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| Claude Code | `.claude/settings.json` hooks | `digest` at session start, `count` on each prompt |
|
|
106
|
+
| Codex | `.codex/hooks.json` (+ `codex_hooks = true` in `.codex/config.toml`) | the same, as plain text |
|
|
107
|
+
| Gemini CLI | `.gemini/settings.json` hooks, `GEMINI.md` | the same, as `hookSpecificOutput.additionalContext` JSON |
|
|
108
|
+
| Cursor | `.cursor/hooks.json` (`sessionStart`) | `digest` as `additional_context` |
|
|
109
|
+
| Copilot, Windsurf, Amp, … | a block in `AGENTS.md` | how to list, take, answer and close threads |
|
|
110
|
+
|
|
111
|
+
An agent signs what it writes: threads show Codex, Gemini or Cursor rather than Claude.
|
|
112
|
+
Gemini CLI, Codex and Cursor are recognised from their environment; any other passes
|
|
113
|
+
`--as <agent>` (`npx notemend reply 3 "…" --as copilot`).
|
|
114
|
+
|
|
115
|
+
### MCP
|
|
116
|
+
|
|
117
|
+
`npx notemend mcp` serves the queue over stdio: `list_comments`, `start`, `reply`,
|
|
118
|
+
`resolve`, `reopen` and `screenshot`. Each runs the CLI above, so the cloud, agent keys
|
|
119
|
+
and sign-in work as they do there; replies are signed with the client's own name. It is in
|
|
120
|
+
the MCP Registry as `io.github.zharkodv-cmd/notemend`.
|
|
121
|
+
|
|
122
|
+
One click: **Add to Cursor**, **Add to VS Code** and the **Claude Desktop** bundle are on
|
|
123
|
+
[app.notemend.com/docs/mcp](https://app.notemend.com/docs/mcp). By hand:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
codex mcp add notemend -- npx notemend mcp
|
|
127
|
+
claude mcp add notemend -- npx notemend mcp
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{ "mcpServers": { "notemend": { "command": "npx", "args": ["notemend", "mcp", "--root", "/path/to/site"] } } }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Claude Code plugin
|
|
135
|
+
|
|
136
|
+
The hooks and the tools in one, no `init` needed for Claude Code:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
/plugin marketplace add https://app.notemend.com/claude/marketplace.json
|
|
140
|
+
/plugin install notemend@notemend
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Its files are in `plugin/`; `npm run pack` puts them at the package root, and
|
|
144
|
+
`npm run mcpb` rebuilds the Claude Desktop bundle (`web/public/notemend.mcpb`) after a pack.
|
|
145
|
+
|
|
146
|
+
### An agent that works on its own
|
|
147
|
+
|
|
148
|
+
A project's settings make agent keys (shown once, kept only as a hash). With one as
|
|
149
|
+
`NOTEMEND_KEY`, the CLI needs no sign-in: it lists the project's threads and takes,
|
|
150
|
+
answers and closes them as the agent — nothing else. `npx notemend init --ci` writes
|
|
151
|
+
`.github/workflows/notemend.yml`: every hour (or by hand) it asks for the agent's threads
|
|
152
|
+
and, when there are some, runs Claude Code's GitHub Action to fix them on a branch, close
|
|
153
|
+
each with what it changed, and open a pull request. It needs the repository secrets
|
|
154
|
+
`NOTEMEND_KEY` and `ANTHROPIC_API_KEY`, and `.notemend.json` committed. A person still
|
|
155
|
+
merges, and confirms each fix on the live site.
|
|
156
|
+
|
|
157
|
+
## The bar
|
|
158
|
+
|
|
159
|
+
Review a site the way you review a design file:
|
|
9
160
|
|
|
10
161
|
- **Pages** — every route in one menu, grouped by folder (or your own groups), with the
|
|
11
162
|
open-comment count per page and per breakpoint.
|
|
@@ -34,7 +185,35 @@ It exists only under `astro dev` or `next dev`. Nothing of it reaches a build.
|
|
|
34
185
|
size, rotatable. Both are working surfaces: comments go to the breakpoint the width
|
|
35
186
|
falls in, and remember the device ("iPhone SE · bars folded").
|
|
36
187
|
|
|
37
|
-
|
|
188
|
+
### Using the bar
|
|
189
|
+
|
|
190
|
+
| Control | |
|
|
191
|
+
|---|---|
|
|
192
|
+
| **Pages** | jump to any page. Chips show comment counts per breakpoint: blue open, clay resolved by an agent, green resolved |
|
|
193
|
+
| **Grid** | the layout grid overlay |
|
|
194
|
+
| **Spacing / Size / Type** | inspectors, one at a time. Hold **Alt** to measure the parent |
|
|
195
|
+
| **Sanity** | hides Sanity's visual-editing overlay (only with `sanity: true`) |
|
|
196
|
+
| **Comment** | comment mode: click anything to pin a note. The badge counts open threads |
|
|
197
|
+
| **Panel** | the list of this page's threads at this breakpoint |
|
|
198
|
+
| **Resolved** | shows or hides resolved threads, on the page and in the menu |
|
|
199
|
+
| **Breakpoints** | open the canvas at a band. The widest band is your own window |
|
|
200
|
+
| **Device** | *Preview*: a real screen with its browser. *DevTools*: the canvas at a device's size. Comments work in both |
|
|
201
|
+
| **Tab** on top of the bar | folds the bar below the window edge and back |
|
|
202
|
+
|
|
203
|
+
Keys: **Esc** closes a menu, then the note you are writing, then the open thread, then
|
|
204
|
+
comment mode. **Enter** sends, **Shift+Enter** is a new line. Double-click a canvas
|
|
205
|
+
handle to snap to the band's edge.
|
|
206
|
+
|
|
207
|
+
Everything the bar remembers (modes, canvas width, a half-typed comment) is in
|
|
208
|
+
`localStorage`, per browser.
|
|
209
|
+
|
|
210
|
+
## The bar in your dev server
|
|
211
|
+
|
|
212
|
+
For Astro and Next.js projects the same bar runs under `astro dev` / `next dev` — with no
|
|
213
|
+
account, comments stay in `feedback/comments.json` and screenshots in `feedback/images/`;
|
|
214
|
+
linked, they go to the project. Nothing of it reaches a build.
|
|
215
|
+
|
|
216
|
+
### Install
|
|
38
217
|
|
|
39
218
|
```sh
|
|
40
219
|
npm i -D notemend
|
|
@@ -49,7 +228,7 @@ npx notemend init
|
|
|
49
228
|
`<title>` or `metadata.title`, grouped by folder; dynamic routes are listed as examples
|
|
50
229
|
for you to fill in;
|
|
51
230
|
- **grid** — the container and grid classes the overlay should borrow;
|
|
52
|
-
- **
|
|
231
|
+
- **agents** — which coding agents you use, to wire each (above);
|
|
53
232
|
- **git** — whether to keep pasted screenshots out of it.
|
|
54
233
|
|
|
55
234
|
It writes `notemend.config.mjs`, wires the bar in (Astro: `notemend()` in `astro.config`;
|
|
@@ -63,7 +242,7 @@ After upgrading, stop and start `astro dev`. Astro's own restart (on a config ch
|
|
|
63
242
|
runs in the same Node process, which keeps the old server half of the bar loaded while
|
|
64
243
|
the browser half is already new.
|
|
65
244
|
|
|
66
|
-
|
|
245
|
+
#### Next.js
|
|
67
246
|
|
|
68
247
|
App Router, Next 15.3 or later, Turbopack or webpack. Next has no integrations, so
|
|
69
248
|
`init` writes three small pieces instead:
|
|
@@ -99,171 +278,7 @@ export const config = { matcher: ['/((?!__devbar|_next/static|_next/image|favico
|
|
|
99
278
|
There is no `enabled` switch under Next: to keep the bar out of a Playwright run, add
|
|
100
279
|
your own condition to the `instrumentation-client` line.
|
|
101
280
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Webflow, Tilda, WordPress, Framer, a live Next or Astro site: the cabinet's **Install**
|
|
105
|
-
tab hands out one line for the site's custom code,
|
|
106
|
-
|
|
107
|
-
```html
|
|
108
|
-
<script src="https://app.notemend.com/bar.js" data-project="<project-id>" defer></script>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
and the whole bar — comments, grid, spacing, size and type inspectors, breakpoints,
|
|
112
|
-
devices — runs on the live site. A visitor sees nothing: the bar wakes only for people in
|
|
113
|
-
the project, once they open the site with `?notemend` (the cabinet's "Open site", the
|
|
114
|
-
link you send a client) and sign in in the small window it opens. The cabinet's
|
|
115
|
-
**Preview** tab shows the site at desktop, tablet or mobile width with the bar in it, no
|
|
116
|
-
second sign-in. The pages menu comes from the site's `sitemap.xml` and the pages that
|
|
117
|
-
already have threads.
|
|
118
|
-
|
|
119
|
-
`bar.js` is `src/hosted.ts` bundled by the cabinet's build (`web/scripts/build-bar.mjs`);
|
|
120
|
-
its endpoints are the dev server's, served by the cabinet (`src/hosting.mjs`) as whoever
|
|
121
|
-
is signed in, so the database's rules decide what they may read and write.
|
|
122
|
-
|
|
123
|
-
## With your team
|
|
124
|
-
|
|
125
|
-
On its own the bar files comments into `feedback/comments.json` on your computer. Linked
|
|
126
|
-
to a project in the Notemend cabinet, it files them into the project instead, so everyone
|
|
127
|
-
in it — teammates and clients — sees the same threads, each with its writer's name and
|
|
128
|
-
face, and the cabinet keeps the history:
|
|
129
|
-
|
|
130
|
-
```sh
|
|
131
|
-
npx notemend login # opens the cabinet in your browser — allow, and this computer is signed in
|
|
132
|
-
npx notemend link # a new project named after the site (package.json name and homepage)
|
|
133
|
-
npx notemend link <project-id> # or an existing one — the id is under "Connect" in the cabinet
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
`login --code` signs in with the six-digit code the cabinet emails instead, without a
|
|
137
|
-
browser. The browser way works for every account, Google ones included: the cabinet
|
|
138
|
-
hands the terminal a session of its own, so signing out in one never signs out the other.
|
|
139
|
-
|
|
140
|
-
`link` writes `.notemend.json`; commit it and the whole team files into the same project.
|
|
141
|
-
Claude works the same as before — `npx notemend` lists the open threads and fetches their
|
|
142
|
-
screenshots into `feedback/images/`, `npx notemend done` marks a fix — and a person
|
|
143
|
-
confirms it with the tick, here or in the cabinet, which records who and when.
|
|
144
|
-
`npx notemend whoami` says who you are and where comments go; `unlink` and `logout` undo.
|
|
145
|
-
|
|
146
|
-
## Using the bar
|
|
147
|
-
|
|
148
|
-
| Control | |
|
|
149
|
-
|---|---|
|
|
150
|
-
| **Pages** | jump to any page. Chips show comment counts per breakpoint: blue open, clay resolved by Claude, green resolved |
|
|
151
|
-
| **Grid** | the layout grid overlay |
|
|
152
|
-
| **Spacing / Size / Type** | inspectors, one at a time. Hold **Alt** to measure the parent |
|
|
153
|
-
| **Sanity** | hides Sanity's visual-editing overlay (only with `sanity: true`) |
|
|
154
|
-
| **Comment** | comment mode: click anything to pin a note. The badge counts open threads |
|
|
155
|
-
| **Panel** | the list of this page's threads at this breakpoint |
|
|
156
|
-
| **Resolved** | shows or hides resolved threads, on the page and in the menu |
|
|
157
|
-
| **Breakpoints** | open the canvas at a band. The widest band is your own window |
|
|
158
|
-
| **Device** | *Preview*: a real screen with its browser. *DevTools*: the canvas at a device's size. Comments work in both |
|
|
159
|
-
| **Tab** on top of the bar | folds the bar below the window edge and back |
|
|
160
|
-
|
|
161
|
-
Keys: **Esc** closes a menu, then the note you are writing, then the open thread, then
|
|
162
|
-
comment mode. **Enter** sends, **Shift+Enter** is a new line. Double-click a canvas
|
|
163
|
-
handle to snap to the band's edge.
|
|
164
|
-
|
|
165
|
-
Everything the bar remembers (modes, canvas width, a half-typed comment) is in
|
|
166
|
-
`localStorage`, per browser.
|
|
167
|
-
|
|
168
|
-
## Comments and your coding agent
|
|
169
|
-
|
|
170
|
-
Threads are stored in `feedback/comments.json`, screenshots in `feedback/images/`, both
|
|
171
|
-
at the project root. The CLI reads and answers them:
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
npx notemend open comments, newest last
|
|
175
|
-
npx notemend all including resolved
|
|
176
|
-
npx notemend start <id> take it before fixing: the thread shows the agent is on it
|
|
177
|
-
npx notemend reply <id> "..." answer in the thread as Claude
|
|
178
|
-
npx notemend done <id> "..." resolve as Claude + note (stays marked until you confirm)
|
|
179
|
-
npx notemend note <id> "..." reply without changing status
|
|
180
|
-
npx notemend reopen <id>
|
|
181
|
-
npx notemend rm <id> delete the thread and its screenshots
|
|
182
|
-
npx notemend prune [--dry] move resolved threads to feedback/archive/
|
|
183
|
-
npx notemend digest compact open list (SessionStart hook)
|
|
184
|
-
npx notemend count one line (UserPromptSubmit hook)
|
|
185
|
-
npx notemend init [--yes] set a project up
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
The root is the nearest directory with a `package.json` above the working directory, or
|
|
189
|
-
`--root <dir>`.
|
|
190
|
-
|
|
191
|
-
A thread Claude resolves is marked in clay until you confirm it with the tick, so "done"
|
|
192
|
-
and "checked" stay apart. The panel numbers threads per page and breakpoint (`#4`); the
|
|
193
|
-
CLI prints both that and the file-wide id: `#4 (id 10)`.
|
|
194
|
-
|
|
195
|
-
`prune` keeps the store readable: resolved threads and their screenshots move to
|
|
196
|
-
`feedback/archive/`, and the next comment carries on the numbering. Nothing is deleted.
|
|
197
|
-
|
|
198
|
-
The Claude Code hooks `init` adds to `.claude/settings.json` (both print nothing while the
|
|
199
|
-
queue is empty, and nothing at all where the package is not installed):
|
|
200
|
-
|
|
201
|
-
```json
|
|
202
|
-
"SessionStart": [{ "hooks": [{ "type": "command",
|
|
203
|
-
"command": "if [ -f \"$CLAUDE_PROJECT_DIR/node_modules/notemend/bin/notemend.mjs\" ]; then node \"$CLAUDE_PROJECT_DIR/node_modules/notemend/bin/notemend.mjs\" --root \"$CLAUDE_PROJECT_DIR\" digest; fi" }] }],
|
|
204
|
-
"UserPromptSubmit": [{ "hooks": [{ "type": "command",
|
|
205
|
-
"command": "if [ -f \"$CLAUDE_PROJECT_DIR/node_modules/notemend/bin/notemend.mjs\" ]; then node \"$CLAUDE_PROJECT_DIR/node_modules/notemend/bin/notemend.mjs\" --root \"$CLAUDE_PROJECT_DIR\" count; fi" }] }]
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Other coding agents
|
|
209
|
-
|
|
210
|
-
The queue is plain CLI, so any agent that runs commands can work it. `init` asks which
|
|
211
|
-
ones you use and wires each the way it reads context:
|
|
212
|
-
|
|
213
|
-
| Agent | What `init` writes | What it reads |
|
|
214
|
-
|---|---|---|
|
|
215
|
-
| Claude Code | `.claude/settings.json` hooks | `digest` at session start, `count` on each prompt |
|
|
216
|
-
| Codex | `.codex/hooks.json` (+ `codex_hooks = true` in `.codex/config.toml`) | the same, as plain text |
|
|
217
|
-
| Gemini CLI | `.gemini/settings.json` hooks, `GEMINI.md` | the same, as `hookSpecificOutput.additionalContext` JSON |
|
|
218
|
-
| Cursor | `.cursor/hooks.json` (`sessionStart`) | `digest` as `additional_context` |
|
|
219
|
-
| Copilot, Windsurf, Amp, … | a block in `AGENTS.md` | how to list, answer and close threads |
|
|
220
|
-
|
|
221
|
-
Before and after: with Playwright in the project, `npx notemend shot 3 --before` files a
|
|
222
|
-
picture of the element at the comment's width, and `npx notemend done 3 "…" --shot` files
|
|
223
|
-
one after the fix (`--url` points at the dev server; localhost:4321, or :3000 for Next).
|
|
224
|
-
|
|
225
|
-
### An agent that works on its own
|
|
226
|
-
|
|
227
|
-
A project's settings make agent keys (shown once, kept only as a hash). With one as
|
|
228
|
-
`NOTEMEND_KEY`, the CLI needs no sign-in: it lists the project's threads and answers and
|
|
229
|
-
closes them as the agent — nothing else. `npx notemend init --ci` writes
|
|
230
|
-
`.github/workflows/notemend.yml`: every hour (or by hand) it asks for the agent's threads
|
|
231
|
-
and, when there are some, runs Claude Code's GitHub Action to fix them on a branch, close
|
|
232
|
-
each with what it changed, and open a pull request. It needs the repository secrets
|
|
233
|
-
`NOTEMEND_KEY` and `ANTHROPIC_API_KEY`, and `.notemend.json` committed. A person still
|
|
234
|
-
merges, and confirms each fix on the live site.
|
|
235
|
-
|
|
236
|
-
`npx notemend init --agents=claude,codex,cursor` skips the question. An agent signs what
|
|
237
|
-
it writes with `--as`: `npx notemend reply 3 "…" --as codex` — threads in the bar and the
|
|
238
|
-
cabinet then show Codex rather than Claude. Gemini CLI and Codex are recognised from their
|
|
239
|
-
environment without the flag; anything unnamed is Claude, as before.
|
|
240
|
-
|
|
241
|
-
### MCP
|
|
242
|
-
|
|
243
|
-
`npx notemend mcp` serves the same queue as an MCP server over stdio, for agents that
|
|
244
|
-
take tools rather than a shell: `list_comments`, `reply`, `resolve`, `reopen` and
|
|
245
|
-
`screenshot`. Each runs the CLI above, so the cloud, agent keys and sign-in work as they
|
|
246
|
-
do there; replies are signed by the client's own name (Cursor, Codex…) unless `--as`
|
|
247
|
-
says otherwise. Run it from the project, or pass `--root <dir>`.
|
|
248
|
-
|
|
249
|
-
One click: Add to Cursor, Add to VS Code and the Claude Desktop bundle are on
|
|
250
|
-
https://app.notemend.com/docs/mcp. Claude Code takes the plugin — the hooks and the tools
|
|
251
|
-
in one — with `/plugin marketplace add https://app.notemend.com/claude/marketplace.json`
|
|
252
|
-
and `/plugin install notemend@notemend` (its files are in `plugin/`; `npm run pack` puts
|
|
253
|
-
them at the package root). `npm run mcpb` rebuilds `web/public/notemend.mcpb` after a pack.
|
|
254
|
-
|
|
255
|
-
```sh
|
|
256
|
-
claude mcp add notemend -- npx notemend mcp # Claude Code (the hooks above already cover it)
|
|
257
|
-
codex mcp add notemend -- npx notemend mcp # Codex
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Cursor, `.cursor/mcp.json`; Claude Desktop and Zed take the same command:
|
|
261
|
-
|
|
262
|
-
```json
|
|
263
|
-
{ "mcpServers": { "notemend": { "command": "npx", "args": ["notemend", "mcp", "--root", "/path/to/site"] } } }
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
## Configuration
|
|
281
|
+
### Configuration
|
|
267
282
|
|
|
268
283
|
Everything lives in `notemend.config.mjs` at the project root — named exports or one
|
|
269
284
|
default object. It is re-read on every page load: edit, refresh, no restart.
|
|
@@ -308,7 +323,7 @@ Under Astro the same keys can be passed to `notemend({ … })` in `astro.config`
|
|
|
308
323
|
win over the file. `enabled` only works there: `notemend({ enabled: process.env.CI !== 'true' })` keeps
|
|
309
324
|
the bar out of, say, a Playwright run.
|
|
310
325
|
|
|
311
|
-
|
|
326
|
+
#### A private install
|
|
312
327
|
|
|
313
328
|
Installed from a repo the build machine cannot reach (an `optionalDependency` on a
|
|
314
329
|
private GitHub repo), import it behind a catch so a missing package never breaks the
|
|
@@ -332,7 +347,7 @@ export default defineConfig({ integrations: [...(notemend ? [notemend()] : [])]
|
|
|
332
347
|
- The bar stays above the page's modal `<dialog>`s: while one is open, the bar moves into
|
|
333
348
|
a popover host inside it (top layer, not inert) and back out when it closes.
|
|
334
349
|
- Writes to `/__devbar/comments` from another origin are refused — any site open in the
|
|
335
|
-
same browser could otherwise post a "comment"
|
|
350
|
+
same browser could otherwise post a "comment" an agent would read as a task.
|
|
336
351
|
- The bar is sized in px on purpose: a site with a fluid root font-size would otherwise
|
|
337
352
|
scale the dev UI along with the design.
|
|
338
353
|
- A preview is an iframe, and some things only a real device or DevTools emulation shows:
|
package/bin/notemend.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import{existsSync as P,readFileSync as v}from"node:fs";import{mkdtemp as K,rm as
|
|
|
11
11
|
notemend start <id> [--force] take a comment before fixing it: the thread shows the
|
|
12
12
|
agent is on it, and another agent is refused
|
|
13
13
|
notemend reply <id> "text" answer in the thread (also: note)
|
|
14
|
-
notemend done <id> ["note"] resolve it as
|
|
14
|
+
notemend done <id> ["note"] resolve it as the agent, with an optional note
|
|
15
15
|
notemend reopen <id> | rm <id>
|
|
16
16
|
notemend prune [--dry] move resolved threads to feedback/archive/
|
|
17
17
|
notemend shot <id> [--before] screenshot the element into the thread (Playwright);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "notemend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"mcpName": "io.github.zharkodv-cmd/notemend",
|
|
5
5
|
"description": "A dev-only toolbar for Astro and Next.js sites: pinned page comments Claude Code can read and answer, spacing/size/type inspectors in design px, a layout grid, breakpoint canvas and device preview.",
|
|
6
6
|
"keywords": [
|
package/src/client/run.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{categoryOf as oe}from"../store/categories.mjs";import{stateOf as O,STATE_LABEL as hs}from"../store/status.mjs";import{agentName as dt,handleOf as bs,threadOf as ys,workingOn as vs}from"../store/thread.mjs";import{api as U,canvasCopy as $s,host as Es,imageUrl as ws}from"./api";import{DEVTOOLS_DEVICES as fo}from"../devtools-devices.mjs";import{drawDevice as Ss,pageTints as Ls,inkFor as ho}from"./devices";import{recentLogs as xs}from"./logs";function Rs(M){const B=M.breakpoints,Xe=M.categories,se=(B.find(t=>!Number.isFinite(t.max))??B[0]).id,Et=t=>B.find(e=>t>=e.min&&t<=e.max)?.id??se,ct=t=>t.breakpoint||se,Dt=t=>M.devices.find(e=>e.id===t),$=$s,z=(t,e)=>{$||localStorage.setItem(t,e)},re=t=>t.replace(/\/+$/,"")||"/";let P=re(location.pathname);for(const t of document.querySelectorAll(".devtools .dt-icon[title]"))t.dataset.tip=t.title,t.getAttribute("aria-label")||t.setAttribute("aria-label",t.title),t.removeAttribute("title");const Ze=document.querySelector(".dt-grid-btn"),Ot=document.querySelector(".dt-grid-overlay"),Qe="dt-grid",ae=t=>{Ot.hidden=!t,Ze.setAttribute("aria-pressed",String(t)),z(Qe,t?"1":"0"),q({grid:t})};Ze.addEventListener("click",()=>ae(Ot.hidden===!0));const tn=document.querySelector(".dt-pages-name"),en=()=>{let t=null;for(const o of document.querySelectorAll(".dt-pages a"))re(new URL(o.href).pathname)===P?(o.setAttribute("aria-current","page"),t=o):o.removeAttribute("aria-current");const e=document.title.split("\u2014").pop()?.trim(),n=t?.querySelector(".dt-menu-label")?.textContent?.trim();tn.textContent=n||e||tn.textContent};en();const nn=[...document.querySelectorAll(".devtools details")];let ie=!1;const wt=()=>{const t=nn.filter(e=>e.open);for(const e of t)e.open=!1;return t};document.addEventListener("keydown",t=>{if(t.key!=="Escape")return;const[e]=wt();e&&(e.querySelector("summary")?.focus(),t.preventDefault(),t.stopImmediatePropagation())},!0),document.addEventListener("pointerdown",t=>{ie=nn.some(e=>e.open&&!e.contains(t.target))&&wt().length>0},!0),addEventListener("blur",wt);const Z=document.querySelector(".dt-pad-overlay"),on="dt-inspect",bo=[".devtools",".dt-pad-overlay",".dt-grid-overlay",".dt-frame",".dt-history",".dt-review",...M.ignore].join(", "),le=[".devtools",".dt-notes-overlay",".dt-history",".dt-frame",".dt-review",...M.ignore].join(", "),de={pads:document.querySelector(".dt-pad-btn"),sizes:document.querySelector(".dt-size-btn"),type:document.querySelector(".dt-type-btn")};let ut="",ce=0;const k=t=>{const e=parseFloat(getComputedStyle(document.documentElement).fontSize)/16,n=t/e;return Math.round(n*10)%10===0?String(Math.round(n)):n.toFixed(1)},_=(t,e,n,o,s,r="p")=>{if(n<=0||o<=0)return"";const c=s!==""?`<span class="dt-pad-chip dt-pad-chip--${r}" style="left:${t+n/2}px;top:${e+o/2}px">${s}</span>`:"";return`<span class="dt-pad-zone dt-pad-zone--${r}" style="left:${t}px;top:${e}px;width:${n}px;height:${o}px"></span>${c}`},yo=["paddingTop","paddingRight","paddingBottom","paddingLeft","marginTop","marginRight","marginBottom","marginLeft","columnGap","rowGap"],vo=t=>{let e=t;for(let n=0;e&&n<6;n++,e=e.parentElement){const o=getComputedStyle(e);if(yo.some(s=>parseFloat(o[s])>=2))return e}return t},$o=(t,e)=>{const n=parseFloat(e.columnGap)||0,o=parseFloat(e.rowGap)||0;if(!(e.display.includes("flex")||e.display.includes("grid"))||!n&&!o)return"";const s=[...t.children].map(c=>c.getBoundingClientRect()).filter(c=>c.width||c.height);let r="";for(let c=1;c<s.length;c++){const d=s[c-1],a=s[c];if(n&&a.left>=d.right-1){const l=Math.max(d.top,a.top);r+=_(d.right,l,a.left-d.right,Math.min(d.bottom,a.bottom)-l,k(n),"g")}else if(o&&a.top>=d.bottom-1){const l=Math.min(d.left,a.left);r+=_(l,d.bottom,Math.max(d.right,a.right)-l,a.top-d.bottom,k(o),"g")}}return r},ue=t=>`<span class="dt-pad-outline" style="left:${t.left}px;top:${t.top}px;width:${t.width}px;height:${t.height}px"></span>`,me=(t,e,n="")=>`<span class="dt-pad-tag" style="left:${e.left}px;top:${Math.max(2,e.top-22)}px">${t.tagName.toLowerCase()+(t.classList[0]?"."+t.classList[0]:"")}${n}</span>`,Eo=t=>{const e=t.getBoundingClientRect(),n=getComputedStyle(t),o=parseFloat(n.paddingTop),s=parseFloat(n.paddingRight),r=parseFloat(n.paddingBottom),c=parseFloat(n.paddingLeft);let d=ue(e);d+=_(e.left,e.top,e.width,o,o>=2?k(o):""),d+=_(e.left,e.bottom-r,e.width,r,r>=2?k(r):""),d+=_(e.left,e.top+o,c,e.height-o-r,c>=2?k(c):""),d+=_(e.right-s,e.top+o,s,e.height-o-r,s>=2?k(s):"");const a=parseFloat(n.marginTop),l=parseFloat(n.marginRight),m=parseFloat(n.marginBottom),b=parseFloat(n.marginLeft);d+=_(e.left,e.top-a,e.width,a,a>=2?k(a):"","m"),d+=_(e.left,e.bottom,e.width,m,m>=2?k(m):"","m"),d+=_(e.left-b,e.top,b,e.height,b>=2?k(b):"","m"),d+=_(e.right,e.top,l,e.height,l>=2?k(l):"","m"),d+=$o(t,n);const i=parseFloat(n.columnGap)||0,v=parseFloat(n.rowGap)||0,x=(n.display.includes("flex")||n.display.includes("grid"))&&(i||v)?` \xB7 gap ${k(i)}${v&&v!==i?"/"+k(v):""}`:"";Z.innerHTML=d+me(t,e,x)},wo=t=>{const e=t.getBoundingClientRect();Z.innerHTML=ue(e)+`<span class="dt-pad-chip" style="left:${e.left+e.width/2}px;top:${e.top+e.height/2}px">${k(e.width)} \xD7 ${k(e.height)}</span>`+me(t,e)},sn=t=>{const e=(t.match(/[\d.]+/g)||[]).map(Number);if(e.length<3)return t;const n="#"+e.slice(0,3).map(o=>Math.round(o).toString(16).padStart(2,"0")).join("");return e[3]!==void 0&&e[3]<1?`${n} \xB7 ${Math.round(e[3]*100)}%`:n};let pe=null;const So=()=>{if(pe)return pe;const t=getComputedStyle(document.documentElement),e=document.createElement("span");e.style.display="none",document.body.append(e);const n=[];for(const o of Array.from(t)){if(!o.startsWith("--"))continue;const s=t.getPropertyValue(o).trim();if(!/^(#|rgb|hsl|oklch|lab|color\()/i.test(s))continue;e.style.color="rgb(1, 2, 3)",e.style.color=s;const r=getComputedStyle(e).color;r&&r!=="rgb(1, 2, 3)"&&n.push([r,o])}return e.remove(),n.sort((o,s)=>o[1].length-s[1].length),pe=n,n},Lo=t=>So().find(([e])=>e===t)?.[1]??"",xo={100:"Thin",200:"Extra Light",300:"Light",400:"Regular",500:"Medium",600:"Semi Bold",700:"Bold",800:"Extra Bold",900:"Black"},rn=t=>(t.match(/[\d.]+/g)??[]).map(Number).concat([1]).slice(0,4),an=(t,e)=>t.slice(0,3).map((n,o)=>n*t[3]+e[o]*(1-t[3])).concat([1]),To=t=>{const e=[];for(let n=t;n;n=n.parentElement){const o=getComputedStyle(n);if(o.backgroundImage!=="none")return null;const s=rn(o.backgroundColor);if(s[3]>0&&(e.push(s),s[3]>=1))break}return e.reduceRight((n,o)=>an(o,n),[255,255,255,1])},ln=t=>{const[e,n,o]=t.slice(0,3).map(s=>(s/=255,s<=.03928?s/12.92:((s+.055)/1.055)**2.4));return .2126*e+.7152*n+.0722*o},Mo=(t,e,n)=>{const o=To(t);if(!o)return["Contrast","<em>on an image</em>"];const s=an(rn(e.color),o),[r,c]=[ln(s),ln(o)].sort((m,b)=>b-m),d=(r+.05)/(c+.05),a=n>=24||n>=18.66&&Number(e.fontWeight)>=700,l=d>=(a?4.5:7)?"AAA":d>=(a?3:4.5)?"AA":"fails AA";return["Contrast",`${d.toFixed(2)}:1 <em class="${l==="fails AA"?"dt-type-bad":""}">${l}${a?" \xB7 large":""}</em>`]},ko=t=>{const e=t.getBoundingClientRect(),n=getComputedStyle(t),o=parseFloat(n.fontSize),s=n.lineHeight==="normal"?0:parseFloat(n.lineHeight),r=parseFloat(n.letterSpacing)||0,c=Lo(n.color),d=i=>`${(i*100).toFixed(1).replace(/\.0$/,"")}%`,a=[["Font",n.fontFamily.split(",")[0].replace(/["']/g,"")+(n.fontStyle!=="normal"?` ${n.fontStyle}`:"")],["Weight",`${xo[n.fontWeight]??""} <em>${n.fontWeight}</em>`],["Size",`${k(o)}px`],["Line",s?`${d(s/o)} <em>${k(s)}px</em>`:"normal"],["Letter",r?`${d(r/o)} <em>${r.toFixed(2).replace(/\.?0+$/,"")}px</em>`:"0"],["Color",`<i class="dt-type-sw" style="background:${n.color}"></i>${c?`${c} <em>${sn(n.color)}</em>`:sn(n.color)}`],Mo(t,n,o)],m=e.bottom+150<innerHeight?e.bottom+8:Math.max(8,e.top-158),b=Math.min(Math.max(8,e.left),innerWidth-210);Z.innerHTML=ue(e)+me(t,e)+`<div class="dt-type-card" style="left:${b}px;top:${m}px">${a.map(([i,v])=>`<span class="dt-type-row"><b>${i}</b><span>${v}</span></span>`).join("")}</div>`},dn=t=>{ce||(ce=requestAnimationFrame(()=>{ce=0,Z.hidden=!0;let e=document.elementFromPoint(t.clientX,t.clientY);if(Z.hidden=!1,!e||e.closest(bo)){Z.innerHTML="";return}t.altKey&&e.parentElement&&e.parentElement!==document.body&&(e=e.parentElement),ut==="sizes"?wo(e):ut==="type"?ko(e):Eo(t.altKey?e:vo(e))}))},Pt=t=>{ut=t;for(const[e,n]of Object.entries(de))n.setAttribute("aria-pressed",String(e===t));z(on,t),q({inspect:t}),Z.hidden=!t,Z.innerHTML="",t?document.addEventListener("mousemove",dn):document.removeEventListener("mousemove",dn)};for(const[t,e]of Object.entries(de))e.addEventListener("click",()=>{t!==ut&&rt(!1),Pt(ut===t?"":t)});const cn=document.querySelector(".dt-notes-btn"),ge=[...document.querySelectorAll(".dt-bp")],Ft=document.querySelector(".dt-history-btn"),D=document.querySelector(".dt-history"),fe=document.querySelector(".dt-studio-btn"),w=document.querySelector(".dt-device"),un=document.querySelector(".dt-shell"),St=document.querySelector('.dt-chrome[data-at="top"]'),jt=document.querySelector('.dt-chrome[data-at="bottom"]'),Lt=document.querySelector(".dt-ruler"),G=document.querySelector(".dt-devices"),mt=document.querySelector(".dt-notes-hi"),Ho=mt.querySelector(".dt-hi-box"),mn=mt.querySelector(".dt-hi-tag"),Ao=[...document.querySelectorAll(".dt-frame-handle")],pn=document.querySelector(".dt-frame-size"),he=document.querySelector(".dt-frame-rotate"),xt=document.querySelector(".dt-frame-bars"),C=document.querySelector(".dt-frame"),S=C.querySelector("iframe"),be=document.querySelector(".dt-notes-count"),f=document.querySelector(".dt-notes-overlay"),Co=".devtools, .dt-tab, .dt-notes-overlay, .dt-history, .dt-frame, .dt-review";for(const t of document.querySelectorAll(Co))for(const e of["pointerdown","mousedown","pointerup","mouseup","touchstart","touchend","click","focusin","focusout"])t.addEventListener(e,n=>n.stopPropagation());const gn="dt-notes";let h=[],H=!1,y=null,u=null,ye=0,F="",Q=[],L=null,R=[];const Kt=(t=u)=>!!(t&&(t.text?.trim()||t.images?.length)),j=()=>{u&&Kt()&&R.push(u),u=null};let Tt=null,J=null;const fn=()=>`dt-draft:${P}${$?":canvas":""}`;let ft=fn(),Wt=0;const ve=()=>{if(!u&&!R.length&&!F){localStorage.removeItem(ft);return}const t={draft:u,parked:R,reply:F?{for:Tt,text:F}:null};try{localStorage.setItem(ft,JSON.stringify(t))}catch{const e=n=>({...n,images:void 0});try{localStorage.setItem(ft,JSON.stringify({...t,draft:u&&e(u),parked:R.map(e)}))}catch{}}},zt=()=>{clearTimeout(Wt),Wt=setTimeout(ve,200)};addEventListener("pagehide",()=>{clearTimeout(Wt),ve()});let tt=localStorage.getItem("dt-resolved")!=="0";const No=[[/Edg\/(\d+)/,"Edge"],[/OPR\/(\d+)/,"Opera"],[/Firefox\/(\d+)/,"Firefox"],[/Chrome\/(\d+)/,"Chrome"],[/Version\/(\d+).*Safari/,"Safari"]],Bo=[[/iPhone|iPad/,"iOS"],[/Android/,"Android"],[/Mac OS X/,"macOS"],[/Windows/,"Windows"],[/Linux/,"Linux"]],Ro=()=>{const t=navigator.userAgent,e=r=>r.find(([c])=>c.test(t)),n=e(No),o=e(Bo),s=n?n[0].exec(t)?.[1]:null;return[n?`${n[1]}${s?" "+s:""}`:"unknown browser",o?.[1]].filter(Boolean).join(" \xB7 ")},et=()=>Et(innerWidth),_t=()=>J??et(),Mt=()=>{const t=_t();for(const e of ge)e.setAttribute("aria-pressed",String(e.dataset.bp===t))},qo=t=>{const e=[];let n=t;for(;n&&n.tagName!=="HTML"&&e.length<8;){if(n.id){e.unshift("#"+CSS.escape(n.id));break}const o=n.parentElement,s=n.tagName.toLowerCase(),r=o?[...o.children].filter(c=>c.tagName===n.tagName):[];e.unshift(r.length>1?`${s}:nth-of-type(${r.indexOf(n)+1})`:s),n=o}return e.join(">")},$e=(t,e)=>!e||K(t.textContent||"").slice(0,60)===e,Io=(t,e)=>[t||"*",...(e||[]).map(n=>"."+CSS.escape(n))].join(""),hn=t=>{try{return[...document.querySelectorAll(Io(t.tag,t.classes))]}catch{return[]}},bn=t=>{const e=K(t.textContent||"").replace(/\s+/g," ");for(let n=t.parentElement,o=0;n&&n!==document.body&&o<4;n=n.parentElement,o++){const s=K(n.textContent||"").replace(/\s+/g," ");if(s&&s!==e)return s}return""},Do=t=>bn(t).slice(0,80),Ee=(t,e)=>!e||bn(t).startsWith(e),Y=t=>{let e=null;try{e=document.querySelector(t.selector)}catch{e=null}if(e&&$e(e,t.label)&&Ee(e,t.ctx))return e;const n=hn(t),o=K(t.label)?n.filter(r=>$e(r,t.label)):[];return(o.find(r=>Ee(r,t.ctx))??n.find(r=>t.ctx&&Ee(r,t.ctx)&&!K(t.label)))||o[0]||(e&&$e(e,t.label)?e:null)||(t.nth!=null?n[t.nth]:null)||n[0]||e},Oo=t=>{let e=null;for(let n=t.parentElement;n&&n!==document.body;n=n.parentElement){const o=getComputedStyle(n);if(o.overflowX==="visible"&&o.overflowY==="visible")continue;const s=n.getBoundingClientRect();e=e?{l:Math.max(e.l,s.left),t:Math.max(e.t,s.top),r:Math.min(e.r,s.right),b:Math.min(e.b,s.bottom)}:{l:s.left,t:s.top,r:s.right,b:s.bottom}}return e},V=t=>{const e=Y(t);if(!e)return null;const n=e.getBoundingClientRect();if(!n.width&&!n.height)return null;const o=n.left+n.width*t.rx,s=n.top+n.height*t.ry,r=Oo(e);if(!r)return{x:o,y:s,off:""};if(r.r-r.l<18||r.b-r.t<18)return null;const c=Math.min(Math.max(o,r.l),r.r-18),d=Math.min(Math.max(s,r.t+18),r.b),a=o<r.l?"l":o>r.r-18?"r":s<r.t+18?"t":s>r.b?"b":"";return{x:c,y:d,off:a}},yn=t=>{for(let e=t;e&&e!==document.body;e=e.parentElement)if(e.getAnimations().some(n=>n.playState==="running"))return!0;return!1};let Gt=[];const we=t=>{for(const e of Gt)e.play();Gt=[];for(let e=t;e&&e!==document.body;e=e.parentElement)for(const n of e.getAnimations())n.playState==="running"&&(n.pause(),Gt.push(n))},vn=()=>{if(u)return Y(u);const t=y!=null?h.find(n=>n.id===y):null;if(!t)return null;const e=V(t);return e&&!e.off?Y(t):null},$n=t=>t?new Date(t).toLocaleString(void 0,{day:"numeric",month:"short",hour:"2-digit",minute:"2-digit"}):"",Yt='<svg viewBox="0 0 20 20" width="13" height="13" aria-hidden="true"><path d="M4 6h12M8 6V4.2h4V6M6.6 6l.55 9.8h5.7L13.4 6"/></svg>',Se='<svg viewBox="0 0 20 20" width="13" height="13" aria-hidden="true"><path d="M4.8 10.4 8.2 13.8l7-7.6" stroke-linejoin="round"/></svg>',Le='<svg viewBox="0 0 20 20" width="12" height="12" aria-hidden="true"><path d="M12.6 4.4l3 3-8.4 8.4H4.2v-3z" stroke-linejoin="round"/></svg>',Vt='<svg viewBox="0 0 20 20" width="13" height="13" aria-hidden="true"><path d="M5.4 5.4l9.2 9.2M14.6 5.4l-9.2 9.2"/></svg>',kt=t=>`<svg viewBox="0 0 24 24" width="${t}" height="${t}" aria-hidden="true" class="dt-claude"><path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"/></svg>`,En=()=>{try{return localStorage.getItem("nm-for")??"claude"}catch{return"claude"}},wn=t=>t==="claude"?"#d97757":t?"#0d99ff":"#717171",Sn=(t,e)=>`<label class="dt-pill" style="--cat:${wn(t)}"><select class="dt-for-select"${e?` data-id="${e}"`:""} aria-label="Who this is for">
|
|
2
|
-
<option value="claude"${t==="claude"?" selected":""}>
|
|
2
|
+
<option value="claude"${t==="claude"?" selected":""}>AI agent</option>
|
|
3
3
|
<option value=""${t?"":" selected"}>Anyone</option>
|
|
4
4
|
${(M.team??[]).filter(n=>n.email!==M.me?.email).map(n=>`<option value="${g(n.email)}"${t===n.email?" selected":""}>${g(n.name||n.email)}</option>`).join("")}
|
|
5
5
|
</select></label>`,Ln=t=>{const e=K(t?.textContent).replace(/\s+/g," ");return e&&e.length<=2e3?e:""},Po=t=>`<div class="dt-suggest dt-suggest--read"><del>${g(t.from)}</del><ins>${g(t.to)}</ins></div>`,K=t=>(t??"").replace(/[\u200B-\u200D\uFEFF\u2060]/g,"").trim(),xn=t=>K(t)?`<span class="dt-note-info" data-tip="${g(K(t))}"
|