@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,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 */
|
package/src/cli/cli.mjs
ADDED
|
@@ -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 */
|