@tellet/create 0.9.0 → 0.10.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 +106 -140
- package/dist/index.js +117 -32
- package/dist/scaffold/project.js +1 -1
- 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 +2 -3
package/README.md
CHANGED
|
@@ -1,188 +1,154 @@
|
|
|
1
1
|
# Tell it. Let it.
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> AI agents that run your business — email, delegate, schedule, embed.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx create
|
|
6
|
+
npx @tellet/create
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Or start instantly at [tellet.com](https://tellet.com) — no setup required.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
---
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- **Orchestrator** — Manage your entire company through conversation
|
|
15
|
-
- **Knowledge Base** — pgvector-powered, agents reference it for accurate answers
|
|
16
|
-
- **Dashboard** — Stats, agent chat, conversations, onboarding
|
|
17
|
-
- **Tool Marketplace** — Stripe, Email, GitHub, Slack, Notion via MCP
|
|
18
|
-
- **Embeddable Widget** — One script tag to add AI chat to any website
|
|
19
|
-
- **3-Tier Deployment** — Free (Vercel) / Cloud (Railway) / Enterprise (AWS)
|
|
13
|
+
tellet generates an AI agent team for your business. Agents don't just chat — they send emails, delegate tasks to each other, run scheduled automations, and serve customers through an embeddable widget.
|
|
20
14
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
npx create-tellet
|
|
25
|
-
|
|
26
|
-
? New or Connect? → New business or add AI to existing
|
|
27
|
-
? Deployment? → Quick Start / Cloud / Enterprise
|
|
28
|
-
? AI Provider? → Anthropic / OpenAI
|
|
29
|
-
? Company name? → Sunny Coffee
|
|
30
|
-
? Describe your business → We sell specialty coffee...
|
|
15
|
+
## What agents can do
|
|
31
16
|
|
|
32
|
-
|
|
17
|
+
- **Email customers** — Send follow-ups, confirmations, and outreach via Resend
|
|
18
|
+
- **Delegate to each other** — Support agent routes billing questions to sales automatically
|
|
19
|
+
- **Run on schedule** — Daily reports, weekly summaries, automated follow-ups via cron
|
|
20
|
+
- **Search knowledge** — Reference your product info, policies, and FAQ for accurate answers
|
|
21
|
+
- **Serve customers anywhere** — Embeddable widget for any website, one script tag
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
Barista (customer_support)
|
|
36
|
-
Roaster (marketing)
|
|
37
|
-
Grinder (sales)
|
|
23
|
+
## Two ways to start
|
|
38
24
|
|
|
39
|
-
|
|
40
|
-
"Coffee worth waking up for"
|
|
25
|
+
### Hosted (tellet.com) — recommended
|
|
41
26
|
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
1. Sign up at [tellet.com](https://tellet.com)
|
|
28
|
+
2. Describe your business in one sentence
|
|
29
|
+
3. AI generates your agent team with email, delegation, and scheduling
|
|
30
|
+
4. Embed the widget on your site, agents go to work
|
|
44
31
|
|
|
45
|
-
|
|
32
|
+
### Self-hosted (CLI)
|
|
46
33
|
|
|
47
34
|
```bash
|
|
48
|
-
|
|
49
|
-
npx create-tellet
|
|
35
|
+
npx @tellet/create
|
|
50
36
|
cd your-company
|
|
51
37
|
npm install && npm run dev
|
|
52
|
-
|
|
53
|
-
# Cloud (Docker + Railway, $5/mo)
|
|
54
|
-
npx create-tellet # choose "Cloud"
|
|
55
|
-
cd your-company
|
|
56
|
-
docker compose up # local dev
|
|
57
|
-
railway up # deploy
|
|
58
|
-
|
|
59
|
-
# Enterprise (AWS CDK, $5-15/mo)
|
|
60
|
-
npx create-tellet # choose "Enterprise"
|
|
61
|
-
cd your-company/infra
|
|
62
|
-
npm install && npx cdk deploy
|
|
63
38
|
```
|
|
64
39
|
|
|
65
|
-
##
|
|
40
|
+
## How it works
|
|
66
41
|
|
|
67
42
|
```
|
|
68
|
-
|
|
69
|
-
│ Owner ↔ Orchestrator │
|
|
70
|
-
│ ↕ │
|
|
71
|
-
│ Agent Team (CS · Marketing · Sales · Ops) │
|
|
72
|
-
│ ↕ │
|
|
73
|
-
│ MCP Bridge Layer │
|
|
74
|
-
│ (KB, Stripe, DB, Email, Custom API...) │
|
|
75
|
-
└────────────────────────────────────────────┘
|
|
76
|
-
```
|
|
43
|
+
You: "I run a coffee shop called Sunny Coffee"
|
|
77
44
|
|
|
78
|
-
|
|
45
|
+
Generating your AI team...
|
|
79
46
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
│ ├── (site)/ # Public website with chat widget
|
|
85
|
-
│ ├── (dashboard)/ # Management dashboard + Orchestrator
|
|
86
|
-
│ └── api/
|
|
87
|
-
│ ├── chat/ # Streaming chat API (tool use)
|
|
88
|
-
│ ├── orchestrator/ # Orchestrator API (tool use loop)
|
|
89
|
-
│ └── cron/ # Scheduled agent tasks
|
|
90
|
-
├── components/
|
|
91
|
-
│ ├── chat/ # ChatWidget, Markdown
|
|
92
|
-
│ ├── dashboard/ # Sidebar, Stats, AgentChat, Orchestrator
|
|
93
|
-
│ └── sections/ # Landing page sections
|
|
94
|
-
├── lib/
|
|
95
|
-
│ ├── engine/ # Agent runtime with tool use agentic loop
|
|
96
|
-
│ ├── providers/ # LLM providers (Anthropic, OpenAI)
|
|
97
|
-
│ ├── mcp/ # MCP client, Knowledge Base, tool registry
|
|
98
|
-
│ ├── orchestrator/ # Orchestrator tools + executor
|
|
99
|
-
│ └── scheduler.ts # Cron/heartbeat agent scheduler
|
|
100
|
-
├── public/widget.js # Embeddable chat widget
|
|
101
|
-
├── tellet.json # Configuration (single source of truth)
|
|
102
|
-
├── Dockerfile # Docker deployment (Cloud/Enterprise)
|
|
103
|
-
├── docker-compose.yml # Local dev with PostgreSQL + pgvector
|
|
104
|
-
├── railway.toml # Railway auto-deploy
|
|
105
|
-
└── infra/ # AWS CDK (Enterprise only)
|
|
106
|
-
```
|
|
47
|
+
Your team:
|
|
48
|
+
Barista (customer_support) — emails, knowledge, delegation
|
|
49
|
+
Roaster (marketing) — emails, knowledge, delegation
|
|
50
|
+
Grinder (sales) — emails, knowledge, delegation
|
|
107
51
|
|
|
108
|
-
|
|
52
|
+
Orchestrator tools:
|
|
53
|
+
✓ schedule_task — cron-based agent automation
|
|
54
|
+
✓ list_scheduled_tasks — view all schedules
|
|
55
|
+
✓ cancel_scheduled_task
|
|
109
56
|
|
|
110
|
-
|
|
57
|
+
✓ Your AI company is live!
|
|
58
|
+
```
|
|
111
59
|
|
|
112
|
-
|
|
113
|
-
- "Update the website tagline" — modifies site content
|
|
114
|
-
- "Add Stripe to my agents" — installs tools from marketplace
|
|
115
|
-
- "Schedule marketing to post daily at 9am" — sets up cron tasks
|
|
116
|
-
- "Add our refund policy to the Knowledge Base" — agents reference it
|
|
60
|
+
## Architecture
|
|
117
61
|
|
|
118
|
-
|
|
62
|
+
```
|
|
63
|
+
┌─ tellet Platform ──────────────────────────────────┐
|
|
64
|
+
│ │
|
|
65
|
+
│ Owner ↔ Orchestrator (schedule, manage, configure) │
|
|
66
|
+
│ ↕ │
|
|
67
|
+
│ Agent Team (CS · Marketing · Sales · Ops) │
|
|
68
|
+
│ ↕ delegate_to_agent ↕ │
|
|
69
|
+
│ Actions: email · search · schedule │
|
|
70
|
+
│ ↕ │
|
|
71
|
+
│ Channels: dashboard · widget · cron │
|
|
72
|
+
│ │
|
|
73
|
+
└─────────────────────────────────────────────────────┘
|
|
74
|
+
```
|
|
119
75
|
|
|
120
|
-
|
|
76
|
+
## Agent capabilities by role
|
|
121
77
|
|
|
122
|
-
|
|
|
123
|
-
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
78
|
+
| Role | search_knowledge | send_email | delegate_to_agent |
|
|
79
|
+
|------|:---:|:---:|:---:|
|
|
80
|
+
| customer_support | Y | Y | Y |
|
|
81
|
+
| sales | Y | Y | Y |
|
|
82
|
+
| marketing | Y | Y | Y |
|
|
83
|
+
| operations | Y | Y | Y |
|
|
84
|
+
| analytics | Y | - | Y |
|
|
129
85
|
|
|
130
|
-
|
|
86
|
+
## Orchestrator commands
|
|
131
87
|
|
|
132
|
-
|
|
88
|
+
The Orchestrator manages your AI company through conversation:
|
|
133
89
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
90
|
+
- "Schedule marketing to send a weekly summary every Monday 9am"
|
|
91
|
+
- "Show all scheduled tasks"
|
|
92
|
+
- "Add our refund policy to the Knowledge Base"
|
|
93
|
+
- "Update the support agent's system prompt"
|
|
94
|
+
- "Show my stats"
|
|
139
95
|
|
|
140
|
-
##
|
|
96
|
+
## Embeddable widget
|
|
141
97
|
|
|
142
|
-
|
|
98
|
+
Add AI chat to any website:
|
|
143
99
|
|
|
144
|
-
```
|
|
145
|
-
|
|
100
|
+
```html
|
|
101
|
+
<script src="https://tellet.com/widget.js"></script>
|
|
102
|
+
<script>
|
|
103
|
+
Tellet.init({ companyId: "your-company-id" });
|
|
104
|
+
</script>
|
|
146
105
|
```
|
|
147
106
|
|
|
148
|
-
|
|
149
|
-
- Embeddable widget for your existing site:
|
|
107
|
+
Features: dark/light theme, streaming responses, session persistence, mobile responsive.
|
|
150
108
|
|
|
151
|
-
|
|
152
|
-
<script src="https://your-tellet.com/widget.js"
|
|
153
|
-
data-agent="support"
|
|
154
|
-
data-api="https://your-tellet.com"></script>
|
|
155
|
-
```
|
|
109
|
+
## Project structure (self-hosted)
|
|
156
110
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
111
|
+
```
|
|
112
|
+
your-company/
|
|
113
|
+
├─�� app/
|
|
114
|
+
│ ├── (site)/ # Public website with chat widget
|
|
115
|
+
│ ├── (dashboard)/ # Management dashboard + Orchestrator
|
|
116
|
+
│ └── api/
|
|
117
|
+
│ ├── chat/ # Agent chat (SSE, tool use loop)
|
|
118
|
+
│ ├── orchestrator/ # Orchestrator (scheduling, management)
|
|
119
|
+
│ ├── cron/ # Scheduled task execution
|
|
120
|
+
│ └── widget/ # Public widget API (CORS, no auth)
|
|
121
|
+
├── lib/
|
|
122
|
+
│ ├── engine/ # Agent runtime with agentic loop
|
|
123
|
+
│ ├── actions/ # Role-based tools (email, delegate, search)
|
|
124
|
+
│ ├── scheduling/ # Cron parser + task executor
|
|
125
|
+
│ ├── orchestrator/ # Orchestrator tools + executor
|
|
126
|
+
│ ├── providers/ # LLM providers (Anthropic, OpenAI)
|
|
127
|
+
│ └── mcp/ # Knowledge base, tool registry
|
|
128
|
+
├── public/widget.js # Embeddable chat widget
|
|
129
|
+
├── supabase/migrations/ # Database schema
|
|
130
|
+
└── vercel.json # Cron configuration
|
|
176
131
|
```
|
|
177
132
|
|
|
178
133
|
## Tech stack
|
|
179
134
|
|
|
180
135
|
- [Next.js 16](https://nextjs.org/) — App framework
|
|
181
|
-
- [
|
|
182
|
-
- [MCP](https://modelcontextprotocol.io/) — Tool integration protocol
|
|
136
|
+
- [Supabase](https://supabase.com/) — Auth, database, pgvector, RLS
|
|
183
137
|
- [Anthropic Claude](https://anthropic.com/) / [OpenAI](https://openai.com/) — AI models
|
|
138
|
+
- [Resend](https://resend.com/) — Email delivery
|
|
139
|
+
- [Vercel Cron](https://vercel.com/docs/cron-jobs) — Scheduled task execution
|
|
184
140
|
- [Tailwind CSS 4](https://tailwindcss.com/) — Styling
|
|
185
|
-
|
|
141
|
+
|
|
142
|
+
## Environment variables
|
|
143
|
+
|
|
144
|
+
| Variable | Required | Description |
|
|
145
|
+
|----------|:---:|-------------|
|
|
146
|
+
| `NEXT_PUBLIC_SUPABASE_URL` | Y | Supabase project URL |
|
|
147
|
+
| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | Y | Supabase publishable key |
|
|
148
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Y | Supabase service role key |
|
|
149
|
+
| `ANTHROPIC_API_KEY` | Y | Claude API key |
|
|
150
|
+
| `RESEND_API_KEY` | - | Resend API key (for email) |
|
|
151
|
+
| `CRON_SECRET` | - | Secret for cron endpoint auth |
|
|
186
152
|
|
|
187
153
|
## License
|
|
188
154
|
|
|
@@ -190,6 +156,6 @@ MIT
|
|
|
190
156
|
|
|
191
157
|
## Links
|
|
192
158
|
|
|
193
|
-
- [tellet.com](https://tellet.com) —
|
|
159
|
+
- [tellet.com](https://tellet.com) — Hosted platform (free)
|
|
194
160
|
- [GitHub](https://github.com/agentic-company/tellet) — Source code
|
|
195
|
-
- [npm](https://www.npmjs.com/package/create
|
|
161
|
+
- [npm](https://www.npmjs.com/package/@tellet/create) — CLI package
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import * as p from "@clack/prompts";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { generateAgents } from "./ai/generate.js";
|
|
5
5
|
import { scaffoldProject } from "./scaffold/project.js";
|
|
6
|
+
import { setupSupabase } from "./setup/supabase.js";
|
|
7
|
+
import { installDependencies } from "./setup/deps.js";
|
|
6
8
|
async function main() {
|
|
7
9
|
console.clear();
|
|
8
10
|
console.log();
|
|
@@ -198,33 +200,15 @@ async function main() {
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
// Step 7b: Infrastructure setup (tier-dependent)
|
|
203
|
+
// For quickstart: we'll collect Supabase info now, but may override
|
|
204
|
+
// with automated setup after scaffolding
|
|
201
205
|
let supabaseUrl = "";
|
|
202
206
|
let supabaseKey = "";
|
|
207
|
+
let useAutomatedSupabase = false;
|
|
203
208
|
if (tier === "quickstart") {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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;
|
|
209
|
+
// We'll handle Supabase setup after scaffold for CLI automation
|
|
210
|
+
// For now, just mark that we need it
|
|
211
|
+
useAutomatedSupabase = true;
|
|
228
212
|
}
|
|
229
213
|
else if (tier === "cloud") {
|
|
230
214
|
p.log.info(chalk.dim("Cloud mode: PostgreSQL runs in Docker. No Supabase needed."));
|
|
@@ -236,6 +220,10 @@ async function main() {
|
|
|
236
220
|
}
|
|
237
221
|
// Step 8: Scaffold project
|
|
238
222
|
s.start("Creating your project...");
|
|
223
|
+
const slug = company.name
|
|
224
|
+
.toLowerCase()
|
|
225
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
226
|
+
.replace(/^-|-$/g, "");
|
|
239
227
|
try {
|
|
240
228
|
const projectDir = await scaffoldProject({
|
|
241
229
|
company: {
|
|
@@ -252,15 +240,111 @@ async function main() {
|
|
|
252
240
|
infra: {
|
|
253
241
|
anthropicKey,
|
|
254
242
|
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
255
|
-
supabaseUrl,
|
|
256
|
-
supabaseKey,
|
|
243
|
+
supabaseUrl: supabaseUrl || "PLACEHOLDER",
|
|
244
|
+
supabaseKey: supabaseKey || "PLACEHOLDER",
|
|
257
245
|
},
|
|
258
246
|
});
|
|
259
247
|
s.stop("Project created!");
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
248
|
+
// ─────────────────────────────────────────────────────────
|
|
249
|
+
// Post-scaffold automation
|
|
250
|
+
// ─────────────────────────────────────────────────────────
|
|
251
|
+
// Step 9: Supabase setup (quickstart only)
|
|
252
|
+
if (tier === "quickstart" && useAutomatedSupabase) {
|
|
253
|
+
const result = await setupSupabase(projectDir);
|
|
254
|
+
if (result.method === "local" && result.credentials) {
|
|
255
|
+
// Auto-detected credentials from local Supabase
|
|
256
|
+
supabaseUrl = result.credentials.url;
|
|
257
|
+
supabaseKey = result.credentials.anonKey;
|
|
258
|
+
// Rewrite .env.local with real credentials
|
|
259
|
+
const { writeEnvFile } = await import("./setup/env.js");
|
|
260
|
+
await writeEnvFile(projectDir, {
|
|
261
|
+
anthropicKey,
|
|
262
|
+
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
263
|
+
supabaseUrl,
|
|
264
|
+
supabaseKey,
|
|
265
|
+
serviceRoleKey: result.credentials.serviceRoleKey,
|
|
266
|
+
});
|
|
267
|
+
p.log.success(chalk.green("Supabase configured automatically!") +
|
|
268
|
+
chalk.dim(` → ${supabaseUrl}`));
|
|
269
|
+
}
|
|
270
|
+
else if (result.method === "remote") {
|
|
271
|
+
// Remote linked — user needs to provide URL/key for .env
|
|
272
|
+
p.log.info(chalk.dim("Project linked. Get your URL and key from Settings → API."));
|
|
273
|
+
const supabase = await p.group({
|
|
274
|
+
url: () => p.text({
|
|
275
|
+
message: "Your Supabase project URL:",
|
|
276
|
+
placeholder: "https://xxx.supabase.co",
|
|
277
|
+
validate: (v) => !v || !v.includes("supabase")
|
|
278
|
+
? "Please enter a valid Supabase URL"
|
|
279
|
+
: undefined,
|
|
280
|
+
}),
|
|
281
|
+
key: () => p.text({
|
|
282
|
+
message: "Your Supabase publishable key (anon/public):",
|
|
283
|
+
placeholder: "sb_publishable_...",
|
|
284
|
+
validate: (v) => (!v ? "Key is required" : undefined),
|
|
285
|
+
}),
|
|
286
|
+
}, {
|
|
287
|
+
onCancel: () => {
|
|
288
|
+
p.cancel("Setup cancelled.");
|
|
289
|
+
process.exit(0);
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
supabaseUrl = supabase.url;
|
|
293
|
+
supabaseKey = supabase.key;
|
|
294
|
+
const { writeEnvFile } = await import("./setup/env.js");
|
|
295
|
+
await writeEnvFile(projectDir, {
|
|
296
|
+
anthropicKey,
|
|
297
|
+
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
298
|
+
supabaseUrl,
|
|
299
|
+
supabaseKey,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Manual fallback — same as before
|
|
304
|
+
p.log.info(`${chalk.bold("Supabase setup")} ${chalk.dim("(free tier works fine)")}\n` +
|
|
305
|
+
` ${chalk.dim("1.")} Create a project at ${chalk.cyan("https://supabase.com/dashboard/new")}\n` +
|
|
306
|
+
` ${chalk.dim("2.")} Go to Settings → API to find your URL and keys`);
|
|
307
|
+
const supabase = await p.group({
|
|
308
|
+
url: () => p.text({
|
|
309
|
+
message: "Your Supabase project URL:",
|
|
310
|
+
placeholder: "https://xxx.supabase.co",
|
|
311
|
+
validate: (v) => !v || !v.includes("supabase")
|
|
312
|
+
? "Please enter a valid Supabase URL"
|
|
313
|
+
: undefined,
|
|
314
|
+
}),
|
|
315
|
+
key: () => p.text({
|
|
316
|
+
message: "Your Supabase publishable key (anon/public):",
|
|
317
|
+
placeholder: "sb_publishable_...",
|
|
318
|
+
validate: (v) => (!v ? "Key is required" : undefined),
|
|
319
|
+
}),
|
|
320
|
+
}, {
|
|
321
|
+
onCancel: () => {
|
|
322
|
+
p.cancel("Setup cancelled.");
|
|
323
|
+
process.exit(0);
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
supabaseUrl = supabase.url;
|
|
327
|
+
supabaseKey = supabase.key;
|
|
328
|
+
const { writeEnvFile } = await import("./setup/env.js");
|
|
329
|
+
await writeEnvFile(projectDir, {
|
|
330
|
+
anthropicKey,
|
|
331
|
+
openaiKey: provider === "openai" ? apiKey : undefined,
|
|
332
|
+
supabaseUrl,
|
|
333
|
+
supabaseKey,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Step 10: Install dependencies
|
|
338
|
+
const installConfirm = await p.confirm({
|
|
339
|
+
message: "Install dependencies now? (npm install)",
|
|
340
|
+
initialValue: true,
|
|
341
|
+
});
|
|
342
|
+
if (!p.isCancel(installConfirm) && installConfirm) {
|
|
343
|
+
await installDependencies(projectDir);
|
|
344
|
+
}
|
|
345
|
+
// ─────────────────────────────────────────────────────────
|
|
346
|
+
// Final output
|
|
347
|
+
// ─────────────────────────────────────────────────────────
|
|
264
348
|
const widgetSnippet = mode === "connect"
|
|
265
349
|
? [
|
|
266
350
|
``,
|
|
@@ -270,11 +354,12 @@ async function main() {
|
|
|
270
354
|
`${chalk.cyan(' data-api="https://YOUR_URL"></script>')}`,
|
|
271
355
|
]
|
|
272
356
|
: [];
|
|
357
|
+
const didInstall = !p.isCancel(installConfirm) && installConfirm === true;
|
|
358
|
+
const devCmd = didInstall ? "npm run dev" : "npm install && npm run dev";
|
|
273
359
|
if (tier === "quickstart") {
|
|
274
360
|
p.note([
|
|
275
361
|
`cd ${slug}`,
|
|
276
|
-
|
|
277
|
-
`npm run dev ${chalk.dim("→ http://localhost:3000")}`,
|
|
362
|
+
`${devCmd} ${chalk.dim("→ http://localhost:3000")}`,
|
|
278
363
|
``,
|
|
279
364
|
`Dashboard: ${chalk.dim("/dashboard")}`,
|
|
280
365
|
`Orchestrator: ${chalk.dim("floating button in dashboard")}`,
|
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,24 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
/**
|
|
4
|
+
* Run `npm install` in the project directory with live output.
|
|
5
|
+
*/
|
|
6
|
+
export async function installDependencies(projectDir) {
|
|
7
|
+
const s = p.spinner();
|
|
8
|
+
s.start("Installing dependencies...");
|
|
9
|
+
const result = spawnSync("npm", ["install"], {
|
|
10
|
+
cwd: projectDir,
|
|
11
|
+
stdio: "pipe",
|
|
12
|
+
timeout: 300_000,
|
|
13
|
+
});
|
|
14
|
+
if (result.status !== 0) {
|
|
15
|
+
s.stop("Failed to install dependencies.");
|
|
16
|
+
const stderr = result.stderr?.toString().trim();
|
|
17
|
+
if (stderr) {
|
|
18
|
+
p.log.error(stderr.slice(0, 500));
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
s.stop("Dependencies installed!");
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface EnvOptions {
|
|
2
|
+
anthropicKey: string;
|
|
3
|
+
openaiKey?: string;
|
|
4
|
+
supabaseUrl: string;
|
|
5
|
+
supabaseKey: string;
|
|
6
|
+
serviceRoleKey?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Write (or overwrite) the .env.local file with Supabase + API credentials.
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeEnvFile(projectDir: string, options: EnvOptions): Promise<void>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/** Escape a value for .env files — wrap in quotes if it contains special chars. */
|
|
4
|
+
function envQuote(v) {
|
|
5
|
+
if (/[\s#"'\\$`!]/.test(v) || v.includes("\n")) {
|
|
6
|
+
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
|
|
7
|
+
}
|
|
8
|
+
return v;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Write (or overwrite) the .env.local file with Supabase + API credentials.
|
|
12
|
+
*/
|
|
13
|
+
export async function writeEnvFile(projectDir, options) {
|
|
14
|
+
const lines = [
|
|
15
|
+
`# Orchestrator (always Anthropic)`,
|
|
16
|
+
`ANTHROPIC_API_KEY=${envQuote(options.anthropicKey)}`,
|
|
17
|
+
];
|
|
18
|
+
if (options.openaiKey) {
|
|
19
|
+
lines.push(`OPENAI_API_KEY=${envQuote(options.openaiKey)}`);
|
|
20
|
+
}
|
|
21
|
+
lines.push(``, `# Supabase`, `NEXT_PUBLIC_SUPABASE_URL=${envQuote(options.supabaseUrl)}`, `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=${envQuote(options.supabaseKey)}`);
|
|
22
|
+
if (options.serviceRoleKey) {
|
|
23
|
+
lines.push(`SUPABASE_SERVICE_ROLE_KEY=${envQuote(options.serviceRoleKey)}`);
|
|
24
|
+
}
|
|
25
|
+
await fs.writeFile(path.join(projectDir, ".env.local"), lines.join("\n") + "\n");
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Check if the Supabase CLI is installed and accessible. */
|
|
2
|
+
export declare function hasSupabaseCLI(): boolean;
|
|
3
|
+
/** Check if Docker is running (required for `supabase start`). */
|
|
4
|
+
export declare function hasDocker(): boolean;
|
|
5
|
+
export interface SupabaseCredentials {
|
|
6
|
+
url: string;
|
|
7
|
+
anonKey: string;
|
|
8
|
+
serviceRoleKey?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Initialize Supabase in the project directory.
|
|
12
|
+
* Creates supabase/config.toml if not already present.
|
|
13
|
+
*/
|
|
14
|
+
export declare function supabaseInit(projectDir: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Start local Supabase (Docker containers).
|
|
17
|
+
* Returns credentials on success, null on failure.
|
|
18
|
+
*/
|
|
19
|
+
export declare function supabaseStartLocal(projectDir: string): Promise<SupabaseCredentials | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Link to a remote Supabase project.
|
|
22
|
+
* Prompts for project ref, runs `supabase link`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function supabaseLinkRemote(projectDir: string): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Push migrations to the database.
|
|
27
|
+
*/
|
|
28
|
+
export declare function supabasePushMigrations(projectDir: string): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Full Supabase setup flow for quickstart tier.
|
|
31
|
+
* Returns credentials or null if user chose manual setup.
|
|
32
|
+
*/
|
|
33
|
+
export declare function setupSupabase(projectDir: string): Promise<{
|
|
34
|
+
credentials: SupabaseCredentials | null;
|
|
35
|
+
method: "local" | "remote" | "manual";
|
|
36
|
+
}>;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
/** Check if the Supabase CLI is installed and accessible. */
|
|
5
|
+
export function hasSupabaseCLI() {
|
|
6
|
+
try {
|
|
7
|
+
execFileSync("supabase", ["--version"], { stdio: "ignore" });
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Check if Docker is running (required for `supabase start`). */
|
|
15
|
+
export function hasDocker() {
|
|
16
|
+
try {
|
|
17
|
+
execFileSync("docker", ["info"], { stdio: "ignore", timeout: 5000 });
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse `supabase status` output to extract local dev credentials.
|
|
26
|
+
* The output format is like:
|
|
27
|
+
* API URL: http://127.0.0.1:54321
|
|
28
|
+
* anon key: eyJ...
|
|
29
|
+
* service_role key: eyJ...
|
|
30
|
+
*/
|
|
31
|
+
function parseSupabaseStatus(output) {
|
|
32
|
+
const urlMatch = output.match(/API URL:\s*(http\S+)/);
|
|
33
|
+
const anonMatch = output.match(/anon key:\s*(\S+)/);
|
|
34
|
+
const serviceMatch = output.match(/service_role key:\s*(\S+)/);
|
|
35
|
+
if (!urlMatch || !anonMatch)
|
|
36
|
+
return null;
|
|
37
|
+
return {
|
|
38
|
+
url: urlMatch[1],
|
|
39
|
+
anonKey: anonMatch[1],
|
|
40
|
+
serviceRoleKey: serviceMatch?.[1],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run supabase CLI command in a directory, returning stdout.
|
|
45
|
+
*/
|
|
46
|
+
function runSupabase(args, cwd) {
|
|
47
|
+
return execFileSync("supabase", args, {
|
|
48
|
+
cwd,
|
|
49
|
+
encoding: "utf-8",
|
|
50
|
+
timeout: 120_000,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run supabase CLI with live stdio (visible to user).
|
|
55
|
+
* Returns the exit code.
|
|
56
|
+
*/
|
|
57
|
+
function runSupabaseLive(args, cwd) {
|
|
58
|
+
const result = spawnSync("supabase", args, {
|
|
59
|
+
cwd,
|
|
60
|
+
stdio: "inherit",
|
|
61
|
+
timeout: 300_000,
|
|
62
|
+
});
|
|
63
|
+
return result.status ?? 1;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Initialize Supabase in the project directory.
|
|
67
|
+
* Creates supabase/config.toml if not already present.
|
|
68
|
+
*/
|
|
69
|
+
export function supabaseInit(projectDir) {
|
|
70
|
+
try {
|
|
71
|
+
runSupabase(["init", "--with-intellij-settings=false", "--with-vscode-settings=false"], projectDir);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Start local Supabase (Docker containers).
|
|
80
|
+
* Returns credentials on success, null on failure.
|
|
81
|
+
*/
|
|
82
|
+
export async function supabaseStartLocal(projectDir) {
|
|
83
|
+
p.log.info(chalk.dim("Starting local Supabase (this may take a minute on first run)..."));
|
|
84
|
+
const code = runSupabaseLive(["start"], projectDir);
|
|
85
|
+
if (code !== 0) {
|
|
86
|
+
p.log.error("Failed to start local Supabase.");
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
p.log.success("Local Supabase is running!");
|
|
90
|
+
// Extract credentials from status
|
|
91
|
+
try {
|
|
92
|
+
const output = runSupabase(["status"], projectDir);
|
|
93
|
+
return parseSupabaseStatus(output);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Link to a remote Supabase project.
|
|
101
|
+
* Prompts for project ref, runs `supabase link`.
|
|
102
|
+
*/
|
|
103
|
+
export async function supabaseLinkRemote(projectDir) {
|
|
104
|
+
const refInput = await p.text({
|
|
105
|
+
message: "Supabase project ref (from Dashboard → Settings → General):",
|
|
106
|
+
placeholder: "abcdefghijklmnopqrst",
|
|
107
|
+
validate: (v) => !v || v.length < 10
|
|
108
|
+
? "Please enter a valid project ref"
|
|
109
|
+
: undefined,
|
|
110
|
+
});
|
|
111
|
+
if (p.isCancel(refInput))
|
|
112
|
+
return false;
|
|
113
|
+
p.log.info(chalk.dim("Linking to Supabase project (follow the prompts below)..."));
|
|
114
|
+
const code = runSupabaseLive(["link", "--project-ref", refInput], projectDir);
|
|
115
|
+
if (code !== 0) {
|
|
116
|
+
p.log.error("Failed to link project.");
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
p.log.success("Linked to Supabase project!");
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Push migrations to the database.
|
|
124
|
+
*/
|
|
125
|
+
export async function supabasePushMigrations(projectDir) {
|
|
126
|
+
p.log.info(chalk.dim("Applying database migrations..."));
|
|
127
|
+
const code = runSupabaseLive(["db", "push"], projectDir);
|
|
128
|
+
if (code !== 0) {
|
|
129
|
+
p.log.error("Migration push failed.");
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
p.log.success("Migrations applied!");
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Full Supabase setup flow for quickstart tier.
|
|
137
|
+
* Returns credentials or null if user chose manual setup.
|
|
138
|
+
*/
|
|
139
|
+
export async function setupSupabase(projectDir) {
|
|
140
|
+
const cliAvailable = hasSupabaseCLI();
|
|
141
|
+
const dockerAvailable = hasDocker();
|
|
142
|
+
if (!cliAvailable) {
|
|
143
|
+
p.log.info(chalk.dim("Supabase CLI not found. Install it for automated setup:\n" +
|
|
144
|
+
" brew install supabase/tap/supabase\n" +
|
|
145
|
+
" — or —\n" +
|
|
146
|
+
" npx supabase --version"));
|
|
147
|
+
return { credentials: null, method: "manual" };
|
|
148
|
+
}
|
|
149
|
+
// Build options based on what's available
|
|
150
|
+
const options = [];
|
|
151
|
+
if (dockerAvailable) {
|
|
152
|
+
options.push({
|
|
153
|
+
value: "local",
|
|
154
|
+
label: "Local development",
|
|
155
|
+
hint: "supabase start — runs Postgres + Auth locally via Docker",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
options.push({
|
|
159
|
+
value: "remote",
|
|
160
|
+
label: "Remote project",
|
|
161
|
+
hint: "Link to an existing Supabase project",
|
|
162
|
+
}, {
|
|
163
|
+
value: "manual",
|
|
164
|
+
label: "Manual setup",
|
|
165
|
+
hint: "I'll enter the URL and key myself",
|
|
166
|
+
});
|
|
167
|
+
if (!dockerAvailable) {
|
|
168
|
+
p.log.info(chalk.dim("Docker not detected — local Supabase requires Docker Desktop."));
|
|
169
|
+
}
|
|
170
|
+
const choice = await p.select({
|
|
171
|
+
message: "Supabase setup:",
|
|
172
|
+
options,
|
|
173
|
+
});
|
|
174
|
+
if (p.isCancel(choice)) {
|
|
175
|
+
return { credentials: null, method: "manual" };
|
|
176
|
+
}
|
|
177
|
+
const method = choice;
|
|
178
|
+
if (method === "manual") {
|
|
179
|
+
return { credentials: null, method: "manual" };
|
|
180
|
+
}
|
|
181
|
+
// Initialize supabase in project
|
|
182
|
+
const inited = supabaseInit(projectDir);
|
|
183
|
+
if (!inited) {
|
|
184
|
+
p.log.error("Failed to initialize Supabase in the project directory.");
|
|
185
|
+
return { credentials: null, method: "manual" };
|
|
186
|
+
}
|
|
187
|
+
if (method === "local") {
|
|
188
|
+
const creds = await supabaseStartLocal(projectDir);
|
|
189
|
+
if (creds) {
|
|
190
|
+
// Apply migrations to local DB
|
|
191
|
+
await supabasePushMigrations(projectDir);
|
|
192
|
+
}
|
|
193
|
+
return { credentials: creds, method: "local" };
|
|
194
|
+
}
|
|
195
|
+
// Remote
|
|
196
|
+
const linked = await supabaseLinkRemote(projectDir);
|
|
197
|
+
if (linked) {
|
|
198
|
+
const pushConfirm = await p.confirm({
|
|
199
|
+
message: "Apply database migrations now?",
|
|
200
|
+
initialValue: true,
|
|
201
|
+
});
|
|
202
|
+
if (!p.isCancel(pushConfirm) && pushConfirm) {
|
|
203
|
+
await supabasePushMigrations(projectDir);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { credentials: null, method: "remote" };
|
|
207
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellet/create",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "AI agents that run your business — email, delegate, schedule, embed. One command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-tellet": "dist/index.js",
|
|
8
7
|
"create": "dist/index.js"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|