phewsh 0.15.20 → 0.15.23
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 +17 -2
- package/commands/session.js +1 -1
- package/commands/welcome.js +4 -0
- package/lib/intro.js +111 -0
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -74,6 +74,8 @@ const COMMANDS = {
|
|
|
74
74
|
seq: () => require('../commands/sequence')(),
|
|
75
75
|
ambient: () => require('../commands/ambient')(),
|
|
76
76
|
hook: () => require('../commands/hook')(),
|
|
77
|
+
welcome: () => require('../commands/welcome')(),
|
|
78
|
+
intro: () => require('../commands/welcome')(),
|
|
77
79
|
help: showHelp,
|
|
78
80
|
version: showVersion,
|
|
79
81
|
};
|
|
@@ -158,10 +160,23 @@ function exitAfterUpdate(code = 0) {
|
|
|
158
160
|
setTimeout(() => process.exit(code), 2000);
|
|
159
161
|
}
|
|
160
162
|
|
|
163
|
+
async function maybeFirstRunIntro() {
|
|
164
|
+
// First ever run → play the intro once, then never again. Marker, not config,
|
|
165
|
+
// so it's independent of login state. Never blocks the session on failure.
|
|
166
|
+
try {
|
|
167
|
+
const fs = require('fs'), path = require('path'), os = require('os');
|
|
168
|
+
const marker = path.join(os.homedir(), '.phewsh', '.welcomed');
|
|
169
|
+
if (fs.existsSync(marker)) return;
|
|
170
|
+
if (process.stdout.isTTY) await require('../lib/intro').playIntro();
|
|
171
|
+
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
172
|
+
fs.writeFileSync(marker, new Date().toISOString());
|
|
173
|
+
} catch { /* the intro is a nicety — never let it block the session */ }
|
|
174
|
+
}
|
|
175
|
+
|
|
161
176
|
if (!command) {
|
|
162
|
-
// Bare `phewsh` —
|
|
177
|
+
// Bare `phewsh` — first run gets the intro, then drop into the session.
|
|
163
178
|
// Session handles missing API key gracefully with /login and /key commands
|
|
164
|
-
COMMANDS.session();
|
|
179
|
+
maybeFirstRunIntro().then(() => COMMANDS.session());
|
|
165
180
|
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
166
181
|
showHelp();
|
|
167
182
|
exitAfterUpdate(0);
|
package/commands/session.js
CHANGED
|
@@ -1086,7 +1086,7 @@ async function main() {
|
|
|
1086
1086
|
if (global._phewshChildren) {
|
|
1087
1087
|
global._phewshChildren.forEach(c => { try { c.kill(); } catch {} });
|
|
1088
1088
|
}
|
|
1089
|
-
|
|
1089
|
+
try { require('../lib/intro').farewell(); } catch { /* sign-off is a nicety */ }
|
|
1090
1090
|
console.log(` ${sage('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
|
|
1091
1091
|
if (decisionsThisSession > 0) {
|
|
1092
1092
|
const stillPending = pendingDecisions().length;
|
package/lib/intro.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// The first impression. A staggered, terminal-safe reveal: the mark, the
|
|
2
|
+
// promise, then the magic moment — phewsh scans the machine and your installed
|
|
3
|
+
// AI tools light up one by one. That last beat IS the value prop: "it already
|
|
4
|
+
// works with everything you have."
|
|
5
|
+
//
|
|
6
|
+
// Safe by construction: line-by-line printing only, no cursor-up rewrites
|
|
7
|
+
// (Apple Terminal hazard). Non-TTY / piped → everything prints instantly.
|
|
8
|
+
// Timing + harness list + output are injectable so the sequence is testable.
|
|
9
|
+
|
|
10
|
+
const ui = require('./ui');
|
|
11
|
+
|
|
12
|
+
// The exhale mark (😮💨) — rasterized from assets/phew.svg to a 30×14 grid
|
|
13
|
+
// once, baked here so there's no runtime cost. It draws in row by row.
|
|
14
|
+
const FACE = [
|
|
15
|
+
' ███ ██ ███',
|
|
16
|
+
' █ █',
|
|
17
|
+
' █ █',
|
|
18
|
+
' █ █ █ █',
|
|
19
|
+
' █ ██ ██ █',
|
|
20
|
+
'█ █ █ █',
|
|
21
|
+
'█ █████ █████ █',
|
|
22
|
+
'█ █',
|
|
23
|
+
'█ █',
|
|
24
|
+
' █ ██ ██ █ █ █',
|
|
25
|
+
' ██ █ █ █',
|
|
26
|
+
' █ █ ██ █',
|
|
27
|
+
' ███ ███ █',
|
|
28
|
+
' ███ ███',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// The shush mark (🤫) — rasterized from assets/shh.svg, same 30×14 grid.
|
|
32
|
+
// Bookends the session: phew opens it, shh signs off.
|
|
33
|
+
const SHH = [
|
|
34
|
+
' ██ ███',
|
|
35
|
+
' ██ █',
|
|
36
|
+
' █ █',
|
|
37
|
+
' █ ███ ███ █',
|
|
38
|
+
' █ █',
|
|
39
|
+
'█ ██ █',
|
|
40
|
+
'█ █ █ █',
|
|
41
|
+
'█ █ █',
|
|
42
|
+
' █ █',
|
|
43
|
+
' █ █ █ █',
|
|
44
|
+
' █ █ ██ █ █ █',
|
|
45
|
+
' █ █ █ █',
|
|
46
|
+
' ██ █ ██',
|
|
47
|
+
' ██ █',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const LOGO = ['█▀█ █░█ █▀▀ █░█ █▀ █░█', '█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'];
|
|
51
|
+
|
|
52
|
+
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
53
|
+
|
|
54
|
+
async function playIntro(opts = {}) {
|
|
55
|
+
const {
|
|
56
|
+
animated = !!process.stdout.isTTY,
|
|
57
|
+
delay = sleep,
|
|
58
|
+
out = console.log,
|
|
59
|
+
listHarnesses = require('./harnesses').listHarnesses,
|
|
60
|
+
} = opts;
|
|
61
|
+
|
|
62
|
+
const { b, cream, sage, slate, teal, green } = ui;
|
|
63
|
+
const pause = async (ms) => { if (animated) await delay(ms); };
|
|
64
|
+
|
|
65
|
+
out('');
|
|
66
|
+
for (const line of FACE) { out(` ${cream(line)}`); await pause(55); }
|
|
67
|
+
await pause(160);
|
|
68
|
+
out('');
|
|
69
|
+
for (const line of LOGO) { out(` ${b(cream(line))}`); await pause(90); }
|
|
70
|
+
await pause(140);
|
|
71
|
+
out(` ${sage('Keep all your AI tools.')} ${cream('phewsh is the one memory they share.')}`);
|
|
72
|
+
out('');
|
|
73
|
+
await pause(260);
|
|
74
|
+
|
|
75
|
+
// The magic beat: discover the tools already on this machine.
|
|
76
|
+
out(` ${slate('scanning your machine for AI tools…')}`);
|
|
77
|
+
await pause(320);
|
|
78
|
+
|
|
79
|
+
let harnesses = [];
|
|
80
|
+
try { harnesses = listHarnesses().filter((h) => h.installed); } catch { /* none */ }
|
|
81
|
+
|
|
82
|
+
if (harnesses.length === 0) {
|
|
83
|
+
out(` ${slate('· none found yet — install Claude Code, Codex, or Gemini and phewsh picks it up.')}`);
|
|
84
|
+
} else {
|
|
85
|
+
for (const h of harnesses) {
|
|
86
|
+
out(` ${green('✓')} ${cream(h.label.padEnd(14))} ${sage(h.role || '')}`);
|
|
87
|
+
await pause(130);
|
|
88
|
+
}
|
|
89
|
+
await pause(160);
|
|
90
|
+
out('');
|
|
91
|
+
const n = harnesses.length;
|
|
92
|
+
out(` ${teal('●')} ${sage(`Found ${n} tool${n !== 1 ? 's' : ''} — they'll all share one memory now.`)}`);
|
|
93
|
+
}
|
|
94
|
+
out('');
|
|
95
|
+
await pause(160);
|
|
96
|
+
out(` ${sage('Next:')} ${cream('just type to start')} ${slate('·')} ${cream('phewsh setup')} ${slate('to pick a default route')}`);
|
|
97
|
+
out('');
|
|
98
|
+
|
|
99
|
+
return { toolsFound: harnesses.length };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Quiet sign-off — the shush mark, printed static (no animation; exit is instant).
|
|
103
|
+
function farewell(opts = {}) {
|
|
104
|
+
const { out = console.log } = opts;
|
|
105
|
+
const { slate } = ui;
|
|
106
|
+
out('');
|
|
107
|
+
for (const line of SHH) out(` ${slate(line)}`);
|
|
108
|
+
out('');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { playIntro, farewell, LOGO, FACE, SHH };
|