@erllecta/coding-helper 1.0.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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * This file was auto-generated by openapi-typescript.
3
+ * Do not make direct changes to the file.
4
+ */
5
+ export {};
package/dist/app.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ flags?: {
4
+ refresh?: boolean;
5
+ };
6
+ };
7
+ export default function App({ flags }: Props): React.JSX.Element;
8
+ export {};
package/dist/app.js ADDED
@@ -0,0 +1,157 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box } from 'ink';
3
+ import ApiKeyInput from './components/ApiKeyInput.js';
4
+ import MainMenu from './components/MainMenu.js';
5
+ import ModeSelect from './components/ModeSelect.js';
6
+ import AssistantList from './components/AssistantList.js';
7
+ import AssistantDetail from './components/AssistantDetail.js';
8
+ import ToolSelect from './components/ToolSelect.js';
9
+ import ReviewScreen from './components/ReviewScreen.js';
10
+ import SuccessScreen from './components/SuccessScreen.js';
11
+ import McpSetup from './components/McpSetup.js';
12
+ import { getApiKey, setApiKey, getAssistantId, setAssistantId, getAssistantName, setAssistantName, setTargets, getTargets, } from './store.js';
13
+ import { writeClaudeSettings, hasClaudeMcpConfig } from './tools/claude-code.js';
14
+ import { writeOpenCodeConfig, hasOpenCodeMcpConfig } from './tools/opencode.js';
15
+ import { getAnthropicBaseUrl } from './api/ergpt.js';
16
+ export default function App({ flags = {} }) {
17
+ const [screen, setScreen] = useState('api-key-input');
18
+ const [apiKey, setApiKeyState] = useState('');
19
+ const [selectedAssistant, setSelectedAssistant] = useState(null);
20
+ const [assistantWithTools, setAssistantWithTools] = useState(null);
21
+ const [targets, setTargetsState] = useState([]);
22
+ const [useAssistant, setUseAssistant] = useState(null);
23
+ const [mcpConfigured, setMcpConfigured] = useState({ claudeCode: false, opencode: false });
24
+ useEffect(() => {
25
+ const savedKey = getApiKey();
26
+ if (savedKey) {
27
+ setApiKeyState(savedKey);
28
+ }
29
+ const savedAssistantId = getAssistantId();
30
+ setMcpConfigured({
31
+ claudeCode: hasClaudeMcpConfig(),
32
+ opencode: hasOpenCodeMcpConfig(),
33
+ });
34
+ if (flags.refresh && savedKey && savedAssistantId) {
35
+ setUseAssistant(true);
36
+ setScreen('assistant-detail');
37
+ return;
38
+ }
39
+ if (savedKey) {
40
+ setScreen('main-menu');
41
+ }
42
+ }, []);
43
+ const handleApiKeySuccess = () => {
44
+ setScreen('main-menu');
45
+ };
46
+ const handleMainMenuSelect = (nextScreen) => {
47
+ if (nextScreen === 'assistant-detail') {
48
+ setUseAssistant(true);
49
+ }
50
+ setScreen(nextScreen);
51
+ };
52
+ const handleModeSelect = (withAssistant) => {
53
+ setUseAssistant(withAssistant);
54
+ if (withAssistant) {
55
+ setScreen('assistant-list');
56
+ }
57
+ else {
58
+ setSelectedAssistant(null);
59
+ setAssistantWithTools(null);
60
+ setScreen('tool-select');
61
+ }
62
+ };
63
+ const handleAssistantSelect = (assistant) => {
64
+ setSelectedAssistant(assistant);
65
+ setScreen('assistant-detail');
66
+ };
67
+ const handleAssistantConfirm = (assistant) => {
68
+ setAssistantWithTools(assistant);
69
+ setScreen('tool-select');
70
+ };
71
+ const handleToolSelect = (selectedTargets) => {
72
+ setTargetsState(selectedTargets);
73
+ setScreen('review');
74
+ };
75
+ const handleApply = () => {
76
+ const key = apiKey || getApiKey() || '';
77
+ const assistantId = useAssistant && assistantWithTools ? assistantWithTools.id : null;
78
+ const assistantName = useAssistant && assistantWithTools ? assistantWithTools.name : null;
79
+ const baseUrl = getAnthropicBaseUrl(assistantId ?? undefined);
80
+ setApiKey(key);
81
+ setAssistantId(assistantId ?? undefined);
82
+ setAssistantName(assistantName ?? undefined);
83
+ setTargets(targets);
84
+ for (const target of targets) {
85
+ if (target === 'claude-code') {
86
+ writeClaudeSettings(key, baseUrl);
87
+ }
88
+ else if (target === 'opencode') {
89
+ writeOpenCodeConfig(key, baseUrl);
90
+ }
91
+ }
92
+ setScreen('success');
93
+ };
94
+ const handleDone = () => {
95
+ setMcpConfigured({
96
+ claudeCode: hasClaudeMcpConfig(),
97
+ opencode: hasOpenCodeMcpConfig(),
98
+ });
99
+ setScreen('main-menu');
100
+ setUseAssistant(null);
101
+ setSelectedAssistant(null);
102
+ setAssistantWithTools(null);
103
+ setTargetsState([]);
104
+ };
105
+ const goBack = () => {
106
+ switch (screen) {
107
+ case 'mode-select':
108
+ setScreen('main-menu');
109
+ break;
110
+ case 'assistant-list':
111
+ setScreen('mode-select');
112
+ break;
113
+ case 'assistant-detail':
114
+ setScreen('assistant-list');
115
+ break;
116
+ case 'tool-select':
117
+ if (useAssistant) {
118
+ setScreen('assistant-detail');
119
+ }
120
+ else {
121
+ setScreen('mode-select');
122
+ }
123
+ break;
124
+ case 'review':
125
+ setScreen('tool-select');
126
+ break;
127
+ default:
128
+ setScreen('main-menu');
129
+ }
130
+ };
131
+ const savedKey = getApiKey();
132
+ const savedAssistantId = getAssistantId();
133
+ const savedAssistantName = getAssistantName();
134
+ const savedTargets = getTargets();
135
+ return (React.createElement(Box, { flexDirection: "column" },
136
+ screen === 'api-key-input' && (React.createElement(ApiKeyInput, { onSuccess: handleApiKeySuccess, initialKey: savedKey ?? '' })),
137
+ screen === 'main-menu' && (React.createElement(MainMenu, { onSelect: handleMainMenuSelect, hasAssistant: !!savedAssistantId, assistantName: savedAssistantName, targets: savedTargets, mcpConfigured: mcpConfigured })),
138
+ screen === 'mode-select' && (React.createElement(ModeSelect, { onSelect: handleModeSelect, onBack: goBack })),
139
+ screen === 'assistant-list' && (React.createElement(AssistantList, { apiKey: apiKey || savedKey || '', onSelect: handleAssistantSelect, onBack: goBack })),
140
+ screen === 'assistant-detail' && selectedAssistant && (React.createElement(AssistantDetail, { apiKey: apiKey || savedKey || '', assistant: selectedAssistant, onSelect: handleAssistantConfirm, onBack: goBack })),
141
+ screen === 'assistant-detail' &&
142
+ !selectedAssistant &&
143
+ savedAssistantId && (React.createElement(AssistantDetail, { apiKey: apiKey || savedKey || '', assistant: {
144
+ id: savedAssistantId,
145
+ name: savedAssistantName || 'Неизвестный',
146
+ description: '',
147
+ prompt: '',
148
+ temperature: 1,
149
+ private: false,
150
+ created_at: '',
151
+ updated_at: '',
152
+ }, onSelect: handleAssistantConfirm, onBack: () => setScreen('main-menu') })),
153
+ screen === 'tool-select' && (React.createElement(ToolSelect, { onSelect: handleToolSelect, onBack: goBack, initialTools: targets })),
154
+ screen === 'review' && (React.createElement(ReviewScreen, { apiKey: apiKey || savedKey || '', assistantId: useAssistant && assistantWithTools ? assistantWithTools.id : null, assistantName: useAssistant && assistantWithTools ? assistantWithTools.name : null, targets: targets, onApply: handleApply, onBack: goBack })),
155
+ screen === 'success' && (React.createElement(SuccessScreen, { targets: targets, assistantName: useAssistant && assistantWithTools ? assistantWithTools.name : null, onDone: handleDone })),
156
+ screen === 'mcp-setup' && (React.createElement(McpSetup, { onBack: () => setScreen('main-menu'), onComplete: handleDone }))));
157
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import React from 'react';
3
+ import { render } from 'ink';
4
+ import meow from 'meow';
5
+ import App from './app.js';
6
+ import { getApiKey, getAssistantId, getAssistantName, getTargets, clearConfig, } from './store.js';
7
+ import { getAnthropicBaseUrl } from './api/ergpt.js';
8
+ const cli = meow(`
9
+ Использование
10
+ $ ergpt-helpers
11
+
12
+ Опции
13
+ --status Показать текущую конфигурацию
14
+ --refresh Обновить конфигурацию ассистента
15
+ --clear Очистить всю конфигурацию
16
+
17
+ Примеры
18
+ $ ergpt-helpers
19
+ $ ergpt-helpers --status
20
+ $ ergpt-helpers --refresh
21
+ $ ergpt-helpers --clear
22
+ `, {
23
+ importMeta: import.meta,
24
+ flags: {
25
+ status: {
26
+ type: 'boolean',
27
+ default: false,
28
+ },
29
+ refresh: {
30
+ type: 'boolean',
31
+ default: false,
32
+ },
33
+ clear: {
34
+ type: 'boolean',
35
+ default: false,
36
+ },
37
+ },
38
+ });
39
+ if (cli.flags.status) {
40
+ const apiKey = getApiKey();
41
+ const assistantId = getAssistantId();
42
+ const assistantName = getAssistantName();
43
+ const targets = getTargets();
44
+ console.log('\nER-GPT Coding Helpers - Статус\n');
45
+ console.log(`API Ключ: ${apiKey ? `${apiKey.slice(0, 8)}...${apiKey.slice(-4)}` : 'Не задан'}`);
46
+ console.log(`Ассистент: ${assistantName ?? 'Не выбран'}`);
47
+ console.log(`Инструменты: ${targets?.length ? targets.map(t => t === 'claude-code' ? 'Claude Code' : 'OpenCode').join(', ') : 'Не выбраны'}`);
48
+ console.log(`Base URL: ${getAnthropicBaseUrl(assistantId)}`);
49
+ process.exit(0);
50
+ }
51
+ if (cli.flags.clear) {
52
+ clearConfig();
53
+ console.log('\n✓ Конфигурация очищена\n');
54
+ process.exit(0);
55
+ }
56
+ render(React.createElement(App, { flags: cli.flags }));
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ onSuccess: () => void;
4
+ initialKey?: string;
5
+ }
6
+ export default function ApiKeyInput({ onSuccess, initialKey }: Props): React.JSX.Element;
7
+ export {};
@@ -0,0 +1,47 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { validateApiKey } from '../api/ergpt.js';
5
+ import { setApiKey } from '../store.js';
6
+ export default function ApiKeyInput({ onSuccess, initialKey = '' }) {
7
+ const [apiKey, setApiKeyState] = useState(initialKey);
8
+ const [validating, setValidating] = useState(false);
9
+ const [error, setError] = useState(null);
10
+ const handleSubmit = async (value = apiKey) => {
11
+ if (!value.trim()) {
12
+ setError('Требуется API ключ');
13
+ return;
14
+ }
15
+ setValidating(true);
16
+ setError(null);
17
+ try {
18
+ const isValid = await validateApiKey(value.trim());
19
+ if (isValid) {
20
+ setApiKey(value.trim());
21
+ onSuccess();
22
+ }
23
+ else {
24
+ setError('Неверный API ключ');
25
+ }
26
+ }
27
+ catch {
28
+ setError('Не удалось проверить API ключ');
29
+ }
30
+ finally {
31
+ setValidating(false);
32
+ }
33
+ };
34
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
35
+ React.createElement(Box, { marginBottom: 1 },
36
+ React.createElement(Text, { bold: true, color: "cyan" }, "ER-GPT Coding Helpers")),
37
+ React.createElement(Box, { marginBottom: 1 },
38
+ React.createElement(Text, { dimColor: true }, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0432\u0430\u0448 ER-GPT API \u043A\u043B\u044E\u0447 \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u044F")),
39
+ React.createElement(Box, { marginBottom: 1 },
40
+ React.createElement(Box, { marginRight: 1 },
41
+ React.createElement(Text, null, "API \u041A\u043B\u044E\u0447:")),
42
+ validating ? (React.createElement(Text, { dimColor: true }, "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430...")) : (React.createElement(TextInput, { value: apiKey, onChange: setApiKeyState, onSubmit: handleSubmit, placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 API \u043A\u043B\u044E\u0447", mask: "*" }))),
43
+ error && (React.createElement(Box, { marginBottom: 1 },
44
+ React.createElement(Text, { color: "red" }, error))),
45
+ React.createElement(Box, null,
46
+ React.createElement(Text, { dimColor: true }, "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 Enter \u0434\u043B\u044F \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0438"))));
47
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { Assistant, AssistantWithTools } from '../types.js';
3
+ interface Props {
4
+ apiKey: string;
5
+ assistant: Assistant;
6
+ onSelect: (assistant: AssistantWithTools) => void;
7
+ onBack: () => void;
8
+ }
9
+ export default function AssistantDetail({ apiKey, assistant, onSelect, onBack }: Props): React.JSX.Element;
10
+ export {};
@@ -0,0 +1,125 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import SelectInput from 'ink-select-input';
4
+ import { getAssistant, getKnowledgeBase } from '../api/ergpt.js';
5
+ export default function AssistantDetail({ apiKey, assistant, onSelect, onBack }) {
6
+ const [details, setDetails] = useState(null);
7
+ const [tools, setTools] = useState([]);
8
+ const [knowledgeBase, setKnowledgeBase] = useState(null);
9
+ const [loading, setLoading] = useState(true);
10
+ const [error, setError] = useState(null);
11
+ const [showFullPrompt, setShowFullPrompt] = useState(false);
12
+ useEffect(() => {
13
+ async function loadDetails() {
14
+ setLoading(true);
15
+ setError(null);
16
+ try {
17
+ const result = await getAssistant(apiKey, assistant.id);
18
+ setDetails(result);
19
+ setTools(result.tools || []);
20
+ if (result.kb_id) {
21
+ try {
22
+ const kb = await getKnowledgeBase(apiKey, result.kb_id);
23
+ setKnowledgeBase(kb);
24
+ }
25
+ catch {
26
+ // Ignore KB loading errors
27
+ }
28
+ }
29
+ }
30
+ catch (err) {
31
+ setError(err instanceof Error ? err.message : 'Не удалось загрузить детали ассистента');
32
+ }
33
+ finally {
34
+ setLoading(false);
35
+ }
36
+ }
37
+ loadDetails();
38
+ }, [apiKey, assistant.id]);
39
+ const truncateText = (text, maxLength) => {
40
+ if (text.length <= maxLength)
41
+ return text;
42
+ return text.slice(0, maxLength) + '...';
43
+ };
44
+ if (showFullPrompt && details) {
45
+ const promptLines = details.prompt.split('\n');
46
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
47
+ React.createElement(Box, { marginBottom: 1 },
48
+ React.createElement(Text, { bold: true, color: "cyan" },
49
+ "\u041F\u0440\u043E\u043C\u043F\u0442: ",
50
+ assistant.name)),
51
+ React.createElement(Box, { marginBottom: 1, flexDirection: "column" }, promptLines.map((line, i) => (React.createElement(Text, { key: i, dimColor: true }, line || ' ')))),
52
+ React.createElement(Box, { marginTop: 1 },
53
+ React.createElement(SelectInput, { items: [{ key: 'back', label: '← Назад к деталям', value: 'back' }], onSelect: () => setShowFullPrompt(false) }))));
54
+ }
55
+ const items = [
56
+ { key: 'select', label: '✓ Использовать этого ассистента', value: 'select' },
57
+ { key: 'prompt', label: '📄 Посмотреть полный промпт', value: 'prompt' },
58
+ { key: 'back', label: '← Назад', value: 'back' },
59
+ ];
60
+ const handleSelect = (item) => {
61
+ switch (item.value) {
62
+ case 'back':
63
+ onBack();
64
+ break;
65
+ case 'prompt':
66
+ setShowFullPrompt(true);
67
+ break;
68
+ case 'select':
69
+ if (details) {
70
+ onSelect(details);
71
+ }
72
+ break;
73
+ }
74
+ };
75
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
76
+ React.createElement(Box, { marginBottom: 1 },
77
+ React.createElement(Text, { bold: true, color: "cyan" }, assistant.name)),
78
+ loading && (React.createElement(Box, { marginBottom: 1 },
79
+ React.createElement(Text, { dimColor: true }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0434\u0435\u0442\u0430\u043B\u0435\u0439..."))),
80
+ error && (React.createElement(Box, { marginBottom: 1 },
81
+ React.createElement(Text, { color: "red" }, error))),
82
+ details && (React.createElement(React.Fragment, null,
83
+ React.createElement(Box, { marginBottom: 1 },
84
+ React.createElement(Box, { marginRight: 1 },
85
+ React.createElement(Text, { bold: true }, "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435:")),
86
+ React.createElement(Text, null, details.description || 'Нет описания')),
87
+ React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
88
+ React.createElement(Text, { bold: true }, "\u041F\u0440\u043E\u043C\u043F\u0442:"),
89
+ React.createElement(Box, { marginLeft: 2 },
90
+ React.createElement(Text, { dimColor: true }, truncateText(details.prompt, 150))),
91
+ details.prompt.length > 150 && (React.createElement(Box, { marginLeft: 2 },
92
+ React.createElement(Text, { dimColor: true, color: "gray" }, "(\u043D\u0430\u0436\u043C\u0438\u0442\u0435 \"\u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u043F\u043E\u043B\u043D\u044B\u0439 \u043F\u0440\u043E\u043C\u043F\u0442\" \u0434\u043B\u044F \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430)")))),
93
+ React.createElement(Box, { marginBottom: 1 },
94
+ React.createElement(Box, { marginRight: 1 },
95
+ React.createElement(Text, { bold: true }, "\u0422\u0435\u043C\u043F\u0435\u0440\u0430\u0442\u0443\u0440\u0430:")),
96
+ React.createElement(Text, null, details.temperature)),
97
+ details.tags && details.tags.length > 0 && (React.createElement(Box, { marginBottom: 1 },
98
+ React.createElement(Box, { marginRight: 1 },
99
+ React.createElement(Text, { bold: true }, "\u0422\u0435\u0433\u0438:")),
100
+ React.createElement(Text, null, details.tags.join(', ')))),
101
+ knowledgeBase && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
102
+ React.createElement(Text, { bold: true }, "\u0411\u0430\u0437\u0430 \u0437\u043D\u0430\u043D\u0438\u0439:"),
103
+ React.createElement(Box, { marginLeft: 2 },
104
+ React.createElement(Box, { marginRight: 1 },
105
+ React.createElement(Text, { dimColor: true }, "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435:")),
106
+ React.createElement(Text, null, knowledgeBase.name)),
107
+ knowledgeBase.description && (React.createElement(Box, { marginLeft: 2 },
108
+ React.createElement(Box, { marginRight: 1 },
109
+ React.createElement(Text, { dimColor: true }, "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435:")),
110
+ React.createElement(Text, { dimColor: true }, truncateText(knowledgeBase.description, 80)))))),
111
+ tools && tools.length > 0 && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
112
+ React.createElement(Text, { bold: true },
113
+ "\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B (",
114
+ tools.length,
115
+ "):"),
116
+ tools.slice(0, 5).map((tool) => (React.createElement(Box, { key: tool.id, marginLeft: 2 },
117
+ React.createElement(Text, { dimColor: true },
118
+ "\u2022 ",
119
+ tool.tool?.name || 'Неизвестно')))),
120
+ tools.length > 5 && (React.createElement(Box, { marginLeft: 2 },
121
+ React.createElement(Text, { dimColor: true },
122
+ " ... \u0438 \u0435\u0449\u0451 ",
123
+ tools.length - 5))))),
124
+ React.createElement(SelectInput, { items: items, onSelect: handleSelect })))));
125
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { Assistant } from '../types.js';
3
+ interface Props {
4
+ apiKey: string;
5
+ onSelect: (assistant: Assistant) => void;
6
+ onBack: () => void;
7
+ }
8
+ export default function AssistantList({ apiKey, onSelect, onBack }: Props): React.JSX.Element;
9
+ export {};
@@ -0,0 +1,86 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import SelectInput from 'ink-select-input';
5
+ import { getAssistants } from '../api/ergpt.js';
6
+ export default function AssistantList({ apiKey, onSelect, onBack }) {
7
+ const [assistants, setAssistants] = useState([]);
8
+ const [loading, setLoading] = useState(true);
9
+ const [error, setError] = useState(null);
10
+ const [search, setSearch] = useState('');
11
+ const [cursor, setCursor] = useState();
12
+ const [hasMore, setHasMore] = useState(false);
13
+ const loadAssistants = useCallback(async (searchTerm, cursorVal) => {
14
+ setLoading(true);
15
+ setError(null);
16
+ try {
17
+ const response = await getAssistants(apiKey, {
18
+ limit: 20,
19
+ search: searchTerm || undefined,
20
+ cursor: cursorVal,
21
+ group: 'public',
22
+ });
23
+ if (cursorVal) {
24
+ setAssistants((prev) => [...prev, ...response.result]);
25
+ }
26
+ else {
27
+ setAssistants(response.result);
28
+ }
29
+ setCursor(response.pagination.cursor);
30
+ setHasMore(response.pagination.has_more);
31
+ }
32
+ catch (err) {
33
+ setError(err instanceof Error ? err.message : 'Не удалось загрузить ассистентов');
34
+ }
35
+ finally {
36
+ setLoading(false);
37
+ }
38
+ }, [apiKey]);
39
+ useEffect(() => {
40
+ loadAssistants();
41
+ }, [loadAssistants]);
42
+ useEffect(() => {
43
+ const timer = setTimeout(() => {
44
+ setCursor(undefined);
45
+ loadAssistants(search);
46
+ }, 300);
47
+ return () => clearTimeout(timer);
48
+ }, [search]);
49
+ const items = assistants.map((a) => ({
50
+ key: a.id,
51
+ label: `${a.name} ${a.tags?.length ? `[${a.tags.join(', ')}]` : ''}`,
52
+ value: a.id,
53
+ }));
54
+ if (hasMore) {
55
+ items.push({ key: 'more', label: '→ Загрузить ещё', value: 'more' });
56
+ }
57
+ items.push({ key: 'back', label: '← Назад', value: 'back' });
58
+ const handleSelect = (item) => {
59
+ if (item.value === 'back') {
60
+ onBack();
61
+ }
62
+ else if (item.value === 'more') {
63
+ loadAssistants(search, cursor);
64
+ }
65
+ else {
66
+ const assistant = assistants.find((a) => a.id === item.value);
67
+ if (assistant) {
68
+ onSelect(assistant);
69
+ }
70
+ }
71
+ };
72
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
73
+ React.createElement(Box, { marginBottom: 1 },
74
+ React.createElement(Text, { bold: true }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u0430")),
75
+ React.createElement(Box, { marginBottom: 1 },
76
+ React.createElement(Box, { marginRight: 1 },
77
+ React.createElement(Text, null, "\u041F\u043E\u0438\u0441\u043A:")),
78
+ React.createElement(TextInput, { value: search, onChange: setSearch, placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u043B\u044F \u043F\u043E\u0438\u0441\u043A\u0430..." })),
79
+ loading && (React.createElement(Box, { marginBottom: 1 },
80
+ React.createElement(Text, { dimColor: true }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430..."))),
81
+ error && (React.createElement(Box, { marginBottom: 1 },
82
+ React.createElement(Text, { color: "red" }, error))),
83
+ !loading && !error && assistants.length === 0 && (React.createElement(Box, { marginBottom: 1 },
84
+ React.createElement(Text, { dimColor: true }, "\u0410\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u044B \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B"))),
85
+ !loading && assistants.length > 0 && (React.createElement(SelectInput, { items: items, onSelect: handleSelect }))));
86
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import type { Screen, Tool } from '../types.js';
3
+ interface Props {
4
+ onSelect: (screen: Screen) => void;
5
+ hasAssistant: boolean;
6
+ assistantName?: string;
7
+ targets?: Tool[];
8
+ mcpConfigured?: {
9
+ claudeCode: boolean;
10
+ opencode: boolean;
11
+ };
12
+ }
13
+ export default function MainMenu({ onSelect, hasAssistant, assistantName, targets, mcpConfigured, }: Props): React.JSX.Element;
14
+ export {};
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import SelectInput from 'ink-select-input';
4
+ export default function MainMenu({ onSelect, hasAssistant, assistantName, targets = [], mcpConfigured, }) {
5
+ const items = [
6
+ { key: 'configure', label: 'Настроить ассистента', value: 'mode-select' },
7
+ { key: 'mcp', label: 'Настроить MCP серверы', value: 'mcp-setup' },
8
+ { key: 'status', label: 'Посмотреть текущий конфиг', value: 'review' },
9
+ ];
10
+ if (hasAssistant) {
11
+ items.splice(1, 0, {
12
+ key: 'refresh',
13
+ label: 'Обновить ассистента',
14
+ value: 'assistant-detail',
15
+ });
16
+ }
17
+ items.push({ key: 'exit', label: 'Выход', value: 'exit' });
18
+ const mcpTargets = [];
19
+ if (mcpConfigured?.claudeCode)
20
+ mcpTargets.push('Claude Code');
21
+ if (mcpConfigured?.opencode)
22
+ mcpTargets.push('OpenCode');
23
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
24
+ React.createElement(Box, { marginBottom: 1 },
25
+ React.createElement(Text, { bold: true, color: "cyan" }, "ER-GPT Coding Helpers")),
26
+ React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
27
+ assistantName && (React.createElement(Text, null,
28
+ "\u0410\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442: ",
29
+ React.createElement(Text, { color: "green" }, assistantName))),
30
+ targets.length > 0 && (React.createElement(Text, null,
31
+ "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B:",
32
+ ' ',
33
+ React.createElement(Text, { color: "yellow" }, targets
34
+ .map(t => (t === 'claude-code' ? 'Claude Code' : 'OpenCode'))
35
+ .join(', ')))),
36
+ mcpTargets.length > 0 && (React.createElement(Text, null,
37
+ "MCP: ",
38
+ React.createElement(Text, { color: "magenta" }, mcpTargets.join(', ')))),
39
+ !assistantName && targets.length === 0 && (React.createElement(Text, { dimColor: true }, "\u041A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u0437\u0430\u0434\u0430\u043D\u0430"))),
40
+ React.createElement(Box, { marginBottom: 1 },
41
+ React.createElement(Text, { dimColor: true }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435:")),
42
+ React.createElement(SelectInput, { items: items, onSelect: item => {
43
+ if (item.value === 'exit') {
44
+ process.exit(0);
45
+ }
46
+ onSelect(item.value);
47
+ } })));
48
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ onBack: () => void;
4
+ onComplete: () => void;
5
+ }
6
+ export default function McpSetup({ onBack, onComplete }: Props): React.JSX.Element;
7
+ export {};