@yesvara/svara 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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +497 -0
  3. package/dist/chunk-CIESM3BP.mjs +33 -0
  4. package/dist/chunk-FEA5KIJN.mjs +418 -0
  5. package/dist/cli/index.d.mts +1 -0
  6. package/dist/cli/index.d.ts +1 -0
  7. package/dist/cli/index.js +328 -0
  8. package/dist/cli/index.mjs +39 -0
  9. package/dist/dev-OYGXXK2B.mjs +69 -0
  10. package/dist/index.d.mts +967 -0
  11. package/dist/index.d.ts +967 -0
  12. package/dist/index.js +1976 -0
  13. package/dist/index.mjs +1502 -0
  14. package/dist/new-7K4NIDZO.mjs +177 -0
  15. package/dist/retriever-4QY667XF.mjs +7 -0
  16. package/examples/01-basic/index.ts +26 -0
  17. package/examples/02-with-tools/index.ts +73 -0
  18. package/examples/03-rag-knowledge/index.ts +41 -0
  19. package/examples/04-multi-channel/index.ts +91 -0
  20. package/package.json +74 -0
  21. package/src/app/index.ts +176 -0
  22. package/src/channels/telegram.ts +122 -0
  23. package/src/channels/web.ts +118 -0
  24. package/src/channels/whatsapp.ts +161 -0
  25. package/src/cli/commands/dev.ts +87 -0
  26. package/src/cli/commands/new.ts +213 -0
  27. package/src/cli/index.ts +78 -0
  28. package/src/core/agent.ts +607 -0
  29. package/src/core/llm.ts +406 -0
  30. package/src/core/types.ts +183 -0
  31. package/src/database/schema.ts +79 -0
  32. package/src/database/sqlite.ts +239 -0
  33. package/src/index.ts +94 -0
  34. package/src/memory/context.ts +49 -0
  35. package/src/memory/conversation.ts +51 -0
  36. package/src/rag/chunker.ts +165 -0
  37. package/src/rag/loader.ts +216 -0
  38. package/src/rag/retriever.ts +248 -0
  39. package/src/tools/executor.ts +54 -0
  40. package/src/tools/index.ts +89 -0
  41. package/src/tools/registry.ts +44 -0
  42. package/src/types.ts +131 -0
  43. package/tsconfig.json +26 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @module channels/telegram
3
+ *
4
+ * Telegram Bot channel.
5
+ * Used when you call: `agent.connectChannel('telegram', { token: '...' })`
6
+ */
7
+
8
+ import type { SvaraAgent, SvaraChannel } from '../core/agent.js';
9
+ import type { IncomingMessage, ChannelName } from '../core/types.js';
10
+
11
+ export interface TelegramChannelConfig {
12
+ token: string;
13
+ mode?: 'polling' | 'webhook';
14
+ webhookUrl?: string;
15
+ pollingInterval?: number;
16
+ }
17
+
18
+ interface TGUpdate {
19
+ update_id: number;
20
+ message?: { message_id: number; from: { id: number; username?: string }; chat: { id: number }; date: number; text?: string };
21
+ }
22
+
23
+ export class TelegramChannel implements SvaraChannel {
24
+ readonly name: ChannelName = 'telegram';
25
+
26
+ private agent!: SvaraAgent;
27
+ private baseUrl: string;
28
+ private lastUpdateId = 0;
29
+ private pollingTimer: ReturnType<typeof setInterval> | null = null;
30
+
31
+ constructor(private config: TelegramChannelConfig) {
32
+ if (!config.token) throw new Error('[@yesvara/svara] Telegram requires a bot token.');
33
+ this.baseUrl = `https://api.telegram.org/bot${config.token}`;
34
+ }
35
+
36
+ async mount(agent: SvaraAgent): Promise<void> {
37
+ this.agent = agent;
38
+ const me = await this.api<{ username: string }>('getMe');
39
+ console.log(`[@yesvara/svara] Telegram connected as @${me.username}`);
40
+
41
+ if (this.config.mode === 'webhook' && this.config.webhookUrl) {
42
+ await this.api('setWebhook', { url: `${this.config.webhookUrl}/telegram/webhook` });
43
+ console.log(`[@yesvara/svara] Telegram webhook registered.`);
44
+ } else {
45
+ this.startPolling();
46
+ }
47
+ }
48
+
49
+ async send(sessionId: string, text: string): Promise<void> {
50
+ const chatId = parseInt(sessionId, 10);
51
+ if (!isNaN(chatId)) await this.sendMessage(chatId, text);
52
+ }
53
+
54
+ async stop(): Promise<void> {
55
+ if (this.pollingTimer) clearInterval(this.pollingTimer);
56
+ }
57
+
58
+ private startPolling(): void {
59
+ const interval = this.config.pollingInterval ?? 1000;
60
+ console.log('[@yesvara/svara] Telegram polling started...');
61
+ this.pollingTimer = setInterval(async () => {
62
+ try {
63
+ const updates = await this.api<TGUpdate[]>('getUpdates', {
64
+ offset: this.lastUpdateId + 1,
65
+ allowed_updates: ['message'],
66
+ });
67
+ for (const update of updates) {
68
+ this.lastUpdateId = update.update_id;
69
+ if (update.message?.text) await this.handleUpdate(update);
70
+ }
71
+ } catch { /* polling errors are transient */ }
72
+ }, interval);
73
+ }
74
+
75
+ private async handleUpdate(update: TGUpdate): Promise<void> {
76
+ const msg = update.message!;
77
+ const message: IncomingMessage = {
78
+ id: String(msg.message_id),
79
+ sessionId: String(msg.chat.id),
80
+ userId: String(msg.from.id),
81
+ channel: 'telegram',
82
+ text: msg.text ?? '',
83
+ timestamp: new Date(msg.date * 1000),
84
+ raw: msg,
85
+ };
86
+
87
+ await this.api('sendChatAction', { chat_id: msg.chat.id, action: 'typing' }).catch(() => {});
88
+
89
+ try {
90
+ const result = await this.agent.receive(message);
91
+ for (const chunk of this.split(result.response, 4096)) {
92
+ await this.sendMessage(msg.chat.id, chunk);
93
+ }
94
+ } catch (err) {
95
+ await this.sendMessage(msg.chat.id, 'Sorry, something went wrong. Please try again.');
96
+ console.error('[@yesvara/svara] Telegram error:', (err as Error).message);
97
+ }
98
+ }
99
+
100
+ private async sendMessage(chatId: number, text: string): Promise<void> {
101
+ await this.api('sendMessage', { chat_id: chatId, text, parse_mode: 'Markdown' });
102
+ }
103
+
104
+ private async api<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {
105
+ const res = await fetch(`${this.baseUrl}/${method}`, {
106
+ method: params ? 'POST' : 'GET',
107
+ headers: { 'Content-Type': 'application/json' },
108
+ body: params ? JSON.stringify(params) : undefined,
109
+ });
110
+ const data = await res.json() as { ok: boolean; result: T; description?: string };
111
+ if (!data.ok) throw new Error(`Telegram API: ${data.description}`);
112
+ return data.result;
113
+ }
114
+
115
+ private split(text: string, max: number): string[] {
116
+ if (text.length <= max) return [text];
117
+ const chunks: string[] = [];
118
+ let rest = text;
119
+ while (rest.length > 0) { chunks.push(rest.slice(0, max)); rest = rest.slice(max); }
120
+ return chunks;
121
+ }
122
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @module channels/web
3
+ *
4
+ * HTTP channel — exposes the agent as a REST API with SSE streaming.
5
+ *
6
+ * Used automatically when you call:
7
+ * ```ts
8
+ * agent.connectChannel('web', { port: 3000 });
9
+ * ```
10
+ *
11
+ * Or mount it on SvaraApp/Express:
12
+ * ```ts
13
+ * app.route('/chat', agent.handler());
14
+ * ```
15
+ *
16
+ * API:
17
+ * POST /chat — { message, sessionId? } → { response, sessionId, usage }
18
+ * GET /health — { status: 'ok' }
19
+ */
20
+
21
+ import express, { type Express } from 'express';
22
+ import type { SvaraAgent, SvaraChannel } from '../core/agent.js';
23
+ import type { IncomingMessage, ChannelName } from '../core/types.js';
24
+
25
+ export interface WebChannelConfig {
26
+ port?: number; // default 3000
27
+ cors?: boolean | string;
28
+ apiKey?: string;
29
+ path?: string; // base path prefix, default ''
30
+ }
31
+
32
+ export class WebChannel implements SvaraChannel {
33
+ readonly name: ChannelName = 'web';
34
+
35
+ private app: Express;
36
+ private server: ReturnType<Express['listen']> | null = null;
37
+ private agent!: SvaraAgent;
38
+
39
+ constructor(private config: WebChannelConfig = {}) {
40
+ this.app = this.buildApp();
41
+ }
42
+
43
+ async mount(agent: SvaraAgent): Promise<void> {
44
+ this.agent = agent;
45
+ this.attachRoutes();
46
+
47
+ const port = this.config.port ?? 3000;
48
+ return new Promise((resolve, reject) => {
49
+ this.server = this.app.listen(port, () => {
50
+ console.log(`[@yesvara/svara] Web channel running at http://localhost:${port}`);
51
+ resolve();
52
+ });
53
+ this.server.on('error', reject);
54
+ });
55
+ }
56
+
57
+ async send(_sessionId: string, _text: string): Promise<void> {
58
+ // Push-based sending is handled via SSE in the /chat/stream route
59
+ }
60
+
61
+ async stop(): Promise<void> {
62
+ return new Promise((resolve) => {
63
+ this.server?.close(() => resolve()) ?? resolve();
64
+ });
65
+ }
66
+
67
+ // ─── Private ───────────────────────────────────────────────────────────────
68
+
69
+ private buildApp(): Express {
70
+ const app = express();
71
+ app.use(express.json({ limit: '10mb' }));
72
+
73
+ if (this.config.cors) {
74
+ const origin = this.config.cors === true ? '*' : this.config.cors;
75
+ app.use((_req, res, next) => {
76
+ res.setHeader('Access-Control-Allow-Origin', origin as string);
77
+ res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
78
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
79
+ next();
80
+ });
81
+ app.options('*', (_req, res) => res.sendStatus(204));
82
+ }
83
+
84
+ if (this.config.apiKey) {
85
+ app.use((req, res, next) => {
86
+ const token = req.headers.authorization?.replace('Bearer ', '');
87
+ if (token !== this.config.apiKey) {
88
+ res.status(401).json({ error: 'Unauthorized' });
89
+ return;
90
+ }
91
+ next();
92
+ });
93
+ }
94
+
95
+ return app;
96
+ }
97
+
98
+ private attachRoutes(): void {
99
+ const base = this.config.path ?? '';
100
+
101
+ this.app.get(`${base}/health`, (_req, res) => {
102
+ res.json({ status: 'ok', agent: this.agent.name, timestamp: new Date().toISOString() });
103
+ });
104
+
105
+ this.app.post(`${base}/chat`, this.agent.handler());
106
+ }
107
+
108
+ private buildMessage(body: { message: string; sessionId?: string; userId?: string }): IncomingMessage {
109
+ return {
110
+ id: crypto.randomUUID(),
111
+ sessionId: body.sessionId ?? crypto.randomUUID(),
112
+ userId: body.userId ?? 'web-user',
113
+ channel: 'web',
114
+ text: body.message,
115
+ timestamp: new Date(),
116
+ };
117
+ }
118
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @module channels/whatsapp
3
+ *
4
+ * WhatsApp Business API channel (Meta Cloud API).
5
+ * Used when you call: `agent.connectChannel('whatsapp', { ... })`
6
+ *
7
+ * Requires the 'web' channel to be mounted first (it shares the Express server).
8
+ * Webhook endpoint: POST /whatsapp/webhook
9
+ */
10
+
11
+ import type { SvaraAgent, SvaraChannel } from '../core/agent.js';
12
+ import type { IncomingMessage, ChannelName } from '../core/types.js';
13
+ import type express from 'express';
14
+
15
+ export interface WhatsAppChannelConfig {
16
+ /** WhatsApp Cloud API access token */
17
+ token: string;
18
+ /** Your WhatsApp Phone Number ID */
19
+ phoneId: string;
20
+ /** Token to verify the webhook with Meta */
21
+ verifyToken: string;
22
+ /** API version, default 'v19.0' */
23
+ apiVersion?: string;
24
+ }
25
+
26
+ interface WABody {
27
+ object: string;
28
+ entry: Array<{
29
+ changes: Array<{
30
+ value: {
31
+ messages?: Array<{
32
+ id: string; from: string; timestamp: string;
33
+ type: string; text?: { body: string };
34
+ }>;
35
+ };
36
+ }>;
37
+ }>;
38
+ }
39
+
40
+ export class WhatsAppChannel implements SvaraChannel {
41
+ readonly name: ChannelName = 'whatsapp';
42
+
43
+ private agent!: SvaraAgent;
44
+ private readonly apiUrl: string;
45
+
46
+ constructor(private config: WhatsAppChannelConfig) {
47
+ if (!config.token || !config.phoneId || !config.verifyToken) {
48
+ throw new Error(
49
+ '[@yesvara/svara] WhatsApp requires: token, phoneId, and verifyToken.'
50
+ );
51
+ }
52
+ const version = config.apiVersion ?? 'v19.0';
53
+ this.apiUrl = `https://graph.facebook.com/${version}/${config.phoneId}`;
54
+ }
55
+
56
+ async mount(agent: SvaraAgent): Promise<void> {
57
+ this.agent = agent;
58
+
59
+ // Get the web channel's Express app to attach webhook routes
60
+ const webChannel = (agent as unknown as {
61
+ channels: Map<string, { app?: express.Application }>;
62
+ }).channels?.get('web');
63
+
64
+ const app = (webChannel as { app?: { post: (...a: unknown[]) => void; get: (...a: unknown[]) => void } })?.app;
65
+
66
+ if (!app) {
67
+ console.warn(
68
+ '[@yesvara/svara] WhatsApp: no "web" channel found. ' +
69
+ 'Add connectChannel("web", ...) before connectChannel("whatsapp", ...) ' +
70
+ 'so the webhook can be mounted.'
71
+ );
72
+ return;
73
+ }
74
+
75
+ // Webhook verification
76
+ app.get('/whatsapp/webhook', (req: { query: Record<string, string> }, res: { status: (n: number) => { send: (s: unknown) => void } }) => {
77
+ const { 'hub.mode': mode, 'hub.verify_token': token, 'hub.challenge': challenge } = req.query;
78
+ if (mode === 'subscribe' && token === this.config.verifyToken) {
79
+ console.log('[@yesvara/svara] WhatsApp webhook verified.');
80
+ res.status(200).send(challenge);
81
+ } else {
82
+ res.status(403).send('Forbidden');
83
+ }
84
+ });
85
+
86
+ // Incoming messages
87
+ app.post('/whatsapp/webhook', async (req: { body: WABody }, res: { sendStatus: (n: number) => void }) => {
88
+ res.sendStatus(200); // ack immediately
89
+ if (req.body.object !== 'whatsapp_business_account') return;
90
+
91
+ for (const entry of req.body.entry) {
92
+ for (const change of entry.changes) {
93
+ for (const waMsg of change.value.messages ?? []) {
94
+ if (waMsg.type !== 'text' || !waMsg.text?.body) continue;
95
+ await this.handle(waMsg).catch((err: Error) =>
96
+ console.error('[@yesvara/svara] WhatsApp error:', err.message)
97
+ );
98
+ }
99
+ }
100
+ }
101
+ });
102
+
103
+ console.log('[@yesvara/svara] WhatsApp webhook mounted at /whatsapp/webhook');
104
+ }
105
+
106
+ async send(to: string, text: string): Promise<void> {
107
+ await this.sendMessage(to, text);
108
+ }
109
+
110
+ async stop(): Promise<void> { /* HTTP-based, no persistent connection */ }
111
+
112
+ private async handle(waMsg: { id: string; from: string; timestamp: string; text?: { body: string } }): Promise<void> {
113
+ const message: IncomingMessage = {
114
+ id: waMsg.id,
115
+ sessionId: waMsg.from,
116
+ userId: waMsg.from,
117
+ channel: 'whatsapp',
118
+ text: waMsg.text?.body ?? '',
119
+ timestamp: new Date(parseInt(waMsg.timestamp) * 1000),
120
+ raw: waMsg,
121
+ };
122
+
123
+ try {
124
+ const result = await this.agent.receive(message);
125
+ for (const chunk of this.split(result.response, 4000)) {
126
+ await this.sendMessage(waMsg.from, chunk);
127
+ }
128
+ } catch (err) {
129
+ await this.sendMessage(waMsg.from, 'Sorry, something went wrong. Please try again.');
130
+ throw err;
131
+ }
132
+ }
133
+
134
+ private async sendMessage(to: string, text: string): Promise<void> {
135
+ const res = await fetch(`${this.apiUrl}/messages`, {
136
+ method: 'POST',
137
+ headers: {
138
+ Authorization: `Bearer ${this.config.token}`,
139
+ 'Content-Type': 'application/json',
140
+ },
141
+ body: JSON.stringify({
142
+ messaging_product: 'whatsapp',
143
+ to,
144
+ type: 'text',
145
+ text: { body: text },
146
+ }),
147
+ });
148
+ if (!res.ok) {
149
+ const err = await res.json() as { error?: { message: string } };
150
+ throw new Error(`WhatsApp API: ${err.error?.message}`);
151
+ }
152
+ }
153
+
154
+ private split(text: string, max: number): string[] {
155
+ if (text.length <= max) return [text];
156
+ const chunks: string[] = [];
157
+ let rest = text;
158
+ while (rest.length > 0) { chunks.push(rest.slice(0, max)); rest = rest.slice(max); }
159
+ return chunks;
160
+ }
161
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @module cli/commands/dev
3
+ * SvaraJS — `svara dev` command
4
+ *
5
+ * Starts a development server with hot-reload and a pretty REPL.
6
+ * Wraps tsx watch for TypeScript + restarts on file changes.
7
+ */
8
+
9
+ import { spawn } from 'child_process';
10
+ import path from 'path';
11
+ import fs from 'fs';
12
+
13
+ interface DevOptions {
14
+ entry?: string;
15
+ port?: number;
16
+ }
17
+
18
+ export async function devServer(options: DevOptions = {}): Promise<void> {
19
+ const entry = options.entry ?? findEntry();
20
+
21
+ if (!entry) {
22
+ console.error(`
23
+ āŒ Could not find entry file.
24
+
25
+ Create src/index.ts or specify one:
26
+ svara dev --entry src/app.ts
27
+ `);
28
+ process.exit(1);
29
+ }
30
+
31
+ console.log(`
32
+ šŸš€ SvaraJS Dev Server
33
+ ─────────────────────
34
+ Entry: ${entry}
35
+ Watch: enabled
36
+
37
+ `);
38
+
39
+ const runner = spawn(
40
+ 'npx',
41
+ ['tsx', 'watch', '--clear-screen=false', entry],
42
+ {
43
+ stdio: 'inherit',
44
+ env: {
45
+ ...process.env,
46
+ NODE_ENV: 'development',
47
+ ...(options.port ? { PORT: String(options.port) } : {}),
48
+ },
49
+ }
50
+ );
51
+
52
+ runner.on('error', (err) => {
53
+ console.error('\nāŒ Dev server error:', err.message);
54
+ console.error('Make sure "tsx" is installed: npm install -D tsx');
55
+ });
56
+
57
+ runner.on('exit', (code) => {
58
+ if (code !== 0 && code !== null) {
59
+ process.exit(code);
60
+ }
61
+ });
62
+
63
+ // Forward signals
64
+ for (const signal of ['SIGINT', 'SIGTERM'] as const) {
65
+ process.on(signal, () => {
66
+ runner.kill(signal);
67
+ process.exit(0);
68
+ });
69
+ }
70
+ }
71
+
72
+ function findEntry(): string | null {
73
+ const candidates = [
74
+ 'src/index.ts',
75
+ 'src/app.ts',
76
+ 'src/main.ts',
77
+ 'index.ts',
78
+ ];
79
+
80
+ for (const candidate of candidates) {
81
+ if (fs.existsSync(path.resolve(process.cwd(), candidate))) {
82
+ return candidate;
83
+ }
84
+ }
85
+
86
+ return null;
87
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @module cli/commands/new
3
+ * SvaraJS — `svara new <project-name>` command
4
+ *
5
+ * Scaffolds a new SvaraJS project with opinionated defaults.
6
+ * Creates a ready-to-run project in seconds.
7
+ */
8
+
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+ import { execSync } from 'child_process';
12
+
13
+ interface ScaffoldOptions {
14
+ name: string;
15
+ provider?: 'openai' | 'anthropic' | 'ollama';
16
+ channels?: string[];
17
+ installDeps?: boolean;
18
+ }
19
+
20
+ export async function newProject(options: ScaffoldOptions): Promise<void> {
21
+ const { name, provider = 'openai', channels = ['web'] } = options;
22
+ const targetDir = path.resolve(process.cwd(), name);
23
+
24
+ console.log(`\n✨ Creating SvaraJS project: ${name}\n`);
25
+
26
+ // Check if directory already exists
27
+ try {
28
+ await fs.access(targetDir);
29
+ console.error(`āŒ Directory "${name}" already exists.`);
30
+ process.exit(1);
31
+ } catch {
32
+ // Good — it doesn't exist
33
+ }
34
+
35
+ await fs.mkdir(targetDir, { recursive: true });
36
+ await fs.mkdir(path.join(targetDir, 'src'), { recursive: true });
37
+ await fs.mkdir(path.join(targetDir, 'docs'), { recursive: true });
38
+ await fs.mkdir(path.join(targetDir, 'data'), { recursive: true });
39
+
40
+ const files: Record<string, string> = {
41
+ 'package.json': generatePackageJson(name),
42
+ 'tsconfig.json': generateTsConfig(),
43
+ '.env.example': generateEnvExample(provider, channels),
44
+ '.gitignore': generateGitignore(),
45
+ 'src/index.ts': generateIndexFile(name, provider, channels),
46
+ 'docs/README.md': `# ${name} Knowledge Base\n\nAdd your documents here for RAG.\n`,
47
+ };
48
+
49
+ for (const [filePath, content] of Object.entries(files)) {
50
+ const fullPath = path.join(targetDir, filePath);
51
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
52
+ await fs.writeFile(fullPath, content, 'utf-8');
53
+ console.log(` āœ“ ${filePath}`);
54
+ }
55
+
56
+ if (options.installDeps !== false) {
57
+ console.log('\nšŸ“¦ Installing dependencies...\n');
58
+ try {
59
+ execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
60
+ } catch {
61
+ console.warn('\nāš ļø Dependency install failed. Run "npm install" manually.\n');
62
+ }
63
+ }
64
+
65
+ console.log(`
66
+ āœ… Project ready!
67
+
68
+ cd ${name}
69
+ cp .env.example .env # Add your API keys
70
+ npm run dev # Start the agent
71
+
72
+ šŸ“š Docs: https://svarajs.dev
73
+ `);
74
+ }
75
+
76
+ // ─── File Templates ────────────────────────────────────────────────────────────
77
+
78
+ function generatePackageJson(name: string): string {
79
+ return JSON.stringify({
80
+ name,
81
+ version: '0.1.0',
82
+ private: true,
83
+ scripts: {
84
+ dev: 'tsx watch src/index.ts',
85
+ build: 'tsc',
86
+ start: 'node dist/index.js',
87
+ },
88
+ dependencies: {
89
+ svarajs: 'latest',
90
+ dotenv: '^16.4.5',
91
+ },
92
+ devDependencies: {
93
+ '@types/node': '^20.14.2',
94
+ tsx: '^4.15.7',
95
+ typescript: '^5.4.5',
96
+ },
97
+ }, null, 2);
98
+ }
99
+
100
+ function generateTsConfig(): string {
101
+ return JSON.stringify({
102
+ compilerOptions: {
103
+ target: 'ES2022',
104
+ module: 'CommonJS',
105
+ moduleResolution: 'bundler',
106
+ outDir: 'dist',
107
+ rootDir: 'src',
108
+ strict: true,
109
+ esModuleInterop: true,
110
+ skipLibCheck: true,
111
+ },
112
+ include: ['src/**/*'],
113
+ exclude: ['node_modules', 'dist'],
114
+ }, null, 2);
115
+ }
116
+
117
+ function generateEnvExample(
118
+ provider: string,
119
+ channels: string[]
120
+ ): string {
121
+ const lines = ['# SvaraJS Environment Variables', ''];
122
+
123
+ if (provider === 'openai') {
124
+ lines.push('# OpenAI', 'OPENAI_API_KEY=sk-...', '');
125
+ } else if (provider === 'anthropic') {
126
+ lines.push('# Anthropic', 'ANTHROPIC_API_KEY=sk-ant-...', '');
127
+ }
128
+
129
+ if (channels.includes('telegram')) {
130
+ lines.push('# Telegram', 'TELEGRAM_BOT_TOKEN=...', '');
131
+ }
132
+ if (channels.includes('whatsapp')) {
133
+ lines.push('# WhatsApp', 'WA_ACCESS_TOKEN=...', 'WA_PHONE_ID=...', 'WA_VERIFY_TOKEN=...', '');
134
+ }
135
+
136
+ return lines.join('\n');
137
+ }
138
+
139
+ function generateIndexFile(
140
+ name: string,
141
+ provider: string,
142
+ channels: string[]
143
+ ): string {
144
+ const modelMap: Record<string, string> = {
145
+ openai: 'gpt-4o',
146
+ anthropic: 'claude-opus-4-6',
147
+ ollama: 'llama3',
148
+ };
149
+
150
+ const channelSetup = channels
151
+ .map((ch) => {
152
+ if (ch === 'web') return `agent.use('web', { port: 3000, cors: true });`;
153
+ if (ch === 'telegram') return `agent.use('telegram', { token: process.env.TELEGRAM_BOT_TOKEN! });`;
154
+ if (ch === 'whatsapp') return [
155
+ `agent.use('whatsapp', {`,
156
+ ` token: process.env.WA_ACCESS_TOKEN!,`,
157
+ ` phoneId: process.env.WA_PHONE_ID!,`,
158
+ ` verifyToken: process.env.WA_VERIFY_TOKEN!,`,
159
+ `});`,
160
+ ].join('\n');
161
+ return '';
162
+ })
163
+ .filter(Boolean)
164
+ .join('\n');
165
+
166
+ return `import 'dotenv/config';
167
+ import { svara } from 'svarajs';
168
+
169
+ /**
170
+ * ${name} — powered by SvaraJS
171
+ */
172
+ const agent = svara({
173
+ llm: {
174
+ provider: '${provider}',
175
+ model: '${modelMap[provider] ?? 'gpt-4o'}',
176
+ },
177
+ systemPrompt: \`You are a helpful AI assistant called ${name}.
178
+ Be concise, friendly, and always try your best to help.\`,
179
+ memory: {
180
+ type: 'conversation',
181
+ maxMessages: 20,
182
+ },
183
+ verbose: true,
184
+ });
185
+
186
+ // ── Tools ──────────────────────────────────────────────────────────────────
187
+ agent.tool('get_time', {
188
+ description: 'Get the current date and time',
189
+ parameters: {},
190
+ execute: async () => ({
191
+ datetime: new Date().toISOString(),
192
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
193
+ }),
194
+ });
195
+
196
+ // ── Channels ────────────────────────────────────────────────────────────────
197
+ ${channelSetup}
198
+
199
+ // ── Start ────────────────────────────────────────────────────────────────────
200
+ agent.start().then(() => {
201
+ console.log('šŸš€ ${name} is running!');
202
+ });
203
+ `;
204
+ }
205
+
206
+ function generateGitignore(): string {
207
+ return `node_modules/
208
+ dist/
209
+ .env
210
+ data/*.db
211
+ *.log
212
+ `;
213
+ }