stikfix 1.2.0
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/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/host/src/bind.js +69 -0
- package/dist/host/src/bootstrap/register.js +556 -0
- package/dist/host/src/config.js +147 -0
- package/dist/host/src/extension-id.js +49 -0
- package/dist/host/src/folder-picker.js +142 -0
- package/dist/host/src/index.js +110 -0
- package/dist/host/src/native-host.js +143 -0
- package/dist/host/src/native-msg.js +100 -0
- package/dist/host/src/probe.js +62 -0
- package/dist/host/src/read-note.js +175 -0
- package/dist/host/src/security.js +76 -0
- package/dist/host/src/serial.js +32 -0
- package/dist/host/src/server.js +451 -0
- package/dist/host/src/types.js +5 -0
- package/dist/host/src/validate-folder.js +68 -0
- package/dist/host/src/write-note.js +158 -0
- package/dist/host/stikfix-init.cjs +500 -0
- package/dist/host/stikfix-native.cjs +257 -0
- package/package.json +66 -0
- package/public/icon/128.png +0 -0
- package/public/icon/16.png +0 -0
- package/public/icon/256.png +0 -0
- package/public/icon/32.png +0 -0
- package/public/icon/48.png +0 -0
- package/public/icon/stikfix.ico +0 -0
- package/public/icon/stikfix.svg +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Omer Nesher
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="public/icon/128.png" alt="stikfix" width="96" height="96" />
|
|
4
|
+
|
|
5
|
+
# stikfix
|
|
6
|
+
|
|
7
|
+
**Stick a note on any web page. Your AI coding agent reads it and fixes it. That's the whole loop.**
|
|
8
|
+
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](https://www.npmjs.com/package/stikfix)
|
|
11
|
+
[](#load-the-extension)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The problem you already know too well
|
|
18
|
+
|
|
19
|
+
You spot something off in your UI — a button that's 4px too low, a heading that wraps weird, a modal that scrolls when it shouldn't. So you screenshot it. Paste it into a chat. Type a paragraph explaining *which* button, on *which* page, and what "right" looks like. Your agent guesses. You screenshot again. Round and round.
|
|
20
|
+
|
|
21
|
+
That ping-pong is slow, lossy, and maddening — and it throws away everything the browser already knows about the element you're pointing at.
|
|
22
|
+
|
|
23
|
+
**stikfix kills the ping-pong.** You drop a note *directly on the thing*, and it lands on disk as a precise, context-rich markdown file in your project's `notes/` folder — which your AI agent can just read, fix, and mark done. A durable, file-based review loop instead of ephemeral chat.
|
|
24
|
+
|
|
25
|
+
No cloud. No accounts. No sign-up. Everything stays on `127.0.0.1` — **your code and your notes never leave your machine.**
|
|
26
|
+
|
|
27
|
+
## How it feels to use
|
|
28
|
+
|
|
29
|
+
1. Click **Enter Review Mode** on any page.
|
|
30
|
+
2. Drop a **sticky note** — free-floating, or click an element to anchor it. Anchored notes auto-capture the CSS selector, computed styles, `outerHTML`, the bounding box, an auto-highlighted screenshot, and the React component name. No describing required.
|
|
31
|
+
3. Tell your agent **"read my notes."** It reads the fresh `.md` files, makes the fixes, and **writes a reply back into each note** — so the pin turns **green ✓** right on the page with a one-line "here's what I changed" (no fix is lost in a chat log). Anything ambiguous turns **amber** with the agent's clarifying question.
|
|
32
|
+
4. Glance at the pins (or open the **notes panel** to filter/search/jump), drop a few more, repeat. Pins update live as the agent works — no refresh. Your UI gets tighter every loop.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
### 1. Install the host (one command)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx stikfix init --root /path/to/your/project
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This turnkey installer:
|
|
43
|
+
|
|
44
|
+
- registers the secure native-messaging host for **Chrome and Edge** (user-level, no admin rights),
|
|
45
|
+
- writes a small config pointing at your project,
|
|
46
|
+
- creates a **"Stikfix Host" launcher on your Desktop** so you can start the backend with a double-click — no terminal babysitting,
|
|
47
|
+
- prints your stable **extension ID** (you never copy-paste a token).
|
|
48
|
+
|
|
49
|
+
> **Keep it current:** re-run `npx --yes stikfix@latest init --root /path/to/your/project` anytime to update the host.
|
|
50
|
+
|
|
51
|
+
### 2. Load the extension
|
|
52
|
+
|
|
53
|
+
For v1.0 the extension is loaded **unpacked** (a Chrome Web Store listing is coming next):
|
|
54
|
+
|
|
55
|
+
1. Open `chrome://extensions` (or `edge://extensions`).
|
|
56
|
+
2. Turn on **Developer mode**.
|
|
57
|
+
3. Click **Load unpacked** and pick the **`.output/chrome-mv3/`** folder from this repo.
|
|
58
|
+
|
|
59
|
+
The loaded ID matches what the installer printed — nothing to type. (Building `.output/chrome-mv3/` from source is covered under [Development](#development).)
|
|
60
|
+
|
|
61
|
+
### 3. Start the backend
|
|
62
|
+
|
|
63
|
+
Double-click the **Stikfix Host** launcher on your Desktop. That's it — no commands, and double-clicking again is safe (it won't launch a second copy). The host listens on a port in the `39240–39260` range; the extension discovers it automatically.
|
|
64
|
+
|
|
65
|
+
### 4. Pair in one click
|
|
66
|
+
|
|
67
|
+
Open the stikfix popup in your toolbar and hit **Pair with host**. The token is handed over automatically through the OS native-messaging channel — you never see it, never paste it. Pair once and it stays paired, even after restarts.
|
|
68
|
+
|
|
69
|
+
### 5. Drop a note
|
|
70
|
+
|
|
71
|
+
Open your app, click **Enter Review Mode**, and start dropping notes. The first note on a new site opens an **OS folder picker** so you can choose where that origin's notes live; it's remembered after that. When you're ready, tell your AI agent **"read my notes."**
|
|
72
|
+
|
|
73
|
+
## Features
|
|
74
|
+
|
|
75
|
+
- **Free notes** — a draggable post-it you can drop anywhere on the page. Captures URL, title, timestamp, viewport, and a screenshot.
|
|
76
|
+
- **Element notes** — click any element and the note auto-captures a robust unique CSS selector ([`@medv/finder`](https://github.com/antonmedv/finder)), curated computed styles, truncated `outerHTML`, bounding box, `data-*`, accessibility role/label, the best-effort **React component name**, and an **auto element-highlight screenshot** showing exactly which element you meant.
|
|
77
|
+
- **Region / marquee capture** — the camera tool dims the page, gives you a crosshair, and lets you drag a rectangle. stikfix hides its own UI, captures, and crops the screenshot DPR-correctly. Stack multiple per note; each is a deletable thumbnail.
|
|
78
|
+
- **Persistent on-page pins** — notes stay visible as pins on the page across reloads. View, edit, and delete them in place — backed by host-side CRUD over the localhost relay. Overlapping pins fan out automatically so dense pages stay readable.
|
|
79
|
+
- **Two-way status, on the page** — pins reflect the loop: **unread** (yellow), **flagged** (amber — the agent needs you to clarify, with its question on hover), **resolved** (green ✓ — fixed, with the agent's reply on hover). Resolved notes stay visible so you can verify the fix; archived (`read`) notes disappear.
|
|
80
|
+
- **Notes panel** — a chip-toggled list of every note: counts by status, filter chips, text search, and click-to-jump that scrolls right to the pin. Flip **All pages** to browse every note across the project, not just the current page.
|
|
81
|
+
- **Live updates** — while Review Mode is on, pins and the panel refresh on their own (~4s, only when the tab is visible) as the agent writes replies and resolves notes. No manual reload.
|
|
82
|
+
- **Per-origin project routing** — the first note on a new site opens an OS folder picker; after that, every tab routes to the right project's `notes/` folder automatically. No per-note picking.
|
|
83
|
+
- **Cross-browser host** — one `npx stikfix init` registers Chrome and Edge in a single pass.
|
|
84
|
+
- **The `review-notes` AI skill** — the portable agent half of the loop (see below).
|
|
85
|
+
|
|
86
|
+
## How notes reach your AI
|
|
87
|
+
|
|
88
|
+
Every note becomes a markdown file in your project's `notes/` folder, named `<serial>-<YYYYMMDD-HHmmss>.md` (e.g. `0007-20260531-143022.md`), with any screenshots written alongside it as `<base>+<N>.png`. Element notes embed a full **Element context** section — selector, computed styles, `outerHTML`, React component — so an agent can locate the exact code without guessing.
|
|
89
|
+
|
|
90
|
+
The **review-notes** skill is the agent half of the loop. It works through your unread notes in serial order, applies each fix, then writes a short **`reply`** into the note and marks it **`resolved`** (the pin turns green ✓ on the page). Re-running is always idempotent — resolved, flagged, and archived notes are skipped.
|
|
91
|
+
|
|
92
|
+
**Claude Code (project-local):**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
mkdir -p .claude/skills/review-notes
|
|
96
|
+
cp /path/to/stikfix/skill/SKILL.md .claude/skills/review-notes/SKILL.md
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Any other folder-reading agent (Cursor, Codex, etc.):** point it at [`skill/SKILL.md`](skill/SKILL.md) — it's plain markdown, no Claude-specific bits. For example: *"follow the instructions in skill/SKILL.md."*
|
|
100
|
+
|
|
101
|
+
**Just say the word.** Any of these kicks it off:
|
|
102
|
+
|
|
103
|
+
- "read my notes"
|
|
104
|
+
- "process review notes"
|
|
105
|
+
- "fix sticky notes"
|
|
106
|
+
- "what notes do I have"
|
|
107
|
+
|
|
108
|
+
**Under the hood, it:**
|
|
109
|
+
|
|
110
|
+
1. Finds every unread `notes/*.md` (skips `*.read.md` and already-resolved/flagged notes), oldest serial first.
|
|
111
|
+
2. Reads the note plus its screenshots, then makes the code change.
|
|
112
|
+
3. Marks the note **resolved** with a `reply` (and optional `fixed_in` commit ref) — only *after* the fix lands. If a fix is interrupted, the note stays unread and is retried next run. Resolved notes stay on the page (green ✓) so you can verify; archiving/dismissing (`status: read` + `*.read.md` rename, which hides the pin) is a separate step done after you've acknowledged them.
|
|
113
|
+
4. Flags anything ambiguous instead of guessing — sets **flagged** with a `reply` question (amber pin), so it surfaces again next run for you to clarify.
|
|
114
|
+
5. Gives you a one-line recap: N resolved, K flagged, J already done.
|
|
115
|
+
|
|
116
|
+
Run it on a clean directory and it just says "no unread notes." Safe to fire any time.
|
|
117
|
+
|
|
118
|
+
## Demo
|
|
119
|
+
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+
*Recorded walkthrough coming soon — the placeholder above will be replaced with a GIF of the full drop-a-note → "read my notes" loop.*
|
|
123
|
+
|
|
124
|
+
## Cross-browser
|
|
125
|
+
|
|
126
|
+
| Browser | v1.0 status | Notes |
|
|
127
|
+
|---------|-------------|-------|
|
|
128
|
+
| **Chrome** | Supported | Load unpacked from `.output/chrome-mv3/` |
|
|
129
|
+
| **Edge** | Supported | Drop-in; the same artifact loads directly. `init` registers Edge automatically |
|
|
130
|
+
| Firefox | Documented (next) | Different native-messaging manifest key; path documented |
|
|
131
|
+
| Safari | Documented (next) | App-bundled extension via Mac App Store; path documented |
|
|
132
|
+
|
|
133
|
+
Full packaging details, manifest locations, and the Firefox/Safari paths are in [docs/cross-browser.md](docs/cross-browser.md).
|
|
134
|
+
|
|
135
|
+
## Security
|
|
136
|
+
|
|
137
|
+
stikfix is built localhost-first on purpose:
|
|
138
|
+
|
|
139
|
+
- **`127.0.0.1` only.** The host binds to localhost and nothing else — never `0.0.0.0`, not reachable from your network.
|
|
140
|
+
- **Authorized writes only.** Every `POST /annotation` is token-checked. The token is delivered to the extension over the OS native-messaging channel during pairing — it never travels over the web and you never handle it.
|
|
141
|
+
- **Stays in its lane.** The host only writes `.md` and `.png` files inside the project folder you chose (its `notes/` dir). Path-traversal attempts are rejected; no eval, no shelling out.
|
|
142
|
+
- **Sensible limits.** Oversized payloads are turned away (12 MB body cap → `413`).
|
|
143
|
+
- **Right project, every time.** Each tab's origin is mapped to its project, so notes land in the correct folder automatically.
|
|
144
|
+
- **MIT, clean-room.** Original implementation written from spec — contains no code from the GPL-3.0 upstream. See [CLEAN-ROOM.md](./CLEAN-ROOM.md) for the full provenance declaration and GPL grep audit.
|
|
145
|
+
|
|
146
|
+
## Development
|
|
147
|
+
|
|
148
|
+
Build the extension and host from source:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm install # installs deps + runs `wxt prepare`
|
|
152
|
+
npm run build # builds the extension (.output/chrome-mv3) + host bundles
|
|
153
|
+
npm test # host test suite
|
|
154
|
+
npm run check # full gate: tsc, clean-room check, host smoke test, all tests
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Useful scripts:
|
|
158
|
+
|
|
159
|
+
- `npm run dev` — WXT dev server with HMR for the extension.
|
|
160
|
+
- `npm run host -- --root /path/to/project --origin http://localhost:3000` — start the host directly from a terminal (the Desktop launcher is the easy path; this is the manual one). On Windows PowerShell, use the equals form (`--root=C:\path`) or set `STIKFIX_ROOT` / `STIKFIX_ORIGINS` env vars, since npm 11.x swallows unknown `--flags`.
|
|
161
|
+
|
|
162
|
+
**Removing stikfix:**
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npx stikfix uninstall
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This removes the native-messaging manifest, the desktop launcher, and the local config — no leftovers.
|
|
169
|
+
|
|
170
|
+
### Architecture (one-liner)
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
[Chrome/Edge Extension (MV3)] --POST /annotation--> [stikfix host (localhost)] --writes--> notes/NNNN-<ts>.md
|
|
174
|
+
you annotate token-authed, 127.0.0.1 only your AI agent reads these
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Pairing rides a separate OS-level native-messaging channel so the token never touches the web; notes themselves flow over the localhost HTTP relay (`GET /status`, `POST /annotation`, plus `GET/PUT/DELETE /annotation/<serial>` for the persistent on-page pins).
|
|
178
|
+
|
|
179
|
+
## Tech stack
|
|
180
|
+
|
|
181
|
+
TypeScript ES modules both halves. Extension built with [WXT](https://wxt.dev) (Vite, MV3) — vanilla DOM in a shadow root, [`@medv/finder`](https://github.com/antonmedv/finder) for selectors, [`interactjs`](https://interactjs.io) for drag + marquee, native `chrome.tabs.captureVisibleTab` + canvas crop for screenshots. The host is Node built-ins only plus [`yaml`](https://eemeli.org/yaml/) for safe frontmatter.
|
|
182
|
+
|
|
183
|
+
## License & provenance
|
|
184
|
+
|
|
185
|
+
MIT © 2026 Omer Nesher. See [LICENSE](./LICENSE).
|
|
186
|
+
|
|
187
|
+
This is an **original, clean-room implementation**. See [CLEAN-ROOM.md](./CLEAN-ROOM.md) for the full MIT provenance declaration, clean-room method narrative, and the live GPL grep audit result.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port-discovery helpers for stikfix-host.
|
|
3
|
+
* Extracted so tests can import bindServer without running the full index.ts boot.
|
|
4
|
+
* WR-06: removeAllListeners between scan attempts prevents stale-handler accumulation.
|
|
5
|
+
*/
|
|
6
|
+
export const PORT_RANGE_START = 39240;
|
|
7
|
+
export const PORT_RANGE_END = 39260;
|
|
8
|
+
export const BIND_HOST = '127.0.0.1'; // T-02-bind: NEVER 0.0.0.0
|
|
9
|
+
/**
|
|
10
|
+
* Attempt to bind `server` on a single port.
|
|
11
|
+
* Resolves true if bound, false on EADDRINUSE, throws on other errors.
|
|
12
|
+
* Uses 'once' listeners so a failed attempt does not accumulate handlers.
|
|
13
|
+
*/
|
|
14
|
+
export function tryListen(server, port) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const onError = (e) => {
|
|
17
|
+
if (e.code === 'EADDRINUSE') {
|
|
18
|
+
resolve(false);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
reject(e);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const onListening = () => {
|
|
25
|
+
resolve(true);
|
|
26
|
+
};
|
|
27
|
+
server.once('error', onError);
|
|
28
|
+
server.once('listening', onListening);
|
|
29
|
+
server.listen(port, BIND_HOST);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Find the first free port in the range [start, end] and bind the server to it.
|
|
34
|
+
* Honors a caller-supplied preferred port (e.g. from --port) by trying it first.
|
|
35
|
+
* Throws if no port in range is free.
|
|
36
|
+
*
|
|
37
|
+
* WR-06: The comment previously described probe-server-per-attempt, but the
|
|
38
|
+
* implementation reused the same server in a loop. We keep single-server reuse
|
|
39
|
+
* (simpler) and make the code and comment agree: after each failed EADDRINUSE
|
|
40
|
+
* attempt, removeAllListeners('error') and removeAllListeners('listening') are
|
|
41
|
+
* called before the next attempt to prevent stale-handler accumulation.
|
|
42
|
+
*/
|
|
43
|
+
export async function bindServer(server, preferredPort, startPort = PORT_RANGE_START, endPort = PORT_RANGE_END) {
|
|
44
|
+
// Try preferred port first if specified
|
|
45
|
+
if (preferredPort !== undefined) {
|
|
46
|
+
const bound = await tryListen(server, preferredPort);
|
|
47
|
+
if (bound) {
|
|
48
|
+
return server.address().port;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`stikfix-host: --port ${preferredPort} is already in use. ` +
|
|
51
|
+
`Remove --port to auto-scan ${PORT_RANGE_START}–${PORT_RANGE_END}.`);
|
|
52
|
+
}
|
|
53
|
+
// Scan range, reusing the real server across attempts.
|
|
54
|
+
// WR-06: Remove stale handlers between attempts so a prior failed listen()
|
|
55
|
+
// does not accumulate duplicate error/listening listeners on the server.
|
|
56
|
+
// startPort/endPort default to the production range; tests override them with
|
|
57
|
+
// an OS-assigned ephemeral range so the scan-past-occupied path can be
|
|
58
|
+
// exercised hermetically (never colliding with a real host on 39240).
|
|
59
|
+
for (let port = startPort; port <= endPort; port++) {
|
|
60
|
+
server.removeAllListeners('error');
|
|
61
|
+
server.removeAllListeners('listening');
|
|
62
|
+
const bound = await tryListen(server, port);
|
|
63
|
+
if (bound) {
|
|
64
|
+
return server.address().port;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`stikfix-host: no free port found in ${startPort}–${endPort}. ` +
|
|
68
|
+
`Use --port to specify a different port.`);
|
|
69
|
+
}
|