caroushell 0.1.23 → 0.1.24

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
@@ -5,7 +5,6 @@ const terminal_1 = require("./terminal");
5
5
  const keyboard_1 = require("./keyboard");
6
6
  const carousel_1 = require("./carousel");
7
7
  const history_suggester_1 = require("./history-suggester");
8
- const ai_suggester_1 = require("./ai-suggester");
9
8
  const file_suggester_1 = require("./file-suggester");
10
9
  const spawner_1 = require("./spawner");
11
10
  const logs_1 = require("./logs");
@@ -18,14 +17,14 @@ class App {
18
17
  this.terminal = deps.terminal ?? new terminal_1.Terminal();
19
18
  this.keyboard = deps.keyboard ?? new keyboard_1.Keyboard();
20
19
  this.history = deps.topPanel ?? new history_suggester_1.HistorySuggester();
21
- this.ai = deps.bottomPanel ?? new ai_suggester_1.AISuggester();
20
+ this.bottomSuggester = deps.bottomPanel ?? new carousel_1.NullSuggester();
22
21
  this.files = deps.files ?? new file_suggester_1.FileSuggester();
23
- this.suggesters = deps.suggesters ?? [this.history, this.ai, this.files];
22
+ this.suggesters = deps.suggesters ?? [this.history, this.bottomSuggester, this.files];
24
23
  this.carousel = new carousel_1.Carousel({
25
24
  top: this.history,
26
- bottom: this.ai,
25
+ bottom: this.bottomSuggester,
27
26
  topRows: 2,
28
- bottomRows: 2,
27
+ bottomRows: this.bottomSuggester instanceof carousel_1.NullSuggester ? 0 : 2,
29
28
  terminal: this.terminal,
30
29
  });
31
30
  this.queueUpdateSuggestions = () => {
@@ -137,9 +136,9 @@ class App {
137
136
  };
138
137
  }
139
138
  async init() {
140
- await this.history.init();
141
- await this.ai.init();
142
- await this.files.init();
139
+ for (const s of this.suggesters) {
140
+ await s.init();
141
+ }
143
142
  }
144
143
  async run() {
145
144
  await this.init();
@@ -185,6 +184,7 @@ class App {
185
184
  this.terminal.write("\n");
186
185
  this.keyboard.disableCapture();
187
186
  this.terminal.disableWrites();
187
+ await this.preBroadcastCommand(cmd);
188
188
  try {
189
189
  const storeInHistory = await (0, spawner_1.runUserCommand)(cmd);
190
190
  if (storeInHistory) {
@@ -291,6 +291,19 @@ class App {
291
291
  this.usingFileSuggestions = true;
292
292
  this.carousel.setTopSuggester(this.files);
293
293
  }
294
+ async preBroadcastCommand(cmd) {
295
+ const listeners = this.suggesters
296
+ .map((suggester) => suggester.onCommandWillRun?.(cmd))
297
+ .filter(Boolean);
298
+ if (listeners.length === 0)
299
+ return;
300
+ try {
301
+ await Promise.all(listeners);
302
+ }
303
+ catch (err) {
304
+ (0, logs_1.logLine)("suggester onCommandWillRun error: " + err?.message);
305
+ }
306
+ }
294
307
  async broadcastCommand(cmd) {
295
308
  const listeners = this.suggesters
296
309
  .map((suggester) => suggester.onCommandRan?.(cmd))
package/dist/carousel.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Carousel = void 0;
3
+ exports.Carousel = exports.NullSuggester = void 0;
4
4
  exports.getDisplayWidth = getDisplayWidth;
5
5
  const logs_1 = require("./logs");
6
6
  const terminal_1 = require("./terminal");
@@ -57,6 +57,16 @@ function getDisplayWidth(text) {
57
57
  }
58
58
  return width;
59
59
  }
60
+ class NullSuggester {
61
+ constructor() {
62
+ this.prefix = "";
63
+ }
64
+ async init() { }
65
+ async refreshSuggestions() { }
66
+ latest() { return []; }
67
+ descriptionForAi() { return ""; }
68
+ }
69
+ exports.NullSuggester = NullSuggester;
60
70
  class Carousel {
61
71
  constructor(opts) {
62
72
  this.index = 0;
package/dist/config.js CHANGED
@@ -88,7 +88,6 @@ async function doesConfigExist() {
88
88
  }
89
89
  }
90
90
  async function getConfig() {
91
- const configPath = getConfigPath();
92
91
  const raw = await readConfigFile();
93
92
  const envApiKey = process.env.CAROUSHELL_API_KEY || process.env.GEMINI_API_KEY || undefined;
94
93
  const envApiUrl = process.env.CAROUSHELL_API_URL || undefined;
@@ -107,9 +106,6 @@ async function getConfig() {
107
106
  if (!resolved.model && geminiApiKey) {
108
107
  resolved.model = GEMINI_DEFAULT_MODEL;
109
108
  }
110
- if (!resolved.apiUrl || !resolved.apiKey || !resolved.model) {
111
- throw new Error(`Config at ${configPath} is missing required fields. Please include apiUrl, apiKey, and model (or just GEMINI_API_KEY).`);
112
- }
113
109
  return resolved;
114
110
  }
115
111
  function isGeminiUrl(url) {
@@ -70,7 +70,23 @@ async function runHelloNewUserFlow(configPath) {
70
70
  await fs_1.promises.mkdir(dir, { recursive: true });
71
71
  console.log("");
72
72
  console.log("Welcome to Caroushell!");
73
- console.log(`Let's set up AI suggestions. You'll need an API endpoint URL, a key, and model id. These will be stored at ${configPath}`);
73
+ console.log("");
74
+ const rl = readline_1.default.createInterface({
75
+ input: process.stdin,
76
+ output: process.stdout,
77
+ });
78
+ const wantsAi = (await prompt("Do you want to set up AI auto-complete? (y/n): ", rl))
79
+ .trim()
80
+ .toLowerCase();
81
+ if (wantsAi !== "y" && wantsAi !== "yes") {
82
+ rl.close();
83
+ // Writing noAi or any key will skip the new user flow next run
84
+ await fs_1.promises.writeFile(configPath, "noAi = true\n", "utf8");
85
+ console.log("\nSkipping AI setup. You can set it up later by editing " + configPath);
86
+ console.log("");
87
+ return null;
88
+ }
89
+ console.log(`\nLet's set up AI suggestions. You'll need an API endpoint URL, a key, and model id. These will be stored at ${configPath}`);
74
90
  console.log("");
75
91
  console.log("Some example endpoints you can paste:");
76
92
  console.log(" - OpenRouter: https://openrouter.ai/api/v1");
@@ -78,10 +94,6 @@ async function runHelloNewUserFlow(configPath) {
78
94
  console.log(" - Google: https://generativelanguage.googleapis.com/v1beta/openai");
79
95
  console.log("");
80
96
  console.log("Press Ctrl+C any time to abort.\n");
81
- const rl = readline_1.default.createInterface({
82
- input: process.stdin,
83
- output: process.stdout,
84
- });
85
97
  let apiUrl = "";
86
98
  while (!apiUrl) {
87
99
  const answer = (await prompt("API URL: ", rl)).trim();
@@ -48,9 +48,10 @@ class HistorySuggester {
48
48
  .catch(() => { });
49
49
  await fs_1.promises.appendFile(this.filePath, this.serializeHistoryEntry(command), "utf8");
50
50
  }
51
- async onCommandRan(command) {
51
+ async onCommandWillRun(command) {
52
52
  await this.add(command);
53
53
  }
54
+ async onCommandRan(command) { }
54
55
  latest() {
55
56
  return this.filteredItems;
56
57
  }
package/dist/main.js CHANGED
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const fs_1 = require("fs");
5
5
  const path_1 = require("path");
6
6
  const app_1 = require("./app");
7
+ const ai_suggester_1 = require("./ai-suggester");
8
+ const carousel_1 = require("./carousel");
7
9
  const hello_new_user_1 = require("./hello-new-user");
8
10
  const logs_1 = require("./logs");
9
11
  const config_1 = require("./config");
@@ -25,7 +27,11 @@ async function main() {
25
27
  if (!(await (0, config_1.doesConfigExist)())) {
26
28
  await (0, hello_new_user_1.runHelloNewUserFlow)((0, config_1.getConfigPath)());
27
29
  }
28
- const app = new app_1.App();
30
+ const config = await (0, config_1.getConfig)();
31
+ const bottomPanel = config.apiUrl && config.apiKey && config.model
32
+ ? new ai_suggester_1.AISuggester()
33
+ : new carousel_1.NullSuggester();
34
+ const app = new app_1.App({ bottomPanel });
29
35
  await app.run();
30
36
  }
31
37
  main().catch((err) => {
package/dist/spawner.js CHANGED
@@ -144,10 +144,19 @@ async function runUserCommand(command) {
144
144
  stdio: "inherit",
145
145
  windowsVerbatimArguments: true,
146
146
  });
147
- await new Promise((resolve, reject) => {
148
- proc.on("error", reject);
149
- proc.on("close", () => resolve());
150
- });
147
+ // While a user command owns the terminal, Ctrl+C should interrupt that command
148
+ // without taking down the parent Caroushell process.
149
+ const ignoreSigint = () => { };
150
+ process.on("SIGINT", ignoreSigint);
151
+ try {
152
+ await new Promise((resolve, reject) => {
153
+ proc.on("error", reject);
154
+ proc.on("close", () => resolve());
155
+ });
156
+ }
157
+ finally {
158
+ process.off("SIGINT", ignoreSigint);
159
+ }
151
160
  // Why save failed commands? Well eg sometimes we want to run a test
152
161
  // many times until we fix it.
153
162
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caroushell",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Terminal carousel that suggests commands from history, config, and AI.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/main.js",