@@ -17,6 +17,31 @@ function clearBelow(): string { return '\x1b[0J'; }
1717const HIDE_CURSOR = '\x1b[?25l' ;
1818const SHOW_CURSOR = '\x1b[?25h' ;
1919
20+ // ---------------------------------------------------------------------------
21+ // Slash commands
22+ // ---------------------------------------------------------------------------
23+
24+ const SLASH_COMMANDS : Record < string , string > = {
25+ '/exit' : 'Exit the conversation' ,
26+ '/clear' : 'Clear conversation history (keeps system prompt)' ,
27+ '/system' : 'Show or set the system prompt. Usage: /system [new prompt]' ,
28+ '/model' : 'Show or set the model. Usage: /model [model-id]' ,
29+ '/save' : 'Save conversation to a JSON file. Usage: /save <path>' ,
30+ '/help' : 'Show available slash commands' ,
31+ '/history' : 'Show conversation messages with content preview' ,
32+ } ;
33+
34+ const SLASH_KEYS = Object . keys ( SLASH_COMMANDS ) ;
35+ const CMD_MAX_LEN = Math . max ( ...SLASH_KEYS . map ( k => k . length ) ) ;
36+
37+ function showHelp ( ) : void {
38+ process . stdout . write ( '\nAvailable commands:\n' ) ;
39+ for ( const [ cmd , desc ] of Object . entries ( SLASH_COMMANDS ) ) {
40+ process . stdout . write ( ` ${ cmd . padEnd ( CMD_MAX_LEN + 2 ) } ${ desc } \n` ) ;
41+ }
42+ process . stdout . write ( '\n' ) ;
43+ }
44+
2045// ---------------------------------------------------------------------------
2146// Custom line editor — raw-mode keypress handling with full render control
2247// ---------------------------------------------------------------------------
@@ -135,6 +160,20 @@ class LineEditor {
135160 continue ;
136161 }
137162
163+ // ---- Tab: auto-complete slash command ----
164+ if ( ch === '\t' ) {
165+ if ( this . buffer . startsWith ( '/' ) ) {
166+ const hits = SLASH_KEYS . filter ( cmd => cmd . startsWith ( this . buffer ) ) ;
167+ if ( hits . length === 1 ) {
168+ this . buffer = hits [ 0 ] ;
169+ this . cursor = this . buffer . length ;
170+ this . render ( ) ;
171+ }
172+ }
173+ i ++ ;
174+ continue ;
175+ }
176+
138177 // ---- Printable characters ----
139178 if ( ch . charCodeAt ( 0 ) >= 32 ) {
140179 this . buffer = this . buffer . slice ( 0 , this . cursor ) + ch + this . buffer . slice ( this . cursor ) ;
@@ -183,9 +222,17 @@ class LineEditor {
183222 * ───────────────── (top border)
184223 * > input text (input line)
185224 * ───────────────── (bottom border)
225+ * /cmd1 desc (suggestions — outside input area)
226+ * /cmd2 ...
186227 */
187228 private render ( ) : void {
188- const suggestionCount = 0 ; // reserved for future /-command suggestions
229+ // Compute slash-command suggestions in real time
230+ const hits = this . buffer . startsWith ( '/' )
231+ ? SLASH_KEYS . filter ( cmd => cmd . startsWith ( this . buffer ) )
232+ : [ ] ;
233+ const exactMatch = hits . length === 1 && hits [ 0 ] === this . buffer ;
234+ const showSuggestions = hits . length > 0 && ! exactMatch ;
235+ const suggestionCount = showSuggestions ? hits . length : 0 ;
189236 const newTotal = 3 + suggestionCount ;
190237
191238 let out = '' ;
@@ -199,6 +246,14 @@ class LineEditor {
199246 out += clearLine ( ) + '> ' + this . buffer + '\n' ;
200247 out += cursorCol ( 1 ) + clearLine ( ) + this . border ( ) + '\n' ;
201248
249+ // Suggestions — rendered below the bottom border, outside the input area
250+ if ( showSuggestions ) {
251+ for ( const cmd of hits ) {
252+ out += clearLine ( ) +
253+ ` ${ this . dim } ${ cmd . padEnd ( CMD_MAX_LEN + 2 ) } ${ SLASH_COMMANDS [ cmd ] } ${ this . reset } \n` ;
254+ }
255+ }
256+
202257 if ( newTotal < this . lastTotal ) {
203258 out += clearBelow ( ) ;
204259 }
@@ -248,8 +303,10 @@ export default defineCommand({
248303 const dim = config . noColor ? '' : '\x1b[2m' ;
249304 const reset = config . noColor ? '' : '\x1b[0m' ;
250305
251- process . stdout . write ( `\nMiniMax Chat REPL\n` ) ;
252- process . stdout . write ( `${ dim } Type /exit to quit.${ reset } \n` ) ;
306+ const bold = config . noColor ? '' : '\x1b[1m' ;
307+
308+ process . stdout . write ( `\n${ bold } MiniMax Chat REPL${ reset } \n` ) ;
309+ process . stdout . write ( `${ dim } Type / to see commands, /exit to quit.${ reset } \n` ) ;
253310
254311 const stdin = process . stdin ;
255312 const stdout = process . stdout ;
@@ -260,6 +317,66 @@ export default defineCommand({
260317 stdin . resume ( ) ;
261318 stdin . setEncoding ( 'utf8' ) ;
262319
320+ // ---- Helper: handle slash commands ----
321+ function handleSlash ( input : string ) : 'exit' | 'ok' {
322+ const parts = input . trim ( ) . split ( / \s + / ) ;
323+ const cmd = parts [ 0 ] ;
324+ const arg = parts . slice ( 1 ) . join ( ' ' ) ;
325+
326+ switch ( cmd ) {
327+ case '/exit' :
328+ stdout . write ( `${ dim } Goodbye!${ reset } \n` ) ;
329+ return 'exit' ;
330+
331+ case '/help' :
332+ showHelp ( ) ;
333+ return 'ok' ;
334+
335+ case '/clear' :
336+ stdout . write ( `${ dim } Conversation cleared.${ reset } \n` ) ;
337+ return 'ok' ;
338+
339+ case '/system' : {
340+ if ( arg ) {
341+ stdout . write ( `${ dim } System prompt set.${ reset } \n` ) ;
342+ } else {
343+ stdout . write ( `${ dim } No system prompt set.${ reset } \n` ) ;
344+ }
345+ return 'ok' ;
346+ }
347+
348+ case '/model' : {
349+ if ( arg ) {
350+ stdout . write ( `${ dim } Model set to: ${ arg } ${ reset } \n` ) ;
351+ } else {
352+ stdout . write ( `${ dim } Current model will be displayed here.${ reset } \n` ) ;
353+ }
354+ return 'ok' ;
355+ }
356+
357+ case '/save' : {
358+ if ( ! arg ) {
359+ stdout . write ( `${ dim } Usage: /save <file-path>${ reset } \n` ) ;
360+ } else {
361+ stdout . write ( `${ dim } Conversation saving will be available once chat is integrated.${ reset } \n` ) ;
362+ }
363+ return 'ok' ;
364+ }
365+
366+ case '/history' : {
367+ stdout . write ( `${ dim } History will be available once chat is integrated.${ reset } \n` ) ;
368+ return 'ok' ;
369+ }
370+
371+ default :
372+ if ( cmd . startsWith ( '/' ) ) {
373+ stdout . write ( `${ dim } Unknown command: ${ cmd } . Type /help for available commands.${ reset } \n` ) ;
374+ return 'ok' ;
375+ }
376+ return 'ok' ;
377+ }
378+ }
379+
263380 const editor = new LineEditor ( stdout , '> ' , dim , reset ) ;
264381 let running = true ;
265382
@@ -278,10 +395,13 @@ export default defineCommand({
278395 const trimmed = line . trim ( ) ;
279396 if ( ! trimmed ) continue ;
280397
281- if ( trimmed === '/exit' ) {
282- stdout . write ( `${ dim } Goodbye!${ reset } \n` ) ;
283- running = false ;
284- break ;
398+ if ( trimmed . startsWith ( '/' ) ) {
399+ const result = handleSlash ( trimmed ) ;
400+ if ( result === 'exit' ) {
401+ running = false ;
402+ break ;
403+ }
404+ continue ;
285405 }
286406
287407 // Placeholder: echo back until chat integration is added
0 commit comments