opencode-account-manager 0.6.3 → 0.6.5

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 (95) 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 +72 -5
  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/Box.js +2 -2
  39. package/dist/tui/components/Box.js.map +1 -1
  40. package/dist/tui/components/DashboardView.d.ts +3 -2
  41. package/dist/tui/components/DashboardView.d.ts.map +1 -1
  42. package/dist/tui/components/DashboardView.js +50 -4
  43. package/dist/tui/components/DashboardView.js.map +1 -1
  44. package/dist/tui/components/ExportModal.js +4 -4
  45. package/dist/tui/components/ExportModal.js.map +1 -1
  46. package/dist/tui/components/FileBrowser.js +1 -1
  47. package/dist/tui/components/HealthBadge.d.ts +9 -0
  48. package/dist/tui/components/HealthBadge.d.ts.map +1 -0
  49. package/dist/tui/components/HealthBadge.js +56 -0
  50. package/dist/tui/components/HealthBadge.js.map +1 -0
  51. package/dist/tui/components/ImportModal.js +4 -4
  52. package/dist/tui/components/ImportModal.js.map +1 -1
  53. package/dist/tui/components/PasswordInput.js +2 -2
  54. package/dist/tui/components/PasswordInput.js.map +1 -1
  55. package/dist/tui/components/StatusBadge.d.ts +2 -1
  56. package/dist/tui/components/StatusBadge.d.ts.map +1 -1
  57. package/dist/tui/components/StatusBadge.js +30 -2
  58. package/dist/tui/components/StatusBadge.js.map +1 -1
  59. package/dist/tui/components/index.d.ts +1 -0
  60. package/dist/tui/components/index.d.ts.map +1 -1
  61. package/dist/tui/components/index.js +3 -1
  62. package/dist/tui/components/index.js.map +1 -1
  63. package/docs/BLUEPRINT.md +476 -476
  64. package/docs/ROADMAP.md +125 -107
  65. package/package.json +36 -36
  66. package/src/cli.ts +139 -38
  67. package/src/core/config-store.ts +278 -171
  68. package/src/core/crypto.ts +162 -162
  69. package/src/core/health-log.ts +173 -0
  70. package/src/core/health-oauth.ts +190 -0
  71. package/src/core/health-orchestrator.ts +224 -0
  72. package/src/core/importers/amExport.ts +177 -177
  73. package/src/core/opencode-config.ts +217 -217
  74. package/src/core/paths.ts +10 -6
  75. package/src/core/types.ts +193 -147
  76. package/src/tui/Dashboard.tsx +557 -478
  77. package/src/tui/components/AccountList.tsx +122 -104
  78. package/src/tui/components/ActionPalette.tsx +117 -117
  79. package/src/tui/components/Box.tsx +2 -2
  80. package/src/tui/components/DashboardView.tsx +285 -230
  81. package/src/tui/components/ExportModal.tsx +255 -255
  82. package/src/tui/components/FileBrowser.tsx +393 -393
  83. package/src/tui/components/Header.tsx +26 -26
  84. package/src/tui/components/HealthBadge.tsx +64 -0
  85. package/src/tui/components/ImportModal.tsx +334 -334
  86. package/src/tui/components/McpServerList.tsx +67 -67
  87. package/src/tui/components/Menu.tsx +61 -61
  88. package/src/tui/components/PasswordInput.tsx +159 -159
  89. package/src/tui/components/ProviderList.tsx +59 -59
  90. package/src/tui/components/SectionBox.tsx +35 -35
  91. package/src/tui/components/StatsRow.tsx +33 -33
  92. package/src/tui/components/StatusBadge.tsx +36 -3
  93. package/src/tui/components/index.ts +15 -14
  94. package/test-minimal.js +26 -26
  95. 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="cyan"
93
- paddingX={2}
94
- paddingY={1}
95
- >
96
- {/* Title */}
97
- <Box marginBottom={1}>
98
- <Text bold color="cyan">{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
+ }