@nifrajs/cli 1.3.0 → 1.4.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/dist/cli.js +1 -1
- package/dist/introspect.d.ts +5 -12
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +8 -23
- package/dist/introspect.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +3 -16
- package/dist/mcp.js.map +1 -1
- package/docs/llms-full.txt +47 -11
- package/docs/types.json +850 -36
- package/package.json +8 -7
- package/src/cli.ts +1 -1
- package/src/introspect.ts +15 -37
- package/src/mcp-http.ts +1 -1
- package/src/mcp.ts +7 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "The nifra CLI — zero-config dev/build/start for a nifra app (`nifra dev`, `nifra build`, `nifra start`).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -41,12 +41,13 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@nifrajs/client": "1.
|
|
45
|
-
"@nifrajs/
|
|
46
|
-
"@nifrajs/
|
|
47
|
-
"@nifrajs/
|
|
48
|
-
"@nifrajs/
|
|
49
|
-
"
|
|
44
|
+
"@nifrajs/client": "1.4.0",
|
|
45
|
+
"@nifrajs/core": "1.4.0",
|
|
46
|
+
"@nifrajs/mcp": "1.4.0",
|
|
47
|
+
"@nifrajs/runner": "1.4.0",
|
|
48
|
+
"@nifrajs/schema": "1.4.0",
|
|
49
|
+
"@nifrajs/web": "1.4.0",
|
|
50
|
+
"create-nifra": "1.4.0"
|
|
50
51
|
},
|
|
51
52
|
"repository": {
|
|
52
53
|
"type": "git",
|
package/src/cli.ts
CHANGED
|
@@ -82,7 +82,7 @@ Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with
|
|
|
82
82
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).`
|
|
83
83
|
|
|
84
84
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
85
|
-
const CLI_VERSION = "1.
|
|
85
|
+
const CLI_VERSION = "1.4.0"
|
|
86
86
|
|
|
87
87
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
88
88
|
const asAdapter = (v: unknown): RenderAdapter => v as RenderAdapter
|
package/src/introspect.ts
CHANGED
|
@@ -5,38 +5,14 @@
|
|
|
5
5
|
* *this* project's actual surface, not just generic docs.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { type ReflectedRoute, reflectRoutes, type SchemaReflection } from "@nifrajs/core/reflection"
|
|
8
9
|
import type { Manifest } from "@nifrajs/web"
|
|
9
10
|
import { discoverRoutes } from "@nifrajs/web/fs"
|
|
10
11
|
import type { LoadedApp } from "./load.ts"
|
|
11
12
|
|
|
12
|
-
/** Structural view of a `@nifrajs/core` route descriptor — declared locally so the CLI needs no type
|
|
13
|
-
* dependency on core. `app.routes()` returns these. */
|
|
14
|
-
interface RouteDesc {
|
|
15
|
-
readonly method: string
|
|
16
|
-
readonly path: string
|
|
17
|
-
readonly schema?: unknown
|
|
18
|
-
}
|
|
19
|
-
|
|
20
13
|
/** Read the backend's registered routes, if it's a `server()` with a `.routes()` method. */
|
|
21
|
-
export function backendRoutes(backend: unknown):
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
return (backend as { routes(): RouteDesc[] }).routes()
|
|
25
|
-
} catch {
|
|
26
|
-
return []
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return []
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** The JSON Schema a `t`/Standard Schema carries (nifra's `t` is TypeBox-backed and exposes `.jsonSchema`),
|
|
33
|
-
* or `undefined` for a validator that doesn't expose one. This is the exact field-level contract. */
|
|
34
|
-
function jsonSchemaOf(schema: unknown): unknown {
|
|
35
|
-
if (schema && typeof schema === "object" && "jsonSchema" in schema) {
|
|
36
|
-
const js = (schema as { jsonSchema?: unknown }).jsonSchema
|
|
37
|
-
if (js !== undefined && js !== null) return js
|
|
38
|
-
}
|
|
39
|
-
return undefined
|
|
14
|
+
export function backendRoutes(backend: unknown): ReflectedRoute[] {
|
|
15
|
+
return [...reflectRoutes(backend)]
|
|
40
16
|
}
|
|
41
17
|
|
|
42
18
|
interface JsonSchemaNode {
|
|
@@ -99,16 +75,16 @@ export function tsTypeOf(schema: unknown, depth = 0): string {
|
|
|
99
75
|
|
|
100
76
|
/** One route's request/response field shapes, indented under its line. Reads the declared schemas off the
|
|
101
77
|
* descriptor so an agent gets the exact input + output contract — not just "this route is validated". */
|
|
102
|
-
function schemaLines(schema:
|
|
103
|
-
const s = schema
|
|
78
|
+
function schemaLines(schema: ReflectedRoute["schema"]): string[] {
|
|
79
|
+
const s = schema
|
|
104
80
|
if (!s) return []
|
|
105
81
|
const out: string[] = []
|
|
106
82
|
for (const key of ["query", "body", "response"] as const) {
|
|
107
83
|
const declared = s[key]
|
|
108
84
|
if (!declared) continue
|
|
109
|
-
const js =
|
|
85
|
+
const js = declared.jsonSchema
|
|
110
86
|
out.push(
|
|
111
|
-
js
|
|
87
|
+
js !== undefined
|
|
112
88
|
? ` - ${key}: \`${tsTypeOf(js)}\``
|
|
113
89
|
: ` - ${key}: _(validated; shape not introspectable from this validator)_`,
|
|
114
90
|
)
|
|
@@ -153,7 +129,7 @@ export function clientCall(method: string, path: string, schema: unknown): strin
|
|
|
153
129
|
}
|
|
154
130
|
|
|
155
131
|
/** Markdown section listing the backend's API routes with their request + response field shapes. */
|
|
156
|
-
export function apiRoutesSection(routes: readonly
|
|
132
|
+
export function apiRoutesSection(routes: readonly ReflectedRoute[]): string {
|
|
157
133
|
if (routes.length === 0) {
|
|
158
134
|
return "## API routes\n\nNo `backend.ts` server routes found (this app may be frontend-only)."
|
|
159
135
|
}
|
|
@@ -171,7 +147,7 @@ export function apiRoutesSection(routes: readonly RouteDesc[]): string {
|
|
|
171
147
|
* body/query/response shapes or the per-route `call` line. Bounds the first-call payload on a big backend:
|
|
172
148
|
* an agent gets the full route list (so it knows what's mounted) plus a pointer to fetch any route's full
|
|
173
149
|
* contract via the `path`/`kind` slice, instead of every schema up front. */
|
|
174
|
-
export function apiRoutesIndexSection(routes: readonly
|
|
150
|
+
export function apiRoutesIndexSection(routes: readonly ReflectedRoute[]): string {
|
|
175
151
|
if (routes.length === 0) {
|
|
176
152
|
return "## API routes (backend.ts)\n\nNo `backend.ts` server routes found (this app may be frontend-only)."
|
|
177
153
|
}
|
|
@@ -287,8 +263,10 @@ export interface RouteJson {
|
|
|
287
263
|
export function routesToJson(app: LoadedApp, pathPrefix?: string): RouteJson[] {
|
|
288
264
|
const shape = (v: unknown): string | undefined => {
|
|
289
265
|
if (!v) return undefined
|
|
290
|
-
const js =
|
|
291
|
-
return js
|
|
266
|
+
const js = (v as SchemaReflection).jsonSchema
|
|
267
|
+
return js !== undefined
|
|
268
|
+
? tsTypeOf(js)
|
|
269
|
+
: "(validated; shape not introspectable from this validator)"
|
|
292
270
|
}
|
|
293
271
|
let routes = backendRoutes(app.backend)
|
|
294
272
|
if (pathPrefix !== undefined && pathPrefix !== "") {
|
|
@@ -297,7 +275,7 @@ export function routesToJson(app: LoadedApp, pathPrefix?: string): RouteJson[] {
|
|
|
297
275
|
return [...routes]
|
|
298
276
|
.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method))
|
|
299
277
|
.map((r) => {
|
|
300
|
-
const s = r.schema
|
|
278
|
+
const s = r.schema
|
|
301
279
|
const body = shape(s?.body)
|
|
302
280
|
const query = shape(s?.query)
|
|
303
281
|
const response = shape(s?.response)
|
|
@@ -353,7 +331,7 @@ export interface PageRouteInput {
|
|
|
353
331
|
|
|
354
332
|
export interface RouteTableInput {
|
|
355
333
|
readonly pages: readonly PageRouteInput[]
|
|
356
|
-
readonly api:
|
|
334
|
+
readonly api: ReadonlyArray<Pick<ReflectedRoute, "method" | "path">>
|
|
357
335
|
/** The page router's `apiPrefix` (default `/api`); an API route at/under it is auto-mounted. `""`
|
|
358
336
|
* disables the auto-mount, so no API route is marked auto-mounted. */
|
|
359
337
|
readonly apiPrefix?: string
|
package/src/mcp-http.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { docsTools } from "./mcp-docs-tools.ts"
|
|
|
26
26
|
export type { TypeEntry } from "./types-search.ts"
|
|
27
27
|
|
|
28
28
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
29
|
-
const VERSION = "1.
|
|
29
|
+
const VERSION = "1.4.0"
|
|
30
30
|
const SERVER_INFO = { name: "nifra-docs", version: VERSION }
|
|
31
31
|
const DOCS_HEALTH =
|
|
32
32
|
"nifra docs MCP — POST JSON-RPC 2.0 here (methods: initialize, tools/list, tools/call). Tools: nifra_docs, nifra_example."
|
package/src/mcp.ts
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { readFile, stat } from "node:fs/promises"
|
|
27
27
|
import { basename, resolve, sep } from "node:path"
|
|
28
28
|
import { fileURLToPath } from "node:url"
|
|
29
|
+
import { type ReflectedRoute, reflectRoutes } from "@nifrajs/core/reflection"
|
|
29
30
|
import { Glob } from "bun"
|
|
30
31
|
import { loadDocsCorpus } from "./docs-search.ts"
|
|
31
32
|
import { loadExamplesCorpus } from "./examples.ts"
|
|
@@ -978,17 +979,8 @@ export function projectTools(
|
|
|
978
979
|
]
|
|
979
980
|
}
|
|
980
981
|
|
|
981
|
-
/** A `.tool()`-registered route as seen through the backend's `routes()` introspection. */
|
|
982
|
-
type ToolRoute = {
|
|
983
|
-
readonly schema?: { readonly body?: unknown; readonly response?: unknown }
|
|
984
|
-
readonly tool?: {
|
|
985
|
-
readonly name: string
|
|
986
|
-
readonly description: string
|
|
987
|
-
readonly annotations?: McpTool["annotations"]
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
982
|
type ToolBackend = {
|
|
991
|
-
readonly routes?: () =>
|
|
983
|
+
readonly routes?: () => readonly unknown[]
|
|
992
984
|
readonly fetch: (req: Request) => Promise<Response>
|
|
993
985
|
}
|
|
994
986
|
|
|
@@ -997,28 +989,15 @@ type ToolBackend = {
|
|
|
997
989
|
export function extractBackendTools(backend: unknown): McpTool[] {
|
|
998
990
|
const b = backend as ToolBackend | null
|
|
999
991
|
if (!b || typeof b.routes !== "function") return []
|
|
1000
|
-
|
|
1001
|
-
try {
|
|
1002
|
-
routes = b.routes()
|
|
1003
|
-
} catch {
|
|
1004
|
-
return []
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
const jsonSchemaOf = (schema: unknown): unknown => {
|
|
1008
|
-
if (schema && typeof schema === "object" && "jsonSchema" in schema) {
|
|
1009
|
-
const js = (schema as { jsonSchema?: unknown }).jsonSchema
|
|
1010
|
-
if (js !== undefined && js !== null) return js
|
|
1011
|
-
}
|
|
1012
|
-
return undefined
|
|
1013
|
-
}
|
|
992
|
+
const routes = reflectRoutes(b)
|
|
1014
993
|
|
|
1015
994
|
return routes
|
|
1016
995
|
.filter(
|
|
1017
996
|
(
|
|
1018
997
|
r,
|
|
1019
|
-
): r is
|
|
1020
|
-
schema: NonNullable<
|
|
1021
|
-
tool: NonNullable<
|
|
998
|
+
): r is ReflectedRoute & {
|
|
999
|
+
schema: NonNullable<ReflectedRoute["schema"]>
|
|
1000
|
+
tool: NonNullable<ReflectedRoute["tool"]>
|
|
1022
1001
|
} => r.schema !== undefined && r.tool !== undefined,
|
|
1023
1002
|
)
|
|
1024
1003
|
.map((r) => {
|
|
@@ -1027,7 +1006,7 @@ export function extractBackendTools(backend: unknown): McpTool[] {
|
|
|
1027
1006
|
return {
|
|
1028
1007
|
name: toolInfo.name,
|
|
1029
1008
|
description: toolInfo.description,
|
|
1030
|
-
inputSchema: (
|
|
1009
|
+
inputSchema: (s.body?.jsonSchema ?? {
|
|
1031
1010
|
type: "object",
|
|
1032
1011
|
properties: {},
|
|
1033
1012
|
}) as McpTool["inputSchema"],
|