@townco/cli 0.1.14 → 0.1.15

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.
@@ -102,6 +102,34 @@ function HttpUrlStage({ serverName, value, onChange, onNext, onBack, onError, })
102
102
  };
103
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
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
+ }
105
133
  function ReviewStage({ config, onEdit, onSave, onBack }) {
106
134
  const [reviewSelection, setReviewSelection] = useState(null);
107
135
  const reviewOptions = [
@@ -137,9 +165,13 @@ function ReviewStage({ config, onEdit, onSave, onBack }) {
137
165
  label: "Edit URL",
138
166
  value: "httpUrl",
139
167
  description: "Change the HTTP URL",
168
+ }, {
169
+ label: "Edit headers",
170
+ value: "httpHeaders",
171
+ description: "Change the HTTP headers",
140
172
  });
141
173
  }
142
- 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(Text, { children: [_jsx(Text, { bold: true, children: "URL: " }), config.url] }))] }), _jsx(SingleSelect, { options: reviewOptions, selected: reviewSelection, onChange: setReviewSelection, onSubmit: (value) => {
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) => {
143
175
  setReviewSelection(value);
144
176
  if (value === "continue") {
145
177
  onSave();
@@ -148,7 +180,8 @@ function ReviewStage({ config, onEdit, onSave, onBack }) {
148
180
  value === "transport" ||
149
181
  value === "stdioCommand" ||
150
182
  value === "stdioArgs" ||
151
- value === "httpUrl") {
183
+ value === "httpUrl" ||
184
+ value === "httpHeaders") {
152
185
  onEdit(value);
153
186
  }
154
187
  }, onCancel: onBack })] }));
@@ -228,7 +261,7 @@ function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand
228
261
  }
229
262
  // If URL provided, transport is http
230
263
  if (initialUrl) {
231
- return initialName ? "review" : "name";
264
+ return initialName ? "httpHeaders" : "name";
232
265
  }
233
266
  return "transport";
234
267
  };
@@ -278,6 +311,7 @@ function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand
278
311
  }),
279
312
  ...(config.transport === "http" && {
280
313
  url: config.url,
314
+ ...(config.headers && { headers: config.headers }),
281
315
  }),
282
316
  };
283
317
  // Save to global MCP storage
@@ -390,8 +424,11 @@ function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand
390
424
  setConfig({ ...config, url });
391
425
  if (isEditingFromReview) {
392
426
  setIsEditingFromReview(false);
427
+ setStage("review");
428
+ }
429
+ else {
430
+ setStage("httpHeaders");
393
431
  }
394
- setStage("review");
395
432
  }, onBack: () => {
396
433
  if (initialUrl) {
397
434
  exit();
@@ -401,6 +438,18 @@ function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand
401
438
  }
402
439
  }, onError: setSaveError }));
403
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
+ }
404
453
  // Review stage
405
454
  if (stage === "review") {
406
455
  return (_jsx(ReviewStage, { config: config, onEdit: (editStage) => {
@@ -413,7 +462,7 @@ function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand
413
462
  setStage("stdioArgs");
414
463
  }
415
464
  else {
416
- setStage("httpUrl");
465
+ setStage("httpHeaders");
417
466
  }
418
467
  } }));
419
468
  }
@@ -1,188 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
2
  import { Box, render, Text, useApp, useInput } from "ink";
2
3
  import { useEffect, useState } from "react";
3
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  import { listMCPConfigs } from "../lib/mcp-storage";
5
-
6
5
  // ============================================================================
7
6
  // Main Component
8
7
  // ============================================================================
9
8
  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
- });
9
+ const [result, setResult] = useState(null);
10
+ const [loading, setLoading] = useState(true);
11
+ const { exit } = useApp();
12
+ useEffect(() => {
13
+ function loadConfigs() {
14
+ try {
15
+ const configs = listMCPConfigs();
16
+ setResult({ configs });
17
+ }
18
+ catch (error) {
19
+ const errorMsg = error instanceof Error ? error.message : String(error);
20
+ setResult({ configs: [], error: errorMsg });
21
+ }
22
+ finally {
23
+ setLoading(false);
24
+ }
25
+ }
26
+ loadConfigs();
27
+ }, []);
28
+ // Exit on any key press when not loading
29
+ useInput((_input, key) => {
30
+ if (!loading) {
31
+ if (key.return || key.escape || _input === "q") {
32
+ exit();
33
+ }
34
+ }
35
+ });
36
+ if (loading) {
37
+ return (_jsx(Box, { children: _jsx(Text, { children: "Loading MCP servers..." }) }));
38
+ }
39
+ if (result?.error) {
40
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "red", children: "\u274C Error loading MCP servers" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: result.error }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Press Enter or Q to exit" }) })] }));
41
+ }
42
+ if (!result || result.configs.length === 0) {
43
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "No MCP servers configured" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Add one with: town mcp add" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Press Enter or Q to exit" }) })] }));
44
+ }
45
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Configured MCP Servers (", result.configs.length, ")"] }) }), result.configs.map((config, index) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { bold: true, color: "cyan", children: [index + 1, ". ", config.name] }) }), _jsx(Box, { paddingLeft: 3, children: config.transport === "http" ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Transport: ", _jsx(Text, { color: "green", children: "HTTP" })] }), _jsxs(Text, { children: ["URL: ", config.url] }), config.headers && Object.keys(config.headers).length > 0 && (_jsxs(Text, { children: ["Headers:", " ", Object.entries(config.headers)
46
+ .map(([key, value]) => `${key}: ${value}`)
47
+ .join(", ")] }))] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Transport: ", _jsx(Text, { color: "blue", children: "stdio" })] }), _jsxs(Text, { children: ["Command: ", config.command] }), config.args && config.args.length > 0 && (_jsxs(Text, { children: ["Args: ", config.args.join(" ")] }))] })) })] }, config.name))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use `town mcp remove` to remove a server or `town mcp add` to add one" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter or Q to exit" }) })] }));
174
48
  }
175
49
  // ============================================================================
176
50
  // Export and Runner
177
51
  // ============================================================================
178
52
  export default MCPListApp;
179
53
  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
- }
54
+ const { waitUntilExit, clear } = render(_jsx(MCPListApp, {}));
55
+ try {
56
+ await waitUntilExit();
57
+ }
58
+ finally {
59
+ clear();
60
+ // Ensure cursor is visible
61
+ process.stdout.write("\x1B[?25h");
62
+ }
188
63
  }
@@ -7,16 +7,14 @@ import { join } from "node:path";
7
7
  import { agentExists, getAgentPath } from "@townco/agent/storage";
8
8
  import { render } from "ink";
9
9
  import open from "open";
10
- import { useCallback, useMemo, useRef, useState } from "react";
10
+ import { useCallback, useMemo, useRef } from "react";
11
11
  import { TabbedOutput } from "../components/TabbedOutput.js";
12
12
  import { findAvailablePort } from "../lib/port-utils.js";
13
13
  function GuiRunner({ agentProcess, guiProcess, agentPort, onExit, }) {
14
- const [guiPort, setGuiPort] = useState(5173);
15
14
  const browserOpenedRef = useRef(false);
16
15
  const handlePortDetected = useCallback((processIndex, port) => {
17
16
  // Process index 1 is the GUI process
18
17
  if (processIndex === 1) {
19
- setGuiPort(port);
20
18
  // Open browser once we know the actual port
21
19
  if (!browserOpenedRef.current) {
22
20
  browserOpenedRef.current = true;
@@ -33,8 +31,8 @@ function GuiRunner({ agentProcess, guiProcess, agentPort, onExit, }) {
33
31
  // TabbedOutput will update the displayed port internally via onPortDetected callback
34
32
  const processes = useMemo(() => [
35
33
  { name: "Agent", process: agentProcess, port: agentPort },
36
- { name: "GUI", process: guiProcess, port: guiPort },
37
- ], [agentProcess, guiProcess, agentPort, guiPort]);
34
+ { name: "GUI", process: guiProcess }, // Port will be detected dynamically
35
+ ], [agentProcess, guiProcess, agentPort]);
38
36
  return (_jsx(TabbedOutput, { processes: processes, onExit: onExit, onPortDetected: handlePortDetected }));
39
37
  }
40
38
  async function loadEnvVars() {
@@ -1,9 +1,10 @@
1
1
  export type MCPConfig = {
2
- name: string;
3
- transport: "stdio" | "http";
4
- command?: string;
5
- args?: string[];
6
- url?: string;
2
+ name: string;
3
+ transport: "stdio" | "http";
4
+ command?: string;
5
+ args?: string[];
6
+ url?: string;
7
+ headers?: Record<string, string>;
7
8
  };
8
9
  /**
9
10
  * Save an MCP config to the store
@@ -1,7 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
-
5
4
  // ============================================================================
6
5
  // Constants
7
6
  // ============================================================================
@@ -14,40 +13,38 @@ const MCPS_FILE = join(TOWN_CONFIG_DIR, "mcps.json");
14
13
  * Ensure the config directory exists
15
14
  */
16
15
  function ensureConfigDir() {
17
- if (!existsSync(TOWN_CONFIG_DIR)) {
18
- mkdirSync(TOWN_CONFIG_DIR, { recursive: true });
19
- }
16
+ if (!existsSync(TOWN_CONFIG_DIR)) {
17
+ mkdirSync(TOWN_CONFIG_DIR, { recursive: true });
18
+ }
20
19
  }
21
20
  /**
22
21
  * Load all MCP configs from the JSON file
23
22
  */
24
23
  function loadStore() {
25
- ensureConfigDir();
26
- if (!existsSync(MCPS_FILE)) {
27
- return {};
28
- }
29
- try {
30
- const content = readFileSync(MCPS_FILE, "utf-8");
31
- return JSON.parse(content);
32
- } catch (error) {
33
- throw new Error(
34
- `Failed to load MCP configs: ${error instanceof Error ? error.message : String(error)}`,
35
- );
36
- }
24
+ ensureConfigDir();
25
+ if (!existsSync(MCPS_FILE)) {
26
+ return {};
27
+ }
28
+ try {
29
+ const content = readFileSync(MCPS_FILE, "utf-8");
30
+ return JSON.parse(content);
31
+ }
32
+ catch (error) {
33
+ throw new Error(`Failed to load MCP configs: ${error instanceof Error ? error.message : String(error)}`);
34
+ }
37
35
  }
38
36
  /**
39
37
  * Save all MCP configs to the JSON file
40
38
  */
41
39
  function saveStore(store) {
42
- ensureConfigDir();
43
- try {
44
- const content = JSON.stringify(store, null, 2);
45
- writeFileSync(MCPS_FILE, content, "utf-8");
46
- } catch (error) {
47
- throw new Error(
48
- `Failed to save MCP configs: ${error instanceof Error ? error.message : String(error)}`,
49
- );
50
- }
40
+ ensureConfigDir();
41
+ try {
42
+ const content = JSON.stringify(store, null, 2);
43
+ writeFileSync(MCPS_FILE, content, "utf-8");
44
+ }
45
+ catch (error) {
46
+ throw new Error(`Failed to save MCP configs: ${error instanceof Error ? error.message : String(error)}`);
47
+ }
51
48
  }
52
49
  // ============================================================================
53
50
  // Public API
@@ -56,54 +53,59 @@ function saveStore(store) {
56
53
  * Save an MCP config to the store
57
54
  */
58
55
  export function saveMCPConfig(config) {
59
- const store = loadStore();
60
- store[config.name] = config;
61
- saveStore(store);
56
+ const store = loadStore();
57
+ store[config.name] = config;
58
+ saveStore(store);
62
59
  }
63
60
  /**
64
61
  * Load an MCP config by name
65
62
  */
66
63
  export function loadMCPConfig(name) {
67
- const store = loadStore();
68
- return store[name] || null;
64
+ const store = loadStore();
65
+ return store[name] || null;
69
66
  }
70
67
  /**
71
68
  * Delete an MCP config by name
72
69
  */
73
70
  export function deleteMCPConfig(name) {
74
- const store = loadStore();
75
- if (store[name]) {
76
- delete store[name];
77
- saveStore(store);
78
- return true;
79
- }
80
- return false;
71
+ const store = loadStore();
72
+ if (store[name]) {
73
+ delete store[name];
74
+ saveStore(store);
75
+ return true;
76
+ }
77
+ return false;
81
78
  }
82
79
  /**
83
80
  * List all MCP configs
84
81
  */
85
82
  export function listMCPConfigs() {
86
- const store = loadStore();
87
- return Object.values(store).sort((a, b) => a.name.localeCompare(b.name));
83
+ const store = loadStore();
84
+ return Object.values(store).sort((a, b) => a.name.localeCompare(b.name));
88
85
  }
89
86
  /**
90
87
  * Check if an MCP config exists
91
88
  */
92
89
  export function mcpConfigExists(name) {
93
- const store = loadStore();
94
- return name in store;
90
+ const store = loadStore();
91
+ return name in store;
95
92
  }
96
93
  /**
97
94
  * Get a summary of an MCP config for display
98
95
  */
99
96
  export function getMCPSummary(config) {
100
- if (config.transport === "http") {
101
- return `HTTP: ${config.url}`;
102
- } else {
103
- const parts = [`Stdio: ${config.command}`];
104
- if (config.args && config.args.length > 0) {
105
- parts.push(`[${config.args.join(" ")}]`);
106
- }
107
- return parts.join(" ");
108
- }
97
+ if (config.transport === "http") {
98
+ const parts = [`HTTP: ${config.url}`];
99
+ if (config.headers && Object.keys(config.headers).length > 0) {
100
+ parts.push(`(${Object.keys(config.headers).length} header${Object.keys(config.headers).length === 1 ? "" : "s"})`);
101
+ }
102
+ return parts.join(" ");
103
+ }
104
+ else {
105
+ const parts = [`Stdio: ${config.command}`];
106
+ if (config.args && config.args.length > 0) {
107
+ parts.push(`[${config.args.join(" ")}]`);
108
+ }
109
+ return parts.join(" ");
110
+ }
109
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "town": "./dist/index.js"
@@ -19,16 +19,16 @@
19
19
  "build": "tsc"
20
20
  },
21
21
  "devDependencies": {
22
- "@townco/tsconfig": "0.1.6",
22
+ "@townco/tsconfig": "0.1.7",
23
23
  "@types/bun": "^1.3.1",
24
24
  "@types/react": "^19.2.2"
25
25
  },
26
26
  "dependencies": {
27
27
  "@optique/core": "^0.6.2",
28
28
  "@optique/run": "^0.6.2",
29
- "@townco/agent": "0.1.14",
30
- "@townco/secret": "0.1.9",
31
- "@townco/ui": "0.1.9",
29
+ "@townco/agent": "0.1.15",
30
+ "@townco/secret": "0.1.10",
31
+ "@townco/ui": "0.1.10",
32
32
  "@types/inquirer": "^9.0.9",
33
33
  "ink": "^6.4.0",
34
34
  "ink-text-input": "^6.0.0",