@oh-my-pi/pi-tui 1.337.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,654 @@
1
+ # @oh-my-pi/pi-tui
2
+
3
+ Minimal terminal UI framework with differential rendering and synchronized output for flicker-free interactive CLI applications.
4
+
5
+ ## Features
6
+
7
+ - **Differential Rendering**: Three-strategy rendering system that only updates what changed
8
+ - **Synchronized Output**: Uses CSI 2026 for atomic screen updates (no flicker)
9
+ - **Bracketed Paste Mode**: Handles large pastes correctly with markers for >10 line pastes
10
+ - **Component-based**: Simple Component interface with render() method
11
+ - **Theme Support**: Components accept theme interfaces for customizable styling
12
+ - **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container
13
+ - **Inline Images**: Renders images in terminals that support Kitty or iTerm2 graphics protocols
14
+ - **Autocomplete Support**: File paths and slash commands
15
+
16
+ ## Quick Start
17
+
18
+ ```typescript
19
+ import { TUI, Text, Editor, ProcessTerminal } from "@oh-my-pi/pi-tui";
20
+
21
+ // Create terminal
22
+ const terminal = new ProcessTerminal();
23
+
24
+ // Create TUI
25
+ const tui = new TUI(terminal);
26
+
27
+ // Add components
28
+ tui.addChild(new Text("Welcome to my app!"));
29
+
30
+ const editor = new Editor(editorTheme);
31
+ editor.onSubmit = (text) => {
32
+ console.log("Submitted:", text);
33
+ tui.addChild(new Text(`You said: ${text}`));
34
+ };
35
+ tui.addChild(editor);
36
+
37
+ // Start
38
+ tui.start();
39
+ ```
40
+
41
+ ## Core API
42
+
43
+ ### TUI
44
+
45
+ Main container that manages components and rendering.
46
+
47
+ ```typescript
48
+ const tui = new TUI(terminal);
49
+ tui.addChild(component);
50
+ tui.removeChild(component);
51
+ tui.start();
52
+ tui.stop();
53
+ tui.requestRender(); // Request a re-render
54
+
55
+ // Global debug key handler (Shift+Ctrl+D)
56
+ tui.onDebug = () => console.log("Debug triggered");
57
+ ```
58
+
59
+ ### Component Interface
60
+
61
+ All components implement:
62
+
63
+ ```typescript
64
+ interface Component {
65
+ render(width: number): string[];
66
+ handleInput?(data: string): void;
67
+ invalidate?(): void;
68
+ }
69
+ ```
70
+
71
+ | Method | Description |
72
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
73
+ | `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. |
74
+ | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
75
+ | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
76
+
77
+ ## Built-in Components
78
+
79
+ ### Container
80
+
81
+ Groups child components.
82
+
83
+ ```typescript
84
+ const container = new Container();
85
+ container.addChild(component);
86
+ container.removeChild(component);
87
+ ```
88
+
89
+ ### Box
90
+
91
+ Container that applies padding and background color to all children.
92
+
93
+ ```typescript
94
+ const box = new Box(
95
+ 1, // paddingX (default: 1)
96
+ 1, // paddingY (default: 1)
97
+ (text) => chalk.bgGray(text) // optional background function
98
+ );
99
+ box.addChild(new Text("Content"));
100
+ box.setBgFn((text) => chalk.bgBlue(text)); // Change background dynamically
101
+ ```
102
+
103
+ ### Text
104
+
105
+ Displays multi-line text with word wrapping and padding.
106
+
107
+ ```typescript
108
+ const text = new Text(
109
+ "Hello World", // text content
110
+ 1, // paddingX (default: 1)
111
+ 1, // paddingY (default: 1)
112
+ (text) => chalk.bgGray(text) // optional background function
113
+ );
114
+ text.setText("Updated text");
115
+ text.setCustomBgFn((text) => chalk.bgBlue(text));
116
+ ```
117
+
118
+ ### TruncatedText
119
+
120
+ Single-line text that truncates to fit viewport width. Useful for status lines and headers.
121
+
122
+ ```typescript
123
+ const truncated = new TruncatedText(
124
+ "This is a very long line that will be truncated...",
125
+ 0, // paddingX (default: 0)
126
+ 0 // paddingY (default: 0)
127
+ );
128
+ ```
129
+
130
+ ### Input
131
+
132
+ Single-line text input with horizontal scrolling.
133
+
134
+ ```typescript
135
+ const input = new Input();
136
+ input.onSubmit = (value) => console.log(value);
137
+ input.setValue("initial");
138
+ input.getValue();
139
+ ```
140
+
141
+ **Key Bindings:**
142
+
143
+ - `Enter` - Submit
144
+ - `Ctrl+A` / `Ctrl+E` - Line start/end
145
+ - `Ctrl+W` or `Alt+Backspace` - Delete word backwards
146
+ - `Ctrl+U` - Delete to start of line
147
+ - `Ctrl+K` - Delete to end of line
148
+ - `Ctrl+Left` / `Ctrl+Right` - Word navigation
149
+ - `Alt+Left` / `Alt+Right` - Word navigation
150
+ - Arrow keys, Backspace, Delete work as expected
151
+
152
+ ### Editor
153
+
154
+ Multi-line text editor with autocomplete, file completion, and paste handling.
155
+
156
+ ```typescript
157
+ interface EditorTheme {
158
+ borderColor: (str: string) => string;
159
+ selectList: SelectListTheme;
160
+ }
161
+
162
+ const editor = new Editor(theme);
163
+ editor.onSubmit = (text) => console.log(text);
164
+ editor.onChange = (text) => console.log("Changed:", text);
165
+ editor.disableSubmit = true; // Disable submit temporarily
166
+ editor.setAutocompleteProvider(provider);
167
+ editor.borderColor = (s) => chalk.blue(s); // Change border dynamically
168
+ ```
169
+
170
+ **Features:**
171
+
172
+ - Multi-line editing with word wrap
173
+ - Slash command autocomplete (type `/`)
174
+ - File path autocomplete (press `Tab`)
175
+ - Large paste handling (>10 lines creates `[paste #1 +50 lines]` marker)
176
+ - Horizontal lines above/below editor
177
+ - Fake cursor rendering (hidden real cursor)
178
+
179
+ **Key Bindings:**
180
+
181
+ - `Enter` - Submit
182
+ - `Shift+Enter`, `Ctrl+Enter`, or `Alt+Enter` - New line (terminal-dependent, Alt+Enter most reliable)
183
+ - `Tab` - Autocomplete
184
+ - `Ctrl+K` - Delete line
185
+ - `Ctrl+A` / `Ctrl+E` - Line start/end
186
+ - Arrow keys, Backspace, Delete work as expected
187
+
188
+ ### Markdown
189
+
190
+ Renders markdown with syntax highlighting and theming support.
191
+
192
+ ```typescript
193
+ interface MarkdownTheme {
194
+ heading: (text: string) => string;
195
+ link: (text: string) => string;
196
+ linkUrl: (text: string) => string;
197
+ code: (text: string) => string;
198
+ codeBlock: (text: string) => string;
199
+ codeBlockBorder: (text: string) => string;
200
+ quote: (text: string) => string;
201
+ quoteBorder: (text: string) => string;
202
+ hr: (text: string) => string;
203
+ listBullet: (text: string) => string;
204
+ bold: (text: string) => string;
205
+ italic: (text: string) => string;
206
+ strikethrough: (text: string) => string;
207
+ underline: (text: string) => string;
208
+ highlightCode?: (code: string, lang?: string) => string[];
209
+ }
210
+
211
+ interface DefaultTextStyle {
212
+ color?: (text: string) => string;
213
+ bgColor?: (text: string) => string;
214
+ bold?: boolean;
215
+ italic?: boolean;
216
+ strikethrough?: boolean;
217
+ underline?: boolean;
218
+ }
219
+
220
+ const md = new Markdown(
221
+ "# Hello\n\nSome **bold** text",
222
+ 1, // paddingX
223
+ 1, // paddingY
224
+ theme, // MarkdownTheme
225
+ defaultStyle // optional DefaultTextStyle
226
+ );
227
+ md.setText("Updated markdown");
228
+ ```
229
+
230
+ **Features:**
231
+
232
+ - Headings, bold, italic, code blocks, lists, links, blockquotes
233
+ - HTML tags rendered as plain text
234
+ - Optional syntax highlighting via `highlightCode`
235
+ - Padding support
236
+ - Render caching for performance
237
+
238
+ ### Loader
239
+
240
+ Animated loading spinner.
241
+
242
+ ```typescript
243
+ const loader = new Loader(
244
+ tui, // TUI instance for render updates
245
+ (s) => chalk.cyan(s), // spinner color function
246
+ (s) => chalk.gray(s), // message color function
247
+ "Loading..." // message (default: "Loading...")
248
+ );
249
+ loader.start();
250
+ loader.setMessage("Still loading...");
251
+ loader.stop();
252
+ ```
253
+
254
+ ### CancellableLoader
255
+
256
+ Extends Loader with Escape key handling and an AbortSignal for cancelling async operations.
257
+
258
+ ```typescript
259
+ const loader = new CancellableLoader(
260
+ tui, // TUI instance for render updates
261
+ (s) => chalk.cyan(s), // spinner color function
262
+ (s) => chalk.gray(s), // message color function
263
+ "Working..." // message
264
+ );
265
+ loader.onAbort = () => done(null); // Called when user presses Escape
266
+ doAsyncWork(loader.signal).then(done);
267
+ ```
268
+
269
+ **Properties:**
270
+
271
+ - `signal: AbortSignal` - Aborted when user presses Escape
272
+ - `aborted: boolean` - Whether the loader was aborted
273
+ - `onAbort?: () => void` - Callback when user presses Escape
274
+
275
+ ### SelectList
276
+
277
+ Interactive selection list with keyboard navigation.
278
+
279
+ ```typescript
280
+ interface SelectItem {
281
+ value: string;
282
+ label: string;
283
+ description?: string;
284
+ }
285
+
286
+ interface SelectListTheme {
287
+ selectedPrefix: (text: string) => string;
288
+ selectedText: (text: string) => string;
289
+ description: (text: string) => string;
290
+ scrollInfo: (text: string) => string;
291
+ noMatch: (text: string) => string;
292
+ }
293
+
294
+ const list = new SelectList(
295
+ [
296
+ { value: "opt1", label: "Option 1", description: "First option" },
297
+ { value: "opt2", label: "Option 2", description: "Second option" },
298
+ ],
299
+ 5, // maxVisible
300
+ theme // SelectListTheme
301
+ );
302
+
303
+ list.onSelect = (item) => console.log("Selected:", item);
304
+ list.onCancel = () => console.log("Cancelled");
305
+ list.onSelectionChange = (item) => console.log("Highlighted:", item);
306
+ list.setFilter("opt"); // Filter items
307
+ ```
308
+
309
+ **Controls:**
310
+
311
+ - Arrow keys: Navigate
312
+ - Enter: Select
313
+ - Escape: Cancel
314
+
315
+ ### SettingsList
316
+
317
+ Settings panel with value cycling and submenus.
318
+
319
+ ```typescript
320
+ interface SettingItem {
321
+ id: string;
322
+ label: string;
323
+ description?: string;
324
+ currentValue: string;
325
+ values?: string[]; // If provided, Enter/Space cycles through these
326
+ submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
327
+ }
328
+
329
+ interface SettingsListTheme {
330
+ label: (text: string, selected: boolean) => string;
331
+ value: (text: string, selected: boolean) => string;
332
+ description: (text: string) => string;
333
+ cursor: string;
334
+ hint: (text: string) => string;
335
+ }
336
+
337
+ const settings = new SettingsList(
338
+ [
339
+ { id: "theme", label: "Theme", currentValue: "dark", values: ["dark", "light"] },
340
+ { id: "model", label: "Model", currentValue: "gpt-4", submenu: (val, done) => modelSelector },
341
+ ],
342
+ 10, // maxVisible
343
+ theme, // SettingsListTheme
344
+ (id, newValue) => console.log(`${id} changed to ${newValue}`),
345
+ () => console.log("Cancelled")
346
+ );
347
+ settings.updateValue("theme", "light");
348
+ ```
349
+
350
+ **Controls:**
351
+
352
+ - Arrow keys: Navigate
353
+ - Enter/Space: Activate (cycle value or open submenu)
354
+ - Escape: Cancel
355
+
356
+ ### Spacer
357
+
358
+ Empty lines for vertical spacing.
359
+
360
+ ```typescript
361
+ const spacer = new Spacer(2); // 2 empty lines (default: 1)
362
+ ```
363
+
364
+ ### Image
365
+
366
+ Renders images inline for terminals that support the Kitty graphics protocol (Kitty, Ghostty, WezTerm) or iTerm2 inline images. Falls back to a text placeholder on unsupported terminals.
367
+
368
+ ```typescript
369
+ interface ImageTheme {
370
+ fallbackColor: (str: string) => string;
371
+ }
372
+
373
+ interface ImageOptions {
374
+ maxWidthCells?: number;
375
+ maxHeightCells?: number;
376
+ filename?: string;
377
+ }
378
+
379
+ const image = new Image(
380
+ base64Data, // base64-encoded image data
381
+ "image/png", // MIME type
382
+ theme, // ImageTheme
383
+ options // optional ImageOptions
384
+ );
385
+ tui.addChild(image);
386
+ ```
387
+
388
+ Supported formats: PNG, JPEG, GIF, WebP. Dimensions are parsed from the image headers automatically.
389
+
390
+ ## Autocomplete
391
+
392
+ ### CombinedAutocompleteProvider
393
+
394
+ Supports both slash commands and file paths.
395
+
396
+ ```typescript
397
+ import { CombinedAutocompleteProvider } from "@oh-my-pi/pi-tui";
398
+
399
+ const provider = new CombinedAutocompleteProvider(
400
+ [
401
+ { name: "help", description: "Show help" },
402
+ { name: "clear", description: "Clear screen" },
403
+ { name: "delete", description: "Delete last message" },
404
+ ],
405
+ process.cwd() // base path for file completion
406
+ );
407
+
408
+ editor.setAutocompleteProvider(provider);
409
+ ```
410
+
411
+ **Features:**
412
+
413
+ - Type `/` to see slash commands
414
+ - Press `Tab` for file path completion
415
+ - Works with `~/`, `./`, `../`, and `@` prefix
416
+ - Filters to attachable files for `@` prefix
417
+
418
+ ## Key Detection
419
+
420
+ Helper functions for detecting keyboard input (supports Kitty keyboard protocol):
421
+
422
+ ```typescript
423
+ import {
424
+ isEnter,
425
+ isEscape,
426
+ isTab,
427
+ isShiftTab,
428
+ isArrowUp,
429
+ isArrowDown,
430
+ isArrowLeft,
431
+ isArrowRight,
432
+ isCtrlA,
433
+ isCtrlC,
434
+ isCtrlE,
435
+ isCtrlK,
436
+ isCtrlO,
437
+ isCtrlP,
438
+ isCtrlLeft,
439
+ isCtrlRight,
440
+ isAltLeft,
441
+ isAltRight,
442
+ isShiftEnter,
443
+ isAltEnter,
444
+ isShiftCtrlO,
445
+ isShiftCtrlD,
446
+ isShiftCtrlP,
447
+ isBackspace,
448
+ isDelete,
449
+ isHome,
450
+ isEnd,
451
+ // ... and more
452
+ } from "@oh-my-pi/pi-tui";
453
+
454
+ if (isCtrlC(data)) {
455
+ process.exit(0);
456
+ }
457
+ ```
458
+
459
+ ## Differential Rendering
460
+
461
+ The TUI uses three rendering strategies:
462
+
463
+ 1. **First Render**: Output all lines without clearing scrollback
464
+ 2. **Width Changed or Change Above Viewport**: Clear screen and full re-render
465
+ 3. **Normal Update**: Move cursor to first changed line, clear to end, render changed lines
466
+
467
+ All updates are wrapped in **synchronized output** (`\x1b[?2026h` ... `\x1b[?2026l`) for atomic, flicker-free rendering.
468
+
469
+ ## Terminal Interface
470
+
471
+ The TUI works with any object implementing the `Terminal` interface:
472
+
473
+ ```typescript
474
+ interface Terminal {
475
+ start(onInput: (data: string) => void, onResize: () => void): void;
476
+ stop(): void;
477
+ write(data: string): void;
478
+ get columns(): number;
479
+ get rows(): number;
480
+ moveBy(lines: number): void;
481
+ hideCursor(): void;
482
+ showCursor(): void;
483
+ clearLine(): void;
484
+ clearFromCursor(): void;
485
+ clearScreen(): void;
486
+ }
487
+ ```
488
+
489
+ **Built-in implementations:**
490
+
491
+ - `ProcessTerminal` - Uses `process.stdin/stdout`
492
+ - `VirtualTerminal` - For testing (uses `@xterm/headless`)
493
+
494
+ ## Utilities
495
+
496
+ ```typescript
497
+ import { visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
498
+
499
+ // Get visible width of string (ignoring ANSI codes)
500
+ const width = visibleWidth("\x1b[31mHello\x1b[0m"); // 5
501
+
502
+ // Truncate string to width (preserving ANSI codes, adds ellipsis)
503
+ const truncated = truncateToWidth("Hello World", 8); // "Hello..."
504
+
505
+ // Truncate without ellipsis
506
+ const truncatedNoEllipsis = truncateToWidth("Hello World", 8, ""); // "Hello Wo"
507
+
508
+ // Wrap text to width (preserving ANSI codes across line breaks)
509
+ const lines = wrapTextWithAnsi("This is a long line that needs wrapping", 20);
510
+ // ["This is a long line", "that needs wrapping"]
511
+ ```
512
+
513
+ ## Creating Custom Components
514
+
515
+ When creating custom components, **each line returned by `render()` must not exceed the `width` parameter**. The TUI will error if any line is wider than the terminal.
516
+
517
+ ### Handling Input
518
+
519
+ Use the key detection utilities to handle keyboard input:
520
+
521
+ ```typescript
522
+ import { isEnter, isEscape, isArrowUp, isArrowDown, isCtrlC, isTab, isBackspace } from "@oh-my-pi/pi-tui";
523
+ import type { Component } from "@oh-my-pi/pi-tui";
524
+
525
+ class MyInteractiveComponent implements Component {
526
+ private selectedIndex = 0;
527
+ private items = ["Option 1", "Option 2", "Option 3"];
528
+
529
+ public onSelect?: (index: number) => void;
530
+ public onCancel?: () => void;
531
+
532
+ handleInput(data: string): void {
533
+ if (isArrowUp(data)) {
534
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
535
+ } else if (isArrowDown(data)) {
536
+ this.selectedIndex = Math.min(this.items.length - 1, this.selectedIndex + 1);
537
+ } else if (isEnter(data)) {
538
+ this.onSelect?.(this.selectedIndex);
539
+ } else if (isEscape(data) || isCtrlC(data)) {
540
+ this.onCancel?.();
541
+ }
542
+ }
543
+
544
+ render(width: number): string[] {
545
+ return this.items.map((item, i) => {
546
+ const prefix = i === this.selectedIndex ? "> " : " ";
547
+ return truncateToWidth(prefix + item, width);
548
+ });
549
+ }
550
+ }
551
+ ```
552
+
553
+ ### Handling Line Width
554
+
555
+ Use the provided utilities to ensure lines fit:
556
+
557
+ ```typescript
558
+ import { visibleWidth, truncateToWidth } from "@oh-my-pi/pi-tui";
559
+ import type { Component } from "@oh-my-pi/pi-tui";
560
+
561
+ class MyComponent implements Component {
562
+ private text: string;
563
+
564
+ constructor(text: string) {
565
+ this.text = text;
566
+ }
567
+
568
+ render(width: number): string[] {
569
+ // Option 1: Truncate long lines
570
+ return [truncateToWidth(this.text, width)];
571
+
572
+ // Option 2: Check and pad to exact width
573
+ const line = this.text;
574
+ const visible = visibleWidth(line);
575
+ if (visible > width) {
576
+ return [truncateToWidth(line, width)];
577
+ }
578
+ // Pad to exact width (optional, for backgrounds)
579
+ return [line + " ".repeat(width - visible)];
580
+ }
581
+ }
582
+ ```
583
+
584
+ ### ANSI Code Considerations
585
+
586
+ Both `visibleWidth()` and `truncateToWidth()` correctly handle ANSI escape codes:
587
+
588
+ - `visibleWidth()` ignores ANSI codes when calculating width
589
+ - `truncateToWidth()` preserves ANSI codes and properly closes them when truncating
590
+
591
+ ```typescript
592
+ import chalk from "chalk";
593
+
594
+ const styled = chalk.red("Hello") + " " + chalk.blue("World");
595
+ const width = visibleWidth(styled); // 11 (not counting ANSI codes)
596
+ const truncated = truncateToWidth(styled, 8); // Red "Hello" + " W..." with proper reset
597
+ ```
598
+
599
+ ### Caching
600
+
601
+ For performance, components should cache their rendered output and only re-render when necessary:
602
+
603
+ ```typescript
604
+ class CachedComponent implements Component {
605
+ private text: string;
606
+ private cachedWidth?: number;
607
+ private cachedLines?: string[];
608
+
609
+ render(width: number): string[] {
610
+ if (this.cachedLines && this.cachedWidth === width) {
611
+ return this.cachedLines;
612
+ }
613
+
614
+ const lines = [truncateToWidth(this.text, width)];
615
+
616
+ this.cachedWidth = width;
617
+ this.cachedLines = lines;
618
+ return lines;
619
+ }
620
+
621
+ invalidate(): void {
622
+ this.cachedWidth = undefined;
623
+ this.cachedLines = undefined;
624
+ }
625
+ }
626
+ ```
627
+
628
+ ## Example
629
+
630
+ See `test/chat-simple.ts` for a complete chat interface example with:
631
+
632
+ - Markdown messages with custom background colors
633
+ - Loading spinner during responses
634
+ - Editor with autocomplete and slash commands
635
+ - Spacers between messages
636
+
637
+ Run it:
638
+
639
+ ```bash
640
+ npx tsx test/chat-simple.ts
641
+ ```
642
+
643
+ ## Development
644
+
645
+ ```bash
646
+ # Install dependencies (from monorepo root)
647
+ npm install
648
+
649
+ # Run type checking
650
+ npm run check
651
+
652
+ # Run the demo
653
+ npx tsx test/chat-simple.ts
654
+ ```
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@oh-my-pi/pi-tui",
3
+ "version": "1.337.0",
4
+ "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "scripts": {
8
+ "test": "bun test test/*.test.ts"
9
+ },
10
+ "files": [
11
+ "src/**/*",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "tui",
16
+ "terminal",
17
+ "ui",
18
+ "text-editor",
19
+ "differential-rendering",
20
+ "typescript",
21
+ "cli"
22
+ ],
23
+ "author": "Mario Zechner",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/can1357/oh-my-pi.git",
28
+ "directory": "packages/tui"
29
+ },
30
+ "engines": {
31
+ "bun": ">=1.0.0"
32
+ },
33
+ "types": "./src/index.ts",
34
+ "dependencies": {
35
+ "@types/mime-types": "^2.1.4",
36
+ "chalk": "^5.5.0",
37
+ "get-east-asian-width": "^1.3.0",
38
+ "marked": "^15.0.12",
39
+ "mime-types": "^3.0.1"
40
+ },
41
+ "devDependencies": {
42
+ "@xterm/headless": "^5.5.0",
43
+ "@xterm/xterm": "^5.5.0"
44
+ }
45
+ }