phewsh 0.11.11 → 0.11.13

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.
@@ -17,7 +17,9 @@ const { select, refreshSession: refreshSess } = require('../lib/supabase');
17
17
  const { readPPS } = require('../lib/pps');
18
18
  const { push, pull, ensureValidToken } = require('./sync');
19
19
 
20
- const { b, d, w, g, green, cyan, yellow } = ui;
20
+ // Brand palette shortcuts
21
+ const { b, d, w, g, green, cyan, yellow,
22
+ teal, peach, sage, slate, cream, ember } = ui;
21
23
 
22
24
  // Sync awareness: compare local .intent/ timestamps with cloud updated_at
23
25
  async function checkSyncStatus(config) {
@@ -32,7 +34,6 @@ async function checkSyncStatus(config) {
32
34
  const cloudId = pps?.adapters?.phewsh?.cloud_id;
33
35
  const projectName = path.basename(process.cwd());
34
36
 
35
- // Find cloud project
36
37
  const query = cloudId
37
38
  ? `id=eq.${cloudId}&user_id=eq.${config.supabaseUserId}&select=id,updated_at`
38
39
  : `name=eq.${encodeURIComponent(projectName)}&user_id=eq.${config.supabaseUserId}&select=id,updated_at`;
@@ -42,7 +43,6 @@ async function checkSyncStatus(config) {
42
43
 
43
44
  const project = projects[0];
44
45
 
45
- // Get latest cloud artifact updated_at
46
46
  const artifacts = await select(
47
47
  'artifacts',
48
48
  `project_id=eq.${project.id}&user_id=eq.${config.supabaseUserId}&select=kind,updated_at&order=updated_at.desc&limit=1`,
@@ -53,7 +53,6 @@ async function checkSyncStatus(config) {
53
53
  ? new Date(artifacts[0].updated_at).getTime()
54
54
  : new Date(project.updated_at).getTime();
55
55
 
56
- // Get latest local file mtime
57
56
  const localFiles = ['vision.md', 'plan.md', 'next.md'];
58
57
  let latestLocal = 0;
59
58
  for (const file of localFiles) {
@@ -67,7 +66,6 @@ async function checkSyncStatus(config) {
67
66
  if (latestLocal === 0) return { status: 'local-only' };
68
67
 
69
68
  const drift = Math.abs(cloudTime - latestLocal);
70
- // Within 60 seconds = synced
71
69
  if (drift < 60000) return { status: 'synced' };
72
70
 
73
71
  if (cloudTime > latestLocal) {
@@ -78,7 +76,7 @@ async function checkSyncStatus(config) {
78
76
  return { status: 'local-newer', ago };
79
77
  }
80
78
  } catch {
81
- return null; // Network error — silently skip
79
+ return null;
82
80
  }
83
81
  }
84
82
 
@@ -140,7 +138,6 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
140
138
  const body = { model: modelId, max_tokens: 2048, messages, stream: true };
141
139
  if (systemPrompt) body.system = systemPrompt;
142
140
 
143
- // Start spinner while waiting for first token
144
141
  const spin = ui.spinner('thinking');
145
142
 
146
143
  const response = await fetch('https://api.anthropic.com/v1/messages', {
@@ -178,7 +175,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
178
175
  const parsed = JSON.parse(data);
179
176
  if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
180
177
  if (firstToken) {
181
- spin.stop(); // Clear spinner before first output
178
+ spin.stop();
182
179
  firstToken = false;
183
180
  }
184
181
  process.stdout.write(parsed.delta.text);
@@ -194,7 +191,7 @@ async function streamChat(apiKey, messages, systemPrompt, modelId) {
194
191
  }
195
192
  }
196
193
 
197
- if (firstToken) spin.stop(); // In case we got no tokens
194
+ if (firstToken) spin.stop();
198
195
  process.stdout.write('\n');
199
196
 
200
197
  return { content: fullResponse, promptTokens, completionTokens, model: modelId };
@@ -204,58 +201,59 @@ async function main() {
204
201
  let config = loadConfig();
205
202
  let intentFiles = loadIntentContext();
206
203
  let systemPrompt = buildSystemPrompt(intentFiles);
207
- const messages = []; // conversation history
204
+ const messages = [];
208
205
  const projectName = path.basename(process.cwd());
209
206
  let currentModel = DEFAULT_MODEL;
210
207
  let totalPromptTokens = 0;
211
208
  let totalCompletionTokens = 0;
212
209
 
213
- // ── Animated brand reveal ──────────────────────────────
210
+ // ── The Exhale: animated brand reveal ──────────────────
214
211
  await ui.brandReveal();
215
212
 
216
- // ── First-run welcome or status panel ──────────────────
213
+ // ── First-run welcome ──────────────────────────────────
217
214
  if (!config?.apiKey) {
218
- console.log(` ${b(w('Welcome to PHEWSH.'))}`);
219
- console.log(` ${g('Your AI knows your project. No more re-explaining.')}`);
215
+ console.log(` ${b(cream('Welcome.'))}`);
216
+ console.log(` ${sage('.intent/ is your project\'s working memory.')}`);
217
+ console.log(` ${sage('Define it once — every AI tool reads it. No more re-explaining.')}`);
220
218
  console.log('');
221
- console.log(` ${w('To chat, you need an API key.')} ${g('(not a subscription)')}`);
222
- console.log(` ${g('ChatGPT Plus / Claude Pro are separate API keys are pay-as-you-go.')}`);
219
+ console.log(` ${cream('To chat, you need an API key.')} ${slate('(not a subscription)')}`);
220
+ console.log(` ${slate('ChatGPT Plus / Claude Pro don\'t include API access.')}`);
221
+ console.log(` ${slate('API keys are pay-as-you-go — both providers offer free credits.')}`);
223
222
  console.log('');
224
- console.log(` ${cyan('1')} ${b('Anthropic')} ${g('(recommended)')}`);
225
- console.log(` ${g('console.anthropic.com/settings/keys')}`);
226
- console.log(` ${g('Direct access to Claude. Best quality. ~$0.01/message.')}`);
223
+ console.log(` ${teal('1')} ${b(cream('Anthropic'))} ${slate('(recommended)')}`);
224
+ console.log(` ${sage('console.anthropic.com/settings/keys')}`);
225
+ console.log(` ${slate('Direct Claude access. Best quality. ~$0.01/message.')}`);
227
226
  console.log('');
228
- console.log(` ${cyan('2')} ${b('OpenRouter')}`);
229
- console.log(` ${g('openrouter.ai/keys')}`);
230
- console.log(` ${g('One key, many models (Claude, GPT, Gemini, etc.)')}`);
227
+ console.log(` ${teal('2')} ${b(cream('OpenRouter'))}`);
228
+ console.log(` ${sage('openrouter.ai/keys')}`);
229
+ console.log(` ${slate('One key Claude, GPT, Gemini, and more.')}`);
231
230
  console.log('');
232
- console.log(` ${g('Got a key? Type')} /key ${g('to paste it in.')}`);
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).')}`);
231
+ console.log(` ${sage('Got a key?')} ${cream('/key')} ${sage('to paste it in.')}`);
232
+ console.log(` ${sage('Curious?')} ${cream('/tour')} ${sage('to explore (no key needed).')}`);
235
233
  console.log('');
236
234
  } else if (!config.apiKey.startsWith('sk-')) {
237
- console.log(` ${yellow('!')} Stored API key looks invalid.`);
238
- console.log(` ${g('Run')} /key ${g('to set a new one')}`);
235
+ console.log(` ${ember('!')} ${sage('Stored API key looks invalid.')}`);
236
+ console.log(` ${sage('Run')} ${cream('/key')} ${sage('to set a new one')}`);
239
237
  console.log('');
240
- config.apiKey = null; // Don't try to use a bad key
238
+ config.apiKey = null;
241
239
  }
242
240
 
243
241
  // ── Project status ─────────────────────────────────────
244
242
  if (intentFiles.length > 0) {
245
- console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} ${intentFiles.map(f => f.file).join(', ')}`);
243
+ console.log(` ${teal('●')} ${cream(projectName)} ${slate('·')} ${sage(intentFiles.map(f => f.file).join(', '))}`);
246
244
  } else {
247
- console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} no .intent/ context`);
248
- console.log(` ${g(' run /init to create .intent/ artifacts')}`);
245
+ console.log(` ${teal('●')} ${cream(projectName)} ${slate('·')} ${sage('no .intent/ context')}`);
246
+ console.log(` ${slate(' run /init to create .intent/ artifacts')}`);
249
247
  }
250
- console.log(` ${g(' model:')} ${MODELS[currentModel].name}`);
248
+ console.log(` ${slate(' model:')} ${sage(MODELS[currentModel].name)}`);
251
249
  if (config?.email) {
252
- console.log(` ${g(' user:')} ${config.email}`);
250
+ console.log(` ${slate(' user:')} ${sage(config.email)}`);
253
251
  }
254
252
 
255
- // ── Interop line ───────────────────────────────────────
253
+ // ── Interop ────────────────────────────────────────────
256
254
  ui.interopLine(config, intentFiles);
257
255
 
258
- // Sync status check (non-blocking, 3s timeout)
256
+ // Sync status (non-blocking)
259
257
  if (config?.supabaseUserId && intentFiles.length > 0) {
260
258
  const syncResult = await Promise.race([
261
259
  checkSyncStatus(config),
@@ -263,32 +261,32 @@ async function main() {
263
261
  ]);
264
262
  if (syncResult) {
265
263
  if (syncResult.status === 'cloud-newer') {
266
- console.log(` ${yellow('↓')} Cloud is newer (${syncResult.ago}) — run /pull`);
264
+ console.log(` ${ember('↓')} ${sage('Cloud is newer (' + syncResult.ago + ') — run /pull')}`);
267
265
  } else if (syncResult.status === 'local-newer') {
268
- console.log(` ${yellow('↑')} Local changes not pushed (${syncResult.ago}) — run /push`);
266
+ console.log(` ${ember('↑')} ${sage('Local changes not pushed (' + syncResult.ago + ') — run /push')}`);
269
267
  } else if (syncResult.status === 'synced') {
270
- console.log(` ${green('↕')} ${g('synced')}`);
268
+ console.log(` ${teal('↕')} ${slate('synced')}`);
271
269
  } else if (syncResult.status === 'local-only') {
272
- console.log(` ${g('↕ not linked to cloud — run /push to sync')}`);
270
+ console.log(` ${slate('↕ not linked to cloud — run /push to sync')}`);
273
271
  }
274
272
  }
275
273
  }
276
274
 
277
275
  console.log('');
278
- ui.divider('');
276
+ ui.divider('line');
279
277
  if (!config?.apiKey) {
280
- console.log(` ${g('type')} /key ${g('to get started ·')} /tour ${g('to explore ·')} /help ${g('for all commands')}`);
278
+ console.log(` ${sage('type')} ${cream('/key')} ${sage('to get started ·')} ${cream('/tour')} ${sage('to explore ·')} ${cream('/help')} ${sage('for commands')}`);
281
279
  } else {
282
280
  console.log(` ${ui.randomTip()}`);
283
- console.log(` ${g('type naturally · /help for commands · /quit to exit')}`);
281
+ console.log(` ${sage('type naturally ·')} ${cream('/help')} ${sage('for commands ·')} ${cream('/quit')} ${sage('to exit')}`);
284
282
  }
285
- ui.divider('');
283
+ ui.divider('line');
286
284
  console.log('');
287
285
 
288
286
  const rl = readline.createInterface({
289
287
  input: process.stdin,
290
288
  output: process.stdout,
291
- prompt: ` ${cyan('phewsh')} ${green('>')} `,
289
+ prompt: ` ${teal('phewsh')} ${sage('>')} `,
292
290
  historySize: 100,
293
291
  });
294
292
 
@@ -311,53 +309,55 @@ async function main() {
311
309
  if (cmd === 'quit' || cmd === 'exit' || cmd === 'q') {
312
310
  const turns = messages.length / 2;
313
311
  console.log('');
314
- ui.divider('');
315
- console.log(` ${g('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
316
- ui.divider('');
312
+ ui.divider('line');
313
+ console.log(` ${sage('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
314
+ ui.divider('line');
317
315
  console.log('');
318
316
  process.exit(0);
319
317
  }
320
318
 
321
319
  if (cmd === 'help' || cmd === 'h') {
322
- console.log(`
323
- ${b(w('Session commands'))}
324
-
325
- ${w('conversation')}
326
- ${cyan('/clear')} Clear conversation history
327
- ${cyan('/run')} ${d('<prompt>')} One-shot prompt (doesn't add to conversation)
328
- ${cyan('/quit')} End session
329
-
330
- ${w('project')}
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
338
-
339
- ${w('sync')}
340
- ${cyan('/push')} Push .intent/ to cloud
341
- ${cyan('/pull')} Pull .intent/ from cloud (reloads context)
342
- ${cyan('/sync')} Check sync status
343
-
344
- ${w('configuration')}
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
355
- `);
320
+ console.log('');
321
+ ui.divider('line');
322
+ console.log(` ${b(cream('Session commands'))}`);
323
+ ui.divider('line');
324
+ console.log('');
325
+ console.log(` ${cream('conversation')}`);
326
+ console.log(` ${teal('/clear')} ${sage('Clear conversation history')}`);
327
+ console.log(` ${teal('/run')} ${slate('<prompt>')} ${sage('One-shot prompt (no history)')}`);
328
+ console.log(` ${teal('/quit')} ${sage('End session')}`);
329
+ console.log('');
330
+ console.log(` ${cream('project')}`);
331
+ console.log(` ${teal('/init')} ${sage('Create .intent/ artifacts in this directory')}`);
332
+ console.log(` ${teal('/clarify')} ${sage('AI-assisted artifact generation')}`);
333
+ console.log(` ${teal('/gate')} ${sage('Declare constraints (budget, time, skill)')}`);
334
+ console.log(` ${teal('/export')} ${sage('Export portable context for other AI tools')}`);
335
+ console.log(` ${teal('/context')} ${sage('Show loaded .intent/ files')}`);
336
+ console.log(` ${teal('/reload')} ${sage('Reload .intent/ context from disk')}`);
337
+ console.log(` ${teal('/status')} ${sage('Show session stats')}`);
338
+ console.log('');
339
+ console.log(` ${cream('sync')}`);
340
+ console.log(` ${teal('/push')} ${sage('Push .intent/ to cloud')}`);
341
+ console.log(` ${teal('/pull')} ${sage('Pull .intent/ from cloud (reloads context)')}`);
342
+ console.log(` ${teal('/sync')} ${sage('Check sync status')}`);
343
+ console.log('');
344
+ console.log(` ${cream('configuration')}`);
345
+ console.log(` ${teal('/login')} ${sage('Set up identity + cloud sync')}`);
346
+ console.log(` ${teal('/key')} ${sage('Set or update your API key')}`);
347
+ console.log(` ${teal('/model')} ${slate('<name>')} ${sage('Switch model (sonnet, opus, haiku)')}`);
348
+ console.log(` ${teal('/models')} ${sage('List available models')}`);
349
+ console.log(` ${teal('/provider')} ${sage('Show current provider info')}`);
350
+ console.log(` ${teal('/update')} ${sage('Update phewsh to the latest version')}`);
351
+ console.log('');
352
+ console.log(` ${cream('explore')}`);
353
+ console.log(` ${teal('/tour')} ${sage('Guided walkthrough of PHEWSH')}`);
354
+ console.log(` ${teal('/system')} ${sage('Show current system prompt')}`);
355
+ console.log('');
356
356
  rl.prompt();
357
357
  return;
358
358
  }
359
359
 
360
- // ── /tour — guided walkthrough ─────────────────────
360
+ // ── /tour ──────────────────────────────────────────
361
361
  if (cmd === 'tour') {
362
362
  const pages = ui.TOUR_PAGES;
363
363
  let pageIdx = cmdArg ? parseInt(cmdArg) - 1 : 0;
@@ -366,15 +366,15 @@ async function main() {
366
366
 
367
367
  const page = pages[pageIdx];
368
368
  console.log('');
369
- ui.divider('');
370
- console.log(` ${b(w(page.title))} ${g(`(${pageIdx + 1}/${pages.length})`)}`);
371
- ui.divider('');
369
+ ui.divider('line');
370
+ console.log(` ${b(cream(page.title))} ${slate(`(${pageIdx + 1}/${pages.length})`)}`);
371
+ ui.divider('line');
372
372
  page.body.forEach(line => console.log(line));
373
373
  console.log('');
374
374
  if (pageIdx < pages.length - 1) {
375
- console.log(` ${g('next:')} /tour ${pageIdx + 2} ${g('·')} ${g('or /tour 1-' + pages.length + ' to jump')}`);
375
+ console.log(` ${sage('next:')} ${cream('/tour ' + (pageIdx + 2))} ${slate('·')} ${sage('/tour 1-' + pages.length + ' to jump')}`);
376
376
  } else {
377
- console.log(` ${green('●')} ${g('End of tour. You\'re ready to go.')}`);
377
+ console.log(` ${teal('●')} ${sage('End of tour. You\'re ready.')}`);
378
378
  }
379
379
  console.log('');
380
380
  rl.prompt();
@@ -383,7 +383,7 @@ async function main() {
383
383
 
384
384
  if (cmd === 'clear') {
385
385
  messages.length = 0;
386
- console.log(` ${g('conversation cleared')}`);
386
+ console.log(` ${sage('conversation cleared')}`);
387
387
  rl.prompt();
388
388
  return;
389
389
  }
@@ -391,13 +391,13 @@ async function main() {
391
391
  if (cmd === 'context') {
392
392
  if (intentFiles.length > 0) {
393
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('');
394
+ console.log(` ${b(cream('Loaded from'))} ${teal('.intent/')}`);
395
+ ui.divider('line');
396
+ intentFiles.forEach(f => console.log(` ${teal('●')} ${cream(f.file)} ${slate('(' + f.content.length + ' chars)')}`));
397
+ ui.divider('line');
398
398
  } else {
399
- console.log(`\n ${g('No .intent/ context found in')} ${process.cwd()}`);
400
- console.log(` ${g('Run')} /init ${g('to create one')}`);
399
+ console.log(`\n ${sage('No .intent/ context found in')} ${slate(process.cwd())}`);
400
+ console.log(` ${sage('Run')} ${cream('/init')} ${sage('to create one')}`);
401
401
  }
402
402
  console.log('');
403
403
  rl.prompt();
@@ -406,7 +406,7 @@ async function main() {
406
406
 
407
407
  if (cmd === 'status') {
408
408
  const turns = messages.length / 2;
409
- config = loadConfig(); // refresh
409
+ config = loadConfig();
410
410
  ui.statusPanel('Session', [
411
411
  ['Turns', String(turns)],
412
412
  ['Tokens', `${totalPromptTokens} in → ${totalCompletionTokens} out`],
@@ -414,7 +414,7 @@ async function main() {
414
414
  ['Context', intentFiles.length > 0 ? intentFiles.map(f => f.file).join(', ') : 'none', intentFiles.length > 0 ? 'green' : 'yellow'],
415
415
  ['Model', MODELS[currentModel].name],
416
416
  ['Provider', MODELS[currentModel].provider],
417
- ['User', config?.email || g('not logged in')],
417
+ ['User', config?.email || slate('not logged in')],
418
418
  ['API key', config?.apiKey ? config.apiKey.slice(0, 8) + '...' : 'not set', config?.apiKey ? 'green' : 'yellow'],
419
419
  ]);
420
420
  rl.prompt();
@@ -424,34 +424,32 @@ async function main() {
424
424
  if (cmd === 'reload') {
425
425
  intentFiles = loadIntentContext();
426
426
  systemPrompt = buildSystemPrompt(intentFiles);
427
- console.log(` ${green('●')} Reloaded ${intentFiles.length} artifact${intentFiles.length !== 1 ? 's' : ''}`);
427
+ console.log(` ${teal('●')} ${sage('Reloaded ' + intentFiles.length + ' artifact' + (intentFiles.length !== 1 ? 's' : ''))}`);
428
428
  rl.prompt();
429
429
  return;
430
430
  }
431
431
 
432
432
  if (cmd === 'system') {
433
- console.log(`\n${g(systemPrompt)}\n`);
433
+ console.log(`\n${slate(systemPrompt)}\n`);
434
434
  rl.prompt();
435
435
  return;
436
436
  }
437
437
 
438
438
  if (cmd === 'init') {
439
439
  if (fs.existsSync(path.join(INTENT_DIR, 'vision.md'))) {
440
- console.log(`\n ${g('.intent/ already exists in')} ${process.cwd()}`);
441
- console.log(` ${g('Use /reload to refresh context')}\n`);
440
+ console.log(`\n ${sage('.intent/ already exists in')} ${slate(process.cwd())}`);
441
+ console.log(` ${sage('Use /reload to refresh context')}\n`);
442
442
  } else {
443
443
  try {
444
- // Delegate to the intent --init command
445
444
  const { execSync } = require('child_process');
446
445
  execSync('node ' + path.join(__dirname, 'intent.js') + ' --init', { stdio: 'inherit' });
447
- // Reload context after init
448
446
  intentFiles = loadIntentContext();
449
447
  systemPrompt = buildSystemPrompt(intentFiles);
450
448
  if (intentFiles.length > 0) {
451
- console.log(` ${green('●')} Context loaded: ${intentFiles.map(f => f.file).join(', ')}`);
449
+ console.log(` ${teal('●')} ${sage('Context loaded:')} ${cream(intentFiles.map(f => f.file).join(', '))}`);
452
450
  }
453
451
  } catch (err) {
454
- console.error(` ${g('Init failed:')} ${err.message}`);
452
+ console.error(` ${sage('Init failed:')} ${err.message}`);
455
453
  }
456
454
  }
457
455
  console.log('');
@@ -461,7 +459,7 @@ async function main() {
461
459
 
462
460
  if (cmd === 'clarify') {
463
461
  if (!config?.apiKey) {
464
- console.log(`\n ${yellow('!')} No API key. Run /key to set one.\n`);
462
+ console.log(`\n ${ember('!')} ${sage('No API key. Run /key to set one.')}\n`);
465
463
  rl.prompt();
466
464
  return;
467
465
  }
@@ -469,14 +467,13 @@ async function main() {
469
467
  const { execSync } = require('child_process');
470
468
  const args = cmdArg ? `--text "${cmdArg.replace(/"/g, '\\"')}"` : '';
471
469
  execSync(`node ${path.join(__dirname, 'clarify.js')} ${args}`, { stdio: 'inherit' });
472
- // Reload context after clarify
473
470
  intentFiles = loadIntentContext();
474
471
  systemPrompt = buildSystemPrompt(intentFiles);
475
472
  if (intentFiles.length > 0) {
476
- console.log(` ${green('●')} Context loaded: ${intentFiles.map(f => f.file).join(', ')}`);
473
+ console.log(` ${teal('●')} ${sage('Context loaded:')} ${cream(intentFiles.map(f => f.file).join(', '))}`);
477
474
  }
478
475
  } catch (err) {
479
- console.error(` ${g('Clarify failed:')} ${err.message}`);
476
+ console.error(` ${sage('Clarify failed:')} ${err.message}`);
480
477
  }
481
478
  console.log('');
482
479
  rl.prompt();
@@ -488,11 +485,10 @@ async function main() {
488
485
  const { execSync } = require('child_process');
489
486
  const gateArg = cmdArg || 'status';
490
487
  execSync(`node ${path.join(__dirname, 'gate.js')} ${gateArg}`, { stdio: 'inherit' });
491
- // Reload context after gate changes (gate.json may have updated)
492
488
  intentFiles = loadIntentContext();
493
489
  systemPrompt = buildSystemPrompt(intentFiles);
494
490
  } catch (err) {
495
- console.error(` ${g('Gate failed:')} ${err.message}`);
491
+ console.error(` ${sage('Gate failed:')} ${err.message}`);
496
492
  }
497
493
  rl.prompt();
498
494
  return;
@@ -505,12 +501,12 @@ async function main() {
505
501
  if (content) {
506
502
  const outPath = path.join(process.cwd(), '.phewsh.context');
507
503
  fs.writeFileSync(outPath, content);
508
- console.log(`\n ${green('●')} Written to ${outPath}\n`);
504
+ console.log(`\n ${teal('●')} ${sage('Written to')} ${cream(outPath)}\n`);
509
505
  } else {
510
- console.log(`\n ${g('No artifacts to export')}\n`);
506
+ console.log(`\n ${sage('No artifacts to export')}\n`);
511
507
  }
512
508
  } catch (err) {
513
- console.error(` ${g('Export failed:')} ${err.message}`);
509
+ console.error(` ${sage('Export failed:')} ${err.message}`);
514
510
  }
515
511
  rl.prompt();
516
512
  return;
@@ -518,16 +514,16 @@ async function main() {
518
514
 
519
515
  if (cmd === 'push') {
520
516
  if (!config?.supabaseUserId) {
521
- console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
517
+ console.log(`\n ${ember('!')} ${sage('Not logged in. Run /login first.')}\n`);
522
518
  rl.prompt();
523
519
  return;
524
520
  }
525
521
  try {
526
522
  const token = await ensureValidToken(config);
527
- if (!token) { console.log(`\n ${yellow('!')} Session expired. Run /login.\n`); rl.prompt(); return; }
523
+ if (!token) { console.log(`\n ${ember('!')} ${sage('Session expired. Run /login.')}\n`); rl.prompt(); return; }
528
524
  await push(config, token);
529
525
  } catch (err) {
530
- console.error(` ${yellow('!')} Push failed: ${err.message}\n`);
526
+ console.error(` ${ember('!')} ${sage('Push failed:')} ${err.message}\n`);
531
527
  }
532
528
  rl.prompt();
533
529
  return;
@@ -535,22 +531,21 @@ async function main() {
535
531
 
536
532
  if (cmd === 'pull') {
537
533
  if (!config?.supabaseUserId) {
538
- console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
534
+ console.log(`\n ${ember('!')} ${sage('Not logged in. Run /login first.')}\n`);
539
535
  rl.prompt();
540
536
  return;
541
537
  }
542
538
  try {
543
539
  const token = await ensureValidToken(config);
544
- if (!token) { console.log(`\n ${yellow('!')} Session expired. Run /login.\n`); rl.prompt(); return; }
540
+ if (!token) { console.log(`\n ${ember('!')} ${sage('Session expired. Run /login.')}\n`); rl.prompt(); return; }
545
541
  await pull(config, token);
546
- // Reload context after pull
547
542
  intentFiles = loadIntentContext();
548
543
  systemPrompt = buildSystemPrompt(intentFiles);
549
544
  if (intentFiles.length > 0) {
550
- console.log(` ${green('●')} Context reloaded: ${intentFiles.map(f => f.file).join(', ')}`);
545
+ console.log(` ${teal('●')} ${sage('Context reloaded:')} ${cream(intentFiles.map(f => f.file).join(', '))}`);
551
546
  }
552
547
  } catch (err) {
553
- console.error(` ${yellow('!')} Pull failed: ${err.message}\n`);
548
+ console.error(` ${ember('!')} ${sage('Pull failed:')} ${err.message}\n`);
554
549
  }
555
550
  console.log('');
556
551
  rl.prompt();
@@ -558,24 +553,23 @@ async function main() {
558
553
  }
559
554
 
560
555
  if (cmd === 'sync') {
561
- // Show sync status
562
556
  if (!config?.supabaseUserId) {
563
- console.log(`\n ${yellow('!')} Not logged in. Run /login first.\n`);
557
+ console.log(`\n ${ember('!')} ${sage('Not logged in. Run /login first.')}\n`);
564
558
  rl.prompt();
565
559
  return;
566
560
  }
567
561
  const syncSpin = ui.spinner('checking sync');
568
562
  const syncResult = await checkSyncStatus(config);
569
563
  if (!syncResult) {
570
- syncSpin.stop(`${g('Could not check sync status')}`);
564
+ syncSpin.stop(`${sage('Could not check sync status')}`);
571
565
  } else if (syncResult.status === 'cloud-newer') {
572
- syncSpin.stop(`${yellow('↓')} Cloud is newer (${syncResult.ago}) — run /pull`);
566
+ syncSpin.stop(`${ember('↓')} ${sage('Cloud is newer (' + syncResult.ago + ') — run /pull')}`);
573
567
  } else if (syncResult.status === 'local-newer') {
574
- syncSpin.stop(`${yellow('↑')} Local changes not pushed (${syncResult.ago}) — run /push`);
568
+ syncSpin.stop(`${ember('↑')} ${sage('Local changes not pushed (' + syncResult.ago + ') — run /push')}`);
575
569
  } else if (syncResult.status === 'synced') {
576
- syncSpin.stop(`${green('↕')} In sync`);
570
+ syncSpin.stop(`${teal('↕')} ${sage('In sync')}`);
577
571
  } else if (syncResult.status === 'local-only') {
578
- syncSpin.stop(`${g('↕ Not linked to cloud — run /push to sync')}`);
572
+ syncSpin.stop(`${slate('↕ Not linked to cloud — run /push to sync')}`);
579
573
  }
580
574
  console.log('');
581
575
  rl.prompt();
@@ -586,9 +580,9 @@ async function main() {
586
580
  try {
587
581
  const { execSync } = require('child_process');
588
582
  execSync('node ' + path.join(__dirname, 'login.js'), { stdio: 'inherit' });
589
- config = loadConfig(); // refresh after login
583
+ config = loadConfig();
590
584
  } catch (err) {
591
- console.error(` ${g('Login failed:')} ${err.message}`);
585
+ console.error(` ${sage('Login failed:')} ${err.message}`);
592
586
  }
593
587
  rl.prompt();
594
588
  return;
@@ -596,56 +590,54 @@ async function main() {
596
590
 
597
591
  if (cmd === 'key') {
598
592
  if (cmdArg) {
599
- // Inline: /key sk-ant-...
600
593
  const apiKey = cmdArg.trim();
601
594
  config = loadConfig() || {};
602
595
  if (apiKey.startsWith('sk-ant-') || apiKey.startsWith('sk-')) {
603
596
  config.apiKey = apiKey;
604
597
  config.provider = 'anthropic';
605
598
  saveConfig(config);
606
- console.log(` ${green('●')} Anthropic API key saved. You're ready to go — just type.\n`);
599
+ console.log(` ${teal('●')} ${sage('Anthropic key saved. You\'re ready — just type.')}\n`);
607
600
  } else if (apiKey.startsWith('sk-or-')) {
608
601
  config.apiKey = apiKey;
609
602
  config.provider = 'openrouter';
610
603
  saveConfig(config);
611
- console.log(` ${green('●')} OpenRouter API key saved. You're ready to go — just type.\n`);
604
+ console.log(` ${teal('●')} ${sage('OpenRouter key saved. You\'re ready — just type.')}\n`);
612
605
  } else {
613
606
  config.apiKey = apiKey;
614
607
  saveConfig(config);
615
- console.log(` ${green('●')} API key saved. You're ready to go — just type.\n`);
608
+ console.log(` ${teal('●')} ${sage('API key saved. You\'re ready — just type.')}\n`);
616
609
  }
617
610
  rl.prompt();
618
611
  return;
619
612
  }
620
613
  console.log('');
621
- ui.divider('');
622
- console.log(` ${b(w('Where to get an API key'))}`);
623
- ui.divider('');
614
+ ui.divider('line');
615
+ console.log(` ${b(cream('Where to get an API key'))}`);
616
+ ui.divider('line');
624
617
  console.log('');
625
- console.log(` ${cyan('Anthropic')} ${g('(recommended)')}`);
626
- console.log(` ${g('1.')} Go to ${w('console.anthropic.com/settings/keys')}`);
627
- console.log(` ${g('2.')} Create key → copy it (starts with sk-ant-)`)
618
+ console.log(` ${teal('Anthropic')} ${slate('(recommended)')}`);
619
+ console.log(` ${sage('1.')} Go to ${cream('console.anthropic.com/settings/keys')}`);
620
+ console.log(` ${sage('2.')} Create key → copy it ${slate('(starts with sk-ant-)')}`);
628
621
  console.log('');
629
- console.log(` ${cyan('OpenRouter')} ${g('(multi-model)')}`);
630
- console.log(` ${g('1.')} Go to ${w('openrouter.ai/keys')}`);
631
- console.log(` ${g('2.')} Create key → copy it (starts with sk-or-)`)
622
+ console.log(` ${teal('OpenRouter')} ${slate('(multi-model)')}`);
623
+ console.log(` ${sage('1.')} Go to ${cream('openrouter.ai/keys')}`);
624
+ console.log(` ${sage('2.')} Create key → copy it ${slate('(starts with sk-or-)')}`);
632
625
  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.')}`);
626
+ console.log(` ${slate('Note: API keys subscriptions. Both providers offer free credits.')}`);
635
627
  console.log('');
636
628
  const keyRl = readline.createInterface({ input: process.stdin, output: process.stdout });
637
- keyRl.question(` Paste your API key\n > `, (apiKey) => {
629
+ keyRl.question(` ${sage('Paste your API key')}\n ${teal('>')} `, (apiKey) => {
638
630
  keyRl.close();
639
631
  apiKey = apiKey.trim();
640
632
  if (!apiKey) {
641
- console.log(` ${g('Cancelled')}\n`);
633
+ console.log(` ${slate('Cancelled')}\n`);
642
634
  } else {
643
635
  config = loadConfig() || {};
644
636
  config.apiKey = apiKey;
645
637
  if (apiKey.startsWith('sk-or-')) config.provider = 'openrouter';
646
638
  else config.provider = 'anthropic';
647
639
  saveConfig(config);
648
- console.log(`\n ${green('●')} API key saved. You're ready — just type naturally.\n`);
640
+ console.log(`\n ${teal('●')} ${sage('API key saved. You\'re ready — just type naturally.')}\n`);
649
641
  }
650
642
  rl.prompt();
651
643
  });
@@ -654,35 +646,34 @@ async function main() {
654
646
 
655
647
  if (cmd === 'models') {
656
648
  console.log('');
657
- ui.divider('');
658
- console.log(` ${b(w('Available models'))}`);
659
- ui.divider('');
649
+ ui.divider('line');
650
+ console.log(` ${b(cream('Available models'))}`);
651
+ ui.divider('line');
660
652
  for (const [key, model] of Object.entries(MODELS)) {
661
- const active = key === currentModel ? ` ${green('●')}` : '';
662
- console.log(` ${w(key.padEnd(16))} ${g(model.name)}${active}`);
653
+ const active = key === currentModel ? ` ${teal('●')}` : '';
654
+ console.log(` ${cream(key.padEnd(16))} ${sage(model.name)}${active}`);
663
655
  }
664
- console.log(`\n ${g('Switch with:')} /model <name>\n`);
656
+ console.log(`\n ${sage('Switch with:')} ${cream('/model <name>')}\n`);
665
657
  rl.prompt();
666
658
  return;
667
659
  }
668
660
 
669
661
  if (cmd === 'model') {
670
662
  if (!cmdArg) {
671
- console.log(` ${g('Current:')} ${MODELS[currentModel].name}`);
672
- console.log(` ${g('Usage:')} /model <sonnet|opus|haiku>`);
663
+ console.log(` ${sage('Current:')} ${cream(MODELS[currentModel].name)}`);
664
+ console.log(` ${sage('Usage:')} ${cream('/model <sonnet|opus|haiku>')}`);
673
665
  rl.prompt();
674
666
  return;
675
667
  }
676
- // Fuzzy match model name
677
668
  const query = cmdArg.toLowerCase().replace('claude-', '').replace('claude', '');
678
669
  const match = Object.keys(MODELS).find(k =>
679
670
  k.includes(query) || MODELS[k].name.toLowerCase().includes(query)
680
671
  );
681
672
  if (match) {
682
673
  currentModel = match;
683
- console.log(` ${green('●')} Switched to ${MODELS[match].name}`);
674
+ console.log(` ${teal('●')} ${sage('Switched to')} ${cream(MODELS[match].name)}`);
684
675
  } else {
685
- console.log(` ${g('Unknown model. Available:')} ${Object.keys(MODELS).join(', ')}`);
676
+ console.log(` ${sage('Unknown model. Available:')} ${cream(Object.keys(MODELS).join(', '))}`);
686
677
  }
687
678
  rl.prompt();
688
679
  return;
@@ -701,26 +692,26 @@ async function main() {
701
692
  }
702
693
 
703
694
  if (cmd === 'update' || cmd === 'upgrade') {
704
- const updateSpin = ui.spinner('checking for updates');
695
+ const updateSpin = ui.spinner('checking for updates', 'gentle');
705
696
  try {
706
697
  const pkg = require('../../package.json');
707
698
  const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(5000) });
708
699
  const data = await res.json();
709
700
  if (!data.version || data.version === pkg.version) {
710
- updateSpin.stop(`${green('●')} Already on the latest version (${pkg.version})`);
701
+ updateSpin.stop(`${teal('●')} ${sage('Already on the latest version (' + pkg.version + ')')}`);
711
702
  console.log('');
712
703
  rl.prompt();
713
704
  return;
714
705
  }
715
- updateSpin.stop(`${cyan(pkg.version)} → ${cyan(data.version)}`);
716
- console.log(` ${g('Installing...')}\n`);
706
+ updateSpin.stop(`${peach(pkg.version)} ${sage('')} ${peach(data.version)}`);
707
+ console.log(` ${sage('Installing...')}\n`);
717
708
  const { execSync } = require('child_process');
718
709
  execSync(`npm install -g ${pkg.name}@latest`, { stdio: 'inherit' });
719
- console.log(`\n ${green('●')} Updated to ${data.version}`);
720
- console.log(` ${g('Restart phewsh to use the new version.')}\n`);
710
+ console.log(`\n ${teal('●')} ${sage('Updated to')} ${cream(data.version)}`);
711
+ console.log(` ${slate('Restart phewsh to use the new version.')}\n`);
721
712
  } catch (err) {
722
- updateSpin.stop(`${yellow('!')} Update failed: ${err.message}`);
723
- console.log(` ${g('You can update manually:')} npm install -g phewsh\n`);
713
+ updateSpin.stop(`${ember('!')} ${sage('Update failed:')} ${err.message}`);
714
+ console.log(` ${sage('You can update manually:')} ${cream('npm install -g phewsh')}\n`);
724
715
  }
725
716
  rl.prompt();
726
717
  return;
@@ -728,16 +719,15 @@ async function main() {
728
719
 
729
720
  if (cmd === 'run') {
730
721
  if (!cmdArg) {
731
- console.log(` ${g('Usage:')} /run <prompt>`);
722
+ console.log(` ${sage('Usage:')} ${cream('/run <prompt>')}`);
732
723
  rl.prompt();
733
724
  return;
734
725
  }
735
726
  if (!config?.apiKey) {
736
- console.log(` ${yellow('!')} No API key. Run /key to set one.`);
727
+ console.log(` ${ember('!')} ${sage('No API key. Run /key to set one.')}`);
737
728
  rl.prompt();
738
729
  return;
739
730
  }
740
- // One-shot: don't add to conversation history
741
731
  console.log('');
742
732
  try {
743
733
  const result = await streamChat(
@@ -747,7 +737,7 @@ async function main() {
747
737
  MODELS[currentModel].id
748
738
  );
749
739
  if (result.promptTokens || result.completionTokens) {
750
- console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens`));
740
+ console.log(slate(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens`));
751
741
  }
752
742
  trackSap({
753
743
  userId: config.supabaseUserId,
@@ -766,7 +756,7 @@ async function main() {
766
756
  }
767
757
 
768
758
  // Unknown slash command
769
- console.log(` ${g('Unknown command:')} /${cmd} ${g('— type /help for available commands')}`);
759
+ console.log(` ${sage('Unknown command:')} ${cream('/' + cmd)} ${slate('— type /help for available commands')}`);
770
760
  rl.prompt();
771
761
  return;
772
762
  }
@@ -774,12 +764,12 @@ async function main() {
774
764
  // Regular input → send to AI
775
765
  if (!config?.apiKey) {
776
766
  console.log('');
777
- console.log(` ${yellow('Almost there!')} You need an API key to chat.`);
778
- console.log(` Type ${w('/key')} and paste one in — takes 10 seconds.`);
767
+ console.log(` ${peach('Almost there.')} ${sage('You need an API key to chat.')}`);
768
+ console.log(` ${sage('Type')} ${cream('/key')} ${sage('and paste one in — takes 10 seconds.')}`);
779
769
  console.log('');
780
- console.log(` ${g('Get a key:')} ${cyan('console.anthropic.com/settings/keys')}`);
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)')}`);
770
+ console.log(` ${slate('Get a key:')} ${sage('console.anthropic.com/settings/keys')}`);
771
+ console.log(` ${slate('Or try:')} ${sage('openrouter.ai/keys')}`);
772
+ console.log(` ${slate('Explore:')} ${cream('/tour')} ${slate('to see what PHEWSH does (no key needed)')}`);
783
773
  console.log('');
784
774
  rl.prompt();
785
775
  return;
@@ -792,16 +782,13 @@ async function main() {
792
782
  const result = await streamChat(config.apiKey, messages, systemPrompt, MODELS[currentModel].id);
793
783
  messages.push({ role: 'assistant', content: result.content });
794
784
 
795
- // Track totals
796
785
  if (result.promptTokens) totalPromptTokens += result.promptTokens;
797
786
  if (result.completionTokens) totalCompletionTokens += result.completionTokens;
798
787
 
799
- // Token count footer
800
788
  if (result.promptTokens || result.completionTokens) {
801
- console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens · ${MODELS[currentModel].name}`));
789
+ console.log(slate(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens · ${MODELS[currentModel].name}`));
802
790
  }
803
791
 
804
- // SAP tracking (fire-and-forget)
805
792
  trackSap({
806
793
  userId: config.supabaseUserId,
807
794
  source: 'cli',
@@ -812,7 +799,6 @@ async function main() {
812
799
  });
813
800
  } catch (err) {
814
801
  console.error(`\n ${err.message}\n`);
815
- // Remove the failed user message
816
802
  messages.pop();
817
803
  }
818
804
 
@@ -821,7 +807,7 @@ async function main() {
821
807
  });
822
808
 
823
809
  rl.on('close', () => {
824
- console.log(`\n ${g('Session ended')}\n`);
810
+ console.log(`\n ${sage('session ended')}\n`);
825
811
  process.exit(0);
826
812
  });
827
813
  }