cawdex 1.35.69 → 1.35.74
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/README.md +188 -188
- package/dist/command-palette.js +5 -4
- package/dist/command-palette.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +20 -13
- package/dist/config.js.map +1 -1
- package/dist/curator.d.ts +2 -2
- package/dist/curator.js +2 -2
- package/dist/fixed-footer.d.ts +29 -0
- package/dist/fixed-footer.js +383 -0
- package/dist/fixed-footer.js.map +1 -0
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +7 -7
- package/dist/hooks.js.map +1 -1
- package/dist/index.js +352 -57
- package/dist/index.js.map +1 -1
- package/dist/instant-answer.d.ts +6 -1
- package/dist/instant-answer.js +32 -1
- package/dist/instant-answer.js.map +1 -1
- package/dist/live-queue.d.ts +0 -32
- package/dist/live-queue.js +15 -0
- package/dist/live-queue.js.map +1 -1
- package/dist/modes.d.ts +2 -1
- package/dist/modes.js +368 -361
- package/dist/modes.js.map +1 -1
- package/dist/query.d.ts +1 -0
- package/dist/query.js +163 -44
- package/dist/query.js.map +1 -1
- package/dist/swarm.d.ts +8 -3
- package/dist/swarm.js +39 -14
- package/dist/swarm.js.map +1 -1
- package/dist/system-prompt.js +14 -14
- package/dist/system-prompt.js.map +1 -1
- package/dist/theme.d.ts +4 -0
- package/dist/theme.js +22 -0
- package/dist/theme.js.map +1 -1
- package/dist/tools/skill.d.ts +2 -2
- package/dist/tools/skill.js +2 -2
- package/dist/types.d.ts +15 -0
- package/dist/types.js.map +1 -1
- package/dist/updater.d.ts +16 -0
- package/dist/updater.js +157 -0
- package/dist/updater.js.map +1 -0
- package/dist/walkthrough.js +7 -7
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import { initHooksDir, runHooks, listHooks, saveHooksConfig, clearQuarantinedHoo
|
|
|
27
27
|
import { printUsageSummary, setBudget } from './cost-tracker.js';
|
|
28
28
|
import { getCompactionStats, OPENROUTER_FREE_ROUTER_SAFE_CONTEXT_WINDOW_TOKENS, OPENROUTER_UNKNOWN_FREE_MODEL_CONTEXT_WINDOW_TOKENS, } from './compaction.js';
|
|
29
29
|
import { extractPatterns, printInstinctStatus, pruneExpired, listInstincts, exportInstincts, importInstincts } from './learning.js';
|
|
30
|
-
import { MODES, listModes } from './modes.js';
|
|
30
|
+
import { MODES, listModes, normalizeModeName } from './modes.js';
|
|
31
31
|
import { printModelOptions, switchModel, classifyComplexity, routeModel } from './model-router.js';
|
|
32
32
|
import { buildCommitPrompt, buildPRPrompt, printDiff, printLog } from './git-workflow.js';
|
|
33
33
|
import { buildReviewPrompt, buildTDDPrompt, buildSecurityReviewPrompt, runAudit, printAuditReport, buildPlanPrompt, buildE2EPrompt, buildBuildFixPrompt, buildEvalPrompt, buildBenchmarkPrompt, splitBenchmarkArgs, } from './evaluation.js';
|
|
@@ -79,7 +79,7 @@ import { status as sandboxStatus } from './sandbox.js';
|
|
|
79
79
|
// API key rotation pool (/keys)
|
|
80
80
|
import { listStatus as keyPoolStatus, setPool as syncKeyPool } from './key-rotation.js';
|
|
81
81
|
// Agentic swarm — fan-out concurrent agents on the same task (/swarm)
|
|
82
|
-
import { buildSwarmAgentTask, buildSwarmPlan, decodeSwarmSentinel, encodeSwarmSentinel, formatSwarmResults, parseSwarmCommandArgs, resolveAgents, runSwarm, } from './swarm.js';
|
|
82
|
+
import { buildSwarmAgentTask, buildSwarmPlan, clampSwarmMaxAgents, decodeSwarmSentinel, encodeSwarmSentinel, formatSwarmResults, parseSwarmCommandArgs, resolveAgents, runSwarm, } from './swarm.js';
|
|
83
83
|
// Voice / accessibility — built-in dictation (Whisper) + readout (ElevenLabs)
|
|
84
84
|
import { printVoiceStatus, isVoiceEnabled, getTtsConfig, getSttConfig, getAccessibilityConfig, speak, dictateOnce, } from './voice.js';
|
|
85
85
|
import { isFfmpegAvailable, audioCue, startRecording, probeMic, micProbeMessage } from './audio.js';
|
|
@@ -88,6 +88,8 @@ import { COMMAND_CATALOG, allSlashCommandNames, completeSlashCommandNames, resol
|
|
|
88
88
|
import { inlineSuggest, resolveInlineSuggestQuestionInput } from './inline-suggest.js';
|
|
89
89
|
import { normalizeTypeaheadDraftForPrompt } from './prompt-buffer.js';
|
|
90
90
|
import { maybeInstantAnswer } from './instant-answer.js';
|
|
91
|
+
import { getCurrentVersion, startStartupUpdateCheck } from './updater.js';
|
|
92
|
+
import { activateFooter, askWithFooterPrompt, buildFooterSnapshot, deactivateFooter, isFooterActive, shouldUseFixedFooter, updateFooter, } from './fixed-footer.js';
|
|
91
93
|
/**
|
|
92
94
|
* Unified prompt resolver — prefers the bundled ECC prompt for a given
|
|
93
95
|
* intent and falls back to the built-in builder when ECC isn't installed.
|
|
@@ -127,8 +129,8 @@ function clipSetupValue(value, max = 120) {
|
|
|
127
129
|
const single = value.replace(/\s+/g, ' ').trim();
|
|
128
130
|
return single.length <= max ? single : single.slice(0, max - 1) + '…';
|
|
129
131
|
}
|
|
130
|
-
async function runSwarmSetupWizard(rl, plan) {
|
|
131
|
-
if (!stdin.isTTY || process.env.CAWDEX_SWARM_WIZARD === '0')
|
|
132
|
+
async function runSwarmSetupWizard(rl, plan, config) {
|
|
133
|
+
if (!stdin.isTTY || process.env.CAWDEX_SWARM_WIZARD === '0' || config.swarm?.setupWizard === false)
|
|
132
134
|
return plan;
|
|
133
135
|
const next = {
|
|
134
136
|
...plan,
|
|
@@ -189,6 +191,115 @@ function printUnknownSlashCommand(cmd) {
|
|
|
189
191
|
console.log(d(' Did you mean: ') + suggestions.map((entry) => c(entry.command)).join(d(', ')));
|
|
190
192
|
console.log(d(' Try: ') + c(`/help ${suggestions[0].command}`) + d(' for exact usage.'));
|
|
191
193
|
}
|
|
194
|
+
async function promptSecretLine(rl, prompt, existingValue = '') {
|
|
195
|
+
const suffix = existingValue ? ' [stored; Enter keeps it]' : '';
|
|
196
|
+
const label = `${prompt}${suffix}: `;
|
|
197
|
+
if (!stdin.isTTY || !stdout.isTTY) {
|
|
198
|
+
const answer = await rl.question(chalk.yellow(label));
|
|
199
|
+
return answer.trim() || existingValue;
|
|
200
|
+
}
|
|
201
|
+
return new Promise((resolve) => {
|
|
202
|
+
let value = '';
|
|
203
|
+
const wasRaw = stdin.isRaw;
|
|
204
|
+
const keypressListeners = stdin.listeners('keypress').slice();
|
|
205
|
+
const detached = keypressListeners.filter((listener) => !listener.__cawdexHotkey__);
|
|
206
|
+
function render() {
|
|
207
|
+
const shown = value ? '*'.repeat(Math.min(value.length, 16)) : '';
|
|
208
|
+
stdout.write(`\r\x1b[2K${chalk.yellow(label)}${chalk.dim(shown)}`);
|
|
209
|
+
}
|
|
210
|
+
function cleanup() {
|
|
211
|
+
stdin.removeListener('data', onData);
|
|
212
|
+
for (const listener of detached)
|
|
213
|
+
stdin.on('keypress', listener);
|
|
214
|
+
try {
|
|
215
|
+
stdin.setRawMode(wasRaw);
|
|
216
|
+
}
|
|
217
|
+
catch { /* noop */ }
|
|
218
|
+
stdout.write('\n');
|
|
219
|
+
}
|
|
220
|
+
function done(nextValue) {
|
|
221
|
+
cleanup();
|
|
222
|
+
resolve(nextValue.trim() || existingValue);
|
|
223
|
+
}
|
|
224
|
+
function onData(buf) {
|
|
225
|
+
if (buf.length === 1 && buf[0] === 0x03) {
|
|
226
|
+
cleanup();
|
|
227
|
+
process.exit(130);
|
|
228
|
+
}
|
|
229
|
+
if (buf.length === 1 && buf[0] === 0x1B) {
|
|
230
|
+
done(existingValue);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (buf.length === 1 && (buf[0] === 0x0D || buf[0] === 0x0A)) {
|
|
234
|
+
done(value);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (buf.length === 1 && (buf[0] === 0x7F || buf[0] === 0x08)) {
|
|
238
|
+
value = value.slice(0, -1);
|
|
239
|
+
render();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const text = buf.toString('utf8').replace(/[^\x20-\x7E]/g, '');
|
|
243
|
+
if (text) {
|
|
244
|
+
value += text;
|
|
245
|
+
render();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const listener of detached)
|
|
249
|
+
stdin.removeListener('keypress', listener);
|
|
250
|
+
try {
|
|
251
|
+
stdin.setRawMode(true);
|
|
252
|
+
}
|
|
253
|
+
catch { /* noop */ }
|
|
254
|
+
stdin.resume();
|
|
255
|
+
stdin.on('data', onData);
|
|
256
|
+
render();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async function runAppearanceWizard(rl, config) {
|
|
260
|
+
const themeChoices = [
|
|
261
|
+
{ label: 'full', detail: 'banner and full context on launch', value: 'full' },
|
|
262
|
+
{ label: 'compact', detail: 'same banner, tighter future surfaces', value: 'compact' },
|
|
263
|
+
{ label: 'minimal', detail: 'small launch header', value: 'minimal' },
|
|
264
|
+
];
|
|
265
|
+
const currentTheme = config.theme || 'full';
|
|
266
|
+
const themeDefault = Math.max(0, themeChoices.findIndex((choice) => choice.value === currentTheme));
|
|
267
|
+
const selectedTheme = await selectConfigChoice(rl, 'Display theme', themeChoices, {
|
|
268
|
+
defaultIndex: themeDefault,
|
|
269
|
+
fallbackPrompt: ` Display theme [${themeDefault + 1}]: `,
|
|
270
|
+
parseFallback: (answer, defaultIndex) => {
|
|
271
|
+
const raw = (answer || String(defaultIndex + 1)).trim().toLowerCase();
|
|
272
|
+
const byName = themeChoices.find((choice) => choice.value === raw);
|
|
273
|
+
if (byName)
|
|
274
|
+
return byName.value;
|
|
275
|
+
return themeChoices[Number.parseInt(raw, 10) - 1]?.value;
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
const paletteChoices = listPalettes().map((meta) => ({
|
|
279
|
+
label: meta.id,
|
|
280
|
+
detail: meta.description,
|
|
281
|
+
value: meta.id,
|
|
282
|
+
}));
|
|
283
|
+
const currentPalette = getPaletteId();
|
|
284
|
+
const paletteDefault = Math.max(0, paletteChoices.findIndex((choice) => choice.value === currentPalette));
|
|
285
|
+
const selectedPalette = await selectConfigChoice(rl, 'Color palette', paletteChoices, {
|
|
286
|
+
defaultIndex: paletteDefault,
|
|
287
|
+
fallbackPrompt: ` Color palette [${paletteDefault + 1}]: `,
|
|
288
|
+
parseFallback: (answer, defaultIndex) => {
|
|
289
|
+
const raw = (answer || String(defaultIndex + 1)).trim().toLowerCase();
|
|
290
|
+
const resolved = resolvePaletteId(raw);
|
|
291
|
+
if (resolved)
|
|
292
|
+
return resolved;
|
|
293
|
+
return paletteChoices[Number.parseInt(raw, 10) - 1]?.value;
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
config.theme = selectedTheme;
|
|
297
|
+
setPalette(selectedPalette);
|
|
298
|
+
config.palette = selectedPalette;
|
|
299
|
+
saveConfig(config);
|
|
300
|
+
console.log(theme.brandBold(` ${sym.mark} Appearance: ${selectedTheme} / ${selectedPalette}`));
|
|
301
|
+
console.log(theme.dim(' Brand · ') + theme.brand('brand') + theme.dim(' · ') + theme.success('success') + theme.dim(' · ') + theme.warning('warning') + theme.dim(' · ') + theme.error('error') + theme.dim(' · ') + theme.command('command'));
|
|
302
|
+
}
|
|
192
303
|
async function setupWizard(rl, currentConfig) {
|
|
193
304
|
console.log(chalk.bold.cyan(`\n ${BRAND_NAME} — First-time Setup\n`));
|
|
194
305
|
const providerKeys = Object.keys(PROVIDERS);
|
|
@@ -220,8 +331,9 @@ async function setupWizard(rl, currentConfig) {
|
|
|
220
331
|
}
|
|
221
332
|
let apiKey = '';
|
|
222
333
|
let openaiAuth;
|
|
334
|
+
const sameProvider = normalizeProvider(currentConfig?.provider ?? '') === normalizeProvider(provider.name);
|
|
223
335
|
if (provider.requiresKey) {
|
|
224
|
-
apiKey = await rl
|
|
336
|
+
apiKey = await promptSecretLine(rl, ` API Key for ${provider.name}`, sameProvider ? currentConfig?.apiKey ?? '' : '');
|
|
225
337
|
}
|
|
226
338
|
if (providerKey === 'openai-codex') {
|
|
227
339
|
openaiAuth = {
|
|
@@ -241,7 +353,6 @@ async function setupWizard(rl, currentConfig) {
|
|
|
241
353
|
console.log(chalk.dim(` After setup, run /openai-login or run "codex login". ${BRAND_NAME} will read ~/.codex/auth.json.`));
|
|
242
354
|
}
|
|
243
355
|
}
|
|
244
|
-
const sameProvider = normalizeProvider(currentConfig?.provider ?? '') === normalizeProvider(provider.name);
|
|
245
356
|
let model = sameProvider && currentConfig?.model ? currentConfig.model : provider.defaultModel;
|
|
246
357
|
const modelInput = await rl.question(chalk.yellow(` Model [${model}]: `));
|
|
247
358
|
if (modelInput.trim())
|
|
@@ -274,6 +385,46 @@ async function setupWizard(rl, currentConfig) {
|
|
|
274
385
|
console.log(chalk.dim(' Free variants usually end with :free. Switch later with /openrouter-free or /model <id>.'));
|
|
275
386
|
console.log('');
|
|
276
387
|
}
|
|
388
|
+
let fallbackModel;
|
|
389
|
+
if (providerKey === 'openrouter') {
|
|
390
|
+
const currentFallback = sameProvider ? currentConfig?.fallbackModel : undefined;
|
|
391
|
+
const fallbackDefaultIndex = currentFallback === PROVIDERS.openrouter.defaultModel
|
|
392
|
+
? 1
|
|
393
|
+
: currentFallback
|
|
394
|
+
? 2
|
|
395
|
+
: 0;
|
|
396
|
+
console.log(chalk.white(' OpenRouter fallback'));
|
|
397
|
+
console.log(chalk.dim(' Fallback retries can hide a bad primary model by silently switching to another model.'));
|
|
398
|
+
console.log(chalk.dim(' Choose explicitly; you can change this later with /fallback.'));
|
|
399
|
+
const fallbackChoice = await selectConfigChoice(rl, 'Fallback behavior', [
|
|
400
|
+
{ label: 'Disable fallback', detail: 'show the primary model failure; never switch to free automatically', value: 'off' },
|
|
401
|
+
{ label: 'Use openrouter/free', detail: 'retry once with the free router on timeout, empty, or cryptic errors', value: 'free' },
|
|
402
|
+
{ label: 'Custom fallback model', detail: 'enter a specific OpenRouter model id', value: 'custom' },
|
|
403
|
+
], {
|
|
404
|
+
defaultIndex: fallbackDefaultIndex,
|
|
405
|
+
fallbackPrompt: ` Fallback behavior [${fallbackDefaultIndex + 1}]: `,
|
|
406
|
+
parseFallback: (answer, defaultIndex) => {
|
|
407
|
+
const raw = (answer || String(defaultIndex + 1)).trim().toLowerCase();
|
|
408
|
+
if (['off', 'none', 'disable', 'disabled', 'no', 'n', '1'].includes(raw))
|
|
409
|
+
return 'off';
|
|
410
|
+
if (['free', 'openrouter/free', 'yes', 'y', '2'].includes(raw))
|
|
411
|
+
return 'free';
|
|
412
|
+
if (['custom', 'model', '3'].includes(raw))
|
|
413
|
+
return 'custom';
|
|
414
|
+
return undefined;
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
if (fallbackChoice === 'free')
|
|
418
|
+
fallbackModel = PROVIDERS.openrouter.defaultModel;
|
|
419
|
+
if (fallbackChoice === 'custom') {
|
|
420
|
+
const customFallback = currentFallback && currentFallback !== PROVIDERS.openrouter.defaultModel
|
|
421
|
+
? currentFallback
|
|
422
|
+
: '';
|
|
423
|
+
const answer = await rl.question(chalk.yellow(` Fallback model${customFallback ? ` [${customFallback}]` : ''}: `));
|
|
424
|
+
fallbackModel = answer.trim() || customFallback || undefined;
|
|
425
|
+
}
|
|
426
|
+
console.log('');
|
|
427
|
+
}
|
|
277
428
|
const permissionChoices = [
|
|
278
429
|
{ label: 'ask', detail: 'prompt before writes/commands (safest)', value: 'ask' },
|
|
279
430
|
{ label: 'auto', detail: 'auto-approve reads, ask for destructive', value: 'auto' },
|
|
@@ -293,21 +444,17 @@ async function setupWizard(rl, currentConfig) {
|
|
|
293
444
|
return byNumber?.value;
|
|
294
445
|
},
|
|
295
446
|
});
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
console.log(chalk.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const memoryDefaultIndex = currentConfig?.memory?.enabled === false ? 1 : 0;
|
|
305
|
-
const memoryEnabled = await selectConfigChoice(rl, 'Enable MemPalace memory', [
|
|
306
|
-
{ label: 'Enable', detail: 'local global + project memory stores', value: true },
|
|
307
|
-
{ label: 'Disable', detail: 'can be re-enabled later with /memory enable', value: false },
|
|
447
|
+
// /config stays focused on the main live controls: model/fallback,
|
|
448
|
+
// permissions, and swarm. Other subsystems keep their own commands.
|
|
449
|
+
console.log(chalk.white('\n Swarm settings'));
|
|
450
|
+
console.log(chalk.dim(' Defaults for /swarm <task>. Per-task setup still comes from the task text.'));
|
|
451
|
+
const swarmWizardDefaultIndex = currentConfig?.swarm?.setupWizard === false ? 1 : 0;
|
|
452
|
+
const swarmSetupWizard = await selectConfigChoice(rl, 'Swarm setup wizard', [
|
|
453
|
+
{ label: 'Enable', detail: 'ask goal/budget/stack/assets/quality before natural /swarm tasks', value: true },
|
|
454
|
+
{ label: 'Disable', detail: 'infer setup fields without asking', value: false },
|
|
308
455
|
], {
|
|
309
|
-
defaultIndex:
|
|
310
|
-
fallbackPrompt: `
|
|
456
|
+
defaultIndex: swarmWizardDefaultIndex,
|
|
457
|
+
fallbackPrompt: ` Swarm setup wizard? [${swarmWizardDefaultIndex === 0 ? 'Y/n' : 'y/N'}]: `,
|
|
311
458
|
parseFallback: (answer, defaultIndex) => {
|
|
312
459
|
const raw = answer.trim().toLowerCase();
|
|
313
460
|
if (!raw)
|
|
@@ -324,31 +471,51 @@ async function setupWizard(rl, currentConfig) {
|
|
|
324
471
|
return undefined;
|
|
325
472
|
},
|
|
326
473
|
});
|
|
474
|
+
const currentMaxAgents = clampSwarmMaxAgents(currentConfig?.swarm?.maxAgents);
|
|
475
|
+
const maxAgentsAnswer = await rl.question(chalk.yellow(` Max swarm agents [${currentMaxAgents}]: `));
|
|
476
|
+
const maxAgents = maxAgentsAnswer.trim()
|
|
477
|
+
? clampSwarmMaxAgents(Number.parseInt(maxAgentsAnswer.trim(), 10))
|
|
478
|
+
: currentMaxAgents;
|
|
479
|
+
const askOptionalSwarmDefault = async (label, current) => {
|
|
480
|
+
const answer = await rl.question(chalk.yellow(` ${label}${current ? ` [${current}]` : ''}: `));
|
|
481
|
+
return answer.trim() || current || undefined;
|
|
482
|
+
};
|
|
483
|
+
const defaultBudget = await askOptionalSwarmDefault('Default budget/time', currentConfig?.swarm?.defaultBudget);
|
|
484
|
+
const defaultTarget = await askOptionalSwarmDefault('Default target platform/stack', currentConfig?.swarm?.defaultTarget);
|
|
485
|
+
const defaultAssets = await askOptionalSwarmDefault('Default starting assets/resources', currentConfig?.swarm?.defaultAssets);
|
|
486
|
+
const defaultQuality = await askOptionalSwarmDefault('Default quality bar/release target', currentConfig?.swarm?.defaultQuality);
|
|
327
487
|
const config = {
|
|
488
|
+
...(currentConfig ?? {}),
|
|
328
489
|
apiKey,
|
|
329
490
|
baseURL,
|
|
330
491
|
model,
|
|
331
|
-
fallbackModel: providerKey === 'openrouter' ? PROVIDERS.openrouter.defaultModel : undefined,
|
|
332
492
|
provider: provider.name,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
temperature: 0.3,
|
|
493
|
+
maxTokens: currentConfig?.maxTokens ?? 8192,
|
|
494
|
+
temperature: currentConfig?.temperature ?? 0.3,
|
|
336
495
|
permissionMode: permMode,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
496
|
+
swarm: {
|
|
497
|
+
setupWizard: swarmSetupWizard,
|
|
498
|
+
maxAgents,
|
|
499
|
+
...(defaultBudget ? { defaultBudget } : {}),
|
|
500
|
+
...(defaultTarget ? { defaultTarget } : {}),
|
|
501
|
+
...(defaultAssets ? { defaultAssets } : {}),
|
|
502
|
+
...(defaultQuality ? { defaultQuality } : {}),
|
|
341
503
|
},
|
|
342
504
|
};
|
|
505
|
+
if (fallbackModel)
|
|
506
|
+
config.fallbackModel = fallbackModel;
|
|
507
|
+
else
|
|
508
|
+
delete config.fallbackModel;
|
|
509
|
+
if (openaiAuth)
|
|
510
|
+
config.openaiAuth = openaiAuth;
|
|
511
|
+
else
|
|
512
|
+
delete config.openaiAuth;
|
|
343
513
|
applyModelSelection(config, model);
|
|
344
514
|
saveConfig(config);
|
|
345
515
|
console.log(chalk.green(`\n Config saved to ${getConfigDir()}/config.json`));
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
else {
|
|
350
|
-
console.log(chalk.dim(` MemPalace: disabled. Re-enable anytime with /memory enable.`));
|
|
351
|
-
}
|
|
516
|
+
console.log(chalk.dim(` Configured: /model, /fallback, /perm, and /swarm. Memory stays under /memory.`));
|
|
517
|
+
console.log();
|
|
518
|
+
return config;
|
|
352
519
|
console.log();
|
|
353
520
|
return config;
|
|
354
521
|
}
|
|
@@ -494,6 +661,9 @@ function printResumedHistory(messages, sessionName) {
|
|
|
494
661
|
* and modeTag, so this is effectively a no-op there.
|
|
495
662
|
*/
|
|
496
663
|
async function askWithDecoratedPrompt(rl, sessionTag, modeTag, promptGlyph, prefill = '') {
|
|
664
|
+
if (isFooterActive()) {
|
|
665
|
+
return askWithFooterPrompt(rl, prefill);
|
|
666
|
+
}
|
|
497
667
|
const decorative = sessionTag + modeTag;
|
|
498
668
|
if (decorative.length > 0) {
|
|
499
669
|
process.stdout.write(decorative);
|
|
@@ -691,7 +861,10 @@ function parseSlashCommand(input) {
|
|
|
691
861
|
// { handled: true } — local command, output printed to stdout
|
|
692
862
|
// { handled: false, injectPrompt } — LLM-driven, prompt ready to send
|
|
693
863
|
export function handleSlashCommand(input, config, messages, session, mode) {
|
|
694
|
-
const
|
|
864
|
+
const parsed = parseSlashCommand(input);
|
|
865
|
+
const resolved = resolveCommandEntry(parsed.cmd);
|
|
866
|
+
const cmd = resolved?.entry.command ?? parsed.cmd;
|
|
867
|
+
const args = parsed.args;
|
|
695
868
|
switch (cmd) {
|
|
696
869
|
// ── Help ──────────────────────────────────────────
|
|
697
870
|
case '/help': {
|
|
@@ -715,6 +888,7 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
715
888
|
console.log(d(' ') + c('/theme [mode]') + d(' — toggle display mode (full/compact/minimal)'));
|
|
716
889
|
console.log(d(' ') + c('/palette [id]') + d(' — switch color palette; run /palettes to list'));
|
|
717
890
|
console.log(d(' ') + c('/palettes') + d(' — list available color palettes with preview'));
|
|
891
|
+
console.log(d(' ') + c('/footer [sub]') + d(' — customize the fixed bottom footer and startup prompt'));
|
|
718
892
|
console.log(d(' ') + c('/clear') + d(' — clear conversation'));
|
|
719
893
|
console.log(d(' ') + c('/back [n]') + d(' — rewind to before the nth most-recent user turn (no arg lists turns)'));
|
|
720
894
|
console.log(d(' ') + c('/fork [name]') + d(' — branch current conversation; previous session reachable via /resume (alias: /branch)'));
|
|
@@ -742,9 +916,9 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
742
916
|
console.log(d(' ') + c('/keys [add|rm]') + d(' — multi-key rotation pool (e.g. several OpenRouter accounts)'));
|
|
743
917
|
console.log(d(' ') + c('/route') + d(' — auto-route model based on next message'));
|
|
744
918
|
console.log(h('\n ── Modes ──'));
|
|
745
|
-
console.log(d(' ') + c('/mode [name]') + d(' — switch mode (dev/review/tdd/research/plan/debug/architect/
|
|
919
|
+
console.log(d(' ') + c('/mode [name]') + d(' — switch mode (dev/review/tdd/research/plan/debug/architect/sentience/design)'));
|
|
746
920
|
console.log(d(' ') + c('/modes') + d(' — list all modes (read-only; use /mode <name> to switch)'));
|
|
747
|
-
console.log(d(' ') + c('/
|
|
921
|
+
console.log(d(' ') + c('/sentience') + d(' — self-improving learning loop (alias: /hermes)'));
|
|
748
922
|
console.log(d(' ') + c('/design [task]') + d(' — switch to design mode (Stitch-powered UI generation); optional task to start immediately'));
|
|
749
923
|
console.log(h('\n ── Session ──'));
|
|
750
924
|
console.log(d(' ') + c('/sessions') + d(' — list saved sessions'));
|
|
@@ -890,10 +1064,90 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
890
1064
|
}
|
|
891
1065
|
}
|
|
892
1066
|
else {
|
|
893
|
-
|
|
894
|
-
console.log(chalk.dim(` Current theme: ${current}`));
|
|
1067
|
+
return { handled: true, injectPrompt: '__PICK_APPEARANCE__' };
|
|
895
1068
|
}
|
|
896
1069
|
return { handled: true };
|
|
1070
|
+
case '/footer': {
|
|
1071
|
+
const input = args.trim();
|
|
1072
|
+
const parts = input.split(/\s+/).filter(Boolean);
|
|
1073
|
+
const sub = (parts[0] || '').toLowerCase();
|
|
1074
|
+
config.footer = config.footer ?? { enabled: true, openingPrompt: '/model' };
|
|
1075
|
+
const refreshFooter = () => {
|
|
1076
|
+
const snapshot = buildFooterSnapshot(config, mode.current, session, process.cwd());
|
|
1077
|
+
if (shouldUseFixedFooter(config))
|
|
1078
|
+
activateFooter(snapshot);
|
|
1079
|
+
else
|
|
1080
|
+
deactivateFooter();
|
|
1081
|
+
updateFooter(snapshot);
|
|
1082
|
+
};
|
|
1083
|
+
const printStatus = () => {
|
|
1084
|
+
console.log(theme.header('\n Footer'));
|
|
1085
|
+
console.log(theme.dim(` Enabled: ${config.footer?.enabled === false ? 'off' : 'on'}`));
|
|
1086
|
+
console.log(theme.dim(` Version: ${getCurrentVersion()}`));
|
|
1087
|
+
console.log(theme.dim(` Opening prompt: ${config.footer?.openingPrompt ?? '/model'}`));
|
|
1088
|
+
console.log(theme.dim(` Status row: ${config.footer?.template || '(default)'}`));
|
|
1089
|
+
console.log(theme.dim(` Template keys: {provider}, {model}, {permissions}, {session}, {mode}, {cwd}, {workspace}, {sandbox}, {cost}, {budget}, {activity}, {version}`));
|
|
1090
|
+
console.log(theme.dim('\n Commands:'));
|
|
1091
|
+
console.log(theme.dim(' /footer on | off'));
|
|
1092
|
+
console.log(theme.dim(' /footer text Provider {provider} | Model {model} | v{version}'));
|
|
1093
|
+
console.log(theme.dim(' /footer opening /model (or: /footer opening off)'));
|
|
1094
|
+
console.log(theme.dim(' /footer clear (clear custom status row)'));
|
|
1095
|
+
console.log(theme.dim(' /footer reset (default footer + /model opening prompt)'));
|
|
1096
|
+
};
|
|
1097
|
+
if (!sub) {
|
|
1098
|
+
printStatus();
|
|
1099
|
+
return { handled: true };
|
|
1100
|
+
}
|
|
1101
|
+
if (sub === 'on') {
|
|
1102
|
+
config.footer.enabled = true;
|
|
1103
|
+
saveConfig(config);
|
|
1104
|
+
refreshFooter();
|
|
1105
|
+
console.log(chalk.green(' Footer: on'));
|
|
1106
|
+
return { handled: true };
|
|
1107
|
+
}
|
|
1108
|
+
if (sub === 'off') {
|
|
1109
|
+
config.footer.enabled = false;
|
|
1110
|
+
saveConfig(config);
|
|
1111
|
+
refreshFooter();
|
|
1112
|
+
console.log(chalk.green(' Footer: off'));
|
|
1113
|
+
return { handled: true };
|
|
1114
|
+
}
|
|
1115
|
+
if (sub === 'reset') {
|
|
1116
|
+
config.footer = { enabled: true, openingPrompt: '/model' };
|
|
1117
|
+
saveConfig(config);
|
|
1118
|
+
refreshFooter();
|
|
1119
|
+
console.log(chalk.green(' Footer reset to defaults.'));
|
|
1120
|
+
return { handled: true };
|
|
1121
|
+
}
|
|
1122
|
+
if (sub === 'clear') {
|
|
1123
|
+
delete config.footer.template;
|
|
1124
|
+
saveConfig(config);
|
|
1125
|
+
refreshFooter();
|
|
1126
|
+
console.log(chalk.green(' Footer status row: default'));
|
|
1127
|
+
return { handled: true };
|
|
1128
|
+
}
|
|
1129
|
+
if (sub === 'opening') {
|
|
1130
|
+
const value = input.slice(parts[0].length).trim();
|
|
1131
|
+
config.footer.openingPrompt = /^(off|none|disable)$/i.test(value) ? '' : (value || '/model');
|
|
1132
|
+
saveConfig(config);
|
|
1133
|
+
refreshFooter();
|
|
1134
|
+
console.log(chalk.green(` Footer opening prompt: ${config.footer.openingPrompt || '(off)'}`));
|
|
1135
|
+
return { handled: true };
|
|
1136
|
+
}
|
|
1137
|
+
const template = sub === 'text'
|
|
1138
|
+
? input.slice(parts[0].length).trim()
|
|
1139
|
+
: input;
|
|
1140
|
+
if (!template) {
|
|
1141
|
+
printStatus();
|
|
1142
|
+
return { handled: true };
|
|
1143
|
+
}
|
|
1144
|
+
config.footer.template = template;
|
|
1145
|
+
saveConfig(config);
|
|
1146
|
+
refreshFooter();
|
|
1147
|
+
console.log(chalk.green(' Footer status row updated.'));
|
|
1148
|
+
console.log(chalk.dim(` ${template}`));
|
|
1149
|
+
return { handled: true };
|
|
1150
|
+
}
|
|
897
1151
|
// ── Clear ─────────────────────────────────────────
|
|
898
1152
|
case '/clear': {
|
|
899
1153
|
// Also reset the global state that's keyed to the conversation
|
|
@@ -1174,7 +1428,7 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1174
1428
|
if (!target) {
|
|
1175
1429
|
const cur = config.fallbackModel ?? '(not set)';
|
|
1176
1430
|
console.log(chalk.dim(` Current fallback model: ${cur}`));
|
|
1177
|
-
console.log(chalk.dim(` Used once per chain when the primary returns
|
|
1431
|
+
console.log(chalk.dim(` Used once per chain when the primary times out, returns empty text, or hits an unknown provider error.`));
|
|
1178
1432
|
console.log(chalk.dim(` Set with: /fallback <model-id> · disable with: /fallback off`));
|
|
1179
1433
|
return { handled: true };
|
|
1180
1434
|
}
|
|
@@ -1187,18 +1441,19 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1187
1441
|
config.fallbackModel = target;
|
|
1188
1442
|
saveConfig(config);
|
|
1189
1443
|
console.log(chalk.green(` Fallback model: ${target}`));
|
|
1190
|
-
console.log(chalk.dim(` Will be tried automatically once per chain if ${config.model} returns
|
|
1444
|
+
console.log(chalk.dim(` Will be tried automatically once per chain if ${config.model} times out, returns empty text, or hits an unknown provider error.`));
|
|
1191
1445
|
return { handled: true };
|
|
1192
1446
|
}
|
|
1193
1447
|
case '/openrouter-free': {
|
|
1194
1448
|
config.provider = PROVIDERS.openrouter.name;
|
|
1195
1449
|
config.baseURL = PROVIDERS.openrouter.baseURL;
|
|
1196
1450
|
applyModelSelection(config, PROVIDERS.openrouter.defaultModel);
|
|
1197
|
-
config.fallbackModel =
|
|
1451
|
+
config.fallbackModel = undefined;
|
|
1198
1452
|
saveConfig(config);
|
|
1199
1453
|
resetClient();
|
|
1200
1454
|
console.log(chalk.green(' OpenRouter free-tier mode enabled.'));
|
|
1201
1455
|
console.log(chalk.dim(` Model: ${config.model}`));
|
|
1456
|
+
console.log(chalk.dim(' Fallback: disabled. Enable explicitly with /fallback <model-id>.'));
|
|
1202
1457
|
console.log(chalk.dim(' The free router picks a currently available zero-cost model that supports the request shape.'));
|
|
1203
1458
|
return { handled: true };
|
|
1204
1459
|
}
|
|
@@ -1280,8 +1535,8 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1280
1535
|
}
|
|
1281
1536
|
// ── Mode ──────────────────────────────────────────
|
|
1282
1537
|
case '/mode':
|
|
1283
|
-
if (args &&
|
|
1284
|
-
mode.current = args;
|
|
1538
|
+
if (args && normalizeModeName(args)) {
|
|
1539
|
+
mode.current = normalizeModeName(args);
|
|
1285
1540
|
const m = MODES[mode.current];
|
|
1286
1541
|
console.log(chalk.green(` Mode: ${m.label} — ${m.description}`));
|
|
1287
1542
|
// Soft hint when switching mode with conversation history present.
|
|
@@ -1310,10 +1565,11 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1310
1565
|
console.log(chalk.dim(` Current: ${mode.current} (${MODES[mode.current].description})`));
|
|
1311
1566
|
}
|
|
1312
1567
|
return { handled: true };
|
|
1313
|
-
// ──
|
|
1568
|
+
// ── Sentience shorthand (/hermes remains a compatibility alias) ──
|
|
1569
|
+
case '/sentience':
|
|
1314
1570
|
case '/hermes': {
|
|
1315
|
-
mode.current = '
|
|
1316
|
-
const m = MODES.
|
|
1571
|
+
mode.current = 'sentience';
|
|
1572
|
+
const m = MODES.sentience;
|
|
1317
1573
|
console.log(chalk.cyan(` Mode: ${m.label}`));
|
|
1318
1574
|
console.log(chalk.dim(` ${m.description}`));
|
|
1319
1575
|
console.log(chalk.dim(` Recall → user-model → parallelize → distill → persist → schedule.`));
|
|
@@ -1442,9 +1698,10 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
1442
1698
|
// mode.current is a { current: Mode } box (shared closure
|
|
1443
1699
|
// ref) so mutation propagates to the hotkey listener +
|
|
1444
1700
|
// prompt rendering automatically.
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
mode.current
|
|
1701
|
+
const loadedMode = loaded.mode ? normalizeModeName(loaded.mode) : null;
|
|
1702
|
+
if (loadedMode && loadedMode !== mode.current) {
|
|
1703
|
+
changes.push(`mode ${mode.current} -> ${loadedMode}`);
|
|
1704
|
+
mode.current = loadedMode;
|
|
1448
1705
|
}
|
|
1449
1706
|
// Adopt the resumed session's identity so subsequent
|
|
1450
1707
|
// autosaves write to its file, not the current session's.
|
|
@@ -2802,9 +3059,7 @@ export function handleSlashCommand(input, config, messages, session, mode) {
|
|
|
2802
3059
|
case '/palette': {
|
|
2803
3060
|
const target = args.trim().toLowerCase();
|
|
2804
3061
|
if (!target) {
|
|
2805
|
-
|
|
2806
|
-
console.log(chalk.dim(` Run /palettes to see all options.`));
|
|
2807
|
-
return { handled: true };
|
|
3062
|
+
return { handled: true, injectPrompt: '__PICK_APPEARANCE__' };
|
|
2808
3063
|
}
|
|
2809
3064
|
const resolved = resolvePaletteId(target);
|
|
2810
3065
|
if (!resolved) {
|
|
@@ -3316,6 +3571,16 @@ async function main() {
|
|
|
3316
3571
|
permissionMode: config.permissionMode,
|
|
3317
3572
|
});
|
|
3318
3573
|
const messages = [];
|
|
3574
|
+
const syncFooter = () => {
|
|
3575
|
+
const snapshot = buildFooterSnapshot(config, mode.current, session, process.cwd());
|
|
3576
|
+
if (!nonInteractive && shouldUseFixedFooter(config)) {
|
|
3577
|
+
activateFooter(snapshot);
|
|
3578
|
+
}
|
|
3579
|
+
else {
|
|
3580
|
+
deactivateFooter();
|
|
3581
|
+
}
|
|
3582
|
+
updateFooter(snapshot);
|
|
3583
|
+
};
|
|
3319
3584
|
// Session start hook + memory persistence
|
|
3320
3585
|
await runHooks({ event: 'SessionStart', sessionId: session.id, cwd: process.cwd(), permissionMode: config.permissionMode });
|
|
3321
3586
|
const memoryContext = onSessionStart(session.id, process.cwd());
|
|
@@ -3361,6 +3626,17 @@ async function main() {
|
|
|
3361
3626
|
console.log(theme.dim(` Free-tier-safe switch: /openrouter-free`));
|
|
3362
3627
|
console.log('');
|
|
3363
3628
|
}
|
|
3629
|
+
syncFooter();
|
|
3630
|
+
startStartupUpdateCheck({
|
|
3631
|
+
onUpdateStarted: (currentVersion, latestVersion) => {
|
|
3632
|
+
deactivateFooter();
|
|
3633
|
+
console.log(theme.info(` Update available: cawdex ${currentVersion} -> ${latestVersion}. Downloading in the background; restart Cawdex to use it.`));
|
|
3634
|
+
syncFooter();
|
|
3635
|
+
},
|
|
3636
|
+
onError: (error) => {
|
|
3637
|
+
dbgEmit('debug', 'update.check.failed', { error: error.slice(0, 200) });
|
|
3638
|
+
},
|
|
3639
|
+
});
|
|
3364
3640
|
let autoRoute = false;
|
|
3365
3641
|
// ── F-key hotkey listener ────────────────────────────────
|
|
3366
3642
|
// Voice / accessibility hotkeys — all on the F-row.
|
|
@@ -4202,6 +4478,7 @@ async function main() {
|
|
|
4202
4478
|
});
|
|
4203
4479
|
messages.push({ role: 'user', content: resolvedPrompt.prompt });
|
|
4204
4480
|
try {
|
|
4481
|
+
await saveWithSnapshot();
|
|
4205
4482
|
await runQuery({
|
|
4206
4483
|
config,
|
|
4207
4484
|
messages,
|
|
@@ -4235,10 +4512,12 @@ async function main() {
|
|
|
4235
4512
|
process.exit(1);
|
|
4236
4513
|
}
|
|
4237
4514
|
}
|
|
4515
|
+
let firstInteractivePrompt = true;
|
|
4238
4516
|
// Main REPL loop
|
|
4239
4517
|
while (true) {
|
|
4240
4518
|
let input;
|
|
4241
4519
|
try {
|
|
4520
|
+
syncFooter();
|
|
4242
4521
|
// Screen-reader-aware prompt construction:
|
|
4243
4522
|
// - The Unicode prompt glyph (❯) gets re-substituted by the
|
|
4244
4523
|
// symbol→word filter on EVERY readline redraw (one per keystroke),
|
|
@@ -4260,7 +4539,9 @@ async function main() {
|
|
|
4260
4539
|
// repaints the prompt line on every render — it needs both the
|
|
4261
4540
|
// styled string (to write with color) and the visible-char
|
|
4262
4541
|
// length (to position the cursor at end-of-filter).
|
|
4263
|
-
const promptStyled =
|
|
4542
|
+
const promptStyled = isFooterActive()
|
|
4543
|
+
? theme.prompt('> ')
|
|
4544
|
+
: sessionTag + modeTag + theme.prompt(promptGlyph);
|
|
4264
4545
|
globalThis
|
|
4265
4546
|
.__cawdexPromptStyled = promptStyled;
|
|
4266
4547
|
globalThis
|
|
@@ -4276,7 +4557,11 @@ async function main() {
|
|
|
4276
4557
|
g.__cawdexQueuedInput = undefined;
|
|
4277
4558
|
g.__cawdexSlashPrefillInput = undefined;
|
|
4278
4559
|
const restoredDraft = normalizeTypeaheadDraftForPrompt(queued);
|
|
4279
|
-
const
|
|
4560
|
+
const openingPrompt = firstInteractivePrompt && isFooterActive()
|
|
4561
|
+
? (config.footer?.openingPrompt ?? '/model')
|
|
4562
|
+
: '';
|
|
4563
|
+
firstInteractivePrompt = false;
|
|
4564
|
+
const prefill = slashPrefill || restoredDraft || openingPrompt;
|
|
4280
4565
|
input = await askWithDecoratedPrompt(rl, sessionTag, modeTag, promptGlyph, prefill);
|
|
4281
4566
|
}
|
|
4282
4567
|
catch {
|
|
@@ -4342,9 +4627,11 @@ async function main() {
|
|
|
4342
4627
|
messages.push(...result.newMessages);
|
|
4343
4628
|
}
|
|
4344
4629
|
if (trimmed.startsWith('/config') && !result?.shouldExit) {
|
|
4630
|
+
deactivateFooter();
|
|
4345
4631
|
config = await setupWizard(rl, config);
|
|
4346
4632
|
resetClient();
|
|
4347
4633
|
printThemedBanner(config.provider, config.model, mode.current, config.permissionMode, session.id, ALL_TOOLS.map((t) => t.name));
|
|
4634
|
+
syncFooter();
|
|
4348
4635
|
continue;
|
|
4349
4636
|
}
|
|
4350
4637
|
if (trimmed === '/route') {
|
|
@@ -4402,6 +4689,10 @@ async function main() {
|
|
|
4402
4689
|
}
|
|
4403
4690
|
continue;
|
|
4404
4691
|
}
|
|
4692
|
+
else if (result.injectPrompt === '__PICK_APPEARANCE__') {
|
|
4693
|
+
await runAppearanceWizard(rl, config);
|
|
4694
|
+
continue;
|
|
4695
|
+
}
|
|
4405
4696
|
else if (result.injectPrompt === '__RESUME_PICK__') {
|
|
4406
4697
|
const sessions = listSessions();
|
|
4407
4698
|
if (sessions.length === 0) {
|
|
@@ -4466,9 +4757,9 @@ async function main() {
|
|
|
4466
4757
|
console.log(chalk.yellow(' /swarm: could not parse swarm payload.'));
|
|
4467
4758
|
continue;
|
|
4468
4759
|
}
|
|
4469
|
-
let plan = buildSwarmPlan(payload);
|
|
4760
|
+
let plan = buildSwarmPlan(payload, config.swarm);
|
|
4470
4761
|
if (payload.mode === 'auto') {
|
|
4471
|
-
plan = await runSwarmSetupWizard(rl, plan);
|
|
4762
|
+
plan = await runSwarmSetupWizard(rl, plan, config);
|
|
4472
4763
|
}
|
|
4473
4764
|
try {
|
|
4474
4765
|
const agents = resolveAgents(plan.agents);
|
|
@@ -4496,6 +4787,7 @@ async function main() {
|
|
|
4496
4787
|
else {
|
|
4497
4788
|
messages.push({ role: 'user', content: result.injectPrompt });
|
|
4498
4789
|
}
|
|
4790
|
+
await saveWithSnapshot();
|
|
4499
4791
|
await runQuery({ config, messages, cwd: process.cwd(), rl, sessionId: session.id, mode: mode.current });
|
|
4500
4792
|
await saveWithSnapshot();
|
|
4501
4793
|
continue;
|
|
@@ -4524,6 +4816,7 @@ async function main() {
|
|
|
4524
4816
|
}
|
|
4525
4817
|
// Add user message and run query
|
|
4526
4818
|
messages.push({ role: 'user', content: trimmed });
|
|
4819
|
+
await saveWithSnapshot();
|
|
4527
4820
|
await runQuery({
|
|
4528
4821
|
config,
|
|
4529
4822
|
messages,
|
|
@@ -4545,12 +4838,14 @@ async function main() {
|
|
|
4545
4838
|
await runHooks({ event: 'SessionStop', sessionId: session.id, cwd: process.cwd(), permissionMode: config.permissionMode });
|
|
4546
4839
|
// Final save
|
|
4547
4840
|
await saveWithSnapshot();
|
|
4841
|
+
deactivateFooter();
|
|
4548
4842
|
console.log(chalk.dim(`\nSession saved: ${session.id}`));
|
|
4549
4843
|
console.log(chalk.dim('Goodbye!\n'));
|
|
4550
4844
|
rl.close();
|
|
4551
4845
|
process.exit(0);
|
|
4552
4846
|
}
|
|
4553
4847
|
main().catch((err) => {
|
|
4848
|
+
deactivateFooter();
|
|
4554
4849
|
console.error(chalk.red(`Fatal: ${err.message || err}`));
|
|
4555
4850
|
process.exit(1);
|
|
4556
4851
|
});
|