phewsh 0.15.77 → 0.15.79

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/bin/phewsh.js CHANGED
@@ -113,6 +113,7 @@ const COMMANDS = {
113
113
  remember: () => require('../commands/remember')(),
114
114
  task: () => require('../commands/task')(),
115
115
  dispatch: () => require('../commands/task')(),
116
+ ion: () => require('../commands/ion')(),
116
117
  pack: () => require('../commands/pack')(),
117
118
  hook: () => require('../commands/hook')(),
118
119
  feedback: () => require('../commands/feedback')(),
@@ -159,7 +160,8 @@ function showHelp() {
159
160
  console.log(` ${cyan('seq')} ${g('Sequence all memory → optimal context for any agent')}`);
160
161
  console.log(` ${cyan('watch')} ${g('Auto-sync .intent/ → native harness files + cloud')}`);
161
162
  console.log(` ${cyan('push/pull')} ${g('Manual sync to/from phewsh.com/intent')}`);
162
- console.log(` ${cyan('serve')} ${g('Execution bridge — run from phewsh.com/intent')}`);
163
+ console.log(` ${cyan('serve')} ${g('Execution bridge — run from phewsh.com/ion or /intent')}`);
164
+ console.log(` ${cyan('ion')} ${g('Shared visual room for humans + local agents')}`);
163
165
  console.log(` ${cyan('mcp')} ${g('Connect AI agents via MCP protocol')}`);
164
166
  console.log(` ${cyan('ambient')} ${g('Continuity without launching phewsh — enhance your other tools')}`);
165
167
  console.log(` ${cyan('shim')} ${g('Guaranteed launch banner — phewsh prints status before each tool')}`);
@@ -0,0 +1,115 @@
1
+ // phewsh ion — command surface for the shared visual room.
2
+ //
3
+ // Ion is not a second execution system. It is the room over the existing
4
+ // Phewsh primitives:
5
+ // - `serve` exposes the local worker bridge
6
+ // - `task` owns shared requests, invites, claims, reviews, and receipts
7
+ // - `/ion` is the browser room where humans can see and steer the loop
8
+
9
+ const { execFileSync } = require('child_process');
10
+
11
+ const WEB_URL = 'https://phewsh.com/ion';
12
+
13
+ const cream = (s) => `\x1b[97m${s}\x1b[0m`;
14
+ const sage = (s) => `\x1b[38;5;247m${s}\x1b[0m`;
15
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
16
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
17
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
18
+
19
+ function openBrowser(url = WEB_URL) {
20
+ try {
21
+ if (process.platform === 'darwin') execFileSync('open', [url]);
22
+ else if (process.platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url]);
23
+ else execFileSync('xdg-open', [url]);
24
+ console.log(`\n ${green('✓')} Opened ${cream(url)}\n`);
25
+ } catch {
26
+ console.log(`\n ${sage('Could not open browser. Visit:')} ${cream(url)}\n`);
27
+ }
28
+ }
29
+
30
+ function showHelp() {
31
+ console.log(`
32
+ ${cream('phewsh ion')} — shared rooms for humans + local agents
33
+
34
+ ${sage('Open the room')}
35
+ ${cyan('phewsh ion')} ${sage('open phewsh.com/ion')}
36
+ ${cyan('phewsh ion open')} ${sage('same')}
37
+
38
+ ${sage('Make this machine available')}
39
+ ${cyan('phewsh ion serve')} ${sage('start the local worker bridge')}
40
+ ${cyan('phewsh ion status')} ${sage('show bridge/project/task status')}
41
+
42
+ ${sage('Use the existing task loop')}
43
+ ${cyan('phewsh ion task')} ${sage('list shared tasks')}
44
+ ${cyan('phewsh ion request "..."')} ${sage('request work in the room')}
45
+ ${cyan('phewsh ion claim next')} ${sage('manual claim + isolated PR flow')}
46
+ ${cyan('phewsh ion invite <email>')} ${sage('invite a teammate')}
47
+ ${cyan('phewsh ion join')} ${sage('accept pending invites')}
48
+
49
+ ${sage('Connectors')}
50
+ ${cyan('phewsh ion connectors')} ${sage('show Slack/Discord connector plan')}
51
+ `);
52
+ }
53
+
54
+ function showConnectors() {
55
+ console.log(`
56
+ ${cream('Ion connectors')}
57
+
58
+ ${sage('Canonical room:')} ${cream('phewsh.com/ion')}
59
+ ${sage('Execution:')} ${cream('phewsh ion serve')} ${sage('or')} ${cream('phewsh ion claim <id>')}
60
+
61
+ ${sage('Slack and Discord are connectors, not the core room yet.')}
62
+ ${sage('Planned flow:')}
63
+ Slack/Discord mention
64
+ → Ion task request
65
+ → human approval in Ion
66
+ → local/VPS Phewsh worker runs
67
+ → branch/PR/evidence returns to Ion
68
+ → notification posts back to Slack/Discord
69
+
70
+ ${sage('Do not build bot auto-execution first. Keep Ion as the source of truth.')}
71
+ `);
72
+ }
73
+
74
+ function runTask(args) {
75
+ process.argv = [process.argv[0], process.argv[1], 'task', ...args];
76
+ return require('./task')();
77
+ }
78
+
79
+ function runServe(args) {
80
+ process.argv = [process.argv[0], process.argv[1], 'serve', ...args];
81
+ return require('./serve')();
82
+ }
83
+
84
+ module.exports = async function run() {
85
+ const args = process.argv.slice(3);
86
+ const sub = args[0] || 'open';
87
+ const rest = args.slice(1);
88
+
89
+ try {
90
+ if (sub === '--help' || sub === '-h' || sub === 'help') return showHelp();
91
+ if (sub === 'open') return openBrowser();
92
+ if (sub === 'serve' || sub === 'worker') return runServe(rest);
93
+ if (sub === 'status') {
94
+ console.log(`\n ${cream('Ion room:')} ${cyan(WEB_URL)}`);
95
+ console.log(` ${sage('Local worker:')} ${cream('phewsh ion serve')}`);
96
+ return runTask(['list']);
97
+ }
98
+ if (sub === 'task' || sub === 'tasks') return runTask(rest.length ? rest : ['list']);
99
+ if (sub === 'request' || sub === 'new') return runTask(['new', ...rest]);
100
+ if (sub === 'claim') return runTask(['claim', ...rest]);
101
+ if (sub === 'invite') return runTask(['invite', ...rest]);
102
+ if (sub === 'join') return runTask(['join']);
103
+ if (sub === 'reconcile') return runTask(['reconcile', ...rest]);
104
+ if (sub === 'connectors' || sub === 'slack' || sub === 'discord') return showConnectors();
105
+
106
+ console.log(`\n ${red('✗')} Unknown ion command: ${sub}\n`);
107
+ showHelp();
108
+ process.exitCode = 1;
109
+ } catch (err) {
110
+ console.error(`\n ${red('✗')} ${err.message}\n`);
111
+ process.exitCode = 1;
112
+ }
113
+ };
114
+
115
+ module.exports.openBrowser = openBrowser;
package/commands/serve.js CHANGED
@@ -12,7 +12,7 @@
12
12
  // phewsh serve --port 8080 Start on custom port
13
13
 
14
14
  const http = require('http');
15
- const { execSync, spawn } = require('child_process');
15
+ const { execFileSync, spawn } = require('child_process');
16
16
  const crypto = require('crypto');
17
17
  const os = require('os');
18
18
  const path = require('path');
@@ -35,6 +35,18 @@ function getPort() {
35
35
  return 7483;
36
36
  }
37
37
 
38
+ // The project this worker serves = the directory it was started in. The
39
+ // normalized origin remote is the identity the claim path already verifies
40
+ // against (task.js repo-match); name alone is display, remote is truth.
41
+ function currentProject() {
42
+ let remote = null;
43
+ try {
44
+ remote = execFileSync('git', ['remote', 'get-url', 'origin'], { stdio: ['ignore', 'pipe', 'ignore'] })
45
+ .toString().trim() || null;
46
+ } catch { /* not a git repo, or no origin — worker still serves the directory */ }
47
+ return { name: path.basename(process.cwd()), remote };
48
+ }
49
+
38
50
  // ─── Runtime Detection ─────────────────────────────────────────────────────
39
51
 
40
52
  // Harness runners — shared table in lib/harnesses.js. PHEWSH is not a
@@ -220,7 +232,7 @@ function json(req, res, data, status = 200) {
220
232
 
221
233
  function main() {
222
234
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
223
- console.log('\n phewsh serve — local execution bridge for phewsh.com/intent');
235
+ console.log('\n phewsh serve — local execution bridge for phewsh.com/ion and phewsh.com/intent');
224
236
  console.log(' Runs a loopback server so the web workspace can dispatch to your');
225
237
  console.log(' installed agents. Stays running until you stop it (ctrl+c).');
226
238
  console.log('\n Usage: phewsh serve [--port <n>] (default 7483)\n');
@@ -245,10 +257,12 @@ function main() {
245
257
  return;
246
258
  }
247
259
 
248
- // Health check
260
+ // Health check — includes which project this worker is serving, so the
261
+ // web can say "worker online — <project>" instead of an anonymous dot.
249
262
  if (url.pathname === '/health' && req.method === 'GET') {
250
263
  return json(req, res, {
251
264
  status: 'ok',
265
+ project: currentProject(),
252
266
  runtimes: detectRuntimes(),
253
267
  version: require('../package.json').version,
254
268
  uptime: process.uptime(),
@@ -351,6 +365,26 @@ function main() {
351
365
 
352
366
  const server = http.createServer(handleRequest);
353
367
 
368
+ // One worker per machine (per port) for now. A second `phewsh serve` used to
369
+ // die with a raw EADDRINUSE stack — say what's true and how to proceed instead.
370
+ server.on('error', (err) => {
371
+ if (err.code === 'EADDRINUSE') {
372
+ console.log('');
373
+ console.log(` ${yellow('●')} A phewsh worker is already running on port ${port}.`);
374
+ console.log('');
375
+ console.log(` ${g('One worker per machine for now — the running worker serves the project')}`);
376
+ console.log(` ${g('directory it was started in. To serve a different project:')}`);
377
+ console.log(` ${g('· stop the other worker (Ctrl+C) and start this one, or')}`);
378
+ console.log(` ${g('· run on another port:')} ${w(`phewsh serve --port ${port + 1}`)}`);
379
+ console.log(` ${g('(note: phewsh.com currently discovers port 7483 only)')}`);
380
+ console.log('');
381
+ console.log(` ${g('A one-worker-many-projects registry is the planned next step.')}`);
382
+ console.log('');
383
+ process.exit(1);
384
+ }
385
+ throw err;
386
+ });
387
+
354
388
  server.listen(port, '127.0.0.1', () => {
355
389
  const mirror = http.createServer(handleRequest);
356
390
  mirror.on('error', () => { /* IPv6 unavailable or already bound */ });
@@ -358,7 +392,7 @@ function main() {
358
392
 
359
393
  console.log('');
360
394
  console.log(` ${b(w('PHEWSH Serve'))} ${g('v' + require('../package.json').version)}`);
361
- console.log(` ${g('Live execution bridge for phewsh.com/intent')}`);
395
+ console.log(` ${g('Live execution bridge for phewsh.com/ion and phewsh.com/intent')}`);
362
396
  console.log('');
363
397
  console.log(` ${green('●')} Running on ${w(`http://localhost:${port}`)}`);
364
398
  console.log(` ${g('Web cockpit:')} ${w('phewsh.com/cockpit')} ${g('— mirrors this machine live')}`);
@@ -374,7 +408,7 @@ function main() {
374
408
  console.log(` ${g('https://docs.anthropic.com/en/docs/claude-code')}`);
375
409
  }
376
410
  console.log('');
377
- console.log(` ${g('Open phewsh.com/intent → Work tab to see the Live indicator')}`);
411
+ console.log(` ${g('Open phewsh.com/ion to see the worker online, or phewsh.com/intent → Work')}`);
378
412
  console.log(` ${g('Press Ctrl+C to stop')}`);
379
413
  console.log('');
380
414
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.15.77",
4
- "description": "One mission. Many AI tools. No lost context. PHEWSH keeps your project's intent, decisions, and working context aligned across Claude Code, Codex, Cursor, Gemini and your team \u2014 so the next AI knows what the last one learned.",
3
+ "version": "0.15.79",
4
+ "description": "One mission. Many AI tools. No lost context. PHEWSH keeps your project's intent, decisions, and working context aligned across Claude Code, Codex, Cursor, Gemini and your team so the next AI knows what the last one learned.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"
7
7
  },