blume 1.4.3 → 1.5.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/CHANGELOG.md +17 -0
- package/dist/cli/index.js +1621 -576
- package/dist/cli/index.js.map +109 -104
- package/dist/types/ai/component-markdown.d.ts +14 -4
- package/dist/types/core/config-input.d.ts +79 -27
- package/dist/types/core/config.d.ts +2 -1
- package/dist/types/core/data.d.ts +16 -1
- package/dist/types/core/diagnostics.d.ts +5 -1
- package/dist/types/core/i18n-ui.d.ts +12 -0
- package/dist/types/core/schema.d.ts +112 -15
- package/dist/types/core/sources/types.d.ts +3 -1
- package/dist/types/core/standard-schema.d.ts +7 -3
- package/dist/types/core/types.d.ts +43 -2
- package/dist/types/core/ui-packs/index.d.ts +9 -1
- package/dist/types/openapi/references.d.ts +6 -5
- package/dist/types/seo/x-handle.d.ts +3 -2
- package/docs/advanced/api-reference.mdx +8 -6
- package/docs/configuration/search.mdx +2 -0
- package/docs/configuration/seo.mdx +1 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/meta.mdx +2 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +35 -1
- package/docs/content/versioning.mdx +106 -0
- package/docs/reference/cli.mdx +1 -0
- package/docs/reference/frontmatter.mdx +3 -0
- package/package.json +3 -1
- package/skills/blume-migrate/SKILL.md +2 -2
- package/skills/blume-migrate/references/docusaurus.md +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +1 -1
- package/src/ai/agent-readability.ts +37 -10
- package/src/ai/ask-context.ts +5 -1
- package/src/ai/ask.ts +10 -1
- package/src/ai/component-markdown.ts +80 -43
- package/src/ai/llms.ts +40 -16
- package/src/ai/mcp/data.ts +48 -12
- package/src/ai/mcp/discovery.ts +28 -11
- package/src/ai/mcp/server.ts +183 -38
- package/src/ai/mcp/tools.ts +3 -3
- package/src/ai/skills.ts +32 -9
- package/src/ai/visibility.ts +2 -2
- package/src/astro/component-slots.ts +2 -0
- package/src/astro/examples.ts +6 -2
- package/src/astro/generate.ts +54 -29
- package/src/astro/integration.ts +13 -2
- package/src/astro/islands.ts +16 -9
- package/src/astro/templates.ts +152 -33
- package/src/audit/agent.ts +2 -2
- package/src/audit/checks/content.ts +26 -11
- package/src/audit/checks/dns-aid.ts +3 -0
- package/src/audit/checks/indexability.ts +24 -6
- package/src/audit/checks/llms.ts +9 -4
- package/src/audit/checks/network.ts +2 -0
- package/src/audit/checks/social.ts +18 -10
- package/src/audit/crawl.ts +37 -9
- package/src/audit/report.ts +20 -19
- package/src/audit/run.ts +5 -2
- package/src/audit/snapshot.ts +2 -4
- package/src/audit/types.ts +25 -3
- package/src/blume-modules.d.ts +5 -1
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/build.ts +15 -9
- package/src/cli/commands/dev.ts +2 -0
- package/src/cli/commands/doctor.ts +2 -0
- package/src/cli/commands/eval.ts +7 -3
- package/src/cli/commands/init.ts +9 -9
- package/src/cli/commands/mcp-stdio.ts +3 -0
- package/src/cli/commands/translate.ts +14 -3
- package/src/cli/commands/version.ts +85 -0
- package/src/cli/dev-lock.ts +31 -10
- package/src/cli/eject-scripts.ts +17 -2
- package/src/cli/index.ts +2 -0
- package/src/cli/init/questions.ts +1 -1
- package/src/cli/init/scaffold.ts +22 -15
- package/src/cli/internal-error.ts +1 -0
- package/src/components/content/auto-type-table.ts +3 -0
- package/src/components/content/diff.ts +9 -5
- package/src/components/content/github-info.ts +2 -0
- package/src/components/islands/ask-ai.tsx +33 -25
- package/src/components/islands/hooks.ts +5 -1
- package/src/components/islands/webmcp.ts +49 -12
- package/src/components/layout/Header.astro +25 -1
- package/src/components/layout/NavSelector.astro +11 -2
- package/src/components/layout/NavTree.astro +4 -2
- package/src/components/layout/RootLayout.astro +18 -0
- package/src/components/layout/Search.astro +77 -13
- package/src/components/layout/VersionBanner.astro +39 -0
- package/src/components/layout/analytics-client.ts +8 -5
- package/src/components/layout/hydration-hint.ts +1 -1
- package/src/components/layout/nav-utils.ts +1 -4
- package/src/components/layout/overrides.ts +25 -12
- package/src/components/layout/search/algolia.ts +18 -5
- package/src/components/layout/search/endpoint.ts +3 -0
- package/src/components/layout/search/flexsearch.ts +23 -7
- package/src/components/layout/search/orama-cloud.ts +1 -1
- package/src/components/layout/search/orama.ts +4 -1
- package/src/components/layout/search/pagefind.ts +2 -0
- package/src/components/layout/search/types.ts +13 -1
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/ApiOverview.astro +32 -6
- package/src/components/openapi/AsyncApiOperation.astro +237 -0
- package/src/components/openapi/Bindings.astro +89 -0
- package/src/components/openapi/MethodBadge.astro +3 -0
- package/src/components/openapi/Operation.astro +7 -2
- package/src/components/openapi/PanelTabs.astro +131 -0
- package/src/components/openapi/ParametersTable.astro +2 -0
- package/src/components/openapi/RequestPanel.astro +12 -119
- package/src/components/openapi/async-snippets.ts +174 -0
- package/src/components/openapi/async.ts +348 -0
- package/src/components/openapi/helpers.ts +52 -20
- package/src/components/openapi/security.ts +102 -29
- package/src/components/openapi/snippets.ts +11 -11
- package/src/core/component-overrides.ts +28 -23
- package/src/core/config-input.ts +88 -27
- package/src/core/config.ts +20 -7
- package/src/core/content.ts +3 -1
- package/src/core/data.ts +16 -1
- package/src/core/define-components.ts +5 -0
- package/src/core/diagnostics.ts +46 -38
- package/src/core/frontmatter.ts +33 -7
- package/src/core/graph.ts +137 -53
- package/src/core/i18n-ui.ts +15 -0
- package/src/core/i18n.ts +16 -8
- package/src/core/load-module.ts +1 -0
- package/src/core/manifest.ts +92 -3
- package/src/core/meta.ts +44 -14
- package/src/core/nav-diagnostics.ts +3 -3
- package/src/core/navigation.ts +247 -67
- package/src/core/project-graph.ts +15 -3
- package/src/core/schema.ts +213 -67
- package/src/core/sources/assets.ts +2 -0
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/github-releases.ts +39 -31
- package/src/core/sources/mdx-remote.ts +4 -0
- package/src/core/sources/normalize.ts +67 -20
- package/src/core/sources/notion.ts +49 -17
- package/src/core/sources/portable-text.ts +32 -11
- package/src/core/sources/sanity.ts +68 -14
- package/src/core/sources/types.ts +4 -0
- package/src/core/sources/watch.ts +1 -1
- package/src/core/standard-schema.ts +9 -3
- package/src/core/text-width.ts +26 -0
- package/src/core/tsconfig-aliases.ts +9 -5
- package/src/core/types.ts +45 -2
- package/src/core/ui-packs/index.ts +9 -1
- package/src/core/version-cut.ts +301 -0
- package/src/core/version.ts +2 -0
- package/src/core/versions.ts +170 -0
- package/src/deploy/adapter-output.ts +5 -2
- package/src/deploy/cloudflare-negotiation.ts +25 -10
- package/src/deploy/sitemap.ts +33 -1
- package/src/deploy/vercel-negotiation.ts +11 -4
- package/src/eval/report.ts +4 -4
- package/src/eval/run.ts +2 -2
- package/src/eval/schema.ts +1 -1
- package/src/markdown/base-links.ts +6 -6
- package/src/markdown/directives.ts +7 -1
- package/src/markdown/heading-anchors.ts +17 -6
- package/src/markdown/index.ts +73 -24
- package/src/markdown/inline-code.ts +14 -2
- package/src/markdown/language-icon.ts +6 -2
- package/src/markdown/mdast.ts +18 -4
- package/src/markdown/package-commands.ts +6 -8
- package/src/markdown/table-wrap.ts +4 -1
- package/src/markdown/twoslash.ts +2 -0
- package/src/og/card.ts +30 -11
- package/src/og/derive.ts +43 -27
- package/src/openapi/asyncapi.ts +366 -0
- package/src/openapi/model.ts +126 -57
- package/src/openapi/parse.ts +97 -5
- package/src/openapi/references.ts +12 -10
- package/src/openapi/render-mdx.ts +73 -34
- package/src/openapi/scalar.ts +6 -8
- package/src/openapi/source.ts +98 -28
- package/src/registry/eject.ts +7 -2
- package/src/search/documents.ts +25 -5
- package/src/search/facets.ts +7 -5
- package/src/search/orama-index.ts +66 -20
- package/src/search/popular.ts +10 -5
- package/src/search/providers.ts +2 -2
- package/src/search/sync/index.ts +2 -0
- package/src/search/sync/typesense.ts +4 -2
- package/src/seo/jsonld.ts +24 -6
- package/src/seo/x-handle.ts +8 -3
- package/src/theme/chrome-icons.ts +7 -2
- package/src/theme/fonts.ts +8 -4
- package/src/theme/icons.ts +4 -2
- package/src/theme/palette.ts +22 -14
- package/src/translate/meta.ts +15 -6
- package/src/translate/report.ts +9 -5
- package/src/translate/run.ts +10 -4
- package/src/translate/validate.ts +52 -17
- package/src/translate/work-list.ts +0 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Astro's collections carry no schema here, so a page's `seo.x.creator` reaches
|
|
8
8
|
* them as raw frontmatter, and the schema's own transform never runs on it.
|
|
9
9
|
* (Blume's page pipeline does reject a non-string `creator` before the page is
|
|
10
|
-
* built, so
|
|
10
|
+
* built, so the string guard is defense in depth rather than the expected
|
|
11
|
+
* path.)
|
|
11
12
|
*/
|
|
12
|
-
export declare const normalizeXHandle: (value:
|
|
13
|
+
export declare const normalizeXHandle: <Value>(value: Value) => string | undefined;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: OpenAPI / AsyncAPI
|
|
3
|
-
description: Drop in an OpenAPI spec and get a native API reference — one real page per operation, in your sidebar and search.
|
|
3
|
+
description: Drop in an OpenAPI or AsyncAPI spec and get a native API reference — one real page per operation, in your sidebar and search.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, and generated code samples. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. The config below points Blume at the public Petstore spec as an example.
|
|
6
|
+
Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, and generated code samples. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. Event-driven APIs get the [same treatment from an AsyncAPI spec](#asyncapi). The config below points Blume at the public Petstore spec as an example.
|
|
7
7
|
|
|
8
8
|
```ts blume.config.ts lineNumbers
|
|
9
9
|
openapi: {
|
|
@@ -153,7 +153,7 @@ Blume's own [`i18n`](/docs/content/i18n) translates the docs chrome, but Scalar
|
|
|
153
153
|
|
|
154
154
|
## AsyncAPI
|
|
155
155
|
|
|
156
|
-
Event-driven APIs use a sibling `asyncapi` block with the same shape.
|
|
156
|
+
Event-driven APIs use a sibling `asyncapi` block with the same shape — and the same native renderer. Each `send`/`receive` operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, and an Authorization section derived from the spec's `securitySchemes` (server-level and operation-level, alternatives as "or" groups). Only the default route differs (`/events`):
|
|
157
157
|
|
|
158
158
|
```ts blume.config.ts lineNumbers
|
|
159
159
|
asyncapi: {
|
|
@@ -162,6 +162,8 @@ asyncapi: {
|
|
|
162
162
|
}
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
AsyncAPI **2.x specs are normalized to 3.x automatically** with the official AsyncAPI converter, so `publish`/`subscribe` channels map onto `send`/`receive` operation pages with stable URLs — later upgrading the spec file itself through the converter moves nothing. Operations group by tag; untagged operations group under their channel address.
|
|
166
|
+
|
|
167
|
+
Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on the `openapi` block; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.
|
|
168
|
+
|
|
169
|
+
Everything documented above carries over unchanged: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags, and search indexing by operation summary and tag. Setting `renderer: "scalar"` opts back into the embedded Scalar SPA — Scalar auto-detects the document type and renders channels, operations, messages, and a Models section (no interactive playground for events) — where, as with OpenAPI, only `noindex` applies.
|
|
@@ -42,6 +42,8 @@ Write `href` as if the site were mounted at the root — a `basePath` is applied
|
|
|
42
42
|
|
|
43
43
|
For every indexable page, Blume indexes its title, description, and body reduced to plain text — code blocks, images, and markup are stripped, so results stay relevant. The index is built from your source files, so it's identical in dev and production.
|
|
44
44
|
|
|
45
|
+
On a [versioned](/docs/content/versioning) site, results default to the version being viewed, with an "All versions" toggle in the dialog footer (remembered per reader). Cross-version hits name their version on the row. Orama, FlexSearch, Algolia, and Typesense honor the scoping — hosted records carry a `version` facet, with the current docs uploaded as `"current"` — while Pagefind stays unscoped, matching its locale behavior.
|
|
46
|
+
|
|
45
47
|
## Tags
|
|
46
48
|
|
|
47
49
|
Add `search.tags` to a page's frontmatter to group it under a filter in the search dialog — readers can narrow results to a tag with a click. Tags also become a facet on the hosted providers.
|
|
@@ -249,7 +249,7 @@ URLs are absolute when `deployment.site` is set. Pages marked `seo.noindex` are
|
|
|
249
249
|
|
|
250
250
|
## Sitemap
|
|
251
251
|
|
|
252
|
-
Blume writes a `sitemap.xml` of every indexable page at build time. It needs an absolute [`deployment.site`](/docs/deployment) and lists every page except drafts, hidden, and `noindex` pages. On by default:
|
|
252
|
+
Blume writes a `sitemap.xml` of every indexable page at build time. It needs an absolute [`deployment.site`](/docs/deployment) and lists every page except drafts, hidden, and `noindex` pages. On a [versioned](/docs/content/versioning) site, archived pages whose canonical points at their live equivalent are left out too — the live page is the one to index. On by default:
|
|
253
253
|
|
|
254
254
|
```ts blume.config.ts lineNumbers
|
|
255
255
|
seo: {
|
package/docs/content/i18n.mdx
CHANGED
|
@@ -3,7 +3,7 @@ title: Internationalization
|
|
|
3
3
|
description: Serve your docs in multiple languages with locale-aware routing, per-language navigation, translated UI, and SEO — all convention-first.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Blume serves one project in many languages. Drop translated files into the right place and Blume wires up routing, the language switcher, per-locale navigation, and SEO for you — there's no separate routing layer to maintain. It's opt-in: without an `i18n` block, your site stays single-language exactly as before.
|
|
6
|
+
Blume serves one project in many languages. Drop translated files into the right place and Blume wires up routing, the language switcher, per-locale navigation, and SEO for you — there's no separate routing layer to maintain. It's opt-in: without an `i18n` block, your site stays single-language exactly as before. It also composes with [versioning](/docs/content/versioning) — a frozen snapshot keeps its translations, and locale fallback works within each version.
|
|
7
7
|
|
|
8
8
|
## Enable it
|
|
9
9
|
|
package/docs/content/meta.mdx
CHANGED
|
@@ -31,11 +31,12 @@ Every field is optional — set only what you want to override.
|
|
|
31
31
|
| `icon` | `string` | Icon shown next to the label. |
|
|
32
32
|
| `order` | `number` | Position among sibling groups and pages. Lower numbers sort first. |
|
|
33
33
|
| `collapsed` | `boolean` | Under the [`group` display mode](/docs/content/navigation#display-modes), whether the group starts collapsed. |
|
|
34
|
+
| `display` | `"flat" \| "group" \| "page"` | Render mode for this group; overrides the global [`navigation.sidebar.display`](/docs/content/navigation#display-modes). |
|
|
34
35
|
| `pages` | `string[]` | Explicit order for the group's children, by slug. |
|
|
35
36
|
|
|
36
37
|
The `pages` array lists children by slug — the folder or file name with its numeric prefix and any parentheses stripped (so `01-quickstart.mdx` is `"quickstart"`). Children you leave out still appear, after the listed ones.
|
|
37
38
|
|
|
38
|
-
How groups render — flat headers, collapsible disclosures, or drill-in panels —
|
|
39
|
+
How groups render — flat headers, collapsible disclosures, or drill-in panels — defaults to the sidebar-wide `navigation.sidebar.display`; set `display` here to override it for this group alone. A folder's `index` page can also set it from frontmatter, which wins over `meta.ts` — see [per-group overrides](/docs/content/navigation#per-group-overrides).
|
|
39
40
|
|
|
40
41
|
## Computed meta
|
|
41
42
|
|
package/docs/content/meta.ts
CHANGED
|
@@ -69,7 +69,39 @@ navigation: {
|
|
|
69
69
|
`page` mode keeps deep sections tidy — reach for it when groups have many children and you'd rather drill into them than scroll past them.
|
|
70
70
|
:::
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
### Per-group overrides
|
|
73
|
+
|
|
74
|
+
Any generated group can opt out of the global mode — no explicit sidebar required. Set `display` in the folder's [`meta.ts`](/docs/content/meta), or — when the folder has an `index` page — under `sidebar` in that page's frontmatter, and only that group changes:
|
|
75
|
+
|
|
76
|
+
```ts meta.ts
|
|
77
|
+
import { defineMeta } from "blume";
|
|
78
|
+
|
|
79
|
+
export default defineMeta({
|
|
80
|
+
title: "Client SDKs",
|
|
81
|
+
display: "page",
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```yaml index.mdx
|
|
86
|
+
---
|
|
87
|
+
title: Client SDKs
|
|
88
|
+
sidebar:
|
|
89
|
+
display: page
|
|
90
|
+
---
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A generated group's effective mode resolves highest priority first:
|
|
94
|
+
|
|
95
|
+
1. `sidebar.display` in the group's own `index` page frontmatter
|
|
96
|
+
2. `display` in the folder's `meta.ts`
|
|
97
|
+
3. The global `navigation.sidebar.display`
|
|
98
|
+
4. The Blume default (`flat`)
|
|
99
|
+
|
|
100
|
+
A group's `display` applies to that group only — nested subgroups resolve their own value through the same chain. A `page`-mode group with an index page still drills into its sub-panel: the index page lists as the panel's first item, and landing on its URL opens the panel directly.
|
|
101
|
+
|
|
102
|
+
`sidebar.display` means nothing anywhere else — on a non-index page, on the content root's own `index` page (the root isn't a group; use `navigation.sidebar.display`), or on any page when an [explicit sidebar](#explicit-sidebar) is configured (its items own each group's mode) — so Blume reports a `BLUME_SIDEBAR_DISPLAY_IGNORED` warning instead of silently dropping it. `collapsed` remains specific to `group` mode; it's inert when a group resolves to `flat` or `page`.
|
|
103
|
+
|
|
104
|
+
A group in an [explicit sidebar](#explicit-sidebar) overrides the global mode with its own `display`, exactly as before.
|
|
73
105
|
|
|
74
106
|
## Ordering
|
|
75
107
|
|
|
@@ -171,6 +203,8 @@ navigation: {
|
|
|
171
203
|
|
|
172
204
|
Each item takes a `label`, a `path`, and optional `icon`, `description`, and `tag`. `kind` (`dropdown`, `product`, `version`, or `language`) is a hint for how the selector is used; all render the same dropdown.
|
|
173
205
|
|
|
206
|
+
With [versioning](/docs/content/versioning) configured, Blume renders a version selector automatically — declaring your own `kind: "version"` selector here replaces the automatic one, so hand-rolled setups keep working.
|
|
207
|
+
|
|
174
208
|
## Featured links
|
|
175
209
|
|
|
176
210
|
Pin links to the top of the sidebar, above every section — a blog, a changelog, a contact or support page that should always be one click away. Unlike the generated tree, featured links are **not scoped by tab**: they show on every route, on every breakpoint.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Versioning
|
|
3
|
+
description: Freeze snapshots of your docs per release with a version switcher, canonical-to-latest SEO, version-scoped search, and a version-aware agent surface.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Blume versions your docs the way releases actually work: the latest documentation lives at your content root with clean, unprefixed URLs, and each past version is a frozen snapshot in its own folder. Cut a snapshot when you ship, and Blume wires up the switcher, the "old version" notice, search scoping, SEO, and the agent surface for you. It's opt-in: without a `versions` block, nothing changes.
|
|
7
|
+
|
|
8
|
+
## Enable it
|
|
9
|
+
|
|
10
|
+
Add a `versions` block naming the current docs and any archived snapshots:
|
|
11
|
+
|
|
12
|
+
```ts blume.config.ts lineNumbers
|
|
13
|
+
versions: {
|
|
14
|
+
current: { label: "v2.0", badge: "Latest" },
|
|
15
|
+
archived: [
|
|
16
|
+
{ id: "v1.0" },
|
|
17
|
+
{ id: "v0.9", label: "0.9 (legacy)" },
|
|
18
|
+
],
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`current` labels the unprefixed tree in the switcher (with an optional `badge`). Each archived entry's `id` is both the snapshot's directory name and its URL segment — ids must start with a letter (`v1.0`, not `1.0`) so they can never collide with [numeric ordering prefixes](/docs/content/navigation#ordering). List archived versions newest first; that order is the switcher order.
|
|
23
|
+
|
|
24
|
+
## Cut a version
|
|
25
|
+
|
|
26
|
+
When you release, freeze the current docs with one command:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
blume version v1.0
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This copies your content tree into `docs/v1.0/` (existing snapshots excluded), rewrites root-absolute links inside the copy so they stay within the snapshot (`/guides/x` becomes `/v1.0/guides/x`, fenced and inline code untouched), and registers the id in `blume.config.ts` — or prints the entry to paste when your config is shaped in a way it won't touch. Links to pages that aren't part of the copied tree — generated API references, remote sources like a changelog — keep pointing at the live pages, since the snapshot has no copy of them. Run `blume version` with no id to list the configured versions.
|
|
33
|
+
|
|
34
|
+
Review and commit the new directory like any other content. Restart `blume dev` to pick it up.
|
|
35
|
+
|
|
36
|
+
```txt
|
|
37
|
+
docs/
|
|
38
|
+
index.mdx -> / (latest)
|
|
39
|
+
guides/quickstart.mdx -> /guides/quickstart
|
|
40
|
+
v1.0/
|
|
41
|
+
index.mdx -> /v1.0 (frozen)
|
|
42
|
+
guides/quickstart.mdx -> /v1.0/guides/quickstart
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Archived means frozen.** Future edits belong in the live tree; a snapshot is the docs as they were. Blume leans on that: snapshots keep their own folder meta and translations, [`blume translate`](/docs/reference/translate) never retranslates them, and a configured explicit sidebar applies only to the current docs — a snapshot's sidebar always comes from its own files.
|
|
46
|
+
|
|
47
|
+
## The switcher and the notice
|
|
48
|
+
|
|
49
|
+
With versions configured, the header grows a version dropdown automatically. Switching lands on the same page in the target version when it exists, and on that version's root when it doesn't (set `switcher.redirect: "root"` to always land on the root). If you declare your own `kind: "version"` selector in [`navigation.selectors`](/docs/content/navigation#selectors), it replaces the automatic one.
|
|
50
|
+
|
|
51
|
+
Every archived page also shows a non-dismissible notice with a "Go to latest" link pointing at the page's live equivalent. Customize or disable it per version:
|
|
52
|
+
|
|
53
|
+
```ts blume.config.ts
|
|
54
|
+
archived: [
|
|
55
|
+
{ id: "v1.0", banner: "These docs cover the 1.x SDK." },
|
|
56
|
+
{ id: "v0.9", banner: false },
|
|
57
|
+
];
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## SEO
|
|
61
|
+
|
|
62
|
+
Old docs are search engines' favorite trap: the stale page outranks the live one, or both compete. Blume defaults to the answer SEO guides recommend and no other docs framework automates — archived pages stay indexable but declare the **latest equivalent as their canonical**, so the live page is authoritative while version-only content (a page that no longer exists in the latest docs) remains findable with a self-canonical.
|
|
63
|
+
|
|
64
|
+
Per version you can pick a different treatment:
|
|
65
|
+
|
|
66
|
+
```ts blume.config.ts
|
|
67
|
+
archived: [
|
|
68
|
+
{ id: "v1.0" }, // canonical → latest (default)
|
|
69
|
+
{ id: "v0.9", canonical: "self" }, // every page authoritative
|
|
70
|
+
{ id: "v0.8", noindex: true }, // deindexed entirely
|
|
71
|
+
];
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The sitemap follows suit: archived pages whose canonical points at a live equivalent are left out, `noindex` versions are left out wholesale, and version-only pages stay listed. A page's own `seo.canonical` frontmatter always wins.
|
|
75
|
+
|
|
76
|
+
## Search
|
|
77
|
+
|
|
78
|
+
The search dialog scopes results to the version being viewed, with an "All versions" toggle (remembered per reader) beside the language one. Cross-version hits name their version on the result row. Orama (the default), FlexSearch, Algolia, and Typesense all honor the scoping — hosted records carry a `version` facet, with the current docs uploaded as `"current"` — while Pagefind stays unscoped, matching its locale behavior.
|
|
79
|
+
|
|
80
|
+
## Agents
|
|
81
|
+
|
|
82
|
+
The agent surface is version-aware — something no other docs framework does:
|
|
83
|
+
|
|
84
|
+
- The MCP `search_docs` and `list_pages` tools default to the current docs and accept `version`: an archived id (`"v1.0"`) or `"all"`. `get_navigation` returns an archived snapshot's tree on request.
|
|
85
|
+
- `llms.txt` sections archived versions after the current docs, labeled `1.0 (archived)`, so an agent reading the index knows which docs are frozen.
|
|
86
|
+
- `llms-full.txt` stays current-only — the flat dump never interleaves frozen copies of the same page.
|
|
87
|
+
- Raw Markdown mirrors (`.md` URLs) exist for every version's pages, as for any route.
|
|
88
|
+
|
|
89
|
+
## With i18n
|
|
90
|
+
|
|
91
|
+
Versioning composes with [internationalization](/docs/content/i18n). On disk the version folder is outermost — a snapshot naturally contains its locale folders — while in URLs the locale stays outermost, matching the rest of the site:
|
|
92
|
+
|
|
93
|
+
```txt
|
|
94
|
+
docs/
|
|
95
|
+
guides/x.mdx -> /guides/x
|
|
96
|
+
fr/guides/x.mdx -> /fr/guides/x
|
|
97
|
+
v1.0/
|
|
98
|
+
guides/x.mdx -> /v1.0/guides/x
|
|
99
|
+
fr/guides/x.mdx -> /fr/v1.0/guides/x
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Locale fallback works within each version: an untranslated snapshot page renders the fallback locale's content at the localized URL, and `hreflang` alternates group per version. A version id may not collide with a configured locale code — Blume rejects that config outright.
|
|
103
|
+
|
|
104
|
+
## What stays unversioned
|
|
105
|
+
|
|
106
|
+
Versioning covers the docs content tree. The blog, changelog, API references generated from OpenAPI specs, and custom pages are always current. Two more behaviors worth knowing: header tabs are defined against the current docs, so inside an archived tree the sidebar renders unscoped by tabs; and large sites should note each snapshot is a full copy — content, search index entries, and navigation data all grow per version.
|
package/docs/reference/cli.mdx
CHANGED
|
@@ -24,6 +24,7 @@ blume <command> [options]
|
|
|
24
24
|
| `blume audit` | Audit the built site for SEO and health issues. |
|
|
25
25
|
| `blume eval` | Test the docs: an agent answers your questions using only the documentation. |
|
|
26
26
|
| `blume translate` | Translate docs into the configured locales with a local agent CLI. |
|
|
27
|
+
| `blume version [id]` | Freeze the current docs as an [archived version](/docs/content/versioning) (no id lists configured versions). |
|
|
27
28
|
|
|
28
29
|
## Common flags
|
|
29
30
|
|
|
@@ -46,8 +46,11 @@ sidebar:
|
|
|
46
46
|
icon: download
|
|
47
47
|
badge: New
|
|
48
48
|
hidden: false
|
|
49
|
+
display: page
|
|
49
50
|
```
|
|
50
51
|
|
|
52
|
+
`display` sets the render mode of the page's folder group ([per-group overrides](/docs/content/navigation#per-group-overrides)) and is only meaningful on a folder's `index` page under the generated sidebar — anywhere else (a non-index page, the content root's own `index` page, or any page under an explicit `navigation.sidebar`) it has no group to configure, and Blume warns with `BLUME_SIDEBAR_DISPLAY_IGNORED`.
|
|
53
|
+
|
|
51
54
|
## SEO
|
|
52
55
|
|
|
53
56
|
```yaml lineNumbers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blume",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Documentation that's fast, AI-ready, and zero-config.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"@astrojs/node": "^11.0.0",
|
|
73
73
|
"@astrojs/react": "^6.0.0",
|
|
74
74
|
"@astrojs/vercel": "^11.0.3",
|
|
75
|
+
"@asyncapi/converter": "^2.0.2",
|
|
75
76
|
"@clack/prompts": "^1.7.0",
|
|
76
77
|
"@iconify-json/lucide": "^1.2.115",
|
|
77
78
|
"@iconify/types": "^2.0.0",
|
|
@@ -134,6 +135,7 @@
|
|
|
134
135
|
"sharp": "^0.35.3",
|
|
135
136
|
"shiki": "^4.2.0",
|
|
136
137
|
"simple-icons": "^13.0.0",
|
|
138
|
+
"string-width": "^8.1.0",
|
|
137
139
|
"tailwindcss": "^4.3.3",
|
|
138
140
|
"takumi-js": "^2.2.1",
|
|
139
141
|
"tinyglobby": "^0.2.10",
|
|
@@ -44,8 +44,8 @@ The single biggest shift for most sources — especially Mintlify — is that **
|
|
|
44
44
|
|
|
45
45
|
- **Folders become groups, files become pages.** A page's sidebar label is its frontmatter `title`; a group's label is the humanized folder name.
|
|
46
46
|
- **Ordering resolves highest-priority-first:** an explicit `navigation.sidebar` (replaces the whole tree) → a folder's `meta.ts` `pages` array → a page's frontmatter `sidebar.order` → the filesystem (`index` first, then numeric filename prefix like `01-`, then alphabetical).
|
|
47
|
-
- **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order.
|
|
48
|
-
- **Sidebar render mode
|
|
47
|
+
- **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages, display })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order.
|
|
48
|
+
- **Sidebar render mode: a global default with per-folder overrides.** `navigation.sidebar.display` in `blume.config.ts` is `"flat"` (default), `"group"` (collapsible), or `"page"` (drill-in sub-panel) and sets the mode for every group at once. A folder can override its **own** group: `display` in its `meta.ts`, or — sugar when the folder has an `index` page — `sidebar.display` in that index page's frontmatter. Precedence: index frontmatter → `meta.ts` → global config → `flat`; an override applies to that one group only (nested subgroups resolve their own chain). So a source's per-category collapse/drill-in modes migrate per folder — only reach for the global mode when the whole sidebar changes. Under an explicit `navigation.sidebar`, the config item's own `display` field is the only per-group control (frontmatter/meta `display` is ignored there, with a `BLUME_SIDEBAR_DISPLAY_IGNORED` warning).
|
|
49
49
|
- **An explicit `navigation.sidebar` replaces filesystem generation entirely.** Use it only for a nav shape files can't express. Its items are a page route string, a group (`{ label, items }`), or a link (`{ label, href }`).
|
|
50
50
|
- **Config-declared nesting has no on-disk counterpart — materialize it or it flattens silently.** When a source (Mintlify `groups`, Nextra `_meta`, a Docusaurus sidebar…) declares a nested group, its pages usually sit **flat in one folder** and the grouping lives only in config. Filesystem-derived nav sees the flat folder and drops the inner group. To keep the nesting you must **either** move those pages into a real subfolder (`meta.ts` for label/`collapsed`) — which changes their URLs, so add `redirects` — **or** declare the group in an explicit `navigation.sidebar`, which nests the existing routes without moving a file. Walk config `pages`/nav arrays **recursively** during inventory and record where config nesting depth exceeds on-disk depth; that gap is exactly what gets lost.
|
|
51
51
|
|
|
@@ -41,7 +41,7 @@ Every Docusaurus repo serves **`static/`** at the site root (`static/img/foo.png
|
|
|
41
41
|
## Navigation: `sidebars.js` + `_category_.json`
|
|
42
42
|
|
|
43
43
|
- **Autogenerated sidebar** (`{ type: 'autogenerated', dirName: '...' }`) → Blume's default filesystem navigation. Docusaurus strips numeric prefixes (`01-`) exactly like Blume, so the convention round-trips; no config needed.
|
|
44
|
-
- **Explicit sidebar** (arrays of doc IDs, categories, links) → restructure into folders where possible; use `navigation.sidebar` only for shapes files can't express. A category `{ type: 'category', label, items }` → a folder (label → `meta.ts` `title`); `collapsed` → `meta.ts` `collapsed
|
|
44
|
+
- **Explicit sidebar** (arrays of doc IDs, categories, links) → restructure into folders where possible; use `navigation.sidebar` only for shapes files can't express. A category `{ type: 'category', label, items }` → a folder (label → `meta.ts` `title`); `collapsed` → `meta.ts` `collapsed`, and collapsible rendering → that folder's `meta.ts` `display: "group"` (or `navigation.sidebar.display: "group"` once in config when every category collapses).
|
|
45
45
|
- **`_category_.json`** (also `.yml`) (`{ label, position, collapsed, collapsible, link, className, customProps }`) → a folder `meta.ts`: `label`→`title`, `position`→`order`, `collapsed`→`collapsed`. `link.type: 'generated-index'` → an `index` page in the folder — **and the old URL was `/docs/category/<slug>`**, so add a redirect and rewrite inbound links. `link.type: 'doc'` → make that doc the folder's `index`. `collapsible`/`className`/`customProps` → drop (report).
|
|
46
46
|
- **`src/pages/` — inventory, don't nuke.** Nearly every repo has a React landing page (`src/pages/index.tsx`) and often extra Markdown pages. Markdown pages → content pages; the React home page → rebuild as a docs index or a custom `.astro` page under `content.pages` — report either way.
|
|
47
47
|
|
|
@@ -39,7 +39,7 @@ Everything else is `defineConfig({ title })`.
|
|
|
39
39
|
| `description` | **drop** (folders have no description) |
|
|
40
40
|
| `collapsible: false` | **drop** (report — no per-folder equivalent) |
|
|
41
41
|
|
|
42
|
-
`meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages`
|
|
42
|
+
`meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages`, `display`. Render mode is per-folder or global: a folder that needs collapsible rendering sets its own `meta.ts` `display: "group"` (drill-in is `"page"`); when the whole sidebar should collapse, set `navigation.sidebar.display: "group"` once in `blume.config.ts` instead of repeating it per folder.
|
|
43
43
|
|
|
44
44
|
**`root: true` folders are Fumadocs' tab mechanism** — Fumadocs UI renders them as layout tabs and scopes the sidebar to the active one. That is exactly Blume's `navigation.tabs`: add `{ label, path, icon? }` per root folder (label/icon from its `meta.json`), pointing at the folder's route. Blume then scopes the sidebar by URL prefix the same way. Don't try to model root folders inside `meta.ts`.
|
|
45
45
|
|
|
@@ -83,7 +83,7 @@ The codemod touches **only frontmatter**. Icons in MDX **body** (`<Icon icon="
|
|
|
83
83
|
|
|
84
84
|
Mintlify's `navigation` object (`tabs`/`anchors`/`dropdowns`/`products`/`versions`/`languages`/`groups`/`pages`) is fully config-declared. **Prefer restructuring content into folders**, not porting the config verbatim:
|
|
85
85
|
|
|
86
|
-
- **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page —
|
|
86
|
+
- **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages, display })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page — defaults to the global `navigation.sidebar.display` in `blume.config.ts`; a folder overrides its own group with `meta.ts` `display`, or `sidebar.display` in its `index` page's frontmatter.)
|
|
87
87
|
- **Config-only nested groups don't exist on disk — you must materialize them, or they flatten silently.** A nested `{ group, pages }` almost never has a matching subfolder: its pages sit **flat in the parent directory** (e.g. `platform/analytics/getting-started.mdx`, `…/quick-reference.mdx`) and the grouping lives **only** in the `docs.json` `pages` array. If you leave the files where they are, filesystem-derived nav sees one flat folder and the inner group vanishes — Mintlify's `Analytics → Reference → {…}` becomes a flat `Analytics → {…}`. To preserve it you must **either** move those pages into a real subfolder (`platform/analytics/reference/`, with a `meta.ts` for the label/`collapsed`), **or** declare the shape in an explicit `navigation.sidebar`. Walk **every** `pages` array recursively and treat any nested `group` object as a folder-move to plan, not files already in place. When inventorying the nav (workflow step 2), record the config nesting depth separately from the on-disk depth — they diverge exactly here.
|
|
88
88
|
- **This is the canonical case for `navigation.sidebar` over folders.** Materializing a purely-presentational nested group as a subfolder changes URLs (`/platform/analytics/getting-started` → `/platform/analytics/reference/getting-started`) for no reason other than a visual grouping, forcing a `redirects` entry per page. When you want to keep the nesting **and** the URLs, an explicit `navigation.sidebar` group (`{ label, items }`) is the better trade — it nests the existing routes without moving any file. Pick per group; don't reflexively flatten.
|
|
89
89
|
- **`pages`** entries are page refs (paths without extension) → files at the corresponding path. An entry that's `"GET /path"` is an OpenAPI endpoint stub → **delete it** (Blume generates these; see OpenAPI).
|
|
@@ -47,12 +47,41 @@ const askApiUrl = (
|
|
|
47
47
|
: endpoint;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
/** The `.well-known` discovery URLs a site can publish. */
|
|
51
|
+
interface WellKnownArtifacts {
|
|
52
|
+
httpMessageSignaturesDirectory?: string;
|
|
53
|
+
apiCatalog?: string;
|
|
54
|
+
agentSkills?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The agent-facing artifact index the manifest publishes. */
|
|
58
|
+
interface AgentArtifacts extends WellKnownArtifacts {
|
|
59
|
+
markdown: { contentNegotiation?: string; pattern: string };
|
|
60
|
+
llmsFullTxt?: string;
|
|
61
|
+
llmsTxt?: string;
|
|
62
|
+
mcp?: { discovery: string; url: string };
|
|
63
|
+
askApi?: string;
|
|
64
|
+
sitemap?: string;
|
|
65
|
+
feeds?: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The published `agent-readability.json` document. */
|
|
69
|
+
export interface AgentReadabilityManifest {
|
|
70
|
+
artifacts: AgentArtifacts;
|
|
71
|
+
description?: string;
|
|
72
|
+
generator?: string;
|
|
73
|
+
name: string;
|
|
74
|
+
site: string | null;
|
|
75
|
+
contentUsage?: Record<string, boolean>;
|
|
76
|
+
repository?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
/** The `.well-known` discovery artifacts the site publishes, if any. */
|
|
51
80
|
const wellKnownArtifacts = (
|
|
52
81
|
config: BlumeProject["config"],
|
|
53
82
|
abs: (path: string) => string
|
|
54
|
-
):
|
|
55
|
-
const artifacts:
|
|
83
|
+
): WellKnownArtifacts => {
|
|
84
|
+
const artifacts: WellKnownArtifacts = {};
|
|
56
85
|
if (config.ai.webBotAuth.keys.length > 0) {
|
|
57
86
|
artifacts.httpMessageSignaturesDirectory = abs(
|
|
58
87
|
"/.well-known/http-message-signatures-directory"
|
|
@@ -77,7 +106,7 @@ const wellKnownArtifacts = (
|
|
|
77
106
|
*/
|
|
78
107
|
export const buildAgentReadability = (
|
|
79
108
|
project: BlumeProject
|
|
80
|
-
):
|
|
109
|
+
): AgentReadabilityManifest | null => {
|
|
81
110
|
const { config } = project;
|
|
82
111
|
if (!config.seo.agentReadability) {
|
|
83
112
|
return null;
|
|
@@ -103,12 +132,10 @@ export const buildAgentReadability = (
|
|
|
103
132
|
config.deployment.output === "server" &&
|
|
104
133
|
(config.deployment.adapter === "vercel" ||
|
|
105
134
|
config.deployment.adapter === "cloudflare");
|
|
106
|
-
const
|
|
107
|
-
markdown: {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
},
|
|
111
|
-
};
|
|
135
|
+
const markdown: AgentArtifacts["markdown"] = negotiates
|
|
136
|
+
? { contentNegotiation: "text/markdown", pattern: abs("/{route}.md") }
|
|
137
|
+
: { pattern: abs("/{route}.md") };
|
|
138
|
+
const artifacts: AgentArtifacts = { markdown };
|
|
112
139
|
if (config.ai.llmsTxt.enabled) {
|
|
113
140
|
artifacts.llmsFullTxt = abs("/llms-full.txt");
|
|
114
141
|
artifacts.llmsTxt = abs("/llms.txt");
|
|
@@ -135,7 +162,7 @@ export const buildAgentReadability = (
|
|
|
135
162
|
}
|
|
136
163
|
|
|
137
164
|
const version = project.manifest?.blumeVersion;
|
|
138
|
-
const manifest:
|
|
165
|
+
const manifest: AgentReadabilityManifest = {
|
|
139
166
|
artifacts,
|
|
140
167
|
description: config.description,
|
|
141
168
|
generator: version ? `blume@${version}` : undefined,
|
package/src/ai/ask-context.ts
CHANGED
|
@@ -102,9 +102,13 @@ const TERM = /[\p{L}\p{M}\p{N}]+/gu;
|
|
|
102
102
|
* window silently degrades to the head of the page. The regex fallback covers
|
|
103
103
|
* runtimes without the segmenter and still handles spaced scripts correctly.
|
|
104
104
|
*/
|
|
105
|
+
const hasSegmenter = (
|
|
106
|
+
segmenter: typeof Intl.Segmenter | undefined
|
|
107
|
+
): segmenter is typeof Intl.Segmenter => typeof segmenter === "function";
|
|
108
|
+
|
|
105
109
|
const segmentQuery = (query: string): string[] => {
|
|
106
110
|
const lowered = query.normalize("NFC").toLowerCase();
|
|
107
|
-
if (
|
|
111
|
+
if (!hasSegmenter(Intl.Segmenter)) {
|
|
108
112
|
return lowered.match(TERM) ?? [];
|
|
109
113
|
}
|
|
110
114
|
const pieces: string[] = [];
|
package/src/ai/ask.ts
CHANGED
|
@@ -35,7 +35,16 @@ const OPENAI_COMPATIBLE = "openai-compatible";
|
|
|
35
35
|
/** The AI SDK provider package the OpenAI-compatible providers install. */
|
|
36
36
|
const OPENAI_COMPATIBLE_DEP = "@ai-sdk/openai-compatible";
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Connection presets keyed by provider name. Open-keyed on purpose: lookups
|
|
40
|
+
* use the configured `ai.ask.provider`, which includes values with no preset
|
|
41
|
+
* (the generic `openai-compatible`).
|
|
42
|
+
*/
|
|
43
|
+
interface AskPresetRegistry {
|
|
44
|
+
[provider: string]: AskPreset;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ASK_PRESETS: AskPresetRegistry = {
|
|
39
48
|
inkeep: {
|
|
40
49
|
apiKeyEnv: "INKEEP_API_KEY",
|
|
41
50
|
baseUrl: "https://api.inkeep.com/v1",
|