@orderful/droid 0.33.1 → 0.34.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 (78) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bin/droid.js +673 -101
  3. package/dist/commands/config.d.ts.map +1 -1
  4. package/dist/commands/repos.d.ts +21 -0
  5. package/dist/commands/repos.d.ts.map +1 -0
  6. package/dist/commands/tui/components/SettingsDetails.d.ts +2 -1
  7. package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -1
  8. package/dist/commands/tui/types.d.ts +1 -1
  9. package/dist/commands/tui/types.d.ts.map +1 -1
  10. package/dist/commands/tui/views/ReposManagementScreen.d.ts +6 -0
  11. package/dist/commands/tui/views/ReposManagementScreen.d.ts.map +1 -0
  12. package/dist/commands/tui/views/ReposViewerScreen.d.ts +5 -0
  13. package/dist/commands/tui/views/ReposViewerScreen.d.ts.map +1 -0
  14. package/dist/commands/tui.d.ts.map +1 -1
  15. package/dist/index.js +149 -26
  16. package/dist/lib/config.d.ts +34 -1
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/types.d.ts +10 -1
  19. package/dist/lib/types.d.ts.map +1 -1
  20. package/dist/tools/brain/skills/brain/SKILL.md +2 -2
  21. package/dist/tools/brain/skills/brain/references/workflows.md +10 -10
  22. package/dist/tools/coach/skills/coach/SKILL.md +6 -6
  23. package/dist/tools/codex/skills/codex/SKILL.md +5 -5
  24. package/dist/tools/codex/skills/codex/references/creating.md +2 -2
  25. package/dist/tools/codex/skills/codex/references/decisions.md +2 -2
  26. package/dist/tools/codex/skills/codex/references/loading.md +1 -1
  27. package/dist/tools/codex/skills/codex/references/topics.md +2 -2
  28. package/dist/tools/codex/skills/codex/scripts/git-finish-write.d.ts +1 -1
  29. package/dist/tools/codex/skills/codex/scripts/git-finish-write.ts +2 -2
  30. package/dist/tools/codex/skills/codex/scripts/git-preamble.d.ts +1 -1
  31. package/dist/tools/codex/skills/codex/scripts/git-preamble.ts +3 -3
  32. package/dist/tools/codex/skills/codex/scripts/git-start-write.d.ts +1 -1
  33. package/dist/tools/codex/skills/codex/scripts/git-start-write.ts +2 -2
  34. package/dist/tools/comments/skills/comments/SKILL.md +9 -9
  35. package/dist/tools/plan/skills/plan/SKILL.md +2 -2
  36. package/dist/tools/plan/skills/plan/references/workflows.md +2 -2
  37. package/dist/tools/project/skills/project/SKILL.md +1 -1
  38. package/dist/tools/project/skills/project/references/creating.md +2 -2
  39. package/dist/tools/project/skills/project/references/loading.md +1 -1
  40. package/dist/tools/tech-design/skills/tech-design/SKILL.md +2 -2
  41. package/dist/tools/tech-design/skills/tech-design/references/publish.md +3 -3
  42. package/dist/tools/tech-design/skills/tech-design/references/start.md +29 -3
  43. package/dist/tools/tech-design/skills/tech-design/references/think.md +1 -1
  44. package/dist/tools/wrapup/skills/wrapup/references/subagent-prompts.md +3 -3
  45. package/package.json +1 -1
  46. package/src/bin/droid.ts +39 -0
  47. package/src/commands/config.ts +14 -1
  48. package/src/commands/repos.ts +185 -0
  49. package/src/commands/tui/components/SettingsDetails.tsx +42 -13
  50. package/src/commands/tui/types.ts +1 -1
  51. package/src/commands/tui/views/ReposManagementScreen.tsx +291 -0
  52. package/src/commands/tui/views/ReposViewerScreen.tsx +49 -0
  53. package/src/commands/tui.tsx +51 -4
  54. package/src/lib/config.test.ts +228 -1
  55. package/src/lib/config.ts +193 -4
  56. package/src/lib/types.ts +13 -1
  57. package/src/tools/brain/skills/brain/SKILL.md +2 -2
  58. package/src/tools/brain/skills/brain/references/workflows.md +10 -10
  59. package/src/tools/coach/skills/coach/SKILL.md +6 -6
  60. package/src/tools/codex/skills/codex/SKILL.md +5 -5
  61. package/src/tools/codex/skills/codex/references/creating.md +2 -2
  62. package/src/tools/codex/skills/codex/references/decisions.md +2 -2
  63. package/src/tools/codex/skills/codex/references/loading.md +1 -1
  64. package/src/tools/codex/skills/codex/references/topics.md +2 -2
  65. package/src/tools/codex/skills/codex/scripts/git-finish-write.ts +2 -2
  66. package/src/tools/codex/skills/codex/scripts/git-preamble.ts +3 -3
  67. package/src/tools/codex/skills/codex/scripts/git-start-write.ts +2 -2
  68. package/src/tools/comments/skills/comments/SKILL.md +9 -9
  69. package/src/tools/plan/skills/plan/SKILL.md +2 -2
  70. package/src/tools/plan/skills/plan/references/workflows.md +2 -2
  71. package/src/tools/project/skills/project/SKILL.md +1 -1
  72. package/src/tools/project/skills/project/references/creating.md +2 -2
  73. package/src/tools/project/skills/project/references/loading.md +1 -1
  74. package/src/tools/tech-design/skills/tech-design/SKILL.md +2 -2
  75. package/src/tools/tech-design/skills/tech-design/references/publish.md +3 -3
  76. package/src/tools/tech-design/skills/tech-design/references/start.md +29 -3
  77. package/src/tools/tech-design/skills/tech-design/references/think.md +1 -1
  78. package/src/tools/wrapup/skills/wrapup/references/subagent-prompts.md +3 -3
@@ -0,0 +1,185 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import {
4
+ getRepos,
5
+ getRepo,
6
+ addRepo,
7
+ removeRepo,
8
+ getRepoPath,
9
+ } from '../lib/config';
10
+ import type { RepoConfig } from '../lib/types';
11
+
12
+ interface ReposListOptions {
13
+ json?: boolean;
14
+ }
15
+
16
+ /**
17
+ * List all repos
18
+ */
19
+ export async function reposListCommand(options?: ReposListOptions): Promise<void> {
20
+ const repos = getRepos();
21
+
22
+ if (repos.length === 0) {
23
+ console.log(chalk.yellow('No repos configured.'));
24
+ console.log(chalk.gray('\nAdd a repo with:'));
25
+ console.log(chalk.gray(' droid repos add <name>'));
26
+ return;
27
+ }
28
+
29
+ if (options?.json) {
30
+ console.log(JSON.stringify(repos, null, 2));
31
+ return;
32
+ }
33
+
34
+ console.log(chalk.bold('\n📦 Registered Repos\n'));
35
+
36
+ for (const repo of repos) {
37
+ const expandedPath = getRepoPath(repo.name);
38
+ console.log(chalk.cyan(`${repo.name}`));
39
+ console.log(chalk.gray(` Path: ${expandedPath}`));
40
+ if (repo.description) {
41
+ console.log(chalk.gray(` ${repo.description}`));
42
+ }
43
+ console.log();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Add a repo
49
+ */
50
+ export async function reposAddCommand(
51
+ name?: string,
52
+ path?: string,
53
+ description?: string,
54
+ ): Promise<void> {
55
+ let repoName = name;
56
+ let repoPath = path;
57
+ let repoDescription = description;
58
+
59
+ // Interactive prompts if arguments not provided
60
+ if (!repoName) {
61
+ const answers = await inquirer.prompt([
62
+ {
63
+ type: 'input',
64
+ name: 'name',
65
+ message: 'Repo name:',
66
+ validate: (input) => (input.trim() ? true : 'Name is required'),
67
+ },
68
+ ]);
69
+ repoName = answers.name;
70
+ }
71
+
72
+ if (!repoPath) {
73
+ const answers = await inquirer.prompt([
74
+ {
75
+ type: 'input',
76
+ name: 'path',
77
+ message: 'Repo path:',
78
+ default: `~/src/github.com/${repoName}`,
79
+ validate: (input) => (input.trim() ? true : 'Path is required'),
80
+ },
81
+ ]);
82
+ repoPath = answers.path;
83
+ }
84
+
85
+ if (!repoDescription) {
86
+ const answers = await inquirer.prompt([
87
+ {
88
+ type: 'input',
89
+ name: 'description',
90
+ message: 'Description (optional):',
91
+ },
92
+ ]);
93
+ repoDescription = answers.description || undefined;
94
+ }
95
+
96
+ // At this point, repoName and repoPath should be defined
97
+ if (!repoName || !repoPath) {
98
+ console.error(chalk.red('Name and path are required'));
99
+ return;
100
+ }
101
+
102
+ const repo: RepoConfig = {
103
+ name: repoName,
104
+ path: repoPath,
105
+ description: repoDescription,
106
+ };
107
+
108
+ addRepo(repo);
109
+
110
+ const expandedPath = getRepoPath(repoName);
111
+ console.log(chalk.green(`\n✓ Added repo: ${repoName}`));
112
+ console.log(chalk.gray(` Path: ${expandedPath}`));
113
+ if (repoDescription) {
114
+ console.log(chalk.gray(` ${repoDescription}`));
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Remove a repo
120
+ */
121
+ export async function reposRemoveCommand(name?: string): Promise<void> {
122
+ let repoName = name;
123
+
124
+ if (!repoName) {
125
+ const repos = getRepos();
126
+
127
+ if (repos.length === 0) {
128
+ console.log(chalk.yellow('No repos configured.'));
129
+ return;
130
+ }
131
+
132
+ const answers = await inquirer.prompt([
133
+ {
134
+ type: 'list',
135
+ name: 'name',
136
+ message: 'Select repo to remove:',
137
+ choices: repos.map((r) => r.name),
138
+ },
139
+ ]);
140
+ repoName = answers.name;
141
+ }
142
+
143
+ // At this point, repoName should be defined
144
+ if (!repoName) {
145
+ console.error(chalk.red('Repo name is required'));
146
+ return;
147
+ }
148
+
149
+ // Confirm removal
150
+ const answers = await inquirer.prompt([
151
+ {
152
+ type: 'confirm',
153
+ name: 'confirm',
154
+ message: `Remove repo '${repoName}'?`,
155
+ default: false,
156
+ },
157
+ ]);
158
+
159
+ if (!answers.confirm) {
160
+ console.log(chalk.gray('Cancelled'));
161
+ return;
162
+ }
163
+
164
+ const existed = removeRepo(repoName);
165
+
166
+ if (existed) {
167
+ console.log(chalk.green(`\n✓ Removed repo: ${repoName}`));
168
+ } else {
169
+ console.log(chalk.yellow(`\nRepo '${repoName}' not found`));
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Get a single repo (for scripts)
175
+ */
176
+ export async function reposGetCommand(name: string): Promise<void> {
177
+ const repo = getRepo(name);
178
+
179
+ if (!repo) {
180
+ console.error(chalk.red(`Repo '${name}' not found`));
181
+ process.exit(1);
182
+ }
183
+
184
+ console.log(JSON.stringify(repo, null, 2));
185
+ }
@@ -1,11 +1,12 @@
1
1
  import { Box, Text } from 'ink';
2
- import { loadConfig, getAutoUpdateConfig } from '../../../lib/config';
2
+ import { loadConfig, getAutoUpdateConfig, getRepos } from '../../../lib/config';
3
3
  import { Platform } from '../../../lib/types';
4
4
  import { colors } from '../constants';
5
5
 
6
6
  export interface SettingsDetailsProps {
7
7
  isFocused: boolean;
8
8
  detectedPlatforms: Platform[];
9
+ selectedAction: number;
9
10
  onRedetect?: () => void;
10
11
  }
11
12
 
@@ -18,10 +19,12 @@ const PLATFORM_LABELS: Record<Platform, string> = {
18
19
  export function SettingsDetails({
19
20
  isFocused,
20
21
  detectedPlatforms,
22
+ selectedAction,
21
23
  onRedetect: _onRedetect, // TODO: Add re-detect button when no platforms found
22
24
  }: SettingsDetailsProps) {
23
25
  const config = loadConfig();
24
26
  const autoUpdateConfig = getAutoUpdateConfig();
27
+ const repos = getRepos();
25
28
 
26
29
  const platformsText = detectedPlatforms.length > 0
27
30
  ? detectedPlatforms.map(p => PLATFORM_LABELS[p]).join(', ')
@@ -31,50 +34,76 @@ export function SettingsDetails({
31
34
  <Box flexDirection="column" paddingLeft={2} flexGrow={1}>
32
35
  <Text color={colors.text} bold>Settings</Text>
33
36
 
37
+ {/* Global Settings */}
34
38
  <Box flexDirection="column" marginTop={1}>
39
+ <Text color={colors.textDim} bold>Global</Text>
35
40
  <Text>
36
- <Text color={colors.textDim}>Platforms: </Text>
41
+ <Text color={colors.textDim}> Detected platforms: </Text>
37
42
  <Text color={colors.text}>{platformsText}</Text>
38
43
  </Text>
39
44
  <Text>
40
- <Text color={colors.textDim}>Your @mention: </Text>
45
+ <Text color={colors.textDim}> Your @mention: </Text>
41
46
  <Text color={colors.text}>{config.user_mention}</Text>
42
47
  </Text>
43
48
  <Text>
44
- <Text color={colors.textDim}>Auto-update tools: </Text>
49
+ <Text color={colors.textDim}> Auto-update tools: </Text>
45
50
  <Text color={colors.text}>{autoUpdateConfig.tools ? 'enabled' : 'disabled'}</Text>
46
51
  </Text>
47
52
  <Text>
48
- <Text color={colors.textDim}>Auto-update app: </Text>
53
+ <Text color={colors.textDim}> Auto-update app: </Text>
49
54
  <Text color={colors.text}>{autoUpdateConfig.app ? 'enabled' : 'disabled'}</Text>
50
55
  </Text>
51
56
  </Box>
52
57
 
53
- <Box marginTop={2}>
58
+ {/* Repos */}
59
+ <Box flexDirection="column" marginTop={1}>
60
+ <Text color={colors.textDim} bold>Repos</Text>
61
+ {repos.length === 0 ? (
62
+ <Text color={colors.textDim}> No repos configured</Text>
63
+ ) : (
64
+ <Text color={colors.textDim}> {repos.map(r => r.name).join(', ')} ({repos.length} total)</Text>
65
+ )}
66
+ </Box>
67
+
68
+ <Box marginTop={1}>
54
69
  <Text color={colors.textDim}>Config: ~/.droid/config.yaml</Text>
55
70
  </Box>
56
71
 
57
72
  {isFocused && (
58
- <Box marginTop={2}>
73
+ <Box marginTop={2} flexDirection="row" gap={2}>
74
+ <Text
75
+ backgroundColor={selectedAction === 0 ? colors.primary : undefined}
76
+ color={selectedAction === 0 ? '#ffffff' : colors.textDim}
77
+ bold={selectedAction === 0}
78
+ >
79
+ {' '}Edit Config{' '}
80
+ </Text>
81
+ <Text
82
+ backgroundColor={selectedAction === 1 ? colors.primary : undefined}
83
+ color={selectedAction === 1 ? '#ffffff' : colors.textDim}
84
+ bold={selectedAction === 1}
85
+ >
86
+ {' '}View Repos{' '}
87
+ </Text>
59
88
  <Text
60
- backgroundColor={colors.primary}
61
- color="#ffffff"
62
- bold
89
+ backgroundColor={selectedAction === 2 ? colors.primary : undefined}
90
+ color={selectedAction === 2 ? '#ffffff' : colors.textDim}
91
+ bold={selectedAction === 2}
63
92
  >
64
- {' '}Edit{' '}
93
+ {' '}Manage Repos{' '}
65
94
  </Text>
66
95
  </Box>
67
96
  )}
68
97
 
69
98
  {isFocused && (
70
99
  <Box marginTop={1}>
71
- <Text color={colors.textDim}>enter edit · esc back</Text>
100
+ <Text color={colors.textDim}>←→ select · enter confirm · esc back</Text>
72
101
  </Box>
73
102
  )}
74
103
 
75
104
  {!isFocused && (
76
105
  <Box marginTop={2}>
77
- <Text color={colors.textDim}>press enter to edit</Text>
106
+ <Text color={colors.textDim}>press enter for options</Text>
78
107
  </Box>
79
108
  )}
80
109
  </Box>
@@ -1,4 +1,4 @@
1
1
  export type Tab = 'tools' | 'settings';
2
- export type View = 'welcome' | 'tool-updates' | 'setup' | 'menu' | 'detail' | 'configure' | 'readme' | 'explorer';
2
+ export type View = 'welcome' | 'tool-updates' | 'setup' | 'menu' | 'detail' | 'configure' | 'readme' | 'explorer' | 'repos' | 'view-repos';
3
3
  export type SetupStep = 'user_mention' | 'auto_update' | 'confirm';
4
4
  export type ComponentType = 'skill' | 'command' | 'agent';
@@ -0,0 +1,291 @@
1
+ import { Box, Text, useInput } from 'ink';
2
+ import TextInput from 'ink-text-input';
3
+ import { useState } from 'react';
4
+ import { getRepos, addRepo, removeRepo } from '../../../lib/config';
5
+ import type { RepoConfig } from '../../../lib/types';
6
+ import { colors } from '../constants';
7
+
8
+ export interface ReposManagementScreenProps {
9
+ onComplete: () => void;
10
+ onCancel: () => void;
11
+ }
12
+
13
+ type Screen = 'list' | 'add-name' | 'add-path' | 'add-desc' | 'confirm-delete';
14
+
15
+ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: ReposManagementScreenProps) {
16
+ const [repos, setRepos] = useState<RepoConfig[]>(() => getRepos());
17
+ const [selectedIndex, setSelectedIndex] = useState(0);
18
+ const [screen, setScreen] = useState<Screen>('list');
19
+ const [message, setMessage] = useState<{ text: string; type: 'success' | 'error' } | null>(null);
20
+
21
+ // Add repo state
22
+ const [newRepoName, setNewRepoName] = useState('');
23
+ const [newRepoPath, setNewRepoPath] = useState('');
24
+ const [newRepoDesc, setNewRepoDesc] = useState('');
25
+
26
+ // Delete repo state
27
+ const [repoToDelete, setRepoToDelete] = useState<string | null>(null);
28
+
29
+ const handleAddRepo = () => {
30
+ if (!newRepoName || !newRepoPath) {
31
+ setMessage({ text: 'Name and path are required', type: 'error' });
32
+ return;
33
+ }
34
+
35
+ const newRepo: RepoConfig = {
36
+ name: newRepoName,
37
+ path: newRepoPath,
38
+ description: newRepoDesc || undefined,
39
+ };
40
+
41
+ addRepo(newRepo);
42
+ setRepos(getRepos());
43
+ setMessage({ text: `Added ${newRepoName}`, type: 'success' });
44
+
45
+ // Reset form
46
+ setNewRepoName('');
47
+ setNewRepoPath('');
48
+ setNewRepoDesc('');
49
+ setScreen('list');
50
+ };
51
+
52
+ const handleDeleteRepo = (repoName: string) => {
53
+ removeRepo(repoName);
54
+ setRepos(getRepos());
55
+ setMessage({ text: `Removed ${repoName}`, type: 'success' });
56
+ setRepoToDelete(null);
57
+ setScreen('list');
58
+
59
+ // Adjust selected index if needed
60
+ if (selectedIndex >= repos.length - 1) {
61
+ setSelectedIndex(Math.max(0, repos.length - 2));
62
+ }
63
+ };
64
+
65
+ // List screen navigation
66
+ useInput((input, key) => {
67
+ if (message) setMessage(null);
68
+
69
+ if (key.escape) {
70
+ onCancel();
71
+ return;
72
+ }
73
+
74
+ if (key.upArrow) {
75
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
76
+ }
77
+
78
+ if (key.downArrow) {
79
+ // +1 for "Add New Repo" option
80
+ const maxIndex = repos.length;
81
+ setSelectedIndex((prev) => Math.min(maxIndex, prev + 1));
82
+ }
83
+
84
+ if (key.return) {
85
+ if (selectedIndex === repos.length) {
86
+ // "Add New Repo" selected
87
+ setScreen('add-name');
88
+ } else if (selectedIndex < repos.length) {
89
+ // Repo selected - prompt to delete
90
+ setRepoToDelete(repos[selectedIndex].name);
91
+ setScreen('confirm-delete');
92
+ }
93
+ }
94
+ }, { isActive: screen === 'list' });
95
+
96
+ // Add name input
97
+ useInput((input, key) => {
98
+ if (key.escape) {
99
+ setNewRepoName('');
100
+ setScreen('list');
101
+ }
102
+ }, { isActive: screen === 'add-name' });
103
+
104
+ // Add path input
105
+ useInput((input, key) => {
106
+ if (key.escape) {
107
+ setNewRepoPath('');
108
+ setScreen('add-name');
109
+ }
110
+ }, { isActive: screen === 'add-path' });
111
+
112
+ // Add description input
113
+ useInput((input, key) => {
114
+ if (key.escape) {
115
+ setNewRepoDesc('');
116
+ setScreen('add-path');
117
+ }
118
+ }, { isActive: screen === 'add-desc' });
119
+
120
+ // Confirm delete
121
+ useInput((input, key) => {
122
+ if (key.escape || input === 'n') {
123
+ setRepoToDelete(null);
124
+ setScreen('list');
125
+ }
126
+ if (input === 'y' && repoToDelete) {
127
+ handleDeleteRepo(repoToDelete);
128
+ }
129
+ }, { isActive: screen === 'confirm-delete' });
130
+
131
+ if (screen === 'add-name') {
132
+ return (
133
+ <Box flexDirection="column" padding={2}>
134
+ <Text color={colors.text} bold>Add Repository</Text>
135
+ <Box marginTop={1}>
136
+ <Text color={colors.textDim}>Repository name: </Text>
137
+ <TextInput
138
+ value={newRepoName}
139
+ onChange={setNewRepoName}
140
+ onSubmit={() => {
141
+ if (newRepoName) {
142
+ setScreen('add-path');
143
+ }
144
+ }}
145
+ />
146
+ </Box>
147
+ <Box marginTop={1}>
148
+ <Text color={colors.textDim}>esc cancel</Text>
149
+ </Box>
150
+ </Box>
151
+ );
152
+ }
153
+
154
+ if (screen === 'add-path') {
155
+ return (
156
+ <Box flexDirection="column" padding={2}>
157
+ <Text color={colors.text} bold>Add Repository</Text>
158
+ <Box marginTop={1}>
159
+ <Text color={colors.textDim}>Name: </Text>
160
+ <Text color={colors.text}>{newRepoName}</Text>
161
+ </Box>
162
+ <Box marginTop={1}>
163
+ <Text color={colors.textDim}>Path (e.g., ~/src/github.com/{newRepoName}): </Text>
164
+ <TextInput
165
+ value={newRepoPath}
166
+ onChange={setNewRepoPath}
167
+ onSubmit={() => {
168
+ if (newRepoPath) {
169
+ setScreen('add-desc');
170
+ }
171
+ }}
172
+ placeholder={`~/src/github.com/${newRepoName}`}
173
+ />
174
+ </Box>
175
+ <Box marginTop={1}>
176
+ <Text color={colors.textDim}>esc back</Text>
177
+ </Box>
178
+ </Box>
179
+ );
180
+ }
181
+
182
+ if (screen === 'add-desc') {
183
+ return (
184
+ <Box flexDirection="column" padding={2}>
185
+ <Text color={colors.text} bold>Add Repository</Text>
186
+ <Box marginTop={1}>
187
+ <Text color={colors.textDim}>Name: </Text>
188
+ <Text color={colors.text}>{newRepoName}</Text>
189
+ </Box>
190
+ <Box marginTop={1}>
191
+ <Text color={colors.textDim}>Path: </Text>
192
+ <Text color={colors.text}>{newRepoPath}</Text>
193
+ </Box>
194
+ <Box marginTop={1}>
195
+ <Text color={colors.textDim}>Description (optional): </Text>
196
+ <TextInput
197
+ value={newRepoDesc}
198
+ onChange={setNewRepoDesc}
199
+ onSubmit={handleAddRepo}
200
+ />
201
+ </Box>
202
+ <Box marginTop={1}>
203
+ <Text color={colors.textDim}>enter save · esc back</Text>
204
+ </Box>
205
+ </Box>
206
+ );
207
+ }
208
+
209
+ if (screen === 'confirm-delete' && repoToDelete) {
210
+ const repo = repos.find((r) => r.name === repoToDelete);
211
+ return (
212
+ <Box flexDirection="column" padding={2}>
213
+ <Text color={colors.text} bold>Remove Repository</Text>
214
+ <Box marginTop={1} flexDirection="column">
215
+ <Text color={colors.text}>{repo?.name}</Text>
216
+ <Text color={colors.textDim}>{repo?.path}</Text>
217
+ </Box>
218
+ <Box marginTop={2}>
219
+ <Text color={colors.error}>Remove this repository from the registry?</Text>
220
+ </Box>
221
+ <Box marginTop={1}>
222
+ <Text color={colors.textDim}>y yes · n no</Text>
223
+ </Box>
224
+ </Box>
225
+ );
226
+ }
227
+
228
+ // List screen
229
+ return (
230
+ <Box flexDirection="column" padding={2}>
231
+ <Text color={colors.text} bold>Manage Repositories</Text>
232
+
233
+ {message && (
234
+ <Box marginTop={1}>
235
+ <Text color={message.type === 'success' ? colors.success : colors.error}>
236
+ {message.text}
237
+ </Text>
238
+ </Box>
239
+ )}
240
+
241
+ <Box flexDirection="column" marginTop={1}>
242
+ {repos.length === 0 ? (
243
+ <Box marginY={1}>
244
+ <Text color={colors.textDim}>No repos configured</Text>
245
+ </Box>
246
+ ) : (
247
+ repos.map((repo, index) => (
248
+ <Box key={repo.name} flexDirection="column" marginTop={index > 0 ? 1 : 0}>
249
+ <Box>
250
+ <Text
251
+ color={selectedIndex === index ? colors.primary : colors.text}
252
+ bold={selectedIndex === index}
253
+ >
254
+ {selectedIndex === index ? '> ' : ' '}
255
+ {repo.name}
256
+ </Text>
257
+ </Box>
258
+ <Box paddingLeft={2}>
259
+ <Text color={colors.textDim}>{repo.path}</Text>
260
+ </Box>
261
+ {repo.description && (
262
+ <Box paddingLeft={2}>
263
+ <Text color={colors.textDim}>{repo.description}</Text>
264
+ </Box>
265
+ )}
266
+ </Box>
267
+ ))
268
+ )}
269
+
270
+ {/* Add New Repo option */}
271
+ <Box marginTop={repos.length > 0 ? 2 : 0}>
272
+ <Text
273
+ color={selectedIndex === repos.length ? colors.primary : colors.textDim}
274
+ bold={selectedIndex === repos.length}
275
+ >
276
+ {selectedIndex === repos.length ? '> ' : ' '}
277
+ + Add New Repository
278
+ </Text>
279
+ </Box>
280
+ </Box>
281
+
282
+ <Box marginTop={2}>
283
+ <Text color={colors.textDim}>
284
+ {repos.length > 0 && selectedIndex < repos.length
285
+ ? 'enter remove · esc back'
286
+ : '↑↓ navigate · enter select · esc back'}
287
+ </Text>
288
+ </Box>
289
+ </Box>
290
+ );
291
+ }
@@ -0,0 +1,49 @@
1
+ import { Box, Text, useInput } from 'ink';
2
+ import { getRepos } from '../../../lib/config';
3
+ import { colors } from '../constants';
4
+
5
+ export interface ReposViewerScreenProps {
6
+ onClose: () => void;
7
+ }
8
+
9
+ export function ReposViewerScreen({ onClose }: ReposViewerScreenProps) {
10
+ const repos = getRepos();
11
+
12
+ useInput((input, key) => {
13
+ if (key.escape) {
14
+ onClose();
15
+ }
16
+ });
17
+
18
+ return (
19
+ <Box flexDirection="column" padding={2}>
20
+ <Text color={colors.text} bold>Repositories</Text>
21
+
22
+ {repos.length === 0 ? (
23
+ <Box marginTop={1}>
24
+ <Text color={colors.textDim}>No repos configured</Text>
25
+ </Box>
26
+ ) : (
27
+ <Box flexDirection="column" marginTop={1}>
28
+ {repos.map((repo, index) => (
29
+ <Box key={repo.name} flexDirection="column" marginTop={index > 0 ? 2 : 0}>
30
+ <Text color={colors.text} bold>{repo.name}</Text>
31
+ <Box marginTop={0}>
32
+ <Text color={colors.textDim}>Path: {repo.path}</Text>
33
+ </Box>
34
+ {repo.description && (
35
+ <Box marginTop={0}>
36
+ <Text color={colors.textDim}>{repo.description}</Text>
37
+ </Box>
38
+ )}
39
+ </Box>
40
+ ))}
41
+ </Box>
42
+ )}
43
+
44
+ <Box marginTop={2}>
45
+ <Text color={colors.textDim}>esc back</Text>
46
+ </Box>
47
+ </Box>
48
+ );
49
+ }