phewsh 0.15.31 → 0.15.33

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.
@@ -17,7 +17,7 @@ const intentDir = () => path.join(process.cwd(), '.intent');
17
17
  const { select, refreshSession: refreshSess } = require('../lib/supabase');
18
18
  const { readPPS } = require('../lib/pps');
19
19
  const { push, pull, ensureValidToken } = require('./sync');
20
- const { HARNESSES, interactiveLaunchArgs, listHarnesses, runViaHarness, cancelActive } = require('../lib/harnesses');
20
+ const { HARNESSES, INSTALL, interactiveLaunchArgs, listHarnesses, runViaHarness, cancelActive } = require('../lib/harnesses');
21
21
  const { recordDecision, labelOutcome, pendingDecisions, recentDecisions, outcomeStats, OUTCOMES } = require('../lib/outcomes');
22
22
  const { suggest, suggestAll } = require('../lib/suggest');
23
23
  const continuity = require('../lib/continuity');
@@ -56,6 +56,23 @@ const { recordProject, listProjects, scanForProjects, fmtAgo } = require('../lib
56
56
  const { b, d, w, g, green, cyan, yellow,
57
57
  teal, peach, sage, slate, cream, ember } = ui;
58
58
 
59
+ // Best-effort copy to the system clipboard so the brief survives the native
60
+ // tool taking over the terminal (and any trust/permission gate). Returns true
61
+ // if a clipboard tool accepted it. Never throws — it's a convenience.
62
+ function copyToClipboard(text) {
63
+ const { spawnSync } = require('child_process');
64
+ const tries = process.platform === 'darwin' ? [['pbcopy', []]]
65
+ : process.platform === 'win32' ? [['clip', []]]
66
+ : [['wl-copy', []], ['xclip', ['-selection', 'clipboard']], ['xsel', ['--clipboard', '--input']]];
67
+ for (const [bin, args] of tries) {
68
+ try {
69
+ const r = spawnSync(bin, args, { input: text, stdio: ['pipe', 'ignore', 'ignore'] });
70
+ if (r.status === 0) return true;
71
+ } catch (_) { /* try next */ }
72
+ }
73
+ return false;
74
+ }
75
+
59
76
  // Sync awareness: compare local .intent/ timestamps with cloud updated_at
60
77
  async function checkSyncStatus(config) {
61
78
  if (!config?.supabaseUserId || !config?.supabaseAccessToken) return null;
@@ -2367,7 +2384,16 @@ async function main() {
2367
2384
  return;
2368
2385
  }
2369
2386
  if (!harnesses.find(x => x.id === target)?.installed) {
2370
- console.log(` ${ember('!')} ${sage(h.label + ' is not installed on this machine.')}`);
2387
+ const how = INSTALL[target];
2388
+ console.log('');
2389
+ console.log(` ${ember('!')} ${sage(h.label + ' isn\'t installed on this machine yet.')}`);
2390
+ if (how) {
2391
+ const isCmd = !how.startsWith('see ');
2392
+ console.log(` ${sage('Install:')} ${isCmd ? cream(how) : slate(how)}`);
2393
+ }
2394
+ if (h.auth) console.log(` ${sage('Sign in:')} ${slate(h.auth + ' (the tool prompts you on first run)')}`);
2395
+ console.log(` ${sage('Then run:')} ${cream('phewsh ' + target)} ${slate('— phewsh briefs it and hands off')}`);
2396
+ console.log('');
2371
2397
  rl.prompt();
2372
2398
  return;
2373
2399
  }
@@ -2375,9 +2401,13 @@ async function main() {
2375
2401
  const preflightTruth = await auditTruth();
2376
2402
  const before = captureSnapshot(preflightTruth);
2377
2403
  const generatedBrief = await generateBrief({ report: preflightTruth });
2378
- const launchBrief = `${generatedBrief.content}\n\nThis is a PHEWSH transition briefing. Use it as project context, preserve native tool behavior, and verify claims against the repository before acting. When you finish, exit this tool — PHEWSH resumes, runs an automatic postflight comparing what changed against this brief, and offers reconciliation.`;
2404
+ const launchBrief = `${generatedBrief.content}\n\nYou are receiving a PHEWSH handoff brief — acknowledge in one short line that you've loaded it, then proceed. Use it as project context, preserve native tool behavior, and verify claims against the repository before acting. When you finish, exit this tool — PHEWSH resumes, runs an automatic postflight comparing what changed against this brief, and offers reconciliation.`;
2379
2405
  const savedBrief = persistBrief(generatedBrief.content, { project: projectName, route: target });
2380
2406
  const launch = interactiveLaunchArgs(target, launchBrief, { model: harnessModel });
2407
+ // Foolproof fallback: the brief on the clipboard survives the native
2408
+ // tool taking over the terminal and any trust/permission gate. If the
2409
+ // tool ignores or never received it, the human just pastes it in.
2410
+ const briefOnClipboard = copyToClipboard(launchBrief);
2381
2411
  const decisionId = recordDecision({
2382
2412
  project: projectName,
2383
2413
  route: target,
@@ -2391,8 +2421,13 @@ async function main() {
2391
2421
  console.log(` ${sage('Route:')} ${cream(h.label)}${harnessModel ? slate(' · model ' + harnessModel) : slate(' · native default model')}`);
2392
2422
  console.log(` ${sage('Git:')} ${cream(preflightTruth.git.shortHead || 'unknown')} ${slate('· ' + before.dirty.length + ' uncommitted path(s) before work')}`);
2393
2423
  console.log(` ${sage('Truth:')} ${preflightTruth.conflicts.length ? peach(preflightTruth.conflicts.length + ' conflict(s) carried explicitly') : green('no explicit conflicts')}`);
2394
- console.log(` ${sage('Brief:')} ${launch.briefingPassed ? green('passed directly to the native harness') : peach('not injectable for this harness; available via /brief')}`);
2395
- console.log(` ${sage('Record:')} ${savedBrief.written ? slate('exact briefing saved locally for this transition') : peach('briefing persistence unavailable; launch continues')}`);
2424
+ console.log(` ${sage('Brief:')} ${launch.briefingPassed ? green('auto-attached to ' + h.label) : peach('not auto-injectable for this tool')}${briefOnClipboard ? slate(' · copied to your clipboard') : ''}`);
2425
+ console.log(` ${sage('Record:')} ${savedBrief.written ? slate('exact briefing saved locally' + (savedBrief.file ? ' → ' + savedBrief.file : '')) : peach('briefing persistence unavailable; launch continues')}`);
2426
+ if (briefOnClipboard) {
2427
+ console.log(` ${slate('Foolproof:')} ${sage('if ' + h.label + ' shows a trust prompt or doesn\'t mention the brief, just paste it in')} ${slate('(⌘V / Ctrl+V)')}`);
2428
+ } else if (savedBrief.file) {
2429
+ console.log(` ${slate('Foolproof:')} ${sage('if ' + h.label + ' doesn\'t pick up the brief, paste from')} ${cream(savedBrief.file)}`);
2430
+ }
2396
2431
  console.log(` ${slate('After exit PHEWSH will compare Git, files, intent claims, generated drift, and contradictions.')}`);
2397
2432
  ui.divider('line');
2398
2433
  console.log(` ${teal('●')} ${sage('Handing the terminal to')} ${cream(h.label)} ${slate('— exit to return for postflight')}`);
package/lib/harnesses.js CHANGED
@@ -36,6 +36,27 @@ const HARNESSES = {
36
36
  'droid': { bin: 'droid', label: 'Droid', role: 'agentic coding', bestFor: 'agentic coding', auth: 'Factory account', args: (p) => ['exec', p] },
37
37
  };
38
38
 
39
+ // Best-effort install hints, shown when a harness isn't on the machine yet.
40
+ // Commands where the install path is well-known and stable; a docs pointer
41
+ // otherwise. We never auto-run these — phewsh shows them; the human decides.
42
+ // (Install paths and auth change upstream; treat as a guidepost, not gospel.)
43
+ const INSTALL = {
44
+ 'claude-code': 'npm i -g @anthropic-ai/claude-code',
45
+ 'codex': 'npm i -g @openai/codex',
46
+ 'gemini': 'npm i -g @google/gemini-cli',
47
+ 'cursor': 'curl https://cursor.com/install -fsS | bash',
48
+ 'opencode': 'npm i -g opencode-ai',
49
+ 'grok': 'npm i -g @vibe-kit/grok-cli',
50
+ 'copilot': 'npm i -g @github/copilot',
51
+ 'kiro': 'see kiro.dev/downloads',
52
+ 'hermes': 'see nousresearch.com',
53
+ 'pi': 'see pi.ai',
54
+ 'aider': 'python -m pip install aider-chat',
55
+ 'goose': 'see block.github.io/goose',
56
+ 'amp': 'npm i -g @sourcegraph/amp',
57
+ 'droid': 'see factory.ai',
58
+ };
59
+
39
60
  // In-flight harness children — so ESC in the session can cancel a turn.
40
61
  const ACTIVE_CHILDREN = new Set();
41
62
 
@@ -199,6 +220,7 @@ function runViaHarness(id, systemPrompt, userPrompt, opts = {}) {
199
220
 
200
221
  module.exports = {
201
222
  HARNESSES,
223
+ INSTALL,
202
224
  isInstalled,
203
225
  detectInstalled,
204
226
  interactiveLaunchArgs,
package/lib/ui.js CHANGED
@@ -98,10 +98,10 @@ async function brandReveal(fast = false) {
98
98
  console.log(` ${b(cream('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
99
99
  console.log(` ${b(cream('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
100
100
  await pause(160);
101
- // The value prop, plain language, off the bat — the approved north-star line.
102
- // It exhales in (typewriter) when animated; lands instantly otherwise. Safe:
103
- // the color wrapper is emitted up front, so only the words reveal.
104
- const tagline = 'Stop re-explaining yourself to AI.';
101
+ // The value prop, plain language, off the bat — the alignment wedge, not
102
+ // "memory". It exhales in (typewriter) when animated; lands instantly
103
+ // otherwise. Safe: the color wrapper is emitted up front, only words reveal.
104
+ const tagline = 'One mission. Many AI tools. No lost context.';
105
105
  if (fast || !process.stdout.isTTY) {
106
106
  console.log(` ${sage(tagline)}`);
107
107
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.15.31",
3
+ "version": "0.15.33",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"