phewsh 0.13.2 → 0.14.0
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 +1 -1
- package/commands/session.js +168 -98
- package/commands/setup.js +13 -1
- package/lib/ui.js +2 -2
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -7,7 +7,7 @@ const command = args[0];
|
|
|
7
7
|
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
|
-
const g = (s) => `\x1b[38;2;
|
|
10
|
+
const g = (s) => `\x1b[38;2;152;164;158m${s}\x1b[0m`; // slate (matches ui.js)
|
|
11
11
|
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
12
12
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
13
13
|
|
package/commands/session.js
CHANGED
|
@@ -17,7 +17,7 @@ const { select, refreshSession: refreshSess } = require('../lib/supabase');
|
|
|
17
17
|
const { readPPS } = require('../lib/pps');
|
|
18
18
|
const { push, pull, ensureValidToken } = require('./sync');
|
|
19
19
|
const { HARNESSES, listHarnesses, runViaHarness } = require('../lib/harnesses');
|
|
20
|
-
const { recordDecision, labelOutcome, pendingDecisions, OUTCOMES } = require('../lib/outcomes');
|
|
20
|
+
const { recordDecision, labelOutcome, pendingDecisions, outcomeStats, OUTCOMES } = require('../lib/outcomes');
|
|
21
21
|
const { recordSessionEvent } = require('../lib/receipts-data');
|
|
22
22
|
|
|
23
23
|
// Brand palette shortcuts
|
|
@@ -266,6 +266,7 @@ async function main() {
|
|
|
266
266
|
let route = resolveRoute(config, harnesses);
|
|
267
267
|
let sessionMode = null; // INTENT_MODES id once picked
|
|
268
268
|
let awaitingOutcome = null; // decision id eligible for 1-4 labeling
|
|
269
|
+
let awaitingFallback = null; // { input, fullSystem, options } after a route failure
|
|
269
270
|
let decisionsThisSession = 0;
|
|
270
271
|
|
|
271
272
|
// ── The Exhale: animated brand reveal ──────────────────
|
|
@@ -278,45 +279,54 @@ async function main() {
|
|
|
278
279
|
route = resolveRoute(config, harnesses);
|
|
279
280
|
}
|
|
280
281
|
|
|
281
|
-
// ──
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
} else {
|
|
286
|
-
statusParts.push(slate('no .intent/ — run /init'));
|
|
287
|
-
}
|
|
288
|
-
statusParts.push(sage('via ' + routeLabel(route, config)));
|
|
289
|
-
console.log(` ${statusParts.join(slate(' · '))}`);
|
|
290
|
-
|
|
291
|
-
// Capabilities: what's installed on this machine, no setup required
|
|
292
|
-
if (installedHarnesses.length > 0) {
|
|
293
|
-
const caps = installedHarnesses.map(h =>
|
|
294
|
-
`${teal('✓')} ${sage(h.label)}${h.headless ? '' : slate(' (/work)')}`
|
|
295
|
-
).join(slate(' · '));
|
|
296
|
-
console.log(` ${caps}`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Decisions from past sessions still waiting on an outcome
|
|
300
|
-
const pendingPast = pendingDecisions();
|
|
301
|
-
if (pendingPast.length > 0) {
|
|
302
|
-
console.log(` ${peach('◌')} ${sage(`${pendingPast.length} decision${pendingPast.length !== 1 ? 's' : ''} awaiting outcome`)} ${slate('— /outcomes to label')}`);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Sync status (one-line, non-blocking)
|
|
282
|
+
// ── Mission control: the whole state of your AI work, one screen ──────
|
|
283
|
+
// PROJECT what am I in · ROUTE where typing goes · BACKUP what's ready if
|
|
284
|
+
// the route hits a wall · WEB am I mirrored · RECORD what's accumulated
|
|
285
|
+
let syncState = null;
|
|
306
286
|
if (config?.supabaseUserId && intentFiles.length > 0) {
|
|
307
|
-
|
|
287
|
+
syncState = await Promise.race([
|
|
308
288
|
checkSyncStatus(config),
|
|
309
289
|
new Promise(resolve => setTimeout(() => resolve(null), 3000)),
|
|
310
290
|
]);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const row = (label, value) => console.log(` ${slate(label.padEnd(9))}${value}`);
|
|
294
|
+
|
|
295
|
+
row('PROJECT', cream(projectName) + (intentFiles.length > 0
|
|
296
|
+
? slate(' · ') + teal('●') + sage(` .intent/ ${intentFiles.length} file${intentFiles.length !== 1 ? 's' : ''}`)
|
|
297
|
+
: slate(' · no memory yet — ') + sage('/init')));
|
|
298
|
+
|
|
299
|
+
row('ROUTE', route
|
|
300
|
+
? cream(routeLabel(route, config)) + (route.type === 'harness' ? slate(' · no API key needed') : '')
|
|
301
|
+
: ember('none — /key or install an agent CLI'));
|
|
302
|
+
|
|
303
|
+
const backups = harnesses.filter(h => h.installed && h.headless && !(route?.type === 'harness' && route.id === h.id));
|
|
304
|
+
const workOnly = installedHarnesses.filter(h => !h.headless);
|
|
305
|
+
const backupParts = backups.map(h => `${teal('✓')} ${sage(h.label)}`);
|
|
306
|
+
if (config?.apiKey && route?.type !== 'api') backupParts.push(`${teal('✓')} ${sage('direct API')}`);
|
|
307
|
+
workOnly.forEach(h => backupParts.push(sage(h.label) + slate(' /work')));
|
|
308
|
+
row('BACKUP', backupParts.length > 0
|
|
309
|
+
? backupParts.join(slate(' · ')) + slate(' — context travels if the route hits a wall')
|
|
310
|
+
: slate('none — install Codex or Gemini to cover usage limits'));
|
|
311
|
+
|
|
312
|
+
if (config?.supabaseUserId) {
|
|
313
|
+
const syncLabel = syncState?.status === 'synced' ? teal('↕ ') + sage('mirrored')
|
|
314
|
+
: syncState?.status === 'cloud-newer' ? ember('↓ ') + sage(`cloud newer (${syncState.ago}) — /pull`)
|
|
315
|
+
: syncState?.status === 'local-newer' ? ember('↑ ') + sage('local ahead — /push')
|
|
316
|
+
: sage('linked');
|
|
317
|
+
row('WEB', cream(config.email || 'logged in') + slate(' · ') + syncLabel);
|
|
318
|
+
} else {
|
|
319
|
+
row('WEB', sage('local-only (works fine)') + slate(' · /login mirrors this at phewsh.com/intent'));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const oStats = outcomeStats();
|
|
323
|
+
const oLabeled = oStats.total - oStats.pending;
|
|
324
|
+
if (oStats.total > 0) {
|
|
325
|
+
const keptRate = oLabeled > 0 ? Math.round((oStats.kept / oLabeled) * 100) + '% kept' : 'unlabeled';
|
|
326
|
+
row('RECORD', cream(`${oStats.total} decision${oStats.total !== 1 ? 's' : ''}`) + slate(' · ') + sage(keptRate)
|
|
327
|
+
+ (oStats.pending > 0 ? slate(' · ') + peach(`${oStats.pending} awaiting outcome`) + slate(' — /outcomes') : ''));
|
|
328
|
+
} else {
|
|
329
|
+
row('RECORD', slate('empty — decisions and outcomes accumulate as you work'));
|
|
320
330
|
}
|
|
321
331
|
|
|
322
332
|
console.log('');
|
|
@@ -333,10 +343,107 @@ async function main() {
|
|
|
333
343
|
} else {
|
|
334
344
|
console.log(` ${b(cream('What are you trying to do?'))}`);
|
|
335
345
|
console.log(` ${teal('1')} ${sage('Build')} ${slate('·')} ${teal('2')} ${sage('Research')} ${slate('·')} ${teal('3')} ${sage('Decide')} ${slate('·')} ${teal('4')} ${sage('Review')} ${slate('·')} ${teal('5')} ${sage('Ask another model')}`);
|
|
336
|
-
console.log(` ${slate('pick a number, or just type —
|
|
346
|
+
console.log(` ${slate('pick a number, or just type — your context travels with every route')}`);
|
|
337
347
|
}
|
|
338
348
|
console.log('');
|
|
339
349
|
|
|
350
|
+
// ── Turn runners — every route records a decision, leaves a receipt ────
|
|
351
|
+
// Both return true on success so the fallback flow can chain them.
|
|
352
|
+
|
|
353
|
+
async function runHarnessTurn(input, harnessId, fullSystem) {
|
|
354
|
+
const decisionId = recordDecision({
|
|
355
|
+
project: projectName, route: harnessId, mode: sessionMode, summary: input,
|
|
356
|
+
});
|
|
357
|
+
decisionsThisSession++;
|
|
358
|
+
try {
|
|
359
|
+
const output = await runViaHarness(harnessId, fullSystem, buildHarnessPrompt(messages, input));
|
|
360
|
+
messages.push({ role: 'user', content: input });
|
|
361
|
+
messages.push({ role: 'assistant', content: (output || '').trim() });
|
|
362
|
+
recordSessionEvent(harnessId, projectName, 'task_complete', {
|
|
363
|
+
taskId: decisionId, success: true, summary: input.slice(0, 140),
|
|
364
|
+
});
|
|
365
|
+
awaitingOutcome = decisionId;
|
|
366
|
+
console.log(slate(` via ${HARNESSES[harnessId].label} · outcome? 1 kept · 2 reverted · 3 superseded · 4 failed · or keep typing`));
|
|
367
|
+
return true;
|
|
368
|
+
} catch (err) {
|
|
369
|
+
try { labelOutcome(decisionId, 'failed'); } catch { /* keep going */ }
|
|
370
|
+
recordSessionEvent(harnessId, projectName, 'task_complete', {
|
|
371
|
+
taskId: decisionId, success: false, summary: input.slice(0, 140),
|
|
372
|
+
});
|
|
373
|
+
const limitHit = /limit|quota|rate|usage|exhaust/i.test(err.message);
|
|
374
|
+
console.error(`\n ${ember('!')} ${cream(HARNESSES[harnessId].label)} ${sage(limitHit ? 'hit a usage wall' : 'failed')}${slate(' — ' + err.message.split('\n')[0])}`);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function runApiTurn(input, fullSystem) {
|
|
380
|
+
const decisionId = recordDecision({
|
|
381
|
+
project: projectName, route: 'api', mode: sessionMode, summary: input,
|
|
382
|
+
});
|
|
383
|
+
decisionsThisSession++;
|
|
384
|
+
messages.push({ role: 'user', content: input });
|
|
385
|
+
console.log('');
|
|
386
|
+
try {
|
|
387
|
+
const result = await streamChat(config.apiKey, messages, fullSystem, MODELS[currentModel].id);
|
|
388
|
+
messages.push({ role: 'assistant', content: result.content });
|
|
389
|
+
if (result.promptTokens) totalPromptTokens += result.promptTokens;
|
|
390
|
+
if (result.completionTokens) totalCompletionTokens += result.completionTokens;
|
|
391
|
+
if (result.promptTokens || result.completionTokens) {
|
|
392
|
+
console.log(slate(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens · ${MODELS[currentModel].name} · outcome? 1-4 or keep typing`));
|
|
393
|
+
}
|
|
394
|
+
awaitingOutcome = decisionId;
|
|
395
|
+
trackSap({
|
|
396
|
+
userId: config.supabaseUserId,
|
|
397
|
+
source: 'cli',
|
|
398
|
+
model: MODELS[currentModel].id,
|
|
399
|
+
promptTokens: result.promptTokens,
|
|
400
|
+
completionTokens: result.completionTokens,
|
|
401
|
+
accessToken: config.supabaseAccessToken,
|
|
402
|
+
});
|
|
403
|
+
return true;
|
|
404
|
+
} catch (err) {
|
|
405
|
+
try { labelOutcome(decisionId, 'failed'); } catch { /* keep going */ }
|
|
406
|
+
messages.pop();
|
|
407
|
+
console.error(`\n ${ember('!')} ${sage('API route failed')}${slate(' — ' + err.message.split('\n')[0])}`);
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Fallbacks are a first-class flow: the route changes, the context and
|
|
413
|
+
// record do not. Ask by default; auto-switch only if setup said so.
|
|
414
|
+
async function offerFallbacks(input, fullSystem, failedId) {
|
|
415
|
+
const options = harnesses
|
|
416
|
+
.filter(h => h.installed && h.headless && h.id !== failedId)
|
|
417
|
+
.map(h => h.id);
|
|
418
|
+
if (config?.apiKey && failedId !== 'api') options.push('api');
|
|
419
|
+
|
|
420
|
+
if (options.length === 0) {
|
|
421
|
+
console.log(` ${sage('No fallback ready.')} ${slate('Install Codex or Gemini, or add an API key with /key — context would travel automatically.')}`);
|
|
422
|
+
console.log('');
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (config?.fallback === 'auto') {
|
|
427
|
+
const fb = options[0];
|
|
428
|
+
const fbLabel = fb === 'api' ? 'direct API' : HARNESSES[fb].label;
|
|
429
|
+
console.log(` ${peach('↻')} ${sage('auto-fallback →')} ${cream(fbLabel)} ${slate('· same context, same record')}`);
|
|
430
|
+
const ok = fb === 'api'
|
|
431
|
+
? await runApiTurn(input, fullSystem)
|
|
432
|
+
: await runHarnessTurn(input, fb, fullSystem);
|
|
433
|
+
if (!ok) console.log(` ${ember('!')} ${sage('Fallback failed too — /provider to inspect routes.')}`);
|
|
434
|
+
console.log('');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const list = options.map((id, i) =>
|
|
439
|
+
`${teal(String(i + 1))} ${sage(id === 'api' ? 'direct API (your key)' : HARNESSES[id].label)}`
|
|
440
|
+
).join(slate(' · '));
|
|
441
|
+
console.log(` ${sage('Retry with your context intact:')} ${list} ${slate('· enter = skip')}`);
|
|
442
|
+
console.log(` ${slate('prefer auto-switching? phewsh setup sets it once')}`);
|
|
443
|
+
awaitingFallback = { input, fullSystem, options };
|
|
444
|
+
console.log('');
|
|
445
|
+
}
|
|
446
|
+
|
|
340
447
|
const rl = readline.createInterface({
|
|
341
448
|
input: process.stdin,
|
|
342
449
|
output: process.stdout,
|
|
@@ -354,6 +461,24 @@ async function main() {
|
|
|
354
461
|
return;
|
|
355
462
|
}
|
|
356
463
|
|
|
464
|
+
// A bare number right after a route failure picks the fallback
|
|
465
|
+
if (awaitingFallback) {
|
|
466
|
+
const af = awaitingFallback;
|
|
467
|
+
awaitingFallback = null;
|
|
468
|
+
const n = parseInt(input, 10);
|
|
469
|
+
if (n >= 1 && n <= af.options.length) {
|
|
470
|
+
const fb = af.options[n - 1];
|
|
471
|
+
const ok = fb === 'api'
|
|
472
|
+
? await runApiTurn(af.input, af.fullSystem)
|
|
473
|
+
: await runHarnessTurn(af.input, fb, af.fullSystem);
|
|
474
|
+
if (!ok) await offerFallbacks(af.input, af.fullSystem, fb);
|
|
475
|
+
console.log('');
|
|
476
|
+
rl.prompt();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
// anything else: drop the offer and treat it as fresh input
|
|
480
|
+
}
|
|
481
|
+
|
|
357
482
|
// A bare 1-4 right after a routed action labels its outcome
|
|
358
483
|
if (awaitingOutcome && /^[1-4]$/.test(input)) {
|
|
359
484
|
const outcome = OUTCOMES[parseInt(input, 10) - 1];
|
|
@@ -834,6 +959,7 @@ async function main() {
|
|
|
834
959
|
rows.push([h.label, h.installed ? `installed (${h.auth})` : 'not installed', h.installed ? 'green' : undefined]);
|
|
835
960
|
}
|
|
836
961
|
rows.push(['API key', config?.apiKey ? config.apiKey.slice(0, 8) + '... (' + (config.provider || 'anthropic') + ')' : 'not set — optional', config?.apiKey ? 'green' : 'yellow']);
|
|
962
|
+
rows.push(['Fallback', config?.fallback === 'auto' ? 'auto-switch on failure' : 'ask before switching', 'peach']);
|
|
837
963
|
if (route?.type === 'api') rows.push(['Model', MODELS[currentModel].name, 'cyan']);
|
|
838
964
|
ui.statusPanel('Provider', rows);
|
|
839
965
|
console.log(` ${slate('switch:')} ${cream('/use <' + harnesses.filter(h => h.installed).map(h => h.id).concat(config?.apiKey ? ['api'] : []).join('|') + '>')}`);
|
|
@@ -1083,68 +1209,12 @@ async function main() {
|
|
|
1083
1209
|
: null;
|
|
1084
1210
|
const fullSystem = modeHint ? `${systemPrompt}\n\n${modeHint}` : systemPrompt;
|
|
1085
1211
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
project: projectName,
|
|
1090
|
-
route: route.type === 'api' ? 'api' : route.id,
|
|
1091
|
-
mode: sessionMode,
|
|
1092
|
-
summary: input,
|
|
1093
|
-
});
|
|
1094
|
-
decisionsThisSession++;
|
|
1095
|
-
|
|
1096
|
-
if (route.type === 'harness') {
|
|
1097
|
-
try {
|
|
1098
|
-
const output = await runViaHarness(route.id, fullSystem, buildHarnessPrompt(messages, input));
|
|
1099
|
-
messages.push({ role: 'user', content: input });
|
|
1100
|
-
messages.push({ role: 'assistant', content: (output || '').trim() });
|
|
1101
|
-
recordSessionEvent(route.id, projectName, 'task_complete', {
|
|
1102
|
-
taskId: decisionId, success: true, summary: input.slice(0, 140),
|
|
1103
|
-
});
|
|
1104
|
-
awaitingOutcome = decisionId;
|
|
1105
|
-
console.log(slate(` via ${HARNESSES[route.id].label} · outcome? 1 kept · 2 reverted · 3 superseded · 4 failed · or keep typing`));
|
|
1106
|
-
} catch (err) {
|
|
1107
|
-
try { labelOutcome(decisionId, 'failed'); } catch { /* keep going */ }
|
|
1108
|
-
recordSessionEvent(route.id, projectName, 'task_complete', {
|
|
1109
|
-
taskId: decisionId, success: false, summary: input.slice(0, 140),
|
|
1110
|
-
});
|
|
1111
|
-
console.error(`\n ${ember('!')} ${err.message}`);
|
|
1112
|
-
if (installedHarnesses.length > 1 || config?.apiKey) {
|
|
1113
|
-
console.log(` ${slate('switch routes with /use — /provider shows what\'s available')}`);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
console.log('');
|
|
1117
|
-
rl.prompt();
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
messages.push({ role: 'user', content: input });
|
|
1122
|
-
console.log('');
|
|
1212
|
+
const ok = route.type === 'harness'
|
|
1213
|
+
? await runHarnessTurn(input, route.id, fullSystem)
|
|
1214
|
+
: await runApiTurn(input, fullSystem);
|
|
1123
1215
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
messages.push({ role: 'assistant', content: result.content });
|
|
1127
|
-
|
|
1128
|
-
if (result.promptTokens) totalPromptTokens += result.promptTokens;
|
|
1129
|
-
if (result.completionTokens) totalCompletionTokens += result.completionTokens;
|
|
1130
|
-
|
|
1131
|
-
if (result.promptTokens || result.completionTokens) {
|
|
1132
|
-
console.log(slate(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens · ${MODELS[currentModel].name} · outcome? 1-4 or keep typing`));
|
|
1133
|
-
}
|
|
1134
|
-
awaitingOutcome = decisionId;
|
|
1135
|
-
|
|
1136
|
-
trackSap({
|
|
1137
|
-
userId: config.supabaseUserId,
|
|
1138
|
-
source: 'cli',
|
|
1139
|
-
model: MODELS[currentModel].id,
|
|
1140
|
-
promptTokens: result.promptTokens,
|
|
1141
|
-
completionTokens: result.completionTokens,
|
|
1142
|
-
accessToken: config.supabaseAccessToken,
|
|
1143
|
-
});
|
|
1144
|
-
} catch (err) {
|
|
1145
|
-
try { labelOutcome(decisionId, 'failed'); } catch { /* keep going */ }
|
|
1146
|
-
console.error(`\n ${err.message}\n`);
|
|
1147
|
-
messages.pop();
|
|
1216
|
+
if (!ok) {
|
|
1217
|
+
await offerFallbacks(input, fullSystem, route.type === 'harness' ? route.id : 'api');
|
|
1148
1218
|
}
|
|
1149
1219
|
|
|
1150
1220
|
console.log('');
|
package/commands/setup.js
CHANGED
|
@@ -63,6 +63,7 @@ module.exports = async function setup() {
|
|
|
63
63
|
if (!process.stdin.isTTY) {
|
|
64
64
|
if (chatCapable.length > 0) {
|
|
65
65
|
config.defaultRoute = chatCapable[0].id;
|
|
66
|
+
if (!config.fallback) config.fallback = 'ask';
|
|
66
67
|
saveConfig(config);
|
|
67
68
|
console.log(` ${teal('●')} ${sage('Auto-configured (non-interactive): default route =')} ${cream(chatCapable[0].label)} ${slate('— no API key needed')}`);
|
|
68
69
|
console.log(` ${slate('Change anytime: run `phewsh setup` in your own terminal, or /use inside a session.')}`);
|
|
@@ -128,9 +129,20 @@ module.exports = async function setup() {
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
// ── 4. Fallback behavior — first-class, not buried config ────────────
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(` ${b(cream('If your route hits a usage limit or fails:'))}`);
|
|
135
|
+
console.log(` ${teal('1')} ${sage('Ask me before switching')} ${slate('(default — shows what changes, context always travels)')}`);
|
|
136
|
+
console.log(` ${teal('2')} ${sage('Auto-switch to the next available route')}`);
|
|
137
|
+
console.log('');
|
|
138
|
+
const fbAnswer = await ask(rl, ` ${teal('>')} ${slate('1-2, enter = 1: ')}`);
|
|
139
|
+
config.fallback = fbAnswer.trim() === '2' ? 'auto' : 'ask';
|
|
140
|
+
saveConfig(config);
|
|
141
|
+
console.log(` ${teal('●')} ${sage('Fallback:')} ${cream(config.fallback === 'auto' ? 'auto-switch' : 'ask first')} ${slate('— either way, your project context and record stay intact')}`);
|
|
142
|
+
|
|
131
143
|
rl.close();
|
|
132
144
|
|
|
133
|
-
// ──
|
|
145
|
+
// ── 5. Done ───────────────────────────────────────────
|
|
134
146
|
console.log('');
|
|
135
147
|
ui.divider('line');
|
|
136
148
|
console.log(` ${teal('●')} ${b(cream('Setup complete.'))}`);
|
package/lib/ui.js
CHANGED
|
@@ -11,8 +11,8 @@ const rgbBg = (r, g, b) => (s) => `\x1b[48;2;${r};${g};${b}m${s}\x1b[0m`;
|
|
|
11
11
|
// Brand colors — relief, quiet, future
|
|
12
12
|
const teal = rgb(100, 215, 195); // cool calm — primary
|
|
13
13
|
const peach = rgb(255, 195, 145); // warm exhale — accent
|
|
14
|
-
const sage = rgb(
|
|
15
|
-
const slate = rgb(
|
|
14
|
+
const sage = rgb(190, 208, 198); // quiet — secondary text
|
|
15
|
+
const slate = rgb(152, 164, 158); // whisper — dim text (bright enough for dark terminals)
|
|
16
16
|
const cream = rgb(240, 235, 225); // clarity — bright text
|
|
17
17
|
const ember = rgb(220, 140, 90); // glow — warnings/energy
|
|
18
18
|
|