magus-cli 7.3.0 → 7.4.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 (130) hide show
  1. package/package.json +4 -4
  2. package/scripts/build-binaries.ts +4 -2
  3. package/src/__tests__/blind/c1-npm-package.test.ts +206 -0
  4. package/src/__tests__/blind/c10-colour-roles.test.ts +265 -0
  5. package/src/__tests__/blind/c11-cli-parse-refusal.test.ts +454 -0
  6. package/src/__tests__/blind/c12-skill-frontmatter.test.ts +323 -0
  7. package/src/__tests__/blind/c13-wrong-shape.test.ts +603 -0
  8. package/src/__tests__/blind/c14-cli-row-label.test.ts +259 -0
  9. package/src/__tests__/blind/c15-probe-ownership.test.ts +533 -0
  10. package/src/__tests__/blind/c16-nested-wrong-shape.test.ts +297 -0
  11. package/src/__tests__/blind/c17-enter-label.test.ts +625 -0
  12. package/src/__tests__/blind/c18-hook-shape.test.ts +354 -0
  13. package/src/__tests__/blind/c2-settings-parse.test.ts +350 -0
  14. package/src/__tests__/blind/c4-key-dispatch.test.ts +397 -0
  15. package/src/__tests__/blind/c5-keymap.test.ts +461 -0
  16. package/src/__tests__/blind/c6-request-gate.test.ts +133 -0
  17. package/src/__tests__/blind/c7-tab-tiers.test.ts +320 -0
  18. package/src/__tests__/blind/c8-text-width.test.ts +137 -0
  19. package/src/__tests__/blind/c9-cli-tool-version.test.ts +239 -0
  20. package/src/__tests__/blind/child-run.ts +114 -0
  21. package/src/__tests__/cli-tools-adapter.test.ts +5 -2
  22. package/src/__tests__/component-badge.test.ts +20 -36
  23. package/src/__tests__/footer-hints.test.ts +1 -1
  24. package/src/__tests__/manifest.test.ts +25 -17
  25. package/src/__tests__/marketplace-badge.test.ts +4 -4
  26. package/src/__tests__/models-screen-state.test.ts +36 -13
  27. package/src/__tests__/npm-package-name-api.test.ts +111 -0
  28. package/src/__tests__/npm-package-name.test.ts +280 -0
  29. package/src/__tests__/scope-squares.test.tsx +28 -114
  30. package/src/__tests__/shutdown.test.ts +578 -0
  31. package/src/__tests__/styles-manager.test.ts +4 -4
  32. package/src/__tests__/styles-screen-state.test.ts +26 -12
  33. package/src/__tests__/tabbar-layout.test.ts +44 -32
  34. package/src/__tests__/theme-adaptive-colors.test.ts +6 -59
  35. package/src/__tests__/tool-probe.test.ts +47 -22
  36. package/src/__tests__/upgrade-spawn.test.ts +355 -0
  37. package/src/__tests__/uppercase-keybindings.test.ts +25 -12
  38. package/src/cli/ansi.ts +2 -2
  39. package/src/cli/bootstrap.ts +33 -2
  40. package/src/cli/doctor.ts +105 -14
  41. package/src/cli/install.ts +8 -2
  42. package/src/cli/profile.ts +18 -1
  43. package/src/cli/router.ts +31 -18
  44. package/src/cli/update-view.ts +1 -1
  45. package/src/cli/upgrade.ts +37 -9
  46. package/src/data/cli-tools.ts +13 -2
  47. package/src/data/npm-package.ts +15 -0
  48. package/src/services/claude-settings.ts +151 -73
  49. package/src/services/cli-tool-commands.ts +15 -5
  50. package/src/services/cli-tool-version.ts +101 -0
  51. package/src/services/hook-registration.ts +23 -6
  52. package/src/services/manifest.ts +117 -52
  53. package/src/services/plugin-manager.ts +19 -5
  54. package/src/services/profile-adopt.ts +7 -2
  55. package/src/services/profile-startup.ts +87 -7
  56. package/src/services/profiles.ts +36 -23
  57. package/src/services/settings-file.ts +158 -4
  58. package/src/services/settings-manager.ts +19 -5
  59. package/src/services/skills-manager.ts +46 -0
  60. package/src/services/tool-probe.ts +272 -45
  61. package/src/services/toolchain.ts +39 -0
  62. package/src/services/update-engine.ts +5 -0
  63. package/src/services/update-plan.ts +9 -0
  64. package/src/services/version-check.ts +20 -10
  65. package/src/tui.tsx +48 -21
  66. package/src/ui/App.tsx +58 -197
  67. package/src/ui/adapters/cliToolsAdapter.ts +141 -12
  68. package/src/ui/adapters/pluginsAdapter.ts +10 -7
  69. package/src/ui/adapters/settingsAdapter.ts +0 -21
  70. package/src/ui/adapters/skillsAdapter.ts +27 -8
  71. package/src/ui/adapters/stylesAdapter.ts +15 -11
  72. package/src/ui/components/CategoryHeader.tsx +7 -1
  73. package/src/ui/components/EmptyFilterState.tsx +2 -2
  74. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  75. package/src/ui/components/ScreenErrorBoundary.tsx +105 -0
  76. package/src/ui/components/TabBar.tsx +93 -21
  77. package/src/ui/components/layout/FooterHints.tsx +6 -27
  78. package/src/ui/components/layout/ScreenLayout.tsx +68 -42
  79. package/src/ui/components/layout/index.ts +1 -5
  80. package/src/ui/components/modals/ConfirmModal.tsx +2 -19
  81. package/src/ui/components/modals/HelpOverlay.tsx +186 -0
  82. package/src/ui/components/modals/MessageModal.tsx +3 -15
  83. package/src/ui/components/modals/ModalContainer.tsx +99 -60
  84. package/src/ui/components/primitives/ErrorNotice.tsx +38 -0
  85. package/src/ui/components/primitives/ListCategoryRow.tsx +18 -7
  86. package/src/ui/components/primitives/MetaText.tsx +1 -1
  87. package/src/ui/components/primitives/ScopeDetail.tsx +31 -26
  88. package/src/ui/components/primitives/ScopeSquares.tsx +23 -28
  89. package/src/ui/components/primitives/SelectableRow.tsx +24 -1
  90. package/src/ui/components/primitives/index.ts +4 -3
  91. package/src/ui/hooks/useProfileStartupModal.ts +15 -3
  92. package/src/ui/input/KeyRouter.tsx +180 -0
  93. package/src/ui/input/key-dispatch.ts +149 -0
  94. package/src/ui/keymap.ts +898 -0
  95. package/src/ui/renderers/cliToolRenderers.tsx +128 -66
  96. package/src/ui/renderers/gitignoreRenderers.tsx +41 -43
  97. package/src/ui/renderers/markdownRenderer.tsx +2 -2
  98. package/src/ui/renderers/mcpRenderers.tsx +28 -14
  99. package/src/ui/renderers/modelRenderers.tsx +12 -13
  100. package/src/ui/renderers/modelVisuals.tsx +0 -2
  101. package/src/ui/renderers/pluginRenderers.tsx +47 -54
  102. package/src/ui/renderers/profileRenderers.tsx +32 -37
  103. package/src/ui/renderers/settingsRenderers.tsx +84 -55
  104. package/src/ui/renderers/skillRenderers.tsx +237 -190
  105. package/src/ui/renderers/styleRenderers.tsx +13 -2
  106. package/src/ui/runtime/shutdown.ts +268 -0
  107. package/src/ui/screens/AliasScreen.tsx +118 -100
  108. package/src/ui/screens/CliToolsScreen.tsx +98 -87
  109. package/src/ui/screens/EnvVarsScreen.tsx +44 -42
  110. package/src/ui/screens/GitignoreScreen.tsx +50 -55
  111. package/src/ui/screens/McpRegistryScreen.tsx +52 -86
  112. package/src/ui/screens/McpScreen.tsx +47 -33
  113. package/src/ui/screens/ModelsScreen.tsx +59 -104
  114. package/src/ui/screens/PluginsScreen.tsx +123 -127
  115. package/src/ui/screens/ProfilesScreen.tsx +49 -46
  116. package/src/ui/screens/SkillsScreen.tsx +184 -204
  117. package/src/ui/screens/StylesScreen.tsx +104 -157
  118. package/src/ui/state/AppContext.tsx +6 -0
  119. package/src/ui/state/DimensionsContext.tsx +1 -10
  120. package/src/ui/state/reducer.ts +5 -8
  121. package/src/ui/state/types.ts +11 -9
  122. package/src/ui/text-width.ts +341 -0
  123. package/src/ui/theme-mode.ts +34 -71
  124. package/src/ui/theme.ts +50 -28
  125. package/src/utils/request-gate.ts +37 -0
  126. package/src/ui/components/ScopeIndicator.tsx +0 -63
  127. package/src/ui/components/SearchInput.tsx +0 -57
  128. package/src/ui/hooks/index.ts +0 -2
  129. package/src/ui/hooks/useKeyboard.ts +0 -24
  130. package/src/ui/hooks/useKeyboardHandler.ts +0 -63
@@ -27,14 +27,17 @@ import {
27
27
  DetailSection,
28
28
  KeyValueLine,
29
29
  MetaText,
30
+ ScopeDot,
31
+ ScopeKeyChip,
30
32
  ScopeSquares,
31
33
  SelectableRow,
34
+ rowLead,
32
35
  } from "../components/primitives/index.js";
33
36
  import type {
34
37
  PluginContentsEntry,
35
38
  PluginSkillDetailEntry,
36
39
  } from "../state/types.js";
37
- import { componentBadge as badgeColors } from "../theme-mode.js";
40
+ import { componentChip } from "../theme-mode.js";
38
41
  import { type UiColor, theme } from "../theme.js";
39
42
  import { renderMarkdown } from "./markdownRenderer.js";
40
43
  import { type PluginRowSegment, pluginRowText } from "./pluginRowText.js";
@@ -58,7 +61,7 @@ function categoryRow(
58
61
  return (
59
62
  <SelectableRow selected={true}>
60
63
  <strong>
61
- {" "}
64
+ {rowLead(true)}
62
65
  {arrow} {mp.displayName}
63
66
  {versionLabel}
64
67
  {count}{" "}
@@ -67,14 +70,13 @@ function categoryRow(
67
70
  );
68
71
  }
69
72
 
70
- // Map tone to a statusColor for CategoryHeader (legacy component)
73
+ // The adapter names a role; the badge after the title wears its ink.
71
74
  const statusColorMap: Record<PluginCategoryItem["tone"], UiColor> = {
72
- yellow: theme.colors.warning,
73
- gray: theme.colors.muted,
74
- green: theme.colors.success,
75
- red: theme.colors.danger,
76
- purple: theme.colors.accent,
77
- teal: theme.colors.info,
75
+ neutral: theme.colors.muted,
76
+ info: theme.colors.info,
77
+ success: theme.colors.success,
78
+ warning: theme.colors.warning,
79
+ danger: theme.colors.danger,
78
80
  byline: theme.colors.byline,
79
81
  };
80
82
 
@@ -122,7 +124,7 @@ function categoryDetail(
122
124
  <text fg={isEnabled ? theme.colors.success : theme.colors.muted}>
123
125
  {isEnabled ? "● Added" : "○ Not added"}
124
126
  </text>
125
- <text fg={theme.colors.link}>github.com/{mp.source.repo}</text>
127
+ <text fg={theme.colors.info}>github.com/{mp.source.repo}</text>
126
128
  <text fg={theme.colors.text}>Plugins: {item.pluginCount}</text>
127
129
  <ActionHints
128
130
  hints={[
@@ -185,6 +187,9 @@ function RowSegments({
185
187
  );
186
188
  }
187
189
 
190
+ /** Drawn in the scope column of a plugin no marketplace we read lists. */
191
+ const ORPHAN_MARK = "✗";
192
+
188
193
  function pluginRow(
189
194
  item: PluginPluginItem,
190
195
  isSelected: boolean,
@@ -210,7 +215,7 @@ function pluginRow(
210
215
  if (isSelected) {
211
216
  return (
212
217
  <text bg={theme.selection.bg} fg={theme.selection.fg}>
213
- {" "}
218
+ {rowLead(true)}
214
219
  <ScopeSquares
215
220
  user={hasUser}
216
221
  project={hasProject}
@@ -233,12 +238,16 @@ function pluginRow(
233
238
  : plugin.name;
234
239
 
235
240
  if (plugin.isOrphaned) {
236
- // The red squares are the presence marker for "published by no marketplace
237
- // we read". WHAT is known about it — moved, withdrawn, or its marketplace
238
- // was never read — is the status suffix, the same words the CLI prints.
241
+ // ✗ in the scope column is the presence marker for "published by no
242
+ // marketplace we read" — not three squares, which would claim it is
243
+ // installed at every scope. WHAT is known about it — moved, withdrawn, or
244
+ // its marketplace was never read — is the status suffix, the same words
245
+ // the CLI prints.
239
246
  return (
240
247
  <text fg={theme.colors.text}>
241
- <span fg={theme.colors.danger}> ■■■ </span>
248
+ <span
249
+ fg={theme.colors.danger}
250
+ >{`${rowLead(false)} ${ORPHAN_MARK} `}</span>
242
251
  {/* An orphan is never expandable, but it keeps the arrow cell so its
243
252
  name lands in the same column as every other plugin's. */}
244
253
  <span>{" "}</span>
@@ -447,7 +456,7 @@ function pluginDetail(
447
456
  isInstalled
448
457
  ? theme.colors.success
449
458
  : brokenInstall
450
- ? theme.colors.warning
459
+ ? theme.colors.danger
451
460
  : theme.colors.muted
452
461
  }
453
462
  >
@@ -505,7 +514,7 @@ function pluginDetail(
505
514
  label="Version"
506
515
  value={
507
516
  <span>
508
- <span fg={theme.colors.link}>v{plugin.version}</span>
517
+ <span fg={theme.colors.info}>v{plugin.version}</span>
509
518
  {showInstalledVersion ? (
510
519
  <span> (v{plugin.installedVersion} installed)</span>
511
520
  ) : null}
@@ -541,7 +550,7 @@ function pluginDetail(
541
550
  {plugin.category ? (
542
551
  <KeyValueLine
543
552
  label="Category"
544
- value={<span fg={theme.colors.accent}>{plugin.category}</span>}
553
+ value={<span fg={theme.colors.info}>{plugin.category}</span>}
545
554
  />
546
555
  ) : null}
547
556
  {plugin.author ? (
@@ -553,9 +562,7 @@ function pluginDetail(
553
562
  {components.length > 0 ? (
554
563
  <KeyValueLine
555
564
  label="Contains"
556
- value={
557
- <span fg={theme.colors.warning}>{components.join(" · ")}</span>
558
- }
565
+ value={<span fg={theme.colors.info}>{components.join(" · ")}</span>}
559
566
  />
560
567
  ) : null}
561
568
  {item.isExpandable ? (
@@ -577,21 +584,18 @@ function pluginDetail(
577
584
  keyHint="u"
578
585
  name="User"
579
586
  qualifier="global"
580
- color={theme.scopes.user}
581
587
  scope={plugin.userScope}
582
588
  />
583
589
  <ScopeLine
584
590
  keyHint="p"
585
591
  name="Project"
586
592
  qualifier="team"
587
- color={theme.scopes.project}
588
593
  scope={plugin.projectScope}
589
594
  />
590
595
  <ScopeLine
591
596
  keyHint="l"
592
597
  name="Local"
593
598
  qualifier="private"
594
- color={theme.scopes.local}
595
599
  scope={plugin.localScope}
596
600
  />
597
601
  </box>
@@ -636,30 +640,25 @@ function ScopeLine({
636
640
  keyHint,
637
641
  name,
638
642
  qualifier,
639
- color,
640
643
  scope,
641
644
  }: {
642
645
  keyHint: string;
643
646
  name: string;
644
647
  qualifier: string;
645
- color: UiColor;
646
648
  scope?: ScopeStatus;
647
649
  }): React.ReactNode {
648
650
  const installed = isInstalledInScope(scope);
649
651
  return (
650
652
  <text fg={theme.colors.text}>
651
- <span bg={color} fg={theme.hints.fg}>
652
- {" "}
653
- {keyHint}{" "}
654
- </span>
655
- <span fg={installed ? color : theme.colors.muted}>
656
- {installed ? " ● " : " ○ "}
657
- </span>
658
- <span fg={color}>{name}</span>
659
- <span> {qualifier}</span>
660
- {scope?.version ? <span fg={color}> v{scope.version}</span> : null}
653
+ <ScopeKeyChip scopeKey={keyHint} />
654
+ <ScopeDot installed={installed} />
655
+ <span>{name}</span>
656
+ <span fg={theme.colors.muted}> {qualifier}</span>
657
+ {scope?.version ? (
658
+ <span fg={theme.colors.info}> v{scope.version}</span>
659
+ ) : null}
661
660
  {!installed && scope?.enabled ? (
662
- <span fg={theme.colors.warning}> enabled, not installed</span>
661
+ <span fg={theme.colors.danger}> enabled, not installed</span>
663
662
  ) : null}
664
663
  </text>
665
664
  );
@@ -702,7 +701,7 @@ function ReleaseHistory({
702
701
  ? theme.colors.warning
703
702
  : isInstalled
704
703
  ? theme.colors.success
705
- : theme.colors.link;
704
+ : theme.colors.info;
706
705
 
707
706
  return (
708
707
  <box key={release.version} flexDirection="column" marginBottom={1}>
@@ -795,13 +794,6 @@ export const COMPONENT_ICON: Record<PluginComponentKind, string> = {
795
794
  mcp: "⬢",
796
795
  };
797
796
 
798
- const COMPONENT_COLOR: Record<PluginComponentKind, UiColor> = {
799
- skill: theme.colors.accent,
800
- command: theme.colors.info,
801
- agent: theme.colors.link,
802
- mcp: theme.colors.warning,
803
- };
804
-
805
797
  const COMPONENT_NOUN: Record<PluginComponentKind, string> = {
806
798
  skill: "Skill",
807
799
  command: "Command",
@@ -829,7 +821,7 @@ function componentBadge(
829
821
  const icon = COMPONENT_ICON[kind];
830
822
  if (isSelected) return ` ${icon} `;
831
823
 
832
- const chip = badgeColors(kind);
824
+ const chip = componentChip();
833
825
  return <span bg={chip.bg} fg={chip.fg}>{` ${icon} `}</span>;
834
826
  }
835
827
 
@@ -845,7 +837,8 @@ function componentRow(
845
837
  if (isSelected) {
846
838
  return (
847
839
  <text bg={theme.selection.bg} fg={theme.selection.fg}>
848
- {COMPONENT_INDENT}
840
+ {rowLead(true)}
841
+ {COMPONENT_INDENT.slice(1)}
849
842
  {componentBadge(component.kind, true)}
850
843
  {`${" ".repeat(BADGE_GAP)}${label} `}
851
844
  </text>
@@ -868,7 +861,7 @@ function contentsStatusRow(
868
861
  if (isSelected) {
869
862
  return (
870
863
  <text bg={theme.selection.bg} fg={theme.selection.fg}>
871
- {`${pad}${item.label} `}
864
+ {`${rowLead(true)}${pad.slice(1)}${item.label} `}
872
865
  </text>
873
866
  );
874
867
  }
@@ -938,7 +931,7 @@ function componentDetail(
938
931
  value={
939
932
  <span>
940
933
  {componentBadge(component.kind, false)}
941
- <span fg={COMPONENT_COLOR[component.kind]}>
934
+ <span fg={theme.colors.text}>
942
935
  {` ${COMPONENT_NOUN[component.kind]}`}
943
936
  </span>
944
937
  </span>
@@ -946,29 +939,29 @@ function componentDetail(
946
939
  />
947
940
  <KeyValueLine
948
941
  label="Plugin"
949
- value={<span fg={theme.colors.accent}>{plugin.name}</span>}
942
+ value={<span fg={theme.colors.info}>{plugin.name}</span>}
950
943
  />
951
944
  {component.group ? (
952
945
  <KeyValueLine
953
946
  label="Group"
954
- value={<span fg={theme.colors.accent}>{component.group}</span>}
947
+ value={<span fg={theme.colors.info}>{component.group}</span>}
955
948
  />
956
949
  ) : null}
957
950
  {component.mcpCommand ? (
958
951
  <KeyValueLine
959
952
  label="Runs"
960
- value={<span fg={theme.colors.link}>{component.mcpCommand}</span>}
953
+ value={<span fg={theme.colors.info}>{component.mcpCommand}</span>}
961
954
  />
962
955
  ) : null}
963
956
  <KeyValueLine
964
957
  label="Path"
965
- value={<span fg={theme.colors.link}>{component.repoPath}</span>}
958
+ value={<span fg={theme.colors.info}>{component.repoPath}</span>}
966
959
  />
967
960
  {rest.map((field) => (
968
961
  <KeyValueLine
969
962
  key={field.key}
970
963
  label={field.key}
971
- value={<span fg={theme.colors.warning}>{field.value}</span>}
964
+ value={<span fg={theme.colors.text}>{field.value}</span>}
972
965
  />
973
966
  ))}
974
967
  </box>
@@ -1,13 +1,14 @@
1
1
  import type React from "react";
2
2
  import type { PredefinedProfile } from "../../data/predefined-profiles.js";
3
3
  import type { ProfileEntry } from "../../types/index.js";
4
+ import { ListCategoryRow, rowLead } from "../components/primitives/index.js";
4
5
  import type { ItemRenderer } from "../registry.js";
5
- import { type UiColor, theme } from "../theme.js";
6
+ import { theme } from "../theme.js";
6
7
 
7
8
  // ─── List item types ───────────────────────────────────────────────────────────
8
9
 
9
10
  export type ProfileListItem =
10
- | { kind: "header"; label: string; color: UiColor; count: number }
11
+ | { kind: "header"; label: string; count: number }
11
12
  | { kind: "predefined"; profile: PredefinedProfile }
12
13
  | { kind: "saved"; entry: ProfileEntry };
13
14
 
@@ -92,19 +93,20 @@ export function formatDate(iso: string | undefined): string {
92
93
 
93
94
  // ─── Header renderer ───────────────────────────────────────────────────────────
94
95
 
96
+ /** A section label. Neutral: both sections are just groups, and the cursor
97
+ * steps over them, so a header wearing the accent would read as the cursor. */
95
98
  const headerRenderer: ItemRenderer<{
96
99
  kind: "header";
97
100
  label: string;
98
- color: UiColor;
99
101
  count: number;
100
102
  }> = {
101
- renderRow: ({ item }) => (
102
- <text bg={item.color} fg={theme.hints.fg}>
103
- <strong>
104
- {" "}
105
- {item.label} ({item.count}){" "}
106
- </strong>
107
- </text>
103
+ renderRow: ({ item, isSelected }) => (
104
+ <ListCategoryRow
105
+ selected={isSelected}
106
+ title={item.label}
107
+ count={item.count}
108
+ expandable={false}
109
+ />
108
110
  ),
109
111
  renderDetail: () => (
110
112
  <text fg={theme.colors.muted}>Select a profile to see details</text>
@@ -129,8 +131,7 @@ const predefinedRenderer: ItemRenderer<{
129
131
  if (isSelected) {
130
132
  return (
131
133
  <text bg={theme.selection.bg} fg={theme.selection.fg}>
132
- {" "}
133
- {profile.name} {countStr}{" "}
134
+ {rowLead(true)} {profile.name} {countStr}{" "}
134
135
  </text>
135
136
  );
136
137
  }
@@ -174,8 +175,8 @@ const predefinedRenderer: ItemRenderer<{
174
175
  >{`\nMagus (${profile.magusPlugins.length})`}</text>
175
176
  </box>
176
177
  <box>
177
- <text fg={theme.colors.info}>
178
- {profile.magusPlugins.map((p) => ` ■ ${p}`).join("\n")}
178
+ <text fg={theme.colors.text}>
179
+ {profile.magusPlugins.map((p) => ` • ${p}`).join("\n")}
179
180
  </text>
180
181
  </box>
181
182
 
@@ -186,8 +187,8 @@ const predefinedRenderer: ItemRenderer<{
186
187
  >{`\nAnthropic (${profile.anthropicPlugins.length})`}</text>
187
188
  </box>
188
189
  <box>
189
- <text fg={theme.colors.accent}>
190
- {profile.anthropicPlugins.map((p) => ` ■ ${p}`).join("\n")}
190
+ <text fg={theme.colors.text}>
191
+ {profile.anthropicPlugins.map((p) => ` • ${p}`).join("\n")}
191
192
  </text>
192
193
  </box>
193
194
 
@@ -200,8 +201,8 @@ const predefinedRenderer: ItemRenderer<{
200
201
  >{`\nSkills (${profile.skills.length})`}</text>
201
202
  </box>
202
203
  <box>
203
- <text fg={theme.colors.warning}>
204
- {profile.skills.map((s) => ` ■ ${s}`).join("\n")}
204
+ <text fg={theme.colors.text}>
205
+ {profile.skills.map((s) => ` • ${s}`).join("\n")}
205
206
  </text>
206
207
  </box>
207
208
  </>
@@ -256,22 +257,22 @@ const savedRenderer: ItemRenderer<{ kind: "saved"; entry: ProfileEntry }> = {
256
257
  if (isSelected) {
257
258
  return (
258
259
  <text bg={theme.selection.bg} fg={theme.selection.fg}>
259
- {" "}
260
+ {rowLead(true)}
260
261
  {marker} {label}{" "}
261
262
  </text>
262
263
  );
263
264
  }
264
265
 
265
- const scopeColor =
266
- entry.scope === "user" ? theme.scopes.user : theme.scopes.project;
266
+ // The words say the scope; it needs no hue of its own.
267
267
  const scopeLabel = entry.scope === "user" ? "[user]" : "[proj]";
268
268
 
269
269
  return (
270
270
  <text fg={theme.colors.text}>
271
271
  <span fg={entry.active ? theme.colors.success : theme.colors.text}>
272
+ {rowLead(false)}
272
273
  {marker}{" "}
273
274
  </span>
274
- <span fg={scopeColor}>{scopeLabel} </span>
275
+ <span fg={theme.colors.muted}>{scopeLabel} </span>
275
276
  <span fg={theme.colors.text}>{label}</span>
276
277
  </text>
277
278
  );
@@ -281,10 +282,6 @@ const savedRenderer: ItemRenderer<{ kind: "saved"; entry: ProfileEntry }> = {
281
282
  const { entry: selectedProfile } = item;
282
283
  const plugins = Object.keys(selectedProfile.plugins ?? {});
283
284
  const cleanPlugins = plugins.map(stripSuffix);
284
- const scopeColor =
285
- selectedProfile.scope === "user"
286
- ? theme.scopes.user
287
- : theme.scopes.project;
288
285
  const scopeLabel =
289
286
  selectedProfile.scope === "user"
290
287
  ? "User (~/.claude/profiles.json)"
@@ -295,9 +292,9 @@ const savedRenderer: ItemRenderer<{ kind: "saved"; entry: ProfileEntry }> = {
295
292
  <text fg={theme.colors.info}>
296
293
  <strong>{selectedProfile.name}</strong>
297
294
  </text>
298
- <box marginTop={1}>
295
+ <box flexDirection="row" marginTop={1}>
299
296
  <text fg={theme.colors.muted}>Scope: </text>
300
- <text fg={scopeColor}>{scopeLabel}</text>
297
+ <text fg={theme.colors.text}>{scopeLabel}</text>
301
298
  {/* Stated in words as well as the row glyph: the detail pane is
302
299
  where someone looks to check, and "●" alone is not an answer
303
300
  to "is this the one that is running?". */}
@@ -330,36 +327,36 @@ const savedRenderer: ItemRenderer<{ kind: "saved"; entry: ProfileEntry }> = {
330
327
  )}
331
328
  </box>
332
329
  <box marginTop={2} flexDirection="column">
333
- <box>
330
+ <box flexDirection="row">
334
331
  <text bg={theme.selection.bg} fg={theme.selection.fg}>
335
332
  {" "}
336
333
  Enter/a{" "}
337
334
  </text>
338
335
  <text fg={theme.colors.muted}> Apply profile</text>
339
336
  </box>
340
- <box marginTop={1}>
337
+ <box flexDirection="row" marginTop={1}>
341
338
  <text bg={theme.colors.dim} fg={theme.hints.fg}>
342
339
  {" "}
343
340
  r{" "}
344
341
  </text>
345
342
  <text fg={theme.colors.muted}> Rename</text>
346
343
  </box>
347
- <box marginTop={1}>
344
+ <box flexDirection="row" marginTop={1}>
348
345
  <text bg={theme.colors.danger} fg={theme.hints.fg}>
349
346
  {" "}
350
347
  d{" "}
351
348
  </text>
352
349
  <text fg={theme.colors.muted}> Delete</text>
353
350
  </box>
354
- <box marginTop={1}>
355
- <text bg={theme.colors.link} fg={theme.hints.fg}>
351
+ <box flexDirection="row" marginTop={1}>
352
+ <text bg={theme.colors.dim} fg={theme.hints.fg}>
356
353
  {" "}
357
354
  c{" "}
358
355
  </text>
359
356
  <text fg={theme.colors.muted}> Copy JSON to clipboard</text>
360
357
  </box>
361
- <box marginTop={1}>
362
- <text bg={theme.colors.success} fg={theme.hints.fg}>
358
+ <box flexDirection="row" marginTop={1}>
359
+ <text bg={theme.colors.dim} fg={theme.hints.fg}>
363
360
  {" "}
364
361
  i{" "}
365
362
  </text>
@@ -410,7 +407,6 @@ export function buildProfileListItems(
410
407
  items.push({
411
408
  kind: "header",
412
409
  label: "Presets",
413
- color: theme.colors.accent,
414
410
  count: PREDEFINED_PROFILES.length,
415
411
  });
416
412
  for (const p of PREDEFINED_PROFILES) {
@@ -419,7 +415,6 @@ export function buildProfileListItems(
419
415
  items.push({
420
416
  kind: "header",
421
417
  label: "Your Profiles",
422
- color: theme.colors.info,
423
418
  count: profileList.length,
424
419
  });
425
420
  for (const e of profileList) {