caroushell 0.1.30 → 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 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 ?? [this.history, this.bottomSuggester, this.files];
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() { return []; }
67
- descriptionForAi() { return ""; }
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
- return `${color}${prefix}${lineText}${reset}`;
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
- }
@@ -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("Customize your shell prompt with plain text and tokens.");
69
- logFn("Available tokens:");
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
- const answer = await prompter.ask("Prompt template: ");
75
- const trimmed = answer.trim();
76
- if (!trimmed || trimmed === defaultPromptTemplate.trim()) {
77
- return undefined;
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
- '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' }],
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: '\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 },
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: '\u001b[1;3C', meta: true },
26
- { sequence: '\u001b[1;9C', meta: true },
27
- { sequence: '\u001bf', meta: true },
25
+ { sequence: "\u001b[1;3C", meta: true },
26
+ { sequence: "\u001b[1;9C", meta: true },
27
+ { sequence: "\u001bf", meta: true },
28
28
  ],
29
- 'ctrl-left': [
30
- { sequence: '\u001b[1;5D', ctrl: true },
31
- { sequence: '\u001b[5D', ctrl: true },
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: '\u001b[1;3D', meta: true },
34
- { sequence: '\u001b[1;9D', meta: true },
35
- { sequence: '\u001bb', meta: true },
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: '\u001b[H' }, { sequence: '\u001b[1~' }],
39
- end: [{ sequence: '\u001b[F' }, { sequence: '\u001b[4~' }],
40
- delete: [{ sequence: '\u001b[3~' }],
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
- 'focus-in': [{ sequence: '\u001b[I' }],
43
- 'focus-out': [{ sequence: '\u001b[O' }],
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('utf8');
82
+ this.stdin.setEncoding("utf8");
83
83
  if (this.stdin.isTTY)
84
84
  this.stdin.setRawMode(true);
85
- this.stdin.on('data', this.onData);
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('data', this.onData);
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 === 'need-more')
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 === 'focus-in' || evt.name === 'focus-out') {
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('key', evt);
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 !== '\t') {
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('key', { name: 'char', sequence: ch });
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 'need-more';
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, config_1.buildPromptLine0)(config) });
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caroushell",
3
- "version": "0.1.30",
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",
@@ -19,6 +19,7 @@
19
19
  "start": "node dist/main.js",
20
20
  "test": "node --import tsx --test tests/*.ts",
21
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
  },