wisp-agents 0.5.0 → 0.7.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/CHANGELOG.md +36 -1
- package/README.md +71 -9
- package/bin/wisp.js +182 -69
- package/package.json +15 -4
- package/server/adapters/antigravity.js +155 -0
- package/server/adapters/index.js +6 -2
- package/server/client.js +177 -0
- package/server/index.js +157 -1
- package/server/mcp-wisp.js +164 -0
- package/server/openapi.js +204 -0
- package/server/preferences.js +296 -0
- package/server/render.js +100 -0
- package/server/runner.js +2 -1
- package/server/update.js +136 -0
- package/skills/wisp/SKILL.md +55 -0
- package/web/css/app.css +83 -1
- package/web/index.html +5 -0
- package/web/js/app.js +369 -23
- package/web/logos/antigravity-color.svg +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-10-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Update button next to Search when a newer `wisp-agents` is on npm (checked at start, then once a day while wisp stays open — survives sleep, and the UI re-asks on focus). One click runs `npm install -g wisp-agents@latest` (or relaunches through `npx …@latest`) and restarts wisp; it asks first if agents are running. Git checkouts just get the `git pull` hint. Opt out with `WISP_UPDATE_CHECK=0`.
|
|
13
|
+
- Rerun a task: right-click a card → **Rerun…** (or `⇧R`, or ⌘K → Rerun). A dialog opens with the original prompt (editable), attachments and mode, plus the harness / model / thinking-level picker. You can pick a different setup without changing the launcher's defaults. Rerunning starts a new session in the same folder and keeps the original.
|
|
14
|
+
- Learning from sessions: after a session goes quiet (90 s, or right away when marked done or archived), a background retro (same harness, read-only, no tools; Haiku on Claude Code) records your preferences and the agent's lessons (a mistake and the way that worked), globally and per project. Later turns get them in their system prompt. Stored in `~/.wisp/preferences.json`; API `GET/PUT /api/preferences`. See [Learning](README.md#learning).
|
|
15
|
+
- Memory view (Settings → *Memory* → Open, or ⌘K → Memory): a tab per scope with preferences and lessons, the session that taught each item, a token estimate, and inline edit / add / forget. The *Retros* tab is a timeline of every retro run (last 30), including *nothing new* and failures with the reason, plus a live entry while one runs. Switching Memory off stops both learning and applying.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- The card you were reading stays in view after collapsing a section, and the open card is followed when it moves to another section.
|
|
20
|
+
|
|
21
|
+
## [0.6.0] - 2026-10-17
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Antigravity CLI adapter (`agy --input-format stream-json --output-format stream-json`). Plan → `--mode plan`, Edits → `--mode accept-edits`, Full auto → `--dangerously-skip-permissions`; models from `agy models`, `--effort low|medium|high`, follow-ups via `--conversation`. Like Cursor it has no flag to inject MCP servers, so Antigravity agents ask with a `question` block. Override the binary with `WISP_ANTIGRAVITY_BIN`.
|
|
26
|
+
- Integrations: wisp can now be driven by other agents, editors and scripts. Reference: [docs/integrations.md](docs/integrations.md).
|
|
27
|
+
- `wisp mcp`: a stdio MCP server with `wisp_launch`, `wisp_wait`, `wisp_get`, `wisp_reply`, `wisp_list`, `wisp_stop` and `wisp_agents`, so Claude Code, Codex, opencode, Cursor or Claude Desktop can hand tasks to wisp and read the results.
|
|
28
|
+
- Agent skill (`skills/wisp/SKILL.md`), installed with `wisp skill install [--agent claude|codex|opencode|all] [--project]`.
|
|
29
|
+
- Scripting CLI: `wisp ls`, `show`, `wait`, `reply`, `stop`, plus `--json`, `--wait`, `--cwd`, `--model` and `--effort` on launches. Use `wisp -- …` for a task whose first word is a command name.
|
|
30
|
+
- JS SDK: `import { WispClient } from 'wisp-agents'`. It connects to the running wisp (or starts a headless one) and covers launch, wait, reply, presentation, uploads and the event stream. `wisp-agents/render`, `/protocol` and `/server` are exported too.
|
|
31
|
+
- HTTP API: `GET /api/health`, `GET /api/openapi.json` (OpenAPI 3.1), `GET /api/sessions/:id/wait` (long-poll until it finishes or asks), `GET /api/sessions/:id/presentation` (blocks + markdown, `?format=markdown`), and `state`/`agent`/`project`/`cwd`/`archived`/`limit` filters on `GET /api/sessions`.
|
|
32
|
+
- `~/.wisp/server.json` advertises the running server's URL, port and pid.
|
|
33
|
+
- Webhooks (`PATCH /api/config {"webhooks": [...]}`): POST `session.finished` / `session.waiting` with the presentation as markdown.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Clicking a harness in the harness · model picker (or Enter on it) switches to it with the model and thinking level shown, instead of only previewing its models.
|
|
38
|
+
- Antigravity models are listed with newer `agy` builds that print tab-separated `agy models` output.
|
|
39
|
+
- A harness CLI installed while wisp is running is detected without restarting wisp.
|
|
40
|
+
|
|
8
41
|
## [0.5.0] - 2026-10-17
|
|
9
42
|
|
|
10
43
|
### Added
|
|
@@ -89,7 +122,9 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
89
122
|
- Session queue with follow-ups, answers to agent questions, terminal takeover and mark-as-done.
|
|
90
123
|
- Older turns are folded, with a floating collapse-all button and back-to-top.
|
|
91
124
|
|
|
92
|
-
[Unreleased]: https://github.com/M0xoo/wisp/compare/v0.
|
|
125
|
+
[Unreleased]: https://github.com/M0xoo/wisp/compare/v0.7.0...HEAD
|
|
126
|
+
[0.7.0]: https://github.com/M0xoo/wisp/compare/v0.6.0...v0.7.0
|
|
127
|
+
[0.6.0]: https://github.com/M0xoo/wisp/compare/v0.5.0...v0.6.0
|
|
93
128
|
[0.5.0]: https://github.com/M0xoo/wisp/compare/v0.4.0...v0.5.0
|
|
94
129
|
[0.4.0]: https://github.com/M0xoo/wisp/compare/v0.3.0...v0.4.0
|
|
95
130
|
[0.3.0]: https://github.com/M0xoo/wisp/compare/v0.2.2...v0.3.0
|
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ Launch coding agents in the background, skip the transcript, and read the presen
|
|
|
13
13
|
[](https://nodejs.org)
|
|
14
14
|
[](https://www.electronjs.org)
|
|
15
15
|
[](package.json)
|
|
16
|
-
[](#adding-a-harness)
|
|
16
|
+
[](#adding-a-harness)
|
|
17
17
|
|
|
18
|
-
[Quick start](#quick-start) · [Features](#features) · [CLI](#cli) · [Shortcuts](#keyboard-shortcuts) · [Architecture](#architecture) · [Adding a harness](#adding-a-harness) · [Roadmap](#roadmap)
|
|
18
|
+
[Quick start](#quick-start) · [Features](#features) · [CLI](#cli) · [Integrations](#integrations) · [Shortcuts](#keyboard-shortcuts) · [Architecture](#architecture) · [Adding a harness](#adding-a-harness) · [Roadmap](#roadmap)
|
|
19
19
|
|
|
20
20
|
</div>
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ Launch coding agents in the background, skip the transcript, and read the presen
|
|
|
23
23
|
|
|
24
24
|
## Why wisp?
|
|
25
25
|
|
|
26
|
-
Agent transcripts are long, and you rarely need them. wisp runs agent harnesses (Claude Code, Codex, opencode, and
|
|
26
|
+
Agent transcripts are long, and you rarely need them. wisp runs agent harnesses (Claude Code, Codex, opencode, Cursor, and Antigravity) on your projects in the background. When an agent finishes, it hands you a short, animated **presentation**: diffs, test results, commits, flows, and decisions. The session then waits in your queue.
|
|
27
27
|
|
|
28
28
|
From there you can **follow up**, **answer its questions**, **take it over** in your terminal, or **mark it done**.
|
|
29
29
|
|
|
@@ -44,15 +44,17 @@ From there you can **follow up**, **answer its questions**, **take it over** in
|
|
|
44
44
|
| 📥 **A queue** | Sessions sort into *Needs you*, *Working* (with a live one-line status), and *Done*. Archive what you don't need to see. |
|
|
45
45
|
| 💬 **Follow-ups that interrupt** | A follow-up sent mid-run interrupts the agent and resumes it with your message. Turn on **Queue** to wait for the run to finish instead; queued messages can be cancelled. |
|
|
46
46
|
| 🔎 **Command palette** | `⌘K` / `Ctrl+K` fuzzy-searches sessions, projects, and actions. |
|
|
47
|
-
| 🤖 **Any harness** | Claude Code, Codex, opencode, or
|
|
47
|
+
| 🤖 **Any harness** | Claude Code, Codex, opencode, Cursor CLI, or Antigravity CLI. Pick the harness, model and thinking level together in the launcher; both are remembered per harness. Each card shows the harness it ran on. |
|
|
48
48
|
| 🖥️ **Take over** | Opens your terminal with the harness's resume command (`claude --resume <id>`, `codex resume <id>`, …) in the project folder. Works with Terminal, iTerm, Ghostty, Warp, cmux, kitty, WezTerm, GNOME Terminal, Konsole, Alacritty, Windows Terminal, PowerShell, or a custom command. |
|
|
49
49
|
| 📎 **Attachments** | Paste screenshots or drop images, video, logs, or PDFs into the launcher. |
|
|
50
|
+
| 🧠 **Learns as you go** | After a session goes quiet, a background retro records your preferences and the agent's lessons (a mistake and the way that worked), globally and per project. Later runs get them in their prompt. See [Learning](#learning). |
|
|
50
51
|
| 🔐 **Permission modes** | Choose **Plan** (read-only), **Edits** (file edits auto-accepted), or **Full auto** for each launch. `Shift+Tab` in the prompt cycles modes. |
|
|
52
|
+
| 🔌 **Integrations** | An MCP server (`wisp mcp`), an agent skill, a scriptable CLI, a JS SDK, an OpenAPI-described HTTP API, and webhooks. Hand tasks to wisp from Claude Code, Codex, opencode, Cursor, scripts or CI. |
|
|
51
53
|
| 🪶 **No runtime dependencies** | The server uses only Node built-ins. Electron is needed only for the desktop app. |
|
|
52
54
|
|
|
53
55
|
## Quick start
|
|
54
56
|
|
|
55
|
-
> **Requirements:** macOS, Linux, or Windows; Node.js ≥ 20; and at least one harness installed and signed in: [Claude Code](https://claude.com/claude-code) (`claude`), [Codex](https://github.com/openai/codex) (`codex`), [opencode](https://opencode.ai) (`opencode`),
|
|
57
|
+
> **Requirements:** macOS, Linux, or Windows; Node.js ≥ 20; and at least one harness installed and signed in: [Claude Code](https://claude.com/claude-code) (`claude`), [Codex](https://github.com/openai/codex) (`codex`), [opencode](https://opencode.ai) (`opencode`), [Cursor CLI](https://cursor.com/cli) (`cursor-agent`), or [Antigravity CLI](https://antigravity.google/docs/cli/headless/) (`agy`). Override a binary path with `WISP_CLAUDE_BIN`, `WISP_CODEX_BIN`, `WISP_OPENCODE_BIN`, `WISP_CURSOR_BIN`, or `WISP_ANTIGRAVITY_BIN`.
|
|
56
58
|
|
|
57
59
|
```sh
|
|
58
60
|
npx wisp-agents # open the wisp desktop app
|
|
@@ -101,20 +103,59 @@ wisp # open wisp (or focus the running one)
|
|
|
101
103
|
wisp "fix the flaky auth test" # launch a task in this folder (it becomes a project)
|
|
102
104
|
wisp add [path] # add a folder as a project (default: cwd)
|
|
103
105
|
wisp --mode plan "audit deps" # permission mode: plan | edits | auto
|
|
104
|
-
wisp --agent codex "add a test" # harness: claude | codex | opencode | cursor
|
|
106
|
+
wisp --agent codex "add a test" # harness: claude | codex | opencode | cursor | antigravity
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Scripting (every command takes `--json`):
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
wisp "add a test" --wait # block until it finishes, print the presentation as markdown
|
|
113
|
+
wisp ls --state running,waiting # list sessions
|
|
114
|
+
wisp show <id> # state + latest presentation
|
|
115
|
+
wisp wait <id> # until it finishes or asks you something
|
|
116
|
+
wisp reply <id> "also do X" # follow up (answers a pending question)
|
|
117
|
+
wisp stop <id>
|
|
118
|
+
wisp mcp # MCP server for other agents and editors
|
|
119
|
+
wisp skill install # install the wisp skill for Claude Code (--agent all for Codex/opencode too)
|
|
105
120
|
```
|
|
106
121
|
|
|
107
122
|
| Option | Description |
|
|
108
123
|
|---|---|
|
|
109
124
|
| `--port`, `-p N` | Port to use (default `4477`, or `WISP_PORT`) |
|
|
110
125
|
| `--mode`, `-m` | `plan` · `edits` · `auto` |
|
|
111
|
-
| `--agent`, `-a` | `claude` · `codex` · `opencode` · `cursor` (default: the agent last picked in the launcher) |
|
|
126
|
+
| `--agent`, `-a` | `claude` · `codex` · `opencode` · `cursor` · `antigravity` (default: the agent last picked in the launcher) |
|
|
127
|
+
| `--model` · `--effort` | Model id and thinking level for the launch |
|
|
128
|
+
| `--cwd`, `-C DIR` | Project folder (default: the current one) |
|
|
129
|
+
| `--wait`, `-w` | Wait for the run to finish or ask, then print the presentation |
|
|
130
|
+
| `--json` | Machine-readable output |
|
|
112
131
|
| `--browser`, `-b` | Use the browser instead of the desktop app |
|
|
113
132
|
| `--no-open` | Just run the server; open nothing |
|
|
114
133
|
| `--version`, `-v` | Print the version |
|
|
115
134
|
|
|
116
135
|
> `npm install -g wisp-agents` (or `npm link` from a clone) puts `wisp` on your `PATH`. Without installing, use `npx wisp-agents …` with the same arguments.
|
|
117
136
|
|
|
137
|
+
## Integrations
|
|
138
|
+
|
|
139
|
+
wisp is a local server, and the app is just one client of it. Other agents, editors and scripts can launch tasks into the same queue and read the presentations back. Full reference: [docs/integrations.md](docs/integrations.md).
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
claude mcp add wisp -- npx -y wisp-agents mcp # Claude Code can now delegate with wisp_launch / wisp_wait / wisp_reply
|
|
143
|
+
wisp skill install --agent all # teach Claude Code, Codex and opencode when and how to use wisp
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
import { WispClient } from 'wisp-agents';
|
|
148
|
+
const wisp = await WispClient.connect();
|
|
149
|
+
const s = await wisp.launch({ prompt: 'fix the flaky auth test', cwd: '/path/to/repo' });
|
|
150
|
+
console.log((await wisp.wait(s.id)).state, (await wisp.presentation(s.id)).markdown);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- **MCP** (`wisp mcp`): `wisp_launch`, `wisp_wait`, `wisp_get`, `wisp_reply`, `wisp_list`, `wisp_stop`, `wisp_agents`. Config snippets for Claude Code, Codex, opencode, Cursor and Claude Desktop are in the docs.
|
|
154
|
+
- **Skill** ([`skills/wisp/SKILL.md`](skills/wisp/SKILL.md)): when to delegate, how to brief, and to pass a waiting agent's question on to you instead of answering it.
|
|
155
|
+
- **HTTP API**: `GET /api/openapi.json`. The new routes are `/api/health`, `/api/sessions/:id/wait` (long-poll), `/api/sessions/:id/presentation` (blocks + markdown), and filters on `/api/sessions`.
|
|
156
|
+
- **Discovery**: the running server writes `~/.wisp/server.json` (`url`, `port`, `pid`, `version`).
|
|
157
|
+
- **Webhooks**: `PATCH /api/config {"webhooks":[…]}`. wisp POSTs `session.finished` and `session.waiting` with the presentation as markdown.
|
|
158
|
+
|
|
118
159
|
## Keyboard shortcuts
|
|
119
160
|
|
|
120
161
|
| Key | Action |
|
|
@@ -146,6 +187,7 @@ web/ (UI) ⇄ HTTP + SSE ⇄ server/ ── adapters/claude.js ── claude -
|
|
|
146
187
|
├─ adapters/codex.js ── codex exec --json
|
|
147
188
|
├─ adapters/opencode.js ── opencode run --format json
|
|
148
189
|
├─ adapters/cursor.js ── cursor-agent -p --output-format stream-json
|
|
190
|
+
├─ adapters/antigravity.js ── agy --output-format stream-json
|
|
149
191
|
└─ mcp-ask.js (ask_user MCP tool, injected per run)
|
|
150
192
|
```
|
|
151
193
|
|
|
@@ -155,6 +197,10 @@ wisp/
|
|
|
155
197
|
│ └── wisp.js # CLI: starts the server or talks to the running one
|
|
156
198
|
├── server/
|
|
157
199
|
│ ├── index.js # HTTP API, SSE stream, static UI, uploads
|
|
200
|
+
│ ├── openapi.js # OpenAPI document for the public API
|
|
201
|
+
│ ├── client.js # JS SDK (wisp-agents/client), used by the CLI and MCP server
|
|
202
|
+
│ ├── mcp-wisp.js # `wisp mcp`: wisp as tools for other agents
|
|
203
|
+
│ ├── render.js # presentation → markdown
|
|
158
204
|
│ ├── runner.js # session lifecycle, follow-ups, event normalization
|
|
159
205
|
│ ├── protocol.js # system prompt + ```wisp block parser (markdown fallback)
|
|
160
206
|
│ ├── mcp-ask.js # ask_user MCP server
|
|
@@ -167,7 +213,9 @@ wisp/
|
|
|
167
213
|
│ ├── claude.js # Claude Code adapter
|
|
168
214
|
│ ├── codex.js # Codex CLI adapter
|
|
169
215
|
│ ├── opencode.js # opencode adapter
|
|
170
|
-
│
|
|
216
|
+
│ ├── cursor.js # Cursor CLI adapter
|
|
217
|
+
│ └── antigravity.js # Antigravity CLI adapter
|
|
218
|
+
├── skills/wisp/SKILL.md # agent skill: how other agents use wisp
|
|
171
219
|
├── web/ # vanilla JS UI, no build step
|
|
172
220
|
│ ├── index.html
|
|
173
221
|
│ ├── css/app.css
|
|
@@ -201,6 +249,20 @@ If the harness has no system-prompt flag, prepend `systemPrompt` to the first pr
|
|
|
201
249
|
- [x] Codex adapter
|
|
202
250
|
- [x] opencode adapter
|
|
203
251
|
- [x] Cursor CLI adapter
|
|
252
|
+
- [x] Antigravity CLI adapter
|
|
253
|
+
|
|
254
|
+
## Learning
|
|
255
|
+
|
|
256
|
+
After a session has been quiet for 90 seconds (or right away when you mark it done or archive it), wisp runs a short **retro**: the same harness, read-only and without tools (Claude Code runs it on Haiku), reads a digest of the turns it hasn't seen yet. The digest holds your requests and follow-ups, questions and answers, failed tool calls and the commands run around them, and how each run ended. The retro then updates four small lists:
|
|
257
|
+
|
|
258
|
+
| | Everywhere | This project |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| **Preferences**: how you like agents to work | report style, git habits, "never …" | tooling, conventions, areas to leave alone |
|
|
261
|
+
| **Lessons**: a mistake and the way that worked | "use `python3`, not `python`" | "run tests with `node --test`; `npm test -- x` runs everything" |
|
|
262
|
+
|
|
263
|
+
The retro gets the current lists along with the digest, so when you change your mind it rewrites or drops the old item instead of adding a contradicting one. Each list holds at most 10 items of 180 characters or less, and the digest is capped at 10k characters, so the prompt block stays small. Every later turn gets the lists in its system prompt, and the current request always wins over them.
|
|
264
|
+
|
|
265
|
+
**Memory** (Settings → *Memory*, or `⌘K` → *Memory*) has a tab for each scope (Everywhere, then each project) with its preferences and lessons, the retro that taught each one, and an estimate of the tokens it adds to the prompt. You can edit, add or forget any item. The *Retros* tab is a timeline of every run: what it added or removed, *nothing new*, or why it failed. Switching Memory off stops both learning and applying. The data lives in `~/.wisp/preferences.json`, and the API is `GET/PUT /api/preferences`.
|
|
204
266
|
|
|
205
267
|
## Telemetry
|
|
206
268
|
|
|
@@ -228,7 +290,7 @@ npm test # adapters, process launching, terminals, and the full ask_user
|
|
|
228
290
|
npm run test:real # smoke test against the real CLIs installed on this machine
|
|
229
291
|
```
|
|
230
292
|
|
|
231
|
-
`npm test` needs no network or accounts. A fake CLI (`test/fixtures/fake-agent.mjs`) speaks each harness's real output format, edits files, and calls `ask_user` through the real MCP bridge. On Windows the fakes are installed as npm-style `.cmd` shims, which is how npm installs the real CLIs. CI (`.github/workflows/test.yml`) runs the suite on Linux, macOS, and Windows with Node 20 and 22, then runs `test:real` against freshly installed Claude Code, Codex, opencode, and
|
|
293
|
+
`npm test` needs no network or accounts. A fake CLI (`test/fixtures/fake-agent.mjs`) speaks each harness's real output format, edits files, and calls `ask_user` through the real MCP bridge. On Windows the fakes are installed as npm-style `.cmd` shims, which is how npm installs the real CLIs. CI (`.github/workflows/test.yml`) runs the suite on Linux, macOS, and Windows with Node 20 and 22, then runs `test:real` against freshly installed Claude Code, Codex, opencode, Cursor, and Antigravity CLIs.
|
|
232
294
|
|
|
233
295
|
## License
|
|
234
296
|
|
package/bin/wisp.js
CHANGED
|
@@ -1,100 +1,213 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// wisp — launcher + queue for coding agents.
|
|
3
3
|
//
|
|
4
|
-
// wisp
|
|
5
|
-
// wisp "fix the tests"
|
|
6
|
-
// wisp add [path]
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// --browser use the browser instead of the desktop app
|
|
11
|
-
// --no-open just run the server, open nothing
|
|
4
|
+
// wisp open the wisp desktop app (or focus the running one)
|
|
5
|
+
// wisp "fix the tests" launch a wisp in the current folder
|
|
6
|
+
// wisp add [path] add a folder as a project (default: current folder)
|
|
7
|
+
// wisp ls | show | wait | reply | stop script sessions (see --help)
|
|
8
|
+
// wisp mcp stdio MCP server, so other agents and editors can use wisp
|
|
9
|
+
// wisp skill [install] the agent skill that teaches other agents to use wisp
|
|
12
10
|
|
|
13
11
|
import { exec, spawn } from 'node:child_process';
|
|
14
|
-
import { existsSync } from 'node:fs';
|
|
12
|
+
import fs, { existsSync } from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
15
14
|
import { createRequire } from 'node:module';
|
|
16
15
|
import path from 'node:path';
|
|
17
16
|
import { fileURLToPath } from 'node:url';
|
|
18
17
|
import { wispAppBinary } from './mac-app.js';
|
|
19
18
|
|
|
19
|
+
const HELP = `usage:
|
|
20
|
+
wisp open wisp
|
|
21
|
+
wisp "task" launch a task in this folder
|
|
22
|
+
wisp add [path] add a project
|
|
23
|
+
wisp ls list sessions (--state running,waiting --limit N)
|
|
24
|
+
wisp show <id> state + latest presentation as markdown
|
|
25
|
+
wisp wait <id> block until it finishes or asks you something
|
|
26
|
+
wisp reply <id> "text" follow up (answers a pending question) (--queue)
|
|
27
|
+
wisp stop <id> stop a run
|
|
28
|
+
wisp mcp run the MCP server (stdio) for other agents and editors
|
|
29
|
+
wisp skill [install] print / install the wisp agent skill (--agent claude|codex|opencode|all --project)
|
|
30
|
+
wisp -- "ls the repo" a task that starts with a command name
|
|
31
|
+
|
|
32
|
+
launch options: --mode plan|edits|auto --agent claude|codex|opencode|cursor|antigravity --model ID --effort LEVEL
|
|
33
|
+
--cwd DIR --wait --json
|
|
34
|
+
options: --port N --browser --no-open --json --version`;
|
|
35
|
+
|
|
36
|
+
const COMMANDS = new Set(['add', 'ls', 'list', 'show', 'wait', 'reply', 'stop', 'mcp', 'skill']);
|
|
20
37
|
const argv = process.argv.slice(2);
|
|
21
|
-
const flags = { open: true, browser: false, port: Number(process.env.WISP_PORT) || 4477, mode: null, agent: null };
|
|
38
|
+
const flags = { open: true, browser: false, port: Number(process.env.WISP_PORT) || 4477, mode: null, agent: null, model: null, effort: null, cwd: null, json: false, wait: false, queue: undefined, state: null, limit: null, project: false };
|
|
22
39
|
const words = [];
|
|
40
|
+
let literal = false;
|
|
23
41
|
for (let i = 0; i < argv.length; i++) {
|
|
24
42
|
const a = argv[i];
|
|
25
|
-
if (
|
|
43
|
+
if (literal) words.push(a);
|
|
44
|
+
else if (a === '--') literal = words.length === 0 ? 'task' : true;
|
|
45
|
+
else if (a === '--no-open') flags.open = false;
|
|
26
46
|
else if (a === '--browser' || a === '-b') flags.browser = true;
|
|
27
47
|
else if (a === '--port' || a === '-p') flags.port = Number(argv[++i]);
|
|
28
48
|
else if (a === '--mode' || a === '-m') flags.mode = argv[++i];
|
|
29
49
|
else if (a === '--agent' || a === '-a') flags.agent = argv[++i];
|
|
50
|
+
else if (a === '--model') flags.model = argv[++i];
|
|
51
|
+
else if (a === '--effort') flags.effort = argv[++i];
|
|
52
|
+
else if (a === '--cwd' || a === '-C') flags.cwd = path.resolve(argv[++i].replace(/^~(?=$|[\\/])/, os.homedir()));
|
|
53
|
+
else if (a === '--state') flags.state = argv[++i];
|
|
54
|
+
else if (a === '--limit') flags.limit = Number(argv[++i]);
|
|
55
|
+
else if (a === '--json') flags.json = true;
|
|
56
|
+
else if (a === '--wait' || a === '-w') flags.wait = true;
|
|
57
|
+
else if (a === '--queue') flags.queue = true;
|
|
58
|
+
else if (a === '--project') flags.project = true;
|
|
30
59
|
else if (a === '--version' || a === '-v') {
|
|
31
|
-
|
|
32
|
-
console.log(JSON.parse(readFileSync(new URL('../package.json', import.meta.url))).version);
|
|
60
|
+
console.log(JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url))).version);
|
|
33
61
|
process.exit(0);
|
|
34
62
|
} else if (a === '--help' || a === '-h') {
|
|
35
|
-
console.log(
|
|
63
|
+
console.log(HELP);
|
|
36
64
|
process.exit(0);
|
|
37
65
|
} else words.push(a);
|
|
38
66
|
}
|
|
67
|
+
const command = literal !== 'task' && COMMANDS.has(words[0]) ? words[0] : null;
|
|
68
|
+
const cwd = flags.cwd || process.cwd();
|
|
69
|
+
const die = (msg) => {
|
|
70
|
+
console.error(` ✗ ${msg}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
};
|
|
39
73
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
await api('/config');
|
|
55
|
-
running = true;
|
|
56
|
-
} catch {}
|
|
57
|
-
|
|
58
|
-
// The desktop app (Electron is an optional dependency) runs the server in its own process, so
|
|
59
|
-
// it outlives this terminal. Without Electron, or with --browser/--no-open, serve from here.
|
|
60
|
-
// A task or `add` sent to an already-running wisp just goes over the API, no window.
|
|
61
|
-
const electron = flags.open && !flags.browser && (!running || !words.length) ? electronBinary() : null;
|
|
62
|
-
if (electron) {
|
|
63
|
-
const child = spawn(electron, [fileURLToPath(new URL('..', import.meta.url))], {
|
|
64
|
-
detached: true,
|
|
65
|
-
stdio: 'ignore',
|
|
66
|
-
env: { ...process.env, WISP_PORT: String(flags.port) },
|
|
67
|
-
});
|
|
68
|
-
child.unref();
|
|
69
|
-
if (!running) await waitForServer();
|
|
70
|
-
console.log(`\n ◌ wisp is open\n`);
|
|
71
|
-
} else if (!running) {
|
|
72
|
-
const { startServer } = await import('../server/index.js');
|
|
73
|
-
await startServer({ port: flags.port });
|
|
74
|
-
console.log(`\n ◌ wisp is floating at ${base}\n`);
|
|
74
|
+
// --- commands that only talk to the API (and start a headless wisp if none is running) ---
|
|
75
|
+
|
|
76
|
+
if (command === 'mcp') {
|
|
77
|
+
const { serveMcp } = await import('../server/mcp-wisp.js');
|
|
78
|
+
serveMcp(); // stdout belongs to the MCP protocol from here on
|
|
79
|
+
} else if (command === 'skill') {
|
|
80
|
+
skill(words[1]);
|
|
81
|
+
} else if (command && command !== 'add') {
|
|
82
|
+
await scripted(command, words.slice(1)).catch((e) => die(e.message));
|
|
83
|
+
} else if (!command && words.length && (flags.json || flags.wait)) {
|
|
84
|
+
await scripted('launch', words).catch((e) => die(e.message));
|
|
85
|
+
} else {
|
|
86
|
+
await interactive();
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
async function scripted(cmd, args) {
|
|
90
|
+
const { WispClient } = await import('../server/client.js');
|
|
91
|
+
const wisp = await WispClient.connect({ port: argv.some((a) => a === '--port' || a === '-p') ? flags.port : undefined });
|
|
92
|
+
const out = (v, text) => console.log(flags.json ? JSON.stringify(v, null, 2) : text);
|
|
93
|
+
const need = (v, what) => v || die(`${what} required — see wisp --help`);
|
|
94
|
+
const show = async (id, summary) => {
|
|
95
|
+
const p = await wisp.presentation(id);
|
|
96
|
+
const s = summary || (await wisp.sessions()).find((x) => x.id === id);
|
|
97
|
+
out({ ...s, presentation: p }, `◌ ${s?.title} [${s?.state}${summary?.timedOut ? ', still running' : ''}] ${id}\n\n${p.markdown}`);
|
|
98
|
+
};
|
|
99
|
+
switch (cmd) {
|
|
100
|
+
case 'launch': {
|
|
101
|
+
const s = await wisp.launch({ prompt: args.join(' '), cwd, agent: flags.agent || undefined, mode: flags.mode || undefined, model: flags.model ?? undefined, effort: flags.effort ?? undefined });
|
|
102
|
+
if (!flags.wait) return out(s, ` ◌ launched: ${s.title} (${s.id})`);
|
|
103
|
+
if (!flags.json) console.error(` ◌ launched: ${s.title} (${s.id}) — waiting…`);
|
|
104
|
+
return show(s.id, await wisp.wait(s.id));
|
|
105
|
+
}
|
|
106
|
+
case 'ls':
|
|
107
|
+
case 'list': {
|
|
108
|
+
const list = await wisp.sessions({ state: flags.state, limit: flags.limit, cwd: flags.cwd || undefined, archived: flags.state ? undefined : false });
|
|
109
|
+
return out(list, list.map((s) => `${s.id} ${s.state.padEnd(7)} ${s.agent.padEnd(8)} ${s.project.padEnd(16).slice(0, 16)} ${s.title}${s.question ? ` — asks: ${s.question}` : ''}`).join('\n') || 'No sessions.');
|
|
110
|
+
}
|
|
111
|
+
case 'show':
|
|
112
|
+
return show(need(args[0], 'session id'));
|
|
113
|
+
case 'wait': {
|
|
114
|
+
const id = need(args[0], 'session id');
|
|
115
|
+
return show(id, await wisp.wait(id));
|
|
116
|
+
}
|
|
117
|
+
case 'reply': {
|
|
118
|
+
const id = need(args[0], 'session id');
|
|
119
|
+
const r = await wisp.reply(id, need(args.slice(1).join(' ').trim(), 'text'), { queue: flags.queue });
|
|
120
|
+
if (!flags.wait) return out(r, r.answered ? ' ✓ answered' : r.interrupted ? ' ✓ interrupted; resuming with your message' : r.queued ? ' ✓ queued' : ' ✓ sent');
|
|
121
|
+
return show(id, await wisp.wait(id));
|
|
122
|
+
}
|
|
123
|
+
case 'stop':
|
|
124
|
+
return out(await wisp.stop(need(args[0], 'session id')), ' ✓ stopped');
|
|
125
|
+
}
|
|
92
126
|
}
|
|
93
127
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
128
|
+
// Installs skills/wisp/SKILL.md where each harness looks for skills.
|
|
129
|
+
function skill(sub) {
|
|
130
|
+
const src = fileURLToPath(new URL('../skills/wisp/SKILL.md', import.meta.url));
|
|
131
|
+
if (sub !== 'install') return process.stdout.write(fs.readFileSync(src, 'utf8'));
|
|
132
|
+
const home = os.homedir();
|
|
133
|
+
const base = flags.project ? cwd : null;
|
|
134
|
+
const targets = {
|
|
135
|
+
claude: base ? path.join(base, '.claude', 'skills') : path.join(home, '.claude', 'skills'),
|
|
136
|
+
codex: base ? path.join(base, '.codex', 'skills') : path.join(process.env.CODEX_HOME || path.join(home, '.codex'), 'skills'),
|
|
137
|
+
opencode: base ? path.join(base, '.opencode', 'skill') : path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'opencode', 'skill'),
|
|
138
|
+
};
|
|
139
|
+
const which = !flags.agent ? ['claude'] : flags.agent === 'all' ? Object.keys(targets) : [flags.agent];
|
|
140
|
+
for (const a of which) {
|
|
141
|
+
if (!targets[a]) die(`no skill location for ${a} (try claude, codex, opencode or all)`);
|
|
142
|
+
const dir = path.join(targets[a], 'wisp');
|
|
143
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
144
|
+
fs.copyFileSync(src, path.join(dir, 'SKILL.md'));
|
|
145
|
+
console.log(` ✓ ${a}: ${path.join(dir, 'SKILL.md')}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// --- the original flow: open the app, launch from the terminal, add projects ---
|
|
150
|
+
|
|
151
|
+
async function interactive() {
|
|
152
|
+
const base = `http://localhost:${flags.port}`;
|
|
153
|
+
const api = (p, body) =>
|
|
154
|
+
fetch(`${base}/api${p}`, {
|
|
155
|
+
method: body ? 'POST' : 'GET',
|
|
156
|
+
headers: { 'content-type': 'application/json' },
|
|
157
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
158
|
+
}).then(async (r) => {
|
|
159
|
+
const j = await r.json();
|
|
160
|
+
if (!r.ok) throw new Error(j.error || r.statusText);
|
|
161
|
+
return j;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
let running = false;
|
|
165
|
+
try {
|
|
166
|
+
await api('/config');
|
|
167
|
+
running = true;
|
|
168
|
+
} catch {}
|
|
169
|
+
|
|
170
|
+
// The desktop app (Electron is an optional dependency) runs the server in its own process, so
|
|
171
|
+
// it outlives this terminal. Without Electron, or with --browser/--no-open, serve from here.
|
|
172
|
+
// A task or `add` sent to an already-running wisp just goes over the API, no window.
|
|
173
|
+
const electron = flags.open && !flags.browser && (!running || !words.length) ? electronBinary() : null;
|
|
174
|
+
if (electron) {
|
|
175
|
+
const child = spawn(electron, [fileURLToPath(new URL('..', import.meta.url))], {
|
|
176
|
+
detached: true,
|
|
177
|
+
stdio: 'ignore',
|
|
178
|
+
env: { ...process.env, WISP_PORT: String(flags.port) },
|
|
179
|
+
});
|
|
180
|
+
child.unref();
|
|
181
|
+
if (!running) await waitForServer(api);
|
|
182
|
+
console.log(`\n ◌ wisp is open\n`);
|
|
183
|
+
} else if (!running) {
|
|
184
|
+
const { startServer } = await import('../server/index.js');
|
|
185
|
+
await startServer({ port: flags.port });
|
|
186
|
+
console.log(`\n ◌ wisp is floating at ${base}\n`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let openPath = '/';
|
|
190
|
+
if (command === 'add') {
|
|
191
|
+
const dir = path.resolve(words[1] || process.cwd());
|
|
192
|
+
const p = await api('/projects', { path: dir }).catch((e) => console.error(` ✗ ${e.message}`));
|
|
193
|
+
if (p) console.log(` ✓ project added: ${p.name} (${p.path})`);
|
|
194
|
+
if (running || electron) process.exit(0);
|
|
195
|
+
} else if (words.length) {
|
|
196
|
+
const cfg = await api('/config');
|
|
197
|
+
const agent = flags.agent || cfg.defaults.agent;
|
|
198
|
+
// Models are remembered per agent (ids aren't portable between harnesses).
|
|
199
|
+
const model = flags.model ?? cfg.defaults.models?.[agent] ?? (agent === cfg.defaults.agent ? cfg.defaults.model : '');
|
|
200
|
+
const s = await api('/sessions', { prompt: words.join(' '), cwd, agent, mode: flags.mode || cfg.defaults.mode, model, effort: flags.effort || '' });
|
|
201
|
+
console.log(` ◌ launched: ${s.title}`);
|
|
202
|
+
openPath = `/#/s/${s.id}`;
|
|
203
|
+
if (running || electron) process.exit(0); // the running wisp will show it in the queue
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (electron) process.exit(0);
|
|
207
|
+
if (flags.open) {
|
|
208
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start ""' : 'xdg-open';
|
|
209
|
+
exec(`${cmd} "${base}${openPath}"`);
|
|
210
|
+
}
|
|
98
211
|
}
|
|
99
212
|
|
|
100
213
|
function electronBinary() {
|
|
@@ -107,7 +220,7 @@ function electronBinary() {
|
|
|
107
220
|
}
|
|
108
221
|
}
|
|
109
222
|
|
|
110
|
-
async function waitForServer() {
|
|
223
|
+
async function waitForServer(api) {
|
|
111
224
|
for (let i = 0; i < 150; i++) {
|
|
112
225
|
try {
|
|
113
226
|
return await api('/config');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wisp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Launch coding agents, skip the transcript, read the presentation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"CHANGELOG.md",
|
|
13
13
|
"electron/",
|
|
14
14
|
"server/",
|
|
15
|
+
"skills/",
|
|
15
16
|
"web/"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
@@ -19,9 +20,9 @@
|
|
|
19
20
|
"app": "node bin/wisp.js",
|
|
20
21
|
"dev": "WISP_DEV=1 node bin/wisp.js",
|
|
21
22
|
"dev:web": "node --watch-path=server bin/wisp.js --no-open",
|
|
22
|
-
"test": "node --test test/proc.test.js test/terminal.test.js test/adapters.test.js test/e2e.test.js test/fuzzy.test.js",
|
|
23
|
+
"test": "node --test test/proc.test.js test/terminal.test.js test/adapters.test.js test/e2e.test.js test/fuzzy.test.js test/integrations.test.js test/update.test.js test/learn.test.js",
|
|
23
24
|
"test:real": "node test/real-clis.mjs",
|
|
24
|
-
"prepublishOnly": "node --check bin/wisp.js && node --check bin/mac-app.js && node -e \"import('./server/index.js')\" && node --check electron/main.js"
|
|
25
|
+
"prepublishOnly": "node --check bin/wisp.js && node --check bin/mac-app.js && node -e \"import('./server/index.js').then(() => import('./server/mcp-wisp.js'))\" && node --check electron/main.js"
|
|
25
26
|
},
|
|
26
27
|
"engines": {
|
|
27
28
|
"node": ">=20"
|
|
@@ -33,7 +34,9 @@
|
|
|
33
34
|
"ai",
|
|
34
35
|
"agents",
|
|
35
36
|
"cli",
|
|
36
|
-
"launcher"
|
|
37
|
+
"launcher",
|
|
38
|
+
"mcp",
|
|
39
|
+
"sdk"
|
|
37
40
|
],
|
|
38
41
|
"author": "M0xoo",
|
|
39
42
|
"license": "MIT",
|
|
@@ -46,6 +49,14 @@
|
|
|
46
49
|
"url": "https://github.com/M0xoo/wisp/issues"
|
|
47
50
|
},
|
|
48
51
|
"main": "electron/main.js",
|
|
52
|
+
"exports": {
|
|
53
|
+
".": "./server/client.js",
|
|
54
|
+
"./client": "./server/client.js",
|
|
55
|
+
"./server": "./server/index.js",
|
|
56
|
+
"./protocol": "./server/protocol.js",
|
|
57
|
+
"./render": "./server/render.js",
|
|
58
|
+
"./package.json": "./package.json"
|
|
59
|
+
},
|
|
49
60
|
"optionalDependencies": {
|
|
50
61
|
"electron": "^44.8.0"
|
|
51
62
|
}
|