@hashgraphonline/conversational-agent 0.1.211 → 0.1.213
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/cli/dist/CLIApp.d.ts +9 -0
- package/cli/dist/CLIApp.js +127 -0
- package/cli/dist/LocalConversationalAgent.d.ts +37 -0
- package/cli/dist/LocalConversationalAgent.js +58 -0
- package/cli/dist/app.d.ts +16 -0
- package/cli/dist/app.js +13 -0
- package/cli/dist/cli.d.ts +2 -0
- package/cli/dist/cli.js +51 -0
- package/cli/dist/components/AppContainer.d.ts +16 -0
- package/cli/dist/components/AppContainer.js +24 -0
- package/cli/dist/components/AppScreens.d.ts +2 -0
- package/cli/dist/components/AppScreens.js +259 -0
- package/cli/dist/components/ChatScreen.d.ts +15 -0
- package/cli/dist/components/ChatScreen.js +39 -0
- package/cli/dist/components/DebugLoadingScreen.d.ts +5 -0
- package/cli/dist/components/DebugLoadingScreen.js +31 -0
- package/cli/dist/components/LoadingScreen.d.ts +2 -0
- package/cli/dist/components/LoadingScreen.js +16 -0
- package/cli/dist/components/LoadingScreenDebug.d.ts +5 -0
- package/cli/dist/components/LoadingScreenDebug.js +27 -0
- package/cli/dist/components/MCPConfigScreen.d.ts +28 -0
- package/cli/dist/components/MCPConfigScreen.js +168 -0
- package/cli/dist/components/ScreenRouter.d.ts +12 -0
- package/cli/dist/components/ScreenRouter.js +22 -0
- package/cli/dist/components/SetupScreen.d.ts +15 -0
- package/cli/dist/components/SetupScreen.js +65 -0
- package/cli/dist/components/SingleLoadingScreen.d.ts +5 -0
- package/cli/dist/components/SingleLoadingScreen.js +27 -0
- package/cli/dist/components/StatusBadge.d.ts +7 -0
- package/cli/dist/components/StatusBadge.js +28 -0
- package/cli/dist/components/TerminalWindow.d.ts +8 -0
- package/cli/dist/components/TerminalWindow.js +24 -0
- package/cli/dist/components/WelcomeScreen.d.ts +11 -0
- package/cli/dist/components/WelcomeScreen.js +47 -0
- package/cli/dist/context/AppContext.d.ts +68 -0
- package/cli/dist/context/AppContext.js +363 -0
- package/cli/dist/hooks/useInitializeAgent.d.ts +19 -0
- package/cli/dist/hooks/useInitializeAgent.js +28 -0
- package/cli/dist/hooks/useStableState.d.ts +38 -0
- package/cli/dist/hooks/useStableState.js +68 -0
- package/cli/dist/managers/AgentManager.d.ts +57 -0
- package/cli/dist/managers/AgentManager.js +119 -0
- package/cli/dist/managers/ConfigManager.d.ts +53 -0
- package/cli/dist/managers/ConfigManager.js +173 -0
- package/cli/dist/types.d.ts +31 -0
- package/cli/dist/types.js +19 -0
- package/dist/cjs/conversational-agent.d.ts +0 -8
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index6.js +11 -25
- package/dist/esm/index6.js.map +1 -1
- package/dist/types/conversational-agent.d.ts +0 -8
- package/package.json +27 -29
- package/src/conversational-agent.ts +14 -30
- package/cli/readme.md +0 -181
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import { TerminalWindow } from './TerminalWindow.js';
|
|
6
|
+
import { StatusBadge } from './StatusBadge.js';
|
|
7
|
+
import { BRAND_COLORS } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Chat screen component
|
|
10
|
+
*/
|
|
11
|
+
export const ChatScreen = React.memo(({ config, messages, input, isLoading, setInput, sendMessage, showLogs, logs, }) => {
|
|
12
|
+
return (React.createElement(TerminalWindow, { title: `Conversational Agent Chat (${config.network})` },
|
|
13
|
+
React.createElement(Box, { flexDirection: "column", minHeight: 25 },
|
|
14
|
+
showLogs && (React.createElement(Box, { borderStyle: "single", borderColor: BRAND_COLORS.dark, marginBottom: 1, padding: 1, height: 10, flexDirection: "column", overflow: "hidden" },
|
|
15
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
16
|
+
React.createElement(Text, { color: BRAND_COLORS.hedera.smoke, bold: true }, "\uD83D\uDCCB Logs")),
|
|
17
|
+
React.createElement(Box, { flexDirection: "column", overflow: "hidden" }, logs.slice(-8).map((log, i) => (React.createElement(Text, { key: i, color: BRAND_COLORS.comments, dimColor: true, wrap: "truncate" }, log)))))),
|
|
18
|
+
React.createElement(Box, { flexDirection: "column", flexGrow: 1, marginBottom: 1, overflow: "hidden" }, messages.slice(-15).map((msg, index) => (React.createElement(Box, { key: `${msg.timestamp.getTime()}-${index}`, marginBottom: 1 },
|
|
19
|
+
msg.role === 'user' && (React.createElement(Box, { flexDirection: "column" },
|
|
20
|
+
React.createElement(Box, null,
|
|
21
|
+
React.createElement(Text, { color: BRAND_COLORS.green }, "$ "),
|
|
22
|
+
React.createElement(Text, { wrap: "wrap" }, msg.content)))),
|
|
23
|
+
msg.role === 'assistant' && (React.createElement(Box, { flexDirection: "column" },
|
|
24
|
+
React.createElement(Box, null,
|
|
25
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "\u2192 "),
|
|
26
|
+
React.createElement(Text, { wrap: "wrap" }, msg.content)))),
|
|
27
|
+
msg.role === 'system' && (React.createElement(Box, { flexDirection: "column" },
|
|
28
|
+
React.createElement(Box, null,
|
|
29
|
+
React.createElement(StatusBadge, { status: "info" }),
|
|
30
|
+
React.createElement(Text, { color: BRAND_COLORS.hedera.smoke, wrap: "wrap" }, msg.content)))))))),
|
|
31
|
+
React.createElement(Box, { borderStyle: "single", borderTop: true, borderColor: BRAND_COLORS.dark, paddingTop: 1, paddingX: 1 }, isLoading ? (React.createElement(Box, null,
|
|
32
|
+
React.createElement(Spinner, { type: "dots" }),
|
|
33
|
+
React.createElement(Text, { color: BRAND_COLORS.hedera.smoke }, " Processing..."))) : (React.createElement(Box, { flexDirection: "row", width: "100%" },
|
|
34
|
+
React.createElement(Text, { color: BRAND_COLORS.green }, "$ "),
|
|
35
|
+
React.createElement(Box, { flexGrow: 1 },
|
|
36
|
+
React.createElement(TextInput, { value: input, onChange: setInput, onSubmit: sendMessage, placeholder: "Type your message..." }))))),
|
|
37
|
+
React.createElement(Box, { marginTop: 1, paddingX: 1 },
|
|
38
|
+
React.createElement(Text, { dimColor: true }, "Ctrl+C to exit, ESC to return to menu, L to toggle logs")))));
|
|
39
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { BRAND_COLORS } from '../types.js';
|
|
5
|
+
let globalRenderCount = 0;
|
|
6
|
+
let globalInstanceCount = 0;
|
|
7
|
+
/**
|
|
8
|
+
* Debug loading screen to trace double rendering issue
|
|
9
|
+
*/
|
|
10
|
+
export const DebugLoadingScreen = () => {
|
|
11
|
+
const instanceId = useRef(++globalInstanceCount);
|
|
12
|
+
const renderCount = useRef(0);
|
|
13
|
+
const mountTime = useRef(Date.now());
|
|
14
|
+
renderCount.current++;
|
|
15
|
+
globalRenderCount++;
|
|
16
|
+
console.error(`[LOADING-${instanceId.current}] Render #${renderCount.current} (Global: ${globalRenderCount}) at ${Date.now() - mountTime.current}ms`);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
console.error(`[LOADING-${instanceId.current}] MOUNTED`);
|
|
19
|
+
console.error(new Error('Mount Stack Trace').stack);
|
|
20
|
+
return () => {
|
|
21
|
+
console.error(`[LOADING-${instanceId.current}] UNMOUNTED after ${renderCount.current} renders`);
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
return (React.createElement(Box, { flexDirection: "column", alignItems: "center", paddingY: 4 },
|
|
25
|
+
React.createElement(Box, { marginBottom: 2 },
|
|
26
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "Initializing "),
|
|
27
|
+
React.createElement(Text, { color: BRAND_COLORS.purple }, "Hashgraph "),
|
|
28
|
+
React.createElement(Text, { color: BRAND_COLORS.green }, "Online "),
|
|
29
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "Agent...")),
|
|
30
|
+
React.createElement(Spinner, { type: "dots" })));
|
|
31
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { Box, Text, useStdout } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { BRAND_COLORS } from '../types.js';
|
|
5
|
+
export const LoadingScreen = () => {
|
|
6
|
+
const { write } = useStdout();
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
write('\x1Bc');
|
|
9
|
+
}, [write]);
|
|
10
|
+
return (React.createElement(Box, { flexDirection: "column", alignItems: "center", paddingY: 4 },
|
|
11
|
+
React.createElement(Box, { marginBottom: 2 },
|
|
12
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "Initializing "),
|
|
13
|
+
React.createElement(Text, { color: BRAND_COLORS.purple }, "Conversational "),
|
|
14
|
+
React.createElement(Text, { color: BRAND_COLORS.green }, "Agent...")),
|
|
15
|
+
React.createElement(Spinner, { type: "dots" })));
|
|
16
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { Box, Text, Static } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { BRAND_COLORS } from '../types.js';
|
|
5
|
+
let renderCount = 0;
|
|
6
|
+
/**
|
|
7
|
+
* Loading screen component with debug logging
|
|
8
|
+
*/
|
|
9
|
+
export const LoadingScreenDebug = () => {
|
|
10
|
+
renderCount++;
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
console.error(`[LOADING] Mounted - render #${renderCount}`);
|
|
13
|
+
return () => {
|
|
14
|
+
console.error(`[LOADING] Unmounted - after ${renderCount} renders`);
|
|
15
|
+
};
|
|
16
|
+
}, []);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
console.error(`[LOADING] Render #${renderCount}`);
|
|
19
|
+
});
|
|
20
|
+
return (React.createElement(Box, { flexDirection: "column", alignItems: "center", paddingY: 4 },
|
|
21
|
+
React.createElement(Static, { items: [{ id: '1' }] }, () => (React.createElement(Box, { marginBottom: 2 },
|
|
22
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "Initializing "),
|
|
23
|
+
React.createElement(Text, { color: BRAND_COLORS.purple }, "Hashgraph "),
|
|
24
|
+
React.createElement(Text, { color: BRAND_COLORS.green }, "Online "),
|
|
25
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "Agent...")))),
|
|
26
|
+
React.createElement(Spinner, { type: "dots" })));
|
|
27
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Screen } from '../types';
|
|
3
|
+
import { type MCPServerConfig } from '@hashgraphonline/conversational-agent';
|
|
4
|
+
interface MCPConfig {
|
|
5
|
+
enableFilesystem: boolean;
|
|
6
|
+
filesystemPath: string;
|
|
7
|
+
customServers: MCPServerConfig[];
|
|
8
|
+
addingCustom: boolean;
|
|
9
|
+
newServerName: string;
|
|
10
|
+
newServerCommand: string;
|
|
11
|
+
newServerArgs: string;
|
|
12
|
+
newServerEnv: string;
|
|
13
|
+
currentField: number;
|
|
14
|
+
}
|
|
15
|
+
interface Props {
|
|
16
|
+
mcpConfig: MCPConfig;
|
|
17
|
+
editingFilesystemPath: boolean;
|
|
18
|
+
onSetMcpConfig: (config: Partial<MCPConfig>) => void;
|
|
19
|
+
onSetEditingFilesystemPath: (editing: boolean) => void;
|
|
20
|
+
onSetScreen: (screen: Screen) => void;
|
|
21
|
+
onSaveMCPConfig: () => void;
|
|
22
|
+
getMCPConfigPath: () => string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* MCP Configuration screen
|
|
26
|
+
*/
|
|
27
|
+
export declare const MCPConfigScreen: React.FC<Props>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import SelectInput from 'ink-select-input';
|
|
5
|
+
import { TerminalWindow } from './TerminalWindow.js';
|
|
6
|
+
import { StatusBadge } from './StatusBadge.js';
|
|
7
|
+
import { BRAND_COLORS } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* MCP Configuration screen
|
|
10
|
+
*/
|
|
11
|
+
export const MCPConfigScreen = ({ mcpConfig, editingFilesystemPath, onSetMcpConfig, onSetEditingFilesystemPath, onSetScreen, onSaveMCPConfig, getMCPConfigPath, }) => {
|
|
12
|
+
useInput((_, key) => {
|
|
13
|
+
if (key.escape) {
|
|
14
|
+
if (mcpConfig.addingCustom) {
|
|
15
|
+
onSetMcpConfig({
|
|
16
|
+
addingCustom: false,
|
|
17
|
+
newServerName: '',
|
|
18
|
+
newServerCommand: '',
|
|
19
|
+
newServerArgs: '',
|
|
20
|
+
newServerEnv: '',
|
|
21
|
+
currentField: 0,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (editingFilesystemPath) {
|
|
25
|
+
onSetEditingFilesystemPath(false);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (editingFilesystemPath) {
|
|
30
|
+
return (React.createElement(TerminalWindow, { title: "Edit Filesystem Path" },
|
|
31
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
32
|
+
React.createElement(Box, { marginBottom: 2 },
|
|
33
|
+
React.createElement(Text, null, "Enter the directory path for the filesystem server:")),
|
|
34
|
+
React.createElement(TextInput, { value: mcpConfig.filesystemPath, onChange: value => onSetMcpConfig({ filesystemPath: value }), onSubmit: () => {
|
|
35
|
+
onSetEditingFilesystemPath(false);
|
|
36
|
+
onSaveMCPConfig();
|
|
37
|
+
}, placeholder: process.cwd() }),
|
|
38
|
+
React.createElement(Box, { marginTop: 2 },
|
|
39
|
+
React.createElement(Text, { dimColor: true }, "Press Enter to save, Escape to cancel")))));
|
|
40
|
+
}
|
|
41
|
+
if (mcpConfig.addingCustom) {
|
|
42
|
+
const customFields = [
|
|
43
|
+
'newServerName',
|
|
44
|
+
'newServerCommand',
|
|
45
|
+
'newServerArgs',
|
|
46
|
+
'newServerEnv',
|
|
47
|
+
];
|
|
48
|
+
const fieldLabels = [
|
|
49
|
+
'Server Name:',
|
|
50
|
+
'Command:',
|
|
51
|
+
'Arguments (comma-separated):',
|
|
52
|
+
'Environment Variables (KEY=value, comma-separated):',
|
|
53
|
+
];
|
|
54
|
+
const placeholders = [
|
|
55
|
+
'my-server',
|
|
56
|
+
'npx',
|
|
57
|
+
'-y, @modelcontextprotocol/server-github',
|
|
58
|
+
'MCP_LOG_LEVEL=info, GIT_SIGN_COMMITS=false',
|
|
59
|
+
];
|
|
60
|
+
return (React.createElement(TerminalWindow, { title: "Add Custom MCP Server" },
|
|
61
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
62
|
+
customFields.map((field, index) => (React.createElement(Box, { key: field, marginY: 1 },
|
|
63
|
+
React.createElement(Text, { color: mcpConfig.currentField === index
|
|
64
|
+
? BRAND_COLORS.blue
|
|
65
|
+
: BRAND_COLORS.hedera.smoke }, fieldLabels[index]),
|
|
66
|
+
mcpConfig.currentField === index ? (React.createElement(TextInput, { value: mcpConfig[field], onChange: value => onSetMcpConfig({ [field]: value }), onSubmit: () => {
|
|
67
|
+
if (index < customFields.length - 1) {
|
|
68
|
+
onSetMcpConfig({ currentField: index + 1 });
|
|
69
|
+
}
|
|
70
|
+
else if (mcpConfig.newServerName &&
|
|
71
|
+
mcpConfig.newServerCommand) {
|
|
72
|
+
const env = {};
|
|
73
|
+
if (mcpConfig.newServerEnv) {
|
|
74
|
+
mcpConfig.newServerEnv.split(',').forEach(envVar => {
|
|
75
|
+
const [key, value] = envVar.trim().split('=');
|
|
76
|
+
if (key && value) {
|
|
77
|
+
env[key] = value;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const newServer = {
|
|
82
|
+
name: mcpConfig.newServerName,
|
|
83
|
+
command: mcpConfig.newServerCommand,
|
|
84
|
+
args: mcpConfig.newServerArgs
|
|
85
|
+
.split(',')
|
|
86
|
+
.map(arg => arg.trim())
|
|
87
|
+
.filter(Boolean),
|
|
88
|
+
transport: 'stdio',
|
|
89
|
+
autoConnect: true,
|
|
90
|
+
...(Object.keys(env).length > 0 && { env }),
|
|
91
|
+
};
|
|
92
|
+
const newCustomServers = [...mcpConfig.customServers, newServer];
|
|
93
|
+
onSetMcpConfig({
|
|
94
|
+
customServers: newCustomServers,
|
|
95
|
+
addingCustom: false,
|
|
96
|
+
newServerName: '',
|
|
97
|
+
newServerCommand: '',
|
|
98
|
+
newServerArgs: '',
|
|
99
|
+
newServerEnv: '',
|
|
100
|
+
currentField: 0,
|
|
101
|
+
});
|
|
102
|
+
onSaveMCPConfig();
|
|
103
|
+
}
|
|
104
|
+
}, placeholder: placeholders[index] })) : (React.createElement(Text, { color: BRAND_COLORS.hedera.smoke }, String(mcpConfig[field] || `(${placeholders[index]})`)))))),
|
|
105
|
+
React.createElement(Box, { marginTop: 2 },
|
|
106
|
+
React.createElement(Text, { dimColor: true }, "Press Tab or Enter to move between fields"),
|
|
107
|
+
React.createElement(Text, { dimColor: true }, "Press Enter on the last field to add the server"),
|
|
108
|
+
React.createElement(Text, { dimColor: true }, "Press Escape to cancel")))));
|
|
109
|
+
}
|
|
110
|
+
const menuItems = [
|
|
111
|
+
{
|
|
112
|
+
label: `Filesystem Server: ${mcpConfig.enableFilesystem ? 'Enabled' : 'Disabled'}`,
|
|
113
|
+
value: 'filesystem',
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
if (mcpConfig.enableFilesystem) {
|
|
117
|
+
menuItems.push({
|
|
118
|
+
label: `Filesystem Path: ${mcpConfig.filesystemPath}`,
|
|
119
|
+
value: 'filesystem-path',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
mcpConfig.customServers.forEach((server, index) => {
|
|
123
|
+
menuItems.push({
|
|
124
|
+
label: `${server.name} (${server.command})`,
|
|
125
|
+
value: `custom-${index}`,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
menuItems.push({ label: 'Add Custom Server', value: 'add-custom' }, { label: 'Done (changes auto-saved)', value: 'save' }, { label: 'Back to Menu', value: 'back' });
|
|
129
|
+
return (React.createElement(TerminalWindow, { title: "MCP Server Configuration" },
|
|
130
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
131
|
+
React.createElement(Box, { marginBottom: 2 },
|
|
132
|
+
React.createElement(StatusBadge, { status: "info" }),
|
|
133
|
+
React.createElement(Text, null, "Configure Model Context Protocol servers")),
|
|
134
|
+
React.createElement(SelectInput, { items: menuItems, onSelect: item => {
|
|
135
|
+
if (item.value === 'filesystem') {
|
|
136
|
+
const enableFilesystem = !mcpConfig.enableFilesystem;
|
|
137
|
+
onSetMcpConfig({ enableFilesystem });
|
|
138
|
+
onSaveMCPConfig();
|
|
139
|
+
}
|
|
140
|
+
else if (item.value === 'filesystem-path') {
|
|
141
|
+
onSetEditingFilesystemPath(true);
|
|
142
|
+
}
|
|
143
|
+
else if (item.value === 'add-custom') {
|
|
144
|
+
onSetMcpConfig({ addingCustom: true });
|
|
145
|
+
}
|
|
146
|
+
else if (item.value === 'save') {
|
|
147
|
+
onSaveMCPConfig();
|
|
148
|
+
onSetScreen('welcome');
|
|
149
|
+
}
|
|
150
|
+
else if (item.value === 'back') {
|
|
151
|
+
onSetScreen('welcome');
|
|
152
|
+
}
|
|
153
|
+
else if (item.value.startsWith('custom-')) {
|
|
154
|
+
const index = parseInt(item.value.replace('custom-', ''));
|
|
155
|
+
const newCustomServers = mcpConfig.customServers.filter((_, i) => i !== index);
|
|
156
|
+
onSetMcpConfig({ customServers: newCustomServers });
|
|
157
|
+
onSaveMCPConfig();
|
|
158
|
+
}
|
|
159
|
+
} }),
|
|
160
|
+
React.createElement(Box, { marginY: 2 },
|
|
161
|
+
React.createElement(Text, { dimColor: true }, "Press Enter to toggle/edit servers, or select actions"),
|
|
162
|
+
mcpConfig.enableFilesystem && (React.createElement(Text, { dimColor: true },
|
|
163
|
+
"Filesystem path: ",
|
|
164
|
+
mcpConfig.filesystemPath)),
|
|
165
|
+
React.createElement(Text, { dimColor: true },
|
|
166
|
+
"Config saved to: ",
|
|
167
|
+
getMCPConfigPath())))));
|
|
168
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Config } from '../types';
|
|
3
|
+
import { type AppState } from '../hooks/useStableState';
|
|
4
|
+
interface Props {
|
|
5
|
+
state: AppState;
|
|
6
|
+
currentConfig: Config;
|
|
7
|
+
actions: any;
|
|
8
|
+
stableHandlers: any;
|
|
9
|
+
exit: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare const ScreenRouter: React.FC<Props>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { WelcomeScreen } from './WelcomeScreen.js';
|
|
3
|
+
import { ChatScreen } from './ChatScreen.js';
|
|
4
|
+
import { LoadingScreen } from './LoadingScreen.js';
|
|
5
|
+
import { SetupScreen } from './SetupScreen.js';
|
|
6
|
+
import { MCPConfigScreen } from './MCPConfigScreen.js';
|
|
7
|
+
export const ScreenRouter = ({ state, currentConfig, actions, stableHandlers, exit, }) => {
|
|
8
|
+
switch (state.screen) {
|
|
9
|
+
case 'welcome':
|
|
10
|
+
return (React.createElement(WelcomeScreen, { config: currentConfig, onExit: exit, onInitializeAgent: stableHandlers.initializeAgent, onSetScreen: actions.setScreen }));
|
|
11
|
+
case 'loading':
|
|
12
|
+
return React.createElement(LoadingScreen, null);
|
|
13
|
+
case 'setup':
|
|
14
|
+
return (React.createElement(SetupScreen, { config: currentConfig, currentField: state.currentField, error: state.error, onUpdateConfig: stableHandlers.updateConfig, onSetCurrentField: actions.setCurrentField, onInitializeAgent: stableHandlers.initializeAgent }));
|
|
15
|
+
case 'mcp-config':
|
|
16
|
+
return (React.createElement(MCPConfigScreen, { mcpConfig: state.mcpConfig, editingFilesystemPath: state.editingFilesystemPath, onSetMcpConfig: actions.setMcpConfig, onSetEditingFilesystemPath: actions.setEditingFilesystemPath, onSetScreen: actions.setScreen, onSaveMCPConfig: stableHandlers.saveMCPConfig, getMCPConfigPath: stableHandlers.getMCPConfigPath }));
|
|
17
|
+
case 'chat':
|
|
18
|
+
return (React.createElement(ChatScreen, { config: currentConfig, messages: state.messages, input: state.input, isLoading: state.isLoading, setInput: actions.setInput, sendMessage: stableHandlers.sendMessage, showLogs: false, logs: state.logs }));
|
|
19
|
+
default:
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Config } from '../types';
|
|
3
|
+
interface Props {
|
|
4
|
+
config: Config;
|
|
5
|
+
currentField: number;
|
|
6
|
+
error: string | null;
|
|
7
|
+
onUpdateConfig: (field: keyof Config, value: string) => void;
|
|
8
|
+
onSetCurrentField: (field: number) => void;
|
|
9
|
+
onInitializeAgent: () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Setup screen for configuring credentials
|
|
13
|
+
*/
|
|
14
|
+
export declare const SetupScreen: React.FC<Props>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import SelectInput from 'ink-select-input';
|
|
5
|
+
import { TerminalWindow } from './TerminalWindow.js';
|
|
6
|
+
import { StatusBadge } from './StatusBadge.js';
|
|
7
|
+
import { BRAND_COLORS } from '../types.js';
|
|
8
|
+
const fields = ['accountId', 'privateKey', 'network', 'openAIApiKey'];
|
|
9
|
+
/**
|
|
10
|
+
* Setup screen for configuring credentials
|
|
11
|
+
*/
|
|
12
|
+
export const SetupScreen = ({ config, currentField, error, onUpdateConfig, onSetCurrentField, onInitializeAgent, }) => {
|
|
13
|
+
useInput((_, key) => {
|
|
14
|
+
if (key.tab && !key.shift) {
|
|
15
|
+
const nextField = (currentField + 1) % fields.length;
|
|
16
|
+
onSetCurrentField(nextField);
|
|
17
|
+
}
|
|
18
|
+
else if (key.tab && key.shift) {
|
|
19
|
+
const prevField = currentField === 0 ? fields.length - 1 : currentField - 1;
|
|
20
|
+
onSetCurrentField(prevField);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return (React.createElement(TerminalWindow, { title: "Configuration" },
|
|
24
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
25
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
26
|
+
React.createElement(StatusBadge, { status: "info" }),
|
|
27
|
+
React.createElement(Text, null, "Configure your Hedera account credentials")),
|
|
28
|
+
fields.map((field, index) => (React.createElement(Box, { key: field, marginY: 1 },
|
|
29
|
+
React.createElement(Box, { width: 20 },
|
|
30
|
+
React.createElement(Text, { color: currentField === index
|
|
31
|
+
? BRAND_COLORS.blue
|
|
32
|
+
: BRAND_COLORS.hedera.smoke },
|
|
33
|
+
field === 'accountId' && 'Account ID:',
|
|
34
|
+
field === 'privateKey' && 'Private Key:',
|
|
35
|
+
field === 'network' && 'Network:',
|
|
36
|
+
field === 'openAIApiKey' && 'OpenAI Key:')),
|
|
37
|
+
currentField === index ? (field === 'network' ? (React.createElement(SelectInput, { items: [
|
|
38
|
+
{ label: 'Testnet', value: 'testnet' },
|
|
39
|
+
{ label: 'Mainnet', value: 'mainnet' },
|
|
40
|
+
], initialIndex: config.network === 'testnet' ? 0 : 1, onSelect: item => {
|
|
41
|
+
onUpdateConfig('network', item.value);
|
|
42
|
+
if (currentField < fields.length - 1) {
|
|
43
|
+
onSetCurrentField(currentField + 1);
|
|
44
|
+
}
|
|
45
|
+
} })) : (React.createElement(TextInput, { value: config[field], onChange: value => onUpdateConfig(field, value), onSubmit: () => {
|
|
46
|
+
if (currentField < fields.length - 1) {
|
|
47
|
+
onSetCurrentField(currentField + 1);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
onInitializeAgent();
|
|
51
|
+
}
|
|
52
|
+
}, mask: field === 'privateKey' || field === 'openAIApiKey'
|
|
53
|
+
? '*'
|
|
54
|
+
: undefined }))) : (React.createElement(Text, { color: BRAND_COLORS.hedera.smoke }, field === 'network'
|
|
55
|
+
? config[field]
|
|
56
|
+
: config[field]
|
|
57
|
+
? '••••••••'
|
|
58
|
+
: '(not set)'))))),
|
|
59
|
+
error && (React.createElement(Box, { marginTop: 2 },
|
|
60
|
+
React.createElement(StatusBadge, { status: "error" }),
|
|
61
|
+
React.createElement(Text, { color: "red" }, error))),
|
|
62
|
+
React.createElement(Box, { marginTop: 2 },
|
|
63
|
+
React.createElement(Text, { dimColor: true }, "Press Tab to navigate fields, Enter to submit"),
|
|
64
|
+
React.createElement(Text, { dimColor: true }, "Complete all fields and press Enter on the last field to save and start")))));
|
|
65
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { BRAND_COLORS } from '../types.js';
|
|
5
|
+
let globalRenderCount = 0;
|
|
6
|
+
/**
|
|
7
|
+
* Loading screen that tracks its own renders
|
|
8
|
+
*/
|
|
9
|
+
export const SingleLoadingScreen = () => {
|
|
10
|
+
const renderCount = useRef(0);
|
|
11
|
+
const instanceId = useRef(Math.random());
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
renderCount.current++;
|
|
14
|
+
globalRenderCount++;
|
|
15
|
+
console.error(`[LOADING] Instance ${instanceId.current.toFixed(4)} - Render #${renderCount.current} (Global: ${globalRenderCount})`);
|
|
16
|
+
return () => {
|
|
17
|
+
console.error(`[LOADING] Instance ${instanceId.current.toFixed(4)} - Unmounting after ${renderCount.current} renders`);
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
return (React.createElement(Box, { flexDirection: "column", alignItems: "center", paddingY: 4 },
|
|
21
|
+
React.createElement(Box, { marginBottom: 2 },
|
|
22
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "Initializing "),
|
|
23
|
+
React.createElement(Text, { color: BRAND_COLORS.purple }, "Hashgraph "),
|
|
24
|
+
React.createElement(Text, { color: BRAND_COLORS.green }, "Online "),
|
|
25
|
+
React.createElement(Text, { color: BRAND_COLORS.blue }, "Agent...")),
|
|
26
|
+
React.createElement(Spinner, { type: "dots" })));
|
|
27
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
const BRAND_COLORS = {
|
|
4
|
+
blue: '#5599fe',
|
|
5
|
+
green: '#48df7b',
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Status badge component
|
|
9
|
+
*/
|
|
10
|
+
export const StatusBadge = ({ status }) => {
|
|
11
|
+
const colors = {
|
|
12
|
+
success: BRAND_COLORS.green,
|
|
13
|
+
info: BRAND_COLORS.blue,
|
|
14
|
+
warning: '#FFBD2E',
|
|
15
|
+
error: '#FF5F57',
|
|
16
|
+
};
|
|
17
|
+
const labels = {
|
|
18
|
+
success: 'SUCCESS',
|
|
19
|
+
info: 'INFO',
|
|
20
|
+
warning: 'WARNING',
|
|
21
|
+
error: 'ERROR',
|
|
22
|
+
};
|
|
23
|
+
return (React.createElement(Box, { marginRight: 1 },
|
|
24
|
+
React.createElement(Text, { color: colors[status], bold: true },
|
|
25
|
+
"[",
|
|
26
|
+
labels[status],
|
|
27
|
+
"]")));
|
|
28
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
const BRAND_COLORS = {
|
|
4
|
+
blue: '#5599fe',
|
|
5
|
+
dark: '#3f4174',
|
|
6
|
+
hedera: {
|
|
7
|
+
smoke: '#8c8c8c',
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Terminal window decoration component
|
|
12
|
+
*/
|
|
13
|
+
export const TerminalWindow = ({ title, children, }) => {
|
|
14
|
+
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: BRAND_COLORS.blue },
|
|
15
|
+
React.createElement(Box, { paddingX: 1, borderStyle: "single", borderBottom: true, borderColor: BRAND_COLORS.dark },
|
|
16
|
+
React.createElement(Box, { marginRight: 1 },
|
|
17
|
+
React.createElement(Text, { color: "red" }, "\u25CF"),
|
|
18
|
+
React.createElement(Text, null, " "),
|
|
19
|
+
React.createElement(Text, { color: "yellow" }, "\u25CF"),
|
|
20
|
+
React.createElement(Text, null, " "),
|
|
21
|
+
React.createElement(Text, { color: "green" }, "\u25CF")),
|
|
22
|
+
React.createElement(Text, { color: BRAND_COLORS.hedera.smoke }, title)),
|
|
23
|
+
React.createElement(Box, { paddingX: 2, paddingY: 1, flexDirection: "column" }, children)));
|
|
24
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Config, type Screen } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Welcome screen component
|
|
5
|
+
*/
|
|
6
|
+
export declare const WelcomeScreen: React.FC<{
|
|
7
|
+
config: Config;
|
|
8
|
+
onExit: () => void;
|
|
9
|
+
onInitializeAgent: () => void;
|
|
10
|
+
onSetScreen: (screen: Screen) => void;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
import Gradient from 'ink-gradient';
|
|
5
|
+
import { BRAND_COLORS } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Welcome screen component
|
|
8
|
+
*/
|
|
9
|
+
export const WelcomeScreen = ({ config, onExit, onInitializeAgent, onSetScreen }) => (React.createElement(Box, { flexDirection: "column", alignItems: "center", paddingY: 2 },
|
|
10
|
+
React.createElement(Box, { flexDirection: "column", alignItems: "center", marginBottom: 2 },
|
|
11
|
+
React.createElement(Box, null,
|
|
12
|
+
React.createElement(Gradient, { name: "atlas" },
|
|
13
|
+
React.createElement(Text, { bold: true }, "\u2566 \u2566\u2554\u2550\u2557\u2554\u2550\u2557\u2566 \u2566\u2554\u2550\u2557\u2566\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566 \u2566 \u2554\u2550\u2557\u2554\u2557\u2554\u2566 \u2566\u2554\u2557\u2554\u2554\u2550\u2557"))),
|
|
14
|
+
React.createElement(Box, null,
|
|
15
|
+
React.createElement(Gradient, { name: "atlas" },
|
|
16
|
+
React.createElement(Text, { bold: true }, "\u2560\u2550\u2563\u2560\u2550\u2563\u255A\u2550\u2557\u2560\u2550\u2563\u2551 \u2566\u2560\u2566\u255D\u2560\u2550\u2563\u2560\u2550\u255D\u2560\u2550\u2563 \u2551 \u2551\u2551\u2551\u2551\u2551 \u2551\u2551\u2551\u2551\u2551\u2563 "))),
|
|
17
|
+
React.createElement(Box, null,
|
|
18
|
+
React.createElement(Gradient, { name: "atlas" },
|
|
19
|
+
React.createElement(Text, { bold: true }, "\u2569 \u2569\u2569 \u2569\u255A\u2550\u255D\u2569 \u2569\u255A\u2550\u255D\u2569\u255A\u2550\u2569 \u2569\u2569 \u2569 \u2569 \u255A\u2550\u255D\u255D\u255A\u255D\u2569\u2550\u255D\u2569\u255D\u255A\u255D\u255A\u2550\u255D"))),
|
|
20
|
+
React.createElement(Text, { color: BRAND_COLORS.hedera.smoke }, "Conversational Agent CLI"),
|
|
21
|
+
React.createElement(Text, { color: BRAND_COLORS.hedera.smoke, dimColor: true }, "Powered by Hashgraph Online")),
|
|
22
|
+
React.createElement(Box, { marginY: 2 },
|
|
23
|
+
React.createElement(SelectInput, { items: [
|
|
24
|
+
{ label: 'Start Chat', value: 'chat' },
|
|
25
|
+
{ label: 'Configure', value: 'setup' },
|
|
26
|
+
{ label: 'MCP Servers', value: 'mcp-config' },
|
|
27
|
+
{ label: 'Exit', value: 'exit' },
|
|
28
|
+
], onSelect: item => {
|
|
29
|
+
if (item.value === 'exit') {
|
|
30
|
+
onExit();
|
|
31
|
+
}
|
|
32
|
+
else if (item.value === 'chat') {
|
|
33
|
+
if (config.accountId &&
|
|
34
|
+
config.privateKey &&
|
|
35
|
+
config.openAIApiKey) {
|
|
36
|
+
onInitializeAgent();
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
onSetScreen('setup');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
onSetScreen(item.value);
|
|
44
|
+
}
|
|
45
|
+
} })),
|
|
46
|
+
React.createElement(Box, { marginTop: 2 },
|
|
47
|
+
React.createElement(Text, { dimColor: true }, "Press \u2191/\u2193 to navigate, Enter to select"))));
|