veryfront 0.1.129 → 0.1.131

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 (112) hide show
  1. package/esm/cli/auth/login.d.ts.map +1 -1
  2. package/esm/cli/auth/login.js +11 -0
  3. package/esm/cli/auth/provider-store.d.ts +20 -0
  4. package/esm/cli/auth/provider-store.d.ts.map +1 -0
  5. package/esm/cli/auth/provider-store.js +62 -0
  6. package/esm/cli/auth/providers/anthropic.d.ts +2 -0
  7. package/esm/cli/auth/providers/anthropic.d.ts.map +1 -0
  8. package/esm/cli/auth/providers/anthropic.js +37 -0
  9. package/esm/cli/auth/providers/openai.d.ts +2 -0
  10. package/esm/cli/auth/providers/openai.d.ts.map +1 -0
  11. package/esm/cli/auth/providers/openai.js +35 -0
  12. package/esm/cli/auth/utils.d.ts +5 -0
  13. package/esm/cli/auth/utils.d.ts.map +1 -1
  14. package/esm/cli/auth/utils.js +9 -0
  15. package/esm/cli/commands/config/command-help.d.ts +3 -0
  16. package/esm/cli/commands/config/command-help.d.ts.map +1 -0
  17. package/esm/cli/commands/config/command-help.js +13 -0
  18. package/esm/cli/commands/config/handler.d.ts +5 -0
  19. package/esm/cli/commands/config/handler.d.ts.map +1 -0
  20. package/esm/cli/commands/config/handler.js +70 -0
  21. package/esm/cli/commands/open/command-help.d.ts +3 -0
  22. package/esm/cli/commands/open/command-help.d.ts.map +1 -0
  23. package/esm/cli/commands/open/command-help.js +17 -0
  24. package/esm/cli/commands/open/command.d.ts +14 -0
  25. package/esm/cli/commands/open/command.d.ts.map +1 -0
  26. package/esm/cli/commands/open/command.js +22 -0
  27. package/esm/cli/commands/open/handler.d.ts +3 -0
  28. package/esm/cli/commands/open/handler.d.ts.map +1 -0
  29. package/esm/cli/commands/open/handler.js +29 -0
  30. package/esm/cli/help/command-definitions.d.ts.map +1 -1
  31. package/esm/cli/help/command-definitions.js +4 -0
  32. package/esm/cli/router.d.ts.map +1 -1
  33. package/esm/cli/router.js +26 -1
  34. package/esm/deno.js +1 -1
  35. package/esm/src/channels/control-plane.js +6 -6
  36. package/esm/src/discovery/handlers/agent-handler.d.ts.map +1 -1
  37. package/esm/src/discovery/handlers/agent-handler.js +10 -1
  38. package/esm/src/platform/compat/framework-source-resolver.d.ts +8 -0
  39. package/esm/src/platform/compat/framework-source-resolver.d.ts.map +1 -1
  40. package/esm/src/platform/compat/framework-source-resolver.js +77 -1
  41. package/esm/src/rendering/rsc/client-boot.ts +18 -1
  42. package/esm/src/server/handlers/preview/markdown-html-generator.d.ts +2 -0
  43. package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
  44. package/esm/src/server/handlers/preview/markdown-html-generator.js +10 -7
  45. package/esm/src/server/handlers/preview/markdown-preview.handler.d.ts.map +1 -1
  46. package/esm/src/server/handlers/preview/markdown-preview.handler.js +6 -3
  47. package/esm/src/server/handlers/request/api/project-discovery.d.ts.map +1 -1
  48. package/esm/src/server/handlers/request/api/project-discovery.js +16 -5
  49. package/esm/src/server/handlers/request/api/security-headers.d.ts +1 -0
  50. package/esm/src/server/handlers/request/api/security-headers.d.ts.map +1 -1
  51. package/esm/src/server/handlers/request/api/security-headers.js +4 -1
  52. package/esm/src/server/handlers/request/openapi-docs.handler.d.ts.map +1 -1
  53. package/esm/src/server/handlers/request/openapi-docs.handler.js +10 -6
  54. package/esm/src/server/handlers/request/rsc/index.d.ts.map +1 -1
  55. package/esm/src/server/handlers/request/rsc/index.js +5 -2
  56. package/esm/src/server/handlers/request/ssr/ssr-response-builder.d.ts.map +1 -1
  57. package/esm/src/server/handlers/request/ssr/ssr-response-builder.js +12 -2
  58. package/esm/src/server/handlers/response/not-found.d.ts.map +1 -1
  59. package/esm/src/server/handlers/response/not-found.js +14 -15
  60. package/esm/src/server/services/rsc/endpoints/endpoint-router.d.ts +1 -1
  61. package/esm/src/server/services/rsc/endpoints/endpoint-router.d.ts.map +1 -1
  62. package/esm/src/server/services/rsc/endpoints/endpoint-router.js +3 -3
  63. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.d.ts.map +1 -1
  64. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.js +1 -1
  65. package/esm/src/server/services/rsc/endpoints/types.d.ts +1 -0
  66. package/esm/src/server/services/rsc/endpoints/types.d.ts.map +1 -1
  67. package/esm/src/server/services/rsc/orchestrators/handler.d.ts +1 -1
  68. package/esm/src/server/services/rsc/orchestrators/handler.d.ts.map +1 -1
  69. package/esm/src/server/services/rsc/orchestrators/handler.js +2 -2
  70. package/esm/src/server/services/rsc/orchestrators/page-handler.d.ts +1 -1
  71. package/esm/src/server/services/rsc/orchestrators/page-handler.d.ts.map +1 -1
  72. package/esm/src/server/services/rsc/orchestrators/page-handler.js +7 -5
  73. package/esm/src/transforms/esm/import-parser.d.ts.map +1 -1
  74. package/esm/src/transforms/esm/import-parser.js +6 -0
  75. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts +1 -1
  76. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts.map +1 -1
  77. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.js +10 -66
  78. package/esm/src/utils/version-constant.d.ts +1 -1
  79. package/esm/src/utils/version-constant.js +1 -1
  80. package/package.json +1 -1
  81. package/src/cli/auth/login.ts +12 -0
  82. package/src/cli/auth/provider-store.ts +82 -0
  83. package/src/cli/auth/providers/anthropic.ts +46 -0
  84. package/src/cli/auth/providers/openai.ts +45 -0
  85. package/src/cli/auth/utils.ts +10 -0
  86. package/src/cli/commands/config/command-help.ts +15 -0
  87. package/src/cli/commands/config/handler.ts +90 -0
  88. package/src/cli/commands/open/command-help.ts +19 -0
  89. package/src/cli/commands/open/command.ts +28 -0
  90. package/src/cli/commands/open/handler.ts +38 -0
  91. package/src/cli/help/command-definitions.ts +4 -0
  92. package/src/cli/router.ts +28 -1
  93. package/src/deno.js +1 -1
  94. package/src/src/channels/control-plane.ts +6 -6
  95. package/src/src/discovery/handlers/agent-handler.ts +10 -1
  96. package/src/src/platform/compat/framework-source-resolver.ts +101 -1
  97. package/src/src/server/handlers/preview/markdown-html-generator.ts +12 -6
  98. package/src/src/server/handlers/preview/markdown-preview.handler.ts +6 -3
  99. package/src/src/server/handlers/request/api/project-discovery.ts +18 -5
  100. package/src/src/server/handlers/request/api/security-headers.ts +10 -1
  101. package/src/src/server/handlers/request/openapi-docs.handler.ts +10 -6
  102. package/src/src/server/handlers/request/rsc/index.ts +5 -2
  103. package/src/src/server/handlers/request/ssr/ssr-response-builder.ts +16 -2
  104. package/src/src/server/handlers/response/not-found.ts +14 -15
  105. package/src/src/server/services/rsc/endpoints/endpoint-router.ts +3 -3
  106. package/src/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +1 -1
  107. package/src/src/server/services/rsc/endpoints/types.ts +1 -0
  108. package/src/src/server/services/rsc/orchestrators/handler.ts +2 -2
  109. package/src/src/server/services/rsc/orchestrators/page-handler.ts +8 -5
  110. package/src/src/transforms/esm/import-parser.ts +12 -0
  111. package/src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts +10 -69
  112. package/src/src/utils/version-constant.ts +1 -1
@@ -6,6 +6,7 @@
6
6
 
7
7
  import type { AuthMethod } from "./login.js";
8
8
  import type { ParsedArgs } from "../shared/types.js";
9
+ import type { ProviderName } from "./provider-store.js";
9
10
 
10
11
  /**
11
12
  * Parse login method from CLI arguments
@@ -19,3 +20,12 @@ export function parseLoginMethod(
19
20
  if (args.token) return "token";
20
21
  return undefined;
21
22
  }
23
+
24
+ /**
25
+ * Parse --provider flag from CLI arguments
26
+ */
27
+ export function parseProvider(args: ParsedArgs): ProviderName | undefined {
28
+ const provider = args.provider as string | undefined;
29
+ if (provider === "anthropic" || provider === "openai") return provider;
30
+ return undefined;
31
+ }
@@ -0,0 +1,15 @@
1
+ import type { CommandHelp } from "../../help/types.js";
2
+
3
+ export const configHelp: CommandHelp = {
4
+ name: "config",
5
+ category: "project",
6
+ description: "Show effective project configuration",
7
+ usage: "veryfront config [options]",
8
+ options: [
9
+ { flag: "--json", description: "Output as JSON" },
10
+ ],
11
+ examples: [
12
+ "veryfront config",
13
+ "veryfront config --json",
14
+ ],
15
+ };
@@ -0,0 +1,90 @@
1
+ import type { ParsedArgs } from "../../shared/types.js";
2
+ import { cliLogger } from "../../utils/index.js";
3
+ import { getEnv } from "../../../src/platform/index.js";
4
+ import { createSuccessEnvelope, isJsonMode, outputJson } from "../../shared/json-output.js";
5
+ import { bold, dim } from "../../ui/colors.js";
6
+
7
+ const ENV_OVERRIDES: Record<string, string> = {
8
+ projectSlug: "VERYFRONT_PROJECT_SLUG",
9
+ apiBaseUrl: "VERYFRONT_API_BASE_URL",
10
+ apiToken: "VERYFRONT_API_TOKEN",
11
+ nodeEnv: "NODE_ENV",
12
+ veryfrontEnv: "VERYFRONT_ENV",
13
+ debug: "VERYFRONT_DEBUG",
14
+ };
15
+
16
+ export async function detectConfigSource(
17
+ projectDir: string,
18
+ ): Promise<string | null> {
19
+ const { createFileSystem } = await import("../../../src/platform/index.js");
20
+ const { join } = await import("../../../src/platform/compat/path/index.js");
21
+ const fs = createFileSystem();
22
+
23
+ for (
24
+ const name of [
25
+ "veryfront.config.ts",
26
+ "veryfront.config.js",
27
+ "veryfront.json",
28
+ ]
29
+ ) {
30
+ if (await fs.exists(join(projectDir, name))) return name;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ export function getEnvOverrides(): string[] {
36
+ const overrides: string[] = [];
37
+ for (const [field, envVar] of Object.entries(ENV_OVERRIDES)) {
38
+ if (getEnv(envVar)) overrides.push(`${field} (${envVar})`);
39
+ }
40
+ return overrides;
41
+ }
42
+
43
+ export async function handleConfigCommand(_args: ParsedArgs): Promise<void> {
44
+ const { getEnvironmentConfig } = await import("../../../src/config/index.js");
45
+ const { cwd } = await import("../../../src/platform/index.js");
46
+ const config = getEnvironmentConfig();
47
+
48
+ const projectDir = cwd();
49
+ const configSource = await detectConfigSource(projectDir);
50
+ const envOverrides = getEnvOverrides();
51
+
52
+ const configData = {
53
+ projectSlug: config.projectSlug ?? null,
54
+ nodeEnv: config.nodeEnv,
55
+ veryfrontEnv: config.veryfrontEnv || null,
56
+ apiBaseUrl: config.apiBaseUrl,
57
+ debug: config.debug,
58
+ ci: config.ci,
59
+ hasApiToken: !!config.apiToken,
60
+ configSource,
61
+ envOverrides,
62
+ };
63
+
64
+ if (isJsonMode()) {
65
+ await outputJson(createSuccessEnvelope("config", configData));
66
+ return;
67
+ }
68
+
69
+ cliLogger.info(`\n ${bold("Project Configuration")}\n`);
70
+ cliLogger.info(
71
+ ` ${dim("Project slug:")} ${configData.projectSlug ?? "(not set)"}`,
72
+ );
73
+ cliLogger.info(` ${dim("Environment:")} ${configData.nodeEnv}`);
74
+ cliLogger.info(
75
+ ` ${dim("VF Environment:")} ${configData.veryfrontEnv ?? "(not set)"}`,
76
+ );
77
+ cliLogger.info(` ${dim("API endpoint:")} ${configData.apiBaseUrl}`);
78
+ cliLogger.info(` ${dim("Debug:")} ${configData.debug}`);
79
+ cliLogger.info(` ${dim("CI:")} ${configData.ci}`);
80
+ cliLogger.info(
81
+ ` ${dim("Authenticated:")} ${configData.hasApiToken ? "yes" : "no"}`,
82
+ );
83
+ cliLogger.info(
84
+ ` ${dim("Config file:")} ${configData.configSource ?? "(none)"}`,
85
+ );
86
+ if (envOverrides.length > 0) {
87
+ cliLogger.info(` ${dim("Env overrides:")} ${envOverrides.join(", ")}`);
88
+ }
89
+ cliLogger.info("");
90
+ }
@@ -0,0 +1,19 @@
1
+ import type { CommandHelp } from "../../help/types.js";
2
+
3
+ export const openHelp: CommandHelp = {
4
+ name: "open",
5
+ category: "project",
6
+ description: "Open project URLs in the browser",
7
+ usage: "veryfront open [options]",
8
+ options: [
9
+ { flag: "--env <name>", description: "Open a specific environment URL" },
10
+ { flag: "--studio", description: "Open Veryfront Studio" },
11
+ { flag: "--json", description: "Output URL as JSON instead of opening" },
12
+ ],
13
+ examples: [
14
+ "veryfront open",
15
+ "veryfront open --env staging",
16
+ "veryfront open --studio",
17
+ "veryfront open --json",
18
+ ],
19
+ };
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ import { createArgParser } from "../../shared/args.js";
3
+
4
+ export const OpenArgsSchema = z.object({
5
+ env: z.string().optional(),
6
+ studio: z.boolean().default(false),
7
+ projectSlug: z.string().optional(),
8
+ });
9
+
10
+ export type OpenOptions = z.infer<typeof OpenArgsSchema>;
11
+
12
+ export const parseOpenArgs = createArgParser(OpenArgsSchema, {
13
+ env: { keys: ["env"], type: "string" },
14
+ studio: { keys: ["studio"], type: "boolean" },
15
+ projectSlug: { keys: ["project-slug", "project", "p"], type: "string" },
16
+ });
17
+
18
+ const DASHBOARD_BASE = "https://veryfront.com";
19
+
20
+ export function buildUrl(projectSlug: string, options: OpenOptions): string {
21
+ if (options.studio) {
22
+ return `${DASHBOARD_BASE}/studio/${projectSlug}`;
23
+ }
24
+ if (options.env) {
25
+ return `${DASHBOARD_BASE}/projects/${projectSlug}/environments/${options.env}`;
26
+ }
27
+ return `${DASHBOARD_BASE}/projects/${projectSlug}`;
28
+ }
@@ -0,0 +1,38 @@
1
+ import type { ParsedArgs } from "../../shared/types.js";
2
+ import { parseArgsOrThrow } from "../../shared/args.js";
3
+ import { cliLogger, exitProcess } from "../../utils/index.js";
4
+ import { createSuccessEnvelope, isJsonMode, outputJson } from "../../shared/json-output.js";
5
+ import { buildUrl, parseOpenArgs } from "./command.js";
6
+
7
+ export async function handleOpenCommand(args: ParsedArgs): Promise<void> {
8
+ const opts = parseArgsOrThrow(parseOpenArgs, "open", args);
9
+
10
+ let projectSlug = opts.projectSlug;
11
+ if (!projectSlug) {
12
+ const { cwd } = await import("../../../src/platform/index.js");
13
+ const { getEnvironmentConfig } = await import("../../../src/config/index.js");
14
+ const { readConfigFile } = await import("../../shared/config.js");
15
+ projectSlug = getEnvironmentConfig().projectSlug ??
16
+ (await readConfigFile(cwd()))?.projectSlug ??
17
+ undefined;
18
+ }
19
+
20
+ if (!projectSlug) {
21
+ cliLogger.error(
22
+ "No project found. Run from a project directory or use --project-slug",
23
+ );
24
+ exitProcess(1);
25
+ return;
26
+ }
27
+
28
+ const url = buildUrl(projectSlug, opts);
29
+
30
+ if (isJsonMode()) {
31
+ await outputJson(createSuccessEnvelope("open", { url }));
32
+ return;
33
+ }
34
+
35
+ const { openBrowser } = await import("../../auth/browser.js");
36
+ await openBrowser(url);
37
+ console.log(` Opening ${url}`);
38
+ }
@@ -42,6 +42,8 @@ import { schemaHelp } from "../commands/schema/command-help.js";
42
42
  import { testHelp } from "../commands/test/command-help.js";
43
43
  import { lintHelp } from "../commands/lint/command-help.js";
44
44
  import { skillsHelp } from "../commands/skills/command-help.js";
45
+ import { configHelp } from "../commands/config/command-help.js";
46
+ import { openHelp } from "../commands/open/command-help.js";
45
47
  import { completionsHelp } from "../commands/completions/command-help.js";
46
48
 
47
49
  /**
@@ -84,5 +86,7 @@ export const COMMANDS: CommandRegistry = {
84
86
  test: testHelp,
85
87
  lint: lintHelp,
86
88
  skills: skillsHelp,
89
+ config: configHelp,
90
+ open: openHelp,
87
91
  completions: completionsHelp,
88
92
  };
package/src/cli/router.ts CHANGED
@@ -40,6 +40,8 @@ import { handleSchemaCommand } from "./commands/schema/handler.js";
40
40
  import { handleTestCommand } from "./commands/test/handler.js";
41
41
  import { handleLintCommand } from "./commands/lint/handler.js";
42
42
  import { handleSkillsCommand } from "./commands/skills/handler.js";
43
+ import { handleConfigCommand } from "./commands/config/handler.js";
44
+ import { handleOpenCommand } from "./commands/open/handler.js";
43
45
  import { handleCompletionsCommand } from "./commands/completions/handler.js";
44
46
  import { login, logout, whoami } from "./auth/index.js";
45
47
  import { parseLoginMethod } from "./auth/utils.js";
@@ -84,9 +86,32 @@ const commands: Record<string, (args: ParsedArgs) => Promise<void>> = {
84
86
  "deploy": handleDeployCommand,
85
87
  "up": handleUpCommand,
86
88
  "login": async (args) => {
89
+ const { parseProvider } = await import("./auth/utils.js");
90
+ const provider = parseProvider(args);
91
+ if (provider === "anthropic") {
92
+ const { loginAnthropic } = await import("./auth/providers/anthropic.js");
93
+ await loginAnthropic();
94
+ return;
95
+ }
96
+ if (provider === "openai") {
97
+ const { loginOpenAI } = await import("./auth/providers/openai.js");
98
+ await loginOpenAI(args["base-url"] as string | undefined);
99
+ return;
100
+ }
87
101
  await login(parseLoginMethod(args));
88
102
  },
89
- "logout": async () => {
103
+ "logout": async (args) => {
104
+ const { parseProvider } = await import("./auth/utils.js");
105
+ const provider = parseProvider(args);
106
+ if (provider) {
107
+ const { deleteProviderToken } = await import(
108
+ "./auth/provider-store.js"
109
+ );
110
+ await deleteProviderToken(provider);
111
+ const { logSuccess } = await import("./utils/index.js");
112
+ logSuccess(`${provider} API key removed`);
113
+ return;
114
+ }
90
115
  await logout();
91
116
  },
92
117
  "whoami": async () => {
@@ -105,6 +130,8 @@ const commands: Record<string, (args: ParsedArgs) => Promise<void>> = {
105
130
  "test": handleTestCommand,
106
131
  "lint": handleLintCommand,
107
132
  "skills": handleSkillsCommand,
133
+ "config": handleConfigCommand,
134
+ "open": handleOpenCommand,
108
135
  "completions": handleCompletionsCommand,
109
136
  };
110
137
 
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.129",
3
+ "version": "0.1.131",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -233,14 +233,14 @@ function resolveAgentSkills(agent: Agent): RuntimeAgentSkill[] {
233
233
  .sort((left, right) => left.name.localeCompare(right.name));
234
234
  }
235
235
 
236
- function getRuntimeAgentMetadata(agent: Agent): RuntimeAgent {
236
+ function getRuntimeAgentMetadata(id: string, agent: Agent): RuntimeAgent {
237
237
  const rawConfig = agent.config as unknown as Record<string, unknown>;
238
238
 
239
239
  return RuntimeAgentSchema.parse({
240
- id: agent.id,
240
+ id,
241
241
  name: typeof rawConfig.name === "string" && rawConfig.name.trim().length > 0
242
242
  ? rawConfig.name
243
- : agent.id,
243
+ : id,
244
244
  description: typeof rawConfig.description === "string" ? rawConfig.description : null,
245
245
  model: agent.config.model ?? null,
246
246
  version: typeof rawConfig.version === "string" ? rawConfig.version : null,
@@ -255,9 +255,9 @@ export async function listRuntimeAgents(
255
255
  await deps.ensureProjectDiscovery(ctx);
256
256
 
257
257
  const agents = deps.getAllAgentIds()
258
- .map((id) => deps.getAgent(id))
259
- .filter((agent): agent is Agent => Boolean(agent))
260
- .map(getRuntimeAgentMetadata)
258
+ .map((id) => ({ id, agent: deps.getAgent(id) }))
259
+ .filter((entry): entry is { id: string; agent: Agent } => Boolean(entry.agent))
260
+ .map(({ id, agent }) => getRuntimeAgentMetadata(id, agent))
261
261
  .sort((left, right) => left.name.localeCompare(right.name));
262
262
 
263
263
  return RuntimeAgentListResponseSchema.parse({ agents });
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type { Agent } from "../../agent/index.js";
6
6
  import { registerAgent } from "../../agent/index.js";
7
+ import { agentRegistry } from "../../agent/composition/index.js";
7
8
  import type { DiscoveryHandler } from "../types.js";
8
9
  import { filenameToId, trackAgentPath } from "../discovery-utils.js";
9
10
 
@@ -11,8 +12,16 @@ export const agentHandler: DiscoveryHandler<Agent> = {
11
12
  typeName: "agent",
12
13
  validate: (item): item is Agent =>
13
14
  item !== null && typeof item === "object" && typeof (item as Agent).generate === "function",
14
- getId: (agent, file) => agent.id || filenameToId(file),
15
+ getId: (agent, file) => {
16
+ const configuredId = agent.config.id;
17
+ return typeof configuredId === "string" && configuredId.trim().length > 0
18
+ ? configuredId
19
+ : filenameToId(file);
20
+ },
15
21
  register: (id, agent, file) => {
22
+ if (agent.id !== id) {
23
+ agentRegistry.delete(agent.id);
24
+ }
16
25
  registerAgent(id, agent);
17
26
  trackAgentPath(id, file);
18
27
  return agent;
@@ -1,9 +1,10 @@
1
1
  import { join } from "./path/index.js";
2
2
  import type { FileInfo } from "../adapters/base.js";
3
3
  import { createFileSystem } from "./fs.js";
4
- import { getFrameworkRootFromMeta } from "./vfs-paths.js";
4
+ import { getFrameworkRoot, getFrameworkRootFromMeta } from "./vfs-paths.js";
5
5
 
6
6
  export const FRAMEWORK_ROOT = getFrameworkRootFromMeta(import.meta.url);
7
+ export const FRAMEWORK_SRC_DIR = join(FRAMEWORK_ROOT, "src");
7
8
  export const FRAMEWORK_EMBEDDED_SRC_DIR = join(FRAMEWORK_ROOT, "dist", "framework-src");
8
9
 
9
10
  export const DEFAULT_FRAMEWORK_SOURCE_EXTENSIONS = [
@@ -37,6 +38,12 @@ export interface ResolveFrameworkSourcePathOptions {
37
38
  includeIndexFallback?: boolean;
38
39
  }
39
40
 
41
+ export interface ResolveRelativeFrameworkSourceImportOptions {
42
+ fileSystem?: FrameworkSourceFileSystem;
43
+ exists?: (path: string) => Promise<boolean>;
44
+ extensions?: readonly string[];
45
+ }
46
+
40
47
  export function getFrameworkSourceLookupDirs(extraLookupDirs: string[] = []): string[] {
41
48
  const seen = new Set<string>();
42
49
  const ordered = [
@@ -52,6 +59,48 @@ export function getFrameworkSourceLookupDirs(extraLookupDirs: string[] = []): st
52
59
  });
53
60
  }
54
61
 
62
+ export function isFrameworkSourcePath(path: string): boolean {
63
+ return path.startsWith(`${FRAMEWORK_SRC_DIR}/`) ||
64
+ path.startsWith(`${FRAMEWORK_EMBEDDED_SRC_DIR}/`);
65
+ }
66
+
67
+ function expandFrameworkCandidatePaths(candidatePath: string): string[] {
68
+ const candidates = [candidatePath];
69
+ const candidateRoot = getFrameworkRoot(candidatePath);
70
+ const candidateSrcDir = candidateRoot ? join(candidateRoot, "src") : FRAMEWORK_SRC_DIR;
71
+ const candidateEmbeddedDir = candidateRoot
72
+ ? join(candidateRoot, "dist", "framework-src")
73
+ : FRAMEWORK_EMBEDDED_SRC_DIR;
74
+
75
+ if (candidatePath.startsWith(`${candidateSrcDir}/`)) {
76
+ const relativePath = candidatePath.slice(candidateSrcDir.length + 1);
77
+ candidates.push(join(candidateEmbeddedDir, relativePath));
78
+ }
79
+
80
+ return [...new Set(candidates)];
81
+ }
82
+
83
+ async function findExistingFrameworkCandidate(
84
+ candidatePath: string,
85
+ options: ResolveRelativeFrameworkSourceImportOptions = {},
86
+ ): Promise<string | null> {
87
+ const fs = options.fileSystem ?? createFileSystem();
88
+ const exists = options.exists ?? (async (path: string) => {
89
+ try {
90
+ const stat = await fs.stat(path);
91
+ return stat.isFile;
92
+ } catch {
93
+ return false;
94
+ }
95
+ });
96
+
97
+ for (const candidate of expandFrameworkCandidatePaths(candidatePath)) {
98
+ if (await exists(candidate)) return candidate;
99
+ }
100
+
101
+ return null;
102
+ }
103
+
55
104
  export async function resolveFrameworkSourcePath(
56
105
  relativePathWithoutExt: string,
57
106
  options: ResolveFrameworkSourcePathOptions = {},
@@ -87,3 +136,54 @@ export async function resolveFrameworkSourcePath(
87
136
 
88
137
  return null;
89
138
  }
139
+
140
+ export async function resolveRelativeFrameworkSourceImport(
141
+ specifier: string,
142
+ fromSourcePath: string,
143
+ options: ResolveRelativeFrameworkSourceImportOptions = {},
144
+ ): Promise<string | null> {
145
+ const extensions = options.extensions ?? DEFAULT_FRAMEWORK_SOURCE_EXTENSIONS;
146
+ const fromDir = fromSourcePath.substring(0, fromSourcePath.lastIndexOf("/"));
147
+ const parts = fromDir.split("/").filter(Boolean);
148
+ const importParts = specifier.split("/").filter(Boolean);
149
+
150
+ for (const part of importParts) {
151
+ if (part === "..") {
152
+ parts.pop();
153
+ } else if (part !== ".") {
154
+ parts.push(part);
155
+ }
156
+ }
157
+
158
+ const basePath = "/" + parts.join("/");
159
+
160
+ if (/\.(tsx?|jsx?|mjs)$/.test(specifier)) {
161
+ const explicitCandidates = [basePath, `${basePath}.src`];
162
+
163
+ if (basePath.endsWith(".js") || basePath.endsWith(".mjs")) {
164
+ const stem = basePath.replace(/\.(?:m?js)$/, "");
165
+ for (const ext of [".ts", ".tsx", ".jsx", ".js", ".mjs"]) {
166
+ explicitCandidates.push(`${stem}${ext}.src`, `${stem}${ext}`);
167
+ }
168
+ }
169
+
170
+ for (const candidate of explicitCandidates) {
171
+ const resolved = await findExistingFrameworkCandidate(candidate, options);
172
+ if (resolved) return resolved;
173
+ }
174
+
175
+ return null;
176
+ }
177
+
178
+ for (const ext of extensions) {
179
+ const candidate = await findExistingFrameworkCandidate(basePath + ext, options);
180
+ if (candidate) return candidate;
181
+ }
182
+
183
+ for (const ext of extensions) {
184
+ const candidate = await findExistingFrameworkCandidate(join(basePath, "index" + ext), options);
185
+ if (candidate) return candidate;
186
+ }
187
+
188
+ return null;
189
+ }
@@ -11,6 +11,7 @@ import * as dntShim from "../../../../_dnt.shims.js";
11
11
 
12
12
 
13
13
  import { escapeHtml } from "../../../utils/html-escape.js";
14
+ import { buildNonceAttribute } from "../../../html/html-escape.js";
14
15
 
15
16
  /** Options for generating markdown preview HTML. */
16
17
  interface MarkdownHtmlOptions {
@@ -28,6 +29,8 @@ interface MarkdownHtmlOptions {
28
29
  projectId: string;
29
30
  /** File path of the markdown file. */
30
31
  filePath: string;
32
+ /** CSP nonce for inline scripts. */
33
+ nonce?: string;
31
34
  }
32
35
 
33
36
  /**
@@ -60,9 +63,11 @@ function buildStudioScript(
60
63
  url: URL,
61
64
  projectId: string,
62
65
  filePath: string,
66
+ nonce?: string,
63
67
  ): string {
64
68
  const studioEmbed = url.searchParams.get("studio_embed") === "true";
65
69
  if (!studioEmbed) return "";
70
+ const nonceAttr = buildNonceAttribute(nonce);
66
71
 
67
72
  const rawQueryProjectId = url.searchParams.get("vf_project_id")?.trim() || "";
68
73
  // Validate query param before using it in bridge config.
@@ -79,8 +84,8 @@ function buildStudioScript(
79
84
 
80
85
  // Escape </script> sequences to prevent XSS breakout from inline JSON
81
86
  const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
82
- return `<script>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>
83
- <script type="module" src="/_veryfront/studio-bridge.js"></script>`;
87
+ return `<script${nonceAttr}>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>
88
+ <script type="module" src="/_veryfront/studio-bridge.js"${nonceAttr}></script>`;
84
89
  }
85
90
 
86
91
  /**
@@ -91,11 +96,12 @@ function buildStudioScript(
91
96
  * studio bridge integration.
92
97
  */
93
98
  export function generateMarkdownHtml(options: MarkdownHtmlOptions): string {
94
- const { rawHtml, title, description, request, url, projectId, filePath } = options;
99
+ const { rawHtml, title, description, request, url, projectId, filePath, nonce } = options;
95
100
 
96
101
  const theme = detectTheme(request, url);
97
- const studioScript = buildStudioScript(url, projectId, filePath);
102
+ const studioScript = buildStudioScript(url, projectId, filePath, nonce);
98
103
  const themeAttrs = theme ? ` data-theme="${theme}" style="color-scheme: ${theme};"` : "";
104
+ const nonceAttr = buildNonceAttribute(nonce);
99
105
 
100
106
  return `<!DOCTYPE html>
101
107
  <html lang="en"${themeAttrs}>
@@ -117,7 +123,7 @@ export function generateMarkdownHtml(options: MarkdownHtmlOptions): string {
117
123
 
118
124
  ${studioScript}
119
125
 
120
- <script type="module">
126
+ <script type="module"${nonceAttr}>
121
127
  import mermaid from 'https://esm.sh/mermaid@11.4.1?pin=v135';
122
128
 
123
129
  function getMermaidTheme() {
@@ -177,7 +183,7 @@ export function generateMarkdownHtml(options: MarkdownHtmlOptions): string {
177
183
  </script>
178
184
 
179
185
  <!-- Preview HMR -->
180
- <script src="/_veryfront/preview-hmr.js"></script>
186
+ <script src="/_veryfront/preview-hmr.js"${nonceAttr}></script>
181
187
  </body>
182
188
  </html>`;
183
189
  }
@@ -154,6 +154,7 @@ export class MarkdownPreviewHandler extends BaseHandler {
154
154
  "server",
155
155
  );
156
156
 
157
+ const responseBuilder = this.createResponseBuilder(ctx);
157
158
  const html = generateMarkdownHtml({
158
159
  rawHtml: bundle.rawHtml || "",
159
160
  title: frontmatter.title != null ? String(frontmatter.title) : filePath,
@@ -162,18 +163,20 @@ export class MarkdownPreviewHandler extends BaseHandler {
162
163
  url,
163
164
  projectId: ctx.projectSlug || ctx.projectId || "markdown-preview",
164
165
  filePath,
166
+ nonce: responseBuilder.nonce,
165
167
  });
166
168
 
167
- const responseBuilder = this.createResponseBuilder(ctx)
169
+ responseBuilder
168
170
  .withCache("no-cache")
169
- .withContentType("text/html; charset=utf-8", html, HTTP_OK);
171
+ .withSecurity(ctx.securityConfig ?? undefined, req);
172
+ const response = responseBuilder.withContentType("text/html; charset=utf-8", html, HTTP_OK);
170
173
 
171
174
  logger.debug("Serving markdown preview", {
172
175
  filePath,
173
176
  htmlLength: html.length,
174
177
  });
175
178
 
176
- return this.respond(responseBuilder);
179
+ return this.respond(response);
177
180
  } catch (error) {
178
181
  logger.error("Error rendering markdown", {
179
182
  filePath,
@@ -16,6 +16,23 @@ const logger = serverLogger.component("api-wrapper");
16
16
  */
17
17
  const discoveredProjects = new Map<string, Promise<void>>();
18
18
 
19
+ function buildDiscoveryConfig(ctx: HandlerContext) {
20
+ const ai = ctx.config?.ai;
21
+ const skillDiscoveryEnabled = ai?.skills?.discovery?.enabled ?? true;
22
+
23
+ return {
24
+ baseDir: ctx.projectDir,
25
+ toolDirs: ai?.tools?.discovery?.paths ?? ["tools"],
26
+ agentDirs: ai?.agents?.discovery?.paths ?? ["agents"],
27
+ skillDirs: skillDiscoveryEnabled ? (ai?.skills?.discovery?.paths ?? ["skills"]) : [],
28
+ resourceDirs: ["resources"],
29
+ promptDirs: ["prompts"],
30
+ workflowDirs: ["workflows"],
31
+ fsAdapter: ctx.adapter.fs,
32
+ verbose: false,
33
+ };
34
+ }
35
+
19
36
  /** Build a discovery cache key that incorporates the release/version. */
20
37
  function discoveryKey(ctx: HandlerContext): string {
21
38
  const cacheContext = tryGetCacheKeyContext();
@@ -74,11 +91,7 @@ export async function ensureProjectDiscovery(ctx: HandlerContext): Promise<void>
74
91
  agentRegistry.clear();
75
92
  toolRegistry.clear();
76
93
 
77
- const result = await discoverAll({
78
- baseDir: ctx.projectDir,
79
- fsAdapter: ctx.adapter.fs,
80
- verbose: false,
81
- });
94
+ const result = await discoverAll(buildDiscoveryConfig(ctx));
82
95
 
83
96
  const logData = {
84
97
  projectSlug: ctx.projectSlug,
@@ -31,10 +31,19 @@ export function getSecurityHeader(
31
31
  }
32
32
 
33
33
  export function applySecurityHeaders(headers: dntShim.Headers, ctx: HandlerContext, req?: dntShim.Request): void {
34
+ applySecurityHeadersWithNonce(headers, ctx, generateNonce(), req);
35
+ }
36
+
37
+ export function applySecurityHeadersWithNonce(
38
+ headers: dntShim.Headers,
39
+ ctx: HandlerContext,
40
+ nonce: string,
41
+ req?: dntShim.Request,
42
+ ): void {
34
43
  coreApplySecurityHeaders(
35
44
  headers,
36
45
  isDev(ctx),
37
- generateNonce(),
46
+ nonce,
38
47
  ctx.cspUserHeader ?? null,
39
48
  ctx.securityConfig,
40
49
  ctx.adapter,