phewsh 0.12.0 → 0.12.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/README.md CHANGED
@@ -23,7 +23,7 @@ Connect the web app to your local machine for real-time task execution:
23
23
  phewsh serve
24
24
  ```
25
25
 
26
- This starts a bridge on `localhost:7483`. Open [phewsh.com/intent](https://phewsh.com/intent) and go to the Work tab — you'll see a green "Live" indicator. Agent tasks get a "Run Live" button that executes via Claude Code on your machine.
26
+ This starts a bridge on `localhost:7483`. Open [phewsh.com/intent](https://phewsh.com/intent) and go to the Work tab — you'll see a green "Live" indicator. Agent tasks get a "Run Live" button that executes on your machine through whichever agent CLIs you have installed: **Claude Code, Codex, Gemini, Cursor Agent, OpenCode**. PHEWSH detects them automatically and every run leaves a receipt (`phewsh receipts`).
27
27
 
28
28
  ## Interactive Shell
29
29
 
@@ -67,6 +67,7 @@ phewsh pull # Sync cloud to local
67
67
  phewsh mcp setup # Configure MCP server for agent connectivity
68
68
  phewsh mcp serve # Coordination bridge — route work to live agents
69
69
  phewsh receipts # Proof trail — what agents actually did, with evidence
70
+ phewsh update # Update phewsh to the latest version
70
71
  phewsh style # Build your style identity
71
72
  ```
72
73
 
package/bin/phewsh.js CHANGED
@@ -64,6 +64,7 @@ const COMMANDS = {
64
64
  watch: () => require('../commands/watch')(),
65
65
  mcp: () => require('../commands/mcp')(),
66
66
  receipts: () => require('../commands/receipts')(),
67
+ update: () => require('../commands/update')(),
67
68
  serve: () => require('../commands/serve')(),
68
69
  sequence: () => require('../commands/sequence')(),
69
70
  seq: () => require('../commands/sequence')(),
@@ -103,6 +104,7 @@ function showHelp() {
103
104
  console.log(` ${b(w('configure'))}`);
104
105
  console.log(` ${cyan('login')} ${g('Identity + API key + cloud sync')}`);
105
106
  console.log(` ${cyan('link')} ${g('Link local .intent/ to cloud project')}`);
107
+ console.log(` ${cyan('update')} ${g('Update phewsh to the latest version')}`);
106
108
  console.log('');
107
109
  console.log(` ${g('Works in: Claude Code · Cursor · ChatGPT · any MCP agent')}`);
108
110
  console.log(` ${g('No account needed. Account adds sync + sharing.')}`);
@@ -127,7 +129,7 @@ function checkForUpdates() {
127
129
  newer[2] > current[2];
128
130
  if (isNewer) {
129
131
  console.log(g(`\n Update available: ${pkg.version} → ${data.version}`));
130
- console.log(g(` Run: npm install -g phewsh\n`));
132
+ console.log(g(` Run: phewsh update\n`));
131
133
  }
132
134
  }
133
135
  })
package/commands/serve.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // phewsh serve — HTTP bridge server for live execution from the web UI
2
2
  //
3
- // Starts a local server that the PHEWSH web app connects to for live task execution.
4
- // When running, the web UI shows a green "Live" indicator and "Run Live" buttons.
3
+ // Starts a local server that the PHEWSH web app connects to for live task
4
+ // execution. When running, the web UI shows a green "Live" indicator and
5
+ // "Run Live" buttons for every agent CLI installed on this machine —
6
+ // Claude Code, Codex, Gemini, Cursor Agent, OpenCode. PHEWSH is not a
7
+ // harness; it dispatches to the harnesses you already have, and every run
8
+ // leaves a receipt (~/.phewsh/ — see `phewsh receipts`).
5
9
  //
6
10
  // Usage:
7
11
  // phewsh serve Start on default port (7483)
@@ -31,15 +35,27 @@ function getPort() {
31
35
 
32
36
  // ─── Runtime Detection ─────────────────────────────────────────────────────
33
37
 
38
+ // Harness runners — how to invoke each agent CLI headlessly. PHEWSH is not a
39
+ // harness; it's the layer that dispatches to whichever harnesses you have.
40
+ // Detection is honest: a runtime is only "connected" if its binary is on PATH.
41
+ const RUNNERS = {
42
+ 'claude-code': { bin: 'claude', label: 'Claude Code', args: (p) => ['-p', p, '--output-format', 'text'] },
43
+ 'codex': { bin: 'codex', label: 'Codex CLI', args: (p) => ['exec', p] },
44
+ 'gemini': { bin: 'gemini', label: 'Gemini CLI', args: (p) => ['-p', p] },
45
+ 'cursor': { bin: 'cursor-agent', label: 'Cursor Agent', args: (p) => ['-p', p, '--output-format', 'text'] },
46
+ 'opencode': { bin: 'opencode', label: 'OpenCode', args: (p) => ['run', p] },
47
+ };
48
+
34
49
  function detectRuntimes() {
35
50
  const runtimes = [];
36
51
 
37
- // Check for Claude Code CLI
38
- try {
39
- execSync('which claude', { stdio: 'pipe' });
40
- runtimes.push({ id: 'claude-code', label: 'Claude Code', connected: true });
41
- } catch {
42
- runtimes.push({ id: 'claude-code', label: 'Claude Code', connected: false });
52
+ for (const [id, r] of Object.entries(RUNNERS)) {
53
+ let connected = false;
54
+ try {
55
+ execSync(`which ${r.bin}`, { stdio: 'pipe' });
56
+ connected = true;
57
+ } catch { /* not installed */ }
58
+ runtimes.push({ id, label: r.label, connected });
43
59
  }
44
60
 
45
61
  // Always report human as available
@@ -87,8 +103,9 @@ async function executeJob(jobId) {
87
103
 
88
104
  const { runtimeId, packet } = job;
89
105
 
90
- if (runtimeId === 'claude-code') {
91
- await executeViaClaude(job, packet);
106
+ const runner = RUNNERS[runtimeId];
107
+ if (runner) {
108
+ await executeViaHarness(job, packet, runner);
92
109
  } else {
93
110
  job.status = 'error';
94
111
  job.error = `Runtime ${runtimeId} not supported for live execution yet`;
@@ -96,7 +113,7 @@ async function executeJob(jobId) {
96
113
  }
97
114
  }
98
115
 
99
- async function executeViaClaude(job, packet) {
116
+ async function executeViaHarness(job, packet, runner) {
100
117
  // Build a prompt from the dispatch packet
101
118
  const prompt = [
102
119
  `# Task: ${packet.objective?.task || 'Execute task'}`,
@@ -115,15 +132,17 @@ async function executeViaClaude(job, packet) {
115
132
  ].filter(Boolean).join('\n');
116
133
 
117
134
  return new Promise((resolve) => {
118
- job.statusText = 'Launching Claude Code...';
135
+ job.statusText = `Launching ${runner.label}...`;
119
136
 
120
- // Use claude CLI with --print flag for non-interactive output
121
- const child = spawn('claude', ['-p', prompt, '--output-format', 'text'], {
137
+ const child = spawn(runner.bin, runner.args(prompt), {
122
138
  stdio: ['pipe', 'pipe', 'pipe'],
123
139
  env: { ...process.env },
124
140
  cwd: process.cwd(),
125
141
  });
126
142
 
143
+ // Some harnesses (codex exec, gemini) wait for stdin EOF before running.
144
+ child.stdin.end();
145
+
127
146
  let stdout = '';
128
147
  let stderr = '';
129
148
 
@@ -149,7 +168,7 @@ async function executeViaClaude(job, packet) {
149
168
  console.log(` ${green('✓')} Job ${job.jobId.slice(0, 8)} completed`);
150
169
  } else {
151
170
  job.status = 'error';
152
- job.error = stderr.trim() || `Claude exited with code ${code}`;
171
+ job.error = stderr.trim() || `${runner.label} exited with code ${code}`;
153
172
  job.statusText = 'Failed';
154
173
  console.log(` ${yellow('✗')} Job ${job.jobId.slice(0, 8)} failed: ${job.error.slice(0, 100)}`);
155
174
  }
@@ -160,11 +179,11 @@ async function executeViaClaude(job, packet) {
160
179
  taskId: packet?.id || job.actionId || job.jobId,
161
180
  result: success ? job.result : job.error,
162
181
  success,
163
- agentId: 'claude-code',
182
+ agentId: job.runtimeId,
164
183
  executor: 'phewsh-serve',
165
184
  reportedAt: new Date().toISOString(),
166
185
  });
167
- recordSessionEvent('claude-code', 'web', 'task_complete', {
186
+ recordSessionEvent(job.runtimeId, 'web', 'task_complete', {
168
187
  taskId: packet?.id || job.actionId || job.jobId,
169
188
  success,
170
189
  result: (success ? job.result : job.error || '').slice(0, 200),
@@ -0,0 +1,79 @@
1
+ // phewsh update — update the CLI to the latest published version.
2
+ //
3
+ // Usage:
4
+ // phewsh update Check npm and install the latest version
5
+ // phewsh update --check Just compare versions, don't install
6
+
7
+ const { spawn } = require('child_process');
8
+
9
+ const b = (s) => `\x1b[1m${s}\x1b[0m`;
10
+ const w = (s) => `\x1b[97m${s}\x1b[0m`;
11
+ const g = (s) => `\x1b[38;2;130;142;138m${s}\x1b[0m`;
12
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
13
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
14
+
15
+ function isNewer(latest, current) {
16
+ const l = latest.split('.').map(Number);
17
+ const c = current.split('.').map(Number);
18
+ return l[0] !== c[0] ? l[0] > c[0] : l[1] !== c[1] ? l[1] > c[1] : l[2] > c[2];
19
+ }
20
+
21
+ async function main() {
22
+ const checkOnly = process.argv.includes('--check');
23
+ const pkg = require('../package.json');
24
+
25
+ console.log('');
26
+ console.log(` ${b(w('phewsh update'))}`);
27
+ console.log('');
28
+ console.log(` ${g('current:')} v${pkg.version}`);
29
+
30
+ let latest;
31
+ try {
32
+ const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, {
33
+ signal: AbortSignal.timeout(5000),
34
+ });
35
+ latest = (await res.json()).version;
36
+ } catch {
37
+ console.log(` ${yellow('Could not reach the npm registry. Try again later.')}`);
38
+ console.log('');
39
+ return;
40
+ }
41
+ console.log(` ${g('latest: ')} v${latest}`);
42
+ console.log('');
43
+
44
+ if (!latest || !isNewer(latest, pkg.version)) {
45
+ console.log(` ${green('Already up to date.')}`);
46
+ console.log('');
47
+ return;
48
+ }
49
+
50
+ if (checkOnly) {
51
+ console.log(` ${w(`v${latest} available.`)} Run ${w('phewsh update')} to install.`);
52
+ console.log('');
53
+ return;
54
+ }
55
+
56
+ console.log(` ${g('Installing')} ${w(`phewsh@${latest}`)} ${g('via npm…')}`);
57
+ console.log('');
58
+
59
+ const child = spawn('npm', ['install', '-g', `${pkg.name}@latest`], { stdio: 'inherit' });
60
+ child.on('close', (code) => {
61
+ console.log('');
62
+ if (code === 0) {
63
+ console.log(` ${green('✓')} Updated to v${latest}. New shells pick it up immediately.`);
64
+ } else {
65
+ console.log(` ${yellow('Update failed')} (npm exited ${code}).`);
66
+ console.log(` ${g('If it was a permissions error, fix your npm prefix or run:')}`);
67
+ console.log(` ${w(`sudo npm install -g ${pkg.name}@latest`)}`);
68
+ }
69
+ console.log('');
70
+ process.exit(code ?? 0);
71
+ });
72
+ child.on('error', (err) => {
73
+ console.log(` ${yellow('Could not run npm:')} ${err.message}`);
74
+ console.log('');
75
+ process.exit(1);
76
+ });
77
+ }
78
+
79
+ module.exports = main;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"