@townco/agent 0.1.22 → 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/acp-server/adapter.d.ts +10 -14
- package/dist/acp-server/cli.d.ts +1 -3
- package/dist/acp-server/cli.js +5 -9
- package/dist/acp-server/http.d.ts +1 -3
- package/dist/acp-server/http.js +2 -2
- package/dist/bin.js +0 -0
- package/dist/definition/index.d.ts +6 -0
- package/dist/definition/index.js +9 -0
- package/dist/index.js +11 -5
- package/dist/runner/agent-runner.d.ts +6 -0
- package/dist/runner/index.d.ts +1 -3
- package/dist/runner/index.js +14 -18
- package/dist/runner/langchain/index.js +35 -23
- package/dist/runner/langchain/tools/todo.d.ts +32 -48
- package/dist/runner/langchain/tools/web_search.d.ts +1 -1
- package/dist/runner/tools.d.ts +16 -0
- package/dist/runner/tools.js +10 -1
- package/dist/scaffold/claude-scaffold.d.ts +8 -0
- package/dist/scaffold/claude-scaffold.js +58 -0
- package/dist/scaffold/copy-gui.js +7 -81
- package/dist/scaffold/copy-tui.js +1 -65
- package/dist/scaffold/index.d.ts +3 -0
- package/dist/scaffold/index.js +27 -31
- package/dist/scaffold/project-scaffold.d.ts +12 -0
- package/dist/scaffold/project-scaffold.js +317 -0
- package/dist/scaffold/templates/dot-claude/CLAUDE-append.md +71 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.js +60 -24
- package/dist/templates/index.d.ts +7 -2
- package/dist/templates/index.js +13 -16
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/tool.d.ts +36 -0
- package/dist/utils/tool.js +33 -0
- package/index.ts +11 -7
- package/package.json +7 -6
- package/templates/index.ts +23 -18
- package/dist/definition/mcp.d.ts +0 -0
- package/dist/definition/mcp.js +0 -0
- package/dist/definition/tools/todo.d.ts +0 -49
- package/dist/definition/tools/todo.js +0 -80
- package/dist/definition/tools/web_search.d.ts +0 -4
- package/dist/definition/tools/web_search.js +0 -26
- package/dist/dev-agent/index.d.ts +0 -2
- package/dist/dev-agent/index.js +0 -18
- package/dist/example.d.ts +0 -2
- package/dist/example.js +0 -19
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cp, mkdir
|
|
1
|
+
import { cp, mkdir } from "node:fs/promises";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
@@ -29,68 +29,4 @@ export async function copyTuiApp(agentPath) {
|
|
|
29
29
|
const targetPath = join(tuiDir, item);
|
|
30
30
|
await cp(sourcePath, targetPath, { recursive: true });
|
|
31
31
|
}
|
|
32
|
-
// Create a standalone tsconfig.json for the TUI
|
|
33
|
-
const tuiTsConfig = {
|
|
34
|
-
compilerOptions: {
|
|
35
|
-
allowArbitraryExtensions: true,
|
|
36
|
-
allowUnreachableCode: false,
|
|
37
|
-
allowUnusedLabels: false,
|
|
38
|
-
declaration: true,
|
|
39
|
-
emitDecoratorMetadata: true,
|
|
40
|
-
esModuleInterop: true,
|
|
41
|
-
exactOptionalPropertyTypes: true,
|
|
42
|
-
experimentalDecorators: true,
|
|
43
|
-
jsx: "react-jsx",
|
|
44
|
-
lib: ["ESNext"],
|
|
45
|
-
module: "ESNext",
|
|
46
|
-
moduleResolution: "bundler",
|
|
47
|
-
noFallthroughCasesInSwitch: true,
|
|
48
|
-
noImplicitAny: true,
|
|
49
|
-
noImplicitOverride: true,
|
|
50
|
-
noImplicitReturns: true,
|
|
51
|
-
noUncheckedIndexedAccess: true,
|
|
52
|
-
noUncheckedSideEffectImports: true,
|
|
53
|
-
noUnusedLocals: false,
|
|
54
|
-
noUnusedParameters: true,
|
|
55
|
-
resolveJsonModule: true,
|
|
56
|
-
skipLibCheck: true,
|
|
57
|
-
strict: true,
|
|
58
|
-
stripInternal: true,
|
|
59
|
-
target: "ESNext",
|
|
60
|
-
verbatimModuleSyntax: true,
|
|
61
|
-
outDir: "./dist",
|
|
62
|
-
rootDir: "./src",
|
|
63
|
-
},
|
|
64
|
-
include: ["src/**/*"],
|
|
65
|
-
exclude: ["node_modules", "dist"],
|
|
66
|
-
};
|
|
67
|
-
await writeFile(join(tuiDir, "tsconfig.json"), JSON.stringify(tuiTsConfig, null, 2));
|
|
68
|
-
// Generate a custom package.json for the TUI
|
|
69
|
-
const packageJson = {
|
|
70
|
-
name: "agent-tui",
|
|
71
|
-
version: "0.0.1",
|
|
72
|
-
type: "module",
|
|
73
|
-
private: true,
|
|
74
|
-
bin: {
|
|
75
|
-
"agent-tui": "./dist/index.js",
|
|
76
|
-
},
|
|
77
|
-
scripts: {
|
|
78
|
-
build: "tsc",
|
|
79
|
-
start: "bun dist/index.js",
|
|
80
|
-
},
|
|
81
|
-
dependencies: {
|
|
82
|
-
"@townco/ui": "^0.1.0",
|
|
83
|
-
"@optique/core": "^0.6.2",
|
|
84
|
-
"@optique/run": "^0.6.2",
|
|
85
|
-
ink: "^6.4.0",
|
|
86
|
-
"ink-text-input": "^6.0.0",
|
|
87
|
-
react: "^19.2.0",
|
|
88
|
-
},
|
|
89
|
-
devDependencies: {
|
|
90
|
-
"@types/node": "^24.10.0",
|
|
91
|
-
"@types/react": "^19.2.2",
|
|
92
|
-
typescript: "^5.9.3",
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
await writeFile(join(tuiDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
96
32
|
}
|
package/dist/scaffold/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface ScaffoldOptions {
|
|
|
5
5
|
overwrite?: boolean;
|
|
6
6
|
includeGui?: boolean;
|
|
7
7
|
includeTui?: boolean;
|
|
8
|
+
agentsDir: string;
|
|
8
9
|
}
|
|
9
10
|
export interface ScaffoldResult {
|
|
10
11
|
success: boolean;
|
|
@@ -15,3 +16,5 @@ export interface ScaffoldResult {
|
|
|
15
16
|
* Scaffold a new agent package
|
|
16
17
|
*/
|
|
17
18
|
export declare function scaffoldAgent(options: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
19
|
+
export { initForClaudeCode } from "./claude-scaffold";
|
|
20
|
+
export { scaffoldProject } from "./project-scaffold";
|
package/dist/scaffold/index.js
CHANGED
|
@@ -1,52 +1,47 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { generateAgentJson, generateBinTs, generateEnvExample, generateGitignore, generateIndexTs, generatePackageJson, generateReadme, generateTsConfig, getTemplateVars, } from "../templates";
|
|
4
|
+
import { generateBinTs, generateIndexTs, getTemplateVars } from "../templates";
|
|
6
5
|
import { copyGuiApp } from "./copy-gui";
|
|
7
6
|
import { copyTuiApp } from "./copy-tui";
|
|
8
7
|
/**
|
|
9
8
|
* Scaffold a new agent package
|
|
10
9
|
*/
|
|
11
10
|
export async function scaffoldAgent(options) {
|
|
12
|
-
const { name, definition, overwrite = false, includeGui = true, includeTui = true, } = options;
|
|
11
|
+
const { name, definition, overwrite = false, includeGui = true, includeTui = true, agentsDir, } = options;
|
|
13
12
|
try {
|
|
14
|
-
// Ensure
|
|
15
|
-
await
|
|
13
|
+
// Ensure the agents directory exists
|
|
14
|
+
await mkdir(agentsDir, { recursive: true });
|
|
16
15
|
// Check if agent already exists
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const agentPath = join(agentsDir, name);
|
|
17
|
+
try {
|
|
18
|
+
const agentStat = await stat(agentPath);
|
|
19
|
+
if (agentStat.isDirectory() && !overwrite) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
path: agentPath,
|
|
23
|
+
error: `Agent "${name}" already exists. Use overwrite option to replace it.`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Agent doesn't exist, which is fine
|
|
24
29
|
}
|
|
25
|
-
const agentPath = getAgentPath(name);
|
|
26
30
|
// Create the agent directory
|
|
27
31
|
await mkdir(agentPath, { recursive: true });
|
|
28
32
|
const vars = getTemplateVars(name, definition);
|
|
29
|
-
// Generate
|
|
33
|
+
// Generate template files - just the essentials
|
|
30
34
|
const files = [
|
|
31
|
-
{ path: "
|
|
32
|
-
{ path: "agent.json", content: generateAgentJson(vars) },
|
|
33
|
-
{ path: "index.ts", content: generateIndexTs() },
|
|
35
|
+
{ path: "index.ts", content: await generateIndexTs(vars) },
|
|
34
36
|
{ path: "bin.ts", content: generateBinTs(), executable: true },
|
|
35
|
-
{ path: "tsconfig.json", content: generateTsConfig() },
|
|
36
|
-
{ path: "README.md", content: generateReadme(vars) },
|
|
37
|
-
{ path: ".gitignore", content: generateGitignore() },
|
|
38
37
|
];
|
|
39
|
-
// Add .env.example if needed
|
|
40
|
-
const envExample = generateEnvExample(vars);
|
|
41
|
-
if (envExample) {
|
|
42
|
-
files.push({ path: ".env.example", content: envExample });
|
|
43
|
-
}
|
|
44
38
|
// Write all files
|
|
45
39
|
for (const file of files) {
|
|
46
40
|
const filePath = join(agentPath, file.path);
|
|
47
41
|
await writeFile(filePath, file.content, "utf-8");
|
|
48
|
-
// Make executable if
|
|
49
|
-
if (file.executable) {
|
|
42
|
+
// Make executable if specified
|
|
43
|
+
if ("executable" in file && file.executable) {
|
|
44
|
+
const { chmod } = await import("node:fs/promises");
|
|
50
45
|
await chmod(filePath, 0o755);
|
|
51
46
|
}
|
|
52
47
|
}
|
|
@@ -64,8 +59,6 @@ export async function scaffoldAgent(options) {
|
|
|
64
59
|
const tuiPath = join(agentPath, "tui");
|
|
65
60
|
await runBunInstall(tuiPath);
|
|
66
61
|
}
|
|
67
|
-
// Run bun install in agent root to fetch dependencies from npm
|
|
68
|
-
await runBunInstall(agentPath);
|
|
69
62
|
return {
|
|
70
63
|
success: true,
|
|
71
64
|
path: agentPath,
|
|
@@ -74,7 +67,7 @@ export async function scaffoldAgent(options) {
|
|
|
74
67
|
catch (error) {
|
|
75
68
|
return {
|
|
76
69
|
success: false,
|
|
77
|
-
path:
|
|
70
|
+
path: join(agentsDir, name),
|
|
78
71
|
error: error instanceof Error ? error.message : String(error),
|
|
79
72
|
};
|
|
80
73
|
}
|
|
@@ -101,3 +94,6 @@ function runBunInstall(agentPath) {
|
|
|
101
94
|
});
|
|
102
95
|
});
|
|
103
96
|
}
|
|
97
|
+
export { initForClaudeCode } from "./claude-scaffold";
|
|
98
|
+
// Export project scaffolding
|
|
99
|
+
export { scaffoldProject } from "./project-scaffold";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ScaffoldProjectOptions {
|
|
2
|
+
path: string;
|
|
3
|
+
}
|
|
4
|
+
export interface ScaffoldProjectResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
path: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Scaffold a new standalone project
|
|
11
|
+
*/
|
|
12
|
+
export declare function scaffoldProject(options: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { initForClaudeCode } from "./claude-scaffold.js";
|
|
5
|
+
/**
|
|
6
|
+
* Generate project package.json
|
|
7
|
+
*/
|
|
8
|
+
function generateProjectPackageJson() {
|
|
9
|
+
const pkg = {
|
|
10
|
+
name: "town-agents-project",
|
|
11
|
+
version: "0.1.0",
|
|
12
|
+
type: "module",
|
|
13
|
+
private: true,
|
|
14
|
+
scripts: {},
|
|
15
|
+
dependencies: {
|
|
16
|
+
"@agentclientprotocol/sdk": "^0.5.1",
|
|
17
|
+
"@optique/core": "^0.6.2",
|
|
18
|
+
"@optique/run": "^0.6.2",
|
|
19
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
20
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
21
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
22
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
23
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
24
|
+
"@townco/ui": "^0.1.0",
|
|
25
|
+
"@townco/agent": "^0.1.20",
|
|
26
|
+
"class-variance-authority": "^0.7.1",
|
|
27
|
+
clsx: "^2.1.1",
|
|
28
|
+
"ink-text-input": "^6.0.0",
|
|
29
|
+
ink: "^6.4.0",
|
|
30
|
+
"lucide-react": "^0.552.0",
|
|
31
|
+
"react-dom": "^19.2.0",
|
|
32
|
+
"react-markdown": "^10.1.0",
|
|
33
|
+
react: "^19.2.0",
|
|
34
|
+
"remark-gfm": "^4.0.1",
|
|
35
|
+
"tailwind-merge": "^3.3.1",
|
|
36
|
+
zod: "^4.1.12",
|
|
37
|
+
zustand: "^5.0.8",
|
|
38
|
+
},
|
|
39
|
+
devDependencies: {
|
|
40
|
+
"@tailwindcss/postcss": "^4.1.17",
|
|
41
|
+
"@types/react": "^19.2.2",
|
|
42
|
+
"@types/react-dom": "^19.2.2",
|
|
43
|
+
"@vitejs/plugin-react": "^5.1.0",
|
|
44
|
+
autoprefixer: "^10.4.21",
|
|
45
|
+
postcss: "^8.5.6",
|
|
46
|
+
tailwindcss: "^4.1.17",
|
|
47
|
+
typescript: "^5.9.3",
|
|
48
|
+
vite: "^7.2.1",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return JSON.stringify(pkg, null, 2);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generate add-two-numbers tool
|
|
55
|
+
*/
|
|
56
|
+
function generateAddTwoNumbersTool() {
|
|
57
|
+
return `// biome-ignore lint/suspicious/noExplicitAny: .
|
|
58
|
+
export const schema = (z: any) =>
|
|
59
|
+
z.object({
|
|
60
|
+
a: z.number().describe("First number"),
|
|
61
|
+
b: z.number().describe("Second number"),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const name = "add_two_numbers";
|
|
65
|
+
export const description = "Add two numbers together and return the result";
|
|
66
|
+
|
|
67
|
+
export default function addTwoNumbers(input: unknown) {
|
|
68
|
+
const { a, b } = input as { a: number; b: number };
|
|
69
|
+
return { result: a + b };
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate project tsconfig.json
|
|
75
|
+
*/
|
|
76
|
+
function generateProjectTsConfig() {
|
|
77
|
+
const config = {
|
|
78
|
+
compilerOptions: {
|
|
79
|
+
target: "ESNext",
|
|
80
|
+
module: "ESNext",
|
|
81
|
+
moduleResolution: "bundler",
|
|
82
|
+
lib: ["ESNext"],
|
|
83
|
+
outDir: "./dist",
|
|
84
|
+
strict: true,
|
|
85
|
+
esModuleInterop: true,
|
|
86
|
+
skipLibCheck: true,
|
|
87
|
+
forceConsistentCasingInFileNames: true,
|
|
88
|
+
resolveJsonModule: true,
|
|
89
|
+
allowSyntheticDefaultImports: true,
|
|
90
|
+
},
|
|
91
|
+
include: ["agents/**/*.ts", "tools/**/*.ts"],
|
|
92
|
+
exclude: ["node_modules", "dist"],
|
|
93
|
+
};
|
|
94
|
+
return JSON.stringify(config, null, 2);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Generate project README.md
|
|
98
|
+
*/
|
|
99
|
+
function generateProjectReadme() {
|
|
100
|
+
return `# Town Agents Project
|
|
101
|
+
|
|
102
|
+
A standalone project for developing Town agents with custom tools.
|
|
103
|
+
|
|
104
|
+
## Project Structure
|
|
105
|
+
|
|
106
|
+
\`\`\`
|
|
107
|
+
.
|
|
108
|
+
├── agents/ # Agent implementations (created with 'town create')
|
|
109
|
+
├── tools/ # Custom tools
|
|
110
|
+
│ └── add-two-numbers.ts # Example tool
|
|
111
|
+
├── package.json # Project dependencies
|
|
112
|
+
└── tsconfig.json # TypeScript configuration
|
|
113
|
+
\`\`\`
|
|
114
|
+
|
|
115
|
+
## Getting Started
|
|
116
|
+
|
|
117
|
+
1. Install dependencies (if not already done):
|
|
118
|
+
|
|
119
|
+
\`\`\`bash
|
|
120
|
+
bun install
|
|
121
|
+
\`\`\`
|
|
122
|
+
|
|
123
|
+
2. Create your first agent:
|
|
124
|
+
|
|
125
|
+
\`\`\`bash
|
|
126
|
+
town create
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
This will launch an interactive wizard to create a new agent in the \`agents/\` directory.
|
|
130
|
+
The agent will include GUI and TUI interfaces for interaction.
|
|
131
|
+
|
|
132
|
+
## Creating Agents
|
|
133
|
+
|
|
134
|
+
Run \`town create\` inside your project directory to create new agents.
|
|
135
|
+
Each agent will be placed in \`agents/<agent-name>/\` with the following structure:
|
|
136
|
+
|
|
137
|
+
\`\`\`
|
|
138
|
+
agents/my-agent/
|
|
139
|
+
├── agent.json # Agent configuration
|
|
140
|
+
├── index.ts # Agent entry point
|
|
141
|
+
├── gui/ # Web-based GUI interface
|
|
142
|
+
└── tui/ # Terminal UI interface
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
## Running Agents
|
|
146
|
+
|
|
147
|
+
Each agent can be run in different modes:
|
|
148
|
+
|
|
149
|
+
\`\`\`bash
|
|
150
|
+
# CLI mode (stdio)
|
|
151
|
+
bun agents/<agent-name>/index.ts stdio
|
|
152
|
+
|
|
153
|
+
# HTTP server mode
|
|
154
|
+
PORT=3100 bun agents/<agent-name>/index.ts http
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
## Creating Custom Tools
|
|
158
|
+
|
|
159
|
+
Tools are defined in the \`tools/\` directory. Each tool exports:
|
|
160
|
+
|
|
161
|
+
- \`schema\`: A Zod schema factory function
|
|
162
|
+
- \`name\`: The tool name
|
|
163
|
+
- \`description\`: Tool description
|
|
164
|
+
- \`default\`: The tool implementation function
|
|
165
|
+
|
|
166
|
+
Example:
|
|
167
|
+
|
|
168
|
+
\`\`\`typescript
|
|
169
|
+
export const schema = (z: any) =>
|
|
170
|
+
z.object({
|
|
171
|
+
input: z.string(),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
export const name = "my_tool";
|
|
175
|
+
export const description = "My custom tool";
|
|
176
|
+
|
|
177
|
+
export default function myTool(input: unknown) {
|
|
178
|
+
// Implementation
|
|
179
|
+
return { result: "success" };
|
|
180
|
+
}
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
To use a tool in an agent, import it in the agent's \`index.ts\`:
|
|
184
|
+
|
|
185
|
+
\`\`\`typescript
|
|
186
|
+
import myTool, { name, description, schema } from "../../tools/my-tool.js";
|
|
187
|
+
|
|
188
|
+
const agent: AgentDefinition = {
|
|
189
|
+
// ...
|
|
190
|
+
tools: [
|
|
191
|
+
{
|
|
192
|
+
type: "direct",
|
|
193
|
+
name,
|
|
194
|
+
description,
|
|
195
|
+
fn: myTool,
|
|
196
|
+
schema: schema(z),
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
\`\`\`
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Generate .gitignore
|
|
205
|
+
*/
|
|
206
|
+
function generateProjectGitignore() {
|
|
207
|
+
return `node_modules
|
|
208
|
+
dist
|
|
209
|
+
.env
|
|
210
|
+
*.log
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Run bun install
|
|
215
|
+
*/
|
|
216
|
+
function runBunInstall(projectPath) {
|
|
217
|
+
return new Promise((resolve, reject) => {
|
|
218
|
+
const bunInstall = spawn("bun", ["install"], {
|
|
219
|
+
cwd: projectPath,
|
|
220
|
+
stdio: "pipe",
|
|
221
|
+
});
|
|
222
|
+
let stdout = "";
|
|
223
|
+
let stderr = "";
|
|
224
|
+
// Capture stdout
|
|
225
|
+
bunInstall.stdout?.on("data", (data) => {
|
|
226
|
+
stdout += data.toString();
|
|
227
|
+
});
|
|
228
|
+
// Capture stderr
|
|
229
|
+
bunInstall.stderr?.on("data", (data) => {
|
|
230
|
+
stderr += data.toString();
|
|
231
|
+
});
|
|
232
|
+
bunInstall.on("close", (code) => {
|
|
233
|
+
if (code === 0) {
|
|
234
|
+
resolve();
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Print captured output on failure
|
|
238
|
+
if (stdout) {
|
|
239
|
+
process.stdout.write(stdout);
|
|
240
|
+
}
|
|
241
|
+
if (stderr) {
|
|
242
|
+
process.stderr.write(stderr);
|
|
243
|
+
}
|
|
244
|
+
reject(new Error(`bun install failed with code ${code}`));
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
bunInstall.on("error", (error) => {
|
|
248
|
+
// Print captured output on error
|
|
249
|
+
if (stdout) {
|
|
250
|
+
process.stdout.write(stdout);
|
|
251
|
+
}
|
|
252
|
+
if (stderr) {
|
|
253
|
+
process.stderr.write(stderr);
|
|
254
|
+
}
|
|
255
|
+
reject(error);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Scaffold a new standalone project
|
|
261
|
+
*/
|
|
262
|
+
export async function scaffoldProject(options) {
|
|
263
|
+
const { path: projectPath } = options;
|
|
264
|
+
try {
|
|
265
|
+
// Create project root directory
|
|
266
|
+
await mkdir(projectPath, { recursive: true });
|
|
267
|
+
// Create agents directory (empty, to be populated by 'town create')
|
|
268
|
+
const agentsDir = join(projectPath, "agents");
|
|
269
|
+
await mkdir(agentsDir, { recursive: true });
|
|
270
|
+
// Create tools directory
|
|
271
|
+
const toolsDir = join(projectPath, "tools");
|
|
272
|
+
await mkdir(toolsDir, { recursive: true });
|
|
273
|
+
// Generate and write files
|
|
274
|
+
const files = [
|
|
275
|
+
{
|
|
276
|
+
path: "package.json",
|
|
277
|
+
content: generateProjectPackageJson(),
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
path: "tsconfig.json",
|
|
281
|
+
content: generateProjectTsConfig(),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
path: ".gitignore",
|
|
285
|
+
content: generateProjectGitignore(),
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
path: "README.md",
|
|
289
|
+
content: generateProjectReadme(),
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
path: join("tools", "add-two-numbers.ts"),
|
|
293
|
+
content: generateAddTwoNumbersTool(),
|
|
294
|
+
},
|
|
295
|
+
];
|
|
296
|
+
// Write all files
|
|
297
|
+
for (const file of files) {
|
|
298
|
+
const filePath = join(projectPath, file.path);
|
|
299
|
+
await writeFile(filePath, file.content, "utf-8");
|
|
300
|
+
}
|
|
301
|
+
// Run bun install
|
|
302
|
+
await runBunInstall(projectPath);
|
|
303
|
+
// Initialize Claude Code workspace integration
|
|
304
|
+
await initForClaudeCode(projectPath);
|
|
305
|
+
return {
|
|
306
|
+
success: true,
|
|
307
|
+
path: projectPath,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
return {
|
|
312
|
+
success: false,
|
|
313
|
+
path: projectPath,
|
|
314
|
+
error: error instanceof Error ? error.message : String(error),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Town Agent SDK
|
|
2
|
+
This project has code for agents developed using the Town agent SDK. The
|
|
3
|
+
structure of this repository is:
|
|
4
|
+
- `agents/`: code for main agent loop and configuration
|
|
5
|
+
- `tools/`: code for function-based tools that are shared by agents
|
|
6
|
+
|
|
7
|
+
## Writing custom tools
|
|
8
|
+
You may add one of the built-in tools to the agent SDK (e.g. `web_search`), or
|
|
9
|
+
you may implement your own. To do this:
|
|
10
|
+
1. add a file `tools/<tool-name>.ts` with the following structure:
|
|
11
|
+
```
|
|
12
|
+
import * as z from 'zod';
|
|
13
|
+
|
|
14
|
+
export const schema = z.object({
|
|
15
|
+
a: z.number().describe("First number"),
|
|
16
|
+
b: z.number().describe("Second number"),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const name = "add_two_numbers";
|
|
20
|
+
export const description = "Add two numbers together and return the result";
|
|
21
|
+
|
|
22
|
+
export default function addTwoNumbers(input: unknown) {
|
|
23
|
+
const { a, b } = input as { a: number; b: number };
|
|
24
|
+
return { result: a + b };
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
where
|
|
28
|
+
- `schema` is a zod schema for the tool input
|
|
29
|
+
- `name` is the name of the tool (given to the LLM)
|
|
30
|
+
- `description` describes the tool (given to the LLM)
|
|
31
|
+
- default-exported function containing the body of the function
|
|
32
|
+
|
|
33
|
+
2. Modify the agent code to import the `createTool` utility and the new tool
|
|
34
|
+
```
|
|
35
|
+
import { createTool } from '@townco/agent/utils';
|
|
36
|
+
import * as newCoolTool from '../../tools/new-cool-tool';
|
|
37
|
+
```
|
|
38
|
+
and modify the agent's tools list
|
|
39
|
+
```
|
|
40
|
+
const agent: AgentDefinition = {
|
|
41
|
+
model: "claude-sonnet-4-5-20250929",
|
|
42
|
+
systemPrompt: "You are a helpful assistant.\n",
|
|
43
|
+
tools: ["todo_write", "web_search", createTool(newCoolTool)],
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuring MCPs
|
|
48
|
+
You may configure stdio and streamable HTTP-based MCPs. Here is an example:
|
|
49
|
+
```
|
|
50
|
+
const agent: AgentDefinition = {
|
|
51
|
+
model: "claude-sonnet-4-5-20250929",
|
|
52
|
+
systemPrompt: "You are a helpful assistant.\n",
|
|
53
|
+
tools: ["todo_write", "web_search"],
|
|
54
|
+
mcps: [
|
|
55
|
+
{
|
|
56
|
+
transport: "http",
|
|
57
|
+
name: "foobar",
|
|
58
|
+
url: "http://town.com:8080/mcp",
|
|
59
|
+
headers: {
|
|
60
|
+
'Authorization': `Bearer ${process.env.MCP_API_KEY}`
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
transport: 'stdio',
|
|
65
|
+
name: 'local-mcp',
|
|
66
|
+
command: 'node',
|
|
67
|
+
args: ['path/to/local/mcp.js'],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
```
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -22,3 +22,8 @@ export declare function deleteAgent(name: string): Promise<void>;
|
|
|
22
22
|
* Ensure the agents directory exists
|
|
23
23
|
*/
|
|
24
24
|
export declare function ensureAgentsDir(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if we're inside a Town project by looking for project markers
|
|
27
|
+
* Returns the project root path if found, null otherwise
|
|
28
|
+
*/
|
|
29
|
+
export declare function isInsideTownProject(startPath?: string): Promise<string | null>;
|