caroushell 0.1.29 → 0.1.31
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 +5 -1
- package/dist/carousel.js +16 -4
- package/dist/config.js +0 -33
- package/dist/hello-new-user.js +27 -7
- package/dist/keyboard.js +39 -39
- package/dist/main.js +2 -1
- package/dist/prompt.js +74 -0
- package/dist/terminal.js +17 -37
- package/package.json +4 -2
package/dist/app.js
CHANGED
|
@@ -19,7 +19,11 @@ class App {
|
|
|
19
19
|
this.history = deps.topPanel ?? new history_suggester_1.HistorySuggester();
|
|
20
20
|
this.bottomSuggester = deps.bottomPanel ?? new carousel_1.NullSuggester();
|
|
21
21
|
this.files = deps.files ?? new file_suggester_1.FileSuggester();
|
|
22
|
-
this.suggesters = deps.suggesters ?? [
|
|
22
|
+
this.suggesters = deps.suggesters ?? [
|
|
23
|
+
this.history,
|
|
24
|
+
this.bottomSuggester,
|
|
25
|
+
this.files,
|
|
26
|
+
];
|
|
23
27
|
this.carousel = new carousel_1.Carousel({
|
|
24
28
|
top: this.history,
|
|
25
29
|
bottom: this.bottomSuggester,
|
package/dist/carousel.js
CHANGED
|
@@ -63,8 +63,12 @@ class NullSuggester {
|
|
|
63
63
|
}
|
|
64
64
|
async init() { }
|
|
65
65
|
async refreshSuggestions() { }
|
|
66
|
-
latest() {
|
|
67
|
-
|
|
66
|
+
latest() {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
descriptionForAi() {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
exports.NullSuggester = NullSuggester;
|
|
70
74
|
class Carousel {
|
|
@@ -149,10 +153,18 @@ class Carousel {
|
|
|
149
153
|
return prefix;
|
|
150
154
|
}
|
|
151
155
|
getFormattedPromptRow(lineIndex, lineText, promptSelected) {
|
|
152
|
-
const { reset, dim } = terminal_1.colors;
|
|
156
|
+
const { reset, dimmest, dim } = terminal_1.colors;
|
|
153
157
|
const color = promptSelected ? terminal_1.colors.purple : dim;
|
|
158
|
+
const separatorColor = promptSelected ? dim : dimmest;
|
|
154
159
|
const prefix = this.getPromptPrefix(lineIndex);
|
|
155
|
-
|
|
160
|
+
// Color the separators differently from the prompt prefix
|
|
161
|
+
const formattedPrefix = prefix
|
|
162
|
+
.split("")
|
|
163
|
+
.map((char) => char === ":" || char === ">"
|
|
164
|
+
? `${separatorColor}${char}${color}`
|
|
165
|
+
: char)
|
|
166
|
+
.join("");
|
|
167
|
+
return `${color}${formattedPrefix}${lineText}${reset}`;
|
|
156
168
|
}
|
|
157
169
|
getCurrentRow() {
|
|
158
170
|
return this.getRow(this.index);
|
package/dist/config.js
CHANGED
|
@@ -40,7 +40,6 @@ exports.configFolder = configFolder;
|
|
|
40
40
|
exports.getConfigPath = getConfigPath;
|
|
41
41
|
exports.doesConfigExist = doesConfigExist;
|
|
42
42
|
exports.getConfig = getConfig;
|
|
43
|
-
exports.buildPromptLine0 = buildPromptLine0;
|
|
44
43
|
const fs_1 = require("fs");
|
|
45
44
|
const path = __importStar(require("path"));
|
|
46
45
|
const os = __importStar(require("os"));
|
|
@@ -114,35 +113,3 @@ function isGeminiUrl(url) {
|
|
|
114
113
|
return (lower.includes("generativelanguage.googleapis.com") ||
|
|
115
114
|
lower.includes("gemini"));
|
|
116
115
|
}
|
|
117
|
-
function normalizeCwd(cwd) {
|
|
118
|
-
const normalized = cwd.replace(/\\/g, "/");
|
|
119
|
-
const home = os.homedir().replace(/\\/g, "/");
|
|
120
|
-
if (normalized === home)
|
|
121
|
-
return "~";
|
|
122
|
-
if (normalized.startsWith(home + "/")) {
|
|
123
|
-
return "~" + normalized.slice(home.length);
|
|
124
|
-
}
|
|
125
|
-
return normalized;
|
|
126
|
-
}
|
|
127
|
-
function shortenPath(p) {
|
|
128
|
-
const parts = p.split("/");
|
|
129
|
-
return parts
|
|
130
|
-
.map((part, i) => {
|
|
131
|
-
if (i === parts.length - 1)
|
|
132
|
-
return part;
|
|
133
|
-
if (part === "" || part === "~")
|
|
134
|
-
return part;
|
|
135
|
-
return part[0];
|
|
136
|
-
})
|
|
137
|
-
.join("/");
|
|
138
|
-
}
|
|
139
|
-
function buildPromptLine0(config) {
|
|
140
|
-
return () => {
|
|
141
|
-
const normalized = normalizeCwd(process.cwd());
|
|
142
|
-
const template = config.prompt ?? "$> ";
|
|
143
|
-
return template
|
|
144
|
-
.replace(/\{hostname\}/g, os.hostname())
|
|
145
|
-
.replace(/\{directory\}/g, normalized)
|
|
146
|
-
.replace(/\{short-directory\}/g, shortenPath(normalized));
|
|
147
|
-
};
|
|
148
|
-
}
|
package/dist/hello-new-user.js
CHANGED
|
@@ -43,6 +43,16 @@ const readline_1 = __importDefault(require("readline"));
|
|
|
43
43
|
const ai_suggester_1 = require("./ai-suggester");
|
|
44
44
|
const preferredModels = ["gemini-2.5-flash-lite", "gpt-4o-mini"];
|
|
45
45
|
const defaultPromptTemplate = "$> ";
|
|
46
|
+
const promptPresets = [
|
|
47
|
+
{ key: "1", label: "Minimal", template: defaultPromptTemplate },
|
|
48
|
+
{ key: "2", label: "Hostname", template: "{hostname} > " },
|
|
49
|
+
{
|
|
50
|
+
key: "3",
|
|
51
|
+
label: "Hostname + short path",
|
|
52
|
+
template: "{hostname}:{short-directory} > ",
|
|
53
|
+
},
|
|
54
|
+
{ key: "4", label: "Path", template: "{directory} > " },
|
|
55
|
+
];
|
|
46
56
|
function serializeToml(obj) {
|
|
47
57
|
const lines = [];
|
|
48
58
|
const sections = [];
|
|
@@ -65,18 +75,28 @@ function serializeToml(obj) {
|
|
|
65
75
|
return lines.join("\n");
|
|
66
76
|
}
|
|
67
77
|
async function askPromptConfig(prompter, logFn) {
|
|
68
|
-
logFn("
|
|
69
|
-
|
|
78
|
+
logFn("Choose a shell prompt style.");
|
|
79
|
+
for (const preset of promptPresets) {
|
|
80
|
+
logFn(` ${preset.key}. ${preset.label}: ${preset.template}`);
|
|
81
|
+
}
|
|
82
|
+
logFn("");
|
|
83
|
+
logFn("You can customize this later in the config file with these tokens:");
|
|
70
84
|
logFn(" {hostname}");
|
|
85
|
+
logFn(" {user}");
|
|
71
86
|
logFn(" {directory}");
|
|
72
87
|
logFn(" {short-directory}");
|
|
73
88
|
logFn(`Press Enter to keep the default prompt: ${defaultPromptTemplate}`);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
while (true) {
|
|
90
|
+
const answer = (await prompter.ask("Prompt style [1-4]: ")).trim();
|
|
91
|
+
if (!answer || answer === "1") {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const preset = promptPresets.find((candidate) => candidate.key === answer);
|
|
95
|
+
if (preset) {
|
|
96
|
+
return preset.template;
|
|
97
|
+
}
|
|
98
|
+
logFn("Choose 1, 2, 3, or 4.");
|
|
78
99
|
}
|
|
79
|
-
return answer;
|
|
80
100
|
}
|
|
81
101
|
function isInteractive() {
|
|
82
102
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
package/dist/keyboard.js
CHANGED
|
@@ -6,41 +6,41 @@ const events_1 = require("events");
|
|
|
6
6
|
// Map semantic key names to escape/control sequences
|
|
7
7
|
const KEY_DEFINITIONS = {
|
|
8
8
|
// Control keys
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
tab: [{ sequence:
|
|
13
|
-
enter: [{ sequence:
|
|
14
|
-
backspace: [{ sequence:
|
|
15
|
-
escape: [{ sequence:
|
|
9
|
+
"ctrl-c": [{ sequence: "\u0003", ctrl: true }], // ^C
|
|
10
|
+
"ctrl-d": [{ sequence: "\u0004", ctrl: true }], // ^D
|
|
11
|
+
"ctrl-u": [{ sequence: "\u0015", ctrl: true }], // ^U
|
|
12
|
+
tab: [{ sequence: "\t" }],
|
|
13
|
+
enter: [{ sequence: "\r" }, { sequence: "\n" }],
|
|
14
|
+
backspace: [{ sequence: "\u007f" }, { sequence: "\u0008" }], // DEL, BS (Windows)
|
|
15
|
+
escape: [{ sequence: "\u001b" }],
|
|
16
16
|
// Arrows (ANSI)
|
|
17
|
-
up: [{ sequence:
|
|
18
|
-
down: [{ sequence:
|
|
19
|
-
right: [{ sequence:
|
|
20
|
-
left: [{ sequence:
|
|
21
|
-
|
|
22
|
-
{ sequence:
|
|
23
|
-
{ sequence:
|
|
17
|
+
up: [{ sequence: "\u001b[A" }],
|
|
18
|
+
down: [{ sequence: "\u001b[B" }],
|
|
19
|
+
right: [{ sequence: "\u001b[C" }],
|
|
20
|
+
left: [{ sequence: "\u001b[D" }],
|
|
21
|
+
"ctrl-right": [
|
|
22
|
+
{ sequence: "\u001b[1;5C", ctrl: true },
|
|
23
|
+
{ sequence: "\u001b[5C", ctrl: true },
|
|
24
24
|
// Option/Alt-based word jumps (macOS/iTerm send meta-modified arrows or ESC+b/f)
|
|
25
|
-
{ sequence:
|
|
26
|
-
{ sequence:
|
|
27
|
-
{ sequence:
|
|
25
|
+
{ sequence: "\u001b[1;3C", meta: true },
|
|
26
|
+
{ sequence: "\u001b[1;9C", meta: true },
|
|
27
|
+
{ sequence: "\u001bf", meta: true },
|
|
28
28
|
],
|
|
29
|
-
|
|
30
|
-
{ sequence:
|
|
31
|
-
{ sequence:
|
|
29
|
+
"ctrl-left": [
|
|
30
|
+
{ sequence: "\u001b[1;5D", ctrl: true },
|
|
31
|
+
{ sequence: "\u001b[5D", ctrl: true },
|
|
32
32
|
// Option/Alt-based word jumps (macOS/iTerm send meta-modified arrows or ESC+b/f)
|
|
33
|
-
{ sequence:
|
|
34
|
-
{ sequence:
|
|
35
|
-
{ sequence:
|
|
33
|
+
{ sequence: "\u001b[1;3D", meta: true },
|
|
34
|
+
{ sequence: "\u001b[1;9D", meta: true },
|
|
35
|
+
{ sequence: "\u001bb", meta: true },
|
|
36
36
|
],
|
|
37
37
|
// Home/End/Delete variants
|
|
38
|
-
home: [{ sequence:
|
|
39
|
-
end: [{ sequence:
|
|
40
|
-
delete: [{ sequence:
|
|
38
|
+
home: [{ sequence: "\u001b[H" }, { sequence: "\u001b[1~" }],
|
|
39
|
+
end: [{ sequence: "\u001b[F" }, { sequence: "\u001b[4~" }],
|
|
40
|
+
delete: [{ sequence: "\u001b[3~" }],
|
|
41
41
|
// Focus in/out (sent by some terminals on focus change - swallow these)
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
"focus-in": [{ sequence: "\u001b[I" }],
|
|
43
|
+
"focus-out": [{ sequence: "\u001b[O" }],
|
|
44
44
|
};
|
|
45
45
|
exports.KEY_SEQUENCES = Object.fromEntries(Object.entries(KEY_DEFINITIONS).map(([name, defs]) => [
|
|
46
46
|
name,
|
|
@@ -72,28 +72,28 @@ class Keyboard extends events_1.EventEmitter {
|
|
|
72
72
|
constructor(stdin = process.stdin) {
|
|
73
73
|
super();
|
|
74
74
|
this.capturing = false;
|
|
75
|
-
this.buffer =
|
|
75
|
+
this.buffer = "";
|
|
76
76
|
this.onData = (data) => this.handleData(data);
|
|
77
77
|
this.stdin = stdin;
|
|
78
78
|
}
|
|
79
79
|
enableCapture() {
|
|
80
80
|
if (this.capturing)
|
|
81
81
|
return;
|
|
82
|
-
this.stdin.setEncoding(
|
|
82
|
+
this.stdin.setEncoding("utf8");
|
|
83
83
|
if (this.stdin.isTTY)
|
|
84
84
|
this.stdin.setRawMode(true);
|
|
85
|
-
this.stdin.on(
|
|
85
|
+
this.stdin.on("data", this.onData);
|
|
86
86
|
this.stdin.resume();
|
|
87
87
|
this.capturing = true;
|
|
88
88
|
}
|
|
89
89
|
disableCapture() {
|
|
90
90
|
if (!this.capturing)
|
|
91
91
|
return;
|
|
92
|
-
this.stdin.off(
|
|
92
|
+
this.stdin.off("data", this.onData);
|
|
93
93
|
if (this.stdin.isTTY)
|
|
94
94
|
this.stdin.setRawMode(false);
|
|
95
95
|
this.stdin.pause();
|
|
96
|
-
this.buffer =
|
|
96
|
+
this.buffer = "";
|
|
97
97
|
this.capturing = false;
|
|
98
98
|
}
|
|
99
99
|
handleData(data) {
|
|
@@ -104,27 +104,27 @@ class Keyboard extends events_1.EventEmitter {
|
|
|
104
104
|
// Try to consume as many full key sequences as possible
|
|
105
105
|
while (this.buffer.length > 0) {
|
|
106
106
|
const evt = this.matchSequence(this.buffer);
|
|
107
|
-
if (evt ===
|
|
107
|
+
if (evt === "need-more")
|
|
108
108
|
return; // wait for more bytes
|
|
109
109
|
if (evt) {
|
|
110
110
|
// Swallow focus in/out sequences so they don't show up as visible chars
|
|
111
|
-
if (evt.name ===
|
|
111
|
+
if (evt.name === "focus-in" || evt.name === "focus-out") {
|
|
112
112
|
this.buffer = this.buffer.slice(evt.sequence.length);
|
|
113
113
|
continue;
|
|
114
114
|
}
|
|
115
|
-
this.emit(
|
|
115
|
+
this.emit("key", evt);
|
|
116
116
|
this.buffer = this.buffer.slice(evt.sequence.length);
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
119
119
|
// No mapped sequence at buffer start; emit first char as 'char'
|
|
120
120
|
const ch = this.buffer[0];
|
|
121
121
|
const code = ch.charCodeAt(0);
|
|
122
|
-
if (code < 32 && ch !==
|
|
122
|
+
if (code < 32 && ch !== "\t") {
|
|
123
123
|
// ignore other control chars
|
|
124
124
|
this.buffer = this.buffer.slice(1);
|
|
125
125
|
continue;
|
|
126
126
|
}
|
|
127
|
-
this.emit(
|
|
127
|
+
this.emit("key", { name: "char", sequence: ch });
|
|
128
128
|
this.buffer = this.buffer.slice(1);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -149,7 +149,7 @@ class Keyboard extends events_1.EventEmitter {
|
|
|
149
149
|
return matched;
|
|
150
150
|
// If current buffer is a prefix to any known sequence, wait for more
|
|
151
151
|
if (KEY_PREFIXES.has(buf))
|
|
152
|
-
return
|
|
152
|
+
return "need-more";
|
|
153
153
|
// No sequence match
|
|
154
154
|
return null;
|
|
155
155
|
}
|
package/dist/main.js
CHANGED
|
@@ -9,6 +9,7 @@ const carousel_1 = require("./carousel");
|
|
|
9
9
|
const hello_new_user_1 = require("./hello-new-user");
|
|
10
10
|
const logs_1 = require("./logs");
|
|
11
11
|
const config_1 = require("./config");
|
|
12
|
+
const prompt_1 = require("./prompt");
|
|
12
13
|
function shouldPrintVersion() {
|
|
13
14
|
return process.argv.includes("--version");
|
|
14
15
|
}
|
|
@@ -31,7 +32,7 @@ async function main() {
|
|
|
31
32
|
const bottomPanel = config.apiUrl && config.apiKey && config.model
|
|
32
33
|
? new ai_suggester_1.AISuggester()
|
|
33
34
|
: new carousel_1.NullSuggester();
|
|
34
|
-
const app = new app_1.App({ bottomPanel, promptLine0: (0,
|
|
35
|
+
const app = new app_1.App({ bottomPanel, promptLine0: (0, prompt_1.buildPromptLine0)(config) });
|
|
35
36
|
await app.run();
|
|
36
37
|
}
|
|
37
38
|
main().catch((err) => {
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildPromptLine0 = buildPromptLine0;
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
function normalizeCwd(cwd) {
|
|
39
|
+
const normalized = cwd.replace(/\\/g, "/");
|
|
40
|
+
const home = os.homedir().replace(/\\/g, "/");
|
|
41
|
+
if (normalized === home)
|
|
42
|
+
return "~";
|
|
43
|
+
if (normalized.startsWith(home + "/")) {
|
|
44
|
+
return "~" + normalized.slice(home.length);
|
|
45
|
+
}
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
function shortenPath(p) {
|
|
49
|
+
const parts = p.split("/");
|
|
50
|
+
return parts
|
|
51
|
+
.map((part, i) => {
|
|
52
|
+
if (i === parts.length - 1)
|
|
53
|
+
return part;
|
|
54
|
+
if (part === "" || part === "~")
|
|
55
|
+
return part;
|
|
56
|
+
return part[0];
|
|
57
|
+
})
|
|
58
|
+
.join("/");
|
|
59
|
+
}
|
|
60
|
+
function getCurrentUsername() {
|
|
61
|
+
return process.env.USERNAME || process.env.USER || os.userInfo().username;
|
|
62
|
+
}
|
|
63
|
+
function buildPromptLine0(config) {
|
|
64
|
+
return () => {
|
|
65
|
+
const normalized = normalizeCwd(process.cwd());
|
|
66
|
+
const username = getCurrentUsername();
|
|
67
|
+
const template = config.prompt ?? "$> ";
|
|
68
|
+
return template
|
|
69
|
+
.replace(/\{hostname\}/g, os.hostname())
|
|
70
|
+
.replace(/\{directory\}/g, normalized)
|
|
71
|
+
.replace(/\{short-directory\}/g, shortenPath(normalized))
|
|
72
|
+
.replace(/\{user\}/g, username);
|
|
73
|
+
};
|
|
74
|
+
}
|
package/dist/terminal.js
CHANGED
|
@@ -22,8 +22,6 @@ class Terminal {
|
|
|
22
22
|
this.cursorRow = 0;
|
|
23
23
|
this.cursorCol = 0;
|
|
24
24
|
this.writesDisabled = false;
|
|
25
|
-
this.pendingBlock = null;
|
|
26
|
-
this.renderScheduled = false;
|
|
27
25
|
}
|
|
28
26
|
disableWrites() {
|
|
29
27
|
this.writesDisabled = true;
|
|
@@ -72,63 +70,47 @@ class Terminal {
|
|
|
72
70
|
write(text) {
|
|
73
71
|
if (!this.canWrite())
|
|
74
72
|
return;
|
|
75
|
-
this.flushPendingRender();
|
|
76
73
|
this.out.write(text);
|
|
77
74
|
}
|
|
78
75
|
hideCursor() {
|
|
79
|
-
this.
|
|
76
|
+
if (!this.canWrite())
|
|
77
|
+
return;
|
|
78
|
+
this.out.write("\x1b[?25l");
|
|
80
79
|
}
|
|
81
80
|
showCursor() {
|
|
82
|
-
this.write("\x1b[?25h");
|
|
83
|
-
}
|
|
84
|
-
// Render a block of lines by clearing previous block (if any) and writing fresh
|
|
85
|
-
renderBlock(lines, cursorRow, cursorCol) {
|
|
86
81
|
if (!this.canWrite())
|
|
87
82
|
return;
|
|
88
|
-
this.
|
|
89
|
-
lines: [...lines],
|
|
90
|
-
cursorRow,
|
|
91
|
-
cursorCol,
|
|
92
|
-
};
|
|
93
|
-
if (this.renderScheduled)
|
|
94
|
-
return;
|
|
95
|
-
this.renderScheduled = true;
|
|
96
|
-
queueMicrotask(() => this.flushPendingRender());
|
|
83
|
+
this.out.write("\x1b[?25h");
|
|
97
84
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.renderScheduled = false;
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const pending = this.pendingBlock;
|
|
104
|
-
this.pendingBlock = null;
|
|
105
|
-
this.renderScheduled = false;
|
|
85
|
+
// Render a block of lines by clearing previous block (if any) and writing fresh
|
|
86
|
+
renderBlock(lines, cursorRow, cursorCol) {
|
|
106
87
|
if (!this.canWrite())
|
|
107
88
|
return;
|
|
108
89
|
this.withCork(() => {
|
|
90
|
+
this.hideCursor();
|
|
109
91
|
this.moveCursorToTopOfBlock();
|
|
110
92
|
if (this.activeRows > 0) {
|
|
111
93
|
readline_1.default.cursorTo(this.out, 0);
|
|
112
94
|
readline_1.default.clearScreenDown(this.out);
|
|
113
95
|
}
|
|
114
|
-
for (let i = 0; i <
|
|
115
|
-
this.out.write(
|
|
116
|
-
if (i <
|
|
96
|
+
for (let i = 0; i < lines.length; i++) {
|
|
97
|
+
this.out.write(lines[i]);
|
|
98
|
+
if (i < lines.length - 1)
|
|
117
99
|
this.out.write("\n");
|
|
118
100
|
}
|
|
119
|
-
this.activeRows =
|
|
101
|
+
this.activeRows = lines.length;
|
|
120
102
|
this.cursorRow = Math.max(0, this.activeRows - 1);
|
|
121
|
-
const lastLine =
|
|
103
|
+
const lastLine = lines[this.cursorRow] || "";
|
|
122
104
|
this.cursorCol = lastLine.length;
|
|
123
|
-
const needsPosition = typeof
|
|
124
|
-
typeof pending.cursorCol === "number";
|
|
105
|
+
const needsPosition = typeof cursorRow === "number" || typeof cursorCol === "number";
|
|
125
106
|
if (needsPosition) {
|
|
126
|
-
const targetRow = typeof
|
|
127
|
-
? Math.min(Math.max(
|
|
107
|
+
const targetRow = typeof cursorRow === "number"
|
|
108
|
+
? Math.min(Math.max(cursorRow, 0), Math.max(0, this.activeRows - 1))
|
|
128
109
|
: this.cursorRow;
|
|
129
|
-
const targetCol = Math.max(0,
|
|
110
|
+
const targetCol = Math.max(0, cursorCol ?? this.cursorCol);
|
|
130
111
|
this.moveCursorTo(targetRow, targetCol);
|
|
131
112
|
}
|
|
113
|
+
this.showCursor();
|
|
132
114
|
});
|
|
133
115
|
}
|
|
134
116
|
moveCursorTo(lineIndex, column) {
|
|
@@ -149,8 +131,6 @@ class Terminal {
|
|
|
149
131
|
// When we have printed arbitrary output that is not managed by renderBlock,
|
|
150
132
|
// reset internal line tracking so the next render starts fresh.
|
|
151
133
|
resetBlockTracking() {
|
|
152
|
-
this.pendingBlock = null;
|
|
153
|
-
this.renderScheduled = false;
|
|
154
134
|
this.activeRows = 0;
|
|
155
135
|
this.cursorRow = 0;
|
|
156
136
|
this.cursorCol = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caroushell",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Terminal carousel that suggests commands from history, config, and AI.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"prepare": "npm run build",
|
|
19
19
|
"start": "node dist/main.js",
|
|
20
20
|
"test": "node --import tsx --test tests/*.ts",
|
|
21
|
-
"test:generate": "tsx src/test-generate.ts",
|
|
21
|
+
"test:ai-generate": "tsx src/test-generate.ts",
|
|
22
|
+
"format": "prettier . --write",
|
|
22
23
|
"lint": "eslint . --ext .ts",
|
|
23
24
|
"release": "tsx scripts/release.ts"
|
|
24
25
|
},
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
|
48
49
|
"@typescript-eslint/parser": "^8.46.4",
|
|
49
50
|
"eslint": "^9.39.1",
|
|
51
|
+
"prettier": "^3.8.1",
|
|
50
52
|
"tsx": "^4.19.2",
|
|
51
53
|
"typescript": "^5.6.3"
|
|
52
54
|
},
|