opencode-replay 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ramtin Javanmardi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # opencode-replay
2
+
3
+ A CLI tool that generates static HTML transcripts from [OpenCode](https://github.com/sst/opencode) sessions, enabling browsing, searching, and sharing of AI-assisted coding conversations.
4
+
5
+ ## Why?
6
+
7
+ OpenCode stores session data in `~/.local/share/opencode/storage/` as JSON files, but this data isn't easily browsable or shareable. `opencode-replay` transforms these sessions into clean, searchable, static HTML pages.
8
+
9
+ **Use cases:**
10
+ - **PR Documentation** - Attach session transcripts to pull requests showing the AI collaboration process
11
+ - **Self-Review** - Analyze past sessions to identify effective prompting patterns
12
+ - **Team Sharing** - Share session transcripts with teammates for knowledge transfer
13
+ - **Debugging** - Review what happened in a session when something went wrong
14
+ - **Compliance** - Maintain audit trails of AI-assisted code generation
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ # Using bun (recommended)
20
+ bun install -g opencode-replay
21
+
22
+ # Using npm
23
+ npm install -g opencode-replay
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```bash
29
+ # Generate HTML for current project's sessions
30
+ cd /path/to/your/project
31
+ opencode-replay
32
+
33
+ # Open the generated transcript in your browser
34
+ opencode-replay --open
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ### Basic Commands
40
+
41
+ ```bash
42
+ # Generate HTML for current project (auto-detects from cwd)
43
+ opencode-replay
44
+
45
+ # Generate HTML for ALL projects across your machine
46
+ opencode-replay --all
47
+
48
+ # Specify output directory (default: ./opencode-replay-output)
49
+ opencode-replay -o ./my-transcripts
50
+
51
+ # Export a specific session by ID
52
+ opencode-replay --session ses_4957d04cdffeJwdujYPBCKpIsb
53
+
54
+ # Open in browser after generation
55
+ opencode-replay --open
56
+
57
+ # Include raw JSON export alongside HTML
58
+ opencode-replay --json
59
+ ```
60
+
61
+ ### HTTP Server Mode
62
+
63
+ Serve generated transcripts via HTTP for easier viewing and sharing:
64
+
65
+ ```bash
66
+ # Generate and serve via HTTP (default port: 3000)
67
+ opencode-replay --serve
68
+
69
+ # Serve on a custom port
70
+ opencode-replay --serve --port 8080
71
+
72
+ # Serve existing output without regenerating
73
+ opencode-replay --serve --no-generate -o ./existing-output
74
+ ```
75
+
76
+ The built-in server includes:
77
+ - Automatic MIME type detection
78
+ - ETag-based caching for efficient reloads
79
+ - Directory index serving (serves `index.html` for directory requests)
80
+ - Path traversal protection
81
+ - Auto-opens browser on start
82
+
83
+ ### All Options
84
+
85
+ | Option | Short | Description |
86
+ |--------|-------|-------------|
87
+ | `--all` | `-a` | Generate for all projects (default: current project only) |
88
+ | `--output <dir>` | `-o` | Output directory (default: `./opencode-replay-output`) |
89
+ | `--session <id>` | `-s` | Generate for a specific session only |
90
+ | `--json` | | Include raw JSON export alongside HTML |
91
+ | `--open` | | Open in browser after generation |
92
+ | `--storage <path>` | | Custom storage path (default: `~/.local/share/opencode/storage`) |
93
+ | `--serve` | | Start HTTP server after generation |
94
+ | `--port <number>` | | Server port (default: `3000`) |
95
+ | `--no-generate` | | Skip generation, only serve existing output |
96
+ | `--help` | `-h` | Show help message |
97
+ | `--version` | `-v` | Show version |
98
+
99
+ ### Examples
100
+
101
+ ```bash
102
+ # Generate transcripts for your current project
103
+ cd ~/workspace/my-project
104
+ opencode-replay
105
+
106
+ # Generate all sessions and open in browser
107
+ opencode-replay --all --open
108
+
109
+ # Export a specific session with JSON data
110
+ opencode-replay --session ses_abc123 --json -o ./session-export
111
+
112
+ # Use custom storage location
113
+ opencode-replay --storage /custom/path/to/storage
114
+
115
+ # Generate and serve for easy sharing
116
+ opencode-replay --serve --port 8080
117
+
118
+ # Quick preview of existing transcripts
119
+ opencode-replay --serve --no-generate -o ./my-transcripts
120
+ ```
121
+
122
+ ## Output Structure
123
+
124
+ ```
125
+ opencode-replay-output/
126
+ ├── index.html # Master index (all sessions)
127
+ ├── assets/
128
+ │ ├── styles.css # Stylesheet
129
+ │ └── search.js # Client-side search (coming soon)
130
+ ├── projects/ # Only in --all mode
131
+ │ └── {project-name}/
132
+ │ └── index.html # Project-level session list
133
+ └── sessions/
134
+ └── {session-id}/
135
+ ├── index.html # Session overview with timeline
136
+ ├── page-001.html # Conversation pages (5 prompts each)
137
+ ├── page-002.html
138
+ └── session.json # Raw data (if --json flag)
139
+ ```
140
+
141
+ ## Tool Renderers
142
+
143
+ Each tool type has specialized rendering:
144
+
145
+ | Tool | Display |
146
+ |------|---------|
147
+ | `bash` | Terminal-style command with `$` prefix, dark output box |
148
+ | `read` | File path header with content preview and line numbers |
149
+ | `write` | File path with "Created" badge, content preview |
150
+ | `edit` | Side-by-side diff view (old/new comparison) |
151
+ | `glob` | Pattern with file list and type icons |
152
+ | `grep` | Pattern with matching lines (file:line format) |
153
+ | `task` | Agent type badge with collapsible prompt/result |
154
+ | `todowrite` | Checklist with status icons |
155
+ | `webfetch` | Clickable URL with content preview |
156
+ | `batch` | Nested tool call summary |
157
+
158
+ ## Requirements
159
+
160
+ - [Bun](https://bun.sh) runtime (recommended) or Node.js 18+
161
+ - OpenCode sessions in standard storage location
162
+
163
+ ## Development
164
+
165
+ ```bash
166
+ # Clone the repository
167
+ git clone https://github.com/ramtinJ95/opencode-replay
168
+ cd opencode-replay
169
+
170
+ # Install dependencies
171
+ bun install
172
+
173
+ # Run in development mode
174
+ bun run src/index.ts
175
+
176
+ # Type check
177
+ bun run typecheck
178
+
179
+ # Build for distribution
180
+ bun run build
181
+ ```
182
+
183
+ ## License
184
+
185
+ MIT
@@ -0,0 +1,300 @@
1
+ /**
2
+ * OpenCode Replay - Lightweight Syntax Highlighter
3
+ * A minimal syntax highlighter for common programming languages
4
+ * No external dependencies, works offline
5
+ */
6
+
7
+ (function() {
8
+ 'use strict';
9
+
10
+ // Language definitions with token patterns
11
+ const languages = {
12
+ // JavaScript/TypeScript
13
+ javascript: {
14
+ patterns: [
15
+ { type: 'comment', pattern: /\/\/.*$/gm },
16
+ { type: 'comment', pattern: /\/\*[\s\S]*?\*\//g },
17
+ { type: 'string', pattern: /(["'`])(?:(?!\1)[^\\]|\\.)*\1/g },
18
+ { type: 'keyword', pattern: /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|try|catch|finally|throw|new|class|extends|import|export|from|default|async|await|yield|typeof|instanceof|in|of|void|delete|this|super|static|get|set)\b/g },
19
+ { type: 'boolean', pattern: /\b(true|false|null|undefined|NaN|Infinity)\b/g },
20
+ { type: 'number', pattern: /\b\d+\.?\d*([eE][+-]?\d+)?\b/g },
21
+ { type: 'function', pattern: /\b([a-zA-Z_$][\w$]*)\s*(?=\()/g },
22
+ { type: 'class-name', pattern: /\b([A-Z][\w]*)\b/g },
23
+ { type: 'operator', pattern: /[+\-*/%=<>!&|^~?:]+/g },
24
+ { type: 'punctuation', pattern: /[{}[\]();,]/g }
25
+ ]
26
+ },
27
+ typescript: { extends: 'javascript' },
28
+ jsx: { extends: 'javascript' },
29
+ tsx: { extends: 'javascript' },
30
+
31
+ // Python
32
+ python: {
33
+ patterns: [
34
+ { type: 'comment', pattern: /#.*$/gm },
35
+ { type: 'string', pattern: /("""[\s\S]*?"""|'''[\s\S]*?''')/g },
36
+ { type: 'string', pattern: /(["'])(?:(?!\1)[^\\]|\\.)*\1/g },
37
+ { type: 'keyword', pattern: /\b(and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield|True|False|None)\b/g },
38
+ { type: 'builtin', pattern: /\b(print|len|range|str|int|float|list|dict|set|tuple|bool|type|isinstance|hasattr|getattr|setattr|open|input|map|filter|reduce|zip|enumerate|sorted|reversed|sum|min|max|abs|round)\b/g },
39
+ { type: 'number', pattern: /\b\d+\.?\d*([eE][+-]?\d+)?\b/g },
40
+ { type: 'function', pattern: /\b([a-zA-Z_][\w]*)\s*(?=\()/g },
41
+ { type: 'class-name', pattern: /\bclass\s+([A-Z][\w]*)/g },
42
+ { type: 'decorator', pattern: /@[\w.]+/g },
43
+ { type: 'operator', pattern: /[+\-*/%=<>!&|^~@]+/g },
44
+ { type: 'punctuation', pattern: /[{}[\]();:,]/g }
45
+ ]
46
+ },
47
+
48
+ // JSON
49
+ json: {
50
+ patterns: [
51
+ { type: 'property', pattern: /"[^"\\]*(?:\\.[^"\\]*)*"(?=\s*:)/g },
52
+ { type: 'string', pattern: /"[^"\\]*(?:\\.[^"\\]*)*"/g },
53
+ { type: 'number', pattern: /-?\b\d+\.?\d*([eE][+-]?\d+)?\b/g },
54
+ { type: 'boolean', pattern: /\b(true|false|null)\b/g },
55
+ { type: 'punctuation', pattern: /[{}[\]:,]/g }
56
+ ]
57
+ },
58
+
59
+ // HTML/XML
60
+ html: {
61
+ patterns: [
62
+ { type: 'comment', pattern: /<!--[\s\S]*?-->/g },
63
+ { type: 'tag', pattern: /<\/?[\w-]+/g },
64
+ { type: 'attr-name', pattern: /\s[\w-]+(?==)/g },
65
+ { type: 'attr-value', pattern: /=\s*(["'])(?:(?!\1)[^\\]|\\.)*\1/g },
66
+ { type: 'punctuation', pattern: /[<>\/=]/g }
67
+ ]
68
+ },
69
+ xml: { extends: 'html' },
70
+ svg: { extends: 'html' },
71
+
72
+ // CSS
73
+ css: {
74
+ patterns: [
75
+ { type: 'comment', pattern: /\/\*[\s\S]*?\*\//g },
76
+ { type: 'selector', pattern: /[^{}]+(?=\s*\{)/g },
77
+ { type: 'property', pattern: /[\w-]+(?=\s*:)/g },
78
+ { type: 'string', pattern: /(["'])(?:(?!\1)[^\\]|\\.)*\1/g },
79
+ { type: 'number', pattern: /-?\b\d+\.?\d*(px|em|rem|%|vh|vw|deg|s|ms)?\b/g },
80
+ { type: 'function', pattern: /[\w-]+(?=\()/g },
81
+ { type: 'punctuation', pattern: /[{}();:,]/g }
82
+ ]
83
+ },
84
+ scss: { extends: 'css' },
85
+ less: { extends: 'css' },
86
+
87
+ // Bash/Shell
88
+ bash: {
89
+ patterns: [
90
+ { type: 'comment', pattern: /#.*$/gm },
91
+ { type: 'string', pattern: /(["'])(?:(?!\1)[^\\]|\\.)*\1/g },
92
+ { type: 'keyword', pattern: /\b(if|then|else|elif|fi|for|while|do|done|case|esac|function|return|exit|break|continue|export|source|alias|unalias|cd|pwd|echo|printf|read|test)\b/g },
93
+ { type: 'builtin', pattern: /\b(ls|cat|grep|sed|awk|find|xargs|sort|uniq|head|tail|wc|cut|tr|mkdir|rm|cp|mv|chmod|chown|curl|wget|tar|gzip|gunzip|ssh|scp|git|npm|yarn|bun|node|python|pip)\b/g },
94
+ { type: 'variable', pattern: /\$[\w]+|\$\{[^}]+\}/g },
95
+ { type: 'operator', pattern: /[|&;><]+/g },
96
+ { type: 'punctuation', pattern: /[()[\]{}]/g }
97
+ ]
98
+ },
99
+ sh: { extends: 'bash' },
100
+ shell: { extends: 'bash' },
101
+ zsh: { extends: 'bash' },
102
+
103
+ // SQL
104
+ sql: {
105
+ patterns: [
106
+ { type: 'comment', pattern: /--.*$/gm },
107
+ { type: 'comment', pattern: /\/\*[\s\S]*?\*\//g },
108
+ { type: 'string', pattern: /(["'])(?:(?!\1)[^\\]|\\.)*\1/g },
109
+ { type: 'keyword', pattern: /\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|ON|AND|OR|NOT|IN|EXISTS|BETWEEN|LIKE|IS|NULL|AS|ORDER|BY|GROUP|HAVING|LIMIT|OFFSET|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|INDEX|VIEW|DROP|ALTER|ADD|COLUMN|PRIMARY|KEY|FOREIGN|REFERENCES|UNIQUE|DEFAULT|CHECK|CONSTRAINT|CASCADE|UNION|ALL|DISTINCT|COUNT|SUM|AVG|MIN|MAX|CASE|WHEN|THEN|ELSE|END)\b/gi },
110
+ { type: 'number', pattern: /\b\d+\.?\d*\b/g },
111
+ { type: 'operator', pattern: /[=<>!+\-*/%]+/g },
112
+ { type: 'punctuation', pattern: /[(),;.]/g }
113
+ ]
114
+ },
115
+
116
+ // Go
117
+ go: {
118
+ patterns: [
119
+ { type: 'comment', pattern: /\/\/.*$/gm },
120
+ { type: 'comment', pattern: /\/\*[\s\S]*?\*\//g },
121
+ { type: 'string', pattern: /(["'`])(?:(?!\1)[^\\]|\\.)*\1/g },
122
+ { type: 'keyword', pattern: /\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/g },
123
+ { type: 'builtin', pattern: /\b(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)\b/g },
124
+ { type: 'boolean', pattern: /\b(true|false|nil|iota)\b/g },
125
+ { type: 'number', pattern: /\b\d+\.?\d*([eE][+-]?\d+)?\b/g },
126
+ { type: 'function', pattern: /\b([a-zA-Z_][\w]*)\s*(?=\()/g },
127
+ { type: 'class-name', pattern: /\b([A-Z][\w]*)\b/g },
128
+ { type: 'operator', pattern: /[+\-*/%=<>!&|^:]+/g },
129
+ { type: 'punctuation', pattern: /[{}[\]();,]/g }
130
+ ]
131
+ },
132
+
133
+ // Rust
134
+ rust: {
135
+ patterns: [
136
+ { type: 'comment', pattern: /\/\/.*$/gm },
137
+ { type: 'comment', pattern: /\/\*[\s\S]*?\*\//g },
138
+ { type: 'string', pattern: /(["'])(?:(?!\1)[^\\]|\\.)*\1/g },
139
+ { type: 'keyword', pattern: /\b(as|async|await|break|const|continue|crate|dyn|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|Self|static|struct|super|trait|true|type|unsafe|use|where|while)\b/g },
140
+ { type: 'builtin', pattern: /\b(Option|Result|Some|None|Ok|Err|Vec|String|Box|Rc|Arc|Cell|RefCell|HashMap|HashSet|println!|print!|format!|vec!|panic!|assert!|debug!)\b/g },
141
+ { type: 'number', pattern: /\b\d+\.?\d*([eE][+-]?\d+)?[iu]?(8|16|32|64|128|size)?\b/g },
142
+ { type: 'function', pattern: /\b([a-z_][\w]*)\s*(?=\()/g },
143
+ { type: 'class-name', pattern: /\b([A-Z][\w]*)\b/g },
144
+ { type: 'lifetime', pattern: /'[a-z_][\w]*/g },
145
+ { type: 'operator', pattern: /[+\-*/%=<>!&|^:?]+/g },
146
+ { type: 'punctuation', pattern: /[{}[\]();,]/g }
147
+ ]
148
+ },
149
+
150
+ // YAML
151
+ yaml: {
152
+ patterns: [
153
+ { type: 'comment', pattern: /#.*$/gm },
154
+ { type: 'property', pattern: /^[\w-]+(?=\s*:)/gm },
155
+ { type: 'string', pattern: /(["'])(?:(?!\1)[^\\]|\\.)*\1/g },
156
+ { type: 'boolean', pattern: /\b(true|false|yes|no|on|off|null|~)\b/gi },
157
+ { type: 'number', pattern: /\b\d+\.?\d*\b/g },
158
+ { type: 'punctuation', pattern: /[:\-[\]{}|>]/g }
159
+ ]
160
+ },
161
+ yml: { extends: 'yaml' },
162
+
163
+ // Markdown
164
+ markdown: {
165
+ patterns: [
166
+ { type: 'title', pattern: /^#{1,6}\s+.+$/gm },
167
+ { type: 'bold', pattern: /\*\*[^*]+\*\*|__[^_]+__/g },
168
+ { type: 'italic', pattern: /\*[^*]+\*|_[^_]+_/g },
169
+ { type: 'code', pattern: /`[^`]+`/g },
170
+ { type: 'url', pattern: /\[[^\]]+\]\([^)]+\)/g },
171
+ { type: 'list', pattern: /^[\s]*[-*+]\s/gm },
172
+ { type: 'blockquote', pattern: /^>\s.+$/gm }
173
+ ]
174
+ },
175
+ md: { extends: 'markdown' },
176
+
177
+ // Diff
178
+ diff: {
179
+ patterns: [
180
+ { type: 'deleted', pattern: /^-.*$/gm },
181
+ { type: 'inserted', pattern: /^\+.*$/gm },
182
+ { type: 'coord', pattern: /^@@.*@@$/gm },
183
+ { type: 'comment', pattern: /^(diff|index|---|\+\+\+).*$/gm }
184
+ ]
185
+ },
186
+
187
+ // Plain text (no highlighting)
188
+ text: { patterns: [] },
189
+ plaintext: { patterns: [] }
190
+ };
191
+
192
+ // Resolve language extensions
193
+ function getLanguagePatterns(lang) {
194
+ const langDef = languages[lang] || languages.text;
195
+ if (langDef.extends) {
196
+ return languages[langDef.extends]?.patterns || [];
197
+ }
198
+ return langDef.patterns || [];
199
+ }
200
+
201
+ // Escape HTML special characters
202
+ function escapeHtml(str) {
203
+ return str
204
+ .replace(/&/g, '&amp;')
205
+ .replace(/</g, '&lt;')
206
+ .replace(/>/g, '&gt;')
207
+ .replace(/"/g, '&quot;')
208
+ .replace(/'/g, '&#39;');
209
+ }
210
+
211
+ // Highlight code with given language
212
+ function highlight(code, lang) {
213
+ const normalizedLang = (lang || 'text').toLowerCase().replace(/[^a-z0-9]/g, '');
214
+ const patterns = getLanguagePatterns(normalizedLang);
215
+
216
+ if (patterns.length === 0) {
217
+ return escapeHtml(code);
218
+ }
219
+
220
+ // Track which parts of the code have been tokenized
221
+ const tokens = [];
222
+
223
+ // Apply each pattern
224
+ for (const { type, pattern } of patterns) {
225
+ // Reset pattern lastIndex
226
+ pattern.lastIndex = 0;
227
+ let match;
228
+
229
+ while ((match = pattern.exec(code)) !== null) {
230
+ tokens.push({
231
+ type,
232
+ start: match.index,
233
+ end: match.index + match[0].length,
234
+ text: match[0]
235
+ });
236
+ }
237
+ }
238
+
239
+ // Sort tokens by start position
240
+ tokens.sort((a, b) => a.start - b.start);
241
+
242
+ // Remove overlapping tokens (keep first match)
243
+ const filtered = [];
244
+ let lastEnd = 0;
245
+ for (const token of tokens) {
246
+ if (token.start >= lastEnd) {
247
+ filtered.push(token);
248
+ lastEnd = token.end;
249
+ }
250
+ }
251
+
252
+ // Build highlighted HTML
253
+ let result = '';
254
+ let pos = 0;
255
+ for (const token of filtered) {
256
+ if (token.start > pos) {
257
+ result += escapeHtml(code.slice(pos, token.start));
258
+ }
259
+ result += `<span class="token ${token.type}">${escapeHtml(token.text)}</span>`;
260
+ pos = token.end;
261
+ }
262
+ if (pos < code.length) {
263
+ result += escapeHtml(code.slice(pos));
264
+ }
265
+
266
+ return result;
267
+ }
268
+
269
+ // Find and highlight all code blocks on page load
270
+ function highlightAll() {
271
+ const codeBlocks = document.querySelectorAll('pre code[class*="language-"]');
272
+
273
+ for (const block of codeBlocks) {
274
+ // Extract language from class
275
+ const classes = block.className.split(/\s+/);
276
+ const langClass = classes.find(c => c.startsWith('language-'));
277
+ const lang = langClass ? langClass.replace('language-', '') : 'text';
278
+
279
+ // Get original code text
280
+ const code = block.textContent || '';
281
+
282
+ // Apply highlighting
283
+ block.innerHTML = highlight(code, lang);
284
+ }
285
+ }
286
+
287
+ // Run on DOM ready
288
+ if (document.readyState === 'loading') {
289
+ document.addEventListener('DOMContentLoaded', highlightAll);
290
+ } else {
291
+ highlightAll();
292
+ }
293
+
294
+ // Expose API for dynamic use
295
+ window.OpenCodeHighlight = {
296
+ highlight,
297
+ highlightAll,
298
+ languages: Object.keys(languages)
299
+ };
300
+ })();