@compilr-dev/cli 0.4.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/README.md +110 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +317 -0
- package/dist/agents/registry.d.ts +66 -0
- package/dist/agents/registry.js +238 -0
- package/dist/agents/types.d.ts +40 -0
- package/dist/agents/types.js +94 -0
- package/dist/commands/custom-registry.d.ts +69 -0
- package/dist/commands/custom-registry.js +246 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/types.d.ts +31 -0
- package/dist/commands/types.js +26 -0
- package/dist/commands.d.ts +63 -0
- package/dist/commands.js +324 -0
- package/dist/db/index.d.ts +42 -0
- package/dist/db/index.js +146 -0
- package/dist/db/repositories/document-repository.d.ts +63 -0
- package/dist/db/repositories/document-repository.js +184 -0
- package/dist/db/repositories/index.d.ts +9 -0
- package/dist/db/repositories/index.js +6 -0
- package/dist/db/repositories/project-repository.d.ts +132 -0
- package/dist/db/repositories/project-repository.js +337 -0
- package/dist/db/repositories/work-item-repository.d.ts +115 -0
- package/dist/db/repositories/work-item-repository.js +389 -0
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +143 -0
- package/dist/debug.d.ts +8 -0
- package/dist/debug.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +348 -0
- package/dist/index.old.d.ts +7 -0
- package/dist/index.old.js +1014 -0
- package/dist/repl.d.ts +121 -0
- package/dist/repl.js +1878 -0
- package/dist/settings/index.d.ts +80 -0
- package/dist/settings/index.js +195 -0
- package/dist/shared-handlers.d.ts +63 -0
- package/dist/shared-handlers.js +57 -0
- package/dist/slash-autocomplete.d.ts +41 -0
- package/dist/slash-autocomplete.js +638 -0
- package/dist/state.d.ts +75 -0
- package/dist/state.js +130 -0
- package/dist/tabbed-menu.d.ts +11 -0
- package/dist/tabbed-menu.js +328 -0
- package/dist/templates/backlog-md.d.ts +7 -0
- package/dist/templates/backlog-md.js +94 -0
- package/dist/templates/claude-md.d.ts +7 -0
- package/dist/templates/claude-md.js +189 -0
- package/dist/templates/coding-standards.d.ts +7 -0
- package/dist/templates/coding-standards.js +299 -0
- package/dist/templates/compilr-md.d.ts +7 -0
- package/dist/templates/compilr-md.js +189 -0
- package/dist/templates/config-json.d.ts +38 -0
- package/dist/templates/config-json.js +39 -0
- package/dist/templates/gitignore.d.ts +7 -0
- package/dist/templates/gitignore.js +85 -0
- package/dist/templates/index.d.ts +19 -0
- package/dist/templates/index.js +302 -0
- package/dist/templates/package-json.d.ts +7 -0
- package/dist/templates/package-json.js +111 -0
- package/dist/templates/readme-md.d.ts +7 -0
- package/dist/templates/readme-md.js +161 -0
- package/dist/templates/tsconfig.d.ts +7 -0
- package/dist/templates/tsconfig.js +61 -0
- package/dist/templates/types.d.ts +33 -0
- package/dist/templates/types.js +24 -0
- package/dist/test-autocomplete.d.ts +7 -0
- package/dist/test-autocomplete.js +85 -0
- package/dist/test-tabbed-menu.d.ts +7 -0
- package/dist/test-tabbed-menu.js +25 -0
- package/dist/themes/colors.d.ts +49 -0
- package/dist/themes/colors.js +135 -0
- package/dist/themes/index.d.ts +23 -0
- package/dist/themes/index.js +24 -0
- package/dist/themes/registry.d.ts +60 -0
- package/dist/themes/registry.js +195 -0
- package/dist/themes/types.d.ts +82 -0
- package/dist/themes/types.js +7 -0
- package/dist/tool-selector.d.ts +71 -0
- package/dist/tool-selector.js +184 -0
- package/dist/tools/ask-user-simple.d.ts +19 -0
- package/dist/tools/ask-user-simple.js +86 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +113 -0
- package/dist/tools/backlog.d.ts +53 -0
- package/dist/tools/backlog.js +709 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +121 -0
- package/dist/ui/agents-overlay.d.ts +12 -0
- package/dist/ui/agents-overlay.js +501 -0
- package/dist/ui/arch-type-overlay.d.ts +20 -0
- package/dist/ui/arch-type-overlay.js +229 -0
- package/dist/ui/ask-user-overlay.d.ts +26 -0
- package/dist/ui/ask-user-overlay.js +647 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay.js +242 -0
- package/dist/ui/backlog-overlay.d.ts +17 -0
- package/dist/ui/backlog-overlay.js +786 -0
- package/dist/ui/commands-overlay.d.ts +11 -0
- package/dist/ui/commands-overlay.js +410 -0
- package/dist/ui/config-overlay.d.ts +34 -0
- package/dist/ui/config-overlay.js +977 -0
- package/dist/ui/conversation.d.ts +82 -0
- package/dist/ui/conversation.js +508 -0
- package/dist/ui/diff.d.ts +38 -0
- package/dist/ui/diff.js +182 -0
- package/dist/ui/ephemeral.d.ts +111 -0
- package/dist/ui/ephemeral.js +413 -0
- package/dist/ui/file-autocomplete.d.ts +45 -0
- package/dist/ui/file-autocomplete.js +237 -0
- package/dist/ui/footer.d.ts +153 -0
- package/dist/ui/footer.js +422 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/init-overlay.d.ts +24 -0
- package/dist/ui/init-overlay.js +525 -0
- package/dist/ui/input-prompt-v2.d.ts +179 -0
- package/dist/ui/input-prompt-v2.js +991 -0
- package/dist/ui/input-prompt.d.ts +97 -0
- package/dist/ui/input-prompt.js +800 -0
- package/dist/ui/iteration-limit-overlay.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay.js +150 -0
- package/dist/ui/keys-overlay.d.ts +14 -0
- package/dist/ui/keys-overlay.js +181 -0
- package/dist/ui/model-warning-overlay.d.ts +30 -0
- package/dist/ui/model-warning-overlay.js +171 -0
- package/dist/ui/overlay-controller.d.ts +25 -0
- package/dist/ui/overlay-controller.js +35 -0
- package/dist/ui/overlays.d.ts +47 -0
- package/dist/ui/overlays.js +627 -0
- package/dist/ui/permission-overlay.d.ts +16 -0
- package/dist/ui/permission-overlay.js +494 -0
- package/dist/ui/terminal.d.ts +117 -0
- package/dist/ui/terminal.js +237 -0
- package/dist/ui/todo-zone.d.ts +112 -0
- package/dist/ui/todo-zone.js +353 -0
- package/dist/ui/tools-overlay.d.ts +26 -0
- package/dist/ui/tools-overlay.js +278 -0
- package/dist/ui/tutorial-overlay.d.ts +10 -0
- package/dist/ui/tutorial-overlay.js +936 -0
- package/dist/ui/types.d.ts +103 -0
- package/dist/ui/types.js +33 -0
- package/dist/utils/credentials.d.ts +55 -0
- package/dist/utils/credentials.js +268 -0
- package/dist/utils/model-tiers.d.ts +37 -0
- package/dist/utils/model-tiers.js +118 -0
- package/dist/utils/project-memory.d.ts +47 -0
- package/dist/utils/project-memory.js +117 -0
- package/dist/utils/project-status.d.ts +56 -0
- package/dist/utils/project-status.js +237 -0
- package/package.json +66 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iteration Limit Overlay
|
|
3
|
+
*
|
|
4
|
+
* Modal overlay shown when the agent reaches its iteration limit.
|
|
5
|
+
* Asks the user if they want to continue or stop execution.
|
|
6
|
+
*/
|
|
7
|
+
export type IterationLimitResult = {
|
|
8
|
+
continue: true;
|
|
9
|
+
additionalIterations: number;
|
|
10
|
+
} | {
|
|
11
|
+
continue: false;
|
|
12
|
+
};
|
|
13
|
+
export interface IterationLimitOverlayOptions {
|
|
14
|
+
iteration: number;
|
|
15
|
+
maxIterations: number;
|
|
16
|
+
toolCallCount: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Show the iteration limit overlay
|
|
20
|
+
*/
|
|
21
|
+
export declare function showIterationLimitOverlay(options: IterationLimitOverlayOptions): Promise<IterationLimitResult>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iteration Limit Overlay
|
|
3
|
+
*
|
|
4
|
+
* Modal overlay shown when the agent reaches its iteration limit.
|
|
5
|
+
* Asks the user if they want to continue or stop execution.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import * as terminal from './terminal.js';
|
|
9
|
+
import { getStyles } from '../themes/index.js';
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Rendering
|
|
12
|
+
// =============================================================================
|
|
13
|
+
function render(options, state, previousLineCount = 0, targetLineCount = 0) {
|
|
14
|
+
const s = getStyles();
|
|
15
|
+
const lines = [];
|
|
16
|
+
const cols = terminal.getTerminalWidth();
|
|
17
|
+
const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
|
|
18
|
+
// Clear previous render
|
|
19
|
+
if (previousLineCount > 0) {
|
|
20
|
+
terminal.clearLinesAbove(previousLineCount);
|
|
21
|
+
}
|
|
22
|
+
// Header
|
|
23
|
+
lines.push(border);
|
|
24
|
+
lines.push(' ' + s.warning('⚠') + ' ' + chalk.bold('Iteration Limit Reached'));
|
|
25
|
+
lines.push('');
|
|
26
|
+
// Stats
|
|
27
|
+
lines.push(' Iterations: ' + s.primary(String(options.iteration)) + ' / ' + s.muted(String(options.maxIterations)));
|
|
28
|
+
lines.push(' Tool calls: ' + s.primary(String(options.toolCallCount)));
|
|
29
|
+
lines.push('');
|
|
30
|
+
lines.push(' ' + s.muted('The agent has reached its iteration limit.'));
|
|
31
|
+
lines.push(' ' + s.muted('Would you like to continue?'));
|
|
32
|
+
lines.push('');
|
|
33
|
+
// Options
|
|
34
|
+
const optionLabels = ['Yes, continue (+10 iterations)', 'Yes, continue (+20 iterations)', 'No, stop here'];
|
|
35
|
+
const optionKeys = ['1', '2', 'n'];
|
|
36
|
+
for (let i = 0; i < optionLabels.length; i++) {
|
|
37
|
+
const isCursor = state.selectedIndex === i;
|
|
38
|
+
const prefix = isCursor ? ' ❯ ' : ' ';
|
|
39
|
+
const key = `[${optionKeys[i]}] `;
|
|
40
|
+
if (isCursor) {
|
|
41
|
+
lines.push(s.primary(prefix + key + optionLabels[i]));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
lines.push(s.muted(prefix + key + optionLabels[i]));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Footer
|
|
48
|
+
lines.push('');
|
|
49
|
+
lines.push(s.muted(' ↑↓ Navigate · Enter Select · 1/2/n Quick select'));
|
|
50
|
+
// Bottom border
|
|
51
|
+
lines.push(border);
|
|
52
|
+
// Pad with empty lines to maintain consistent height
|
|
53
|
+
while (lines.length < targetLineCount) {
|
|
54
|
+
lines.push('');
|
|
55
|
+
}
|
|
56
|
+
// Render all lines
|
|
57
|
+
terminal.write(lines.join('\n'));
|
|
58
|
+
return lines.length;
|
|
59
|
+
}
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Main Export
|
|
62
|
+
// =============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Show the iteration limit overlay
|
|
65
|
+
*/
|
|
66
|
+
export async function showIterationLimitOverlay(options) {
|
|
67
|
+
const state = {
|
|
68
|
+
selectedIndex: 0, // Default to "Yes, continue (+10)"
|
|
69
|
+
};
|
|
70
|
+
let lineCount = 0;
|
|
71
|
+
let maxLineCount = 0;
|
|
72
|
+
// Ensure we start from a fresh line
|
|
73
|
+
terminal.writeLine('');
|
|
74
|
+
terminal.hideCursor();
|
|
75
|
+
const wasRawMode = process.stdin.isRaw;
|
|
76
|
+
terminal.enableRawMode();
|
|
77
|
+
// Initial render
|
|
78
|
+
lineCount = render(options, state, 0);
|
|
79
|
+
maxLineCount = Math.max(maxLineCount, lineCount);
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
const cleanup = (result) => {
|
|
82
|
+
terminal.clearLinesAbove(maxLineCount);
|
|
83
|
+
// Show result summary
|
|
84
|
+
const s = getStyles();
|
|
85
|
+
if (result.continue) {
|
|
86
|
+
terminal.writeLine(s.muted(`Iteration limit: `) + s.success(`Continuing (+${String(result.additionalIterations)})`));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
terminal.writeLine(s.muted(`Iteration limit: `) + s.error(`Stopped`));
|
|
90
|
+
}
|
|
91
|
+
terminal.writeLine(''); // Blank line for separation
|
|
92
|
+
terminal.showCursor();
|
|
93
|
+
if (!wasRawMode) {
|
|
94
|
+
terminal.disableRawMode();
|
|
95
|
+
}
|
|
96
|
+
process.stdin.removeListener('data', handleData);
|
|
97
|
+
resolve(result);
|
|
98
|
+
};
|
|
99
|
+
const handleData = (data) => {
|
|
100
|
+
const isEscape = data.length === 1 && data[0] === 0x1b;
|
|
101
|
+
const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
|
|
102
|
+
const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
|
|
103
|
+
const isCtrlC = data.length === 1 && data[0] === 0x03;
|
|
104
|
+
const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
|
|
105
|
+
// Ctrl+C or Escape = stop
|
|
106
|
+
if (isCtrlC || isEscape) {
|
|
107
|
+
cleanup({ continue: false });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Quick keys
|
|
111
|
+
const key = data.toString();
|
|
112
|
+
if (key === '1') {
|
|
113
|
+
cleanup({ continue: true, additionalIterations: 10 });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (key === '2') {
|
|
117
|
+
cleanup({ continue: true, additionalIterations: 20 });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (key.toLowerCase() === 'n') {
|
|
121
|
+
cleanup({ continue: false });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Arrow navigation
|
|
125
|
+
if (isUpArrow) {
|
|
126
|
+
state.selectedIndex = Math.max(0, state.selectedIndex - 1);
|
|
127
|
+
}
|
|
128
|
+
else if (isDownArrow) {
|
|
129
|
+
state.selectedIndex = Math.min(2, state.selectedIndex + 1);
|
|
130
|
+
}
|
|
131
|
+
else if (isEnter) {
|
|
132
|
+
// Select based on current index
|
|
133
|
+
if (state.selectedIndex === 0) {
|
|
134
|
+
cleanup({ continue: true, additionalIterations: 10 });
|
|
135
|
+
}
|
|
136
|
+
else if (state.selectedIndex === 1) {
|
|
137
|
+
cleanup({ continue: true, additionalIterations: 20 });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
cleanup({ continue: false });
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Re-render
|
|
145
|
+
lineCount = render(options, state, maxLineCount, maxLineCount);
|
|
146
|
+
maxLineCount = Math.max(maxLineCount, lineCount);
|
|
147
|
+
};
|
|
148
|
+
process.stdin.on('data', handleData);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys Overlay
|
|
3
|
+
*
|
|
4
|
+
* Modal overlay for managing API keys.
|
|
5
|
+
* Shows status of all providers and allows setting/deleting keys.
|
|
6
|
+
*/
|
|
7
|
+
export interface KeysOverlayResult {
|
|
8
|
+
/** Whether any keys were changed */
|
|
9
|
+
changed: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Show the keys management overlay
|
|
13
|
+
*/
|
|
14
|
+
export declare function showKeysOverlay(): Promise<KeysOverlayResult>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys Overlay
|
|
3
|
+
*
|
|
4
|
+
* Modal overlay for managing API keys.
|
|
5
|
+
* Shows status of all providers and allows setting/deleting keys.
|
|
6
|
+
*/
|
|
7
|
+
import * as terminal from './terminal.js';
|
|
8
|
+
import { getStyles } from '../themes/index.js';
|
|
9
|
+
import { getAllProviderStatuses, setApiKey, deleteApiKey, } from '../utils/credentials.js';
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Rendering
|
|
12
|
+
// =============================================================================
|
|
13
|
+
function render(state, previousLineCount = 0) {
|
|
14
|
+
const s = getStyles();
|
|
15
|
+
const lines = [];
|
|
16
|
+
const cols = terminal.getTerminalWidth();
|
|
17
|
+
const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
|
|
18
|
+
// Clear previous render
|
|
19
|
+
if (previousLineCount > 0) {
|
|
20
|
+
terminal.clearLinesAbove(previousLineCount);
|
|
21
|
+
}
|
|
22
|
+
// Header
|
|
23
|
+
lines.push(border);
|
|
24
|
+
lines.push(' ' + s.primaryBold('API Keys'));
|
|
25
|
+
lines.push('');
|
|
26
|
+
if (state.mode === 'list') {
|
|
27
|
+
// List mode - show all providers
|
|
28
|
+
for (let i = 0; i < state.providers.length; i++) {
|
|
29
|
+
const p = state.providers[i];
|
|
30
|
+
const isCursor = state.selectedIndex === i;
|
|
31
|
+
const prefix = isCursor ? ' ❯ ' : ' ';
|
|
32
|
+
// Status indicator
|
|
33
|
+
let status;
|
|
34
|
+
if (p.provider === 'ollama') {
|
|
35
|
+
status = s.secondary('Local (no key needed)');
|
|
36
|
+
}
|
|
37
|
+
else if (p.hasKey) {
|
|
38
|
+
const source = p.fromEnv ? s.muted(' (env)') : '';
|
|
39
|
+
status = s.success(`✓ ${p.masked ?? '***'}`) + source;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
status = s.warning('○ Not set');
|
|
43
|
+
}
|
|
44
|
+
// Provider name
|
|
45
|
+
const name = isCursor ? s.primary(p.name) : p.name;
|
|
46
|
+
const line = `${prefix}${name.padEnd(22)} ${status}`;
|
|
47
|
+
lines.push(line);
|
|
48
|
+
}
|
|
49
|
+
lines.push('');
|
|
50
|
+
lines.push(s.muted(' ↑↓ Navigate · Enter Edit · d Delete · Esc Close'));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Input mode - show key entry
|
|
54
|
+
const provider = state.providers.find(p => p.provider === state.editingProvider);
|
|
55
|
+
if (provider) {
|
|
56
|
+
lines.push(` Set API key for ${s.primaryBold(provider.name)}:`);
|
|
57
|
+
lines.push('');
|
|
58
|
+
lines.push(` > ${state.inputBuffer}${s.primary('▋')}`);
|
|
59
|
+
lines.push('');
|
|
60
|
+
lines.push(s.muted(` Get key: ${provider.keyUrl}`));
|
|
61
|
+
lines.push('');
|
|
62
|
+
lines.push(s.muted(' Enter Save · Esc Cancel'));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Bottom border
|
|
66
|
+
lines.push(border);
|
|
67
|
+
// Render all lines
|
|
68
|
+
terminal.write(lines.join('\n'));
|
|
69
|
+
return lines.length;
|
|
70
|
+
}
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Main Export
|
|
73
|
+
// =============================================================================
|
|
74
|
+
/**
|
|
75
|
+
* Show the keys management overlay
|
|
76
|
+
*/
|
|
77
|
+
export async function showKeysOverlay() {
|
|
78
|
+
const state = {
|
|
79
|
+
mode: 'list',
|
|
80
|
+
selectedIndex: 0,
|
|
81
|
+
editingProvider: null,
|
|
82
|
+
inputBuffer: '',
|
|
83
|
+
cancelled: false,
|
|
84
|
+
providers: getAllProviderStatuses(),
|
|
85
|
+
};
|
|
86
|
+
let lineCount = 0;
|
|
87
|
+
let changed = false;
|
|
88
|
+
// Initial render
|
|
89
|
+
lineCount = render(state, lineCount);
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const stdin = process.stdin;
|
|
92
|
+
stdin.setRawMode(true);
|
|
93
|
+
stdin.resume();
|
|
94
|
+
const cleanup = () => {
|
|
95
|
+
stdin.removeListener('data', handleKey);
|
|
96
|
+
stdin.setRawMode(false);
|
|
97
|
+
stdin.pause();
|
|
98
|
+
// Clear the overlay
|
|
99
|
+
terminal.clearLinesAbove(lineCount);
|
|
100
|
+
};
|
|
101
|
+
const handleKey = (data) => {
|
|
102
|
+
const key = data.toString();
|
|
103
|
+
if (state.mode === 'list') {
|
|
104
|
+
// List mode key handling
|
|
105
|
+
switch (key) {
|
|
106
|
+
case '\x1B': // Escape
|
|
107
|
+
case 'q':
|
|
108
|
+
cleanup();
|
|
109
|
+
resolve({ changed });
|
|
110
|
+
return;
|
|
111
|
+
case '\x1B[A': // Up arrow
|
|
112
|
+
if (state.selectedIndex > 0) {
|
|
113
|
+
state.selectedIndex--;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
case '\x1B[B': // Down arrow
|
|
117
|
+
if (state.selectedIndex < state.providers.length - 1) {
|
|
118
|
+
state.selectedIndex++;
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case '\r': // Enter - edit selected
|
|
122
|
+
case '\n': {
|
|
123
|
+
const selected = state.providers[state.selectedIndex];
|
|
124
|
+
if (selected.provider !== 'ollama') {
|
|
125
|
+
state.mode = 'input';
|
|
126
|
+
state.editingProvider = selected.provider;
|
|
127
|
+
state.inputBuffer = '';
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case 'd':
|
|
132
|
+
case 'D': {
|
|
133
|
+
// Delete key for selected provider
|
|
134
|
+
const selected = state.providers[state.selectedIndex];
|
|
135
|
+
if (selected.provider !== 'ollama' && selected.hasKey && !selected.fromEnv) {
|
|
136
|
+
deleteApiKey(selected.provider);
|
|
137
|
+
state.providers = getAllProviderStatuses();
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Input mode key handling
|
|
146
|
+
if (key === '\x1B') {
|
|
147
|
+
// Escape - cancel input
|
|
148
|
+
state.mode = 'list';
|
|
149
|
+
state.editingProvider = null;
|
|
150
|
+
state.inputBuffer = '';
|
|
151
|
+
}
|
|
152
|
+
else if (key === '\r' || key === '\n') {
|
|
153
|
+
// Enter - save key
|
|
154
|
+
if (state.inputBuffer.trim() && state.editingProvider) {
|
|
155
|
+
setApiKey(state.editingProvider, state.inputBuffer.trim());
|
|
156
|
+
state.providers = getAllProviderStatuses();
|
|
157
|
+
changed = true;
|
|
158
|
+
}
|
|
159
|
+
state.mode = 'list';
|
|
160
|
+
state.editingProvider = null;
|
|
161
|
+
state.inputBuffer = '';
|
|
162
|
+
}
|
|
163
|
+
else if (key === '\x7F' || key === '\b') {
|
|
164
|
+
// Backspace
|
|
165
|
+
state.inputBuffer = state.inputBuffer.slice(0, -1);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Handle paste (multiple characters) or single printable character
|
|
169
|
+
// Filter to only printable ASCII characters
|
|
170
|
+
const printable = key.split('').filter(c => c >= ' ' && c <= '~').join('');
|
|
171
|
+
if (printable.length > 0) {
|
|
172
|
+
state.inputBuffer += printable;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Re-render
|
|
177
|
+
lineCount = render(state, lineCount);
|
|
178
|
+
};
|
|
179
|
+
stdin.on('data', handleKey);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Warning Overlay
|
|
3
|
+
*
|
|
4
|
+
* Shows a warning when the user runs a command that requires
|
|
5
|
+
* a larger model than they're currently using.
|
|
6
|
+
*
|
|
7
|
+
* Offers options to:
|
|
8
|
+
* - Switch to a recommended model
|
|
9
|
+
* - Use a simpler alternative command
|
|
10
|
+
* - Continue anyway
|
|
11
|
+
* - Cancel
|
|
12
|
+
*/
|
|
13
|
+
import type { TierLevel } from '../utils/model-tiers.js';
|
|
14
|
+
export interface ModelWarningOptions {
|
|
15
|
+
/** The command being run (e.g., '/design') */
|
|
16
|
+
command: string;
|
|
17
|
+
/** Current model ID */
|
|
18
|
+
currentModel: string;
|
|
19
|
+
/** Current model's tier */
|
|
20
|
+
currentTier: TierLevel;
|
|
21
|
+
/** Suggested upgrade model (if available) */
|
|
22
|
+
suggestedModel?: string;
|
|
23
|
+
/** Alternative simpler command (e.g., '/sketch') */
|
|
24
|
+
alternativeCommand?: string;
|
|
25
|
+
}
|
|
26
|
+
export type ModelWarningChoice = 'switch' | 'alternative' | 'continue' | 'cancel';
|
|
27
|
+
/**
|
|
28
|
+
* Show the model warning overlay and return the user's choice.
|
|
29
|
+
*/
|
|
30
|
+
export declare function showModelWarningOverlay(options: ModelWarningOptions): Promise<ModelWarningChoice>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Warning Overlay
|
|
3
|
+
*
|
|
4
|
+
* Shows a warning when the user runs a command that requires
|
|
5
|
+
* a larger model than they're currently using.
|
|
6
|
+
*
|
|
7
|
+
* Offers options to:
|
|
8
|
+
* - Switch to a recommended model
|
|
9
|
+
* - Use a simpler alternative command
|
|
10
|
+
* - Continue anyway
|
|
11
|
+
* - Cancel
|
|
12
|
+
*/
|
|
13
|
+
import * as terminal from './terminal.js';
|
|
14
|
+
import { getStyles } from '../themes/index.js';
|
|
15
|
+
import { pauseForOverlay, resumeAfterOverlay } from './overlay-controller.js';
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Rendering
|
|
18
|
+
// =============================================================================
|
|
19
|
+
function render(options, selectedIndex, previousLineCount = 0, targetLineCount = 0) {
|
|
20
|
+
const s = getStyles();
|
|
21
|
+
const lines = [];
|
|
22
|
+
const cols = terminal.getTerminalWidth();
|
|
23
|
+
const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
|
|
24
|
+
// Clear previous render
|
|
25
|
+
if (previousLineCount > 0) {
|
|
26
|
+
terminal.clearLinesAbove(previousLineCount);
|
|
27
|
+
}
|
|
28
|
+
// Build options list
|
|
29
|
+
const choiceOptions = [];
|
|
30
|
+
if (options.suggestedModel) {
|
|
31
|
+
choiceOptions.push({
|
|
32
|
+
key: '1',
|
|
33
|
+
label: `Switch to ${options.suggestedModel} and continue`,
|
|
34
|
+
choice: 'switch',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (options.alternativeCommand) {
|
|
38
|
+
choiceOptions.push({
|
|
39
|
+
key: String(choiceOptions.length + 1),
|
|
40
|
+
label: `Use ${options.alternativeCommand} instead (simpler, works with any model)`,
|
|
41
|
+
choice: 'alternative',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
choiceOptions.push({
|
|
45
|
+
key: String(choiceOptions.length + 1),
|
|
46
|
+
label: 'Continue with current model anyway',
|
|
47
|
+
choice: 'continue',
|
|
48
|
+
});
|
|
49
|
+
// Header
|
|
50
|
+
lines.push(border);
|
|
51
|
+
lines.push(' ' + s.warning('⚠ Model Recommendation'));
|
|
52
|
+
lines.push('');
|
|
53
|
+
// Description
|
|
54
|
+
lines.push(` ${options.command} works best with larger models for complex`);
|
|
55
|
+
lines.push(' multi-question workflows.');
|
|
56
|
+
lines.push('');
|
|
57
|
+
// Current and recommended
|
|
58
|
+
lines.push(` Current: ${s.muted(options.currentModel)} (${options.currentTier} tier)`);
|
|
59
|
+
if (options.suggestedModel) {
|
|
60
|
+
lines.push(` Recommended: ${s.primary(options.suggestedModel)} (large tier)`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
// Separator
|
|
64
|
+
lines.push(border);
|
|
65
|
+
// Options
|
|
66
|
+
for (let i = 0; i < choiceOptions.length; i++) {
|
|
67
|
+
const opt = choiceOptions[i];
|
|
68
|
+
const isCursor = selectedIndex === i;
|
|
69
|
+
const prefix = isCursor ? ' ❯ ' : ' ';
|
|
70
|
+
if (isCursor) {
|
|
71
|
+
lines.push(s.primary(`${prefix}[${opt.key}] ${opt.label}`));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
lines.push(s.muted(`${prefix}[${opt.key}] ${opt.label}`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Footer
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(s.muted(' ↑↓ Navigate · Enter Select · Esc Cancel'));
|
|
80
|
+
lines.push(border);
|
|
81
|
+
// Pad with empty lines to maintain consistent height
|
|
82
|
+
while (lines.length < targetLineCount) {
|
|
83
|
+
lines.push('');
|
|
84
|
+
}
|
|
85
|
+
// Render all lines
|
|
86
|
+
terminal.write(lines.join('\n'));
|
|
87
|
+
return lines.length;
|
|
88
|
+
}
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// Main Export
|
|
91
|
+
// =============================================================================
|
|
92
|
+
/**
|
|
93
|
+
* Show the model warning overlay and return the user's choice.
|
|
94
|
+
*/
|
|
95
|
+
export async function showModelWarningOverlay(options) {
|
|
96
|
+
// Build choice list to track what each index maps to
|
|
97
|
+
const choices = [];
|
|
98
|
+
if (options.suggestedModel) {
|
|
99
|
+
choices.push('switch');
|
|
100
|
+
}
|
|
101
|
+
if (options.alternativeCommand) {
|
|
102
|
+
choices.push('alternative');
|
|
103
|
+
}
|
|
104
|
+
choices.push('continue');
|
|
105
|
+
let selectedIndex = 0;
|
|
106
|
+
let lineCount = 0;
|
|
107
|
+
let maxLineCount = 0;
|
|
108
|
+
// Pause footer animation
|
|
109
|
+
pauseForOverlay();
|
|
110
|
+
// Clear and prepare
|
|
111
|
+
terminal.clearToEndOfScreen();
|
|
112
|
+
terminal.writeLine('');
|
|
113
|
+
terminal.hideCursor();
|
|
114
|
+
const wasRawMode = process.stdin.isRaw;
|
|
115
|
+
terminal.enableRawMode();
|
|
116
|
+
// Initial render
|
|
117
|
+
lineCount = render(options, selectedIndex, 0);
|
|
118
|
+
maxLineCount = Math.max(maxLineCount, lineCount);
|
|
119
|
+
// Re-render to stabilize
|
|
120
|
+
lineCount = render(options, selectedIndex, lineCount, lineCount);
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const cleanup = () => {
|
|
123
|
+
terminal.clearLinesAbove(maxLineCount);
|
|
124
|
+
terminal.writeLine('');
|
|
125
|
+
terminal.showCursor();
|
|
126
|
+
if (!wasRawMode) {
|
|
127
|
+
terminal.disableRawMode();
|
|
128
|
+
}
|
|
129
|
+
process.stdin.removeListener('data', handleData);
|
|
130
|
+
resumeAfterOverlay();
|
|
131
|
+
};
|
|
132
|
+
const handleData = (data) => {
|
|
133
|
+
const isEscape = data.length === 1 && data[0] === 0x1b;
|
|
134
|
+
const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
|
|
135
|
+
const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
|
|
136
|
+
const isCtrlC = data.length === 1 && data[0] === 0x03;
|
|
137
|
+
const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
|
|
138
|
+
// Ctrl+C or Escape cancels
|
|
139
|
+
if (isCtrlC || isEscape) {
|
|
140
|
+
cleanup();
|
|
141
|
+
resolve('cancel');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Navigation
|
|
145
|
+
if (isUpArrow) {
|
|
146
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
147
|
+
}
|
|
148
|
+
else if (isDownArrow) {
|
|
149
|
+
selectedIndex = Math.min(choices.length - 1, selectedIndex + 1);
|
|
150
|
+
}
|
|
151
|
+
else if (isEnter) {
|
|
152
|
+
cleanup();
|
|
153
|
+
resolve(choices[selectedIndex]);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
else if (data.length === 1 && data[0] >= 0x31 && data[0] <= 0x39) {
|
|
157
|
+
// Number keys 1-9
|
|
158
|
+
const numIndex = data[0] - 0x31; // 0-indexed
|
|
159
|
+
if (numIndex < choices.length) {
|
|
160
|
+
cleanup();
|
|
161
|
+
resolve(choices[numIndex]);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Re-render
|
|
166
|
+
lineCount = render(options, selectedIndex, maxLineCount, maxLineCount);
|
|
167
|
+
maxLineCount = Math.max(maxLineCount, lineCount);
|
|
168
|
+
};
|
|
169
|
+
process.stdin.on('data', handleData);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay Controller
|
|
3
|
+
*
|
|
4
|
+
* Global controller for managing overlay display and footer coordination.
|
|
5
|
+
* Tools and overlays can use this to pause/resume the footer animation
|
|
6
|
+
* and take exclusive control of the terminal.
|
|
7
|
+
*/
|
|
8
|
+
type PauseCallback = () => void;
|
|
9
|
+
type ResumeCallback = () => void;
|
|
10
|
+
/**
|
|
11
|
+
* Register the footer pause/resume callbacks.
|
|
12
|
+
* Called once during REPL initialization.
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerFooterCallbacks(pause: PauseCallback, resume: ResumeCallback): void;
|
|
15
|
+
/**
|
|
16
|
+
* Pause footer animation and clear it for overlay use.
|
|
17
|
+
* Call this before displaying an overlay.
|
|
18
|
+
*/
|
|
19
|
+
export declare function pauseForOverlay(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Resume footer animation after overlay is done.
|
|
22
|
+
* Call this after the overlay closes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resumeAfterOverlay(): void;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay Controller
|
|
3
|
+
*
|
|
4
|
+
* Global controller for managing overlay display and footer coordination.
|
|
5
|
+
* Tools and overlays can use this to pause/resume the footer animation
|
|
6
|
+
* and take exclusive control of the terminal.
|
|
7
|
+
*/
|
|
8
|
+
let pauseFooter = null;
|
|
9
|
+
let resumeFooter = null;
|
|
10
|
+
/**
|
|
11
|
+
* Register the footer pause/resume callbacks.
|
|
12
|
+
* Called once during REPL initialization.
|
|
13
|
+
*/
|
|
14
|
+
export function registerFooterCallbacks(pause, resume) {
|
|
15
|
+
pauseFooter = pause;
|
|
16
|
+
resumeFooter = resume;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Pause footer animation and clear it for overlay use.
|
|
20
|
+
* Call this before displaying an overlay.
|
|
21
|
+
*/
|
|
22
|
+
export function pauseForOverlay() {
|
|
23
|
+
if (pauseFooter) {
|
|
24
|
+
pauseFooter();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resume footer animation after overlay is done.
|
|
29
|
+
* Call this after the overlay closes.
|
|
30
|
+
*/
|
|
31
|
+
export function resumeAfterOverlay() {
|
|
32
|
+
if (resumeFooter) {
|
|
33
|
+
resumeFooter();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlays
|
|
3
|
+
*
|
|
4
|
+
* Modal overlays that temporarily take over the screen.
|
|
5
|
+
* - Help menu
|
|
6
|
+
* - Confirmation dialogs
|
|
7
|
+
* - Permission prompts
|
|
8
|
+
* - Context stats
|
|
9
|
+
*/
|
|
10
|
+
import type { ContextStats, PermissionResult } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Show the tabbed help menu
|
|
13
|
+
*/
|
|
14
|
+
export declare function showHelp(): Promise<void>;
|
|
15
|
+
export interface ConfirmationOptions {
|
|
16
|
+
confirmText?: string;
|
|
17
|
+
cancelText?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Show a confirmation dialog
|
|
21
|
+
*/
|
|
22
|
+
export declare function showConfirmation(message: string, options?: ConfirmationOptions): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* Show permission prompt for tool execution
|
|
25
|
+
*/
|
|
26
|
+
export declare function showPermissionPrompt(tool: string, args: string): Promise<PermissionResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Show context window usage stats
|
|
29
|
+
*/
|
|
30
|
+
export declare function showContextStats(stats: ContextStats): void;
|
|
31
|
+
export interface TokenUsage {
|
|
32
|
+
inputTokens: number;
|
|
33
|
+
outputTokens: number;
|
|
34
|
+
totalTokens: number;
|
|
35
|
+
estimatedCost?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Show session token usage
|
|
39
|
+
*/
|
|
40
|
+
export declare function showTokenUsage(usage: TokenUsage): void;
|
|
41
|
+
/**
|
|
42
|
+
* Show available tools in compact column format
|
|
43
|
+
*/
|
|
44
|
+
export declare function showTools(tools: Array<{
|
|
45
|
+
name: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
}>): void;
|