@tellet/create 0.9.0 → 0.12.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.
- package/README.md +151 -135
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +54 -0
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +156 -0
- package/dist/commands/build.d.ts +1 -0
- package/dist/commands/build.js +14 -0
- package/dist/commands/deploy.d.ts +1 -0
- package/dist/commands/deploy.js +117 -0
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.js +15 -0
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +4 -0
- package/dist/commands/shared.d.ts +69 -0
- package/dist/commands/shared.js +73 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +53 -0
- package/dist/index.js +8 -336
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +25 -0
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +264 -0
- package/dist/scaffold/project.js +1 -1
- package/dist/scaffold-flow.d.ts +1 -0
- package/dist/scaffold-flow.js +402 -0
- package/dist/setup/deps.d.ts +4 -0
- package/dist/setup/deps.js +24 -0
- package/dist/setup/env.d.ts +12 -0
- package/dist/setup/env.js +26 -0
- package/dist/setup/supabase.d.ts +36 -0
- package/dist/setup/supabase.js +207 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,337 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
console.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
console.log();
|
|
11
|
-
p.intro(chalk.bgHex("#8b5cf6").white(" @tellet/create "));
|
|
12
|
-
// Step 0: New or Connect
|
|
13
|
-
const modeChoice = await p.select({
|
|
14
|
-
message: "What would you like to do?",
|
|
15
|
-
options: [
|
|
16
|
-
{
|
|
17
|
-
value: "new",
|
|
18
|
-
label: "New",
|
|
19
|
-
hint: "Build a new AI company from scratch",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
value: "connect",
|
|
23
|
-
label: "Connect",
|
|
24
|
-
hint: "Add AI agents to your existing business",
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
});
|
|
28
|
-
if (p.isCancel(modeChoice)) {
|
|
29
|
-
p.cancel("Setup cancelled.");
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
const mode = modeChoice;
|
|
33
|
-
// Step 1: Company info
|
|
34
|
-
const company = await p.group({
|
|
35
|
-
name: () => p.text({
|
|
36
|
-
message: "What's your company name?",
|
|
37
|
-
placeholder: "Sunny Coffee",
|
|
38
|
-
validate: (v) => (!v ? "Company name is required" : undefined),
|
|
39
|
-
}),
|
|
40
|
-
description: () => p.text({
|
|
41
|
-
message: "Describe your business (what you do, who your customers are, what help you need):",
|
|
42
|
-
placeholder: "We sell specialty coffee subscriptions. Customers are coffee enthusiasts aged 25-45...",
|
|
43
|
-
validate: (v) => !v || v.length < 20
|
|
44
|
-
? "Please provide at least a few sentences"
|
|
45
|
-
: undefined,
|
|
46
|
-
}),
|
|
47
|
-
}, {
|
|
48
|
-
onCancel: () => {
|
|
49
|
-
p.cancel("Setup cancelled.");
|
|
50
|
-
process.exit(0);
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
// Step 2: Deployment tier
|
|
54
|
-
const tierChoice = await p.select({
|
|
55
|
-
message: "Deployment mode:",
|
|
56
|
-
options: [
|
|
57
|
-
{
|
|
58
|
-
value: "quickstart",
|
|
59
|
-
label: "Quick Start",
|
|
60
|
-
hint: "Vercel + Supabase — free, instant deploy",
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
value: "cloud",
|
|
64
|
-
label: "Cloud",
|
|
65
|
-
hint: "Railway / Render / Fly.io — Docker, $5-20/mo",
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
value: "enterprise",
|
|
69
|
-
label: "Enterprise",
|
|
70
|
-
hint: "AWS CDK — auto-provision Lambda + RDS + CloudFront",
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
});
|
|
74
|
-
if (p.isCancel(tierChoice)) {
|
|
75
|
-
p.cancel("Setup cancelled.");
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
const tier = tierChoice;
|
|
79
|
-
// Step 3: Choose AI provider
|
|
80
|
-
const providerChoice = await p.select({
|
|
81
|
-
message: "Choose your AI provider:",
|
|
82
|
-
options: [
|
|
83
|
-
{
|
|
84
|
-
value: "anthropic",
|
|
85
|
-
label: "Anthropic (Claude)",
|
|
86
|
-
hint: "recommended — also powers the Orchestrator",
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
value: "openai",
|
|
90
|
-
label: "OpenAI (GPT)",
|
|
91
|
-
hint: "uses gpt-4.1 for generation",
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
});
|
|
95
|
-
if (p.isCancel(providerChoice)) {
|
|
96
|
-
p.cancel("Setup cancelled.");
|
|
97
|
-
process.exit(0);
|
|
98
|
-
}
|
|
99
|
-
const provider = providerChoice;
|
|
100
|
-
// Step 4: Get API key
|
|
101
|
-
const envKey = provider === "anthropic"
|
|
102
|
-
? process.env.ANTHROPIC_API_KEY
|
|
103
|
-
: process.env.OPENAI_API_KEY;
|
|
104
|
-
const envName = provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
105
|
-
const keyPrefix = provider === "anthropic" ? "sk-ant-" : "sk-";
|
|
106
|
-
const keyPlaceholder = provider === "anthropic" ? "sk-ant-..." : "sk-...";
|
|
107
|
-
let apiKey = envKey || "";
|
|
108
|
-
if (!apiKey) {
|
|
109
|
-
const keyInput = await p.text({
|
|
110
|
-
message: `Your ${provider === "anthropic" ? "Anthropic" : "OpenAI"} API key:`,
|
|
111
|
-
placeholder: keyPlaceholder,
|
|
112
|
-
validate: (v) => !v || !v.startsWith(keyPrefix)
|
|
113
|
-
? `Please enter a valid key (starts with ${keyPrefix})`
|
|
114
|
-
: undefined,
|
|
115
|
-
});
|
|
116
|
-
if (p.isCancel(keyInput)) {
|
|
117
|
-
p.cancel("Setup cancelled.");
|
|
118
|
-
process.exit(0);
|
|
119
|
-
}
|
|
120
|
-
apiKey = keyInput;
|
|
121
|
-
process.env[envName] = apiKey;
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
p.log.info(chalk.dim(`Using ${envName} from environment.`));
|
|
125
|
-
}
|
|
126
|
-
// Orchestrator always needs Anthropic
|
|
127
|
-
let anthropicKey = "";
|
|
128
|
-
if (provider === "anthropic") {
|
|
129
|
-
anthropicKey = apiKey;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const existingKey = process.env.ANTHROPIC_API_KEY || "";
|
|
133
|
-
if (existingKey) {
|
|
134
|
-
anthropicKey = existingKey;
|
|
135
|
-
p.log.info(chalk.dim("Using ANTHROPIC_API_KEY for Orchestrator."));
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
p.log.info(chalk.dim("The Orchestrator requires an Anthropic API key (Claude tool use)."));
|
|
139
|
-
const orchKeyInput = await p.text({
|
|
140
|
-
message: "Anthropic API key for Orchestrator:",
|
|
141
|
-
placeholder: "sk-ant-...",
|
|
142
|
-
validate: (v) => !v || !v.startsWith("sk-ant-")
|
|
143
|
-
? "Please enter a valid Anthropic key (starts with sk-ant-)"
|
|
144
|
-
: undefined,
|
|
145
|
-
});
|
|
146
|
-
if (p.isCancel(orchKeyInput)) {
|
|
147
|
-
p.cancel("Setup cancelled.");
|
|
148
|
-
process.exit(0);
|
|
149
|
-
}
|
|
150
|
-
anthropicKey = orchKeyInput;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// Step 5: Generate agents + site content
|
|
154
|
-
const s = p.spinner();
|
|
155
|
-
s.start("Generating your AI team and website...");
|
|
156
|
-
let agents;
|
|
157
|
-
try {
|
|
158
|
-
agents = await generateAgents(company.name, company.description, provider);
|
|
159
|
-
s.stop("Your AI team is ready!");
|
|
160
|
-
}
|
|
161
|
-
catch (err) {
|
|
162
|
-
s.stop("Failed to generate agents.");
|
|
163
|
-
p.log.error(err instanceof Error ? err.message : "Check your API key");
|
|
164
|
-
p.cancel("Setup failed.");
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
// Step 6: Show agents + site preview
|
|
168
|
-
p.log.info(chalk.bold("Meet your team:"));
|
|
169
|
-
console.log();
|
|
170
|
-
for (const agent of agents.agents) {
|
|
171
|
-
console.log(` ${chalk.hex("#8b5cf6").bold(agent.name)} ${chalk.dim(`(${agent.role})`)}`);
|
|
172
|
-
console.log(` ${chalk.dim(agent.description)}`);
|
|
173
|
-
console.log();
|
|
174
|
-
}
|
|
175
|
-
if (agents.site && mode === "new") {
|
|
176
|
-
p.log.info(chalk.bold("Your website:"));
|
|
177
|
-
console.log(` ${chalk.hex("#f59e0b")(agents.site.tagline)}`);
|
|
178
|
-
console.log(` ${chalk.dim(agents.site.subtitle)}`);
|
|
179
|
-
console.log();
|
|
180
|
-
}
|
|
181
|
-
const confirm = await p.confirm({
|
|
182
|
-
message: "Looks good?",
|
|
183
|
-
initialValue: true,
|
|
184
|
-
});
|
|
185
|
-
if (p.isCancel(confirm) || !confirm) {
|
|
186
|
-
p.cancel("Setup cancelled.");
|
|
187
|
-
process.exit(0);
|
|
188
|
-
}
|
|
189
|
-
// Step 7a: Connect mode — website URL for KB
|
|
190
|
-
let websiteUrl = "";
|
|
191
|
-
if (mode === "connect") {
|
|
192
|
-
const urlInput = await p.text({
|
|
193
|
-
message: "Your existing website URL (for Knowledge Base crawling):",
|
|
194
|
-
placeholder: "https://my-business.com",
|
|
195
|
-
});
|
|
196
|
-
if (!p.isCancel(urlInput) && urlInput) {
|
|
197
|
-
websiteUrl = urlInput;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// Step 7b: Infrastructure setup (tier-dependent)
|
|
201
|
-
let supabaseUrl = "";
|
|
202
|
-
let supabaseKey = "";
|
|
203
|
-
if (tier === "quickstart") {
|
|
204
|
-
p.log.info(`${chalk.bold("Supabase setup")} ${chalk.dim("(free tier works fine)")}\n` +
|
|
205
|
-
` ${chalk.dim("1.")} Create a project at ${chalk.cyan("https://supabase.com/dashboard/new")}\n` +
|
|
206
|
-
` ${chalk.dim("2.")} Go to Settings → API to find your URL and keys`);
|
|
207
|
-
const supabase = await p.group({
|
|
208
|
-
url: () => p.text({
|
|
209
|
-
message: "Your Supabase project URL:",
|
|
210
|
-
placeholder: "https://xxx.supabase.co",
|
|
211
|
-
validate: (v) => !v || !v.includes("supabase")
|
|
212
|
-
? "Please enter a valid Supabase URL"
|
|
213
|
-
: undefined,
|
|
214
|
-
}),
|
|
215
|
-
key: () => p.text({
|
|
216
|
-
message: "Your Supabase publishable key (anon/public):",
|
|
217
|
-
placeholder: "sb_publishable_...",
|
|
218
|
-
validate: (v) => (!v ? "Key is required" : undefined),
|
|
219
|
-
}),
|
|
220
|
-
}, {
|
|
221
|
-
onCancel: () => {
|
|
222
|
-
p.cancel("Setup cancelled.");
|
|
223
|
-
process.exit(0);
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
supabaseUrl = supabase.url;
|
|
227
|
-
supabaseKey = supabase.key;
|
|
228
|
-
}
|
|
229
|
-
else if (tier === "cloud") {
|
|
230
|
-
p.log.info(chalk.dim("Cloud mode: PostgreSQL runs in Docker. No Supabase needed."));
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
p.log.info(`${chalk.bold("AWS Enterprise setup")}\n` +
|
|
234
|
-
` ${chalk.dim("Requires:")} AWS CLI configured + CDK bootstrapped\n` +
|
|
235
|
-
` ${chalk.dim("Cost:")} ~$5-15/mo (Lambda + RDS free tier)`);
|
|
236
|
-
}
|
|
237
|
-
// Step 8: Scaffold project
|
|
238
|
-
s.start("Creating your project...");
|
|
239
|
-
try {
|
|
240
|
-
const projectDir = await scaffoldProject({
|
|
241
|
-
company: {
|
|
242
|
-
name: company.name,
|
|
243
|
-
description: company.description,
|
|
244
|
-
industry: agents.industry,
|
|
245
|
-
},
|
|
246
|
-
agents: agents.agents,
|
|
247
|
-
site: agents.site,
|
|
248
|
-
provider,
|
|
249
|
-
tier,
|
|
250
|
-
mode,
|
|
251
|
-
websiteUrl,
|
|
252
|
-
infra: {
|
|
253
|
-
anthropicKey,
|
|
254
|
-
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
255
|
-
supabaseUrl,
|
|
256
|
-
supabaseKey,
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
s.stop("Project created!");
|
|
260
|
-
const slug = company.name
|
|
261
|
-
.toLowerCase()
|
|
262
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
263
|
-
.replace(/^-|-$/g, "");
|
|
264
|
-
const widgetSnippet = mode === "connect"
|
|
265
|
-
? [
|
|
266
|
-
``,
|
|
267
|
-
`${chalk.bold("Embed in your existing site:")}`,
|
|
268
|
-
`${chalk.cyan('<script src="https://YOUR_URL/widget.js"')}`,
|
|
269
|
-
`${chalk.cyan(' data-agent="' + agents.agents[0].id + '"')}`,
|
|
270
|
-
`${chalk.cyan(' data-api="https://YOUR_URL"></script>')}`,
|
|
271
|
-
]
|
|
272
|
-
: [];
|
|
273
|
-
if (tier === "quickstart") {
|
|
274
|
-
p.note([
|
|
275
|
-
`cd ${slug}`,
|
|
276
|
-
`npm install`,
|
|
277
|
-
`npm run dev ${chalk.dim("→ http://localhost:3000")}`,
|
|
278
|
-
``,
|
|
279
|
-
`Dashboard: ${chalk.dim("/dashboard")}`,
|
|
280
|
-
`Orchestrator: ${chalk.dim("floating button in dashboard")}`,
|
|
281
|
-
`Agents: ${chalk.dim(`${agents.agents.length} active`)}`,
|
|
282
|
-
...widgetSnippet,
|
|
283
|
-
].join("\n"), mode === "connect" ? "Your AI agents are ready" : "Your AI company is ready");
|
|
284
|
-
p.outro(`Deploy: ${chalk.cyan("vercel deploy")}`);
|
|
285
|
-
}
|
|
286
|
-
else if (tier === "cloud") {
|
|
287
|
-
p.note([
|
|
288
|
-
`cd ${slug}`,
|
|
289
|
-
``,
|
|
290
|
-
`${chalk.bold("Local development:")}`,
|
|
291
|
-
`docker compose up ${chalk.dim("→ http://localhost:3000")}`,
|
|
292
|
-
``,
|
|
293
|
-
`${chalk.bold("Deploy to Railway:")}`,
|
|
294
|
-
`railway login`,
|
|
295
|
-
`railway init`,
|
|
296
|
-
`railway add --plugin postgresql`,
|
|
297
|
-
`railway up`,
|
|
298
|
-
``,
|
|
299
|
-
`Dashboard: ${chalk.dim("/dashboard")}`,
|
|
300
|
-
`Orchestrator: ${chalk.dim("floating button in dashboard")}`,
|
|
301
|
-
`Agents: ${chalk.dim(`${agents.agents.length} active`)}`,
|
|
302
|
-
].join("\n"), "Your AI company is ready");
|
|
303
|
-
p.outro(`Or deploy to ${chalk.cyan("Render")}, ${chalk.cyan("Fly.io")}, or any Docker host`);
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
p.note([
|
|
307
|
-
`cd ${slug}`,
|
|
308
|
-
``,
|
|
309
|
-
`${chalk.bold("Local development:")}`,
|
|
310
|
-
`docker compose up ${chalk.dim("→ http://localhost:3000")}`,
|
|
311
|
-
``,
|
|
312
|
-
`${chalk.bold("Deploy to AWS:")}`,
|
|
313
|
-
`cd infra`,
|
|
314
|
-
`npm install`,
|
|
315
|
-
`npx cdk bootstrap ${chalk.dim("(first time only)")}`,
|
|
316
|
-
`npx cdk deploy ${chalk.dim("→ CloudFront URL in output")}`,
|
|
317
|
-
``,
|
|
318
|
-
`${chalk.bold("Update API keys:")}`,
|
|
319
|
-
`aws secretsmanager put-secret-value \\`,
|
|
320
|
-
` --secret-id ${slug}/api-keys \\`,
|
|
321
|
-
` --secret-string '{"ANTHROPIC_API_KEY":"sk-ant-..."}'`,
|
|
322
|
-
``,
|
|
323
|
-
`Dashboard: ${chalk.dim("/dashboard")}`,
|
|
324
|
-
`Orchestrator: ${chalk.dim("floating button in dashboard")}`,
|
|
325
|
-
`Agents: ${chalk.dim(`${agents.agents.length} active`)}`,
|
|
326
|
-
`Est. cost: ${chalk.dim("$5-15/mo")}`,
|
|
327
|
-
].join("\n"), "Your AI company is ready (AWS)");
|
|
328
|
-
p.outro(`Run ${chalk.cyan("cd infra && npx cdk deploy")} to provision AWS infrastructure`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
catch (err) {
|
|
332
|
-
s.stop("Failed to create project.");
|
|
333
|
-
p.log.error(err instanceof Error ? err.message : String(err));
|
|
334
|
-
process.exit(1);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
main().catch(console.error);
|
|
2
|
+
// Entry point for both `npx @tellet/create` and `tellet <command>`
|
|
3
|
+
// Delegates to cli.ts which handles routing.
|
|
4
|
+
import { run } from "./cli.js";
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
run(args).catch((err) => {
|
|
7
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
8
|
+
process.exit(1);
|
|
9
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startMcpServer(): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { registerTools } from "./tools.js";
|
|
7
|
+
async function getVersion() {
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const pkg = await fs.readJSON(path.resolve(__dirname, "..", "..", "package.json"));
|
|
10
|
+
return pkg.version;
|
|
11
|
+
}
|
|
12
|
+
export async function startMcpServer() {
|
|
13
|
+
const version = await getVersion();
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: "tellet",
|
|
16
|
+
version,
|
|
17
|
+
}, {
|
|
18
|
+
instructions: "Tellet MCP server — manage AI agent projects. " +
|
|
19
|
+
"Use these tools inside a tellet project directory (one containing tellet.json). " +
|
|
20
|
+
"Tools: project_status, agent_list, agent_add, agent_remove, config_read, dev_start, deploy_info.",
|
|
21
|
+
});
|
|
22
|
+
registerTools(server);
|
|
23
|
+
const transport = new StdioServerTransport();
|
|
24
|
+
await server.connect(transport);
|
|
25
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import { loadConfig, saveConfig, nameToId, generateAgentFile, updateAgentRegistry, } from "../commands/shared.js";
|
|
6
|
+
const VALID_ROLES = [
|
|
7
|
+
"customer_support",
|
|
8
|
+
"sales",
|
|
9
|
+
"marketing",
|
|
10
|
+
"operations",
|
|
11
|
+
"analytics",
|
|
12
|
+
"development",
|
|
13
|
+
];
|
|
14
|
+
export function registerTools(server) {
|
|
15
|
+
// ─── project_status ───────────────────────────────────
|
|
16
|
+
server.registerTool("project_status", {
|
|
17
|
+
title: "Project Status",
|
|
18
|
+
description: "Show tellet project status: company info, agents, channels, LLM config, and environment health.",
|
|
19
|
+
inputSchema: z.object({}),
|
|
20
|
+
}, async () => {
|
|
21
|
+
const config = await tryLoadConfig();
|
|
22
|
+
if (!config)
|
|
23
|
+
return configError();
|
|
24
|
+
const envPath = path.resolve(process.cwd(), ".env.local");
|
|
25
|
+
const envExists = await fs.pathExists(envPath);
|
|
26
|
+
const nodeModules = await fs.pathExists(path.resolve(process.cwd(), "node_modules"));
|
|
27
|
+
const enabledChannels = Object.entries(config.channels)
|
|
28
|
+
.filter(([, v]) => v.enabled)
|
|
29
|
+
.map(([k]) => k);
|
|
30
|
+
const text = [
|
|
31
|
+
`Company: ${config.company.name}`,
|
|
32
|
+
`Industry: ${config.company.industry}`,
|
|
33
|
+
`Mode: ${config.mode}`,
|
|
34
|
+
`Provider: ${config.llm.provider} (${config.llm.defaultModel})`,
|
|
35
|
+
`Agents: ${config.agents.length} (${config.agents.map((a) => a.name).join(", ")})`,
|
|
36
|
+
`Channels: ${enabledChannels.join(", ") || "none"}`,
|
|
37
|
+
`Storage: ${config.storage}`,
|
|
38
|
+
`.env.local: ${envExists ? "found" : "MISSING"}`,
|
|
39
|
+
`node_modules: ${nodeModules ? "installed" : "NOT INSTALLED"}`,
|
|
40
|
+
].join("\n");
|
|
41
|
+
return { content: [{ type: "text", text }] };
|
|
42
|
+
});
|
|
43
|
+
// ─── agent_list ───────────────────────────────────────
|
|
44
|
+
server.registerTool("agent_list", {
|
|
45
|
+
title: "List Agents",
|
|
46
|
+
description: "List all agents in the tellet project with their roles, models, channels, and tools.",
|
|
47
|
+
inputSchema: z.object({}),
|
|
48
|
+
}, async () => {
|
|
49
|
+
const config = await tryLoadConfig();
|
|
50
|
+
if (!config)
|
|
51
|
+
return configError();
|
|
52
|
+
if (config.agents.length === 0) {
|
|
53
|
+
return { content: [{ type: "text", text: "No agents configured." }] };
|
|
54
|
+
}
|
|
55
|
+
const lines = config.agents.map((a) => [
|
|
56
|
+
`- ${a.name} (${a.id})`,
|
|
57
|
+
` Role: ${a.role}`,
|
|
58
|
+
` Model: ${a.model}`,
|
|
59
|
+
` Channels: ${a.channels.join(", ")}`,
|
|
60
|
+
` Tools: ${a.tools.length > 0 ? a.tools.join(", ") : "none"}`,
|
|
61
|
+
].join("\n"));
|
|
62
|
+
return { content: [{ type: "text", text: lines.join("\n\n") }] };
|
|
63
|
+
});
|
|
64
|
+
// ─── agent_add ────────────────────────────────────────
|
|
65
|
+
server.registerTool("agent_add", {
|
|
66
|
+
title: "Add Agent",
|
|
67
|
+
description: "Add a new AI agent to the tellet project. Updates tellet.json, creates the agent file, and updates the registry.",
|
|
68
|
+
inputSchema: z.object({
|
|
69
|
+
name: z.string().describe("Agent display name (e.g. Luna, Atlas)"),
|
|
70
|
+
role: z
|
|
71
|
+
.enum(VALID_ROLES)
|
|
72
|
+
.describe("Agent role"),
|
|
73
|
+
description: z
|
|
74
|
+
.string()
|
|
75
|
+
.describe("What this agent does (used in system prompt)"),
|
|
76
|
+
}),
|
|
77
|
+
}, async ({ name, role, description }) => {
|
|
78
|
+
const config = await tryLoadConfig();
|
|
79
|
+
if (!config)
|
|
80
|
+
return configError();
|
|
81
|
+
const id = nameToId(name);
|
|
82
|
+
if (!id) {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: "Error: Name must contain at least one letter or number." }],
|
|
85
|
+
isError: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (config.agents.some((a) => a.id === id)) {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: `Error: Agent "${id}" already exists.` }],
|
|
91
|
+
isError: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const model = config.llm.defaultModel;
|
|
95
|
+
const systemPrompt = `You are ${name}, the ${role.replace(/_/g, " ")} agent for ${config.company.name}. ${description}`;
|
|
96
|
+
config.agents.push({
|
|
97
|
+
id,
|
|
98
|
+
name,
|
|
99
|
+
role,
|
|
100
|
+
model,
|
|
101
|
+
channels: ["web_chat"],
|
|
102
|
+
tools: role === "customer_support" ? ["search_knowledge"] : [],
|
|
103
|
+
});
|
|
104
|
+
await saveConfig(config);
|
|
105
|
+
const agentsDir = path.resolve(process.cwd(), "agents");
|
|
106
|
+
await fs.mkdirp(agentsDir);
|
|
107
|
+
await fs.writeFile(path.join(agentsDir, `${id}.ts`), generateAgentFile({ id, name, role, model, systemPrompt }));
|
|
108
|
+
await updateAgentRegistry(config);
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `Added agent "${name}" (${role})\nID: ${id}\nFile: agents/${id}.ts\nTotal agents: ${config.agents.length}`,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
// ─── agent_remove ─────────────────────────────────────
|
|
119
|
+
server.registerTool("agent_remove", {
|
|
120
|
+
title: "Remove Agent",
|
|
121
|
+
description: "Remove an agent from the tellet project by ID.",
|
|
122
|
+
inputSchema: z.object({
|
|
123
|
+
id: z.string().describe("Agent ID to remove"),
|
|
124
|
+
}),
|
|
125
|
+
}, async ({ id }) => {
|
|
126
|
+
const config = await tryLoadConfig();
|
|
127
|
+
if (!config)
|
|
128
|
+
return configError();
|
|
129
|
+
const agent = config.agents.find((a) => a.id === id);
|
|
130
|
+
if (!agent) {
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: "text",
|
|
135
|
+
text: `Error: Agent "${id}" not found. Available: ${config.agents.map((a) => a.id).join(", ")}`,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
config.agents = config.agents.filter((a) => a.id !== id);
|
|
142
|
+
await saveConfig(config);
|
|
143
|
+
const agentPath = path.resolve(process.cwd(), "agents", `${id}.ts`);
|
|
144
|
+
if (await fs.pathExists(agentPath)) {
|
|
145
|
+
await fs.remove(agentPath);
|
|
146
|
+
}
|
|
147
|
+
await updateAgentRegistry(config);
|
|
148
|
+
return {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: `Removed agent "${agent.name}" (${id})\nRemaining agents: ${config.agents.length}`,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
// ─── config_read ──────────────────────────────────────
|
|
158
|
+
server.registerTool("config_read", {
|
|
159
|
+
title: "Read Config",
|
|
160
|
+
description: "Read the full tellet.json configuration file.",
|
|
161
|
+
inputSchema: z.object({}),
|
|
162
|
+
}, async () => {
|
|
163
|
+
const configPath = path.resolve(process.cwd(), "tellet.json");
|
|
164
|
+
if (!(await fs.pathExists(configPath))) {
|
|
165
|
+
return configError();
|
|
166
|
+
}
|
|
167
|
+
const raw = await fs.readFile(configPath, "utf-8");
|
|
168
|
+
return { content: [{ type: "text", text: raw }] };
|
|
169
|
+
});
|
|
170
|
+
// ─── dev_start ────────────────────────────────────────
|
|
171
|
+
server.registerTool("dev_start", {
|
|
172
|
+
title: "Start Dev Server",
|
|
173
|
+
description: "Start the Next.js development server (next dev). Launches in the background — success means the process was spawned, not that the server is healthy.",
|
|
174
|
+
inputSchema: z.object({
|
|
175
|
+
port: z
|
|
176
|
+
.number()
|
|
177
|
+
.int()
|
|
178
|
+
.min(1024)
|
|
179
|
+
.max(65535)
|
|
180
|
+
.optional()
|
|
181
|
+
.describe("Port number (default: 3000)"),
|
|
182
|
+
}),
|
|
183
|
+
}, async ({ port }) => {
|
|
184
|
+
const config = await tryLoadConfig();
|
|
185
|
+
if (!config)
|
|
186
|
+
return configError();
|
|
187
|
+
const nodeModules = await fs.pathExists(path.resolve(process.cwd(), "node_modules"));
|
|
188
|
+
if (!nodeModules) {
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: "Error: node_modules not found. Run npm install first.",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const args = ["next", "dev"];
|
|
200
|
+
if (port)
|
|
201
|
+
args.push("--port", String(port));
|
|
202
|
+
const child = spawn("npx", args, {
|
|
203
|
+
cwd: process.cwd(),
|
|
204
|
+
detached: true,
|
|
205
|
+
stdio: "ignore",
|
|
206
|
+
});
|
|
207
|
+
child.unref();
|
|
208
|
+
const actualPort = port || 3000;
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: `Dev server spawned for ${config.company.name} on http://localhost:${actualPort}\nPID: ${child.pid}\nNote: Check the port is not already in use.`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
// ─── deploy_info ──────────────────────────────────────
|
|
219
|
+
server.registerTool("deploy_info", {
|
|
220
|
+
title: "Deploy Info",
|
|
221
|
+
description: "Show deployment options and commands based on the project structure.",
|
|
222
|
+
inputSchema: z.object({}),
|
|
223
|
+
}, async () => {
|
|
224
|
+
const config = await tryLoadConfig();
|
|
225
|
+
if (!config)
|
|
226
|
+
return configError();
|
|
227
|
+
const cwd = process.cwd();
|
|
228
|
+
const hasInfra = await fs.pathExists(path.join(cwd, "infra"));
|
|
229
|
+
const hasDocker = await fs.pathExists(path.join(cwd, "docker-compose.yml"));
|
|
230
|
+
const hasRailway = await fs.pathExists(path.join(cwd, "railway.toml"));
|
|
231
|
+
const options = [];
|
|
232
|
+
options.push("## Vercel (recommended for Quick Start)", "```", "npx vercel", "```", "");
|
|
233
|
+
if (hasRailway || hasDocker) {
|
|
234
|
+
options.push("## Railway", "```", "railway login", "railway init", "railway add --plugin postgresql", "railway up", "```", "");
|
|
235
|
+
}
|
|
236
|
+
if (hasDocker) {
|
|
237
|
+
options.push("## Docker", "```", "docker build -t tellet .", "docker compose up -d", "```", "Works with Render, Fly.io, DigitalOcean, or any Docker host.", "");
|
|
238
|
+
}
|
|
239
|
+
if (hasInfra) {
|
|
240
|
+
options.push("## AWS CDK", "```", "cd infra && npm install", "npx cdk bootstrap # first time only", "npx cdk deploy", "```", "");
|
|
241
|
+
}
|
|
242
|
+
return { content: [{ type: "text", text: options.join("\n") }] };
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// ─── Helpers ──────────────────────────────────────────
|
|
246
|
+
async function tryLoadConfig() {
|
|
247
|
+
try {
|
|
248
|
+
return await loadConfig();
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function configError() {
|
|
255
|
+
return {
|
|
256
|
+
content: [
|
|
257
|
+
{
|
|
258
|
+
type: "text",
|
|
259
|
+
text: "No tellet.json found in current directory. Make sure the MCP server is running from inside a tellet project.",
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
isError: true,
|
|
263
|
+
};
|
|
264
|
+
}
|
package/dist/scaffold/project.js
CHANGED
|
@@ -181,7 +181,7 @@ export default defineAgent({
|
|
|
181
181
|
}
|
|
182
182
|
function generateMigration(agents, company) {
|
|
183
183
|
const seedValues = agents
|
|
184
|
-
.map((a) => `(
|
|
184
|
+
.map((a) => `(${pgEscape(a.id)}, ${pgEscape(a.name)}, ${pgEscape(a.role)}, ${pgEscape(a.systemPrompt)}, ${pgEscape(a.model)}, 'active', '{}')`)
|
|
185
185
|
.join(",\n");
|
|
186
186
|
return `-- tellet schema
|
|
187
187
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): Promise<void>;
|