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 +2 -0
- package/commands/status.js +118 -0
- package/commands/update.js +18 -3
- package/lib/shims.js +6 -12
- package/package.json +1 -1
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;
|
package/commands/update.js
CHANGED
|
@@ -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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 —
|
|
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
|
-
|
|
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
|
|
128
|
+
if (d && d.tracked && d.commitsSince > 0) record = `${d.commitsSince} behind`;
|
|
131
129
|
} catch { /* nicety */ }
|
|
132
|
-
|
|
133
|
-
|
|
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
|
}
|