@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
@@ -0,0 +1,237 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as FrontmanAstro__Cli__Style$FrontmanAiAstro from "./FrontmanAstro__Cli__Style.res.mjs";
4
+
5
+ function banner() {
6
+ let l1 = FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleBold(" ___ _ ");
7
+ let l2 = FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleBold(" | __| _ ___ _ _ | |_ _ __ __ _ _ _ ");
8
+ let l3 = FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleBold(" | _| '_/ _ \\ ' \\| _| ' \\/ _` | ' \\ ");
9
+ let l4 = FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleBold(" |_||_| \\___/_||_|\\__|_|_|_\\__,_|_||_|");
10
+ let tagline = FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleDim(" AI that sees your DOM and edits your frontend");
11
+ return `
12
+ ` + l1 + `
13
+ ` + l2 + `
14
+ ` + l3 + `
15
+ ` + l4 + `
16
+
17
+ ` + tagline + `
18
+ `;
19
+ }
20
+
21
+ function middlewareTemplate(host) {
22
+ return `import { createMiddleware, makeConfig } from '@frontman-ai/astro';
23
+ import { defineMiddleware } from 'astro:middleware';
24
+
25
+ const config = makeConfig({ host: '` + host + `' });
26
+ const frontman = createMiddleware(config);
27
+
28
+ export const onRequest = defineMiddleware(async (context, next) => {
29
+ return frontman(context, next);
30
+ });
31
+ `;
32
+ }
33
+
34
+ function configTemplate(host) {
35
+ return `import { defineConfig } from 'astro/config';
36
+ import node from '@astrojs/node';
37
+ import { frontmanIntegration } from '@frontman-ai/astro/integration';
38
+
39
+ const isProd = process.env.NODE_ENV === 'production';
40
+
41
+ export default defineConfig({
42
+ // SSR needed in dev for Frontman middleware routes
43
+ ...(isProd ? {} : { output: 'server', adapter: node({ mode: 'standalone' }) }),
44
+ integrations: [frontmanIntegration()],
45
+ });
46
+ `;
47
+ }
48
+
49
+ function config(fileName, _host) {
50
+ let bar = FrontmanAstro__Cli__Style$FrontmanAiAstro.yellow("|");
51
+ return ` ` + bar + `
52
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.yellowBold(fileName) + ` needs manual modification.
53
+ ` + bar + `
54
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("1.") + ` Add imports at the top of the file:
55
+ ` + bar + `
56
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("import node from '@astrojs/node';") + `
57
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("import { frontmanIntegration } from '@frontman-ai/astro/integration';") + `
58
+ ` + bar + `
59
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("2.") + ` Add SSR config for dev mode ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("(needed for middleware routes)") + `:
60
+ ` + bar + `
61
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("const isProd = process.env.NODE_ENV === 'production';") + `
62
+ ` + bar + `
63
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("export default defineConfig({") + `
64
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim(" ...(isProd ? {} : { output: 'server', adapter: node({ mode: 'standalone' }) }),") + `
65
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim(" // ... your existing config") + `
66
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("});") + `
67
+ ` + bar + `
68
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("3.") + ` Add ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold("frontmanIntegration()") + ` to your integrations array:
69
+ ` + bar + `
70
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("integrations: [frontmanIntegration(), ...yourExistingIntegrations]") + `
71
+ ` + bar + `
72
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold("Docs:") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("https://frontman.sh/docs/astro") + `
73
+ ` + bar;
74
+ }
75
+
76
+ function middleware(fileName, host) {
77
+ let bar = FrontmanAstro__Cli__Style$FrontmanAiAstro.yellow("|");
78
+ return ` ` + bar + `
79
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.yellowBold(fileName) + ` needs manual modification.
80
+ ` + bar + `
81
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("1.") + ` Add imports at the top of the file:
82
+ ` + bar + `
83
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("import { createMiddleware, makeConfig } from '@frontman-ai/astro';") + `
84
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("import { defineMiddleware, sequence } from 'astro:middleware';") + `
85
+ ` + bar + `
86
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("2.") + ` Create the Frontman middleware instance ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("(after imports)") + `:
87
+ ` + bar + `
88
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim(`const frontmanConfig = makeConfig({ host: '` + host + `' });`) + `
89
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("const frontman = createMiddleware(frontmanConfig);") + `
90
+ ` + bar + `
91
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("3.") + ` Combine with your existing middleware using ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold("sequence()") + `:
92
+ ` + bar + `
93
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("const frontmanMiddleware = defineMiddleware(async (context, next) => {") + `
94
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim(" return frontman(context, next);") + `
95
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("});") + `
96
+ ` + bar + `
97
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("export const onRequest = sequence(frontmanMiddleware, yourExistingMiddleware);") + `
98
+ ` + bar + `
99
+ ` + bar + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold("Docs:") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("https://frontman.sh/docs/astro") + `
100
+ ` + bar;
101
+ }
102
+
103
+ let ManualInstructions = {
104
+ config: config,
105
+ middleware: middleware
106
+ };
107
+
108
+ function configManualSetup(fileName, _host) {
109
+ return `
110
+ ` + fileName + ` already exists and requires manual modification.
111
+
112
+ Add the following to your ` + fileName + `:
113
+
114
+ 1. Add imports at the top of the file:
115
+
116
+ import node from '@astrojs/node';
117
+ import { frontmanIntegration } from '@frontman-ai/astro/integration';
118
+
119
+ 2. Add SSR config for dev mode (needed for middleware routes):
120
+
121
+ const isProd = process.env.NODE_ENV === 'production';
122
+
123
+ export default defineConfig({
124
+ ...(isProd ? {} : { output: 'server', adapter: node({ mode: 'standalone' }) }),
125
+ // ... your existing config
126
+ });
127
+
128
+ 3. Add frontmanIntegration() to your integrations array:
129
+
130
+ integrations: [frontmanIntegration(), ...yourExistingIntegrations],
131
+
132
+ For full documentation, see: https://frontman.sh/docs/astro
133
+ `;
134
+ }
135
+
136
+ function middlewareManualSetup(fileName, host) {
137
+ return `
138
+ ` + fileName + ` already exists and requires manual modification.
139
+
140
+ Add the following to your ` + fileName + `:
141
+
142
+ 1. Add imports at the top of the file:
143
+
144
+ import { createMiddleware, makeConfig } from '@frontman-ai/astro';
145
+ import { defineMiddleware, sequence } from 'astro:middleware';
146
+
147
+ 2. Create the Frontman middleware instance (after imports):
148
+
149
+ const frontmanConfig = makeConfig({ host: '` + host + `' });
150
+ const frontman = createMiddleware(frontmanConfig);
151
+
152
+ 3. Combine with your existing middleware using sequence():
153
+
154
+ const frontmanMiddleware = defineMiddleware(async (context, next) => {
155
+ return frontman(context, next);
156
+ });
157
+
158
+ export const onRequest = sequence(frontmanMiddleware, yourExistingMiddleware);
159
+
160
+ For full documentation, see: https://frontman.sh/docs/astro
161
+ `;
162
+ }
163
+
164
+ let ErrorMessages = {
165
+ configManualSetup: configManualSetup,
166
+ middlewareManualSetup: middlewareManualSetup
167
+ };
168
+
169
+ function fileCreated(fileName) {
170
+ return ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.check + ` Created ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold(fileName);
171
+ }
172
+
173
+ function fileSkipped(fileName) {
174
+ return ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("–") + ` Skipped ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold(fileName) + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("(already configured)");
175
+ }
176
+
177
+ function hostUpdated(fileName, oldHost, newHost) {
178
+ return ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.check + ` Updated ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold(fileName) + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim(`(host: '` + oldHost + `' -> '` + newHost + `')`);
179
+ }
180
+
181
+ function fileAutoEdited(fileName) {
182
+ return ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.check + ` Auto-edited ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold(fileName) + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("(Frontman integrated via AI)");
183
+ }
184
+
185
+ function autoEditFailed(fileName, error) {
186
+ return ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.warn + ` Auto-edit failed for ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold(fileName) + `: ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim(error);
187
+ }
188
+
189
+ function manualEditRequired(fileName) {
190
+ return ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.warn + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.bold(fileName) + ` requires manual setup ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("(see details below)");
191
+ }
192
+
193
+ function installComplete(devCommand) {
194
+ return `
195
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleBold("Frontman setup complete!") + `
196
+
197
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleBold("Next steps:") + `
198
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("1.") + ` Start your dev server ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim(devCommand) + `
199
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("2.") + ` Open your browser to ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("http://localhost:4321/frontman") + `
200
+
201
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("┌───────────────────────────────────────────────┐") + `
202
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + `
203
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + ` Questions? Comments? Need support? ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + `
204
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + `
205
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + ` Join us on Discord: ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + `
206
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purpleDim("https://discord.gg/J77jBzMM") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + `
207
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("│") + `
208
+ ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.purple("└───────────────────────────────────────────────┘") + `
209
+ `;
210
+ }
211
+
212
+ let dryRunHeader = ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.warn + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.yellowBold("DRY RUN MODE") + ` ` + FrontmanAstro__Cli__Style$FrontmanAiAstro.dim("— No files will be created") + `
213
+ `;
214
+
215
+ let SuccessMessages = {
216
+ fileCreated: fileCreated,
217
+ fileSkipped: fileSkipped,
218
+ hostUpdated: hostUpdated,
219
+ fileAutoEdited: fileAutoEdited,
220
+ autoEditFailed: autoEditFailed,
221
+ manualEditRequired: manualEditRequired,
222
+ installComplete: installComplete,
223
+ dryRunHeader: dryRunHeader
224
+ };
225
+
226
+ let Style;
227
+
228
+ export {
229
+ Style,
230
+ banner,
231
+ middlewareTemplate,
232
+ configTemplate,
233
+ ManualInstructions,
234
+ ErrorMessages,
235
+ SuccessMessages,
236
+ }
237
+ /* dryRunHeader Not a pure module */
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // CLI wrapper to add shebang for npx/bin execution
3
+ import './FrontmanAstro__Cli.res.mjs';
@@ -0,0 +1,164 @@
1
+ // Get client pages tool - lists Astro pages from the filesystem
2
+ // Excludes API routes (src/pages/api/) - use a separate tool for those
3
+
4
+ module Path = FrontmanBindings.Path
5
+ module Fs = FrontmanBindings.Fs
6
+ module Tool = FrontmanFrontmanProtocol.FrontmanProtocol__Tool
7
+
8
+ let name = "get_client_pages"
9
+ let visibleToAgent = true
10
+
11
+ let description = `Lists Astro client pages from the pages directory.
12
+
13
+ Parameters: None
14
+
15
+ Returns array of page paths based on file-system routing conventions.
16
+ Excludes API routes (src/pages/api/) - focuses on renderable pages only.`
17
+
18
+ // Dynamic route types in Astro
19
+ type dynamicType =
20
+ | Static // no brackets
21
+ | SingleParam // [slug]
22
+ | RestParam // [...slug]
23
+ | OptionalParam // [[slug]]
24
+
25
+ @schema
26
+ type input = {placeholder?: bool}
27
+
28
+ @schema
29
+ type page = {
30
+ path: string,
31
+ file: string,
32
+ isDynamic: bool,
33
+ dynamicType: string, // "static" | "single" | "rest" | "optional"
34
+ }
35
+
36
+ @schema
37
+ type output = array<page>
38
+
39
+ // Analyze a segment for dynamic route type
40
+ let analyzeDynamicSegment = (segment: string): dynamicType => {
41
+ if segment->String.startsWith("[[") && segment->String.endsWith("]]") {
42
+ OptionalParam
43
+ } else if segment->String.startsWith("[...") && segment->String.endsWith("]") {
44
+ RestParam
45
+ } else if segment->String.startsWith("[") && segment->String.endsWith("]") {
46
+ SingleParam
47
+ } else {
48
+ Static
49
+ }
50
+ }
51
+
52
+ // Convert dynamicType to string for JSON output
53
+ let dynamicTypeToString = (dt: dynamicType): string => {
54
+ switch dt {
55
+ | Static => "static"
56
+ | SingleParam => "single"
57
+ | RestParam => "rest"
58
+ | OptionalParam => "optional"
59
+ }
60
+ }
61
+
62
+ // Check if segment is any kind of dynamic
63
+ let isDynamicSegment = (segment: string): bool => {
64
+ analyzeDynamicSegment(segment) != Static
65
+ }
66
+
67
+ // Convert file path to route path
68
+ let fileToRoute = (filePath: string): string => {
69
+ filePath
70
+ ->String.replaceRegExp(%re("/\.(astro|md|mdx|html)$/"), "")
71
+ ->String.replaceRegExp(%re("/\/index$/"), "")
72
+ ->(p => p == "" ? "/" : p)
73
+ }
74
+
75
+ // Get the most significant dynamic type from all segments
76
+ // Priority: rest > optional > single > static
77
+ let getMostSignificantDynamicType = (segments: array<string>): dynamicType => {
78
+ segments->Array.reduce(Static, (acc, segment) => {
79
+ let segType = analyzeDynamicSegment(segment)
80
+ switch (acc, segType) {
81
+ | (_, RestParam) => RestParam
82
+ | (RestParam, _) => RestParam
83
+ | (_, OptionalParam) => OptionalParam
84
+ | (OptionalParam, _) => OptionalParam
85
+ | (_, SingleParam) => SingleParam
86
+ | (SingleParam, _) => SingleParam
87
+ | _ => Static
88
+ }
89
+ })
90
+ }
91
+
92
+ // Recursively find page files
93
+ let rec findPages = async (
94
+ baseDir: string,
95
+ currentPath: string,
96
+ ~projectRoot: string,
97
+ ): array<page> => {
98
+ let fullPath = Path.join([projectRoot, baseDir, currentPath])
99
+
100
+ try {
101
+ let entries = await Fs.Promises.readdir(fullPath)
102
+
103
+ let pagesArrays =
104
+ await entries
105
+ ->Array.map(async entry => {
106
+ let entryPath = Path.join([fullPath, entry])
107
+ let stats = await Fs.Promises.stat(entryPath)
108
+
109
+ if Fs.isDirectory(stats) {
110
+ // Skip special directories
111
+ if entry->String.startsWith("_") || entry == "api" || entry == "components" {
112
+ []
113
+ } else {
114
+ await findPages(baseDir, Path.join([currentPath, entry]), ~projectRoot)
115
+ }
116
+ } else if (
117
+ entry->String.endsWith(".astro") ||
118
+ entry->String.endsWith(".md") ||
119
+ entry->String.endsWith(".mdx") ||
120
+ entry->String.endsWith(".html")
121
+ ) {
122
+ let fileName = entry->String.replaceRegExp(%re("/\.(astro|md|mdx|html)$/"), "")
123
+ let routePath = fileToRoute(Path.join([currentPath, fileName]))
124
+ let segments = Path.join([currentPath, fileName])->String.split("/")
125
+ let hasDynamic = segments->Array.some(isDynamicSegment)
126
+ let dynType = getMostSignificantDynamicType(segments)
127
+ [
128
+ {
129
+ path: routePath,
130
+ file: Path.join([baseDir, currentPath, entry]),
131
+ isDynamic: hasDynamic,
132
+ dynamicType: dynamicTypeToString(dynType),
133
+ },
134
+ ]
135
+ } else {
136
+ []
137
+ }
138
+ })
139
+ ->Promise.all
140
+
141
+ pagesArrays->Array.flat
142
+ } catch {
143
+ | _ => []
144
+ }
145
+ }
146
+
147
+ let execute = async (ctx: Tool.serverExecutionContext, _input: input): Tool.toolResult<output> => {
148
+ try {
149
+ // Try src/pages directory first
150
+ let srcPages = await findPages("src/pages", "", ~projectRoot=ctx.projectRoot)
151
+
152
+ // Try pages directory (legacy)
153
+ let rootPages = await findPages("pages", "", ~projectRoot=ctx.projectRoot)
154
+
155
+ let allPages = Array.concat(srcPages, rootPages)
156
+
157
+ Ok(allPages)
158
+ } catch {
159
+ | exn =>
160
+ let msg =
161
+ exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("Unknown error")
162
+ Error(`Failed to find pages: ${msg}`)
163
+ }
164
+ }
@@ -0,0 +1,180 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Fs from "fs";
5
+ import * as Nodepath from "node:path";
6
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
7
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
8
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
10
+
11
+ let inputSchema = S.schema(s => ({
12
+ placeholder: s.m(S.option(S.bool))
13
+ }));
14
+
15
+ let pageSchema = S.schema(s => ({
16
+ path: s.m(S.string),
17
+ file: s.m(S.string),
18
+ isDynamic: s.m(S.bool),
19
+ dynamicType: s.m(S.string)
20
+ }));
21
+
22
+ let outputSchema = S.array(pageSchema);
23
+
24
+ function analyzeDynamicSegment(segment) {
25
+ if (segment.startsWith("[[") && segment.endsWith("]]")) {
26
+ return "OptionalParam";
27
+ } else if (segment.startsWith("[...") && segment.endsWith("]")) {
28
+ return "RestParam";
29
+ } else if (segment.startsWith("[") && segment.endsWith("]")) {
30
+ return "SingleParam";
31
+ } else {
32
+ return "Static";
33
+ }
34
+ }
35
+
36
+ function dynamicTypeToString(dt) {
37
+ switch (dt) {
38
+ case "Static" :
39
+ return "static";
40
+ case "SingleParam" :
41
+ return "single";
42
+ case "RestParam" :
43
+ return "rest";
44
+ case "OptionalParam" :
45
+ return "optional";
46
+ }
47
+ }
48
+
49
+ function isDynamicSegment(segment) {
50
+ return analyzeDynamicSegment(segment) !== "Static";
51
+ }
52
+
53
+ function fileToRoute(filePath) {
54
+ let p = filePath.replace(/\.(astro|md|mdx|html)$/, "").replace(/\/index$/, "");
55
+ if (p === "") {
56
+ return "/";
57
+ } else {
58
+ return p;
59
+ }
60
+ }
61
+
62
+ function getMostSignificantDynamicType(segments) {
63
+ return Stdlib_Array.reduce(segments, "Static", (acc, segment) => {
64
+ let segType = analyzeDynamicSegment(segment);
65
+ if (segType === "RestParam") {
66
+ return "RestParam";
67
+ }
68
+ switch (acc) {
69
+ case "Static" :
70
+ case "SingleParam" :
71
+ break;
72
+ case "RestParam" :
73
+ return "RestParam";
74
+ case "OptionalParam" :
75
+ return "OptionalParam";
76
+ }
77
+ switch (segType) {
78
+ case "Static" :
79
+ if (acc === "Static") {
80
+ return "Static";
81
+ } else {
82
+ return "SingleParam";
83
+ }
84
+ case "SingleParam" :
85
+ return "SingleParam";
86
+ case "OptionalParam" :
87
+ return "OptionalParam";
88
+ }
89
+ });
90
+ }
91
+
92
+ async function findPages(baseDir, currentPath, projectRoot) {
93
+ let fullPath = Nodepath.join(projectRoot, baseDir, currentPath);
94
+ try {
95
+ let entries = await Fs.promises.readdir(fullPath);
96
+ return (await Promise.all(entries.map(async entry => {
97
+ let entryPath = Nodepath.join(fullPath, entry);
98
+ let stats = await Fs.promises.stat(entryPath);
99
+ if (stats.isDirectory()) {
100
+ if (entry.startsWith("_") || entry === "api" || entry === "components") {
101
+ return [];
102
+ } else {
103
+ return await findPages(baseDir, Nodepath.join(currentPath, entry), projectRoot);
104
+ }
105
+ }
106
+ if (!(entry.endsWith(".astro") || entry.endsWith(".md") || entry.endsWith(".mdx") || entry.endsWith(".html"))) {
107
+ return [];
108
+ }
109
+ let fileName = entry.replace(/\.(astro|md|mdx|html)$/, "");
110
+ let routePath = fileToRoute(Nodepath.join(currentPath, fileName));
111
+ let segments = Nodepath.join(currentPath, fileName).split("/");
112
+ let hasDynamic = segments.some(isDynamicSegment);
113
+ let dynType = getMostSignificantDynamicType(segments);
114
+ return [{
115
+ path: routePath,
116
+ file: Nodepath.join(baseDir, currentPath, entry),
117
+ isDynamic: hasDynamic,
118
+ dynamicType: dynamicTypeToString(dynType)
119
+ }];
120
+ }))).flat();
121
+ } catch (exn) {
122
+ return [];
123
+ }
124
+ }
125
+
126
+ async function execute(ctx, _input) {
127
+ try {
128
+ let srcPages = await findPages("src/pages", "", ctx.projectRoot);
129
+ let rootPages = await findPages("pages", "", ctx.projectRoot);
130
+ let allPages = srcPages.concat(rootPages);
131
+ return {
132
+ TAG: "Ok",
133
+ _0: allPages
134
+ };
135
+ } catch (raw_exn) {
136
+ let exn = Primitive_exceptions.internalToException(raw_exn);
137
+ let msg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "Unknown error");
138
+ return {
139
+ TAG: "Error",
140
+ _0: `Failed to find pages: ` + msg
141
+ };
142
+ }
143
+ }
144
+
145
+ let Path;
146
+
147
+ let Fs$1;
148
+
149
+ let Tool;
150
+
151
+ let name = "get_client_pages";
152
+
153
+ let visibleToAgent = true;
154
+
155
+ let description = `Lists Astro client pages from the pages directory.
156
+
157
+ Parameters: None
158
+
159
+ Returns array of page paths based on file-system routing conventions.
160
+ Excludes API routes (src/pages/api/) - focuses on renderable pages only.`;
161
+
162
+ export {
163
+ Path,
164
+ Fs$1 as Fs,
165
+ Tool,
166
+ name,
167
+ visibleToAgent,
168
+ description,
169
+ inputSchema,
170
+ pageSchema,
171
+ outputSchema,
172
+ analyzeDynamicSegment,
173
+ dynamicTypeToString,
174
+ isDynamicSegment,
175
+ fileToRoute,
176
+ getMostSignificantDynamicType,
177
+ findPages,
178
+ execute,
179
+ }
180
+ /* inputSchema Not a pure module */