@nifrajs/cli 3.5.0 → 4.0.2
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 +42 -3
- package/dist/app-files.d.ts +11 -0
- package/dist/app-files.d.ts.map +1 -0
- package/dist/app-files.js +34 -0
- package/dist/app-files.js.map +1 -0
- package/dist/assure-hydration.d.ts +7 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +128 -43
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +16 -2
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +5 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +32 -10
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/cdn-check.d.ts +41 -0
- package/dist/cdn-check.d.ts.map +1 -0
- package/dist/cdn-check.js +207 -0
- package/dist/cdn-check.js.map +1 -0
- package/dist/check-diagnostics.d.ts +4 -0
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +13 -0
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +49 -7
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +291 -62
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +99 -63
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +42 -16
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +262 -82
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +62 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +275 -35
- package/dist/command-catalog.js.map +1 -1
- package/dist/config-target.d.ts +8 -0
- package/dist/config-target.d.ts.map +1 -0
- package/dist/config-target.js +45 -0
- package/dist/config-target.js.map +1 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +52 -13
- package/dist/contracts.js.map +1 -1
- package/dist/db-audit.d.ts +37 -0
- package/dist/db-audit.d.ts.map +1 -0
- package/dist/db-audit.js +99 -0
- package/dist/db-audit.js.map +1 -0
- package/dist/db-child.d.ts +64 -0
- package/dist/db-child.d.ts.map +1 -0
- package/dist/db-child.js +213 -0
- package/dist/db-child.js.map +1 -0
- package/dist/db-config.d.ts +60 -0
- package/dist/db-config.d.ts.map +1 -0
- package/dist/db-config.js +176 -0
- package/dist/db-config.js.map +1 -0
- package/dist/db-tool.d.ts +84 -0
- package/dist/db-tool.d.ts.map +1 -0
- package/dist/db-tool.js +537 -0
- package/dist/db-tool.js.map +1 -0
- package/dist/dev-bun-config.d.ts +20 -13
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +98 -41
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/dev-feed-tool.d.ts +106 -0
- package/dist/dev-feed-tool.d.ts.map +1 -0
- package/dist/dev-feed-tool.js +713 -0
- package/dist/dev-feed-tool.js.map +1 -0
- package/dist/dev-server-client.d.ts +67 -0
- package/dist/dev-server-client.d.ts.map +1 -0
- package/dist/dev-server-client.js +195 -0
- package/dist/dev-server-client.js.map +1 -0
- package/dist/diff-tool.d.ts +3 -3
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +6 -5
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +12 -4
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +76 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +15 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +73 -1
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +48 -22
- package/dist/fix-recipes.js.map +1 -1
- package/dist/frontend-guidance.js +5 -5
- package/dist/frontend-guidance.js.map +1 -1
- package/dist/i18n-check.d.ts +16 -0
- package/dist/i18n-check.d.ts.map +1 -0
- package/dist/i18n-check.js +154 -0
- package/dist/i18n-check.js.map +1 -0
- package/dist/init-agents.d.ts +49 -13
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +239 -34
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/script-kind.d.ts +7 -0
- package/dist/internal/script-kind.d.ts.map +1 -0
- package/dist/internal/script-kind.js +12 -0
- package/dist/internal/script-kind.js.map +1 -0
- package/dist/internal/source-facts.d.ts +5 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +61 -5
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +49 -24
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +507 -67
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +5 -1
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +92 -27
- package/dist/introspect.js.map +1 -1
- package/dist/learn.js +3 -3
- package/dist/learn.js.map +1 -1
- package/dist/levels-tool.d.ts +10 -0
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +33 -3
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +67 -17
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +78 -18
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +3 -2
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-app-tool.d.ts.map +1 -1
- package/dist/mcp-app-tool.js +2 -0
- package/dist/mcp-app-tool.js.map +1 -1
- package/dist/mcp-context.js +1 -1
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-delegate.d.ts +57 -0
- package/dist/mcp-delegate.d.ts.map +1 -0
- package/dist/mcp-delegate.js +130 -0
- package/dist/mcp-delegate.js.map +1 -0
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +23 -5
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-exec.d.ts +2 -0
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +132 -108
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.d.ts.map +1 -1
- package/dist/mcp-http.js +17 -5
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +14 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +45 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts +48 -0
- package/dist/mcp-isolate.d.ts.map +1 -0
- package/dist/mcp-isolate.js +222 -0
- package/dist/mcp-isolate.js.map +1 -0
- package/dist/mcp-project-child.d.ts +74 -0
- package/dist/mcp-project-child.d.ts.map +1 -0
- package/dist/mcp-project-child.js +130 -0
- package/dist/mcp-project-child.js.map +1 -0
- package/dist/mcp-render.js +2 -2
- package/dist/mcp-render.js.map +1 -1
- package/dist/mcp-root.d.ts +37 -9
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +110 -23
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-run.d.ts +18 -1
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +82 -9
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp-ws.js +4 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts +16 -3
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +81 -53
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +44 -0
- package/dist/migrate-layout.d.ts.map +1 -0
- package/dist/migrate-layout.js +1155 -0
- package/dist/migrate-layout.js.map +1 -0
- package/dist/openapi-tool.d.ts +2 -2
- package/dist/openapi-tool.d.ts.map +1 -1
- package/dist/openapi-tool.js.map +1 -1
- package/dist/openapi-types.d.ts +2 -1
- package/dist/openapi-types.d.ts.map +1 -1
- package/dist/openapi-types.js +240 -12
- package/dist/openapi-types.js.map +1 -1
- package/dist/pipeline-report.d.ts +3 -2
- package/dist/pipeline-report.d.ts.map +1 -1
- package/dist/pipeline-report.js +19 -16
- package/dist/pipeline-report.js.map +1 -1
- package/dist/port.d.ts +5 -4
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +46 -29
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +5 -0
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/4.0.0.d.ts +9 -0
- package/dist/recipes/4.0.0.d.ts.map +1 -0
- package/dist/recipes/4.0.0.js +36 -0
- package/dist/recipes/4.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/release-verification.d.ts +11 -1
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +47 -4
- package/dist/release-verification.js.map +1 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +12 -8
- package/dist/review.js.map +1 -1
- package/dist/route-types.d.ts +28 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +106 -0
- package/dist/route-types.js.map +1 -0
- package/dist/rules/codes.d.ts +9 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +9 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/data-guard.d.ts +12 -0
- package/dist/rules/data-guard.d.ts.map +1 -0
- package/dist/rules/data-guard.js +213 -0
- package/dist/rules/data-guard.js.map +1 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +17 -5
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/islands.d.ts.map +1 -1
- package/dist/rules/islands.js +7 -4
- package/dist/rules/islands.js.map +1 -1
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +71 -38
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/nano.d.ts.map +1 -1
- package/dist/rules/nano.js +15 -6
- package/dist/rules/nano.js.map +1 -1
- package/dist/rules/pages.d.ts +15 -0
- package/dist/rules/pages.d.ts.map +1 -0
- package/dist/rules/pages.js +88 -0
- package/dist/rules/pages.js.map +1 -0
- package/dist/rules/routes.d.ts +11 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +180 -2
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.d.ts +10 -0
- package/dist/rules/secrets.d.ts.map +1 -0
- package/dist/rules/secrets.js +169 -0
- package/dist/rules/secrets.js.map +1 -0
- package/dist/rules/security.d.ts +7 -0
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +171 -65
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/typescript.d.ts +16 -0
- package/dist/rules/typescript.d.ts.map +1 -0
- package/dist/rules/typescript.js +59 -0
- package/dist/rules/typescript.js.map +1 -0
- package/dist/rules/zones.d.ts +17 -0
- package/dist/rules/zones.d.ts.map +1 -0
- package/dist/rules/zones.js +174 -0
- package/dist/rules/zones.js.map +1 -0
- package/dist/scaffold.d.ts +25 -12
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +132 -87
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts +17 -3
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +941 -153
- package/dist/sdk.js.map +1 -1
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +23 -8
- package/dist/search-terms.js.map +1 -1
- package/dist/smoke.d.ts +79 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +494 -0
- package/dist/smoke.js.map +1 -0
- package/dist/sync-manifest.d.ts +10 -2
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +44 -19
- package/dist/sync-manifest.js.map +1 -1
- package/dist/sync-routes.d.ts +4 -3
- package/dist/sync-routes.d.ts.map +1 -1
- package/dist/sync-routes.js +4 -3
- package/dist/sync-routes.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +30 -11
- package/dist/test-tool.js.map +1 -1
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +11 -5
- package/dist/types-search.js.map +1 -1
- package/dist/upgrade.d.ts +36 -6
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +167 -29
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +4 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +45 -12
- package/dist/verification.js.map +1 -1
- package/dist/web-app-options.d.ts +19 -0
- package/dist/web-app-options.d.ts.map +1 -0
- package/dist/web-app-options.js +49 -0
- package/dist/web-app-options.js.map +1 -0
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +29 -13
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +185 -29
- package/docs/llms-full.txt +3807 -580
- package/docs/types.json +14229 -10939
- package/package.json +19 -12
- package/src/app-files.ts +40 -0
- package/src/assure-hydration.ts +139 -46
- package/src/assure.ts +17 -2
- package/src/capabilities-tool.ts +35 -9
- package/src/cdn-check.ts +268 -0
- package/src/check-diagnostics.ts +17 -0
- package/src/check-scan.ts +358 -63
- package/src/check.ts +97 -62
- package/src/cli.ts +296 -87
- package/src/command-catalog.ts +389 -48
- package/src/config-target.ts +47 -0
- package/src/contracts.ts +54 -12
- package/src/db-audit.ts +128 -0
- package/src/db-child.ts +285 -0
- package/src/db-config.ts +224 -0
- package/src/db-tool.ts +690 -0
- package/src/dev-bun-config.ts +116 -40
- package/src/dev-feed-tool.ts +912 -0
- package/src/dev-server-client.ts +267 -0
- package/src/diff-tool.ts +6 -5
- package/src/docs-search.ts +10 -3
- package/src/doctor.ts +98 -15
- package/src/env-file.ts +78 -1
- package/src/fix-recipes.ts +53 -32
- package/src/frontend-guidance.ts +5 -5
- package/src/i18n-check.ts +179 -0
- package/src/init-agents.ts +301 -38
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/script-kind.ts +11 -0
- package/src/internal/source-facts.ts +96 -22
- package/src/internal/typescript-import.ts +663 -68
- package/src/introspect.ts +90 -27
- package/src/learn.ts +3 -3
- package/src/levels-tool.ts +47 -2
- package/src/load.ts +140 -33
- package/src/manifest-tool.ts +3 -2
- package/src/mcp-app-tool.ts +2 -0
- package/src/mcp-context.ts +1 -1
- package/src/mcp-delegate.ts +162 -0
- package/src/mcp-docs-tools.ts +23 -5
- package/src/mcp-exec.ts +160 -132
- package/src/mcp-http.ts +19 -5
- package/src/mcp-io.ts +57 -0
- package/src/mcp-isolate.ts +287 -0
- package/src/mcp-project-child.ts +182 -0
- package/src/mcp-render.ts +2 -2
- package/src/mcp-root.ts +117 -22
- package/src/mcp-run.ts +104 -10
- package/src/mcp-ws.ts +4 -1
- package/src/mcp.ts +106 -64
- package/src/migrate-layout.ts +1338 -0
- package/src/openapi-tool.ts +7 -3
- package/src/openapi-types.ts +315 -13
- package/src/pipeline-report.ts +22 -18
- package/src/port.ts +53 -30
- package/src/project-facts.ts +5 -0
- package/src/recipes/4.0.0.ts +37 -0
- package/src/recipes/index.ts +2 -0
- package/src/release-verification.ts +74 -5
- package/src/review.ts +13 -8
- package/src/route-types.ts +133 -0
- package/src/rules/codes.ts +9 -0
- package/src/rules/data-guard.ts +230 -0
- package/src/rules/index.ts +18 -5
- package/src/rules/islands.ts +12 -4
- package/src/rules/legacy.ts +71 -39
- package/src/rules/nano.ts +18 -6
- package/src/rules/pages.ts +99 -0
- package/src/rules/routes.ts +181 -2
- package/src/rules/secrets.ts +174 -0
- package/src/rules/security.ts +198 -70
- package/src/rules/typescript.ts +77 -0
- package/src/rules/zones.ts +195 -0
- package/src/scaffold.ts +147 -95
- package/src/sdk.ts +1215 -158
- package/src/search-terms.ts +24 -7
- package/src/smoke.ts +607 -0
- package/src/sync-manifest.ts +50 -19
- package/src/sync-routes.ts +4 -3
- package/src/test-tool.ts +32 -11
- package/src/types-search.ts +11 -4
- package/src/upgrade.ts +205 -25
- package/src/verification-plan.ts +13 -0
- package/src/verification.ts +50 -12
- package/src/web-app-options.ts +57 -0
- package/src/work-graph.ts +27 -13
package/src/mcp-exec.ts
CHANGED
|
@@ -6,17 +6,20 @@
|
|
|
6
6
|
* replaceable.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { type Dirent, realpathSync } from "node:fs"
|
|
10
|
+
import { readdir, stat } from "node:fs/promises"
|
|
11
|
+
import { relative, resolve } from "node:path"
|
|
11
12
|
import { fileURLToPath } from "node:url"
|
|
12
|
-
import {
|
|
13
|
+
import { BACKEND_APP_FILE, CONFIG_FILE, FRAMEWORK_FILE } from "./app-files.ts"
|
|
13
14
|
import {
|
|
14
15
|
type CommandCatalogEntry,
|
|
15
16
|
type CommandCtx,
|
|
16
17
|
type CommandSpec,
|
|
17
18
|
commandCatalog,
|
|
19
|
+
commandMcpInputSchema,
|
|
18
20
|
commandMcpName,
|
|
19
21
|
findCommandSpec,
|
|
22
|
+
toCommandCatalogEntry,
|
|
20
23
|
} from "./command-catalog.ts"
|
|
21
24
|
import { collectContractProof } from "./contract-proof.ts"
|
|
22
25
|
import { loadDocsCorpus } from "./docs-search.ts"
|
|
@@ -33,9 +36,6 @@ import {
|
|
|
33
36
|
CHILD_INPUT_MAX_BYTES,
|
|
34
37
|
CHILD_OUTPUT_MAX_BYTES,
|
|
35
38
|
CHILD_TIMEOUT_MS,
|
|
36
|
-
LOCAL_TOOL_FETCH_TIMEOUT_MS,
|
|
37
|
-
notNifraResponse,
|
|
38
|
-
readBoundedResponse,
|
|
39
39
|
readBoundedStream,
|
|
40
40
|
timeoutMessage,
|
|
41
41
|
validateLocalPort,
|
|
@@ -44,6 +44,24 @@ import { mcpProjectPathError, resolveMcpProjectPath } from "./mcp-path.ts"
|
|
|
44
44
|
import type { McpTool, McpToolContext } from "./mcp-protocol.ts"
|
|
45
45
|
import { loadTypesCorpus } from "./types-search.ts"
|
|
46
46
|
|
|
47
|
+
const CODEFRAME_SOURCE = /\.(?:[cm]?[jt]sx?|vue|svelte|astro|mdx)$/i
|
|
48
|
+
|
|
49
|
+
/** The files a stack the caller supplies may show a codeframe from: project source, never a dotfile
|
|
50
|
+
* or anything under a dot directory (`.env`, `.git/`, `.nifra/`). */
|
|
51
|
+
function explainSourceGate(root: string): (file: string) => boolean {
|
|
52
|
+
// The codeframe reads a native realpath, which expands a Windows short name (`RUNNER~1`); the root
|
|
53
|
+
// has to be spelled the same way or every file looks like it sits outside it.
|
|
54
|
+
let base = resolve(root)
|
|
55
|
+
try {
|
|
56
|
+
base = realpathSync.native(base)
|
|
57
|
+
} catch {}
|
|
58
|
+
return (file) =>
|
|
59
|
+
CODEFRAME_SOURCE.test(file) &&
|
|
60
|
+
!relative(base, file)
|
|
61
|
+
.split(/[\\/]/)
|
|
62
|
+
.some((segment) => segment.startsWith("."))
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
/** Path to a sibling child entry (`mcp-run` / `mcp-render` / `mcp-ws`), resolved next to this module (`.ts` in
|
|
48
66
|
* dev, `.js` once built). Each runs in a FRESH subprocess per call so the project's current code loads. */
|
|
49
67
|
|
|
@@ -57,12 +75,21 @@ export function toMcpTool(
|
|
|
57
75
|
const { cwd } = options
|
|
58
76
|
const loadAppCached = options.loadAppCached ?? createCachedAppLoader(cwd)
|
|
59
77
|
const pathFields = ["config", "out", "lockfile", "before", "after", "baseline", "file"] as const
|
|
78
|
+
const catalogEntry = entry ?? toCommandCatalogEntry(spec)
|
|
60
79
|
return {
|
|
61
|
-
name: commandMcpName(
|
|
62
|
-
description:
|
|
63
|
-
inputSchema:
|
|
80
|
+
name: commandMcpName(catalogEntry.name),
|
|
81
|
+
description: catalogEntry.summary,
|
|
82
|
+
inputSchema: commandMcpInputSchema(catalogEntry),
|
|
64
83
|
handler: async (args: Record<string, unknown>, context: McpToolContext) => {
|
|
65
84
|
const raw = { ...args }
|
|
85
|
+
for (const field of catalogEntry.cliOnlyFields) {
|
|
86
|
+
if (raw[field] !== undefined)
|
|
87
|
+
return JSON.stringify(
|
|
88
|
+
{ ok: false, error: `${field} is available only from the nifra CLI` },
|
|
89
|
+
null,
|
|
90
|
+
2,
|
|
91
|
+
)
|
|
92
|
+
}
|
|
66
93
|
const dir = raw.dir
|
|
67
94
|
if (dir !== undefined && typeof dir !== "string") return dirError(undefined)
|
|
68
95
|
const target = resolveProjectDir(cwd, dir as string | undefined)
|
|
@@ -138,7 +165,7 @@ export function projectTools(
|
|
|
138
165
|
{
|
|
139
166
|
name: "nifra_verify",
|
|
140
167
|
description:
|
|
141
|
-
"Run the shared repository verification plan. Set release=true for the full release plan; the response preserves the declarative gate order and each gate's remediation.",
|
|
168
|
+
"Run the shared repository verification plan. Set release=true for the full release plan; the response preserves the declarative gate order and each gate's remediation. A gate whose script the project's package.json does not declare is reported as undeclared and not run; the result passes when every gate that ran passed.",
|
|
142
169
|
inputSchema: {
|
|
143
170
|
type: "object",
|
|
144
171
|
properties: {
|
|
@@ -243,7 +270,7 @@ export function projectTools(
|
|
|
243
270
|
{
|
|
244
271
|
name: "nifra_explain",
|
|
245
272
|
description:
|
|
246
|
-
"Turn a nifra error into a STRUCTURED diagnostic instead of eyeballing a stack trace: a stable `code`, the top frame in YOUR source, a codeframe around the offending line, and - when nifra recognises the failure - the plain-language `cause` + `fix` + docs anchor. Pass `error` (and `stack` if you have it, e.g. from nifra_run/nifra_test output or a failing build) to explain a specific failure
|
|
273
|
+
"Turn a nifra error into a STRUCTURED diagnostic instead of eyeballing a stack trace: a stable `code`, the top frame in YOUR source, a codeframe around the offending line, and - when nifra recognises the failure - the plain-language `cause` + `fix` + docs anchor. Pass `error` (and `stack` if you have it, e.g. from nifra_run/nifra_test output or a failing build) to explain a specific failure. With no `error`, returns the running dev server's most recent error of any kind (SSR, loader, API, build, browser, hydration) with its request id - the server is found automatically; `port`/`dir` pick one when several run. nifra_errors lists them all. Returns the same JSON the dev overlay renders.",
|
|
247
274
|
inputSchema: {
|
|
248
275
|
type: "object",
|
|
249
276
|
properties: {
|
|
@@ -265,144 +292,119 @@ export function projectTools(
|
|
|
265
292
|
port: {
|
|
266
293
|
type: "number",
|
|
267
294
|
description:
|
|
268
|
-
"
|
|
295
|
+
"The dev server's port, when the workspace runs several. Optional: the project's `nifra dev` server is found automatically.",
|
|
296
|
+
},
|
|
297
|
+
dir: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "The app directory, when the workspace runs more than one dev server.",
|
|
269
300
|
},
|
|
270
301
|
},
|
|
271
302
|
additionalProperties: false,
|
|
272
303
|
},
|
|
273
|
-
handler: async (args) => {
|
|
274
|
-
const { error, stack, name, port } = args as {
|
|
304
|
+
handler: async (args, context) => {
|
|
305
|
+
const { error, stack, name, port, dir } = args as {
|
|
275
306
|
error?: string
|
|
276
307
|
stack?: string
|
|
277
308
|
name?: string
|
|
278
309
|
port?: number
|
|
310
|
+
dir?: string
|
|
279
311
|
}
|
|
280
312
|
if (error !== undefined || stack !== undefined) {
|
|
281
313
|
const { buildDiagnostic } = await import("@nifrajs/web/diagnostic")
|
|
282
314
|
const e = new Error(error ?? "")
|
|
283
315
|
if (name !== undefined) e.name = name
|
|
284
316
|
if (stack !== undefined) e.stack = stack
|
|
285
|
-
return JSON.stringify(
|
|
317
|
+
return JSON.stringify(
|
|
318
|
+
buildDiagnostic(e, { root: cwd, showSource: explainSourceGate(cwd) }),
|
|
319
|
+
null,
|
|
320
|
+
2,
|
|
321
|
+
)
|
|
286
322
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
},
|
|
309
|
-
null,
|
|
310
|
-
2,
|
|
311
|
-
)
|
|
312
|
-
}
|
|
313
|
-
return await readBoundedResponse(res)
|
|
314
|
-
} catch (cause) {
|
|
315
|
-
return JSON.stringify(
|
|
316
|
-
{
|
|
317
|
-
code: "NIFRA_NONE",
|
|
318
|
-
message: `could not reach a nifra dev server at :${validPort} - ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
319
|
-
},
|
|
320
|
-
null,
|
|
321
|
-
2,
|
|
322
|
-
)
|
|
323
|
-
}
|
|
323
|
+
const target = resolveProjectDir(cwd, dir)
|
|
324
|
+
if (target === null) return dirError(dir)
|
|
325
|
+
if (port !== undefined && validateLocalPort(port) === undefined) {
|
|
326
|
+
return JSON.stringify(
|
|
327
|
+
{ code: "NIFRA_INVALID_PORT", message: "port must be an integer from 1 to 65535." },
|
|
328
|
+
null,
|
|
329
|
+
2,
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
const { explainLatest } = await import("./dev-feed-tool.ts")
|
|
333
|
+
try {
|
|
334
|
+
return JSON.stringify(await explainLatest(target, port, context.signal), null, 2)
|
|
335
|
+
} catch (cause) {
|
|
336
|
+
return JSON.stringify(
|
|
337
|
+
{
|
|
338
|
+
code: "NIFRA_NONE",
|
|
339
|
+
message: `could not read the dev server's errors - ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
340
|
+
},
|
|
341
|
+
null,
|
|
342
|
+
2,
|
|
343
|
+
)
|
|
324
344
|
}
|
|
325
|
-
return JSON.stringify(
|
|
326
|
-
{
|
|
327
|
-
code: "NIFRA_NONE",
|
|
328
|
-
message:
|
|
329
|
-
"Pass `error` (and `stack` if available) to explain a failure, or `port` to fetch the dev server's last error.",
|
|
330
|
-
},
|
|
331
|
-
null,
|
|
332
|
-
2,
|
|
333
|
-
)
|
|
334
345
|
},
|
|
335
346
|
},
|
|
336
347
|
{
|
|
337
348
|
name: "nifra_inspect",
|
|
338
349
|
description:
|
|
339
|
-
"Observe what your requests ACTUALLY did on the running dev server
|
|
350
|
+
"Observe what your requests ACTUALLY did on the running dev server: one trace per request, `{ requestId, method, path, status, durationMs, bytes, isr, errorIds, logCount }`. After nifra_run or a real browser request, call this to SEE the outcome (the status, how long, ISR hit/miss, whether it logged or failed) instead of guessing; pass a trace's `requestId` to nifra_errors/nifra_logs for what that request failed with or printed. Every `nifra dev` server keeps these traces and is found automatically; `port`/`dir` pick one when several run. Narrow with `path` (a path prefix), `requestId`, `since` (a cursor from an earlier call) or `limit` (most recent N). An explicit `port` this project did not start falls back to the `@nifrajs/devtools` plugin's traces.",
|
|
340
351
|
inputSchema: {
|
|
341
352
|
type: "object",
|
|
342
353
|
properties: {
|
|
343
|
-
port: {
|
|
354
|
+
port: {
|
|
355
|
+
type: "number",
|
|
356
|
+
description:
|
|
357
|
+
"The dev server's port. Optional: the project's server is found automatically.",
|
|
358
|
+
},
|
|
344
359
|
path: { type: "string", description: "Only traces whose path starts with this prefix." },
|
|
360
|
+
requestId: { type: "string", description: "Only the trace with this request id." },
|
|
361
|
+
since: {
|
|
362
|
+
type: "number",
|
|
363
|
+
description: "Only traces after this cursor (the `cursor` an earlier call returned).",
|
|
364
|
+
},
|
|
345
365
|
limit: { type: "number", description: "Return only the most recent N traces." },
|
|
366
|
+
dir: {
|
|
367
|
+
type: "string",
|
|
368
|
+
description: "The app directory, when the workspace runs more than one dev server.",
|
|
369
|
+
},
|
|
346
370
|
},
|
|
347
|
-
required: ["port"],
|
|
348
371
|
additionalProperties: false,
|
|
349
372
|
},
|
|
350
|
-
handler: async (args) => {
|
|
351
|
-
const { port, path, limit } = args as {
|
|
352
|
-
|
|
373
|
+
handler: async (args, context) => {
|
|
374
|
+
const { port, path, requestId, since, limit, dir } = args as {
|
|
375
|
+
port?: number
|
|
376
|
+
path?: string
|
|
377
|
+
requestId?: string
|
|
378
|
+
since?: number
|
|
379
|
+
limit?: number
|
|
380
|
+
dir?: string
|
|
381
|
+
}
|
|
382
|
+
const target = resolveProjectDir(cwd, dir)
|
|
383
|
+
if (target === null) return dirError(dir)
|
|
384
|
+
if (port !== undefined && validateLocalPort(port) === undefined) {
|
|
353
385
|
return JSON.stringify(
|
|
354
386
|
{
|
|
355
|
-
|
|
356
|
-
|
|
387
|
+
requests: [],
|
|
388
|
+
code: "NIFRA_INVALID_PORT",
|
|
389
|
+
note: "port must be an integer from 1 to 65535.",
|
|
357
390
|
},
|
|
358
391
|
null,
|
|
359
392
|
2,
|
|
360
393
|
)
|
|
361
394
|
}
|
|
395
|
+
const { inspectRequests } = await import("./dev-feed-tool.ts")
|
|
362
396
|
try {
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
note: "port must be an integer from 1 to 65535.",
|
|
370
|
-
},
|
|
371
|
-
null,
|
|
372
|
-
2,
|
|
373
|
-
)
|
|
374
|
-
}
|
|
375
|
-
const url = new URL(`http://127.0.0.1:${validPort}/_nifra/devtools/state`)
|
|
376
|
-
if (path !== undefined) url.searchParams.set("path", path)
|
|
377
|
-
if (limit !== undefined) url.searchParams.set("limit", String(limit))
|
|
378
|
-
const res = await fetch(url, {
|
|
379
|
-
signal: AbortSignal.timeout(LOCAL_TOOL_FETCH_TIMEOUT_MS),
|
|
380
|
-
})
|
|
381
|
-
if (res.headers.get("x-nifra-devtools") !== "true") return notNifraResponse("DevTools")
|
|
382
|
-
if (res.status === 404) {
|
|
383
|
-
return JSON.stringify(
|
|
384
|
-
{
|
|
385
|
-
events: [],
|
|
386
|
-
note: "No DevTools endpoint on that server. Mount `devtools()` from @nifrajs/web and run in development.",
|
|
387
|
-
},
|
|
388
|
-
null,
|
|
389
|
-
2,
|
|
390
|
-
)
|
|
391
|
-
}
|
|
392
|
-
if (!res.ok) {
|
|
393
|
-
return JSON.stringify(
|
|
394
|
-
{ events: [], note: `DevTools state returned ${res.status}.` },
|
|
395
|
-
null,
|
|
396
|
-
2,
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
|
-
return await readBoundedResponse(res)
|
|
397
|
+
const result = await inspectRequests(
|
|
398
|
+
target,
|
|
399
|
+
{ port, path, requestId, since, limit },
|
|
400
|
+
context.signal,
|
|
401
|
+
)
|
|
402
|
+
return JSON.stringify(result, null, 2)
|
|
400
403
|
} catch (cause) {
|
|
401
|
-
const validPort = validateLocalPort(port)
|
|
402
404
|
return JSON.stringify(
|
|
403
405
|
{
|
|
404
|
-
|
|
405
|
-
note: `Could not
|
|
406
|
+
requests: [],
|
|
407
|
+
note: `Could not read the dev server's request traces - ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
406
408
|
},
|
|
407
409
|
null,
|
|
408
410
|
2,
|
|
@@ -413,7 +415,7 @@ export function projectTools(
|
|
|
413
415
|
{
|
|
414
416
|
name: "nifra_openapi",
|
|
415
417
|
description:
|
|
416
|
-
'Return this project\'s backend OpenAPI 3.1 document generated from backend.ts route schemas via @nifrajs/schema. Use `format:"json"` for machine edits (default) or `format:"yaml"` for humans. Pass `path` (a route prefix like /api/orders, mirroring nifra_routes) to narrow a large backend to operations under that prefix instead of the whole document. Frontend-only apps return a valid empty paths object.',
|
|
418
|
+
'Return this project\'s backend OpenAPI 3.1 document generated from backend/app.ts route schemas via @nifrajs/schema. Use `format:"json"` for machine edits (default) or `format:"yaml"` for humans. Pass `path` (a route prefix like /api/orders, mirroring nifra_routes) to narrow a large backend to operations under that prefix instead of the whole document. Frontend-only apps return a valid empty paths object.',
|
|
417
419
|
inputSchema: {
|
|
418
420
|
type: "object",
|
|
419
421
|
properties: {
|
|
@@ -435,7 +437,7 @@ export function projectTools(
|
|
|
435
437
|
{
|
|
436
438
|
name: "nifra_run",
|
|
437
439
|
description:
|
|
438
|
-
"Run HTTP requests through this project's backend and return structured results
|
|
440
|
+
"Run HTTP requests through this project's backend and return structured results: status, headers, parsed body, `logs` (what the handler printed, per request, secrets redacted) and `errors` (a Diagnostic with codeframe, cause and fix for a thrown error or the unhandled error behind a bare 500). Use it to verify code after editing: by default the backend is re-loaded in a fresh process each call. Pass warm:true to reuse a hot worker while source files are unchanged; it restarts automatically when files change. Each request: { method?, path, body?, headers? }.",
|
|
439
441
|
inputSchema: {
|
|
440
442
|
type: "object",
|
|
441
443
|
properties: {
|
|
@@ -454,7 +456,7 @@ export function projectTools(
|
|
|
454
456
|
},
|
|
455
457
|
entry: {
|
|
456
458
|
type: "string",
|
|
457
|
-
description: "Backend entry file (default: backend.ts | app.ts).",
|
|
459
|
+
description: "Backend entry file (default: backend/app.ts | app.ts).",
|
|
458
460
|
},
|
|
459
461
|
warm: {
|
|
460
462
|
type: "boolean",
|
|
@@ -524,7 +526,7 @@ export function projectTools(
|
|
|
524
526
|
},
|
|
525
527
|
entry: {
|
|
526
528
|
type: "string",
|
|
527
|
-
description: "Backend entry file (default: backend.ts | app.ts).",
|
|
529
|
+
description: "Backend entry file (default: backend/app.ts | app.ts).",
|
|
528
530
|
},
|
|
529
531
|
},
|
|
530
532
|
required: ["path"],
|
|
@@ -541,7 +543,8 @@ export function projectTools(
|
|
|
541
543
|
properties: {
|
|
542
544
|
pattern: {
|
|
543
545
|
type: "string",
|
|
544
|
-
description:
|
|
546
|
+
description:
|
|
547
|
+
"Optional test file/path pattern inside the project, passed as an argv item to `bun test`.",
|
|
545
548
|
},
|
|
546
549
|
timeoutMs: {
|
|
547
550
|
type: "number",
|
|
@@ -573,7 +576,7 @@ export function projectTools(
|
|
|
573
576
|
{
|
|
574
577
|
name: "nifra_scaffold",
|
|
575
578
|
description:
|
|
576
|
-
'Map a URL path to the CORRECT routes/
|
|
579
|
+
'Map a URL path to the CORRECT routes/ files and get a contract-correct route pair: the page and its .backend.ts half (loader + output schema). Agents routinely place file routes wrong - this applies the convention for you: ":id"/"[id]" → [id], "*rest" → [...rest], "/" → index. Pass path (e.g. "/users/:id"). Returns both files + the route contract + stubs (the page is ready-to-write for react/preact/solid/vanilla; for vue/svelte use nifra_example for the page body).',
|
|
577
580
|
inputSchema: {
|
|
578
581
|
type: "object",
|
|
579
582
|
properties: {
|
|
@@ -584,7 +587,7 @@ export function projectTools(
|
|
|
584
587
|
write: {
|
|
585
588
|
type: "boolean",
|
|
586
589
|
description:
|
|
587
|
-
"When true, create
|
|
590
|
+
"When true, create both files if a verified ready-to-write page stub exists. Refuses overwrite.",
|
|
588
591
|
},
|
|
589
592
|
variant: {
|
|
590
593
|
type: "string",
|
|
@@ -612,7 +615,7 @@ export function projectTools(
|
|
|
612
615
|
if (write !== true) return renderScaffold(path, framework, flavour)
|
|
613
616
|
const result = await writeScaffoldRoute(cwd, path, framework, flavour)
|
|
614
617
|
const status = result.written
|
|
615
|
-
? `Written: \`${result.file}\``
|
|
618
|
+
? `Written: \`${result.file}\` and \`${result.backend.file}\``
|
|
616
619
|
: `Not written: ${result.reason ?? "no write performed"}`
|
|
617
620
|
return `${status}\n\n${renderScaffold(path, framework, flavour)}`
|
|
618
621
|
},
|
|
@@ -628,13 +631,17 @@ export function projectTools(
|
|
|
628
631
|
},
|
|
629
632
|
additionalProperties: false,
|
|
630
633
|
},
|
|
631
|
-
handler: async (args) => {
|
|
634
|
+
handler: async (args, context) => {
|
|
632
635
|
const opts = args as { dir?: string; interact?: boolean }
|
|
633
636
|
const target = resolveProjectDir(cwd, opts.dir)
|
|
634
637
|
if (target === null) return dirError(opts.dir)
|
|
635
638
|
const { runHydrationAssurance } = await import("./assure-hydration.ts")
|
|
636
639
|
return JSON.stringify(
|
|
637
|
-
await runHydrationAssurance(
|
|
640
|
+
await runHydrationAssurance(
|
|
641
|
+
target,
|
|
642
|
+
{ interact: opts.interact === true },
|
|
643
|
+
{ signal: context.signal },
|
|
644
|
+
),
|
|
638
645
|
null,
|
|
639
646
|
2,
|
|
640
647
|
)
|
|
@@ -678,7 +685,10 @@ export async function spawnChild(
|
|
|
678
685
|
if (new TextEncoder().encode(encodedInput).byteLength > CHILD_INPUT_MAX_BYTES)
|
|
679
686
|
return `${label} input exceeded ${CHILD_INPUT_MAX_BYTES} bytes.`
|
|
680
687
|
if (signal?.aborted) return `${label} cancelled${cancellationSuffix(signal)}.`
|
|
681
|
-
const proc = Bun.spawn([
|
|
688
|
+
const proc = Bun.spawn([process.execPath, childPath(child), cwd], {
|
|
689
|
+
cwd,
|
|
690
|
+
// Explicit: without `env`, Bun passes the environment it started with, missing `--env-file` values.
|
|
691
|
+
env: process.env,
|
|
682
692
|
stdin: "pipe",
|
|
683
693
|
stdout: "pipe",
|
|
684
694
|
stderr: "pipe",
|
|
@@ -732,24 +742,38 @@ export async function spawnChild(
|
|
|
732
742
|
}
|
|
733
743
|
}
|
|
734
744
|
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
745
|
+
const WARM_RUN_SOURCE = /\.(?:[cm]?[jt]sx?|json)$/
|
|
746
|
+
// Pruned while walking, not filtered after: a scan that descends into node_modules on every warm call
|
|
747
|
+
// costs more than the cold start the worker saves.
|
|
748
|
+
const WARM_RUN_SKIPPED_DIR = /^(?:\..*|node_modules|dist(?:-[a-z0-9]+)?|build|coverage)$/
|
|
738
749
|
const WARM_RUN_EXTRA_FILES = ["bun.lock", "bun.lockb"] as const
|
|
739
750
|
const MAX_WARM_PENDING = 64
|
|
740
751
|
|
|
741
752
|
async function warmRunFingerprint(cwd: string): Promise<string> {
|
|
742
753
|
const parts: string[] = []
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
if (WARM_RUN_IGNORED.test(rel)) continue
|
|
754
|
+
const walk = async (dir: string): Promise<void> => {
|
|
755
|
+
let entries: Dirent[]
|
|
746
756
|
try {
|
|
747
|
-
|
|
748
|
-
if (s.isFile()) parts.push(`${rel}:${s.mtimeMs}:${s.size}`)
|
|
757
|
+
entries = await readdir(resolve(cwd, dir), { withFileTypes: true })
|
|
749
758
|
} catch {
|
|
750
|
-
|
|
759
|
+
return
|
|
760
|
+
}
|
|
761
|
+
for (const entry of entries) {
|
|
762
|
+
const rel = dir === "" ? entry.name : `${dir}/${entry.name}`
|
|
763
|
+
if (entry.isDirectory()) {
|
|
764
|
+
if (!WARM_RUN_SKIPPED_DIR.test(entry.name)) await walk(rel)
|
|
765
|
+
continue
|
|
766
|
+
}
|
|
767
|
+
if (entry.name.startsWith(".") || !WARM_RUN_SOURCE.test(entry.name)) continue
|
|
768
|
+
try {
|
|
769
|
+
const s = await stat(resolve(cwd, rel))
|
|
770
|
+
if (s.isFile()) parts.push(`${rel}:${s.mtimeMs}:${s.size}`)
|
|
771
|
+
} catch {
|
|
772
|
+
// A file can disappear while an agent is editing. The next call will rescan the settled tree.
|
|
773
|
+
}
|
|
751
774
|
}
|
|
752
775
|
}
|
|
776
|
+
await walk("")
|
|
753
777
|
for (const rel of WARM_RUN_EXTRA_FILES) {
|
|
754
778
|
parts.push(`${rel}:${await fileFingerprint(resolve(cwd, rel))}`)
|
|
755
779
|
}
|
|
@@ -795,7 +819,10 @@ export class WarmWorker {
|
|
|
795
819
|
readonly fingerprint: string,
|
|
796
820
|
private readonly label: string,
|
|
797
821
|
) {
|
|
798
|
-
this.proc = Bun.spawn([
|
|
822
|
+
this.proc = Bun.spawn([process.execPath, childPath(child), cwd, "--worker"], {
|
|
823
|
+
cwd,
|
|
824
|
+
// Explicit: without `env`, Bun passes the environment it started with, missing `--env-file` values.
|
|
825
|
+
env: process.env,
|
|
799
826
|
stdin: "pipe",
|
|
800
827
|
stdout: "pipe",
|
|
801
828
|
stderr: "pipe",
|
|
@@ -1081,7 +1108,7 @@ export interface CachedAppLoaderOptions {
|
|
|
1081
1108
|
readonly fingerprint?: (cwd: string) => Promise<string>
|
|
1082
1109
|
}
|
|
1083
1110
|
|
|
1084
|
-
const APP_FINGERPRINT_FILES = [
|
|
1111
|
+
const APP_FINGERPRINT_FILES = [CONFIG_FILE, FRAMEWORK_FILE, BACKEND_APP_FILE] as const
|
|
1085
1112
|
|
|
1086
1113
|
function cacheToken(input: string): string {
|
|
1087
1114
|
let hash = 2166136261
|
|
@@ -1104,7 +1131,8 @@ async function fileFingerprint(path: string): Promise<string> {
|
|
|
1104
1131
|
}
|
|
1105
1132
|
}
|
|
1106
1133
|
|
|
1107
|
-
|
|
1134
|
+
/** `mtime:size` of the app's config, framework and backend entry files: what notices an edit. */
|
|
1135
|
+
export async function appFingerprint(cwd: string): Promise<string> {
|
|
1108
1136
|
return (
|
|
1109
1137
|
await Promise.all(
|
|
1110
1138
|
APP_FINGERPRINT_FILES.map(
|
package/src/mcp-http.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { docsTools } from "./mcp-docs-tools.ts"
|
|
|
29
29
|
export type { TypeEntry } from "./types-search.ts"
|
|
30
30
|
|
|
31
31
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
32
|
-
const VERSION = "
|
|
32
|
+
const VERSION = "4.0.2"
|
|
33
33
|
const SERVER_INFO = { name: "nifra-docs", version: VERSION }
|
|
34
34
|
// Derive the GET/health tool list from the tools actually served, so the line can never drift from them.
|
|
35
35
|
const docsHealth = (tools: McpTool[]): string =>
|
|
@@ -40,10 +40,12 @@ const docsHealth = (tools: McpTool[]): string =>
|
|
|
40
40
|
/** The project-independent tools, reading the package's bundled corpus from disk (CLI use): the text
|
|
41
41
|
* docs tools plus the `nifra_gallery` MCP Apps widget tool. */
|
|
42
42
|
export function publicDocsTools(): McpTool[] {
|
|
43
|
-
return [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
return [...openAiDocsTools(), examplesAppTool(loadExamplesCorpus)]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The text-only tool surface used by the public OpenAI Agent Plugin package. */
|
|
47
|
+
function openAiDocsTools(): McpTool[] {
|
|
48
|
+
return docsTools(loadDocsCorpus, loadExamplesCorpus, loadTypesCorpus)
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/**
|
|
@@ -59,9 +61,21 @@ export function respondMcpHttp(
|
|
|
59
61
|
return respondMcpHttpCore(request, tools, SERVER_INFO, { health: docsHealth(tools), ...options })
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/** The text-only handler used by the OpenAI Agent Plugin surface. */
|
|
65
|
+
function handleOpenAiMcpHttp(request: Request): Promise<Response> {
|
|
66
|
+
return respondMcpHttp(request, openAiDocsTools(), {
|
|
67
|
+
features: {
|
|
68
|
+
instructions:
|
|
69
|
+
"Nifra's public documentation tools for the OpenAI plugin. The server is stateless, read-only, and limited to Nifra's bundled documentation corpus, verified examples, API types, learning path, and frontend guidance.",
|
|
70
|
+
},
|
|
71
|
+
allowAnyOrigin: true,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
/** The CLI HTTP handler: serves the disk-backed corpus tools + registers the examples widget's `ui://`
|
|
63
76
|
* resource so MCP Apps hosts can render `nifra_gallery`. (`nifra docs-mcp` / `bun run` this file.) */
|
|
64
77
|
export function handleMcpHttp(request: Request): Promise<Response> {
|
|
78
|
+
if (new URL(request.url).pathname === "/openai") return handleOpenAiMcpHttp(request)
|
|
65
79
|
return respondMcpHttp(request, publicDocsTools(), {
|
|
66
80
|
features: {
|
|
67
81
|
resources: [examplesWidget.resource],
|
package/src/mcp-io.ts
CHANGED
|
@@ -154,6 +154,63 @@ export function serializeBoundedJson(
|
|
|
154
154
|
return text
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/** What {@link readHeadTail} kept: the start and end of a stream, and how many bytes fell between. */
|
|
158
|
+
export interface HeadTailOutput {
|
|
159
|
+
readonly head: string
|
|
160
|
+
readonly tail: string
|
|
161
|
+
/** Bytes dropped between `head` and `tail`; when 0, `head + tail` is the whole stream. */
|
|
162
|
+
readonly droppedBytes: number
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Drain a stream to its end, keeping at most `headBytes` from its start and `tailBytes` from its end,
|
|
167
|
+
* so a child that prints without limit costs bounded memory and still runs to completion.
|
|
168
|
+
*/
|
|
169
|
+
export async function readHeadTail(
|
|
170
|
+
stream: ReadableStream<Uint8Array>,
|
|
171
|
+
headBytes: number,
|
|
172
|
+
tailBytes: number,
|
|
173
|
+
): Promise<HeadTailOutput> {
|
|
174
|
+
const head: Uint8Array[] = []
|
|
175
|
+
let headSize = 0
|
|
176
|
+
const tail: Uint8Array[] = []
|
|
177
|
+
let tailSize = 0
|
|
178
|
+
let droppedBytes = 0
|
|
179
|
+
for await (let chunk of stream) {
|
|
180
|
+
if (headSize < headBytes) {
|
|
181
|
+
const taken = chunk.subarray(0, headBytes - headSize)
|
|
182
|
+
head.push(taken)
|
|
183
|
+
headSize += taken.byteLength
|
|
184
|
+
chunk = chunk.subarray(taken.byteLength)
|
|
185
|
+
}
|
|
186
|
+
if (chunk.byteLength === 0) continue
|
|
187
|
+
tail.push(chunk)
|
|
188
|
+
tailSize += chunk.byteLength
|
|
189
|
+
while (tailSize > tailBytes) {
|
|
190
|
+
const first = tail[0]!
|
|
191
|
+
const excess = tailSize - tailBytes
|
|
192
|
+
if (first.byteLength <= excess) {
|
|
193
|
+
tail.shift()
|
|
194
|
+
tailSize -= first.byteLength
|
|
195
|
+
droppedBytes += first.byteLength
|
|
196
|
+
} else {
|
|
197
|
+
tail[0] = first.subarray(excess)
|
|
198
|
+
tailSize -= excess
|
|
199
|
+
droppedBytes += excess
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const decode = (chunks: readonly Uint8Array[]): string => Buffer.concat(chunks).toString("utf8")
|
|
204
|
+
return { head: decode(head), tail: decode(tail), droppedBytes }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** A {@link HeadTailOutput} as one string, marking where bytes were dropped. */
|
|
208
|
+
export function joinHeadTail(output: HeadTailOutput): string {
|
|
209
|
+
return output.droppedBytes === 0
|
|
210
|
+
? output.head + output.tail
|
|
211
|
+
: `${output.head}\n…(${output.droppedBytes} bytes omitted)…\n${output.tail}`
|
|
212
|
+
}
|
|
213
|
+
|
|
157
214
|
/** Read child output incrementally and cancel as soon as the byte budget is crossed. */
|
|
158
215
|
export async function readBoundedStream(
|
|
159
216
|
stream: ReadableStream<Uint8Array>,
|