claudeup 4.6.1 → 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 (42) 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/settings-manager.js +84 -5
  6. package/src/services/settings-manager.ts +86 -5
  7. package/src/ui/adapters/settingsAdapter.js +8 -8
  8. package/src/ui/adapters/settingsAdapter.ts +8 -8
  9. package/src/ui/components/TabBar.js +1 -23
  10. package/src/ui/components/TabBar.tsx +1 -26
  11. package/src/ui/components/modals/ConfirmModal.js +1 -1
  12. package/src/ui/components/modals/ConfirmModal.tsx +17 -16
  13. package/src/ui/components/modals/InputModal.js +2 -13
  14. package/src/ui/components/modals/InputModal.tsx +21 -24
  15. package/src/ui/components/modals/LoadingModal.js +1 -1
  16. package/src/ui/components/modals/LoadingModal.tsx +6 -6
  17. package/src/ui/components/modals/MessageModal.js +4 -4
  18. package/src/ui/components/modals/MessageModal.tsx +13 -13
  19. package/src/ui/components/modals/ModalContainer.js +25 -2
  20. package/src/ui/components/modals/ModalContainer.tsx +25 -2
  21. package/src/ui/components/modals/SelectModal.js +3 -4
  22. package/src/ui/components/modals/SelectModal.tsx +18 -15
  23. package/src/ui/renderers/settingsRenderers.js +1 -1
  24. package/src/ui/renderers/settingsRenderers.tsx +5 -3
  25. package/src/ui/screens/CliToolsScreen.js +2 -2
  26. package/src/ui/screens/CliToolsScreen.tsx +3 -1
  27. package/src/ui/screens/EnvVarsScreen.js +27 -10
  28. package/src/ui/screens/EnvVarsScreen.tsx +33 -16
  29. package/src/ui/screens/McpRegistryScreen.js +2 -2
  30. package/src/ui/screens/McpRegistryScreen.tsx +3 -1
  31. package/src/ui/screens/McpScreen.js +1 -1
  32. package/src/ui/screens/McpScreen.tsx +2 -1
  33. package/src/ui/screens/ModelSelectorScreen.js +2 -2
  34. package/src/ui/screens/ModelSelectorScreen.tsx +3 -2
  35. package/src/ui/screens/ProfilesScreen.js +1 -1
  36. package/src/ui/screens/ProfilesScreen.tsx +2 -1
  37. package/src/ui/screens/StatusLineScreen.js +1 -1
  38. package/src/ui/screens/StatusLineScreen.tsx +2 -1
  39. package/src/ui/state/DimensionsContext.js +2 -2
  40. package/src/ui/state/DimensionsContext.tsx +3 -3
  41. package/src/ui/components/ScrollableDetail.js +0 -23
  42. 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.1",
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>;
@@ -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
+ }
@@ -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;
@@ -1,5 +1,4 @@
1
- import React, { useState } from "react";
2
- import { useKeyboard } from "../../hooks/useKeyboard.js";
1
+ import React from "react";
3
2
 
4
3
  interface InputModalProps {
5
4
  /** Modal title */
@@ -19,43 +18,41 @@ export function InputModal({
19
18
  label,
20
19
  defaultValue = "",
21
20
  onSubmit,
22
- onCancel,
23
21
  }: InputModalProps) {
24
- const [value, setValue] = useState(defaultValue);
25
-
26
- useKeyboard((key) => {
27
- if (key.name === "enter") {
28
- onSubmit(value);
29
- } else if (key.name === "escape") {
30
- onCancel();
31
- }
32
- });
33
-
34
22
  return (
35
23
  <box
36
24
  flexDirection="column"
37
25
  border
38
26
  borderStyle="rounded"
39
- borderColor="cyan"
40
- backgroundColor="#1a1a2e"
41
- paddingLeft={2}
42
- paddingRight={2}
27
+ borderColor="#525252"
28
+ backgroundColor="#1C1C1E"
29
+ paddingLeft={3}
30
+ paddingRight={3}
43
31
  paddingTop={1}
44
32
  paddingBottom={1}
45
33
  width={60}
46
34
  >
47
- <text>
48
- <strong>{title}</strong>
49
- </text>
35
+ <box marginBottom={1}>
36
+ <text fg="#EDEDED">
37
+ <strong>{title}</strong>
38
+ </text>
39
+ </box>
50
40
 
51
- <box marginTop={1} marginBottom={1}>
52
- <text>{label}</text>
41
+ <box marginBottom={1}>
42
+ <text fg="#A1A1AA">{label}</text>
53
43
  </box>
54
44
 
55
- <input value={value} onChange={setValue} focused width={54} />
45
+ <box border borderStyle="rounded" borderColor="#3F3F46" paddingLeft={1} paddingRight={1} width={54}>
46
+ <input
47
+ value={defaultValue}
48
+ onSubmit={onSubmit as any}
49
+ focused
50
+ width={50}
51
+ />
52
+ </box>
56
53
 
57
54
  <box marginTop={1}>
58
- <text fg="#666666">Enter to confirm • Escape to cancel</text>
55
+ <text fg="#71717A">↵ to confirm • Esc to cancel</text>
59
56
  </box>
60
57
  </box>
61
58
  );
@@ -9,6 +9,6 @@ export function LoadingModal({ message }) {
9
9
  }, 80);
10
10
  return () => clearInterval(interval);
11
11
  }, []);
12
- return (_jsxs("box", { flexDirection: "row", border: true, borderStyle: "rounded", borderColor: "cyan", backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs("text", { children: [" ", message] })] }));
12
+ return (_jsxs("box", { flexDirection: "row", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "#A1A1AA", children: SPINNER_FRAMES[frame] }), _jsxs("text", { fg: "#EDEDED", children: [" ", message] })] }));
13
13
  }
14
14
  export default LoadingModal;
@@ -23,15 +23,15 @@ export function LoadingModal({ message }: LoadingModalProps) {
23
23
  flexDirection="row"
24
24
  border
25
25
  borderStyle="rounded"
26
- borderColor="cyan"
27
- backgroundColor="#1a1a2e"
28
- paddingLeft={2}
29
- paddingRight={2}
26
+ borderColor="#525252"
27
+ backgroundColor="#1C1C1E"
28
+ paddingLeft={3}
29
+ paddingRight={3}
30
30
  paddingTop={1}
31
31
  paddingBottom={1}
32
32
  >
33
- <text fg="cyan">{SPINNER_FRAMES[frame]}</text>
34
- <text> {message}</text>
33
+ <text fg="#A1A1AA">{SPINNER_FRAMES[frame]}</text>
34
+ <text fg="#EDEDED"> {message}</text>
35
35
  </box>
36
36
  );
37
37
  }
@@ -1,9 +1,9 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
2
2
  import { useKeyboard } from "../../hooks/useKeyboard.js";
3
3
  const variantConfig = {
4
- info: { icon: "ℹ", color: "cyan" },
5
- success: { icon: "✓", color: "green" },
6
- error: { icon: "✗", color: "red" },
4
+ info: { icon: "ℹ", color: "#60A5FA" },
5
+ success: { icon: "✓", color: "#4ADE80" },
6
+ error: { icon: "✗", color: "#F87171" },
7
7
  };
8
8
  export function MessageModal({ title, message, variant, onDismiss, }) {
9
9
  const config = variantConfig[variant];
@@ -11,6 +11,6 @@ export function MessageModal({ title, message, variant, onDismiss, }) {
11
11
  // Any key dismisses
12
12
  onDismiss();
13
13
  });
14
- return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: config.color, backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsxs("box", { children: [_jsxs("text", { fg: config.color, children: [config.icon, " "] }), _jsx("text", { children: _jsx("strong", { children: title }) })] }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsx("text", { fg: "#666666", children: "Press any key to continue" }) })] }));
14
+ return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "#525252", backgroundColor: "#1C1C1E", paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsxs("box", { marginBottom: 1, children: [_jsxs("text", { fg: config.color, children: [config.icon, " "] }), _jsx("text", { fg: "#EDEDED", children: _jsx("strong", { children: title }) })] }), _jsx("box", { marginBottom: 1, children: _jsx("text", { fg: "#A1A1AA", children: message }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#71717A", children: "Press any key to continue" }) })] }));
15
15
  }
16
16
  export default MessageModal;