claudeup 4.6.0 → 4.7.0

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/data/settings-catalog.js +2 -7
  3. package/src/data/settings-catalog.ts +2 -7
  4. package/src/opentui.d.ts +7 -2
  5. package/src/services/claude-settings.js +31 -4
  6. package/src/services/claude-settings.ts +31 -4
  7. package/src/services/settings-manager.js +84 -5
  8. package/src/services/settings-manager.ts +86 -5
  9. package/src/types/index.ts +1 -1
  10. package/src/ui/adapters/settingsAdapter.js +8 -8
  11. package/src/ui/adapters/settingsAdapter.ts +8 -8
  12. package/src/ui/components/TabBar.js +1 -23
  13. package/src/ui/components/TabBar.tsx +1 -26
  14. package/src/ui/components/modals/ConfirmModal.js +1 -1
  15. package/src/ui/components/modals/ConfirmModal.tsx +17 -16
  16. package/src/ui/components/modals/InputModal.js +2 -13
  17. package/src/ui/components/modals/InputModal.tsx +21 -24
  18. package/src/ui/components/modals/LoadingModal.js +1 -1
  19. package/src/ui/components/modals/LoadingModal.tsx +6 -6
  20. package/src/ui/components/modals/MessageModal.js +4 -4
  21. package/src/ui/components/modals/MessageModal.tsx +13 -13
  22. package/src/ui/components/modals/ModalContainer.js +25 -2
  23. package/src/ui/components/modals/ModalContainer.tsx +25 -2
  24. package/src/ui/components/modals/SelectModal.js +3 -4
  25. package/src/ui/components/modals/SelectModal.tsx +18 -15
  26. package/src/ui/renderers/settingsRenderers.js +1 -1
  27. package/src/ui/renderers/settingsRenderers.tsx +5 -3
  28. package/src/ui/screens/CliToolsScreen.js +2 -2
  29. package/src/ui/screens/CliToolsScreen.tsx +3 -1
  30. package/src/ui/screens/EnvVarsScreen.js +27 -10
  31. package/src/ui/screens/EnvVarsScreen.tsx +33 -16
  32. package/src/ui/screens/McpRegistryScreen.js +2 -2
  33. package/src/ui/screens/McpRegistryScreen.tsx +3 -1
  34. package/src/ui/screens/McpScreen.js +1 -1
  35. package/src/ui/screens/McpScreen.tsx +2 -1
  36. package/src/ui/screens/ModelSelectorScreen.js +2 -2
  37. package/src/ui/screens/ModelSelectorScreen.tsx +3 -2
  38. package/src/ui/screens/ProfilesScreen.js +1 -1
  39. package/src/ui/screens/ProfilesScreen.tsx +2 -1
  40. package/src/ui/screens/StatusLineScreen.js +1 -1
  41. package/src/ui/screens/StatusLineScreen.tsx +2 -1
  42. package/src/ui/state/DimensionsContext.js +2 -2
  43. package/src/ui/state/DimensionsContext.tsx +3 -3
  44. package/src/ui/components/ScrollableDetail.js +0 -23
  45. package/src/ui/components/ScrollableDetail.tsx +0 -55
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeup",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -186,13 +186,8 @@ export const SETTINGS_CATALOG = [
186
186
  description: "Adjust Claude's response style",
187
187
  category: "workflow",
188
188
  type: "select",
189
- options: [
190
- { label: "Default", value: "" },
191
- { label: "Concise", value: "concise" },
192
- { label: "Explanatory", value: "explanatory" },
193
- { label: "Formal", value: "formal" },
194
- { label: "Minimal", value: "minimal" },
195
- ],
189
+ // Options populated dynamically from installed output-style plugins
190
+ options: [],
196
191
  storage: { type: "setting", key: "outputStyle" },
197
192
  },
198
193
  {
@@ -233,13 +233,8 @@ export const SETTINGS_CATALOG: SettingDefinition[] = [
233
233
  description: "Adjust Claude's response style",
234
234
  category: "workflow",
235
235
  type: "select",
236
- options: [
237
- { label: "Default", value: "" },
238
- { label: "Concise", value: "concise" },
239
- { label: "Explanatory", value: "explanatory" },
240
- { label: "Formal", value: "formal" },
241
- { label: "Minimal", value: "minimal" },
242
- ],
236
+ // Options populated dynamically from installed output-style plugins
237
+ options: [],
243
238
  storage: { type: "setting", key: "outputStyle" },
244
239
  },
245
240
  {
package/src/opentui.d.ts CHANGED
@@ -101,8 +101,10 @@ interface TextProps {
101
101
  }
102
102
 
103
103
  interface InputProps {
104
- value: string;
105
- onChange: (value: string) => void;
104
+ value?: string;
105
+ onChange?: (value: string) => void;
106
+ onInput?: (value: string) => void;
107
+ onSubmit?: ((value: string) => void) | undefined;
106
108
  placeholder?: string;
107
109
  focused?: boolean;
108
110
  width?: number;
@@ -137,6 +139,9 @@ interface TabSelectProps {
137
139
 
138
140
  interface ScrollboxProps {
139
141
  focused?: boolean;
142
+ height?: number | `${number}%` | "auto";
143
+ scrollY?: boolean;
144
+ scrollX?: boolean;
140
145
  style?: {
141
146
  rootOptions?: Record<string, unknown>;
142
147
  wrapperOptions?: Record<string, unknown>;
@@ -209,24 +209,51 @@ export async function removeLocalInstalledPluginVersion(pluginId, projectPath) {
209
209
  await removeFromInstalledPluginsRegistry(pluginId, "local", projectPath ? path.resolve(projectPath) : undefined);
210
210
  }
211
211
  // Status line management
212
+ // Claude Code expects statusLine as an object: { type: "template", template: "..." }
213
+ // or { type: "command", command: "..." }. Writing a bare string causes a validation error
214
+ // that skips the entire settings file.
215
+ function wrapStatusLine(template) {
216
+ if (!template)
217
+ return undefined;
218
+ return { type: "template", template };
219
+ }
220
+ function unwrapStatusLine(value) {
221
+ if (!value)
222
+ return undefined;
223
+ if (typeof value === "string")
224
+ return value; // legacy format
225
+ return value.template ?? value.command;
226
+ }
212
227
  export async function setStatusLine(template, projectPath) {
213
228
  const settings = await readSettings(projectPath);
214
- settings.statusLine = template;
229
+ const wrapped = wrapStatusLine(template);
230
+ if (wrapped) {
231
+ settings.statusLine = wrapped;
232
+ }
233
+ else {
234
+ delete settings.statusLine;
235
+ }
215
236
  await writeSettings(settings, projectPath);
216
237
  }
217
238
  export async function getStatusLine(projectPath) {
218
239
  const settings = await readSettings(projectPath);
219
- return settings.statusLine;
240
+ return unwrapStatusLine(settings.statusLine);
220
241
  }
221
242
  // Global status line management
222
243
  export async function setGlobalStatusLine(template) {
223
244
  const settings = await readGlobalSettings();
224
- settings.statusLine = template;
245
+ const wrapped = wrapStatusLine(template);
246
+ if (wrapped) {
247
+ settings.statusLine = wrapped;
248
+ }
249
+ else {
250
+ delete settings.statusLine;
251
+ }
225
252
  await writeGlobalSettings(settings);
226
253
  }
227
254
  export async function getGlobalStatusLine() {
228
255
  const settings = await readGlobalSettings();
229
- return settings.statusLine;
256
+ return unwrapStatusLine(settings.statusLine);
230
257
  }
231
258
  // Get effective status line (project overrides global)
232
259
  export async function getEffectiveStatusLine(projectPath) {
@@ -311,12 +311,34 @@ export async function removeLocalInstalledPluginVersion(
311
311
  }
312
312
 
313
313
  // Status line management
314
+ // Claude Code expects statusLine as an object: { type: "template", template: "..." }
315
+ // or { type: "command", command: "..." }. Writing a bare string causes a validation error
316
+ // that skips the entire settings file.
317
+
318
+ function wrapStatusLine(template: string): { type: string; template: string } | undefined {
319
+ if (!template) return undefined;
320
+ return { type: "template", template };
321
+ }
322
+
323
+ function unwrapStatusLine(
324
+ value: string | { type: string; template?: string; command?: string } | undefined,
325
+ ): string | undefined {
326
+ if (!value) return undefined;
327
+ if (typeof value === "string") return value; // legacy format
328
+ return value.template ?? value.command;
329
+ }
330
+
314
331
  export async function setStatusLine(
315
332
  template: string,
316
333
  projectPath?: string,
317
334
  ): Promise<void> {
318
335
  const settings = await readSettings(projectPath);
319
- settings.statusLine = template;
336
+ const wrapped = wrapStatusLine(template);
337
+ if (wrapped) {
338
+ settings.statusLine = wrapped;
339
+ } else {
340
+ delete settings.statusLine;
341
+ }
320
342
  await writeSettings(settings, projectPath);
321
343
  }
322
344
 
@@ -324,19 +346,24 @@ export async function getStatusLine(
324
346
  projectPath?: string,
325
347
  ): Promise<string | undefined> {
326
348
  const settings = await readSettings(projectPath);
327
- return settings.statusLine;
349
+ return unwrapStatusLine(settings.statusLine);
328
350
  }
329
351
 
330
352
  // Global status line management
331
353
  export async function setGlobalStatusLine(template: string): Promise<void> {
332
354
  const settings = await readGlobalSettings();
333
- settings.statusLine = template;
355
+ const wrapped = wrapStatusLine(template);
356
+ if (wrapped) {
357
+ settings.statusLine = wrapped;
358
+ } else {
359
+ delete settings.statusLine;
360
+ }
334
361
  await writeGlobalSettings(settings);
335
362
  }
336
363
 
337
364
  export async function getGlobalStatusLine(): Promise<string | undefined> {
338
365
  const settings = await readGlobalSettings();
339
- return settings.statusLine;
366
+ return unwrapStatusLine(settings.statusLine);
340
367
  }
341
368
 
342
369
  // Get effective status line (project overrides global)
@@ -1,3 +1,5 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
1
3
  import { readSettings, writeSettings, readGlobalSettings, writeGlobalSettings, } from "./claude-settings.js";
2
4
  /** Read the current value of a setting from the given scope */
3
5
  export async function readSettingValue(setting, scope, projectPath) {
@@ -62,11 +64,6 @@ export async function writeSettingValue(setting, value, scope, projectPath) {
62
64
  }
63
65
  }
64
66
  else if (setting.storage.type === "attribution-text") {
65
- const attr = settings.attribution;
66
- // If attribution is explicitly disabled ({ commit: "", pr: "" }), do not overwrite it
67
- if (attr && attr.commit === "" && attr.pr === "") {
68
- return;
69
- }
70
67
  if (value && value.trim().length > 0) {
71
68
  // Write custom text: commit gets a Co-Authored-By trailer + the text; pr gets the text
72
69
  settings.attribution = {
@@ -162,3 +159,85 @@ export async function readAllSettingsBothScopes(catalog, projectPath) {
162
159
  }
163
160
  return result;
164
161
  }
162
+ /**
163
+ * Discover available output styles from installed plugins.
164
+ * Scans enabled plugins for outputStyles entries and *-output-style plugin names.
165
+ * Returns options suitable for a select setting.
166
+ */
167
+ export async function discoverOutputStyles(projectPath) {
168
+ const styles = [
169
+ { label: "Default", value: "" },
170
+ ];
171
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
172
+ const cacheDir = path.join(homeDir, ".claude", "plugins", "cache");
173
+ // Collect enabled plugins from both scopes
174
+ const enabledPlugins = new Set();
175
+ try {
176
+ const userSettings = await readGlobalSettings();
177
+ for (const [k, v] of Object.entries(userSettings.enabledPlugins || {})) {
178
+ if (v)
179
+ enabledPlugins.add(k);
180
+ }
181
+ }
182
+ catch { }
183
+ try {
184
+ const projSettings = await readSettings(projectPath);
185
+ for (const [k, v] of Object.entries(projSettings.enabledPlugins || {})) {
186
+ if (v)
187
+ enabledPlugins.add(k);
188
+ }
189
+ }
190
+ catch { }
191
+ const seen = new Set();
192
+ for (const pluginId of enabledPlugins) {
193
+ const [pluginName, marketplace] = pluginId.split("@");
194
+ // Check if this is a dedicated output-style plugin (e.g. "explanatory-output-style")
195
+ if (pluginName.endsWith("-output-style")) {
196
+ const styleName = pluginName
197
+ .replace(/-output-style$/, "")
198
+ .split("-")
199
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
200
+ .join(" ");
201
+ if (!seen.has(styleName)) {
202
+ seen.add(styleName);
203
+ styles.push({ label: styleName, value: styleName });
204
+ }
205
+ continue;
206
+ }
207
+ // Check plugin.json for outputStyles entries
208
+ if (!marketplace)
209
+ continue;
210
+ const pluginDir = path.join(cacheDir, marketplace, pluginName);
211
+ try {
212
+ const versions = fs.readdirSync(pluginDir).sort();
213
+ const latest = versions[versions.length - 1];
214
+ if (!latest)
215
+ continue;
216
+ const manifestPath = path.join(pluginDir, latest, "plugin.json");
217
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
218
+ const outputStyles = manifest.outputStyles;
219
+ if (!outputStyles?.length)
220
+ continue;
221
+ for (const stylePath of outputStyles) {
222
+ const fullPath = path.join(pluginDir, latest, stylePath);
223
+ try {
224
+ const content = fs.readFileSync(fullPath, "utf-8");
225
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
226
+ if (fmMatch) {
227
+ const nameMatch = fmMatch[1].match(/^name:\s*(.+)$/m);
228
+ if (nameMatch) {
229
+ const name = nameMatch[1].trim();
230
+ if (!seen.has(name)) {
231
+ seen.add(name);
232
+ styles.push({ label: name, value: name });
233
+ }
234
+ }
235
+ }
236
+ }
237
+ catch { }
238
+ }
239
+ }
240
+ catch { }
241
+ }
242
+ return styles;
243
+ }
@@ -1,3 +1,5 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
1
3
  import {
2
4
  readSettings,
3
5
  writeSettings,
@@ -80,11 +82,6 @@ export async function writeSettingValue(
80
82
  delete (settings as any).attribution;
81
83
  }
82
84
  } else if (setting.storage.type === "attribution-text") {
83
- const attr = (settings as any).attribution;
84
- // If attribution is explicitly disabled ({ commit: "", pr: "" }), do not overwrite it
85
- if (attr && attr.commit === "" && attr.pr === "") {
86
- return;
87
- }
88
85
  if (value && value.trim().length > 0) {
89
86
  // Write custom text: commit gets a Co-Authored-By trailer + the text; pr gets the text
90
87
  (settings as any).attribution = {
@@ -188,3 +185,87 @@ export async function readAllSettingsBothScopes(
188
185
  }
189
186
  return result;
190
187
  }
188
+
189
+ /**
190
+ * Discover available output styles from installed plugins.
191
+ * Scans enabled plugins for outputStyles entries and *-output-style plugin names.
192
+ * Returns options suitable for a select setting.
193
+ */
194
+ export async function discoverOutputStyles(
195
+ projectPath?: string,
196
+ ): Promise<Array<{ label: string; value: string }>> {
197
+ const styles: Array<{ label: string; value: string }> = [
198
+ { label: "Default", value: "" },
199
+ ];
200
+
201
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
202
+ const cacheDir = path.join(homeDir, ".claude", "plugins", "cache");
203
+
204
+ // Collect enabled plugins from both scopes
205
+ const enabledPlugins = new Set<string>();
206
+ try {
207
+ const userSettings = await readGlobalSettings();
208
+ for (const [k, v] of Object.entries((userSettings as any).enabledPlugins || {})) {
209
+ if (v) enabledPlugins.add(k);
210
+ }
211
+ } catch {}
212
+ try {
213
+ const projSettings = await readSettings(projectPath);
214
+ for (const [k, v] of Object.entries((projSettings as any).enabledPlugins || {})) {
215
+ if (v) enabledPlugins.add(k);
216
+ }
217
+ } catch {}
218
+
219
+ const seen = new Set<string>();
220
+
221
+ for (const pluginId of enabledPlugins) {
222
+ const [pluginName, marketplace] = pluginId.split("@");
223
+
224
+ // Check if this is a dedicated output-style plugin (e.g. "explanatory-output-style")
225
+ if (pluginName.endsWith("-output-style")) {
226
+ const styleName = pluginName
227
+ .replace(/-output-style$/, "")
228
+ .split("-")
229
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
230
+ .join(" ");
231
+ if (!seen.has(styleName)) {
232
+ seen.add(styleName);
233
+ styles.push({ label: styleName, value: styleName });
234
+ }
235
+ continue;
236
+ }
237
+
238
+ // Check plugin.json for outputStyles entries
239
+ if (!marketplace) continue;
240
+ const pluginDir = path.join(cacheDir, marketplace, pluginName);
241
+ try {
242
+ const versions = fs.readdirSync(pluginDir).sort();
243
+ const latest = versions[versions.length - 1];
244
+ if (!latest) continue;
245
+ const manifestPath = path.join(pluginDir, latest, "plugin.json");
246
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
247
+ const outputStyles = manifest.outputStyles as string[] | undefined;
248
+ if (!outputStyles?.length) continue;
249
+
250
+ for (const stylePath of outputStyles) {
251
+ const fullPath = path.join(pluginDir, latest, stylePath);
252
+ try {
253
+ const content = fs.readFileSync(fullPath, "utf-8");
254
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
255
+ if (fmMatch) {
256
+ const nameMatch = fmMatch[1].match(/^name:\s*(.+)$/m);
257
+ if (nameMatch) {
258
+ const name = nameMatch[1].trim();
259
+ if (!seen.has(name)) {
260
+ seen.add(name);
261
+ styles.push({ label: name, value: name });
262
+ }
263
+ }
264
+ }
265
+ } catch {}
266
+ }
267
+ } catch {}
268
+ }
269
+
270
+ return styles;
271
+ }
@@ -88,7 +88,7 @@ export interface ClaudeSettings {
88
88
  enabledPlugins?: Record<string, boolean>;
89
89
  extraKnownMarketplaces?: Record<string, MarketplaceSource>;
90
90
  installedPluginVersions?: Record<string, string>;
91
- statusLine?: string;
91
+ statusLine?: string | { type: string; template?: string; command?: string };
92
92
  hooks?: Record<string, ClaudeHookGroup[]>;
93
93
  }
94
94
 
@@ -52,12 +52,12 @@ export function getEffectiveValue(scoped) {
52
52
  }
53
53
  export function formatValue(setting, value) {
54
54
  if (value === undefined || value === "") {
55
- if (setting.defaultValue !== undefined) {
56
- return setting.type === "boolean"
57
- ? setting.defaultValue === "true"
58
- ? "on"
59
- : "off"
60
- : setting.defaultValue || "default";
55
+ if (setting.type === "boolean" && setting.defaultValue !== undefined) {
56
+ return setting.defaultValue === "true" ? "on" : "off";
57
+ }
58
+ if (setting.type === "select" && setting.defaultValue !== undefined) {
59
+ const opt = setting.options?.find((o) => o.value === setting.defaultValue);
60
+ return opt ? opt.label : setting.defaultValue;
61
61
  }
62
62
  return "—";
63
63
  }
@@ -68,8 +68,8 @@ export function formatValue(setting, value) {
68
68
  const opt = setting.options.find((o) => o.value === value);
69
69
  return opt ? opt.label : value;
70
70
  }
71
- if (value.length > 20) {
72
- return value.slice(0, 20) + "...";
71
+ if (value.length > 12) {
72
+ return value.slice(0, 12) + "";
73
73
  }
74
74
  return value;
75
75
  }
@@ -94,12 +94,12 @@ export function formatValue(
94
94
  value: string | undefined,
95
95
  ): string {
96
96
  if (value === undefined || value === "") {
97
- if (setting.defaultValue !== undefined) {
98
- return setting.type === "boolean"
99
- ? setting.defaultValue === "true"
100
- ? "on"
101
- : "off"
102
- : setting.defaultValue || "default";
97
+ if (setting.type === "boolean" && setting.defaultValue !== undefined) {
98
+ return setting.defaultValue === "true" ? "on" : "off";
99
+ }
100
+ if (setting.type === "select" && setting.defaultValue !== undefined) {
101
+ const opt = setting.options?.find((o) => o.value === setting.defaultValue);
102
+ return opt ? opt.label : setting.defaultValue;
103
103
  }
104
104
  return "—";
105
105
  }
@@ -113,8 +113,8 @@ export function formatValue(
113
113
  return opt ? opt.label : value;
114
114
  }
115
115
 
116
- if (value.length > 20) {
117
- return value.slice(0, 20) + "...";
116
+ if (value.length > 12) {
117
+ return value.slice(0, 12) + "";
118
118
  }
119
119
  return value;
120
120
  }
@@ -1,5 +1,4 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
2
- import { useKeyboardHandler } from "../hooks/useKeyboardHandler";
3
2
  const TABS = [
4
3
  { key: "1", label: "Plugins", screen: "plugins" },
5
4
  { key: "2", label: "Skills", screen: "skills" },
@@ -8,28 +7,7 @@ const TABS = [
8
7
  { key: "5", label: "Profiles", screen: "profiles" },
9
8
  { key: "6", label: "CLI", screen: "cli-tools" },
10
9
  ];
11
- export function TabBar({ currentScreen, onTabChange }) {
12
- // Handle number key shortcuts (1-5)
13
- useKeyboardHandler((input, key) => {
14
- if (!onTabChange)
15
- return;
16
- // Number keys 1-5
17
- const tabIndex = Number.parseInt(input, 10);
18
- if (tabIndex >= 1 && tabIndex <= TABS.length) {
19
- const tab = TABS[tabIndex - 1];
20
- if (tab && tab.screen !== currentScreen) {
21
- onTabChange(tab.screen);
22
- }
23
- }
24
- // Tab key to cycle through tabs
25
- if (key.tab) {
26
- const currentIndex = TABS.findIndex((t) => t.screen === currentScreen);
27
- const nextIndex = key.shift
28
- ? (currentIndex - 1 + TABS.length) % TABS.length
29
- : (currentIndex + 1) % TABS.length;
30
- onTabChange(TABS[nextIndex].screen);
31
- }
32
- });
10
+ export function TabBar({ currentScreen }) {
33
11
  return (_jsx("box", { flexDirection: "row", gap: 0, children: TABS.map((tab, index) => {
34
12
  const isSelected = tab.screen === currentScreen;
35
13
  const isLast = index === TABS.length - 1;
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
- import { useKeyboardHandler } from "../hooks/useKeyboardHandler";
3
2
  import type { Screen } from "../state/types.js";
4
3
 
5
4
  interface Tab {
@@ -19,33 +18,9 @@ const TABS: Tab[] = [
19
18
 
20
19
  interface TabBarProps {
21
20
  currentScreen: Screen;
22
- onTabChange?: (screen: Screen) => void;
23
21
  }
24
22
 
25
- export function TabBar({ currentScreen, onTabChange }: TabBarProps) {
26
- // Handle number key shortcuts (1-5)
27
- useKeyboardHandler((input, key) => {
28
- if (!onTabChange) return;
29
-
30
- // Number keys 1-5
31
- const tabIndex = Number.parseInt(input, 10);
32
- if (tabIndex >= 1 && tabIndex <= TABS.length) {
33
- const tab = TABS[tabIndex - 1];
34
- if (tab && tab.screen !== currentScreen) {
35
- onTabChange(tab.screen);
36
- }
37
- }
38
-
39
- // Tab key to cycle through tabs
40
- if (key.tab) {
41
- const currentIndex = TABS.findIndex((t) => t.screen === currentScreen);
42
- const nextIndex = key.shift
43
- ? (currentIndex - 1 + TABS.length) % TABS.length
44
- : (currentIndex + 1) % TABS.length;
45
- onTabChange(TABS[nextIndex].screen);
46
- }
47
- });
48
-
23
+ export function TabBar({ currentScreen }: TabBarProps) {
49
24
  return (
50
25
  <box flexDirection="row" gap={0}>
51
26
  {TABS.map((tab, index) => {
@@ -9,6 +9,6 @@ export function ConfirmModal({ title, message, onConfirm, onCancel, }) {
9
9
  onCancel();
10
10
  }
11
11
  });
12
- return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "yellow", backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "green", children: "[Y]" }), _jsx("span", { children: "es " }), _jsx("span", { fg: "red", children: "[N]" }), _jsx("span", { children: "o" })] }) })] }));
12
+ return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#EDEDED", children: _jsx("strong", { children: title }) }) }), _jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#A1A1AA", children: message }) }), _jsxs("box", { marginTop: 1, children: [_jsx("text", { fg: "#71717A", children: "Press " }), _jsx("text", { fg: "#EDEDED", children: "Y" }), _jsx("text", { fg: "#71717A", children: " to confirm \u2022 " }), _jsx("text", { fg: "#EDEDED", children: "N" }), _jsx("text", { fg: "#71717A", children: " to cancel" })] })] }));
13
13
  }
14
14
  export default ConfirmModal;
@@ -31,28 +31,29 @@ export function ConfirmModal({
31
31
  flexDirection="column"
32
32
  border
33
33
  borderStyle="rounded"
34
- borderColor="yellow"
35
- backgroundColor="#1a1a2e"
36
- paddingLeft={2}
37
- paddingRight={2}
34
+ borderColor="#525252"
35
+ backgroundColor="#1C1C1E"
36
+ paddingLeft={3}
37
+ paddingRight={3}
38
38
  paddingTop={1}
39
39
  paddingBottom={1}
40
40
  width={60}
41
41
  >
42
- <text>
43
- <strong>{title}</strong>
44
- </text>
45
- <box marginTop={1} marginBottom={1}>
46
- <text>{message}</text>
47
- </box>
48
- <box>
49
- <text>
50
- <span fg="green">[Y]</span>
51
- <span>es </span>
52
- <span fg="red">[N]</span>
53
- <span>o</span>
42
+ <box marginBottom={1}>
43
+ <text fg="#EDEDED">
44
+ <strong>{title}</strong>
54
45
  </text>
55
46
  </box>
47
+ <box marginBottom={1}>
48
+ <text fg="#A1A1AA">{message}</text>
49
+ </box>
50
+ <box marginTop={1}>
51
+ <text fg="#71717A">Press </text>
52
+ <text fg="#EDEDED">Y</text>
53
+ <text fg="#71717A"> to confirm • </text>
54
+ <text fg="#EDEDED">N</text>
55
+ <text fg="#71717A"> to cancel</text>
56
+ </box>
56
57
  </box>
57
58
  );
58
59
  }
@@ -1,16 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
- import { useState } from "react";
3
- import { useKeyboard } from "../../hooks/useKeyboard.js";
4
- export function InputModal({ title, label, defaultValue = "", onSubmit, onCancel, }) {
5
- const [value, setValue] = useState(defaultValue);
6
- useKeyboard((key) => {
7
- if (key.name === "enter") {
8
- onSubmit(value);
9
- }
10
- else if (key.name === "escape") {
11
- onCancel();
12
- }
13
- });
14
- return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "cyan", backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: label }) }), _jsx("input", { value: value, onChange: setValue, focused: true, width: 54 }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#666666", children: "Enter to confirm \u2022 Escape to cancel" }) })] }));
2
+ export function InputModal({ title, label, defaultValue = "", onSubmit, }) {
3
+ return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#EDEDED", children: _jsx("strong", { children: title }) }) }), _jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#A1A1AA", children: label }) }), _jsx("box", { border: true, borderStyle: "rounded", borderColor: "#3F3F46", paddingLeft: 1, paddingRight: 1, width: 54, children: _jsx("input", { value: defaultValue, onSubmit: onSubmit, focused: true, width: 50 }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#71717A", children: "\u21B5 to confirm \u2022 Esc to cancel" }) })] }));
15
4
  }
16
5
  export default InputModal;