archicore 0.2.2 → 0.2.4
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/dist/cli/commands/interactive.js +190 -109
- package/dist/cli/ui/autocomplete.d.ts +29 -0
- package/dist/cli/ui/autocomplete.js +250 -0
- package/dist/cli/ui/colors.d.ts +3 -0
- package/dist/cli/ui/colors.js +21 -17
- package/dist/cli/ui/index.d.ts +1 -0
- package/dist/cli/ui/index.js +1 -0
- package/package.json +8 -2
|
@@ -29,51 +29,6 @@ const COMMANDS = [
|
|
|
29
29
|
{ name: 'logout', aliases: [], description: 'Log out from ArchiCore' },
|
|
30
30
|
{ name: 'exit', aliases: ['quit', 'q'], description: 'Exit ArchiCore CLI' },
|
|
31
31
|
];
|
|
32
|
-
// Get all command names and aliases for autocomplete
|
|
33
|
-
function getAllCommandNames() {
|
|
34
|
-
const names = [];
|
|
35
|
-
for (const cmd of COMMANDS) {
|
|
36
|
-
names.push('/' + cmd.name);
|
|
37
|
-
for (const alias of cmd.aliases) {
|
|
38
|
-
names.push('/' + alias);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return names.sort();
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Tab autocomplete function for readline
|
|
45
|
-
* Works on Linux, Windows, and macOS
|
|
46
|
-
*/
|
|
47
|
-
function completer(line) {
|
|
48
|
-
// If line starts with /, autocomplete commands
|
|
49
|
-
if (line.startsWith('/')) {
|
|
50
|
-
const allCommands = getAllCommandNames();
|
|
51
|
-
const hits = allCommands.filter((cmd) => cmd.startsWith(line));
|
|
52
|
-
// If exact match or no matches, return the line as-is
|
|
53
|
-
if (hits.length === 0) {
|
|
54
|
-
return [allCommands, line];
|
|
55
|
-
}
|
|
56
|
-
// Show matching commands with descriptions
|
|
57
|
-
if (hits.length > 1) {
|
|
58
|
-
console.log();
|
|
59
|
-
for (const hit of hits) {
|
|
60
|
-
const cmdName = hit.slice(1); // Remove leading /
|
|
61
|
-
const cmd = COMMANDS.find(c => c.name === cmdName || c.aliases.includes(cmdName));
|
|
62
|
-
if (cmd) {
|
|
63
|
-
const desc = colors.dim(cmd.description);
|
|
64
|
-
console.log(` ${colors.primary(hit.padEnd(18))} ${desc}`);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
console.log(` ${colors.primary(hit)}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
console.log();
|
|
71
|
-
}
|
|
72
|
-
return [hits, line];
|
|
73
|
-
}
|
|
74
|
-
// For non-command input, no autocomplete
|
|
75
|
-
return [[], line];
|
|
76
|
-
}
|
|
77
32
|
const state = {
|
|
78
33
|
running: true,
|
|
79
34
|
projectId: null,
|
|
@@ -177,31 +132,67 @@ export async function startInteractiveMode() {
|
|
|
177
132
|
console.log(colors.muted(` Project: ${colors.primary(state.projectName)}`));
|
|
178
133
|
}
|
|
179
134
|
console.log();
|
|
180
|
-
console.log(colors.muted(' Type /
|
|
181
|
-
console.log(colors.muted(' Press Tab to autocomplete
|
|
135
|
+
console.log(colors.muted(' Type / to see commands, or ask a question about your code.'));
|
|
136
|
+
console.log(colors.muted(' Press Tab to autocomplete. Type ? for shortcuts.'));
|
|
182
137
|
console.log();
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const prompt = () => {
|
|
193
|
-
const prefix = state.projectName
|
|
138
|
+
// Interactive input with real-time autocomplete
|
|
139
|
+
await startInteractiveInput(state);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Start interactive input loop with real-time command suggestions
|
|
143
|
+
*/
|
|
144
|
+
async function startInteractiveInput(state) {
|
|
145
|
+
const getPromptPrefix = () => {
|
|
146
|
+
return state.projectName
|
|
194
147
|
? colors.muted(`[${state.projectName}] `)
|
|
195
148
|
: '';
|
|
196
|
-
process.stdout.write(`${prefix}${colors.primary(icons.arrow)} `);
|
|
197
149
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
150
|
+
const promptStr = () => `${getPromptPrefix()}${colors.primary(icons.arrow)} `;
|
|
151
|
+
// State for current input
|
|
152
|
+
let currentInput = '';
|
|
153
|
+
let menuVisible = false;
|
|
154
|
+
let selectedIndex = 0;
|
|
155
|
+
let filteredCommands = [];
|
|
156
|
+
// Filter commands based on input
|
|
157
|
+
const filterCommands = (input) => {
|
|
158
|
+
if (!input.startsWith('/'))
|
|
159
|
+
return [];
|
|
160
|
+
const query = input.slice(1).toLowerCase();
|
|
161
|
+
if (!query)
|
|
162
|
+
return COMMANDS.slice(0, 10);
|
|
163
|
+
return COMMANDS.filter(cmd => cmd.name.toLowerCase().startsWith(query) ||
|
|
164
|
+
cmd.aliases.some(a => a.toLowerCase().startsWith(query))).slice(0, 10);
|
|
165
|
+
};
|
|
166
|
+
// Render the menu
|
|
167
|
+
const renderMenu = () => {
|
|
168
|
+
if (filteredCommands.length === 0)
|
|
203
169
|
return;
|
|
170
|
+
console.log();
|
|
171
|
+
for (let i = 0; i < filteredCommands.length; i++) {
|
|
172
|
+
const cmd = filteredCommands[i];
|
|
173
|
+
const isSelected = i === selectedIndex;
|
|
174
|
+
const nameStr = `/${cmd.name}`.padEnd(18);
|
|
175
|
+
if (isSelected) {
|
|
176
|
+
console.log(` ${colors.highlight(nameStr)} ${colors.muted(cmd.description)}`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.log(` ${colors.primary(nameStr)} ${colors.dim(cmd.description)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
console.log(colors.dim(' ↑↓ select • Tab complete • Enter run'));
|
|
183
|
+
};
|
|
184
|
+
// Clear menu lines
|
|
185
|
+
const clearMenu = (lines) => {
|
|
186
|
+
for (let i = 0; i < lines; i++) {
|
|
187
|
+
readline.moveCursor(process.stdout, 0, -1);
|
|
188
|
+
readline.clearLine(process.stdout, 0);
|
|
204
189
|
}
|
|
190
|
+
};
|
|
191
|
+
// Process submitted input
|
|
192
|
+
const processInput = async (input) => {
|
|
193
|
+
const trimmed = input.trim();
|
|
194
|
+
if (!trimmed)
|
|
195
|
+
return;
|
|
205
196
|
state.history.push(trimmed);
|
|
206
197
|
try {
|
|
207
198
|
await handleInput(trimmed);
|
|
@@ -209,57 +200,147 @@ export async function startInteractiveMode() {
|
|
|
209
200
|
catch (error) {
|
|
210
201
|
printError(String(error));
|
|
211
202
|
}
|
|
212
|
-
|
|
203
|
+
};
|
|
204
|
+
// Use raw mode for real-time input
|
|
205
|
+
if (process.stdin.isTTY) {
|
|
206
|
+
readline.emitKeypressEvents(process.stdin);
|
|
207
|
+
process.stdin.setRawMode(true);
|
|
208
|
+
}
|
|
209
|
+
process.stdout.write(promptStr());
|
|
210
|
+
const handleKeypress = async (str, key) => {
|
|
211
|
+
// Handle Ctrl+C
|
|
212
|
+
if (key.ctrl && key.name === 'c') {
|
|
213
|
+
if (menuVisible) {
|
|
214
|
+
clearMenu(filteredCommands.length + 2);
|
|
215
|
+
menuVisible = false;
|
|
216
|
+
}
|
|
213
217
|
console.log();
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
rl.close();
|
|
218
|
+
state.running = false;
|
|
219
|
+
printGoodbye();
|
|
220
|
+
process.exit(0);
|
|
218
221
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
// Handle Enter
|
|
223
|
+
if (key.name === 'return') {
|
|
224
|
+
if (menuVisible && filteredCommands.length > 0) {
|
|
225
|
+
// Select from menu
|
|
226
|
+
clearMenu(filteredCommands.length + 2);
|
|
227
|
+
menuVisible = false;
|
|
228
|
+
const selected = filteredCommands[selectedIndex];
|
|
229
|
+
currentInput = '/' + selected.name;
|
|
230
|
+
readline.clearLine(process.stdout, 0);
|
|
231
|
+
readline.cursorTo(process.stdout, 0);
|
|
232
|
+
process.stdout.write(promptStr() + currentInput);
|
|
233
|
+
}
|
|
234
|
+
// Submit
|
|
235
|
+
if (menuVisible) {
|
|
236
|
+
clearMenu(filteredCommands.length + 2);
|
|
237
|
+
menuVisible = false;
|
|
238
|
+
}
|
|
229
239
|
console.log();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
240
|
+
await processInput(currentInput);
|
|
241
|
+
currentInput = '';
|
|
242
|
+
selectedIndex = 0;
|
|
243
|
+
filteredCommands = [];
|
|
244
|
+
if (state.running) {
|
|
245
|
+
console.log();
|
|
246
|
+
process.stdout.write(promptStr());
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
process.stdin.setRawMode(false);
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Handle Tab - autocomplete
|
|
255
|
+
if (key.name === 'tab') {
|
|
256
|
+
if (filteredCommands.length > 0) {
|
|
257
|
+
if (menuVisible) {
|
|
258
|
+
clearMenu(filteredCommands.length + 2);
|
|
248
259
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
260
|
+
const selected = filteredCommands[selectedIndex];
|
|
261
|
+
currentInput = '/' + selected.name + ' ';
|
|
262
|
+
menuVisible = false;
|
|
263
|
+
filteredCommands = [];
|
|
264
|
+
readline.clearLine(process.stdout, 0);
|
|
265
|
+
readline.cursorTo(process.stdout, 0);
|
|
266
|
+
process.stdout.write(promptStr() + currentInput);
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
252
269
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
270
|
+
// Handle Escape - close menu
|
|
271
|
+
if (key.name === 'escape') {
|
|
272
|
+
if (menuVisible) {
|
|
273
|
+
clearMenu(filteredCommands.length + 2);
|
|
274
|
+
menuVisible = false;
|
|
275
|
+
filteredCommands = [];
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
256
278
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
279
|
+
// Handle Up arrow
|
|
280
|
+
if (key.name === 'up') {
|
|
281
|
+
if (menuVisible && selectedIndex > 0) {
|
|
282
|
+
clearMenu(filteredCommands.length + 2);
|
|
283
|
+
selectedIndex--;
|
|
284
|
+
renderMenu();
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
// Handle Down arrow
|
|
289
|
+
if (key.name === 'down') {
|
|
290
|
+
if (menuVisible && selectedIndex < filteredCommands.length - 1) {
|
|
291
|
+
clearMenu(filteredCommands.length + 2);
|
|
292
|
+
selectedIndex++;
|
|
293
|
+
renderMenu();
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
// Handle Backspace
|
|
298
|
+
if (key.name === 'backspace') {
|
|
299
|
+
if (currentInput.length > 0) {
|
|
300
|
+
if (menuVisible) {
|
|
301
|
+
clearMenu(filteredCommands.length + 2);
|
|
302
|
+
}
|
|
303
|
+
currentInput = currentInput.slice(0, -1);
|
|
304
|
+
readline.clearLine(process.stdout, 0);
|
|
305
|
+
readline.cursorTo(process.stdout, 0);
|
|
306
|
+
process.stdout.write(promptStr() + currentInput);
|
|
307
|
+
// Update menu
|
|
308
|
+
filteredCommands = filterCommands(currentInput);
|
|
309
|
+
selectedIndex = 0;
|
|
310
|
+
menuVisible = currentInput.startsWith('/') && filteredCommands.length > 0;
|
|
311
|
+
if (menuVisible) {
|
|
312
|
+
renderMenu();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// Regular character input
|
|
318
|
+
if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
|
319
|
+
if (menuVisible) {
|
|
320
|
+
clearMenu(filteredCommands.length + 2);
|
|
321
|
+
}
|
|
322
|
+
currentInput += str;
|
|
323
|
+
readline.clearLine(process.stdout, 0);
|
|
324
|
+
readline.cursorTo(process.stdout, 0);
|
|
325
|
+
process.stdout.write(promptStr() + currentInput);
|
|
326
|
+
// Show menu when typing /
|
|
327
|
+
filteredCommands = filterCommands(currentInput);
|
|
328
|
+
selectedIndex = 0;
|
|
329
|
+
menuVisible = currentInput.startsWith('/') && filteredCommands.length > 0;
|
|
330
|
+
if (menuVisible) {
|
|
331
|
+
renderMenu();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
process.stdin.on('keypress', handleKeypress);
|
|
336
|
+
// Keep process alive
|
|
337
|
+
await new Promise((resolve) => {
|
|
338
|
+
const checkRunning = setInterval(() => {
|
|
339
|
+
if (!state.running) {
|
|
340
|
+
clearInterval(checkRunning);
|
|
341
|
+
resolve();
|
|
342
|
+
}
|
|
343
|
+
}, 100);
|
|
263
344
|
});
|
|
264
345
|
}
|
|
265
346
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Autocomplete Input for ArchiCore CLI
|
|
3
|
+
*
|
|
4
|
+
* Shows command suggestions in real-time as user types,
|
|
5
|
+
* similar to Claude's terminal interface.
|
|
6
|
+
*
|
|
7
|
+
* Works on Linux, Windows, and macOS.
|
|
8
|
+
*/
|
|
9
|
+
import * as readline from 'readline';
|
|
10
|
+
export interface CommandItem {
|
|
11
|
+
name: string;
|
|
12
|
+
aliases: string[];
|
|
13
|
+
description: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AutocompleteOptions {
|
|
16
|
+
commands: CommandItem[];
|
|
17
|
+
prompt?: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create an interactive input with autocomplete
|
|
22
|
+
*/
|
|
23
|
+
export declare function createAutocompleteInput(options: AutocompleteOptions): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Simple readline-based input with inline suggestions
|
|
26
|
+
* More compatible fallback for environments where raw mode doesn't work well
|
|
27
|
+
*/
|
|
28
|
+
export declare function createSimpleAutocomplete(rl: readline.Interface, commands: CommandItem[], prompt: string): void;
|
|
29
|
+
//# sourceMappingURL=autocomplete.d.ts.map
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Autocomplete Input for ArchiCore CLI
|
|
3
|
+
*
|
|
4
|
+
* Shows command suggestions in real-time as user types,
|
|
5
|
+
* similar to Claude's terminal interface.
|
|
6
|
+
*
|
|
7
|
+
* Works on Linux, Windows, and macOS.
|
|
8
|
+
*/
|
|
9
|
+
import * as readline from 'readline';
|
|
10
|
+
import { colors } from './colors.js';
|
|
11
|
+
/**
|
|
12
|
+
* Create an interactive input with autocomplete
|
|
13
|
+
*/
|
|
14
|
+
export function createAutocompleteInput(options) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const { commands, prompt = '> ' } = options;
|
|
17
|
+
const state = {
|
|
18
|
+
input: '',
|
|
19
|
+
cursorPos: 0,
|
|
20
|
+
selectedIndex: 0,
|
|
21
|
+
filteredCommands: [],
|
|
22
|
+
showMenu: false,
|
|
23
|
+
};
|
|
24
|
+
// Filter commands based on input
|
|
25
|
+
const filterCommands = (input) => {
|
|
26
|
+
if (!input.startsWith('/'))
|
|
27
|
+
return [];
|
|
28
|
+
const query = input.slice(1).toLowerCase();
|
|
29
|
+
if (!query) {
|
|
30
|
+
return commands.slice(0, 8); // Show first 8 commands
|
|
31
|
+
}
|
|
32
|
+
return commands.filter(cmd => cmd.name.toLowerCase().startsWith(query) ||
|
|
33
|
+
cmd.aliases.some(a => a.toLowerCase().startsWith(query))).slice(0, 8);
|
|
34
|
+
};
|
|
35
|
+
// Render the current state
|
|
36
|
+
const render = () => {
|
|
37
|
+
// Clear previous output
|
|
38
|
+
readline.clearLine(process.stdout, 0);
|
|
39
|
+
readline.cursorTo(process.stdout, 0);
|
|
40
|
+
// Render prompt and input
|
|
41
|
+
const displayPrompt = colors.primary(prompt);
|
|
42
|
+
process.stdout.write(displayPrompt + state.input);
|
|
43
|
+
// If showing menu, render it below
|
|
44
|
+
if (state.showMenu && state.filteredCommands.length > 0) {
|
|
45
|
+
console.log(); // New line for menu
|
|
46
|
+
for (let i = 0; i < state.filteredCommands.length; i++) {
|
|
47
|
+
const cmd = state.filteredCommands[i];
|
|
48
|
+
const isSelected = i === state.selectedIndex;
|
|
49
|
+
const cmdName = `/${cmd.name}`.padEnd(18);
|
|
50
|
+
const desc = cmd.description;
|
|
51
|
+
if (isSelected) {
|
|
52
|
+
console.log(` ${colors.menuItemSelected(cmdName)} ${colors.muted(desc)}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(` ${colors.menuItem(cmdName)} ${colors.menuDescription(desc)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Hint at bottom
|
|
59
|
+
console.log(colors.dim(' ↑↓ navigate • Tab/Enter select • Esc cancel'));
|
|
60
|
+
// Move cursor back to input line
|
|
61
|
+
const menuLines = state.filteredCommands.length + 2;
|
|
62
|
+
readline.moveCursor(process.stdout, 0, -menuLines);
|
|
63
|
+
readline.cursorTo(process.stdout, prompt.length + state.cursorPos);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
// Clear the menu from display
|
|
67
|
+
const clearMenu = () => {
|
|
68
|
+
if (state.showMenu && state.filteredCommands.length > 0) {
|
|
69
|
+
const menuLines = state.filteredCommands.length + 2;
|
|
70
|
+
for (let i = 0; i < menuLines; i++) {
|
|
71
|
+
readline.moveCursor(process.stdout, 0, 1);
|
|
72
|
+
readline.clearLine(process.stdout, 0);
|
|
73
|
+
}
|
|
74
|
+
readline.moveCursor(process.stdout, 0, -menuLines);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
// Update state based on input
|
|
78
|
+
const updateState = () => {
|
|
79
|
+
state.filteredCommands = filterCommands(state.input);
|
|
80
|
+
state.showMenu = state.input.startsWith('/') && state.filteredCommands.length > 0;
|
|
81
|
+
state.selectedIndex = Math.min(state.selectedIndex, Math.max(0, state.filteredCommands.length - 1));
|
|
82
|
+
};
|
|
83
|
+
// Handle selection
|
|
84
|
+
const selectItem = () => {
|
|
85
|
+
if (state.filteredCommands.length > 0 && state.selectedIndex < state.filteredCommands.length) {
|
|
86
|
+
const selected = state.filteredCommands[state.selectedIndex];
|
|
87
|
+
state.input = '/' + selected.name + ' ';
|
|
88
|
+
state.cursorPos = state.input.length;
|
|
89
|
+
state.showMenu = false;
|
|
90
|
+
state.filteredCommands = [];
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
// Set up raw mode for keystroke capture
|
|
94
|
+
if (process.stdin.isTTY) {
|
|
95
|
+
process.stdin.setRawMode(true);
|
|
96
|
+
}
|
|
97
|
+
process.stdin.resume();
|
|
98
|
+
process.stdin.setEncoding('utf8');
|
|
99
|
+
// Initial render
|
|
100
|
+
process.stdout.write(colors.primary(prompt));
|
|
101
|
+
const handleKeypress = (key) => {
|
|
102
|
+
const code = key.charCodeAt(0);
|
|
103
|
+
// Ctrl+C - exit
|
|
104
|
+
if (key === '\x03') {
|
|
105
|
+
clearMenu();
|
|
106
|
+
console.log();
|
|
107
|
+
process.stdin.setRawMode(false);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
// Enter - submit or select
|
|
111
|
+
if (key === '\r' || key === '\n') {
|
|
112
|
+
if (state.showMenu && state.filteredCommands.length > 0) {
|
|
113
|
+
clearMenu();
|
|
114
|
+
selectItem();
|
|
115
|
+
render();
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
clearMenu();
|
|
119
|
+
console.log();
|
|
120
|
+
process.stdin.setRawMode(false);
|
|
121
|
+
process.stdin.removeListener('data', handleKeypress);
|
|
122
|
+
resolve(state.input);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Tab - autocomplete
|
|
127
|
+
if (key === '\t') {
|
|
128
|
+
if (state.showMenu && state.filteredCommands.length > 0) {
|
|
129
|
+
clearMenu();
|
|
130
|
+
selectItem();
|
|
131
|
+
render();
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Escape - close menu or clear input
|
|
136
|
+
if (key === '\x1b' || key === '\x1b\x1b') {
|
|
137
|
+
if (state.showMenu) {
|
|
138
|
+
clearMenu();
|
|
139
|
+
state.showMenu = false;
|
|
140
|
+
render();
|
|
141
|
+
}
|
|
142
|
+
else if (state.input) {
|
|
143
|
+
clearMenu();
|
|
144
|
+
state.input = '';
|
|
145
|
+
state.cursorPos = 0;
|
|
146
|
+
updateState();
|
|
147
|
+
render();
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Arrow keys (escape sequences)
|
|
152
|
+
if (key.startsWith('\x1b[')) {
|
|
153
|
+
const seq = key.slice(2);
|
|
154
|
+
// Up arrow
|
|
155
|
+
if (seq === 'A') {
|
|
156
|
+
if (state.showMenu && state.selectedIndex > 0) {
|
|
157
|
+
clearMenu();
|
|
158
|
+
state.selectedIndex--;
|
|
159
|
+
render();
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Down arrow
|
|
164
|
+
if (seq === 'B') {
|
|
165
|
+
if (state.showMenu && state.selectedIndex < state.filteredCommands.length - 1) {
|
|
166
|
+
clearMenu();
|
|
167
|
+
state.selectedIndex++;
|
|
168
|
+
render();
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Left arrow
|
|
173
|
+
if (seq === 'D') {
|
|
174
|
+
if (state.cursorPos > 0) {
|
|
175
|
+
state.cursorPos--;
|
|
176
|
+
readline.cursorTo(process.stdout, prompt.length + state.cursorPos);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Right arrow
|
|
181
|
+
if (seq === 'C') {
|
|
182
|
+
if (state.cursorPos < state.input.length) {
|
|
183
|
+
state.cursorPos++;
|
|
184
|
+
readline.cursorTo(process.stdout, prompt.length + state.cursorPos);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Backspace
|
|
191
|
+
if (key === '\x7f' || key === '\b') {
|
|
192
|
+
if (state.cursorPos > 0) {
|
|
193
|
+
clearMenu();
|
|
194
|
+
state.input = state.input.slice(0, state.cursorPos - 1) + state.input.slice(state.cursorPos);
|
|
195
|
+
state.cursorPos--;
|
|
196
|
+
updateState();
|
|
197
|
+
render();
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Regular character
|
|
202
|
+
if (code >= 32 && code < 127) {
|
|
203
|
+
clearMenu();
|
|
204
|
+
state.input = state.input.slice(0, state.cursorPos) + key + state.input.slice(state.cursorPos);
|
|
205
|
+
state.cursorPos++;
|
|
206
|
+
state.selectedIndex = 0; // Reset selection on new input
|
|
207
|
+
updateState();
|
|
208
|
+
render();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
process.stdin.on('data', handleKeypress);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Simple readline-based input with inline suggestions
|
|
217
|
+
* More compatible fallback for environments where raw mode doesn't work well
|
|
218
|
+
*/
|
|
219
|
+
export function createSimpleAutocomplete(rl, commands, prompt) {
|
|
220
|
+
// This is used as the completer function for readline
|
|
221
|
+
const completer = (line) => {
|
|
222
|
+
if (line.startsWith('/')) {
|
|
223
|
+
const allCommands = [];
|
|
224
|
+
for (const cmd of commands) {
|
|
225
|
+
allCommands.push('/' + cmd.name);
|
|
226
|
+
for (const alias of cmd.aliases) {
|
|
227
|
+
allCommands.push('/' + alias);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const hits = allCommands.filter(c => c.startsWith(line));
|
|
231
|
+
if (hits.length > 1) {
|
|
232
|
+
console.log();
|
|
233
|
+
for (const hit of hits) {
|
|
234
|
+
const cmdName = hit.slice(1);
|
|
235
|
+
const cmd = commands.find(c => c.name === cmdName || c.aliases.includes(cmdName));
|
|
236
|
+
if (cmd) {
|
|
237
|
+
console.log(` ${colors.menuItem(hit.padEnd(18))} ${colors.menuDescription(cmd.description)}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
console.log();
|
|
241
|
+
process.stdout.write(prompt + line);
|
|
242
|
+
}
|
|
243
|
+
return [hits, line];
|
|
244
|
+
}
|
|
245
|
+
return [[], line];
|
|
246
|
+
};
|
|
247
|
+
// Return the completer function
|
|
248
|
+
rl._completer = completer;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=autocomplete.js.map
|
package/dist/cli/ui/colors.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ export declare const colors: {
|
|
|
19
19
|
brand: import("chalk").ChalkInstance;
|
|
20
20
|
link: import("chalk").ChalkInstance;
|
|
21
21
|
code: import("chalk").ChalkInstance;
|
|
22
|
+
menuItem: import("chalk").ChalkInstance;
|
|
23
|
+
menuItemSelected: import("chalk").ChalkInstance;
|
|
24
|
+
menuDescription: import("chalk").ChalkInstance;
|
|
22
25
|
};
|
|
23
26
|
export declare const icons: {
|
|
24
27
|
success: string;
|
package/dist/cli/ui/colors.js
CHANGED
|
@@ -3,28 +3,32 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
export const colors = {
|
|
6
|
-
// Primary colors
|
|
7
|
-
primary: chalk.hex('#
|
|
8
|
-
secondary: chalk.hex('#
|
|
9
|
-
accent: chalk.hex('#
|
|
6
|
+
// Primary colors - ArchiCore brand (sky blue from logo #0ea5e9)
|
|
7
|
+
primary: chalk.hex('#0ea5e9'), // Sky Blue (logo color)
|
|
8
|
+
secondary: chalk.hex('#38bdf8'), // Lighter Sky Blue
|
|
9
|
+
accent: chalk.hex('#7dd3fc'), // Even lighter for accents
|
|
10
10
|
// Status colors
|
|
11
|
-
success: chalk.hex('#
|
|
12
|
-
warning: chalk.hex('#
|
|
13
|
-
error: chalk.hex('#
|
|
14
|
-
info: chalk.hex('#
|
|
11
|
+
success: chalk.hex('#22c55e'), // Green
|
|
12
|
+
warning: chalk.hex('#f59e0b'), // Amber
|
|
13
|
+
error: chalk.hex('#ef4444'), // Red
|
|
14
|
+
info: chalk.hex('#0ea5e9'), // Sky Blue (same as primary)
|
|
15
15
|
// Text colors
|
|
16
|
-
dim: chalk.
|
|
17
|
-
muted: chalk.hex('#
|
|
16
|
+
dim: chalk.hex('#64748b'), // Slate
|
|
17
|
+
muted: chalk.hex('#94a3b8'), // Light slate
|
|
18
18
|
highlight: chalk.bold.white,
|
|
19
19
|
// Impact levels
|
|
20
|
-
critical: chalk.hex('#
|
|
21
|
-
high: chalk.hex('#
|
|
22
|
-
medium: chalk.hex('#
|
|
23
|
-
low: chalk.hex('#
|
|
20
|
+
critical: chalk.hex('#dc2626'), // Red
|
|
21
|
+
high: chalk.hex('#ea580c'), // Orange
|
|
22
|
+
medium: chalk.hex('#eab308'), // Yellow
|
|
23
|
+
low: chalk.hex('#22c55e'), // Green
|
|
24
24
|
// Special
|
|
25
|
-
brand: chalk.hex('#
|
|
26
|
-
link: chalk.hex('#
|
|
27
|
-
code: chalk.hex('#
|
|
25
|
+
brand: chalk.hex('#0ea5e9').bold, // Sky Blue bold
|
|
26
|
+
link: chalk.hex('#38bdf8').underline,
|
|
27
|
+
code: chalk.hex('#7dd3fc'), // Light sky blue
|
|
28
|
+
// Menu colors (for autocomplete)
|
|
29
|
+
menuItem: chalk.hex('#0ea5e9'),
|
|
30
|
+
menuItemSelected: chalk.hex('#0ea5e9').bold.inverse,
|
|
31
|
+
menuDescription: chalk.hex('#64748b'),
|
|
28
32
|
};
|
|
29
33
|
export const icons = {
|
|
30
34
|
// Status
|
package/dist/cli/ui/index.d.ts
CHANGED
package/dist/cli/ui/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archicore",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "AI Software Architect - code analysis, impact prediction, semantic search",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc",
|
|
21
|
+
"build:frontend": "node scripts/build-frontend.js",
|
|
22
|
+
"build:all": "npm run build && npm run build:frontend",
|
|
21
23
|
"dev": "tsx watch src/index.ts",
|
|
22
24
|
"start": "node dist/index.js",
|
|
23
25
|
"server": "tsx src/server/index.ts",
|
|
@@ -93,7 +95,11 @@
|
|
|
93
95
|
"@types/multer": "^2.0.0",
|
|
94
96
|
"@types/node": "^22.10.2",
|
|
95
97
|
"@types/pg": "^8.11.6",
|
|
98
|
+
"clean-css": "^5.3.3",
|
|
99
|
+
"html-minifier-terser": "^7.2.0",
|
|
100
|
+
"javascript-obfuscator": "^4.1.1",
|
|
96
101
|
"tsx": "^4.19.2",
|
|
97
|
-
"typescript": "^5.7.2"
|
|
102
|
+
"typescript": "^5.7.2",
|
|
103
|
+
"vite": "^5.4.11"
|
|
98
104
|
}
|
|
99
105
|
}
|