blume 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/dist/cli/index.js +1286 -63
  4. package/dist/cli/index.js.map +32 -21
  5. package/dist/types/core/config-input.d.ts +18 -0
  6. package/dist/types/core/config.d.ts +4 -0
  7. package/dist/types/core/data.d.ts +1 -0
  8. package/dist/types/core/schema.d.ts +132 -17
  9. package/dist/types/core/types.d.ts +5 -3
  10. package/dist/types/openapi/references.d.ts +6 -0
  11. package/docs/advanced/api-reference.mdx +27 -0
  12. package/docs/advanced/changelog.mdx +10 -0
  13. package/docs/configuration/ai.mdx +38 -2
  14. package/docs/configuration/customization.mdx +27 -0
  15. package/docs/configuration/index.mdx +5 -0
  16. package/docs/content/navigation.mdx +12 -0
  17. package/docs/reference/cli.mdx +17 -13
  18. package/docs/reference/eval.mdx +106 -0
  19. package/docs/reference/meta.ts +1 -1
  20. package/package.json +1 -1
  21. package/src/ai/agent-readability.ts +19 -1
  22. package/src/ai/llms.ts +9 -4
  23. package/src/ai/mcp/server.ts +19 -8
  24. package/src/ai/mcp/stdio.ts +35 -0
  25. package/src/astro/generate.ts +25 -2
  26. package/src/astro/templates.ts +114 -22
  27. package/src/cli/commands/eval.ts +291 -0
  28. package/src/cli/commands/init.ts +9 -4
  29. package/src/cli/commands/mcp-stdio.ts +36 -0
  30. package/src/cli/index.ts +4 -0
  31. package/src/cli/required-secrets.ts +1 -1
  32. package/src/components/content/AccordionItem.astro +2 -2
  33. package/src/components/content/TreeFolder.astro +1 -2
  34. package/src/components/islands/AskAI.astro +9 -2
  35. package/src/components/islands/ask-ai.tsx +4 -2
  36. package/src/components/islands/hooks.ts +10 -4
  37. package/src/components/layout/NavTree.astro +37 -19
  38. package/src/components/layout/ReferenceLayout.astro +4 -0
  39. package/src/components/layout/RootLayout.astro +1 -1
  40. package/src/components/openapi/SchemaProperty.astro +3 -3
  41. package/src/core/config-input.ts +18 -0
  42. package/src/core/config.ts +4 -0
  43. package/src/core/data.ts +1 -0
  44. package/src/core/graph.ts +1 -0
  45. package/src/core/navigation.ts +9 -2
  46. package/src/core/schema.ts +51 -4
  47. package/src/core/server-features.ts +1 -1
  48. package/src/core/types.ts +5 -3
  49. package/src/eval/agents.ts +340 -0
  50. package/src/eval/findings.ts +103 -0
  51. package/src/eval/prompts.ts +78 -0
  52. package/src/eval/report.ts +214 -0
  53. package/src/eval/run.ts +290 -0
  54. package/src/eval/schema.ts +124 -0
  55. package/src/openapi/references.ts +23 -2
  56. package/src/openapi/render-mdx.ts +27 -4
  57. package/src/openapi/scalar.ts +1 -0
  58. package/src/openapi/source.ts +11 -4
  59. package/src/registry/eject.ts +23 -1
  60. package/src/search/build.ts +4 -3
@@ -1,4 +1,5 @@
1
1
  import type { ApiOperationRef, ApiSpecData } from "./model.ts";
2
+ import type { ReferenceSource } from "./references.ts";
2
3
 
3
4
  /**
4
5
  * Lower a parsed spec into MDX for the staged content source. Each operation and
@@ -126,7 +127,11 @@ const withDescription = (description: string, component: string): string =>
126
127
 
127
128
  export const operationMdx = (
128
129
  spec: ApiSpecData,
129
- operation: ApiOperationRef
130
+ operation: ApiOperationRef,
131
+ reference?: Pick<
132
+ ReferenceSource,
133
+ "includeInLlms" | "includeInSearch" | "noindex"
134
+ >
130
135
  ): RenderedPage => {
131
136
  const method = operation.method.toUpperCase();
132
137
  const title = operation.summary || `${method} ${operation.path}`;
@@ -142,9 +147,16 @@ export const operationMdx = (
142
147
  `<Operation source="${spec.slug}" id="${operation.key}" />`
143
148
  ),
144
149
  data: {
150
+ ...(reference?.includeInLlms === false ? { ai: { exclude: true } } : {}),
145
151
  ...(operation.deprecated ? { deprecated: true } : {}),
146
- search: { tags: [operation.tag, method] },
147
- seo: { description: operationDescription(spec, operation) },
152
+ search: {
153
+ ...(reference?.includeInSearch === false ? { exclude: true } : {}),
154
+ tags: [operation.tag, method],
155
+ },
156
+ seo: {
157
+ description: operationDescription(spec, operation),
158
+ ...(reference?.noindex ? { noindex: true } : {}),
159
+ },
148
160
  sidebar: { badge: method, label: operation.summary || operation.path },
149
161
  title,
150
162
  // Signals the two-column API layout (request panel instead of the TOC).
@@ -153,7 +165,13 @@ export const operationMdx = (
153
165
  };
154
166
  };
155
167
 
156
- export const overviewMdx = (spec: ApiSpecData): RenderedPage => {
168
+ export const overviewMdx = (
169
+ spec: ApiSpecData,
170
+ reference?: Pick<
171
+ ReferenceSource,
172
+ "includeInLlms" | "includeInSearch" | "noindex"
173
+ >
174
+ ): RenderedPage => {
157
175
  // Tag sections: declared tags in spec order, then any tag an operation
158
176
  // references that isn't declared under `tags`. The section headings are
159
177
  // emitted as real markdown `##` (not markup inside a component) so the
@@ -205,10 +223,15 @@ export const overviewMdx = (spec: ApiSpecData): RenderedPage => {
205
223
  ...tagSections,
206
224
  ].join("\n\n"),
207
225
  data: {
226
+ ...(reference?.includeInLlms === false ? { ai: { exclude: true } } : {}),
227
+ ...(reference?.includeInSearch === false
228
+ ? { search: { exclude: true } }
229
+ : {}),
208
230
  seo: {
209
231
  description:
210
232
  clip(plainProse(spec.description), META_DESCRIPTION_MAX) ||
211
233
  `${apiName(spec)} API reference.`,
234
+ ...(reference?.noindex ? { noindex: true } : {}),
212
235
  },
213
236
  sidebar: { label: "Overview" },
214
237
  title: apiName(spec),
@@ -167,6 +167,7 @@ export const buildReferenceFiles = async (options: {
167
167
  ...ref.scalar,
168
168
  },
169
169
  dataImport: `${"../".repeat(depth + 1)}generated/data.json`,
170
+ noindex: ref.noindex,
170
171
  route: ref.route,
171
172
  title: ref.label,
172
173
  }),
@@ -53,17 +53,24 @@ const toEntry = (rendered: RenderedPage, ref: string): SourceEntry => {
53
53
  /** All staged entries for one spec: operations first, overview last. */
54
54
  const specEntries = (
55
55
  spec: ApiSpecData,
56
- operations: ApiOperationRef[]
56
+ operations: ApiOperationRef[],
57
+ reference: ReferenceSource
57
58
  ): SourceEntry[] => {
58
59
  const entries = operations.map((operation) =>
59
- toEntry(operationMdx(spec, operation), `${routeToRef(operation.route)}.mdx`)
60
+ toEntry(
61
+ operationMdx(spec, operation, reference),
62
+ `${routeToRef(operation.route)}.mdx`
63
+ )
60
64
  );
61
65
  // Overview last so an operation sets the section's routePath before the index
62
66
  // page is inserted (the group's routePath is derived from its first child).
63
67
  // A root-mounted reference refs `index.mdx`, not `/index.mdx`.
64
68
  const base = routeToRef(spec.route);
65
69
  entries.push(
66
- toEntry(overviewMdx(spec), base ? `${base}/index.mdx` : "index.mdx")
70
+ toEntry(
71
+ overviewMdx(spec, reference),
72
+ base ? `${base}/index.mdx` : "index.mdx"
73
+ )
67
74
  );
68
75
  return entries;
69
76
  };
@@ -149,7 +156,7 @@ export const openApiSource = (
149
156
  ]
150
157
  : []),
151
158
  ],
152
- entries: specEntries(spec, operations),
159
+ entries: specEntries(spec, operations, reference),
153
160
  slug: reference.slug,
154
161
  spec,
155
162
  };
@@ -112,7 +112,15 @@ const askFiles = async (
112
112
  genDir: string
113
113
  ): Promise<{ content: string; path: string }[]> => {
114
114
  const { ask } = project.config.ai;
115
- if (!ask?.enabled) {
115
+ if (!(ask?.enabled && !ask.endpoint)) {
116
+ const endpointPath = join(srcDir, "pages", "api", "ask.ts");
117
+ if (existsSync(endpointPath)) {
118
+ const content = await readFile(endpointPath, "utf-8");
119
+ if (content.startsWith("// Generated by Blume. Do not edit.")) {
120
+ await rm(endpointPath, { force: true });
121
+ }
122
+ }
123
+ await rm(join(genDir, "ask-data.json"), { force: true });
116
124
  return [];
117
125
  }
118
126
  const grounded = ask.provider !== "inkeep";
@@ -271,6 +279,15 @@ const examplesPreviewFiles = (
271
279
  ]
272
280
  : [];
273
281
 
282
+ const ejectIntegrationBridge = (
283
+ config: BlumeProject["config"],
284
+ root: string,
285
+ configFile: string | null
286
+ ): Parameters<typeof astroConfigTemplate>[0]["integrationBridge"] =>
287
+ config.integrations.length > 0 && configFile
288
+ ? { configFile: toPosix(relative(root, configFile)) }
289
+ : undefined;
290
+
274
291
  /**
275
292
  * Promote the generated runtime into the project as an owned Astro app. After
276
293
  * eject the project has a normal `astro.config.mjs` and `src/`, the `blume` CLI
@@ -367,6 +384,11 @@ export const eject = async (
367
384
  dataPath: "./src/generated/data.json",
368
385
  examplesPath: "./src/generated/examples.ts",
369
386
  examplesThemePath: "./src/generated/examples.css",
387
+ integrationBridge: ejectIntegrationBridge(
388
+ config,
389
+ root,
390
+ context.configFile
391
+ ),
370
392
  needsReact,
371
393
  needsSvelte,
372
394
  needsVue,
@@ -1,9 +1,10 @@
1
1
  import { join } from "pathe";
2
2
 
3
3
  /**
4
- * Build a local Pagefind search index over the built site. Pagefind only
5
- * indexes elements marked with `data-pagefind-body`, which Blume adds to the
6
- * content of indexable pages, so nav chrome and excluded pages are skipped.
4
+ * Build a local Pagefind search index over the built site. Pagefind indexes
5
+ * every rendered page except those whose `<html>` carries
6
+ * `data-pagefind-ignore`, which Blume stamps on non-indexable pages
7
+ * (search-excluded, or hidden without the opt-in), so those stay out.
7
8
  *
8
9
  * Returns the number of pages indexed.
9
10
  */