@moonsjs/mcp 0.1.8 → 0.1.9
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/dist/cli.js
CHANGED
|
@@ -46,7 +46,7 @@ async function defaultLoadContext() {
|
|
|
46
46
|
lastError = error;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
throw new Error(`
|
|
49
|
+
throw new Error(`failed to load context from ${CONTEXT_URLS2.join(" and ")}: ${String(lastError)}`);
|
|
50
50
|
}
|
|
51
51
|
function searchMarkdown2(text, query, limit = 20) {
|
|
52
52
|
const needle = query.trim().toLowerCase();
|
|
@@ -106,18 +106,18 @@ function createTools2(options) {
|
|
|
106
106
|
scaffoldRoute: async (input) => {
|
|
107
107
|
const kind = input.kind ?? "page";
|
|
108
108
|
if (kind !== "page" && kind !== "api") {
|
|
109
|
-
throw new Error(`kind
|
|
109
|
+
throw new Error(`unknown kind: ${String(kind)}`);
|
|
110
110
|
}
|
|
111
111
|
const route = input.route.trim().replace(/^\/+|\/+$/g, "");
|
|
112
112
|
if (route.length === 0) {
|
|
113
|
-
throw new Error("`route`
|
|
113
|
+
throw new Error("`route` must not be empty (example: orders/[id])");
|
|
114
114
|
}
|
|
115
115
|
for (const segment of route.split("/")) {
|
|
116
116
|
if (segment.length === 0 || segment === "." || segment === "..") {
|
|
117
|
-
throw new Error(`
|
|
117
|
+
throw new Error(`invalid route segment: \`${segment}\``);
|
|
118
118
|
}
|
|
119
119
|
if (/[\\<>:"|?*]/.test(segment)) {
|
|
120
|
-
throw new Error(`
|
|
120
|
+
throw new Error(`route segment contains forbidden characters: \`${segment}\``);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
const dir = kind === "page" ? "src/routes" : "src/api";
|
|
@@ -125,7 +125,7 @@ function createTools2(options) {
|
|
|
125
125
|
const file = join(options.cwd, dir, `${route}${ext}`);
|
|
126
126
|
const relativeFile = relative(options.cwd, file).split(sep).join("/");
|
|
127
127
|
if (existsSync(file)) {
|
|
128
|
-
throw new Error(`file
|
|
128
|
+
throw new Error(`file already exists: ${relativeFile} (not overwriting)`);
|
|
129
129
|
}
|
|
130
130
|
mkdirSync(dirname(file), { recursive: true });
|
|
131
131
|
const content = kind === "page" ? renderPageRoute(route, input.loader === true) : renderApiRoute(route);
|
|
@@ -140,7 +140,7 @@ function createTools2(options) {
|
|
|
140
140
|
file: ".moons/dev.log",
|
|
141
141
|
lines: clamped,
|
|
142
142
|
exists: false,
|
|
143
|
-
text: "
|
|
143
|
+
text: "no dev log yet — run `moons dev` first"
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
const text = readFileSync(file, "utf8");
|
|
@@ -159,7 +159,7 @@ function createTools2(options) {
|
|
|
159
159
|
function renderPageRoute(route, withLoader) {
|
|
160
160
|
if (!withLoader) {
|
|
161
161
|
return [
|
|
162
|
-
`// Route: /${route} —
|
|
162
|
+
`// Route: /${route} — created by MCP scaffold_route.`,
|
|
163
163
|
"export default function Page() {",
|
|
164
164
|
` return <p>/${route}</p>;`,
|
|
165
165
|
"}",
|
|
@@ -168,7 +168,7 @@ function renderPageRoute(route, withLoader) {
|
|
|
168
168
|
`);
|
|
169
169
|
}
|
|
170
170
|
return [
|
|
171
|
-
`// Route: /${route} —
|
|
171
|
+
`// Route: /${route} — created by MCP scaffold_route.`,
|
|
172
172
|
'import { useLoaderData } from "@moonsjs/react";',
|
|
173
173
|
"",
|
|
174
174
|
"export const loader = async () => ({ items: [] as string[] });",
|
|
@@ -183,7 +183,7 @@ function renderPageRoute(route, withLoader) {
|
|
|
183
183
|
}
|
|
184
184
|
function renderApiRoute(route) {
|
|
185
185
|
return [
|
|
186
|
-
`// Endpoint: /api/${route} —
|
|
186
|
+
`// Endpoint: /api/${route} — created by MCP scaffold_route.`,
|
|
187
187
|
'import type { ApiHandlerArgs } from "@moonsjs/server";',
|
|
188
188
|
"",
|
|
189
189
|
"export async function GET({ params }: ApiHandlerArgs): Promise<Response> {",
|
|
@@ -260,4 +260,4 @@ function createMcpServer2(options) {
|
|
|
260
260
|
|
|
261
261
|
export { CONTEXT_URL2, CONTEXT_URLS2, localContextPath2, searchMarkdown2, createTools2, createMcpServer2 };
|
|
262
262
|
|
|
263
|
-
//# debugId=
|
|
263
|
+
//# debugId=BF000E7FA14B037F64756E2164756E21
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/tools.ts", "../src/server.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { MOONS_ERROR_CODES, findErrorEntry, type ErrorCatalogEntry } from \"@moonsjs/core\";\nimport {\n collectFns,\n collectRoutes,\n executeAudit,\n executeCheck,\n runTypegen,\n type AuditJson,\n type CheckJson,\n type FnsJson,\n type RoutesJson,\n} from \"@moonsjs/cli\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, relative, sep } from \"node:path\";\n\n/** Sumber teks konteks framework (default: lokal dulu, lalu GitHub raw). */\nexport type ContextFetcher = () => Promise<string>;\n\n export const CONTEXT_URL =\n \"https://raw.githubusercontent.com/moonsjs/docs/main/ai/CONTEXT.md\";\n\n/** Rantai fallback remote: situs docs → pages.dev → GitHub raw (repo docs). */\nexport const CONTEXT_URLS: readonly string[] = [\n \"https://docs.devmoon.net/raw/ai/CONTEXT.md\",\n \"https://moonsjs-docs.pages.dev/raw/ai/CONTEXT.md\",\n CONTEXT_URL,\n];\n\n/** Path konteks offline di paket `@moonsjs/cli`, dari entry yang di-resolve. */\nexport function localContextPath(cliEntry: string): string {\n return join(dirname(cliEntry), \"..\", \"docs\", \"ai\", \"CONTEXT.md\");\n}\n\nfunction readLocalContext(): { source: string; text: string } | null {\n try {\n const entry = createRequire(import.meta.url).resolve(\"@moonsjs/cli\");\n const path = localContextPath(entry);\n if (existsSync(path)) {\n return { source: `local:${path}`, text: readFileSync(path, \"utf8\") };\n }\n } catch {\n // fallthrough ke remote\n }\n return null;\n}\n\nasync function defaultLoadContext(): Promise<{ source: string; text: string }> {\n const local = readLocalContext();\n if (local !== null) {\n return local;\n }\n let lastError: unknown = null;\n for (const url of CONTEXT_URLS) {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n return { source: url, text: await response.text() };\n } catch (error) {\n lastError = error;\n }\n }\n throw new Error(\n `
|
|
5
|
+
"import { MOONS_ERROR_CODES, findErrorEntry, type ErrorCatalogEntry } from \"@moonsjs/core\";\nimport {\n collectFns,\n collectRoutes,\n executeAudit,\n executeCheck,\n runTypegen,\n type AuditJson,\n type CheckJson,\n type FnsJson,\n type RoutesJson,\n} from \"@moonsjs/cli\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, relative, sep } from \"node:path\";\n\n/** Sumber teks konteks framework (default: lokal dulu, lalu GitHub raw). */\nexport type ContextFetcher = () => Promise<string>;\n\n export const CONTEXT_URL =\n \"https://raw.githubusercontent.com/moonsjs/docs/main/ai/CONTEXT.md\";\n\n/** Rantai fallback remote: situs docs → pages.dev → GitHub raw (repo docs). */\nexport const CONTEXT_URLS: readonly string[] = [\n \"https://docs.devmoon.net/raw/ai/CONTEXT.md\",\n \"https://moonsjs-docs.pages.dev/raw/ai/CONTEXT.md\",\n CONTEXT_URL,\n];\n\n/** Path konteks offline di paket `@moonsjs/cli`, dari entry yang di-resolve. */\nexport function localContextPath(cliEntry: string): string {\n return join(dirname(cliEntry), \"..\", \"docs\", \"ai\", \"CONTEXT.md\");\n}\n\nfunction readLocalContext(): { source: string; text: string } | null {\n try {\n const entry = createRequire(import.meta.url).resolve(\"@moonsjs/cli\");\n const path = localContextPath(entry);\n if (existsSync(path)) {\n return { source: `local:${path}`, text: readFileSync(path, \"utf8\") };\n }\n } catch {\n // fallthrough ke remote\n }\n return null;\n}\n\nasync function defaultLoadContext(): Promise<{ source: string; text: string }> {\n const local = readLocalContext();\n if (local !== null) {\n return local;\n }\n let lastError: unknown = null;\n for (const url of CONTEXT_URLS) {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n return { source: url, text: await response.text() };\n } catch (error) {\n lastError = error;\n }\n }\n throw new Error(\n `failed to load context from ${CONTEXT_URLS.join(\" and \")}: ${String(lastError)}`,\n );\n}\n\nexport interface DocMatch {\n /** Judul section terakhir sebelum baris ini. */\n section: string;\n /** Nomor baris (1-based) di CONTEXT.md. */\n line: number;\n text: string;\n}\n\nexport interface SearchDocsResult {\n source: string;\n query: string;\n matches: DocMatch[];\n}\n\nexport interface ExplainErrorResult {\n code: string;\n found: boolean;\n entry?: ErrorCatalogEntry;\n validCodes: string[];\n}\n\nexport interface Toolset {\n listRoutes: () => Promise<RoutesJson>;\n getFnRegistry: () => Promise<FnsJson>;\n runCheck: () => Promise<CheckJson>;\n searchDocs: (query: string) => Promise<SearchDocsResult>;\n /** Addendum B.6: kode error §15 → pemicu + saran. */\n explainError: (code: string) => Promise<ExplainErrorResult>;\n /** Addendum H: audit keamanan (RPC/env/cookie/CSP/dist) machine-readable. */\n audit: () => Promise<AuditJson>;\n /** Addendum H: jalankan typegen (`.moons/*.d.ts`). */\n runTypegen: () => Promise<{ ok: true; outDir: string }>;\n /** Addendum L.4: buat file route mengikuti konvensi §11 (tanpa menimpa). */\n scaffoldRoute: (input: {\n route: string;\n kind?: \"page\" | \"api\";\n loader?: boolean;\n }) => Promise<ScaffoldRouteResult>;\n /** Addendum L.4: baca N baris terakhir `.moons/dev.log` (dikelola `moons dev`). */\n tailDevLogs: (lines: number) => Promise<TailDevLogsResult>;\n}\n\nexport interface ScaffoldRouteResult {\n file: string;\n kind: \"page\" | \"api\";\n created: true;\n}\n\nexport interface TailDevLogsResult {\n file: string;\n lines: number;\n exists: boolean;\n text: string;\n}\n\nexport interface ToolsetOptions {\n cwd: string;\n fetchContext?: ContextFetcher;\n}\n\n/** Pencarian substring case-insensitive atas markdown; returns baris + section. */\nexport function searchMarkdown(text: string, query: string, limit = 20): DocMatch[] {\n const needle = query.trim().toLowerCase();\n if (needle.length === 0) {\n return [];\n }\n\n const lines = text.split(\"\\n\");\n const matches: DocMatch[] = [];\n let section = \"(top)\";\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index] as string;\n const heading = /^#{1,6}\\s+(.*)$/.exec(line);\n if (heading !== null) {\n section = (heading[1] ?? \"\").trim();\n }\n if (line.toLowerCase().includes(needle)) {\n matches.push({ section, line: index + 1, text: line.trim() });\n if (matches.length >= limit) {\n break;\n }\n }\n }\n\n return matches;\n}\n\n/** Toolset MCP (Addendum A.7) — deterministik, tanpa panggilan LLM. */\nexport function createTools(options: ToolsetOptions): Toolset {\n const loadContext =\n options.fetchContext === undefined\n ? defaultLoadContext\n : async (): Promise<{ source: string; text: string }> => ({\n source: CONTEXT_URL,\n text: await (options.fetchContext as ContextFetcher)(),\n });\n let cached: { source: string; text: string } | null = null;\n\n return {\n listRoutes: () => collectRoutes(options.cwd),\n getFnRegistry: () => Promise.resolve(collectFns(options.cwd)),\n runCheck: () => executeCheck(options.cwd, { quiet: true }),\n searchDocs: async (query: string): Promise<SearchDocsResult> => {\n if (cached === null) {\n cached = await loadContext();\n }\n return { source: cached.source, query, matches: searchMarkdown(cached.text, query) };\n },\n explainError: async (code: string): Promise<ExplainErrorResult> => {\n const normalized = code.trim().toUpperCase();\n const entry = findErrorEntry(normalized);\n return {\n code: normalized,\n found: entry !== undefined,\n ...(entry === undefined ? {} : { entry }),\n validCodes: [...MOONS_ERROR_CODES],\n };\n },\n audit: () => executeAudit(options.cwd, { env: process.env }),\n runTypegen: async () => {\n await runTypegen(options.cwd);\n return { ok: true as const, outDir: \".moons\" };\n },\n scaffoldRoute: async (input) => {\n const kind = input.kind ?? \"page\";\n if (kind !== \"page\" && kind !== \"api\") {\n throw new Error(`unknown kind: ${String(kind)}`);\n }\n const route = input.route.trim().replace(/^\\/+|\\/+$/g, \"\");\n if (route.length === 0) {\n throw new Error(\"`route` must not be empty (example: orders/[id])\");\n }\n for (const segment of route.split(\"/\")) {\n if (segment.length === 0 || segment === \".\" || segment === \"..\") {\n throw new Error(`invalid route segment: \\`${segment}\\``);\n }\n if (/[\\\\<>:\"|?*]/.test(segment)) {\n throw new Error(`route segment contains forbidden characters: \\`${segment}\\``);\n }\n }\n const dir = kind === \"page\" ? \"src/routes\" : \"src/api\";\n const ext = kind === \"page\" ? \".tsx\" : \".ts\";\n const file = join(options.cwd, dir, `${route}${ext}`);\n const relativeFile = relative(options.cwd, file).split(sep).join(\"/\");\n if (existsSync(file)) {\n throw new Error(`file already exists: ${relativeFile} (not overwriting)`);\n }\n mkdirSync(dirname(file), { recursive: true });\n const content =\n kind === \"page\"\n ? renderPageRoute(route, input.loader === true)\n : renderApiRoute(route);\n writeFileSync(file, content);\n return { file: relativeFile, kind, created: true as const };\n },\n tailDevLogs: async (lines) => {\n const clamped = Math.min(Math.max(Math.trunc(lines), 1), 500);\n const file = join(options.cwd, \".moons\", \"dev.log\");\n if (!existsSync(file)) {\n return {\n file: \".moons/dev.log\",\n lines: clamped,\n exists: false,\n text: \"no dev log yet — run `moons dev` first\",\n };\n }\n const text = readFileSync(file, \"utf8\");\n const all = text.length === 0 ? [] : text.replace(/\\n$/, \"\").split(\"\\n\");\n return {\n file: \".moons/dev.log\",\n lines: clamped,\n exists: true,\n text: all.slice(-clamped).join(\"\\n\"),\n };\n },\n };\n}\n\n/** Template route page (§11): default export + loader opsional. */\nfunction renderPageRoute(route: string, withLoader: boolean): string {\n if (!withLoader) {\n return [\n `// Route: /${route} — created by MCP scaffold_route.`,\n \"export default function Page() {\",\n ` return <p>/${route}</p>;`,\n \"}\",\n \"\",\n ].join(\"\\n\");\n }\n return [\n `// Route: /${route} — created by MCP scaffold_route.`,\n 'import { useLoaderData } from \"@moonsjs/react\";',\n \"\",\n \"export const loader = async () => ({ items: [] as string[] });\",\n \"\",\n \"export default function Page() {\",\n \" const data = useLoaderData<typeof loader>();\",\n \" return <p>{data.items.length} item</p>;\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n}\n\n/** Template endpoint API (Addendum D.1): export method + params. */\nfunction renderApiRoute(route: string): string {\n return [\n `// Endpoint: /api/${route} — created by MCP scaffold_route.`,\n 'import type { ApiHandlerArgs } from \"@moonsjs/server\";',\n \"\",\n \"export async function GET({ params }: ApiHandlerArgs): Promise<Response> {\",\n \" return Response.json({ ok: true, params });\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n}\n",
|
|
6
6
|
"import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { createTools, type ToolsetOptions } from \"./tools\";\n\nfunction jsonContent(value: unknown, isError = false): {\n content: Array<{ type: \"text\"; text: string }>;\n isError?: boolean;\n} {\n return {\n content: [{ type: \"text\", text: JSON.stringify(value, null, 2) }],\n ...(isError ? { isError: true } : {}),\n };\n}\n\nasync function guard(\n run: () => Promise<unknown>,\n): Promise<{ content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n try {\n return jsonContent(await run());\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return jsonContent({ ok: false, error: message }, true);\n }\n}\n\n/** Bangun MCP server `moons` (stdio) untuk satu project (Addendum A.7). */\nexport function createMcpServer(options: ToolsetOptions): McpServer {\n const tools = createTools(options);\n const server = new McpServer({ name: \"moons\", version: \"0.1.0\" });\n\n server.registerTool(\n \"list_routes\",\n {\n description:\n \"Route table of the Moons app (path, file, key, kind, params, layouts) — scanned, no build needed.\",\n },\n () => guard(() => tools.listRoutes()),\n );\n\n server.registerTool(\n \"get_fn_registry\",\n {\n description:\n \"serverFn registry of the Moons app (file#export → hash, line) plus the registry map written to .moons/fn-registry.json.\",\n },\n () => guard(() => tools.getFnRegistry()),\n );\n\n server.registerTool(\n \"run_check\",\n {\n description:\n \"Definition of done: typegen → tsc --noEmit → build client → build server. Returns { ok, steps }.\",\n },\n () => guard(() => tools.runCheck()),\n );\n\n server.registerTool(\n \"search_docs\",\n {\n description:\n \"Search the Moons agent context pack (docs/ai/CONTEXT.md) by substring; returns matching lines with their sections.\",\n inputSchema: { query: z.string().describe(\"substring to search (case-insensitive)\") },\n },\n ({ query }) => guard(() => tools.searchDocs(query)),\n );\n\n server.registerTool(\n \"audit\",\n {\n description:\n \"Security audit: serverFn inventory, env() access map, cookie flags, CSP, and a dist/client scan for env-like secrets (Addendum H).\",\n },\n () => guard(() => tools.audit()),\n );\n\n server.registerTool(\n \"run_typegen\",\n {\n description:\n \"Run typegen: writes .moons/routes.d.ts + .moons/context.d.ts for the app in cwd.\",\n },\n () => guard(() => tools.runTypegen()),\n );\n\n server.registerTool(\n \"scaffold_route\",\n {\n description:\n \"Create a route file following Moons conventions: page → src/routes/<route>.tsx (default export + optional loader), api → src/api/<route>.ts (GET handler). Never overwrites existing files (Addendum L.4).\",\n inputSchema: {\n route: z.string().describe('route path without extension, e.g. \"orders/[id]\"'),\n kind: z.enum([\"page\", \"api\"]).optional().describe(\"default: page\"),\n loader: z\n .boolean()\n .optional()\n .describe(\"page only: include a sample loader + useLoaderData\"),\n },\n },\n ({ route, kind, loader }) =>\n guard(() =>\n tools.scaffoldRoute({\n route,\n ...(kind === undefined ? {} : { kind }),\n ...(loader === undefined ? {} : { loader }),\n }),\n ),\n );\n\n server.registerTool(\n \"tail_dev_logs\",\n {\n description:\n \"Tail the CLI-managed dev log (.moons/dev.log written by `moons dev`); default 50 lines, max 500 (Addendum L.4).\",\n inputSchema: {\n lines: z.number().int().min(1).max(500).optional().describe(\"default 50\"),\n },\n },\n ({ lines }) => guard(() => tools.tailDevLogs(lines ?? 50)),\n );\n\n server.registerTool(\n \"explain_error\",\n {\n description:\n \"Explain a Moons error code (format [moons] E_...): trigger, details, and fix suggestions. Returns found=false + validCodes for unknown codes.\",\n inputSchema: { code: z.string().describe(\"error code, e.g. E_SERVER_IMPORT_IN_CLIENT\") },\n },\n ({ code }) => guard(() => tools.explainError(code)),\n );\n\n return server;\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA;AACA;AACA;AAKQ,IAAM,eACZ;AAGK,IAAM,gBAAkC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,iBAAgB,CAAC,UAA0B;AAAA,EACzD,OAAO,KAAK,QAAQ,QAAQ,GAAG,MAAM,QAAQ,MAAM,YAAY;AAAA;AAGjE,SAAS,gBAAgB,GAA4C;AAAA,EACnE,IAAI;AAAA,IACF,MAAM,QAAQ,cAAc,YAAY,GAAG,EAAE,QAAQ,cAAc;AAAA,IACnE,MAAM,OAAO,kBAAiB,KAAK;AAAA,IACnC,IAAI,WAAW,IAAI,GAAG;AAAA,MACpB,OAAO,EAAE,QAAQ,SAAS,QAAQ,MAAM,aAAa,MAAM,MAAM,EAAE;AAAA,IACrE;AAAA,IACA,MAAM;AAAA,EAGR,OAAO;AAAA;AAGT,eAAe,kBAAkB,GAA8C;AAAA,EAC7E,MAAM,QAAQ,iBAAiB;AAAA,EAC/B,IAAI,UAAU,MAAM;AAAA,IAClB,OAAO;AAAA,EACT;AAAA,EACA,IAAI,YAAqB;AAAA,EACzB,WAAW,OAAO,eAAc;AAAA,IAC9B,IAAI;AAAA,MACF,MAAM,WAAW,MAAM,MAAM,GAAG;AAAA,MAChC,IAAI,CAAC,SAAS,IAAI;AAAA,QAChB,MAAM,IAAI,MAAM,QAAQ,SAAS,QAAQ;AAAA,MAC3C;AAAA,MACA,OAAO,EAAE,QAAQ,KAAK,MAAM,MAAM,SAAS,KAAK,EAAE;AAAA,MAClD,OAAO,OAAO;AAAA,MACd,YAAY;AAAA;AAAA,EAEhB;AAAA,EACA,MAAM,IAAI,MACR,
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "BF000E7FA14B037F64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
package/src/tools.ts
CHANGED
|
@@ -63,7 +63,7 @@ async function defaultLoadContext(): Promise<{ source: string; text: string }> {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
throw new Error(
|
|
66
|
-
`
|
|
66
|
+
`failed to load context from ${CONTEXT_URLS.join(" and ")}: ${String(lastError)}`,
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -194,18 +194,18 @@ export function createTools(options: ToolsetOptions): Toolset {
|
|
|
194
194
|
scaffoldRoute: async (input) => {
|
|
195
195
|
const kind = input.kind ?? "page";
|
|
196
196
|
if (kind !== "page" && kind !== "api") {
|
|
197
|
-
throw new Error(`kind
|
|
197
|
+
throw new Error(`unknown kind: ${String(kind)}`);
|
|
198
198
|
}
|
|
199
199
|
const route = input.route.trim().replace(/^\/+|\/+$/g, "");
|
|
200
200
|
if (route.length === 0) {
|
|
201
|
-
throw new Error("`route`
|
|
201
|
+
throw new Error("`route` must not be empty (example: orders/[id])");
|
|
202
202
|
}
|
|
203
203
|
for (const segment of route.split("/")) {
|
|
204
204
|
if (segment.length === 0 || segment === "." || segment === "..") {
|
|
205
|
-
throw new Error(`
|
|
205
|
+
throw new Error(`invalid route segment: \`${segment}\``);
|
|
206
206
|
}
|
|
207
207
|
if (/[\\<>:"|?*]/.test(segment)) {
|
|
208
|
-
throw new Error(`
|
|
208
|
+
throw new Error(`route segment contains forbidden characters: \`${segment}\``);
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
const dir = kind === "page" ? "src/routes" : "src/api";
|
|
@@ -213,7 +213,7 @@ export function createTools(options: ToolsetOptions): Toolset {
|
|
|
213
213
|
const file = join(options.cwd, dir, `${route}${ext}`);
|
|
214
214
|
const relativeFile = relative(options.cwd, file).split(sep).join("/");
|
|
215
215
|
if (existsSync(file)) {
|
|
216
|
-
throw new Error(`file
|
|
216
|
+
throw new Error(`file already exists: ${relativeFile} (not overwriting)`);
|
|
217
217
|
}
|
|
218
218
|
mkdirSync(dirname(file), { recursive: true });
|
|
219
219
|
const content =
|
|
@@ -231,7 +231,7 @@ export function createTools(options: ToolsetOptions): Toolset {
|
|
|
231
231
|
file: ".moons/dev.log",
|
|
232
232
|
lines: clamped,
|
|
233
233
|
exists: false,
|
|
234
|
-
text: "
|
|
234
|
+
text: "no dev log yet — run `moons dev` first",
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
237
|
const text = readFileSync(file, "utf8");
|
|
@@ -250,7 +250,7 @@ export function createTools(options: ToolsetOptions): Toolset {
|
|
|
250
250
|
function renderPageRoute(route: string, withLoader: boolean): string {
|
|
251
251
|
if (!withLoader) {
|
|
252
252
|
return [
|
|
253
|
-
`// Route: /${route} —
|
|
253
|
+
`// Route: /${route} — created by MCP scaffold_route.`,
|
|
254
254
|
"export default function Page() {",
|
|
255
255
|
` return <p>/${route}</p>;`,
|
|
256
256
|
"}",
|
|
@@ -258,7 +258,7 @@ function renderPageRoute(route: string, withLoader: boolean): string {
|
|
|
258
258
|
].join("\n");
|
|
259
259
|
}
|
|
260
260
|
return [
|
|
261
|
-
`// Route: /${route} —
|
|
261
|
+
`// Route: /${route} — created by MCP scaffold_route.`,
|
|
262
262
|
'import { useLoaderData } from "@moonsjs/react";',
|
|
263
263
|
"",
|
|
264
264
|
"export const loader = async () => ({ items: [] as string[] });",
|
|
@@ -274,7 +274,7 @@ function renderPageRoute(route: string, withLoader: boolean): string {
|
|
|
274
274
|
/** Template endpoint API (Addendum D.1): export method + params. */
|
|
275
275
|
function renderApiRoute(route: string): string {
|
|
276
276
|
return [
|
|
277
|
-
`// Endpoint: /api/${route} —
|
|
277
|
+
`// Endpoint: /api/${route} — created by MCP scaffold_route.`,
|
|
278
278
|
'import type { ApiHandlerArgs } from "@moonsjs/server";',
|
|
279
279
|
"",
|
|
280
280
|
"export async function GET({ params }: ApiHandlerArgs): Promise<Response> {",
|