@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.
- package/CHANGELOG.md +16 -0
- package/dist/bin/droid.js +673 -101
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/repos.d.ts +21 -0
- package/dist/commands/repos.d.ts.map +1 -0
- package/dist/commands/tui/components/SettingsDetails.d.ts +2 -1
- package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -1
- package/dist/commands/tui/types.d.ts +1 -1
- package/dist/commands/tui/types.d.ts.map +1 -1
- package/dist/commands/tui/views/ReposManagementScreen.d.ts +6 -0
- package/dist/commands/tui/views/ReposManagementScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/ReposViewerScreen.d.ts +5 -0
- package/dist/commands/tui/views/ReposViewerScreen.d.ts.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/index.js +149 -26
- package/dist/lib/config.d.ts +34 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/brain/skills/brain/SKILL.md +2 -2
- package/dist/tools/brain/skills/brain/references/workflows.md +10 -10
- package/dist/tools/coach/skills/coach/SKILL.md +6 -6
- package/dist/tools/codex/skills/codex/SKILL.md +5 -5
- package/dist/tools/codex/skills/codex/references/creating.md +2 -2
- package/dist/tools/codex/skills/codex/references/decisions.md +2 -2
- package/dist/tools/codex/skills/codex/references/loading.md +1 -1
- package/dist/tools/codex/skills/codex/references/topics.md +2 -2
- package/dist/tools/codex/skills/codex/scripts/git-finish-write.d.ts +1 -1
- package/dist/tools/codex/skills/codex/scripts/git-finish-write.ts +2 -2
- package/dist/tools/codex/skills/codex/scripts/git-preamble.d.ts +1 -1
- package/dist/tools/codex/skills/codex/scripts/git-preamble.ts +3 -3
- package/dist/tools/codex/skills/codex/scripts/git-start-write.d.ts +1 -1
- package/dist/tools/codex/skills/codex/scripts/git-start-write.ts +2 -2
- package/dist/tools/comments/skills/comments/SKILL.md +9 -9
- package/dist/tools/plan/skills/plan/SKILL.md +2 -2
- package/dist/tools/plan/skills/plan/references/workflows.md +2 -2
- package/dist/tools/project/skills/project/SKILL.md +1 -1
- package/dist/tools/project/skills/project/references/creating.md +2 -2
- package/dist/tools/project/skills/project/references/loading.md +1 -1
- package/dist/tools/tech-design/skills/tech-design/SKILL.md +2 -2
- package/dist/tools/tech-design/skills/tech-design/references/publish.md +3 -3
- package/dist/tools/tech-design/skills/tech-design/references/start.md +29 -3
- package/dist/tools/tech-design/skills/tech-design/references/think.md +1 -1
- package/dist/tools/wrapup/skills/wrapup/references/subagent-prompts.md +3 -3
- package/package.json +1 -1
- package/src/bin/droid.ts +39 -0
- package/src/commands/config.ts +14 -1
- package/src/commands/repos.ts +185 -0
- package/src/commands/tui/components/SettingsDetails.tsx +42 -13
- package/src/commands/tui/types.ts +1 -1
- package/src/commands/tui/views/ReposManagementScreen.tsx +291 -0
- package/src/commands/tui/views/ReposViewerScreen.tsx +49 -0
- package/src/commands/tui.tsx +51 -4
- package/src/lib/config.test.ts +228 -1
- package/src/lib/config.ts +193 -4
- package/src/lib/types.ts +13 -1
- package/src/tools/brain/skills/brain/SKILL.md +2 -2
- package/src/tools/brain/skills/brain/references/workflows.md +10 -10
- package/src/tools/coach/skills/coach/SKILL.md +6 -6
- package/src/tools/codex/skills/codex/SKILL.md +5 -5
- package/src/tools/codex/skills/codex/references/creating.md +2 -2
- package/src/tools/codex/skills/codex/references/decisions.md +2 -2
- package/src/tools/codex/skills/codex/references/loading.md +1 -1
- package/src/tools/codex/skills/codex/references/topics.md +2 -2
- package/src/tools/codex/skills/codex/scripts/git-finish-write.ts +2 -2
- package/src/tools/codex/skills/codex/scripts/git-preamble.ts +3 -3
- package/src/tools/codex/skills/codex/scripts/git-start-write.ts +2 -2
- package/src/tools/comments/skills/comments/SKILL.md +9 -9
- package/src/tools/plan/skills/plan/SKILL.md +2 -2
- package/src/tools/plan/skills/plan/references/workflows.md +2 -2
- package/src/tools/project/skills/project/SKILL.md +1 -1
- package/src/tools/project/skills/project/references/creating.md +2 -2
- package/src/tools/project/skills/project/references/loading.md +1 -1
- package/src/tools/tech-design/skills/tech-design/SKILL.md +2 -2
- package/src/tools/tech-design/skills/tech-design/references/publish.md +3 -3
- package/src/tools/tech-design/skills/tech-design/references/start.md +29 -3
- package/src/tools/tech-design/skills/tech-design/references/think.md +1 -1
- 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}>
|
|
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
|
-
|
|
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=
|
|
62
|
-
bold
|
|
89
|
+
backgroundColor={selectedAction === 2 ? colors.primary : undefined}
|
|
90
|
+
color={selectedAction === 2 ? '#ffffff' : colors.textDim}
|
|
91
|
+
bold={selectedAction === 2}
|
|
63
92
|
>
|
|
64
|
-
{' '}
|
|
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}
|
|
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
|
|
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
|
+
}
|