@ammduncan/easel 0.6.1 → 0.6.2
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 +6 -0
- package/README.md +4 -2
- package/dist/cli.js +106 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to easel. This project adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 0.6.2 — 2026-06-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **`setup` now actually puts `easel` on PATH, making the documented bare CLI commands truthful.** The README's CLI section documents `easel open / update / restart / …`, but neither install path ever exposed the binary: `npx -y @ammduncan/easel setup` runs once from the ephemeral npx cache and leaves nothing behind, and clone installs' `bin/easel setup` never linked. Setup now closes the gap per install flavor: clone installs get `npm link` (skipped when `easel` already resolves); npx-cache runs get a real `npm install -g` pinned to their own version, then **delegate setup to the global copy** so the MCP/hook registrations point at paths that survive npx cache pruning (the cache-pruning drift left one machine's MCP pinned to a stale 0.5.1 cache while 0.6.x shipped). Failures degrade to a printed hint — setup never hard-fails on the PATH step.
|
|
9
|
+
- **`easel update` now works for npm installs.** It previously assumed a git checkout (`git pull` flavor, "clone installs only") and just failed elsewhere. Without a `.git` dir it now runs `npm install -g @ammduncan/easel@latest` and re-runs setup from the new copy.
|
|
10
|
+
|
|
5
11
|
## 0.6.1 — 2026-06-04
|
|
6
12
|
|
|
7
13
|
### Changed
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Requires Node 20+.
|
|
|
31
31
|
npx -y @ammduncan/easel setup
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
That registers the MCP at user scope, installs the `using-easel` skill so the agent knows when to push,
|
|
34
|
+
That registers the MCP at user scope, installs the `using-easel` skill so the agent knows when to push, adds the `SessionStart` hooks that resolve session IDs and auto-open the tab, and installs the package globally so the bare `easel` command works from any shell. Restart Claude Code and you're done.
|
|
35
35
|
|
|
36
36
|
### Cursor / Claude Desktop / Windsurf / Codex
|
|
37
37
|
|
|
@@ -81,6 +81,8 @@ npm install && npm run build
|
|
|
81
81
|
bin/easel setup
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
Setup runs `npm link`, so bare `easel` works from any shell afterwards.
|
|
85
|
+
|
|
84
86
|
## Tools the agent gets
|
|
85
87
|
|
|
86
88
|
| Tool | What it does |
|
|
@@ -134,7 +136,7 @@ easel config print / set { preset, theme, density }
|
|
|
134
136
|
easel setup Claude Code: hooks + MCP + skill
|
|
135
137
|
easel setup --client <name> register the MCP in another client (cursor, claude-desktop, windsurf, codex)
|
|
136
138
|
easel restart kill + respawn the HTTP server (handy after a build)
|
|
137
|
-
easel update git pull + build + setup
|
|
139
|
+
easel update clone installs: git pull + build + setup · npm installs: npm install -g @latest + setup
|
|
138
140
|
easel server run the HTTP server in the foreground (debug)
|
|
139
141
|
easel version
|
|
140
142
|
```
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { copyFileSync, mkdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { ensureHttpServer, readLock } from "./server-manager.js";
|
|
@@ -11,6 +11,7 @@ import { registerSession } from "./session-store.js";
|
|
|
11
11
|
import { listClients, setupClient, } from "./client-setup.js";
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const PROJECT_ROOT = resolve(__dirname, "..");
|
|
14
|
+
const NPM_PKG = "@ammduncan/easel";
|
|
14
15
|
function help() {
|
|
15
16
|
console.log(`easel — live browser feed for Claude Code for Claude Code sessions
|
|
16
17
|
|
|
@@ -28,7 +29,7 @@ Usage:
|
|
|
28
29
|
easel setup --client claude-desktop register MCP in ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
29
30
|
easel setup --client windsurf register MCP in ~/.codeium/windsurf/mcp_config.json
|
|
30
31
|
easel setup --client codex register MCP in ~/.codex/config.toml + copy skill to ~/.codex/skills/
|
|
31
|
-
easel update git pull +
|
|
32
|
+
easel update clone installs: git pull + build + setup · npm installs: npm install -g @latest + setup
|
|
32
33
|
easel mcp run the stdio MCP server in the foreground (used by clients)
|
|
33
34
|
easel restart kill the running HTTP server and respawn it (picks up new builds/paths)
|
|
34
35
|
easel server run the HTTP server in the foreground (debug)
|
|
@@ -97,7 +98,91 @@ function openInBrowser(url) {
|
|
|
97
98
|
console.error(`[easel] couldn't open browser: ${err.message}`);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
101
|
+
function binResolvesOnPath() {
|
|
102
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
103
|
+
try {
|
|
104
|
+
const r = spawnSync(cmd, ["easel"], { encoding: "utf-8" });
|
|
105
|
+
return r.status === 0 && r.stdout.trim().length > 0;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function pkgVersion() {
|
|
112
|
+
try {
|
|
113
|
+
const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, "package.json"), "utf-8"));
|
|
114
|
+
return pkg.version ?? "latest";
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return "latest";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function globalPkgDir() {
|
|
121
|
+
try {
|
|
122
|
+
const r = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
|
|
123
|
+
if (r.status !== 0)
|
|
124
|
+
return null;
|
|
125
|
+
const dir = join(r.stdout.trim(), NPM_PKG);
|
|
126
|
+
return existsSync(dir) ? dir : null;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Re-runs setup from the globally installed copy so its registrations point at
|
|
133
|
+
// the global paths. Returns false if there is no global copy to delegate to.
|
|
134
|
+
function rerunSetupFromGlobal() {
|
|
135
|
+
const dir = globalPkgDir();
|
|
136
|
+
if (!dir) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const r = spawnSync("node", [join(dir, "dist", "cli.js"), "setup"], {
|
|
140
|
+
stdio: "inherit",
|
|
141
|
+
env: { ...process.env, EASEL_SETUP_CHILD: "1" },
|
|
142
|
+
});
|
|
143
|
+
return r.status === 0;
|
|
144
|
+
}
|
|
145
|
+
// Make bare `easel` work after setup, as the README documents. Clone installs
|
|
146
|
+
// get `npm link`. npx-cache installs get a real global install — the cache is
|
|
147
|
+
// pruned unpredictably — and setup is then re-run from the global copy so the
|
|
148
|
+
// MCP/hook registrations point at paths that survive pruning.
|
|
149
|
+
// Returns true when setup was fully delegated to the global copy.
|
|
150
|
+
function ensureBinOnPath() {
|
|
151
|
+
const inNpxCache = PROJECT_ROOT.split(sep).includes("_npx");
|
|
152
|
+
if (!inNpxCache) {
|
|
153
|
+
if (binResolvesOnPath()) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const r = spawnSync("npm", ["link", "--silent", "--no-audit", "--no-fund"], {
|
|
157
|
+
cwd: PROJECT_ROOT,
|
|
158
|
+
stdio: "ignore",
|
|
159
|
+
});
|
|
160
|
+
if (r.status === 0) {
|
|
161
|
+
console.log(" - linked `easel` into the global bin (npm link)");
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.warn(` - couldn't put \`easel\` on PATH — run \`npm link\` in ${PROJECT_ROOT}`);
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const version = pkgVersion();
|
|
169
|
+
console.log(`[easel] installing ${NPM_PKG}@${version} globally so \`easel\` is on PATH…`);
|
|
170
|
+
const installed = spawnSync("npm", ["install", "-g", "--silent", "--no-audit", "--no-fund", `${NPM_PKG}@${version}`], { stdio: "inherit" });
|
|
171
|
+
if (installed.status !== 0 || !rerunSetupFromGlobal()) {
|
|
172
|
+
console.warn(` - global install failed — \`easel\` won't be on PATH; run \`npm install -g ${NPM_PKG}\` manually`);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
100
177
|
function cmdSetup() {
|
|
178
|
+
// Put `easel` on PATH first — for npx-cache runs this delegates the whole
|
|
179
|
+
// setup to a freshly installed global copy (so registered paths outlive the
|
|
180
|
+
// cache), in which case there's nothing left to do here.
|
|
181
|
+
if (!process.env.EASEL_SETUP_CHILD) {
|
|
182
|
+
if (ensureBinOnPath()) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
101
186
|
mkdirSync(HOOK_DIR, { recursive: true });
|
|
102
187
|
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
103
188
|
const hookScript = resolve(PROJECT_ROOT, "scripts", "easel-session-id.mjs");
|
|
@@ -264,6 +349,25 @@ async function cmdConfig(args) {
|
|
|
264
349
|
function cmdUpdate() {
|
|
265
350
|
console.log("[easel] checking for updates…");
|
|
266
351
|
const run = (cmd, args) => spawnSync(cmd, args, { stdio: "inherit", cwd: PROJECT_ROOT });
|
|
352
|
+
// npm/global installs have no git checkout — update from the registry and
|
|
353
|
+
// re-run setup from the new copy so registrations track the new paths.
|
|
354
|
+
if (!existsSync(join(PROJECT_ROOT, ".git"))) {
|
|
355
|
+
const installed = run("npm", [
|
|
356
|
+
"install",
|
|
357
|
+
"-g",
|
|
358
|
+
"--no-audit",
|
|
359
|
+
"--no-fund",
|
|
360
|
+
`${NPM_PKG}@latest`,
|
|
361
|
+
]);
|
|
362
|
+
if (installed.status !== 0) {
|
|
363
|
+
console.error("[easel] npm install -g failed");
|
|
364
|
+
process.exitCode = 1;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
rerunSetupFromGlobal();
|
|
368
|
+
console.log("[easel] updated. Restart Claude Code to pick up tool/skill changes.");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
267
371
|
let r = run("git", ["fetch", "--quiet", "origin", "main"]);
|
|
268
372
|
if (r.status !== 0) {
|
|
269
373
|
console.error("[easel] git fetch failed");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ammduncan/easel",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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",
|