@townco/agent 0.1.0

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.
Files changed (50) hide show
  1. package/README.md +15 -0
  2. package/dist/acp-server/adapter.d.ts +15 -0
  3. package/dist/acp-server/adapter.js +76 -0
  4. package/dist/acp-server/cli.d.ts +3 -0
  5. package/dist/acp-server/cli.js +11 -0
  6. package/dist/acp-server/http.d.ts +3 -0
  7. package/dist/acp-server/http.js +178 -0
  8. package/dist/acp-server/index.d.ts +2 -0
  9. package/dist/acp-server/index.js +2 -0
  10. package/dist/bin.d.ts +2 -0
  11. package/dist/bin.js +3 -0
  12. package/dist/definition/index.d.ts +30 -0
  13. package/dist/definition/index.js +33 -0
  14. package/dist/definition/mcp.d.ts +0 -0
  15. package/dist/definition/mcp.js +1 -0
  16. package/dist/definition/tools/todo.d.ts +33 -0
  17. package/dist/definition/tools/todo.js +77 -0
  18. package/dist/definition/tools/web_search.d.ts +4 -0
  19. package/dist/definition/tools/web_search.js +23 -0
  20. package/dist/example.d.ts +2 -0
  21. package/dist/example.js +20 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +20 -0
  24. package/dist/runner/agent-runner.d.ts +24 -0
  25. package/dist/runner/agent-runner.js +9 -0
  26. package/dist/runner/index.d.ts +4 -0
  27. package/dist/runner/index.js +18 -0
  28. package/dist/runner/langchain/index.d.ts +15 -0
  29. package/dist/runner/langchain/index.js +227 -0
  30. package/dist/runner/langchain/tools/todo.d.ts +33 -0
  31. package/dist/runner/langchain/tools/todo.js +77 -0
  32. package/dist/runner/langchain/tools/web_search.d.ts +4 -0
  33. package/dist/runner/langchain/tools/web_search.js +23 -0
  34. package/dist/runner/tools.d.ts +3 -0
  35. package/dist/runner/tools.js +6 -0
  36. package/dist/scaffold/bundle.d.ts +4 -0
  37. package/dist/scaffold/bundle.js +28 -0
  38. package/dist/scaffold/copy-gui.d.ts +4 -0
  39. package/dist/scaffold/copy-gui.js +210 -0
  40. package/dist/scaffold/index.d.ts +16 -0
  41. package/dist/scaffold/index.js +98 -0
  42. package/dist/storage/index.d.ts +24 -0
  43. package/dist/storage/index.js +58 -0
  44. package/dist/templates/index.d.ts +17 -0
  45. package/dist/templates/index.js +173 -0
  46. package/dist/test-script.d.ts +1 -0
  47. package/dist/test-script.js +17 -0
  48. package/dist/tsconfig.tsbuildinfo +1 -0
  49. package/package.json +72 -0
  50. package/templates/index.ts +203 -0
@@ -0,0 +1,98 @@
1
+ import { spawn } from "node:child_process";
2
+ import { chmod, mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { agentExists, ensureAgentsDir, getAgentPath } from "../storage";
5
+ import { generateAgentJson, generateBinTs, generateEnvExample, generateGitignore, generateIndexTs, generatePackageJson, generateReadme, generateTsConfig, getTemplateVars, } from "../templates";
6
+ import { bundleAgentDependencies } from "./bundle";
7
+ import { copyGuiApp } from "./copy-gui";
8
+ /**
9
+ * Scaffold a new agent package
10
+ */
11
+ export async function scaffoldAgent(options) {
12
+ const { name, definition, overwrite = false, includeGui = true } = options;
13
+ try {
14
+ // Ensure base directory exists
15
+ await ensureAgentsDir();
16
+ // Check if agent already exists
17
+ const exists = await agentExists(name);
18
+ if (exists && !overwrite) {
19
+ return {
20
+ success: false,
21
+ path: getAgentPath(name),
22
+ error: `Agent "${name}" already exists. Use overwrite option to replace it.`,
23
+ };
24
+ }
25
+ const agentPath = getAgentPath(name);
26
+ // Create the agent directory
27
+ await mkdir(agentPath, { recursive: true });
28
+ const vars = getTemplateVars(name, definition);
29
+ // Generate all template files
30
+ const files = [
31
+ { path: "package.json", content: generatePackageJson(vars) },
32
+ { path: "agent.json", content: generateAgentJson(vars) },
33
+ { path: "index.ts", content: generateIndexTs() },
34
+ { 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
+ ];
39
+ // Add .env.example if needed
40
+ const envExample = generateEnvExample(vars);
41
+ if (envExample) {
42
+ files.push({ path: ".env.example", content: envExample });
43
+ }
44
+ // Write all files
45
+ for (const file of files) {
46
+ const filePath = join(agentPath, file.path);
47
+ await writeFile(filePath, file.content, "utf-8");
48
+ // Make executable if needed
49
+ if (file.executable) {
50
+ await chmod(filePath, 0o755);
51
+ }
52
+ }
53
+ // Bundle agent dependencies (copy lib files)
54
+ await bundleAgentDependencies(agentPath);
55
+ // Copy GUI app if requested
56
+ if (includeGui) {
57
+ await copyGuiApp(agentPath);
58
+ // Run bun install in the GUI directory
59
+ const guiPath = join(agentPath, "gui");
60
+ await runBunInstall(guiPath);
61
+ }
62
+ // Run bun install in agent root
63
+ await runBunInstall(agentPath);
64
+ return {
65
+ success: true,
66
+ path: agentPath,
67
+ };
68
+ }
69
+ catch (error) {
70
+ return {
71
+ success: false,
72
+ path: getAgentPath(name),
73
+ error: error instanceof Error ? error.message : String(error),
74
+ };
75
+ }
76
+ }
77
+ /**
78
+ * Run bun install in the agent directory
79
+ */
80
+ function runBunInstall(agentPath) {
81
+ return new Promise((resolve, reject) => {
82
+ const bunInstall = spawn("bun", ["install"], {
83
+ cwd: agentPath,
84
+ stdio: "ignore",
85
+ });
86
+ bunInstall.on("close", (code) => {
87
+ if (code === 0) {
88
+ resolve();
89
+ }
90
+ else {
91
+ reject(new Error(`bun install failed with code ${code}`));
92
+ }
93
+ });
94
+ bunInstall.on("error", (error) => {
95
+ reject(error);
96
+ });
97
+ });
98
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Get the base directory where all agents are stored
3
+ */
4
+ export declare function getAgentsDir(): string;
5
+ /**
6
+ * Get the directory path for a specific agent
7
+ */
8
+ export declare function getAgentPath(name: string): string;
9
+ /**
10
+ * Check if an agent exists
11
+ */
12
+ export declare function agentExists(name: string): Promise<boolean>;
13
+ /**
14
+ * List all created agents
15
+ */
16
+ export declare function listAgents(): Promise<string[]>;
17
+ /**
18
+ * Delete an agent
19
+ */
20
+ export declare function deleteAgent(name: string): Promise<void>;
21
+ /**
22
+ * Ensure the agents directory exists
23
+ */
24
+ export declare function ensureAgentsDir(): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { mkdir, readdir, rm, stat } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const AGENTS_DIR = join(homedir(), ".config", "town", "agents");
5
+ /**
6
+ * Get the base directory where all agents are stored
7
+ */
8
+ export function getAgentsDir() {
9
+ return AGENTS_DIR;
10
+ }
11
+ /**
12
+ * Get the directory path for a specific agent
13
+ */
14
+ export function getAgentPath(name) {
15
+ return join(AGENTS_DIR, name);
16
+ }
17
+ /**
18
+ * Check if an agent exists
19
+ */
20
+ export async function agentExists(name) {
21
+ try {
22
+ const agentPath = getAgentPath(name);
23
+ const stats = await stat(agentPath);
24
+ return stats.isDirectory();
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * List all created agents
32
+ */
33
+ export async function listAgents() {
34
+ try {
35
+ await mkdir(AGENTS_DIR, { recursive: true });
36
+ const entries = await readdir(AGENTS_DIR, { withFileTypes: true });
37
+ return entries
38
+ .filter((entry) => entry.isDirectory())
39
+ .map((entry) => entry.name);
40
+ }
41
+ catch (error) {
42
+ console.error("Error listing agents:", error);
43
+ return [];
44
+ }
45
+ }
46
+ /**
47
+ * Delete an agent
48
+ */
49
+ export async function deleteAgent(name) {
50
+ const agentPath = getAgentPath(name);
51
+ await rm(agentPath, { recursive: true, force: true });
52
+ }
53
+ /**
54
+ * Ensure the agents directory exists
55
+ */
56
+ export async function ensureAgentsDir() {
57
+ await mkdir(AGENTS_DIR, { recursive: true });
58
+ }
@@ -0,0 +1,17 @@
1
+ import type { AgentDefinition } from "../definition";
2
+ export interface TemplateVars {
3
+ name: string;
4
+ model: string;
5
+ tools: string[];
6
+ systemPrompt: string | null;
7
+ hasWebSearch: boolean;
8
+ }
9
+ export declare function getTemplateVars(name: string, definition: AgentDefinition): TemplateVars;
10
+ export declare function generatePackageJson(vars: TemplateVars, useLocalDeps?: boolean): string;
11
+ export declare function generateIndexTs(): string;
12
+ export declare function generateAgentJson(vars: TemplateVars): string;
13
+ export declare function generateBinTs(): string;
14
+ export declare function generateGitignore(): string;
15
+ export declare function generateTsConfig(): string;
16
+ export declare function generateReadme(vars: TemplateVars): string;
17
+ export declare function generateEnvExample(vars: TemplateVars): string | null;
@@ -0,0 +1,173 @@
1
+ export function getTemplateVars(name, definition) {
2
+ const tools = definition.tools || [];
3
+ return {
4
+ name,
5
+ model: definition.model,
6
+ tools,
7
+ systemPrompt: definition.systemPrompt,
8
+ hasWebSearch: tools.includes("web_search"),
9
+ };
10
+ }
11
+ export function generatePackageJson(vars, useLocalDeps = true) {
12
+ // For local development, use link: protocol to reference monorepo packages
13
+ // After npm publish, users will use the published versions
14
+ const dependencies = useLocalDeps
15
+ ? {
16
+ "@townco/agent": "link:../../../../agent_hub/packages/agent",
17
+ "@townco/ui": "link:../../../../agent_hub/packages/ui",
18
+ }
19
+ : {
20
+ "@townco/agent": "^0.1.0",
21
+ "@townco/ui": "^0.1.0",
22
+ };
23
+ const pkg = {
24
+ name: `@townco/agent-${vars.name}`,
25
+ version: "0.0.1",
26
+ type: "module",
27
+ module: "index.ts",
28
+ bin: {
29
+ [`town-agent-${vars.name}`]: "./bin.ts",
30
+ },
31
+ scripts: {
32
+ build: "tsc",
33
+ check: "tsc --noEmit",
34
+ start: "bun index.ts stdio",
35
+ "start-http": "PORT=3100 bun index.ts http",
36
+ },
37
+ dependencies,
38
+ };
39
+ return JSON.stringify(pkg, null, 2);
40
+ }
41
+ export function generateIndexTs() {
42
+ return `import { readFileSync } from "node:fs";
43
+ import { join } from "node:path";
44
+ import { makeHttpTransport, makeStdioTransport } from "@townco/agent/acp-server";
45
+ import type { AgentDefinition } from "@townco/agent/definition";
46
+
47
+ // Load agent definition from JSON file
48
+ const configPath = join(import.meta.dir, "agent.json");
49
+ const agent: AgentDefinition = JSON.parse(readFileSync(configPath, "utf-8"));
50
+
51
+ const transport = process.argv[2] || "stdio";
52
+
53
+ if (transport === "http") {
54
+ makeHttpTransport(agent);
55
+ } else if (transport === "stdio") {
56
+ makeStdioTransport(agent);
57
+ } else {
58
+ console.error(\`Invalid transport: \${transport}\`);
59
+ process.exit(1);
60
+ }
61
+ `;
62
+ }
63
+ export function generateAgentJson(vars) {
64
+ const agentDef = {
65
+ model: vars.model,
66
+ systemPrompt: vars.systemPrompt,
67
+ tools: vars.tools,
68
+ };
69
+ return JSON.stringify(agentDef, null, 2);
70
+ }
71
+ export function generateBinTs() {
72
+ return `#!/usr/bin/env bun
73
+ import "./index.js";
74
+ `;
75
+ }
76
+ export function generateGitignore() {
77
+ return `node_modules
78
+ dist
79
+ .env
80
+ `;
81
+ }
82
+ export function generateTsConfig() {
83
+ const config = {
84
+ compilerOptions: {
85
+ target: "ESNext",
86
+ module: "ESNext",
87
+ moduleResolution: "bundler",
88
+ lib: ["ESNext"],
89
+ outDir: "./dist",
90
+ strict: true,
91
+ esModuleInterop: true,
92
+ skipLibCheck: true,
93
+ forceConsistentCasingInFileNames: true,
94
+ resolveJsonModule: true,
95
+ allowSyntheticDefaultImports: true,
96
+ },
97
+ include: ["**/*.ts"],
98
+ exclude: ["node_modules", "dist"],
99
+ };
100
+ return JSON.stringify(config, null, 2);
101
+ }
102
+ export function generateReadme(vars) {
103
+ const toolsList = vars.tools.length > 0 ? vars.tools.join(", ") : "None";
104
+ const envVars = vars.hasWebSearch
105
+ ? "\n- `EXA_API_KEY`: Required for web_search tool"
106
+ : "";
107
+ return `# ${vars.name}
108
+
109
+ Agent created with \`town create\`.
110
+
111
+ ## Configuration
112
+
113
+ - **Model**: ${vars.model}
114
+ - **Tools**: ${toolsList}
115
+ - **System Prompt**: ${vars.systemPrompt ? "Yes" : "No"}
116
+
117
+ ## Setup
118
+ ${envVars ? `\n### Environment Variables\n${envVars}\n` : ""}
119
+ Install dependencies:
120
+
121
+ \`\`\`bash
122
+ bun install
123
+ \`\`\`
124
+
125
+ ## Usage
126
+
127
+ ### CLI Mode (stdio)
128
+
129
+ \`\`\`bash
130
+ bun start
131
+ \`\`\`
132
+
133
+ Or using the executable:
134
+
135
+ \`\`\`bash
136
+ town-agent-${vars.name}
137
+ \`\`\`
138
+
139
+ ### Server Mode (HTTP)
140
+
141
+ \`\`\`bash
142
+ bun run start-http
143
+ \`\`\`
144
+
145
+ The server will start on port 3100 with the following endpoints:
146
+
147
+ - \`/health\`: Health check
148
+ - \`/rpc\`: Request-response messages
149
+ - \`/events\`: SSE stream for real-time updates
150
+
151
+ ## Development
152
+
153
+ Type check:
154
+
155
+ \`\`\`bash
156
+ bun run check
157
+ \`\`\`
158
+
159
+ Build:
160
+
161
+ \`\`\`bash
162
+ bun run build
163
+ \`\`\`
164
+ `;
165
+ }
166
+ export function generateEnvExample(vars) {
167
+ if (!vars.hasWebSearch) {
168
+ return null;
169
+ }
170
+ return `# Required for web_search tool
171
+ EXA_API_KEY=your_exa_api_key_here
172
+ `;
173
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { LangchainAgent } from "./runner/langchain";
2
+ const agent = new LangchainAgent({
3
+ model: "claude-sonnet-4-5-20250929",
4
+ systemPrompt: "You are a helpful assistant.",
5
+ tools: ["todo_write"],
6
+ });
7
+ for await (const event of agent.invoke({
8
+ prompt: [
9
+ {
10
+ type: "text",
11
+ text: "Whats the weather in Tokyo?",
12
+ },
13
+ ],
14
+ sessionId: "test-session",
15
+ })) {
16
+ console.log(event);
17
+ }