phewsh 0.11.10 → 0.11.11
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 +51 -36
- package/commands/session.js +132 -79
- package/lib/ui.js +252 -0
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -8,6 +8,8 @@ const b = (s) => `\x1b[1m${s}\x1b[0m`; // bold
|
|
|
8
8
|
const d = (s) => `\x1b[2m${s}\x1b[0m`; // dim
|
|
9
9
|
const w = (s) => `\x1b[97m${s}\x1b[0m`; // bright white
|
|
10
10
|
const g = (s) => `\x1b[90m${s}\x1b[0m`; // dark gray
|
|
11
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
12
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
11
13
|
|
|
12
14
|
function showBrand() {
|
|
13
15
|
const fs = require('fs');
|
|
@@ -15,24 +17,32 @@ function showBrand() {
|
|
|
15
17
|
const os = require('os');
|
|
16
18
|
const hasIntent = fs.existsSync(path.join(process.cwd(), '.intent', 'vision.md'));
|
|
17
19
|
const configPath = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} catch { /* no config */ }
|
|
26
|
-
}
|
|
20
|
+
let hasKey = false;
|
|
21
|
+
let email = null;
|
|
22
|
+
try {
|
|
23
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
24
|
+
hasKey = !!config?.apiKey;
|
|
25
|
+
email = config?.email;
|
|
26
|
+
} catch { /* no config */ }
|
|
27
27
|
|
|
28
28
|
console.log('');
|
|
29
29
|
console.log(` ${d('😮\u200d💨')} ${d('🤫')}`);
|
|
30
30
|
console.log('');
|
|
31
31
|
console.log(` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
|
|
32
32
|
console.log(` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
|
|
33
|
-
console.log(` ${g('
|
|
33
|
+
console.log(` ${g('Your project identity for every AI tool.')}`);
|
|
34
34
|
console.log('');
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
// Context-aware hint
|
|
37
|
+
if (!hasKey) {
|
|
38
|
+
console.log(` ${g('Get started:')} ${w('phewsh')} ${g('(guided setup, takes 60 seconds)')}`);
|
|
39
|
+
} else if (hasIntent) {
|
|
40
|
+
console.log(` ${green('●')} .intent/ loaded ${g('·')} ${w('phewsh')} ${g('to chat ·')} ${w('phewsh watch')} ${g('to sync')}`);
|
|
41
|
+
} else if (email) {
|
|
42
|
+
console.log(` ${g('logged in as')} ${email} ${g('·')} ${w('phewsh')} ${g('to start')}`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(` ${g('Ready.')} ${w('phewsh')} ${g('to start a session.')}`);
|
|
45
|
+
}
|
|
36
46
|
console.log('');
|
|
37
47
|
}
|
|
38
48
|
|
|
@@ -64,33 +74,38 @@ function showVersion() {
|
|
|
64
74
|
function showHelp() {
|
|
65
75
|
const pkg = require('../package.json');
|
|
66
76
|
showBrand();
|
|
67
|
-
console.log(` ${g('v' + pkg.version)} · ${g('phewsh.com')}\n`);
|
|
68
|
-
console.log(` ${
|
|
69
|
-
console.log(` ${
|
|
77
|
+
console.log(` ${g('v' + pkg.version)} · ${g('phewsh.com/cli')}\n`);
|
|
78
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
79
|
+
console.log(` ${b('Just type')} ${w('phewsh')} ${b('to start.')} ${g('Everything else is optional.')}`);
|
|
80
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(` ${b(w('start here'))}`);
|
|
83
|
+
console.log(` ${cyan('phewsh')} Open AI session — type naturally, get guided`);
|
|
84
|
+
console.log(` ${cyan('phewsh clarify')} Turn a messy idea into a structured spec`);
|
|
85
|
+
console.log(` ${cyan('phewsh login')} Set up identity + API key`);
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(` ${b(w('project'))}`);
|
|
88
|
+
console.log(` ${cyan('intent')} ${g('Manage .intent/ artifacts — status, open, evolve')}`);
|
|
89
|
+
console.log(` ${cyan('gate')} ${g('Declare constraints (budget, time, skill, urgency)')}`);
|
|
90
|
+
console.log(` ${cyan('context')} ${g('Export portable context for any AI tool')}`);
|
|
91
|
+
console.log(` ${cyan('ai')} ${g('One-shot AI prompt with .intent/ context')}`);
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(` ${b(w('sync'))}`);
|
|
94
|
+
console.log(` ${cyan('push')} ${g('Push .intent/ to cloud')}`);
|
|
95
|
+
console.log(` ${cyan('pull')} ${g('Pull from cloud to .intent/')}`);
|
|
96
|
+
console.log(` ${cyan('watch')} ${g('Live sync — CLAUDE.md + web dashboard auto-update')}`);
|
|
97
|
+
console.log(` ${cyan('serve')} ${g('Execution bridge for the web app')}`);
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(` ${b(w('connect'))}`);
|
|
100
|
+
console.log(` ${cyan('mcp')} ${g('Connect AI agents via MCP protocol')}`);
|
|
101
|
+
console.log(` ${cyan('link')} ${g('Link local .intent/ to cloud project')}`);
|
|
70
102
|
console.log('');
|
|
71
|
-
console.log(` ${b('
|
|
72
|
-
console.log(` ${
|
|
73
|
-
console.log(` ${
|
|
74
|
-
console.log(` ${
|
|
75
|
-
console.log(` ${w('pull')} Pull project from cloud to .intent/`);
|
|
76
|
-
console.log(` ${w('link')} Link local .intent/ to a cloud project`);
|
|
77
|
-
console.log(` ${w('intent')} Manage .intent/ artifacts — status, open, evolve`);
|
|
78
|
-
console.log(` ${w('ai')} One-shot AI prompt (reads .intent/)`);
|
|
79
|
-
console.log(` ${w('gate')} Declare operational constraints (budget, time, skill)`);
|
|
80
|
-
console.log(` ${w('context')} Export portable context for any AI tool`);
|
|
81
|
-
console.log(` ${w('login')} Set up identity, API key, and cloud sync`);
|
|
82
|
-
console.log(` ${w('watch')} Live sync — .intent/ changes push to cloud + CLAUDE.md`);
|
|
83
|
-
console.log(` ${w('serve')} Start live execution bridge for the web app`);
|
|
84
|
-
console.log(` ${w('mcp')} Connect AI agents — setup, sync, status`);
|
|
85
|
-
console.log(` ${w('sap')} Sustainable AI Protocol — usage and accountability`);
|
|
86
|
-
console.log(` ${w('style')} Build your style identity — ingest, profile, sync`);
|
|
87
|
-
console.log(` ${w('mbhd')} MBHD music engine`);
|
|
103
|
+
console.log(` ${b(w('more'))}`);
|
|
104
|
+
console.log(` ${cyan('sap')} ${g('Sustainable AI Protocol — usage tracking')}`);
|
|
105
|
+
console.log(` ${cyan('style')} ${g('Style identity — ingest, profile, sync')}`);
|
|
106
|
+
console.log(` ${cyan('mbhd')} ${g('MBHD music engine')}`);
|
|
88
107
|
console.log('');
|
|
89
|
-
console.log(` ${
|
|
90
|
-
console.log(` ${g('phewsh login')} Set up identity + API key`);
|
|
91
|
-
console.log(` ${g('phewsh')} Open AI session (with .intent/ context)`);
|
|
92
|
-
console.log(` ${g('phewsh clarify')} Compile messy intent → structured spec`);
|
|
93
|
-
console.log(` ${g('phewsh ai run "what\'s next?"')} One-shot prompt`);
|
|
108
|
+
console.log(` ${g('Works standalone · Inside Claude Code · Inside Cursor · With any MCP agent')}`);
|
|
94
109
|
console.log('');
|
|
95
110
|
}
|
|
96
111
|
|
package/commands/session.js
CHANGED
|
@@ -7,6 +7,7 @@ const path = require('path');
|
|
|
7
7
|
const os = require('os');
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
const { trackSap } = require('../lib/supabase');
|
|
10
|
+
const ui = require('../lib/ui');
|
|
10
11
|
|
|
11
12
|
const CONFIG_DIR = path.join(os.homedir(), '.phewsh');
|
|
12
13
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
@@ -16,13 +17,7 @@ const { select, refreshSession: refreshSess } = require('../lib/supabase');
|
|
|
16
17
|
const { readPPS } = require('../lib/pps');
|
|
17
18
|
const { push, pull, ensureValidToken } = require('./sync');
|
|
18
19
|
|
|
19
|
-
const b
|
|
20
|
-
const d = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
21
|
-
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
22
|
-
const g = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
23
|
-
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
24
|
-
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
25
|
-
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
20
|
+
const { b, d, w, g, green, cyan, yellow } = ui;
|
|
26
21
|
|
|
27
22
|
// Sync awareness: compare local .intent/ timestamps with cloud updated_at
|
|
28
23
|
async function checkSyncStatus(config) {
|
|
@@ -145,6 +140,9 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
145
140
|
const body = { model: modelId, max_tokens: 2048, messages, stream: true };
|
|
146
141
|
if (systemPrompt) body.system = systemPrompt;
|
|
147
142
|
|
|
143
|
+
// Start spinner while waiting for first token
|
|
144
|
+
const spin = ui.spinner('thinking');
|
|
145
|
+
|
|
148
146
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
149
147
|
method: 'POST',
|
|
150
148
|
headers: {
|
|
@@ -156,6 +154,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
156
154
|
});
|
|
157
155
|
|
|
158
156
|
if (!response.ok) {
|
|
157
|
+
spin.stop();
|
|
159
158
|
const err = await response.json().catch(() => ({}));
|
|
160
159
|
const msg = err.error?.message || `API error ${response.status}`;
|
|
161
160
|
if (response.status === 401 || msg.includes('invalid')) {
|
|
@@ -167,6 +166,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
167
166
|
let fullResponse = '';
|
|
168
167
|
let promptTokens = null;
|
|
169
168
|
let completionTokens = null;
|
|
169
|
+
let firstToken = true;
|
|
170
170
|
|
|
171
171
|
for await (const chunk of response.body) {
|
|
172
172
|
const text = Buffer.from(chunk).toString('utf-8');
|
|
@@ -177,6 +177,10 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
177
177
|
try {
|
|
178
178
|
const parsed = JSON.parse(data);
|
|
179
179
|
if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
|
|
180
|
+
if (firstToken) {
|
|
181
|
+
spin.stop(); // Clear spinner before first output
|
|
182
|
+
firstToken = false;
|
|
183
|
+
}
|
|
180
184
|
process.stdout.write(parsed.delta.text);
|
|
181
185
|
fullResponse += parsed.delta.text;
|
|
182
186
|
}
|
|
@@ -190,6 +194,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
196
|
|
|
197
|
+
if (firstToken) spin.stop(); // In case we got no tokens
|
|
193
198
|
process.stdout.write('\n');
|
|
194
199
|
|
|
195
200
|
return { content: fullResponse, promptTokens, completionTokens, model: modelId };
|
|
@@ -205,14 +210,10 @@ async function main() {
|
|
|
205
210
|
let totalPromptTokens = 0;
|
|
206
211
|
let totalCompletionTokens = 0;
|
|
207
212
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
console.log(` ${d('😮💨')} ${d('🤫')}`);
|
|
211
|
-
console.log('');
|
|
212
|
-
console.log(` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
|
|
213
|
-
console.log(` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
|
|
214
|
-
console.log('');
|
|
213
|
+
// ── Animated brand reveal ──────────────────────────────
|
|
214
|
+
await ui.brandReveal();
|
|
215
215
|
|
|
216
|
+
// ── First-run welcome or status panel ──────────────────
|
|
216
217
|
if (!config?.apiKey) {
|
|
217
218
|
console.log(` ${b(w('Welcome to PHEWSH.'))}`);
|
|
218
219
|
console.log(` ${g('Your AI knows your project. No more re-explaining.')}`);
|
|
@@ -230,6 +231,7 @@ async function main() {
|
|
|
230
231
|
console.log('');
|
|
231
232
|
console.log(` ${g('Got a key? Type')} /key ${g('to paste it in.')}`);
|
|
232
233
|
console.log(` ${g('Want cloud sync too?')} /login ${g('connects your identity.')}`);
|
|
234
|
+
console.log(` ${g('Curious?')} /tour ${g('to see what PHEWSH can do (no key needed).')}`);
|
|
233
235
|
console.log('');
|
|
234
236
|
} else if (!config.apiKey.startsWith('sk-')) {
|
|
235
237
|
console.log(` ${yellow('!')} Stored API key looks invalid.`);
|
|
@@ -238,6 +240,7 @@ async function main() {
|
|
|
238
240
|
config.apiKey = null; // Don't try to use a bad key
|
|
239
241
|
}
|
|
240
242
|
|
|
243
|
+
// ── Project status ─────────────────────────────────────
|
|
241
244
|
if (intentFiles.length > 0) {
|
|
242
245
|
console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} ${intentFiles.map(f => f.file).join(', ')}`);
|
|
243
246
|
} else {
|
|
@@ -249,6 +252,9 @@ async function main() {
|
|
|
249
252
|
console.log(` ${g(' user:')} ${config.email}`);
|
|
250
253
|
}
|
|
251
254
|
|
|
255
|
+
// ── Interop line ───────────────────────────────────────
|
|
256
|
+
ui.interopLine(config, intentFiles);
|
|
257
|
+
|
|
252
258
|
// Sync status check (non-blocking, 3s timeout)
|
|
253
259
|
if (config?.supabaseUserId && intentFiles.length > 0) {
|
|
254
260
|
const syncResult = await Promise.race([
|
|
@@ -269,17 +275,20 @@ async function main() {
|
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
console.log('');
|
|
278
|
+
ui.divider('─');
|
|
272
279
|
if (!config?.apiKey) {
|
|
273
|
-
console.log(` ${g('type')} /key ${g('to get started · /
|
|
280
|
+
console.log(` ${g('type')} /key ${g('to get started ·')} /tour ${g('to explore ·')} /help ${g('for all commands')}`);
|
|
274
281
|
} else {
|
|
282
|
+
console.log(` ${ui.randomTip()}`);
|
|
275
283
|
console.log(` ${g('type naturally · /help for commands · /quit to exit')}`);
|
|
276
284
|
}
|
|
285
|
+
ui.divider('─');
|
|
277
286
|
console.log('');
|
|
278
287
|
|
|
279
288
|
const rl = readline.createInterface({
|
|
280
289
|
input: process.stdin,
|
|
281
290
|
output: process.stdout,
|
|
282
|
-
prompt: ` ${green('>')} `,
|
|
291
|
+
prompt: ` ${cyan('phewsh')} ${green('>')} `,
|
|
283
292
|
historySize: 100,
|
|
284
293
|
});
|
|
285
294
|
|
|
@@ -301,48 +310,77 @@ async function main() {
|
|
|
301
310
|
|
|
302
311
|
if (cmd === 'quit' || cmd === 'exit' || cmd === 'q') {
|
|
303
312
|
const turns = messages.length / 2;
|
|
304
|
-
console.log(
|
|
313
|
+
console.log('');
|
|
314
|
+
ui.divider('─');
|
|
315
|
+
console.log(` ${g('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
|
|
316
|
+
ui.divider('─');
|
|
317
|
+
console.log('');
|
|
305
318
|
process.exit(0);
|
|
306
319
|
}
|
|
307
320
|
|
|
308
321
|
if (cmd === 'help' || cmd === 'h') {
|
|
309
322
|
console.log(`
|
|
310
|
-
${b('Session commands')}
|
|
323
|
+
${b(w('Session commands'))}
|
|
311
324
|
|
|
312
325
|
${w('conversation')}
|
|
313
|
-
${
|
|
314
|
-
${
|
|
315
|
-
${
|
|
326
|
+
${cyan('/clear')} Clear conversation history
|
|
327
|
+
${cyan('/run')} ${d('<prompt>')} One-shot prompt (doesn't add to conversation)
|
|
328
|
+
${cyan('/quit')} End session
|
|
316
329
|
|
|
317
330
|
${w('project')}
|
|
318
|
-
${
|
|
319
|
-
${
|
|
320
|
-
${
|
|
321
|
-
${
|
|
322
|
-
${
|
|
323
|
-
${
|
|
324
|
-
${
|
|
331
|
+
${cyan('/init')} Create .intent/ artifacts in this directory
|
|
332
|
+
${cyan('/clarify')} AI-assisted artifact generation
|
|
333
|
+
${cyan('/gate')} Declare operational constraints (budget, time, skill)
|
|
334
|
+
${cyan('/export')} Export portable context for other AI tools
|
|
335
|
+
${cyan('/context')} Show loaded .intent/ files
|
|
336
|
+
${cyan('/reload')} Reload .intent/ context from disk
|
|
337
|
+
${cyan('/status')} Show session stats
|
|
325
338
|
|
|
326
339
|
${w('sync')}
|
|
327
|
-
${
|
|
328
|
-
${
|
|
329
|
-
${
|
|
340
|
+
${cyan('/push')} Push .intent/ to cloud
|
|
341
|
+
${cyan('/pull')} Pull .intent/ from cloud (reloads context)
|
|
342
|
+
${cyan('/sync')} Check sync status
|
|
330
343
|
|
|
331
344
|
${w('configuration')}
|
|
332
|
-
${
|
|
333
|
-
${
|
|
334
|
-
${
|
|
335
|
-
${
|
|
336
|
-
${
|
|
337
|
-
${
|
|
338
|
-
|
|
339
|
-
${w('
|
|
340
|
-
${
|
|
345
|
+
${cyan('/login')} Set up identity + cloud sync
|
|
346
|
+
${cyan('/key')} Set or update your API key
|
|
347
|
+
${cyan('/model')} ${d('<name>')} Switch model (sonnet, opus, haiku)
|
|
348
|
+
${cyan('/models')} List available models
|
|
349
|
+
${cyan('/provider')} Show current provider info
|
|
350
|
+
${cyan('/update')} Update phewsh to the latest version
|
|
351
|
+
|
|
352
|
+
${w('explore')}
|
|
353
|
+
${cyan('/tour')} Guided walkthrough of everything PHEWSH can do
|
|
354
|
+
${cyan('/system')} Show current system prompt
|
|
341
355
|
`);
|
|
342
356
|
rl.prompt();
|
|
343
357
|
return;
|
|
344
358
|
}
|
|
345
359
|
|
|
360
|
+
// ── /tour — guided walkthrough ─────────────────────
|
|
361
|
+
if (cmd === 'tour') {
|
|
362
|
+
const pages = ui.TOUR_PAGES;
|
|
363
|
+
let pageIdx = cmdArg ? parseInt(cmdArg) - 1 : 0;
|
|
364
|
+
if (isNaN(pageIdx) || pageIdx < 0) pageIdx = 0;
|
|
365
|
+
if (pageIdx >= pages.length) pageIdx = pages.length - 1;
|
|
366
|
+
|
|
367
|
+
const page = pages[pageIdx];
|
|
368
|
+
console.log('');
|
|
369
|
+
ui.divider('─');
|
|
370
|
+
console.log(` ${b(w(page.title))} ${g(`(${pageIdx + 1}/${pages.length})`)}`);
|
|
371
|
+
ui.divider('─');
|
|
372
|
+
page.body.forEach(line => console.log(line));
|
|
373
|
+
console.log('');
|
|
374
|
+
if (pageIdx < pages.length - 1) {
|
|
375
|
+
console.log(` ${g('next:')} /tour ${pageIdx + 2} ${g('·')} ${g('or /tour 1-' + pages.length + ' to jump')}`);
|
|
376
|
+
} else {
|
|
377
|
+
console.log(` ${green('●')} ${g('End of tour. You\'re ready to go.')}`);
|
|
378
|
+
}
|
|
379
|
+
console.log('');
|
|
380
|
+
rl.prompt();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
346
384
|
if (cmd === 'clear') {
|
|
347
385
|
messages.length = 0;
|
|
348
386
|
console.log(` ${g('conversation cleared')}`);
|
|
@@ -352,8 +390,11 @@ async function main() {
|
|
|
352
390
|
|
|
353
391
|
if (cmd === 'context') {
|
|
354
392
|
if (intentFiles.length > 0) {
|
|
355
|
-
console.log(
|
|
356
|
-
|
|
393
|
+
console.log('');
|
|
394
|
+
console.log(` ${b('Loaded from')} ${cyan('.intent/')}`);
|
|
395
|
+
ui.divider('─');
|
|
396
|
+
intentFiles.forEach(f => console.log(` ${green('●')} ${w(f.file)} ${g('(' + f.content.length + ' chars)')}`));
|
|
397
|
+
ui.divider('─');
|
|
357
398
|
} else {
|
|
358
399
|
console.log(`\n ${g('No .intent/ context found in')} ${process.cwd()}`);
|
|
359
400
|
console.log(` ${g('Run')} /init ${g('to create one')}`);
|
|
@@ -366,16 +407,16 @@ async function main() {
|
|
|
366
407
|
if (cmd === 'status') {
|
|
367
408
|
const turns = messages.length / 2;
|
|
368
409
|
config = loadConfig(); // refresh
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
410
|
+
ui.statusPanel('Session', [
|
|
411
|
+
['Turns', String(turns)],
|
|
412
|
+
['Tokens', `${totalPromptTokens} in → ${totalCompletionTokens} out`],
|
|
413
|
+
['Project', projectName, 'cyan'],
|
|
414
|
+
['Context', intentFiles.length > 0 ? intentFiles.map(f => f.file).join(', ') : 'none', intentFiles.length > 0 ? 'green' : 'yellow'],
|
|
415
|
+
['Model', MODELS[currentModel].name],
|
|
416
|
+
['Provider', MODELS[currentModel].provider],
|
|
417
|
+
['User', config?.email || g('not logged in')],
|
|
418
|
+
['API key', config?.apiKey ? config.apiKey.slice(0, 8) + '...' : 'not set', config?.apiKey ? 'green' : 'yellow'],
|
|
419
|
+
]);
|
|
379
420
|
rl.prompt();
|
|
380
421
|
return;
|
|
381
422
|
}
|
|
@@ -420,7 +461,7 @@ async function main() {
|
|
|
420
461
|
|
|
421
462
|
if (cmd === 'clarify') {
|
|
422
463
|
if (!config?.apiKey) {
|
|
423
|
-
console.log(`\n ${yellow('
|
|
464
|
+
console.log(`\n ${yellow('!')} No API key. Run /key to set one.\n`);
|
|
424
465
|
rl.prompt();
|
|
425
466
|
return;
|
|
426
467
|
}
|
|
@@ -464,7 +505,7 @@ async function main() {
|
|
|
464
505
|
if (content) {
|
|
465
506
|
const outPath = path.join(process.cwd(), '.phewsh.context');
|
|
466
507
|
fs.writeFileSync(outPath, content);
|
|
467
|
-
console.log(`\n ${green('
|
|
508
|
+
console.log(`\n ${green('●')} Written to ${outPath}\n`);
|
|
468
509
|
} else {
|
|
469
510
|
console.log(`\n ${g('No artifacts to export')}\n`);
|
|
470
511
|
}
|
|
@@ -477,16 +518,16 @@ async function main() {
|
|
|
477
518
|
|
|
478
519
|
if (cmd === 'push') {
|
|
479
520
|
if (!config?.supabaseUserId) {
|
|
480
|
-
console.log(`\n ${yellow('
|
|
521
|
+
console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
|
|
481
522
|
rl.prompt();
|
|
482
523
|
return;
|
|
483
524
|
}
|
|
484
525
|
try {
|
|
485
526
|
const token = await ensureValidToken(config);
|
|
486
|
-
if (!token) { console.log(`\n ${yellow('
|
|
527
|
+
if (!token) { console.log(`\n ${yellow('!')} Session expired. Run /login.\n`); rl.prompt(); return; }
|
|
487
528
|
await push(config, token);
|
|
488
529
|
} catch (err) {
|
|
489
|
-
console.error(` ${yellow('
|
|
530
|
+
console.error(` ${yellow('!')} Push failed: ${err.message}\n`);
|
|
490
531
|
}
|
|
491
532
|
rl.prompt();
|
|
492
533
|
return;
|
|
@@ -494,13 +535,13 @@ async function main() {
|
|
|
494
535
|
|
|
495
536
|
if (cmd === 'pull') {
|
|
496
537
|
if (!config?.supabaseUserId) {
|
|
497
|
-
console.log(`\n ${yellow('
|
|
538
|
+
console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
|
|
498
539
|
rl.prompt();
|
|
499
540
|
return;
|
|
500
541
|
}
|
|
501
542
|
try {
|
|
502
543
|
const token = await ensureValidToken(config);
|
|
503
|
-
if (!token) { console.log(`\n ${yellow('
|
|
544
|
+
if (!token) { console.log(`\n ${yellow('!')} Session expired. Run /login.\n`); rl.prompt(); return; }
|
|
504
545
|
await pull(config, token);
|
|
505
546
|
// Reload context after pull
|
|
506
547
|
intentFiles = loadIntentContext();
|
|
@@ -509,7 +550,7 @@ async function main() {
|
|
|
509
550
|
console.log(` ${green('●')} Context reloaded: ${intentFiles.map(f => f.file).join(', ')}`);
|
|
510
551
|
}
|
|
511
552
|
} catch (err) {
|
|
512
|
-
console.error(` ${yellow('
|
|
553
|
+
console.error(` ${yellow('!')} Pull failed: ${err.message}\n`);
|
|
513
554
|
}
|
|
514
555
|
console.log('');
|
|
515
556
|
rl.prompt();
|
|
@@ -519,22 +560,24 @@ async function main() {
|
|
|
519
560
|
if (cmd === 'sync') {
|
|
520
561
|
// Show sync status
|
|
521
562
|
if (!config?.supabaseUserId) {
|
|
522
|
-
console.log(`\n ${yellow('
|
|
563
|
+
console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
|
|
523
564
|
rl.prompt();
|
|
524
565
|
return;
|
|
525
566
|
}
|
|
567
|
+
const syncSpin = ui.spinner('checking sync');
|
|
526
568
|
const syncResult = await checkSyncStatus(config);
|
|
527
569
|
if (!syncResult) {
|
|
528
|
-
|
|
570
|
+
syncSpin.stop(`${g('Could not check sync status')}`);
|
|
529
571
|
} else if (syncResult.status === 'cloud-newer') {
|
|
530
|
-
|
|
572
|
+
syncSpin.stop(`${yellow('↓')} Cloud is newer (${syncResult.ago}) — run /pull`);
|
|
531
573
|
} else if (syncResult.status === 'local-newer') {
|
|
532
|
-
|
|
574
|
+
syncSpin.stop(`${yellow('↑')} Local changes not pushed (${syncResult.ago}) — run /push`);
|
|
533
575
|
} else if (syncResult.status === 'synced') {
|
|
534
|
-
|
|
576
|
+
syncSpin.stop(`${green('↕')} In sync`);
|
|
535
577
|
} else if (syncResult.status === 'local-only') {
|
|
536
|
-
|
|
578
|
+
syncSpin.stop(`${g('↕ Not linked to cloud — run /push to sync')}`);
|
|
537
579
|
}
|
|
580
|
+
console.log('');
|
|
538
581
|
rl.prompt();
|
|
539
582
|
return;
|
|
540
583
|
}
|
|
@@ -575,7 +618,9 @@ async function main() {
|
|
|
575
618
|
return;
|
|
576
619
|
}
|
|
577
620
|
console.log('');
|
|
578
|
-
|
|
621
|
+
ui.divider('─');
|
|
622
|
+
console.log(` ${b(w('Where to get an API key'))}`);
|
|
623
|
+
ui.divider('─');
|
|
579
624
|
console.log('');
|
|
580
625
|
console.log(` ${cyan('Anthropic')} ${g('(recommended)')}`);
|
|
581
626
|
console.log(` ${g('1.')} Go to ${w('console.anthropic.com/settings/keys')}`);
|
|
@@ -585,6 +630,9 @@ async function main() {
|
|
|
585
630
|
console.log(` ${g('1.')} Go to ${w('openrouter.ai/keys')}`);
|
|
586
631
|
console.log(` ${g('2.')} Create key → copy it (starts with sk-or-)`)
|
|
587
632
|
console.log('');
|
|
633
|
+
console.log(` ${g('Note: API keys are separate from ChatGPT Plus / Claude Pro subscriptions.')}`);
|
|
634
|
+
console.log(` ${g('Both providers offer free credits to get started.')}`);
|
|
635
|
+
console.log('');
|
|
588
636
|
const keyRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
589
637
|
keyRl.question(` Paste your API key\n > `, (apiKey) => {
|
|
590
638
|
keyRl.close();
|
|
@@ -605,7 +653,10 @@ async function main() {
|
|
|
605
653
|
}
|
|
606
654
|
|
|
607
655
|
if (cmd === 'models') {
|
|
608
|
-
console.log(
|
|
656
|
+
console.log('');
|
|
657
|
+
ui.divider('─');
|
|
658
|
+
console.log(` ${b(w('Available models'))}`);
|
|
659
|
+
ui.divider('─');
|
|
609
660
|
for (const [key, model] of Object.entries(MODELS)) {
|
|
610
661
|
const active = key === currentModel ? ` ${green('●')}` : '';
|
|
611
662
|
console.log(` ${w(key.padEnd(16))} ${g(model.name)}${active}`);
|
|
@@ -639,35 +690,36 @@ async function main() {
|
|
|
639
690
|
|
|
640
691
|
if (cmd === 'provider') {
|
|
641
692
|
const model = MODELS[currentModel];
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
693
|
+
ui.statusPanel('Provider', [
|
|
694
|
+
['API', 'Anthropic (direct)'],
|
|
695
|
+
['Model', model.name, 'cyan'],
|
|
696
|
+
['Endpoint', 'api.anthropic.com/v1/messages'],
|
|
697
|
+
['Key', config?.apiKey ? config.apiKey.slice(0, 8) + '...' : 'not set', config?.apiKey ? 'green' : 'yellow'],
|
|
698
|
+
]);
|
|
648
699
|
rl.prompt();
|
|
649
700
|
return;
|
|
650
701
|
}
|
|
651
702
|
|
|
652
703
|
if (cmd === 'update' || cmd === 'upgrade') {
|
|
653
|
-
|
|
704
|
+
const updateSpin = ui.spinner('checking for updates');
|
|
654
705
|
try {
|
|
655
706
|
const pkg = require('../../package.json');
|
|
656
707
|
const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(5000) });
|
|
657
708
|
const data = await res.json();
|
|
658
709
|
if (!data.version || data.version === pkg.version) {
|
|
659
|
-
|
|
710
|
+
updateSpin.stop(`${green('●')} Already on the latest version (${pkg.version})`);
|
|
711
|
+
console.log('');
|
|
660
712
|
rl.prompt();
|
|
661
713
|
return;
|
|
662
714
|
}
|
|
663
|
-
|
|
715
|
+
updateSpin.stop(`${cyan(pkg.version)} → ${cyan(data.version)}`);
|
|
664
716
|
console.log(` ${g('Installing...')}\n`);
|
|
665
717
|
const { execSync } = require('child_process');
|
|
666
718
|
execSync(`npm install -g ${pkg.name}@latest`, { stdio: 'inherit' });
|
|
667
719
|
console.log(`\n ${green('●')} Updated to ${data.version}`);
|
|
668
720
|
console.log(` ${g('Restart phewsh to use the new version.')}\n`);
|
|
669
721
|
} catch (err) {
|
|
670
|
-
|
|
722
|
+
updateSpin.stop(`${yellow('!')} Update failed: ${err.message}`);
|
|
671
723
|
console.log(` ${g('You can update manually:')} npm install -g phewsh\n`);
|
|
672
724
|
}
|
|
673
725
|
rl.prompt();
|
|
@@ -681,7 +733,7 @@ async function main() {
|
|
|
681
733
|
return;
|
|
682
734
|
}
|
|
683
735
|
if (!config?.apiKey) {
|
|
684
|
-
console.log(` ${yellow('
|
|
736
|
+
console.log(` ${yellow('!')} No API key. Run /key to set one.`);
|
|
685
737
|
rl.prompt();
|
|
686
738
|
return;
|
|
687
739
|
}
|
|
@@ -727,6 +779,7 @@ async function main() {
|
|
|
727
779
|
console.log('');
|
|
728
780
|
console.log(` ${g('Get a key:')} ${cyan('console.anthropic.com/settings/keys')}`);
|
|
729
781
|
console.log(` ${g('Or try:')} ${cyan('openrouter.ai/keys')}`);
|
|
782
|
+
console.log(` ${g('Explore:')} ${cyan('/tour')} ${g('to see what PHEWSH does (no key needed)')}`);
|
|
730
783
|
console.log('');
|
|
731
784
|
rl.prompt();
|
|
732
785
|
return;
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// phewsh ui — zero-dependency terminal animations and visual helpers
|
|
2
|
+
// Pure ANSI escape sequences. No chalk, no ora, no bloat.
|
|
3
|
+
|
|
4
|
+
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
5
|
+
const d = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
6
|
+
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
7
|
+
const g = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
8
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
9
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
10
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
11
|
+
const magenta = (s) => `\x1b[35m${s}\x1b[0m`;
|
|
12
|
+
const blue = (s) => `\x1b[34m${s}\x1b[0m`;
|
|
13
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
14
|
+
|
|
15
|
+
// ANSI cursor control
|
|
16
|
+
const hide = '\x1b[?25l';
|
|
17
|
+
const show = '\x1b[?25h';
|
|
18
|
+
const up = (n = 1) => `\x1b[${n}A`;
|
|
19
|
+
const clearLine = '\x1b[2K\r';
|
|
20
|
+
|
|
21
|
+
// ── Spinner ──────────────────────────────────────────────
|
|
22
|
+
// Returns { stop } — call stop() when work is done.
|
|
23
|
+
const SPINNER_FRAMES = [' ·', ' · ·', ' · · ·', ' · · · ·', '· · · · ·', ' · · · ·', ' · · ·', ' · ·'];
|
|
24
|
+
const BRAILLE_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
25
|
+
const BREATH_FRAMES = ['░', '▒', '▓', '█', '▓', '▒', '░', ' '];
|
|
26
|
+
const PULSE_FRAMES = ['·', '•', '●', '•'];
|
|
27
|
+
|
|
28
|
+
function spinner(text = 'thinking', style = 'braille') {
|
|
29
|
+
const frames = style === 'dots' ? SPINNER_FRAMES
|
|
30
|
+
: style === 'breath' ? BREATH_FRAMES
|
|
31
|
+
: style === 'pulse' ? PULSE_FRAMES
|
|
32
|
+
: BRAILLE_FRAMES;
|
|
33
|
+
let i = 0;
|
|
34
|
+
let stopped = false;
|
|
35
|
+
process.stdout.write(hide);
|
|
36
|
+
const interval = setInterval(() => {
|
|
37
|
+
if (stopped) return;
|
|
38
|
+
const frame = frames[i % frames.length];
|
|
39
|
+
process.stdout.write(`${clearLine} ${cyan(frame)} ${g(text)}`);
|
|
40
|
+
i++;
|
|
41
|
+
}, style === 'breath' ? 120 : 80);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
update(newText) { text = newText; },
|
|
45
|
+
stop(finalText) {
|
|
46
|
+
stopped = true;
|
|
47
|
+
clearInterval(interval);
|
|
48
|
+
process.stdout.write(`${clearLine}${show}`);
|
|
49
|
+
if (finalText) process.stdout.write(` ${finalText}\n`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Animated brand reveal ────────────────────────────────
|
|
55
|
+
// Draws the PHEWSH logo line by line with a cascading effect.
|
|
56
|
+
function brandReveal(fast = false) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const lines = [
|
|
59
|
+
'',
|
|
60
|
+
` ${d('😮\u200d💨')} ${d('🤫')}`,
|
|
61
|
+
'',
|
|
62
|
+
` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`,
|
|
63
|
+
` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`,
|
|
64
|
+
'',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
if (fast) {
|
|
68
|
+
lines.forEach(l => console.log(l));
|
|
69
|
+
resolve();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let idx = 0;
|
|
74
|
+
const interval = setInterval(() => {
|
|
75
|
+
if (idx >= lines.length) {
|
|
76
|
+
clearInterval(interval);
|
|
77
|
+
resolve();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(lines[idx]);
|
|
81
|
+
idx++;
|
|
82
|
+
}, 60);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Status panel ─────────────────────────────────────────
|
|
87
|
+
// Draws a bordered status box with labeled rows.
|
|
88
|
+
function statusPanel(title, rows) {
|
|
89
|
+
const maxLabel = Math.max(...rows.map(r => r[0].length));
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(` ${b(w(title))}`);
|
|
92
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
93
|
+
for (const [label, value, color] of rows) {
|
|
94
|
+
const colorFn = color === 'green' ? green
|
|
95
|
+
: color === 'yellow' ? yellow
|
|
96
|
+
: color === 'cyan' ? cyan
|
|
97
|
+
: color === 'red' ? red
|
|
98
|
+
: (s) => s;
|
|
99
|
+
console.log(` ${g(label.padEnd(maxLabel + 2))} ${colorFn(value)}`);
|
|
100
|
+
}
|
|
101
|
+
console.log(` ${g('─'.repeat(48))}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Interop badge line ───────────────────────────────────
|
|
106
|
+
// Shows where phewsh is connected / can connect.
|
|
107
|
+
function interopLine(config, intentFiles) {
|
|
108
|
+
const parts = [];
|
|
109
|
+
if (intentFiles.length > 0) parts.push(green('●') + ' .intent/');
|
|
110
|
+
if (config?.apiKey) parts.push(green('●') + ' AI');
|
|
111
|
+
if (config?.supabaseUserId) parts.push(green('●') + ' cloud');
|
|
112
|
+
|
|
113
|
+
// Always show what's available
|
|
114
|
+
const available = [];
|
|
115
|
+
available.push(g('Claude Code'));
|
|
116
|
+
available.push(g('Cursor'));
|
|
117
|
+
available.push(g('ChatGPT'));
|
|
118
|
+
available.push(g('MCP'));
|
|
119
|
+
|
|
120
|
+
if (parts.length > 0) {
|
|
121
|
+
console.log(` ${g('connected')} ${parts.join(g(' · '))}`);
|
|
122
|
+
}
|
|
123
|
+
console.log(` ${g('works with')} ${available.join(g(' · '))}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Divider ──────────────────────────────────────────────
|
|
127
|
+
function divider(char = '·', width = 48) {
|
|
128
|
+
console.log(` ${g(char.repeat(width))}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Typewriter ───────────────────────────────────────────
|
|
132
|
+
// Writes text character by character. Returns a promise.
|
|
133
|
+
function typewrite(text, speed = 20) {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
let i = 0;
|
|
136
|
+
const interval = setInterval(() => {
|
|
137
|
+
if (i >= text.length) {
|
|
138
|
+
clearInterval(interval);
|
|
139
|
+
process.stdout.write('\n');
|
|
140
|
+
resolve();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
process.stdout.write(text[i]);
|
|
144
|
+
i++;
|
|
145
|
+
}, speed);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Welcome tips (rotates each session) ──────────────────
|
|
150
|
+
const TIPS = [
|
|
151
|
+
`${g('tip:')} Type ${w('/clarify')} to turn a messy idea into a structured spec`,
|
|
152
|
+
`${g('tip:')} ${w('/gate')} lets you set budget, time, and skill constraints`,
|
|
153
|
+
`${g('tip:')} Run ${w('phewsh watch')} in another tab for live sync to CLAUDE.md`,
|
|
154
|
+
`${g('tip:')} ${w('/export')} creates a portable context file for any AI tool`,
|
|
155
|
+
`${g('tip:')} ${w('/model opus')} switches to Claude Opus for complex reasoning`,
|
|
156
|
+
`${g('tip:')} Your ${w('.intent/')} files are plain markdown — edit them anytime`,
|
|
157
|
+
`${g('tip:')} ${w('phewsh context --copy')} puts your project context on the clipboard`,
|
|
158
|
+
`${g('tip:')} ${w('/tour')} walks you through everything PHEWSH can do`,
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
function randomTip() {
|
|
162
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Tour content ─────────────────────────────────────────
|
|
166
|
+
const TOUR_PAGES = [
|
|
167
|
+
{
|
|
168
|
+
title: 'What is PHEWSH?',
|
|
169
|
+
body: [
|
|
170
|
+
` PHEWSH gives your project a ${b('portable identity')}.`,
|
|
171
|
+
` Define what you're building once — every AI tool reads it.`,
|
|
172
|
+
'',
|
|
173
|
+
` It works ${w('standalone')} as its own AI shell,`,
|
|
174
|
+
` and ${w('inside')} Claude Code, Cursor, ChatGPT, and any MCP agent.`,
|
|
175
|
+
'',
|
|
176
|
+
` ${g('Your project context lives in')} ${cyan('.intent/')} ${g('— plain markdown files.')}`,
|
|
177
|
+
` ${g('You own them. They travel with your code.')}`,
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
title: 'The .intent/ directory',
|
|
182
|
+
body: [
|
|
183
|
+
` ${cyan('.intent/')}`,
|
|
184
|
+
` ${green('vision.md')} ${g('What this project is and why it exists')}`,
|
|
185
|
+
` ${green('plan.md')} ${g('Strategy, phases, milestones')}`,
|
|
186
|
+
` ${green('next.md')} ${g('Current tasks and what to do right now')}`,
|
|
187
|
+
` ${yellow('gate.json')} ${g('Your constraints (budget, time, skill)')}`,
|
|
188
|
+
'',
|
|
189
|
+
` ${g('Create these with')} /init ${g('or')} /clarify`,
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
title: 'Standalone mode',
|
|
194
|
+
body: [
|
|
195
|
+
` When you run ${w('phewsh')} on its own, you get an AI shell`,
|
|
196
|
+
` that knows your project inside and out.`,
|
|
197
|
+
'',
|
|
198
|
+
` Just type naturally:`,
|
|
199
|
+
` ${cyan('>')} what should I focus on today?`,
|
|
200
|
+
` ${cyan('>')} is my plan realistic given my budget?`,
|
|
201
|
+
` ${cyan('>')} break this feature into tasks`,
|
|
202
|
+
'',
|
|
203
|
+
` ${g('Every message includes your vision, plan, and constraints.')}`,
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title: 'Inside other tools',
|
|
208
|
+
body: [
|
|
209
|
+
` ${b('Claude Code')} ${g('phewsh watch → auto-updates CLAUDE.md')}`,
|
|
210
|
+
` ${b('Cursor')} ${g('phewsh context --file → .phewsh.context')}`,
|
|
211
|
+
` ${b('ChatGPT')} ${g('phewsh context --copy → paste into Custom Instructions')}`,
|
|
212
|
+
` ${b('MCP agents')} ${g('phewsh mcp setup → agents pull tasks automatically')}`,
|
|
213
|
+
'',
|
|
214
|
+
` ${g('Same project identity, every tool. No re-explaining.')}`,
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
title: 'The Decision Gate',
|
|
219
|
+
body: [
|
|
220
|
+
` Before you build, decide ${b('whether')} to build.`,
|
|
221
|
+
` The gate captures what you can actually spend:`,
|
|
222
|
+
'',
|
|
223
|
+
` ${g('Budget')} ${w('$50')} ${g('Skill')} ${w('expert')}`,
|
|
224
|
+
` ${g('Time')} ${w('15 hrs/week')} ${g('Urgency')} ${w('high')}`,
|
|
225
|
+
'',
|
|
226
|
+
` ${g('These constraints shape every AI response.')}`,
|
|
227
|
+
` ${g('Run')} /gate activate ${g('to set yours.')}`,
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
title: 'You\'re ready',
|
|
232
|
+
body: [
|
|
233
|
+
` ${green('●')} Type naturally to chat with your project context`,
|
|
234
|
+
` ${green('●')} ${w('/init')} or ${w('/clarify')} to create .intent/ artifacts`,
|
|
235
|
+
` ${green('●')} ${w('/gate')} to set your constraints`,
|
|
236
|
+
` ${green('●')} ${w('/help')} for all commands`,
|
|
237
|
+
'',
|
|
238
|
+
` ${g('PHEWSH is your project\'s home base.')}`,
|
|
239
|
+
` ${g('The AI that knows you doesn\'t need to be re-taught.')}`,
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
// Colors
|
|
246
|
+
b, d, w, g, green, cyan, yellow, magenta, blue, red,
|
|
247
|
+
// Components
|
|
248
|
+
spinner, brandReveal, statusPanel, interopLine, divider, typewrite,
|
|
249
|
+
randomTip, TOUR_PAGES,
|
|
250
|
+
// ANSI helpers
|
|
251
|
+
hide, show, up, clearLine,
|
|
252
|
+
};
|