@ammduncan/easel 0.2.0 → 0.2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to easel. This project adheres to [Semantic Versioning](https://semver.org/).
4
+
5
+ ## 0.2.1 — 2026-05-22
6
+
7
+ ### Added
8
+ - `easel setup --client cursor|claude-desktop|windsurf` writes the MCP entry into each client's config file, merging into any existing `mcpServers` map. Sibling top-level keys are preserved. (`src/client-setup.ts`)
9
+ - README leads with the npx install; per-client install commands documented; generic JSON snippet for any other MCP-speaking client.
10
+ - This `CHANGELOG.md`.
11
+
12
+ ### Notes
13
+ - The Claude Code setup path (bare `easel setup`) is unchanged.
14
+
15
+ ## 0.2.0 — 2026-05-22
16
+
17
+ ### Added
18
+ - Published to npm as [`@ammduncan/easel`](https://www.npmjs.com/package/@ammduncan/easel). Any MCP client can now install with `npx -y @ammduncan/easel` — no clone, no build.
19
+ - `EASEL_SESSION_ID` env var as the highest-priority override for session resolution.
20
+ - Synthetic PPID-derived session id as a final fallback when no Claude Code hook fired and no transcript exists — gives Cursor, Claude Desktop, Windsurf, and any other MCP client a stable session per chat with zero setup beyond the MCP entry.
21
+ - MCP-side auto-open: non-CC clients have no `SessionStart` hook to open the tab, so the MCP server now opens it on the first tool call when no hook file exists for this PPID. One-shot guard avoids re-opening if the user closes the tab. Claude Code behaviour unchanged.
22
+ - `LICENCE` file (MIT).
23
+
24
+ ### Changed
25
+ - `resolveClaudeSessionId()` is now total — always returns a string. Dead "no session id" error branches removed from `cli.ts` and `mcp.ts`.
26
+ - `package.json` reshaped for npm publishing: scoped name `@ammduncan/easel`, MIT licence, repo/homepage/bugs URLs, keywords for discovery, `engines.node >=20`, `files` whitelist (only bin/dist/scripts/skills/README/LICENCE in the tarball), `prepublishOnly` script, `publishConfig.access=public`.
27
+ - Package size: 47 kB tarball, 165 kB unpacked, 3 runtime deps.
28
+
29
+ ## 0.1.0 — internal baseline
30
+
31
+ The pre-publish baseline. Highlights from the unreleased history:
32
+
33
+ - Renamed project from `claude-display` to `easel`; data root migrated from `~/.claude-display` to `~/.easel` with one-shot `mv` on first start. Browser localStorage keys (`claude-display:*`) and postMessage events migrated to the `easel:*` namespace with a browser-side migration shim. Internal `__CLAUDE_DISPLAY__` window global renamed to `__EASEL__`. Env vars renamed (`CLAUDE_DISPLAY_PORT` → `EASEL_PORT` with legacy fallback).
34
+ - Dropped `jq` as a runtime dependency. Ported `scripts/easel-session-id.sh` to `scripts/easel-session-id.mjs` (Node stdlib only).
35
+ - Added `easel restart` command.
36
+ - MCP tool names finalised as `push`, `open`, `config`, `label` (invoked as `mcp__easel__*`).
37
+ - Skill folder canonicalised at `skills/using-easel/`.
package/README.md CHANGED
@@ -1,37 +1,68 @@
1
1
  # easel
2
2
 
3
- A live browser tab for every Claude Code session. Agents push HTML — explanations, mockups, diagrams, diffs, comparisons — to a scrolling feed you keep open in split-screen. No more wall-of-text in the terminal.
3
+ A live browser tab for every AI coding session. Agents push HTML — explanations, mockups, diagrams, diffs, comparisons — to a scrolling feed you keep open in split-screen. No more wall-of-text in the terminal.
4
4
 
5
5
  ```
6
- ┌──────────── Claude Code (left) ────────────┐ ┌────── easel (right) ──────────────┐
6
+ ┌──────────── agent (left) ──────────────────┐ ┌────── easel (right) ──────────────┐
7
7
  │ │ │ s/<session-id> • 3 pushes • live│
8
8
  │ > walk me through the new auth flow │ │ ───────────────────────────────── │
9
9
  │ │ │ #1 Auth flow overview │
10
- │ pushed to display ↗ — #1 │ │ ┌────────────────────────────────┐│
10
+ │ pushed to easel ↗ — #1 │ │ ┌────────────────────────────────┐│
11
11
  │ │ │ │ Three actors talk to each… ││
12
12
  │ > what could break? │ │ └────────────────────────────────┘│
13
13
  │ │ │ │
14
- │ pushed to display ↗ — #2 │ │ #2 Failure modes │
14
+ │ pushed to easel ↗ — #2 │ │ #2 Failure modes │
15
15
  └────────────────────────────────────────────┘ └────────────────────────────────────┘
16
16
  ```
17
17
 
18
+ Works with **Claude Code**, **Cursor**, **Claude Desktop**, **Windsurf**, and any other MCP-speaking client.
19
+
18
20
  ## Why
19
21
 
20
- Long markdown explanations bury what the agent is actually doing. Visual content (mockups, comparisons, diagrams) is even worse in a TTY. `easel` gives each chat session its own browser tab, and a single MCP tool — `push` — that the agent uses proactively. The terminal stays as a conversation log; the browser carries the substance.
22
+ Long markdown explanations bury what the agent is actually doing. Visual content (mockups, comparisons, diagrams) is even worse in a TTY. easel gives each chat session its own browser tab, and a single MCP tool — `push` — that the agent uses proactively. The terminal stays as a conversation log; the browser carries the substance.
21
23
 
22
24
  ## Install
23
25
 
24
- Requires Node 20+, `git`, and Claude Code.
26
+ Requires Node 20+.
27
+
28
+ ### Claude Code
29
+
30
+ ```bash
31
+ npx -y @ammduncan/easel setup
32
+ ```
33
+
34
+ That registers the MCP at user scope, installs the `using-easel` skill so the agent knows when to push, and adds the `SessionStart` hooks that resolve session IDs and auto-open the tab. Restart Claude Code and you're done.
35
+
36
+ ### Cursor / Claude Desktop / Windsurf
37
+
38
+ One command per client:
25
39
 
26
40
  ```bash
27
- curl -fsSL https://raw.githubusercontent.com/AmmDuncan/easel/main/scripts/install.sh | bash
41
+ npx -y @ammduncan/easel setup --client cursor
42
+ npx -y @ammduncan/easel setup --client claude-desktop
43
+ npx -y @ammduncan/easel setup --client windsurf
28
44
  ```
29
45
 
30
- The installer clones to `~/.local/share/easel` (override with `EASEL_DIR=…`), runs `npm install && npm run build`, then registers the MCP at user scope and adds two `SessionStart` hooks (session-id capture + auto-open tab). Idempotent safe to re-run to update.
46
+ Each writes the MCP entry to the client's config file (`~/.cursor/mcp.json`, `~/Library/Application Support/Claude/claude_desktop_config.json`, or `~/.codeium/windsurf/mcp_config.json`). Restart the client to load it.
31
47
 
32
- Restart Claude Code afterwards.
48
+ ### Any other MCP client
33
49
 
34
- ### Manual install
50
+ Drop this snippet into your client's MCP config:
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "easel": {
56
+ "command": "npx",
57
+ "args": ["-y", "@ammduncan/easel"]
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ The MCP child mints its own session from its parent process ID — no hook required. Set `EASEL_SESSION_ID` in the client's `env` block if you want to pin a specific session.
64
+
65
+ ### From source (for contributors)
35
66
 
36
67
  ```bash
37
68
  git clone https://github.com/AmmDuncan/easel.git ~/work/tools/easel
@@ -56,20 +87,20 @@ Agents invoke them as `mcp__easel__push`, `mcp__easel__open`, etc.
56
87
  - **Presets**: `paper` (warm pitstop-style, amber accent — default), `aurora` (deep canvas + violet glow halos), `slate` (cool neutral, cyan accent)
57
88
  - **Themes**: light / dark, with sun-moon toggle in the topbar
58
89
  - **Density**: `carded` (bordered cards) or `flat` (no chrome, whitespace separates pushes)
59
- - Three swatches + density toggle live in the topbar; config persists in `~/.easel/config.json` and SSE-broadcasts across all open tabs
90
+ - Three swatches + density toggle live in the topbar; config persists in `~/.easel/config.json` and SSE-broadcasts across all open tabs.
60
91
 
61
92
  ## Sessions
62
93
 
63
- - Each Claude Code session gets its own URL: `localhost:7878/s/<session-id>`
64
- - Session IDs come from Claude Code itself (via the pitstop-style SessionStart hook)
65
- - Sessions auto-rename to `cwd-basename` by default; you can rename them via the click-to-edit label in the topbar, or the agent can via the `label` tool
66
- - Idle sessions (>24h since last push) are GC'd every 10 minutes
67
- - Up to 50 pushes per session; oldest evicted from disk first
68
- - Per-push delete (trash icon on each card) + per-session delete (hover any row in the switcher or index)
94
+ - Each chat session gets its own URL: `localhost:7878/s/<session-id>`
95
+ - Session IDs come from the agent client. Claude Code provides them via a SessionStart hook; other MCP clients fall through to a stable PPID-derived id for the MCP child. Override either via the `EASEL_SESSION_ID` env var.
96
+ - Sessions auto-rename to `cwd-basename` by default; you can rename them via the click-to-edit label in the topbar, or the agent can via the `label` tool.
97
+ - Idle sessions (>24h since last push) are GC'd every 10 minutes.
98
+ - Up to 50 pushes per session; oldest evicted from disk first.
99
+ - Per-push delete (trash icon on each card) + per-session delete (hover any row in the switcher or index).
69
100
 
70
101
  ## Tool surface
71
102
 
72
- The MCP exposes one server (`display`) with four tools. HTML is rendered in a sandboxed iframe (`sandbox="allow-scripts"`) with a baseline design system injected — off-white / charcoal, Inter, presentation-scale typography — so plain `<h1>/<h2>/<p>` markup looks right without extra CSS. Authors can also write a full `<!DOCTYPE html>` document and take ownership of styling.
103
+ The MCP exposes one server (`easel`) with four tools. HTML is rendered in a sandboxed iframe (`sandbox="allow-scripts"`) with a baseline design system injected — off-white / charcoal, Inter, presentation-scale typography — so plain `<h1>/<h2>/<p>` markup looks right without extra CSS. Authors can also write a full `<!DOCTYPE html>` document and take ownership of styling.
73
104
 
74
105
  Inside pushed HTML, semantic chips are available out of the box:
75
106
 
@@ -84,6 +115,20 @@ Inside pushed HTML, semantic chips are available out of the box:
84
115
 
85
116
  Each is themed for both light and dark with a soft outer glow.
86
117
 
118
+ ## CLI
119
+
120
+ ```
121
+ easel open ensure server is running, open this session's tab
122
+ easel url print this session's URL
123
+ easel config print / set { preset, theme, density }
124
+ easel setup Claude Code: hooks + MCP + skill
125
+ easel setup --client <name> register the MCP in another client (cursor, claude-desktop, windsurf)
126
+ easel restart kill + respawn the HTTP server (handy after a build)
127
+ easel update git pull + build + setup (clone installs only)
128
+ easel server run the HTTP server in the foreground (debug)
129
+ easel version
130
+ ```
131
+
87
132
  ## Files
88
133
 
89
134
  ```
@@ -93,10 +138,11 @@ src/
93
138
  http-entry.ts process entry for the HTTP server
94
139
  server-manager.ts lockfile + spawn coordination
95
140
  session-store.ts disk persistence + retention sweep
96
- session-id.ts 3-tier resolver (env / hook file / transcript scan)
141
+ session-id.ts 5-tier resolver (env / hook file / transcript scan / synthetic PPID)
97
142
  config-store.ts preset / theme / density persistence
98
- paths.ts shared constants
99
- cli.ts `easel open|url|setup|config|server|version`
143
+ client-setup.ts per-client config writers (cursor, claude-desktop, windsurf)
144
+ paths.ts shared constants + legacy-dir migration
145
+ cli.ts `easel open|url|setup|config|server|restart|update|version`
100
146
  client/
101
147
  viewer.html single-session feed
102
148
  index.html sessions index page
@@ -106,7 +152,7 @@ src/
106
152
  index.js index page client
107
153
  scripts/
108
154
  easel-session-id.mjs SessionStart hook (Node, zero deps)
109
- install.sh one-shot installer
155
+ install.sh one-shot installer (clone installs)
110
156
  copy-client.mjs build-time copy of client assets
111
157
  bin/
112
158
  easel shebang → dist/cli.js
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import { ensureHttpServer, readLock } from "./server-manager.js";
8
8
  import { resolveClaudeSessionId } from "./session-id.js";
9
9
  import { HOOK_DIR, DATA_ROOT } from "./paths.js";
10
10
  import { registerSession } from "./session-store.js";
11
+ import { listClients, setupClient, } from "./client-setup.js";
11
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
13
  const PROJECT_ROOT = resolve(__dirname, "..");
13
14
  function help() {
@@ -22,7 +23,10 @@ Usage:
22
23
  easel config preset paper set preset to paper | aurora | slate
23
24
  easel config theme dark set theme to light | dark
24
25
  easel config preset aurora theme light set both at once
25
- easel setup install SessionStart hook + register MCP in ~/.claude/settings.json
26
+ easel setup install Claude Code hook + register MCP in ~/.claude/settings.json
27
+ easel setup --client cursor register the MCP in Cursor's config
28
+ easel setup --client claude-desktop register the MCP in Claude Desktop's config
29
+ easel setup --client windsurf register the MCP in Windsurf's config
26
30
  easel update git pull + npm install + build + setup (re-runs setup to apply new conventions)
27
31
  easel restart kill the running HTTP server and respawn it (picks up new builds/paths)
28
32
  easel server run the HTTP server in the foreground (debug)
@@ -330,9 +334,32 @@ async function main() {
330
334
  case "url":
331
335
  await cmdUrl();
332
336
  return;
333
- case "setup":
337
+ case "setup": {
338
+ const clientIdx = rest.indexOf("--client");
339
+ if (clientIdx !== -1) {
340
+ const name = rest[clientIdx + 1];
341
+ if (!name) {
342
+ console.error(`[easel] --client requires a name. Available: ${listClients().join(", ")}`);
343
+ process.exitCode = 1;
344
+ return;
345
+ }
346
+ if (!listClients().includes(name)) {
347
+ console.error(`[easel] unknown client "${name}". Available: ${listClients().join(", ")}`);
348
+ process.exitCode = 1;
349
+ return;
350
+ }
351
+ try {
352
+ setupClient(name);
353
+ }
354
+ catch (err) {
355
+ console.error(`[easel] setup --client ${name} failed:`, err.message);
356
+ process.exitCode = 1;
357
+ }
358
+ return;
359
+ }
334
360
  cmdSetup();
335
361
  return;
362
+ }
336
363
  case "server":
337
364
  await cmdServer();
338
365
  return;
@@ -0,0 +1,73 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { homedir, platform } from "node:os";
4
+ const CLIENTS = {
5
+ "claude-desktop": {
6
+ name: "claude-desktop",
7
+ label: "Claude Desktop",
8
+ configPath: () => {
9
+ const home = homedir();
10
+ if (platform() === "darwin") {
11
+ return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
12
+ }
13
+ if (platform() === "win32") {
14
+ const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
15
+ return join(appData, "Claude", "claude_desktop_config.json");
16
+ }
17
+ return join(home, ".config", "Claude", "claude_desktop_config.json");
18
+ },
19
+ postSetup: "Quit and relaunch Claude Desktop to load the MCP server.",
20
+ },
21
+ cursor: {
22
+ name: "cursor",
23
+ label: "Cursor",
24
+ configPath: () => join(homedir(), ".cursor", "mcp.json"),
25
+ postSetup: "Open Cursor and toggle MCP servers in Settings → Features → MCP, " +
26
+ "or restart Cursor for the registration to take effect.",
27
+ },
28
+ windsurf: {
29
+ name: "windsurf",
30
+ label: "Windsurf",
31
+ configPath: () => join(homedir(), ".codeium", "windsurf", "mcp_config.json"),
32
+ postSetup: "Restart Windsurf to load the MCP server.",
33
+ },
34
+ };
35
+ export function listClients() {
36
+ return Object.keys(CLIENTS);
37
+ }
38
+ export function setupClient(name) {
39
+ const spec = CLIENTS[name];
40
+ if (!spec) {
41
+ throw new Error(`unknown client: ${name}`);
42
+ }
43
+ const configPath = spec.configPath();
44
+ const config = readJson(configPath);
45
+ const mcpServers = config.mcpServers ?? {};
46
+ mcpServers.easel = {
47
+ command: "npx",
48
+ args: ["-y", "@ammduncan/easel"],
49
+ };
50
+ config.mcpServers = mcpServers;
51
+ mkdirSync(dirname(configPath), { recursive: true });
52
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
53
+ console.log(`[easel] ${spec.label} configured`);
54
+ console.log(` - wrote ${configPath}`);
55
+ console.log(` - ${spec.postSetup}`);
56
+ }
57
+ function readJson(path) {
58
+ if (!existsSync(path))
59
+ return {};
60
+ try {
61
+ const text = readFileSync(path, "utf-8").trim();
62
+ if (!text)
63
+ return {};
64
+ const parsed = JSON.parse(text);
65
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
66
+ return parsed;
67
+ }
68
+ return {};
69
+ }
70
+ catch (err) {
71
+ throw new Error(`couldn't parse existing config at ${path}: ${err.message}`);
72
+ }
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ammduncan/easel",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A live browser tab for every Claude Code (and MCP) session. The push MCP tool appends HTML cards to a scrolling feed you keep open in split-screen.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,6 +36,7 @@
36
36
  "scripts/easel-session-id.mjs",
37
37
  "skills/",
38
38
  "README.md",
39
+ "CHANGELOG.md",
39
40
  "LICENCE"
40
41
  ],
41
42
  "scripts": {