@brmbh/tack 0.0.0-stage → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +203 -2
  2. package/bin/tack.cjs +23 -0
  3. package/package.json +34 -5
package/README.md CHANGED
@@ -1,3 +1,204 @@
1
- # Temporary Holding Version
1
+ # tack
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Point at your running site, say what should change, and hand it to your coding agent.
4
+
5
+ `tack` puts a local proxy in front of your dev server and adds a small toolbar
6
+ to every page. Click an element to comment on it, or click any text and retype
7
+ it in place. Each note stores the page, a verified CSS selector, the element's
8
+ text and HTML, and the **source file:line it most likely comes from**. Your
9
+ agent (Claude Code, Cursor, Codex…) reads the notes, changes the code, and
10
+ resolves them. The pin leaves the page once it's done; the note and the agent's answer stay in the list (`L` → All).
11
+
12
+ Nothing gets installed in your project, and it works with any framework: static
13
+ HTML, Vite, Next, Astro, WordPress, PHP. It runs only on your machine.
14
+
15
+ ## Install
16
+
17
+ **Which agent:** the coding agent that works **in your project folder**, the one that can read and
18
+ change your files and knows your codebase (Claude Code, Codex, Cursor, Gemini CLI, Copilot,
19
+ opened in that folder). Not a browser chat like claude.ai or ChatGPT: it can't see your code, so
20
+ it can't act on the notes. Tack points; that agent finds the right place in your code or CMS.
21
+
22
+ **With your agent:** paste this into it, in your project.
23
+
24
+ ```text
25
+ Set me up to use Tack in this project. Run `npx -y @brmbh/tack@latest init` in the project root;
26
+ it installs the tack skill for this agent. Read the installed skill (.claude/skills/tack/SKILL.md
27
+ or .agents/skills/tack/SKILL.md), then start a tack session on my dev server and give me the URL.
28
+ ```
29
+
30
+ **From the command line**, like any other tool:
31
+
32
+ ```bash
33
+ npx @brmbh/tack init # in your project: installs the agent skill
34
+ npx @brmbh/tack --target http://localhost:3000
35
+ npm i -g @brmbh/tack # optional: a `tack` command everywhere
36
+ ```
37
+
38
+ Needs Node 18+ for `npx`, nothing else: tack ships as a standalone binary for macOS (arm64, x64),
39
+ Linux (x64, arm64) and Windows (x64). `tack init` writes the skill to `.claude/skills/tack/`
40
+ (Claude Code) and `.agents/skills/tack/` (Codex, Cursor, Gemini CLI, GitHub Copilot, Amp, Cline
41
+ and others). `--global` puts it in your home folder instead. Then tell your agent
42
+ "start tack", and later "do the tack notes".
43
+
44
+ ## Use
45
+
46
+ ```bash
47
+ tack --target http://localhost:3000 # → http://localhost:7777
48
+ ```
49
+
50
+ Open the printed URL and browse as usual.
51
+
52
+ | Key | |
53
+ |---|---|
54
+ | `C` | Comment mode: click an element and type. Select text first to quote it. |
55
+ | `E` | Edit mode: click text, retype it, `Enter` saves (`Shift+Enter` = newline), `Esc` cancels. |
56
+ | `L` | Feedback list (all pages). Copy button = Markdown for any agent. |
57
+ | `H` | Show/hide pins. |
58
+ | `Esc` | Leave the current mode. |
59
+
60
+ Pins show open notes only (black). A dot on a pin means the agent asked a question. Done notes leave the canvas and stay in the list (`L` → All), with the agent's answer.
61
+
62
+ ## Hand it to your agent
63
+
64
+ Feedback is written to `<project>/.tack/`, as `notes.json` (the source of truth) and `FEEDBACK.md`
65
+ (readable by anything). Three ways for an agent to pick it up:
66
+
67
+ 1. **Skill** (Claude Code, Codex, Cursor and every agent that reads `.agents/skills`): run
68
+ `tack init` in the project, or `tack init --global`, then tell the agent "do the tack notes".
69
+ 2. **MCP**: `claude mcp add tack -- tack mcp` (without a global install:
70
+ `claude mcp add tack -- npx -y @brmbh/tack mcp`). Tools: `tack_list`, `tack_get`,
71
+ `tack_resolve`, `tack_reply`.
72
+ 3. **CLI** (any agent that can run shell commands):
73
+ ```bash
74
+ tack list --md
75
+ tack resolve 3 -m "Shortened the lead, index.html:1370"
76
+ tack reply 4 -m "Bolder as in weight 700, or bigger?"
77
+ ```
78
+
79
+ Commit `.tack/` if you want the review history in git, or add it to `.gitignore`.
80
+
81
+ ## Console bridge
82
+
83
+ The proxy also sees what the page does, so the agent doesn't have to ask you to open DevTools.
84
+ A tiny script at the top of `<head>` captures `console.*`, uncaught errors (with
85
+ `file:line:col`), unhandled rejections, failed `fetch` calls and broken images/scripts. The
86
+ proxy adds upstream 4xx/5xx responses and **missing files that the dev server answered with
87
+ its HTML fallback** (Vite serves `index.html` with status 200 for any unknown path, so a
88
+ missing `.json` or `.png` looks like success). Everything goes to `.tack/console.jsonl`.
89
+
90
+ ```bash
91
+ tack logs --md # last 50 entries
92
+ tack logs --level problems --since 5m # errors, warnings, network, proxy
93
+ tack logs --page /pricing/ --md
94
+ ```
95
+
96
+ MCP: `tack_console`. Every new note also carries the problems from **its own page load**,
97
+ with repeats collapsed. `tack --verbose` prints every proxied request.
98
+
99
+ ## AI suggestions (optional)
100
+
101
+ In edit mode (`E`), the popover has **Suggest**: say what the new versions should do (or leave it
102
+ empty) and tack streams a few versions of the text. Pick one, it appears on the page, `Enter`
103
+ saves it as a normal edit note for your agent.
104
+
105
+ Any model, your own key, via the [Vercel AI SDK](https://ai-sdk.dev). Set it up in the
106
+ overlay: the **gear icon** opens AI settings. Pick where the model runs (any OpenAI-compatible
107
+ endpoint such as Ollama Cloud, OpenRouter, LM Studio or a local Ollama, or the Vercel AI
108
+ Gateway), paste your key, name the model, **Save & test connection**.
109
+
110
+ The key is stored on your computer in `~/.config/tack/settings.json` (mode 600), for all
111
+ projects. Never in the project, never in the browser; the overlay can write it but only ever
112
+ reads back `••••` plus the last four characters. Environment variables win over the file:
113
+
114
+ ```bash
115
+ TACK_BASE_URL=https://ollama.com/v1 TACK_API_KEY=… TACK_MODEL=minimax-m3:cloud # OpenAI-compatible
116
+ AI_GATEWAY_API_KEY=… TACK_MODEL=anthropic/claude-sonnet-5.5 # AI Gateway
117
+ ```
118
+
119
+ Models without JSON-schema output answer in plain lines, which tack parses as they stream.
120
+
121
+ The model gets the element, its page, the source around the likely file:line and your
122
+ `AGENTS.md` / `CLAUDE.md` / `README.md`, and can **read** more itself (list, search, read files
123
+ in the project). Point it at notes outside the repo with `.tack/config.json`:
124
+
125
+ ```json
126
+ { "knowledge": ["~/notes/my-project"] }
127
+ ```
128
+
129
+ It never writes files, and refuses `.env*`, keys and `.git/`. What it reads is sent to the
130
+ model provider you chose.
131
+
132
+ **Security.** tack listens on `127.0.0.1` only and answers only requests addressed to
133
+ `localhost` / `127.0.0.1` (a foreign site re-resolved to your machine, "DNS rebinding", gets
134
+ `421`). Every write to its API must come from tack's own pages, or from a tool without a
135
+ browser such as the CLI, and be JSON, so other sites open in your browser can't change
136
+ settings or spend your tokens. The key file is plain text with mode 600, like `~/.npmrc`:
137
+ any program running as your user can read it. Prefer the env vars if you keep keys in a
138
+ secrets manager. `--host 0.0.0.0` turns the host check off (you're exposing tack on purpose).
139
+
140
+ ## Options
141
+
142
+ ```
143
+ --target <url> dev server to proxy (required)
144
+ --port <n> proxy port (default 7777)
145
+ --dir <path> project root: where .tack/ lives and source hints are searched (default cwd)
146
+ --store <path> put notes somewhere else
147
+ --host <addr> bind address (default 127.0.0.1)
148
+ ```
149
+
150
+ ## How it works
151
+
152
+ - **Proxy** (`src/server.ts`): forwards every request to the target, injects
153
+ `/__tack/overlay.js` into HTML responses, rewrites absolute links to the dev server so
154
+ you stay inside the proxy, strips CSP headers, and pipes websockets through so hot
155
+ reload keeps working.
156
+ - **Same origin**: the proxy serves the page from its own address, so the overlay can
157
+ read the real DOM, which a screenshot tool can't. Selectors come from the actual
158
+ element and are checked for uniqueness when the note is captured.
159
+ - **Overlay** (`overlay/overlay.ts`): vanilla TypeScript in a shadow root, bundled by
160
+ Bun when requested (prebuilt into the release binary). The site's CSS can't reach it.
161
+ - **Source hints** (`src/hints.ts`): a deterministic text search over the project
162
+ (git-tracked files, minus build output). It still matches when text is wrapped across
163
+ lines, contains HTML entities or smart quotes, or is split by inline tags. It falls
164
+ back to `id`, then class names.
165
+ - **MCP** (`src/mcp.ts`): about 100 lines of JSON-RPC over stdio, no SDK.
166
+ - **Stable targets**: clicks on anonymous wrappers inside a heading or paragraph (split-text
167
+ lines and words, styling spans) snap to the heading/paragraph. Runtime classes
168
+ (`is-*`, `split-*`, `line`, `word`…) never go into selectors, and a pin is only drawn if the
169
+ element's tag and text still match. Otherwise tack looks for the element by text.
170
+ - **Redirects** pass through with their `Location` (rewritten when it points back at the
171
+ target). If the target's root redirects to another host at startup (apex → `www`,
172
+ http → https), tack proxies the final host instead. A later redirect to another host
173
+ (e.g. a locale subdomain) leaves the proxy; tack prints and logs the `--target` to use.
174
+ - **One tack per project**: starting tack again for the same project with another `--target`
175
+ switches the running proxy (same port, notes kept) instead of starting a second one.
176
+ Tools can do the same with `POST /__tack/api/target {"target": "…"}`.
177
+ - **For tools on the page**: `window.__tack` carries `version` and `contract` (the page-event
178
+ contract version); `/__tack/api/status` reports both. Nothing is added to `<html>` and the
179
+ console bridge removes its own `<script>`, so SSR hydration (React, Next) sees the server's markup.
180
+ - **Revalidation**: conditional request headers are stripped, so every request gets a full
181
+ response. A passed-through 304 had turned into an empty 200 (a broken web manifest).
182
+
183
+ ## Develop
184
+
185
+ ```bash
186
+ bun install
187
+ bun test
188
+ bunx tsc --noEmit
189
+ ```
190
+
191
+ Requires [Bun](https://bun.sh) ≥ 1.2 to develop; users get a compiled binary.
192
+
193
+ ## Release
194
+
195
+ ```bash
196
+ # bump "version" in package.json, commit
197
+ bun run release --dry-run # checks, build of all 5 binaries, smoke test of this platform
198
+ bun run release # the same, then publish (platform packages first), wait for npm, tag vX.Y.Z
199
+ ```
200
+
201
+ Run it in a real terminal: with a passkey, npm confirms each package in the browser
202
+ (`--auth-type=web`). A granular npm token with publish rights on `@brmbh/tack*` and 2FA bypass
203
+ (in `NPM_TOKEN` or `~/.npmrc`) skips the confirmations. CI (`release-smoke`) builds and
204
+ smoke-tests every platform on its own runner on each push.
package/bin/tack.cjs ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // Runs the tack binary that npm installed for this platform (@brmbh/tack-<os>-<arch>).
3
+ const { spawn } = require('node:child_process')
4
+
5
+ const os = process.platform === 'win32' ? 'windows' : process.platform
6
+ const name = `@brmbh/tack-${os}-${process.arch}`
7
+ let bin
8
+ try {
9
+ bin = require.resolve(`${name}/bin/tack${process.platform === 'win32' ? '.exe' : ''}`)
10
+ } catch {
11
+ console.error(`tack: no binary for ${process.platform}-${process.arch} (${name} is not installed).`)
12
+ console.error('Supported: macOS arm64/x64, Linux x64/arm64, Windows x64. If yours is one, reinstall without --no-optional.')
13
+ process.exit(1)
14
+ }
15
+
16
+ const child = spawn(bin, process.argv.slice(2), { stdio: 'inherit' })
17
+ // The proxy runs until stopped: hand stop signals to it instead of orphaning it.
18
+ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(sig, () => child.kill(sig))
19
+ child.on('error', (err) => {
20
+ console.error(`tack: could not start ${bin}: ${err.message}`)
21
+ process.exit(1)
22
+ })
23
+ child.on('exit', (code, signal) => process.exit(code ?? (signal ? 1 : 0)))
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
- "name": "@brmbh/tack",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@brmbh/tack",
3
+ "version": "0.1.1",
4
+ "description": "Point at your running site, comment or edit text in place, and hand the feedback to your coding agent.",
5
+ "license": "UNLICENSED",
6
+ "homepage": "https://github.com/brmbh/tack",
7
+ "author": "Jan Brombach",
8
+ "keywords": [
9
+ "feedback",
10
+ "coding-agent",
11
+ "claude-code",
12
+ "cursor",
13
+ "codex",
14
+ "proxy",
15
+ "review",
16
+ "skills"
17
+ ],
18
+ "bin": {
19
+ "tack": "bin/tack.cjs"
20
+ },
21
+ "files": [
22
+ "bin",
23
+ "README.md"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "optionalDependencies": {
29
+ "@brmbh/tack-darwin-arm64": "0.1.1",
30
+ "@brmbh/tack-darwin-x64": "0.1.1",
31
+ "@brmbh/tack-linux-x64": "0.1.1",
32
+ "@brmbh/tack-linux-arm64": "0.1.1",
33
+ "@brmbh/tack-windows-x64": "0.1.1"
34
+ }
35
+ }