@townco/cli 0.1.23 → 0.1.24

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/index.js CHANGED
@@ -4,6 +4,7 @@ import { argument, command, constant, flag, multiple, object, option, optional,
4
4
  import { message } from "@optique/core/message";
5
5
  import { integer, string } from "@optique/core/valueparser";
6
6
  import { run } from "@optique/run";
7
+ import { initForClaudeCode } from "@townco/agent/scaffold";
7
8
  import { isInsideTownProject } from "@townco/agent/storage";
8
9
  import { createSecret, deleteSecret, genenv, listSecrets, } from "@townco/secret";
9
10
  import inquirer from "inquirer";
@@ -14,9 +15,6 @@ import { createProjectCommand } from "./commands/create-project.js";
14
15
  import { deleteCommand } from "./commands/delete.js";
15
16
  import { editCommand } from "./commands/edit.js";
16
17
  import { listCommand } from "./commands/list.js";
17
- import { runMCPAdd } from "./commands/mcp-add.js";
18
- import { runMCPList } from "./commands/mcp-list.js";
19
- import { runMCPRemove } from "./commands/mcp-remove.js";
20
18
  import { runCommand } from "./commands/run.js";
21
19
  import { runToolAdd } from "./commands/tool-add.js";
22
20
  import { runToolList } from "./commands/tool-list.js";
@@ -45,7 +43,10 @@ const parser = or(command("deploy", constant("deploy"), { brief: message `Deploy
45
43
  tools: multiple(option("-t", "--tool", string())),
46
44
  systemPrompt: optional(option("-p", "--prompt", string())),
47
45
  init: optional(option("--init", string())),
48
- }), { brief: message `Create a new agent or project (with --init <path>).` }), command("list", constant("list"), { brief: message `List all agents.` }), command("run", object({
46
+ claude: optional(flag("--claude")),
47
+ }), {
48
+ brief: message `Create a new agent or project (with --init <path>). Use --claude to add Claude Code integration.`,
49
+ }), command("list", constant("list"), { brief: message `List all agents.` }), command("run", object({
49
50
  command: constant("run"),
50
51
  name: argument(string({ metavar: "NAME" })),
51
52
  http: optional(flag("--http")),
@@ -57,20 +58,7 @@ const parser = or(command("deploy", constant("deploy"), { brief: message `Deploy
57
58
  }), { brief: message `Edit an agent.` }), command("delete", object({
58
59
  command: constant("delete"),
59
60
  name: argument(string({ metavar: "NAME" })),
60
- }), { brief: message `Delete an agent.` }), command("mcp", object({
61
- command: constant("mcp"),
62
- subcommand: or(command("add", object({
63
- action: constant("add"),
64
- name: optional(option("-n", "--name", string())),
65
- url: optional(option("-u", "--url", string())),
66
- command: optional(option("-c", "--command", string())),
67
- args: multiple(option("-a", "--arg", string())),
68
- }), { brief: message `Add a new MCP server.` }), command("list", constant("list"), {
69
- brief: message `List all configured MCP servers.`,
70
- }), command("remove", constant("remove"), {
71
- brief: message `Remove an MCP server.`,
72
- })),
73
- }), { brief: message `Manage MCP (Model Context Protocol) servers.` }), command("tool", object({
61
+ }), { brief: message `Delete an agent.` }), command("tool", object({
74
62
  command: constant("tool"),
75
63
  subcommand: or(command("add", object({
76
64
  action: constant("add"),
@@ -113,7 +101,24 @@ async function main(parser, meta) {
113
101
  .with("configure", async () => {
114
102
  await configureCommand();
115
103
  })
116
- .with({ command: "create" }, async ({ name, model, tools, systemPrompt, init }) => {
104
+ .with({ command: "create" }, async ({ name, model, tools, systemPrompt, init, claude }) => {
105
+ // Handle --claude flag (initialize .claude in existing project)
106
+ if (claude === true) {
107
+ if (init !== null && init !== undefined) {
108
+ console.error("Error: --claude flag is redundant with --init (projects include .claude by default)");
109
+ process.exit(1);
110
+ }
111
+ // Check if we're in a Town project
112
+ const projectRoot = await isInsideTownProject();
113
+ if (projectRoot === null) {
114
+ console.error("Error: Not inside a Town project. Use 'town create --init <path>' to create a new project.");
115
+ process.exit(1);
116
+ }
117
+ // Initialize .claude directory only
118
+ await initForClaudeCode(projectRoot);
119
+ console.log("\n✓ Claude Code workspace initialized successfully!");
120
+ return;
121
+ }
117
122
  // Check if --init flag is present for project scaffolding
118
123
  if (init !== null && init !== undefined) {
119
124
  // Project mode - scaffold a standalone project
@@ -186,20 +191,6 @@ async function main(parser, meta) {
186
191
  })
187
192
  .with({ command: "delete" }, async ({ name }) => {
188
193
  await deleteCommand(name);
189
- })
190
- .with({ command: "mcp" }, async ({ subcommand }) => {
191
- await match(subcommand)
192
- .with({ action: "add" }, async ({ name, url, command, args }) => {
193
- await runMCPAdd({
194
- ...(name !== undefined && { name }),
195
- ...(url !== undefined && { url }),
196
- ...(command !== undefined && { command }),
197
- ...(args.length > 0 && { args }),
198
- });
199
- })
200
- .with("list", async () => await runMCPList())
201
- .with("remove", async () => await runMCPRemove())
202
- .exhaustive();
203
194
  })
204
195
  .with({ command: "tool" }, async ({ subcommand }) => {
205
196
  await match(subcommand)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/cli",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "town": "./dist/index.js"
@@ -18,16 +18,16 @@
18
18
  "build": "tsc"
19
19
  },
20
20
  "devDependencies": {
21
- "@townco/tsconfig": "0.1.15",
21
+ "@townco/tsconfig": "0.1.16",
22
22
  "@types/bun": "^1.3.1",
23
23
  "@types/react": "^19.2.2"
24
24
  },
25
25
  "dependencies": {
26
26
  "@optique/core": "^0.6.2",
27
27
  "@optique/run": "^0.6.2",
28
- "@townco/agent": "0.1.23",
29
- "@townco/secret": "0.1.18",
30
- "@townco/ui": "0.1.18",
28
+ "@townco/agent": "0.1.24",
29
+ "@townco/secret": "0.1.19",
30
+ "@townco/ui": "0.1.19",
31
31
  "@types/inquirer": "^9.0.9",
32
32
  "ink": "^6.4.0",
33
33
  "ink-text-input": "^6.0.0",
@@ -1,9 +0,0 @@
1
- interface MCPAddProps {
2
- name?: string;
3
- url?: string;
4
- command?: string;
5
- args?: readonly string[];
6
- }
7
- declare function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand, args: initialArgs, }: MCPAddProps): import("react/jsx-runtime").JSX.Element | null;
8
- export default MCPAddApp;
9
- export declare function runMCPAdd(props?: MCPAddProps): Promise<void>;
@@ -1,494 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { readFileSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { getAgentPath, listAgents } from "@townco/agent/storage";
5
- import { MultiSelect, SingleSelect } from "@townco/ui/tui";
6
- import { Box, render, Text, useApp, useInput } from "ink";
7
- import TextInput from "ink-text-input";
8
- import { useEffect, useState } from "react";
9
- import { mcpConfigExists, saveMCPConfig } from "../lib/mcp-storage";
10
- function TextInputStage({ title, value, onChange, onSubmit, onCancel, placeholder, }) {
11
- useInput((_input, key) => {
12
- if (key.escape) {
13
- onCancel();
14
- }
15
- });
16
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: title }) }), _jsxs(Box, { children: [_jsxs(Text, { children: [">", " "] }), _jsx(TextInput, { value: value, onChange: onChange, onSubmit: onSubmit, ...(placeholder && { placeholder }) })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: Continue \u2022 Esc: Back" }) })] }));
17
- }
18
- function TransportSelectStage({ selected, onChange, onNext, onCancel, }) {
19
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select transport type:" }) }), _jsx(SingleSelect, { options: [
20
- {
21
- label: "stdio",
22
- value: "stdio",
23
- description: "Run MCP server as a local process",
24
- },
25
- {
26
- label: "HTTP",
27
- value: "http",
28
- description: "Connect to MCP server over HTTP",
29
- },
30
- ], selected: selected, onChange: (transport) => onChange(transport), onSubmit: (transport) => onNext(transport), onCancel: onCancel })] }));
31
- }
32
- function NameInputStage({ value, onChange, onNext, onBack, onError, }) {
33
- const handleSubmit = () => {
34
- const trimmed = value.trim();
35
- if (!trimmed) {
36
- return;
37
- }
38
- // Check for duplicate
39
- if (mcpConfigExists(trimmed)) {
40
- onError(`MCP server "${trimmed}" already exists`);
41
- return;
42
- }
43
- onNext(trimmed);
44
- };
45
- return (_jsx(TextInputStage, { title: "Enter MCP server name:", value: value, onChange: onChange, onSubmit: handleSubmit, onCancel: onBack, placeholder: "my-mcp-server" }));
46
- }
47
- function StdioCommandStage({ serverName, value, onChange, onNext, onBack, }) {
48
- const handleSubmit = () => {
49
- const trimmed = value.trim();
50
- if (!trimmed) {
51
- return;
52
- }
53
- onNext(trimmed);
54
- };
55
- return (_jsx(TextInputStage, { title: `Enter command to run MCP server: ${serverName}`, value: value, onChange: onChange, onSubmit: handleSubmit, onCancel: onBack, placeholder: "npx @modelcontextprotocol/server-filesystem" }));
56
- }
57
- function StdioArgsStage({ serverName, args, onAddArg, onRemoveLastArg, onNext, onBack, }) {
58
- const [argInput, setArgInput] = useState("");
59
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Add arguments for: ", serverName, " (", args.length, " added)"] }) }), args.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Current arguments:" }), args.map((arg, index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { children: [index + 1, ". ", arg] }) }, arg)))] })), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { children: [">", " "] }), _jsx(TextInput, { value: argInput, onChange: setArgInput, onSubmit: () => {
60
- const trimmed = argInput.trim();
61
- if (trimmed) {
62
- onAddArg(trimmed);
63
- setArgInput("");
64
- }
65
- }, placeholder: "Enter argument (or leave empty and press Enter to continue)" })] }), _jsx(SingleSelect, { options: [
66
- {
67
- label: "Done adding arguments",
68
- value: "done",
69
- description: "Continue to review",
70
- },
71
- {
72
- label: "Remove last argument",
73
- value: "remove",
74
- description: args.length > 0
75
- ? `Remove: ${args[args.length - 1]}`
76
- : "No arguments to remove",
77
- },
78
- ], selected: null, onChange: () => { }, onSubmit: (value) => {
79
- if (value === "done") {
80
- onNext();
81
- }
82
- else if (value === "remove" && args.length > 0) {
83
- onRemoveLastArg();
84
- }
85
- }, onCancel: onBack })] }));
86
- }
87
- function HttpUrlStage({ serverName, value, onChange, onNext, onBack, onError, }) {
88
- const handleSubmit = () => {
89
- const trimmed = value.trim();
90
- if (!trimmed) {
91
- return;
92
- }
93
- // Basic URL validation
94
- try {
95
- new URL(trimmed);
96
- }
97
- catch {
98
- onError("Invalid URL format");
99
- return;
100
- }
101
- onNext(trimmed);
102
- };
103
- return (_jsx(TextInputStage, { title: `Enter HTTP URL for MCP server: ${serverName}`, value: value, onChange: onChange, onSubmit: handleSubmit, onCancel: onBack, placeholder: "http://localhost:3000/mcp" }));
104
- }
105
- function HttpHeadersStage({ serverName, headers, onAddHeader, onNext, onBack, }) {
106
- const [headerKeyInput, setHeaderKeyInput] = useState("");
107
- const [headerValueInput, setHeaderValueInput] = useState("");
108
- const [inputStage, setInputStage] = useState("key");
109
- const headerEntries = Object.entries(headers);
110
- useInput((_input, key) => {
111
- if (key.escape && inputStage === "key") {
112
- onBack();
113
- }
114
- });
115
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Add HTTP headers for: ", serverName, " (", headerEntries.length, " added)"] }) }), headerEntries.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Current headers:" }), headerEntries.map(([key, value], index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { children: [index + 1, ". ", key, ": ", value] }) }, key)))] })), inputStage === "key" && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { children: [">", " Header name: "] }), _jsx(TextInput, { value: headerKeyInput, onChange: setHeaderKeyInput, onSubmit: () => {
116
- const trimmed = headerKeyInput.trim();
117
- if (trimmed) {
118
- setInputStage("value");
119
- }
120
- else {
121
- onNext();
122
- }
123
- }, placeholder: "Enter header name (or leave empty to continue)" })] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Enter: Submit header name or continue \u2022 Esc: Back" }) })] })), inputStage === "value" && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { children: [">", " Value for ", headerKeyInput, ":", " "] }), _jsx(TextInput, { value: headerValueInput, onChange: setHeaderValueInput, onSubmit: () => {
124
- const trimmedValue = headerValueInput.trim();
125
- if (trimmedValue) {
126
- onAddHeader(headerKeyInput, trimmedValue);
127
- setHeaderKeyInput("");
128
- setHeaderValueInput("");
129
- setInputStage("key");
130
- }
131
- }, placeholder: "Header value" })] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Enter: Add header" }) })] }))] }));
132
- }
133
- function ReviewStage({ config, onEdit, onSave, onBack }) {
134
- const [reviewSelection, setReviewSelection] = useState(null);
135
- const reviewOptions = [
136
- {
137
- label: "Save configuration",
138
- value: "continue",
139
- description: "Save and finish",
140
- },
141
- {
142
- label: "Edit name",
143
- value: "name",
144
- description: "Change the MCP server name",
145
- },
146
- {
147
- label: "Edit transport",
148
- value: "transport",
149
- description: "Change the transport type",
150
- },
151
- ];
152
- if (config.transport === "stdio") {
153
- reviewOptions.push({
154
- label: "Edit command",
155
- value: "stdioCommand",
156
- description: "Change the command",
157
- }, {
158
- label: "Edit arguments",
159
- value: "stdioArgs",
160
- description: "Change the arguments",
161
- });
162
- }
163
- else {
164
- reviewOptions.push({
165
- label: "Edit URL",
166
- value: "httpUrl",
167
- description: "Change the HTTP URL",
168
- }, {
169
- label: "Edit headers",
170
- value: "httpHeaders",
171
- description: "Change the HTTP headers",
172
- });
173
- }
174
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Review MCP Server Configuration:" }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Name: " }), config.name] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Transport: " }), config.transport] }), config.transport === "stdio" && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Command: " }), config.command] }), config.args && config.args.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Arguments:" }), config.args.map((arg, index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { dimColor: true, children: [index + 1, ". ", arg] }) }, arg)))] }))] })), config.transport === "http" && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "URL: " }), config.url] }), config.headers && Object.keys(config.headers).length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Headers:" }), Object.entries(config.headers).map(([key, value], index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { dimColor: true, children: [index + 1, ". ", key, ": ", value] }) }, key)))] }))] }))] }), _jsx(SingleSelect, { options: reviewOptions, selected: reviewSelection, onChange: setReviewSelection, onSubmit: (value) => {
175
- setReviewSelection(value);
176
- if (value === "continue") {
177
- onSave();
178
- }
179
- else if (value === "name" ||
180
- value === "transport" ||
181
- value === "stdioCommand" ||
182
- value === "stdioArgs" ||
183
- value === "httpUrl" ||
184
- value === "httpHeaders") {
185
- onEdit(value);
186
- }
187
- }, onCancel: onBack })] }));
188
- }
189
- function NoAgentsMessage({ onNext }) {
190
- useEffect(() => {
191
- const timer = setTimeout(() => {
192
- onNext();
193
- }, 1000);
194
- return () => clearTimeout(timer);
195
- }, [onNext]);
196
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "No agents found. MCP server will be saved globally." }), _jsx(Text, { dimColor: true, children: "You can attach it to agents later." })] }));
197
- }
198
- function AgentSelectionStage({ selectedAgents, onSelectedAgentsChange, onNext, onBack, }) {
199
- const [availableAgents, setAvailableAgents] = useState([]);
200
- const [isLoading, setIsLoading] = useState(true);
201
- useEffect(() => {
202
- // Fetch available agents
203
- const fetchAgents = async () => {
204
- try {
205
- const agents = await listAgents();
206
- setAvailableAgents(agents);
207
- }
208
- catch (error) {
209
- console.error("Error fetching agents:", error);
210
- setAvailableAgents([]);
211
- }
212
- finally {
213
- setIsLoading(false);
214
- }
215
- };
216
- fetchAgents();
217
- }, []);
218
- // If still loading, show loading message
219
- if (isLoading) {
220
- return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { children: "Loading agents..." }) }));
221
- }
222
- // If no agents available, show info message and auto-proceed
223
- if (availableAgents.length === 0) {
224
- return _jsx(NoAgentsMessage, { onNext: onNext });
225
- }
226
- // Show agent selection UI
227
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Attach MCP server to agents (optional):" }) }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Select which agents should have access to this MCP server." }), _jsx(Text, { dimColor: true, children: "You can skip this step and attach agents later." })] }), _jsx(MultiSelect, { options: availableAgents.map((agent) => ({
228
- label: agent,
229
- value: agent,
230
- })), selected: selectedAgents, onChange: onSelectedAgentsChange, onSubmit: onNext, onCancel: onBack })] }));
231
- }
232
- function DoneStage({ config, status, error, attachedAgents }) {
233
- const { exit } = useApp();
234
- useEffect(() => {
235
- if (status === "done") {
236
- setImmediate(() => {
237
- exit();
238
- });
239
- }
240
- }, [status, exit]);
241
- if (status === "saving") {
242
- return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { children: "\u23F3 Saving MCP server configuration..." }) }));
243
- }
244
- if (status === "error") {
245
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "\u274C Error saving MCP server" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: error }) })] }));
246
- }
247
- if (status === "done") {
248
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", children: "\u2705 MCP server saved successfully!" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Name: ", config.name] }) }), attachedAgents.length > 0 && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { dimColor: true, children: ["Attached to agents: ", attachedAgents.join(", ")] }) }))] }));
249
- }
250
- return null;
251
- }
252
- // ============================================================================
253
- // Main Component
254
- // ============================================================================
255
- function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand, args: initialArgs, }) {
256
- // Determine the starting stage based on what's provided
257
- const determineInitialStage = () => {
258
- // If command provided, transport is stdio
259
- if (initialCommand) {
260
- return initialName ? "stdioArgs" : "name";
261
- }
262
- // If URL provided, transport is http
263
- if (initialUrl) {
264
- return initialName ? "httpHeaders" : "name";
265
- }
266
- return "transport";
267
- };
268
- const { exit } = useApp();
269
- const [stage, setStage] = useState(determineInitialStage());
270
- const [config, setConfig] = useState({
271
- ...(initialName && { name: initialName }),
272
- ...(initialCommand && { transport: "stdio" }),
273
- ...(initialUrl && !initialCommand && { transport: "http" }),
274
- ...(initialCommand && { command: initialCommand }),
275
- ...(initialArgs && initialArgs.length > 0 && { args: [...initialArgs] }),
276
- ...(initialUrl && { url: initialUrl }),
277
- });
278
- const [nameInput, setNameInput] = useState(initialName || "");
279
- const [commandInput, setCommandInput] = useState(initialCommand || "");
280
- const [urlInput, setUrlInput] = useState(initialUrl || "");
281
- const [isEditingFromReview, setIsEditingFromReview] = useState(false);
282
- const [saveStatus, setSaveStatus] = useState("pending");
283
- const [saveError, setSaveError] = useState(null);
284
- const [selectedAgents, setSelectedAgents] = useState([]);
285
- const handleSave = (agentsToAttach) => {
286
- setSaveStatus("saving");
287
- // Save the config
288
- try {
289
- if (!config.name || !config.transport) {
290
- setSaveStatus("error");
291
- setSaveError("Missing required fields");
292
- return;
293
- }
294
- if (config.transport === "stdio" &&
295
- (!config.command || !config.command.trim())) {
296
- setSaveStatus("error");
297
- setSaveError("Command is required for stdio transport");
298
- return;
299
- }
300
- if (config.transport === "http" && (!config.url || !config.url.trim())) {
301
- setSaveStatus("error");
302
- setSaveError("URL is required for http transport");
303
- return;
304
- }
305
- const mcpConfig = {
306
- name: config.name,
307
- transport: config.transport,
308
- ...(config.transport === "stdio" && {
309
- command: config.command,
310
- args: config.args,
311
- }),
312
- ...(config.transport === "http" && {
313
- url: config.url,
314
- ...(config.headers && { headers: config.headers }),
315
- }),
316
- };
317
- // Save to global MCP storage
318
- saveMCPConfig(mcpConfig);
319
- // Update selected agents
320
- for (const agentName of agentsToAttach) {
321
- try {
322
- const agentPath = getAgentPath(agentName);
323
- const agentJsonPath = join(agentPath, "agent.json");
324
- // Read existing agent.json
325
- const agentJsonContent = readFileSync(agentJsonPath, "utf-8");
326
- const agentDef = JSON.parse(agentJsonContent);
327
- // Add MCP config to mcps array (create array if doesn't exist)
328
- if (!agentDef.mcps) {
329
- agentDef.mcps = [];
330
- }
331
- // Check if this MCP server is already in the agent's config
332
- const existingIndex = agentDef.mcps.findIndex((mcp) => mcp.name === mcpConfig.name);
333
- if (existingIndex >= 0) {
334
- // Update existing config
335
- agentDef.mcps[existingIndex] = mcpConfig;
336
- }
337
- else {
338
- // Add new config
339
- agentDef.mcps.push(mcpConfig);
340
- }
341
- // Write back to agent.json
342
- writeFileSync(agentJsonPath, JSON.stringify(agentDef, null, 2));
343
- }
344
- catch (error) {
345
- console.error(`Error updating agent ${agentName}:`, error);
346
- // Continue with other agents even if one fails
347
- }
348
- }
349
- setSaveStatus("done");
350
- }
351
- catch (error) {
352
- setSaveStatus("error");
353
- setSaveError(error instanceof Error ? error.message : "Unknown error occurred");
354
- }
355
- };
356
- // Transport selection stage
357
- if (stage === "transport") {
358
- return (_jsx(TransportSelectStage, { selected: config.transport || null, onChange: (transport) => setConfig({ ...config, transport }), onNext: (transport) => {
359
- setConfig({ ...config, transport });
360
- if (isEditingFromReview) {
361
- setIsEditingFromReview(false);
362
- setStage("review");
363
- }
364
- else {
365
- setStage("name");
366
- }
367
- }, onCancel: () => exit() }));
368
- }
369
- // Name input stage
370
- if (stage === "name") {
371
- return (_jsx(NameInputStage, { value: nameInput, onChange: setNameInput, onNext: (name) => {
372
- setConfig({ ...config, name });
373
- if (isEditingFromReview) {
374
- setIsEditingFromReview(false);
375
- setStage("review");
376
- }
377
- else if (config.transport === "stdio") {
378
- setStage("stdioCommand");
379
- }
380
- else {
381
- setStage("httpUrl");
382
- }
383
- }, onBack: () => {
384
- if (initialCommand || initialUrl) {
385
- exit();
386
- }
387
- else {
388
- setStage("transport");
389
- }
390
- }, onError: setSaveError }));
391
- }
392
- // Stdio command input stage
393
- if (stage === "stdioCommand") {
394
- return (_jsx(StdioCommandStage, { serverName: config.name || "", value: commandInput, onChange: setCommandInput, onNext: (command) => {
395
- setConfig({ ...config, command });
396
- if (isEditingFromReview) {
397
- setIsEditingFromReview(false);
398
- setStage("review");
399
- }
400
- else {
401
- setStage("stdioArgs");
402
- }
403
- }, onBack: () => {
404
- if (initialCommand) {
405
- exit();
406
- }
407
- else {
408
- setStage("name");
409
- }
410
- } }));
411
- }
412
- // Stdio args input stage
413
- if (stage === "stdioArgs") {
414
- return (_jsx(StdioArgsStage, { serverName: config.name || "", args: config.args || [], onAddArg: (arg) => setConfig({ ...config, args: [...(config.args || []), arg] }), onRemoveLastArg: () => setConfig({ ...config, args: (config.args || []).slice(0, -1) }), onNext: () => {
415
- if (isEditingFromReview) {
416
- setIsEditingFromReview(false);
417
- }
418
- setStage("review");
419
- }, onBack: () => setStage("stdioCommand") }));
420
- }
421
- // HTTP URL input stage
422
- if (stage === "httpUrl") {
423
- return (_jsx(HttpUrlStage, { serverName: config.name || "", value: urlInput, onChange: setUrlInput, onNext: (url) => {
424
- setConfig({ ...config, url });
425
- if (isEditingFromReview) {
426
- setIsEditingFromReview(false);
427
- setStage("review");
428
- }
429
- else {
430
- setStage("httpHeaders");
431
- }
432
- }, onBack: () => {
433
- if (initialUrl) {
434
- exit();
435
- }
436
- else {
437
- setStage("name");
438
- }
439
- }, onError: setSaveError }));
440
- }
441
- // HTTP Headers input stage
442
- if (stage === "httpHeaders") {
443
- return (_jsx(HttpHeadersStage, { serverName: config.name || "", headers: config.headers || {}, onAddHeader: (key, value) => setConfig({
444
- ...config,
445
- headers: { ...(config.headers || {}), [key]: value },
446
- }), onNext: () => {
447
- if (isEditingFromReview) {
448
- setIsEditingFromReview(false);
449
- }
450
- setStage("review");
451
- }, onBack: () => setStage("httpUrl") }));
452
- }
453
- // Review stage
454
- if (stage === "review") {
455
- return (_jsx(ReviewStage, { config: config, onEdit: (editStage) => {
456
- setIsEditingFromReview(true);
457
- setStage(editStage);
458
- }, onSave: () => {
459
- setStage("agentSelection");
460
- }, onBack: () => {
461
- if (config.transport === "stdio") {
462
- setStage("stdioArgs");
463
- }
464
- else {
465
- setStage("httpHeaders");
466
- }
467
- } }));
468
- }
469
- // Agent selection stage
470
- if (stage === "agentSelection") {
471
- return (_jsx(AgentSelectionStage, { selectedAgents: selectedAgents, onSelectedAgentsChange: setSelectedAgents, onNext: () => {
472
- handleSave(selectedAgents);
473
- setStage("done");
474
- }, onBack: () => setStage("review") }));
475
- }
476
- // Done stage
477
- if (stage === "done") {
478
- return (_jsx(DoneStage, { config: config, status: saveStatus, error: saveError, attachedAgents: selectedAgents }));
479
- }
480
- return null;
481
- }
482
- // ============================================================================
483
- // Export and Runner
484
- // ============================================================================
485
- export default MCPAddApp;
486
- export async function runMCPAdd(props = {}) {
487
- // Set stdin to raw mode to capture input
488
- if (process.stdin.isTTY) {
489
- process.stdin.setRawMode(true);
490
- }
491
- const { waitUntilExit } = render(_jsx(MCPAddApp, { ...props }));
492
- // Wait for the app to exit before returning
493
- await waitUntilExit();
494
- }
@@ -1,3 +0,0 @@
1
- declare function MCPListApp(): import("react/jsx-runtime").JSX.Element;
2
- export default MCPListApp;
3
- export declare function runMCPList(): Promise<void>;