phewsh 0.15.59 → 0.15.60

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;
@@ -938,7 +967,7 @@ async function main() {
938
967
  'sync', 'harnesses', 'fallback', 'outcomes', 'tour', 'update', 'upgrade',
939
968
  'agents', 'context', 'truth', 'brief', 'wrap', 'reconcile', 'gate', 'reload', 'sequence', 'seq', 'setup', 'system', 'watch',
940
969
  'next', 'recommend', 'guide', 'thread', 'continuity', 'learn', 'stats',
941
- 'pack', 'packs', 'remember', 'commands', 'width',
970
+ 'pack', 'packs', 'remember', 'commands', 'width', 'handoff',
942
971
  ]);
943
972
  const installedIds = harnesses.filter(h => h.installed).map(h => h.id);
944
973
  let turnAbort = null; // AbortController while an API turn streams
@@ -1346,6 +1375,12 @@ async function main() {
1346
1375
  const h = selfheal.heal();
1347
1376
  if (h.healed) console.log(` ${teal('↻')} ${sage("Harness context refreshed from .intent/ — you didn't have to")}`);
1348
1377
  } catch { /* self-heal must never block exit */ }
1378
+ // The handoff is given, not asked for: leaving a project hands you the
1379
+ // verified brief that the next AI tool inherits — the continuity promise
1380
+ // made visible on the way out. Only in a real project; never blocks exit.
1381
+ if (intentFiles.length > 0) {
1382
+ await showHandoff({ projectName, route: route?.id, reason: 'carry this into your next tool', nextHint: false });
1383
+ }
1349
1384
  try { require('../lib/intro').farewell(); } catch { /* sign-off is a nicety */ }
1350
1385
  console.log(` ${sage('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
1351
1386
  if (decisionsThisSession > 0) {
@@ -1658,6 +1693,7 @@ async function main() {
1658
1693
  console.log('');
1659
1694
  console.log(` ${cream('session')}`);
1660
1695
  console.log(` ${teal('/work')} ${slate('[harness]')} ${sage('Preflight, brief, native handoff, automatic postflight')}`);
1696
+ console.log(` ${teal('/handoff')} ${sage('See the verified brief the next AI tool inherits — rendered + saved')}`);
1661
1697
  console.log(` ${teal('/switch')} ${slate('<harness>')} ${sage('Launch another native tool with a freshly verified briefing')}`);
1662
1698
  console.log(` ${teal('/run')} ${slate('<prompt>')} ${sage('One-shot prompt (no history)')}`);
1663
1699
  console.log(` ${teal('esc')} ${sage('Cancel a running turn · clear the input line')}`);
@@ -1785,8 +1821,20 @@ async function main() {
1785
1821
  if (cmd === 'brief') {
1786
1822
  console.log('');
1787
1823
  const { content } = await generateBrief();
1788
- console.log(content);
1824
+ const { renderMarkdown } = require('../lib/md');
1825
+ console.log(renderMarkdown(content));
1789
1826
  console.log('');
1827
+ console.log(` ${slate('this is your handoff — it travels to the next AI tool ·')} ${cream('/handoff')} ${slate('to save + copy it')}`);
1828
+ console.log('');
1829
+ rl.prompt();
1830
+ return;
1831
+ }
1832
+
1833
+ // /handoff — explicitly produce, render, save, and copy the cross-harness
1834
+ // handoff so you can SEE what would carry into the next tool.
1835
+ if (cmd === 'handoff') {
1836
+ console.log(` ${sage('Building your handoff…')}`);
1837
+ await showHandoff({ projectName, route: route?.id, reason: 'what the next AI tool will pick up' });
1790
1838
  rl.prompt();
1791
1839
  return;
1792
1840
  }
@@ -2661,6 +2709,7 @@ async function main() {
2661
2709
  } else {
2662
2710
  console.log(` ${teal('●')} ${sage('Back in phewsh.')} ${slate('No changes detected. 1 kept · 2 undid · 3 redid · 4 flopped · /reconcile · /switch <tool>')}`);
2663
2711
  }
2712
+ console.log(` ${slate('continuing elsewhere?')} ${cream('/handoff')} ${slate('shows the updated brief the next tool inherits')}`);
2664
2713
  console.log('');
2665
2714
  rl.prompt();
2666
2715
  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.60",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"