@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,109 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Constants
|
|
7
|
+
// ============================================================================
|
|
8
|
+
const TOWN_CONFIG_DIR = join(homedir(), ".config", "town");
|
|
9
|
+
const MCPS_FILE = join(TOWN_CONFIG_DIR, "mcps.json");
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Helper Functions
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Ensure the config directory exists
|
|
15
|
+
*/
|
|
16
|
+
function ensureConfigDir() {
|
|
17
|
+
if (!existsSync(TOWN_CONFIG_DIR)) {
|
|
18
|
+
mkdirSync(TOWN_CONFIG_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load all MCP configs from the JSON file
|
|
23
|
+
*/
|
|
24
|
+
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
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Save all MCP configs to the JSON file
|
|
40
|
+
*/
|
|
41
|
+
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
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Public API
|
|
54
|
+
// ============================================================================
|
|
55
|
+
/**
|
|
56
|
+
* Save an MCP config to the store
|
|
57
|
+
*/
|
|
58
|
+
export function saveMCPConfig(config) {
|
|
59
|
+
const store = loadStore();
|
|
60
|
+
store[config.name] = config;
|
|
61
|
+
saveStore(store);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load an MCP config by name
|
|
65
|
+
*/
|
|
66
|
+
export function loadMCPConfig(name) {
|
|
67
|
+
const store = loadStore();
|
|
68
|
+
return store[name] || null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Delete an MCP config by name
|
|
72
|
+
*/
|
|
73
|
+
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;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* List all MCP configs
|
|
84
|
+
*/
|
|
85
|
+
export function listMCPConfigs() {
|
|
86
|
+
const store = loadStore();
|
|
87
|
+
return Object.values(store).sort((a, b) => a.name.localeCompare(b.name));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if an MCP config exists
|
|
91
|
+
*/
|
|
92
|
+
export function mcpConfigExists(name) {
|
|
93
|
+
const store = loadStore();
|
|
94
|
+
return name in store;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get a summary of an MCP config for display
|
|
98
|
+
*/
|
|
99
|
+
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
|
+
}
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"types": "./dist/index.d.ts",
|
|
7
5
|
"bin": {
|
|
8
6
|
"townco": "./dist/index.js"
|
|
9
7
|
},
|
|
@@ -16,33 +14,29 @@
|
|
|
16
14
|
"type": "git",
|
|
17
15
|
"url": "git+https://github.com/federicoweber/agent_hub.git"
|
|
18
16
|
},
|
|
19
|
-
"author": "Federico Weber",
|
|
20
|
-
"engines": {
|
|
21
|
-
"bun": ">=1.3.0"
|
|
22
|
-
},
|
|
23
17
|
"scripts": {
|
|
24
18
|
"check": "tsc --noEmit",
|
|
25
19
|
"build": "tsc"
|
|
26
20
|
},
|
|
27
|
-
"peerDependencies": {
|
|
28
|
-
"react": ">=19.0.0"
|
|
29
|
-
},
|
|
30
21
|
"devDependencies": {
|
|
31
|
-
"@townco/tsconfig": "
|
|
22
|
+
"@townco/tsconfig": "workspace:*",
|
|
32
23
|
"@types/bun": "^1.3.1",
|
|
33
|
-
"@types/react": "^19.2.2"
|
|
34
|
-
"react": "^19.2.0"
|
|
24
|
+
"@types/react": "^19.2.2"
|
|
35
25
|
},
|
|
36
26
|
"dependencies": {
|
|
37
27
|
"@optique/core": "^0.6.2",
|
|
38
28
|
"@optique/run": "^0.6.2",
|
|
39
|
-
"@townco/
|
|
40
|
-
"@townco/
|
|
41
|
-
"@townco/
|
|
29
|
+
"@townco/agent": "workspace:*",
|
|
30
|
+
"@townco/secret": "workspace:*",
|
|
31
|
+
"@townco/ui": "workspace:*",
|
|
42
32
|
"@types/inquirer": "^9.0.9",
|
|
43
33
|
"ink": "^6.4.0",
|
|
44
34
|
"ink-text-input": "^6.0.0",
|
|
45
35
|
"inquirer": "^12.10.0",
|
|
36
|
+
"open": "^10.2.0",
|
|
46
37
|
"ts-pattern": "^5.9.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=19.0.0"
|
|
47
41
|
}
|
|
48
42
|
}
|
package/tui/App.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CliConfig } from "./cli.js";
|
|
2
2
|
export interface AppProps {
|
|
3
|
-
|
|
3
|
+
config: CliConfig;
|
|
4
4
|
}
|
|
5
|
-
export declare function App({
|
|
5
|
+
export declare function App({
|
|
6
|
+
config,
|
|
7
|
+
}: AppProps): import("react/jsx-runtime").JSX.Element;
|
package/tui/App.js
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
1
|
import { AcpClient } from "@townco/ui";
|
|
3
2
|
import { ChatView } from "@townco/ui/tui";
|
|
4
3
|
import { Box, Text } from "ink";
|
|
5
4
|
import { useMemo } from "react";
|
|
5
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
6
6
|
export function App({ config }) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
// Create ACP client
|
|
8
|
+
const client = useMemo(() => {
|
|
9
|
+
try {
|
|
10
|
+
const newClient = new AcpClient({
|
|
11
|
+
type: "stdio",
|
|
12
|
+
options: {
|
|
13
|
+
agentPath: config.agentPath,
|
|
14
|
+
workingDirectory: config.workingDir ?? process.cwd(),
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
return newClient;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("Failed to create client:", error);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}, [config.agentPath, config.workingDir]);
|
|
23
|
+
if (!client) {
|
|
24
|
+
return _jsxs(Box, {
|
|
25
|
+
flexDirection: "column",
|
|
26
|
+
padding: 1,
|
|
27
|
+
children: [
|
|
28
|
+
_jsx(Text, {
|
|
29
|
+
color: "red",
|
|
30
|
+
bold: true,
|
|
31
|
+
children: "Error: Failed to create ACP client",
|
|
32
|
+
}),
|
|
33
|
+
_jsx(Text, {
|
|
34
|
+
color: "gray",
|
|
35
|
+
children: "Please check your agent path and try again.",
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return _jsx(ChatView, { client: client });
|
|
28
41
|
}
|
package/tui/cli.d.ts
CHANGED
|
@@ -2,11 +2,24 @@ import type { InferValue } from "@optique/core/parser";
|
|
|
2
2
|
/**
|
|
3
3
|
* CLI configuration using Optique
|
|
4
4
|
*/
|
|
5
|
-
export declare const cliParser: import("@optique/core/parser").Parser<
|
|
5
|
+
export declare const cliParser: import("@optique/core/parser").Parser<
|
|
6
|
+
{
|
|
6
7
|
readonly agentPath: string;
|
|
7
8
|
readonly workingDir: string | undefined;
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
readonly
|
|
11
|
-
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
readonly agentPath:
|
|
12
|
+
| import("@optique/core/valueparser").ValueParserResult<string>
|
|
13
|
+
| undefined;
|
|
14
|
+
readonly workingDir:
|
|
15
|
+
| [
|
|
16
|
+
| [
|
|
17
|
+
| import("@optique/core/valueparser").ValueParserResult<string>
|
|
18
|
+
| undefined,
|
|
19
|
+
]
|
|
20
|
+
| undefined,
|
|
21
|
+
]
|
|
22
|
+
| undefined;
|
|
23
|
+
}
|
|
24
|
+
>;
|
|
12
25
|
export type CliConfig = InferValue<typeof cliParser>;
|
package/tui/cli.js
CHANGED
|
@@ -5,10 +5,15 @@ import { string } from "@optique/core/valueparser";
|
|
|
5
5
|
* CLI configuration using Optique
|
|
6
6
|
*/
|
|
7
7
|
export const cliParser = object({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
agentPath: option("-a", "--agent", string(), {
|
|
9
|
+
description: [text("Path to the ACP agent executable")],
|
|
10
|
+
}),
|
|
11
|
+
workingDir: withDefault(
|
|
12
|
+
optional(
|
|
13
|
+
option("-d", "--dir", string(), {
|
|
14
|
+
description: [text("Working directory for the agent")],
|
|
15
|
+
}),
|
|
16
|
+
),
|
|
17
|
+
process.cwd(),
|
|
18
|
+
),
|
|
14
19
|
});
|
package/tui/index.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
2
|
import { run } from "@optique/run";
|
|
4
3
|
import { render } from "ink";
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
5
|
import { App } from "./App.js";
|
|
6
6
|
import { cliParser } from "./cli.js";
|
|
7
|
+
|
|
7
8
|
/**
|
|
8
9
|
* Agent Hub TUI
|
|
9
10
|
* Terminal chat interface for ACP agents
|
|
10
11
|
*/
|
|
11
12
|
async function main() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
// Parse CLI arguments
|
|
14
|
+
const config = run(cliParser, {
|
|
15
|
+
help: "both", // Show help for both commands and the program
|
|
16
|
+
programName: "agent-tui",
|
|
17
|
+
description: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: "Terminal chat interface for Agent Client Protocol (ACP) agents",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
version: "0.0.1",
|
|
24
|
+
});
|
|
25
|
+
// Render the app
|
|
26
|
+
render(_jsx(App, { config: config }));
|
|
26
27
|
}
|
|
27
28
|
main().catch((error) => {
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
console.error("Fatal error:", error);
|
|
30
|
+
process.exit(1);
|
|
30
31
|
});
|