phewsh 0.15.59 → 0.15.61

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.
@@ -73,6 +73,35 @@ function copyToClipboard(text) {
73
73
  return false;
74
74
  }
75
75
 
76
+ // Show the cross-harness handoff — the heart of phewsh's continuity promise:
77
+ // the verified brief that travels to whatever AI tool you pick up next, now
78
+ // rendered as readable markdown instead of raw text and saved so the next tool
79
+ // (or the next phewsh session) inherits it. Best-effort and fail-soft; a
80
+ // handoff that can't generate must never block exit or a switch.
81
+ async function showHandoff({ projectName, route, reason = '', nextHint = true } = {}) {
82
+ let result = { shown: false, file: null };
83
+ try {
84
+ const { content } = await generateBrief();
85
+ const { renderMarkdown } = require('../lib/md');
86
+ const saved = persistBrief(content, { project: projectName, route: route || 'unknown' });
87
+ console.log('');
88
+ console.log(` ${teal('●')} ${b(cream('Handoff ready'))}${reason ? ' ' + slate('— ' + reason) : ''}`);
89
+ console.log(` ${slate('This is exactly what travels to the next AI tool — nothing re-explained.')}`);
90
+ ui.divider('line');
91
+ console.log(renderMarkdown(content));
92
+ ui.divider('line');
93
+ if (saved.written) console.log(` ${slate('saved · ' + saved.file.replace(require('os').homedir(), '~'))}`);
94
+ copyToClipboard(content);
95
+ console.log(` ${slate('copied to clipboard · paste into any tool, or')} ${cream('/use codex')} ${slate('to continue here')}`);
96
+ if (nextHint) console.log(` ${slate('full verified brief anytime:')} ${cream('/brief')} ${slate('·')} ${cream('/handoff')}`);
97
+ console.log('');
98
+ result = { shown: true, file: saved.file };
99
+ } catch (err) {
100
+ console.log(` ${ember('!')} ${slate('Could not build the handoff: ' + err.message)}`);
101
+ }
102
+ return result;
103
+ }
104
+
76
105
  // Sync awareness: compare local .intent/ timestamps with cloud updated_at
77
106
  async function checkSyncStatus(config) {
78
107
  if (!config?.supabaseUserId || !config?.supabaseAccessToken) return null;
@@ -397,27 +426,31 @@ async function main() {
397
426
  // ── The Exhale: animated brand reveal ──────────────────
398
427
  await ui.brandReveal();
399
428
 
400
- // Self-aware, plain-language summarythe value prop made real with THIS
401
- // machine's state. Non-technical readers get one warm sentence; the rows
402
- // below give engineers the exact facts. Fail-soft: a missing piece just
403
- // shortens the line, it never blocks the door.
404
- (function narrativeSummary() {
429
+ // The welcome beatwarm, plain-language, and continuity-first. This is the
430
+ // "you made it" moment that frames what phewsh is FOR: one thread across every
431
+ // AI tool, so you switch harnesses mid-work and nothing gets re-explained.
432
+ // Non-technical readers get one inviting sentence; the cockpit rows below give
433
+ // engineers the exact facts. Fail-soft: a missing piece shortens the line, it
434
+ // never blocks the door.
435
+ (function welcome() {
405
436
  try {
406
437
  const toolCount = harnesses.filter(h => h.installed).length;
407
438
  const hasProject = intentFiles.length > 0;
408
439
  const dot = teal('●');
409
440
  const tools = `${toolCount} AI tool${toolCount !== 1 ? 's' : ''}`;
410
- if (hasProject && toolCount > 0) {
411
- console.log(` ${dot} ${sage(`${tools} here, all sharing one memory of`)} ${cream(projectName)}${sage(' so you never start this project over.')}`);
441
+ if (hasProject && toolCount > 1) {
442
+ console.log(` ${dot} ${sage('Welcome back. One shared memory of')} ${cream(projectName)}${sage(`, and ${tools} that all read it`)} ${slate('— switch tools mid-thought, nothing re-explained.')}`);
412
443
  } else if (hasProject) {
413
- console.log(` ${dot} ${sage('phewsh holds one verified memory of')} ${cream(projectName)} ${sage('— ready for whatever AI tool you bring.')}`);
414
- } else if (toolCount > 0) {
415
- console.log(` ${dot} ${sage(`${tools} here.`)} ${sage('Run')} ${cream('/init')} ${sage('and they all work from one shared project memory.')}`);
444
+ console.log(` ${dot} ${sage('Welcome back. One verified memory of')} ${cream(projectName)} ${sage('— ready for whatever AI tool you reach for next.')}`);
445
+ } else if (toolCount > 1) {
446
+ console.log(` ${dot} ${sage(`Welcome — you made it. ${tools} here; this is where they share one thread.`)} ${slate('/init')} ${sage('to begin.')}`);
447
+ } else if (toolCount === 1) {
448
+ console.log(` ${dot} ${sage('Welcome — you made it. Add Codex or Gemini and phewsh keeps every tool on the same page.')}`);
416
449
  } else {
417
- console.log(` ${dot} ${sage('Install Claude Code, Codex, or Gemini phewsh keeps them all on the same page.')}`);
450
+ console.log(` ${dot} ${sage('Welcome — you made it. Install Claude Code, Codex, or Gemini and phewsh keeps them all on the same page.')}`);
418
451
  }
419
452
  console.log('');
420
- } catch { /* the summary is a flourish, never a blocker */ }
453
+ } catch { /* the welcome is a flourish, never a blocker */ }
421
454
  })();
422
455
 
423
456
  if (config?.apiKey && !config.apiKey.startsWith('sk-')) {
@@ -554,6 +587,10 @@ async function main() {
554
587
  console.log(` ${b(cream('What are you trying to do?'))}`);
555
588
  console.log(` ${teal('1')} ${sage('Build')} ${slate('·')} ${teal('2')} ${sage('Research')} ${slate('·')} ${teal('3')} ${sage('Decide')} ${slate('·')} ${teal('4')} ${sage('Review')} ${slate('·')} ${teal('5')} ${sage('Ask another model')}`);
556
589
  console.log(` ${slate('pick a number, or just type — your context travels with every route')}`);
590
+ // One quiet line to the deeper power, without dumping the whole command list:
591
+ // the moves that make phewsh more than a chat box — true continuity across
592
+ // harnesses. /help opens everything when they're ready.
593
+ console.log(` ${slate('⌄ deeper:')} ${cream('/work')} ${slate('hand to a tool')} ${slate('·')} ${cream('/council')} ${slate('ask them all')} ${slate('·')} ${cream('/handoff')} ${slate('what carries over')} ${slate('·')} ${cream('/help')} ${slate('all')}`);
557
594
  }
558
595
 
559
596
  // Self-aware guidance: snapshot the session's state so phewsh can recommend
@@ -637,6 +674,7 @@ async function main() {
637
674
  const tools = continuity.toolsInThread(decisions, { project: projectName });
638
675
  const span = tools >= 2 ? slate(` · ${tools} tools, one thread`) : '';
639
676
  console.log(` ${teal('↻')} ${sage('Picking up — ' + line)}${span} ${slate('· /thread')}`);
677
+ console.log(''); // breathe before "What are you trying to do?"
640
678
  } catch { /* continuity is a nicety, never a blocker */ }
641
679
  }
642
680
 
@@ -704,10 +742,8 @@ async function main() {
704
742
  console.log('');
705
743
  if (!route) {
706
744
  // Nothing to route through: no key, no agent CLIs found on this machine.
707
- console.log(` ${b(cream('Welcome to phewsh.'))}`);
708
- console.log('');
709
- console.log(` ${sage('No agent CLI found (Claude Code, Codex, Gemini, Cursor, OpenCode)')}`);
710
- console.log(` ${sage('and no API key set. Either one gets you running:')}`);
745
+ console.log(` ${b(cream('Two ways to get running:'))}`);
746
+ console.log(` ${slate('No agent CLI found (Claude Code, Codex, Gemini, Cursor, OpenCode) and no API key yet.')}`);
711
747
  console.log('');
712
748
  console.log(` ${teal('/key')} ${sage('Set an API key (10 seconds)')}`);
713
749
  console.log(` ${teal('/tour')} ${sage('See what this does (nothing needed)')}`);
@@ -938,7 +974,7 @@ async function main() {
938
974
  'sync', 'harnesses', 'fallback', 'outcomes', 'tour', 'update', 'upgrade',
939
975
  'agents', 'context', 'truth', 'brief', 'wrap', 'reconcile', 'gate', 'reload', 'sequence', 'seq', 'setup', 'system', 'watch',
940
976
  'next', 'recommend', 'guide', 'thread', 'continuity', 'learn', 'stats',
941
- 'pack', 'packs', 'remember', 'commands', 'width',
977
+ 'pack', 'packs', 'remember', 'commands', 'width', 'handoff',
942
978
  ]);
943
979
  const installedIds = harnesses.filter(h => h.installed).map(h => h.id);
944
980
  let turnAbort = null; // AbortController while an API turn streams
@@ -1346,6 +1382,12 @@ async function main() {
1346
1382
  const h = selfheal.heal();
1347
1383
  if (h.healed) console.log(` ${teal('↻')} ${sage("Harness context refreshed from .intent/ — you didn't have to")}`);
1348
1384
  } catch { /* self-heal must never block exit */ }
1385
+ // The handoff is given, not asked for: leaving a project hands you the
1386
+ // verified brief that the next AI tool inherits — the continuity promise
1387
+ // made visible on the way out. Only in a real project; never blocks exit.
1388
+ if (intentFiles.length > 0) {
1389
+ await showHandoff({ projectName, route: route?.id, reason: 'carry this into your next tool', nextHint: false });
1390
+ }
1349
1391
  try { require('../lib/intro').farewell(); } catch { /* sign-off is a nicety */ }
1350
1392
  console.log(` ${sage('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
1351
1393
  if (decisionsThisSession > 0) {
@@ -1658,6 +1700,7 @@ async function main() {
1658
1700
  console.log('');
1659
1701
  console.log(` ${cream('session')}`);
1660
1702
  console.log(` ${teal('/work')} ${slate('[harness]')} ${sage('Preflight, brief, native handoff, automatic postflight')}`);
1703
+ console.log(` ${teal('/handoff')} ${sage('See the verified brief the next AI tool inherits — rendered + saved')}`);
1661
1704
  console.log(` ${teal('/switch')} ${slate('<harness>')} ${sage('Launch another native tool with a freshly verified briefing')}`);
1662
1705
  console.log(` ${teal('/run')} ${slate('<prompt>')} ${sage('One-shot prompt (no history)')}`);
1663
1706
  console.log(` ${teal('esc')} ${sage('Cancel a running turn · clear the input line')}`);
@@ -1785,8 +1828,20 @@ async function main() {
1785
1828
  if (cmd === 'brief') {
1786
1829
  console.log('');
1787
1830
  const { content } = await generateBrief();
1788
- console.log(content);
1831
+ const { renderMarkdown } = require('../lib/md');
1832
+ console.log(renderMarkdown(content));
1789
1833
  console.log('');
1834
+ console.log(` ${slate('this is your handoff — it travels to the next AI tool ·')} ${cream('/handoff')} ${slate('to save + copy it')}`);
1835
+ console.log('');
1836
+ rl.prompt();
1837
+ return;
1838
+ }
1839
+
1840
+ // /handoff — explicitly produce, render, save, and copy the cross-harness
1841
+ // handoff so you can SEE what would carry into the next tool.
1842
+ if (cmd === 'handoff') {
1843
+ console.log(` ${sage('Building your handoff…')}`);
1844
+ await showHandoff({ projectName, route: route?.id, reason: 'what the next AI tool will pick up' });
1790
1845
  rl.prompt();
1791
1846
  return;
1792
1847
  }
@@ -2661,6 +2716,7 @@ async function main() {
2661
2716
  } else {
2662
2717
  console.log(` ${teal('●')} ${sage('Back in phewsh.')} ${slate('No changes detected. 1 kept · 2 undid · 3 redid · 4 flopped · /reconcile · /switch <tool>')}`);
2663
2718
  }
2719
+ console.log(` ${slate('continuing elsewhere?')} ${cream('/handoff')} ${slate('shows the updated brief the next tool inherits')}`);
2664
2720
  console.log('');
2665
2721
  rl.prompt();
2666
2722
  return;
package/lib/md.js CHANGED
@@ -120,4 +120,13 @@ function streamRenderer(write) {
120
120
  };
121
121
  }
122
122
 
123
- module.exports = { inlineMd, renderLine, streamRenderer };
123
+ // Render a complete markdown string to styled, word-wrapped terminal text —
124
+ // the non-streaming counterpart to streamRenderer, for content we already have
125
+ // in full (a handoff brief, a saved record). Carries fence state across lines
126
+ // so code blocks render literally.
127
+ function renderMarkdown(md) {
128
+ const state = { inFence: false };
129
+ return String(md).split('\n').map((line) => renderLine(line, state)).join('\n');
130
+ }
131
+
132
+ module.exports = { inlineMd, renderLine, streamRenderer, renderMarkdown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.15.59",
3
+ "version": "0.15.61",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"