phewsh 0.15.19 → 0.15.22
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 +18 -2
- package/commands/style.js +9 -0
- package/commands/sync.js +58 -4
- package/commands/welcome.js +4 -0
- package/lib/intro.js +83 -0
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -53,6 +53,7 @@ const COMMANDS = {
|
|
|
53
53
|
push: () => require('../commands/push'),
|
|
54
54
|
pull: () => require('../commands/pull'),
|
|
55
55
|
link: () => require('../commands/link'),
|
|
56
|
+
sync: () => require('../commands/sync').main('status'),
|
|
56
57
|
login: () => require('../commands/login'),
|
|
57
58
|
ai: () => require('../commands/ai'),
|
|
58
59
|
style: () => require('../commands/style'),
|
|
@@ -73,6 +74,8 @@ const COMMANDS = {
|
|
|
73
74
|
seq: () => require('../commands/sequence')(),
|
|
74
75
|
ambient: () => require('../commands/ambient')(),
|
|
75
76
|
hook: () => require('../commands/hook')(),
|
|
77
|
+
welcome: () => require('../commands/welcome')(),
|
|
78
|
+
intro: () => require('../commands/welcome')(),
|
|
76
79
|
help: showHelp,
|
|
77
80
|
version: showVersion,
|
|
78
81
|
};
|
|
@@ -157,10 +160,23 @@ function exitAfterUpdate(code = 0) {
|
|
|
157
160
|
setTimeout(() => process.exit(code), 2000);
|
|
158
161
|
}
|
|
159
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
|
+
|
|
160
176
|
if (!command) {
|
|
161
|
-
// Bare `phewsh` —
|
|
177
|
+
// Bare `phewsh` — first run gets the intro, then drop into the session.
|
|
162
178
|
// Session handles missing API key gracefully with /login and /key commands
|
|
163
|
-
COMMANDS.session();
|
|
179
|
+
maybeFirstRunIntro().then(() => COMMANDS.session());
|
|
164
180
|
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
165
181
|
showHelp();
|
|
166
182
|
exitAfterUpdate(0);
|
package/commands/style.js
CHANGED
|
@@ -174,6 +174,15 @@ async function main() {
|
|
|
174
174
|
const args = process.argv.slice(3);
|
|
175
175
|
const config = loadConfig();
|
|
176
176
|
|
|
177
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
178
|
+
console.log('\n phewsh style — your creative identity (StyleTree)');
|
|
179
|
+
console.log('\n Usage:');
|
|
180
|
+
console.log(' phewsh style show your style profile');
|
|
181
|
+
console.log(' phewsh style --ingest <file> add an artifact to learn from');
|
|
182
|
+
console.log(' phewsh style --status same as bare (profile + sync tip)\n');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
177
186
|
// --status
|
|
178
187
|
if (args.includes('--status') || args.length === 0) {
|
|
179
188
|
const entries = loadLocal();
|
package/commands/sync.js
CHANGED
|
@@ -261,11 +261,63 @@ function isAuthError(err) {
|
|
|
261
261
|
return m.includes('jwt') || m.includes('expired') || m.includes('401') || m.includes('unauthorized');
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
function agoMs(ms) {
|
|
265
|
+
const mins = Math.floor(ms / 60000);
|
|
266
|
+
if (mins < 1) return 'just now';
|
|
267
|
+
if (mins < 60) return `${mins}m ago`;
|
|
268
|
+
const hrs = Math.floor(mins / 60);
|
|
269
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
270
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Compare local .intent/ against the cloud copy. Read-only — tells you which
|
|
274
|
+
// way to sync, without doing it.
|
|
275
|
+
async function status(config, token) {
|
|
276
|
+
if (!fs.existsSync(INTENT_DIR)) {
|
|
277
|
+
console.log('\n No .intent/ here. Run `phewsh clarify` or `phewsh intent --init` first.\n');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const pps = readPPS(INTENT_DIR);
|
|
281
|
+
const cloudId = pps?.adapters?.phewsh?.cloud_id;
|
|
282
|
+
const projectName = getProjectName();
|
|
283
|
+
const query = cloudId
|
|
284
|
+
? `id=eq.${cloudId}&user_id=eq.${config.supabaseUserId}&select=id,updated_at`
|
|
285
|
+
: `name=eq.${encodeURIComponent(projectName)}&user_id=eq.${config.supabaseUserId}&select=id,updated_at`;
|
|
286
|
+
|
|
287
|
+
const projects = await select('projects', query, token);
|
|
288
|
+
if (projects.length === 0) {
|
|
289
|
+
console.log(`\n ↕ "${projectName}" isn't in the cloud yet — run \`phewsh push\` to sync.\n`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const project = projects[0];
|
|
293
|
+
const artifacts = await select(
|
|
294
|
+
'artifacts',
|
|
295
|
+
`project_id=eq.${project.id}&user_id=eq.${config.supabaseUserId}&select=updated_at&order=updated_at.desc&limit=1`,
|
|
296
|
+
token
|
|
297
|
+
);
|
|
298
|
+
const cloudTime = artifacts.length > 0
|
|
299
|
+
? new Date(artifacts[0].updated_at).getTime()
|
|
300
|
+
: new Date(project.updated_at).getTime();
|
|
301
|
+
|
|
302
|
+
let latestLocal = 0;
|
|
303
|
+
for (const f of ['vision.md', 'plan.md', 'next.md']) {
|
|
304
|
+
const p = path.join(INTENT_DIR, f);
|
|
305
|
+
if (fs.existsSync(p)) latestLocal = Math.max(latestLocal, fs.statSync(p).mtimeMs);
|
|
306
|
+
}
|
|
307
|
+
if (latestLocal === 0) { console.log('\n ↕ Not linked to cloud — run `phewsh push`.\n'); return; }
|
|
308
|
+
|
|
309
|
+
const drift = Math.abs(cloudTime - latestLocal);
|
|
310
|
+
if (drift < 60000) console.log('\n ↕ In sync — local and cloud match.\n');
|
|
311
|
+
else if (cloudTime > latestLocal) console.log(`\n ↓ Cloud is newer (${agoMs(Date.now() - cloudTime)}) — run \`phewsh pull\`.\n`);
|
|
312
|
+
else console.log(`\n ↑ Local changes not pushed (${agoMs(Date.now() - latestLocal)}) — run \`phewsh push\`.\n`);
|
|
313
|
+
}
|
|
314
|
+
|
|
264
315
|
async function main(direction = 'push') {
|
|
265
316
|
const argv = process.argv.slice(3);
|
|
266
317
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
267
|
-
console.log(`\n phewsh ${direction
|
|
268
|
-
console.log(`
|
|
318
|
+
console.log(`\n phewsh ${direction} — sync .intent/ with phewsh.com/intent`);
|
|
319
|
+
console.log(` sync show which way to sync push .intent/ → cloud`);
|
|
320
|
+
console.log(` pull cloud → .intent/ link adopt a cloud project\n`);
|
|
269
321
|
return;
|
|
270
322
|
}
|
|
271
323
|
|
|
@@ -284,7 +336,9 @@ async function main(direction = 'push') {
|
|
|
284
336
|
// The token can still be rejected server-side (refresh token also expired).
|
|
285
337
|
// Convert that into the same friendly nudge instead of a raw stack trace.
|
|
286
338
|
try {
|
|
287
|
-
if (direction === '
|
|
339
|
+
if (direction === 'status') {
|
|
340
|
+
await status(config, token);
|
|
341
|
+
} else if (direction === 'pull') {
|
|
288
342
|
await pull(config, token);
|
|
289
343
|
} else if (direction === 'link') {
|
|
290
344
|
const cloudId = argv[0] && !argv[0].startsWith('-') ? argv[0] : process.argv[4];
|
|
@@ -306,4 +360,4 @@ async function main(direction = 'push') {
|
|
|
306
360
|
}
|
|
307
361
|
}
|
|
308
362
|
|
|
309
|
-
module.exports = { main, push, pull, link, ensureValidToken, loadConfig };
|
|
363
|
+
module.exports = { main, push, pull, link, status, ensureValidToken, loadConfig };
|
package/lib/intro.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
const LOGO = ['█▀█ █░█ █▀▀ █░█ █▀ █░█', '█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'];
|
|
32
|
+
|
|
33
|
+
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
34
|
+
|
|
35
|
+
async function playIntro(opts = {}) {
|
|
36
|
+
const {
|
|
37
|
+
animated = !!process.stdout.isTTY,
|
|
38
|
+
delay = sleep,
|
|
39
|
+
out = console.log,
|
|
40
|
+
listHarnesses = require('./harnesses').listHarnesses,
|
|
41
|
+
} = opts;
|
|
42
|
+
|
|
43
|
+
const { b, cream, sage, slate, teal, green } = ui;
|
|
44
|
+
const pause = async (ms) => { if (animated) await delay(ms); };
|
|
45
|
+
|
|
46
|
+
out('');
|
|
47
|
+
for (const line of FACE) { out(` ${cream(line)}`); await pause(55); }
|
|
48
|
+
await pause(160);
|
|
49
|
+
out('');
|
|
50
|
+
for (const line of LOGO) { out(` ${b(cream(line))}`); await pause(90); }
|
|
51
|
+
await pause(140);
|
|
52
|
+
out(` ${sage('Keep all your AI tools.')} ${cream('phewsh is the one memory they share.')}`);
|
|
53
|
+
out('');
|
|
54
|
+
await pause(260);
|
|
55
|
+
|
|
56
|
+
// The magic beat: discover the tools already on this machine.
|
|
57
|
+
out(` ${slate('scanning your machine for AI tools…')}`);
|
|
58
|
+
await pause(320);
|
|
59
|
+
|
|
60
|
+
let harnesses = [];
|
|
61
|
+
try { harnesses = listHarnesses().filter((h) => h.installed); } catch { /* none */ }
|
|
62
|
+
|
|
63
|
+
if (harnesses.length === 0) {
|
|
64
|
+
out(` ${slate('· none found yet — install Claude Code, Codex, or Gemini and phewsh picks it up.')}`);
|
|
65
|
+
} else {
|
|
66
|
+
for (const h of harnesses) {
|
|
67
|
+
out(` ${green('✓')} ${cream(h.label.padEnd(14))} ${sage(h.role || '')}`);
|
|
68
|
+
await pause(130);
|
|
69
|
+
}
|
|
70
|
+
await pause(160);
|
|
71
|
+
out('');
|
|
72
|
+
const n = harnesses.length;
|
|
73
|
+
out(` ${teal('●')} ${sage(`Found ${n} tool${n !== 1 ? 's' : ''} — they'll all share one memory now.`)}`);
|
|
74
|
+
}
|
|
75
|
+
out('');
|
|
76
|
+
await pause(160);
|
|
77
|
+
out(` ${sage('Next:')} ${cream('just type to start')} ${slate('·')} ${cream('phewsh setup')} ${slate('to pick a default route')}`);
|
|
78
|
+
out('');
|
|
79
|
+
|
|
80
|
+
return { toolsFound: harnesses.length };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { playIntro, LOGO, FACE };
|