@veluai/velu 0.1.15 → 0.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.
@@ -40,6 +40,7 @@
40
40
  "type": "string",
41
41
  "description": "Path to the favicon, relative to the project root."
42
42
  },
43
+ "api": { "$ref": "#/$defs/api" },
43
44
  "logo": {
44
45
  "description": "Site logo shown in the header, replacing the name wordmark. A single path used in both themes, or per-theme light/dark images with an optional click-through href. Paths are relative to the project root.",
45
46
  "oneOf": [
@@ -169,6 +170,78 @@
169
170
  "navigation": {
170
171
  "$ref": "#/$defs/navContainer",
171
172
  "description": "Site navigation. Mintlify-compatible: products > versions > languages > tabs > groups > pages. Switchable axes (product/version/language) become URL path prefixes; the default value of each is unprefixed. Dropdowns are intentionally unsupported."
173
+ },
174
+ "url": {
175
+ "type": "string",
176
+ "description": "Production site origin (e.g. \"https://docs.example.com\"), no trailing slash. Used by the static build for canonical URLs, og:url, and sitemap.xml, and by llms.txt for absolute links."
177
+ },
178
+ "thumbnails": {
179
+ "type": "object",
180
+ "additionalProperties": false,
181
+ "description": "Auto-generated OG (social share) image options. Build-only.",
182
+ "properties": {
183
+ "background": {
184
+ "type": "string",
185
+ "description": "Project-relative path to a background image for the OG cards (e.g. \"/images/og-background.png\"). Defaults to a brand-color glow."
186
+ }
187
+ }
188
+ },
189
+ "seo": {
190
+ "type": "object",
191
+ "additionalProperties": false,
192
+ "description": "Search-engine optimization. Build-only: meta tags and sitemap behaviour applied when the site is built (not in the dev preview).",
193
+ "properties": {
194
+ "metatags": {
195
+ "type": "object",
196
+ "description": "Extra meta tags injected on every page, as a flat key→value map (e.g. \"google-site-verification\", \"og:image\").",
197
+ "additionalProperties": { "type": "string" }
198
+ },
199
+ "indexing": {
200
+ "type": "string",
201
+ "enum": ["navigable", "all"],
202
+ "description": "\"navigable\" (default) indexes only pages in navigation; \"all\" also includes hidden pages."
203
+ }
204
+ }
205
+ },
206
+ "contextual": {
207
+ "type": "object",
208
+ "additionalProperties": false,
209
+ "description": "Per-page context menu — the \"Copy Page\" split-button + dropdown of agent/IDE actions shown at the top of each page. Mintlify-compatible. Unset → copy, view, chatgpt, claude.",
210
+ "required": ["options"],
211
+ "properties": {
212
+ "options": {
213
+ "type": "array",
214
+ "description": "Menu items, in order. Known string options, or custom { title, description, href } entries.",
215
+ "items": {
216
+ "oneOf": [
217
+ {
218
+ "type": "string",
219
+ "enum": [
220
+ "copy", "assistant", "view", "download-pdf", "download-spec",
221
+ "chatgpt", "claude", "perplexity", "grok", "aistudio", "devin", "windsurf",
222
+ "mcp", "add-mcp", "cursor", "vscode", "devin-mcp"
223
+ ]
224
+ },
225
+ {
226
+ "type": "object",
227
+ "additionalProperties": false,
228
+ "required": ["title", "href"],
229
+ "properties": {
230
+ "title": { "type": "string", "description": "Menu item label." },
231
+ "description": { "type": "string", "description": "Secondary line under the label." },
232
+ "href": { "type": "string", "description": "URL to open." },
233
+ "icon": { "type": "string", "description": "Lucide icon id." }
234
+ }
235
+ }
236
+ ]
237
+ }
238
+ },
239
+ "display": {
240
+ "type": "string",
241
+ "enum": ["header", "toc"],
242
+ "description": "Where the menu renders. Default \"header\"."
243
+ }
244
+ }
172
245
  }
173
246
  },
174
247
  "$defs": {
@@ -205,6 +278,8 @@
205
278
  "icon": { "type": "string", "description": "lucide icon id." },
206
279
  "expanded": { "type": "boolean", "description": "Start expanded." },
207
280
  "root": { "type": "string", "description": "Landing page path for the group." },
281
+ "openapi": { "$ref": "#/$defs/openapiRef" },
282
+ "api": { "$ref": "#/$defs/api" },
208
283
  "pages": { "$ref": "#/$defs/pages" }
209
284
  }
210
285
  },
@@ -216,11 +291,57 @@
216
291
  "tab": { "type": "string", "description": "Tab label (top nav)." },
217
292
  "icon": { "type": "string" },
218
293
  "href": { "type": "string", "description": "External/override link instead of in-site pages." },
294
+ "openapi": { "$ref": "#/$defs/openapiRef" },
295
+ "api": { "$ref": "#/$defs/api" },
219
296
  "anchors": { "type": "array", "items": { "$ref": "#/$defs/anchor" } },
220
297
  "groups": { "type": "array", "items": { "$ref": "#/$defs/group" } },
221
298
  "pages": { "$ref": "#/$defs/pages" }
222
299
  }
223
300
  },
301
+ "api": {
302
+ "type": "object",
303
+ "additionalProperties": false,
304
+ "description": "API reference behaviour for OpenAPI-generated pages. Set at the root, or on a tab/group with `openapi` to override it for just that section.",
305
+ "properties": {
306
+ "server": {
307
+ "type": "string",
308
+ "description": "Base URL override for the playground + code samples (otherwise the spec's first server is used)."
309
+ },
310
+ "playground": {
311
+ "type": "object",
312
+ "additionalProperties": false,
313
+ "properties": {
314
+ "display": {
315
+ "type": "string",
316
+ "enum": ["interactive", "simple", "none"],
317
+ "description": "Playground mode. \"none\" disables the proxy / live send."
318
+ },
319
+ "proxy": {
320
+ "type": "boolean",
321
+ "description": "Send Try-It requests through the dev proxy to avoid CORS (default true)."
322
+ }
323
+ }
324
+ },
325
+ "examples": {
326
+ "type": "object",
327
+ "additionalProperties": false,
328
+ "properties": {
329
+ "languages": {
330
+ "type": "array",
331
+ "items": { "type": "string", "enum": ["curl", "javascript", "python", "ruby"] },
332
+ "description": "Which request-snippet languages to generate."
333
+ }
334
+ }
335
+ }
336
+ }
337
+ },
338
+ "openapiRef": {
339
+ "description": "Path or URL to an OpenAPI spec (JSON/YAML), or an array of them. Auto-generates a page per operation, grouped by tag.",
340
+ "oneOf": [
341
+ { "type": "string" },
342
+ { "type": "array", "items": { "type": "string" } }
343
+ ]
344
+ },
224
345
  "anchor": {
225
346
  "type": "object",
226
347
  "additionalProperties": false,
package/src/navigation.js CHANGED
@@ -186,9 +186,13 @@ function normalizeGroup(g) {
186
186
  };
187
187
  }
188
188
 
189
- /** A `pages[]` entry is either a string (page path) or a nested group. */
189
+ /** A `pages[]` entry is a string (page path), a generated OpenAPI page
190
+ * ({ apiPage, label, method }), or a nested group. */
190
191
  function normalizePageEntry(entry) {
191
192
  if (typeof entry === 'string') return { kind: 'page', pagePath: entry };
193
+ if (entry && entry.apiPage) {
194
+ return { kind: 'page', pagePath: entry.apiPage, label: entry.label, method: entry.method, api: true };
195
+ }
192
196
  return normalizeGroup(entry);
193
197
  }
194
198
 
@@ -430,5 +434,10 @@ function itemsFor(nodes, pagesMap, ctx) {
430
434
  function itemFor(pageNode, pagesMap, ctx) {
431
435
  const href = urlInCtx(pageNode.pagePath, ctx);
432
436
  const fm = pagesMap[href]?.frontmatter;
433
- return { label: fm?.title ?? titleCase(lastSeg(href)), href };
437
+ // Generated API pages carry their own label + HTTP method (for the sidebar
438
+ // method badge); normal pages source their label from frontmatter.
439
+ const label = pageNode.label ?? fm?.title ?? titleCase(lastSeg(href));
440
+ const item = { label, href };
441
+ if (pageNode.method) item.method = pageNode.method;
442
+ return item;
434
443
  }
@@ -14,6 +14,7 @@ import {
14
14
  NavSelect,
15
15
  Toc,
16
16
  TocBar,
17
+ ContextMenu,
17
18
  Callout,
18
19
  Accordion,
19
20
  AccordionGroup,
@@ -47,6 +48,8 @@ import {
47
48
  ApiClient,
48
49
  ApiField,
49
50
  ApiSidebar,
51
+ ApiSamples,
52
+ NotFound,
50
53
  VeluMark,
51
54
  } from 'velu-ui';
52
55
  import { X, ChevronDown, ChevronUp } from 'lucide-react';
@@ -726,6 +729,9 @@ function DocsPage() {
726
729
  const frontmatter = entry?.frontmatter ?? {};
727
730
  const PageComponent = entry?.Component ?? null;
728
731
  const pageToc = entry?.toc ?? [];
732
+ // No renderable page for this route → show the 404 page (a clean centered
733
+ // takeover: header + footer stay, the docs sidebar/TOC are hidden).
734
+ const isNotFound = !PageComponent;
729
735
 
730
736
  // Switcher option sets (only render a switcher when an axis has >1
731
737
  // option). Anchors are pinned sidebar links shown in the context zone.
@@ -762,6 +768,27 @@ function DocsPage() {
762
768
  />
763
769
  );
764
770
 
771
+ // Keep document.title / meta description / <html lang> in sync on client-side
772
+ // navigation. The HTML is SSR'd with a per-page (build) or generic (dev) title;
773
+ // without this the tab title would stay frozen on the first page as you
774
+ // navigate the SPA, and JS-executing crawlers would read the stale value.
775
+ React.useEffect(() => {
776
+ const t = frontmatter.title;
777
+ document.title = isNotFound
778
+ ? `Page not found - ${site.name}`
779
+ : t && t !== site.name
780
+ ? `${t} - ${site.name}`
781
+ : site.name;
782
+ let descTag = document.querySelector('meta[name="description"]');
783
+ if (!descTag) {
784
+ descTag = document.createElement('meta');
785
+ descTag.setAttribute('name', 'description');
786
+ document.head.appendChild(descTag);
787
+ }
788
+ descTag.setAttribute('content', frontmatter.description || '');
789
+ document.documentElement.lang = nav?.activeLanguageCode || 'en';
790
+ }, [pathname, isNotFound, frontmatter.title, frontmatter.description, nav?.activeLanguageCode]);
791
+
765
792
  // Frontmatter title needs an id so scroll-spy + click-to-scroll work
766
793
  // against it like any other heading.
767
794
  const pageId = React.useMemo(() => {
@@ -1057,6 +1084,8 @@ function DocsPage() {
1057
1084
  data-chat-open={chatOpen ? 'true' : 'false'}
1058
1085
  data-sidebar-open={sidebarOpen ? 'true' : 'false'}
1059
1086
  data-drawer-open={drawerOpen ? 'true' : 'false'}
1087
+ data-api={entry?.api ? 'true' : 'false'}
1088
+ data-not-found={isNotFound ? 'true' : undefined}
1060
1089
  >
1061
1090
  {/* Scrim — visible at mobile while the drawer OR the chatbot
1062
1091
  sheet is open. Sits between the article (z-0) and the
@@ -1263,11 +1292,27 @@ function DocsPage() {
1263
1292
  scrollPaddingBlockEnd: 'var(--s1)',
1264
1293
  }}
1265
1294
  >
1266
- <Sidebar
1267
- sections={nav?.sidebarSections ?? []}
1268
- activeHref={pathname}
1269
- linkComponent={RouterLink}
1270
- />
1295
+ {entry?.api ? (
1296
+ <ApiSidebar
1297
+ sections={(nav?.sidebarSections ?? []).map((s) => ({
1298
+ title: s.title,
1299
+ icon: s.icon,
1300
+ endpoints: (s.items ?? []).map((it) => ({
1301
+ method: it.method,
1302
+ label: it.label,
1303
+ href: it.href,
1304
+ })),
1305
+ }))}
1306
+ activeHref={pathname}
1307
+ linkComponent={RouterLink}
1308
+ />
1309
+ ) : (
1310
+ <Sidebar
1311
+ sections={nav?.sidebarSections ?? []}
1312
+ activeHref={pathname}
1313
+ linkComponent={RouterLink}
1314
+ />
1315
+ )}
1271
1316
  </div>
1272
1317
  <button
1273
1318
  type="button"
@@ -1317,7 +1362,16 @@ function DocsPage() {
1317
1362
  scrollPaddingBlockEnd: `calc(${footerOverlap}px + 2rem)`,
1318
1363
  }}
1319
1364
  >
1320
- <Toc items={toc} activeId={activeId} onSelect={scrollTo} />
1365
+ {/* API reference pages put their code samples in the right rail
1366
+ (where the TOC sits for normal pages). */}
1367
+ {entry?.api && entry.operation ? (
1368
+ <ApiSamples
1369
+ samples={entry.samples}
1370
+ responses={entry.operation.responses}
1371
+ />
1372
+ ) : (
1373
+ <Toc items={toc} activeId={activeId} onSelect={scrollTo} />
1374
+ )}
1321
1375
  </aside>
1322
1376
  )}
1323
1377
 
@@ -1326,6 +1380,21 @@ function DocsPage() {
1326
1380
  and the right margin collapses to 0 while the chatbot is
1327
1381
  open via [data-chat-open="true"]). */}
1328
1382
  <div className="velu-docs-layout__center">
1383
+ {isNotFound ? (
1384
+ <main className="velu-404-main">
1385
+ <NotFound
1386
+ homeHref="/"
1387
+ linkComponent={RouterLink}
1388
+ onSearch={() =>
1389
+ window.dispatchEvent(
1390
+ new KeyboardEvent('keydown', { key: 'k', ctrlKey: true, metaKey: true }),
1391
+ )
1392
+ }
1393
+ onAskAI={IS_DEV_PREVIEW ? undefined : () => askAI('')}
1394
+ />
1395
+ </main>
1396
+ ) : (
1397
+ <>
1329
1398
  {/* Narrow-layout TOC bar — always in DOM, only visible at
1330
1399
  < 1024px (toggled by @container in toc-bar.css). Shares
1331
1400
  its `items` + `activeId` + `onSelect` API with the
@@ -1369,6 +1438,24 @@ function DocsPage() {
1369
1438
  </span>
1370
1439
  </button>
1371
1440
  <div className="velu-docs-layout__article">
1441
+ {/* Per-page agent/IDE action bar: the section eyebrow + a
1442
+ "Copy Page" split-button whose dropdown is driven by the
1443
+ Mintlify-compatible `contextual` config. Renders nothing
1444
+ when there's neither an eyebrow nor any enabled options. */}
1445
+ <ContextMenu
1446
+ eyebrow={
1447
+ nav?.breadcrumb && nav.breadcrumb.length > 1
1448
+ ? nav.breadcrumb[nav.breadcrumb.length - 2].label
1449
+ : undefined
1450
+ }
1451
+ pageUrl={pathname}
1452
+ title={frontmatter.title}
1453
+ isApi={entry?.api === true}
1454
+ siteName={site.name}
1455
+ options={site.contextual?.options ?? []}
1456
+ onAssistant={IS_DEV_PREVIEW ? undefined : () => askAI('')}
1457
+ />
1458
+
1372
1459
  {/* Page hero from MDX frontmatter. `.velu-hero` rules
1373
1460
  (in base.css) keep the title and description tightly
1374
1461
  grouped and create a clear break before the prose body. */}
@@ -1467,6 +1554,8 @@ function DocsPage() {
1467
1554
  )}
1468
1555
  </div>
1469
1556
  </main>
1557
+ </>
1558
+ )}
1470
1559
  </div>
1471
1560
 
1472
1561
  {/* Full site footer — only when the config provides link columns. Spans
@@ -1,28 +1,43 @@
1
1
  ---
2
2
  title: Introduction
3
- description: How to read this API reference.
3
+ description: How this API reference is generated.
4
4
  ---
5
5
 
6
- This section documents the example API. Endpoints are grouped in the
7
- sidebar; each page shows the request, parameters, and a sample
8
- response.
6
+ The endpoints in this tab are generated automatically from an OpenAPI
7
+ spec (`openapi.json`). Each operation becomes its own page with
8
+ parameters, request body, and responses — and an interactive **Try It**
9
+ playground you can use to send real requests.
9
10
 
10
11
  <Callout type="note">
11
12
  This is a second tab ("API Reference"). Tabs let you keep guides and
12
- reference docs in separate top-level sections of the same site.
13
+ reference docs in separate top-level sections of the same site. Point
14
+ the tab's `openapi` field at your own spec to replace this example.
13
15
  </Callout>
14
16
 
15
- ## Base URL
17
+ ## How it works
16
18
 
17
- ```
18
- https://api.example.com/v1
19
+ Add an `openapi` field to a tab (or group) in `velu.json`:
20
+
21
+ ```json
22
+ {
23
+ "tab": "API Reference",
24
+ "openapi": "/openapi.json"
25
+ }
19
26
  ```
20
27
 
21
- ## Authentication
28
+ Velu reads the spec, groups the operations by their tag, and renders a
29
+ page per endpoint. No hand-written endpoint pages required.
22
30
 
23
- Send your key as a bearer token:
31
+ ## The example API
24
32
 
25
- ```bash
26
- curl https://api.example.com/v1/items \
27
- -H "Authorization: Bearer YOUR_TOKEN"
28
- ```
33
+ This starter points at a live demo API — a small **product catalog**
34
+ backed by [dummyjson.com](https://dummyjson.com). Open any endpoint in
35
+ the sidebar, hit **Try It**, and send a real request:
36
+
37
+ - **Get a product** — try `id` = `1`
38
+ - **Search products** — try `q` = `phone`
39
+ - **Add a product** — the body is pre-filled; Send returns a created product
40
+
41
+ Requests are routed through the dev server's proxy so they aren't blocked
42
+ by CORS. Set `api.playground.proxy` to `false` to send straight from the
43
+ browser.
@@ -0,0 +1,160 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": {
4
+ "title": "Product Catalog API",
5
+ "version": "1.0.0",
6
+ "description": "A friendly, fully-hosted example API — a product catalog backed by the live https://dummyjson.com service. Every operation hits the real API: list, search, fetch, create, update and delete products and see real responses."
7
+ },
8
+ "servers": [{ "url": "https://dummyjson.com" }],
9
+ "tags": [{ "name": "Products", "description": "Browse and manage the product catalog." }],
10
+ "paths": {
11
+ "/products": {
12
+ "get": {
13
+ "operationId": "listProducts",
14
+ "summary": "List products",
15
+ "description": "List products, with optional pagination.",
16
+ "tags": ["Products"],
17
+ "parameters": [
18
+ { "name": "limit", "in": "query", "schema": { "type": "integer" }, "description": "How many products to return (0 returns all)." },
19
+ { "name": "skip", "in": "query", "schema": { "type": "integer" }, "description": "How many products to skip (for pagination)." }
20
+ ],
21
+ "responses": {
22
+ "200": {
23
+ "description": "A page of products.",
24
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductList" } } }
25
+ }
26
+ }
27
+ }
28
+ },
29
+ "/products/search": {
30
+ "get": {
31
+ "operationId": "searchProducts",
32
+ "summary": "Search products",
33
+ "description": "Search the catalog by a free-text query.",
34
+ "tags": ["Products"],
35
+ "parameters": [
36
+ { "name": "q", "in": "query", "required": true, "schema": { "type": "string" }, "description": "The search query (try `phone`)." }
37
+ ],
38
+ "responses": {
39
+ "200": {
40
+ "description": "Matching products.",
41
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductList" } } }
42
+ }
43
+ }
44
+ }
45
+ },
46
+ "/products/{id}": {
47
+ "get": {
48
+ "operationId": "getProduct",
49
+ "summary": "Get a product",
50
+ "description": "Retrieve a single product by its id.",
51
+ "tags": ["Products"],
52
+ "parameters": [
53
+ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The product id (try `1`)." }
54
+ ],
55
+ "responses": {
56
+ "200": {
57
+ "description": "The product.",
58
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
59
+ }
60
+ }
61
+ },
62
+ "put": {
63
+ "operationId": "updateProduct",
64
+ "summary": "Update a product",
65
+ "description": "Update fields on an existing product. (Simulated by the upstream API — the response reflects the change but nothing is persisted.)",
66
+ "tags": ["Products"],
67
+ "parameters": [
68
+ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The product id." }
69
+ ],
70
+ "requestBody": {
71
+ "required": true,
72
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductInput" } } }
73
+ },
74
+ "responses": {
75
+ "200": {
76
+ "description": "The updated product.",
77
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
78
+ }
79
+ }
80
+ },
81
+ "delete": {
82
+ "operationId": "deleteProduct",
83
+ "summary": "Delete a product",
84
+ "description": "Delete a product by its id. (Simulated — the response includes `isDeleted` and `deletedOn`.)",
85
+ "tags": ["Products"],
86
+ "parameters": [
87
+ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The product id." }
88
+ ],
89
+ "responses": {
90
+ "200": {
91
+ "description": "The deleted product.",
92
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
93
+ }
94
+ }
95
+ }
96
+ },
97
+ "/products/add": {
98
+ "post": {
99
+ "operationId": "addProduct",
100
+ "summary": "Add a product",
101
+ "description": "Create a new product. (Simulated — the response echoes the body with a fresh `id`.)",
102
+ "tags": ["Products"],
103
+ "requestBody": {
104
+ "required": true,
105
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductInput" } } }
106
+ },
107
+ "responses": {
108
+ "201": {
109
+ "description": "The created product.",
110
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ },
116
+ "components": {
117
+ "schemas": {
118
+ "Product": {
119
+ "type": "object",
120
+ "properties": {
121
+ "id": { "type": "integer", "description": "Unique identifier." },
122
+ "title": { "type": "string", "description": "Product name." },
123
+ "description": { "type": "string", "description": "Product description." },
124
+ "category": { "type": "string", "description": "Category slug." },
125
+ "price": { "type": "number", "description": "Price in USD." },
126
+ "discountPercentage": { "type": "number", "description": "Discount, as a percentage." },
127
+ "rating": { "type": "number", "description": "Average customer rating (0–5)." },
128
+ "stock": { "type": "integer", "description": "Units in stock." },
129
+ "brand": { "type": "string", "description": "Brand name." },
130
+ "thumbnail": { "type": "string", "description": "Thumbnail image URL." }
131
+ }
132
+ },
133
+ "ProductList": {
134
+ "type": "object",
135
+ "properties": {
136
+ "products": { "type": "array", "description": "The products on this page.", "items": { "$ref": "#/components/schemas/Product" } },
137
+ "total": { "type": "integer", "description": "Total number of matching products." },
138
+ "skip": { "type": "integer", "description": "How many were skipped." },
139
+ "limit": { "type": "integer", "description": "How many were returned." }
140
+ }
141
+ },
142
+ "ProductInput": {
143
+ "type": "object",
144
+ "required": ["title"],
145
+ "properties": {
146
+ "title": { "type": "string", "description": "Product name." },
147
+ "description": { "type": "string", "description": "Product description." },
148
+ "category": { "type": "string", "description": "Category slug." },
149
+ "price": { "type": "number", "description": "Price in USD." }
150
+ },
151
+ "example": {
152
+ "title": "Velu Notebook",
153
+ "description": "A dotted-grid notebook for docs notes.",
154
+ "category": "stationery",
155
+ "price": 19.99
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "$schema": "https://veludocs.com/velu.schema.json",
3
3
  "name": "Starter",
4
+ "url": "https://veludocs.com",
4
5
  "colors": {
5
6
  "primary": "#dc143c"
6
7
  },
7
8
  "favicon": "/favicon.svg",
9
+ "contextual": {
10
+ "options": ["copy", "view", "chatgpt", "claude", "mcp", "cursor", "vscode"]
11
+ },
8
12
  "navigation": {
9
13
  "anchors": [
10
14
  { "anchor": "Documentation", "icon": "book-open", "href": "https://veludocs.com" },
@@ -23,11 +27,15 @@
23
27
  {
24
28
  "tab": "API Reference",
25
29
  "icon": "terminal",
30
+ "openapi": "/openapi.json",
26
31
  "groups": [
27
- { "group": "API Documentation", "pages": ["api-reference/introduction"] },
28
- { "group": "Endpoint Examples", "pages": ["api-reference/endpoint/get", "api-reference/endpoint/create"] }
32
+ { "group": "API Documentation", "pages": ["api-reference/introduction"] }
29
33
  ]
30
34
  }
31
35
  ]
36
+ },
37
+ "api": {
38
+ "playground": { "proxy": true },
39
+ "examples": { "languages": ["curl", "javascript", "python", "ruby"] }
32
40
  }
33
41
  }
@@ -1,24 +0,0 @@
1
- ---
2
- title: Create item
3
- description: Create a new item.
4
- ---
5
-
6
- <MethodBadge method="POST" /> `/items`
7
-
8
- Creates an item and returns it.
9
-
10
- ## Body
11
-
12
- <ApiField name="name" type="string" required>
13
- Display name for the item.
14
- </ApiField>
15
-
16
- ## Response
17
-
18
- ```json
19
- {
20
- "id": "itm_2",
21
- "name": "New item",
22
- "created_at": "2024-01-01T00:00:00Z"
23
- }
24
- ```
@@ -1,27 +0,0 @@
1
- ---
2
- title: Get items
3
- description: Retrieve a list of items.
4
- ---
5
-
6
- <MethodBadge method="GET" /> `/items`
7
-
8
- Returns a paginated list of items.
9
-
10
- ## Query parameters
11
-
12
- <ApiField name="limit" type="integer" default="20">
13
- Maximum number of items to return.
14
- </ApiField>
15
-
16
- <ApiField name="cursor" type="string">
17
- Pagination cursor from a previous response.
18
- </ApiField>
19
-
20
- ## Response
21
-
22
- ```json
23
- {
24
- "data": [{ "id": "itm_1", "name": "Example" }],
25
- "next_cursor": null
26
- }
27
- ```