@townco/cli 0.1.3 → 0.1.5
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/commands/configure.d.ts +1 -0
- package/dist/commands/configure.js +146 -0
- package/dist/commands/create.d.ts +5 -6
- package/dist/commands/create.js +2 -2
- package/dist/commands/delete.js +2 -2
- package/dist/commands/edit.js +1 -1
- package/dist/commands/list.js +1 -1
- package/dist/commands/mcp-add.d.ts +14 -0
- package/dist/commands/mcp-add.js +445 -0
- package/dist/commands/mcp-list.d.ts +3 -0
- package/dist/commands/mcp-list.js +188 -0
- package/dist/commands/mcp-remove.d.ts +3 -0
- package/dist/commands/mcp-remove.js +208 -0
- package/dist/commands/run.d.ts +4 -4
- package/dist/commands/run.js +46 -7
- package/dist/index.js +39 -3
- package/dist/lib/mcp-storage.d.ts +31 -0
- package/dist/lib/mcp-storage.js +109 -0
- package/package.json +10 -16
- package/tui/App.d.ts +4 -2
- package/tui/App.js +35 -22
- package/tui/cli.d.ts +18 -5
- package/tui/cli.js +11 -6
- package/tui/index.js +18 -17
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Box, render, Text, useApp, useInput } from "ink";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { listMCPConfigs } from "../lib/mcp-storage";
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Main Component
|
|
8
|
+
// ============================================================================
|
|
9
|
+
function MCPListApp() {
|
|
10
|
+
const [result, setResult] = useState(null);
|
|
11
|
+
const [loading, setLoading] = useState(true);
|
|
12
|
+
const { exit } = useApp();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
function loadConfigs() {
|
|
15
|
+
try {
|
|
16
|
+
const configs = listMCPConfigs();
|
|
17
|
+
setResult({ configs });
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
20
|
+
setResult({ configs: [], error: errorMsg });
|
|
21
|
+
} finally {
|
|
22
|
+
setLoading(false);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
loadConfigs();
|
|
26
|
+
}, []);
|
|
27
|
+
// Exit on any key press when not loading
|
|
28
|
+
useInput((_input, key) => {
|
|
29
|
+
if (!loading) {
|
|
30
|
+
if (key.return || key.escape || _input === "q") {
|
|
31
|
+
exit();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
if (loading) {
|
|
36
|
+
return _jsx(Box, {
|
|
37
|
+
children: _jsx(Text, { children: "Loading MCP servers..." }),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (result?.error) {
|
|
41
|
+
return _jsxs(Box, {
|
|
42
|
+
flexDirection: "column",
|
|
43
|
+
children: [
|
|
44
|
+
_jsx(Box, {
|
|
45
|
+
marginBottom: 1,
|
|
46
|
+
children: _jsx(Text, {
|
|
47
|
+
bold: true,
|
|
48
|
+
color: "red",
|
|
49
|
+
children: "\u274C Error loading MCP servers",
|
|
50
|
+
}),
|
|
51
|
+
}),
|
|
52
|
+
_jsx(Box, {
|
|
53
|
+
marginBottom: 1,
|
|
54
|
+
children: _jsx(Text, { children: result.error }),
|
|
55
|
+
}),
|
|
56
|
+
_jsx(Box, {
|
|
57
|
+
children: _jsx(Text, {
|
|
58
|
+
dimColor: true,
|
|
59
|
+
children: "Press Enter or Q to exit",
|
|
60
|
+
}),
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (!result || result.configs.length === 0) {
|
|
66
|
+
return _jsxs(Box, {
|
|
67
|
+
flexDirection: "column",
|
|
68
|
+
children: [
|
|
69
|
+
_jsx(Box, {
|
|
70
|
+
marginBottom: 1,
|
|
71
|
+
children: _jsx(Text, {
|
|
72
|
+
bold: true,
|
|
73
|
+
children: "No MCP servers configured",
|
|
74
|
+
}),
|
|
75
|
+
}),
|
|
76
|
+
_jsx(Box, {
|
|
77
|
+
marginBottom: 1,
|
|
78
|
+
children: _jsx(Text, {
|
|
79
|
+
dimColor: true,
|
|
80
|
+
children: "Add one with: town mcp add",
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
_jsx(Box, {
|
|
84
|
+
children: _jsx(Text, {
|
|
85
|
+
dimColor: true,
|
|
86
|
+
children: "Press Enter or Q to exit",
|
|
87
|
+
}),
|
|
88
|
+
}),
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return _jsxs(Box, {
|
|
93
|
+
flexDirection: "column",
|
|
94
|
+
children: [
|
|
95
|
+
_jsx(Box, {
|
|
96
|
+
marginBottom: 1,
|
|
97
|
+
children: _jsxs(Text, {
|
|
98
|
+
bold: true,
|
|
99
|
+
children: ["Configured MCP Servers (", result.configs.length, ")"],
|
|
100
|
+
}),
|
|
101
|
+
}),
|
|
102
|
+
result.configs.map((config, index) =>
|
|
103
|
+
_jsxs(
|
|
104
|
+
Box,
|
|
105
|
+
{
|
|
106
|
+
flexDirection: "column",
|
|
107
|
+
marginBottom: 1,
|
|
108
|
+
children: [
|
|
109
|
+
_jsx(Box, {
|
|
110
|
+
children: _jsxs(Text, {
|
|
111
|
+
bold: true,
|
|
112
|
+
color: "cyan",
|
|
113
|
+
children: [index + 1, ". ", config.name],
|
|
114
|
+
}),
|
|
115
|
+
}),
|
|
116
|
+
_jsx(Box, {
|
|
117
|
+
paddingLeft: 3,
|
|
118
|
+
children:
|
|
119
|
+
config.transport === "http"
|
|
120
|
+
? _jsxs(Box, {
|
|
121
|
+
flexDirection: "column",
|
|
122
|
+
children: [
|
|
123
|
+
_jsxs(Text, {
|
|
124
|
+
children: [
|
|
125
|
+
"Transport: ",
|
|
126
|
+
_jsx(Text, { color: "green", children: "HTTP" }),
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
_jsxs(Text, { children: ["URL: ", config.url] }),
|
|
130
|
+
],
|
|
131
|
+
})
|
|
132
|
+
: _jsxs(Box, {
|
|
133
|
+
flexDirection: "column",
|
|
134
|
+
children: [
|
|
135
|
+
_jsxs(Text, {
|
|
136
|
+
children: [
|
|
137
|
+
"Transport: ",
|
|
138
|
+
_jsx(Text, { color: "blue", children: "stdio" }),
|
|
139
|
+
],
|
|
140
|
+
}),
|
|
141
|
+
_jsxs(Text, {
|
|
142
|
+
children: ["Command: ", config.command],
|
|
143
|
+
}),
|
|
144
|
+
config.args &&
|
|
145
|
+
config.args.length > 0 &&
|
|
146
|
+
_jsxs(Text, {
|
|
147
|
+
children: ["Args: ", config.args.join(" ")],
|
|
148
|
+
}),
|
|
149
|
+
],
|
|
150
|
+
}),
|
|
151
|
+
}),
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
config.name,
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
_jsx(Box, {
|
|
158
|
+
marginTop: 1,
|
|
159
|
+
children: _jsx(Text, {
|
|
160
|
+
dimColor: true,
|
|
161
|
+
children:
|
|
162
|
+
"Use `town mcp remove` to remove a server or `town mcp add` to add one",
|
|
163
|
+
}),
|
|
164
|
+
}),
|
|
165
|
+
_jsx(Box, {
|
|
166
|
+
marginTop: 1,
|
|
167
|
+
children: _jsx(Text, {
|
|
168
|
+
dimColor: true,
|
|
169
|
+
children: "Press Enter or Q to exit",
|
|
170
|
+
}),
|
|
171
|
+
}),
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Export and Runner
|
|
177
|
+
// ============================================================================
|
|
178
|
+
export default MCPListApp;
|
|
179
|
+
export async function runMCPList() {
|
|
180
|
+
const { waitUntilExit, clear } = render(_jsx(MCPListApp, {}));
|
|
181
|
+
try {
|
|
182
|
+
await waitUntilExit();
|
|
183
|
+
} finally {
|
|
184
|
+
clear();
|
|
185
|
+
// Ensure cursor is visible
|
|
186
|
+
process.stdout.write("\x1B[?25h");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { SingleSelect } from "@town/ui/tui";
|
|
2
|
+
import { Box, render, Text } from "ink";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import {
|
|
6
|
+
deleteMCPConfig,
|
|
7
|
+
getMCPSummary,
|
|
8
|
+
listMCPConfigs,
|
|
9
|
+
} from "../lib/mcp-storage";
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Main Component
|
|
13
|
+
// ============================================================================
|
|
14
|
+
function MCPRemoveApp() {
|
|
15
|
+
const [stage, setStage] = useState("loading");
|
|
16
|
+
const [configs, setConfigs] = useState([]);
|
|
17
|
+
const [selectedName, setSelectedName] = useState(null);
|
|
18
|
+
const [selectedConfig, setSelectedConfig] = useState(null);
|
|
19
|
+
const [errorMessage, setErrorMessage] = useState("");
|
|
20
|
+
// Load configs on mount
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
function loadConfigs() {
|
|
23
|
+
try {
|
|
24
|
+
const configList = listMCPConfigs();
|
|
25
|
+
setConfigs(configList);
|
|
26
|
+
setStage(configList.length > 0 ? "select" : "error");
|
|
27
|
+
if (configList.length === 0) {
|
|
28
|
+
setErrorMessage("No MCP servers configured");
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
32
|
+
setErrorMessage(errorMsg);
|
|
33
|
+
setStage("error");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
loadConfigs();
|
|
37
|
+
}, []);
|
|
38
|
+
// Handle removal
|
|
39
|
+
const handleRemove = () => {
|
|
40
|
+
if (!selectedName) return;
|
|
41
|
+
setStage("removing");
|
|
42
|
+
try {
|
|
43
|
+
const success = deleteMCPConfig(selectedName);
|
|
44
|
+
if (success) {
|
|
45
|
+
setStage("done");
|
|
46
|
+
// Exit immediately
|
|
47
|
+
process.exit(0);
|
|
48
|
+
} else {
|
|
49
|
+
throw new Error("Failed to delete MCP config");
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
53
|
+
setErrorMessage(errorMsg);
|
|
54
|
+
setStage("error");
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
if (stage === "loading") {
|
|
58
|
+
return _jsx(Box, {
|
|
59
|
+
children: _jsx(Text, { children: "Loading MCP servers..." }),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (stage === "error") {
|
|
63
|
+
return _jsxs(Box, {
|
|
64
|
+
flexDirection: "column",
|
|
65
|
+
children: [
|
|
66
|
+
_jsx(Box, {
|
|
67
|
+
marginBottom: 1,
|
|
68
|
+
children: _jsx(Text, {
|
|
69
|
+
bold: true,
|
|
70
|
+
color: "red",
|
|
71
|
+
children: "\u274C Error",
|
|
72
|
+
}),
|
|
73
|
+
}),
|
|
74
|
+
_jsx(Box, { children: _jsx(Text, { children: errorMessage }) }),
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (stage === "select") {
|
|
79
|
+
const options = configs.map((config) => ({
|
|
80
|
+
label: config.name,
|
|
81
|
+
value: config.name,
|
|
82
|
+
description: getMCPSummary(config),
|
|
83
|
+
}));
|
|
84
|
+
return _jsxs(Box, {
|
|
85
|
+
flexDirection: "column",
|
|
86
|
+
children: [
|
|
87
|
+
_jsx(Box, {
|
|
88
|
+
marginBottom: 1,
|
|
89
|
+
children: _jsx(Text, {
|
|
90
|
+
bold: true,
|
|
91
|
+
children: "Select MCP server to remove",
|
|
92
|
+
}),
|
|
93
|
+
}),
|
|
94
|
+
_jsx(SingleSelect, {
|
|
95
|
+
options: options,
|
|
96
|
+
selected: selectedName,
|
|
97
|
+
onChange: setSelectedName,
|
|
98
|
+
onSubmit: (name) => {
|
|
99
|
+
const found = configs.find((c) => c.name === name);
|
|
100
|
+
if (found) {
|
|
101
|
+
setSelectedName(name);
|
|
102
|
+
setSelectedConfig(found);
|
|
103
|
+
setStage("confirm");
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
onCancel: () => process.exit(0),
|
|
107
|
+
}),
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (stage === "confirm") {
|
|
112
|
+
return _jsxs(Box, {
|
|
113
|
+
flexDirection: "column",
|
|
114
|
+
children: [
|
|
115
|
+
_jsx(Box, {
|
|
116
|
+
marginBottom: 1,
|
|
117
|
+
children: _jsx(Text, { bold: true, children: "Confirm removal" }),
|
|
118
|
+
}),
|
|
119
|
+
_jsx(Box, {
|
|
120
|
+
marginBottom: 1,
|
|
121
|
+
children: _jsxs(Text, {
|
|
122
|
+
children: [
|
|
123
|
+
"Are you sure you want to remove:",
|
|
124
|
+
" ",
|
|
125
|
+
_jsx(Text, { bold: true, children: selectedConfig?.name }),
|
|
126
|
+
],
|
|
127
|
+
}),
|
|
128
|
+
}),
|
|
129
|
+
_jsx(Box, {
|
|
130
|
+
marginBottom: 1,
|
|
131
|
+
children: _jsx(Text, {
|
|
132
|
+
dimColor: true,
|
|
133
|
+
children: getMCPSummary(selectedConfig),
|
|
134
|
+
}),
|
|
135
|
+
}),
|
|
136
|
+
_jsx(SingleSelect, {
|
|
137
|
+
options: [
|
|
138
|
+
{
|
|
139
|
+
label: "Yes, remove it",
|
|
140
|
+
value: "yes",
|
|
141
|
+
description: "Permanently delete this MCP server configuration",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: "No, cancel",
|
|
145
|
+
value: "no",
|
|
146
|
+
description: "Go back to selection",
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
selected: null,
|
|
150
|
+
onChange: () => {},
|
|
151
|
+
onSubmit: (choice) => {
|
|
152
|
+
if (choice === "yes") {
|
|
153
|
+
handleRemove();
|
|
154
|
+
} else {
|
|
155
|
+
setStage("select");
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
onCancel: () => setStage("select"),
|
|
159
|
+
}),
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (stage === "removing") {
|
|
164
|
+
return _jsx(Box, {
|
|
165
|
+
children: _jsx(Text, {
|
|
166
|
+
children: "\uD83D\uDDD1\uFE0F Removing MCP server...",
|
|
167
|
+
}),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (stage === "done") {
|
|
171
|
+
return _jsxs(Box, {
|
|
172
|
+
flexDirection: "column",
|
|
173
|
+
children: [
|
|
174
|
+
_jsx(Box, {
|
|
175
|
+
marginBottom: 1,
|
|
176
|
+
children: _jsx(Text, {
|
|
177
|
+
bold: true,
|
|
178
|
+
color: "green",
|
|
179
|
+
children: "\u2705 MCP server removed successfully",
|
|
180
|
+
}),
|
|
181
|
+
}),
|
|
182
|
+
_jsx(Box, {
|
|
183
|
+
children: _jsxs(Text, {
|
|
184
|
+
children: [
|
|
185
|
+
"Removed: ",
|
|
186
|
+
_jsx(Text, { bold: true, children: selectedConfig?.name }),
|
|
187
|
+
],
|
|
188
|
+
}),
|
|
189
|
+
}),
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return _jsxs(Text, { children: ["Unknown stage: ", stage] });
|
|
194
|
+
}
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Export and Runner
|
|
197
|
+
// ============================================================================
|
|
198
|
+
export default MCPRemoveApp;
|
|
199
|
+
export async function runMCPRemove() {
|
|
200
|
+
const { waitUntilExit, clear } = render(_jsx(MCPRemoveApp, {}));
|
|
201
|
+
try {
|
|
202
|
+
await waitUntilExit();
|
|
203
|
+
} finally {
|
|
204
|
+
clear();
|
|
205
|
+
// Ensure cursor is visible
|
|
206
|
+
process.stdout.write("\x1B[?25h");
|
|
207
|
+
}
|
|
208
|
+
}
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
2
5
|
import { join } from "node:path";
|
|
3
|
-
import { agentExists, getAgentPath } from "@
|
|
6
|
+
import { agentExists, getAgentPath } from "@town/agent/storage";
|
|
7
|
+
import open from "open";
|
|
8
|
+
async function loadEnvVars() {
|
|
9
|
+
const envPath = join(homedir(), ".config", "town", ".env");
|
|
10
|
+
const envVars = {};
|
|
11
|
+
if (!existsSync(envPath)) {
|
|
12
|
+
return envVars;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const content = await readFile(envPath, "utf-8");
|
|
16
|
+
for (const line of content.split("\n")) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
19
|
+
continue;
|
|
20
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
21
|
+
const value = valueParts.join("=").trim();
|
|
22
|
+
if (key && value) {
|
|
23
|
+
envVars[key.trim()] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (_error) {
|
|
28
|
+
console.warn(`Warning: Could not load environment variables from ${envPath}`);
|
|
29
|
+
}
|
|
30
|
+
return envVars;
|
|
31
|
+
}
|
|
4
32
|
export async function runCommand(options) {
|
|
5
33
|
const { name, http = false, gui = false, port = 3100 } = options;
|
|
34
|
+
// Load environment variables from ~/.config/town/.env
|
|
35
|
+
const configEnvVars = await loadEnvVars();
|
|
6
36
|
// Check if agent exists
|
|
7
37
|
const exists = await agentExists(name);
|
|
8
38
|
if (!exists) {
|
|
@@ -35,6 +65,7 @@ export async function runCommand(options) {
|
|
|
35
65
|
stdio: "pipe",
|
|
36
66
|
env: {
|
|
37
67
|
...process.env,
|
|
68
|
+
...configEnvVars,
|
|
38
69
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
39
70
|
PORT: port.toString(),
|
|
40
71
|
},
|
|
@@ -51,6 +82,7 @@ export async function runCommand(options) {
|
|
|
51
82
|
stdio: "inherit",
|
|
52
83
|
env: {
|
|
53
84
|
...process.env,
|
|
85
|
+
...configEnvVars,
|
|
54
86
|
VITE_AGENT_URL: `http://localhost:${port}`,
|
|
55
87
|
},
|
|
56
88
|
});
|
|
@@ -66,6 +98,15 @@ export async function runCommand(options) {
|
|
|
66
98
|
process.exit(code);
|
|
67
99
|
}
|
|
68
100
|
});
|
|
101
|
+
// Open browser after GUI server has time to start (default Vite port is 5173)
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
const guiUrl = "http://localhost:5173";
|
|
104
|
+
console.log(`Opening browser at ${guiUrl}...`);
|
|
105
|
+
open(guiUrl).catch((error) => {
|
|
106
|
+
console.warn(`Could not automatically open browser: ${error.message}`);
|
|
107
|
+
console.log(`Please manually open: ${guiUrl}`);
|
|
108
|
+
});
|
|
109
|
+
}, 2000);
|
|
69
110
|
}, 1000);
|
|
70
111
|
agentProcess.on("close", (code) => {
|
|
71
112
|
if (code !== 0 && code !== null) {
|
|
@@ -87,6 +128,7 @@ export async function runCommand(options) {
|
|
|
87
128
|
stdio: "inherit",
|
|
88
129
|
env: {
|
|
89
130
|
...process.env,
|
|
131
|
+
...configEnvVars,
|
|
90
132
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
91
133
|
PORT: port.toString(),
|
|
92
134
|
},
|
|
@@ -105,18 +147,15 @@ export async function runCommand(options) {
|
|
|
105
147
|
}
|
|
106
148
|
// Default: Start TUI interface with the agent
|
|
107
149
|
console.log(`Starting interactive terminal for agent "${name}"...\n`);
|
|
108
|
-
// Get path to TUI app
|
|
109
|
-
const
|
|
110
|
-
const { dirname } = await import("node:path");
|
|
111
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
112
|
-
const cliRoot = join(dirname(currentFile), "..", "..");
|
|
113
|
-
const tuiPath = join(cliRoot, "tui", "index.js");
|
|
150
|
+
// Get path to TUI app
|
|
151
|
+
const tuiPath = join(process.cwd(), "apps", "tui", "src", "index.tsx");
|
|
114
152
|
// Run TUI with the agent
|
|
115
153
|
const tuiProcess = spawn("bun", [tuiPath, "--agent", binPath], {
|
|
116
154
|
cwd: agentPath,
|
|
117
155
|
stdio: "inherit",
|
|
118
156
|
env: {
|
|
119
157
|
...process.env,
|
|
158
|
+
...configEnvVars,
|
|
120
159
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
121
160
|
},
|
|
122
161
|
});
|
package/dist/index.js
CHANGED
|
@@ -3,13 +3,17 @@ import { argument, command, constant, flag, multiple, object, option, optional,
|
|
|
3
3
|
import { message } from "@optique/core/message";
|
|
4
4
|
import { integer, string } from "@optique/core/valueparser";
|
|
5
5
|
import { run } from "@optique/run";
|
|
6
|
-
import { createSecret, deleteSecret, genenv, listSecrets } from "@
|
|
6
|
+
import { createSecret, deleteSecret, genenv, listSecrets } from "@town/secret";
|
|
7
7
|
import inquirer from "inquirer";
|
|
8
8
|
import { match } from "ts-pattern";
|
|
9
|
+
import { configureCommand } from "./commands/configure.js";
|
|
9
10
|
import { createCommand } from "./commands/create.js";
|
|
10
11
|
import { deleteCommand } from "./commands/delete.js";
|
|
11
12
|
import { editCommand } from "./commands/edit.js";
|
|
12
13
|
import { listCommand } from "./commands/list.js";
|
|
14
|
+
import { runMCPAdd } from "./commands/mcp-add.js";
|
|
15
|
+
import { runMCPList } from "./commands/mcp-list.js";
|
|
16
|
+
import { runMCPRemove } from "./commands/mcp-remove.js";
|
|
13
17
|
import { runCommand } from "./commands/run.js";
|
|
14
18
|
/**
|
|
15
19
|
* Securely prompt for a secret value without echoing to the terminal
|
|
@@ -25,7 +29,9 @@ async function promptSecret(secretName) {
|
|
|
25
29
|
]);
|
|
26
30
|
return answers.value;
|
|
27
31
|
}
|
|
28
|
-
const parser = or(command("deploy", constant("deploy"), { brief: message `Deploy a Town.` }), command("
|
|
32
|
+
const parser = or(command("deploy", constant("deploy"), { brief: message `Deploy a Town.` }), command("configure", constant("configure"), {
|
|
33
|
+
brief: message `Configure environment variables.`,
|
|
34
|
+
}), command("create", object({
|
|
29
35
|
command: constant("create"),
|
|
30
36
|
name: optional(option("-n", "--name", string())),
|
|
31
37
|
model: optional(option("-m", "--model", string())),
|
|
@@ -43,7 +49,20 @@ const parser = or(command("deploy", constant("deploy"), { brief: message `Deploy
|
|
|
43
49
|
}), { brief: message `Edit an agent.` }), command("delete", object({
|
|
44
50
|
command: constant("delete"),
|
|
45
51
|
name: argument(string({ metavar: "NAME" })),
|
|
46
|
-
}), { brief: message `Delete an agent.` }), command("
|
|
52
|
+
}), { brief: message `Delete an agent.` }), command("mcp", object({
|
|
53
|
+
command: constant("mcp"),
|
|
54
|
+
subcommand: or(command("add", object({
|
|
55
|
+
action: constant("add"),
|
|
56
|
+
name: optional(option("-n", "--name", string())),
|
|
57
|
+
url: optional(option("-u", "--url", string())),
|
|
58
|
+
command: optional(option("-c", "--command", string())),
|
|
59
|
+
args: multiple(option("-a", "--arg", string())),
|
|
60
|
+
}), { brief: message `Add a new MCP server.` }), command("list", constant("list"), {
|
|
61
|
+
brief: message `List all configured MCP servers.`,
|
|
62
|
+
}), command("remove", constant("remove"), {
|
|
63
|
+
brief: message `Remove an MCP server.`,
|
|
64
|
+
})),
|
|
65
|
+
}), { brief: message `Manage MCP (Model Context Protocol) servers.` }), command("secret", object({
|
|
47
66
|
command: constant("secret"),
|
|
48
67
|
subcommand: or(command("list", constant("list"), { brief: message `List secrets.` }), command("add", object({
|
|
49
68
|
action: constant("add"),
|
|
@@ -69,6 +88,9 @@ async function main(parser, meta) {
|
|
|
69
88
|
await match(result)
|
|
70
89
|
// TODO
|
|
71
90
|
.with("deploy", async () => { })
|
|
91
|
+
.with("configure", async () => {
|
|
92
|
+
await configureCommand();
|
|
93
|
+
})
|
|
72
94
|
.with({ command: "create" }, async ({ name, model, tools, systemPrompt }) => {
|
|
73
95
|
// Create command starts a long-running Ink session
|
|
74
96
|
// Only pass defined properties to satisfy exactOptionalPropertyTypes
|
|
@@ -98,6 +120,20 @@ async function main(parser, meta) {
|
|
|
98
120
|
})
|
|
99
121
|
.with({ command: "delete" }, async ({ name }) => {
|
|
100
122
|
await deleteCommand(name);
|
|
123
|
+
})
|
|
124
|
+
.with({ command: "mcp" }, async ({ subcommand }) => {
|
|
125
|
+
await match(subcommand)
|
|
126
|
+
.with({ action: "add" }, async ({ name, url, command, args }) => {
|
|
127
|
+
await runMCPAdd({
|
|
128
|
+
...(name !== undefined && { name }),
|
|
129
|
+
...(url !== undefined && { url }),
|
|
130
|
+
...(command !== undefined && { command }),
|
|
131
|
+
...(args.length > 0 && { args }),
|
|
132
|
+
});
|
|
133
|
+
})
|
|
134
|
+
.with("list", async () => await runMCPList())
|
|
135
|
+
.with("remove", async () => await runMCPRemove())
|
|
136
|
+
.exhaustive();
|
|
101
137
|
})
|
|
102
138
|
.with({ command: "secret" }, async ({ subcommand }) => {
|
|
103
139
|
await match(subcommand)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type MCPConfig = {
|
|
2
|
+
name: string;
|
|
3
|
+
transport: "stdio" | "http";
|
|
4
|
+
command?: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
url?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Save an MCP config to the store
|
|
10
|
+
*/
|
|
11
|
+
export declare function saveMCPConfig(config: MCPConfig): void;
|
|
12
|
+
/**
|
|
13
|
+
* Load an MCP config by name
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadMCPConfig(name: string): MCPConfig | null;
|
|
16
|
+
/**
|
|
17
|
+
* Delete an MCP config by name
|
|
18
|
+
*/
|
|
19
|
+
export declare function deleteMCPConfig(name: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* List all MCP configs
|
|
22
|
+
*/
|
|
23
|
+
export declare function listMCPConfigs(): MCPConfig[];
|
|
24
|
+
/**
|
|
25
|
+
* Check if an MCP config exists
|
|
26
|
+
*/
|
|
27
|
+
export declare function mcpConfigExists(name: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Get a summary of an MCP config for display
|
|
30
|
+
*/
|
|
31
|
+
export declare function getMCPSummary(config: MCPConfig): string;
|