@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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Request handlers for Frontman Astro endpoints
|
|
2
|
+
|
|
3
|
+
module Protocol = FrontmanFrontmanProtocol
|
|
4
|
+
module MCP = Protocol.FrontmanProtocol__MCP
|
|
5
|
+
module Relay = Protocol.FrontmanProtocol__Relay
|
|
6
|
+
module Core = FrontmanFrontmanCore
|
|
7
|
+
module CoreServer = Core.FrontmanCore__Server
|
|
8
|
+
module CoreSSE = Core.FrontmanCore__SSE
|
|
9
|
+
module ToolRegistry = FrontmanAstro__ToolRegistry
|
|
10
|
+
module Config = FrontmanAstro__Config
|
|
11
|
+
module WebStreams = FrontmanBindings.WebStreams
|
|
12
|
+
module DOMElementToComponentSource = FrontmanBindings.DOMElementToComponentSource
|
|
13
|
+
|
|
14
|
+
// GET /frontman/tools
|
|
15
|
+
let handleGetTools = (~registry: ToolRegistry.t, ~config: Config.t): WebAPI.FetchAPI.response => {
|
|
16
|
+
let response = CoreServer.getToolsResponse(
|
|
17
|
+
~registry,
|
|
18
|
+
~serverName=config.serverName,
|
|
19
|
+
~serverVersion=config.serverVersion,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
let json = response->S.reverseConvertToJsonOrThrow(Relay.toolsResponseSchema)
|
|
23
|
+
let headers = WebAPI.HeadersInit.fromDict(Dict.fromArray([("Content-Type", "application/json")]))
|
|
24
|
+
WebAPI.Response.jsonR(~data=json, ~init={headers: headers})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// POST /frontman/tools/call - executes tool with SSE streaming
|
|
28
|
+
let handleToolCall = async (
|
|
29
|
+
~registry: ToolRegistry.t,
|
|
30
|
+
~config: Config.t,
|
|
31
|
+
req: WebAPI.FetchAPI.request,
|
|
32
|
+
): WebAPI.FetchAPI.response => {
|
|
33
|
+
let body = await req->WebAPI.Request.json
|
|
34
|
+
|
|
35
|
+
let request = try {
|
|
36
|
+
Ok(body->S.parseOrThrow(Relay.toolCallRequestSchema))
|
|
37
|
+
} catch {
|
|
38
|
+
| S.Error(e) => Error(e.message)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
switch request {
|
|
42
|
+
| Error(msg) =>
|
|
43
|
+
let errorResult: MCP.callToolResult = {
|
|
44
|
+
content: [{type_: "text", text: `Invalid request: ${msg}`}],
|
|
45
|
+
isError: Some(true),
|
|
46
|
+
}
|
|
47
|
+
let json = errorResult->S.reverseConvertToJsonOrThrow(MCP.callToolResultSchema)
|
|
48
|
+
WebAPI.Response.jsonR(~data=json, ~init={status: 400})
|
|
49
|
+
|
|
50
|
+
| Ok(request) =>
|
|
51
|
+
// Execute tool using core
|
|
52
|
+
let ctx: CoreServer.executionContext = {
|
|
53
|
+
projectRoot: config.projectRoot,
|
|
54
|
+
sourceRoot: config.sourceRoot,
|
|
55
|
+
onProgress: None,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let resultPromise = CoreServer.executeTool(
|
|
59
|
+
~registry,
|
|
60
|
+
~ctx,
|
|
61
|
+
~name=request.name,
|
|
62
|
+
~arguments=request.arguments,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
let encoder = WebStreams.makeTextEncoder()
|
|
66
|
+
let stream = WebStreams.makeReadableStream({
|
|
67
|
+
start: controller => {
|
|
68
|
+
let _ = resultPromise->Promise.then(result => {
|
|
69
|
+
let mcpResult = CoreServer.resultToMCP(result)
|
|
70
|
+
let eventData = switch mcpResult.isError {
|
|
71
|
+
| Some(true) => CoreSSE.errorEvent(mcpResult)
|
|
72
|
+
| _ => CoreSSE.resultEvent(mcpResult)
|
|
73
|
+
}
|
|
74
|
+
controller->WebStreams.enqueue(encoder->WebStreams.encode(eventData))
|
|
75
|
+
controller->WebStreams.close
|
|
76
|
+
Promise.resolve()
|
|
77
|
+
})
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
WebAPI.Response.fromReadableStream(stream, ~init={headers: CoreSSE.headers()})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Helper to convert absolute path to relative path (relative to sourceRoot)
|
|
86
|
+
// If the path starts with sourceRoot, strip it; otherwise return as-is
|
|
87
|
+
let toRelativePath = (absolutePath: string, sourceRoot: string): string => {
|
|
88
|
+
// Ensure sourceRoot ends with / for proper stripping
|
|
89
|
+
let normalizedRoot = if sourceRoot->String.endsWith("/") {
|
|
90
|
+
sourceRoot
|
|
91
|
+
} else {
|
|
92
|
+
sourceRoot ++ "/"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if absolutePath->String.startsWith(normalizedRoot) {
|
|
96
|
+
absolutePath->String.slice(~start=normalizedRoot->String.length, ~end=absolutePath->String.length)
|
|
97
|
+
} else if absolutePath->String.startsWith(sourceRoot) {
|
|
98
|
+
// Handle case where sourceRoot doesn't end with / but path matches exactly
|
|
99
|
+
absolutePath->String.slice(~start=sourceRoot->String.length, ~end=absolutePath->String.length)
|
|
100
|
+
} else {
|
|
101
|
+
absolutePath
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// CORS headers for preflight requests
|
|
106
|
+
let corsHeaders = () => {
|
|
107
|
+
WebAPI.HeadersInit.fromDict(
|
|
108
|
+
Dict.fromArray([
|
|
109
|
+
("Access-Control-Allow-Origin", "*"),
|
|
110
|
+
("Access-Control-Allow-Methods", "GET, POST, OPTIONS"),
|
|
111
|
+
("Access-Control-Allow-Headers", "Content-Type"),
|
|
112
|
+
]),
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle CORS preflight
|
|
117
|
+
let handleCORS = (): WebAPI.FetchAPI.response => {
|
|
118
|
+
WebAPI.Response.fromNull(~init={status: 204, headers: corsHeaders()})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// POST /frontman/resolve-source-location - resolves source location via source maps
|
|
122
|
+
let handleResolveSourceLocation = async (
|
|
123
|
+
~config: Config.t,
|
|
124
|
+
req: WebAPI.FetchAPI.request,
|
|
125
|
+
): WebAPI.FetchAPI.response => {
|
|
126
|
+
let body = await req->WebAPI.Request.json
|
|
127
|
+
|
|
128
|
+
let requestObj = body->JSON.Decode.object
|
|
129
|
+
|
|
130
|
+
switch requestObj {
|
|
131
|
+
| None =>
|
|
132
|
+
WebAPI.Response.jsonR(
|
|
133
|
+
~data=JSON.Encode.object(Dict.fromArray([("error", JSON.Encode.string("Invalid request body"))])),
|
|
134
|
+
~init={status: 400},
|
|
135
|
+
)
|
|
136
|
+
| Some(obj) =>
|
|
137
|
+
let componentName = obj->Dict.get("componentName")->Option.flatMap(JSON.Decode.string)
|
|
138
|
+
let file = obj->Dict.get("file")->Option.flatMap(JSON.Decode.string)
|
|
139
|
+
let line = obj->Dict.get("line")->Option.flatMap(JSON.Decode.float)
|
|
140
|
+
let column = obj->Dict.get("column")->Option.flatMap(JSON.Decode.float)
|
|
141
|
+
|
|
142
|
+
switch (componentName, file, line, column) {
|
|
143
|
+
| (Some(componentName), Some(file), Some(line), Some(column)) =>
|
|
144
|
+
try {
|
|
145
|
+
let sourceLocation: DOMElementToComponentSource.sourceLocation = {
|
|
146
|
+
componentName,
|
|
147
|
+
file,
|
|
148
|
+
line: line->Float.toInt,
|
|
149
|
+
column: column->Float.toInt,
|
|
150
|
+
componentProps: None,
|
|
151
|
+
parent: None,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let resolved = await DOMElementToComponentSource.resolveSourceLocationInServer(sourceLocation)
|
|
155
|
+
|
|
156
|
+
// Convert absolute path to relative path (relative to sourceRoot)
|
|
157
|
+
// This ensures the agent can use the path directly with MCP tools
|
|
158
|
+
let relativeFile = toRelativePath(resolved.file, config.sourceRoot)
|
|
159
|
+
|
|
160
|
+
let responseJson = JSON.Encode.object(
|
|
161
|
+
Dict.fromArray([
|
|
162
|
+
("componentName", JSON.Encode.string(resolved.componentName)),
|
|
163
|
+
("file", JSON.Encode.string(relativeFile)),
|
|
164
|
+
("line", JSON.Encode.float(resolved.line->Int.toFloat)),
|
|
165
|
+
("column", JSON.Encode.float(resolved.column->Int.toFloat)),
|
|
166
|
+
]),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
let headers = WebAPI.HeadersInit.fromDict(Dict.fromArray([("Content-Type", "application/json")]))
|
|
170
|
+
WebAPI.Response.jsonR(~data=responseJson, ~init={headers: headers})
|
|
171
|
+
} catch {
|
|
172
|
+
| exn =>
|
|
173
|
+
let msg =
|
|
174
|
+
exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("Unknown error")
|
|
175
|
+
WebAPI.Response.jsonR(
|
|
176
|
+
~data=JSON.Encode.object(
|
|
177
|
+
Dict.fromArray([
|
|
178
|
+
("error", JSON.Encode.string("Failed to resolve source location")),
|
|
179
|
+
("details", JSON.Encode.string(msg)),
|
|
180
|
+
]),
|
|
181
|
+
),
|
|
182
|
+
~init={status: 500},
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
| _ =>
|
|
186
|
+
WebAPI.Response.jsonR(
|
|
187
|
+
~data=JSON.Encode.object(
|
|
188
|
+
Dict.fromArray([
|
|
189
|
+
("error", JSON.Encode.string("Missing required fields: componentName, file, line, column")),
|
|
190
|
+
]),
|
|
191
|
+
),
|
|
192
|
+
~init={status: 400},
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Web from "stream/web";
|
|
5
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
6
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
7
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
9
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
10
|
+
import * as FrontmanCore__SSE$FrontmanFrontmanCore from "@frontman/frontman-core/src/FrontmanCore__SSE.res.mjs";
|
|
11
|
+
import * as Server from "dom-element-to-component-source/server";
|
|
12
|
+
import * as FrontmanCore__Server$FrontmanFrontmanCore from "@frontman/frontman-core/src/FrontmanCore__Server.res.mjs";
|
|
13
|
+
import * as FrontmanProtocol__MCP$FrontmanFrontmanProtocol from "@frontman/frontman-protocol/src/FrontmanProtocol__MCP.res.mjs";
|
|
14
|
+
import * as FrontmanProtocol__Relay$FrontmanFrontmanProtocol from "@frontman/frontman-protocol/src/FrontmanProtocol__Relay.res.mjs";
|
|
15
|
+
|
|
16
|
+
function handleGetTools(registry, config) {
|
|
17
|
+
let response = FrontmanCore__Server$FrontmanFrontmanCore.getToolsResponse(registry, config.serverName, config.serverVersion);
|
|
18
|
+
let json = S.reverseConvertToJsonOrThrow(response, FrontmanProtocol__Relay$FrontmanFrontmanProtocol.toolsResponseSchema);
|
|
19
|
+
let headers = Object.fromEntries([[
|
|
20
|
+
"Content-Type",
|
|
21
|
+
"application/json"
|
|
22
|
+
]]);
|
|
23
|
+
return Response.json(json, {
|
|
24
|
+
headers: Primitive_option.some(headers)
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function handleToolCall(registry, config, req) {
|
|
29
|
+
let body = await req.json();
|
|
30
|
+
let request;
|
|
31
|
+
try {
|
|
32
|
+
request = {
|
|
33
|
+
TAG: "Ok",
|
|
34
|
+
_0: S.parseOrThrow(body, FrontmanProtocol__Relay$FrontmanFrontmanProtocol.toolCallRequestSchema)
|
|
35
|
+
};
|
|
36
|
+
} catch (raw_e) {
|
|
37
|
+
let e = Primitive_exceptions.internalToException(raw_e);
|
|
38
|
+
if (e.RE_EXN_ID === S.$$Error) {
|
|
39
|
+
request = {
|
|
40
|
+
TAG: "Error",
|
|
41
|
+
_0: e._1.message
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (request.TAG === "Ok") {
|
|
48
|
+
let request$1 = request._0;
|
|
49
|
+
let ctx_projectRoot = config.projectRoot;
|
|
50
|
+
let ctx_sourceRoot = config.sourceRoot;
|
|
51
|
+
let ctx = {
|
|
52
|
+
projectRoot: ctx_projectRoot,
|
|
53
|
+
sourceRoot: ctx_sourceRoot,
|
|
54
|
+
onProgress: undefined
|
|
55
|
+
};
|
|
56
|
+
let resultPromise = FrontmanCore__Server$FrontmanFrontmanCore.executeTool(registry, ctx, request$1.name, request$1.arguments);
|
|
57
|
+
let encoder = new TextEncoder();
|
|
58
|
+
let stream = new Web.ReadableStream({
|
|
59
|
+
start: controller => {
|
|
60
|
+
resultPromise.then(result => {
|
|
61
|
+
let mcpResult = FrontmanCore__Server$FrontmanFrontmanCore.resultToMCP(result);
|
|
62
|
+
let match = mcpResult.isError;
|
|
63
|
+
let eventData = match !== undefined && match ? FrontmanCore__SSE$FrontmanFrontmanCore.errorEvent(mcpResult) : FrontmanCore__SSE$FrontmanFrontmanCore.resultEvent(mcpResult);
|
|
64
|
+
controller.enqueue(encoder.encode(eventData));
|
|
65
|
+
controller.close();
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return new Response(stream, {
|
|
71
|
+
headers: Primitive_option.some(FrontmanCore__SSE$FrontmanFrontmanCore.headers())
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
let errorResult_content = [{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: `Invalid request: ` + request._0
|
|
77
|
+
}];
|
|
78
|
+
let errorResult_isError = true;
|
|
79
|
+
let errorResult = {
|
|
80
|
+
content: errorResult_content,
|
|
81
|
+
isError: errorResult_isError
|
|
82
|
+
};
|
|
83
|
+
let json = S.reverseConvertToJsonOrThrow(errorResult, FrontmanProtocol__MCP$FrontmanFrontmanProtocol.callToolResultSchema);
|
|
84
|
+
return Response.json(json, {
|
|
85
|
+
status: 400
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function toRelativePath(absolutePath, sourceRoot) {
|
|
90
|
+
let normalizedRoot = sourceRoot.endsWith("/") ? sourceRoot : sourceRoot + "/";
|
|
91
|
+
if (absolutePath.startsWith(normalizedRoot)) {
|
|
92
|
+
return absolutePath.slice(normalizedRoot.length, absolutePath.length);
|
|
93
|
+
} else if (absolutePath.startsWith(sourceRoot)) {
|
|
94
|
+
return absolutePath.slice(sourceRoot.length, absolutePath.length);
|
|
95
|
+
} else {
|
|
96
|
+
return absolutePath;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function corsHeaders() {
|
|
101
|
+
return Object.fromEntries([
|
|
102
|
+
[
|
|
103
|
+
"Access-Control-Allow-Origin",
|
|
104
|
+
"*"
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
"Access-Control-Allow-Methods",
|
|
108
|
+
"GET, POST, OPTIONS"
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
"Access-Control-Allow-Headers",
|
|
112
|
+
"Content-Type"
|
|
113
|
+
]
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function handleCORS() {
|
|
118
|
+
return new Response(null, {
|
|
119
|
+
status: 204,
|
|
120
|
+
headers: Primitive_option.some(corsHeaders())
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function handleResolveSourceLocation(config, req) {
|
|
125
|
+
let body = await req.json();
|
|
126
|
+
let requestObj = Stdlib_JSON.Decode.object(body);
|
|
127
|
+
if (requestObj === undefined) {
|
|
128
|
+
return Response.json(Object.fromEntries([[
|
|
129
|
+
"error",
|
|
130
|
+
"Invalid request body"
|
|
131
|
+
]]), {
|
|
132
|
+
status: 400
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
let componentName = Stdlib_Option.flatMap(requestObj["componentName"], Stdlib_JSON.Decode.string);
|
|
136
|
+
let file = Stdlib_Option.flatMap(requestObj["file"], Stdlib_JSON.Decode.string);
|
|
137
|
+
let line = Stdlib_Option.flatMap(requestObj["line"], Stdlib_JSON.Decode.float);
|
|
138
|
+
let column = Stdlib_Option.flatMap(requestObj["column"], Stdlib_JSON.Decode.float);
|
|
139
|
+
if (componentName !== undefined && file !== undefined && line !== undefined && column !== undefined) {
|
|
140
|
+
try {
|
|
141
|
+
let sourceLocation_line = line | 0;
|
|
142
|
+
let sourceLocation_column = column | 0;
|
|
143
|
+
let sourceLocation = {
|
|
144
|
+
componentName: componentName,
|
|
145
|
+
file: file,
|
|
146
|
+
line: sourceLocation_line,
|
|
147
|
+
column: sourceLocation_column,
|
|
148
|
+
componentProps: undefined,
|
|
149
|
+
parent: undefined
|
|
150
|
+
};
|
|
151
|
+
let resolved = await Server.resolveSourceLocationInServer(sourceLocation);
|
|
152
|
+
let relativeFile = toRelativePath(resolved.file, config.sourceRoot);
|
|
153
|
+
let responseJson = Object.fromEntries([
|
|
154
|
+
[
|
|
155
|
+
"componentName",
|
|
156
|
+
resolved.componentName
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
"file",
|
|
160
|
+
relativeFile
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
"line",
|
|
164
|
+
resolved.line
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
"column",
|
|
168
|
+
resolved.column
|
|
169
|
+
]
|
|
170
|
+
]);
|
|
171
|
+
let headers = Object.fromEntries([[
|
|
172
|
+
"Content-Type",
|
|
173
|
+
"application/json"
|
|
174
|
+
]]);
|
|
175
|
+
return Response.json(responseJson, {
|
|
176
|
+
headers: Primitive_option.some(headers)
|
|
177
|
+
});
|
|
178
|
+
} catch (raw_exn) {
|
|
179
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
180
|
+
let msg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "Unknown error");
|
|
181
|
+
return Response.json(Object.fromEntries([
|
|
182
|
+
[
|
|
183
|
+
"error",
|
|
184
|
+
"Failed to resolve source location"
|
|
185
|
+
],
|
|
186
|
+
[
|
|
187
|
+
"details",
|
|
188
|
+
msg
|
|
189
|
+
]
|
|
190
|
+
]), {
|
|
191
|
+
status: 500
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return Response.json(Object.fromEntries([[
|
|
196
|
+
"error",
|
|
197
|
+
"Missing required fields: componentName, file, line, column"
|
|
198
|
+
]]), {
|
|
199
|
+
status: 400
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let Protocol;
|
|
204
|
+
|
|
205
|
+
let MCP;
|
|
206
|
+
|
|
207
|
+
let Relay;
|
|
208
|
+
|
|
209
|
+
let Core;
|
|
210
|
+
|
|
211
|
+
let CoreServer;
|
|
212
|
+
|
|
213
|
+
let CoreSSE;
|
|
214
|
+
|
|
215
|
+
let ToolRegistry;
|
|
216
|
+
|
|
217
|
+
let Config;
|
|
218
|
+
|
|
219
|
+
let WebStreams;
|
|
220
|
+
|
|
221
|
+
let DOMElementToComponentSource;
|
|
222
|
+
|
|
223
|
+
export {
|
|
224
|
+
Protocol,
|
|
225
|
+
MCP,
|
|
226
|
+
Relay,
|
|
227
|
+
Core,
|
|
228
|
+
CoreServer,
|
|
229
|
+
CoreSSE,
|
|
230
|
+
ToolRegistry,
|
|
231
|
+
Config,
|
|
232
|
+
WebStreams,
|
|
233
|
+
DOMElementToComponentSource,
|
|
234
|
+
handleGetTools,
|
|
235
|
+
handleToolCall,
|
|
236
|
+
toRelativePath,
|
|
237
|
+
corsHeaders,
|
|
238
|
+
handleCORS,
|
|
239
|
+
handleResolveSourceLocation,
|
|
240
|
+
}
|
|
241
|
+
/* S Not a pure module */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Tool registry for Astro - composes core tools with Astro specific tools
|
|
2
|
+
|
|
3
|
+
module Core = FrontmanFrontmanCore
|
|
4
|
+
module CoreRegistry = Core.FrontmanCore__ToolRegistry
|
|
5
|
+
|
|
6
|
+
// Re-export types from core
|
|
7
|
+
type tool = CoreRegistry.tool
|
|
8
|
+
type t = CoreRegistry.t
|
|
9
|
+
|
|
10
|
+
// Astro specific tools
|
|
11
|
+
let astroTools: array<tool> = [module(FrontmanAstro__Tool__GetPages)]
|
|
12
|
+
|
|
13
|
+
let make = (): t => {
|
|
14
|
+
CoreRegistry.coreTools()->CoreRegistry.addTools(astroTools)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Re-export functions from core
|
|
18
|
+
let getToolByName = CoreRegistry.getToolByName
|
|
19
|
+
let getToolDefinitions = CoreRegistry.getToolDefinitions
|
|
20
|
+
let addTools = CoreRegistry.addTools
|
|
21
|
+
let count = CoreRegistry.count
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as FrontmanAstro__Tool__GetPages$FrontmanAiAstro from "./tools/FrontmanAstro__Tool__GetPages.res.mjs";
|
|
4
|
+
import * as FrontmanCore__ToolRegistry$FrontmanFrontmanCore from "@frontman/frontman-core/src/FrontmanCore__ToolRegistry.res.mjs";
|
|
5
|
+
|
|
6
|
+
let astroTools = [{
|
|
7
|
+
name: FrontmanAstro__Tool__GetPages$FrontmanAiAstro.name,
|
|
8
|
+
description: FrontmanAstro__Tool__GetPages$FrontmanAiAstro.description,
|
|
9
|
+
inputSchema: FrontmanAstro__Tool__GetPages$FrontmanAiAstro.inputSchema,
|
|
10
|
+
outputSchema: FrontmanAstro__Tool__GetPages$FrontmanAiAstro.outputSchema,
|
|
11
|
+
execute: FrontmanAstro__Tool__GetPages$FrontmanAiAstro.execute,
|
|
12
|
+
visibleToAgent: true
|
|
13
|
+
}];
|
|
14
|
+
|
|
15
|
+
function make() {
|
|
16
|
+
return FrontmanCore__ToolRegistry$FrontmanFrontmanCore.addTools(FrontmanCore__ToolRegistry$FrontmanFrontmanCore.coreTools(), astroTools);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let Core;
|
|
20
|
+
|
|
21
|
+
let CoreRegistry;
|
|
22
|
+
|
|
23
|
+
let getToolByName = FrontmanCore__ToolRegistry$FrontmanFrontmanCore.getToolByName;
|
|
24
|
+
|
|
25
|
+
let getToolDefinitions = FrontmanCore__ToolRegistry$FrontmanFrontmanCore.getToolDefinitions;
|
|
26
|
+
|
|
27
|
+
let addTools = FrontmanCore__ToolRegistry$FrontmanFrontmanCore.addTools;
|
|
28
|
+
|
|
29
|
+
let count = FrontmanCore__ToolRegistry$FrontmanFrontmanCore.count;
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
Core,
|
|
33
|
+
CoreRegistry,
|
|
34
|
+
astroTools,
|
|
35
|
+
make,
|
|
36
|
+
getToolByName,
|
|
37
|
+
getToolDefinitions,
|
|
38
|
+
addTools,
|
|
39
|
+
count,
|
|
40
|
+
}
|
|
41
|
+
/* FrontmanAstro__Tool__GetPages-FrontmanAiAstro Not a pure module */
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Frontman Dev Toolbar App
|
|
2
|
+
|
|
3
|
+
module Bindings = FrontmanAstro__AstroBindings
|
|
4
|
+
|
|
5
|
+
// Type for the annotations API exposed on window
|
|
6
|
+
type annotation = {
|
|
7
|
+
file: string,
|
|
8
|
+
loc: string,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type annotationsApi = {
|
|
12
|
+
get: WebAPI.DOMAPI.element => option<annotation>,
|
|
13
|
+
has: WebAPI.DOMAPI.element => bool,
|
|
14
|
+
size: unit => int,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// External binding to window.__frontman_annotations__
|
|
18
|
+
@val @scope("window")
|
|
19
|
+
external annotations: option<annotationsApi> = "__frontman_annotations__"
|
|
20
|
+
|
|
21
|
+
// The toolbar app definition
|
|
22
|
+
let app: Bindings.toolbarAppConfig = {
|
|
23
|
+
init: (_canvas, _app, _server) => {
|
|
24
|
+
switch annotations {
|
|
25
|
+
| Some(api) if api.size() > 0 =>
|
|
26
|
+
Console.log(`[Frontman] SUCCESS - Captured ${api.size()->Int.toString} elements`)
|
|
27
|
+
|
|
28
|
+
// Test retrieval on first h1 or body child
|
|
29
|
+
let testEl =
|
|
30
|
+
WebAPI.Global.document->WebAPI.Document.querySelector("h1")->Null.toOption
|
|
31
|
+
switch testEl {
|
|
32
|
+
| Some(el) =>
|
|
33
|
+
switch api.get(el) {
|
|
34
|
+
| Some(data) =>
|
|
35
|
+
Console.log(`[Frontman] Test element: H1 -> ${data.file}:${data.loc}`)
|
|
36
|
+
| None =>
|
|
37
|
+
Console.log("[Frontman] Test element H1 has no annotation (might be in layout)")
|
|
38
|
+
}
|
|
39
|
+
| None => ()
|
|
40
|
+
}
|
|
41
|
+
| Some(_) =>
|
|
42
|
+
Console.log("[Frontman] WARNING - Annotations API exists but captured 0 elements")
|
|
43
|
+
| None =>
|
|
44
|
+
Console.log("[Frontman] FAILED - window.__frontman_annotations__ not found")
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Export as default for Astro to pick up
|
|
50
|
+
let default = Bindings.defineToolbarApp(app)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Toolbar from "astro/toolbar";
|
|
4
|
+
|
|
5
|
+
let app = {
|
|
6
|
+
init: (_canvas, _app, _server) => {
|
|
7
|
+
let api = window.__frontman_annotations__;
|
|
8
|
+
if (api !== undefined) {
|
|
9
|
+
if (api.size() > 0) {
|
|
10
|
+
console.log(`[Frontman] SUCCESS - Captured ` + api.size().toString() + ` elements`);
|
|
11
|
+
let testEl = document.querySelector("h1");
|
|
12
|
+
if (testEl === null) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let data = api.get(testEl);
|
|
16
|
+
if (data !== undefined) {
|
|
17
|
+
console.log(`[Frontman] Test element: H1 -> ` + data.file + `:` + data.loc);
|
|
18
|
+
} else {
|
|
19
|
+
console.log("[Frontman] Test element H1 has no annotation (might be in layout)");
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log("[Frontman] WARNING - Annotations API exists but captured 0 elements");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
console.log("[Frontman] FAILED - window.__frontman_annotations__ not found");
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let $$default = Toolbar.defineToolbarApp(app);
|
|
31
|
+
|
|
32
|
+
let Bindings;
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
Bindings,
|
|
36
|
+
app,
|
|
37
|
+
$$default as default,
|
|
38
|
+
}
|
|
39
|
+
/* default Not a pure module */
|