caroushell 0.1.4 → 0.1.6

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/app.js CHANGED
@@ -67,7 +67,13 @@ class App {
67
67
  const cmd = this.carousel.getCurrentRow().trim();
68
68
  this.carousel.setInputBuffer("", 0);
69
69
  await this.runCommand(cmd);
70
+ // Carousel should point to the prompt
70
71
  this.carousel.resetIndex();
72
+ // After arbitrary output, reset render block tracking
73
+ this.terminal.resetBlockTracking();
74
+ // Render the prompt, without this we'd wait for the suggestions to call render
75
+ // and it would appear slow
76
+ this.render();
71
77
  this.queueUpdateSuggestions();
72
78
  },
73
79
  char: (evt) => {
@@ -129,7 +135,6 @@ class App {
129
135
  await fn(evt);
130
136
  }
131
137
  }
132
- queueUpdateSuggestions() { }
133
138
  render() {
134
139
  this.carousel.render();
135
140
  // Cursor placement handled inside carousel render.
@@ -140,7 +145,6 @@ class App {
140
145
  // Log an empty line
141
146
  this.terminal.renderBlock([">"]);
142
147
  this.terminal.write("\n");
143
- this.terminal.resetBlockTracking();
144
148
  return;
145
149
  }
146
150
  // Log command in yellow
@@ -159,10 +163,10 @@ class App {
159
163
  finally {
160
164
  this.keyboard.resume();
161
165
  }
162
- // After arbitrary output, reset render block tracking
163
- this.terminal.resetBlockTracking();
164
166
  }
165
167
  exit() {
168
+ // Clear terminal contents before shutting down to leave a clean screen.
169
+ this.terminal.renderBlock([]);
166
170
  this.keyboard.stop();
167
171
  process.exit(0);
168
172
  }
package/dist/carousel.js CHANGED
@@ -25,11 +25,11 @@ class Carousel {
25
25
  }
26
26
  const topPromise = this.top.suggest(this, this.topRowCount);
27
27
  const bottomPromise = this.bottom.suggest(this, this.bottomRowCount);
28
- topPromise.then((r) => {
28
+ void topPromise.then((r) => {
29
29
  this.latestTop = r;
30
30
  this.render();
31
31
  });
32
- bottomPromise.then((r) => {
32
+ void bottomPromise.then((r) => {
33
33
  this.latestBottom = r;
34
34
  this.render();
35
35
  });
@@ -23,7 +23,7 @@ class HistorySuggester {
23
23
  catch { }
24
24
  try {
25
25
  const data = await fs_1.promises.readFile(this.filePath, "utf8");
26
- this.items = data.split(/\r?\n/).filter(Boolean);
26
+ this.items = this.parseHistory(data);
27
27
  }
28
28
  catch {
29
29
  this.items = [];
@@ -32,29 +32,34 @@ class HistorySuggester {
32
32
  async add(command) {
33
33
  if (!command.trim())
34
34
  return;
35
- if (this.items[this.items.length - 1] === command) {
35
+ if (this.items[0] === command) {
36
36
  // Deduplicate recent duplicate
37
37
  return;
38
38
  }
39
- this.items.push(command);
39
+ this.items.unshift(command);
40
40
  if (this.items.length > this.maxItems)
41
- this.items.shift();
41
+ this.items.pop();
42
42
  await fs_1.promises
43
43
  .mkdir(path_1.default.dirname(this.filePath), { recursive: true })
44
44
  .catch(() => { });
45
- await fs_1.promises.writeFile(this.filePath, this.items.join("\n"), "utf8");
45
+ await fs_1.promises.appendFile(this.filePath, this.serializeHistoryEntry(command), "utf8");
46
46
  }
47
47
  async suggest(carousel, maxDisplayed) {
48
48
  const input = carousel.getCurrentRow();
49
49
  if (!input) {
50
- return this.items.reverse();
50
+ // this.items 0 index is newest
51
+ return this.items;
51
52
  }
52
53
  const q = input.toLowerCase();
53
54
  const matched = [];
54
- for (let i = this.items.length - 1; i >= 0; i--) {
55
+ // iterate from newest to oldest so we skip older duplicates
56
+ const seen = new Set();
57
+ for (let i = 0; i < this.items.length; i++) {
55
58
  const it = this.items[i];
56
- if (it.toLowerCase().includes(q))
59
+ if (it.toLowerCase().includes(q) && !seen.has(it)) {
60
+ seen.add(it);
57
61
  matched.push(it);
62
+ }
58
63
  }
59
64
  return matched;
60
65
  }
@@ -75,5 +80,32 @@ class HistorySuggester {
75
80
  }
76
81
  return lines.join("\n");
77
82
  }
83
+ parseHistory(data) {
84
+ const entries = [];
85
+ let currentLines = [];
86
+ const flush = () => {
87
+ if (currentLines.length > 0) {
88
+ entries.push(currentLines.join("\n"));
89
+ currentLines = [];
90
+ }
91
+ };
92
+ const rows = data.split(/\n/);
93
+ for (const rawLine of rows) {
94
+ const line = rawLine.replace(/\r$/, "");
95
+ if (line.startsWith("+")) {
96
+ currentLines.push(line.slice(1));
97
+ }
98
+ else {
99
+ flush();
100
+ }
101
+ }
102
+ flush();
103
+ return entries.slice(-this.maxItems).reverse();
104
+ }
105
+ serializeHistoryEntry(command) {
106
+ const timestamp = new Date().toISOString();
107
+ const lines = command.split("\n").map((line) => `+${line}`);
108
+ return `\n# ${timestamp}\n${lines.join("\n")}\n`;
109
+ }
78
110
  }
79
111
  exports.HistorySuggester = HistorySuggester;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caroushell",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Terminal carousel that suggests commands from history, config, and AI.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/main.js",
@@ -14,10 +14,12 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "dev": "tsx src/main.ts",
17
- "build": "tsc -p tsconfig.json",
17
+ "build": "tsc -p tsconfig.release.json",
18
18
  "prepare": "npm run build",
19
19
  "start": "node dist/main.js",
20
- "test:generate": "tsx src/test-generate.ts"
20
+ "test:generate": "tsx src/test-generate.ts",
21
+ "lint": "eslint . --ext .ts",
22
+ "release": "tsx scripts/release.ts"
21
23
  },
22
24
  "keywords": [
23
25
  "cli",