@tellet/create 0.10.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 +51 -1
- 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 -421
- 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-flow.d.ts +1 -0
- package/dist/scaffold-flow.js +402 -0
- package/package.json +5 -3
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): Promise<void>;
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { generateAgents } from "./ai/generate.js";
|
|
4
|
+
import { scaffoldProject } from "./scaffold/project.js";
|
|
5
|
+
import { setupSupabase } from "./setup/supabase.js";
|
|
6
|
+
import { installDependencies } from "./setup/deps.js";
|
|
7
|
+
export async function main() {
|
|
8
|
+
console.clear();
|
|
9
|
+
console.log();
|
|
10
|
+
console.log(chalk.bold(` ${chalk.white("tel")}${chalk.yellow("let")} ${chalk.dim("— Build Your AI Company")}`));
|
|
11
|
+
console.log();
|
|
12
|
+
p.intro(chalk.bgHex("#8b5cf6").white(" @tellet/create "));
|
|
13
|
+
// Step 0: New or Connect
|
|
14
|
+
const modeChoice = await p.select({
|
|
15
|
+
message: "What would you like to do?",
|
|
16
|
+
options: [
|
|
17
|
+
{
|
|
18
|
+
value: "new",
|
|
19
|
+
label: "New",
|
|
20
|
+
hint: "Build a new AI company from scratch",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "connect",
|
|
24
|
+
label: "Connect",
|
|
25
|
+
hint: "Add AI agents to your existing business",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
if (p.isCancel(modeChoice)) {
|
|
30
|
+
p.cancel("Setup cancelled.");
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
const mode = modeChoice;
|
|
34
|
+
// Step 1: Company info
|
|
35
|
+
const company = await p.group({
|
|
36
|
+
name: () => p.text({
|
|
37
|
+
message: "What's your company name?",
|
|
38
|
+
placeholder: "Sunny Coffee",
|
|
39
|
+
validate: (v) => (!v ? "Company name is required" : undefined),
|
|
40
|
+
}),
|
|
41
|
+
description: () => p.text({
|
|
42
|
+
message: "Describe your business (what you do, who your customers are, what help you need):",
|
|
43
|
+
placeholder: "We sell specialty coffee subscriptions. Customers are coffee enthusiasts aged 25-45...",
|
|
44
|
+
validate: (v) => !v || v.length < 20
|
|
45
|
+
? "Please provide at least a few sentences"
|
|
46
|
+
: undefined,
|
|
47
|
+
}),
|
|
48
|
+
}, {
|
|
49
|
+
onCancel: () => {
|
|
50
|
+
p.cancel("Setup cancelled.");
|
|
51
|
+
process.exit(0);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
// Step 2: Deployment tier
|
|
55
|
+
const tierChoice = await p.select({
|
|
56
|
+
message: "Deployment mode:",
|
|
57
|
+
options: [
|
|
58
|
+
{
|
|
59
|
+
value: "quickstart",
|
|
60
|
+
label: "Quick Start",
|
|
61
|
+
hint: "Vercel + Supabase — free, instant deploy",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: "cloud",
|
|
65
|
+
label: "Cloud",
|
|
66
|
+
hint: "Railway / Render / Fly.io — Docker, $5-20/mo",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
value: "enterprise",
|
|
70
|
+
label: "Enterprise",
|
|
71
|
+
hint: "AWS CDK — auto-provision Lambda + RDS + CloudFront",
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
if (p.isCancel(tierChoice)) {
|
|
76
|
+
p.cancel("Setup cancelled.");
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
const tier = tierChoice;
|
|
80
|
+
// Step 3: Choose AI provider
|
|
81
|
+
const providerChoice = await p.select({
|
|
82
|
+
message: "Choose your AI provider:",
|
|
83
|
+
options: [
|
|
84
|
+
{
|
|
85
|
+
value: "anthropic",
|
|
86
|
+
label: "Anthropic (Claude)",
|
|
87
|
+
hint: "recommended — also powers the Orchestrator",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
value: "openai",
|
|
91
|
+
label: "OpenAI (GPT)",
|
|
92
|
+
hint: "uses gpt-4.1 for generation",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
if (p.isCancel(providerChoice)) {
|
|
97
|
+
p.cancel("Setup cancelled.");
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
const provider = providerChoice;
|
|
101
|
+
// Step 4: Get API key
|
|
102
|
+
const envKey = provider === "anthropic"
|
|
103
|
+
? process.env.ANTHROPIC_API_KEY
|
|
104
|
+
: process.env.OPENAI_API_KEY;
|
|
105
|
+
const envName = provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
106
|
+
const keyPrefix = provider === "anthropic" ? "sk-ant-" : "sk-";
|
|
107
|
+
const keyPlaceholder = provider === "anthropic" ? "sk-ant-..." : "sk-...";
|
|
108
|
+
let apiKey = envKey || "";
|
|
109
|
+
if (!apiKey) {
|
|
110
|
+
const keyInput = await p.text({
|
|
111
|
+
message: `Your ${provider === "anthropic" ? "Anthropic" : "OpenAI"} API key:`,
|
|
112
|
+
placeholder: keyPlaceholder,
|
|
113
|
+
validate: (v) => !v || !v.startsWith(keyPrefix)
|
|
114
|
+
? `Please enter a valid key (starts with ${keyPrefix})`
|
|
115
|
+
: undefined,
|
|
116
|
+
});
|
|
117
|
+
if (p.isCancel(keyInput)) {
|
|
118
|
+
p.cancel("Setup cancelled.");
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
apiKey = keyInput;
|
|
122
|
+
process.env[envName] = apiKey;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
p.log.info(chalk.dim(`Using ${envName} from environment.`));
|
|
126
|
+
}
|
|
127
|
+
// Orchestrator always needs Anthropic
|
|
128
|
+
let anthropicKey = "";
|
|
129
|
+
if (provider === "anthropic") {
|
|
130
|
+
anthropicKey = apiKey;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const existingKey = process.env.ANTHROPIC_API_KEY || "";
|
|
134
|
+
if (existingKey) {
|
|
135
|
+
anthropicKey = existingKey;
|
|
136
|
+
p.log.info(chalk.dim("Using ANTHROPIC_API_KEY for Orchestrator."));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
p.log.info(chalk.dim("The Orchestrator requires an Anthropic API key (Claude tool use)."));
|
|
140
|
+
const orchKeyInput = await p.text({
|
|
141
|
+
message: "Anthropic API key for Orchestrator:",
|
|
142
|
+
placeholder: "sk-ant-...",
|
|
143
|
+
validate: (v) => !v || !v.startsWith("sk-ant-")
|
|
144
|
+
? "Please enter a valid Anthropic key (starts with sk-ant-)"
|
|
145
|
+
: undefined,
|
|
146
|
+
});
|
|
147
|
+
if (p.isCancel(orchKeyInput)) {
|
|
148
|
+
p.cancel("Setup cancelled.");
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
anthropicKey = orchKeyInput;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Step 5: Generate agents + site content
|
|
155
|
+
const s = p.spinner();
|
|
156
|
+
s.start("Generating your AI team and website...");
|
|
157
|
+
let agents;
|
|
158
|
+
try {
|
|
159
|
+
agents = await generateAgents(company.name, company.description, provider);
|
|
160
|
+
s.stop("Your AI team is ready!");
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
s.stop("Failed to generate agents.");
|
|
164
|
+
p.log.error(err instanceof Error ? err.message : "Check your API key");
|
|
165
|
+
p.cancel("Setup failed.");
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
// Step 6: Show agents + site preview
|
|
169
|
+
p.log.info(chalk.bold("Meet your team:"));
|
|
170
|
+
console.log();
|
|
171
|
+
for (const agent of agents.agents) {
|
|
172
|
+
console.log(` ${chalk.hex("#8b5cf6").bold(agent.name)} ${chalk.dim(`(${agent.role})`)}`);
|
|
173
|
+
console.log(` ${chalk.dim(agent.description)}`);
|
|
174
|
+
console.log();
|
|
175
|
+
}
|
|
176
|
+
if (agents.site && mode === "new") {
|
|
177
|
+
p.log.info(chalk.bold("Your website:"));
|
|
178
|
+
console.log(` ${chalk.hex("#f59e0b")(agents.site.tagline)}`);
|
|
179
|
+
console.log(` ${chalk.dim(agents.site.subtitle)}`);
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
182
|
+
const confirm = await p.confirm({
|
|
183
|
+
message: "Looks good?",
|
|
184
|
+
initialValue: true,
|
|
185
|
+
});
|
|
186
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
187
|
+
p.cancel("Setup cancelled.");
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
// Step 7a: Connect mode — website URL for KB
|
|
191
|
+
let websiteUrl = "";
|
|
192
|
+
if (mode === "connect") {
|
|
193
|
+
const urlInput = await p.text({
|
|
194
|
+
message: "Your existing website URL (for Knowledge Base crawling):",
|
|
195
|
+
placeholder: "https://my-business.com",
|
|
196
|
+
});
|
|
197
|
+
if (!p.isCancel(urlInput) && urlInput) {
|
|
198
|
+
websiteUrl = urlInput;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Step 7b: Infrastructure setup (tier-dependent)
|
|
202
|
+
let supabaseUrl = "";
|
|
203
|
+
let supabaseKey = "";
|
|
204
|
+
let useAutomatedSupabase = false;
|
|
205
|
+
if (tier === "quickstart") {
|
|
206
|
+
useAutomatedSupabase = true;
|
|
207
|
+
}
|
|
208
|
+
else if (tier === "cloud") {
|
|
209
|
+
p.log.info(chalk.dim("Cloud mode: PostgreSQL runs in Docker. No Supabase needed."));
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
p.log.info(`${chalk.bold("AWS Enterprise setup")}\n` +
|
|
213
|
+
` ${chalk.dim("Requires:")} AWS CLI configured + CDK bootstrapped\n` +
|
|
214
|
+
` ${chalk.dim("Cost:")} ~$5-15/mo (Lambda + RDS free tier)`);
|
|
215
|
+
}
|
|
216
|
+
// Step 8: Scaffold project
|
|
217
|
+
s.start("Creating your project...");
|
|
218
|
+
const slug = company.name
|
|
219
|
+
.toLowerCase()
|
|
220
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
221
|
+
.replace(/^-|-$/g, "");
|
|
222
|
+
try {
|
|
223
|
+
const projectDir = await scaffoldProject({
|
|
224
|
+
company: {
|
|
225
|
+
name: company.name,
|
|
226
|
+
description: company.description,
|
|
227
|
+
industry: agents.industry,
|
|
228
|
+
},
|
|
229
|
+
agents: agents.agents,
|
|
230
|
+
site: agents.site,
|
|
231
|
+
provider,
|
|
232
|
+
tier,
|
|
233
|
+
mode,
|
|
234
|
+
websiteUrl,
|
|
235
|
+
infra: {
|
|
236
|
+
anthropicKey,
|
|
237
|
+
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
238
|
+
supabaseUrl: supabaseUrl || "PLACEHOLDER",
|
|
239
|
+
supabaseKey: supabaseKey || "PLACEHOLDER",
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
s.stop("Project created!");
|
|
243
|
+
// Post-scaffold automation
|
|
244
|
+
// Step 9: Supabase setup (quickstart only)
|
|
245
|
+
if (tier === "quickstart" && useAutomatedSupabase) {
|
|
246
|
+
const result = await setupSupabase(projectDir);
|
|
247
|
+
if (result.method === "local" && result.credentials) {
|
|
248
|
+
supabaseUrl = result.credentials.url;
|
|
249
|
+
supabaseKey = result.credentials.anonKey;
|
|
250
|
+
const { writeEnvFile } = await import("./setup/env.js");
|
|
251
|
+
await writeEnvFile(projectDir, {
|
|
252
|
+
anthropicKey,
|
|
253
|
+
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
254
|
+
supabaseUrl,
|
|
255
|
+
supabaseKey,
|
|
256
|
+
serviceRoleKey: result.credentials.serviceRoleKey,
|
|
257
|
+
});
|
|
258
|
+
p.log.success(chalk.green("Supabase configured automatically!") +
|
|
259
|
+
chalk.dim(` → ${supabaseUrl}`));
|
|
260
|
+
}
|
|
261
|
+
else if (result.method === "remote") {
|
|
262
|
+
p.log.info(chalk.dim("Project linked. Get your URL and key from Settings → API."));
|
|
263
|
+
const supabase = await p.group({
|
|
264
|
+
url: () => p.text({
|
|
265
|
+
message: "Your Supabase project URL:",
|
|
266
|
+
placeholder: "https://xxx.supabase.co",
|
|
267
|
+
validate: (v) => !v || !v.includes("supabase")
|
|
268
|
+
? "Please enter a valid Supabase URL"
|
|
269
|
+
: undefined,
|
|
270
|
+
}),
|
|
271
|
+
key: () => p.text({
|
|
272
|
+
message: "Your Supabase publishable key (anon/public):",
|
|
273
|
+
placeholder: "sb_publishable_...",
|
|
274
|
+
validate: (v) => (!v ? "Key is required" : undefined),
|
|
275
|
+
}),
|
|
276
|
+
}, {
|
|
277
|
+
onCancel: () => {
|
|
278
|
+
p.cancel("Setup cancelled.");
|
|
279
|
+
process.exit(0);
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
supabaseUrl = supabase.url;
|
|
283
|
+
supabaseKey = supabase.key;
|
|
284
|
+
const { writeEnvFile } = await import("./setup/env.js");
|
|
285
|
+
await writeEnvFile(projectDir, {
|
|
286
|
+
anthropicKey,
|
|
287
|
+
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
288
|
+
supabaseUrl,
|
|
289
|
+
supabaseKey,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
p.log.info(`${chalk.bold("Supabase setup")} ${chalk.dim("(free tier works fine)")}\n` +
|
|
294
|
+
` ${chalk.dim("1.")} Create a project at ${chalk.cyan("https://supabase.com/dashboard/new")}\n` +
|
|
295
|
+
` ${chalk.dim("2.")} Go to Settings → API to find your URL and keys`);
|
|
296
|
+
const supabase = await p.group({
|
|
297
|
+
url: () => p.text({
|
|
298
|
+
message: "Your Supabase project URL:",
|
|
299
|
+
placeholder: "https://xxx.supabase.co",
|
|
300
|
+
validate: (v) => !v || !v.includes("supabase")
|
|
301
|
+
? "Please enter a valid Supabase URL"
|
|
302
|
+
: undefined,
|
|
303
|
+
}),
|
|
304
|
+
key: () => p.text({
|
|
305
|
+
message: "Your Supabase publishable key (anon/public):",
|
|
306
|
+
placeholder: "sb_publishable_...",
|
|
307
|
+
validate: (v) => (!v ? "Key is required" : undefined),
|
|
308
|
+
}),
|
|
309
|
+
}, {
|
|
310
|
+
onCancel: () => {
|
|
311
|
+
p.cancel("Setup cancelled.");
|
|
312
|
+
process.exit(0);
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
supabaseUrl = supabase.url;
|
|
316
|
+
supabaseKey = supabase.key;
|
|
317
|
+
const { writeEnvFile } = await import("./setup/env.js");
|
|
318
|
+
await writeEnvFile(projectDir, {
|
|
319
|
+
anthropicKey,
|
|
320
|
+
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
321
|
+
supabaseUrl,
|
|
322
|
+
supabaseKey,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Step 10: Install dependencies
|
|
327
|
+
const installConfirm = await p.confirm({
|
|
328
|
+
message: "Install dependencies now? (npm install)",
|
|
329
|
+
initialValue: true,
|
|
330
|
+
});
|
|
331
|
+
if (!p.isCancel(installConfirm) && installConfirm) {
|
|
332
|
+
await installDependencies(projectDir);
|
|
333
|
+
}
|
|
334
|
+
// Final output
|
|
335
|
+
const widgetSnippet = mode === "connect"
|
|
336
|
+
? [
|
|
337
|
+
``,
|
|
338
|
+
`${chalk.bold("Embed in your existing site:")}`,
|
|
339
|
+
`${chalk.cyan('<script src="https://YOUR_URL/widget.js"')}`,
|
|
340
|
+
`${chalk.cyan(' data-agent="' + agents.agents[0].id + '"')}`,
|
|
341
|
+
`${chalk.cyan(' data-api="https://YOUR_URL"></script>')}`,
|
|
342
|
+
]
|
|
343
|
+
: [];
|
|
344
|
+
const didInstall = !p.isCancel(installConfirm) && installConfirm === true;
|
|
345
|
+
const devCmd = didInstall ? "tellet dev" : "npm install && tellet dev";
|
|
346
|
+
if (tier === "quickstart") {
|
|
347
|
+
p.note([
|
|
348
|
+
`cd ${slug}`,
|
|
349
|
+
`${devCmd} ${chalk.dim("→ http://localhost:3000")}`,
|
|
350
|
+
``,
|
|
351
|
+
`${chalk.bold("CLI commands:")}`,
|
|
352
|
+
`tellet status ${chalk.dim("project info")}`,
|
|
353
|
+
`tellet agent list ${chalk.dim("list agents")}`,
|
|
354
|
+
`tellet deploy ${chalk.dim("deploy guide")}`,
|
|
355
|
+
``,
|
|
356
|
+
`Dashboard: ${chalk.dim("/dashboard")}`,
|
|
357
|
+
`Orchestrator: ${chalk.dim("floating button in dashboard")}`,
|
|
358
|
+
`Agents: ${chalk.dim(`${agents.agents.length} active`)}`,
|
|
359
|
+
...widgetSnippet,
|
|
360
|
+
].join("\n"), mode === "connect" ? "Your AI agents are ready" : "Your AI company is ready");
|
|
361
|
+
p.outro(`Deploy: ${chalk.cyan("tellet deploy")}`);
|
|
362
|
+
}
|
|
363
|
+
else if (tier === "cloud") {
|
|
364
|
+
p.note([
|
|
365
|
+
`cd ${slug}`,
|
|
366
|
+
``,
|
|
367
|
+
`${chalk.bold("Local development:")}`,
|
|
368
|
+
`docker compose up ${chalk.dim("→ http://localhost:3000")}`,
|
|
369
|
+
``,
|
|
370
|
+
`${chalk.bold("Deploy to Railway:")}`,
|
|
371
|
+
`tellet deploy`,
|
|
372
|
+
``,
|
|
373
|
+
`Dashboard: ${chalk.dim("/dashboard")}`,
|
|
374
|
+
`Orchestrator: ${chalk.dim("floating button in dashboard")}`,
|
|
375
|
+
`Agents: ${chalk.dim(`${agents.agents.length} active`)}`,
|
|
376
|
+
].join("\n"), "Your AI company is ready");
|
|
377
|
+
p.outro(`Or deploy to ${chalk.cyan("Render")}, ${chalk.cyan("Fly.io")}, or any Docker host`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
p.note([
|
|
381
|
+
`cd ${slug}`,
|
|
382
|
+
``,
|
|
383
|
+
`${chalk.bold("Local development:")}`,
|
|
384
|
+
`docker compose up ${chalk.dim("→ http://localhost:3000")}`,
|
|
385
|
+
``,
|
|
386
|
+
`${chalk.bold("Deploy to AWS:")}`,
|
|
387
|
+
`tellet deploy`,
|
|
388
|
+
``,
|
|
389
|
+
`Dashboard: ${chalk.dim("/dashboard")}`,
|
|
390
|
+
`Orchestrator: ${chalk.dim("floating button in dashboard")}`,
|
|
391
|
+
`Agents: ${chalk.dim(`${agents.agents.length} active`)}`,
|
|
392
|
+
`Est. cost: ${chalk.dim("$5-15/mo")}`,
|
|
393
|
+
].join("\n"), "Your AI company is ready (AWS)");
|
|
394
|
+
p.outro(`Run ${chalk.cyan("tellet deploy")} to provision AWS infrastructure`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
s.stop("Failed to create project.");
|
|
399
|
+
p.log.error(err instanceof Error ? err.message : String(err));
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellet/create",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI agents that run your business —
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "AI agents that run your business — create, manage, and deploy. One CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create": "dist/index.js"
|
|
7
|
+
"create": "dist/index.js",
|
|
8
|
+
"tellet": "dist/index.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"dist",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@anthropic-ai/sdk": "^0.80.0",
|
|
43
44
|
"@clack/prompts": "^1.1.0",
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
44
46
|
"chalk": "^5.6.2",
|
|
45
47
|
"fs-extra": "^11.3.4",
|
|
46
48
|
"openai": "^6.32.0"
|