opencode-account-manager 0.6.4 → 0.6.6

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 (86) hide show
  1. package/README.md +235 -216
  2. package/README_VI.md +235 -216
  3. package/dist/cli.js +83 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/config-store.d.ts +12 -0
  6. package/dist/core/config-store.d.ts.map +1 -1
  7. package/dist/core/config-store.js +98 -0
  8. package/dist/core/config-store.js.map +1 -1
  9. package/dist/core/health-log.d.ts +9 -0
  10. package/dist/core/health-log.d.ts.map +1 -0
  11. package/dist/core/health-log.js +154 -0
  12. package/dist/core/health-log.js.map +1 -0
  13. package/dist/core/health-oauth.d.ts +5 -0
  14. package/dist/core/health-oauth.d.ts.map +1 -0
  15. package/dist/core/health-oauth.js +147 -0
  16. package/dist/core/health-oauth.js.map +1 -0
  17. package/dist/core/health-orchestrator.d.ts +32 -0
  18. package/dist/core/health-orchestrator.d.ts.map +1 -0
  19. package/dist/core/health-orchestrator.js +148 -0
  20. package/dist/core/health-orchestrator.js.map +1 -0
  21. package/dist/core/health-utils.d.ts +15 -0
  22. package/dist/core/health-utils.d.ts.map +1 -0
  23. package/dist/core/health-utils.js +60 -0
  24. package/dist/core/health-utils.js.map +1 -0
  25. package/dist/core/paths.d.ts +1 -0
  26. package/dist/core/paths.d.ts.map +1 -1
  27. package/dist/core/paths.js +4 -0
  28. package/dist/core/paths.js.map +1 -1
  29. package/dist/core/types.d.ts +26 -0
  30. package/dist/core/types.d.ts.map +1 -1
  31. package/dist/tui/Dashboard.d.ts.map +1 -1
  32. package/dist/tui/Dashboard.js +69 -2
  33. package/dist/tui/Dashboard.js.map +1 -1
  34. package/dist/tui/components/AccountList.d.ts +5 -3
  35. package/dist/tui/components/AccountList.d.ts.map +1 -1
  36. package/dist/tui/components/AccountList.js +9 -3
  37. package/dist/tui/components/AccountList.js.map +1 -1
  38. package/dist/tui/components/DashboardView.d.ts +3 -2
  39. package/dist/tui/components/DashboardView.d.ts.map +1 -1
  40. package/dist/tui/components/DashboardView.js +102 -17
  41. package/dist/tui/components/DashboardView.js.map +1 -1
  42. package/dist/tui/components/HealthBadge.d.ts +9 -0
  43. package/dist/tui/components/HealthBadge.d.ts.map +1 -0
  44. package/dist/tui/components/HealthBadge.js +56 -0
  45. package/dist/tui/components/HealthBadge.js.map +1 -0
  46. package/dist/tui/components/StatusBadge.d.ts +2 -1
  47. package/dist/tui/components/StatusBadge.d.ts.map +1 -1
  48. package/dist/tui/components/StatusBadge.js +30 -2
  49. package/dist/tui/components/StatusBadge.js.map +1 -1
  50. package/dist/tui/components/index.d.ts +1 -0
  51. package/dist/tui/components/index.d.ts.map +1 -1
  52. package/dist/tui/components/index.js +3 -1
  53. package/dist/tui/components/index.js.map +1 -1
  54. package/docs/BLUEPRINT.md +476 -476
  55. package/docs/ROADMAP.md +125 -107
  56. package/package.json +38 -38
  57. package/src/cli.ts +139 -38
  58. package/src/core/config-store.ts +278 -171
  59. package/src/core/crypto.ts +162 -162
  60. package/src/core/health-log.ts +173 -0
  61. package/src/core/health-oauth.ts +190 -0
  62. package/src/core/health-orchestrator.ts +224 -0
  63. package/src/core/importers/amExport.ts +177 -177
  64. package/src/core/opencode-config.ts +217 -217
  65. package/src/core/paths.ts +10 -6
  66. package/src/core/types.ts +193 -147
  67. package/src/tui/Dashboard.tsx +557 -478
  68. package/src/tui/components/AccountList.tsx +122 -104
  69. package/src/tui/components/ActionPalette.tsx +117 -117
  70. package/src/tui/components/Box.tsx +7 -7
  71. package/src/tui/components/DashboardView.tsx +324 -220
  72. package/src/tui/components/ExportModal.tsx +255 -255
  73. package/src/tui/components/FileBrowser.tsx +393 -393
  74. package/src/tui/components/Header.tsx +26 -26
  75. package/src/tui/components/HealthBadge.tsx +64 -0
  76. package/src/tui/components/ImportModal.tsx +334 -334
  77. package/src/tui/components/McpServerList.tsx +67 -67
  78. package/src/tui/components/Menu.tsx +61 -61
  79. package/src/tui/components/PasswordInput.tsx +159 -159
  80. package/src/tui/components/ProviderList.tsx +59 -59
  81. package/src/tui/components/SectionBox.tsx +35 -35
  82. package/src/tui/components/StatsRow.tsx +33 -33
  83. package/src/tui/components/StatusBadge.tsx +36 -3
  84. package/src/tui/components/index.ts +15 -14
  85. package/test-minimal.js +26 -26
  86. package/test-with-accounts.js +58 -58
@@ -1,67 +1,67 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import { McpServerInfo } from "../../core/opencode-config";
4
-
5
- interface McpServerListProps {
6
- servers: McpServerInfo[];
7
- }
8
-
9
- export function McpServerList({ servers }: McpServerListProps) {
10
- if (servers.length === 0) {
11
- return (
12
- <Box paddingX={1}>
13
- <Text dimColor>No MCP servers configured</Text>
14
- </Box>
15
- );
16
- }
17
-
18
- return (
19
- <Box flexDirection="column" paddingX={1}>
20
- {/* Header */}
21
- <Box>
22
- <Box width={20}>
23
- <Text bold dimColor>SERVER</Text>
24
- </Box>
25
- <Box width={10}>
26
- <Text bold dimColor>STATUS</Text>
27
- </Box>
28
- <Box width={8}>
29
- <Text bold dimColor>ENV</Text>
30
- </Box>
31
- <Box>
32
- <Text bold dimColor>COMMAND</Text>
33
- </Box>
34
- </Box>
35
-
36
- {/* Rows */}
37
- {servers.map((server) => (
38
- <Box key={server.id}>
39
- <Box width={20}>
40
- <Text>{truncate(server.id, 18)}</Text>
41
- </Box>
42
- <Box width={10}>
43
- {server.enabled ? (
44
- <Text>enabled</Text>
45
- ) : (
46
- <Text dimColor>disabled</Text>
47
- )}
48
- </Box>
49
- <Box width={8}>
50
- {server.hasEnvVars ? (
51
- <Text>{server.envVarCount}</Text>
52
- ) : (
53
- <Text dimColor>-</Text>
54
- )}
55
- </Box>
56
- <Box>
57
- <Text dimColor>{truncate(server.command, 40)}</Text>
58
- </Box>
59
- </Box>
60
- ))}
61
- </Box>
62
- );
63
- }
64
-
65
- function truncate(str: string, len: number): string {
66
- return str.length > len ? str.slice(0, len - 1) + "…" : str;
67
- }
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import { McpServerInfo } from "../../core/opencode-config";
4
+
5
+ interface McpServerListProps {
6
+ servers: McpServerInfo[];
7
+ }
8
+
9
+ export function McpServerList({ servers }: McpServerListProps) {
10
+ if (servers.length === 0) {
11
+ return (
12
+ <Box paddingX={1}>
13
+ <Text dimColor>No MCP servers configured</Text>
14
+ </Box>
15
+ );
16
+ }
17
+
18
+ return (
19
+ <Box flexDirection="column" paddingX={1}>
20
+ {/* Header */}
21
+ <Box>
22
+ <Box width={20}>
23
+ <Text bold dimColor>SERVER</Text>
24
+ </Box>
25
+ <Box width={10}>
26
+ <Text bold dimColor>STATUS</Text>
27
+ </Box>
28
+ <Box width={8}>
29
+ <Text bold dimColor>ENV</Text>
30
+ </Box>
31
+ <Box>
32
+ <Text bold dimColor>COMMAND</Text>
33
+ </Box>
34
+ </Box>
35
+
36
+ {/* Rows */}
37
+ {servers.map((server) => (
38
+ <Box key={server.id}>
39
+ <Box width={20}>
40
+ <Text>{truncate(server.id, 18)}</Text>
41
+ </Box>
42
+ <Box width={10}>
43
+ {server.enabled ? (
44
+ <Text>enabled</Text>
45
+ ) : (
46
+ <Text dimColor>disabled</Text>
47
+ )}
48
+ </Box>
49
+ <Box width={8}>
50
+ {server.hasEnvVars ? (
51
+ <Text>{server.envVarCount}</Text>
52
+ ) : (
53
+ <Text dimColor>-</Text>
54
+ )}
55
+ </Box>
56
+ <Box>
57
+ <Text dimColor>{truncate(server.command, 40)}</Text>
58
+ </Box>
59
+ </Box>
60
+ ))}
61
+ </Box>
62
+ );
63
+ }
64
+
65
+ function truncate(str: string, len: number): string {
66
+ return str.length > len ? str.slice(0, len - 1) + "…" : str;
67
+ }
@@ -1,19 +1,19 @@
1
1
  import React from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
 
4
- export type MenuAction =
5
- | "refresh"
6
- | "export"
7
- | "export-selected"
8
- | "import-file"
9
- | "import-am"
10
- | "toggle-select-mode"
11
- | "select-all"
12
- | "select-none"
13
- | "enable-selected"
14
- | "disable-selected"
15
- | "delete-selected"
16
- | "quit";
4
+ export type MenuAction =
5
+ | "refresh"
6
+ | "export"
7
+ | "export-selected"
8
+ | "import-file"
9
+ | "import-am"
10
+ | "toggle-select-mode"
11
+ | "select-all"
12
+ | "select-none"
13
+ | "enable-selected"
14
+ | "disable-selected"
15
+ | "delete-selected"
16
+ | "quit";
17
17
 
18
18
  interface MenuItem {
19
19
  label: string;
@@ -23,21 +23,21 @@ interface MenuItem {
23
23
  normalModeOnly?: boolean;
24
24
  }
25
25
 
26
- const MENU_ITEMS: MenuItem[] = [
27
- { label: "Refresh", key: "R", action: "refresh" },
28
- { label: "Export", key: "E", action: "export", normalModeOnly: true },
29
- { label: "Import", key: "I", action: "import-file", normalModeOnly: true },
30
- { label: "AM Import", key: "A", action: "import-am", normalModeOnly: true },
31
- { label: "Select Mode", key: "S", action: "toggle-select-mode", normalModeOnly: true },
32
- { label: "Exit Select", key: "S", action: "toggle-select-mode", selectModeOnly: true },
33
- { label: "All", key: "A", action: "select-all", selectModeOnly: true },
34
- { label: "None", key: "N", action: "select-none", selectModeOnly: true },
35
- { label: "Enable", key: "E", action: "enable-selected", selectModeOnly: true },
36
- { label: "Disable", key: "D", action: "disable-selected", selectModeOnly: true },
37
- { label: "Export", key: "X", action: "export-selected", selectModeOnly: true },
38
- { label: "Delete", key: "DEL", action: "delete-selected", selectModeOnly: true },
39
- { label: "Quit", key: "Q", action: "quit" },
40
- ];
26
+ const MENU_ITEMS: MenuItem[] = [
27
+ { label: "Refresh", key: "R", action: "refresh" },
28
+ { label: "Export", key: "E", action: "export", normalModeOnly: true },
29
+ { label: "Import", key: "I", action: "import-file", normalModeOnly: true },
30
+ { label: "AM Import", key: "A", action: "import-am", normalModeOnly: true },
31
+ { label: "Select Mode", key: "S", action: "toggle-select-mode", normalModeOnly: true },
32
+ { label: "Exit Select", key: "S", action: "toggle-select-mode", selectModeOnly: true },
33
+ { label: "All", key: "A", action: "select-all", selectModeOnly: true },
34
+ { label: "None", key: "N", action: "select-none", selectModeOnly: true },
35
+ { label: "Enable", key: "E", action: "enable-selected", selectModeOnly: true },
36
+ { label: "Disable", key: "D", action: "disable-selected", selectModeOnly: true },
37
+ { label: "Export", key: "X", action: "export-selected", selectModeOnly: true },
38
+ { label: "Delete", key: "DEL", action: "delete-selected", selectModeOnly: true },
39
+ { label: "Quit", key: "Q", action: "quit" },
40
+ ];
41
41
 
42
42
  interface MenuBarProps {
43
43
  onSelect: (action: MenuAction) => void;
@@ -46,30 +46,30 @@ interface MenuBarProps {
46
46
  }
47
47
 
48
48
  export function MenuBar({ onSelect, selectMode = false, selectedCount = 0 }: MenuBarProps) {
49
- useInput((input, key) => {
50
- const lower = input.toLowerCase();
51
-
52
- if (selectMode) {
53
- // Select mode keys
54
- if (lower === "s" || key.escape) onSelect("toggle-select-mode");
55
- if (lower === "a") onSelect("select-all");
56
- if (lower === "n") onSelect("select-none");
57
- if (lower === "e") onSelect("enable-selected");
58
- if (lower === "d") onSelect("disable-selected");
59
- if (lower === "x") onSelect("export-selected");
60
- if (key.delete || lower === "backspace") onSelect("delete-selected");
61
- if (lower === "r") onSelect("refresh");
62
- if (lower === "q") onSelect("quit");
63
- } else {
64
- // Normal mode keys
65
- if (lower === "r") onSelect("refresh");
66
- if (lower === "e") onSelect("export");
67
- if (lower === "i") onSelect("import-file");
68
- if (lower === "a") onSelect("import-am");
69
- if (lower === "s") onSelect("toggle-select-mode");
70
- if (lower === "q" || key.escape) onSelect("quit");
71
- }
72
- });
49
+ useInput((input, key) => {
50
+ const lower = input.toLowerCase();
51
+
52
+ if (selectMode) {
53
+ // Select mode keys
54
+ if (lower === "s" || key.escape) onSelect("toggle-select-mode");
55
+ if (lower === "a") onSelect("select-all");
56
+ if (lower === "n") onSelect("select-none");
57
+ if (lower === "e") onSelect("enable-selected");
58
+ if (lower === "d") onSelect("disable-selected");
59
+ if (lower === "x") onSelect("export-selected");
60
+ if (key.delete || lower === "backspace") onSelect("delete-selected");
61
+ if (lower === "r") onSelect("refresh");
62
+ if (lower === "q") onSelect("quit");
63
+ } else {
64
+ // Normal mode keys
65
+ if (lower === "r") onSelect("refresh");
66
+ if (lower === "e") onSelect("export");
67
+ if (lower === "i") onSelect("import-file");
68
+ if (lower === "a") onSelect("import-am");
69
+ if (lower === "s") onSelect("toggle-select-mode");
70
+ if (lower === "q" || key.escape) onSelect("quit");
71
+ }
72
+ });
73
73
 
74
74
  const visibleItems = MENU_ITEMS.filter(item => {
75
75
  if (selectMode && item.normalModeOnly) return false;
@@ -78,17 +78,17 @@ export function MenuBar({ onSelect, selectMode = false, selectedCount = 0 }: Men
78
78
  });
79
79
 
80
80
  return (
81
- <Box flexDirection="column">
82
- {selectMode && (
83
- <Box marginBottom={1} paddingX={1}>
84
- <Text dimColor bold>
85
- SELECT MODE - {selectedCount} selected | ↑↓ navigate | SPACE toggle | ←→ switch section
86
- </Text>
87
- </Box>
88
- )}
81
+ <Box flexDirection="column">
82
+ {selectMode && (
83
+ <Box marginBottom={1} paddingX={1}>
84
+ <Text dimColor bold>
85
+ SELECT MODE - {selectedCount} selected | ↑↓ navigate | SPACE toggle | ←→ switch section
86
+ </Text>
87
+ </Box>
88
+ )}
89
89
  <Box
90
90
  borderStyle="single"
91
- borderColor={selectMode ? "white" : "gray"}
91
+ borderColor={selectMode ? "white" : "gray"}
92
92
  paddingX={1}
93
93
  justifyContent="space-between"
94
94
  >
@@ -1,159 +1,159 @@
1
- import React, { useState } from "react";
2
- import { Box, Text, useInput } from "ink";
3
-
4
- interface PasswordInputProps {
5
- mode: "single" | "confirm";
6
- title?: string;
7
- subtitle?: string;
8
- warning?: string;
9
- onSubmit: (password: string) => void;
10
- onCancel: () => void;
11
- }
12
-
13
- export function PasswordInput({
14
- mode,
15
- title = "Enter Password",
16
- subtitle,
17
- warning,
18
- onSubmit,
19
- onCancel,
20
- }: PasswordInputProps) {
21
- const [password, setPassword] = useState("");
22
- const [confirmPassword, setConfirmPassword] = useState("");
23
- const [activeField, setActiveField] = useState<"password" | "confirm">("password");
24
- const [error, setError] = useState<string | null>(null);
25
-
26
- useInput((input, key) => {
27
- // Handle escape
28
- if (key.escape) {
29
- onCancel();
30
- return;
31
- }
32
-
33
- // Handle tab to switch fields (in confirm mode)
34
- if (key.tab && mode === "confirm") {
35
- setActiveField(prev => prev === "password" ? "confirm" : "password");
36
- return;
37
- }
38
-
39
- // Handle enter
40
- if (key.return) {
41
- if (mode === "confirm" && activeField === "password") {
42
- // Move to confirm field
43
- setActiveField("confirm");
44
- return;
45
- }
46
-
47
- // Validate
48
- if (password.length === 0) {
49
- setError("Password cannot be empty");
50
- return;
51
- }
52
-
53
- if (mode === "confirm" && password !== confirmPassword) {
54
- setError("Passwords do not match");
55
- return;
56
- }
57
-
58
- onSubmit(password);
59
- return;
60
- }
61
-
62
- // Handle backspace
63
- if (key.backspace || key.delete) {
64
- if (activeField === "password") {
65
- setPassword(prev => prev.slice(0, -1));
66
- } else {
67
- setConfirmPassword(prev => prev.slice(0, -1));
68
- }
69
- setError(null);
70
- return;
71
- }
72
-
73
- // Handle regular input (printable characters)
74
- if (input && input.length === 1 && !key.ctrl && !key.meta) {
75
- if (activeField === "password") {
76
- setPassword(prev => prev + input);
77
- } else {
78
- setConfirmPassword(prev => prev + input);
79
- }
80
- setError(null);
81
- }
82
- });
83
-
84
- const maskPassword = (pwd: string): string => {
85
- return "•".repeat(pwd.length);
86
- };
87
-
88
- return (
89
- <Box
90
- flexDirection="column"
91
- borderStyle="round"
92
- borderColor="gray"
93
- paddingX={2}
94
- paddingY={1}
95
- >
96
- {/* Title */}
97
- <Box marginBottom={1}>
98
- <Text bold>{title}</Text>
99
- {subtitle && <Text dimColor> - {subtitle}</Text>}
100
- </Box>
101
-
102
- {/* Password field */}
103
- <Box>
104
- <Text dimColor>Password: </Text>
105
- <Box
106
- borderStyle={activeField === "password" ? "single" : undefined}
107
- borderColor="yellow"
108
- paddingX={1}
109
- minWidth={30}
110
- >
111
- <Text color={activeField === "password" ? "white" : "gray"}>
112
- {maskPassword(password)}
113
- {activeField === "password" && <Text color="yellow">▌</Text>}
114
- </Text>
115
- </Box>
116
- </Box>
117
-
118
- {/* Confirm field (only in confirm mode) */}
119
- {mode === "confirm" && (
120
- <Box marginTop={1}>
121
- <Text dimColor>Confirm: </Text>
122
- <Box
123
- borderStyle={activeField === "confirm" ? "single" : undefined}
124
- borderColor="yellow"
125
- paddingX={1}
126
- minWidth={30}
127
- >
128
- <Text color={activeField === "confirm" ? "white" : "gray"}>
129
- {maskPassword(confirmPassword)}
130
- {activeField === "confirm" && <Text color="yellow">▌</Text>}
131
- </Text>
132
- </Box>
133
- </Box>
134
- )}
135
-
136
- {/* Warning */}
137
- {warning && (
138
- <Box marginTop={1}>
139
- <Text color="yellow">⚠️ {warning}</Text>
140
- </Box>
141
- )}
142
-
143
- {/* Error */}
144
- {error && (
145
- <Box marginTop={1}>
146
- <Text color="red">✗ {error}</Text>
147
- </Box>
148
- )}
149
-
150
- {/* Help */}
151
- <Box marginTop={1}>
152
- <Text dimColor>
153
- {mode === "confirm" && "[Tab] Switch field "}
154
- [Enter] Confirm [Esc] Cancel
155
- </Text>
156
- </Box>
157
- </Box>
158
- );
159
- }
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+
4
+ interface PasswordInputProps {
5
+ mode: "single" | "confirm";
6
+ title?: string;
7
+ subtitle?: string;
8
+ warning?: string;
9
+ onSubmit: (password: string) => void;
10
+ onCancel: () => void;
11
+ }
12
+
13
+ export function PasswordInput({
14
+ mode,
15
+ title = "Enter Password",
16
+ subtitle,
17
+ warning,
18
+ onSubmit,
19
+ onCancel,
20
+ }: PasswordInputProps) {
21
+ const [password, setPassword] = useState("");
22
+ const [confirmPassword, setConfirmPassword] = useState("");
23
+ const [activeField, setActiveField] = useState<"password" | "confirm">("password");
24
+ const [error, setError] = useState<string | null>(null);
25
+
26
+ useInput((input, key) => {
27
+ // Handle escape
28
+ if (key.escape) {
29
+ onCancel();
30
+ return;
31
+ }
32
+
33
+ // Handle tab to switch fields (in confirm mode)
34
+ if (key.tab && mode === "confirm") {
35
+ setActiveField(prev => prev === "password" ? "confirm" : "password");
36
+ return;
37
+ }
38
+
39
+ // Handle enter
40
+ if (key.return) {
41
+ if (mode === "confirm" && activeField === "password") {
42
+ // Move to confirm field
43
+ setActiveField("confirm");
44
+ return;
45
+ }
46
+
47
+ // Validate
48
+ if (password.length === 0) {
49
+ setError("Password cannot be empty");
50
+ return;
51
+ }
52
+
53
+ if (mode === "confirm" && password !== confirmPassword) {
54
+ setError("Passwords do not match");
55
+ return;
56
+ }
57
+
58
+ onSubmit(password);
59
+ return;
60
+ }
61
+
62
+ // Handle backspace
63
+ if (key.backspace || key.delete) {
64
+ if (activeField === "password") {
65
+ setPassword(prev => prev.slice(0, -1));
66
+ } else {
67
+ setConfirmPassword(prev => prev.slice(0, -1));
68
+ }
69
+ setError(null);
70
+ return;
71
+ }
72
+
73
+ // Handle regular input (printable characters)
74
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
75
+ if (activeField === "password") {
76
+ setPassword(prev => prev + input);
77
+ } else {
78
+ setConfirmPassword(prev => prev + input);
79
+ }
80
+ setError(null);
81
+ }
82
+ });
83
+
84
+ const maskPassword = (pwd: string): string => {
85
+ return "•".repeat(pwd.length);
86
+ };
87
+
88
+ return (
89
+ <Box
90
+ flexDirection="column"
91
+ borderStyle="round"
92
+ borderColor="gray"
93
+ paddingX={2}
94
+ paddingY={1}
95
+ >
96
+ {/* Title */}
97
+ <Box marginBottom={1}>
98
+ <Text bold>{title}</Text>
99
+ {subtitle && <Text dimColor> - {subtitle}</Text>}
100
+ </Box>
101
+
102
+ {/* Password field */}
103
+ <Box>
104
+ <Text dimColor>Password: </Text>
105
+ <Box
106
+ borderStyle={activeField === "password" ? "single" : undefined}
107
+ borderColor="yellow"
108
+ paddingX={1}
109
+ minWidth={30}
110
+ >
111
+ <Text color={activeField === "password" ? "white" : "gray"}>
112
+ {maskPassword(password)}
113
+ {activeField === "password" && <Text color="yellow">▌</Text>}
114
+ </Text>
115
+ </Box>
116
+ </Box>
117
+
118
+ {/* Confirm field (only in confirm mode) */}
119
+ {mode === "confirm" && (
120
+ <Box marginTop={1}>
121
+ <Text dimColor>Confirm: </Text>
122
+ <Box
123
+ borderStyle={activeField === "confirm" ? "single" : undefined}
124
+ borderColor="yellow"
125
+ paddingX={1}
126
+ minWidth={30}
127
+ >
128
+ <Text color={activeField === "confirm" ? "white" : "gray"}>
129
+ {maskPassword(confirmPassword)}
130
+ {activeField === "confirm" && <Text color="yellow">▌</Text>}
131
+ </Text>
132
+ </Box>
133
+ </Box>
134
+ )}
135
+
136
+ {/* Warning */}
137
+ {warning && (
138
+ <Box marginTop={1}>
139
+ <Text color="yellow">⚠️ {warning}</Text>
140
+ </Box>
141
+ )}
142
+
143
+ {/* Error */}
144
+ {error && (
145
+ <Box marginTop={1}>
146
+ <Text color="red">✗ {error}</Text>
147
+ </Box>
148
+ )}
149
+
150
+ {/* Help */}
151
+ <Box marginTop={1}>
152
+ <Text dimColor>
153
+ {mode === "confirm" && "[Tab] Switch field "}
154
+ [Enter] Confirm [Esc] Cancel
155
+ </Text>
156
+ </Box>
157
+ </Box>
158
+ );
159
+ }