@agentuity/cli 3.0.0-alpha.0 → 3.0.0-alpha.2

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 (40) hide show
  1. package/dist/cmd/project/frameworks.d.ts +13 -13
  2. package/dist/cmd/project/frameworks.d.ts.map +1 -1
  3. package/dist/cmd/project/frameworks.js +26 -21
  4. package/dist/cmd/project/frameworks.js.map +1 -1
  5. package/dist/cmd/project/scaffold.d.ts.map +1 -1
  6. package/dist/cmd/project/scaffold.js +16 -26
  7. package/dist/cmd/project/scaffold.js.map +1 -1
  8. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  9. package/dist/cmd/project/template-flow.js +4 -1
  10. package/dist/cmd/project/template-flow.js.map +1 -1
  11. package/package.json +7 -7
  12. package/src/cmd/project/frameworks.ts +31 -47
  13. package/src/cmd/project/scaffold.ts +17 -29
  14. package/src/cmd/project/template-flow.ts +3 -1
  15. package/src/cmd/project/templates/astro/src/pages/api/translate.ts +22 -0
  16. package/src/cmd/project/templates/astro/src/pages/index.astro +160 -0
  17. package/src/cmd/project/templates/hono/src/index.ts +103 -0
  18. package/src/cmd/project/templates/nextjs/src/app/api/translate/route.ts +19 -0
  19. package/src/cmd/project/templates/nextjs/src/app/globals.css +74 -0
  20. package/src/cmd/project/templates/nextjs/src/app/page.tsx +234 -0
  21. package/src/cmd/project/templates/nuxt/app.vue +191 -0
  22. package/src/cmd/project/templates/nuxt/server/api/translate.post.ts +18 -0
  23. package/src/cmd/project/templates/remix/app/routes/api.translate.ts +24 -0
  24. package/src/cmd/project/templates/remix/app/routes/home.tsx +241 -0
  25. package/src/cmd/project/templates/sveltekit/src/routes/+page.server.ts +24 -0
  26. package/src/cmd/project/templates/sveltekit/src/routes/+page.svelte +204 -0
  27. package/src/cmd/project/templates/vite-react/server.ts +39 -0
  28. package/src/cmd/project/templates/vite-react/src/App.tsx +241 -0
  29. package/src/cmd/project/templates/vite-react/src/index.css +31 -0
  30. package/src/cmd/project/templates/vite-react/src/main.tsx +15 -0
  31. package/dist/cmd/project/frameworks-ai-examples.d.ts +0 -15
  32. package/dist/cmd/project/frameworks-ai-examples.d.ts.map +0 -1
  33. package/dist/cmd/project/frameworks-ai-examples.js +0 -160
  34. package/dist/cmd/project/frameworks-ai-examples.js.map +0 -1
  35. package/dist/cmd/project/frameworks-landing-pages.d.ts +0 -17
  36. package/dist/cmd/project/frameworks-landing-pages.d.ts.map +0 -1
  37. package/dist/cmd/project/frameworks-landing-pages.js +0 -242
  38. package/dist/cmd/project/frameworks-landing-pages.js.map +0 -1
  39. package/src/cmd/project/frameworks-ai-examples.ts +0 -166
  40. package/src/cmd/project/frameworks-landing-pages.ts +0 -267
@@ -11,6 +11,7 @@ import { join } from 'node:path';
11
11
  import type { Logger } from '@agentuity/core';
12
12
  import * as tui from '../../tui';
13
13
  import type { FrameworkScaffold } from './frameworks';
14
+ import { applyOverlay } from './frameworks';
14
15
 
15
16
  interface ScaffoldOptions {
16
17
  /** Absolute path to the target directory */
@@ -73,7 +74,7 @@ export async function scaffoldFramework(options: ScaffoldOptions): Promise<void>
73
74
  *
74
75
  * - Adds @agentuity/cli as devDependency
75
76
  * - Merges deploy/build scripts into package.json
76
- * - Adds AI example files if requested
77
+ * - Applies template overlay (AI example files + landing page)
77
78
  * - Generates AGENTS.md documentation
78
79
  */
79
80
  async function augmentProject(
@@ -89,35 +90,22 @@ async function augmentProject(
89
90
  callback: async (progress) => {
90
91
  // Step 1: Merge package.json
91
92
  await mergePackageJson(dest, framework);
92
- progress(40);
93
-
94
- // Step 2: Add AI example files
95
- if (includeAiExample && framework.aiExample) {
96
- const files = framework.aiExample();
97
- for (const [relativePath, content] of Object.entries(files)) {
98
- const filePath = join(dest, relativePath);
99
- const dir = join(filePath, '..');
100
- mkdirSync(dir, { recursive: true });
101
- await Bun.write(filePath, content);
102
- logger.debug('Created AI example: %s', relativePath);
103
- }
104
- }
105
- progress(50);
106
-
107
- // Step 3: Replace default landing page with Agentuity-branded page
108
- if (framework.landingPage) {
109
- const files = framework.landingPage();
110
- for (const [relativePath, content] of Object.entries(files)) {
111
- const filePath = join(dest, relativePath);
112
- const dir = join(filePath, '..');
113
- mkdirSync(dir, { recursive: true });
114
- await Bun.write(filePath, content);
115
- logger.debug('Created landing page: %s', relativePath);
93
+ progress(25);
94
+
95
+ // Step 2: Apply template overlay if configured
96
+ if (framework.overlayDir) {
97
+ if (includeAiExample) {
98
+ applyOverlay(dest, framework.overlayDir);
99
+ logger.debug('Applied template overlay: %s', framework.overlayDir);
100
+ } else {
101
+ // When AI example is not requested, we still want the landing page
102
+ // but without the API route. For now, skip the entire overlay.
103
+ logger.debug('Skipped template overlay (AI example not requested)');
116
104
  }
117
105
  }
118
106
  progress(75);
119
107
 
120
- // Step 4: Add .gitignore entries
108
+ // Step 3: Add .gitignore entries
121
109
  await appendGitignore(dest);
122
110
  progress(100);
123
111
  },
@@ -137,13 +125,13 @@ async function mergePackageJson(dest: string, framework: FrameworkScaffold): Pro
137
125
  pkg.scripts = pkg.scripts ?? {};
138
126
 
139
127
  // Add @agentuity/cli as devDependency
140
- pkg.devDependencies['@agentuity/cli'] = 'latest';
128
+ pkg.devDependencies['@agentuity/cli'] = '^3.0.0';
141
129
 
142
130
  // Add framework-specific dependencies
143
131
  if (framework.dependencies) {
144
132
  for (const dep of framework.dependencies) {
145
133
  if (!pkg.dependencies[dep]) {
146
- pkg.dependencies[dep] = 'latest';
134
+ pkg.dependencies[dep] = dep.startsWith('@agentuity/') ? '^3.0.0' : 'latest';
147
135
  }
148
136
  }
149
137
  }
@@ -152,7 +140,7 @@ async function mergePackageJson(dest: string, framework: FrameworkScaffold): Pro
152
140
  if (framework.devDependencies) {
153
141
  for (const dep of framework.devDependencies) {
154
142
  if (!pkg.devDependencies[dep]) {
155
- pkg.devDependencies[dep] = 'latest';
143
+ pkg.devDependencies[dep] = dep.startsWith('@agentuity/') ? '^3.0.0' : 'latest';
156
144
  }
157
145
  }
158
146
  }
@@ -242,11 +242,13 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<CreateF
242
242
 
243
243
  // Step 3: Ask about AI example
244
244
  let includeAiExample = true;
245
- if (isInteractive && selectedFramework.aiExample) {
245
+ if (isInteractive && selectedFramework.overlayDir) {
246
246
  includeAiExample = await prompt.confirm({
247
247
  message: 'Include an AI example? (OpenAI API route)',
248
248
  initial: true,
249
249
  });
250
+ } else if (!selectedFramework.overlayDir) {
251
+ includeAiExample = false;
250
252
  }
251
253
 
252
254
  // Step 4: Scaffold the framework
@@ -0,0 +1,22 @@
1
+ import type { APIRoute } from 'astro';
2
+ import { generateText } from 'ai';
3
+ import { openai } from '@ai-sdk/openai';
4
+
5
+ export const POST: APIRoute = async ({ request }) => {
6
+ const { text, toLanguage, model = 'gpt-4o-mini' } = await request.json();
7
+
8
+ const { text: translation, usage } = await generateText({
9
+ model: openai(model),
10
+ prompt: `Translate the following text to ${toLanguage}. Return only the translation, nothing else.\n\n${text}`,
11
+ });
12
+
13
+ return new Response(
14
+ JSON.stringify({
15
+ translation,
16
+ tokens: usage?.totalTokens ?? 0,
17
+ model,
18
+ toLanguage,
19
+ }),
20
+ { headers: { 'Content-Type': 'application/json' } },
21
+ );
22
+ };
@@ -0,0 +1,160 @@
1
+ ---
2
+ const LANGUAGES = ['Spanish', 'French', 'German', 'Chinese'] as const;
3
+ const MODELS = ['gpt-4o-mini', 'gpt-4o', 'gpt-4.1-nano'] as const;
4
+ const DEFAULT_TEXT =
5
+ 'Welcome to Agentuity! This translation demo shows what you can build with the platform. It connects to AI models through our gateway — no separate API keys needed. Try translating this text into different languages to see it in action.';
6
+ ---
7
+
8
+ <html lang="en">
9
+ <head>
10
+ <meta charset="utf-8" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <title>Agentuity + Astro</title>
13
+ <link
14
+ href="https://cdn.jsdelivr.net/npm/tailwindcss@4/@tailwindcss/postcss@4/index.min.css"
15
+ rel="stylesheet"
16
+ />
17
+ </head>
18
+ <body style="background-color: oklch(0.141 0.005 285.823); margin: 0; font-family: system-ui, -apple-system, sans-serif;">
19
+ <div class="flex min-h-screen justify-center font-sans text-white">
20
+ <div class="flex w-full max-w-3xl flex-col gap-4 p-16">
21
+ <!-- Header -->
22
+ <div class="relative mb-8 flex flex-col items-center justify-center gap-2 text-center">
23
+ <svg
24
+ aria-hidden="true"
25
+ class="mb-4 h-auto w-12"
26
+ fill="none"
27
+ height="191"
28
+ viewBox="0 0 220 191"
29
+ width="220"
30
+ xmlns="http://www.w3.org/2000/svg"
31
+ >
32
+ <path
33
+ clip-rule="evenodd"
34
+ d="M220 191H0L31.427 136.5H0L8 122.5H180.5L220 191ZM47.5879 136.5L24.2339 177H195.766L172.412 136.5H47.5879Z"
35
+ fill="var(--color-cyan-500)"
36
+ fill-rule="evenodd"
37
+ />
38
+ <path
39
+ clip-rule="evenodd"
40
+ d="M110 0L157.448 82.5H189L197 96.5H54.5L110 0ZM78.7021 82.5L110 28.0811L141.298 82.5H78.7021Z"
41
+ fill="var(--color-cyan-500)"
42
+ fill-rule="evenodd"
43
+ />
44
+ </svg>
45
+ <h1 class="text-5xl font-thin">Welcome to Agentuity</h1>
46
+ <p class="text-lg text-gray-400">
47
+ <span class="font-serif italic">Astro</span> + AI Gateway
48
+ </p>
49
+ </div>
50
+
51
+ <!-- Translate Form -->
52
+ <div class="flex flex-col gap-6 rounded-lg border border-gray-900 bg-black p-8 text-gray-400 shadow-2xl" id="form-container">
53
+ <div class="flex flex-wrap items-center gap-1.5">
54
+ Translate to
55
+ <select id="toLanguage" class="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400">
56
+ {LANGUAGES.map((lang) => <option value={lang}>{lang}</option>)}
57
+ </select>
58
+ using
59
+ <select id="model" class="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400">
60
+ {MODELS.map((m) => <option value={m}>{m}</option>)}
61
+ </select>
62
+ <div class="group relative z-0 ml-auto">
63
+ <div class="absolute inset-0 rounded-lg bg-linear-to-r from-cyan-700 via-blue-500 to-purple-600 opacity-75 blur-xl transition-all duration-700 group-hover:opacity-100 group-hover:blur-2xl" />
64
+ <div class="absolute inset-0 rounded-lg bg-cyan-500/50 opacity-50 blur-3xl" />
65
+ <button
66
+ id="translate-btn"
67
+ class="relative cursor-pointer rounded-lg bg-gray-950 px-4 py-2 font-semibold text-white shadow-2xl disabled:cursor-not-allowed disabled:opacity-50"
68
+ type="button"
69
+ >
70
+ Translate
71
+ </button>
72
+ </div>
73
+ </div>
74
+
75
+ <textarea
76
+ id="text-input"
77
+ class="z-10 min-h-28 resize-y rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-white focus:outline-2 focus:outline-offset-2 focus:outline-cyan-500"
78
+ placeholder="Enter text to translate..."
79
+ rows="4"
80
+ >{DEFAULT_TEXT}</textarea>
81
+
82
+ <div id="result" class="output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600">
83
+ Translation will appear here
84
+ </div>
85
+ </div>
86
+
87
+ <!-- How it works -->
88
+ <div class="rounded-lg border border-gray-900 bg-black p-8">
89
+ <h3 class="m-0 mb-6 text-xl font-normal leading-none text-white">How it works</h3>
90
+ <div class="flex flex-col gap-6">
91
+ <div class="flex items-start gap-3">
92
+ <div class="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950">
93
+ <svg aria-hidden="true" class="size-2.5" fill="none" height="24" stroke="var(--color-green-500)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M20 6 9 17l-5-5" /></svg>
94
+ </div>
95
+ <div>
96
+ <h4 class="-mt-0.5 mb-0.5 text-sm font-normal text-white">AI Gateway routing</h4>
97
+ <p class="text-xs text-gray-400"><code class="text-white">agentuity dev</code> automatically sets OPENAI_API_KEY and OPENAI_BASE_URL so the AI SDK routes through the Agentuity gateway.</p>
98
+ </div>
99
+ </div>
100
+ <div class="flex items-start gap-3">
101
+ <div class="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950">
102
+ <svg aria-hidden="true" class="size-2.5" fill="none" height="24" stroke="var(--color-green-500)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M20 6 9 17l-5-5" /></svg>
103
+ </div>
104
+ <div>
105
+ <h4 class="-mt-0.5 mb-0.5 text-sm font-normal text-white">API endpoints</h4>
106
+ <p class="text-xs text-gray-400">Edit <code class="text-white">src/pages/api/translate.ts</code> to change the AI model, prompt, or add new endpoints.</p>
107
+ </div>
108
+ </div>
109
+ <div class="flex items-start gap-3">
110
+ <div class="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950">
111
+ <svg aria-hidden="true" class="size-2.5" fill="none" height="24" stroke="var(--color-green-500)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M20 6 9 17l-5-5" /></svg>
112
+ </div>
113
+ <div>
114
+ <h4 class="-mt-0.5 mb-0.5 text-sm font-normal text-white">Astro</h4>
115
+ <p class="text-xs text-gray-400">Add pages in <code class="text-white">src/pages/</code> — content-focused framework with island architecture.</p>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <script>
124
+ const btn = document.getElementById('translate-btn')!;
125
+ const textInput = document.getElementById('text-input')! as HTMLTextAreaElement;
126
+ const toLangSelect = document.getElementById('toLanguage')! as HTMLSelectElement;
127
+ const modelSelect = document.getElementById('model')! as HTMLSelectElement;
128
+ const resultDiv = document.getElementById('result')!;
129
+
130
+ btn.addEventListener('click', async () => {
131
+ const text = textInput.value;
132
+ const toLanguage = toLangSelect.value;
133
+ const model = modelSelect.value;
134
+
135
+ btn.textContent = 'Translating';
136
+ btn.setAttribute('disabled', '');
137
+ resultDiv.textContent = '';
138
+ resultDiv.className = 'rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600';
139
+
140
+ try {
141
+ const res = await fetch('/api/translate', {
142
+ method: 'POST',
143
+ headers: { 'Content-Type': 'application/json' },
144
+ body: JSON.stringify({ text, toLanguage, model }),
145
+ });
146
+ if (!res.ok) throw new Error(`API error ${res.status}`);
147
+ const data = await res.json();
148
+ resultDiv.className = 'output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-cyan-500';
149
+ resultDiv.innerHTML = `${data.translation}<div class="flex gap-4 text-xs text-gray-500 mt-3">${data.tokens > 0 ? `<span>Tokens <strong class="text-gray-400">${data.tokens}</strong></span>` : ''}<span>Model <strong class="text-gray-400">${data.model}</strong></span><span>Language <strong class="text-gray-400">${data.toLanguage}</strong></span></div>`;
150
+ } catch (err) {
151
+ resultDiv.className = 'rounded-md border border-red-800 bg-red-950 px-4 py-3 text-sm text-red-400';
152
+ resultDiv.textContent = err instanceof Error ? err.message : 'Translation failed';
153
+ } finally {
154
+ btn.textContent = 'Translate';
155
+ btn.removeAttribute('disabled');
156
+ }
157
+ });
158
+ </script>
159
+ </body>
160
+ </html>
@@ -0,0 +1,103 @@
1
+ import { Hono } from 'hono';
2
+ import { generateText } from 'ai';
3
+ import { openai } from '@ai-sdk/openai';
4
+
5
+ const app = new Hono();
6
+
7
+ // API route
8
+ app.post('/api/translate', async (c) => {
9
+ const { text, toLanguage, model = 'gpt-4o-mini' } = await c.req.json();
10
+
11
+ const { text: translation, usage } = await generateText({
12
+ model: openai(model),
13
+ prompt: `Translate the following text to ${toLanguage}. Return only the translation, nothing else.\n\n${text}`,
14
+ });
15
+
16
+ return c.json({
17
+ translation,
18
+ tokens: usage?.totalTokens ?? 0,
19
+ model,
20
+ toLanguage,
21
+ });
22
+ });
23
+
24
+ // Landing page
25
+ app.get('/', (c) => {
26
+ return c.html(`<!DOCTYPE html>
27
+ <html lang="en">
28
+ <head>
29
+ <meta charset="utf-8" />
30
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
31
+ <title>Agentuity + Hono</title>
32
+ <link href="https://cdn.jsdelivr.net/npm/tailwindcss@4/@tailwindcss/postcss@4/index.min.css" rel="stylesheet" />
33
+ <style>
34
+ body { background-color: oklch(0.141 0.005 285.823); margin: 0; font-family: system-ui, -apple-system, sans-serif; }
35
+ [data-loading="true"]::after { content: '.'; display: inline-block; width: 1rem; animation: ellipsis 1.5s steps(4, end) infinite; text-align: left; }
36
+ @keyframes ellipsis { 0% { content: '.'; } 25% { content: '..'; } 50% { content: '...'; } 75% { content: ''; } }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <div class="flex min-h-screen justify-center font-sans text-white">
41
+ <div class="flex w-full max-w-3xl flex-col gap-4 p-16">
42
+ <div class="relative mb-8 flex flex-col items-center justify-center gap-2 text-center">
43
+ <svg aria-hidden="true" class="mb-4 h-auto w-12" fill="none" height="191" viewBox="0 0 220 191" width="220" xmlns="http://www.w3.org/2000/svg">
44
+ <path clip-rule="evenodd" d="M220 191H0L31.427 136.5H0L8 122.5H180.5L220 191ZM47.5879 136.5L24.2339 177H195.766L172.412 136.5H47.5879Z" fill="var(--color-cyan-500)" fill-rule="evenodd" />
45
+ <path clip-rule="evenodd" d="M110 0L157.448 82.5H189L197 96.5H54.5L110 0ZM78.7021 82.5L110 28.0811L141.298 82.5H78.7021Z" fill="var(--color-cyan-500)" fill-rule="evenodd" />
46
+ </svg>
47
+ <h1 class="text-5xl font-thin">Welcome to Agentuity</h1>
48
+ <p class="text-lg text-gray-400"><span class="font-serif italic">Hono</span> + AI Gateway</p>
49
+ </div>
50
+ <div class="flex flex-col gap-6 rounded-lg border border-gray-900 bg-black p-8 text-gray-400 shadow-2xl">
51
+ <div class="flex flex-wrap items-center gap-1.5">
52
+ Translate to
53
+ <select id="toLanguage" class="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400">
54
+ <option value="Spanish">Spanish</option><option value="French">French</option><option value="German">German</option><option value="Chinese">Chinese</option>
55
+ </select>
56
+ using
57
+ <select id="model" class="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400">
58
+ <option value="gpt-4o-mini">gpt-4o-mini</option><option value="gpt-4o">gpt-4o</option><option value="gpt-4.1-nano">gpt-4.1-nano</option>
59
+ </select>
60
+ <div class="group relative z-0 ml-auto">
61
+ <div class="absolute inset-0 rounded-lg bg-linear-to-r from-cyan-700 via-blue-500 to-purple-600 opacity-75 blur-xl transition-all duration-700 group-hover:opacity-100 group-hover:blur-2xl" />
62
+ <div class="absolute inset-0 rounded-lg bg-cyan-500/50 opacity-50 blur-3xl" />
63
+ <button id="translate-btn" class="relative cursor-pointer rounded-lg bg-gray-950 px-4 py-2 font-semibold text-white shadow-2xl disabled:cursor-not-allowed disabled:opacity-50" type="button">Translate</button>
64
+ </div>
65
+ </div>
66
+ <textarea id="text-input" class="z-10 min-h-28 resize-y rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-white focus:outline-2 focus:outline-offset-2 focus:outline-cyan-500" placeholder="Enter text to translate..." rows="4">Welcome to Agentuity! This translation demo shows what you can build with the platform. It connects to AI models through our gateway — no separate API keys needed. Try translating this text into different languages to see it in action.</textarea>
67
+ <div id="result" class="output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600">Translation will appear here</div>
68
+ </div>
69
+ <div class="rounded-lg border border-gray-900 bg-black p-8">
70
+ <h3 class="m-0 mb-6 text-xl font-normal leading-none text-white">How it works</h3>
71
+ <div class="flex flex-col gap-6">
72
+ <div class="flex items-start gap-3"><div class="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950"><svg aria-hidden="true" class="size-2.5" fill="none" height="24" stroke="var(--color-green-500)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M20 6 9 17l-5-5" /></svg></div><div><h4 class="-mt-0.5 mb-0.5 text-sm font-normal text-white">AI Gateway routing</h4><p class="text-xs text-gray-400"><code class="text-white">agentuity dev</code> automatically sets OPENAI_API_KEY and OPENAI_BASE_URL so the AI SDK routes through the Agentuity gateway.</p></div></div>
73
+ <div class="flex items-start gap-3"><div class="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950"><svg aria-hidden="true" class="size-2.5" fill="none" height="24" stroke="var(--color-green-500)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M20 6 9 17l-5-5" /></svg></div><div><h4 class="-mt-0.5 mb-0.5 text-sm font-normal text-white">API routes</h4><p class="text-xs text-gray-400">Edit <code class="text-white">src/index.ts</code> to change the AI model, prompt, or add new routes.</p></div></div>
74
+ <div class="flex items-start gap-3"><div class="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950"><svg aria-hidden="true" class="size-2.5" fill="none" height="24" stroke="var(--color-green-500)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M20 6 9 17l-5-5" /></svg></div><div><h4 class="-mt-0.5 mb-0.5 text-sm font-normal text-white">Hono</h4><p class="text-xs text-gray-400">Lightweight, fast web framework for the edge. Add routes in <code class="text-white">src/index.ts</code>.</p></div></div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <script>
80
+ const btn = document.getElementById('translate-btn');
81
+ const textInput = document.getElementById('text-input');
82
+ const toLangSelect = document.getElementById('toLanguage');
83
+ const modelSelect = document.getElementById('model');
84
+ const resultDiv = document.getElementById('result');
85
+ btn.addEventListener('click', async () => {
86
+ const text = textInput.value, toLanguage = toLangSelect.value, model = modelSelect.value;
87
+ btn.textContent = 'Translating'; btn.disabled = true;
88
+ resultDiv.textContent = ''; resultDiv.className = 'rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600';
89
+ try {
90
+ const res = await fetch('/api/translate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, toLanguage, model }) });
91
+ if (!res.ok) throw new Error('API error ' + res.status);
92
+ const data = await res.json();
93
+ resultDiv.className = 'output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-cyan-500';
94
+ resultDiv.innerHTML = data.translation + '<div class="flex gap-4 text-xs text-gray-500 mt-3">' + (data.tokens > 0 ? '<span>Tokens <strong class="text-gray-400">' + data.tokens + '</strong></span>' : '') + '<span>Model <strong class="text-gray-400">' + data.model + '</strong></span><span>Language <strong class="text-gray-400">' + data.toLanguage + '</strong></span></div>';
95
+ } catch (err) { resultDiv.className = 'rounded-md border border-red-800 bg-red-950 px-4 py-3 text-sm text-red-400'; resultDiv.textContent = err.message || 'Translation failed'; }
96
+ finally { btn.textContent = 'Translate'; btn.disabled = false; }
97
+ });
98
+ </script>
99
+ </body>
100
+ </html>`);
101
+ });
102
+
103
+ export default app;
@@ -0,0 +1,19 @@
1
+ import { generateText } from 'ai';
2
+ import { openai } from '@ai-sdk/openai';
3
+ import { NextResponse } from 'next/server';
4
+
5
+ export async function POST(request: Request) {
6
+ const { text, toLanguage, model = 'gpt-4o-mini' } = await request.json();
7
+
8
+ const { text: translation, usage } = await generateText({
9
+ model: openai(model),
10
+ prompt: `Translate the following text to ${toLanguage}. Return only the translation, nothing else.\n\n${text}`,
11
+ });
12
+
13
+ return NextResponse.json({
14
+ translation,
15
+ tokens: usage?.totalTokens ?? 0,
16
+ model,
17
+ toLanguage,
18
+ });
19
+ }
@@ -0,0 +1,74 @@
1
+ @import 'tailwindcss';
2
+
3
+ @theme {
4
+ /* Cyan (Agentuity Brand) */
5
+ --color-cyan-50: oklch(0.9812 0.027 196.72);
6
+ --color-cyan-100: oklch(0.965 0.0516 196.33);
7
+ --color-cyan-200: oklch(0.938 0.0956 195.64);
8
+ --color-cyan-300: oklch(0.9193 0.1285 195.15);
9
+ --color-cyan-400: oklch(0.9089 0.1478 194.87);
10
+ --color-cyan-500: oklch(0.9054 0.15455 194.769);
11
+ --color-cyan-600: oklch(0.7653 0.1306 194.77);
12
+ --color-cyan-700: oklch(0.6183 0.10555 194.769);
13
+ --color-cyan-800: oklch(0.462 0.078864 194.769);
14
+ --color-cyan-900: oklch(0.2907 0.0496 194.77);
15
+ --color-cyan-950: oklch(0.1932 0.033 194.77);
16
+
17
+ /* Gray (Zinc) */
18
+ --color-gray-50: oklch(0.985 0 0);
19
+ --color-gray-100: oklch(0.967 0.001 286.375);
20
+ --color-gray-200: oklch(0.92 0.004 286.32);
21
+ --color-gray-300: oklch(0.871 0.006 286.286);
22
+ --color-gray-400: oklch(0.705 0.015 286.067);
23
+ --color-gray-500: oklch(0.552 0.016 285.938);
24
+ --color-gray-600: oklch(0.442 0.017 285.786);
25
+ --color-gray-700: oklch(0.37 0.013 285.805);
26
+ --color-gray-800: oklch(0.274 0.006 286.033);
27
+ --color-gray-900: oklch(0.21 0.006 285.885);
28
+ --color-gray-950: oklch(0.141 0.005 285.823);
29
+
30
+ /* Green (for checkmarks) */
31
+ --color-green-500: oklch(0.723 0.219 149.579);
32
+ --color-green-950: oklch(0.171 0.052 150.3);
33
+
34
+ /* Red (for errors) */
35
+ --color-red-400: oklch(0.704 0.191 22.216);
36
+ --color-red-800: oklch(0.395 0.141 25.723);
37
+ --color-red-950: oklch(0.258 0.092 26.042);
38
+
39
+ /* Blue/Purple (for glow) */
40
+ --color-blue-500: oklch(0.623 0.214 259.815);
41
+ --color-purple-600: oklch(0.558 0.288 302.321);
42
+
43
+ /* Animations */
44
+ --animate-ellipsis: ellipsis 1.5s steps(4, end) infinite;
45
+
46
+ @keyframes ellipsis {
47
+ 0% {
48
+ content: '.';
49
+ }
50
+ 25% {
51
+ content: '..';
52
+ }
53
+ 50% {
54
+ content: '...';
55
+ }
56
+ 75% {
57
+ content: '';
58
+ }
59
+ }
60
+ }
61
+
62
+ [data-loading='true']::after {
63
+ content: '.';
64
+ @apply inline-block w-4 animate-ellipsis text-left;
65
+ }
66
+
67
+ body {
68
+ background-color: oklch(0.141 0.005 285.823);
69
+ font-family:
70
+ system-ui,
71
+ -apple-system,
72
+ sans-serif;
73
+ margin: 0;
74
+ }