@daliovic/cc-statusline 1.2.0 → 1.3.1

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/README.md CHANGED
@@ -96,6 +96,15 @@ Supported calculation methods:
96
96
  | 12 | UOIF France |
97
97
  | 13 | Diyanet Turkey |
98
98
  | 14 | Spiritual Administration of Muslims of Russia |
99
+ | 15 | Moonsighting Committee Worldwide |
100
+ | 16 | Dubai (experimental) |
101
+ | 17 | JAKIM, Malaysia |
102
+ | 18 | Tunisia |
103
+ | 19 | Algeria |
104
+ | 20 | KEMENAG, Indonesia |
105
+ | 21 | Morocco |
106
+ | 22 | Comunidade Islamica de Lisboa |
107
+ | 23 | Ministry of Awqaf, Jordan |
99
108
 
100
109
  ### Environment Variables
101
110
 
package/dist/config.js CHANGED
@@ -18,6 +18,15 @@ export const PRAYER_METHODS = {
18
18
  12: "Union Organization Islamic de France",
19
19
  13: "Diyanet İşleri Başkanlığı, Turkey",
20
20
  14: "Spiritual Administration of Muslims of Russia",
21
+ 15: "Moonsighting Committee Worldwide",
22
+ 16: "Dubai (experimental)",
23
+ 17: "JAKIM, Malaysia",
24
+ 18: "Tunisia",
25
+ 19: "Algeria",
26
+ 20: "KEMENAG, Indonesia",
27
+ 21: "Morocco",
28
+ 22: "Comunidade Islamica de Lisboa",
29
+ 23: "Ministry of Awqaf, Jordan",
21
30
  };
22
31
  export const DEFAULT_CONFIG = {
23
32
  show: {
@@ -32,6 +41,9 @@ export const DEFAULT_CONFIG = {
32
41
  contextWarning: 75,
33
42
  cacheTtlMs: 300000,
34
43
  },
44
+ wizard: {
45
+ autosave: true,
46
+ },
35
47
  colors: {
36
48
  model: 36, // cyan
37
49
  context: 248, // gray
@@ -57,6 +69,7 @@ export function loadConfig() {
57
69
  return {
58
70
  show: { ...DEFAULT_CONFIG.show, ...loaded.show },
59
71
  thresholds: { ...DEFAULT_CONFIG.thresholds, ...loaded.thresholds },
72
+ wizard: { ...DEFAULT_CONFIG.wizard, ...loaded.wizard },
60
73
  colors: { ...DEFAULT_CONFIG.colors, ...loaded.colors },
61
74
  prayer: { ...DEFAULT_CONFIG.prayer, ...loaded.prayer },
62
75
  };
package/dist/prayer.js CHANGED
@@ -24,11 +24,33 @@ function formatTimeLeft(diffMs) {
24
24
  }
25
25
  async function getLocationFromIP() {
26
26
  try {
27
- const res = await fetch("http://ip-api.com/json/?fields=lat,lon");
27
+ const res = await fetch("http://ip-api.com/json/?fields=lat,lon,city,country");
28
28
  if (!res.ok)
29
29
  return null;
30
30
  const data = await res.json();
31
- return { latitude: data.lat, longitude: data.lon };
31
+ return { latitude: data.lat, longitude: data.lon, city: data.city, country: data.country };
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ export async function geocodeCity(city, country) {
38
+ try {
39
+ const query = encodeURIComponent(`${city}, ${country}`);
40
+ const res = await fetch(`https://nominatim.openstreetmap.org/search?q=${query}&format=json&limit=1`, {
41
+ headers: { "User-Agent": "cc-statusline/1.0" },
42
+ });
43
+ if (!res.ok)
44
+ return null;
45
+ const data = await res.json();
46
+ if (!data.length)
47
+ return null;
48
+ return {
49
+ latitude: parseFloat(data[0].lat),
50
+ longitude: parseFloat(data[0].lon),
51
+ city,
52
+ country,
53
+ };
32
54
  }
33
55
  catch {
34
56
  return null;
package/dist/wizard.js CHANGED
@@ -1,9 +1,21 @@
1
1
  import { select, checkbox, input, confirm } from "@inquirer/prompts";
2
2
  import { DEFAULT_CONFIG, loadConfig, saveConfig, CONFIG_PATH, PRAYER_METHODS } from "./config.js";
3
+ // Clear screen
4
+ function clearScreen() {
5
+ process.stdout.write("\x1b[2J\x1b[H");
6
+ }
3
7
  // ANSI color helper
4
8
  function colorize(code, text) {
5
9
  return `\x1b[38;5;${code}m${text}\x1b[0m`;
6
10
  }
11
+ // Handle Ctrl+C cancellation
12
+ function isCancelled(error) {
13
+ if (!(error instanceof Error))
14
+ return false;
15
+ return error.name === "ExitPromptError" ||
16
+ error.name === "AbortError" ||
17
+ error.message.includes("force closed");
18
+ }
7
19
  function showPreview(config) {
8
20
  const c = config.colors;
9
21
  const parts = [];
@@ -31,211 +43,327 @@ function showPreview(config) {
31
43
  }
32
44
  console.log("\n Preview: " + parts.join(" \x1b[2m\u{2502}\x1b[0m ") + "\n");
33
45
  }
34
- async function visibilityMenu(config) {
35
- const choices = [
36
- { name: "Model indicator", value: "model", checked: config.show.model },
37
- { name: "Context usage", value: "context", checked: config.show.context },
38
- { name: "5-hour usage limit", value: "usage5hr", checked: config.show.usage5hr },
39
- { name: "7-day usage limit", value: "usage7day", checked: config.show.usage7day },
40
- { name: "Budget delta arrows", value: "delta", checked: config.show.delta },
41
- { name: "Prayer times", value: "prayer", checked: config.show.prayer },
42
- ];
43
- const selected = await checkbox({
44
- message: "Toggle items to show (space to toggle, enter to confirm)",
45
- choices,
46
- });
47
- config.show.model = selected.includes("model");
48
- config.show.context = selected.includes("context");
49
- config.show.usage5hr = selected.includes("usage5hr");
50
- config.show.usage7day = selected.includes("usage7day");
51
- config.show.delta = selected.includes("delta");
52
- config.show.prayer = selected.includes("prayer");
46
+ function autoSave(config) {
47
+ if (config.wizard.autosave) {
48
+ saveConfig(config);
49
+ console.log(" \x1b[2m(autosaved)\x1b[0m");
50
+ }
53
51
  }
54
- async function colorsMenu(config) {
55
- const colorOptions = [
56
- { name: `Model (current: ${colorize(config.colors.model, String(config.colors.model))})`, value: "model" },
57
- { name: `Context (current: ${colorize(config.colors.context, String(config.colors.context))})`, value: "context" },
58
- { name: `Context warning (current: ${colorize(config.colors.contextWarning, String(config.colors.contextWarning))})`, value: "contextWarning" },
59
- { name: `Usage (current: ${colorize(config.colors.usage, String(config.colors.usage))})`, value: "usage" },
60
- { name: `Delta under budget (current: ${colorize(config.colors.deltaUnder, String(config.colors.deltaUnder))})`, value: "deltaUnder" },
61
- { name: `Delta over budget (current: ${colorize(config.colors.deltaOver, String(config.colors.deltaOver))})`, value: "deltaOver" },
62
- { name: `Prayer (current: ${colorize(config.colors.prayer, String(config.colors.prayer))})`, value: "prayer" },
63
- { name: "Back", value: "back" },
64
- ];
65
- while (true) {
66
- const choice = await select({
67
- message: "Select color to change (ANSI 256 codes: 0-255)",
68
- choices: colorOptions,
69
- });
70
- if (choice === "back")
71
- break;
72
- const current = config.colors[choice];
73
- const newValue = await input({
74
- message: `Enter ANSI color code (0-255, current: ${colorize(current, String(current))})`,
75
- default: String(current),
76
- validate: (val) => {
77
- const num = parseInt(val, 10);
78
- if (isNaN(num) || num < 0 || num > 255)
79
- return "Enter a number 0-255";
80
- return true;
81
- },
52
+ async function visibilityMenu(config) {
53
+ try {
54
+ const choices = [
55
+ { name: "Model indicator", value: "model", checked: config.show.model },
56
+ { name: "Context usage", value: "context", checked: config.show.context },
57
+ { name: "5-hour usage limit", value: "usage5hr", checked: config.show.usage5hr },
58
+ { name: "7-day usage limit", value: "usage7day", checked: config.show.usage7day },
59
+ { name: "Budget delta arrows", value: "delta", checked: config.show.delta },
60
+ { name: "Prayer times", value: "prayer", checked: config.show.prayer },
61
+ ];
62
+ const selected = await checkbox({
63
+ message: "Toggle items (space=toggle, enter=confirm, ctrl+c=back)",
64
+ choices,
82
65
  });
83
- config.colors[choice] = parseInt(newValue, 10);
84
- // Update the menu option to show new color
85
- const idx = colorOptions.findIndex(o => o.value === choice);
86
- if (idx >= 0) {
87
- const label = choice.replace(/([A-Z])/g, " $1").toLowerCase();
88
- colorOptions[idx].name = `${label.charAt(0).toUpperCase() + label.slice(1)} (current: ${colorize(config.colors[choice], newValue)})`;
89
- }
66
+ config.show.model = selected.includes("model");
67
+ config.show.context = selected.includes("context");
68
+ config.show.usage5hr = selected.includes("usage5hr");
69
+ config.show.usage7day = selected.includes("usage7day");
70
+ config.show.delta = selected.includes("delta");
71
+ config.show.prayer = selected.includes("prayer");
72
+ autoSave(config);
73
+ return false;
74
+ }
75
+ catch (e) {
76
+ if (isCancelled(e))
77
+ return true;
78
+ throw e;
90
79
  }
91
80
  }
92
- async function prayerMenu(config) {
93
- const methodChoices = Object.entries(PRAYER_METHODS).map(([code, name]) => ({
94
- name: `${name}${Number(code) === config.prayer.method ? " (current)" : ""}`,
95
- value: code,
96
- }));
97
- methodChoices.push({ name: "Back", value: "back" });
98
- while (true) {
99
- const choice = await select({
100
- message: "Prayer settings",
101
- choices: [
102
- { name: `Calculation method: ${PRAYER_METHODS[config.prayer.method]}`, value: "method" },
103
- { name: config.prayer.location
104
- ? `Location: ${config.prayer.location.latitude}, ${config.prayer.location.longitude}`
105
- : "Location: Auto-detect (IP)", value: "location" },
106
- { name: "Back", value: "back" },
107
- ],
108
- });
109
- if (choice === "back")
110
- break;
111
- if (choice === "method") {
112
- const method = await select({
113
- message: "Select calculation method",
114
- choices: methodChoices,
81
+ async function colorsMenu(config) {
82
+ try {
83
+ const colorOptions = [
84
+ { name: `Model (current: ${colorize(config.colors.model, String(config.colors.model))})`, value: "model" },
85
+ { name: `Context (current: ${colorize(config.colors.context, String(config.colors.context))})`, value: "context" },
86
+ { name: `Context warning (current: ${colorize(config.colors.contextWarning, String(config.colors.contextWarning))})`, value: "contextWarning" },
87
+ { name: `Usage (current: ${colorize(config.colors.usage, String(config.colors.usage))})`, value: "usage" },
88
+ { name: `Delta under budget (current: ${colorize(config.colors.deltaUnder, String(config.colors.deltaUnder))})`, value: "deltaUnder" },
89
+ { name: `Delta over budget (current: ${colorize(config.colors.deltaOver, String(config.colors.deltaOver))})`, value: "deltaOver" },
90
+ { name: `Prayer (current: ${colorize(config.colors.prayer, String(config.colors.prayer))})`, value: "prayer" },
91
+ { name: "\x1b[2m← Back\x1b[0m", value: "back" },
92
+ ];
93
+ while (true) {
94
+ const choice = await select({
95
+ message: "Select color to change (0-255)",
96
+ choices: colorOptions,
97
+ });
98
+ if (choice === "back")
99
+ return false;
100
+ const current = config.colors[choice];
101
+ const newValue = await input({
102
+ message: `Enter ANSI color code (0-255, current: ${colorize(current, String(current))})`,
103
+ default: String(current),
104
+ validate: (val) => {
105
+ const num = parseInt(val, 10);
106
+ if (isNaN(num) || num < 0 || num > 255)
107
+ return "Enter a number 0-255";
108
+ return true;
109
+ },
115
110
  });
116
- if (method !== "back") {
117
- config.prayer.method = Number(method);
111
+ config.colors[choice] = parseInt(newValue, 10);
112
+ autoSave(config);
113
+ // Update the menu option to show new color
114
+ const idx = colorOptions.findIndex(o => o.value === choice);
115
+ if (idx >= 0) {
116
+ const label = choice.replace(/([A-Z])/g, " $1").toLowerCase();
117
+ colorOptions[idx].name = `${label.charAt(0).toUpperCase() + label.slice(1)} (current: ${colorize(config.colors[choice], newValue)})`;
118
118
  }
119
119
  }
120
- else if (choice === "location") {
121
- const locChoice = await select({
122
- message: "Location setting",
120
+ }
121
+ catch (e) {
122
+ if (isCancelled(e))
123
+ return true;
124
+ throw e;
125
+ }
126
+ }
127
+ async function prayerMenu(config) {
128
+ try {
129
+ const methodChoices = Object.entries(PRAYER_METHODS).map(([code, name]) => ({
130
+ name: `${name}${Number(code) === config.prayer.method ? " (current)" : ""}`,
131
+ value: code,
132
+ }));
133
+ methodChoices.push({ name: "\x1b[2m← Back\x1b[0m", value: "back" });
134
+ while (true) {
135
+ const locationDisplay = config.prayer.location
136
+ ? config.prayer.location.city
137
+ ? `${config.prayer.location.city}, ${config.prayer.location.country}`
138
+ : `${config.prayer.location.latitude.toFixed(2)}, ${config.prayer.location.longitude.toFixed(2)}`
139
+ : "Auto-detect (IP)";
140
+ const choice = await select({
141
+ message: "Prayer settings",
123
142
  choices: [
124
- { name: "Auto-detect from IP (recommended)", value: "auto" },
125
- { name: "Enter manually", value: "manual" },
143
+ { name: `Calculation method: ${PRAYER_METHODS[config.prayer.method]}`, value: "method" },
144
+ { name: `Location: ${locationDisplay}`, value: "location" },
145
+ { name: "\x1b[2m← Back\x1b[0m", value: "back" },
126
146
  ],
127
147
  });
128
- if (locChoice === "auto") {
129
- config.prayer.location = null;
130
- }
131
- else {
132
- const lat = await input({
133
- message: "Latitude",
134
- default: config.prayer.location?.latitude?.toString() || "",
135
- validate: (val) => {
136
- const num = parseFloat(val);
137
- if (isNaN(num) || num < -90 || num > 90)
138
- return "Enter -90 to 90";
139
- return true;
140
- },
148
+ if (choice === "back")
149
+ return false;
150
+ if (choice === "method") {
151
+ const method = await select({
152
+ message: "Select calculation method",
153
+ choices: methodChoices,
141
154
  });
142
- const lng = await input({
143
- message: "Longitude",
144
- default: config.prayer.location?.longitude?.toString() || "",
145
- validate: (val) => {
146
- const num = parseFloat(val);
147
- if (isNaN(num) || num < -180 || num > 180)
148
- return "Enter -180 to 180";
149
- return true;
150
- },
155
+ if (method !== "back") {
156
+ config.prayer.method = Number(method);
157
+ autoSave(config);
158
+ }
159
+ }
160
+ else if (choice === "location") {
161
+ const locChoice = await select({
162
+ message: "Location setting",
163
+ choices: [
164
+ { name: "Auto-detect from IP (recommended)", value: "auto" },
165
+ { name: "Enter city and country", value: "city" },
166
+ { name: "Enter coordinates manually", value: "coords" },
167
+ ],
151
168
  });
152
- config.prayer.location = {
153
- latitude: parseFloat(lat),
154
- longitude: parseFloat(lng),
155
- };
169
+ if (locChoice === "auto") {
170
+ config.prayer.location = null;
171
+ autoSave(config);
172
+ }
173
+ else if (locChoice === "city") {
174
+ const city = await input({
175
+ message: "City name",
176
+ default: config.prayer.location?.city || "",
177
+ validate: (val) => val.trim() ? true : "Enter a city name",
178
+ });
179
+ const country = await input({
180
+ message: "Country",
181
+ default: config.prayer.location?.country || "",
182
+ validate: (val) => val.trim() ? true : "Enter a country",
183
+ });
184
+ console.log(" Looking up coordinates...");
185
+ const { geocodeCity } = await import("./prayer.js");
186
+ const location = await geocodeCity(city.trim(), country.trim());
187
+ if (location) {
188
+ config.prayer.location = location;
189
+ console.log(` Found: ${location.latitude.toFixed(4)}, ${location.longitude.toFixed(4)}\n`);
190
+ autoSave(config);
191
+ }
192
+ else {
193
+ console.log(" Could not find location. Please try again or enter coordinates manually.\n");
194
+ }
195
+ }
196
+ else {
197
+ const lat = await input({
198
+ message: "Latitude",
199
+ default: config.prayer.location?.latitude?.toString() || "",
200
+ validate: (val) => {
201
+ const num = parseFloat(val);
202
+ if (isNaN(num) || num < -90 || num > 90)
203
+ return "Enter -90 to 90";
204
+ return true;
205
+ },
206
+ });
207
+ const lng = await input({
208
+ message: "Longitude",
209
+ default: config.prayer.location?.longitude?.toString() || "",
210
+ validate: (val) => {
211
+ const num = parseFloat(val);
212
+ if (isNaN(num) || num < -180 || num > 180)
213
+ return "Enter -180 to 180";
214
+ return true;
215
+ },
216
+ });
217
+ config.prayer.location = {
218
+ latitude: parseFloat(lat),
219
+ longitude: parseFloat(lng),
220
+ };
221
+ autoSave(config);
222
+ }
156
223
  }
157
224
  }
158
225
  }
226
+ catch (e) {
227
+ if (isCancelled(e))
228
+ return true;
229
+ throw e;
230
+ }
159
231
  }
160
232
  async function thresholdsMenu(config) {
161
- const contextWarning = await input({
162
- message: "Context warning threshold % (turns orange)",
163
- default: String(config.thresholds.contextWarning),
164
- validate: (val) => {
165
- const num = parseInt(val, 10);
166
- if (isNaN(num) || num < 1 || num > 100)
167
- return "Enter 1-100";
233
+ try {
234
+ const contextWarning = await input({
235
+ message: "Context warning threshold % (turns orange)",
236
+ default: String(config.thresholds.contextWarning),
237
+ validate: (val) => {
238
+ const num = parseInt(val, 10);
239
+ if (isNaN(num) || num < 1 || num > 100)
240
+ return "Enter 1-100";
241
+ return true;
242
+ },
243
+ });
244
+ config.thresholds.contextWarning = parseInt(contextWarning, 10);
245
+ const cacheTtl = await input({
246
+ message: "API cache TTL (milliseconds)",
247
+ default: String(config.thresholds.cacheTtlMs),
248
+ validate: (val) => {
249
+ const num = parseInt(val, 10);
250
+ if (isNaN(num) || num < 0)
251
+ return "Enter a positive number";
252
+ return true;
253
+ },
254
+ });
255
+ config.thresholds.cacheTtlMs = parseInt(cacheTtl, 10);
256
+ autoSave(config);
257
+ return false;
258
+ }
259
+ catch (e) {
260
+ if (isCancelled(e))
168
261
  return true;
169
- },
170
- });
171
- config.thresholds.contextWarning = parseInt(contextWarning, 10);
172
- const cacheTtl = await input({
173
- message: "API cache TTL (milliseconds)",
174
- default: String(config.thresholds.cacheTtlMs),
175
- validate: (val) => {
176
- const num = parseInt(val, 10);
177
- if (isNaN(num) || num < 0)
178
- return "Enter a positive number";
262
+ throw e;
263
+ }
264
+ }
265
+ async function settingsMenu(config) {
266
+ try {
267
+ const autosave = await confirm({
268
+ message: "Enable autosave? (saves after each change)",
269
+ default: config.wizard.autosave,
270
+ });
271
+ config.wizard.autosave = autosave;
272
+ saveConfig(config); // Always save settings changes
273
+ console.log(` Autosave ${autosave ? "enabled" : "disabled"}\n`);
274
+ return false;
275
+ }
276
+ catch (e) {
277
+ if (isCancelled(e))
179
278
  return true;
180
- },
181
- });
182
- config.thresholds.cacheTtlMs = parseInt(cacheTtl, 10);
279
+ throw e;
280
+ }
281
+ }
282
+ function showHeader(config) {
283
+ clearScreen();
284
+ console.log("\x1b[1mcc-statusline Configuration\x1b[0m");
285
+ console.log("\x1b[2mCtrl+C to go back/exit\x1b[0m");
286
+ showPreview(config);
183
287
  }
184
288
  export async function runWizard() {
185
- console.log("\n\x1b[1mcc-statusline Configuration\x1b[0m\n");
186
289
  const config = loadConfig();
187
- showPreview(config);
188
- while (true) {
189
- const choice = await select({
190
- message: "What would you like to configure?",
191
- choices: [
192
- { name: "Visibility (show/hide items)", value: "visibility" },
193
- { name: "Colors", value: "colors" },
194
- { name: "Thresholds", value: "thresholds" },
195
- { name: "Prayer settings", value: "prayer" },
196
- { name: "Reset to defaults", value: "reset" },
197
- { name: "Save & Exit", value: "save" },
198
- { name: "Exit without saving", value: "exit" },
199
- ],
200
- });
201
- switch (choice) {
202
- case "visibility":
203
- await visibilityMenu(config);
204
- showPreview(config);
205
- break;
206
- case "colors":
207
- await colorsMenu(config);
208
- showPreview(config);
209
- break;
210
- case "thresholds":
211
- await thresholdsMenu(config);
212
- break;
213
- case "prayer":
214
- await prayerMenu(config);
215
- break;
216
- case "reset":
217
- const confirmReset = await confirm({
218
- message: "Reset all settings to defaults?",
219
- default: false,
220
- });
221
- if (confirmReset) {
222
- Object.assign(config, JSON.parse(JSON.stringify(DEFAULT_CONFIG)));
223
- console.log(" Reset to defaults.\n");
224
- showPreview(config);
225
- }
226
- break;
227
- case "save":
228
- saveConfig(config);
229
- console.log(`\n Saved to ${CONFIG_PATH}\n`);
230
- return;
231
- case "exit":
232
- const confirmExit = await confirm({
233
- message: "Exit without saving changes?",
234
- default: false,
235
- });
236
- if (confirmExit)
290
+ showHeader(config);
291
+ try {
292
+ while (true) {
293
+ const autosaveLabel = config.wizard.autosave ? "\x1b[32m●\x1b[0m" : "\x1b[2m○\x1b[0m";
294
+ const choice = await select({
295
+ message: "What would you like to configure?",
296
+ choices: [
297
+ { name: "Visibility (show/hide items)", value: "visibility" },
298
+ { name: "Colors", value: "colors" },
299
+ { name: "Thresholds", value: "thresholds" },
300
+ { name: "Prayer settings", value: "prayer" },
301
+ { name: `Settings ${autosaveLabel} autosave`, value: "settings" },
302
+ { name: "Reset to defaults", value: "reset" },
303
+ ...(config.wizard.autosave ? [] : [{ name: "Save & Exit", value: "save" }]),
304
+ { name: config.wizard.autosave ? "Exit" : "Exit without saving", value: "exit" },
305
+ ],
306
+ });
307
+ switch (choice) {
308
+ case "visibility":
309
+ await visibilityMenu(config);
310
+ showHeader(config);
311
+ break;
312
+ case "colors":
313
+ await colorsMenu(config);
314
+ showHeader(config);
315
+ break;
316
+ case "thresholds":
317
+ await thresholdsMenu(config);
318
+ showHeader(config);
319
+ break;
320
+ case "prayer":
321
+ await prayerMenu(config);
322
+ showHeader(config);
323
+ break;
324
+ case "settings":
325
+ await settingsMenu(config);
326
+ showHeader(config);
327
+ break;
328
+ case "reset":
329
+ const confirmReset = await confirm({
330
+ message: "Reset all settings to defaults?",
331
+ default: false,
332
+ });
333
+ if (confirmReset) {
334
+ Object.assign(config, JSON.parse(JSON.stringify(DEFAULT_CONFIG)));
335
+ autoSave(config);
336
+ }
337
+ showHeader(config);
338
+ break;
339
+ case "save":
340
+ saveConfig(config);
341
+ console.log(`\n Saved to ${CONFIG_PATH}\n`);
237
342
  return;
238
- break;
343
+ case "exit":
344
+ if (config.wizard.autosave) {
345
+ return;
346
+ }
347
+ const confirmExit = await confirm({
348
+ message: "Exit without saving changes?",
349
+ default: false,
350
+ });
351
+ if (confirmExit)
352
+ return;
353
+ break;
354
+ }
355
+ }
356
+ }
357
+ catch (e) {
358
+ if (isCancelled(e)) {
359
+ if (config.wizard.autosave) {
360
+ console.log("\n \x1b[2mExited (changes autosaved)\x1b[0m\n");
361
+ }
362
+ else {
363
+ console.log("\n \x1b[2mExited without saving\x1b[0m\n");
364
+ }
365
+ return;
239
366
  }
367
+ throw e;
240
368
  }
241
369
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daliovic/cc-statusline",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Minimal Claude Code statusline with usage limits and budget tracking",
5
5
  "type": "module",
6
6
  "main": "dist/statusline.js",