phewsh 0.15.44 → 0.15.46

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
@@ -87,6 +87,7 @@ const COMMANDS = {
87
87
  seq: () => require('../commands/sequence')(),
88
88
  ambient: () => require('../commands/ambient')(),
89
89
  shim: () => require('../commands/shim')(),
90
+ status: () => require('../commands/status')(),
90
91
  hook: () => require('../commands/hook')(),
91
92
  welcome: () => require('../commands/welcome')(),
92
93
  intro: () => require('../commands/welcome')(),
@@ -113,6 +114,7 @@ function showHelp() {
113
114
  console.log(` ${cyan('intent')} ${g('Create, view, evolve .intent/ artifacts')}`);
114
115
  console.log(` ${cyan('gate')} ${g('Set constraints (budget, time, skill, urgency)')}`);
115
116
  console.log(` ${cyan('context')} ${g('Export .intent/ for any AI tool')}`);
117
+ console.log(` ${cyan('status')} ${g('git status for AI continuity — truth, record, drift, what\'s wired')}`);
116
118
  console.log(` ${cyan('truth')} ${g('Read-only audit: versions, Git, intent, projections, conflicts')}`);
117
119
  console.log(` ${cyan('brief')} ${g('Provider-ready briefing built from verified project truth')}`);
118
120
  console.log(` ${cyan('ai')} ${g('One-shot prompt with .intent/ context')}
@@ -0,0 +1,118 @@
1
+ // phewsh status — "git status for AI continuity."
2
+ //
3
+ // The product, stated as health. The user's real question isn't "did CLAUDE.md
4
+ // load" — it's "will the next AI know what the last one learned?" This answers
5
+ // it: project truth, the decision record, cross-tool continuity, drift, and how
6
+ // phewsh is wired into this machine. Everything else (hooks, shims, base files,
7
+ // /intent) is implementation that feeds this one view. Offline, deterministic.
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const os = require('os');
12
+
13
+ const b = (s) => `\x1b[1m${s}\x1b[0m`;
14
+ const teal = (s) => `\x1b[38;5;79m${s}\x1b[0m`;
15
+ const sage = (s) => `\x1b[38;5;151m${s}\x1b[0m`;
16
+ const slate = (s) => `\x1b[38;5;247m${s}\x1b[0m`;
17
+ const cream = (s) => `\x1b[38;5;230m${s}\x1b[0m`;
18
+ const peach = (s) => `\x1b[38;5;216m${s}\x1b[0m`;
19
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
20
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
21
+
22
+ const ok = green('✓');
23
+ const off = slate('○');
24
+ const warn = peach('⚠');
25
+
26
+ function row(label, value) {
27
+ return ` ${slate(label.padEnd(13))} ${value}`;
28
+ }
29
+
30
+ function projectName(cwd) {
31
+ try {
32
+ const m = JSON.parse(fs.readFileSync(path.join(cwd, '.intent', 'project.json'), 'utf-8'));
33
+ if (m.name) return m.name;
34
+ } catch { /* fall through */ }
35
+ return path.basename(cwd);
36
+ }
37
+
38
+ function loadDecisions() {
39
+ try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.phewsh', 'outcomes', 'decisions.json'), 'utf-8')); }
40
+ catch { return []; }
41
+ }
42
+
43
+ function loadLedger() {
44
+ try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.phewsh', 'ambient.json'), 'utf-8')); }
45
+ catch { return { applied: {} }; }
46
+ }
47
+
48
+ function main() {
49
+ const cwd = process.cwd();
50
+ const intentDir = path.join(cwd, '.intent');
51
+ const hasIntent = fs.existsSync(intentDir);
52
+ const key = path.basename(cwd);
53
+
54
+ console.log('');
55
+ console.log(` ${b('😮‍💨 PHEWSH STATUS')} ${slate('· ' + projectName(cwd))}`);
56
+ console.log('');
57
+
58
+ // ── Continuity asset (the product) ─────────────────────────────────
59
+ if (!hasIntent) {
60
+ console.log(row('Truth', `${off} ${sage('no shared project truth here yet')}`));
61
+ console.log(row('', slate('create it so the next AI inherits this one\'s context:')));
62
+ console.log(row('', cream('phewsh intent --init')));
63
+ } else {
64
+ const files = fs.readdirSync(intentDir).filter(f => /\.(md|json)$/.test(f));
65
+ const has = (f) => files.includes(f);
66
+ console.log(row('Truth', `${ok} ${sage('.intent present')} ${slate('(' + files.length + ' files)')}`));
67
+ console.log(row('Vision', has('vision.md') ? `${ok} ${slate('loaded')}` : `${off} ${slate('missing')}`));
68
+ console.log(row('Next step', (has('next.md') || has('status.md')) ? `${ok} ${slate('loaded')}` : `${off} ${slate('missing')}`));
69
+
70
+ // Decision record
71
+ let stats = { total: 0, pending: 0 };
72
+ try { stats = require('../lib/outcomes').outcomeStats({ project: key }); } catch { /* none */ }
73
+ const pend = stats.pending ? ` ${warn} ${peach(stats.pending + ' pending')}` : ` ${ok} ${slate('all labeled')}`;
74
+ console.log(row('Record', `${cream(stats.total + ' decisions')}${stats.total ? pend : slate(' — none yet')}`));
75
+
76
+ // Cross-tool continuity
77
+ try {
78
+ const continuity = require('../lib/continuity');
79
+ const decisions = loadDecisions();
80
+ const tools = continuity.toolsInThread(decisions, { project: key });
81
+ const line = continuity.lastLeftOff(decisions, { project: key });
82
+ if (tools >= 2) console.log(row('Continuity', `${ok} ${sage(tools + ' tools, one thread')}${line && line.ts ? slate(' · last ' + continuity.agoText(line.ts)) : ''}`));
83
+ else if (tools === 1) console.log(row('Continuity', `${slate('1 tool so far — continuity builds as you switch tools')}`));
84
+ else console.log(row('Continuity', slate('no activity recorded yet')));
85
+ } catch { /* best-effort */ }
86
+
87
+ // Drift
88
+ try {
89
+ const drift = require('../lib/selfheal').commitsSinceIntent(cwd);
90
+ console.log(row('Drift', drift > 0
91
+ ? `${warn} ${peach(drift + ' commit(s) since .intent updated')} ${slate('— reconcile to refresh')}`
92
+ : `${ok} ${slate('0 — record matches the code')}`));
93
+ } catch { /* best-effort */ }
94
+ }
95
+
96
+ // ── Delivery (implementation — how phewsh reaches your tools) ───────
97
+ console.log('');
98
+ console.log(` ${slate('Delivery')}`);
99
+ const ledger = loadLedger();
100
+ let ambientOn = false;
101
+ try { ambientOn = fs.readFileSync(path.join(os.homedir(), '.claude', 'settings.json'), 'utf-8').includes('phewsh hook session-start'); } catch { /* off */ }
102
+ const gb = (ledger.applied && ledger.applied.globalBase) ? 'base files' : null;
103
+ const sc = (ledger.applied && ledger.applied.slashCommands && ledger.applied.slashCommands.tools) || [];
104
+ const ambientBits = [ambientOn ? 'Claude hook' : null, gb, sc.length ? '/intent in ' + sc.join(', ') : null].filter(Boolean);
105
+ console.log(row('Ambient', (ambientOn || gb) ? `${ok} ${sage('on')} ${slate('(' + (ambientBits.join(' · ') || 'context sync') + ')')}` : `${off} ${slate('off — ')}${cream('phewsh ambient on')}`));
106
+
107
+ let shimInstalled = [];
108
+ try { shimInstalled = require('../lib/shims').shimStatus().installed; } catch { /* none */ }
109
+ console.log(row('Shim', shimInstalled.length
110
+ ? `${ok} ${sage('on')} ${slate('(' + shimInstalled.length + ' tools — banner each launch)')}`
111
+ : `${off} ${slate('off — ')}${cream('phewsh shim on')} ${slate('for a visible launch banner')}`));
112
+
113
+ console.log('');
114
+ console.log(` ${slate('The question this answers:')} ${cream('will the next AI know what the last one learned?')}`);
115
+ console.log('');
116
+ }
117
+
118
+ module.exports = main;
@@ -112,9 +112,24 @@ async function main() {
112
112
  if (code === 0) {
113
113
  console.log(` ${green('✓')} Updated to v${latest}. New shells pick it up immediately.`);
114
114
  } else {
115
- console.log(` ${yellow('Update failed')} (npm exited ${code}).`);
116
- console.log(` ${g('If it was a permissions error, fix your npm prefix or run:')}`);
117
- console.log(` ${w(`sudo npm install -g ${pkg.name}@latest`)}`);
115
+ // NEVER suggest `sudo npm i -g` — that root-owns the package dir and
116
+ // poisons every future non-sudo update (the exact trap users hit). The
117
+ // correct fix is to OWN the package, once, then update normally.
118
+ let prefix = '';
119
+ try {
120
+ prefix = require('child_process').execFileSync('npm', ['config', 'get', 'prefix'],
121
+ { encoding: 'utf-8', timeout: 3000 }).trim();
122
+ } catch { /* unknown prefix */ }
123
+ const pkgDir = prefix ? `${prefix}/lib/node_modules/${pkg.name}` : `$(npm prefix -g)/lib/node_modules/${pkg.name}`;
124
+ const binLink = prefix ? `${prefix}/bin/${pkg.name}` : `$(npm prefix -g)/bin/${pkg.name}`;
125
+ console.log(` ${yellow('Update failed')} (npm exited ${code}) — almost certainly a permissions issue.`);
126
+ console.log(` ${g('The phewsh package is probably root-owned from a past `sudo` install.')}`);
127
+ console.log(` ${b('Own it once, then updates never need sudo again:')}`);
128
+ console.log(` ${w(`sudo chown -R $(whoami) "${pkgDir}"`)}`);
129
+ console.log(` ${w(`sudo chown -h $(whoami) "${binLink}"`)}`);
130
+ console.log(` ${w(`phewsh update`)}`);
131
+ console.log(` ${g('Or reinstall with the installer (handles this for you):')} ${w('curl -fsSL phewsh.com/install.sh | sh')}`);
132
+ console.log(` ${g('Do NOT run `sudo npm i -g` — that is what caused this.')}`);
118
133
  }
119
134
  console.log('');
120
135
  process.exit(code ?? 0);
package/lib/shims.js CHANGED
@@ -118,23 +118,17 @@ function preflightBanner(bin, cwd = process.cwd()) {
118
118
  }
119
119
  return `${C.sage('😮‍💨🤫 phewsh active')} ${C.slate('· → ' + bin)}`;
120
120
  }
121
- // Truth present — show the value + continuity health.
121
+ // Truth present — compact health line (like `git status` in a prompt, not a
122
+ // dump). Details live in `phewsh status`. One launch = one glance.
122
123
  const files = fs.readdirSync(intentDir).filter(f => /\.(md|json)$/.test(f)).length;
123
- const vision = firstLine(path.join(intentDir, 'vision.md'));
124
- const next = firstLine(path.join(intentDir, 'next.md')) || firstLine(path.join(intentDir, 'status.md'));
125
- const decisions = decisionCount(cwd);
126
- let health = '✓ intent';
124
+ let record = 'current';
127
125
  try {
128
126
  const { statusDrift } = require('./truth');
129
127
  const d = statusDrift(cwd);
130
- if (d && d.tracked) health += d.commitsSince > 0 ? ` ${C.peach('⚠ record ' + d.commitsSince + ' behind')}` : ' ✓ record current';
128
+ if (d && d.tracked && d.commitsSince > 0) record = `${d.commitsSince} behind`;
131
129
  } catch { /* nicety */ }
132
-
133
- const lines = [`${C.sage('😮‍💨🤫 phewsh active')} ${C.slate(health)} ${C.slate('· → ' + bin)}`];
134
- if (vision) lines.push(` ${C.slate('vision:')} ${C.cream(vision)}`);
135
- if (next) lines.push(` ${C.slate('next: ')} ${C.cream(next)}${decisions ? C.slate(' · ' + decisions + ' decisions') : ''}`);
136
- else if (decisions) lines.push(` ${C.slate(decisions + ' decisions · ' + files + ' intent files')}`);
137
- return lines.join('\n');
130
+ const continuity = decisionCount(cwd) >= 1 ? 'healthy' : 'building';
131
+ return `${C.sage('😮‍💨 phewsh')} ${C.teal('✓')} ${C.slate('intent: ' + files + ' files · record: ' + record + ' · continuity: ' + continuity)} ${C.slate('· phewsh status → ' + bin)}`;
138
132
  } catch {
139
133
  return `😮‍💨🤫 phewsh active · → ${bin}`; // never break the launch
140
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.15.44",
3
+ "version": "0.15.46",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"