wisp-agents 0.0.0-stage → 0.2.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.
@@ -0,0 +1,73 @@
1
+ // Open a command in the user's terminal so they can take over an agent session interactively.
2
+
3
+ import { spawn, execFileSync } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+
6
+ export const TERMINALS = [
7
+ { id: 'auto', label: 'Auto-detect' },
8
+ { id: 'terminal', label: 'Terminal.app', mac: true },
9
+ { id: 'iterm', label: 'iTerm', mac: true, app: '/Applications/iTerm.app' },
10
+ { id: 'ghostty', label: 'Ghostty', app: '/Applications/Ghostty.app', bin: 'ghostty' },
11
+ { id: 'warp', label: 'Warp', mac: true, app: '/Applications/Warp.app' },
12
+ { id: 'kitty', label: 'kitty', app: '/Applications/kitty.app', bin: 'kitty' },
13
+ { id: 'wezterm', label: 'WezTerm', app: '/Applications/WezTerm.app', bin: 'wezterm' },
14
+ { id: 'custom', label: 'Custom command' },
15
+ ];
16
+
17
+ export const shq = (s) => `'${String(s).replace(/'/g, `'\\''`)}'`;
18
+
19
+ const has = (bin) => {
20
+ try {
21
+ execFileSync('which', [bin], { stdio: 'ignore' });
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ };
27
+
28
+ function detect() {
29
+ if (process.platform === 'darwin') {
30
+ if (process.env.TERM_PROGRAM === 'iTerm.app' && fs.existsSync('/Applications/iTerm.app')) return 'iterm';
31
+ if (process.env.TERM_PROGRAM === 'ghostty' && fs.existsSync('/Applications/Ghostty.app')) return 'ghostty';
32
+ return 'terminal';
33
+ }
34
+ for (const t of ['ghostty', 'kitty', 'wezterm']) if (has(t)) return t;
35
+ return 'x-terminal-emulator';
36
+ }
37
+
38
+ const osa = (script) => spawn('osascript', ['-e', script], { detached: true, stdio: 'ignore' }).unref();
39
+ const asStr = (s) => `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
40
+
41
+ // argv: the agent's resume command, e.g. ['claude', '--resume', 'abc'].
42
+ export function openInTerminal({ cwd, argv, terminal = 'auto', custom = '' }) {
43
+ const cmd = argv.map(shq).join(' ');
44
+ const line = `cd ${shq(cwd)} && ${cmd}`;
45
+ const t = terminal === 'auto' ? detect() : terminal;
46
+ const mac = process.platform === 'darwin';
47
+
48
+ switch (t) {
49
+ case 'terminal':
50
+ return osa(`tell application "Terminal"\nactivate\ndo script ${asStr(line)}\nend tell`);
51
+ case 'iterm':
52
+ return osa(`tell application "iTerm"\nactivate\nset w to (create window with default profile)\ntell current session of w to write text ${asStr(line)}\nend tell`);
53
+ case 'warp':
54
+ // Warp has no scripting API; open a tab in the folder and type via System Events.
55
+ spawn('open', ['-a', 'Warp', cwd], { detached: true, stdio: 'ignore' }).unref();
56
+ return setTimeout(() => osa(`tell application "System Events" to keystroke ${asStr(cmd)} & return`), 1200);
57
+ case 'ghostty':
58
+ if (mac) return spawn('open', ['-na', 'Ghostty', '--args', `--working-directory=${cwd}`, '-e', ...argv], { detached: true, stdio: 'ignore' }).unref();
59
+ return spawn('ghostty', [`--working-directory=${cwd}`, '-e', ...argv], { detached: true, stdio: 'ignore' }).unref();
60
+ case 'kitty':
61
+ if (mac) return spawn('open', ['-na', 'kitty', '--args', '--directory', cwd, ...argv], { detached: true, stdio: 'ignore' }).unref();
62
+ return spawn('kitty', ['--directory', cwd, ...argv], { detached: true, stdio: 'ignore' }).unref();
63
+ case 'wezterm':
64
+ return spawn(mac ? '/Applications/WezTerm.app/Contents/MacOS/wezterm' : 'wezterm', ['start', '--cwd', cwd, '--', ...argv], { detached: true, stdio: 'ignore' }).unref();
65
+ case 'custom': {
66
+ if (!custom) throw new Error('Set a custom terminal command in settings, e.g. alacritty --working-directory {cwd} -e {cmd}');
67
+ const full = custom.replaceAll('{cwd}', shq(cwd)).replaceAll('{cmd}', cmd).replaceAll('{line}', shq(line));
68
+ return spawn('sh', ['-c', full], { detached: true, stdio: 'ignore' }).unref();
69
+ }
70
+ default:
71
+ return spawn('x-terminal-emulator', ['-e', 'sh', '-c', `${line}; exec $SHELL`], { detached: true, stdio: 'ignore' }).unref();
72
+ }
73
+ }
@@ -0,0 +1,168 @@
1
+ // The first thing a new user sees: a pre-rendered "session" that explains wisp
2
+ // using the same blocks an agent would. Doubles as a living block gallery.
3
+
4
+ import os from 'node:os';
5
+ import { newId } from './runner.js';
6
+
7
+ export function welcomeSession() {
8
+ const now = Date.now();
9
+ return {
10
+ id: newId(),
11
+ title: 'Welcome to wisp',
12
+ titleAuto: false,
13
+ agent: 'wisp',
14
+ cwd: os.homedir(),
15
+ mode: 'plan',
16
+ model: '',
17
+ state: 'review',
18
+ unread: true,
19
+ createdAt: now,
20
+ updatedAt: now,
21
+ agentSessionId: null,
22
+ queue: [],
23
+ turns: [
24
+ {
25
+ id: newId(),
26
+ prompt: 'What is wisp and how do I use it?',
27
+ startedAt: now - 94_000,
28
+ endedAt: now,
29
+ status: 'done',
30
+ stats: { costUsd: 0, durationMs: 94_000, turns: 1 },
31
+ activity: [
32
+ { t: now - 90_000, kind: 'read', label: 'Reading server/protocol.js', file: 'server/protocol.js' },
33
+ { t: now - 80_000, kind: 'search', label: 'Searching for “blocks”' },
34
+ { t: now - 60_000, kind: 'edit', label: 'Editing web/js/app.js', file: 'web/js/app.js' },
35
+ { t: now - 30_000, kind: 'run', label: 'Run the test suite', detail: 'npm test' },
36
+ ],
37
+ changes: [
38
+ {
39
+ file: 'src/session.ts',
40
+ op: 'edit',
41
+ edits: [
42
+ {
43
+ before: 'export async function run(agent, task) {\n const log = await agent.exec(task);\n return log; // 4,000 lines nobody reads\n}',
44
+ after: 'export async function run(agent, task) {\n const log = await agent.exec(task);\n return present(log); // what changed, why, what\'s next\n}',
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ presentation: {
50
+ blocks: [
51
+ {
52
+ type: 'hero',
53
+ title: 'Send agents off. Read the highlights.',
54
+ summary: 'wisp launches coding agents in the background and hands you a short visual brief when they finish — not a transcript. Follow up right here.',
55
+ status: 'info',
56
+ },
57
+ {
58
+ type: 'flow',
59
+ title: 'How a wisp travels',
60
+ nodes: [
61
+ { id: 'a', label: 'You launch', note: 'task + project' },
62
+ { id: 'b', label: 'Agent works', note: 'Claude Code, headless' },
63
+ { id: 'c', label: 'Presentation', note: 'blocks, not logs' },
64
+ { id: 'd', label: 'You follow up', note: 'or mark done' },
65
+ ],
66
+ edges: [['a', 'b'], ['b', 'c'], ['c', 'd'], ['d', 'b', 'resumes']],
67
+ },
68
+ {
69
+ type: 'stats',
70
+ items: [
71
+ { label: 'lines of transcript you skipped', value: 4182, tone: 'good' },
72
+ { label: 'agents running at once', value: 6, unit: '×' },
73
+ { label: 'block types', value: 19 },
74
+ ],
75
+ },
76
+ {
77
+ type: 'steps',
78
+ title: 'Using it',
79
+ items: [
80
+ { title: 'Launch', detail: 'Press N, describe the task, pick the project folder. Launch several — they run in parallel.' },
81
+ { title: 'Forget about it', detail: 'The queue on the left shows what each agent is doing in one line.' },
82
+ { title: 'Review', detail: 'Finished work lands in “Needs you” with a dot. Open it to see the brief.' },
83
+ { title: 'Follow up or close', detail: 'Reply to resume the same agent session, or press E to mark it done.' },
84
+ ],
85
+ },
86
+ { type: 'diff', file: 'src/session.ts', caption: 'Real diffs come from the agent’s own edits — no copy-paste.' },
87
+ {
88
+ type: 'tests',
89
+ command: 'npm test',
90
+ passed: 128,
91
+ failed: 1,
92
+ skipped: 3,
93
+ durationMs: 6400,
94
+ failures: [{ name: 'session › resumes after restart', message: 'expected state "review", received "running"', file: 'test/session.test.ts:88' }],
95
+ },
96
+ {
97
+ type: 'files',
98
+ items: [
99
+ { path: 'src/session.ts', change: 'modified', note: 'Returns a presentation instead of the raw log.' },
100
+ { path: 'src/present/blocks.ts', change: 'added', note: 'Block schema + validation.' },
101
+ { path: 'src/legacy/transcript.ts', change: 'deleted' },
102
+ ],
103
+ },
104
+ {
105
+ type: 'commits',
106
+ branch: 'feat/presentations',
107
+ items: [
108
+ { hash: 'a1f9c03', message: 'feat(session): return presentations', files: 3, additions: 142, deletions: 37 },
109
+ { hash: '7be21d4', message: 'test: cover resume after restart', files: 1, additions: 48, deletions: 0 },
110
+ ],
111
+ },
112
+ {
113
+ type: 'compare',
114
+ before: { label: 'Without wisp', items: ['Babysit a terminal per agent', 'Scroll 40 screens to find what changed', 'Lose track of which tab finished'] },
115
+ after: { label: 'With wisp', items: ['Fire and forget, many at once', 'One page: what, why, proof', 'A queue that tells you what needs you'] },
116
+ },
117
+ {
118
+ type: 'chart',
119
+ title: 'Time spent per finished task',
120
+ unit: 'min',
121
+ items: [
122
+ { label: 'reading transcripts', value: 11 },
123
+ { label: 'reading a wisp', value: 1.5 },
124
+ ],
125
+ },
126
+ { type: 'terminal', command: 'npx wisp "add dark mode to the settings page"', output: ' ◌ wisp is floating at http://localhost:4477\n\n launched: add dark mode to the settings page', exitCode: 0 },
127
+ {
128
+ type: 'checklist',
129
+ title: 'Permission modes (pick per launch)',
130
+ items: [
131
+ { text: 'Plan — read-only, the agent proposes', done: true },
132
+ { text: 'Edits — the agent may change files, no shell prompts', done: true },
133
+ { text: 'Full auto — no permission prompts at all; use on trusted repos', done: false },
134
+ ],
135
+ },
136
+ {
137
+ type: 'code',
138
+ language: 'json',
139
+ caption: 'Agents answer with blocks like these — you can ask for any of them.',
140
+ code: '{"type":"stats","items":[{"label":"tests","value":48,"delta":"+4","tone":"good"}]}',
141
+ },
142
+ {
143
+ type: 'tree',
144
+ title: 'Agents can sketch structure too',
145
+ items: [
146
+ { path: 'src/present/blocks.ts', change: 'added' },
147
+ { path: 'src/present/render.ts', change: 'added', note: 'pure, no DOM' },
148
+ { path: 'src/session.ts', change: 'modified' },
149
+ { path: 'test/session.test.ts', change: 'modified' },
150
+ ],
151
+ },
152
+ { type: 'callout', tone: 'info', title: 'Agents can ask you mid-run', text: 'When a decision matters, the agent pauses with a question. The session jumps to **Needs you**; your answer goes straight back to the still-running agent.' },
153
+ { type: 'callout', tone: 'warn', title: 'Heads up', text: 'This tour is static. Launch a real wisp with **N** — or take any real session over in your terminal with **T**.' },
154
+ {
155
+ type: 'question',
156
+ question: 'Which agent harness should wisp support next?',
157
+ options: [
158
+ { label: 'Codex', reply: 'Codex next' },
159
+ { label: 'opencode', reply: 'opencode next' },
160
+ ],
161
+ },
162
+ { type: 'next', items: ['Summarize what changed on this branch', 'Find and fix the flakiest test', 'Write a README for this repo'] },
163
+ ],
164
+ },
165
+ },
166
+ ],
167
+ };
168
+ }