@rennii/deepseek-cli 1.0.1 → 1.0.2

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/deepseek-cli.js CHANGED
@@ -174,6 +174,12 @@ function matchingCommands(prefix) {
174
174
  return COMMAND_SUGGESTIONS.filter(([command]) => command.startsWith(prefix.toLowerCase()));
175
175
  }
176
176
 
177
+ function selectedCommand(prefix, selected = 0) {
178
+ const matches = matchingCommands(prefix);
179
+ if (!matches.length) return null;
180
+ return matches[((selected % matches.length) + matches.length) % matches.length][0];
181
+ }
182
+
177
183
  function parseSseEvent(event, state, onDelta) {
178
184
  if (event.response_message_id != null) state.parentMessageId = event.response_message_id;
179
185
  const path = event.p;
@@ -355,20 +361,22 @@ class TerminalUI {
355
361
  ].join("\n"));
356
362
  }
357
363
 
358
- showCommandSuggestions(prefix) {
359
- const suggestions = matchingCommands(prefix).map(([command, description]) =>
360
- ` ${CYAN}${command.slice(0, prefix.length)}${RESET}${DIM}${command.slice(prefix.length)}${RESET} ${DIM}${description}${RESET}`,
364
+ showCommandSuggestions(prefix, selected = 0) {
365
+ const matches = matchingCommands(prefix);
366
+ const active = matches.length ? ((selected % matches.length) + matches.length) % matches.length : 0;
367
+ const suggestions = matches.map(([command, description], index) =>
368
+ `${index === active ? `${CYAN}›${RESET}` : " "} ${CYAN}${command.slice(0, prefix.length)}${RESET}${DIM}${command.slice(prefix.length)}${RESET} ${DIM}${description}${RESET}`,
361
369
  );
362
370
  this.write(`\x1b[s\n\x1b[J${suggestions.join("\n")}\x1b[u`);
363
371
  }
364
372
 
365
373
  clearCommandSuggestions() { this.write("\x1b[J"); }
366
374
 
367
- completeCommand(characters) {
375
+ completeCommand(characters, selected = 0) {
368
376
  const prefix = characters.join("");
369
- const matches = matchingCommands(prefix);
370
- if (matches.length !== 1 || matches[0][0] === prefix.toLowerCase()) return false;
371
- const suffix = matches[0][0].slice(prefix.length);
377
+ const command = selectedCommand(prefix, selected);
378
+ if (!command || command === prefix.toLowerCase()) return false;
379
+ const suffix = command.slice(prefix.length);
372
380
  characters.push(...suffix);
373
381
  this.write(`${DIM}${suffix}${RESET}`);
374
382
  this.clearCommandSuggestions();
@@ -385,6 +393,7 @@ class TerminalUI {
385
393
  const reader = new RawReader();
386
394
  const characters = [];
387
395
  let pending = null;
396
+ let selectedSuggestion = 0;
388
397
  reader.open();
389
398
  try {
390
399
  while (true) {
@@ -394,7 +403,7 @@ class TerminalUI {
394
403
  const character = chunk[index];
395
404
  if (character === "\u0003") throw new UserInterrupted();
396
405
  if (character === "\r" || character === "\n") {
397
- if (characters[0] === "/" && !characters.includes("\n") && this.completeCommand(characters)) continue;
406
+ if (characters[0] === "/" && !characters.includes("\n") && this.completeCommand(characters, selectedSuggestion)) continue;
398
407
  if (characters[0] === "/") this.clearCommandSuggestions();
399
408
  if (index === chunk.length - 1) {
400
409
  const next = await reader.next(120);
@@ -413,18 +422,31 @@ class TerminalUI {
413
422
  characters.pop();
414
423
  this.write("\b \b");
415
424
  }
416
- if (characters[0] === "/" && !characters.includes("\n")) this.showCommandSuggestions(characters.join(""));
425
+ selectedSuggestion = 0;
426
+ if (characters[0] === "/" && !characters.includes("\n")) this.showCommandSuggestions(characters.join(""), selectedSuggestion);
417
427
  else if (!characters.length) this.clearCommandSuggestions();
418
428
  continue;
419
429
  }
420
430
  if (character === "\u001b") {
431
+ const sequence = chunk.slice(index, index + 3);
432
+ const direction = sequence === "\u001b[A" ? -1 : sequence === "\u001b[B" ? 1 : 0;
433
+ if (direction && characters[0] === "/" && !characters.includes("\n")) {
434
+ const matches = matchingCommands(characters.join(""));
435
+ if (matches.length) {
436
+ selectedSuggestion = (selectedSuggestion + direction + matches.length) % matches.length;
437
+ this.showCommandSuggestions(characters.join(""), selectedSuggestion);
438
+ }
439
+ index += 2;
440
+ continue;
441
+ }
421
442
  while (index + 1 < chunk.length && !/[A-Za-z~]/.test(chunk[index + 1])) index += 1;
422
443
  continue;
423
444
  }
424
445
  if (character >= " ") {
425
446
  characters.push(character);
426
447
  this.write(character);
427
- if (characters[0] === "/" && !characters.includes("\n")) this.showCommandSuggestions(characters.join(""));
448
+ selectedSuggestion = 0;
449
+ if (characters[0] === "/" && !characters.includes("\n")) this.showCommandSuggestions(characters.join(""), selectedSuggestion);
428
450
  }
429
451
  }
430
452
  }
@@ -818,7 +840,7 @@ async function main(argv = process.argv.slice(2)) {
818
840
  await cli.run();
819
841
  }
820
842
 
821
- module.exports = { DATA_DIR, DeepSeekCLI, RUNTIME_DATA, SessionStore, bootstrapDataDirectory, extractCommands, formatCommandFeedback, main, matchingCommands, newLocalSession, parseSseEvent, sessionPickerLine };
843
+ module.exports = { DATA_DIR, DeepSeekCLI, RUNTIME_DATA, SessionStore, bootstrapDataDirectory, extractCommands, formatCommandFeedback, main, matchingCommands, newLocalSession, parseSseEvent, selectedCommand, sessionPickerLine };
822
844
 
823
845
  if (require.main === module) {
824
846
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rennii/deepseek-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "DeepSeek terminal client for Termux",
6
6
  "type": "commonjs",
@@ -14,6 +14,7 @@ const {
14
14
  matchingCommands,
15
15
  newLocalSession,
16
16
  parseSseEvent,
17
+ selectedCommand,
17
18
  sessionPickerLine,
18
19
  } = require("./deepseek-cli");
19
20
 
@@ -21,6 +22,12 @@ test("filters slash commands by typed prefix", () => {
21
22
  assert.deepEqual(matchingCommands("/res").map(([command]) => command), ["/resume"]);
22
23
  });
23
24
 
25
+ test("selects a slash-command suggestion by arrow position", () => {
26
+ assert.equal(selectedCommand("/", 0), "/login");
27
+ assert.equal(selectedCommand("/", 1), "/logout");
28
+ assert.equal(selectedCommand("/res", 0), "/resume");
29
+ });
30
+
24
31
  test("login opens the official DeepSeek page", async () => {
25
32
  const directory = mkdtempSync(join(tmpdir(), "deepseek-cli-"));
26
33
  try {