caroushell 0.1.4 → 0.1.7
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/ai-suggester.js +7 -2
- package/dist/app.js +8 -4
- package/dist/carousel.js +2 -2
- package/dist/hello-new-user.js +4 -4
- package/dist/history-suggester.js +40 -8
- package/dist/test-generate.js +2 -0
- package/package.json +5 -3
package/dist/ai-suggester.js
CHANGED
|
@@ -41,6 +41,8 @@ async function generateContent(prompt, options) {
|
|
|
41
41
|
});
|
|
42
42
|
const res = await fetch(request.url, request.init);
|
|
43
43
|
if (!res.ok) {
|
|
44
|
+
const text = await res.text();
|
|
45
|
+
(0, logs_1.logLine)(`ai fetch error: ${res.statusText} (${text})`);
|
|
44
46
|
return "ai fetch error: " + res.statusText;
|
|
45
47
|
}
|
|
46
48
|
const out = await extractText(await res.json());
|
|
@@ -50,6 +52,7 @@ async function generateContent(prompt, options) {
|
|
|
50
52
|
return text;
|
|
51
53
|
}
|
|
52
54
|
catch (err) {
|
|
55
|
+
(0, logs_1.logLine)("ai caught error: " + err);
|
|
53
56
|
return "ai error: " + err.message;
|
|
54
57
|
}
|
|
55
58
|
}
|
|
@@ -75,8 +78,10 @@ function buildRequest(args) {
|
|
|
75
78
|
headers: headers(args.apiKey),
|
|
76
79
|
body: JSON.stringify({
|
|
77
80
|
model: args.model,
|
|
78
|
-
temperature
|
|
79
|
-
|
|
81
|
+
// OpenAI does not support temperature on some models
|
|
82
|
+
// temperature: args.temperature,
|
|
83
|
+
// max_tokens: args.maxOutputTokens,
|
|
84
|
+
// max_completion_tokens: args.maxOutputTokens,
|
|
80
85
|
messages: [
|
|
81
86
|
{
|
|
82
87
|
role: "system",
|
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
|
});
|
package/dist/hello-new-user.js
CHANGED
|
@@ -87,24 +87,24 @@ async function runHelloNewUserFlow(configPath) {
|
|
|
87
87
|
apiKey = answer;
|
|
88
88
|
}
|
|
89
89
|
else {
|
|
90
|
-
console.log("Please enter an API key
|
|
90
|
+
console.log("Please enter an API key. The value is stored in the local config file.");
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
const models = await (0, ai_suggester_1.listModels)(apiUrl, apiKey);
|
|
94
94
|
if (models.length > 0) {
|
|
95
|
-
console.log("Here are a few example model ids.");
|
|
95
|
+
console.log("Here are a few example model ids from your api service. Choose a fast and cheap model because AI suggestions happen as you type.");
|
|
96
96
|
for (const model of models.slice(0, 5)) {
|
|
97
97
|
console.log(` - ${model}`);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
let model = "";
|
|
101
101
|
while (!model) {
|
|
102
|
-
const answer = (await prompt("Model
|
|
102
|
+
const answer = (await prompt("Model id: ", rl)).trim();
|
|
103
103
|
if (answer) {
|
|
104
104
|
model = answer;
|
|
105
105
|
}
|
|
106
106
|
else {
|
|
107
|
-
console.log("Please enter a model
|
|
107
|
+
console.log("Please enter a model id (example: google/gemini-2.5-flash-lite, mistralai/mistral-small-24b-instruct-2501).");
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
rl.close();
|
|
@@ -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 =
|
|
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[
|
|
35
|
+
if (this.items[0] === command) {
|
|
36
36
|
// Deduplicate recent duplicate
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
this.items.
|
|
39
|
+
this.items.unshift(command);
|
|
40
40
|
if (this.items.length > this.maxItems)
|
|
41
|
-
this.items.
|
|
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.
|
|
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
|
-
|
|
50
|
+
// this.items 0 index is newest
|
|
51
|
+
return this.items;
|
|
51
52
|
}
|
|
52
53
|
const q = input.toLowerCase();
|
|
53
54
|
const matched = [];
|
|
54
|
-
|
|
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/dist/test-generate.js
CHANGED
|
@@ -7,7 +7,9 @@ async function main() {
|
|
|
7
7
|
if (!config.apiKey) {
|
|
8
8
|
console.warn("Warning: no API key configured. The answer may be empty.");
|
|
9
9
|
}
|
|
10
|
+
console.log("Using apiUrl:", config.apiUrl);
|
|
10
11
|
const models = await (0, ai_suggester_1.listModels)(config.apiUrl || "", config.apiKey || "");
|
|
12
|
+
models.sort();
|
|
11
13
|
console.log("Available models:", models);
|
|
12
14
|
const question = "What is the capital of France?";
|
|
13
15
|
const answer = await (0, ai_suggester_1.generateContent)(question, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caroushell",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|