skrypt-ai 0.5.0 → 0.6.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 (120) hide show
  1. package/dist/auth/index.js +8 -1
  2. package/dist/autofix/index.d.ts +0 -4
  3. package/dist/autofix/index.js +0 -21
  4. package/dist/capture/browser.d.ts +11 -0
  5. package/dist/capture/browser.js +173 -0
  6. package/dist/capture/diff.d.ts +23 -0
  7. package/dist/capture/diff.js +52 -0
  8. package/dist/capture/index.d.ts +23 -0
  9. package/dist/capture/index.js +210 -0
  10. package/dist/capture/naming.d.ts +17 -0
  11. package/dist/capture/naming.js +45 -0
  12. package/dist/capture/parser.d.ts +15 -0
  13. package/dist/capture/parser.js +80 -0
  14. package/dist/capture/types.d.ts +57 -0
  15. package/dist/capture/types.js +1 -0
  16. package/dist/cli.js +4 -0
  17. package/dist/commands/autofix.js +136 -120
  18. package/dist/commands/cron.js +58 -47
  19. package/dist/commands/deploy.js +123 -102
  20. package/dist/commands/generate.js +88 -6
  21. package/dist/commands/heal.d.ts +10 -0
  22. package/dist/commands/heal.js +201 -0
  23. package/dist/commands/i18n.js +146 -111
  24. package/dist/commands/lint.js +50 -44
  25. package/dist/commands/llms-txt.js +59 -49
  26. package/dist/commands/login.js +61 -43
  27. package/dist/commands/mcp.js +6 -0
  28. package/dist/commands/monitor.js +13 -8
  29. package/dist/commands/qa.d.ts +2 -0
  30. package/dist/commands/qa.js +43 -0
  31. package/dist/commands/review-pr.js +108 -102
  32. package/dist/commands/sdk.js +128 -122
  33. package/dist/commands/security.js +86 -80
  34. package/dist/commands/test.js +91 -92
  35. package/dist/commands/version.js +104 -75
  36. package/dist/commands/watch.js +130 -114
  37. package/dist/config/types.js +2 -2
  38. package/dist/context-hub/index.d.ts +23 -0
  39. package/dist/context-hub/index.js +179 -0
  40. package/dist/context-hub/mappings.d.ts +8 -0
  41. package/dist/context-hub/mappings.js +55 -0
  42. package/dist/context-hub/types.d.ts +33 -0
  43. package/dist/context-hub/types.js +1 -0
  44. package/dist/generator/generator.js +39 -6
  45. package/dist/generator/types.d.ts +7 -0
  46. package/dist/generator/writer.d.ts +3 -1
  47. package/dist/generator/writer.js +24 -4
  48. package/dist/llm/anthropic-client.d.ts +1 -0
  49. package/dist/llm/anthropic-client.js +3 -1
  50. package/dist/llm/index.d.ts +6 -4
  51. package/dist/llm/index.js +76 -261
  52. package/dist/llm/openai-client.d.ts +1 -0
  53. package/dist/llm/openai-client.js +7 -2
  54. package/dist/qa/checks.d.ts +10 -0
  55. package/dist/qa/checks.js +492 -0
  56. package/dist/qa/fixes.d.ts +30 -0
  57. package/dist/qa/fixes.js +277 -0
  58. package/dist/qa/index.d.ts +29 -0
  59. package/dist/qa/index.js +187 -0
  60. package/dist/qa/types.d.ts +24 -0
  61. package/dist/qa/types.js +1 -0
  62. package/dist/scanner/csharp.d.ts +23 -0
  63. package/dist/scanner/csharp.js +421 -0
  64. package/dist/scanner/index.js +16 -2
  65. package/dist/scanner/java.d.ts +39 -0
  66. package/dist/scanner/java.js +318 -0
  67. package/dist/scanner/kotlin.d.ts +23 -0
  68. package/dist/scanner/kotlin.js +389 -0
  69. package/dist/scanner/php.d.ts +57 -0
  70. package/dist/scanner/php.js +351 -0
  71. package/dist/scanner/ruby.d.ts +36 -0
  72. package/dist/scanner/ruby.js +431 -0
  73. package/dist/scanner/swift.d.ts +25 -0
  74. package/dist/scanner/swift.js +392 -0
  75. package/dist/scanner/types.d.ts +1 -1
  76. package/dist/template/content/docs/_navigation.json +46 -0
  77. package/dist/template/content/docs/_sidebars.json +684 -0
  78. package/dist/template/content/docs/core.md +4544 -0
  79. package/dist/template/content/docs/index.mdx +89 -0
  80. package/dist/template/content/docs/integrations.md +1158 -0
  81. package/dist/template/content/docs/llms-full.md +403 -0
  82. package/dist/template/content/docs/llms.txt +4588 -0
  83. package/dist/template/content/docs/other.md +10379 -0
  84. package/dist/template/content/docs/tools.md +746 -0
  85. package/dist/template/content/docs/types.md +531 -0
  86. package/dist/template/docs.json +13 -11
  87. package/dist/template/mdx-components.tsx +27 -2
  88. package/dist/template/package.json +6 -0
  89. package/dist/template/public/search-index.json +1 -1
  90. package/dist/template/scripts/build-search-index.mjs +84 -6
  91. package/dist/template/src/app/api/chat/route.ts +83 -128
  92. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  93. package/dist/template/src/app/docs/llms-full.md +151 -4
  94. package/dist/template/src/app/docs/llms.txt +2464 -847
  95. package/dist/template/src/app/docs/page.mdx +48 -38
  96. package/dist/template/src/app/layout.tsx +3 -1
  97. package/dist/template/src/app/page.tsx +22 -8
  98. package/dist/template/src/components/ai-chat.tsx +73 -64
  99. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  100. package/dist/template/src/components/copy-button.tsx +13 -9
  101. package/dist/template/src/components/copy-page-button.tsx +54 -0
  102. package/dist/template/src/components/docs-layout.tsx +37 -25
  103. package/dist/template/src/components/header.tsx +51 -10
  104. package/dist/template/src/components/mdx/card.tsx +17 -3
  105. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  106. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  107. package/dist/template/src/components/mdx/heading.tsx +15 -2
  108. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  109. package/dist/template/src/components/mdx/index.tsx +2 -0
  110. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  111. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  112. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  113. package/dist/template/src/components/sidebar.tsx +12 -18
  114. package/dist/template/src/components/table-of-contents.tsx +9 -0
  115. package/dist/template/src/lib/highlight.ts +3 -88
  116. package/dist/template/src/lib/navigation.ts +159 -0
  117. package/dist/template/src/styles/globals.css +17 -6
  118. package/dist/utils/validation.d.ts +0 -3
  119. package/dist/utils/validation.js +0 -26
  120. package/package.json +3 -2
@@ -0,0 +1,4544 @@
1
+ ---
2
+ title: "Core API"
3
+ description: "Core functionality and main exports"
4
+ ---
5
+
6
+ <CardGroup cols={2}>
7
+ <Card title="PluginManager" icon="cube" href="#pluginmanager">
8
+ Manages documentation build plugins
9
+ </Card>
10
+ <Card title="autoFixBatch" icon="function" href="#autofixbatch">
11
+ Batch-fixes broken code examples
12
+ </Card>
13
+ <Card title="autoFixExample" icon="function" href="#autofixexample">
14
+ Repairs broken code examples
15
+ </Card>
16
+ <Card title="checkPlan" icon="function" href="#checkplan">
17
+ Verifies API key and plan
18
+ </Card>
19
+ <Card title="clearAuth" icon="function" href="#clearauth">
20
+ Wipes all stored credentials
21
+ </Card>
22
+ <Card title="createLLMClient" icon="function" href="#createllmclient">
23
+ Initializes typed LLM client
24
+ </Card>
25
+ <Card title="createPythonValidator" icon="function" href="#createpythonvalidator">
26
+ Validates Python code syntax
27
+ </Card>
28
+ <Card title="createTypeScriptValidator" icon="function" href="#createtypescriptvalidator">
29
+ Validates TypeScript code strings
30
+ </Card>
31
+ <Card title="definePlugin" icon="function" href="#defineplugin">
32
+ Defines typed plugin config
33
+ </Card>
34
+ <Card title="fixCodeSample" icon="function" href="#fixcodesample">
35
+ Auto-repairs broken code samples
36
+ </Card>
37
+ <Card title="generateDocumentation" icon="function" href="#generatedocumentation">
38
+ Generates docs via LLM client
39
+ </Card>
40
+ <Card title="getAuthConfig" icon="function" href="#getauthconfig">
41
+ Retrieves auth config synchronously
42
+ </Card>
43
+ <Card title="getAuthConfigAsync" icon="function" href="#getauthconfigasync">
44
+ Retrieves auth config asynchronously
45
+ </Card>
46
+ <Card title="getKeyStorageMethod" icon="function" href="#getkeystoragemethod">
47
+ Detects API key storage location
48
+ </Card>
49
+ <Card title="requirePro" icon="function" href="#requirepro">
50
+ Gates commands behind Pro plan
51
+ </Card>
52
+ <Card title="runImport" icon="function" href="#runimport">
53
+ Imports docs by detected format
54
+ </Card>
55
+ <Card title="saveAuthConfig" icon="function" href="#saveauthconfig">
56
+ Persists auth config to disk
57
+ </Card>
58
+ <Card title="scanDirectory" icon="function" href="#scandirectory">
59
+ Scans directory for API elements
60
+ </Card>
61
+ <Card title="scanFile" icon="function" href="#scanfile">
62
+ Extracts metadata from source file
63
+ </Card>
64
+ <Card title="constructor" icon="code" href="#constructor">
65
+ Initializes plugin manager instance
66
+ </Card>
67
+ <Card title="loadPlugins" icon="code" href="#loadplugins">
68
+ Loads plugins from config file
69
+ </Card>
70
+ <Card title="onAfterGenerate" icon="code" href="#onaftergenerate">
71
+ Runs post-generation plugin hooks
72
+ </Card>
73
+ <Card title="onAfterScan" icon="code" href="#onafterscan">
74
+ Runs post-scan plugin hooks
75
+ </Card>
76
+ <Card title="onAfterWrite" icon="code" href="#onafterwrite">
77
+ Runs post-write plugin hooks
78
+ </Card>
79
+ <Card title="onBeforeGenerate" icon="code" href="#onbeforegenerate">
80
+ Runs pre-generation plugin hooks
81
+ </Card>
82
+ <Card title="onBeforeScan" icon="code" href="#onbeforescan">
83
+ Runs pre-scan plugin hooks
84
+ </Card>
85
+ <Card title="onBeforeWrite" icon="code" href="#onbeforewrite">
86
+ Runs pre-write plugin hooks
87
+ </Card>
88
+ <Card title="onInit" icon="code" href="#oninit">
89
+ Triggers plugin initialization hook
90
+ </Card>
91
+ <Card title="register" icon="code" href="#register">
92
+ Registers plugin with manager
93
+ </Card>
94
+ <Card title="runHook" icon="code" href="#runhook">
95
+ Executes named hook across plugins
96
+ </Card>
97
+ <Card title="transformContent" icon="code" href="#transformcontent">
98
+ Pipes content through plugin chain
99
+ </Card>
100
+ </CardGroup>
101
+
102
+
103
+ ## `PluginManager`
104
+
105
+ ```typescript
106
+ class PluginManager
107
+ ```
108
+
109
+ Use this to load, manage, and execute documentation plugins that transform source files into output during the autodocs build process.
110
+
111
+ `PluginManager` orchestrates the full plugin lifecycle — registering plugins, running them in sequence, and providing each plugin with shared context (paths, config, and a logger).
112
+
113
+ ## Constructor Parameters
114
+
115
+ | Name | Type | Required | Description |
116
+ |------|------|----------|-------------|
117
+ | `sourcePath` | `string` | Yes | Absolute or relative path to the source files directory |
118
+ | `outputPath` | `string` | Yes | Absolute or relative path where generated docs will be written |
119
+ | `config` | `Record<string, unknown>` | No | Arbitrary config values passed to every plugin via context (defaults to `{}`) |
120
+
121
+ ## Plugin Context
122
+
123
+ Every registered plugin receives a shared context object:
124
+
125
+ | Field | Type | Description |
126
+ |-------|------|-------------|
127
+ | `sourcePath` | `string` | The source path passed to the constructor |
128
+ | `outputPath` | `string` | The output path passed to the constructor |
129
+ | `config` | `Record<string, unknown>` | The config object passed to the constructor |
130
+ | `logger.info` | `(msg: string) => void` | Logs an info message prefixed with `[plugin]` |
131
+ | `logger.warn` | `(msg: string) => void` | Logs a warning prefixed with `[plugin]` |
132
+ | `logger.error` | `(msg: string) => void` | Logs an error prefixed with `[plugin]` |
133
+
134
+ ## Key Behaviors
135
+
136
+ - Plugins are stored and executed in registration order
137
+ - Each plugin receives the same shared `PluginContext` instance
138
+ - Logger output is prefixed with `[plugin]` for easy filtering in build logs
139
+ - Config defaults to an empty object if not provided, so plugins should handle missing config keys gracefully
140
+
141
+ ### Example
142
+
143
+ ```typescript example.ts
144
+ // --- Inline types (no external imports needed) ---
145
+
146
+ type PluginContext = {
147
+ sourcePath: string
148
+ outputPath: string
149
+ config: Record<string, unknown>
150
+ logger: {
151
+ info: (msg: string) => void
152
+ warn: (msg: string) => void
153
+ error: (msg: string) => void
154
+ }
155
+ }
156
+
157
+ type SkryptPlugin = {
158
+ name: string
159
+ run: (context: PluginContext) => Promise<void>
160
+ }
161
+
162
+ // --- Self-contained PluginManager implementation ---
163
+
164
+ class PluginManager {
165
+ private plugins: SkryptPlugin[] = []
166
+ private context: PluginContext
167
+
168
+ constructor(
169
+ sourcePath: string,
170
+ outputPath: string,
171
+ config: Record<string, unknown> = {}
172
+ ) {
173
+ this.context = {
174
+ sourcePath,
175
+ outputPath,
176
+ config,
177
+ logger: {
178
+ info: (msg) => console.log(`[plugin] INFO: ${msg}`),
179
+ warn: (msg) => console.log(`[plugin] WARN: ${msg}`),
180
+ error: (msg) => console.log(`[plugin] ERROR: ${msg}`),
181
+ },
182
+ }
183
+ }
184
+
185
+ register(plugin: SkryptPlugin): void {
186
+ this.plugins.push(plugin)
187
+ this.context.logger.info(`Registered plugin: ${plugin.name}`)
188
+ }
189
+
190
+ async runAll(): Promise<void> {
191
+ for (const plugin of this.plugins) {
192
+ this.context.logger.info(`Running plugin: ${plugin.name}`)
193
+ await plugin.run(this.context)
194
+ }
195
+ }
196
+ }
197
+
198
+ // --- Example plugins ---
199
+
200
+ const markdownPlugin: SkryptPlugin = {
201
+ name: 'markdown-renderer',
202
+ run: async (ctx) => {
203
+ const outputFormat = ctx.config.outputFormat ?? 'markdown'
204
+ ctx.logger.info(`Rendering docs from "${ctx.sourcePath}" as ${outputFormat}`)
205
+ // Simulate async file processing
206
+ await new Promise((res) => setTimeout(res, 10))
207
+ ctx.logger.info(`Wrote output to "${ctx.outputPath}"`)
208
+ },
209
+ }
210
+
211
+ const lintPlugin: SkryptPlugin = {
212
+ name: 'doc-linter',
213
+ run: async (ctx) => {
214
+ const strict = ctx.config.strict ?? false
215
+ ctx.logger.info(`Linting docs (strict=${strict})`)
216
+ if (!strict) {
217
+ ctx.logger.warn('Strict mode is off — some issues may be skipped')
218
+ }
219
+ },
220
+ }
221
+
222
+ // --- Main usage ---
223
+
224
+ async function main() {
225
+ try {
226
+ const manager = new PluginManager(
227
+ './src',
228
+ './docs/output',
229
+ {
230
+ outputFormat: 'html',
231
+ strict: true,
232
+ }
233
+ )
234
+
235
+ manager.register(markdownPlugin)
236
+ manager.register(lintPlugin)
237
+
238
+ await manager.runAll()
239
+
240
+ console.log('\n✅ All plugins completed successfully.')
241
+ // Expected output:
242
+ // [plugin] INFO: Registered plugin: markdown-renderer
243
+ // [plugin] INFO: Registered plugin: doc-linter
244
+ // [plugin] INFO: Running plugin: markdown-renderer
245
+ // [plugin] INFO: Rendering docs from "./src" as html
246
+ // [plugin] INFO: Wrote output to "./docs/output"
247
+ // [plugin] INFO: Running plugin: doc-linter
248
+ // [plugin] INFO: Linting docs (strict=true)
249
+ // ✅ All plugins completed successfully.
250
+ } catch (error) {
251
+ console.error('Plugin pipeline failed:', error)
252
+ process.exit(1)
253
+ }
254
+ }
255
+
256
+ main()
257
+ ```
258
+
259
+ ### Related
260
+
261
+ <CardGroup cols={3}>
262
+ <Card title="constructor" icon="link" href="#constructor">
263
+ Uses
264
+ </Card>
265
+ <Card title="constructor" icon="link" href="#constructor">
266
+ Used by
267
+ </Card>
268
+ </CardGroup>
269
+
270
+ ---
271
+
272
+
273
+ ## `autoFixBatch`
274
+
275
+ ```typescript
276
+ async function autoFixBatch(examples: CodeExample[], client: LLMClient, options: AutoFixOptions = {}): Promise<Map<number, FixResult>>
277
+ ```
278
+
279
+ Use this to automatically fix multiple broken code examples in a single batch operation, getting back a map of results indexed by position.
280
+
281
+ This is the batch version of `autoFix` — ideal when you have a collection of code snippets that need validation and repair, such as processing documentation examples, test fixtures, or user-submitted code samples all at once.
282
+
283
+ ## Parameters
284
+
285
+ | Name | Type | Required | Description |
286
+ |------|------|----------|-------------|
287
+ | `examples` | `CodeExample[]` | ✅ | Array of code examples to fix. Each example contains the code string and metadata. |
288
+ | `client` | `LLMClient` | ✅ | LLM client instance used to generate fixes for broken examples. |
289
+ | `options` | `AutoFixOptions` | ❌ | Configuration options such as max retry attempts, timeout, and fix strategy. Defaults to `{}`. |
290
+
291
+ ## Returns
292
+
293
+ Returns `Promise<Map<number, FixResult>>` — a Map where:
294
+ - **Key**: the index of the example in the input array (0-based)
295
+ - **Value**: a `FixResult` object containing:
296
+ - `success: boolean` — whether the fix was applied successfully
297
+ - `fixedCode?: string` — the repaired code (present when `success` is `true`)
298
+ - `error?: string` — description of what went wrong (present when `success` is `false`)
299
+ - `attempts?: number` — how many fix attempts were made
300
+
301
+ Examples that could not be fixed will still appear in the map with `success: false`, so you can always check all inputs by iterating the original array indices.
302
+
303
+ ### Example
304
+
305
+ ```typescript example.ts
306
+ // ─── Inline types (do NOT import from autodocs) ───────────────────────────────
307
+
308
+ type CodeExample = {
309
+ code: string
310
+ language?: string
311
+ filename?: string
312
+ }
313
+
314
+ type FixResult = {
315
+ success: boolean
316
+ fixedCode?: string
317
+ error?: string
318
+ attempts?: number
319
+ }
320
+
321
+ type AutoFixOptions = {
322
+ maxAttempts?: number
323
+ timeoutMs?: number
324
+ verbose?: boolean
325
+ }
326
+
327
+ type LLMClient = {
328
+ complete: (prompt: string) => Promise<string>
329
+ }
330
+
331
+ // ─── Simulated autoFixBatch implementation ────────────────────────────────────
332
+
333
+ async function autoFixBatch(
334
+ examples: CodeExample[],
335
+ client: LLMClient,
336
+ options: AutoFixOptions = {}
337
+ ): Promise<Map<number, FixResult>> {
338
+ const { maxAttempts = 3, verbose = false } = options
339
+ const results = new Map<number, FixResult>()
340
+
341
+ for (let i = 0; i < examples.length; i++) {
342
+ const example = examples[i]
343
+
344
+ // Simulate a basic syntax check (real impl would use TypeScript compiler)
345
+ const hasSyntaxError = example.code.includes('???') || example.code.includes('BROKEN')
346
+
347
+ if (!hasSyntaxError) {
348
+ results.set(i, {
349
+ success: true,
350
+ fixedCode: example.code,
351
+ attempts: 1,
352
+ })
353
+ if (verbose) console.log(`[${i}] ✅ No fix needed`)
354
+ continue
355
+ }
356
+
357
+ try {
358
+ // Ask the LLM to fix the broken code
359
+ const prompt = `Fix this broken ${example.language ?? 'TypeScript'} code:\n\n${example.code}`
360
+ const fixedCode = await client.complete(prompt)
361
+
362
+ results.set(i, {
363
+ success: true,
364
+ fixedCode,
365
+ attempts: maxAttempts,
366
+ })
367
+ if (verbose) console.log(`[${i}] 🔧 Fixed after ${maxAttempts} attempt(s)`)
368
+ } catch (err) {
369
+ results.set(i, {
370
+ success: false,
371
+ error: err instanceof Error ? err.message : 'Unknown error',
372
+ attempts: maxAttempts,
373
+ })
374
+ if (verbose) console.log(`[${i}] ❌ Could not fix`)
375
+ }
376
+ }
377
+
378
+ return results
379
+ }
380
+
381
+ // ─── Mock LLM client ──────────────────────────────────────────────────────────
382
+
383
+ function createMockLLMClient(apiKey: string): LLMClient {
384
+ return {
385
+ complete: async (prompt: string): Promise<string> => {
386
+ // Simulate network latency
387
+ await new Promise((r) => setTimeout(r, 50))
388
+
389
+ // Simulate the LLM returning a corrected snippet
390
+ if (prompt.includes('BROKEN')) {
391
+ return `const greet = (name: string): string => \`Hello, \${name}!\``
392
+ }
393
+ throw new Error('LLM could not determine a fix')
394
+ },
395
+ }
396
+ }
397
+
398
+ // ─── Main usage ───────────────────────────────────────────────────────────────
399
+
400
+ const examples: CodeExample[] = [
401
+ {
402
+ // Valid example — should pass through unchanged
403
+ code: `const add = (a: number, b: number): number => a + b`,
404
+ language: 'typescript',
405
+ filename: 'math.ts',
406
+ },
407
+ {
408
+ // Broken example — LLM will fix it
409
+ code: `const greet = BROKEN (name: string) => \`Hello \${name}\``,
410
+ language: 'typescript',
411
+ filename: 'greet.ts',
412
+ },
413
+ {
414
+ // Unfixable example — LLM will throw
415
+ code: `const mystery = ???`,
416
+ language: 'typescript',
417
+ filename: 'mystery.ts',
418
+ },
419
+ ]
420
+
421
+ async function main() {
422
+ const apiKey = process.env.LLM_API_KEY || 'sk-demo-key-12345'
423
+ const client = createMockLLMClient(apiKey)
424
+
425
+ try {
426
+ const results = await autoFixBatch(examples, client, {
427
+ maxAttempts: 2,
428
+ verbose: true,
429
+ })
430
+
431
+ console.log('\n── Batch Fix Results ──────────────────────────────')
432
+ for (const [index, result] of results) {
433
+ const label = examples[index].filename ?? `example[${index}]`
434
+ if (result.success) {
435
+ console.log(`\n✅ ${label} (${result.attempts} attempt(s))`)
436
+ console.log(' Fixed code:', result.fixedCode)
437
+ } else {
438
+ console.log(`\n❌ ${label} — ${result.error}`)
439
+ }
440
+ }
441
+
442
+ // Summarise
443
+ const successCount = [...results.values()].filter((r) => r.success).length
444
+ console.log(`\n── Summary: ${successCount}/${examples.length} fixed successfully ──`)
445
+
446
+ // Expected output:
447
+ // [0] ✅ No fix needed
448
+ // [1] 🔧 Fixed after 2 attempt(s)
449
+ // [2] ❌ Could not fix
450
+ //
451
+ // ✅ math.ts (1 attempt(s)) → original code unchanged
452
+ // ✅ greet.ts (2 attempt(s)) → LLM-repaired code
453
+ // ❌ mystery.ts → LLM could not determine a fix
454
+ //
455
+ // Summary: 2/3 fixed successfully
456
+ } catch (error) {
457
+ console.error('Batch fix failed unexpectedly:', error)
458
+ process.exit(1)
459
+ }
460
+ }
461
+
462
+ main()
463
+ ```
464
+
465
+ ---
466
+
467
+
468
+ ## `autoFixExample`
469
+
470
+ ```typescript
471
+ async function autoFixExample(example: CodeExample, client: LLMClient, options: AutoFixOptions = {}): Promise<FixResult>
472
+ ```
473
+
474
+ Use this to automatically repair broken or invalid code examples by iteratively applying LLM-powered fixes until the code compiles and runs correctly.
475
+
476
+ This function takes a code example that may contain errors, submits it to an LLM client for analysis and correction, and retries up to a configurable number of times — returning the fixed code along with metadata about what changed and how many attempts it took.
477
+
478
+ ## Parameters
479
+
480
+ | Name | Type | Required | Description |
481
+ |------|------|----------|-------------|
482
+ | `example` | `CodeExample` | ✅ | The code example to fix, including source code, language, and any known error context |
483
+ | `client` | `LLMClient` | ✅ | An LLM client instance used to generate fixes (e.g., OpenAI, Anthropic wrapper) |
484
+ | `options` | `AutoFixOptions` | ❌ | Configuration options such as `maxIterations` (default: `3`) to control retry behavior |
485
+
486
+ ## Returns
487
+
488
+ Returns a `Promise<FixResult>` that resolves with:
489
+
490
+ | Field | Type | Description |
491
+ |-------|------|-------------|
492
+ | `success` | `boolean` | Whether the example was successfully fixed |
493
+ | `fixedCode` | `string \| null` | The corrected source code, or `null` if fixing failed |
494
+ | `iterations` | `number` | How many LLM fix attempts were made |
495
+ | `changes` | `string[]` | A list of descriptions of what was changed |
496
+ | `error` | `string \| undefined` | Error message if fixing ultimately failed after all iterations |
497
+
498
+ ### Example
499
+
500
+ ```typescript example.ts
501
+ // ---- Inline type definitions (no external imports needed) ----
502
+
503
+ type CodeExample = {
504
+ code: string
505
+ language: string
506
+ errorMessage?: string
507
+ }
508
+
509
+ type AutoFixOptions = {
510
+ maxIterations?: number
511
+ verbose?: boolean
512
+ }
513
+
514
+ type FixResult = {
515
+ success: boolean
516
+ fixedCode: string | null
517
+ iterations: number
518
+ changes: string[]
519
+ error?: string
520
+ }
521
+
522
+ type LLMClient = {
523
+ complete: (prompt: string) => Promise<string>
524
+ }
525
+
526
+ // ---- Simulated autoFixExample implementation ----
527
+
528
+ async function autoFixExample(
529
+ example: CodeExample,
530
+ client: LLMClient,
531
+ options: AutoFixOptions = {}
532
+ ): Promise<FixResult> {
533
+ const maxIterations = options.maxIterations ?? 3
534
+ const changes: string[] = []
535
+ let currentCode = example.code
536
+ let lastError = example.errorMessage
537
+
538
+ for (let i = 0; i < maxIterations; i++) {
539
+ const prompt = [
540
+ `Fix the following ${example.language} code.`,
541
+ lastError ? `Error: ${lastError}` : '',
542
+ `Code:\n${currentCode}`,
543
+ 'Return only the corrected code, no explanation.',
544
+ ]
545
+ .filter(Boolean)
546
+ .join('\n')
547
+
548
+ try {
549
+ const response = await client.complete(prompt)
550
+
551
+ if (options.verbose) {
552
+ console.log(`[Iteration ${i + 1}] LLM response received`)
553
+ }
554
+
555
+ // Simulate detecting a change
556
+ if (response !== currentCode) {
557
+ changes.push(`Iteration ${i + 1}: Applied LLM suggestion`)
558
+ currentCode = response
559
+ lastError = undefined // Assume fixed after each iteration
560
+ }
561
+
562
+ // Simulate a basic validation check (replace with real TS compile check)
563
+ const hasObviousError = currentCode.includes('SYNTAX_ERROR')
564
+ if (!hasObviousError) {
565
+ return {
566
+ success: true,
567
+ fixedCode: currentCode,
568
+ iterations: i + 1,
569
+ changes,
570
+ }
571
+ }
572
+ } catch (err) {
573
+ return {
574
+ success: false,
575
+ fixedCode: null,
576
+ iterations: i + 1,
577
+ changes,
578
+ error: err instanceof Error ? err.message : String(err),
579
+ }
580
+ }
581
+ }
582
+
583
+ return {
584
+ success: false,
585
+ fixedCode: null,
586
+ iterations: maxIterations,
587
+ changes,
588
+ error: `Could not fix example after ${maxIterations} iterations`,
589
+ }
590
+ }
591
+
592
+ // ---- Simulated LLM client ----
593
+
594
+ function createMockLLMClient(apiKey: string): LLMClient {
595
+ return {
596
+ complete: async (prompt: string): Promise<string> => {
597
+ // Simulate network latency
598
+ await new Promise((resolve) => setTimeout(resolve, 50))
599
+
600
+ // Simulate fixing a missing semicolon and undefined variable
601
+ if (prompt.includes('const x = undefinedVar')) {
602
+ return `const x = 42;\nconsole.log(x);`
603
+ }
604
+
605
+ return prompt.split('Code:\n')[1] ?? ''
606
+ },
607
+ }
608
+ }
609
+
610
+ // ---- Main usage ----
611
+
612
+ const brokenExample: CodeExample = {
613
+ code: `const x = undefinedVar\nconsole.log(x)`,
614
+ language: 'typescript',
615
+ errorMessage: "ReferenceError: undefinedVar is not defined",
616
+ }
617
+
618
+ async function main() {
619
+ const client = createMockLLMClient(
620
+ process.env.LLM_API_KEY || 'sk-your-api-key-here'
621
+ )
622
+
623
+ try {
624
+ const result = await autoFixExample(brokenExample, client, {
625
+ maxIterations: 3,
626
+ verbose: true,
627
+ })
628
+
629
+ if (result.success) {
630
+ console.log('✅ Fix succeeded!')
631
+ console.log('Fixed code:\n', result.fixedCode)
632
+ console.log('Iterations used:', result.iterations)
633
+ console.log('Changes applied:', result.changes)
634
+ // Output:
635
+ // ✅ Fix succeeded!
636
+ // Fixed code:
637
+ // const x = 42;
638
+ // console.log(x);
639
+ // Iterations used: 1
640
+ // Changes applied: [ 'Iteration 1: Applied LLM suggestion' ]
641
+ } else {
642
+ console.warn('❌ Could not fix example:', result.error)
643
+ console.log('Attempts made:', result.iterations)
644
+ }
645
+ } catch (error) {
646
+ console.error('Unexpected failure during autoFixExample:', error)
647
+ }
648
+ }
649
+
650
+ main()
651
+ ```
652
+
653
+ ---
654
+
655
+
656
+ ## `checkPlan`
657
+
658
+ ```typescript
659
+ async function checkPlan(apiKey: string): Promise<PlanCheckResponse>
660
+ ```
661
+
662
+ Use this to verify an API key's validity and retrieve the associated subscription plan details before making API calls or gating features by plan tier.
663
+
664
+ ## Parameters
665
+
666
+ | Name | Type | Required | Description |
667
+ |------|------|----------|-------------|
668
+ | `apiKey` | `string` | Yes | The Bearer token used to authenticate against the plan endpoint |
669
+
670
+ ## Returns
671
+
672
+ Returns a `Promise<PlanCheckResponse>` that resolves with the plan details for the provided API key.
673
+
674
+ | Scenario | Result |
675
+ |----------|--------|
676
+ | Valid API key | Resolves with plan info (e.g., tier, limits, expiry) |
677
+ | Invalid / expired key | Rejects or resolves with an error/unauthorized response |
678
+ | Network failure | Rejects with a fetch error |
679
+
680
+ ### Example
681
+
682
+ ```typescript example.ts
683
+ // Inline types — no external imports needed
684
+ type PlanCheckResponse = {
685
+ plan: 'free' | 'pro' | 'enterprise'
686
+ valid: boolean
687
+ requestsPerMonth: number
688
+ expiresAt: string | null
689
+ error?: string
690
+ }
691
+
692
+ // Simulated implementation matching the real function's behavior
693
+ async function checkPlan(apiKey: string): Promise<PlanCheckResponse> {
694
+ const API_BASE = 'https://api.supermemory.ai'
695
+
696
+ const response = await fetch(`${API_BASE}/v1/plan`, {
697
+ headers: {
698
+ 'Authorization': `Bearer ${apiKey}`,
699
+ 'Content-Type': 'application/json',
700
+ },
701
+ })
702
+
703
+ if (!response.ok) {
704
+ throw new Error(`Plan check failed: ${response.status} ${response.statusText}`)
705
+ }
706
+
707
+ return response.json() as Promise<PlanCheckResponse>
708
+ }
709
+
710
+ // --- Usage ---
711
+ async function main() {
712
+ const apiKey = process.env.SUPERMEMORY_API_KEY || 'sm_your_api_key_here'
713
+
714
+ try {
715
+ const planInfo = await checkPlan(apiKey)
716
+
717
+ if (!planInfo.valid) {
718
+ console.error('API key is invalid or expired.')
719
+ process.exit(1)
720
+ }
721
+
722
+ console.log('Plan details:', planInfo)
723
+ // Expected output:
724
+ // Plan details: {
725
+ // plan: 'pro',
726
+ // valid: true,
727
+ // requestsPerMonth: 50000,
728
+ // expiresAt: '2025-12-31T00:00:00.000Z'
729
+ // }
730
+
731
+ // Gate features by plan tier
732
+ if (planInfo.plan === 'enterprise') {
733
+ console.log('Enterprise features unlocked.')
734
+ } else if (planInfo.plan === 'pro') {
735
+ console.log(`Pro plan active — ${planInfo.requestsPerMonth.toLocaleString()} requests/month.`)
736
+ } else {
737
+ console.log('Free tier — consider upgrading for higher limits.')
738
+ }
739
+
740
+ } catch (error) {
741
+ console.error('Failed to check plan:', error instanceof Error ? error.message : error)
742
+ // Handle invalid keys or network issues gracefully
743
+ }
744
+ }
745
+
746
+ main()
747
+ ```
748
+
749
+ ---
750
+
751
+
752
+ ## `clearAuth`
753
+
754
+ ```typescript
755
+ async function clearAuth(): Promise<void>
756
+ ```
757
+
758
+ Use this to completely sign out a user by wiping all stored authentication credentials — both from the system keychain and any local auth config file.
759
+
760
+ This is the "logout" operation. It removes credentials from two places:
761
+ 1. **System keychain** (macOS Keychain, Linux Secret Service, Windows Credential Manager)
762
+ 2. **Local auth file** on disk (typically `~/.config/yourapp/auth.json` or similar)
763
+
764
+ After calling this, any subsequent authenticated requests will fail until the user logs in again.
765
+
766
+ ### Parameters
767
+
768
+ This function takes no parameters.
769
+
770
+ ### Returns
771
+
772
+ | Type | Description |
773
+ |------|-------------|
774
+ | `Promise<void>` | Resolves when all credentials have been cleared. Rejects if a critical error occurs during cleanup. |
775
+
776
+ > **Note:** If the auth file does not exist, the function completes silently without error — making it safe to call even when the user is already logged out.
777
+
778
+ ### Example
779
+
780
+ ```typescript example.ts
781
+ import { existsSync, unlinkSync, writeFileSync, mkdirSync } from 'fs'
782
+ import { join } from 'path'
783
+ import { homedir } from 'os'
784
+
785
+ // --- Inline simulation of keychain operations ---
786
+ const inMemoryKeychain: Record<string, string> = {
787
+ 'myapp/auth': 'super-secret-token-abc123'
788
+ }
789
+
790
+ async function keychainDelete(): Promise<void> {
791
+ delete inMemoryKeychain['myapp/auth']
792
+ console.log('[keychain] Credentials removed from system keychain')
793
+ }
794
+
795
+ // --- Inline simulation of clearAuth ---
796
+ const AUTH_FILE = join(homedir(), '.config', 'myapp-example', 'auth.json')
797
+
798
+ async function clearAuth(): Promise<void> {
799
+ // Step 1: Remove from system keychain
800
+ await keychainDelete()
801
+
802
+ // Step 2: Remove local auth file if it exists
803
+ if (existsSync(AUTH_FILE)) {
804
+ try {
805
+ unlinkSync(AUTH_FILE)
806
+ console.log(`[auth] Removed auth file: ${AUTH_FILE}`)
807
+ } catch (err) {
808
+ console.warn(`[auth] Could not remove auth file: ${(err as Error).message}`)
809
+ }
810
+ } else {
811
+ console.log('[auth] No auth file found — nothing to remove')
812
+ }
813
+ }
814
+
815
+ // --- Setup: write a fake auth file to demonstrate deletion ---
816
+ function setupFakeAuthFile(): void {
817
+ const dir = join(homedir(), '.config', 'myapp-example')
818
+ mkdirSync(dir, { recursive: true })
819
+ writeFileSync(
820
+ AUTH_FILE,
821
+ JSON.stringify({ token: 'super-secret-token-abc123', userId: 'usr_9f3kd82' }, null, 2),
822
+ { mode: 0o600 }
823
+ )
824
+ console.log(`[setup] Created fake auth file at: ${AUTH_FILE}`)
825
+ }
826
+
827
+ // --- Main: demonstrate clearAuth ---
828
+ async function main() {
829
+ try {
830
+ setupFakeAuthFile()
831
+
832
+ console.log('\nBefore clearAuth:')
833
+ console.log(' Auth file exists:', existsSync(AUTH_FILE))
834
+ console.log(' Keychain entry exists:', 'myapp/auth' in inMemoryKeychain)
835
+
836
+ console.log('\nCalling clearAuth()...')
837
+ await clearAuth()
838
+
839
+ console.log('\nAfter clearAuth:')
840
+ console.log(' Auth file exists:', existsSync(AUTH_FILE))
841
+ console.log(' Keychain entry exists:', 'myapp/auth' in inMemoryKeychain)
842
+
843
+ // Expected output:
844
+ // Before clearAuth:
845
+ // Auth file exists: true
846
+ // Keychain entry exists: true
847
+ // Calling clearAuth()...
848
+ // [keychain] Credentials removed from system keychain
849
+ // [auth] Removed auth file: /home/user/.config/myapp-example/auth.json
850
+ // After clearAuth:
851
+ // Auth file exists: false
852
+ // Keychain entry exists: false
853
+ } catch (error) {
854
+ console.error('Failed to clear auth:', (error as Error).message)
855
+ process.exit(1)
856
+ }
857
+ }
858
+
859
+ main()
860
+ ```
861
+
862
+ ### Related
863
+
864
+ <CardGroup cols={3}>
865
+ <Card title="keychainDelete" icon="link" href="#keychaindelete">
866
+ Uses
867
+ </Card>
868
+ </CardGroup>
869
+
870
+ ---
871
+
872
+
873
+ ## `createLLMClient`
874
+
875
+ ```typescript
876
+ function createLLMClient(config: {
877
+ provider: LLMProvider
878
+ model: string
879
+ baseUrl?: string
880
+ timeout?: number
881
+ maxRetries?: number
882
+ }): LLMClient
883
+ ```
884
+
885
+ Use this to initialize a typed LLM client for a specific AI provider (OpenAI, Anthropic, etc.) with built-in retry and timeout handling.
886
+
887
+ This is your entry point for all LLM interactions — call it once at startup, then reuse the returned client across your application to send prompts and receive completions.
888
+
889
+ ## Parameters
890
+
891
+ | Name | Type | Required | Description |
892
+ |------|------|----------|-------------|
893
+ | `config.provider` | `LLMProvider` | ✅ Yes | The AI provider to use. Accepted values: `"openai"`, `"anthropic"`, `"azure"` |
894
+ | `config.model` | `string` | ✅ Yes | The model identifier to target (e.g. `"gpt-4o"`, `"claude-3-5-sonnet-20241022"`) |
895
+ | `config.baseUrl` | `string` | No | Override the default API endpoint. Useful for proxies, local models, or Azure deployments |
896
+ | `config.timeout` | `number` | No | Request timeout in milliseconds. Defaults to `30000` (30s) |
897
+ | `config.maxRetries` | `number` | No | Number of times to retry on transient failures. Defaults to `2` |
898
+
899
+ ## Returns
900
+
901
+ Returns an `LLMClient` instance with methods to send chat/completion requests to the configured provider. The client is pre-configured with your credentials (read from environment variables), timeout, and retry logic.
902
+
903
+ > **Credentials:** The client automatically reads the appropriate API key from your environment (e.g. `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`) based on the `provider` value.
904
+
905
+ ### Example
906
+
907
+ ```typescript example.ts
908
+ // --- Inline types (mirrors the real library's shape) ---
909
+ type LLMProvider = "openai" | "anthropic" | "azure"
910
+
911
+ interface Message {
912
+ role: "user" | "assistant" | "system"
913
+ content: string
914
+ }
915
+
916
+ interface CompletionResponse {
917
+ id: string
918
+ content: string
919
+ model: string
920
+ usage: { promptTokens: number; completionTokens: number; totalTokens: number }
921
+ }
922
+
923
+ interface LLMClient {
924
+ complete(messages: Message[]): Promise<CompletionResponse>
925
+ provider: LLMProvider
926
+ model: string
927
+ }
928
+
929
+ interface LLMClientConfig {
930
+ provider: LLMProvider
931
+ model: string
932
+ baseUrl?: string
933
+ timeout?: number
934
+ maxRetries?: number
935
+ }
936
+
937
+ // --- Simulated createLLMClient (self-contained, no external imports) ---
938
+ function createLLMClient(config: LLMClientConfig): LLMClient {
939
+ const { provider, model, timeout = 30000, maxRetries = 2 } = config
940
+
941
+ const apiKeyMap: Record<LLMProvider, string> = {
942
+ openai: process.env.OPENAI_API_KEY || "sk-your-openai-key",
943
+ anthropic: process.env.ANTHROPIC_API_KEY || "sk-ant-your-anthropic-key",
944
+ azure: process.env.AZURE_OPENAI_API_KEY || "your-azure-key",
945
+ }
946
+
947
+ const apiKey = apiKeyMap[provider]
948
+ if (!apiKey) {
949
+ throw new Error(`No API key found for provider: ${provider}`)
950
+ }
951
+
952
+ console.log(`[createLLMClient] Initialized ${provider} client`)
953
+ console.log(` Model: ${model}`)
954
+ console.log(` Base URL: ${config.baseUrl ?? "(default)"}`)
955
+ console.log(` Timeout: ${timeout}ms`)
956
+ console.log(` Max Retries: ${maxRetries}`)
957
+
958
+ return {
959
+ provider,
960
+ model,
961
+ async complete(messages: Message[]): Promise<CompletionResponse> {
962
+ // Simulated response — real client would call the provider's API here
963
+ return {
964
+ id: "chatcmpl-abc123",
965
+ content: `Simulated response from ${provider}/${model} to: "${messages.at(-1)?.content}"`,
966
+ model,
967
+ usage: { promptTokens: 12, completionTokens: 28, totalTokens: 40 },
968
+ }
969
+ },
970
+ }
971
+ }
972
+
973
+ // --- Usage ---
974
+ async function main() {
975
+ try {
976
+ // Basic OpenAI setup
977
+ const openaiClient = createLLMClient({
978
+ provider: "openai",
979
+ model: "gpt-4o",
980
+ timeout: 15000,
981
+ maxRetries: 3,
982
+ })
983
+
984
+ const response = await openaiClient.complete([
985
+ { role: "system", content: "You are a helpful assistant." },
986
+ { role: "user", content: "What is the capital of France?" },
987
+ ])
988
+
989
+ console.log("\n--- Completion Response ---")
990
+ console.log("ID: ", response.id)
991
+ console.log("Content: ", response.content)
992
+ console.log("Tokens: ", response.usage.totalTokens)
993
+ // Output:
994
+ // ID: chatcmpl-abc123
995
+ // Content: Simulated response from openai/gpt-4o to: "What is the capital of France?"
996
+ // Tokens: 40
997
+
998
+ // Anthropic with a custom proxy base URL
999
+ const anthropicClient = createLLMClient({
1000
+ provider: "anthropic",
1001
+ model: "claude-3-5-sonnet-20241022",
1002
+ baseUrl: "https://my-proxy.internal/anthropic",
1003
+ timeout: 20000,
1004
+ })
1005
+
1006
+ console.log(`\nAnthropic client ready: ${anthropicClient.provider}/${anthropicClient.model}`)
1007
+ // Output: Anthropic client ready: anthropic/claude-3-5-sonnet-20241022
1008
+
1009
+ } catch (error) {
1010
+ console.error("Failed to create or use LLM client:", error)
1011
+ process.exit(1)
1012
+ }
1013
+ }
1014
+
1015
+ main()
1016
+ ```
1017
+
1018
+ ---
1019
+
1020
+
1021
+ ## `createPythonValidator`
1022
+
1023
+ ```typescript
1024
+ function createPythonValidator(): (code: string) => Promise<ValidationResult>
1025
+ ```
1026
+
1027
+ Use this to validate Python code syntax before executing or storing it — catches syntax errors without running the code.
1028
+
1029
+ `createPythonValidator` returns a reusable validator function that writes code to a temporary file and checks it with `python3 -m py_compile`. Requires `python3` to be available in your system `PATH`.
1030
+
1031
+ ## Parameters
1032
+
1033
+ The factory function takes no parameters. The returned validator accepts:
1034
+
1035
+ | Name | Type | Required | Description |
1036
+ |------|------|----------|-------------|
1037
+ | `code` | `string` | ✅ | The Python source code string to validate |
1038
+
1039
+ ## Returns
1040
+
1041
+ **`createPythonValidator()`** → `(code: string) => Promise<ValidationResult>`
1042
+
1043
+ Returns a validator function. Each call to that validator returns a `Promise<ValidationResult>`:
1044
+
1045
+ | Scenario | Result shape |
1046
+ |----------|-------------|
1047
+ | Valid Python syntax | `{ valid: true, errors: [] }` |
1048
+ | Syntax error detected | `{ valid: false, errors: [{ message: string, line?: number }] }` |
1049
+ | `python3` not found in PATH | `{ valid: false, errors: [{ message: 'python3 not found...' }] }` |
1050
+
1051
+ > **Requirement:** `python3` must be installed and accessible via `PATH`. The validator uses `python3 -m py_compile` internally and cleans up any temporary files after each check.
1052
+
1053
+ ### Example
1054
+
1055
+ ```typescript example.ts
1056
+ import { spawnSync } from 'child_process'
1057
+ import { writeFileSync, unlinkSync } from 'fs'
1058
+ import { join } from 'path'
1059
+ import { tmpdir } from 'os'
1060
+
1061
+ // Inline the ValidationResult type
1062
+ type ValidationError = {
1063
+ message: string
1064
+ line?: number
1065
+ }
1066
+
1067
+ type ValidationResult = {
1068
+ valid: boolean
1069
+ errors: ValidationError[]
1070
+ }
1071
+
1072
+ // Self-contained implementation of createPythonValidator
1073
+ function createPythonValidator(): (code: string) => Promise<ValidationResult> {
1074
+ return async (code: string): Promise<ValidationResult> => {
1075
+ const tmpFile = join(tmpdir(), `py_validate_${Date.now()}.py`)
1076
+
1077
+ try {
1078
+ writeFileSync(tmpFile, code, 'utf8')
1079
+
1080
+ const result = spawnSync('python3', ['-m', 'py_compile', tmpFile], {
1081
+ encoding: 'utf8',
1082
+ timeout: 10_000,
1083
+ })
1084
+
1085
+ if (result.error) {
1086
+ // python3 binary not found or failed to spawn
1087
+ return {
1088
+ valid: false,
1089
+ errors: [{ message: `python3 not found or failed to run: ${result.error.message}` }],
1090
+ }
1091
+ }
1092
+
1093
+ if (result.status !== 0) {
1094
+ // Parse error output like: " File "/tmp/py_validate.py", line 2\n ..."
1095
+ const stderr = result.stderr || ''
1096
+ const lineMatch = stderr.match(/line (\d+)/)
1097
+ const line = lineMatch ? parseInt(lineMatch[1], 10) : undefined
1098
+
1099
+ // Strip the temp file path from the message for cleaner output
1100
+ const cleanMessage = stderr.replace(tmpFile, '<code>').trim()
1101
+
1102
+ return {
1103
+ valid: false,
1104
+ errors: [{ message: cleanMessage, line }],
1105
+ }
1106
+ }
1107
+
1108
+ return { valid: true, errors: [] }
1109
+ } finally {
1110
+ // Always clean up the temp file
1111
+ try {
1112
+ unlinkSync(tmpFile)
1113
+ } catch {
1114
+ // Ignore cleanup errors
1115
+ }
1116
+ }
1117
+ }
1118
+ }
1119
+
1120
+ // --- Usage Example ---
1121
+
1122
+ async function main() {
1123
+ const validate = createPythonValidator()
1124
+
1125
+ // ✅ Valid Python code
1126
+ const validCode = `
1127
+ def greet(name: str) -> str:
1128
+ return f"Hello, {name}!"
1129
+
1130
+ print(greet("world"))
1131
+ `.trim()
1132
+
1133
+ const validResult = await validate(validCode)
1134
+ console.log('Valid code result:', validResult)
1135
+ // Output: { valid: true, errors: [] }
1136
+
1137
+ // ❌ Invalid Python code (missing colon after def)
1138
+ const invalidCode = `
1139
+ def greet(name)
1140
+ return "Hello, " + name
1141
+ `.trim()
1142
+
1143
+ const invalidResult = await validate(invalidCode)
1144
+ console.log('Invalid code result:', invalidResult)
1145
+ // Output: { valid: false, errors: [{ message: "...: invalid syntax (<code>, line 1)", line: 1 }] }
1146
+
1147
+ // Reuse the same validator for multiple checks (efficient)
1148
+ const snippets = [
1149
+ 'x = [i**2 for i in range(10)]', // valid
1150
+ 'if True print("oops")', // invalid - missing colon
1151
+ 'import os\nprint(os.getcwd())', // valid
1152
+ ]
1153
+
1154
+ console.log('\nBatch validation:')
1155
+ for (const snippet of snippets) {
1156
+ const { valid, errors } = await validate(snippet)
1157
+ console.log(` "${snippet.slice(0, 30)}..." → ${valid ? '✅ valid' : `❌ ${errors[0]?.message}`}`)
1158
+ }
1159
+ }
1160
+
1161
+ main().catch(console.error)
1162
+ ```
1163
+
1164
+ ---
1165
+
1166
+
1167
+ ## `createTypeScriptValidator`
1168
+
1169
+ ```typescript
1170
+ function createTypeScriptValidator(): (code: string) => Promise<ValidationResult>
1171
+ ```
1172
+
1173
+ Use this to validate TypeScript code strings at runtime — perfect for checking LLM-generated code, user-submitted snippets, or dynamically built TypeScript before execution.
1174
+
1175
+ Returns a validator function you call with any TypeScript code string. The validator uses the TypeScript compiler (`tsc`) under the hood to catch type errors, syntax issues, and compilation failures.
1176
+
1177
+ ## Parameters
1178
+
1179
+ This factory function takes no parameters.
1180
+
1181
+ ## Returns
1182
+
1183
+ | Value | Type | Description |
1184
+ |---|---|---|
1185
+ | `validator` | `(code: string) => Promise<ValidationResult>` | A reusable async function that accepts a TypeScript code string and resolves to a `ValidationResult` |
1186
+
1187
+ ### `ValidationResult` Shape
1188
+
1189
+ | Field | Type | Description |
1190
+ |---|---|---|
1191
+ | `valid` | `boolean` | `true` if the code compiled without errors |
1192
+ | `errors` | `string[]` | List of compiler error messages; empty array when `valid` is `true` |
1193
+
1194
+ ## Behavior
1195
+
1196
+ - **Valid code** → resolves with `{ valid: true, errors: [] }`
1197
+ - **Invalid code** → resolves with `{ valid: false, errors: ["TS2345: ...", ...] }`
1198
+ - **Runtime failure** (e.g. `typescript` not installed) → resolves with `{ valid: false, errors: ["<internal error message>"] }`
1199
+
1200
+ > **Tip:** Call `createTypeScriptValidator()` once and reuse the returned function across multiple validations to avoid repeated setup overhead.
1201
+
1202
+ ### Example
1203
+
1204
+ ```typescript example.ts
1205
+ import { transpileModule, type TranspileOptions } from 'typescript'
1206
+
1207
+ // --- Inline types (mirrors the real ValidationResult) ---
1208
+ interface ValidationResult {
1209
+ valid: boolean
1210
+ errors: string[]
1211
+ }
1212
+
1213
+ // --- Self-contained implementation mirroring the real function ---
1214
+ function createTypeScriptValidator(): (code: string) => Promise<ValidationResult> {
1215
+ return async (code: string): Promise<ValidationResult> => {
1216
+ try {
1217
+ const options: TranspileOptions = {
1218
+ compilerOptions: {
1219
+ strict: true,
1220
+ noImplicitAny: true,
1221
+ },
1222
+ reportDiagnostics: true,
1223
+ }
1224
+
1225
+ const result = transpileModule(code, options)
1226
+
1227
+ const errors =
1228
+ result.diagnostics
1229
+ ?.filter((d) => d.messageText)
1230
+ .map((d) =>
1231
+ typeof d.messageText === 'string'
1232
+ ? d.messageText
1233
+ : d.messageText.messageText
1234
+ ) ?? []
1235
+
1236
+ return {
1237
+ valid: errors.length === 0,
1238
+ errors,
1239
+ }
1240
+ } catch (error) {
1241
+ return {
1242
+ valid: false,
1243
+ errors: [error instanceof Error ? error.message : String(error)],
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+
1249
+ // --- Usage ---
1250
+ const validateTypeScript = createTypeScriptValidator()
1251
+
1252
+ async function main() {
1253
+ try {
1254
+ // ✅ Valid TypeScript
1255
+ const validCode = `
1256
+ const greet = (name: string): string => {
1257
+ return \`Hello, \${name}!\`
1258
+ }
1259
+ console.log(greet('world'))
1260
+ `
1261
+
1262
+ const validResult = await validateTypeScript(validCode)
1263
+ console.log('Valid code result:', validResult)
1264
+ // Output: { valid: true, errors: [] }
1265
+
1266
+ // ❌ Invalid TypeScript — wrong argument type
1267
+ const invalidCode = `
1268
+ const add = (a: number, b: number): number => a + b
1269
+ const result: number = add("not", "numbers")
1270
+ `
1271
+
1272
+ const invalidResult = await validateTypeScript(invalidCode)
1273
+ console.log('Invalid code result:', invalidResult)
1274
+ // Output: { valid: false, errors: ["Argument of type 'string' is not assignable..."] }
1275
+
1276
+ // ❌ Syntax error
1277
+ const brokenCode = `
1278
+ function broken( {
1279
+ return 42
1280
+ `
1281
+
1282
+ const brokenResult = await validateTypeScript(brokenCode)
1283
+ console.log('Broken code result:', brokenResult)
1284
+ // Output: { valid: false, errors: ["',' expected.", ...] }
1285
+ } catch (error) {
1286
+ console.error('Unexpected failure:', error)
1287
+ }
1288
+ }
1289
+
1290
+ main()
1291
+ ```
1292
+
1293
+ ---
1294
+
1295
+
1296
+ ## `definePlugin`
1297
+
1298
+ ```typescript
1299
+ function definePlugin(plugin: SkryptPlugin): SkryptPlugin
1300
+ ```
1301
+
1302
+ Use this to define a typed plugin configuration object for the Skrypt plugin system with full type safety and IDE autocompletion.
1303
+
1304
+ This is an identity function that acts as a **type helper** — it takes your plugin definition and returns it unchanged, but ensures your object conforms to the `SkryptPlugin` interface at compile time. Think of it as the Skrypt equivalent of Vue's `defineComponent` or Vite's `defineConfig`.
1305
+
1306
+ ## Parameters
1307
+
1308
+ | Name | Type | Required | Description |
1309
+ |------|------|----------|-------------|
1310
+ | `plugin` | `SkryptPlugin` | ✅ | The plugin configuration object to validate and return |
1311
+
1312
+ ## Returns
1313
+
1314
+ Returns the same `SkryptPlugin` object passed in, unchanged. The value is identical to the input — the benefit is purely type-level validation and editor tooling support.
1315
+
1316
+ **When to use this vs a plain object literal:**
1317
+ - Use `definePlugin(...)` when you want TypeScript to catch missing/invalid fields at the definition site
1318
+ - Use `definePlugin(...)` when you want autocomplete on plugin properties in your editor
1319
+ - A plain object works at runtime, but you lose type checking on the shape
1320
+
1321
+ ### Example
1322
+
1323
+ ```typescript example.ts
1324
+ // Inline the SkryptPlugin type (do not import from autodocs)
1325
+ type PluginHookContext = {
1326
+ cwd: string
1327
+ config: Record<string, unknown>
1328
+ }
1329
+
1330
+ type SkryptPlugin = {
1331
+ name: string
1332
+ version?: string
1333
+ description?: string
1334
+ setup?: (context: PluginHookContext) => void | Promise<void>
1335
+ teardown?: () => void | Promise<void>
1336
+ hooks?: {
1337
+ beforeBuild?: () => void | Promise<void>
1338
+ afterBuild?: () => void | Promise<void>
1339
+ }
1340
+ }
1341
+
1342
+ // Identity function — validates shape at compile time, returns plugin unchanged
1343
+ function definePlugin(plugin: SkryptPlugin): SkryptPlugin {
1344
+ return plugin
1345
+ }
1346
+
1347
+ // --- Define a plugin using the helper ---
1348
+
1349
+ const myAnalyticsPlugin = definePlugin({
1350
+ name: 'analytics-reporter',
1351
+ version: '1.2.0',
1352
+ description: 'Reports build metrics to an analytics endpoint',
1353
+
1354
+ setup: async (context) => {
1355
+ console.log(`[analytics-reporter] Initializing in: ${context.cwd}`)
1356
+ console.log(`[analytics-reporter] Config:`, context.config)
1357
+ },
1358
+
1359
+ hooks: {
1360
+ beforeBuild: async () => {
1361
+ console.log('[analytics-reporter] Build starting — recording timestamp')
1362
+ },
1363
+ afterBuild: async () => {
1364
+ const endpoint = process.env.ANALYTICS_URL || 'https://analytics.example.com/build'
1365
+ console.log(`[analytics-reporter] Sending metrics to ${endpoint}`)
1366
+ },
1367
+ },
1368
+
1369
+ teardown: async () => {
1370
+ console.log('[analytics-reporter] Cleaning up resources')
1371
+ },
1372
+ })
1373
+
1374
+ // --- Simulate plugin lifecycle ---
1375
+
1376
+ async function runPluginLifecycle(plugin: SkryptPlugin) {
1377
+ try {
1378
+ console.log(`\nLoading plugin: "${plugin.name}" v${plugin.version ?? 'unknown'}`)
1379
+ console.log(`Description: ${plugin.description ?? 'N/A'}`)
1380
+
1381
+ const context: PluginHookContext = {
1382
+ cwd: process.cwd(),
1383
+ config: { outputDir: 'dist', minify: true },
1384
+ }
1385
+
1386
+ await plugin.setup?.(context)
1387
+ await plugin.hooks?.beforeBuild?.()
1388
+ await plugin.hooks?.afterBuild?.()
1389
+ await plugin.teardown?.()
1390
+
1391
+ console.log(`\nPlugin "${plugin.name}" lifecycle complete.`)
1392
+ // Output:
1393
+ // Loading plugin: "analytics-reporter" v1.2.0
1394
+ // Description: Reports build metrics to an analytics endpoint
1395
+ // [analytics-reporter] Initializing in: /your/project
1396
+ // [analytics-reporter] Config: { outputDir: 'dist', minify: true }
1397
+ // [analytics-reporter] Build starting — recording timestamp
1398
+ // [analytics-reporter] Sending metrics to https://analytics.example.com/build
1399
+ // [analytics-reporter] Cleaning up resources
1400
+ // Plugin "analytics-reporter" lifecycle complete.
1401
+ } catch (error) {
1402
+ console.error(`Plugin "${plugin.name}" failed:`, error)
1403
+ }
1404
+ }
1405
+
1406
+ runPluginLifecycle(myAnalyticsPlugin)
1407
+ ```
1408
+
1409
+ ---
1410
+
1411
+
1412
+ ## `fixCodeSample`
1413
+
1414
+ ```typescript
1415
+ async function fixCodeSample(client: LLMClient, code: string, error: string, context: string, iteration: number = 1): Promise<string>
1416
+ ```
1417
+
1418
+ Use this to automatically repair broken code samples by sending them to an LLM with the error context and receiving a corrected version. Ideal for documentation pipelines, code generation workflows, or CI systems that validate and self-heal code examples.
1419
+
1420
+ ## Parameters
1421
+
1422
+ | Name | Type | Required | Description |
1423
+ |------|------|----------|-------------|
1424
+ | `client` | `LLMClient` | ✅ | An initialized LLM client instance used to generate the fix |
1425
+ | `code` | `string` | ✅ | The broken code sample that needs to be repaired |
1426
+ | `error` | `string` | ✅ | The error message or stack trace produced by the broken code |
1427
+ | `context` | `string` | ✅ | Additional context about what the code is supposed to do (e.g., function docs, expected behavior) |
1428
+ | `iteration` | `number` | ❌ | Which fix attempt this is (default: `1`). Used to apply progressively smarter strategies on repeated failures |
1429
+
1430
+ ## Returns
1431
+
1432
+ Returns a `Promise<string>` containing the corrected code sample. The returned string is the fixed code only — ready to replace the original broken sample.
1433
+
1434
+ ## Notes
1435
+
1436
+ - The `iteration` parameter enables escalating fix strategies: pass `2` or `3` on retry loops to signal that simpler fixes have already been attempted
1437
+ - The `context` parameter significantly improves fix quality — include the function signature, docstring, and expected output
1438
+ - The returned code may still need validation; use this inside a retry loop with a code runner for best results
1439
+
1440
+ ### Example
1441
+
1442
+ ```typescript example.ts
1443
+ // ── Inline types (do not import from autodocs) ──────────────────────────────
1444
+ interface LLMMessage {
1445
+ role: 'system' | 'user' | 'assistant'
1446
+ content: string
1447
+ }
1448
+
1449
+ interface LLMClient {
1450
+ complete(messages: LLMMessage[]): Promise<string>
1451
+ }
1452
+
1453
+ // ── Simulated LLM client (replace with your real client in production) ───────
1454
+ function createMockLLMClient(apiKey: string): LLMClient {
1455
+ return {
1456
+ async complete(messages: LLMMessage[]): Promise<string> {
1457
+ // In production this would call OpenAI / Anthropic / etc.
1458
+ console.log(`[LLMClient] Sending ${messages.length} messages to API...`)
1459
+ console.log(`[LLMClient] Using API key: ${apiKey.slice(0, 8)}...`)
1460
+
1461
+ // Simulate a fixed version of the broken code
1462
+ return `
1463
+ async function fetchUser(userId: string) {
1464
+ try {
1465
+ const response = await fetch(\`https://api.example.com/users/\${userId}\`)
1466
+ if (!response.ok) throw new Error(\`HTTP error: \${response.status}\`)
1467
+ const data = await response.json()
1468
+ console.log('User:', data)
1469
+ return data
1470
+ } catch (error) {
1471
+ console.error('Failed to fetch user:', error)
1472
+ throw error
1473
+ }
1474
+ }
1475
+
1476
+ fetchUser('user_abc123')
1477
+ `.trim()
1478
+ },
1479
+ }
1480
+ }
1481
+
1482
+ // ── Inline implementation of fixCodeSample ───────────────────────────────────
1483
+ async function fixCodeSample(
1484
+ client: LLMClient,
1485
+ code: string,
1486
+ error: string,
1487
+ context: string,
1488
+ iteration: number = 1
1489
+ ): Promise<string> {
1490
+ const strategyHint =
1491
+ iteration === 1
1492
+ ? 'Fix the specific error shown.'
1493
+ : iteration === 2
1494
+ ? 'The previous fix did not work. Try a different approach and simplify the code.'
1495
+ : 'Multiple fixes have failed. Rewrite the example from scratch using only the context provided.'
1496
+
1497
+ const messages: LLMMessage[] = [
1498
+ {
1499
+ role: 'system',
1500
+ content:
1501
+ 'You are an expert code repair assistant. Return ONLY the corrected code with no explanation, markdown fences, or commentary.',
1502
+ },
1503
+ {
1504
+ role: 'user',
1505
+ content: `
1506
+ ## Context
1507
+ ${context}
1508
+
1509
+ ## Broken Code
1510
+ \`\`\`
1511
+ ${code}
1512
+ \`\`\`
1513
+
1514
+ ## Error
1515
+ \`\`\`
1516
+ ${error}
1517
+ \`\`\`
1518
+
1519
+ ## Instructions
1520
+ Fix attempt #${iteration}. ${strategyHint}
1521
+ Return only the corrected code.
1522
+ `.trim(),
1523
+ },
1524
+ ]
1525
+
1526
+ const fixed = await client.complete(messages)
1527
+ return fixed.trim()
1528
+ }
1529
+
1530
+ // ── Example usage ─────────────────────────────────────────────────────────────
1531
+ const brokenCode = `
1532
+ async function fetchUser(userId: string) {
1533
+ const response = await fetch('https://api.example.com/users/' + userId)
1534
+ const data = response.json() // ← missing await
1535
+ console.log('User:', data)
1536
+ return data
1537
+ }
1538
+
1539
+ fetchUser('user_abc123')
1540
+ `.trim()
1541
+
1542
+ const errorMessage = `
1543
+ TypeError: data.name is undefined
1544
+ at fetchUser (example.ts:4:22)
1545
+ `.trim()
1546
+
1547
+ const contextDescription = `
1548
+ fetchUser(userId: string): Promise<User>
1549
+ Fetches a user object from the REST API by ID.
1550
+ Expected output: logs the user object and returns it.
1551
+ The function must handle HTTP errors and use proper async/await.
1552
+ `.trim()
1553
+
1554
+ async function main() {
1555
+ const apiKey = process.env.OPENAI_API_KEY || 'sk-your-api-key-here'
1556
+ const client = createMockLLMClient(apiKey)
1557
+
1558
+ try {
1559
+ console.log('=== Attempt 1 ===')
1560
+ const fixedCode = await fixCodeSample(
1561
+ client,
1562
+ brokenCode,
1563
+ errorMessage,
1564
+ contextDescription,
1565
+ 1 // first attempt — targeted fix
1566
+ )
1567
+ console.log('\nFixed code:\n')
1568
+ console.log(fixedCode)
1569
+
1570
+ // Simulate a retry with escalated strategy
1571
+ console.log('\n=== Attempt 2 (retry with different strategy) ===')
1572
+ const fixedCodeRetry = await fixCodeSample(
1573
+ client,
1574
+ brokenCode,
1575
+ errorMessage,
1576
+ contextDescription,
1577
+ 2 // second attempt — broader rewrite
1578
+ )
1579
+ console.log('\nFixed code (retry):\n')
1580
+ console.log(fixedCodeRetry)
1581
+
1582
+ // Expected output:
1583
+ // A corrected version of fetchUser with proper `await response.json()`
1584
+ // and error handling, ready to replace the broken sample.
1585
+ } catch (error) {
1586
+ console.error('fixCodeSample failed:', error)
1587
+ process.exit(1)
1588
+ }
1589
+ }
1590
+
1591
+ main()
1592
+ ```
1593
+
1594
+ ---
1595
+
1596
+
1597
+ ## `generateDocumentation`
1598
+
1599
+ ```typescript
1600
+ async function generateDocumentation(client: LLMClient, element: ElementContext, options?: { multiLanguage?: boolean }): Promise<GeneratedDocResult>
1601
+ ```
1602
+
1603
+ Use this to automatically generate documentation for a code element (function, class, method, etc.) using an LLM client, with optional multi-language support.
1604
+
1605
+ This is the primary entry point for producing structured documentation output from parsed code context. Pass in your configured LLM client and a description of the code element, and receive a ready-to-use documentation result.
1606
+
1607
+ ## Parameters
1608
+
1609
+ | Name | Type | Required | Description |
1610
+ |------|------|----------|-------------|
1611
+ | `client` | `LLMClient` | ✅ Yes | A configured LLM client instance used to generate the documentation |
1612
+ | `element` | `ElementContext` | ✅ Yes | Context object describing the code element to document (name, signature, source, etc.) |
1613
+ | `options` | `{ multiLanguage?: boolean }` | ❌ No | Optional settings. `multiLanguage` defaults to `true` — when enabled, generates code examples in multiple languages |
1614
+
1615
+ ## Returns
1616
+
1617
+ Returns a `Promise<GeneratedDocResult>` that resolves to a structured documentation object. The exact shape depends on the element type, but typically includes:
1618
+
1619
+ | Field | Description |
1620
+ |-------|-------------|
1621
+ | `markdown` | The generated documentation in Markdown format |
1622
+ | `code` | A self-contained code example |
1623
+ | `summary` | A short plain-text summary of the element |
1624
+
1625
+ Rejects with an error if the LLM client fails or the element context is malformed.
1626
+
1627
+ ### Example
1628
+
1629
+ ```typescript example.ts
1630
+ // ─── Inline type definitions (no external imports needed) ───────────────────
1631
+
1632
+ type LLMMessage = { role: 'user' | 'assistant' | 'system'; content: string }
1633
+
1634
+ interface LLMClient {
1635
+ complete(messages: LLMMessage[]): Promise<string>
1636
+ }
1637
+
1638
+ interface ElementContext {
1639
+ name: string
1640
+ signature: string
1641
+ language: string
1642
+ sourceCode: string
1643
+ docstring?: string
1644
+ }
1645
+
1646
+ interface GeneratedDocResult {
1647
+ markdown: string
1648
+ code: string
1649
+ summary: string
1650
+ }
1651
+
1652
+ // ─── Simulated generateDocumentation (mirrors real behavior) ─────────────────
1653
+
1654
+ async function generateDocumentation(
1655
+ client: LLMClient,
1656
+ element: ElementContext,
1657
+ options?: { multiLanguage?: boolean }
1658
+ ): Promise<GeneratedDocResult> {
1659
+ const useMultiLang = options?.multiLanguage ?? true
1660
+
1661
+ const prompt = [
1662
+ `Generate documentation for the following ${element.language} code element.`,
1663
+ `Name: ${element.name}`,
1664
+ `Signature: ${element.signature}`,
1665
+ useMultiLang
1666
+ ? 'Include code examples in multiple languages (TypeScript, Python, Go).'
1667
+ : 'Include a code example in the original language only.',
1668
+ '',
1669
+ 'Source:',
1670
+ element.sourceCode,
1671
+ element.docstring ? `\nExisting docstring: ${element.docstring}` : '',
1672
+ ].join('\n')
1673
+
1674
+ const raw = await client.complete([{ role: 'user', content: prompt }])
1675
+
1676
+ // In the real implementation, the response is parsed into structured fields.
1677
+ // Here we simulate that parsing step.
1678
+ return {
1679
+ markdown: `## \`${element.name}\`\n\n${raw}`,
1680
+ code: `// Example usage of ${element.name}\nconst result = await ${element.name}(client, element)`,
1681
+ summary: `Generates documentation for \`${element.name}\` using an LLM.`,
1682
+ }
1683
+ }
1684
+
1685
+ // ─── Simulated LLM client (replace with a real client in production) ──────────
1686
+
1687
+ function createMockLLMClient(apiKey: string): LLMClient {
1688
+ console.log(`LLM client initialized (key: ${apiKey.slice(0, 8)}...)`)
1689
+ return {
1690
+ async complete(messages: LLMMessage[]): Promise<string> {
1691
+ // Simulate an LLM response
1692
+ const userMessage = messages.find((m) => m.role === 'user')?.content ?? ''
1693
+ return [
1694
+ 'Use this to calculate the total price including tax.',
1695
+ '',
1696
+ '**Parameters:** `price: number`, `taxRate: number`',
1697
+ '**Returns:** `number` — the final price after tax is applied.',
1698
+ '',
1699
+ userMessage.includes('multiple languages')
1700
+ ? '```typescript\nconst total = calculateTotal(100, 0.08) // 108\n```'
1701
+ : '```python\ntotal = calculate_total(100, 0.08) # 108.0\n```',
1702
+ ].join('\n')
1703
+ },
1704
+ }
1705
+ }
1706
+
1707
+ // ─── Main usage example ───────────────────────────────────────────────────────
1708
+
1709
+ async function main() {
1710
+ const apiKey = process.env.OPENAI_API_KEY || 'sk-your-api-key-here'
1711
+ const client = createMockLLMClient(apiKey)
1712
+
1713
+ const element: ElementContext = {
1714
+ name: 'calculateTotal',
1715
+ signature: 'function calculateTotal(price: number, taxRate: number): number',
1716
+ language: 'TypeScript',
1717
+ sourceCode: `
1718
+ function calculateTotal(price: number, taxRate: number): number {
1719
+ return price + price * taxRate
1720
+ }`.trim(),
1721
+ docstring: 'Calculates the total price after applying tax.',
1722
+ }
1723
+
1724
+ try {
1725
+ // Generate docs with multi-language examples (default behavior)
1726
+ const result = await generateDocumentation(client, element, {
1727
+ multiLanguage: true,
1728
+ })
1729
+
1730
+ console.log('=== Generated Documentation ===')
1731
+ console.log('\n[Markdown]\n', result.markdown)
1732
+ console.log('\n[Code Example]\n', result.code)
1733
+ console.log('\n[Summary]\n', result.summary)
1734
+
1735
+ // Generate docs with single-language example
1736
+ const singleLang = await generateDocumentation(client, element, {
1737
+ multiLanguage: false,
1738
+ })
1739
+ console.log('\n=== Single-Language Mode ===')
1740
+ console.log('[Markdown]\n', singleLang.markdown)
1741
+
1742
+ // Expected output shape:
1743
+ // {
1744
+ // markdown: "## `calculateTotal`\n\nUse this to calculate...",
1745
+ // code: "// Example usage of calculateTotal\nconst result = ...",
1746
+ // summary: "Generates documentation for `calculateTotal` using an LLM."
1747
+ // }
1748
+ } catch (error) {
1749
+ if (error instanceof Error) {
1750
+ console.error('Documentation generation failed:', error.message)
1751
+ } else {
1752
+ console.error('Unexpected error:', error)
1753
+ }
1754
+ process.exit(1)
1755
+ }
1756
+ }
1757
+
1758
+ main()
1759
+ ```
1760
+
1761
+ ---
1762
+
1763
+
1764
+ ## `getAuthConfig`
1765
+
1766
+ ```typescript
1767
+ function getAuthConfig(): AuthConfig
1768
+ ```
1769
+
1770
+ Use this to synchronously retrieve the current authentication configuration from environment variables or a local auth file — without touching the keychain.
1771
+
1772
+ This is the fast, synchronous option for reading auth state. It checks `SKRYPT_API_KEY` env var first, then falls back to a local auth file. When you need keychain access (e.g., in interactive CLI flows), use `getAuthConfigAsync()` instead.
1773
+
1774
+ **Common use cases:**
1775
+ - Reading auth config at startup in a script or server process
1776
+ - Checking if a user is authenticated before making API calls
1777
+ - CI/CD environments where the API key is injected via environment variable
1778
+
1779
+ ## Parameters
1780
+
1781
+ This function takes no parameters.
1782
+
1783
+ ## Returns
1784
+
1785
+ Returns an `AuthConfig` object:
1786
+
1787
+ | Field | Type | Always Present | Description |
1788
+ |-------|------|----------------|-------------|
1789
+ | `apiKey` | `string \| undefined` | No | The API key, sourced from `SKRYPT_API_KEY` env var or auth file |
1790
+ | `email` | `string \| undefined` | No | The user's email address, sourced from the auth file |
1791
+
1792
+ Returns an empty/partial `AuthConfig` (with `undefined` fields) if no credentials are found — it does **not** throw.
1793
+
1794
+ ## Source Priority
1795
+
1796
+ | Priority | Source | Notes |
1797
+ |----------|--------|-------|
1798
+ | 1st | `SKRYPT_API_KEY` env var | Always checked first |
1799
+ | 2nd | Local auth file | `~/.skrypt/auth.json` or similar |
1800
+ | ❌ | Keychain | Never accessed — use `getAuthConfigAsync()` for this |
1801
+
1802
+ ### Example
1803
+
1804
+ ```typescript example.ts
1805
+ // Inline types — no external imports needed
1806
+ type AuthConfig = {
1807
+ apiKey: string | undefined
1808
+ email: string | undefined
1809
+ }
1810
+
1811
+ // Simulate the auth file structure stored on disk
1812
+ type AuthFileMeta = {
1813
+ apiKey?: string
1814
+ email?: string
1815
+ }
1816
+
1817
+ // Simulate reading a local auth file (e.g., ~/.skrypt/auth.json)
1818
+ function readAuthFile(): AuthFileMeta {
1819
+ // In real usage, this reads from the filesystem
1820
+ // Here we simulate a stored auth file with example data
1821
+ return {
1822
+ apiKey: 'sk_file_abc123xyz',
1823
+ email: 'developer@example.com',
1824
+ }
1825
+ }
1826
+
1827
+ // Self-contained implementation of getAuthConfig
1828
+ function getAuthConfig(): AuthConfig {
1829
+ // Priority 1: environment variable
1830
+ if (process.env.SKRYPT_API_KEY) {
1831
+ const fileMeta = readAuthFile()
1832
+ return {
1833
+ apiKey: process.env.SKRYPT_API_KEY,
1834
+ email: fileMeta.email,
1835
+ }
1836
+ }
1837
+
1838
+ // Priority 2: local auth file
1839
+ const fileMeta = readAuthFile()
1840
+ if (fileMeta.apiKey) {
1841
+ return {
1842
+ apiKey: fileMeta.apiKey,
1843
+ email: fileMeta.email,
1844
+ }
1845
+ }
1846
+
1847
+ // No credentials found — return empty config (does not throw)
1848
+ return {
1849
+ apiKey: undefined,
1850
+ email: undefined,
1851
+ }
1852
+ }
1853
+
1854
+ // --- Usage ---
1855
+
1856
+ // Scenario 1: API key set via environment variable (common in CI/CD)
1857
+ process.env.SKRYPT_API_KEY = process.env.SKRYPT_API_KEY || 'sk_live_9f8e7d6c5b4a'
1858
+
1859
+ const config = getAuthConfig()
1860
+
1861
+ if (!config.apiKey) {
1862
+ console.error('Not authenticated. Run `skrypt login` or set SKRYPT_API_KEY.')
1863
+ process.exit(1)
1864
+ }
1865
+
1866
+ console.log('Auth config loaded:')
1867
+ console.log(` API Key: ${config.apiKey}`)
1868
+ console.log(` Email: ${config.email ?? '(not set)'}`)
1869
+ // Output:
1870
+ // Auth config loaded:
1871
+ // API Key: sk_live_9f8e7d6c5b4a
1872
+ // Email: developer@example.com
1873
+
1874
+ // Scenario 2: No env var — falls back to auth file
1875
+ delete process.env.SKRYPT_API_KEY
1876
+
1877
+ const configFromFile = getAuthConfig()
1878
+ console.log('\nFallback to auth file:')
1879
+ console.log(` API Key: ${configFromFile.apiKey}`)
1880
+ console.log(` Email: ${configFromFile.email}`)
1881
+ // Output:
1882
+ // Fallback to auth file:
1883
+ // API Key: sk_file_abc123xyz
1884
+ // Email: developer@example.com
1885
+
1886
+ // Scenario 3: No credentials anywhere
1887
+ function getAuthConfigEmpty(): AuthConfig {
1888
+ return { apiKey: undefined, email: undefined }
1889
+ }
1890
+
1891
+ const empty = getAuthConfigEmpty()
1892
+ console.log('\nNo credentials found:', empty.apiKey ?? 'unauthenticated')
1893
+ // Output:
1894
+ // No credentials found: unauthenticated
1895
+ ```
1896
+
1897
+ ### Related
1898
+
1899
+ <CardGroup cols={3}>
1900
+ <Card title="getAuthConfigAsync" icon="link" href="#getauthconfigasync">
1901
+ Uses
1902
+ </Card>
1903
+ <Card title="getAuthConfigAsync" icon="link" href="#getauthconfigasync">
1904
+ Used by
1905
+ </Card>
1906
+ <Card title="requirePro" icon="link" href="#requirepro">
1907
+ Used by
1908
+ </Card>
1909
+ </CardGroup>
1910
+
1911
+ ---
1912
+
1913
+
1914
+ ## `getAuthConfigAsync`
1915
+
1916
+ ```typescript
1917
+ async function getAuthConfigAsync(): Promise<AuthConfig>
1918
+ ```
1919
+
1920
+ Use this to retrieve the current user's authentication configuration in any CLI command action. It automatically resolves credentials by checking three sources in priority order: environment variable → system keychain → auth file on disk.
1921
+
1922
+ This is the **preferred method** for reading auth in all command handlers — it handles all credential sources transparently so you don't need to check each one manually.
1923
+
1924
+ ## Returns
1925
+
1926
+ | Condition | Result |
1927
+ |---|---|
1928
+ | `SKRYPT_API_KEY` env var is set | Returns `AuthConfig` with API key from env + email from auth file |
1929
+ | Env var not set, keychain has credentials | Returns `AuthConfig` from keychain |
1930
+ | Neither env nor keychain, auth file exists | Returns `AuthConfig` read from auth file |
1931
+ | No credentials found anywhere | Throws or returns empty/null config |
1932
+
1933
+ ## AuthConfig Shape
1934
+
1935
+ | Field | Type | Description |
1936
+ |---|---|---|
1937
+ | `apiKey` | `string` | The API key used to authenticate requests |
1938
+ | `email` | `string \| undefined` | The user's email address, if stored |
1939
+
1940
+ ## Resolution Priority
1941
+
1942
+ ```text
1943
+ SKRYPT_API_KEY (env var)
1944
+ ↓ not found
1945
+ System Keychain
1946
+ ↓ not found
1947
+ ~/.config/skrypt/auth (file)
1948
+ ```
1949
+
1950
+ ### Example
1951
+
1952
+ ```typescript example.ts
1953
+ // Inline types — no external imports needed
1954
+ type AuthConfig = {
1955
+ apiKey: string
1956
+ email?: string
1957
+ }
1958
+
1959
+ type AuthFileMeta = {
1960
+ email?: string
1961
+ apiKey?: string
1962
+ }
1963
+
1964
+ // --- Simulated dependencies (stand-ins for fs, keychain, etc.) ---
1965
+
1966
+ const mockAuthFile: AuthFileMeta = {
1967
+ email: 'jane@example.com',
1968
+ apiKey: 'file-key-abc123',
1969
+ }
1970
+
1971
+ const mockKeychain: AuthFileMeta = {
1972
+ email: 'jane@example.com',
1973
+ apiKey: 'keychain-key-xyz789',
1974
+ }
1975
+
1976
+ function readAuthFile(): AuthFileMeta {
1977
+ // In real usage: reads from ~/.config/skrypt/auth
1978
+ return mockAuthFile
1979
+ }
1980
+
1981
+ async function keychainRetrieve(): Promise<AuthFileMeta | null> {
1982
+ // In real usage: reads from OS keychain (macOS Keychain, etc.)
1983
+ return mockKeychain
1984
+ }
1985
+
1986
+ // --- Self-contained implementation of getAuthConfigAsync ---
1987
+
1988
+ async function getAuthConfigAsync(): Promise<AuthConfig> {
1989
+ // Priority 1: Environment variable
1990
+ if (process.env.SKRYPT_API_KEY) {
1991
+ const fileMeta = readAuthFile()
1992
+ return {
1993
+ apiKey: process.env.SKRYPT_API_KEY,
1994
+ email: fileMeta.email,
1995
+ }
1996
+ }
1997
+
1998
+ // Priority 2: System keychain
1999
+ const keychainMeta = await keychainRetrieve()
2000
+ if (keychainMeta?.apiKey) {
2001
+ return {
2002
+ apiKey: keychainMeta.apiKey,
2003
+ email: keychainMeta.email,
2004
+ }
2005
+ }
2006
+
2007
+ // Priority 3: Auth file on disk
2008
+ const fileMeta = readAuthFile()
2009
+ if (fileMeta?.apiKey) {
2010
+ return {
2011
+ apiKey: fileMeta.apiKey,
2012
+ email: fileMeta.email,
2013
+ }
2014
+ }
2015
+
2016
+ throw new Error(
2017
+ 'No credentials found. Run `skrypt login` or set SKRYPT_API_KEY.'
2018
+ )
2019
+ }
2020
+
2021
+ // --- Usage example ---
2022
+
2023
+ async function main() {
2024
+ try {
2025
+ // Scenario A: env var is set (highest priority)
2026
+ process.env.SKRYPT_API_KEY = process.env.SKRYPT_API_KEY || 'env-key-sk_live_9f3kd82'
2027
+
2028
+ const authConfig = await getAuthConfigAsync()
2029
+
2030
+ console.log('Resolved auth config:')
2031
+ console.log(' API Key:', authConfig.apiKey)
2032
+ console.log(' Email: ', authConfig.email ?? '(not stored)')
2033
+ // Output:
2034
+ // API Key: env-key-sk_live_9f3kd82
2035
+ // Email: jane@example.com
2036
+
2037
+ // Scenario B: no env var — falls through to keychain
2038
+ delete process.env.SKRYPT_API_KEY
2039
+ const authFromKeychain = await getAuthConfigAsync()
2040
+ console.log('\nWithout env var (keychain fallback):')
2041
+ console.log(' API Key:', authFromKeychain.apiKey)
2042
+ // Output:
2043
+ // API Key: keychain-key-xyz789
2044
+
2045
+ } catch (error) {
2046
+ if (error instanceof Error) {
2047
+ console.error('Auth error:', error.message)
2048
+ // Handle: prompt user to run `skrypt login`
2049
+ }
2050
+ }
2051
+ }
2052
+
2053
+ main()
2054
+ ```
2055
+
2056
+ ### Related
2057
+
2058
+ <CardGroup cols={3}>
2059
+ <Card title="getAuthConfig" icon="link" href="#getauthconfig">
2060
+ Uses
2061
+ </Card>
2062
+ <Card title="getAuthConfig" icon="link" href="#getauthconfig">
2063
+ Used by
2064
+ </Card>
2065
+ <Card title="requirePro" icon="link" href="#requirepro">
2066
+ Used by
2067
+ </Card>
2068
+ </CardGroup>
2069
+
2070
+ ---
2071
+
2072
+
2073
+ ## `getKeyStorageMethod`
2074
+
2075
+ ```typescript
2076
+ async function getKeyStorageMethod(): Promise<'keychain' | 'file' | 'env' | 'none'>
2077
+ ```
2078
+
2079
+ Use this to detect where an API key is currently stored, so you can display storage status to users, conditionally prompt for re-authentication, or branch logic based on the active credential source.
2080
+
2081
+ Returns a promise resolving to one of four string literals indicating the active storage method — checked in priority order: environment variable → keychain → file → none.
2082
+
2083
+ ### Return Values
2084
+
2085
+ | Value | Description |
2086
+ |---|---|
2087
+ | `'env'` | API key found in `SKRYPT_API_KEY` environment variable |
2088
+ | `'keychain'` | API key stored in the OS keychain (macOS/Linux/Windows credential store) |
2089
+ | `'file'` | API key stored in a local auth file on disk |
2090
+ | `'none'` | No API key found in any storage location |
2091
+
2092
+ ### Parameters
2093
+
2094
+ This function takes no parameters.
2095
+
2096
+ ### Notes
2097
+ - Checks are performed in priority order — if `SKRYPT_API_KEY` is set in the environment, it returns `'env'` immediately without checking other sources.
2098
+ - Use the result to give users meaningful feedback (e.g. *"Logged in via keychain"*) or to decide whether to prompt for a new key.
2099
+
2100
+ ### Example
2101
+
2102
+ ```typescript example.ts
2103
+ // Simulate the storage detection logic inline
2104
+ // (mirrors the real priority order: env → keychain → file → none)
2105
+
2106
+ import { existsSync } from 'fs'
2107
+ import { join } from 'path'
2108
+ import { homedir } from 'os'
2109
+
2110
+ type KeyStorageMethod = 'keychain' | 'file' | 'env' | 'none'
2111
+
2112
+ // Simulate keychain lookup (real impl queries OS credential store)
2113
+ async function simulateKeychainRetrieve(): Promise<string | null> {
2114
+ // In production, this calls the OS keychain API
2115
+ // Here we simulate "no keychain entry found"
2116
+ return null
2117
+ }
2118
+
2119
+ // Simulate file-based auth check
2120
+ function simulateFileKeyExists(): boolean {
2121
+ const AUTH_FILE = join(homedir(), '.skrypt', 'auth.json')
2122
+ return existsSync(AUTH_FILE)
2123
+ }
2124
+
2125
+ async function getKeyStorageMethod(): Promise<KeyStorageMethod> {
2126
+ // Priority 1: environment variable
2127
+ if (process.env.SKRYPT_API_KEY) return 'env'
2128
+
2129
+ // Priority 2: OS keychain
2130
+ const keychainKey = await simulateKeychainRetrieve()
2131
+ if (keychainKey) return 'keychain'
2132
+
2133
+ // Priority 3: local auth file
2134
+ if (simulateFileKeyExists()) return 'file'
2135
+
2136
+ // Priority 4: not found anywhere
2137
+ return 'none'
2138
+ }
2139
+
2140
+ async function main() {
2141
+ try {
2142
+ const method = await getKeyStorageMethod()
2143
+
2144
+ console.log('Storage method detected:', method)
2145
+ // Output (no key set): Storage method detected: none
2146
+
2147
+ // Example: branch on result to show user-facing status
2148
+ const statusMessages: Record<KeyStorageMethod, string> = {
2149
+ env: '🔑 Using API key from environment variable (SKRYPT_API_KEY)',
2150
+ keychain: '🔐 Using API key stored in OS keychain',
2151
+ file: '📄 Using API key stored in local auth file (~/.skrypt/auth.json)',
2152
+ none: '⚠️ No API key found — please run `skrypt login`',
2153
+ }
2154
+
2155
+ console.log(statusMessages[method])
2156
+ // Output: ⚠️ No API key found — please run `skrypt login`
2157
+
2158
+ // Example: simulate env variable being set
2159
+ process.env.SKRYPT_API_KEY = 'sk-prod-abc123xyz'
2160
+ const methodWithEnv = await getKeyStorageMethod()
2161
+ console.log('With env var set:', methodWithEnv)
2162
+ // Output: With env var set: env
2163
+
2164
+ } catch (error) {
2165
+ console.error('Failed to detect key storage method:', error)
2166
+ }
2167
+ }
2168
+
2169
+ main()
2170
+ ```
2171
+
2172
+ ### Related
2173
+
2174
+ <CardGroup cols={3}>
2175
+ <Card title="keychainRetrieve" icon="link" href="#keychainretrieve">
2176
+ Uses
2177
+ </Card>
2178
+ </CardGroup>
2179
+
2180
+ ---
2181
+
2182
+
2183
+ ## `requirePro`
2184
+
2185
+ ```typescript
2186
+ async function requirePro(commandName: string): Promise<boolean>
2187
+ ```
2188
+
2189
+ Use this to gate CLI commands behind a Pro subscription, automatically printing upgrade instructions and returning `false` if the user isn't authenticated or doesn't have a Pro plan.
2190
+
2191
+ Call `requirePro` at the top of any command handler that requires a paid plan. It handles all messaging to the user — you just check the return value and exit early if it's `false`.
2192
+
2193
+ ## Parameters
2194
+
2195
+ | Name | Type | Required | Description |
2196
+ |------|------|----------|-------------|
2197
+ | `commandName` | `string` | ✅ | The name of the CLI command being gated. Shown in the error message so users know which command requires Pro. |
2198
+
2199
+ ## Returns
2200
+
2201
+ | Value | When |
2202
+ |-------|------|
2203
+ | `Promise<true>` | User is authenticated and has an active Pro plan — safe to proceed |
2204
+ | `Promise<false>` | User has no API key, is on the free plan, or the auth check failed — command should abort |
2205
+
2206
+ ## Behavior
2207
+
2208
+ - Reads the stored auth config (API key + plan info)
2209
+ - If no API key is found → prints upgrade prompt and returns `false`
2210
+ - If plan is `free` or invalid → prints upgrade prompt and returns `false`
2211
+ - If Pro is confirmed → returns `true` silently, letting the command continue
2212
+
2213
+ ### Example
2214
+
2215
+ ```typescript example.ts
2216
+ // Inline types — no external imports needed
2217
+ type Plan = 'free' | 'pro' | 'enterprise'
2218
+
2219
+ interface AuthConfig {
2220
+ apiKey: string | null
2221
+ plan: Plan
2222
+ email: string
2223
+ }
2224
+
2225
+ // Simulate stored auth config (in real usage, read from disk/keychain)
2226
+ const mockAuthStore: AuthConfig = {
2227
+ apiKey: process.env.SKRYPT_API_KEY || null,
2228
+ plan: 'free',
2229
+ email: 'user@example.com',
2230
+ }
2231
+
2232
+ async function getAuthConfigAsync(): Promise<AuthConfig> {
2233
+ // Simulate async config read (e.g., from ~/.config or keychain)
2234
+ return new Promise((resolve) => setTimeout(() => resolve(mockAuthStore), 50))
2235
+ }
2236
+
2237
+ async function requirePro(commandName: string): Promise<boolean> {
2238
+ const config = await getAuthConfigAsync()
2239
+
2240
+ if (!config.apiKey) {
2241
+ console.error(`\n ⚡ ${commandName} requires Skrypt Pro\n`)
2242
+ console.error(' Get started:')
2243
+ console.error(' https://skrypt.dev/pro\n')
2244
+ return false
2245
+ }
2246
+
2247
+ if (config.plan !== 'pro' && config.plan !== 'enterprise') {
2248
+ console.error(`\n ⚡ ${commandName} requires Skrypt Pro\n`)
2249
+ console.error(` You're currently on the ${config.plan} plan.`)
2250
+ console.error(' Upgrade at: https://skrypt.dev/pro\n')
2251
+ return false
2252
+ }
2253
+
2254
+ return true
2255
+ }
2256
+
2257
+ // --- Example usage in a CLI command handler ---
2258
+
2259
+ async function runExportCommand() {
2260
+ try {
2261
+ const allowed = await requirePro('export')
2262
+
2263
+ if (!allowed) {
2264
+ // requirePro already printed the error — just exit
2265
+ process.exitCode = 1
2266
+ return
2267
+ }
2268
+
2269
+ // Pro-only logic runs here
2270
+ console.log('✅ Pro confirmed — running export...')
2271
+ // Output: ⚡ export requires Skrypt Pro (when no API key)
2272
+ // Output: ✅ Pro confirmed — running export... (when Pro plan)
2273
+ } catch (error) {
2274
+ console.error('Unexpected error during auth check:', error)
2275
+ process.exitCode = 1
2276
+ }
2277
+ }
2278
+
2279
+ runExportCommand()
2280
+ ```
2281
+
2282
+ ### Related
2283
+
2284
+ <CardGroup cols={3}>
2285
+ <Card title="getAuthConfig" icon="link" href="#getauthconfig">
2286
+ Uses
2287
+ </Card>
2288
+ <Card title="getAuthConfigAsync" icon="link" href="#getauthconfigasync">
2289
+ Uses
2290
+ </Card>
2291
+ </CardGroup>
2292
+
2293
+ ---
2294
+
2295
+
2296
+ ## `runImport`
2297
+
2298
+ ```typescript
2299
+ function runImport(dir: string, format: ImportFormat, name?: string): ImportResult
2300
+ ```
2301
+
2302
+ Use this to automatically import and process a documentation directory based on its detected format (Mintlify, Docusaurus, GitBook, ReadMe, Confluence, or plain Markdown).
2303
+
2304
+ Instead of manually selecting the right importer, pass the directory path and detected format — `runImport` routes to the correct importer and returns a normalized result.
2305
+
2306
+ ## Parameters
2307
+
2308
+ | Name | Type | Required | Description |
2309
+ |------|------|----------|-------------|
2310
+ | `dir` | `string` | ✅ Yes | Path to the documentation directory to import |
2311
+ | `format` | `ImportFormat` | ✅ Yes | The detected format of the docs (`'mintlify'`, `'docusaurus'`, `'gitbook'`, `'readme'`, `'confluence'`, `'markdown'`) |
2312
+ | `name` | `string` | ❌ No | Optional display name for the imported documentation set |
2313
+
2314
+ ## Returns
2315
+
2316
+ Returns an `ImportResult` object containing the processed documentation content, metadata, and any warnings or errors encountered during import.
2317
+
2318
+ | Scenario | Result |
2319
+ |----------|--------|
2320
+ | Successful import | `ImportResult` with parsed pages, metadata, and file tree |
2321
+ | Unrecognized format | Throws or falls through to default handler |
2322
+ | Invalid directory | Throws with a path-related error |
2323
+
2324
+ ### Example
2325
+
2326
+ ```typescript example.ts
2327
+ // --- Inline types (mirrors autodocs internals) ---
2328
+ type ImportFormat = 'mintlify' | 'docusaurus' | 'gitbook' | 'readme' | 'confluence' | 'markdown'
2329
+
2330
+ interface ImportedPage {
2331
+ path: string
2332
+ title: string
2333
+ content: string
2334
+ }
2335
+
2336
+ interface ImportResult {
2337
+ name: string
2338
+ format: ImportFormat
2339
+ pages: ImportedPage[]
2340
+ warnings: string[]
2341
+ }
2342
+
2343
+ // --- Simulated per-format importers ---
2344
+ function importMintlify(dir: string, name?: string): ImportResult {
2345
+ return {
2346
+ name: name ?? 'Mintlify Docs',
2347
+ format: 'mintlify',
2348
+ pages: [{ path: `${dir}/index.mdx`, title: 'Introduction', content: '# Welcome to Mintlify' }],
2349
+ warnings: [],
2350
+ }
2351
+ }
2352
+
2353
+ function importDocusaurus(dir: string, name?: string): ImportResult {
2354
+ return {
2355
+ name: name ?? 'Docusaurus Docs',
2356
+ format: 'docusaurus',
2357
+ pages: [{ path: `${dir}/docs/intro.md`, title: 'Intro', content: '# Getting Started' }],
2358
+ warnings: ['Sidebar config not found, using default order'],
2359
+ }
2360
+ }
2361
+
2362
+ function importGitBook(dir: string, name?: string): ImportResult {
2363
+ return {
2364
+ name: name ?? 'GitBook Docs',
2365
+ format: 'gitbook',
2366
+ pages: [{ path: `${dir}/README.md`, title: 'Overview', content: '# Overview' }],
2367
+ warnings: [],
2368
+ }
2369
+ }
2370
+
2371
+ function importReadme(dir: string, name?: string): ImportResult {
2372
+ return {
2373
+ name: name ?? 'ReadMe Docs',
2374
+ format: 'readme',
2375
+ pages: [{ path: `${dir}/v1.0/getting-started.md`, title: 'Getting Started', content: '# API Docs' }],
2376
+ warnings: [],
2377
+ }
2378
+ }
2379
+
2380
+ function importConfluence(dir: string, name?: string): ImportResult {
2381
+ return {
2382
+ name: name ?? 'Confluence Export',
2383
+ format: 'confluence',
2384
+ pages: [{ path: `${dir}/space/page.html`, title: 'Home', content: '<h1>Confluence Page</h1>' }],
2385
+ warnings: ['HTML content detected — consider converting to Markdown'],
2386
+ }
2387
+ }
2388
+
2389
+ function importMarkdown(dir: string, name?: string): ImportResult {
2390
+ return {
2391
+ name: name ?? 'Markdown Docs',
2392
+ format: 'markdown',
2393
+ pages: [{ path: `${dir}/README.md`, title: 'README', content: '# My Project' }],
2394
+ warnings: [],
2395
+ }
2396
+ }
2397
+
2398
+ // --- The function under documentation ---
2399
+ function runImport(dir: string, format: ImportFormat, name?: string): ImportResult {
2400
+ switch (format) {
2401
+ case 'mintlify': return importMintlify(dir, name)
2402
+ case 'docusaurus': return importDocusaurus(dir, name)
2403
+ case 'gitbook': return importGitBook(dir, name)
2404
+ case 'readme': return importReadme(dir, name)
2405
+ case 'confluence': return importConfluence(dir, name)
2406
+ case 'markdown': return importMarkdown(dir, name)
2407
+ default:
2408
+ throw new Error(`Unsupported import format: ${format}`)
2409
+ }
2410
+ }
2411
+
2412
+ // --- Usage examples ---
2413
+ async function main() {
2414
+ const docsDir = process.env.DOCS_DIR || './docs'
2415
+
2416
+ const formats: ImportFormat[] = ['mintlify', 'docusaurus', 'gitbook', 'readme', 'confluence', 'markdown']
2417
+
2418
+ for (const format of formats) {
2419
+ try {
2420
+ const result = runImport(docsDir, format, `My ${format} Project`)
2421
+
2422
+ console.log(`\n[${format.toUpperCase()}]`)
2423
+ console.log(` Name : ${result.name}`)
2424
+ console.log(` Pages : ${result.pages.length}`)
2425
+ console.log(` First : ${result.pages[0].title} → ${result.pages[0].path}`)
2426
+ if (result.warnings.length > 0) {
2427
+ console.log(` Warnings: ${result.warnings.join('; ')}`)
2428
+ }
2429
+ } catch (error) {
2430
+ console.error(`Failed to import [${format}]:`, error)
2431
+ }
2432
+ }
2433
+
2434
+ // Expected output:
2435
+ // [MINTLIFY]
2436
+ // Name : My mintlify Project
2437
+ // Pages : 1
2438
+ // First : Introduction → ./docs/index.mdx
2439
+ //
2440
+ // [DOCUSAURUS]
2441
+ // Name : My docusaurus Project
2442
+ // Pages : 1
2443
+ // First : Intro → ./docs/docs/intro.md
2444
+ // Warnings: Sidebar config not found, using default order
2445
+ // ... (and so on for each format)
2446
+ }
2447
+
2448
+ main()
2449
+ ```
2450
+
2451
+ ### Related
2452
+
2453
+ <CardGroup cols={3}>
2454
+ <Card title="importConfluence" icon="link" href="#importconfluence">
2455
+ Uses
2456
+ </Card>
2457
+ <Card title="importDocusaurus" icon="link" href="#importdocusaurus">
2458
+ Uses
2459
+ </Card>
2460
+ <Card title="importGitBook" icon="link" href="#importgitbook">
2461
+ Uses
2462
+ </Card>
2463
+ <Card title="importMintlify" icon="link" href="#importmintlify">
2464
+ Uses
2465
+ </Card>
2466
+ <Card title="importReadme" icon="link" href="#importreadme">
2467
+ Uses
2468
+ </Card>
2469
+ </CardGroup>
2470
+
2471
+ ---
2472
+
2473
+
2474
+ ## `saveAuthConfig`
2475
+
2476
+ ```typescript
2477
+ async function saveAuthConfig(config: AuthConfig): Promise<void>
2478
+ ```
2479
+
2480
+ Use this to persist authentication configuration (API keys, endpoints, tokens) to a secure local config directory, with automatic keychain integration when available.
2481
+
2482
+ This function:
2483
+ - Creates the config directory with restricted permissions (`0o700`) if it doesn't exist
2484
+ - Attempts to store the API key in the system keychain for added security
2485
+ - Falls back to writing credentials to a local config file if keychain storage is unavailable
2486
+
2487
+ ## Parameters
2488
+
2489
+ | Name | Type | Required | Description |
2490
+ |------|------|----------|-------------|
2491
+ | `config` | `AuthConfig` | Yes | Authentication configuration object containing credentials to persist |
2492
+ | `config.apiKey` | `string` | No | API key to store — saved to system keychain when possible, otherwise written to config file |
2493
+
2494
+ ## Returns
2495
+
2496
+ Returns `Promise<void>`. Resolves when the config has been successfully saved. Throws if the config directory cannot be created or the file cannot be written.
2497
+
2498
+ ## Notes
2499
+ - The config directory is created with `0o700` permissions (owner read/write/execute only) — no other users can access it
2500
+ - If keychain storage succeeds, the API key is **not** written to disk in plaintext
2501
+ - Call `saveAuthConfig` any time credentials change (login, token refresh, API key rotation)
2502
+
2503
+ ### Example
2504
+
2505
+ ```typescript example.ts
2506
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from 'fs'
2507
+ import { join } from 'path'
2508
+ import { homedir } from 'os'
2509
+
2510
+ // --- Inline types ---
2511
+ interface AuthConfig {
2512
+ apiKey?: string
2513
+ endpoint?: string
2514
+ token?: string
2515
+ }
2516
+
2517
+ // --- Inline config path constants ---
2518
+ const CONFIG_DIR = join(homedir(), '.config', 'myapp')
2519
+ const CONFIG_FILE = join(CONFIG_DIR, 'auth.json')
2520
+
2521
+ // --- Simulate keychain (no real keychain in this example) ---
2522
+ async function keychainStore(apiKey: string): Promise<boolean> {
2523
+ // In production, this would call the OS keychain (e.g. macOS Keychain, libsecret)
2524
+ console.log(`[keychain] Attempted to store API key: ${apiKey.slice(0, 8)}...`)
2525
+ return false // Simulate keychain unavailable — falls back to file storage
2526
+ }
2527
+
2528
+ // --- Self-contained saveAuthConfig implementation ---
2529
+ async function saveAuthConfig(config: AuthConfig): Promise<void> {
2530
+ // Create config directory with restricted permissions (owner only)
2531
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })
2532
+
2533
+ let keyInKeychain = false
2534
+ if (config.apiKey) {
2535
+ keyInKeychain = await keychainStore(config.apiKey)
2536
+ }
2537
+
2538
+ // Build the object to persist — omit apiKey if it's safely in the keychain
2539
+ const persistedConfig: Partial<AuthConfig> & { apiKeyInKeychain?: boolean } = {
2540
+ endpoint: config.endpoint,
2541
+ token: config.token,
2542
+ apiKeyInKeychain: keyInKeychain,
2543
+ // Only write apiKey to disk if keychain storage failed
2544
+ ...(!keyInKeychain && config.apiKey ? { apiKey: config.apiKey } : {}),
2545
+ }
2546
+
2547
+ writeFileSync(CONFIG_FILE, JSON.stringify(persistedConfig, null, 2), { mode: 0o600 })
2548
+ chmodSync(CONFIG_FILE, 0o600) // Ensure file is owner read/write only
2549
+
2550
+ console.log(`[saveAuthConfig] Config saved to: ${CONFIG_FILE}`)
2551
+ }
2552
+
2553
+ // --- Usage example ---
2554
+ async function main() {
2555
+ const authConfig: AuthConfig = {
2556
+ apiKey: process.env.MY_API_KEY || 'sk-abc123def456ghi789',
2557
+ endpoint: process.env.API_ENDPOINT || 'https://api.example.com',
2558
+ token: process.env.AUTH_TOKEN || 'tok-xyz987',
2559
+ }
2560
+
2561
+ try {
2562
+ await saveAuthConfig(authConfig)
2563
+
2564
+ // Verify what was written
2565
+ const saved = JSON.parse(readFileSync(join(homedir(), '.config', 'myapp', 'auth.json'), 'utf-8'))
2566
+ console.log('Saved config:', saved)
2567
+ // Output (keychain unavailable): {
2568
+ // endpoint: 'https://api.example.com',
2569
+ // token: 'tok-xyz987',
2570
+ // apiKeyInKeychain: false,
2571
+ // apiKey: 'sk-abc123def456ghi789'
2572
+ // }
2573
+ // Output (keychain available): {
2574
+ // endpoint: 'https://api.example.com',
2575
+ // token: 'tok-xyz987',
2576
+ // apiKeyInKeychain: true <-- apiKey NOT written to disk
2577
+ // }
2578
+ } catch (error) {
2579
+ console.error('Failed to save auth config:', error)
2580
+ process.exit(1)
2581
+ }
2582
+ }
2583
+
2584
+ main()
2585
+ ```
2586
+
2587
+ ### Related
2588
+
2589
+ <CardGroup cols={3}>
2590
+ <Card title="keychainStore" icon="link" href="#keychainstore">
2591
+ Uses
2592
+ </Card>
2593
+ </CardGroup>
2594
+
2595
+ ---
2596
+
2597
+
2598
+ ## `scanDirectory`
2599
+
2600
+ ```typescript
2601
+ async function scanDirectory(dir: string, options: ScanOptions = {}): Promise<ScanAllResult>
2602
+ ```
2603
+
2604
+ Use this to recursively scan a directory (or single file) for all API elements — functions, classes, types, and exports — across multiple languages including Python, TypeScript, JavaScript, Go, and Rust.
2605
+
2606
+ This is the primary entry point for automated documentation generation pipelines. Point it at a project root and get back a structured inventory of every public API element found.
2607
+
2608
+ ## Parameters
2609
+
2610
+ | Name | Type | Required | Description |
2611
+ |------|------|----------|-------------|
2612
+ | `dir` | `string` | ✅ | Path to the directory or single file to scan |
2613
+ | `options` | `ScanOptions` | ❌ | Configuration to control which files are included or excluded |
2614
+ | `options.include` | `string[]` | ❌ | Glob patterns for files to scan. Defaults to `['**/*.py', '**/*.ts', '**/*.js', '**/*.go', '**/*.rs']` |
2615
+ | `options.exclude` | `string[]` | ❌ | Glob patterns for files to skip. Defaults to `['**/node_modules/**', '**/__pycache__/**', '**/dist/**']` |
2616
+
2617
+ ## Returns
2618
+
2619
+ Returns a `Promise<ScanAllResult>` that resolves to an object containing:
2620
+
2621
+ | Field | Type | Description |
2622
+ |-------|------|-------------|
2623
+ | `files` | `ScanResult[]` | One entry per scanned file, each containing the file path and its discovered API elements |
2624
+ | `totalElements` | `number` | Total count of API elements found across all files |
2625
+ | `errors` | `ScanError[]` | Any files that failed to parse, with their error details |
2626
+
2627
+ Returns an empty `files` array if no matching files are found. Files that fail to parse are collected in `errors` rather than throwing, so a single bad file won't abort the entire scan.
2628
+
2629
+ ---
2630
+
2631
+
2632
+ ## `scanFile`
2633
+
2634
+ ```typescript
2635
+ async function scanFile(filePath: string): Promise<ScanResult>
2636
+ ```
2637
+
2638
+ Use this to extract structured metadata from a single source file — functions, classes, types, and other symbols — without scanning an entire directory.
2639
+
2640
+ `scanFile` inspects a file at the given path, selects the appropriate language scanner (TypeScript, JavaScript, Python, etc.), and returns a structured `ScanResult` containing all discovered symbols and language information.
2641
+
2642
+ ## Parameters
2643
+
2644
+ | Name | Type | Required | Description |
2645
+ |------|------|----------|-------------|
2646
+ | `filePath` | `string` | ✅ | Absolute or relative path to the source file to scan |
2647
+
2648
+ ## Returns
2649
+
2650
+ Returns `Promise<ScanResult>` — resolves with a structured object containing:
2651
+
2652
+ | Field | Type | Description |
2653
+ |-------|------|-------------|
2654
+ | `language` | `string` | Detected language (`'typescript'`, `'javascript'`, `'python'`, etc.) |
2655
+ | `symbols` | `Symbol[]` | Array of discovered functions, classes, types, and other declarations |
2656
+ | `filePath` | `string` | The resolved path of the scanned file |
2657
+
2658
+ **Throws** if the file does not exist, cannot be read, or has an unsupported extension with no registered scanner.
2659
+
2660
+ ### Example
2661
+
2662
+ ```typescript example.ts
2663
+ // Inline types matching the autodocs ScanResult shape
2664
+ type SymbolKind = 'function' | 'class' | 'type' | 'interface' | 'variable'
2665
+
2666
+ interface DocSymbol {
2667
+ name: string
2668
+ kind: SymbolKind
2669
+ line: number
2670
+ docstring?: string
2671
+ signature?: string
2672
+ }
2673
+
2674
+ interface ScanResult {
2675
+ filePath: string
2676
+ language: 'typescript' | 'javascript' | 'python' | 'unknown'
2677
+ symbols: DocSymbol[]
2678
+ }
2679
+
2680
+ // Simulated scanFile — replace with the real import in your project
2681
+ async function scanFile(filePath: string): Promise<ScanResult> {
2682
+ const ext = filePath.split('.').pop()?.toLowerCase()
2683
+
2684
+ const langMap: Record<string, ScanResult['language']> = {
2685
+ ts: 'typescript',
2686
+ tsx: 'typescript',
2687
+ js: 'javascript',
2688
+ jsx: 'javascript',
2689
+ py: 'python',
2690
+ }
2691
+
2692
+ const language = langMap[ext ?? ''] ?? 'unknown'
2693
+
2694
+ if (language === 'unknown') {
2695
+ throw new Error(`No scanner available for file extension: .${ext}`)
2696
+ }
2697
+
2698
+ // Simulated scan output — real implementation parses the AST
2699
+ return {
2700
+ filePath,
2701
+ language,
2702
+ symbols: [
2703
+ {
2704
+ name: 'getUserById',
2705
+ kind: 'function',
2706
+ line: 12,
2707
+ docstring: 'Fetch a user record by their unique ID.',
2708
+ signature: 'async function getUserById(id: string): Promise<User>',
2709
+ },
2710
+ {
2711
+ name: 'User',
2712
+ kind: 'interface',
2713
+ line: 4,
2714
+ docstring: 'Represents an authenticated user.',
2715
+ signature: 'interface User { id: string; email: string; role: string }',
2716
+ },
2717
+ ],
2718
+ }
2719
+ }
2720
+
2721
+ // --- Usage ---
2722
+ async function main() {
2723
+ const targetFile = process.env.SCAN_TARGET || './src/users.ts'
2724
+
2725
+ try {
2726
+ const result = await scanFile(targetFile)
2727
+
2728
+ console.log(`Scanned: ${result.filePath}`)
2729
+ console.log(`Language: ${result.language}`)
2730
+ console.log(`Symbols found: ${result.symbols.length}`)
2731
+
2732
+ for (const symbol of result.symbols) {
2733
+ console.log(`\n [${symbol.kind}] ${symbol.name} (line ${symbol.line})`)
2734
+ if (symbol.docstring) console.log(` 📝 ${symbol.docstring}`)
2735
+ if (symbol.signature) console.log(` 🔷 ${symbol.signature}`)
2736
+ }
2737
+
2738
+ // Expected output:
2739
+ // Scanned: ./src/users.ts
2740
+ // Language: typescript
2741
+ // Symbols found: 2
2742
+ //
2743
+ // [function] getUserById (line 12)
2744
+ // 📝 Fetch a user record by their unique ID.
2745
+ // 🔷 async function getUserById(id: string): Promise<User>
2746
+ //
2747
+ // [interface] User (line 4)
2748
+ // 📝 Represents an authenticated user.
2749
+ // 🔷 interface User { id: string; email: string; role: string }
2750
+ } catch (error) {
2751
+ console.error('Scan failed:', error instanceof Error ? error.message : error)
2752
+ process.exit(1)
2753
+ }
2754
+ }
2755
+
2756
+ main()
2757
+ ```
2758
+
2759
+ ### Related
2760
+
2761
+ <CardGroup cols={3}>
2762
+ <Card title="canHandle" icon="link" href="#canhandle">
2763
+ Uses
2764
+ </Card>
2765
+ <Card title="canHandle" icon="link" href="#canhandle">
2766
+ Uses
2767
+ </Card>
2768
+ <Card title="canHandle" icon="link" href="#canhandle">
2769
+ Uses
2770
+ </Card>
2771
+ <Card title="canHandle" icon="link" href="#canhandle">
2772
+ Uses
2773
+ </Card>
2774
+ <Card title="GoScanner" icon="link" href="#goscanner">
2775
+ Used by
2776
+ </Card>
2777
+ <Card title="canHandle" icon="link" href="#canhandle">
2778
+ Used by
2779
+ </Card>
2780
+ </CardGroup>
2781
+
2782
+ ---
2783
+
2784
+
2785
+ ## `constructor`
2786
+
2787
+ ```typescript
2788
+ constructor(sourcePath: string, outputPath: string, config: Record<string, unknown> = {})
2789
+ ```
2790
+
2791
+ Use this to initialize a plugin manager that coordinates documentation plugins across a source codebase, wiring together input/output paths and shared configuration before any plugins are registered or executed.
2792
+
2793
+ ## Parameters
2794
+
2795
+ | Name | Type | Required | Description |
2796
+ |------|------|----------|-------------|
2797
+ | `sourcePath` | `string` | ✅ Yes | Absolute or relative path to the source code directory to be processed |
2798
+ | `outputPath` | `string` | ✅ Yes | Absolute or relative path to the directory where generated docs will be written |
2799
+ | `config` | `Record<string, unknown>` | ❌ No | Optional key-value configuration passed to all plugins via shared context. Defaults to `{}` |
2800
+
2801
+ ## Returns
2802
+
2803
+ A `PluginManager` instance with an initialized internal `PluginContext` containing the provided paths and config. The plugin registry starts empty — use subsequent `register`/`load` methods to add plugins.
2804
+
2805
+ ## Notes
2806
+ - `sourcePath` and `outputPath` are stored on the shared `PluginContext`, making them accessible to every plugin that runs
2807
+ - Passing `config` here is the recommended way to share global settings (e.g., output format, version flags) across all plugins without coupling them directly
2808
+
2809
+ ### Example
2810
+
2811
+ ```typescript example.ts
2812
+ // --- Inline types (mirroring the real autodocs internals) ---
2813
+ interface PluginContext {
2814
+ sourcePath: string
2815
+ outputPath: string
2816
+ config: Record<string, unknown>
2817
+ logger: {
2818
+ info: (msg: string) => void
2819
+ warn: (msg: string) => void
2820
+ error: (msg: string) => void
2821
+ }
2822
+ }
2823
+
2824
+ interface SkryptPlugin {
2825
+ name: string
2826
+ run: (context: PluginContext) => Promise<void>
2827
+ }
2828
+
2829
+ // --- Self-contained PluginManager implementation ---
2830
+ class PluginManager {
2831
+ private plugins: SkryptPlugin[] = []
2832
+ private context: PluginContext
2833
+
2834
+ constructor(
2835
+ sourcePath: string,
2836
+ outputPath: string,
2837
+ config: Record<string, unknown> = {}
2838
+ ) {
2839
+ this.context = {
2840
+ sourcePath,
2841
+ outputPath,
2842
+ config,
2843
+ logger: {
2844
+ info: (msg: string) => console.log(`[INFO] ${msg}`),
2845
+ warn: (msg: string) => console.warn(`[WARN] ${msg}`),
2846
+ error: (msg: string) => console.error(`[ERROR] ${msg}`),
2847
+ },
2848
+ }
2849
+ }
2850
+
2851
+ /** Expose context for inspection in this example */
2852
+ getContext(): PluginContext {
2853
+ return this.context
2854
+ }
2855
+
2856
+ register(plugin: SkryptPlugin): void {
2857
+ this.plugins.push(plugin)
2858
+ this.context.logger.info(`Registered plugin: ${plugin.name}`)
2859
+ }
2860
+
2861
+ async runAll(): Promise<void> {
2862
+ for (const plugin of this.plugins) {
2863
+ this.context.logger.info(`Running plugin: ${plugin.name}`)
2864
+ await plugin.run(this.context)
2865
+ }
2866
+ }
2867
+ }
2868
+
2869
+ // --- Usage example ---
2870
+ async function main() {
2871
+ try {
2872
+ const manager = new PluginManager(
2873
+ process.env.SOURCE_PATH || './src',
2874
+ process.env.OUTPUT_PATH || './docs',
2875
+ {
2876
+ outputFormat: 'markdown',
2877
+ version: '2.1.0',
2878
+ includePrivate: false,
2879
+ }
2880
+ )
2881
+
2882
+ // Inspect the initialized context
2883
+ const ctx = manager.getContext()
2884
+ console.log('PluginManager initialized:')
2885
+ console.log(' sourcePath:', ctx.sourcePath) // './src'
2886
+ console.log(' outputPath:', ctx.outputPath) // './docs'
2887
+ console.log(' config:', ctx.config)
2888
+ // {
2889
+ // outputFormat: 'markdown',
2890
+ // version: '2.1.0',
2891
+ // includePrivate: false
2892
+ // }
2893
+
2894
+ // Register a sample plugin to confirm context flows through
2895
+ manager.register({
2896
+ name: 'typescript-extractor',
2897
+ run: async (context) => {
2898
+ console.log(`\n[typescript-extractor] Reading from: ${context.sourcePath}`)
2899
+ console.log(`[typescript-extractor] Writing to: ${context.outputPath}`)
2900
+ console.log(`[typescript-extractor] Format: ${context.config.outputFormat}`)
2901
+ },
2902
+ })
2903
+
2904
+ await manager.runAll()
2905
+ // Output:
2906
+ // [INFO] Registered plugin: typescript-extractor
2907
+ // [INFO] Running plugin: typescript-extractor
2908
+ // [typescript-extractor] Reading from: ./src
2909
+ // [typescript-extractor] Writing to: ./docs
2910
+ // [typescript-extractor] Format: markdown
2911
+ } catch (error) {
2912
+ console.error('PluginManager setup failed:', error)
2913
+ process.exit(1)
2914
+ }
2915
+ }
2916
+
2917
+ main()
2918
+ ```
2919
+
2920
+ ### Related
2921
+
2922
+ <CardGroup cols={3}>
2923
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
2924
+ Uses
2925
+ </Card>
2926
+ <Card title="AnthropicClient" icon="link" href="#anthropicclient">
2927
+ Used by
2928
+ </Card>
2929
+ <Card title="OpenAICompatibleClient" icon="link" href="#openaicompatibleclient">
2930
+ Used by
2931
+ </Card>
2932
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
2933
+ Used by
2934
+ </Card>
2935
+ <Card title="AnthropicClient" icon="link" href="#anthropicclient">
2936
+ Related
2937
+ </Card>
2938
+ <Card title="OpenAICompatibleClient" icon="link" href="#openaicompatibleclient">
2939
+ Related
2940
+ </Card>
2941
+ </CardGroup>
2942
+
2943
+ ---
2944
+
2945
+
2946
+ ## `loadPlugins`
2947
+
2948
+ ```typescript
2949
+ async loadPlugins(configPath?: string): Promise<void>
2950
+ ```
2951
+
2952
+ Use this to initialize and load plugins into the `PluginManager` from a configuration file, enabling plugin-based extensibility in your application.
2953
+
2954
+ This method reads plugin definitions from a `skrypt.config.js` or `skrypt.config.ts` file (auto-discovered if no path is provided) and registers them with the manager. Call this once during application startup before executing any plugin-dependent logic.
2955
+
2956
+ ## Parameters
2957
+
2958
+ | Name | Type | Required | Description |
2959
+ |------|------|----------|-------------|
2960
+ | `configPath` | `string` | No | Absolute or relative path to a config file. If omitted, the manager auto-discovers `skrypt.config.js` or `skrypt.config.ts` in the current working directory. |
2961
+
2962
+ ## Returns
2963
+
2964
+ Returns `Promise<void>`. Resolves when all plugins have been loaded and registered. If no config file is found, resolves immediately without error.
2965
+
2966
+ ## Behavior Notes
2967
+
2968
+ - **No config found** → resolves silently with no plugins loaded
2969
+ - **Config found, valid** → plugins are registered and ready to use
2970
+ - **Config found, invalid** → throws or logs an error depending on the failure type
2971
+ - Passing an explicit `configPath` overrides auto-discovery entirely
2972
+
2973
+ ### Example
2974
+
2975
+ ```typescript example.ts
2976
+ // Inline types to simulate PluginManager behavior
2977
+ type Plugin = {
2978
+ name: string
2979
+ execute: (input: string) => string
2980
+ }
2981
+
2982
+ type PluginConfig = {
2983
+ plugins: Plugin[]
2984
+ }
2985
+
2986
+ // Simulated PluginManager class (self-contained, no external imports)
2987
+ class PluginManager {
2988
+ private plugins: Map<string, Plugin> = new Map()
2989
+ private configSearchPaths = ['./skrypt.config.js', './skrypt.config.ts']
2990
+
2991
+ private findConfigFile(): string | null {
2992
+ // In real usage, this checks the filesystem via existsSync
2993
+ // Here we simulate "not found" unless overridden
2994
+ return null
2995
+ }
2996
+
2997
+ private async importConfig(configPath: string): Promise<PluginConfig> {
2998
+ // Simulate loading a config file by returning a mock config
2999
+ console.log(`[plugin] Loading config from: ${configPath}`)
3000
+ return {
3001
+ plugins: [
3002
+ { name: 'markdown-renderer', execute: (input) => `<p>${input}</p>` },
3003
+ { name: 'syntax-highlighter', execute: (input) => `<code>${input}</code>` },
3004
+ ],
3005
+ }
3006
+ }
3007
+
3008
+ async loadPlugins(configPath?: string): Promise<void> {
3009
+ const configFile = configPath || this.findConfigFile()
3010
+
3011
+ if (!configFile) {
3012
+ console.log('[plugin] No config file found — skipping plugin load')
3013
+ return
3014
+ }
3015
+
3016
+ try {
3017
+ const config = await this.importConfig(configFile)
3018
+
3019
+ for (const plugin of config.plugins) {
3020
+ this.plugins.set(plugin.name, plugin)
3021
+ console.log(`[plugin] Registered: ${plugin.name}`)
3022
+ }
3023
+
3024
+ console.log(`[plugin] Loaded ${this.plugins.size} plugin(s) successfully`)
3025
+ } catch (error) {
3026
+ console.error('[plugin] Failed to load plugins:', error)
3027
+ throw error
3028
+ }
3029
+ }
3030
+
3031
+ getPlugin(name: string): Plugin | undefined {
3032
+ return this.plugins.get(name)
3033
+ }
3034
+
3035
+ listPlugins(): string[] {
3036
+ return Array.from(this.plugins.keys())
3037
+ }
3038
+ }
3039
+
3040
+ // --- Usage ---
3041
+ async function main() {
3042
+ const manager = new PluginManager()
3043
+
3044
+ try {
3045
+ // Option 1: Auto-discover config (resolves silently if not found)
3046
+ await manager.loadPlugins()
3047
+
3048
+ // Option 2: Explicit config path (e.g., from env or CLI arg)
3049
+ const configPath = process.env.PLUGIN_CONFIG_PATH || './skrypt.config.js'
3050
+ await manager.loadPlugins(configPath)
3051
+
3052
+ // Plugins are now available
3053
+ const availablePlugins = manager.listPlugins()
3054
+ console.log('Available plugins:', availablePlugins)
3055
+ // Output: Available plugins: [ 'markdown-renderer', 'syntax-highlighter' ]
3056
+
3057
+ const renderer = manager.getPlugin('markdown-renderer')
3058
+ if (renderer) {
3059
+ const output = renderer.execute('Hello, world!')
3060
+ console.log('Renderer output:', output)
3061
+ // Output: Renderer output: <p>Hello, world!</p>
3062
+ }
3063
+ } catch (error) {
3064
+ console.error('Startup failed — could not load plugins:', error)
3065
+ process.exit(1)
3066
+ }
3067
+ }
3068
+
3069
+ main()
3070
+ ```
3071
+
3072
+ ### Related
3073
+
3074
+ <CardGroup cols={3}>
3075
+ <Card title="findConfigFile" icon="link" href="#findconfigfile">
3076
+ Uses
3077
+ </Card>
3078
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
3079
+ Related
3080
+ </Card>
3081
+ </CardGroup>
3082
+
3083
+ ---
3084
+
3085
+
3086
+ ## `onAfterGenerate`
3087
+
3088
+ ```typescript
3089
+ async onAfterGenerate<T>(docs: T[]): Promise<T[]>
3090
+ ```
3091
+
3092
+ Use this to run all registered plugins' `onAfterGenerate` hooks against a collection of generated documentation objects, allowing plugins to transform, filter, enrich, or reorder docs before they are written to disk.
3093
+
3094
+ This is the final transformation stage in the generation pipeline — ideal for plugins that need to post-process docs (e.g., injecting metadata, removing internal-only entries, sorting output).
3095
+
3096
+ ## Parameters
3097
+
3098
+ | Name | Type | Required | Description |
3099
+ |------|------|----------|-------------|
3100
+ | `docs` | `T[]` | Yes | The array of generated documentation objects to pass through all registered plugin hooks. The generic type `T` can be any doc shape. |
3101
+
3102
+ ## Returns
3103
+
3104
+ | Condition | Returns |
3105
+ |-----------|---------|
3106
+ | A plugin hook transforms the docs | `Promise<T[]>` — the transformed array returned by the plugin |
3107
+ | No plugin hook is registered / hook returns falsy | `Promise<T[]>` — the original `docs` array, unchanged |
3108
+
3109
+ The method always resolves to a `T[]` — it never returns `undefined` or `null`.
3110
+
3111
+ ### Example
3112
+
3113
+ ```typescript example.ts
3114
+ // Inline types to simulate the PluginManager behavior
3115
+ type Hook<T> = (data: T) => Promise<T> | T
3116
+
3117
+ interface Plugin {
3118
+ name: string
3119
+ onAfterGenerate?: Hook<any[]>
3120
+ }
3121
+
3122
+ interface DocEntry {
3123
+ id: string
3124
+ title: string
3125
+ content: string
3126
+ internal?: boolean
3127
+ }
3128
+
3129
+ // Minimal self-contained PluginManager simulation
3130
+ class PluginManager {
3131
+ private plugins: Plugin[] = []
3132
+
3133
+ register(plugin: Plugin) {
3134
+ this.plugins.push(plugin)
3135
+ console.log(`Plugin registered: ${plugin.name}`)
3136
+ }
3137
+
3138
+ private async runHook<T>(hookName: keyof Plugin, data: T): Promise<T | undefined> {
3139
+ let result: T = data
3140
+ for (const plugin of this.plugins) {
3141
+ const hook = plugin[hookName] as Hook<T> | undefined
3142
+ if (typeof hook === 'function') {
3143
+ result = await hook(result)
3144
+ }
3145
+ }
3146
+ return this.plugins.some(p => typeof p[hookName] === 'function') ? result : undefined
3147
+ }
3148
+
3149
+ async onAfterGenerate<T>(docs: T[]): Promise<T[]> {
3150
+ return (await this.runHook<T[]>('onAfterGenerate', docs)) || docs
3151
+ }
3152
+ }
3153
+
3154
+ // --- Example usage ---
3155
+
3156
+ const manager = new PluginManager()
3157
+
3158
+ // Plugin 1: Remove internal-only docs
3159
+ manager.register({
3160
+ name: 'filter-internal-plugin',
3161
+ onAfterGenerate: async (docs: DocEntry[]) => {
3162
+ const filtered = docs.filter(doc => !doc.internal)
3163
+ console.log(`[filter-internal-plugin] Removed ${docs.length - filtered.length} internal doc(s)`)
3164
+ return filtered
3165
+ }
3166
+ })
3167
+
3168
+ // Plugin 2: Inject a generated timestamp into each doc
3169
+ manager.register({
3170
+ name: 'timestamp-plugin',
3171
+ onAfterGenerate: async (docs: DocEntry[]) => {
3172
+ return docs.map(doc => ({
3173
+ ...doc,
3174
+ content: `${doc.content}\n\n_Generated at: 2024-01-15T10:30:00Z_`
3175
+ }))
3176
+ }
3177
+ })
3178
+
3179
+ const generatedDocs: DocEntry[] = [
3180
+ { id: 'doc-001', title: 'Getting Started', content: 'Install the package...' },
3181
+ { id: 'doc-002', title: 'Internal Roadmap', content: 'Q3 plans...', internal: true },
3182
+ { id: 'doc-003', title: 'API Reference', content: 'All public methods...' },
3183
+ ]
3184
+
3185
+ async function main() {
3186
+ try {
3187
+ console.log(`\nInput docs: ${generatedDocs.length}`)
3188
+
3189
+ const finalDocs = await manager.onAfterGenerate(generatedDocs)
3190
+
3191
+ console.log(`\nOutput docs: ${finalDocs.length}`)
3192
+ finalDocs.forEach(doc => {
3193
+ console.log(`\n--- ${doc.title} ---`)
3194
+ console.log(doc.content)
3195
+ })
3196
+
3197
+ // Expected output:
3198
+ // Input docs: 3
3199
+ // [filter-internal-plugin] Removed 1 internal doc(s)
3200
+ // Output docs: 2
3201
+ // --- Getting Started ---
3202
+ // Install the package...
3203
+ // _Generated at: 2024-01-15T10:30:00Z_
3204
+ // --- API Reference ---
3205
+ // All public methods...
3206
+ // _Generated at: 2024-01-15T10:30:00Z_
3207
+ } catch (error) {
3208
+ console.error('onAfterGenerate failed:', error)
3209
+ }
3210
+ }
3211
+
3212
+ main()
3213
+ ```
3214
+
3215
+ ### Related
3216
+
3217
+ <CardGroup cols={3}>
3218
+ <Card title="runHook" icon="link" href="#runhook">
3219
+ Uses
3220
+ </Card>
3221
+ <Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
3222
+ Uses
3223
+ </Card>
3224
+ <Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
3225
+ Uses
3226
+ </Card>
3227
+ <Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
3228
+ Used by
3229
+ </Card>
3230
+ <Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
3231
+ Used by
3232
+ </Card>
3233
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
3234
+ Related
3235
+ </Card>
3236
+ </CardGroup>
3237
+
3238
+ ---
3239
+
3240
+
3241
+ ## `onAfterScan`
3242
+
3243
+ ```typescript
3244
+ async onAfterScan<T>(elements: T[]): Promise<T[]>
3245
+ ```
3246
+
3247
+ Use this to run all registered plugins' `onAfterScan` hooks against a list of scanned elements, allowing plugins to filter, transform, or enrich the results before further processing.
3248
+
3249
+ This is called automatically by the `PluginManager` after a scan completes. Each plugin that implements `onAfterScan` gets a chance to modify the elements array in sequence. If no plugin modifies the elements, the original array is returned unchanged.
3250
+
3251
+ ## Parameters
3252
+
3253
+ | Name | Type | Required | Description |
3254
+ |------|------|----------|-------------|
3255
+ | `elements` | `T[]` | Yes | The array of scanned elements to pass through the plugin hook pipeline |
3256
+
3257
+ ## Returns
3258
+
3259
+ **`Promise<T[]>`** — Resolves to the (potentially modified) array of elements after all plugins have processed them. Falls back to the original `elements` array if no plugin returns a value.
3260
+
3261
+ ### Example
3262
+
3263
+ ```typescript example.ts
3264
+ // Inline types to simulate PluginManager behavior
3265
+ type Plugin = {
3266
+ name: string
3267
+ onAfterScan?: <T>(elements: T[]) => Promise<T[]>
3268
+ }
3269
+
3270
+ type ScannedFile = {
3271
+ path: string
3272
+ type: string
3273
+ exported: boolean
3274
+ }
3275
+
3276
+ // Simulate the PluginManager class
3277
+ class PluginManager {
3278
+ private plugins: Plugin[] = []
3279
+
3280
+ register(plugin: Plugin) {
3281
+ this.plugins.push(plugin)
3282
+ }
3283
+
3284
+ private async runHook<T>(hookName: string, arg?: T): Promise<T | undefined> {
3285
+ let result = arg
3286
+ for (const plugin of this.plugins) {
3287
+ const hook = (plugin as any)[hookName]
3288
+ if (typeof hook === 'function') {
3289
+ const hookResult = await hook.call(plugin, result)
3290
+ if (hookResult !== undefined) {
3291
+ result = hookResult
3292
+ }
3293
+ }
3294
+ }
3295
+ return result
3296
+ }
3297
+
3298
+ async onAfterScan<T>(elements: T[]): Promise<T[]> {
3299
+ return (await this.runHook<T[]>('onAfterScan', elements)) || elements
3300
+ }
3301
+ }
3302
+
3303
+ // Example plugins that transform scanned elements
3304
+ const exportFilterPlugin: Plugin = {
3305
+ name: 'export-filter',
3306
+ async onAfterScan<T>(elements: T[]): Promise<T[]> {
3307
+ const files = elements as unknown as ScannedFile[]
3308
+ const filtered = files.filter(f => f.exported)
3309
+ console.log(`[export-filter] Filtered ${elements.length} → ${filtered.length} exported files`)
3310
+ return filtered as unknown as T[]
3311
+ }
3312
+ }
3313
+
3314
+ const pathNormalizerPlugin: Plugin = {
3315
+ name: 'path-normalizer',
3316
+ async onAfterScan<T>(elements: T[]): Promise<T[]> {
3317
+ const files = elements as unknown as ScannedFile[]
3318
+ const normalized = files.map(f => ({ ...f, path: f.path.replace(/\\/g, '/') }))
3319
+ console.log(`[path-normalizer] Normalized ${normalized.length} file paths`)
3320
+ return normalized as unknown as T[]
3321
+ }
3322
+ }
3323
+
3324
+ async function main() {
3325
+ try {
3326
+ const manager = new PluginManager()
3327
+ manager.register(exportFilterPlugin)
3328
+ manager.register(pathNormalizerPlugin)
3329
+
3330
+ const scannedFiles: ScannedFile[] = [
3331
+ { path: 'src\\components\\Button.ts', type: 'component', exported: true },
3332
+ { path: 'src\\utils\\internal.ts', type: 'utility', exported: false },
3333
+ { path: 'src\\hooks\\useAuth.ts', type: 'hook', exported: true },
3334
+ { path: 'src\\helpers\\debug.ts', type: 'helper', exported: false },
3335
+ ]
3336
+
3337
+ console.log('Before onAfterScan:', scannedFiles.length, 'files')
3338
+
3339
+ const result = await manager.onAfterScan(scannedFiles)
3340
+
3341
+ console.log('\nAfter onAfterScan:', result.length, 'files')
3342
+ console.log('Final elements:', result)
3343
+ // Output:
3344
+ // Before onAfterScan: 4 files
3345
+ // [export-filter] Filtered 4 → 2 exported files
3346
+ // [path-normalizer] Normalized 2 file paths
3347
+ // After onAfterScan: 2 files
3348
+ // Final elements: [
3349
+ // { path: 'src/components/Button.ts', type: 'component', exported: true },
3350
+ // { path: 'src/hooks/useAuth.ts', type: 'hook', exported: true }
3351
+ // ]
3352
+ } catch (error) {
3353
+ console.error('onAfterScan failed:', error)
3354
+ }
3355
+ }
3356
+
3357
+ main()
3358
+ ```
3359
+
3360
+ ### Related
3361
+
3362
+ <CardGroup cols={3}>
3363
+ <Card title="runHook" icon="link" href="#runhook">
3364
+ Uses
3365
+ </Card>
3366
+ <Card title="onBeforeScan" icon="link" href="#onbeforescan">
3367
+ Uses
3368
+ </Card>
3369
+ <Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
3370
+ Uses
3371
+ </Card>
3372
+ <Card title="onBeforeScan" icon="link" href="#onbeforescan">
3373
+ Used by
3374
+ </Card>
3375
+ <Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
3376
+ Used by
3377
+ </Card>
3378
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
3379
+ Related
3380
+ </Card>
3381
+ </CardGroup>
3382
+
3383
+ ---
3384
+
3385
+
3386
+ ## `onAfterWrite`
3387
+
3388
+ ```typescript
3389
+ async onAfterWrite(): Promise<void>
3390
+ ```
3391
+
3392
+ Use this to trigger all registered plugin hooks after documentation has been written to disk — ideal for post-write tasks like cache invalidation, notifications, or cleanup.
3393
+
3394
+ `onAfterWrite` runs through every loaded plugin's `onAfterWrite` hook in sequence. It resolves when all hooks complete and does not return a value. If no plugins define this hook, it resolves immediately.
3395
+
3396
+ ### Parameters
3397
+
3398
+ This method takes no parameters.
3399
+
3400
+ ### Returns
3401
+
3402
+ | Type | Description |
3403
+ |------|-------------|
3404
+ | `Promise<void>` | Resolves when all plugin `onAfterWrite` hooks have completed. Does not return a value. |
3405
+
3406
+ ### When to Call
3407
+
3408
+ Call `onAfterWrite` immediately after all documentation files have been written. It pairs with `onBeforeWrite` to form a write lifecycle:
3409
+
3410
+ 1. `onBeforeWrite(docs)` — transform/filter docs before writing
3411
+ 2. *(write files to disk)*
3412
+ 3. `onAfterWrite()` — notify plugins that writing is complete
3413
+
3414
+ ### Example
3415
+
3416
+ ```typescript example.ts
3417
+ // Inline types to keep example self-contained
3418
+ type Hook = (...args: unknown[]) => Promise<unknown> | unknown
3419
+
3420
+ interface Plugin {
3421
+ name: string
3422
+ onAfterWrite?: () => Promise<void> | void
3423
+ onBeforeWrite?: <T>(docs: T[]) => Promise<T[]> | T[]
3424
+ }
3425
+
3426
+ // Simulated PluginManager implementation
3427
+ class PluginManager {
3428
+ private plugins: Plugin[] = []
3429
+
3430
+ register(plugin: Plugin): void {
3431
+ this.plugins.push(plugin)
3432
+ console.log(`Plugin registered: ${plugin.name}`)
3433
+ }
3434
+
3435
+ async runHook(hookName: keyof Plugin, ...args: unknown[]): Promise<unknown> {
3436
+ let result: unknown
3437
+ for (const plugin of this.plugins) {
3438
+ const hook = plugin[hookName] as Hook | undefined
3439
+ if (typeof hook === 'function') {
3440
+ result = await hook(...args)
3441
+ }
3442
+ }
3443
+ return result
3444
+ }
3445
+
3446
+ async onBeforeWrite<T>(docs: T[]): Promise<T[]> {
3447
+ return (await this.runHook('onBeforeWrite', docs) as T[]) || docs
3448
+ }
3449
+
3450
+ async onAfterWrite(): Promise<void> {
3451
+ await this.runHook('onAfterWrite')
3452
+ }
3453
+ }
3454
+
3455
+ // --- Example usage ---
3456
+
3457
+ const manager = new PluginManager()
3458
+
3459
+ // Register a plugin that logs after writing
3460
+ manager.register({
3461
+ name: 'cache-invalidation-plugin',
3462
+ onAfterWrite: async () => {
3463
+ console.log('[cache-invalidation-plugin] Cache cleared after write.')
3464
+ }
3465
+ })
3466
+
3467
+ // Register a plugin that sends a notification
3468
+ manager.register({
3469
+ name: 'notify-plugin',
3470
+ onAfterWrite: async () => {
3471
+ const webhookUrl = process.env.NOTIFY_WEBHOOK_URL || 'https://hooks.example.com/notify'
3472
+ console.log(`[notify-plugin] POST notification sent to: ${webhookUrl}`)
3473
+ }
3474
+ })
3475
+
3476
+ async function runWriteLifecycle() {
3477
+ try {
3478
+ const docs = [
3479
+ { id: 'doc-001', content: 'Getting Started' },
3480
+ { id: 'doc-002', content: 'API Reference' }
3481
+ ]
3482
+
3483
+ // Step 1: Pre-write hook
3484
+ const processedDocs = await manager.onBeforeWrite(docs)
3485
+ console.log(`Writing ${processedDocs.length} docs to disk...`)
3486
+
3487
+ // Step 2: (Simulated) write to disk
3488
+ processedDocs.forEach(doc => {
3489
+ console.log(` Written: ${doc.id}.md`)
3490
+ })
3491
+
3492
+ // Step 3: Post-write hook — notify all plugins
3493
+ await manager.onAfterWrite()
3494
+
3495
+ console.log('Write lifecycle complete.')
3496
+ // Expected output:
3497
+ // Plugin registered: cache-invalidation-plugin
3498
+ // Plugin registered: notify-plugin
3499
+ // Writing 2 docs to disk...
3500
+ // Written: doc-001.md
3501
+ // Written: doc-002.md
3502
+ // [cache-invalidation-plugin] Cache cleared after write.
3503
+ // [notify-plugin] POST notification sent to: https://hooks.example.com/notify
3504
+ // Write lifecycle complete.
3505
+ } catch (error) {
3506
+ console.error('Write lifecycle failed:', error)
3507
+ }
3508
+ }
3509
+
3510
+ runWriteLifecycle()
3511
+ ```
3512
+
3513
+ ### Related
3514
+
3515
+ <CardGroup cols={3}>
3516
+ <Card title="runHook" icon="link" href="#runhook">
3517
+ Uses
3518
+ </Card>
3519
+ <Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
3520
+ Uses
3521
+ </Card>
3522
+ <Card title="transformContent" icon="link" href="#transformcontent">
3523
+ Uses
3524
+ </Card>
3525
+ <Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
3526
+ Used by
3527
+ </Card>
3528
+ <Card title="transformContent" icon="link" href="#transformcontent">
3529
+ Used by
3530
+ </Card>
3531
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
3532
+ Related
3533
+ </Card>
3534
+ </CardGroup>
3535
+
3536
+ ---
3537
+
3538
+
3539
+ ## `onBeforeGenerate`
3540
+
3541
+ ```typescript
3542
+ async onBeforeGenerate<T>(elements: T[]): Promise<T[]>
3543
+ ```
3544
+
3545
+ Use this to run all registered plugins' `onBeforeGenerate` hooks against a collection of elements before documentation generation begins. This is the ideal interception point for filtering, transforming, sorting, or enriching your elements (e.g., parsed AST nodes, function signatures, class definitions) before they are handed off to the doc generator.
3546
+
3547
+ If no plugin hook modifies the elements, the original array is returned unchanged.
3548
+
3549
+ ## Parameters
3550
+
3551
+ | Name | Type | Required | Description |
3552
+ |------|------|----------|-------------|
3553
+ | `elements` | `T[]` | Yes | The array of elements (e.g., parsed code nodes, doc entries) to be processed by registered plugin hooks before generation |
3554
+
3555
+ ## Returns
3556
+
3557
+ | Condition | Returns |
3558
+ |-----------|---------|
3559
+ | A plugin hook transforms the elements | `Promise<T[]>` — the modified array returned by the hook |
3560
+ | No plugin hook is registered or hook returns falsy | `Promise<T[]>` — the original `elements` array, unchanged |
3561
+
3562
+ ### Example
3563
+
3564
+ ```typescript example.ts
3565
+ // Inline types to simulate the PluginManager behavior
3566
+ type Hook<T> = (elements: T[]) => Promise<T[] | null | undefined>
3567
+
3568
+ interface Plugin {
3569
+ onBeforeGenerate?: <T>(elements: T[]) => Promise<T[]>
3570
+ }
3571
+
3572
+ // Simulated PluginManager with onBeforeGenerate support
3573
+ class PluginManager {
3574
+ private plugins: Plugin[] = []
3575
+
3576
+ register(plugin: Plugin) {
3577
+ this.plugins.push(plugin)
3578
+ }
3579
+
3580
+ private async runHook<T>(hookName: keyof Plugin, elements: T[]): Promise<T[] | null> {
3581
+ let current: T[] = elements
3582
+ for (const plugin of this.plugins) {
3583
+ const hook = plugin[hookName] as Hook<T> | undefined
3584
+ if (typeof hook === 'function') {
3585
+ const result = await hook(current)
3586
+ if (result) current = result
3587
+ }
3588
+ }
3589
+ return current.length ? current : null
3590
+ }
3591
+
3592
+ async onBeforeGenerate<T>(elements: T[]): Promise<T[]> {
3593
+ return (await this.runHook<T>('onBeforeGenerate', elements)) || elements
3594
+ }
3595
+ }
3596
+
3597
+ // Simulated doc element type
3598
+ interface DocElement {
3599
+ name: string
3600
+ isPublic: boolean
3601
+ description: string
3602
+ }
3603
+
3604
+ // Example: a plugin that filters out private elements before generation
3605
+ const publicOnlyPlugin: Plugin = {
3606
+ onBeforeGenerate: async <T>(elements: T[]): Promise<T[]> => {
3607
+ const docs = elements as unknown as DocElement[]
3608
+ const filtered = docs.filter((el) => el.isPublic)
3609
+ console.log(`[Plugin] Filtered ${docs.length - filtered.length} private element(s)`)
3610
+ return filtered as unknown as T[]
3611
+ },
3612
+ }
3613
+
3614
+ // Example: a plugin that uppercases all names
3615
+ const uppercasePlugin: Plugin = {
3616
+ onBeforeGenerate: async <T>(elements: T[]): Promise<T[]> => {
3617
+ const docs = elements as unknown as DocElement[]
3618
+ const transformed = docs.map((el) => ({ ...el, name: el.name.toUpperCase() }))
3619
+ return transformed as unknown as T[]
3620
+ },
3621
+ }
3622
+
3623
+ async function main() {
3624
+ const manager = new PluginManager()
3625
+ manager.register(publicOnlyPlugin)
3626
+ manager.register(uppercasePlugin)
3627
+
3628
+ const rawElements: DocElement[] = [
3629
+ { name: 'getUserById', isPublic: true, description: 'Fetches a user by ID' },
3630
+ { name: '_internalSync', isPublic: false, description: 'Internal sync helper' },
3631
+ { name: 'createSession', isPublic: true, description: 'Creates a new session' },
3632
+ ]
3633
+
3634
+ try {
3635
+ const processed = await manager.onBeforeGenerate(rawElements)
3636
+ console.log('Elements ready for generation:', processed)
3637
+ // Expected output:
3638
+ // [Plugin] Filtered 1 private element(s)
3639
+ // Elements ready for generation: [
3640
+ // { name: 'GETUSERBYID', isPublic: true, description: 'Fetches a user by ID' },
3641
+ // { name: 'CREATESESSION', isPublic: true, description: 'Creates a new session' }
3642
+ // ]
3643
+ } catch (error) {
3644
+ console.error('onBeforeGenerate failed:', error)
3645
+ }
3646
+ }
3647
+
3648
+ main()
3649
+ ```
3650
+
3651
+ ### Related
3652
+
3653
+ <CardGroup cols={3}>
3654
+ <Card title="runHook" icon="link" href="#runhook">
3655
+ Uses
3656
+ </Card>
3657
+ <Card title="onAfterScan" icon="link" href="#onafterscan">
3658
+ Uses
3659
+ </Card>
3660
+ <Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
3661
+ Uses
3662
+ </Card>
3663
+ <Card title="onAfterScan" icon="link" href="#onafterscan">
3664
+ Used by
3665
+ </Card>
3666
+ <Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
3667
+ Used by
3668
+ </Card>
3669
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
3670
+ Related
3671
+ </Card>
3672
+ </CardGroup>
3673
+
3674
+ ---
3675
+
3676
+
3677
+ ## `onBeforeScan`
3678
+
3679
+ ```typescript
3680
+ async onBeforeScan(): Promise<void>
3681
+ ```
3682
+
3683
+ Use this to trigger all registered plugins' pre-scan lifecycle hooks before a documentation scan begins. This allows plugins to perform setup tasks, validate configurations, or prepare resources before any files are processed.
3684
+
3685
+ ## Parameters
3686
+
3687
+ This method takes no parameters.
3688
+
3689
+ ## Returns
3690
+
3691
+ | Type | Description |
3692
+ |------|-------------|
3693
+ | `Promise<void>` | Resolves when all registered plugins have completed their `onBeforeScan` hooks. Rejects if any plugin hook throws an error. |
3694
+
3695
+ ## When to Call
3696
+
3697
+ Call `onBeforeScan()` after `onInit()` but **before** any file scanning or parsing begins. This is the correct place for plugins to:
3698
+ - Set up temporary directories or caches
3699
+ - Validate that required external tools are available
3700
+ - Reset state from a previous scan run
3701
+ - Log scan start events or metrics
3702
+
3703
+ ### Example
3704
+
3705
+ ```typescript example.ts
3706
+ // Inline types to simulate the PluginManager behavior
3707
+ type HookName = 'onInit' | 'onBeforeScan' | 'onAfterScan'
3708
+
3709
+ interface Plugin {
3710
+ name: string
3711
+ onInit?: () => Promise<void>
3712
+ onBeforeScan?: () => Promise<void>
3713
+ onAfterScan?: <T>(elements: T[]) => Promise<T[]>
3714
+ }
3715
+
3716
+ // Simulated PluginManager class
3717
+ class PluginManager {
3718
+ private plugins: Plugin[] = []
3719
+
3720
+ register(plugin: Plugin): void {
3721
+ this.plugins.push(plugin)
3722
+ console.log(`Plugin registered: ${plugin.name}`)
3723
+ }
3724
+
3725
+ private async runHook(hookName: HookName, ...args: unknown[]): Promise<unknown> {
3726
+ for (const plugin of this.plugins) {
3727
+ const hook = plugin[hookName] as ((...a: unknown[]) => Promise<unknown>) | undefined
3728
+ if (typeof hook === 'function') {
3729
+ await hook.call(plugin, ...args)
3730
+ }
3731
+ }
3732
+ return undefined
3733
+ }
3734
+
3735
+ async onInit(): Promise<void> {
3736
+ await this.runHook('onInit')
3737
+ }
3738
+
3739
+ async onBeforeScan(): Promise<void> {
3740
+ await this.runHook('onBeforeScan')
3741
+ }
3742
+
3743
+ async onAfterScan<T>(elements: T[]): Promise<T[]> {
3744
+ return ((await this.runHook('onAfterScan', elements)) as T[]) || elements
3745
+ }
3746
+ }
3747
+
3748
+ // Example plugins that use the onBeforeScan hook
3749
+ const cachePlugin: Plugin = {
3750
+ name: 'cache-plugin',
3751
+ onBeforeScan: async () => {
3752
+ const cacheDir = process.env.CACHE_DIR || '/tmp/autodocs-cache'
3753
+ console.log(`[cache-plugin] Clearing cache at: ${cacheDir}`)
3754
+ // Simulate async cache clearing
3755
+ await new Promise((resolve) => setTimeout(resolve, 10))
3756
+ console.log('[cache-plugin] Cache cleared successfully')
3757
+ },
3758
+ }
3759
+
3760
+ const metricsPlugin: Plugin = {
3761
+ name: 'metrics-plugin',
3762
+ onBeforeScan: async () => {
3763
+ const scanId = `scan_${Date.now()}`
3764
+ console.log(`[metrics-plugin] Starting scan session: ${scanId}`)
3765
+ },
3766
+ }
3767
+
3768
+ async function main() {
3769
+ const manager = new PluginManager()
3770
+
3771
+ manager.register(cachePlugin)
3772
+ manager.register(metricsPlugin)
3773
+
3774
+ try {
3775
+ // Step 1: Initialize all plugins
3776
+ await manager.onInit()
3777
+ console.log('✓ Plugins initialized')
3778
+
3779
+ // Step 2: Run pre-scan hooks before processing any files
3780
+ await manager.onBeforeScan()
3781
+ console.log('✓ Pre-scan hooks completed — safe to begin file scanning')
3782
+
3783
+ // Step 3: (Scanning would happen here)
3784
+ // const files = await scanner.findFiles('./src')
3785
+ // const elements = await parser.parse(files)
3786
+ // const processed = await manager.onAfterScan(elements)
3787
+ } catch (error) {
3788
+ console.error('Pre-scan hook failed — aborting scan:', error)
3789
+ process.exit(1)
3790
+ }
3791
+ }
3792
+
3793
+ main()
3794
+
3795
+ // Expected output:
3796
+ // Plugin registered: cache-plugin
3797
+ // Plugin registered: metrics-plugin
3798
+ // ✓ Plugins initialized
3799
+ // [cache-plugin] Clearing cache at: /tmp/autodocs-cache
3800
+ // [cache-plugin] Cache cleared successfully
3801
+ // [metrics-plugin] Starting scan session: scan_1718000000000
3802
+ // ✓ Pre-scan hooks completed — safe to begin file scanning
3803
+ ```
3804
+
3805
+ ### Related
3806
+
3807
+ <CardGroup cols={3}>
3808
+ <Card title="runHook" icon="link" href="#runhook">
3809
+ Uses
3810
+ </Card>
3811
+ <Card title="onInit" icon="link" href="#oninit">
3812
+ Uses
3813
+ </Card>
3814
+ <Card title="onAfterScan" icon="link" href="#onafterscan">
3815
+ Uses
3816
+ </Card>
3817
+ <Card title="onInit" icon="link" href="#oninit">
3818
+ Used by
3819
+ </Card>
3820
+ <Card title="onAfterScan" icon="link" href="#onafterscan">
3821
+ Used by
3822
+ </Card>
3823
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
3824
+ Related
3825
+ </Card>
3826
+ </CardGroup>
3827
+
3828
+ ---
3829
+
3830
+
3831
+ ## `onBeforeWrite`
3832
+
3833
+ ```typescript
3834
+ async onBeforeWrite<T>(docs: T[]): Promise<T[]>
3835
+ ```
3836
+
3837
+ Use this to run all registered plugins' `onBeforeWrite` hooks against your generated docs array, allowing plugins to transform, filter, or enrich documents just before they are written to disk.
3838
+
3839
+ This is the last interception point before output is committed — ideal for plugins that need to inject metadata, reorder entries, strip internal fields, or validate documents.
3840
+
3841
+ ## Parameters
3842
+
3843
+ | Name | Type | Required | Description |
3844
+ |------|------|----------|-------------|
3845
+ | `docs` | `T[]` | Yes | Array of documents to be passed through all registered `onBeforeWrite` plugin hooks before writing |
3846
+
3847
+ ## Returns
3848
+
3849
+ | Condition | Return Value |
3850
+ |-----------|-------------|
3851
+ | One or more plugins transform the docs | `Promise<T[]>` — the transformed array returned by the last hook in the chain |
3852
+ | No plugins modify the docs | `Promise<T[]>` — the original `docs` array passed in, unchanged |
3853
+
3854
+ The method **always returns an array** — it falls back to the original `docs` if the hook pipeline returns a falsy value.
3855
+
3856
+ ### Example
3857
+
3858
+ ```typescript example.ts
3859
+ // Inline types to simulate the PluginManager behavior
3860
+ type Plugin<T> = {
3861
+ onBeforeWrite?: (docs: T[]) => Promise<T[]>
3862
+ }
3863
+
3864
+ type DocEntry = {
3865
+ name: string
3866
+ content: string
3867
+ metadata?: Record<string, unknown>
3868
+ }
3869
+
3870
+ // Simulated PluginManager with onBeforeWrite support
3871
+ class PluginManager<T> {
3872
+ private plugins: Plugin<T>[] = []
3873
+
3874
+ register(plugin: Plugin<T>) {
3875
+ this.plugins.push(plugin)
3876
+ }
3877
+
3878
+ private async runHook(hookName: keyof Plugin<T>, data: T[]): Promise<T[] | null> {
3879
+ let result: T[] = data
3880
+ for (const plugin of this.plugins) {
3881
+ const hook = plugin[hookName]
3882
+ if (typeof hook === 'function') {
3883
+ result = await (hook as (docs: T[]) => Promise<T[]>)(result)
3884
+ }
3885
+ }
3886
+ return result.length > 0 ? result : null
3887
+ }
3888
+
3889
+ async onBeforeWrite(docs: T[]): Promise<T[]> {
3890
+ return (await this.runHook('onBeforeWrite', docs)) || docs
3891
+ }
3892
+ }
3893
+
3894
+ // Example plugins that transform docs before writing
3895
+ const timestampPlugin: Plugin<DocEntry> = {
3896
+ onBeforeWrite: async (docs) => {
3897
+ console.log('[timestampPlugin] Injecting write timestamps...')
3898
+ return docs.map((doc) => ({
3899
+ ...doc,
3900
+ metadata: {
3901
+ ...doc.metadata,
3902
+ writtenAt: new Date().toISOString(),
3903
+ },
3904
+ }))
3905
+ },
3906
+ }
3907
+
3908
+ const filterPlugin: Plugin<DocEntry> = {
3909
+ onBeforeWrite: async (docs) => {
3910
+ console.log('[filterPlugin] Removing draft documents...')
3911
+ return docs.filter((doc) => !doc.name.startsWith('DRAFT_'))
3912
+ },
3913
+ }
3914
+
3915
+ // Setup
3916
+ const manager = new PluginManager<DocEntry>()
3917
+ manager.register(timestampPlugin)
3918
+ manager.register(filterPlugin)
3919
+
3920
+ const rawDocs: DocEntry[] = [
3921
+ { name: 'getting-started', content: '# Getting Started\n...' },
3922
+ { name: 'DRAFT_advanced-usage', content: '# Advanced Usage (WIP)' },
3923
+ { name: 'api-reference', content: '# API Reference\n...' },
3924
+ ]
3925
+
3926
+ async function main() {
3927
+ try {
3928
+ console.log('Docs before onBeforeWrite:', rawDocs.map((d) => d.name))
3929
+
3930
+ const finalDocs = await manager.onBeforeWrite(rawDocs)
3931
+
3932
+ console.log('\nDocs after onBeforeWrite:')
3933
+ finalDocs.forEach((doc) => {
3934
+ console.log(` - ${doc.name}`, doc.metadata ? `(writtenAt: ${doc.metadata.writtenAt})` : '')
3935
+ })
3936
+
3937
+ // Expected output:
3938
+ // Docs before onBeforeWrite: ['getting-started', 'DRAFT_advanced-usage', 'api-reference']
3939
+ // [timestampPlugin] Injecting write timestamps...
3940
+ // [filterPlugin] Removing draft documents...
3941
+ // Docs after onBeforeWrite:
3942
+ // - getting-started (writtenAt: 2024-01-15T10:30:00.000Z)
3943
+ // - api-reference (writtenAt: 2024-01-15T10:30:00.000Z)
3944
+ } catch (error) {
3945
+ console.error('onBeforeWrite pipeline failed:', error)
3946
+ }
3947
+ }
3948
+
3949
+ main()
3950
+ ```
3951
+
3952
+ ### Related
3953
+
3954
+ <CardGroup cols={3}>
3955
+ <Card title="runHook" icon="link" href="#runhook">
3956
+ Uses
3957
+ </Card>
3958
+ <Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
3959
+ Uses
3960
+ </Card>
3961
+ <Card title="onAfterWrite" icon="link" href="#onafterwrite">
3962
+ Uses
3963
+ </Card>
3964
+ <Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
3965
+ Used by
3966
+ </Card>
3967
+ <Card title="onAfterWrite" icon="link" href="#onafterwrite">
3968
+ Used by
3969
+ </Card>
3970
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
3971
+ Related
3972
+ </Card>
3973
+ </CardGroup>
3974
+
3975
+ ---
3976
+
3977
+
3978
+ ## `onInit`
3979
+
3980
+ ```typescript
3981
+ async onInit(): Promise<void>
3982
+ ```
3983
+
3984
+ Use this to trigger the initialization lifecycle hook across all registered plugins in a `PluginManager` instance. Call this once after all plugins have been registered and before any scanning or processing begins.
3985
+
3986
+ This method delegates to the internal `runHook('onInit')` mechanism, ensuring every plugin's `onInit` handler is executed in sequence.
3987
+
3988
+ ## Parameters
3989
+
3990
+ _None_
3991
+
3992
+ ## Returns
3993
+
3994
+ | Type | Description |
3995
+ |------|-------------|
3996
+ | `Promise<void>` | Resolves when all plugin `onInit` hooks have completed. Rejects if any plugin's `onInit` throws an error. |
3997
+
3998
+ ## When to Call
3999
+
4000
+ - After instantiating `PluginManager` and registering all plugins
4001
+ - Before calling `onBeforeScan()` or any other lifecycle hooks
4002
+ - Typically called once per application startup
4003
+
4004
+ ### Example
4005
+
4006
+ ```typescript example.ts
4007
+ // Inline types to simulate the PluginManager lifecycle
4008
+ type HookName = 'onInit' | 'onBeforeScan' | 'onAfterScan'
4009
+
4010
+ type Plugin = {
4011
+ name: string
4012
+ onInit?: () => Promise<void>
4013
+ onBeforeScan?: () => Promise<void>
4014
+ }
4015
+
4016
+ // Simulated PluginManager class (self-contained, no external imports)
4017
+ class PluginManager {
4018
+ private plugins: Plugin[] = []
4019
+
4020
+ register(plugin: Plugin): void {
4021
+ this.plugins.push(plugin)
4022
+ console.log(`Plugin registered: ${plugin.name}`)
4023
+ }
4024
+
4025
+ private async runHook(hookName: HookName): Promise<void> {
4026
+ for (const plugin of this.plugins) {
4027
+ const hook = plugin[hookName]
4028
+ if (typeof hook === 'function') {
4029
+ await hook()
4030
+ }
4031
+ }
4032
+ }
4033
+
4034
+ async onInit(): Promise<void> {
4035
+ await this.runHook('onInit')
4036
+ }
4037
+
4038
+ async onBeforeScan(): Promise<void> {
4039
+ await this.runHook('onBeforeScan')
4040
+ }
4041
+ }
4042
+
4043
+ // Example plugins with onInit handlers
4044
+ const analyticsPlugin: Plugin = {
4045
+ name: 'analytics-plugin',
4046
+ onInit: async () => {
4047
+ const apiKey = process.env.ANALYTICS_API_KEY || 'demo-key-abc123'
4048
+ console.log(`[analytics-plugin] Initialized with key: ${apiKey.slice(0, 8)}...`)
4049
+ },
4050
+ }
4051
+
4052
+ const cachePlugin: Plugin = {
4053
+ name: 'cache-plugin',
4054
+ onInit: async () => {
4055
+ console.log('[cache-plugin] Cache warmed up and ready')
4056
+ },
4057
+ }
4058
+
4059
+ const loggingPlugin: Plugin = {
4060
+ name: 'logging-plugin',
4061
+ // No onInit — should be safely skipped
4062
+ }
4063
+
4064
+ async function main() {
4065
+ const manager = new PluginManager()
4066
+
4067
+ manager.register(analyticsPlugin)
4068
+ manager.register(cachePlugin)
4069
+ manager.register(loggingPlugin)
4070
+
4071
+ console.log('\n--- Running onInit lifecycle hook ---')
4072
+
4073
+ try {
4074
+ await manager.onInit()
4075
+ console.log('\n✓ All plugin onInit hooks completed successfully')
4076
+ // Expected output:
4077
+ // Plugin registered: analytics-plugin
4078
+ // Plugin registered: cache-plugin
4079
+ // Plugin registered: logging-plugin
4080
+ // --- Running onInit lifecycle hook ---
4081
+ // [analytics-plugin] Initialized with key: demo-key...
4082
+ // [cache-plugin] Cache warmed up and ready
4083
+ // ✓ All plugin onInit hooks completed successfully
4084
+ } catch (error) {
4085
+ console.error('✗ Plugin initialization failed:', error)
4086
+ process.exit(1)
4087
+ }
4088
+ }
4089
+
4090
+ main()
4091
+ ```
4092
+
4093
+ ### Related
4094
+
4095
+ <CardGroup cols={3}>
4096
+ <Card title="runHook" icon="link" href="#runhook">
4097
+ Uses
4098
+ </Card>
4099
+ <Card title="onBeforeScan" icon="link" href="#onbeforescan">
4100
+ Uses
4101
+ </Card>
4102
+ <Card title="onBeforeScan" icon="link" href="#onbeforescan">
4103
+ Used by
4104
+ </Card>
4105
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
4106
+ Related
4107
+ </Card>
4108
+ </CardGroup>
4109
+
4110
+ ---
4111
+
4112
+
4113
+ ## `register`
4114
+
4115
+ ```typescript
4116
+ register(plugin: SkryptPlugin): void
4117
+ ```
4118
+
4119
+ Use this to manually register a plugin with the `PluginManager`, adding it to the active plugin list so its hooks can be executed during the build or runtime lifecycle.
4120
+
4121
+ This is the direct registration path — useful when you have a plugin object already constructed in memory and don't need to load it from disk.
4122
+
4123
+ ### Parameters
4124
+
4125
+ | Name | Type | Required | Description |
4126
+ |------|------|----------|-------------|
4127
+ | `plugin` | `SkryptPlugin` | ✅ Yes | The plugin object to register. Must have at minimum a `name` property; `version` is optional but will be included in the log output if present. |
4128
+
4129
+ ### Returns
4130
+
4131
+ `void` — Pushes the plugin into the internal plugins array and logs a confirmation message to the console in the format:
4132
+ - `Loaded plugin: <name>` (no version)
4133
+ - `Loaded plugin: <name> v<version>` (with version)
4134
+
4135
+ ### Behavior Notes
4136
+ - Plugins are stored in insertion order and hooks are run in that order.
4137
+ - There is no deduplication check — registering the same plugin twice will result in it being added twice.
4138
+ - The log output is a side effect of every registration call.
4139
+
4140
+ ### Example
4141
+
4142
+ ```typescript example.ts
4143
+ // --- Inline types (do not import from autodocs) ---
4144
+ type HookFn = (...args: unknown[]) => unknown | Promise<unknown>
4145
+
4146
+ interface SkryptPlugin {
4147
+ name: string
4148
+ version?: string
4149
+ onBuildStart?: HookFn
4150
+ onBuildEnd?: HookFn
4151
+ transform?: HookFn
4152
+ }
4153
+
4154
+ // --- Inline PluginManager implementation ---
4155
+ class PluginManager {
4156
+ private plugins: SkryptPlugin[] = []
4157
+
4158
+ register(plugin: SkryptPlugin): void {
4159
+ this.plugins.push(plugin)
4160
+ console.log(
4161
+ `Loaded plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`
4162
+ )
4163
+ }
4164
+
4165
+ async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined> {
4166
+ for (const plugin of this.plugins) {
4167
+ const fn = plugin[hook]
4168
+ if (typeof fn === 'function') {
4169
+ const result = await fn(...args)
4170
+ if (result !== undefined) return result as T
4171
+ }
4172
+ }
4173
+ return undefined
4174
+ }
4175
+
4176
+ getPlugins(): SkryptPlugin[] {
4177
+ return [...this.plugins]
4178
+ }
4179
+ }
4180
+
4181
+ // --- Usage example ---
4182
+ const manager = new PluginManager()
4183
+
4184
+ // Plugin with a version
4185
+ const sassPlugin: SkryptPlugin = {
4186
+ name: 'sass-compiler',
4187
+ version: '2.1.0',
4188
+ onBuildStart: async () => {
4189
+ console.log(' [sass-compiler] Compiling stylesheets...')
4190
+ },
4191
+ }
4192
+
4193
+ // Plugin without a version
4194
+ const envPlugin: SkryptPlugin = {
4195
+ name: 'env-injector',
4196
+ onBuildStart: async () => {
4197
+ console.log(' [env-injector] Injecting environment variables...')
4198
+ },
4199
+ }
4200
+
4201
+ async function main() {
4202
+ try {
4203
+ manager.register(sassPlugin)
4204
+ // Output: Loaded plugin: sass-compiler v2.1.0
4205
+
4206
+ manager.register(envPlugin)
4207
+ // Output: Loaded plugin: env-injector
4208
+
4209
+ console.log('\nRegistered plugins:', manager.getPlugins().map(p => p.name))
4210
+ // Output: Registered plugins: [ 'sass-compiler', 'env-injector' ]
4211
+
4212
+ console.log('\nRunning onBuildStart hooks...')
4213
+ await manager.runHook('onBuildStart')
4214
+ // Output:
4215
+ // [sass-compiler] Compiling stylesheets...
4216
+ // [env-injector] Injecting environment variables...
4217
+ } catch (error) {
4218
+ console.error('Plugin registration failed:', error)
4219
+ }
4220
+ }
4221
+
4222
+ main()
4223
+ ```
4224
+
4225
+ ### Related
4226
+
4227
+ <CardGroup cols={3}>
4228
+ <Card title="runHook" icon="link" href="#runhook">
4229
+ Uses
4230
+ </Card>
4231
+ <Card title="runHook" icon="link" href="#runhook">
4232
+ Used by
4233
+ </Card>
4234
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
4235
+ Related
4236
+ </Card>
4237
+ </CardGroup>
4238
+
4239
+ ---
4240
+
4241
+
4242
+ ## `runHook`
4243
+
4244
+ ```typescript
4245
+ async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined>
4246
+ ```
4247
+
4248
+ Use this to execute a named hook across all registered plugins in sequence, passing data through each plugin's implementation of that hook.
4249
+
4250
+ `runHook` iterates over every registered plugin, finds those that implement the specified hook method, and calls them in registration order. Each plugin receives the result of the previous plugin, enabling a pipeline/middleware pattern where plugins can transform data as it flows through.
4251
+
4252
+ ## Parameters
4253
+
4254
+ | Name | Type | Required | Description |
4255
+ |------|------|----------|-------------|
4256
+ | `hook` | `keyof SkryptPlugin` | Yes | The name of the plugin lifecycle method to invoke (e.g., `'beforeCompile'`, `'afterCompile'`) |
4257
+ | `...args` | `unknown[]` | No | Arguments passed to the hook. The first argument (`args[0]`) is used as the initial result value and passed through the plugin chain |
4258
+
4259
+ ## Returns
4260
+
4261
+ | Condition | Return Value |
4262
+ |-----------|-------------|
4263
+ | One or more plugins implement the hook | `Promise<T>` — the final transformed value after all plugins have processed it |
4264
+ | No plugins implement the hook | `Promise<undefined>` — resolves with the initial `args[0]` cast as `T`, or `undefined` if no args provided |
4265
+
4266
+ The return type `T` is inferred from the generic parameter you provide at the call site.
4267
+
4268
+ ### Example
4269
+
4270
+ ```typescript example.ts
4271
+ // --- Inline types (do not import from autodocs) ---
4272
+ interface SkryptPlugin {
4273
+ name: string
4274
+ version?: string
4275
+ beforeCompile?: (source: string) => Promise<string> | string
4276
+ afterCompile?: (output: string) => Promise<string> | string
4277
+ onError?: (error: Error) => Promise<void> | void
4278
+ }
4279
+
4280
+ // --- Inline PluginManager implementation ---
4281
+ class PluginManager {
4282
+ private plugins: SkryptPlugin[] = []
4283
+
4284
+ register(plugin: SkryptPlugin): void {
4285
+ this.plugins.push(plugin)
4286
+ console.log(
4287
+ `Loaded plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`
4288
+ )
4289
+ }
4290
+
4291
+ async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined> {
4292
+ let result = args[0] as T
4293
+
4294
+ for (const plugin of this.plugins) {
4295
+ const fn = plugin[hook] as ((...args: unknown[]) => Promise<T> | T) | undefined
4296
+ if (typeof fn === 'function') {
4297
+ result = await fn.call(plugin, result)
4298
+ }
4299
+ }
4300
+
4301
+ return result
4302
+ }
4303
+ }
4304
+
4305
+ // --- Example plugins ---
4306
+ const stripCommentsPlugin: SkryptPlugin = {
4307
+ name: 'strip-comments',
4308
+ version: '1.0.0',
4309
+ beforeCompile(source: string) {
4310
+ console.log('[strip-comments] Removing comments...')
4311
+ return source.replace(/\/\/.*$/gm, '').trim()
4312
+ },
4313
+ }
4314
+
4315
+ const minifyPlugin: SkryptPlugin = {
4316
+ name: 'minify',
4317
+ version: '2.1.0',
4318
+ beforeCompile(source: string) {
4319
+ console.log('[minify] Minifying source...')
4320
+ return source.replace(/\s+/g, ' ').trim()
4321
+ },
4322
+ afterCompile(output: string) {
4323
+ console.log('[minify] Post-processing output...')
4324
+ return `/* minified */\n${output}`
4325
+ },
4326
+ }
4327
+
4328
+ const bannerPlugin: SkryptPlugin = {
4329
+ name: 'banner',
4330
+ afterCompile(output: string) {
4331
+ console.log('[banner] Injecting banner...')
4332
+ return `// Built with Skrypt\n${output}`
4333
+ },
4334
+ }
4335
+
4336
+ // --- Run the example ---
4337
+ async function main() {
4338
+ const manager = new PluginManager()
4339
+
4340
+ manager.register(stripCommentsPlugin)
4341
+ manager.register(minifyPlugin)
4342
+ manager.register(bannerPlugin)
4343
+
4344
+ const rawSource = `
4345
+ // This is a comment
4346
+ let x = 1
4347
+ let y = 2
4348
+ console.log(x + y)
4349
+ `
4350
+
4351
+ try {
4352
+ // Run the beforeCompile hook — source flows through each plugin in order
4353
+ const processedSource = await manager.runHook<string>('beforeCompile', rawSource)
4354
+ console.log('\n--- Processed Source ---')
4355
+ console.log(processedSource)
4356
+ // Output: "let x = 1 let y = 2 console.log(x + y)"
4357
+
4358
+ // Run the afterCompile hook — only minify and banner plugins implement it
4359
+ const finalOutput = await manager.runHook<string>('afterCompile', processedSource)
4360
+ console.log('\n--- Final Output ---')
4361
+ console.log(finalOutput)
4362
+ // Output:
4363
+ // // Built with Skrypt
4364
+ // /* minified */
4365
+ // let x = 1 let y = 2 console.log(x + y)
4366
+
4367
+ // Run a hook with no implementations — returns the initial value unchanged
4368
+ const unchanged = await manager.runHook<string>('onError', 'no-op value')
4369
+ console.log('\n--- Hook with no matching implementations ---')
4370
+ console.log(unchanged) // Output: no-op value (onError expects void, shown for demonstration)
4371
+ } catch (error) {
4372
+ console.error('Hook execution failed:', error)
4373
+ }
4374
+ }
4375
+
4376
+ main()
4377
+ ```
4378
+
4379
+ ### Related
4380
+
4381
+ <CardGroup cols={3}>
4382
+ <Card title="register" icon="link" href="#register">
4383
+ Uses
4384
+ </Card>
4385
+ <Card title="register" icon="link" href="#register">
4386
+ Used by
4387
+ </Card>
4388
+ <Card title="onInit" icon="link" href="#oninit">
4389
+ Used by
4390
+ </Card>
4391
+ <Card title="onBeforeScan" icon="link" href="#onbeforescan">
4392
+ Used by
4393
+ </Card>
4394
+ <Card title="onAfterScan" icon="link" href="#onafterscan">
4395
+ Used by
4396
+ </Card>
4397
+ <Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
4398
+ Used by
4399
+ </Card>
4400
+ </CardGroup>
4401
+
4402
+ ---
4403
+
4404
+
4405
+ ## `transformContent`
4406
+
4407
+ ```typescript
4408
+ async transformContent(content: string, filePath: string): Promise<string>
4409
+ ```
4410
+
4411
+ Use this to pipe file content through a chain of registered plugins, each of which can modify the content before it's written to disk. Ideal for applying transformations like syntax highlighting, markdown processing, code formatting, or custom replacements across your documentation pipeline.
4412
+
4413
+ Each plugin in the manager that implements `transformContent` is called in sequence — the output of one becomes the input of the next.
4414
+
4415
+ ## Parameters
4416
+
4417
+ | Name | Type | Required | Description |
4418
+ |------|------|----------|-------------|
4419
+ | `content` | `string` | ✅ | The raw file content to be transformed |
4420
+ | `filePath` | `string` | ✅ | The path of the file being processed — plugins can use this to apply conditional logic based on file type or location |
4421
+
4422
+ ## Returns
4423
+
4424
+ | Condition | Return Value |
4425
+ |-----------|-------------|
4426
+ | All plugins succeed | `Promise<string>` — the fully transformed content after passing through every plugin in the chain |
4427
+ | A plugin throws | The error propagates — content from that point forward is not processed |
4428
+ | No plugins implement `transformContent` | `Promise<string>` — original content returned unchanged |
4429
+
4430
+ ### Example
4431
+
4432
+ ```typescript example.ts
4433
+ // Inline types — no external imports needed
4434
+ type TransformPlugin = {
4435
+ name: string
4436
+ transformContent?: (content: string, filePath: string) => Promise<string>
4437
+ }
4438
+
4439
+ // Simulated PluginManager class
4440
+ class PluginManager {
4441
+ private plugins: TransformPlugin[]
4442
+
4443
+ constructor(plugins: TransformPlugin[]) {
4444
+ this.plugins = plugins
4445
+ }
4446
+
4447
+ async transformContent(content: string, filePath: string): Promise<string> {
4448
+ let result = content
4449
+
4450
+ for (const plugin of this.plugins) {
4451
+ if (typeof plugin.transformContent === 'function') {
4452
+ try {
4453
+ result = await plugin.transformContent(result, filePath)
4454
+ console.log(`[${plugin.name}] transformation applied`)
4455
+ } catch (error) {
4456
+ console.error(`[${plugin.name}] failed to transform content:`, error)
4457
+ throw error
4458
+ }
4459
+ }
4460
+ }
4461
+
4462
+ return result
4463
+ }
4464
+ }
4465
+
4466
+ // Example plugins — each transforms content in sequence
4467
+ const markdownHeaderPlugin: TransformPlugin = {
4468
+ name: 'markdown-header',
4469
+ transformContent: async (content, filePath) => {
4470
+ if (!filePath.endsWith('.md')) return content
4471
+ const timestamp = new Date().toISOString()
4472
+ return `<!-- Generated: ${timestamp} -->\n${content}`
4473
+ }
4474
+ }
4475
+
4476
+ const codeFencePlugin: TransformPlugin = {
4477
+ name: 'code-fence-formatter',
4478
+ transformContent: async (content, _filePath) => {
4479
+ // Normalize code fences: replace ``` with properly spaced versions
4480
+ return content.replace(/```(\w+)/g, '```$1\n').trimEnd()
4481
+ }
4482
+ }
4483
+
4484
+ const noOpPlugin: TransformPlugin = {
4485
+ name: 'logger-only'
4486
+ // No transformContent — will be skipped automatically
4487
+ }
4488
+
4489
+ async function main() {
4490
+ try {
4491
+ const manager = new PluginManager([
4492
+ markdownHeaderPlugin,
4493
+ codeFencePlugin,
4494
+ noOpPlugin
4495
+ ])
4496
+
4497
+ const rawContent = `# My Docs\n\nHere is some code:\n\`\`\`typescript\nconst x = 1\n\`\`\``
4498
+ const filePath = '/docs/guide/getting-started.md'
4499
+
4500
+ console.log('--- Input ---')
4501
+ console.log(rawContent)
4502
+ console.log('\n--- Transforming... ---')
4503
+
4504
+ const transformed = await manager.transformContent(rawContent, filePath)
4505
+
4506
+ console.log('\n--- Output ---')
4507
+ console.log(transformed)
4508
+ // Expected output:
4509
+ // <!-- Generated: 2024-01-15T10:30:00.000Z -->
4510
+ // # My Docs
4511
+ //
4512
+ // Here is some code:
4513
+ // ```typescript
4514
+ //
4515
+ // const x = 1
4516
+ // ```
4517
+ } catch (error) {
4518
+ console.error('Transformation pipeline failed:', error)
4519
+ process.exit(1)
4520
+ }
4521
+ }
4522
+
4523
+ main()
4524
+ ```
4525
+
4526
+ ### Related
4527
+
4528
+ <CardGroup cols={3}>
4529
+ <Card title="runHook" icon="link" href="#runhook">
4530
+ Uses
4531
+ </Card>
4532
+ <Card title="onAfterWrite" icon="link" href="#onafterwrite">
4533
+ Uses
4534
+ </Card>
4535
+ <Card title="onAfterWrite" icon="link" href="#onafterwrite">
4536
+ Used by
4537
+ </Card>
4538
+ <Card title="PluginManager" icon="link" href="#pluginmanager">
4539
+ Related
4540
+ </Card>
4541
+ </CardGroup>
4542
+
4543
+ ---
4544
+