@frontman-ai/astro 0.1.4 → 0.1.5
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 +65 -24
- package/dist/index.js +156 -57
- package/dist/integration.js +5807 -5
- package/index.d.ts +75 -0
- package/package.json +17 -8
- package/dist/cli.js +0 -2744
package/README.md
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
# @frontman-ai/astro
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@frontman-ai/astro)
|
|
4
|
+
[](https://astro.build)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Astro integration for [Frontman](https://frontman.sh) — AI-powered development tools that let you edit your frontend from the browser.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- [Astro](https://astro.build) 5.0+
|
|
9
|
-
- HTTP middleware integration
|
|
10
|
-
- SSE (Server-Sent Events) for streaming responses
|
|
11
|
-
|
|
12
|
-
## Features
|
|
8
|
+
## Installation
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Streaming responses via SSE
|
|
10
|
+
```bash
|
|
11
|
+
npx astro add @frontman-ai/astro
|
|
12
|
+
```
|
|
18
13
|
|
|
19
|
-
|
|
14
|
+
Or manually:
|
|
20
15
|
|
|
21
16
|
```bash
|
|
22
17
|
npm install @frontman-ai/astro
|
|
@@ -28,26 +23,72 @@ Add the integration to your `astro.config.mjs`:
|
|
|
28
23
|
|
|
29
24
|
```javascript
|
|
30
25
|
import { defineConfig } from 'astro/config';
|
|
31
|
-
import
|
|
26
|
+
import frontman from '@frontman-ai/astro';
|
|
32
27
|
|
|
33
28
|
export default defineConfig({
|
|
34
29
|
integrations: [
|
|
35
|
-
|
|
30
|
+
frontman({ projectRoot: import.meta.dirname }),
|
|
36
31
|
],
|
|
37
32
|
});
|
|
38
33
|
```
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
Then start your dev server and open `http://localhost:4321/frontman/`.
|
|
36
|
+
|
|
37
|
+
## What it does
|
|
38
|
+
|
|
39
|
+
The integration automatically (in dev mode only):
|
|
40
|
+
|
|
41
|
+
- Registers a dev toolbar app for element selection
|
|
42
|
+
- Captures Astro source annotations so the AI knows which `.astro` file and line each element comes from
|
|
43
|
+
- Serves the Frontman UI at `/<basePath>/` (default: `/frontman/`)
|
|
44
|
+
- Exposes tool endpoints for AI interactions (file edits, screenshots, etc.)
|
|
45
|
+
|
|
46
|
+
> **Note:** Element source detection requires `devToolbar.enabled: true` (the default). Astro only emits `data-astro-source-file` / `data-astro-source-loc` annotations when the dev toolbar is enabled. If you've disabled it, Frontman will log a warning and fall back to CSS selector-based detection.
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
All options are optional with sensible defaults:
|
|
51
|
+
|
|
52
|
+
| Option | Default | Description |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `projectRoot` | `PROJECT_ROOT` env var, `PWD`, or `"."` | Path to the project root directory |
|
|
55
|
+
| `sourceRoot` | Same as `projectRoot` | Root for source file resolution (useful in monorepos) |
|
|
56
|
+
| `basePath` | `"frontman"` | URL prefix for Frontman routes |
|
|
57
|
+
| `host` | `FRONTMAN_HOST` env var or `"api.frontman.sh"` | Frontman server host for client connections |
|
|
58
|
+
| `serverName` | `"frontman-astro"` | Server name included in tool responses |
|
|
59
|
+
| `serverVersion` | `"1.0.0"` | Server version included in tool responses |
|
|
60
|
+
| `clientUrl` | Auto-generated from `host` | URL to the Frontman client bundle (must include a `host` query parameter) |
|
|
61
|
+
| `isLightTheme` | `false` | Use a light theme for the Frontman UI |
|
|
62
|
+
|
|
63
|
+
### Environment variables
|
|
64
|
+
|
|
65
|
+
| Variable | Description |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `FRONTMAN_HOST` | Override the default server host without changing config |
|
|
68
|
+
| `PROJECT_ROOT` | Override the project root path |
|
|
69
|
+
| `FRONTMAN_CLIENT_URL` | Override the client bundle URL |
|
|
70
|
+
|
|
71
|
+
## How it works
|
|
72
|
+
|
|
73
|
+
The integration uses two Astro hooks:
|
|
74
|
+
|
|
75
|
+
- **`astro:config:setup`** — Registers the dev toolbar app and injects the annotation capture script via `injectScript('head-inline', ...)`
|
|
76
|
+
- **`astro:server:setup`** — Registers Frontman API routes as Vite dev server middleware via `server.middlewares.use()`
|
|
77
|
+
|
|
78
|
+
No manual middleware file needed. No SSR adapter required. Works with static (`output: 'static'`) Astro projects.
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
41
81
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
- `./toolbar` - Dev toolbar app component
|
|
82
|
+
- Astro ^5.0.0
|
|
83
|
+
- Node.js >= 18
|
|
45
84
|
|
|
46
|
-
##
|
|
85
|
+
## Links
|
|
47
86
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
87
|
+
- [Website](https://frontman.sh)
|
|
88
|
+
- [Documentation](https://frontman.sh/docs/astro)
|
|
89
|
+
- [Changelog](https://github.com/frontman-ai/frontman/blob/main/CHANGELOG.md)
|
|
90
|
+
- [Issues](https://github.com/frontman-ai/frontman/issues)
|
|
50
91
|
|
|
51
|
-
##
|
|
92
|
+
## License
|
|
52
93
|
|
|
53
|
-
|
|
94
|
+
[Apache-2.0](https://github.com/frontman-ai/frontman/blob/main/LICENSE)
|
package/dist/index.js
CHANGED
|
@@ -97,8 +97,12 @@ function orElse(opt, other) {
|
|
|
97
97
|
|
|
98
98
|
// src/FrontmanAstro__Config.res.mjs
|
|
99
99
|
var host = process.env["FRONTMAN_HOST"];
|
|
100
|
-
var defaultHost = host !== void 0 ? host : "frontman.
|
|
101
|
-
|
|
100
|
+
var defaultHost = host !== void 0 ? host : "api.frontman.sh";
|
|
101
|
+
var ensureConfig = (function(c2) {
|
|
102
|
+
return c2 || {};
|
|
103
|
+
});
|
|
104
|
+
function makeFromObject(rawConfig) {
|
|
105
|
+
let config = ensureConfig(rawConfig);
|
|
102
106
|
let projectRoot = getOr(orElse(config.projectRoot, orElse(process.env["PROJECT_ROOT"], process.env["PWD"])), ".");
|
|
103
107
|
let sourceRoot = getOr(config.sourceRoot, projectRoot);
|
|
104
108
|
let basePath = getOr(config.basePath, "frontman");
|
|
@@ -699,14 +703,14 @@ function withPathPrepend(b, input, path, maybeDynamicLocationVar, appendSafe, fn
|
|
|
699
703
|
return fn(b, input, path);
|
|
700
704
|
}
|
|
701
705
|
try {
|
|
702
|
-
let $$
|
|
706
|
+
let $$catch2 = (b2, errorVar2) => {
|
|
703
707
|
b2.c = errorVar2 + `.path=` + fromString(path) + `+` + (maybeDynamicLocationVar !== void 0 ? `'["'+` + maybeDynamicLocationVar + `+'"]'+` : "") + errorVar2 + `.path`;
|
|
704
708
|
};
|
|
705
709
|
let fn$1 = (b2) => fn(b2, input, "");
|
|
706
710
|
let prevCode = b.c;
|
|
707
711
|
b.c = "";
|
|
708
712
|
let errorVar = varWithoutAllocation(b.g);
|
|
709
|
-
let maybeResolveVal = $$
|
|
713
|
+
let maybeResolveVal = $$catch2(b, errorVar);
|
|
710
714
|
let catchCode = `if(` + (errorVar + `&&` + errorVar + `.s===s`) + `){` + b.c;
|
|
711
715
|
b.c = "";
|
|
712
716
|
let bb = {
|
|
@@ -5577,41 +5581,6 @@ function make2() {
|
|
|
5577
5581
|
}
|
|
5578
5582
|
|
|
5579
5583
|
// src/FrontmanAstro__Middleware.res.mjs
|
|
5580
|
-
var annotationCaptureScript = `
|
|
5581
|
-
<script>
|
|
5582
|
-
(function() {
|
|
5583
|
-
var annotations = new Map();
|
|
5584
|
-
document.querySelectorAll('[data-astro-source-file]').forEach(function(el) {
|
|
5585
|
-
annotations.set(el, {
|
|
5586
|
-
file: el.getAttribute('data-astro-source-file'),
|
|
5587
|
-
loc: el.getAttribute('data-astro-source-loc')
|
|
5588
|
-
});
|
|
5589
|
-
});
|
|
5590
|
-
window.__frontman_annotations__ = {
|
|
5591
|
-
_map: annotations,
|
|
5592
|
-
get: function(el) { return annotations.get(el); },
|
|
5593
|
-
has: function(el) { return annotations.has(el); },
|
|
5594
|
-
size: function() { return annotations.size; }
|
|
5595
|
-
};
|
|
5596
|
-
console.log('[Frontman] Captured ' + annotations.size + ' elements');
|
|
5597
|
-
})();
|
|
5598
|
-
</script>
|
|
5599
|
-
`;
|
|
5600
|
-
async function injectAnnotationScript(response) {
|
|
5601
|
-
let contentType = response.headers.get("content-type");
|
|
5602
|
-
if (contentType === null) {
|
|
5603
|
-
return response;
|
|
5604
|
-
}
|
|
5605
|
-
if (!contentType.includes("text/html")) {
|
|
5606
|
-
return response;
|
|
5607
|
-
}
|
|
5608
|
-
let html = await response.text();
|
|
5609
|
-
let injectedHtml = html.replace("</body>", annotationCaptureScript + `</body>`);
|
|
5610
|
-
return new Response(injectedHtml, {
|
|
5611
|
-
status: response.status,
|
|
5612
|
-
headers: some(response.headers)
|
|
5613
|
-
});
|
|
5614
|
-
}
|
|
5615
5584
|
function uiHtml(clientUrl, isLightTheme) {
|
|
5616
5585
|
let openrouterKey = flatMap(process.env["OPENROUTER_API_KEY"], (key) => {
|
|
5617
5586
|
if (key !== "") {
|
|
@@ -5661,9 +5630,10 @@ function serveUI(config) {
|
|
|
5661
5630
|
}
|
|
5662
5631
|
function createMiddleware(config) {
|
|
5663
5632
|
let registry = make2();
|
|
5664
|
-
return async (
|
|
5665
|
-
let
|
|
5666
|
-
let
|
|
5633
|
+
return async (request) => {
|
|
5634
|
+
let url2 = new URL(request.url);
|
|
5635
|
+
let pathname = url2.pathname;
|
|
5636
|
+
let method = request.method;
|
|
5667
5637
|
let basePath = `/` + config.basePath;
|
|
5668
5638
|
if (pathname === basePath || pathname.startsWith(basePath + `/`)) {
|
|
5669
5639
|
if (method === "OPTIONS") {
|
|
@@ -5673,9 +5643,9 @@ function createMiddleware(config) {
|
|
|
5673
5643
|
} else if (pathname === basePath + `/tools` && method === "GET") {
|
|
5674
5644
|
return handleGetTools(registry, config);
|
|
5675
5645
|
} else if (pathname === basePath + `/tools/call` && method === "POST") {
|
|
5676
|
-
return await handleToolCall(registry, config,
|
|
5646
|
+
return await handleToolCall(registry, config, request);
|
|
5677
5647
|
} else if (pathname === basePath + `/resolve-source-location` && method === "POST") {
|
|
5678
|
-
return await handleResolveSourceLocation(config,
|
|
5648
|
+
return await handleResolveSourceLocation(config, request);
|
|
5679
5649
|
} else {
|
|
5680
5650
|
return Response.json(Object.fromEntries([[
|
|
5681
5651
|
"error",
|
|
@@ -5685,8 +5655,109 @@ function createMiddleware(config) {
|
|
|
5685
5655
|
});
|
|
5686
5656
|
}
|
|
5687
5657
|
}
|
|
5688
|
-
|
|
5689
|
-
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
|
|
5661
|
+
// ../../node_modules/@rescript/runtime/lib/es6/Stdlib_Promise.js
|
|
5662
|
+
function $$catch(promise, callback) {
|
|
5663
|
+
return promise.catch((err) => callback(internalToException(err)));
|
|
5664
|
+
}
|
|
5665
|
+
|
|
5666
|
+
// ../bindings/src/NodeHttp.res.mjs
|
|
5667
|
+
var collectRequestBody = (async function(req) {
|
|
5668
|
+
const chunks = [];
|
|
5669
|
+
for await (const chunk of req) {
|
|
5670
|
+
chunks.push(chunk);
|
|
5671
|
+
}
|
|
5672
|
+
const { Buffer: Buffer2 } = await import('buffer');
|
|
5673
|
+
return Buffer2.concat(chunks);
|
|
5674
|
+
});
|
|
5675
|
+
|
|
5676
|
+
// src/FrontmanAstro__ViteAdapter.res.mjs
|
|
5677
|
+
var copyHeaders = (function(headers2, res) {
|
|
5678
|
+
headers2.forEach(function(value, key) {
|
|
5679
|
+
res.setHeader(key, value);
|
|
5680
|
+
});
|
|
5681
|
+
});
|
|
5682
|
+
async function toWebRequest(req) {
|
|
5683
|
+
let host2 = getOr(req.headers["host"], "localhost");
|
|
5684
|
+
let url2 = `http://` + host2 + req.url;
|
|
5685
|
+
let method = req.method;
|
|
5686
|
+
let match = method.toUpperCase();
|
|
5687
|
+
let body;
|
|
5688
|
+
switch (match) {
|
|
5689
|
+
case "PATCH":
|
|
5690
|
+
case "POST":
|
|
5691
|
+
case "PUT":
|
|
5692
|
+
body = await collectRequestBody(req);
|
|
5693
|
+
break;
|
|
5694
|
+
default:
|
|
5695
|
+
body = void 0;
|
|
5696
|
+
}
|
|
5697
|
+
let headersDict = req.headers;
|
|
5698
|
+
let init = {
|
|
5699
|
+
method,
|
|
5700
|
+
headers: some(headersDict),
|
|
5701
|
+
body: map(body, (b) => b)
|
|
5702
|
+
};
|
|
5703
|
+
if (body !== void 0) {
|
|
5704
|
+
init["duplex"] = "half";
|
|
5705
|
+
}
|
|
5706
|
+
return new Request(url2, init);
|
|
5707
|
+
}
|
|
5708
|
+
async function writeWebResponse(webResponse, res) {
|
|
5709
|
+
res.statusCode = webResponse.status;
|
|
5710
|
+
copyHeaders(webResponse.headers, res);
|
|
5711
|
+
let body = webResponse.body;
|
|
5712
|
+
if (body !== null) {
|
|
5713
|
+
let reader = body.getReader();
|
|
5714
|
+
let decoder = new TextDecoder();
|
|
5715
|
+
let reading = true;
|
|
5716
|
+
while (reading) {
|
|
5717
|
+
let result = await reader.read();
|
|
5718
|
+
if (result.done) {
|
|
5719
|
+
reading = false;
|
|
5720
|
+
} else {
|
|
5721
|
+
let chunk = result.value;
|
|
5722
|
+
if (!(chunk == null)) {
|
|
5723
|
+
let text = decoder.decode(chunk, {
|
|
5724
|
+
stream: true
|
|
5725
|
+
});
|
|
5726
|
+
res.write(text);
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
res.end();
|
|
5731
|
+
return;
|
|
5732
|
+
}
|
|
5733
|
+
res.end();
|
|
5734
|
+
}
|
|
5735
|
+
function adaptToConnect(middleware, basePath) {
|
|
5736
|
+
let prefix = `/` + basePath;
|
|
5737
|
+
return (req, res, next) => {
|
|
5738
|
+
let reqPath = getOr(req.url.split("?")[0], req.url);
|
|
5739
|
+
if (!(reqPath === prefix || reqPath.startsWith(prefix + `/`))) {
|
|
5740
|
+
return next();
|
|
5741
|
+
}
|
|
5742
|
+
let handleRequest = async () => {
|
|
5743
|
+
let webRequest = await toWebRequest(req);
|
|
5744
|
+
let maybeResponse = await middleware(webRequest);
|
|
5745
|
+
if (maybeResponse !== void 0) {
|
|
5746
|
+
return await writeWebResponse(maybeResponse, res);
|
|
5747
|
+
} else {
|
|
5748
|
+
return next();
|
|
5749
|
+
}
|
|
5750
|
+
};
|
|
5751
|
+
$$catch(handleRequest(), (error) => {
|
|
5752
|
+
console.error("[Frontman] Middleware error:", error);
|
|
5753
|
+
if (res.headersSent) {
|
|
5754
|
+
res.end();
|
|
5755
|
+
} else {
|
|
5756
|
+
res.statusCode = 500;
|
|
5757
|
+
res.end("Internal Server Error");
|
|
5758
|
+
}
|
|
5759
|
+
return Promise.resolve();
|
|
5760
|
+
});
|
|
5690
5761
|
};
|
|
5691
5762
|
}
|
|
5692
5763
|
|
|
@@ -5695,33 +5766,61 @@ var icon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewB
|
|
|
5695
5766
|
function getToolbarAppPath() {
|
|
5696
5767
|
return new URL("./toolbar.js", import.meta.url).pathname;
|
|
5697
5768
|
}
|
|
5698
|
-
|
|
5769
|
+
var annotationCaptureScript = `(function() {
|
|
5770
|
+
function captureAnnotations() {
|
|
5771
|
+
var annotations = new Map();
|
|
5772
|
+
document.querySelectorAll('[data-astro-source-file]').forEach(function(el) {
|
|
5773
|
+
annotations.set(el, {
|
|
5774
|
+
file: el.getAttribute('data-astro-source-file'),
|
|
5775
|
+
loc: el.getAttribute('data-astro-source-loc')
|
|
5776
|
+
});
|
|
5777
|
+
});
|
|
5778
|
+
window.__frontman_annotations__ = {
|
|
5779
|
+
_map: annotations,
|
|
5780
|
+
get: function(el) { return annotations.get(el); },
|
|
5781
|
+
has: function(el) { return annotations.has(el); },
|
|
5782
|
+
size: function() { return annotations.size; }
|
|
5783
|
+
};
|
|
5784
|
+
}
|
|
5785
|
+
// Capture once on initial DOM parse
|
|
5786
|
+
document.addEventListener('DOMContentLoaded', captureAnnotations);
|
|
5787
|
+
// Re-capture on View Transitions (SPA navigations) \u2014 skips the initial
|
|
5788
|
+
// page-load event since DOMContentLoaded already captured
|
|
5789
|
+
var initialLoad = true;
|
|
5790
|
+
document.addEventListener('astro:page-load', function() {
|
|
5791
|
+
if (initialLoad) { initialLoad = false; return; }
|
|
5792
|
+
captureAnnotations();
|
|
5793
|
+
});
|
|
5794
|
+
})();`;
|
|
5795
|
+
function make3(configInput) {
|
|
5796
|
+
let config = makeFromObject(configInput);
|
|
5699
5797
|
return {
|
|
5700
5798
|
name: "frontman",
|
|
5701
5799
|
hooks: {
|
|
5702
5800
|
"astro:config:setup": (ctx2) => {
|
|
5703
5801
|
if (ctx2.command === "dev") {
|
|
5704
|
-
|
|
5802
|
+
if (!ctx2.config.devToolbar.enabled) {
|
|
5803
|
+
console.warn("[Frontman] Astro devToolbar is disabled \u2014 element source detection will be limited. Set `devToolbar: { enabled: true }` in your astro.config to enable full component source resolution.");
|
|
5804
|
+
}
|
|
5805
|
+
ctx2.addDevToolbarApp({
|
|
5705
5806
|
id: "frontman:toolbar",
|
|
5706
5807
|
name: "Frontman",
|
|
5707
5808
|
icon,
|
|
5708
5809
|
entrypoint: getToolbarAppPath()
|
|
5709
5810
|
});
|
|
5811
|
+
return ctx2.injectScript("head-inline", annotationCaptureScript);
|
|
5710
5812
|
}
|
|
5813
|
+
},
|
|
5814
|
+
"astro:server:setup": (param) => {
|
|
5815
|
+
let webMiddleware = createMiddleware(config);
|
|
5816
|
+
let connectMiddleware = adaptToConnect(webMiddleware, config.basePath);
|
|
5817
|
+
param.server.middlewares.use(connectMiddleware);
|
|
5711
5818
|
}
|
|
5712
5819
|
}
|
|
5713
5820
|
};
|
|
5714
5821
|
}
|
|
5715
5822
|
|
|
5716
5823
|
// src/FrontmanAstro.res.mjs
|
|
5717
|
-
var Config;
|
|
5718
|
-
var Middleware;
|
|
5719
|
-
var Server;
|
|
5720
|
-
var ToolRegistry;
|
|
5721
|
-
var Integration;
|
|
5722
|
-
var SSE;
|
|
5723
|
-
var createMiddleware2 = createMiddleware;
|
|
5724
|
-
var makeConfig = makeFromObject;
|
|
5725
5824
|
var frontmanIntegration = make3;
|
|
5726
5825
|
|
|
5727
|
-
export {
|
|
5826
|
+
export { frontmanIntegration as default, frontmanIntegration };
|