claudeup 3.7.2 → 3.9.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 (52) hide show
  1. package/package.json +1 -1
  2. package/src/data/settings-catalog.js +612 -0
  3. package/src/data/settings-catalog.ts +689 -0
  4. package/src/data/skill-repos.js +86 -0
  5. package/src/data/skill-repos.ts +97 -0
  6. package/src/services/plugin-manager.js +2 -0
  7. package/src/services/plugin-manager.ts +3 -0
  8. package/src/services/profiles.js +161 -0
  9. package/src/services/profiles.ts +225 -0
  10. package/src/services/settings-manager.js +108 -0
  11. package/src/services/settings-manager.ts +140 -0
  12. package/src/services/skills-manager.js +239 -0
  13. package/src/services/skills-manager.ts +328 -0
  14. package/src/services/skillsmp-client.js +67 -0
  15. package/src/services/skillsmp-client.ts +89 -0
  16. package/src/types/index.ts +101 -1
  17. package/src/ui/App.js +23 -18
  18. package/src/ui/App.tsx +27 -23
  19. package/src/ui/components/TabBar.js +9 -8
  20. package/src/ui/components/TabBar.tsx +15 -19
  21. package/src/ui/components/layout/ScreenLayout.js +8 -14
  22. package/src/ui/components/layout/ScreenLayout.tsx +51 -58
  23. package/src/ui/components/modals/ModalContainer.js +43 -11
  24. package/src/ui/components/modals/ModalContainer.tsx +44 -12
  25. package/src/ui/components/modals/SelectModal.js +4 -18
  26. package/src/ui/components/modals/SelectModal.tsx +10 -21
  27. package/src/ui/screens/CliToolsScreen.js +2 -2
  28. package/src/ui/screens/CliToolsScreen.tsx +8 -8
  29. package/src/ui/screens/EnvVarsScreen.js +248 -116
  30. package/src/ui/screens/EnvVarsScreen.tsx +419 -184
  31. package/src/ui/screens/McpRegistryScreen.tsx +18 -6
  32. package/src/ui/screens/McpScreen.js +1 -1
  33. package/src/ui/screens/McpScreen.tsx +15 -5
  34. package/src/ui/screens/ModelSelectorScreen.js +3 -5
  35. package/src/ui/screens/ModelSelectorScreen.tsx +12 -16
  36. package/src/ui/screens/PluginsScreen.js +154 -66
  37. package/src/ui/screens/PluginsScreen.tsx +280 -97
  38. package/src/ui/screens/ProfilesScreen.js +255 -0
  39. package/src/ui/screens/ProfilesScreen.tsx +487 -0
  40. package/src/ui/screens/SkillsScreen.js +325 -0
  41. package/src/ui/screens/SkillsScreen.tsx +574 -0
  42. package/src/ui/screens/StatusLineScreen.js +2 -2
  43. package/src/ui/screens/StatusLineScreen.tsx +10 -12
  44. package/src/ui/screens/index.js +3 -2
  45. package/src/ui/screens/index.ts +3 -2
  46. package/src/ui/state/AppContext.js +2 -1
  47. package/src/ui/state/AppContext.tsx +2 -0
  48. package/src/ui/state/reducer.js +151 -19
  49. package/src/ui/state/reducer.ts +167 -19
  50. package/src/ui/state/types.ts +58 -14
  51. package/src/utils/clipboard.js +56 -0
  52. package/src/utils/clipboard.ts +58 -0
@@ -17,12 +17,12 @@ import {
17
17
  import {
18
18
  setMcpEnvVar,
19
19
  getMcpEnvVars,
20
+ readSettings,
20
21
  saveGlobalInstalledPluginVersion,
21
22
  saveLocalInstalledPluginVersion,
22
23
  } from "../../services/claude-settings.js";
23
- import {
24
- saveInstalledPluginVersion,
25
- } from "../../services/plugin-manager.js";
24
+ import { saveProfile } from "../../services/profiles.js";
25
+ import { saveInstalledPluginVersion } from "../../services/plugin-manager.js";
26
26
  import {
27
27
  installPlugin as cliInstallPlugin,
28
28
  uninstallPlugin as cliUninstallPlugin,
@@ -209,53 +209,87 @@ export function PluginsScreen() {
209
209
  );
210
210
  }, [filteredItems]);
211
211
 
212
- // Keyboard handling
212
+ // Keyboard handling — inline search with live filtering
213
213
  useKeyboard((event) => {
214
- // Handle search mode
215
- if (isSearchActive) {
216
- if (event.name === "escape") {
217
- dispatch({ type: "SET_SEARCHING", isSearching: false });
214
+ if (state.modal) return;
215
+
216
+ const hasQuery = pluginsState.searchQuery.length > 0;
217
+
218
+ // Escape: always clear search state fully
219
+ if (event.name === "escape") {
220
+ if (hasQuery || isSearchActive) {
218
221
  dispatch({ type: "PLUGINS_SET_SEARCH", query: "" });
219
- } else if (event.name === "enter") {
220
222
  dispatch({ type: "SET_SEARCHING", isSearching: false });
221
- // Keep the search query, just exit search mode
222
- } else if (event.name === "backspace" || event.name === "delete") {
223
- dispatch({
224
- type: "PLUGINS_SET_SEARCH",
225
- query: pluginsState.searchQuery.slice(0, -1),
226
- });
227
- } else if (event.name.length === 1 && !event.ctrl && !event.meta) {
228
- dispatch({
229
- type: "PLUGINS_SET_SEARCH",
230
- query: pluginsState.searchQuery + event.name,
231
- });
223
+ dispatch({ type: "PLUGINS_SELECT", index: 0 });
232
224
  }
225
+ // Don't return — let GlobalKeyHandler handle Escape too (for quit)
233
226
  return;
234
227
  }
235
228
 
236
- if (state.modal) return;
237
-
238
- // Start search with /
239
- if (event.name === "/") {
240
- dispatch({ type: "SET_SEARCHING", isSearching: true });
229
+ // Backspace: remove last char from search query
230
+ if (event.name === "backspace" || event.name === "delete") {
231
+ if (hasQuery) {
232
+ const newQuery = pluginsState.searchQuery.slice(0, -1);
233
+ dispatch({ type: "PLUGINS_SET_SEARCH", query: newQuery });
234
+ dispatch({ type: "PLUGINS_SELECT", index: 0 });
235
+ // If query becomes empty, exit search mode
236
+ if (newQuery.length === 0) {
237
+ dispatch({ type: "SET_SEARCHING", isSearching: false });
238
+ }
239
+ }
241
240
  return;
242
241
  }
243
242
 
244
- // Navigation
243
+ // Navigation — always works (even during search)
245
244
  if (event.name === "up" || event.name === "k") {
245
+ // 'k' navigates when query is empty, otherwise appends to search
246
+ if (event.name === "k" && (hasQuery || isSearchActive)) {
247
+ dispatch({
248
+ type: "PLUGINS_SET_SEARCH",
249
+ query: pluginsState.searchQuery + event.name,
250
+ });
251
+ dispatch({ type: "PLUGINS_SELECT", index: 0 });
252
+ return;
253
+ }
246
254
  const newIndex = Math.max(0, pluginsState.selectedIndex - 1);
247
255
  dispatch({ type: "PLUGINS_SELECT", index: newIndex });
248
- } else if (event.name === "down" || event.name === "j") {
256
+ return;
257
+ }
258
+ if (event.name === "down" || event.name === "j") {
259
+ // 'j' navigates when query is empty, otherwise appends to search
260
+ if (event.name === "j" && (hasQuery || isSearchActive)) {
261
+ dispatch({
262
+ type: "PLUGINS_SET_SEARCH",
263
+ query: pluginsState.searchQuery + event.name,
264
+ });
265
+ dispatch({ type: "PLUGINS_SELECT", index: 0 });
266
+ return;
267
+ }
249
268
  const newIndex = Math.min(
250
269
  selectableItems.length - 1,
251
270
  pluginsState.selectedIndex + 1,
252
271
  );
253
272
  dispatch({ type: "PLUGINS_SELECT", index: newIndex });
273
+ return;
254
274
  }
255
275
 
256
- // Collapse/expand marketplace
257
- else if (
258
- (event.name === "left" || event.name === "right" || event.name === "<" || event.name === ">") &&
276
+ // Enter — exit search mode (keep filter active) + select/install
277
+ if (event.name === "enter") {
278
+ if (isSearchActive) {
279
+ dispatch({ type: "SET_SEARCHING", isSearching: false });
280
+ // Keep the query — filter stays active, shortcuts resume
281
+ return;
282
+ }
283
+ handleSelect();
284
+ return;
285
+ }
286
+
287
+ // Collapse/expand marketplace — always works
288
+ if (
289
+ (event.name === "left" ||
290
+ event.name === "right" ||
291
+ event.name === "<" ||
292
+ event.name === ">") &&
259
293
  selectableItems[pluginsState.selectedIndex]?.marketplace
260
294
  ) {
261
295
  const item = selectableItems[pluginsState.selectedIndex];
@@ -265,50 +299,49 @@ export function PluginsScreen() {
265
299
  name: item.marketplace.name,
266
300
  });
267
301
  }
302
+ return;
268
303
  }
269
304
 
270
- // Refresh
271
- else if (event.name === "r") {
272
- handleRefresh();
273
- }
274
-
275
- // New marketplace (show instructions)
276
- else if (event.name === "n") {
277
- handleShowAddMarketplaceInstructions();
278
- }
279
-
280
- // Team config help
281
- else if (event.name === "t") {
282
- handleShowTeamConfigHelp();
283
- }
284
-
285
- // Scope-specific toggle shortcuts (u/p/l)
286
- else if (event.name === "u") {
287
- handleScopeToggle("user");
288
- } else if (event.name === "p") {
289
- handleScopeToggle("project");
290
- } else if (event.name === "l") {
291
- handleScopeToggle("local");
292
- }
293
-
294
- // Update plugin (Shift+U)
295
- else if (event.name === "U") {
296
- handleUpdate();
305
+ // When search query is non-empty, printable letters go to the query
306
+ // (shortcuts are suspended while filtering, digits skip to let tab nav work)
307
+ if (hasQuery || isSearchActive) {
308
+ if (event.name.length === 1 && !event.ctrl && !event.meta && !/[0-9]/.test(event.name)) {
309
+ dispatch({
310
+ type: "PLUGINS_SET_SEARCH",
311
+ query: pluginsState.searchQuery + event.name,
312
+ });
313
+ dispatch({ type: "PLUGINS_SELECT", index: 0 });
314
+ }
315
+ return;
297
316
  }
298
317
 
299
- // Update all
300
- else if (event.name === "a") {
301
- handleUpdateAll();
302
- }
318
+ // When search query is empty: action shortcuts work normally
303
319
 
304
- // Delete/uninstall
305
- else if (event.name === "d") {
306
- handleUninstall();
320
+ // Start explicit search mode with /
321
+ if (event.name === "/") {
322
+ dispatch({ type: "SET_SEARCHING", isSearching: true });
323
+ return;
307
324
  }
308
325
 
309
- // Enter for selection
310
- else if (event.name === "enter") {
311
- handleSelect();
326
+ // Action shortcuts (only when query is empty)
327
+ if (event.name === "r") handleRefresh();
328
+ else if (event.name === "n") handleShowAddMarketplaceInstructions();
329
+ else if (event.name === "t") handleShowTeamConfigHelp();
330
+ else if (event.name === "u") handleScopeToggle("user");
331
+ else if (event.name === "p") handleScopeToggle("project");
332
+ else if (event.name === "l") handleScopeToggle("local");
333
+ else if (event.name === "U") handleUpdate();
334
+ else if (event.name === "a") handleUpdateAll();
335
+ else if (event.name === "d") handleUninstall();
336
+ else if (event.name === "s") handleSaveAsProfile();
337
+ // Any other printable letter: start inline search (skip digits — used for tab nav)
338
+ else if (event.name.length === 1 && !event.ctrl && !event.meta && !/[0-9]/.test(event.name)) {
339
+ dispatch({ type: "SET_SEARCHING", isSearching: true });
340
+ dispatch({
341
+ type: "PLUGINS_SET_SEARCH",
342
+ query: event.name,
343
+ });
344
+ dispatch({ type: "PLUGINS_SELECT", index: 0 });
312
345
  }
313
346
  });
314
347
 
@@ -491,9 +524,10 @@ export function PluginsScreen() {
491
524
  const missing = await checkMissingDeps(setup);
492
525
  const hasMissing =
493
526
  (missing.pip?.length || 0) +
494
- (missing.brew?.length || 0) +
495
- (missing.npm?.length || 0) +
496
- (missing.cargo?.length || 0) > 0;
527
+ (missing.brew?.length || 0) +
528
+ (missing.npm?.length || 0) +
529
+ (missing.cargo?.length || 0) >
530
+ 0;
497
531
 
498
532
  if (!hasMissing) return;
499
533
 
@@ -502,7 +536,8 @@ export function PluginsScreen() {
502
536
  if (missing.pip?.length) parts.push(`pip: ${missing.pip.join(", ")}`);
503
537
  if (missing.brew?.length) parts.push(`brew: ${missing.brew.join(", ")}`);
504
538
  if (missing.npm?.length) parts.push(`npm: ${missing.npm.join(", ")}`);
505
- if (missing.cargo?.length) parts.push(`cargo: ${missing.cargo.join(", ")}`);
539
+ if (missing.cargo?.length)
540
+ parts.push(`cargo: ${missing.cargo.join(", ")}`);
506
541
 
507
542
  const wantInstall = await modal.confirm(
508
543
  "Install Dependencies?",
@@ -550,7 +585,11 @@ export function PluginsScreen() {
550
585
  if (scope === "user") {
551
586
  await saveGlobalInstalledPluginVersion(pluginId, version);
552
587
  } else if (scope === "local") {
553
- await saveLocalInstalledPluginVersion(pluginId, version, state.projectPath);
588
+ await saveLocalInstalledPluginVersion(
589
+ pluginId,
590
+ version,
591
+ state.projectPath,
592
+ );
554
593
  } else {
555
594
  await saveInstalledPluginVersion(pluginId, version, state.projectPath);
556
595
  }
@@ -717,12 +756,17 @@ export function PluginsScreen() {
717
756
  if (!item || item.type !== "plugin" || !item.plugin?.hasUpdate) return;
718
757
 
719
758
  const plugin = item.plugin;
720
- const scope: PluginScope = pluginsState.scope === "global" ? "user" : "project";
759
+ const scope: PluginScope =
760
+ pluginsState.scope === "global" ? "user" : "project";
721
761
 
722
762
  modal.loading(`Updating ${plugin.name}...`);
723
763
  try {
724
764
  await cliUpdatePlugin(plugin.id, scope);
725
- await saveVersionAfterInstall(plugin.id, plugin.version || "0.0.0", scope);
765
+ await saveVersionAfterInstall(
766
+ plugin.id,
767
+ plugin.version || "0.0.0",
768
+ scope,
769
+ );
726
770
  modal.hideModal();
727
771
  fetchData();
728
772
  } catch (error) {
@@ -737,13 +781,18 @@ export function PluginsScreen() {
737
781
  const updatable = pluginsState.plugins.data.filter((p) => p.hasUpdate);
738
782
  if (updatable.length === 0) return;
739
783
 
740
- const scope: PluginScope = pluginsState.scope === "global" ? "user" : "project";
784
+ const scope: PluginScope =
785
+ pluginsState.scope === "global" ? "user" : "project";
741
786
  modal.loading(`Updating ${updatable.length} plugin(s)...`);
742
787
 
743
788
  try {
744
789
  for (const plugin of updatable) {
745
790
  await cliUpdatePlugin(plugin.id, scope);
746
- await saveVersionAfterInstall(plugin.id, plugin.version || "0.0.0", scope);
791
+ await saveVersionAfterInstall(
792
+ plugin.id,
793
+ plugin.version || "0.0.0",
794
+ scope,
795
+ );
747
796
  }
748
797
  modal.hideModal();
749
798
  fetchData();
@@ -823,6 +872,47 @@ export function PluginsScreen() {
823
872
  }
824
873
  };
825
874
 
875
+ const handleSaveAsProfile = async () => {
876
+ // Read current enabledPlugins from project settings
877
+ const settings = await readSettings(state.projectPath);
878
+ const enabledPlugins = settings.enabledPlugins ?? {};
879
+
880
+ const name = await modal.input("Save Profile", "Profile name:");
881
+ if (name === null || !name.trim()) return;
882
+
883
+ const scopeChoice = await modal.select(
884
+ "Save to scope",
885
+ "Where should this profile be saved?",
886
+ [
887
+ {
888
+ label: "User — ~/.claude/profiles.json (available everywhere)",
889
+ value: "user",
890
+ },
891
+ {
892
+ label: "Project — .claude/profiles.json (shared with team via git)",
893
+ value: "project",
894
+ },
895
+ ],
896
+ );
897
+ if (scopeChoice === null) return;
898
+
899
+ const scope = scopeChoice as "user" | "project";
900
+
901
+ modal.loading("Saving profile...");
902
+ try {
903
+ await saveProfile(name.trim(), enabledPlugins, scope, state.projectPath);
904
+ modal.hideModal();
905
+ await modal.message(
906
+ "Saved",
907
+ `Profile "${name.trim()}" saved.\nPress 6 to manage profiles.`,
908
+ "success",
909
+ );
910
+ } catch (error) {
911
+ modal.hideModal();
912
+ await modal.message("Error", `Failed to save profile: ${error}`, "error");
913
+ }
914
+ };
915
+
826
916
  const handleUninstall = async () => {
827
917
  const item = selectableItems[pluginsState.selectedIndex];
828
918
  if (!item || item.type !== "plugin" || !item.plugin) return;
@@ -870,7 +960,11 @@ export function PluginsScreen() {
870
960
  modal.loading(`Uninstalling ${plugin.name}...`);
871
961
 
872
962
  try {
873
- await cliUninstallPlugin(plugin.id, scopeValue as PluginScope, state.projectPath);
963
+ await cliUninstallPlugin(
964
+ plugin.id,
965
+ scopeValue as PluginScope,
966
+ state.projectPath,
967
+ );
874
968
  modal.hideModal();
875
969
  fetchData();
876
970
  } catch (error) {
@@ -886,7 +980,9 @@ export function PluginsScreen() {
886
980
  ) {
887
981
  return (
888
982
  <box flexDirection="column" paddingLeft={1} paddingRight={1}>
889
- <text fg="#7e57c2"><strong>claudeup Plugins</strong></text>
983
+ <text fg="#7e57c2">
984
+ <strong>claudeup Plugins</strong>
985
+ </text>
890
986
  <text fg="gray">Loading...</text>
891
987
  </box>
892
988
  );
@@ -899,7 +995,9 @@ export function PluginsScreen() {
899
995
  ) {
900
996
  return (
901
997
  <box flexDirection="column" paddingLeft={1} paddingRight={1}>
902
- <text fg="#7e57c2"><strong>claudeup Plugins</strong></text>
998
+ <text fg="#7e57c2">
999
+ <strong>claudeup Plugins</strong>
1000
+ </text>
903
1001
  <text fg="red">Error loading data</text>
904
1002
  </box>
905
1003
  );
@@ -942,7 +1040,13 @@ export function PluginsScreen() {
942
1040
  ? ` (${item.pluginCount})`
943
1041
  : "";
944
1042
  return (
945
- <text bg="magenta" fg="white"><strong> {arrow} {mp.displayName}{count} </strong></text>
1043
+ <text bg="magenta" fg="white">
1044
+ <strong>
1045
+ {" "}
1046
+ {arrow} {mp.displayName}
1047
+ {count}{" "}
1048
+ </strong>
1049
+ </text>
946
1050
  );
947
1051
  }
948
1052
 
@@ -962,7 +1066,10 @@ export function PluginsScreen() {
962
1066
  let statusIcon = "○";
963
1067
  let statusColor = "gray";
964
1068
 
965
- if (plugin.enabled) {
1069
+ if (plugin.isOrphaned) {
1070
+ statusIcon = "x";
1071
+ statusColor = "red";
1072
+ } else if (plugin.enabled) {
966
1073
  statusIcon = "●";
967
1074
  statusColor = "green";
968
1075
  } else if (plugin.installedVersion) {
@@ -972,7 +1079,9 @@ export function PluginsScreen() {
972
1079
 
973
1080
  // Build version string
974
1081
  let versionStr = "";
975
- if (plugin.installedVersion && plugin.installedVersion !== "0.0.0") {
1082
+ if (plugin.isOrphaned) {
1083
+ versionStr = " deprecated";
1084
+ } else if (plugin.installedVersion && plugin.installedVersion !== "0.0.0") {
976
1085
  versionStr = ` v${plugin.installedVersion}`;
977
1086
  if (plugin.hasUpdate && plugin.version) {
978
1087
  versionStr += ` → v${plugin.version}`;
@@ -996,9 +1105,26 @@ export function PluginsScreen() {
996
1105
  const displayName = segments
997
1106
  ? segments.map((seg) => seg.text).join("")
998
1107
  : plugin.name;
1108
+
1109
+ if (plugin.isOrphaned) {
1110
+ const ver = plugin.installedVersion && plugin.installedVersion !== "0.0.0"
1111
+ ? ` v${plugin.installedVersion}` : "";
1112
+ return (
1113
+ <text>
1114
+ <span fg="red">{" "}{statusIcon} </span>
1115
+ <span fg="gray">{displayName}</span>
1116
+ {ver && <span fg="yellow">{ver}</span>}
1117
+ <span fg="red"> deprecated</span>
1118
+ </text>
1119
+ );
1120
+ }
1121
+
999
1122
  return (
1000
1123
  <text>
1001
- <span fg={statusColor}>{" "}{statusIcon} </span>
1124
+ <span fg={statusColor}>
1125
+ {" "}
1126
+ {statusIcon}{" "}
1127
+ </span>
1002
1128
  <span>{displayName}</span>
1003
1129
  <span fg={plugin.hasUpdate ? "yellow" : "gray"}>{versionStr}</span>
1004
1130
  </text>
@@ -1042,15 +1168,23 @@ export function PluginsScreen() {
1042
1168
 
1043
1169
  return (
1044
1170
  <box flexDirection="column">
1045
- <text fg="cyan"><strong>{mp.displayName}{getBadge()}</strong></text>
1171
+ <text fg="cyan">
1172
+ <strong>
1173
+ {mp.displayName}
1174
+ {getBadge()}
1175
+ </strong>
1176
+ </text>
1046
1177
  <text fg="gray">{mp.description || "No description"}</text>
1047
1178
  <text fg={isEnabled ? "green" : "gray"}>
1048
1179
  {isEnabled ? "● Added" : "○ Not added"}
1049
1180
  </text>
1050
- <text fg="blue">github.com/{mp.source.repo}</text>
1181
+ <text fg="#5c9aff">github.com/{mp.source.repo}</text>
1051
1182
  <text>Plugins: {selectedItem.pluginCount || 0}</text>
1052
1183
  <box marginTop={1}>
1053
- <text bg={isEnabled ? "cyan" : "green"} fg="black"> Enter </text>
1184
+ <text bg={isEnabled ? "cyan" : "green"} fg="black">
1185
+ {" "}
1186
+ Enter{" "}
1187
+ </text>
1054
1188
  <text fg="gray"> {actionHint}</text>
1055
1189
  </box>
1056
1190
  {isEnabled && (
@@ -1066,6 +1200,31 @@ export function PluginsScreen() {
1066
1200
  const plugin = selectedItem.plugin;
1067
1201
  const isInstalled = plugin.enabled || plugin.installedVersion;
1068
1202
 
1203
+ // Orphaned/deprecated plugin
1204
+ if (plugin.isOrphaned) {
1205
+ return (
1206
+ <box flexDirection="column">
1207
+ <box justifyContent="center">
1208
+ <text bg="yellow" fg="black"><strong> {plugin.name} — DEPRECATED </strong></text>
1209
+ </box>
1210
+ <box marginTop={1}>
1211
+ <text fg="yellow">This plugin is no longer in the marketplace.</text>
1212
+ </box>
1213
+ <box marginTop={1}>
1214
+ <text fg="gray">It was removed from the marketplace but still referenced in your settings. Press d to uninstall and clean up.</text>
1215
+ </box>
1216
+ {isInstalled && (
1217
+ <box flexDirection="column" marginTop={2}>
1218
+ <box>
1219
+ <text bg="red" fg="white"> d </text>
1220
+ <text> Uninstall (recommended)</text>
1221
+ </box>
1222
+ </box>
1223
+ )}
1224
+ </box>
1225
+ );
1226
+ }
1227
+
1069
1228
  // Build component counts
1070
1229
  const components: string[] = [];
1071
1230
  if (plugin.agents?.length)
@@ -1089,7 +1248,13 @@ export function PluginsScreen() {
1089
1248
  <box flexDirection="column">
1090
1249
  {/* Plugin name header - centered */}
1091
1250
  <box justifyContent="center">
1092
- <text bg="magenta" fg="white"><strong> {plugin.name}{plugin.hasUpdate ? " ⬆" : ""} </strong></text>
1251
+ <text bg="magenta" fg="white">
1252
+ <strong>
1253
+ {" "}
1254
+ {plugin.name}
1255
+ {plugin.hasUpdate ? " ⬆" : ""}{" "}
1256
+ </strong>
1257
+ </text>
1093
1258
  </box>
1094
1259
 
1095
1260
  {/* Status line */}
@@ -1112,7 +1277,7 @@ export function PluginsScreen() {
1112
1277
  {showVersion && (
1113
1278
  <text>
1114
1279
  <span>Version </span>
1115
- <span fg="blue">v{plugin.version}</span>
1280
+ <span fg="#5c9aff">v{plugin.version}</span>
1116
1281
  {showInstalledVersion &&
1117
1282
  plugin.installedVersion !== plugin.version && (
1118
1283
  <span> (v{plugin.installedVersion} installed)</span>
@@ -1141,10 +1306,15 @@ export function PluginsScreen() {
1141
1306
  {/* Scope Status with shortcuts - each scope has its own color */}
1142
1307
  <box flexDirection="column" marginTop={1}>
1143
1308
  <text>────────────────────────</text>
1144
- <text><strong>Scopes:</strong></text>
1309
+ <text>
1310
+ <strong>Scopes:</strong>
1311
+ </text>
1145
1312
  <box marginTop={1} flexDirection="column">
1146
1313
  <text>
1147
- <span bg="cyan" fg="black"> u </span>
1314
+ <span bg="cyan" fg="black">
1315
+ {" "}
1316
+ u{" "}
1317
+ </span>
1148
1318
  <span fg={plugin.userScope?.enabled ? "cyan" : "gray"}>
1149
1319
  {plugin.userScope?.enabled ? " ● " : " ○ "}
1150
1320
  </span>
@@ -1155,7 +1325,10 @@ export function PluginsScreen() {
1155
1325
  )}
1156
1326
  </text>
1157
1327
  <text>
1158
- <span bg="green" fg="black"> p </span>
1328
+ <span bg="green" fg="black">
1329
+ {" "}
1330
+ p{" "}
1331
+ </span>
1159
1332
  <span fg={plugin.projectScope?.enabled ? "green" : "gray"}>
1160
1333
  {plugin.projectScope?.enabled ? " ● " : " ○ "}
1161
1334
  </span>
@@ -1166,7 +1339,10 @@ export function PluginsScreen() {
1166
1339
  )}
1167
1340
  </text>
1168
1341
  <text>
1169
- <span bg="yellow" fg="black"> l </span>
1342
+ <span bg="yellow" fg="black">
1343
+ {" "}
1344
+ l{" "}
1345
+ </span>
1170
1346
  <span fg={plugin.localScope?.enabled ? "yellow" : "gray"}>
1171
1347
  {plugin.localScope?.enabled ? " ● " : " ○ "}
1172
1348
  </span>
@@ -1184,12 +1360,18 @@ export function PluginsScreen() {
1184
1360
  <box flexDirection="column" marginTop={1}>
1185
1361
  {plugin.hasUpdate && (
1186
1362
  <box>
1187
- <text bg="magenta" fg="white"> U </text>
1363
+ <text bg="magenta" fg="white">
1364
+ {" "}
1365
+ U{" "}
1366
+ </text>
1188
1367
  <text> Update to v{plugin.version}</text>
1189
1368
  </box>
1190
1369
  )}
1191
1370
  <box>
1192
- <text bg="red" fg="white"> d </text>
1371
+ <text bg="red" fg="white">
1372
+ {" "}
1373
+ d{" "}
1374
+ </text>
1193
1375
  <text> Uninstall</text>
1194
1376
  </box>
1195
1377
  </box>
@@ -1201,9 +1383,10 @@ export function PluginsScreen() {
1201
1383
  return null;
1202
1384
  };
1203
1385
 
1204
- const footerHints = isSearchActive
1205
- ? "Type to search │ Enter Confirm │ Esc Cancel"
1206
- : "u/p/l:scopeU:updatea:alld:remove n:add │ t:team │ /:search";
1386
+ const footerHints =
1387
+ isSearchActive || pluginsState.searchQuery
1388
+ ? "↑↓:navEnter:selectEsc:cleartype to filter"
1389
+ : "u/p/l:scope │ U:update │ a:all │ d:remove │ s:profile │ type to search";
1207
1390
 
1208
1391
  // Calculate status for subtitle
1209
1392
  const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";