veryfront 0.1.13 → 0.1.14
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/esm/cli/app/data/slug-words.d.ts.map +1 -1
- package/esm/cli/app/data/slug-words.js +225 -90
- package/esm/cli/app/operations/project-creation.js +4 -3
- package/esm/cli/app/shell.js +1 -1
- package/esm/cli/app/utils.d.ts +5 -4
- package/esm/cli/app/utils.d.ts.map +1 -1
- package/esm/cli/app/utils.js +0 -23
- package/esm/cli/app/views/dashboard.d.ts +1 -1
- package/esm/cli/app/views/dashboard.d.ts.map +1 -1
- package/esm/cli/app/views/dashboard.js +22 -4
- package/esm/cli/auth/callback-server.d.ts.map +1 -1
- package/esm/cli/auth/callback-server.js +3 -2
- package/esm/cli/commands/dev/handler.d.ts.map +1 -1
- package/esm/cli/commands/dev/handler.js +2 -0
- package/esm/cli/commands/init/init-command.d.ts.map +1 -1
- package/esm/cli/commands/init/init-command.js +20 -3
- package/esm/cli/commands/init/interactive-wizard.d.ts +3 -2
- package/esm/cli/commands/init/interactive-wizard.d.ts.map +1 -1
- package/esm/cli/commands/init/interactive-wizard.js +55 -27
- package/esm/cli/mcp/remote-file-tools.d.ts +0 -6
- package/esm/cli/mcp/remote-file-tools.d.ts.map +1 -1
- package/esm/cli/mcp/remote-file-tools.js +37 -15
- package/esm/cli/shared/reserve-slug.d.ts.map +1 -1
- package/esm/cli/shared/reserve-slug.js +8 -3
- package/esm/cli/utils/env-prompt.d.ts.map +1 -1
- package/esm/cli/utils/env-prompt.js +3 -0
- package/esm/deno.d.ts +2 -1
- package/esm/deno.js +8 -4
- package/esm/src/agent/chat-handler.d.ts +4 -3
- package/esm/src/agent/chat-handler.d.ts.map +1 -1
- package/esm/src/agent/chat-handler.js +55 -4
- package/esm/src/agent/react/index.d.ts +1 -1
- package/esm/src/agent/react/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts +18 -0
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.js +54 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts +43 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.js +4 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts +23 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.js +67 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts +8 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.js +97 -0
- package/esm/src/agent/react/use-chat/index.d.ts +1 -1
- package/esm/src/agent/react/use-chat/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/types.d.ts +12 -0
- package/esm/src/agent/react/use-chat/types.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.js +120 -6
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +59 -7
- package/esm/src/build/production-build/templates.d.ts +2 -2
- package/esm/src/build/production-build/templates.d.ts.map +1 -1
- package/esm/src/build/production-build/templates.js +2 -68
- package/esm/src/chat/index.d.ts +1 -1
- package/esm/src/chat/index.d.ts.map +1 -1
- package/esm/src/errors/veryfront-error.d.ts +3 -0
- package/esm/src/errors/veryfront-error.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.js +5 -1
- package/esm/src/platform/compat/http/deno-server.d.ts.map +1 -1
- package/esm/src/platform/compat/http/deno-server.js +3 -2
- package/esm/src/provider/index.d.ts +1 -1
- package/esm/src/provider/index.d.ts.map +1 -1
- package/esm/src/provider/index.js +1 -1
- package/esm/src/provider/local/ai-sdk-adapter.d.ts +19 -0
- package/esm/src/provider/local/ai-sdk-adapter.d.ts.map +1 -0
- package/esm/src/provider/local/ai-sdk-adapter.js +164 -0
- package/esm/src/provider/local/env.d.ts +10 -0
- package/esm/src/provider/local/env.d.ts.map +1 -0
- package/esm/src/provider/local/env.js +23 -0
- package/esm/src/provider/local/local-engine.d.ts +61 -0
- package/esm/src/provider/local/local-engine.d.ts.map +1 -0
- package/esm/src/provider/local/local-engine.js +211 -0
- package/esm/src/provider/local/model-catalog.d.ts +30 -0
- package/esm/src/provider/local/model-catalog.d.ts.map +1 -0
- package/esm/src/provider/local/model-catalog.js +58 -0
- package/esm/src/provider/model-registry.d.ts +14 -0
- package/esm/src/provider/model-registry.d.ts.map +1 -1
- package/esm/src/provider/model-registry.js +58 -2
- package/esm/src/proxy/main.js +34 -6
- package/esm/src/proxy/server-resolver.d.ts +23 -0
- package/esm/src/proxy/server-resolver.d.ts.map +1 -0
- package/esm/src/proxy/server-resolver.js +124 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.d.ts +8 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.js +36 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts +7 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.js +33 -0
- package/esm/src/react/components/ai/chat/index.d.ts +7 -1
- package/esm/src/react/components/ai/chat/index.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/index.js +16 -4
- package/package.json +5 -1
- package/src/cli/app/data/slug-words.ts +225 -90
- package/src/cli/app/operations/project-creation.ts +3 -3
- package/src/cli/app/shell.ts +1 -1
- package/src/cli/app/utils.ts +0 -30
- package/src/cli/app/views/dashboard.ts +27 -4
- package/src/cli/auth/callback-server.ts +3 -2
- package/src/cli/commands/dev/handler.ts +2 -0
- package/src/cli/commands/init/init-command.ts +30 -3
- package/src/cli/commands/init/interactive-wizard.ts +62 -34
- package/src/cli/mcp/remote-file-tools.ts +50 -15
- package/src/cli/shared/reserve-slug.ts +9 -2
- package/src/cli/utils/env-prompt.ts +3 -0
- package/src/deno.js +8 -4
- package/src/src/agent/chat-handler.ts +57 -4
- package/src/src/agent/react/index.ts +2 -0
- package/src/src/agent/react/use-chat/browser-inference/browser-engine.ts +81 -0
- package/src/src/agent/react/use-chat/browser-inference/types.ts +52 -0
- package/src/src/agent/react/use-chat/browser-inference/worker-client.ts +89 -0
- package/src/src/agent/react/use-chat/browser-inference/worker-script.ts +98 -0
- package/src/src/agent/react/use-chat/index.ts +2 -0
- package/src/src/agent/react/use-chat/types.ts +20 -0
- package/src/src/agent/react/use-chat/use-chat.ts +148 -8
- package/src/src/agent/runtime/index.ts +72 -6
- package/src/src/build/production-build/templates.ts +2 -68
- package/src/src/chat/index.ts +2 -0
- package/src/src/errors/veryfront-error.ts +2 -1
- package/src/src/platform/adapters/runtime/deno/adapter.ts +5 -1
- package/src/src/platform/compat/http/deno-server.ts +3 -1
- package/src/src/provider/index.ts +1 -0
- package/src/src/provider/local/ai-sdk-adapter.ts +207 -0
- package/src/src/provider/local/env.ts +26 -0
- package/src/src/provider/local/local-engine.ts +288 -0
- package/src/src/provider/local/model-catalog.ts +73 -0
- package/src/src/provider/model-registry.ts +66 -2
- package/src/src/proxy/main.ts +41 -6
- package/src/src/proxy/server-resolver.ts +151 -0
- package/src/src/react/components/ai/chat/components/inference-badge.tsx +48 -0
- package/src/src/react/components/ai/chat/components/upgrade-cta.tsx +56 -0
- package/src/src/react/components/ai/chat/index.tsx +43 -6
package/src/cli/app/utils.ts
CHANGED
|
@@ -3,13 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Helper functions for project creation and management.
|
|
5
5
|
*/
|
|
6
|
-
import * as dntShim from "../../_dnt.shims.js";
|
|
7
|
-
|
|
8
6
|
|
|
9
7
|
import { cwd } from "../../src/platform/index.js";
|
|
10
8
|
import { join } from "../../src/platform/compat/path/index.js";
|
|
11
|
-
import { getEnvironmentConfig } from "../../src/config/index.js";
|
|
12
|
-
import { capitalizeSeparatedWords } from "../../src/utils/case-utils.js";
|
|
13
9
|
import { readToken } from "../auth/token-store.js";
|
|
14
10
|
import { pullCommand } from "../commands/pull/index.js";
|
|
15
11
|
import { addLog, type AppState, type StateUpdater } from "./state.js";
|
|
@@ -46,32 +42,6 @@ export function normalizeSlug(projectName: string): string {
|
|
|
46
42
|
return projectName.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
export function slugToName(slug: string): string {
|
|
50
|
-
return capitalizeSeparatedWords(slug, "-", " ");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function createRemoteProject(
|
|
54
|
-
token: string,
|
|
55
|
-
slug: string,
|
|
56
|
-
): Promise<{ slug: string }> {
|
|
57
|
-
const apiUrl = getEnvironmentConfig().apiUrl || "https://api.veryfront.com";
|
|
58
|
-
const response = await dntShim.fetch(`${apiUrl}/projects`, {
|
|
59
|
-
method: "POST",
|
|
60
|
-
headers: {
|
|
61
|
-
Authorization: `Bearer ${token}`,
|
|
62
|
-
"Content-Type": "application/json",
|
|
63
|
-
Accept: "application/json",
|
|
64
|
-
},
|
|
65
|
-
body: JSON.stringify({ slug, name: slugToName(slug) }),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (response.ok) return (await response.json()) as { slug: string };
|
|
69
|
-
|
|
70
|
-
const error = await response.json().catch(() => ({}));
|
|
71
|
-
const msg = (error as { message?: string }).message || `HTTP ${response.status}`;
|
|
72
|
-
throw new Error(msg);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
45
|
export function getLocalProjectsFromState(
|
|
76
46
|
appState: AppState,
|
|
77
47
|
): Array<{ slug: string; path: string }> {
|
|
@@ -154,18 +154,35 @@ function renderSection(title: string, isActive = true): string {
|
|
|
154
154
|
* Render the help bar at the bottom
|
|
155
155
|
*/
|
|
156
156
|
function renderHelpBar(state: AppState): string {
|
|
157
|
+
const hasItems = state.projects.items.length > 0 || state.examples.items.length > 0 ||
|
|
158
|
+
(!!state.remote.user && state.remote.projects.length > 0);
|
|
159
|
+
|
|
157
160
|
if (!state.showHelp) {
|
|
158
161
|
const userInfo = state.remote.user ? ` ${dim("-")} ${brand(state.remote.user.email)}` : "";
|
|
162
|
+
if (!hasItems) {
|
|
163
|
+
const authHint = state.remote.user
|
|
164
|
+
? `${dim("x")} ${dim("logout")}`
|
|
165
|
+
: `${dim("a")} ${dim("login")}`;
|
|
166
|
+
return ` ${dim("n")} ${dim("new project")} ${authHint} ${dim("? more")} ${
|
|
167
|
+
dim("q quit")
|
|
168
|
+
}${userInfo}`;
|
|
169
|
+
}
|
|
159
170
|
return ` ${dim("↑↓ select enter open ? more q quit")}${userInfo}`;
|
|
160
171
|
}
|
|
161
172
|
|
|
162
173
|
const lines: string[] = [];
|
|
163
|
-
|
|
174
|
+
|
|
175
|
+
if (hasItems) {
|
|
176
|
+
lines.push(` ${dim("o")} open ${dim("s")} studio ${dim("i")} ide`);
|
|
177
|
+
}
|
|
164
178
|
|
|
165
179
|
if (!state.remote.user) {
|
|
166
180
|
lines.push(` ${dim("n")} new ${dim("a")} login`);
|
|
167
181
|
} else {
|
|
168
|
-
|
|
182
|
+
const parts = [` ${dim("n")} new`];
|
|
183
|
+
if (hasItems) parts.push(`${dim("p")} pull ${dim("u")} push`);
|
|
184
|
+
parts.push(`${dim("x")} logout`);
|
|
185
|
+
lines.push(parts.join(" "));
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
lines.push(` ${dim("? hide q quit")}`);
|
|
@@ -193,6 +210,12 @@ export function renderDashboardBoxed(state: AppState): string {
|
|
|
193
210
|
/**
|
|
194
211
|
* Render empty state when no projects found
|
|
195
212
|
*/
|
|
196
|
-
export function renderEmptyState(): string {
|
|
197
|
-
|
|
213
|
+
export function renderEmptyState(state: AppState): string {
|
|
214
|
+
const lines: string[] = [];
|
|
215
|
+
|
|
216
|
+
lines.push(renderBanner(state), "");
|
|
217
|
+
lines.push(` ${dim("No projects.")} ${brand("n")} ${dim("to create")}`, "");
|
|
218
|
+
lines.push(renderHelpBar(state));
|
|
219
|
+
|
|
220
|
+
return lines.join("\n");
|
|
198
221
|
}
|
|
@@ -186,8 +186,9 @@ function tryStartDenoServer(port: number): CallbackServer {
|
|
|
186
186
|
resolveCallback = resolve;
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
-
//
|
|
190
|
-
const
|
|
189
|
+
// Access native Deno.serve via `self` to bypass dnt shim transform.
|
|
190
|
+
const nativeDeno = (self as unknown as Record<string, typeof dntShim.Deno>)["Deno"]!;
|
|
191
|
+
const server = nativeDeno.serve(
|
|
191
192
|
{ port, hostname: "127.0.0.1", onListen: () => {} },
|
|
192
193
|
(request: dntShim.Request) => {
|
|
193
194
|
const url = new URL(request.url);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*******************************/
|
|
5
5
|
|
|
6
6
|
import { cliLogger as logger } from "../../utils/index.js";
|
|
7
|
-
import { brand, dim, green } from "../../ui/index.js";
|
|
7
|
+
import { brand, dim, green, red } from "../../ui/index.js";
|
|
8
8
|
import { createSpinner } from "../../ui/progress.js";
|
|
9
9
|
import { box } from "../../ui/box.js";
|
|
10
10
|
import { ensureDir } from "../../../deps/jsr.io/@std/fs/1.0.22/mod.js";
|
|
@@ -35,7 +35,11 @@ import {
|
|
|
35
35
|
loadIntegrations,
|
|
36
36
|
validateIntegrations,
|
|
37
37
|
} from "../../templates/integration-loader.js";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
runInteractiveWizard,
|
|
40
|
+
shouldRunWizard,
|
|
41
|
+
validateProjectName,
|
|
42
|
+
} from "./interactive-wizard.js";
|
|
39
43
|
|
|
40
44
|
/**
|
|
41
45
|
* Icon mapping for integrations based on category/name
|
|
@@ -185,8 +189,31 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
185
189
|
let projectName = name;
|
|
186
190
|
let initGit = false;
|
|
187
191
|
|
|
192
|
+
// Validate project name before doing anything else
|
|
193
|
+
if (name) {
|
|
194
|
+
const nameError = validateProjectName(name);
|
|
195
|
+
if (nameError) {
|
|
196
|
+
console.error(red(nameError));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if directory already exists before entering the wizard
|
|
202
|
+
if (name && !options.force) {
|
|
203
|
+
const fs = createFileSystem();
|
|
204
|
+
const targetDir = join(cwd(), name);
|
|
205
|
+
if (await fs.exists(targetDir)) {
|
|
206
|
+
console.error(
|
|
207
|
+
red(
|
|
208
|
+
`Directory "${name}" already exists. Choose a different name or use --force to overwrite.`,
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
if (shouldRunWizard(options)) {
|
|
189
|
-
const wizardResult = await runInteractiveWizard();
|
|
216
|
+
const wizardResult = await runInteractiveWizard(name);
|
|
190
217
|
if (wizardResult.cancelled) {
|
|
191
218
|
return;
|
|
192
219
|
}
|
|
@@ -6,6 +6,13 @@ import { select, textInput } from "../../utils/terminal-select.js";
|
|
|
6
6
|
import { getTemplateSelectOptions, TEMPLATES } from "./catalog.js";
|
|
7
7
|
import type { InitTemplate } from "./types.js";
|
|
8
8
|
|
|
9
|
+
/** Reject path separators and traversal so the name stays a single directory. */
|
|
10
|
+
export function validateProjectName(name: string): string | null {
|
|
11
|
+
if (/[/\\]/.test(name)) return 'Project name cannot contain "/" or "\\"';
|
|
12
|
+
if (name === "." || name === "..") return 'Project name cannot be "." or ".."';
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export interface WizardResult {
|
|
10
17
|
projectName: string | null; // null = use current directory
|
|
11
18
|
template: InitTemplate;
|
|
@@ -18,10 +25,10 @@ function canRunWizard(): boolean {
|
|
|
18
25
|
return !(isCiEnv() || isDenoTestingEnv()) && checkIsInteractive();
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
28
|
+
export async function runInteractiveWizard(existingName?: string): Promise<WizardResult> {
|
|
22
29
|
if (!canRunWizard()) {
|
|
23
30
|
return {
|
|
24
|
-
projectName: null,
|
|
31
|
+
projectName: existingName ?? null,
|
|
25
32
|
template: "minimal",
|
|
26
33
|
initGit: false,
|
|
27
34
|
skipped: true,
|
|
@@ -37,31 +44,28 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
37
44
|
console.log(`│ Let's set up your project.`);
|
|
38
45
|
console.log("│");
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (locationChoice === "new") {
|
|
63
|
-
const name = await textInput("Project name", "my-app");
|
|
64
|
-
if (name === null) {
|
|
47
|
+
let projectName: string | null = existingName ?? null;
|
|
48
|
+
|
|
49
|
+
// Location prompt (skip when name was provided via CLI)
|
|
50
|
+
if (!existingName) {
|
|
51
|
+
const locationChoice = await select(
|
|
52
|
+
"Where should we create your project?",
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
value: "current",
|
|
56
|
+
label: "Current folder",
|
|
57
|
+
description: "Use this directory",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
value: "new",
|
|
61
|
+
label: "New folder",
|
|
62
|
+
description: "Create a new directory",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
0,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (locationChoice === null) {
|
|
65
69
|
console.log(muted("\n Cancelled.\n"));
|
|
66
70
|
return {
|
|
67
71
|
projectName: null,
|
|
@@ -71,12 +75,36 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
71
75
|
cancelled: true,
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
|
|
79
|
+
if (locationChoice === "new") {
|
|
80
|
+
const name = await textInput("Project name", "my-app");
|
|
81
|
+
if (name === null) {
|
|
82
|
+
console.log(muted("\n Cancelled.\n"));
|
|
83
|
+
return {
|
|
84
|
+
projectName: null,
|
|
85
|
+
template: "minimal",
|
|
86
|
+
initGit: false,
|
|
87
|
+
skipped: false,
|
|
88
|
+
cancelled: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const validName = name || "my-app";
|
|
92
|
+
const nameError = validateProjectName(validName);
|
|
93
|
+
if (nameError) {
|
|
94
|
+
console.log(muted(`\n ${nameError}\n`));
|
|
95
|
+
return {
|
|
96
|
+
projectName: null,
|
|
97
|
+
template: "minimal",
|
|
98
|
+
initGit: false,
|
|
99
|
+
skipped: false,
|
|
100
|
+
cancelled: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
projectName = validName;
|
|
76
104
|
}
|
|
77
105
|
}
|
|
78
106
|
|
|
79
|
-
//
|
|
107
|
+
// Template selection
|
|
80
108
|
const templateChoice = await select(
|
|
81
109
|
"What would you like to build?",
|
|
82
110
|
getTemplateSelectOptions(),
|
|
@@ -96,7 +124,7 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
96
124
|
|
|
97
125
|
const template = templateChoice as InitTemplate;
|
|
98
126
|
|
|
99
|
-
//
|
|
127
|
+
// Git init prompt
|
|
100
128
|
const gitChoice = await select(
|
|
101
129
|
"Initialize a git repository?",
|
|
102
130
|
[
|
|
@@ -136,7 +164,7 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
136
164
|
return { projectName, template, initGit, skipped: false, cancelled: false };
|
|
137
165
|
}
|
|
138
166
|
|
|
139
|
-
export function shouldRunWizard(options: { template?: string
|
|
140
|
-
//
|
|
141
|
-
return !options.template
|
|
167
|
+
export function shouldRunWizard(options: { template?: string }): boolean {
|
|
168
|
+
// Always run wizard unless template is explicitly specified via --template
|
|
169
|
+
return !options.template;
|
|
142
170
|
}
|
|
@@ -15,6 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
import type { MCPTool } from "./tools.js";
|
|
16
16
|
import { getEnvironmentConfig } from "../../src/config/index.js";
|
|
17
17
|
import { withSpan } from "../../src/observability/tracing/otlp-setup.js";
|
|
18
|
+
import { randomSuffix } from "../shared/slug.js";
|
|
18
19
|
|
|
19
20
|
import { DEFAULT_LOCAL_API_URL } from "../shared/constants.js";
|
|
20
21
|
|
|
@@ -135,6 +136,46 @@ interface Project {
|
|
|
135
136
|
created_at?: string;
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
const MAX_SLUG_ATTEMPTS = 10;
|
|
140
|
+
|
|
141
|
+
function slugToName(slug: string): string {
|
|
142
|
+
return slug
|
|
143
|
+
.split("-")
|
|
144
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
145
|
+
.join(" ");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a project with slug conflict retry.
|
|
150
|
+
* On 409, appends a random suffix and retries (matches reserveProjectSlug behavior).
|
|
151
|
+
* Name is derived from the base slug (without random suffix) for readability.
|
|
152
|
+
*/
|
|
153
|
+
async function createProjectWithRetry(
|
|
154
|
+
slug: string,
|
|
155
|
+
body: Record<string, unknown>,
|
|
156
|
+
): Promise<{ ok: true; data: Project; slug: string } | { ok: false; error: string }> {
|
|
157
|
+
const name = slugToName(slug);
|
|
158
|
+
let currentSlug = slug;
|
|
159
|
+
|
|
160
|
+
for (let attempt = 1; attempt <= MAX_SLUG_ATTEMPTS; attempt++) {
|
|
161
|
+
const result = await apiRequest<Project>("POST", "/projects", {
|
|
162
|
+
body: { ...body, slug: currentSlug, name },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (result.ok && result.data) {
|
|
166
|
+
return { ok: true, data: result.data, slug: currentSlug };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (result.status !== 409) {
|
|
170
|
+
return { ok: false, error: result.error ?? "Failed to create project" };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
currentSlug = `${slug}-${randomSuffix()}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { ok: false, error: `Could not find available slug after ${MAX_SLUG_ATTEMPTS} attempts` };
|
|
177
|
+
}
|
|
178
|
+
|
|
138
179
|
// ============================================================================
|
|
139
180
|
// Tool: vf_remote_list_files
|
|
140
181
|
// ============================================================================
|
|
@@ -573,8 +614,9 @@ export const vfRemoteDeleteBranch: MCPTool<RemoteDeleteBranchInput, RemoteDelete
|
|
|
573
614
|
// ============================================================================
|
|
574
615
|
|
|
575
616
|
const remoteCreateProjectInput = z.object({
|
|
576
|
-
|
|
577
|
-
|
|
617
|
+
slug: z.string().describe(
|
|
618
|
+
"Project slug (lowercase letters, numbers, hyphens only). A random suffix is appended if the slug is already taken.",
|
|
619
|
+
),
|
|
578
620
|
template: z.string().optional().describe("Template to use (e.g., 'chat', 'rag', 'minimal')"),
|
|
579
621
|
is_public: z.boolean().optional().describe("Whether the project is public (default: false)"),
|
|
580
622
|
});
|
|
@@ -592,13 +634,9 @@ export const vfRemoteCreateProject: MCPTool<RemoteCreateProjectInput, RemoteCrea
|
|
|
592
634
|
description: "Create a new Veryfront project. Returns the project details including ID and slug.",
|
|
593
635
|
inputSchema: remoteCreateProjectInput,
|
|
594
636
|
execute: async (input) => {
|
|
595
|
-
const result = await
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
slug: input.slug,
|
|
599
|
-
template: input.template,
|
|
600
|
-
isPublic: input.is_public,
|
|
601
|
-
},
|
|
637
|
+
const result = await createProjectWithRetry(input.slug, {
|
|
638
|
+
template: input.template,
|
|
639
|
+
isPublic: input.is_public,
|
|
602
640
|
});
|
|
603
641
|
|
|
604
642
|
if (!result.ok) return { success: false, error: result.error };
|
|
@@ -612,9 +650,8 @@ export const vfRemoteCreateProject: MCPTool<RemoteCreateProjectInput, RemoteCrea
|
|
|
612
650
|
|
|
613
651
|
const remoteCloneProjectInput = z.object({
|
|
614
652
|
source_project: z.string().describe("Source project slug or ID to clone from"),
|
|
615
|
-
target_name: z.string().describe("Name for the new project"),
|
|
616
653
|
target_slug: z.string().describe(
|
|
617
|
-
"Slug for the new project (lowercase letters, numbers, hyphens only)",
|
|
654
|
+
"Slug for the new project (lowercase letters, numbers, hyphens only). A random suffix is appended if the slug is already taken.",
|
|
618
655
|
),
|
|
619
656
|
file_pattern: z.string().optional().describe(
|
|
620
657
|
"Optional file pattern to filter which files to clone (e.g., '*.tsx')",
|
|
@@ -639,9 +676,7 @@ export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneP
|
|
|
639
676
|
withSpan(
|
|
640
677
|
"cli.mcp.tool.vf_remote_clone_project",
|
|
641
678
|
async () => {
|
|
642
|
-
const createResult = await
|
|
643
|
-
body: { name: input.target_name, slug: input.target_slug },
|
|
644
|
-
});
|
|
679
|
+
const createResult = await createProjectWithRetry(input.target_slug, {});
|
|
645
680
|
|
|
646
681
|
if (!createResult.ok) {
|
|
647
682
|
return { success: false, error: `Failed to create project: ${createResult.error}` };
|
|
@@ -684,7 +719,7 @@ export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneP
|
|
|
684
719
|
|
|
685
720
|
const createFileResult = await apiRequest<{ id: string; path: string }>(
|
|
686
721
|
"PUT",
|
|
687
|
-
`/${
|
|
722
|
+
`/${createResult.slug}/files/${encodeFilePath(file.path)}`,
|
|
688
723
|
{ body: { content: getResult.data.content } },
|
|
689
724
|
);
|
|
690
725
|
|
|
@@ -9,8 +9,13 @@ import * as dntShim from "../../_dnt.shims.js";
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
import { type EnvironmentConfig, getEnvironmentConfig } from "../../src/config/index.js";
|
|
12
|
+
import { capitalizeSeparatedWords } from "../../src/utils/case-utils.js";
|
|
12
13
|
import { randomSuffix } from "./slug.js";
|
|
13
14
|
|
|
15
|
+
function slugToName(slug: string): string {
|
|
16
|
+
return capitalizeSeparatedWords(slug, "-", " ");
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export interface ReserveResult {
|
|
15
20
|
slug: string;
|
|
16
21
|
projectId: string;
|
|
@@ -39,10 +44,11 @@ export async function reserveProjectSlug(
|
|
|
39
44
|
token: string,
|
|
40
45
|
env: EnvironmentConfig = getEnvironmentConfig(),
|
|
41
46
|
): Promise<ReserveResult> {
|
|
47
|
+
const name = slugToName(slug);
|
|
42
48
|
let currentSlug = slug;
|
|
43
49
|
|
|
44
50
|
for (let attempt = 1; attempt <= MAX_SLUG_ATTEMPTS; attempt++) {
|
|
45
|
-
const result = await tryCreateProject(currentSlug, token, env);
|
|
51
|
+
const result = await tryCreateProject(currentSlug, name, token, env);
|
|
46
52
|
|
|
47
53
|
if (result.success) {
|
|
48
54
|
return {
|
|
@@ -64,6 +70,7 @@ export async function reserveProjectSlug(
|
|
|
64
70
|
|
|
65
71
|
async function tryCreateProject(
|
|
66
72
|
slug: string,
|
|
73
|
+
name: string,
|
|
67
74
|
token: string,
|
|
68
75
|
env: EnvironmentConfig = getEnvironmentConfig(),
|
|
69
76
|
): Promise<CreateProjectResult> {
|
|
@@ -75,7 +82,7 @@ async function tryCreateProject(
|
|
|
75
82
|
"Content-Type": "application/json",
|
|
76
83
|
Accept: "application/json",
|
|
77
84
|
},
|
|
78
|
-
body: JSON.stringify({ slug, name
|
|
85
|
+
body: JSON.stringify({ slug, name }),
|
|
79
86
|
});
|
|
80
87
|
|
|
81
88
|
if (response.ok) {
|
package/src/deno.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"nodeModulesDir": "auto",
|
|
5
5
|
"exclude": [
|
|
6
6
|
"npm/",
|
|
@@ -252,7 +252,8 @@ export default {
|
|
|
252
252
|
"@babel/parser": "npm:@babel/parser@7.26.3",
|
|
253
253
|
"@babel/traverse": "npm:@babel/traverse@7.26.3",
|
|
254
254
|
"@babel/generator": "npm:@babel/generator@7.26.3",
|
|
255
|
-
"@babel/types": "npm:@babel/types@7.26.3"
|
|
255
|
+
"@babel/types": "npm:@babel/types@7.26.3",
|
|
256
|
+
"@huggingface/transformers": "npm:@huggingface/transformers@3.4.2"
|
|
256
257
|
},
|
|
257
258
|
"compilerOptions": {
|
|
258
259
|
"jsx": "react-jsx",
|
|
@@ -278,7 +279,7 @@ export default {
|
|
|
278
279
|
"proxy": "deno run --allow-all cli/main.ts serve --mode=proxy",
|
|
279
280
|
"dev": "deno run --allow-all cli/main.ts dev",
|
|
280
281
|
"production": "deno run --allow-all cli/main.ts serve --mode=production",
|
|
281
|
-
"build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
|
|
282
|
+
"build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno run -A scripts/build/prebundle-client-scripts.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
|
|
282
283
|
"build:npm": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/build-npm-dnt.ts",
|
|
283
284
|
"release": "deno run -A scripts/release.ts",
|
|
284
285
|
"test": "VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --parallel --allow-all '--ignore=tests/e2e,tests/integration/compiled-binary-e2e.test.ts' --unstable-worker-options --unstable-net",
|
|
@@ -383,7 +384,10 @@ export default {
|
|
|
383
384
|
"proseWrap": "preserve"
|
|
384
385
|
},
|
|
385
386
|
"allowScripts": {
|
|
386
|
-
"allow": [
|
|
387
|
+
"allow": [
|
|
388
|
+
"npm:sharp@0.33.5",
|
|
389
|
+
"npm:onnxruntime-node@1.20.1"
|
|
390
|
+
],
|
|
387
391
|
"deny": [
|
|
388
392
|
"npm:esbuild@0.20.2",
|
|
389
393
|
"npm:protobufjs@7.5.4"
|
|
@@ -2,6 +2,8 @@ import * as dntShim from "../../_dnt.shims.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAgent } from "./composition/index.js";
|
|
4
4
|
import type { Message } from "./types.js";
|
|
5
|
+
import { fromError } from "../errors/veryfront-error.js";
|
|
6
|
+
import { DEFAULT_LOCAL_MODEL } from "../provider/local/model-catalog.js";
|
|
5
7
|
|
|
6
8
|
// ---------------------------------------------------------------------------
|
|
7
9
|
// Zod schemas for validating AI SDK v5 chat UI messages
|
|
@@ -129,11 +131,25 @@ export interface ChatHandlerOptions {
|
|
|
129
131
|
) => Record<string, unknown> | Promise<Record<string, unknown>>);
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Extract the raw Request from either a raw Request or a Pages Router APIContext.
|
|
136
|
+
* Pages Router handlers receive `(ctx)` where `ctx.request` is the Request.
|
|
137
|
+
* App Router handlers receive `(request, context)` where `request` IS the Request.
|
|
138
|
+
*/
|
|
139
|
+
function extractRequest(requestOrCtx: unknown): dntShim.Request {
|
|
140
|
+
if (requestOrCtx instanceof dntShim.Request) return requestOrCtx;
|
|
141
|
+
// Pages Router APIContext — has a .request property
|
|
142
|
+
const ctx = requestOrCtx as { request?: dntShim.Request };
|
|
143
|
+
if (ctx.request instanceof dntShim.Request) return ctx.request;
|
|
144
|
+
throw new Error("Invalid handler argument: expected Request or APIContext");
|
|
145
|
+
}
|
|
146
|
+
|
|
132
147
|
/**
|
|
133
148
|
* Create a POST handler for a chat API route.
|
|
134
149
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
150
|
+
* Works with both App Router and Pages Router:
|
|
151
|
+
* - App Router: `app/api/chat/route.ts` — handler receives `(request, context)`
|
|
152
|
+
* - Pages Router: `pages/api/chat.ts` — handler receives `(ctx)`
|
|
137
153
|
*
|
|
138
154
|
* @example
|
|
139
155
|
* ```ts
|
|
@@ -145,12 +161,22 @@ export function createChatHandler(
|
|
|
145
161
|
agentId: string,
|
|
146
162
|
options?: ChatHandlerOptions,
|
|
147
163
|
) {
|
|
148
|
-
|
|
164
|
+
// deno-lint-ignore no-explicit-any
|
|
165
|
+
return async function POST(requestOrCtx: any): Promise<dntShim.Response> {
|
|
166
|
+
const request = extractRequest(requestOrCtx);
|
|
167
|
+
// Resolve agent outside try so it's available in the catch block for
|
|
168
|
+
// extracting the system prompt in the 503 fallback response.
|
|
169
|
+
let agent: ReturnType<typeof getAgent> | undefined;
|
|
170
|
+
try {
|
|
171
|
+
agent = getAgent(agentId);
|
|
172
|
+
} catch {
|
|
173
|
+
return dntShim.Response.json({ error: "Agent not found" }, { status: 404 });
|
|
174
|
+
}
|
|
175
|
+
|
|
149
176
|
try {
|
|
150
177
|
const body = await request.json();
|
|
151
178
|
const { messages: rawMessages } = chatRequestSchema.parse(body);
|
|
152
179
|
|
|
153
|
-
const agent = getAgent(agentId);
|
|
154
180
|
if (!agent) {
|
|
155
181
|
return dntShim.Response.json({ error: "Agent not found" }, { status: 404 });
|
|
156
182
|
}
|
|
@@ -177,6 +203,33 @@ export function createChatHandler(
|
|
|
177
203
|
);
|
|
178
204
|
}
|
|
179
205
|
|
|
206
|
+
// Detect structured "no_ai_available" errors from local engine
|
|
207
|
+
const vfError = fromError(error);
|
|
208
|
+
if (vfError?.type === "no_ai_available") {
|
|
209
|
+
// Resolve the agent's system prompt so the browser can use it for inference
|
|
210
|
+
const systemConfig = agent?.config?.system;
|
|
211
|
+
let systemPrompt = "You are a helpful AI assistant.";
|
|
212
|
+
if (typeof systemConfig === "string") {
|
|
213
|
+
systemPrompt = systemConfig;
|
|
214
|
+
} else if (typeof systemConfig === "function") {
|
|
215
|
+
try {
|
|
216
|
+
systemPrompt = await systemConfig();
|
|
217
|
+
} catch {
|
|
218
|
+
// Fall back to default
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return dntShim.Response.json(
|
|
223
|
+
{
|
|
224
|
+
code: "NO_AI_AVAILABLE",
|
|
225
|
+
fallback: "browser",
|
|
226
|
+
model: DEFAULT_LOCAL_MODEL,
|
|
227
|
+
systemPrompt,
|
|
228
|
+
},
|
|
229
|
+
{ status: 503 },
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
180
233
|
return dntShim.Response.json(
|
|
181
234
|
{ error: "Internal server error" },
|
|
182
235
|
{ status: 500 },
|