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.
- package/package.json +1 -1
- package/src/data/settings-catalog.js +2 -7
- package/src/data/settings-catalog.ts +2 -7
- package/src/opentui.d.ts +7 -2
- package/src/services/claude-settings.js +31 -4
- package/src/services/claude-settings.ts +31 -4
- package/src/services/settings-manager.js +84 -5
- package/src/services/settings-manager.ts +86 -5
- package/src/types/index.ts +1 -1
- package/src/ui/adapters/settingsAdapter.js +8 -8
- package/src/ui/adapters/settingsAdapter.ts +8 -8
- package/src/ui/components/TabBar.js +1 -23
- package/src/ui/components/TabBar.tsx +1 -26
- package/src/ui/components/modals/ConfirmModal.js +1 -1
- package/src/ui/components/modals/ConfirmModal.tsx +17 -16
- package/src/ui/components/modals/InputModal.js +2 -13
- package/src/ui/components/modals/InputModal.tsx +21 -24
- package/src/ui/components/modals/LoadingModal.js +1 -1
- package/src/ui/components/modals/LoadingModal.tsx +6 -6
- package/src/ui/components/modals/MessageModal.js +4 -4
- package/src/ui/components/modals/MessageModal.tsx +13 -13
- package/src/ui/components/modals/ModalContainer.js +25 -2
- package/src/ui/components/modals/ModalContainer.tsx +25 -2
- package/src/ui/components/modals/SelectModal.js +3 -4
- package/src/ui/components/modals/SelectModal.tsx +18 -15
- package/src/ui/renderers/settingsRenderers.js +1 -1
- package/src/ui/renderers/settingsRenderers.tsx +5 -3
- package/src/ui/screens/CliToolsScreen.js +2 -2
- package/src/ui/screens/CliToolsScreen.tsx +3 -1
- package/src/ui/screens/EnvVarsScreen.js +27 -10
- package/src/ui/screens/EnvVarsScreen.tsx +33 -16
- package/src/ui/screens/McpRegistryScreen.js +2 -2
- package/src/ui/screens/McpRegistryScreen.tsx +3 -1
- package/src/ui/screens/McpScreen.js +1 -1
- package/src/ui/screens/McpScreen.tsx +2 -1
- package/src/ui/screens/ModelSelectorScreen.js +2 -2
- package/src/ui/screens/ModelSelectorScreen.tsx +3 -2
- package/src/ui/screens/ProfilesScreen.js +1 -1
- package/src/ui/screens/ProfilesScreen.tsx +2 -1
- package/src/ui/screens/StatusLineScreen.js +1 -1
- package/src/ui/screens/StatusLineScreen.tsx +2 -1
- package/src/ui/state/DimensionsContext.js +2 -2
- package/src/ui/state/DimensionsContext.tsx +3 -3
- package/src/ui/components/ScrollableDetail.js +0 -23
- package/src/ui/components/ScrollableDetail.tsx +0 -55
package/package.json
CHANGED
|
@@ -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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
|
105
|
-
onChange
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -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.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 >
|
|
72
|
-
return value.slice(0,
|
|
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.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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 >
|
|
117
|
-
return value.slice(0,
|
|
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
|
|
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
|
|
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: "
|
|
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="
|
|
35
|
-
backgroundColor="#
|
|
36
|
-
paddingLeft={
|
|
37
|
-
paddingRight={
|
|
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
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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;
|