@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.
- package/README.md +53 -0
- package/dist/cli.js +2744 -0
- package/package.json +66 -0
- package/src/FrontmanAstro.res +20 -0
- package/src/FrontmanAstro.res.mjs +36 -0
- package/src/FrontmanAstro__AstroBindings.res +46 -0
- package/src/FrontmanAstro__AstroBindings.res.mjs +2 -0
- package/src/FrontmanAstro__Config.res +85 -0
- package/src/FrontmanAstro__Config.res.mjs +44 -0
- package/src/FrontmanAstro__Integration.res +35 -0
- package/src/FrontmanAstro__Integration.res.mjs +36 -0
- package/src/FrontmanAstro__Middleware.res +149 -0
- package/src/FrontmanAstro__Middleware.res.mjs +141 -0
- package/src/FrontmanAstro__Server.res +196 -0
- package/src/FrontmanAstro__Server.res.mjs +241 -0
- package/src/FrontmanAstro__ToolRegistry.res +21 -0
- package/src/FrontmanAstro__ToolRegistry.res.mjs +41 -0
- package/src/FrontmanAstro__ToolbarApp.res +50 -0
- package/src/FrontmanAstro__ToolbarApp.res.mjs +39 -0
- package/src/cli/FrontmanAstro__Cli.res +126 -0
- package/src/cli/FrontmanAstro__Cli.res.mjs +180 -0
- package/src/cli/FrontmanAstro__Cli__AutoEdit.res +300 -0
- package/src/cli/FrontmanAstro__Cli__AutoEdit.res.mjs +266 -0
- package/src/cli/FrontmanAstro__Cli__Detect.res +298 -0
- package/src/cli/FrontmanAstro__Cli__Detect.res.mjs +345 -0
- package/src/cli/FrontmanAstro__Cli__Files.res +244 -0
- package/src/cli/FrontmanAstro__Cli__Files.res.mjs +321 -0
- package/src/cli/FrontmanAstro__Cli__Install.res +224 -0
- package/src/cli/FrontmanAstro__Cli__Install.res.mjs +194 -0
- package/src/cli/FrontmanAstro__Cli__Style.res +22 -0
- package/src/cli/FrontmanAstro__Cli__Style.res.mjs +61 -0
- package/src/cli/FrontmanAstro__Cli__Templates.res +226 -0
- package/src/cli/FrontmanAstro__Cli__Templates.res.mjs +237 -0
- package/src/cli/cli.mjs +3 -0
- package/src/tools/FrontmanAstro__Tool__GetPages.res +164 -0
- 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,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 */
|