@dxtmisha/scripts 0.7.10 → 0.7.11

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.
@@ -0,0 +1,375 @@
1
+ import {
2
+ type Browser,
3
+ type EvaluateFunc,
4
+ type HTTPResponse,
5
+ launch,
6
+ type Page,
7
+ type PuppeteerLifeCycleEvent
8
+ } from 'puppeteer'
9
+ import { sleep } from '@dxtmisha/functional-basic'
10
+
11
+ import {
12
+ type ScreenshotMetrics,
13
+ type ScreenshotOptions,
14
+ SCREENSHOT_ARGS,
15
+ SCREENSHOT_FORMAT,
16
+ SCREENSHOT_HEIGHTS,
17
+ SCREENSHOT_TIMEOUT,
18
+ SCREENSHOT_QUALITY,
19
+ SCREENSHOT_WAIT_UNTIL,
20
+ SCREENSHOT_WIDTHS
21
+ } from '../types/screenshotTypes'
22
+
23
+ /**
24
+ * Class for managing browser instances and taking screenshots using Puppeteer.
25
+ *
26
+ * Класс для управления экземплярами браузера и создания скриншотов с помощью Puppeteer.
27
+ */
28
+ export class BrowserItem {
29
+ /** Browser instance / Экземпляр браузера */
30
+ protected browser?: Browser
31
+
32
+ /** Page instance / Экземпляр страницы */
33
+ protected page?: Page
34
+
35
+ /** HTTP Response from page loading / HTTP-ответ при загрузке страницы */
36
+ protected response?: HTTPResponse
37
+
38
+ /** Total scrollable width of the loaded page / Полная прокручиваемая ширина загруженной страницы */
39
+ protected pageWidth: number = -1
40
+
41
+ /** Total scrollable height of the loaded page / Полная прокручиваемая высота загруженной страницы */
42
+ protected pageHeight: number = -1
43
+
44
+ /**
45
+ * Constructor
46
+ * @param url component path / путь к компоненту
47
+ * @param options additional options for capture / дополнительные опции захвата
48
+ */
49
+ constructor(
50
+ protected url: string,
51
+ protected readonly options: ScreenshotOptions = {}
52
+ ) {
53
+ }
54
+
55
+ /** Additional arguments for browser launch / Дополнительные аргументы для запуска браузера */
56
+ get args(): string[] {
57
+ return this.options.args ?? SCREENSHOT_ARGS
58
+ }
59
+
60
+ /** Condition to wait for when navigating to the page / Условие, которое нужно дождаться при переходе на страницу */
61
+ get waitUntil(): PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[] {
62
+ return this.options.waitUntil ?? SCREENSHOT_WAIT_UNTIL
63
+ }
64
+
65
+ /** Timeout for navigating to the page / Таймаут для перехода на страницу */
66
+ get timeout(): number {
67
+ return this.options.timeout ?? SCREENSHOT_TIMEOUT
68
+ }
69
+
70
+ /** Image format for screenshots / Формат изображения для скриншотов */
71
+ get format(): ScreenshotOptions['format'] {
72
+ return this.options.format ?? SCREENSHOT_FORMAT
73
+ }
74
+
75
+ /** Viewport width for the initial capture / Ширина порта просмотра для начального захвата */
76
+ get width(): number {
77
+ return this.options.width ?? SCREENSHOT_WIDTHS
78
+ }
79
+
80
+ /** Viewport height for the initial capture and page-by-page slicing / Высота порта просмотра для начального захвата и постраничной нарезки */
81
+ get height(): number {
82
+ return this.options.height ?? SCREENSHOT_HEIGHTS
83
+ }
84
+
85
+ /** Quality for JPEG/WebP formats (0-100) / Качество для форматов JPEG/WebP (0-100) */
86
+ get quality(): number {
87
+ return this.options.quality ?? SCREENSHOT_QUALITY
88
+ }
89
+
90
+ /** Whether to capture the whole scrollable page at once or in slices / Захватывать ли всю прокручиваемую страницу сразу или по частям */
91
+ get fullPage(): boolean {
92
+ return this.options.fullPage ?? false
93
+ }
94
+
95
+ /** File extension based on the selected format / Расширение файла на основе выбранного формата */
96
+ get extension(): string {
97
+ const format = this.format
98
+
99
+ if (format === 'jpeg') {
100
+ return '.jpg'
101
+ }
102
+
103
+ return `.${format}`
104
+ }
105
+
106
+ /**
107
+ * launches or returns the browser instance.
108
+ *
109
+ * Запускает или возвращает экземпляр браузера.
110
+ * @returns browser instance / экземпляр браузера
111
+ */
112
+ async getBrowser(): Promise<Browser> {
113
+ if (!this.browser) {
114
+ this.browser = await launch({
115
+ headless: true,
116
+ args: this.args
117
+ })
118
+ }
119
+
120
+ return this.browser
121
+ }
122
+
123
+ /**
124
+ * Creates or returns the page instance.
125
+ *
126
+ * Создает или возвращает экземпляр страницы.
127
+ * @returns page instance / экземпляр страницы
128
+ */
129
+ async getPage(): Promise<Page> {
130
+ if (!this.page) {
131
+ this.page = await (await this.getBrowser()).newPage()
132
+
133
+ await this.page.setViewport({
134
+ width: this.width,
135
+ height: this.height
136
+ })
137
+
138
+ this.page.on(
139
+ 'console',
140
+ msg => this.toConsole(`Log: ${msg.text()}`)
141
+ )
142
+
143
+ this.page.on(
144
+ 'pageerror',
145
+ err => this.toConsole(`Error: ${(err as any)?.message}`)
146
+ )
147
+ }
148
+
149
+ return this.page
150
+ }
151
+
152
+ /**
153
+ * Navigates to the URL and returns the HTTP response.
154
+ *
155
+ * Переходит по URL и возвращает HTTP-ответ.
156
+ * @returns HTTP response or undefined / HTTP-ответ или undefined
157
+ */
158
+ async getResponse(): Promise<HTTPResponse | undefined> {
159
+ if (!this.response) {
160
+ const response = await (await this.getPage()).goto(this.url, {
161
+ waitUntil: this.waitUntil,
162
+ timeout: this.timeout
163
+ })
164
+
165
+ if (
166
+ response
167
+ && response.ok()
168
+ ) {
169
+ this.response = response
170
+ } else {
171
+ this.toConsole(
172
+ `Failed to load page: ${this.url}. Status: ${response?.status()}`
173
+ )
174
+ }
175
+ }
176
+
177
+ return this.response
178
+ }
179
+
180
+ /**
181
+ * Retrieves the HTML content of the page.
182
+ *
183
+ * Извлекает HTML-содержимое страницы.
184
+ * @returns html content string / HTML-содержимое
185
+ */
186
+ async getDom(): Promise<string> {
187
+ return await (await this.getPage()).content()
188
+ }
189
+
190
+ /**
191
+ * Extracts scrollable dimensions of the page.
192
+ *
193
+ * Извлекает прокручиваемые размеры страницы.
194
+ * @returns object with width and height / объект с шириной и высотой
195
+ */
196
+ async getMetrics(): Promise<ScreenshotMetrics> {
197
+ if (
198
+ await this.getResponse()
199
+ && (
200
+ this.pageWidth === -1
201
+ || this.pageHeight === -1
202
+ )
203
+ ) {
204
+ const metrics = await this.evaluate(() => {
205
+ return {
206
+ height: document.documentElement.scrollHeight,
207
+ width: document.documentElement.scrollWidth
208
+ }
209
+ })
210
+
211
+ if (metrics) {
212
+ this.pageWidth = metrics.width
213
+ this.pageHeight = metrics.height
214
+ }
215
+ }
216
+
217
+ return {
218
+ height: this.pageHeight,
219
+ width: this.pageWidth
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Evaluates a function in the browser context.
225
+ *
226
+ * Выполняет функцию в контексте браузера.
227
+ * @param pageFunction function to evaluate / функция для выполнения
228
+ * @param args arguments for the function / аргументы для функции
229
+ * @returns result of evaluation / результат выполнения
230
+ */
231
+ async evaluate<
232
+ Params extends unknown[],
233
+ Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
234
+ >(
235
+ pageFunction: Func | string,
236
+ ...args: Params
237
+ ): Promise<Awaited<ReturnType<Func>> | undefined> {
238
+ return await (await this.getPage()).evaluate<Params, Func>(
239
+ pageFunction,
240
+ ...args
241
+ )
242
+ }
243
+
244
+ /**
245
+ * Captures screenshots based on the fullPage option.
246
+ *
247
+ * Создает скриншоты в зависимости от опции fullPage.
248
+ * @param outputPath base path for saving / базовый путь для сохранения
249
+ * @returns this instance / этот экземпляр
250
+ */
251
+ async screenshot(outputPath: string): Promise<this> {
252
+ if (this.fullPage) {
253
+ await this.screenshotFull(outputPath)
254
+ } else {
255
+ await this.screenshotPages(outputPath)
256
+ }
257
+
258
+ return this
259
+ }
260
+
261
+ /**
262
+ * Resets internal page and response states.
263
+ *
264
+ * Сбрасывает состояние страницы и ответа.
265
+ * @returns this instance / этот экземпляр
266
+ */
267
+ async reset(): Promise<this> {
268
+ this.page = undefined
269
+ this.pageWidth = -1
270
+ this.pageHeight = -1
271
+
272
+ this.response = undefined
273
+
274
+ return this
275
+ }
276
+
277
+ /**
278
+ * Formats the final file path based on current state and index.
279
+ *
280
+ * Форматирует итоговый путь к файлу на основе состояния и индекса.
281
+ * @param outputPath original output path / исходный путь сохранения
282
+ * @param index current page index / текущий индекс страницы
283
+ * @param max total pages count / общее количество страниц
284
+ * @returns formatted path string / отформатированная строка пути
285
+ */
286
+ protected getOutputPath(
287
+ outputPath: string,
288
+ index: number = 1,
289
+ max: number = 1
290
+ ): string {
291
+ if (this.fullPage) {
292
+ return outputPath + this.extension
293
+ }
294
+
295
+ return outputPath + `-${max}_${index}` + this.extension
296
+ }
297
+
298
+ /**
299
+ * Internal wrapper for Puppeteer's screenshot method.
300
+ *
301
+ * Внутренняя обертка для метода скриншота Puppeteer.
302
+ * @param path specific file path / путь к конкретному файлу
303
+ * @returns this instance / этот экземпляр
304
+ */
305
+ protected async saveScreenshot(path: string): Promise<this> {
306
+ const type = this.format
307
+ const quality = type === 'png' ? undefined : this.quality
308
+
309
+ await (await this.getPage()).screenshot({
310
+ path,
311
+ type,
312
+ quality,
313
+ fullPage: this.fullPage
314
+ })
315
+
316
+ return this
317
+ }
318
+
319
+ /**
320
+ * Iterates through page slices and saves multiple screenshots.
321
+ *
322
+ * Проходит по частям страницы и сохраняет несколько скриншотов.
323
+ * @param outputPath base output path / базовый путь сохранения
324
+ * @returns this instance / этот экземпляр
325
+ */
326
+ protected async screenshotPages(outputPath: string): Promise<this> {
327
+ const {
328
+ height: pageHeight
329
+ } = await this.getMetrics()
330
+
331
+ const viewportHeight = this.height
332
+ const pages = Math.ceil(pageHeight / viewportHeight)
333
+
334
+ this.toConsole(`Page height: ${pageHeight}px, viewport height: ${viewportHeight}px, total pages: ${pages}`)
335
+
336
+ for (let i = 0; i < pages; i++) {
337
+ const currentY = i * viewportHeight
338
+ const fileOutputPath = this.getOutputPath(outputPath, i + 1, pages)
339
+
340
+ await this.evaluate(y => window.scrollTo(0, y), currentY)
341
+ await sleep(320)
342
+ await this.saveScreenshot(fileOutputPath)
343
+
344
+ this.toConsole(`Saved screenshot page ${i + 1}/${pages} to ${fileOutputPath}`)
345
+ }
346
+
347
+ return this
348
+ }
349
+
350
+ /**
351
+ * Captures a single full page screenshot.
352
+ *
353
+ * Создает один скриншот всей страницы.
354
+ * @param outputPath output path / путь сохранения
355
+ * @returns this instance / этот экземпляр
356
+ */
357
+ protected async screenshotFull(outputPath: string): Promise<this> {
358
+ const outputPathFull = this.getOutputPath(outputPath)
359
+ await this.saveScreenshot(outputPathFull)
360
+
361
+ this.toConsole(`Saved full page screenshot to ${outputPathFull}`)
362
+
363
+ return this
364
+ }
365
+
366
+ /**
367
+ * Logs text with a "Browser" prefix.
368
+ *
369
+ * Выводит текст в консоль с префиксом "Browser".
370
+ * @param text text to log / текст для вывода
371
+ */
372
+ protected toConsole(text: string): void {
373
+ console.log('Browser', text)
374
+ }
375
+ }
@@ -0,0 +1,34 @@
1
+ import { FigmaApi } from '../FigmaApi'
2
+ import { PropertiesConfig } from '../Properties/PropertiesConfig.ts'
3
+
4
+ export class DesignFigma {
5
+ protected readonly api: FigmaApi
6
+
7
+ constructor(
8
+ protected readonly fileKey: string,
9
+ protected readonly nodeId?: string,
10
+ protected readonly token: string = PropertiesConfig.getFigmaToken()
11
+ ) {
12
+ this.api = new FigmaApi(token, fileKey, nodeId)
13
+ }
14
+
15
+ async make(): Promise<void> {
16
+ console.log('Design Figma', await this.initImage())
17
+ }
18
+
19
+ async initImage(): Promise<Record<string, string> | undefined> {
20
+ const image = await this.api.fileImages({
21
+ ids: this.nodeId as string,
22
+ format: 'svg',
23
+ svg_outline_text: false,
24
+ svg_include_id: true,
25
+ svg_include_node_id: true
26
+ })
27
+
28
+ if (image?.err) {
29
+ return undefined
30
+ }
31
+
32
+ return image?.images
33
+ }
34
+ }
@@ -0,0 +1,123 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { ServerStorage } from '@dxtmisha/functional-basic'
3
+ import { BrowserItem } from '../BrowserItem'
4
+ import { PropertiesFile } from '../Properties/PropertiesFile.ts'
5
+
6
+ /**
7
+ * Class for automatic capturing of screenshots by running dev server.
8
+ *
9
+ * Класс для автоматического захвата скриншотов путем запуска сервера разработки.
10
+ */
11
+ export class DesignScreenshot {
12
+ /** indicates if screenshot process is running / указывает, запущен ли процесс создания скриншота */
13
+ protected isReading: boolean = false
14
+
15
+ /** server url / URL сервера */
16
+ protected url?: string
17
+
18
+ /**
19
+ * Constructor
20
+ * @param file output path / путь к файлу
21
+ */
22
+ constructor(
23
+ protected readonly file: string = './ai-screenshot/screenshot'
24
+ ) {
25
+ ServerStorage.setErrorStatus(true)
26
+ }
27
+
28
+ /**
29
+ * starts the screenshot process.
30
+ *
31
+ * Запускает процесс создания скриншота.
32
+ */
33
+ async make() {
34
+ console.info('Screenshot')
35
+
36
+ PropertiesFile.createDir(PropertiesFile.getPathDir(this.file + '.file'))
37
+ this.makeServer()
38
+ }
39
+
40
+ /**
41
+ * screenshot trigger listener.
42
+ *
43
+ * Слушатель триггера для создания скриншота.
44
+ * @returns capture success status / статус успешного захвата
45
+ */
46
+ protected readonly listener = async (): Promise<boolean> => {
47
+ if (
48
+ this.url
49
+ && !this.isReading
50
+ ) {
51
+ this.isReading = true
52
+
53
+ console.info('URL', this.url)
54
+
55
+ const browser = new BrowserItem(this.url, { height: 1024 * 12 })
56
+ await browser.screenshot(this.file)
57
+
58
+ return true
59
+ }
60
+
61
+ return false
62
+ }
63
+
64
+ /**
65
+ * spawns development server.
66
+ *
67
+ * Запускает сервер разработки.
68
+ * @returns this instance / этот экземпляр
69
+ */
70
+ protected makeServer(): this {
71
+ const server = spawn(
72
+ 'npm',
73
+ ['run', 'dev'],
74
+ {
75
+ shell: true,
76
+ stdio: 'pipe'
77
+ }
78
+ )
79
+
80
+ server.stdout?.on('data', (data) => {
81
+ console.log('Server', 'start')
82
+
83
+ const dataString = data.toString()
84
+ this.makeUrl(dataString)
85
+
86
+ if (this.url) {
87
+ console.info('Server', 'init')
88
+
89
+ this.listener()
90
+ .then((success: boolean) => {
91
+ if (success) {
92
+ server.kill()
93
+
94
+ console.info('Server', 'kill')
95
+ }
96
+ })
97
+ }
98
+ })
99
+
100
+ server.stderr?.on('data', (data) => {
101
+ console.error(`Server Error: ${data.toString()}`)
102
+ })
103
+
104
+ return this
105
+ }
106
+
107
+ /**
108
+ * extracts server url from console output.
109
+ *
110
+ * Извлекает URL сервера из консольного вывода.
111
+ * @param data command output / вывод команды
112
+ * @returns this instance / этот экземпляр
113
+ */
114
+ makeUrl(data: string): this {
115
+ const match = data.match(/(https?:\/\/localhost\S+)/)
116
+
117
+ if (match) {
118
+ this.url = match[1]
119
+ }
120
+
121
+ return this
122
+ }
123
+ }
@@ -6,7 +6,7 @@ import { PropertiesFile } from '../Properties/PropertiesFile'
6
6
 
7
7
  import type { DesignTypesList } from '../../types/designTypes'
8
8
 
9
- import { UI_DIR_CONSTRUCTOR, UI_FILE_AI_TYPES } from '../../config'
9
+ import { UI_DIR_CONSTRUCTOR, UI_FILE_AI_DESCRIPTION, UI_FILE_AI_TYPES } from '../../config'
10
10
 
11
11
  /**
12
12
  * Engine for generating compressed and AI-optimized TypeScript type definitions.
@@ -41,19 +41,19 @@ export class DesignTypes {
41
41
  *
42
42
  * Основной метод для выполнения процесса генерации типов.
43
43
  */
44
- make() {
44
+ async make() {
45
45
  console.log('DesignTypes: making AI types...')
46
46
 
47
47
  const files = this.getListByFilter()
48
48
  const fullContent = this.toOneFile(files)
49
49
 
50
- this.toAiEdit(fullContent).then(
51
- (aiContent) => {
52
- this.save(aiContent)
50
+ const aiContent = await this.toAiEdit(fullContent)
51
+ this.save(aiContent)
53
52
 
54
- console.log('DesignTypes: AI types saved.')
55
- }
56
- )
53
+ const aiDescription = await this.toAiDescription(fullContent)
54
+ this.saveDescription(aiDescription)
55
+
56
+ console.log('DesignTypes: AI types saved.')
57
57
  }
58
58
 
59
59
  /**
@@ -168,6 +168,19 @@ export class DesignTypes {
168
168
  }
169
169
  }
170
170
 
171
+ /**
172
+ * Saves the AI-generated project description to a file.
173
+ *
174
+ * Сохраняет сгенерированное ИИ описание проекта в файл.
175
+ * @param content content to save / контент для сохранения
176
+ */
177
+ protected saveDescription(content: string) {
178
+ PropertiesFile.writeByPath(
179
+ UI_FILE_AI_DESCRIPTION,
180
+ content
181
+ )
182
+ }
183
+
171
184
  /**
172
185
  * Combines a list of files into a single string.
173
186
  *
@@ -183,31 +196,17 @@ export class DesignTypes {
183
196
  }
184
197
 
185
198
  /**
186
- * Sends content to AI for optimization.
199
+ * Sends content and a prompt to the AI for processing.
187
200
  *
188
- * Отправляет контент ИИ для оптимизации.
189
- * @param content content to optimize / контент для оптимизации
201
+ * Отправляет контент и промпт ИИ для обработки.
202
+ * @param content content for processing / контент для обработки
203
+ * @param prompt instructions for the AI / инструкции для ИИ
190
204
  */
191
- protected async toAiEdit(content: string): Promise<string> {
205
+ protected async toAi(content: string, prompt: string): Promise<string | undefined> {
192
206
  const ai = useAi()
193
207
 
194
208
  if (ai) {
195
- ai.addPrompt(
196
- 'Remove all Russian comments from this code. '
197
- + 'Simplify and shorten all English comments for AI readability while maintaining a clear balance between brevity and context. Do not delete them even if the code seems obvious. '
198
- + 'Always keep All JSDoc "@example", "@remarks", "@note", and any other notes or warnings. '
199
- + 'Remove all imports. '
200
- + 'Remove all non-public content: delete all private and protected class methods and properties, and any non-exported elements. The final output must contain only the members and entities that are accessible from outside. '
201
- + 'Remove any code segments or data that do not provide useful information for an AI assistant. '
202
- + 'You may remove abstract classes or other structures that provide no practical value for code generation, but do so with extreme caution. Maintain a strict balance: if there is even a 5% chance the item might be relevant for understanding the API or generating code, keep it. Think carefully before every deletion. '
203
- + 'Remove any large Enums that add excessive length without providing critical context. '
204
- + 'Your goal is to create a compact, context-rich file that enables any AI coding assistant to generate high-quality code for a developer. '
205
- + 'Ensure that no public API surface, essential data types, or required logic is lost. '
206
- + 'Do not delete any "type" definitions; they are strictly required. '
207
- + 'Do not delete file paths (labels starting with "// File:"). '
208
- + 'All instructions are mandatory and must be executed perfectly. '
209
- + 'Return ONLY the resulting code. No markdown code blocks, no tags, no explanations, and no additional comments from the AI. NOTHING but the pure code.'
210
- )
209
+ ai.addPrompt(prompt)
211
210
  ai.addPrompt(`File Content: ${content}`)
212
211
 
213
212
  const generate = await ai.generate('go!')
@@ -217,6 +216,57 @@ export class DesignTypes {
217
216
  }
218
217
  }
219
218
 
220
- return content
219
+ return undefined
220
+ }
221
+
222
+ /**
223
+ * Sends content to AI for optimization.
224
+ *
225
+ * Отправляет контент ИИ для оптимизации.
226
+ * @param content content to optimize / контент для оптимизации
227
+ */
228
+ protected async toAiEdit(content: string): Promise<string> {
229
+ const generate = await this.toAi(
230
+ content,
231
+ 'Remove all Russian comments from this code. '
232
+ + 'Simplify and shorten all English comments for AI readability while maintaining a clear balance between brevity and context. Do not delete them even if the code seems obvious. '
233
+ + 'Always keep All JSDoc "@example", "@remarks", "@note", and any other notes or warnings. '
234
+ + 'Remove all imports. '
235
+ + 'Remove all non-public content: delete all private and protected class methods and properties, and any non-exported elements. The final output must contain only the members and entities that are accessible from outside. '
236
+ + 'Remove any code segments or data that do not provide useful information for an AI assistant. '
237
+ + 'You may remove abstract classes or other structures that provide no practical value for code generation, but do so with extreme caution. Maintain a strict balance: if there is even a 5% chance the item might be relevant for understanding the API or generating code, keep it. Think carefully before every deletion. '
238
+ + 'Remove any large Enums that add excessive length without providing critical context. '
239
+ + 'Your goal is to create a compact, context-rich file that enables any AI coding assistant to generate high-quality code for a developer. '
240
+ + 'Ensure that no public API surface, essential data types, or required logic is lost. '
241
+ + 'Do not delete any "type" definitions; they are strictly required. '
242
+ + 'Do not delete file paths (labels starting with "// File:"). '
243
+ + 'All instructions are mandatory and must be executed perfectly. '
244
+ + 'Return ONLY the resulting code. No markdown code blocks, no tags, no explanations, and no additional comments from the AI. NOTHING but the pure code.'
245
+ )
246
+
247
+ return generate ?? content
248
+ }
249
+
250
+ /**
251
+ * Generates a project description and usage guidelines using AI.
252
+ *
253
+ * Генерирует описание проекта и рекомендации по использованию с помощью ИИ.
254
+ * @param content cleaned type definitions / очищенные определения типов
255
+ */
256
+ protected async toAiDescription(content: string): Promise<string> {
257
+ const generate = await this.toAi(
258
+ content,
259
+ 'Analyze the provided code and generate a highly technical, concise project overview for another AI coding assistant. '
260
+ + 'Your goal is to help the AI decide whether it needs to study this library to fulfill a user request. '
261
+ + 'The description must be objective, factual, and free of marketing fluff. '
262
+ + 'Include: '
263
+ + '1. Core Purpose: What is the primary function of this library? (e.g., UI component library, state management, utility for X). '
264
+ + '2. Usage Scenarios: In what specific cases and scenarios is this library indispensable? When should an AI study its API? '
265
+ + '3. Integration Context: How does it relate to other technologies in the stack (if evident)? '
266
+ + 'Ensure the structure is clean and enables immediate context retrieval. '
267
+ + 'Return ONLY the resulting description text. No markdown, no labels like "Description:", no explanations. NOTHING but the pure content.'
268
+ )
269
+
270
+ return generate ?? ''
221
271
  }
222
272
  }