@krantho/config 0.0.0-stage → 1.0.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/dist/defaults.d.ts +15 -0
  3. package/dist/defaults.d.ts.map +1 -0
  4. package/dist/defaults.js +148 -0
  5. package/dist/defaults.js.map +1 -0
  6. package/dist/define.d.ts +11 -0
  7. package/dist/define.d.ts.map +1 -0
  8. package/dist/define.js +12 -0
  9. package/dist/define.js.map +1 -0
  10. package/dist/derive.d.ts +12 -0
  11. package/dist/derive.d.ts.map +1 -0
  12. package/dist/derive.js +90 -0
  13. package/dist/derive.js.map +1 -0
  14. package/dist/env.d.ts +19 -0
  15. package/dist/env.d.ts.map +1 -0
  16. package/dist/env.js +89 -0
  17. package/dist/env.js.map +1 -0
  18. package/dist/errors.d.ts +6 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +11 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/index.d.ts +17 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +12 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/leaves.d.ts +34 -0
  27. package/dist/leaves.d.ts.map +1 -0
  28. package/dist/leaves.js +724 -0
  29. package/dist/leaves.js.map +1 -0
  30. package/dist/llm-providers.d.ts +25 -0
  31. package/dist/llm-providers.d.ts.map +1 -0
  32. package/dist/llm-providers.js +41 -0
  33. package/dist/llm-providers.js.map +1 -0
  34. package/dist/node/index.d.ts +9 -0
  35. package/dist/node/index.d.ts.map +1 -0
  36. package/dist/node/index.js +4 -0
  37. package/dist/node/index.js.map +1 -0
  38. package/dist/node/load.d.ts +20 -0
  39. package/dist/node/load.d.ts.map +1 -0
  40. package/dist/node/load.js +48 -0
  41. package/dist/node/load.js.map +1 -0
  42. package/dist/node/route-slug.d.ts +9 -0
  43. package/dist/node/route-slug.d.ts.map +1 -0
  44. package/dist/node/route-slug.js +23 -0
  45. package/dist/node/route-slug.js.map +1 -0
  46. package/dist/node/seo-definitions.d.ts +8 -0
  47. package/dist/node/seo-definitions.d.ts.map +1 -0
  48. package/dist/node/seo-definitions.js +124 -0
  49. package/dist/node/seo-definitions.js.map +1 -0
  50. package/dist/object-path.d.ts +17 -0
  51. package/dist/object-path.d.ts.map +1 -0
  52. package/dist/object-path.js +75 -0
  53. package/dist/object-path.js.map +1 -0
  54. package/dist/production.d.ts +9 -0
  55. package/dist/production.d.ts.map +1 -0
  56. package/dist/production.js +22 -0
  57. package/dist/production.js.map +1 -0
  58. package/dist/public.d.ts +61 -0
  59. package/dist/public.d.ts.map +1 -0
  60. package/dist/public.js +65 -0
  61. package/dist/public.js.map +1 -0
  62. package/dist/reference.d.ts +8 -0
  63. package/dist/reference.d.ts.map +1 -0
  64. package/dist/reference.js +117 -0
  65. package/dist/reference.js.map +1 -0
  66. package/dist/resolve.d.ts +27 -0
  67. package/dist/resolve.d.ts.map +1 -0
  68. package/dist/resolve.js +87 -0
  69. package/dist/resolve.js.map +1 -0
  70. package/dist/schema.d.ts +437 -0
  71. package/dist/schema.d.ts.map +1 -0
  72. package/dist/schema.js +420 -0
  73. package/dist/schema.js.map +1 -0
  74. package/dist/sources.d.ts +10 -0
  75. package/dist/sources.d.ts.map +1 -0
  76. package/dist/sources.js +17 -0
  77. package/dist/sources.js.map +1 -0
  78. package/dist/types.d.ts +413 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +10 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/webhook-events.d.ts +3 -0
  83. package/dist/webhook-events.d.ts.map +1 -0
  84. package/dist/webhook-events.js +3 -0
  85. package/dist/webhook-events.js.map +1 -0
  86. package/package.json +47 -3
  87. package/README.md +0 -3
package/dist/leaves.js ADDED
@@ -0,0 +1,724 @@
1
+ /** Derive the mechanical environment variable name for a dotted path */
2
+ export function mechanicalEnvName(path) {
3
+ const parts = path
4
+ .split('.')
5
+ .flatMap((segment) => segment.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(' '))
6
+ .map((word) => word.toUpperCase());
7
+ return `KRANTHO_${parts.join('_')}`;
8
+ }
9
+ /** The environment variable a leaf reads, or null when it has none */
10
+ export function envNameFor(leaf) {
11
+ if (leaf.kind === 'structured')
12
+ return null;
13
+ return leaf.env ?? mechanicalEnvName(leaf.path);
14
+ }
15
+ /**
16
+ * Every leaf of the configuration tree.
17
+ *
18
+ * The `env` overrides are the variables that existed before the tree did, which
19
+ * keep their names so running deployments keep working, and the one credential
20
+ * whose name states what the token is. Everything else takes the mechanical form.
21
+ */
22
+ export const LEAVES = [
23
+ // --- site ---
24
+ { path: 'site.domain', kind: 'string', description: 'Bare hostname of the site.' },
25
+ { path: 'site.name', kind: 'string', description: 'Human-readable site name.' },
26
+ {
27
+ path: 'site.language',
28
+ kind: 'string',
29
+ description: 'Primary content language as a locale code.',
30
+ },
31
+ {
32
+ path: 'site.baseUrl',
33
+ kind: 'string',
34
+ description: 'Absolute base URL used for canonical links, the sitemap and robots.',
35
+ },
36
+ {
37
+ path: 'site.supportedLocales',
38
+ kind: 'list',
39
+ description: 'Locales the site publishes. Defaults to the primary language alone.',
40
+ },
41
+ {
42
+ path: 'site.defaultLocale',
43
+ kind: 'string',
44
+ description: 'Locale served when a request names none. Defaults to the primary language.',
45
+ },
46
+ {
47
+ path: 'site.logo',
48
+ kind: 'string',
49
+ description: "Absolute URL of the site's or its organization's logo, used in structured data.",
50
+ },
51
+ {
52
+ path: 'site.description',
53
+ kind: 'string',
54
+ description: 'What the site or its organization is, in a sentence, used in structured data.',
55
+ },
56
+ {
57
+ path: 'site.alternateName',
58
+ kind: 'string',
59
+ description: 'Another name the site or organization is known by.',
60
+ },
61
+ {
62
+ path: 'site.sameAs',
63
+ kind: 'list',
64
+ description: 'Absolute URLs of profile pages that identify the organization.',
65
+ },
66
+ // --- paths ---
67
+ {
68
+ path: 'paths.dataDir',
69
+ kind: 'string',
70
+ description: 'Directory for krantho runtime data, relative to the working directory.',
71
+ },
72
+ { path: 'paths.contentDir', kind: 'string', description: 'Directory holding markdown content.' },
73
+ {
74
+ path: 'paths.seoGeneratedDir',
75
+ kind: 'string',
76
+ description: 'Directory the SEO pipeline writes generated files to. Defaults to seo/generated under the content directory.',
77
+ },
78
+ // --- persistence ---
79
+ {
80
+ path: 'persistence.provider',
81
+ kind: 'string',
82
+ description: 'Persistence backend: sqlite, postgres or filesystem.',
83
+ },
84
+ {
85
+ path: 'persistence.sqlite.path',
86
+ kind: 'string',
87
+ description: 'SQLite database file. Defaults to the data directory plus content.db.',
88
+ },
89
+ {
90
+ path: 'persistence.sqlite.busyTimeoutMs',
91
+ kind: 'int',
92
+ description: "How long a statement waits for another connection's write lock.",
93
+ },
94
+ {
95
+ path: 'persistence.sqlite.walRetries',
96
+ kind: 'int',
97
+ description: 'Attempts to switch a new database file into WAL mode. At least one.',
98
+ },
99
+ {
100
+ path: 'persistence.sqlite.walRetryDelayMs',
101
+ kind: 'int',
102
+ description: 'Pause between WAL switch attempts.',
103
+ },
104
+ {
105
+ path: 'persistence.postgres.url',
106
+ kind: 'string',
107
+ secret: true,
108
+ description: 'Postgres connection URL. Required when the provider is postgres.',
109
+ },
110
+ {
111
+ path: 'persistence.postgres.maxConnections',
112
+ kind: 'int',
113
+ description: 'Connection pool size per process. Unset uses the driver default of 10; keep it small on serverless.',
114
+ },
115
+ {
116
+ path: 'persistence.postgres.prepare',
117
+ kind: 'boolean',
118
+ description: 'Use prepared statements. Unset uses the driver default (on); set false behind a transaction pooler such as PgBouncer or a pooled Neon URL.',
119
+ },
120
+ {
121
+ path: 'persistence.postgres.idleTimeoutSec',
122
+ kind: 'int',
123
+ description: 'Seconds before an idle pooled connection is closed. Unset keeps idle connections open.',
124
+ },
125
+ {
126
+ path: 'persistence.filesystem.contentPath',
127
+ kind: 'string',
128
+ description: 'Content root for the filesystem adapter. Defaults to the content directory.',
129
+ },
130
+ // --- content ---
131
+ {
132
+ path: 'content.cache.enabled',
133
+ kind: 'boolean',
134
+ description: 'Enable the content cache of the API and of server-rendered pages. With cache.pages next-isr, Next.js caches page reads instead; with cdn, the CDN caches the pages and pages read uncached.',
135
+ },
136
+ {
137
+ path: 'content.cache.ttlMs',
138
+ kind: 'int',
139
+ description: 'Cache entry lifetime in milliseconds. A published change reaches server-rendered pages after at most this long, since pages and the API may run in separate processes.',
140
+ },
141
+ { path: 'content.cache.maxEntries', kind: 'int', description: 'Maximum cached documents.' },
142
+ {
143
+ path: 'content.atNotation',
144
+ kind: 'boolean',
145
+ description: 'Expand @field references in markdown bodies.',
146
+ },
147
+ {
148
+ path: 'content.watch',
149
+ kind: 'boolean',
150
+ description: 'Watch filesystem collections and sync changes into the database.',
151
+ },
152
+ {
153
+ path: 'content.defaultStatus.filesystem',
154
+ kind: 'string',
155
+ description: 'Status given to documents imported from markdown files.',
156
+ },
157
+ {
158
+ path: 'content.list.maxLimit',
159
+ kind: 'int',
160
+ description: 'Cap on list responses. Unset means a list returns every row.',
161
+ },
162
+ {
163
+ path: 'content.search.minQueryLength',
164
+ kind: 'int',
165
+ description: 'Shortest accepted search query.',
166
+ },
167
+ {
168
+ path: 'content.search.defaultLimit',
169
+ kind: 'int',
170
+ description: 'Search results returned when the request names no limit.',
171
+ },
172
+ { path: 'content.search.maxLimit', kind: 'int', description: 'Cap on search results.' },
173
+ {
174
+ path: 'content.versions.keep',
175
+ kind: 'int',
176
+ description: 'Versions retained per document when purging.',
177
+ },
178
+ {
179
+ path: 'content.lock.leaseMs',
180
+ kind: 'int',
181
+ description: 'How long an editing lock is held before it expires.',
182
+ },
183
+ // --- cache ---
184
+ {
185
+ path: 'cache.pages',
186
+ kind: 'string',
187
+ description: 'How rendered pages are cached: none (render every request), next-isr (Next.js keeps each page until a content change invalidates it; the page routes must be ISR routes) or cdn (each page carries CDN cache headers and cache tags, and a content change purges the CDN; requires cache.cdn.provider).',
188
+ },
189
+ {
190
+ path: 'cache.cdn.provider',
191
+ kind: 'string',
192
+ description: 'CDN in front of the site, purged when content changes: none, vercel, cloudfront or gcp (Cloud CDN).',
193
+ },
194
+ {
195
+ path: 'cache.cdn.sMaxAgeSec',
196
+ kind: 'int',
197
+ description: 'Seconds the CDN keeps a page when no content change purges it first.',
198
+ },
199
+ {
200
+ path: 'cache.cdn.staleWhileRevalidateSec',
201
+ kind: 'int',
202
+ description: 'Seconds the CDN may serve a stale page while it fetches a fresh one.',
203
+ },
204
+ {
205
+ path: 'cache.cdn.tagHeader',
206
+ kind: 'string',
207
+ description: 'Response header carrying the cache tags with the cloudfront provider. The CloudFront distribution must be configured to read the same header.',
208
+ },
209
+ {
210
+ path: 'cache.cdn.cloudfront.distributionId',
211
+ kind: 'string',
212
+ description: 'CloudFront distribution to purge. Required when the provider is cloudfront.',
213
+ },
214
+ {
215
+ path: 'cache.cdn.gcp.project',
216
+ kind: 'string',
217
+ description: 'Google Cloud project of the URL map. Required when the provider is gcp.',
218
+ },
219
+ {
220
+ path: 'cache.cdn.gcp.urlMap',
221
+ kind: 'string',
222
+ description: 'Cloud CDN URL map to purge. Required when the provider is gcp.',
223
+ },
224
+ {
225
+ path: 'cache.cdn.gcp.accessToken',
226
+ kind: 'string',
227
+ secret: true,
228
+ description: 'OAuth access token for the Compute Engine API. Unset fetches one from the metadata server.',
229
+ },
230
+ {
231
+ path: 'cache.shared',
232
+ kind: 'string',
233
+ description: 'Where Next.js keeps its page and data cache: none (each instance keeps its own) or database (shared by every instance through the persistence backend).',
234
+ },
235
+ // --- auth ---
236
+ {
237
+ path: 'auth.mode',
238
+ kind: 'string',
239
+ env: 'KRANTHO_AUTH',
240
+ description: 'Authentication mode: none, token or oidc.',
241
+ },
242
+ {
243
+ path: 'auth.requireAuthForReads',
244
+ kind: 'boolean',
245
+ description: 'Require authentication for GET endpoints as well as writes. Sign-in, robots.txt, the sitemap, llms.txt and the IndexNow key stay public.',
246
+ },
247
+ {
248
+ path: 'auth.token.secret',
249
+ kind: 'string',
250
+ env: 'KRANTHO_API_TOKEN',
251
+ secret: true,
252
+ description: 'Shared secret accepted by token mode.',
253
+ },
254
+ {
255
+ path: 'auth.token.email',
256
+ kind: 'string',
257
+ env: 'KRANTHO_TOKEN_EMAIL',
258
+ description: 'Email reported for the token-mode identity.',
259
+ },
260
+ {
261
+ path: 'auth.token.role',
262
+ kind: 'string',
263
+ env: 'KRANTHO_TOKEN_ROLE',
264
+ description: 'Role granted to the token-mode identity.',
265
+ },
266
+ {
267
+ path: 'auth.jwtSecret',
268
+ kind: 'string',
269
+ env: 'KRANTHO_JWT_SECRET',
270
+ secret: true,
271
+ description: 'Signing secret for session tokens. An ephemeral one is generated when unset.',
272
+ },
273
+ {
274
+ path: 'auth.oidc.issuer',
275
+ kind: 'string',
276
+ env: 'OIDC_ISSUER_URL',
277
+ description: 'OIDC issuer URL.',
278
+ },
279
+ {
280
+ path: 'auth.oidc.clientId',
281
+ kind: 'string',
282
+ env: 'OIDC_CLIENT_ID',
283
+ description: 'OIDC client identifier.',
284
+ },
285
+ {
286
+ path: 'auth.oidc.clientSecret',
287
+ kind: 'string',
288
+ env: 'OIDC_CLIENT_SECRET',
289
+ secret: true,
290
+ description: 'OIDC client secret, sent on the token exchange (client_secret_post). Unset means a public client (PKCE only).',
291
+ },
292
+ {
293
+ path: 'auth.oidc.redirectUri',
294
+ kind: 'string',
295
+ env: 'OIDC_REDIRECT_URI',
296
+ description: 'OIDC redirect URI registered with the provider.',
297
+ },
298
+ {
299
+ path: 'auth.oidc.postLogoutUri',
300
+ kind: 'string',
301
+ env: 'OIDC_POST_LOGOUT_URI',
302
+ description: 'Where the provider returns the browser after logout.',
303
+ },
304
+ {
305
+ path: 'auth.oidc.scopes',
306
+ kind: 'list',
307
+ description: 'Scopes requested at the authorize step.',
308
+ },
309
+ {
310
+ path: 'auth.oidc.signup',
311
+ kind: 'string',
312
+ env: 'KRANTHO_SIGNUP',
313
+ description: 'Whether a first-time OIDC login creates a user: open or closed.',
314
+ },
315
+ {
316
+ path: 'auth.oidc.studioEntry',
317
+ kind: 'string',
318
+ description: 'How /studio treats a visitor without a session: login shows the Studio sign-in page; redirect sends the visitor to the OIDC provider and answers 404 when sign-in is refused.',
319
+ },
320
+ {
321
+ path: 'auth.oidc.adminEmails',
322
+ kind: 'list',
323
+ env: 'KRANTHO_ADMIN_EMAILS',
324
+ description: 'Emails promoted to Studio on login.',
325
+ },
326
+ {
327
+ path: 'auth.oidc.newUserRole',
328
+ kind: 'string',
329
+ description: 'Role given to a user created by OIDC signup.',
330
+ },
331
+ {
332
+ path: 'auth.workload.issuer',
333
+ kind: 'string',
334
+ description: 'Issuer URL of the platform whose workload identity tokens may call the API without a stored secret, for example https://token.actions.githubusercontent.com for GitHub Actions. Turns on workload identity together with audience and subjects.',
335
+ },
336
+ {
337
+ path: 'auth.workload.audience',
338
+ kind: 'string',
339
+ description: 'Audience a workload identity token must carry: the value the workflow asks the platform for, usually the site URL.',
340
+ },
341
+ {
342
+ path: 'auth.workload.subjects',
343
+ kind: 'list',
344
+ description: 'Subjects a workload identity token may carry: an exact value, or a prefix ending in * such as repo:ORG/REPO:*. A token with any other subject is refused.',
345
+ },
346
+ {
347
+ path: 'auth.session.accessTtlSec',
348
+ kind: 'int',
349
+ description: 'Access token lifetime in seconds.',
350
+ },
351
+ {
352
+ path: 'auth.session.refreshTtlSec',
353
+ kind: 'int',
354
+ description: 'Refresh token lifetime in seconds.',
355
+ },
356
+ {
357
+ path: 'auth.session.cookiePrefix',
358
+ kind: 'string',
359
+ description: 'Prefix for the session cookie names.',
360
+ },
361
+ {
362
+ path: 'auth.session.secure',
363
+ kind: 'string',
364
+ description: 'Secure cookie flag: always, never, or auto — infer it from x-forwarded-proto.',
365
+ },
366
+ {
367
+ path: 'auth.session.sameSite',
368
+ kind: 'string',
369
+ description: 'SameSite cookie policy: lax, strict or none. none requires auth.session.secure always.',
370
+ },
371
+ {
372
+ path: 'auth.trustProxy',
373
+ kind: 'boolean',
374
+ description: 'Widen what auth.session.secure "auto" accepts as proof of HTTPS to the other headers a terminating proxy may send.',
375
+ },
376
+ // --- secrets ---
377
+ {
378
+ path: 'secrets.provider',
379
+ kind: 'string',
380
+ description: 'The vault that encrypts the secrets a site stores in its database, such as a pasted LLM key. Only local is available: it encrypts with a key from the environment.',
381
+ },
382
+ {
383
+ path: 'secrets.local.key',
384
+ kind: 'string',
385
+ secret: true,
386
+ description: 'Key the local vault encrypts stored secrets with, at least 32 characters. Unset uses auth.jwtSecret; with neither, a site stores no secrets in its database.',
387
+ },
388
+ // --- media ---
389
+ {
390
+ path: 'media.provider',
391
+ kind: 'string',
392
+ description: 'Where media files are stored: local, vercel-blob, s3 or gcs. Unset turns media off. Media needs sqlite or postgres persistence.',
393
+ },
394
+ {
395
+ path: 'media.local.dir',
396
+ kind: 'string',
397
+ description: 'Directory the local store writes files to. The host framework must serve it at media.local.urlPrefix.',
398
+ },
399
+ {
400
+ path: 'media.local.urlPrefix',
401
+ kind: 'string',
402
+ description: 'URL prefix the host framework serves the local store directory under.',
403
+ },
404
+ {
405
+ path: 'media.vercelBlob.token',
406
+ kind: 'string',
407
+ secret: true,
408
+ description: 'Read-write token of the Vercel Blob store. Required when the provider is vercel-blob.',
409
+ },
410
+ {
411
+ path: 'media.s3.bucket',
412
+ kind: 'string',
413
+ description: 'S3 bucket name. Required when the provider is s3.',
414
+ },
415
+ {
416
+ path: 'media.s3.region',
417
+ kind: 'string',
418
+ description: 'S3 region, such as eu-north-1, or auto for Cloudflare R2. Required when the provider is s3.',
419
+ },
420
+ {
421
+ path: 'media.s3.endpoint',
422
+ kind: 'string',
423
+ description: 'Endpoint URL of an S3-compatible service such as Cloudflare R2 or MinIO. Unset uses AWS. When set, media.s3.publicUrl is required.',
424
+ },
425
+ {
426
+ path: 'media.s3.forcePathStyle',
427
+ kind: 'boolean',
428
+ description: 'Address the bucket in the URL path rather than the host name, as MinIO needs.',
429
+ },
430
+ {
431
+ path: 'media.s3.publicUrl',
432
+ kind: 'string',
433
+ description: 'Public base URL objects are served from, such as a CDN or bucket domain. Objects must be publicly readable there.',
434
+ },
435
+ {
436
+ path: 'media.s3.accessKeyId',
437
+ kind: 'string',
438
+ description: 'Access key ID. Set it together with media.s3.secretAccessKey; unset both to use the AWS default credential chain.',
439
+ },
440
+ {
441
+ path: 'media.s3.secretAccessKey',
442
+ kind: 'string',
443
+ secret: true,
444
+ description: 'Secret access key paired with media.s3.accessKeyId.',
445
+ },
446
+ {
447
+ path: 'media.gcs.bucket',
448
+ kind: 'string',
449
+ description: 'Google Cloud Storage bucket name. Required when the provider is gcs.',
450
+ },
451
+ {
452
+ path: 'media.gcs.publicUrl',
453
+ kind: 'string',
454
+ description: 'Public base URL objects are served from, such as a CDN in front of the bucket. Objects must be publicly readable there.',
455
+ },
456
+ {
457
+ path: 'media.gcs.credentials',
458
+ kind: 'string',
459
+ secret: true,
460
+ description: 'Service-account key JSON. Unset uses the metadata server of Cloud Run, GKE or Compute Engine.',
461
+ },
462
+ {
463
+ path: 'media.maxFileSize',
464
+ kind: 'int',
465
+ env: 'KRANTHO_MEDIA_MAX_SIZE',
466
+ description: 'Largest accepted upload in bytes.',
467
+ },
468
+ { path: 'media.allowedMimeTypes', kind: 'list', description: 'Upload MIME types accepted.' },
469
+ {
470
+ path: 'media.image.maxWidth',
471
+ kind: 'int',
472
+ description: 'Uploads wider than this are downscaled.',
473
+ },
474
+ { path: 'media.image.quality.jpeg', kind: 'int', description: 'JPEG variant quality.' },
475
+ { path: 'media.image.quality.webp', kind: 'int', description: 'WebP variant quality.' },
476
+ { path: 'media.image.quality.avif', kind: 'int', description: 'AVIF variant quality.' },
477
+ {
478
+ path: 'media.image.quality.normalize',
479
+ kind: 'int',
480
+ description: 'Quality used when re-encoding an upload.',
481
+ },
482
+ { path: 'media.image.presets', kind: 'structured', description: 'Preset name to pixel width.' },
483
+ {
484
+ path: 'media.image.cache.maxEntries',
485
+ kind: 'int',
486
+ env: 'KRANTHO_IMAGE_CACHE_MAX_ENTRIES',
487
+ description: 'Image variants held in memory.',
488
+ },
489
+ {
490
+ path: 'media.image.cache.maxMb',
491
+ kind: 'int',
492
+ env: 'KRANTHO_IMAGE_CACHE_MAX_MB',
493
+ description: 'Memory budget for the image variant cache.',
494
+ },
495
+ {
496
+ path: 'media.image.cache.ttiSec',
497
+ kind: 'int',
498
+ env: 'KRANTHO_IMAGE_CACHE_TTI_SECS',
499
+ description: 'Idle seconds before a cached variant is dropped.',
500
+ },
501
+ // --- llm ---
502
+ {
503
+ path: 'llm.provider',
504
+ kind: 'string',
505
+ description: 'LLM provider used for enrichment: anthropic, claude-code, openai, gemini, openrouter or ollama. Each provider has its own credential below; ollama needs none.',
506
+ },
507
+ {
508
+ path: 'llm.model',
509
+ kind: 'string',
510
+ description: "Model identifier. Unset uses the provider's preset default.",
511
+ },
512
+ {
513
+ path: 'llm.anthropic.apiKey',
514
+ kind: 'string',
515
+ secret: true,
516
+ description: 'API key for the anthropic provider.',
517
+ },
518
+ {
519
+ path: 'llm.openai.apiKey',
520
+ kind: 'string',
521
+ secret: true,
522
+ description: 'API key for the openai provider.',
523
+ },
524
+ {
525
+ path: 'llm.gemini.apiKey',
526
+ kind: 'string',
527
+ secret: true,
528
+ description: 'API key for the gemini provider.',
529
+ },
530
+ {
531
+ path: 'llm.openrouter.apiKey',
532
+ kind: 'string',
533
+ secret: true,
534
+ description: 'API key for the openrouter provider.',
535
+ },
536
+ {
537
+ path: 'llm.claudeCode.authToken',
538
+ kind: 'string',
539
+ env: 'KRANTHO_LLM_CLAUDE_CODE_OAUTH_TOKEN',
540
+ secret: true,
541
+ description: 'The token `claude setup-token` prints, used only by the claude-code provider, for development on your own machine. No other provider reads it; deployments use an API key.',
542
+ },
543
+ {
544
+ path: 'llm.profiles',
545
+ kind: 'structured',
546
+ description: 'How requests to one model are shaped, by model id, over the default for that model: reasoningEffort (openai, gemini, openrouter and ollama; default leaves it to the model), reasoningHeadroom (output tokens added for the thinking a model does) and temperature. Models such as the gpt-5 family and the o-series reject a temperature and need their reasoning kept short, so the defaults ask for the lowest effort each accepts.',
547
+ },
548
+ {
549
+ path: 'llm.baseUrl',
550
+ kind: 'string',
551
+ description: "Override the provider's base URL for a proxy or self-hosted endpoint.",
552
+ },
553
+ // --- assistant ---
554
+ {
555
+ path: 'assistant.enabled',
556
+ kind: 'boolean',
557
+ description: 'Turn on the Studio assistant. It needs sqlite or postgres and an LLM configured in Setup › AI provider.',
558
+ },
559
+ {
560
+ path: 'assistant.provider',
561
+ kind: 'string',
562
+ description: "Provider for the assistant, one of the llm.provider values, using that provider's own credential. Unset uses the provider chosen in Setup › AI provider.",
563
+ },
564
+ {
565
+ path: 'assistant.model',
566
+ kind: 'string',
567
+ description: "Model for the assistant. Unset uses the model chosen in Setup › AI provider, or the provider's default model when assistant.provider is set.",
568
+ },
569
+ {
570
+ path: 'assistant.role',
571
+ kind: 'string',
572
+ description: "The lowest role that may use the assistant: viewer, editor or Studio. It decides who can spend the site's LLM credential.",
573
+ },
574
+ {
575
+ path: 'assistant.confirmTtlSec',
576
+ kind: 'int',
577
+ description: 'Seconds a proposed action can be confirmed in the chat. At most 3600.',
578
+ },
579
+ {
580
+ path: 'assistant.limits.maxTurns',
581
+ kind: 'int',
582
+ description: 'Model calls in one chat request.',
583
+ },
584
+ {
585
+ path: 'assistant.limits.maxToolCalls',
586
+ kind: 'int',
587
+ description: 'Tool executions in one chat request.',
588
+ },
589
+ {
590
+ path: 'assistant.limits.maxTokens',
591
+ kind: 'int',
592
+ description: 'Output token cap for each model call.',
593
+ },
594
+ {
595
+ path: 'assistant.limits.maxRequestTokens',
596
+ kind: 'int',
597
+ description: 'Input plus output tokens across all model calls of one chat request.',
598
+ },
599
+ {
600
+ path: 'assistant.limits.maxHistoryMessages',
601
+ kind: 'int',
602
+ description: 'Conversation messages accepted from Studio in one chat request.',
603
+ },
604
+ {
605
+ path: 'assistant.limits.maxInputChars',
606
+ kind: 'int',
607
+ description: 'Characters accepted from Studio across the conversation.',
608
+ },
609
+ {
610
+ path: 'assistant.limits.toolResultChars',
611
+ kind: 'int',
612
+ description: 'Characters of one tool result sent to the model.',
613
+ },
614
+ // --- seo ---
615
+ { path: 'seo.identity', kind: 'string', description: 'Schema.org type describing the site.' },
616
+ {
617
+ path: 'seo.collections',
618
+ kind: 'structured',
619
+ description: 'Collection name to Schema.org types.',
620
+ },
621
+ { path: 'seo.routes', kind: 'structured', description: 'URL pattern to Schema.org types.' },
622
+ { path: 'seo.site', kind: 'structured', description: 'Rich site-level SEO configuration.' },
623
+ {
624
+ path: 'seo.richCollections',
625
+ kind: 'structured',
626
+ description: 'Rich per-collection SEO configuration.',
627
+ },
628
+ { path: 'seo.richRoutes', kind: 'structured', description: 'Rich per-route SEO configuration.' },
629
+ {
630
+ path: 'seo.seedMode',
631
+ kind: 'string',
632
+ description: 'seed preserves rows already in the database; overwrite replaces them.',
633
+ },
634
+ {
635
+ path: 'seo.readers',
636
+ kind: 'list',
637
+ description: 'Reader profiles (google, bing, or a site-registered one) whose published rules the structured data is checked against.',
638
+ },
639
+ {
640
+ path: 'seo.configDir',
641
+ kind: 'string',
642
+ description: 'Directory of SEO seed files, relative to the working directory.',
643
+ },
644
+ {
645
+ path: 'seo.indexNow.key',
646
+ kind: 'string',
647
+ description: 'IndexNow key. When set, the site serves it at /<key>.txt and notifies IndexNow (Bing and other participating engines) of published, changed and removed pages.',
648
+ },
649
+ {
650
+ path: 'seo.enrichment.meta.minChars',
651
+ kind: 'int',
652
+ description: 'Shortest meta description the SEO checks accept — the editorial limit Studio and the API share, and the target for generated ones.',
653
+ },
654
+ {
655
+ path: 'seo.enrichment.meta.maxChars',
656
+ kind: 'int',
657
+ description: 'Longest meta description the SEO checks accept — the editorial limit Studio and the API share, and the target for generated ones.',
658
+ },
659
+ {
660
+ path: 'seo.enrichment.meta.contentCap',
661
+ kind: 'int',
662
+ description: 'Characters of body text sent to the model for a meta description.',
663
+ },
664
+ {
665
+ path: 'seo.enrichment.meta.maxTokens',
666
+ kind: 'int',
667
+ description: 'Token budget for a meta description completion.',
668
+ },
669
+ {
670
+ path: 'seo.enrichment.title.maxChars',
671
+ kind: 'int',
672
+ description: 'Longest title the SEO checks accept — the editorial limit Studio and the API share.',
673
+ },
674
+ {
675
+ path: 'seo.enrichment.archetype.contentCap',
676
+ kind: 'int',
677
+ description: 'Characters of body text sent to the model for archetype enrichment.',
678
+ },
679
+ {
680
+ path: 'seo.enrichment.archetype.maxTokens',
681
+ kind: 'int',
682
+ description: 'Token budget for an archetype completion.',
683
+ },
684
+ // --- webhooks ---
685
+ {
686
+ path: 'webhooks',
687
+ kind: 'structured',
688
+ description: 'Outgoing webhooks, a list of { url, events, secret? }: url is an absolute http(s) URL, events lists at least one of create, update, publish and delete, and secret signs each delivery with HMAC-SHA256. Read a secret from the environment inside krantho.config.ts, for example secret: process.env.KRANTHO_WEBHOOK_SECRET. Deliveries are logged and retried automatically on SQLite and Postgres; on the filesystem adapter there is no log, so deliveries are fire-and-forget.',
689
+ },
690
+ // --- tools ---
691
+ {
692
+ path: 'tools.concurrency',
693
+ kind: 'int',
694
+ env: 'CLAUDE_CONCURRENCY',
695
+ description: 'Parallel model calls made by the build-time pipeline.',
696
+ },
697
+ {
698
+ path: 'tools.enrich.serviceCap',
699
+ kind: 'int',
700
+ description: 'Characters of a service document sent to the model.',
701
+ },
702
+ {
703
+ path: 'tools.enrich.llmsDescriptionCap',
704
+ kind: 'int',
705
+ description: 'Longest generated llms.txt link description.',
706
+ },
707
+ {
708
+ path: 'tools.enrich.faqMinPerRoute',
709
+ kind: 'int',
710
+ description: 'FAQ entries a route must have before it is considered complete.',
711
+ },
712
+ {
713
+ path: 'tools.enrich.skipFields',
714
+ kind: 'list',
715
+ description: "Frontmatter fields left out of the text the enrichment tools send to the model. A trailing `*` matches every field with that prefix. Site-specific fields belong in the project's own krantho.config.ts.",
716
+ },
717
+ ];
718
+ /** Look a leaf up by its dotted path */
719
+ export function leafByPath(path) {
720
+ return LEAVES.find((leaf) => leaf.path === path);
721
+ }
722
+ /** Dotted paths of every secret leaf */
723
+ export const SECRET_PATHS = LEAVES.filter((leaf) => leaf.secret).map((leaf) => leaf.path);
724
+ //# sourceMappingURL=leaves.js.map