@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.
- package/dist/api/ergpt.d.ts +12 -0
- package/dist/api/ergpt.js +48 -0
- package/dist/api/types/generated.d.ts +3629 -0
- package/dist/api/types/generated.js +5 -0
- package/dist/app.d.ts +8 -0
- package/dist/app.js +157 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +56 -0
- package/dist/components/ApiKeyInput.d.ts +7 -0
- package/dist/components/ApiKeyInput.js +47 -0
- package/dist/components/AssistantDetail.d.ts +10 -0
- package/dist/components/AssistantDetail.js +125 -0
- package/dist/components/AssistantList.d.ts +9 -0
- package/dist/components/AssistantList.js +86 -0
- package/dist/components/MainMenu.d.ts +14 -0
- package/dist/components/MainMenu.js +48 -0
- package/dist/components/McpSetup.d.ts +7 -0
- package/dist/components/McpSetup.js +109 -0
- package/dist/components/ModeSelect.d.ts +7 -0
- package/dist/components/ModeSelect.js +26 -0
- package/dist/components/ReviewScreen.d.ts +12 -0
- package/dist/components/ReviewScreen.js +77 -0
- package/dist/components/SuccessScreen.d.ts +9 -0
- package/dist/components/SuccessScreen.js +24 -0
- package/dist/components/ToolSelect.d.ts +9 -0
- package/dist/components/ToolSelect.js +48 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.js +52 -0
- package/dist/tools/claude-code.d.ts +19 -0
- package/dist/tools/claude-code.js +108 -0
- package/dist/tools/opencode.d.ts +26 -0
- package/dist/tools/opencode.js +99 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.js +1 -0
- package/package.json +83 -0
- package/readme.md +87 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
import { writeClaudeMcpConfig, getClaudeUserConfigPath, } from '../tools/claude-code.js';
|
|
5
|
+
import { writeOpenCodeMcpConfig, getOpenCodeConfigPath, } from '../tools/opencode.js';
|
|
6
|
+
import { getApiKey, getAssistantId, getAssistantName } from '../store.js';
|
|
7
|
+
const ALL_TOOLS = ['claude-code', 'opencode'];
|
|
8
|
+
export default function McpSetup({ onBack, onComplete }) {
|
|
9
|
+
const [selectedTools, setSelectedTools] = useState(new Set());
|
|
10
|
+
const [done, setDone] = useState(false);
|
|
11
|
+
const apiKey = getApiKey();
|
|
12
|
+
const assistantId = getAssistantId();
|
|
13
|
+
const assistantName = getAssistantName();
|
|
14
|
+
const toggleTool = (tool) => {
|
|
15
|
+
const newSet = new Set(selectedTools);
|
|
16
|
+
if (newSet.has(tool)) {
|
|
17
|
+
newSet.delete(tool);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
newSet.add(tool);
|
|
21
|
+
}
|
|
22
|
+
setSelectedTools(newSet);
|
|
23
|
+
};
|
|
24
|
+
const applyConfig = () => {
|
|
25
|
+
if (!apiKey || !assistantId)
|
|
26
|
+
return;
|
|
27
|
+
for (const tool of selectedTools) {
|
|
28
|
+
if (tool === 'claude-code') {
|
|
29
|
+
writeClaudeMcpConfig(assistantId, apiKey);
|
|
30
|
+
}
|
|
31
|
+
else if (tool === 'opencode') {
|
|
32
|
+
writeOpenCodeMcpConfig(assistantId, apiKey);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
setDone(true);
|
|
36
|
+
};
|
|
37
|
+
const items = [
|
|
38
|
+
...ALL_TOOLS.map(tool => ({
|
|
39
|
+
key: tool,
|
|
40
|
+
label: `${selectedTools.has(tool) ? '✓' : ' '} ${tool === 'claude-code' ? 'Claude Code' : 'OpenCode'}`,
|
|
41
|
+
value: tool,
|
|
42
|
+
})),
|
|
43
|
+
{ key: 'apply', label: '→ Применить', value: 'apply' },
|
|
44
|
+
{ key: 'back', label: '← Назад', value: 'back' },
|
|
45
|
+
];
|
|
46
|
+
const handleSelect = (item) => {
|
|
47
|
+
if (item.value === 'back') {
|
|
48
|
+
onBack();
|
|
49
|
+
}
|
|
50
|
+
else if (item.value === 'apply') {
|
|
51
|
+
if (selectedTools.size === 0 || !apiKey || !assistantId)
|
|
52
|
+
return;
|
|
53
|
+
applyConfig();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
toggleTool(item.value);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
if (done) {
|
|
60
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
61
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
62
|
+
React.createElement(Text, { bold: true, color: "green" }, "MCP \u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D!")),
|
|
63
|
+
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
64
|
+
React.createElement(Text, null, "URL: https://api.er-gpt.ru/mcp/sse"),
|
|
65
|
+
React.createElement(Text, null,
|
|
66
|
+
"Assistant: ",
|
|
67
|
+
assistantName),
|
|
68
|
+
React.createElement(Text, { dimColor: true }, "Headers:"),
|
|
69
|
+
React.createElement(Text, { dimColor: true }, " Authorization: Bearer ***"),
|
|
70
|
+
React.createElement(Text, { dimColor: true },
|
|
71
|
+
" X-Assistant-Id: ",
|
|
72
|
+
assistantId)),
|
|
73
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
74
|
+
selectedTools.has('claude-code') && (React.createElement(Text, null,
|
|
75
|
+
" \u2022 Claude Code: ",
|
|
76
|
+
getClaudeUserConfigPath())),
|
|
77
|
+
selectedTools.has('opencode') && (React.createElement(Text, null,
|
|
78
|
+
" \u2022 OpenCode: ",
|
|
79
|
+
getOpenCodeConfigPath()))),
|
|
80
|
+
React.createElement(Box, { marginTop: 1 },
|
|
81
|
+
React.createElement(Text, { dimColor: true }, "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 Enter \u0434\u043B\u044F \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430 \u0432 \u043C\u0435\u043D\u044E")),
|
|
82
|
+
React.createElement(SelectInput, { items: [{ key: 'done', label: 'Готово', value: 'done' }], onSelect: onComplete })));
|
|
83
|
+
}
|
|
84
|
+
if (!apiKey) {
|
|
85
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
86
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
87
|
+
React.createElement(Text, { color: "red" }, "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 API \u043A\u043B\u044E\u0447")),
|
|
88
|
+
React.createElement(SelectInput, { items: [{ key: 'back', label: '← Назад', value: 'back' }], onSelect: onBack })));
|
|
89
|
+
}
|
|
90
|
+
if (!assistantId) {
|
|
91
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
92
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
93
|
+
React.createElement(Text, { color: "red" }, "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u0430")),
|
|
94
|
+
React.createElement(SelectInput, { items: [{ key: 'back', label: '← Назад', value: 'back' }], onSelect: onBack })));
|
|
95
|
+
}
|
|
96
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
97
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
98
|
+
React.createElement(Text, { bold: true }, "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 MCP \u0441\u0435\u0440\u0432\u0435\u0440\u0430")),
|
|
99
|
+
React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
100
|
+
React.createElement(Text, { dimColor: true }, "URL: https://api.er-gpt.ru/mcp/sse"),
|
|
101
|
+
React.createElement(Text, { dimColor: true },
|
|
102
|
+
"Assistant: ",
|
|
103
|
+
assistantName)),
|
|
104
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
105
|
+
React.createElement(Text, { dimColor: true }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0434\u043B\u044F \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438:")),
|
|
106
|
+
React.createElement(SelectInput, { items: items, onSelect: handleSelect }),
|
|
107
|
+
selectedTools.size === 0 && (React.createElement(Box, { marginTop: 1 },
|
|
108
|
+
React.createElement(Text, { color: "yellow" }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442")))));
|
|
109
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
export default function ModeSelect({ onSelect, onBack }) {
|
|
5
|
+
const items = [
|
|
6
|
+
{ key: 'with', label: 'Выбрать ассистента', value: 'with' },
|
|
7
|
+
{ key: 'without', label: 'Без ассистента (обычный режим)', value: 'without' },
|
|
8
|
+
{ key: 'back', label: '← Назад', value: 'back' },
|
|
9
|
+
];
|
|
10
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
11
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
12
|
+
React.createElement(Text, { bold: true }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0435\u0436\u0438\u043C")),
|
|
13
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
14
|
+
React.createElement(Text, { dimColor: true }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043F\u043E\u0441\u043E\u0431 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 API:")),
|
|
15
|
+
React.createElement(SelectInput, { items: items, onSelect: (item) => {
|
|
16
|
+
if (item.value === 'back') {
|
|
17
|
+
onBack();
|
|
18
|
+
}
|
|
19
|
+
else if (item.value === 'with') {
|
|
20
|
+
onSelect(true);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
onSelect(false);
|
|
24
|
+
}
|
|
25
|
+
} })));
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Tool } from '../types.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
assistantId: string | null;
|
|
6
|
+
assistantName: string | null;
|
|
7
|
+
targets: Tool[];
|
|
8
|
+
onApply: () => void;
|
|
9
|
+
onBack: () => void;
|
|
10
|
+
}
|
|
11
|
+
export default function ReviewScreen({ apiKey, assistantId, assistantName, targets, onApply, onBack, }: Props): React.JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
import { getAnthropicBaseUrl, getAssistant } from '../api/ergpt.js';
|
|
5
|
+
import { getClaudeConfigPath } from '../tools/claude-code.js';
|
|
6
|
+
import { getOpenCodeConfigPath } from '../tools/opencode.js';
|
|
7
|
+
export default function ReviewScreen({ apiKey, assistantId, assistantName, targets, onApply, onBack, }) {
|
|
8
|
+
const [assistant, setAssistant] = useState(null);
|
|
9
|
+
const [loading, setLoading] = useState(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (assistantId && apiKey) {
|
|
12
|
+
setLoading(true);
|
|
13
|
+
getAssistant(apiKey, assistantId)
|
|
14
|
+
.then(setAssistant)
|
|
15
|
+
.catch(() => setAssistant(null))
|
|
16
|
+
.finally(() => setLoading(false));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
setAssistant(null);
|
|
20
|
+
}
|
|
21
|
+
}, [assistantId, apiKey]);
|
|
22
|
+
const baseUrl = getAnthropicBaseUrl(assistantId ?? undefined);
|
|
23
|
+
const maskedKey = `${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`;
|
|
24
|
+
const items = [
|
|
25
|
+
{ key: 'apply', label: '✓ Применить конфигурацию', value: 'apply' },
|
|
26
|
+
{ key: 'back', label: '← Назад', value: 'back' },
|
|
27
|
+
];
|
|
28
|
+
const handleSelect = (item) => {
|
|
29
|
+
if (item.value === 'back') {
|
|
30
|
+
onBack();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
onApply();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const activeTools = assistant?.tools?.filter(t => t.active && t.tool) ?? [];
|
|
37
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
38
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
39
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438")),
|
|
40
|
+
React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
41
|
+
React.createElement(Text, { bold: true }, "API \u041A\u043B\u044E\u0447:"),
|
|
42
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
43
|
+
React.createElement(Text, { dimColor: true }, maskedKey))),
|
|
44
|
+
React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
45
|
+
React.createElement(Text, { bold: true }, "\u0420\u0435\u0436\u0438\u043C:"),
|
|
46
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
47
|
+
React.createElement(Text, null, assistantName
|
|
48
|
+
? `Ассистент: ${assistantName}`
|
|
49
|
+
: 'Обычный (без ассистента)'))),
|
|
50
|
+
assistantId && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
51
|
+
React.createElement(Text, { bold: true }, "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u0430:"),
|
|
52
|
+
loading ? (React.createElement(Box, { marginLeft: 2 },
|
|
53
|
+
React.createElement(Text, { dimColor: true }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430..."))) : activeTools.length > 0 ? (activeTools.map(t => (React.createElement(Box, { key: t.id, marginLeft: 2 },
|
|
54
|
+
React.createElement(Text, { dimColor: true },
|
|
55
|
+
"\u2022 ",
|
|
56
|
+
t.tool?.name))))) : (React.createElement(Box, { marginLeft: 2 },
|
|
57
|
+
React.createElement(Text, { dimColor: true }, "\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432"))))),
|
|
58
|
+
React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
59
|
+
React.createElement(Text, { bold: true }, "Base URL:"),
|
|
60
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
61
|
+
React.createElement(Text, { dimColor: true }, baseUrl))),
|
|
62
|
+
React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
63
|
+
React.createElement(Text, { bold: true }, "\u0426\u0435\u043B\u0435\u0432\u044B\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B:"),
|
|
64
|
+
targets.map(tool => (React.createElement(Box, { key: tool, marginLeft: 2 },
|
|
65
|
+
React.createElement(Text, { dimColor: true },
|
|
66
|
+
"\u2022 ",
|
|
67
|
+
tool === 'claude-code' ? 'Claude Code' : 'OpenCode'),
|
|
68
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
69
|
+
React.createElement(Text, { dimColor: true },
|
|
70
|
+
"\u2192",
|
|
71
|
+
' ',
|
|
72
|
+
tool === 'claude-code'
|
|
73
|
+
? getClaudeConfigPath()
|
|
74
|
+
: getOpenCodeConfigPath())))))),
|
|
75
|
+
React.createElement(Box, { marginTop: 1 },
|
|
76
|
+
React.createElement(SelectInput, { items: items, onSelect: handleSelect }))));
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Tool } from '../types.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
targets: Tool[];
|
|
5
|
+
assistantName: string | null;
|
|
6
|
+
onDone: () => void;
|
|
7
|
+
}
|
|
8
|
+
export default function SuccessScreen({ targets, assistantName, onDone }: Props): React.JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
export default function SuccessScreen({ targets, assistantName, onDone }) {
|
|
5
|
+
const items = [{ key: 'done', label: '✓ Готово', value: 'done' }];
|
|
6
|
+
const handleSelect = () => {
|
|
7
|
+
onDone();
|
|
8
|
+
};
|
|
9
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
10
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
11
|
+
React.createElement(Text, { bold: true, color: "green" }, "\u2713 \u041A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044F \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u0430!")),
|
|
12
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
13
|
+
React.createElement(Text, null, assistantName
|
|
14
|
+
? `Ассистент "${assistantName}" настроен`
|
|
15
|
+
: 'Обычный режим настроен')),
|
|
16
|
+
React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
17
|
+
React.createElement(Text, { bold: true }, "\u0422\u0435\u043F\u0435\u0440\u044C \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C:"),
|
|
18
|
+
targets.includes('claude-code') && (React.createElement(Box, { marginLeft: 2 },
|
|
19
|
+
React.createElement(Text, { dimColor: true }, "\u2022 claude"))),
|
|
20
|
+
targets.includes('opencode') && (React.createElement(Box, { marginLeft: 2 },
|
|
21
|
+
React.createElement(Text, { dimColor: true }, "\u2022 opencode")))),
|
|
22
|
+
React.createElement(Box, { marginTop: 1 },
|
|
23
|
+
React.createElement(SelectInput, { items: items, onSelect: handleSelect }))));
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Tool } from '../types.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
onSelect: (tools: Tool[]) => void;
|
|
5
|
+
onBack: () => void;
|
|
6
|
+
initialTools?: Tool[];
|
|
7
|
+
}
|
|
8
|
+
export default function ToolSelect({ onSelect, onBack, initialTools }: Props): React.JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
const ALL_TOOLS = ['claude-code', 'opencode'];
|
|
5
|
+
export default function ToolSelect({ onSelect, onBack, initialTools = [] }) {
|
|
6
|
+
const [selectedTools, setSelectedTools] = useState(new Set(initialTools));
|
|
7
|
+
const toggleTool = (tool) => {
|
|
8
|
+
const newSet = new Set(selectedTools);
|
|
9
|
+
if (newSet.has(tool)) {
|
|
10
|
+
newSet.delete(tool);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
newSet.add(tool);
|
|
14
|
+
}
|
|
15
|
+
setSelectedTools(newSet);
|
|
16
|
+
};
|
|
17
|
+
const items = [
|
|
18
|
+
...ALL_TOOLS.map((tool) => ({
|
|
19
|
+
key: tool,
|
|
20
|
+
label: `${selectedTools.has(tool) ? '✓' : ' '} ${tool === 'claude-code' ? 'Claude Code' : 'OpenCode'}`,
|
|
21
|
+
value: tool,
|
|
22
|
+
})),
|
|
23
|
+
{ key: 'continue', label: '→ Продолжить', value: 'continue' },
|
|
24
|
+
{ key: 'back', label: '← Назад', value: 'back' },
|
|
25
|
+
];
|
|
26
|
+
const handleSelect = (item) => {
|
|
27
|
+
if (item.value === 'back') {
|
|
28
|
+
onBack();
|
|
29
|
+
}
|
|
30
|
+
else if (item.value === 'continue') {
|
|
31
|
+
if (selectedTools.size === 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
onSelect(Array.from(selectedTools));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
toggleTool(item.value);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
41
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
42
|
+
React.createElement(Text, { bold: true }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B")),
|
|
43
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
44
|
+
React.createElement(Text, { dimColor: true }, "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 Enter \u0434\u043B\u044F \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F, \u0437\u0430\u0442\u0435\u043C \"\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C\"")),
|
|
45
|
+
React.createElement(SelectInput, { items: items, onSelect: handleSelect }),
|
|
46
|
+
selectedTools.size === 0 && (React.createElement(Box, { marginTop: 1 },
|
|
47
|
+
React.createElement(Text, { color: "yellow" }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442")))));
|
|
48
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StoreConfig, Tool } from './types.js';
|
|
2
|
+
export declare function getApiKey(): string | undefined;
|
|
3
|
+
export declare function setApiKey(key: string): void;
|
|
4
|
+
export declare function getAssistantId(): string | undefined;
|
|
5
|
+
export declare function setAssistantId(id: string | undefined): void;
|
|
6
|
+
export declare function getAssistantName(): string | undefined;
|
|
7
|
+
export declare function setAssistantName(name: string | undefined): void;
|
|
8
|
+
export declare function getTargets(): Tool[];
|
|
9
|
+
export declare function setTargets(targets: Tool[]): void;
|
|
10
|
+
export declare function clearConfig(): void;
|
|
11
|
+
export declare function getConfigPath(): string;
|
|
12
|
+
export declare function getConfig(): StoreConfig;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
const config = new Conf({
|
|
3
|
+
projectName: 'ergpt-helpers',
|
|
4
|
+
});
|
|
5
|
+
export function getApiKey() {
|
|
6
|
+
return config.get('apiKey');
|
|
7
|
+
}
|
|
8
|
+
export function setApiKey(key) {
|
|
9
|
+
config.set('apiKey', key);
|
|
10
|
+
}
|
|
11
|
+
export function getAssistantId() {
|
|
12
|
+
return config.get('assistantId');
|
|
13
|
+
}
|
|
14
|
+
export function setAssistantId(id) {
|
|
15
|
+
if (id) {
|
|
16
|
+
config.set('assistantId', id);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
config.delete('assistantId');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function getAssistantName() {
|
|
23
|
+
return config.get('assistantName');
|
|
24
|
+
}
|
|
25
|
+
export function setAssistantName(name) {
|
|
26
|
+
if (name) {
|
|
27
|
+
config.set('assistantName', name);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
config.delete('assistantName');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function getTargets() {
|
|
34
|
+
return config.get('targets') ?? [];
|
|
35
|
+
}
|
|
36
|
+
export function setTargets(targets) {
|
|
37
|
+
config.set('targets', targets);
|
|
38
|
+
}
|
|
39
|
+
export function clearConfig() {
|
|
40
|
+
config.clear();
|
|
41
|
+
}
|
|
42
|
+
export function getConfigPath() {
|
|
43
|
+
return config.path;
|
|
44
|
+
}
|
|
45
|
+
export function getConfig() {
|
|
46
|
+
return {
|
|
47
|
+
apiKey: getApiKey(),
|
|
48
|
+
assistantId: getAssistantId(),
|
|
49
|
+
assistantName: getAssistantName(),
|
|
50
|
+
targets: getTargets(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClaudeUserConfig } from '../types.js';
|
|
2
|
+
interface ClaudeSettings {
|
|
3
|
+
env?: {
|
|
4
|
+
ANTHROPIC_AUTH_TOKEN?: string;
|
|
5
|
+
ANTHROPIC_BASE_URL?: string;
|
|
6
|
+
[key: string]: string | undefined;
|
|
7
|
+
};
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare function readClaudeSettings(): ClaudeSettings | null;
|
|
11
|
+
export declare function writeClaudeSettings(apiKey: string, baseUrl: string): void;
|
|
12
|
+
export declare function clearClaudeSettings(): void;
|
|
13
|
+
export declare function getClaudeConfigPath(): string;
|
|
14
|
+
export declare function readClaudeUserConfig(): ClaudeUserConfig | null;
|
|
15
|
+
export declare function writeClaudeMcpConfig(assistantId: string, apiKey: string): void;
|
|
16
|
+
export declare function clearClaudeMcpConfig(): void;
|
|
17
|
+
export declare function getClaudeUserConfigPath(): string;
|
|
18
|
+
export declare function hasClaudeMcpConfig(): boolean;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
5
|
+
const CLAUDE_USER_CONFIG_PATH = path.join(os.homedir(), '.claude.json');
|
|
6
|
+
const MCP_SERVER_NAME = 'ergpt';
|
|
7
|
+
export function readClaudeSettings() {
|
|
8
|
+
try {
|
|
9
|
+
if (!fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8');
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function writeClaudeSettings(apiKey, baseUrl) {
|
|
20
|
+
const dir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
21
|
+
if (!fs.existsSync(dir)) {
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
let settings = {};
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
27
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8');
|
|
28
|
+
settings = JSON.parse(content);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
settings = {};
|
|
33
|
+
}
|
|
34
|
+
settings.env = {
|
|
35
|
+
...settings.env,
|
|
36
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
37
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
38
|
+
};
|
|
39
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
40
|
+
}
|
|
41
|
+
export function clearClaudeSettings() {
|
|
42
|
+
const settings = readClaudeSettings();
|
|
43
|
+
if (settings?.env) {
|
|
44
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
45
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
46
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function getClaudeConfigPath() {
|
|
50
|
+
return CLAUDE_SETTINGS_PATH;
|
|
51
|
+
}
|
|
52
|
+
export function readClaudeUserConfig() {
|
|
53
|
+
try {
|
|
54
|
+
if (!fs.existsSync(CLAUDE_USER_CONFIG_PATH)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const content = fs.readFileSync(CLAUDE_USER_CONFIG_PATH, 'utf-8');
|
|
58
|
+
return JSON.parse(content);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function writeClaudeMcpConfig(assistantId, apiKey) {
|
|
65
|
+
const dir = path.dirname(CLAUDE_USER_CONFIG_PATH);
|
|
66
|
+
if (!fs.existsSync(dir)) {
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
let config = {};
|
|
70
|
+
try {
|
|
71
|
+
if (fs.existsSync(CLAUDE_USER_CONFIG_PATH)) {
|
|
72
|
+
const content = fs.readFileSync(CLAUDE_USER_CONFIG_PATH, 'utf-8');
|
|
73
|
+
config = JSON.parse(content);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
config = {};
|
|
78
|
+
}
|
|
79
|
+
if (!config.mcpServers) {
|
|
80
|
+
config.mcpServers = {};
|
|
81
|
+
}
|
|
82
|
+
config.mcpServers[MCP_SERVER_NAME] = {
|
|
83
|
+
type: 'sse',
|
|
84
|
+
url: 'https://api.er-gpt.ru/mcp/sse',
|
|
85
|
+
headers: {
|
|
86
|
+
Authorization: `Bearer ${apiKey}`,
|
|
87
|
+
'X-Assistant-Id': assistantId,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
fs.writeFileSync(CLAUDE_USER_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
91
|
+
}
|
|
92
|
+
export function clearClaudeMcpConfig() {
|
|
93
|
+
const config = readClaudeUserConfig();
|
|
94
|
+
if (config?.mcpServers?.[MCP_SERVER_NAME]) {
|
|
95
|
+
delete config.mcpServers[MCP_SERVER_NAME];
|
|
96
|
+
if (Object.keys(config.mcpServers).length === 0) {
|
|
97
|
+
delete config.mcpServers;
|
|
98
|
+
}
|
|
99
|
+
fs.writeFileSync(CLAUDE_USER_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function getClaudeUserConfigPath() {
|
|
103
|
+
return CLAUDE_USER_CONFIG_PATH;
|
|
104
|
+
}
|
|
105
|
+
export function hasClaudeMcpConfig() {
|
|
106
|
+
const config = readClaudeUserConfig();
|
|
107
|
+
return !!config?.mcpServers?.[MCP_SERVER_NAME];
|
|
108
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { OpenCodeMcpServerConfig } from '../types.js';
|
|
2
|
+
interface OpenCodeProviderOptions {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface OpenCodeProvider {
|
|
9
|
+
ergpt?: {
|
|
10
|
+
options?: OpenCodeProviderOptions;
|
|
11
|
+
};
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
interface OpenCodeConfig {
|
|
15
|
+
provider?: OpenCodeProvider;
|
|
16
|
+
mcp?: Record<string, OpenCodeMcpServerConfig>;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
export declare function readOpenCodeConfig(): OpenCodeConfig | null;
|
|
20
|
+
export declare function writeOpenCodeConfig(apiKey: string, baseUrl: string): void;
|
|
21
|
+
export declare function clearOpenCodeConfig(): void;
|
|
22
|
+
export declare function getOpenCodeConfigPath(): string;
|
|
23
|
+
export declare function writeOpenCodeMcpConfig(assistantId: string, apiKey: string): void;
|
|
24
|
+
export declare function clearOpenCodeMcpConfig(): void;
|
|
25
|
+
export declare function hasOpenCodeMcpConfig(): boolean;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
const OPENCODE_CONFIG_PATH = path.join(os.homedir(), '.config', 'opencode', 'opencode.json');
|
|
5
|
+
const MCP_SERVER_NAME = 'ergpt';
|
|
6
|
+
export function readOpenCodeConfig() {
|
|
7
|
+
try {
|
|
8
|
+
if (!fs.existsSync(OPENCODE_CONFIG_PATH)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const content = fs.readFileSync(OPENCODE_CONFIG_PATH, 'utf-8');
|
|
12
|
+
return JSON.parse(content);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function writeOpenCodeConfig(apiKey, baseUrl) {
|
|
19
|
+
const dir = path.dirname(OPENCODE_CONFIG_PATH);
|
|
20
|
+
if (!fs.existsSync(dir)) {
|
|
21
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
let config = {};
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(OPENCODE_CONFIG_PATH)) {
|
|
26
|
+
const content = fs.readFileSync(OPENCODE_CONFIG_PATH, 'utf-8');
|
|
27
|
+
config = JSON.parse(content);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
config = {};
|
|
32
|
+
}
|
|
33
|
+
if (!config.provider) {
|
|
34
|
+
config.provider = {};
|
|
35
|
+
}
|
|
36
|
+
if (!config.provider.ergpt) {
|
|
37
|
+
config.provider.ergpt = {};
|
|
38
|
+
}
|
|
39
|
+
if (!config.provider.ergpt.options) {
|
|
40
|
+
config.provider.ergpt.options = {};
|
|
41
|
+
}
|
|
42
|
+
config.provider.ergpt.options.apiKey = apiKey;
|
|
43
|
+
config.provider.ergpt.options.baseURL = baseUrl;
|
|
44
|
+
fs.writeFileSync(OPENCODE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
45
|
+
}
|
|
46
|
+
export function clearOpenCodeConfig() {
|
|
47
|
+
const config = readOpenCodeConfig();
|
|
48
|
+
if (config?.provider?.ergpt?.options) {
|
|
49
|
+
delete config.provider.ergpt.options.apiKey;
|
|
50
|
+
delete config.provider.ergpt.options.baseURL;
|
|
51
|
+
fs.writeFileSync(OPENCODE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function getOpenCodeConfigPath() {
|
|
55
|
+
return OPENCODE_CONFIG_PATH;
|
|
56
|
+
}
|
|
57
|
+
export function writeOpenCodeMcpConfig(assistantId, apiKey) {
|
|
58
|
+
const dir = path.dirname(OPENCODE_CONFIG_PATH);
|
|
59
|
+
if (!fs.existsSync(dir)) {
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
let config = {};
|
|
63
|
+
try {
|
|
64
|
+
if (fs.existsSync(OPENCODE_CONFIG_PATH)) {
|
|
65
|
+
const content = fs.readFileSync(OPENCODE_CONFIG_PATH, 'utf-8');
|
|
66
|
+
config = JSON.parse(content);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
config = {};
|
|
71
|
+
}
|
|
72
|
+
if (!config.mcp) {
|
|
73
|
+
config.mcp = {};
|
|
74
|
+
}
|
|
75
|
+
config.mcp[MCP_SERVER_NAME] = {
|
|
76
|
+
type: 'remote',
|
|
77
|
+
url: 'https://api.er-gpt.ru/mcp/sse',
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Bearer ${apiKey}`,
|
|
80
|
+
'X-Assistant-Id': assistantId,
|
|
81
|
+
},
|
|
82
|
+
enabled: true,
|
|
83
|
+
};
|
|
84
|
+
fs.writeFileSync(OPENCODE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
85
|
+
}
|
|
86
|
+
export function clearOpenCodeMcpConfig() {
|
|
87
|
+
const config = readOpenCodeConfig();
|
|
88
|
+
if (config?.mcp?.[MCP_SERVER_NAME]) {
|
|
89
|
+
delete config.mcp[MCP_SERVER_NAME];
|
|
90
|
+
if (Object.keys(config.mcp).length === 0) {
|
|
91
|
+
delete config.mcp;
|
|
92
|
+
}
|
|
93
|
+
fs.writeFileSync(OPENCODE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function hasOpenCodeMcpConfig() {
|
|
97
|
+
const config = readOpenCodeConfig();
|
|
98
|
+
return !!config?.mcp?.[MCP_SERVER_NAME];
|
|
99
|
+
}
|