wisp-agents 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 +41 -0
- package/bin/mac-app.js +64 -0
- package/bin/wisp.js +3 -1
- package/electron/main.js +18 -1
- package/package.json +5 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `wisp-agents` are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.2.1] - 2026-10-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Desktop app: native right-click menu with spelling suggestions, Copy Link, undo/redo, cut/copy/paste and select all. Inspect Element appears only in dev mode.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- macOS: the Dock shows "wisp" with the wisp icon instead of "Electron". The app now launches from a renamed clone of `Electron.app`, cached per Electron version under `~/Library/Caches/wisp`.
|
|
17
|
+
|
|
18
|
+
## [0.2.0] - 2026-10-16
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `npx wisp-agents` opens the desktop app. Electron is now an optional dependency that is fetched on first launch. The CLI starts the app detached and falls back to the browser when Electron isn't available.
|
|
23
|
+
- `--browser` flag to keep the browser-only behaviour.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Parsing of the `wisp` fence is more robust: malformed blocks are salvaged instead of dropped, and the raw turn text is kept.
|
|
28
|
+
|
|
29
|
+
## [0.1.0] - 2026-10-16
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- First public release on npm as `wisp-agents`, with `wisp` and `wisp-agents` bins and a `--version` / `-v` flag.
|
|
34
|
+
- Launch Claude Code agents in the background and read the presentation they hand back: diffs, tests, commits, flows and questions.
|
|
35
|
+
- Session queue with follow-ups, answers to agent questions, terminal takeover and mark-as-done.
|
|
36
|
+
- Older turns are folded, with a floating collapse-all button and back-to-top.
|
|
37
|
+
|
|
38
|
+
[Unreleased]: https://github.com/M0xoo/wisp/compare/v0.2.1...HEAD
|
|
39
|
+
[0.2.1]: https://github.com/M0xoo/wisp/compare/v0.2.0...v0.2.1
|
|
40
|
+
[0.2.0]: https://github.com/M0xoo/wisp/compare/v0.1.0...v0.2.0
|
|
41
|
+
[0.1.0]: https://github.com/M0xoo/wisp/releases/tag/v0.1.0
|
package/bin/mac-app.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// macOS names the Dock tile, menu bar and Cmd-Tab entry after the running .app bundle's
|
|
2
|
+
// Info.plist, not app.setName(). So run Electron from a clone of Electron.app renamed to wisp
|
|
3
|
+
// (APFS clone: instant, no extra disk), cached per Electron version.
|
|
4
|
+
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const REV = 1; // bump when the patching below changes
|
|
12
|
+
const ICON = fileURLToPath(new URL('../electron/icon.png', import.meta.url));
|
|
13
|
+
|
|
14
|
+
// electronBin: …/Electron.app/Contents/MacOS/Electron → the same binary inside wisp.app, or the
|
|
15
|
+
// original path if anything goes wrong (worst case the Dock says "Electron" again).
|
|
16
|
+
export function wispAppBinary(electronBin) {
|
|
17
|
+
try {
|
|
18
|
+
const src = path.resolve(electronBin, '../../..');
|
|
19
|
+
if (!src.endsWith('.app')) return electronBin;
|
|
20
|
+
const version = readFileSync(path.join(src, '../version'), 'utf8').trim();
|
|
21
|
+
const root = path.join(os.homedir(), 'Library/Caches/wisp');
|
|
22
|
+
const app = path.join(root, `electron-${version}-r${REV}`, 'wisp.app');
|
|
23
|
+
const bin = path.join(app, 'Contents/MacOS', path.basename(electronBin));
|
|
24
|
+
if (existsSync(bin)) return bin;
|
|
25
|
+
|
|
26
|
+
mkdirSync(root, { recursive: true });
|
|
27
|
+
const tmp = mkdtempSync(path.join(root, 'build-'));
|
|
28
|
+
const out = path.join(tmp, 'wisp.app');
|
|
29
|
+
try {
|
|
30
|
+
execFileSync('cp', ['-cR', src, out], { stdio: 'ignore' });
|
|
31
|
+
} catch {
|
|
32
|
+
execFileSync('cp', ['-R', src, out], { stdio: 'ignore' }); // not APFS
|
|
33
|
+
}
|
|
34
|
+
const plist = path.join(out, 'Contents/Info.plist');
|
|
35
|
+
const set = (key, value) => execFileSync('/usr/libexec/PlistBuddy', ['-c', `Set :${key} ${value}`, plist]);
|
|
36
|
+
set('CFBundleName', 'wisp');
|
|
37
|
+
set('CFBundleDisplayName', 'wisp');
|
|
38
|
+
set('CFBundleIdentifier', 'dev.wisp.app');
|
|
39
|
+
try {
|
|
40
|
+
makeIcns(path.join(out, 'Contents/Resources/wisp.icns'), tmp);
|
|
41
|
+
set('CFBundleIconFile', 'wisp.icns');
|
|
42
|
+
} catch {}
|
|
43
|
+
|
|
44
|
+
mkdirSync(path.dirname(app), { recursive: true });
|
|
45
|
+
rmSync(app, { recursive: true, force: true });
|
|
46
|
+
renameSync(out, app);
|
|
47
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
48
|
+
return bin;
|
|
49
|
+
} catch {
|
|
50
|
+
return electronBin;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function makeIcns(dest, tmp) {
|
|
55
|
+
const set = path.join(tmp, 'wisp.iconset');
|
|
56
|
+
mkdirSync(set);
|
|
57
|
+
for (const s of [16, 32, 128, 256, 512]) {
|
|
58
|
+
for (const [scale, suffix] of [[1, ''], [2, '@2x']]) {
|
|
59
|
+
const px = String(s * scale);
|
|
60
|
+
execFileSync('sips', ['-z', px, px, ICON, '--out', path.join(set, `icon_${s}x${s}${suffix}.png`)], { stdio: 'ignore' });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
execFileSync('iconutil', ['-c', 'icns', set, '-o', dest], { stdio: 'ignore' });
|
|
64
|
+
}
|
package/bin/wisp.js
CHANGED
|
@@ -14,6 +14,7 @@ import { existsSync } from 'node:fs';
|
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { wispAppBinary } from './mac-app.js';
|
|
17
18
|
|
|
18
19
|
const argv = process.argv.slice(2);
|
|
19
20
|
const flags = { open: true, browser: false, port: Number(process.env.WISP_PORT) || 4477, mode: null };
|
|
@@ -94,7 +95,8 @@ if (flags.open) {
|
|
|
94
95
|
function electronBinary() {
|
|
95
96
|
try {
|
|
96
97
|
const bin = createRequire(import.meta.url)('electron'); // the electron package exports its binary path
|
|
97
|
-
|
|
98
|
+
if (typeof bin !== 'string' || !existsSync(bin)) return null;
|
|
99
|
+
return process.platform === 'darwin' ? wispAppBinary(bin) : bin;
|
|
98
100
|
} catch {
|
|
99
101
|
return null;
|
|
100
102
|
}
|
package/electron/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// wisp desktop shell. The Node server runs inside Electron's main process and
|
|
2
2
|
// the window loads the same UI the browser mode serves.
|
|
3
3
|
|
|
4
|
-
import { app, BrowserWindow, shell, nativeTheme, Menu, dialog, ipcMain } from 'electron';
|
|
4
|
+
import { app, BrowserWindow, shell, nativeTheme, Menu, dialog, ipcMain, clipboard } from 'electron';
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
@@ -76,6 +76,23 @@ function createWindow() {
|
|
|
76
76
|
app.setBadgeCount(n);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
// Electron has no right-click menu by default: give text and links the usual one.
|
|
80
|
+
win.webContents.on('context-menu', (_, p) => {
|
|
81
|
+
const f = p.editFlags;
|
|
82
|
+
const items = [
|
|
83
|
+
...p.dictionarySuggestions.map((w) => ({ label: w, click: () => win.webContents.replaceMisspelling(w) })),
|
|
84
|
+
...(p.misspelledWord ? [{ type: 'separator' }] : []),
|
|
85
|
+
...(p.linkURL ? [{ label: 'Copy Link', click: () => clipboard.writeText(p.linkURL) }, { type: 'separator' }] : []),
|
|
86
|
+
...(p.isEditable ? [{ role: 'undo', enabled: f.canUndo }, { role: 'redo', enabled: f.canRedo }, { type: 'separator' }] : []),
|
|
87
|
+
...(p.isEditable ? [{ role: 'cut', enabled: f.canCut }] : []),
|
|
88
|
+
...(p.isEditable || p.selectionText ? [{ role: 'copy', enabled: f.canCopy }] : []),
|
|
89
|
+
...(p.isEditable ? [{ role: 'paste', enabled: f.canPaste }, { role: 'pasteAndMatchStyle', enabled: f.canPaste }] : []),
|
|
90
|
+
{ role: 'selectAll', enabled: f.canSelectAll },
|
|
91
|
+
...(DEV ? [{ type: 'separator' }, { label: 'Inspect Element', click: () => win.webContents.inspectElement(p.x, p.y) }] : []),
|
|
92
|
+
];
|
|
93
|
+
Menu.buildFromTemplate(items).popup({ window: win });
|
|
94
|
+
});
|
|
95
|
+
|
|
79
96
|
win.on('closed', () => (win = null));
|
|
80
97
|
}
|
|
81
98
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wisp-agents",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Launch coding agents, skip the transcript, read the presentation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/",
|
|
12
|
+
"CHANGELOG.md",
|
|
12
13
|
"electron/",
|
|
13
14
|
"server/",
|
|
14
15
|
"web/"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|
|
17
18
|
"start": "node bin/wisp.js",
|
|
18
|
-
"app": "
|
|
19
|
-
"dev": "WISP_DEV=1
|
|
19
|
+
"app": "node bin/wisp.js",
|
|
20
|
+
"dev": "WISP_DEV=1 node bin/wisp.js",
|
|
20
21
|
"dev:web": "node --watch-path=server bin/wisp.js --no-open",
|
|
21
|
-
"prepublishOnly": "node --check bin/wisp.js && node -e \"import('./server/index.js')\" && node --check electron/main.js"
|
|
22
|
+
"prepublishOnly": "node --check bin/wisp.js && node --check bin/mac-app.js && node -e \"import('./server/index.js')\" && node --check electron/main.js"
|
|
22
23
|
},
|
|
23
24
|
"engines": {
|
|
24
25
|
"node": ">=20"
|