@frontman-ai/astro 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +53 -0
  2. package/dist/cli.js +2744 -0
  3. package/package.json +66 -0
  4. package/src/FrontmanAstro.res +20 -0
  5. package/src/FrontmanAstro.res.mjs +36 -0
  6. package/src/FrontmanAstro__AstroBindings.res +46 -0
  7. package/src/FrontmanAstro__AstroBindings.res.mjs +2 -0
  8. package/src/FrontmanAstro__Config.res +85 -0
  9. package/src/FrontmanAstro__Config.res.mjs +44 -0
  10. package/src/FrontmanAstro__Integration.res +35 -0
  11. package/src/FrontmanAstro__Integration.res.mjs +36 -0
  12. package/src/FrontmanAstro__Middleware.res +149 -0
  13. package/src/FrontmanAstro__Middleware.res.mjs +141 -0
  14. package/src/FrontmanAstro__Server.res +196 -0
  15. package/src/FrontmanAstro__Server.res.mjs +241 -0
  16. package/src/FrontmanAstro__ToolRegistry.res +21 -0
  17. package/src/FrontmanAstro__ToolRegistry.res.mjs +41 -0
  18. package/src/FrontmanAstro__ToolbarApp.res +50 -0
  19. package/src/FrontmanAstro__ToolbarApp.res.mjs +39 -0
  20. package/src/cli/FrontmanAstro__Cli.res +126 -0
  21. package/src/cli/FrontmanAstro__Cli.res.mjs +180 -0
  22. package/src/cli/FrontmanAstro__Cli__AutoEdit.res +300 -0
  23. package/src/cli/FrontmanAstro__Cli__AutoEdit.res.mjs +266 -0
  24. package/src/cli/FrontmanAstro__Cli__Detect.res +298 -0
  25. package/src/cli/FrontmanAstro__Cli__Detect.res.mjs +345 -0
  26. package/src/cli/FrontmanAstro__Cli__Files.res +244 -0
  27. package/src/cli/FrontmanAstro__Cli__Files.res.mjs +321 -0
  28. package/src/cli/FrontmanAstro__Cli__Install.res +224 -0
  29. package/src/cli/FrontmanAstro__Cli__Install.res.mjs +194 -0
  30. package/src/cli/FrontmanAstro__Cli__Style.res +22 -0
  31. package/src/cli/FrontmanAstro__Cli__Style.res.mjs +61 -0
  32. package/src/cli/FrontmanAstro__Cli__Templates.res +226 -0
  33. package/src/cli/FrontmanAstro__Cli__Templates.res.mjs +237 -0
  34. package/src/cli/cli.mjs +3 -0
  35. package/src/tools/FrontmanAstro__Tool__GetPages.res +164 -0
  36. package/src/tools/FrontmanAstro__Tool__GetPages.res.mjs +180 -0
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@frontman-ai/astro",
3
+ "version": "0.1.0",
4
+ "description": "Astro integration for Frontman",
5
+ "license": "Apache-2.0",
6
+ "author": "Frontman AI",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/frontman-ai/frontman.git",
10
+ "directory": "libs/frontman-astro"
11
+ },
12
+ "homepage": "https://frontman.sh",
13
+ "bugs": {
14
+ "url": "https://github.com/frontman-ai/frontman/issues"
15
+ },
16
+ "keywords": [
17
+ "frontman",
18
+ "astro",
19
+ "ai",
20
+ "development"
21
+ ],
22
+ "type": "module",
23
+ "main": "./src/FrontmanAstro.res.mjs",
24
+ "exports": {
25
+ ".": {
26
+ "import": "./src/FrontmanAstro.res.mjs",
27
+ "default": "./src/FrontmanAstro.res.mjs"
28
+ },
29
+ "./integration": {
30
+ "import": "./src/FrontmanAstro__Integration.res.mjs",
31
+ "default": "./src/FrontmanAstro__Integration.res.mjs"
32
+ },
33
+ "./toolbar": {
34
+ "import": "./src/FrontmanAstro__ToolbarApp.res.mjs",
35
+ "default": "./src/FrontmanAstro__ToolbarApp.res.mjs"
36
+ }
37
+ },
38
+ "bin": {
39
+ "frontman-ai-astro": "./dist/cli.js"
40
+ },
41
+ "files": [
42
+ "src",
43
+ "dist"
44
+ ],
45
+ "scripts": {
46
+ "build": "rescript build",
47
+ "build:cli": "tsup",
48
+ "prepublishOnly": "yarn build && yarn build:cli"
49
+ },
50
+ "peerDependencies": {
51
+ "astro": "^5.0.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "devDependencies": {
57
+ "@rescript/webapi": "catalog:",
58
+ "@vitest/ui": "catalog:",
59
+ "rescript": "catalog:",
60
+ "rescript-vitest": "catalog:",
61
+ "sury": "catalog:",
62
+ "tsup": "^8.0.0",
63
+ "vite": "catalog:",
64
+ "vitest": "catalog:"
65
+ }
66
+ }
@@ -0,0 +1,20 @@
1
+ // Frontman Astro Integration - exposes framework tools via HTTP
2
+ // Used by FrontmanClient__Relay to execute file system operations
3
+
4
+ module Config = FrontmanAstro__Config
5
+ module Middleware = FrontmanAstro__Middleware
6
+ module Server = FrontmanAstro__Server
7
+ module ToolRegistry = FrontmanAstro__ToolRegistry
8
+ module Integration = FrontmanAstro__Integration
9
+
10
+ // Re-export core SSE for convenience
11
+ module SSE = FrontmanFrontmanCore.FrontmanCore__SSE
12
+
13
+ // Re-export for convenience
14
+ let createMiddleware = Middleware.createMiddleware
15
+ // makeConfig accepts an object with optional fields - JS-friendly API
16
+ let makeConfig = Config.makeFromObject
17
+ type config = Config.t
18
+
19
+ // Integration export
20
+ let frontmanIntegration = Integration.make
@@ -0,0 +1,36 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as FrontmanAstro__Config$FrontmanAiAstro from "./FrontmanAstro__Config.res.mjs";
4
+ import * as FrontmanAstro__Middleware$FrontmanAiAstro from "./FrontmanAstro__Middleware.res.mjs";
5
+ import * as FrontmanAstro__Integration$FrontmanAiAstro from "./FrontmanAstro__Integration.res.mjs";
6
+
7
+ let Config;
8
+
9
+ let Middleware;
10
+
11
+ let Server;
12
+
13
+ let ToolRegistry;
14
+
15
+ let Integration;
16
+
17
+ let SSE;
18
+
19
+ let createMiddleware = FrontmanAstro__Middleware$FrontmanAiAstro.createMiddleware;
20
+
21
+ let makeConfig = FrontmanAstro__Config$FrontmanAiAstro.makeFromObject;
22
+
23
+ let frontmanIntegration = FrontmanAstro__Integration$FrontmanAiAstro.make;
24
+
25
+ export {
26
+ Config,
27
+ Middleware,
28
+ Server,
29
+ ToolRegistry,
30
+ Integration,
31
+ SSE,
32
+ createMiddleware,
33
+ makeConfig,
34
+ frontmanIntegration,
35
+ }
36
+ /* FrontmanAstro__Config-FrontmanAiAstro Not a pure module */
@@ -0,0 +1,46 @@
1
+ // Astro Integration API bindings
2
+
3
+ // Dev toolbar app configuration
4
+ // entrypoint: file path to the toolbar app module (string | URL supported, using string for simplicity)
5
+ type devToolbarAppConfig = {
6
+ id: string,
7
+ name: string,
8
+ icon: string,
9
+ entrypoint: string,
10
+ }
11
+
12
+ // Astro command type
13
+ type astroCommand = [#dev | #build | #preview | #sync]
14
+
15
+ // Hook context for astro:config:setup
16
+ type configSetupHookContext = {
17
+ addDevToolbarApp: devToolbarAppConfig => unit,
18
+ config: {root: string},
19
+ command: astroCommand,
20
+ }
21
+
22
+ // Astro integration hooks
23
+ type astroHooks = {
24
+ @as("astro:config:setup")
25
+ configSetup?: configSetupHookContext => unit,
26
+ }
27
+
28
+ // Astro integration type
29
+ type astroIntegration = {
30
+ name: string,
31
+ hooks: astroHooks,
32
+ }
33
+
34
+ // Toolbar app types
35
+ type toolbarCanvas // opaque
36
+ type toolbarApp // opaque
37
+ type toolbarServer // opaque
38
+ type toolbarAppDefinition // opaque - returned by defineToolbarApp
39
+
40
+ type toolbarAppConfig = {
41
+ init: (toolbarCanvas, toolbarApp, toolbarServer) => unit,
42
+ }
43
+
44
+ // defineToolbarApp binding - returns an object that should be export default'd
45
+ @module("astro/toolbar")
46
+ external defineToolbarApp: toolbarAppConfig => toolbarAppDefinition = "defineToolbarApp"
@@ -0,0 +1,2 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+ /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
@@ -0,0 +1,85 @@
1
+ // Astro configuration for Frontman
2
+
3
+ module Bindings = FrontmanBindings
4
+
5
+ // Default host can be overridden via FRONTMAN_HOST env var for remote development
6
+ let defaultHost = switch Bindings.Process.env->Dict.get("FRONTMAN_HOST") {
7
+ | Some(host) => host
8
+ | None => "frontman.local:4000"
9
+ }
10
+
11
+ type t = {
12
+ projectRoot: string,
13
+ // sourceRoot: root for resolving file paths from Astro's data-astro-source-file attributes
14
+ // In a monorepo, this is typically the monorepo root. Defaults to projectRoot.
15
+ sourceRoot: string,
16
+ basePath: string,
17
+ serverName: string,
18
+ serverVersion: string,
19
+ host: string,
20
+ clientUrl: string,
21
+ isLightTheme: bool,
22
+ }
23
+
24
+ // JS-friendly type for config input
25
+ type jsConfigInput = {
26
+ projectRoot?: string,
27
+ sourceRoot?: string,
28
+ basePath?: string,
29
+ serverName?: string,
30
+ serverVersion?: string,
31
+ host?: string,
32
+ clientUrl?: string,
33
+ isLightTheme?: bool,
34
+ }
35
+
36
+ // JS-friendly function that accepts a config object
37
+ // Use this from JavaScript/TypeScript: makeConfig({ projectRoot: "..." })
38
+ let makeFromObject = (config: jsConfigInput): t => {
39
+ let projectRoot =
40
+ config.projectRoot
41
+ ->Option.orElse(
42
+ Bindings.Process.env
43
+ ->Dict.get("PROJECT_ROOT")
44
+ ->Option.orElse(Bindings.Process.env->Dict.get("PWD")),
45
+ )
46
+ ->Option.getOr(".")
47
+
48
+ let sourceRoot = config.sourceRoot->Option.getOr(projectRoot)
49
+ let basePath = config.basePath->Option.getOr("frontman")
50
+ let serverName = config.serverName->Option.getOr("frontman-astro")
51
+ let serverVersion = config.serverVersion->Option.getOr("1.0.0")
52
+ let isLightTheme = config.isLightTheme->Option.getOr(false)
53
+ let host = config.host->Option.getOr(defaultHost)
54
+
55
+ let clientUrl = config.clientUrl->Option.getOr({
56
+ let baseUrl =
57
+ Bindings.Process.env
58
+ ->Dict.get("FRONTMAN_CLIENT_URL")
59
+ ->Option.getOr("http://localhost:5173/src/Main.res.mjs")
60
+ // Use URL API to properly append params (handles base URLs that already have query strings)
61
+ let url = WebAPI.URL.make(~url=baseUrl)
62
+ url.searchParams->WebAPI.URLSearchParams.set(~name="clientName", ~value="astro")
63
+ url.searchParams->WebAPI.URLSearchParams.set(~name="host", ~value=host)
64
+ url.href
65
+ })
66
+
67
+ // Assert clientUrl contains the required "host" query param that the client reads from import.meta.url
68
+ let parsedUrl = WebAPI.URL.make(~url=clientUrl)
69
+ if !(parsedUrl.searchParams->WebAPI.URLSearchParams.has(~name="host")) {
70
+ JsError.throwWithMessage(
71
+ `[frontman-astro] clientUrl must include a "host" query parameter. Got: ${clientUrl}`,
72
+ )
73
+ }
74
+
75
+ {
76
+ projectRoot,
77
+ sourceRoot,
78
+ basePath,
79
+ serverName,
80
+ serverVersion,
81
+ host,
82
+ clientUrl,
83
+ isLightTheme,
84
+ }
85
+ }
@@ -0,0 +1,44 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+
6
+ let host = process.env["FRONTMAN_HOST"];
7
+
8
+ let defaultHost = host !== undefined ? host : "frontman.local:4000";
9
+
10
+ function makeFromObject(config) {
11
+ let projectRoot = Stdlib_Option.getOr(Stdlib_Option.orElse(config.projectRoot, Stdlib_Option.orElse(process.env["PROJECT_ROOT"], process.env["PWD"])), ".");
12
+ let sourceRoot = Stdlib_Option.getOr(config.sourceRoot, projectRoot);
13
+ let basePath = Stdlib_Option.getOr(config.basePath, "frontman");
14
+ let serverName = Stdlib_Option.getOr(config.serverName, "frontman-astro");
15
+ let serverVersion = Stdlib_Option.getOr(config.serverVersion, "1.0.0");
16
+ let isLightTheme = Stdlib_Option.getOr(config.isLightTheme, false);
17
+ let host = Stdlib_Option.getOr(config.host, defaultHost);
18
+ let baseUrl = Stdlib_Option.getOr(process.env["FRONTMAN_CLIENT_URL"], "http://localhost:5173/src/Main.res.mjs");
19
+ let url = new URL(baseUrl);
20
+ let clientUrl = Stdlib_Option.getOr(config.clientUrl, (url.searchParams.set("clientName", "astro"), url.searchParams.set("host", host), url.href));
21
+ let parsedUrl = new URL(clientUrl);
22
+ if (!parsedUrl.searchParams.has("host")) {
23
+ Stdlib_JsError.throwWithMessage(`[frontman-astro] clientUrl must include a "host" query parameter. Got: ` + clientUrl);
24
+ }
25
+ return {
26
+ projectRoot: projectRoot,
27
+ sourceRoot: sourceRoot,
28
+ basePath: basePath,
29
+ serverName: serverName,
30
+ serverVersion: serverVersion,
31
+ host: host,
32
+ clientUrl: clientUrl,
33
+ isLightTheme: isLightTheme
34
+ };
35
+ }
36
+
37
+ let Bindings;
38
+
39
+ export {
40
+ Bindings,
41
+ defaultHost,
42
+ makeFromObject,
43
+ }
44
+ /* host Not a pure module */
@@ -0,0 +1,35 @@
1
+ // Frontman Astro Integration
2
+
3
+ module Bindings = FrontmanAstro__AstroBindings
4
+
5
+ // SVG icon for the toolbar
6
+ let icon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>`
7
+
8
+ // Get the path to the toolbar app entrypoint
9
+ // Uses import.meta.url to resolve relative to this file
10
+ @val @scope(("import", "meta"))
11
+ external importMetaUrl: string = "url"
12
+
13
+ // Helper to create URL and get pathname
14
+ let getToolbarAppPath = () => {
15
+ let url = WebAPI.URL.make(~url="./FrontmanAstro__ToolbarApp.res.mjs", ~base=importMetaUrl)
16
+ url.pathname
17
+ }
18
+
19
+ // Create the Astro integration
20
+ let make = (): Bindings.astroIntegration => {
21
+ name: "frontman",
22
+ hooks: {
23
+ configSetup: ?Some(ctx => {
24
+ // Only add dev toolbar app in dev mode
25
+ if ctx.command == #dev {
26
+ ctx.addDevToolbarApp({
27
+ id: "frontman:toolbar",
28
+ name: "Frontman",
29
+ icon,
30
+ entrypoint: getToolbarAppPath(),
31
+ })
32
+ }
33
+ }),
34
+ },
35
+ }
@@ -0,0 +1,36 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let icon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>`;
5
+
6
+ function getToolbarAppPath() {
7
+ return new URL("./FrontmanAstro__ToolbarApp.res.mjs", import.meta.url).pathname;
8
+ }
9
+
10
+ function make() {
11
+ return {
12
+ name: "frontman",
13
+ hooks: {
14
+ "astro:config:setup": ctx => {
15
+ if (ctx.command === "dev") {
16
+ return ctx.addDevToolbarApp({
17
+ id: "frontman:toolbar",
18
+ name: "Frontman",
19
+ icon: icon,
20
+ entrypoint: getToolbarAppPath()
21
+ });
22
+ }
23
+ }
24
+ }
25
+ };
26
+ }
27
+
28
+ let Bindings;
29
+
30
+ export {
31
+ Bindings,
32
+ icon,
33
+ getToolbarAppPath,
34
+ make,
35
+ }
36
+ /* No side effect */
@@ -0,0 +1,149 @@
1
+ // Astro middleware for Frontman
2
+
3
+ module Config = FrontmanAstro__Config
4
+ module Server = FrontmanAstro__Server
5
+ module ToolRegistry = FrontmanAstro__ToolRegistry
6
+
7
+ // Annotation capture script - injected before </body>
8
+ // Stores paths exactly as Astro provides them (should be absolute paths)
9
+ let annotationCaptureScript = `
10
+ <script>
11
+ (function() {
12
+ var annotations = new Map();
13
+ document.querySelectorAll('[data-astro-source-file]').forEach(function(el) {
14
+ annotations.set(el, {
15
+ file: el.getAttribute('data-astro-source-file'),
16
+ loc: el.getAttribute('data-astro-source-loc')
17
+ });
18
+ });
19
+ window.__frontman_annotations__ = {
20
+ _map: annotations,
21
+ get: function(el) { return annotations.get(el); },
22
+ has: function(el) { return annotations.has(el); },
23
+ size: function() { return annotations.size; }
24
+ };
25
+ console.log('[Frontman] Captured ' + annotations.size + ' elements');
26
+ })();
27
+ </script>
28
+ `
29
+
30
+ // Helper to inject script into HTML response
31
+ let injectAnnotationScript = async (response: WebAPI.FetchAPI.response): WebAPI.FetchAPI.response => {
32
+ let contentType = response.headers->WebAPI.Headers.get("content-type")->Null.toOption
33
+
34
+ switch contentType {
35
+ | Some(ct) if ct->String.includes("text/html") =>
36
+ let html = await response->WebAPI.Response.text
37
+ let injectedHtml = html->String.replace("</body>", `${annotationCaptureScript}</body>`)
38
+
39
+ WebAPI.Response.fromString(
40
+ injectedHtml,
41
+ ~init={
42
+ status: response.status,
43
+ headers: WebAPI.HeadersInit.fromHeaders(response.headers),
44
+ },
45
+ )
46
+ | _ => response
47
+ }
48
+ }
49
+
50
+ // HTML template for the Frontman UI
51
+ let uiHtml = (~clientUrl: string, ~isLightTheme: bool) => {
52
+ // Get the raw env var and filter out empty strings
53
+ let openrouterKey =
54
+ FrontmanBindings.Process.env
55
+ ->Dict.get("OPENROUTER_API_KEY")
56
+ ->Option.flatMap(key => key != "" ? Some(key) : None)
57
+ // Build JSON payload using proper JSON encoding to handle special characters
58
+ let configObj = Dict.fromArray([("framework", JSON.Encode.string("Astro"))])
59
+ openrouterKey->Option.forEach(key => {
60
+ configObj->Dict.set("openrouterKeyValue", JSON.Encode.string(key))
61
+ })
62
+ let runtimeConfig = JSON.stringify(JSON.Encode.object(configObj))
63
+ let themeClass = isLightTheme ? "" : "dark"
64
+ `<!DOCTYPE html>
65
+ <html lang="en" class="${themeClass}">
66
+ <head>
67
+ <meta charset="UTF-8">
68
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
69
+ <title>Frontman</title>
70
+ <style>
71
+ html, body, #root {
72
+ margin: 0;
73
+ padding: 0;
74
+ height: 100%;
75
+ width: 100%;
76
+ }
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <div id="root"></div>
81
+ <script>window.__frontmanRuntime=${runtimeConfig}</script>
82
+ <script type="module" src="${clientUrl}"></script>
83
+ </body>
84
+ </html>`
85
+ }
86
+
87
+ // Serve UI HTML
88
+ let serveUI = (config: Config.t): WebAPI.FetchAPI.response => {
89
+ let html = uiHtml(~clientUrl=config.clientUrl, ~isLightTheme=config.isLightTheme)
90
+ let headers = WebAPI.HeadersInit.fromDict(Dict.fromArray([("Content-Type", "text/html")]))
91
+ WebAPI.Response.fromString(html, ~init={headers: headers})
92
+ }
93
+
94
+ // Type for Astro URL object (subset we need)
95
+ type astroUrl = {pathname: string}
96
+
97
+ // Type for Astro middleware context (subset of APIContext we actually use)
98
+ type astroContext = {
99
+ request: WebAPI.FetchAPI.request,
100
+ url: astroUrl,
101
+ }
102
+
103
+ // Type for Astro next function
104
+ type astroNext = unit => promise<WebAPI.FetchAPI.response>
105
+
106
+ // Create middleware handler
107
+ // Returns a function that can be used directly as Astro middleware
108
+ let createMiddleware = (config: Config.t) => {
109
+ let registry = ToolRegistry.make()
110
+
111
+ async (context: astroContext, next: astroNext): WebAPI.FetchAPI.response => {
112
+ let pathname = context.url.pathname
113
+ let method = context.request.method
114
+
115
+ let basePath = `/${config.basePath}`
116
+
117
+ // Check if this is a frontman route (exact match or subpath)
118
+ if !(pathname == basePath || pathname->String.startsWith(`${basePath}/`)) {
119
+ // Not a frontman route - pass through but inject script into HTML
120
+ let response = await next()
121
+ await injectAnnotationScript(response)
122
+ } else if method == "OPTIONS" {
123
+ // Handle CORS preflight
124
+ Server.handleCORS()
125
+ } else {
126
+ // Route handling
127
+ switch pathname {
128
+ | p if p == basePath || p == `${basePath}/` =>
129
+ serveUI(config)
130
+
131
+ | p if p == `${basePath}/tools` && method == "GET" =>
132
+ Server.handleGetTools(~registry, ~config)
133
+
134
+ | p if p == `${basePath}/tools/call` && method == "POST" =>
135
+ await Server.handleToolCall(~registry, ~config, context.request)
136
+
137
+ | p if p == `${basePath}/resolve-source-location` && method == "POST" =>
138
+ await Server.handleResolveSourceLocation(~config, context.request)
139
+
140
+ | _ =>
141
+ // Unknown frontman route
142
+ WebAPI.Response.jsonR(
143
+ ~data=JSON.Encode.object(Dict.fromArray([("error", JSON.Encode.string("Not found"))])),
144
+ ~init={status: 404},
145
+ )
146
+ }
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,141 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
+ import * as FrontmanAstro__Server$FrontmanAiAstro from "./FrontmanAstro__Server.res.mjs";
6
+ import * as FrontmanAstro__ToolRegistry$FrontmanAiAstro from "./FrontmanAstro__ToolRegistry.res.mjs";
7
+
8
+ let annotationCaptureScript = `
9
+ <script>
10
+ (function() {
11
+ var annotations = new Map();
12
+ document.querySelectorAll('[data-astro-source-file]').forEach(function(el) {
13
+ annotations.set(el, {
14
+ file: el.getAttribute('data-astro-source-file'),
15
+ loc: el.getAttribute('data-astro-source-loc')
16
+ });
17
+ });
18
+ window.__frontman_annotations__ = {
19
+ _map: annotations,
20
+ get: function(el) { return annotations.get(el); },
21
+ has: function(el) { return annotations.has(el); },
22
+ size: function() { return annotations.size; }
23
+ };
24
+ console.log('[Frontman] Captured ' + annotations.size + ' elements');
25
+ })();
26
+ </script>
27
+ `;
28
+
29
+ async function injectAnnotationScript(response) {
30
+ let contentType = response.headers.get("content-type");
31
+ if (contentType === null) {
32
+ return response;
33
+ }
34
+ if (!contentType.includes("text/html")) {
35
+ return response;
36
+ }
37
+ let html = await response.text();
38
+ let injectedHtml = html.replace("</body>", annotationCaptureScript + `</body>`);
39
+ return new Response(injectedHtml, {
40
+ status: response.status,
41
+ headers: Primitive_option.some(response.headers)
42
+ });
43
+ }
44
+
45
+ function uiHtml(clientUrl, isLightTheme) {
46
+ let openrouterKey = Stdlib_Option.flatMap(process.env["OPENROUTER_API_KEY"], key => {
47
+ if (key !== "") {
48
+ return key;
49
+ }
50
+ });
51
+ let configObj = Object.fromEntries([[
52
+ "framework",
53
+ "Astro"
54
+ ]]);
55
+ Stdlib_Option.forEach(openrouterKey, key => {
56
+ configObj["openrouterKeyValue"] = key;
57
+ });
58
+ let runtimeConfig = JSON.stringify(configObj);
59
+ let themeClass = isLightTheme ? "" : "dark";
60
+ return `<!DOCTYPE html>
61
+ <html lang="en" class="` + themeClass + `">
62
+ <head>
63
+ <meta charset="UTF-8">
64
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
65
+ <title>Frontman</title>
66
+ <style>
67
+ html, body, #root {
68
+ margin: 0;
69
+ padding: 0;
70
+ height: 100%;
71
+ width: 100%;
72
+ }
73
+ </style>
74
+ </head>
75
+ <body>
76
+ <div id="root"></div>
77
+ <script>window.__frontmanRuntime=` + runtimeConfig + `</script>
78
+ <script type="module" src="` + clientUrl + `"></script>
79
+ </body>
80
+ </html>`;
81
+ }
82
+
83
+ function serveUI(config) {
84
+ let html = uiHtml(config.clientUrl, config.isLightTheme);
85
+ let headers = Object.fromEntries([[
86
+ "Content-Type",
87
+ "text/html"
88
+ ]]);
89
+ return new Response(html, {
90
+ headers: Primitive_option.some(headers)
91
+ });
92
+ }
93
+
94
+ function createMiddleware(config) {
95
+ let registry = FrontmanAstro__ToolRegistry$FrontmanAiAstro.make();
96
+ return async (context, next) => {
97
+ let pathname = context.url.pathname;
98
+ let method = context.request.method;
99
+ let basePath = `/` + config.basePath;
100
+ if (pathname === basePath || pathname.startsWith(basePath + `/`)) {
101
+ if (method === "OPTIONS") {
102
+ return FrontmanAstro__Server$FrontmanAiAstro.handleCORS();
103
+ } else if (pathname === basePath || pathname === basePath + `/`) {
104
+ return serveUI(config);
105
+ } else if (pathname === basePath + `/tools` && method === "GET") {
106
+ return FrontmanAstro__Server$FrontmanAiAstro.handleGetTools(registry, config);
107
+ } else if (pathname === basePath + `/tools/call` && method === "POST") {
108
+ return await FrontmanAstro__Server$FrontmanAiAstro.handleToolCall(registry, config, context.request);
109
+ } else if (pathname === basePath + `/resolve-source-location` && method === "POST") {
110
+ return await FrontmanAstro__Server$FrontmanAiAstro.handleResolveSourceLocation(config, context.request);
111
+ } else {
112
+ return Response.json(Object.fromEntries([[
113
+ "error",
114
+ "Not found"
115
+ ]]), {
116
+ status: 404
117
+ });
118
+ }
119
+ }
120
+ let response = await next();
121
+ return await injectAnnotationScript(response);
122
+ };
123
+ }
124
+
125
+ let Config;
126
+
127
+ let Server;
128
+
129
+ let ToolRegistry;
130
+
131
+ export {
132
+ Config,
133
+ Server,
134
+ ToolRegistry,
135
+ annotationCaptureScript,
136
+ injectAnnotationScript,
137
+ uiHtml,
138
+ serveUI,
139
+ createMiddleware,
140
+ }
141
+ /* FrontmanAstro__Server-FrontmanAiAstro Not a pure module */