ccexport 0.1.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.
@@ -0,0 +1,467 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= title %></title>
7
+ <%= include_prism %>
8
+ <style>
9
+ /* Solarized Color Palette */
10
+ :root {
11
+ /* Base colors */
12
+ --base03: #002b36;
13
+ --base02: #073642;
14
+ --base01: #586e75;
15
+ --base00: #657b83;
16
+ --base0: #839496;
17
+ --base1: #93a1a1;
18
+ --base2: #eee8d5;
19
+ --base3: #fdf6e3;
20
+
21
+ /* Accent colors */
22
+ --yellow: #b58900;
23
+ --orange: #cb4b16;
24
+ --red: #dc322f;
25
+ --magenta: #d33682;
26
+ --violet: #6c71c4;
27
+ --blue: #268bd2;
28
+ --cyan: #2aa198;
29
+ --green: #859900;
30
+ }
31
+
32
+ /* Light mode (default) */
33
+ :root {
34
+ --bg-primary: var(--base3);
35
+ --bg-secondary: var(--base2);
36
+ --bg-highlight: var(--base2);
37
+ --text-primary: var(--base00);
38
+ --text-secondary: var(--base01);
39
+ --text-emphasis: var(--base01);
40
+ --border-color: var(--base1);
41
+ --accent-primary: var(--blue);
42
+ --accent-secondary: var(--cyan);
43
+ --code-bg: var(--base2);
44
+ --code-text: var(--base01);
45
+ }
46
+
47
+ /* Dark mode */
48
+ @media (prefers-color-scheme: dark) {
49
+ :root {
50
+ --bg-primary: var(--base03);
51
+ --bg-secondary: var(--base02);
52
+ --bg-highlight: var(--base02);
53
+ --text-primary: var(--base0);
54
+ --text-secondary: var(--base1);
55
+ --text-emphasis: var(--base1);
56
+ --border-color: var(--base01);
57
+ --accent-primary: var(--blue);
58
+ --accent-secondary: var(--cyan);
59
+ --code-bg: var(--base02);
60
+ --code-text: var(--base0);
61
+ }
62
+ }
63
+
64
+ body {
65
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
66
+ line-height: 1.6;
67
+ color: var(--text-primary);
68
+ background-color: var(--bg-primary);
69
+ margin: 0;
70
+ padding: 20px;
71
+ transition: background-color 0.3s ease, color 0.3s ease;
72
+ }
73
+
74
+ .conversation-content {
75
+ max-width: 1200px;
76
+ margin: 0 auto;
77
+ background: var(--bg-secondary);
78
+ padding: 32px;
79
+ border-radius: 16px;
80
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
81
+ transition: background-color 0.3s ease;
82
+ }
83
+
84
+ /* Headers */
85
+ h1 {
86
+ color: var(--accent-primary);
87
+ border-bottom: 2px solid var(--accent-secondary);
88
+ padding-bottom: 12px;
89
+ margin-bottom: 24px;
90
+ border-radius: 0 0 8px 8px;
91
+ }
92
+
93
+ h2 {
94
+ color: var(--text-emphasis);
95
+ margin-top: 32px;
96
+ margin-bottom: 16px;
97
+ border-bottom: 1px solid var(--border-color);
98
+ }
99
+
100
+ /* Code blocks - integrate with Prism.js */
101
+ pre[class*="language-"],
102
+ pre:not([class*="language-"]) {
103
+ background: var(--code-bg) !important;
104
+ border-radius: 6px;
105
+ overflow-x: auto;
106
+ margin: 16px;
107
+ padding: 16px;
108
+ border: 1px solid var(--border-color);
109
+ }
110
+
111
+ code[class*="language-"],
112
+ pre:not([class*="language-"]) > code {
113
+ color: var(--code-text) !important;
114
+ text-shadow: none !important;
115
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
116
+ }
117
+
118
+ /* Inline code (no language class) */
119
+ :not(pre) > code:not([class*="language-"]) {
120
+ background: var(--bg-highlight);
121
+ color: var(--text-emphasis);
122
+ padding: 2px 6px;
123
+ border-radius: 6px;
124
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
125
+ border: 1px solid var(--border-color);
126
+ }
127
+
128
+ /* Solarized syntax highlighting colors */
129
+ .token.comment,
130
+ .token.prolog,
131
+ .token.doctype,
132
+ .token.cdata {
133
+ color: var(--base01) !important;
134
+ font-style: italic;
135
+ }
136
+
137
+ .token.punctuation {
138
+ color: var(--text-primary) !important;
139
+ }
140
+
141
+ .token.property,
142
+ .token.tag,
143
+ .token.boolean,
144
+ .token.number,
145
+ .token.constant,
146
+ .token.symbol,
147
+ .token.deleted {
148
+ color: var(--red) !important;
149
+ }
150
+
151
+ .token.selector,
152
+ .token.attr-name,
153
+ .token.string,
154
+ .token.char,
155
+ .token.builtin,
156
+ .token.inserted {
157
+ color: var(--green) !important;
158
+ }
159
+
160
+ .token.operator,
161
+ .token.entity,
162
+ .token.url {
163
+ color: var(--text-primary) !important;
164
+ }
165
+
166
+ .token.atrule,
167
+ .token.attr-value,
168
+ .token.keyword {
169
+ color: var(--blue) !important;
170
+ }
171
+
172
+ .token.function,
173
+ .token.class-name {
174
+ color: var(--yellow) !important;
175
+ }
176
+
177
+ .token.regex,
178
+ .token.important,
179
+ .token.variable {
180
+ color: var(--orange) !important;
181
+ }
182
+
183
+ /* Additional syntax highlighting */
184
+ .token.namespace {
185
+ color: var(--magenta) !important;
186
+ }
187
+
188
+ .token.important,
189
+ .token.bold {
190
+ font-weight: bold;
191
+ }
192
+
193
+ .token.italic {
194
+ font-style: italic;
195
+ }
196
+
197
+ /* Blockquotes (thinking content) */
198
+ blockquote {
199
+ border-left: 4px solid var(--accent-secondary);
200
+ margin: 16px 0;
201
+ padding: 12px 20px;
202
+ background: var(--bg-highlight);
203
+ border-radius: 0 12px 12px 0;
204
+ font-style: italic;
205
+ color: var(--text-secondary);
206
+ }
207
+
208
+ /* Details/Summary (collapsible sections) */
209
+ details {
210
+ margin: 20px 16px;
211
+ border: 1px solid var(--border-color);
212
+ border-radius: 6px;
213
+ overflow: hidden;
214
+ background: var(--bg-primary);
215
+ }
216
+
217
+ summary {
218
+ background: var(--bg-highlight);
219
+ color: var(--text-emphasis);
220
+ padding: 12px 16px;
221
+ cursor: pointer;
222
+ font-weight: 600;
223
+ border-radius: 6px 6px 0 0;
224
+ transition: background-color 0.2s ease;
225
+ }
226
+
227
+ summary:hover {
228
+ background: var(--accent-primary);
229
+ color: var(--bg-primary);
230
+ }
231
+
232
+ details[open] summary {
233
+ border-radius: 6px 6px 0 0;
234
+ }
235
+
236
+ details > *:not(summary) {
237
+ padding: 16px;
238
+ margin: 0;
239
+ }
240
+
241
+ details > pre {
242
+ margin: 0;
243
+ border-radius: 0;
244
+ }
245
+
246
+ /* Tables */
247
+ table {
248
+ border-collapse: collapse;
249
+ width: 100%;
250
+ margin: 16px 0;
251
+ border-radius: 12px;
252
+ overflow: hidden;
253
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
254
+ }
255
+
256
+ th, td {
257
+ padding: 12px 16px;
258
+ text-align: left;
259
+ border-bottom: 1px solid var(--border-color);
260
+ }
261
+
262
+ th {
263
+ background: var(--accent-primary);
264
+ color: var(--bg-primary);
265
+ font-weight: 600;
266
+ }
267
+
268
+ tr:nth-child(even) {
269
+ background: var(--bg-highlight);
270
+ }
271
+
272
+ /* Links */
273
+ a {
274
+ color: var(--accent-primary);
275
+ text-decoration: none;
276
+ border-bottom: 1px solid var(--accent-secondary);
277
+ transition: border-color 0.2s ease;
278
+ }
279
+
280
+ a:hover {
281
+ border-bottom-color: var(--accent-primary);
282
+ }
283
+
284
+ /* Horizontal rules */
285
+ hr {
286
+ border: none;
287
+ height: 2px;
288
+ background: linear-gradient(to right, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
289
+ margin: 32px 0;
290
+ border-radius: 2px;
291
+ }
292
+
293
+ /* Lists */
294
+ ul, ol {
295
+ padding-left: 24px;
296
+ }
297
+
298
+ li {
299
+ margin: 8px 0;
300
+ }
301
+
302
+ /* Strong/Bold text */
303
+ strong, b {
304
+ color: var(--text-emphasis);
305
+ font-weight: 600;
306
+ }
307
+
308
+ p {
309
+ overflow: scroll;
310
+ }
311
+
312
+ /* Session metadata styling */
313
+ p strong {
314
+ color: var(--accent-primary);
315
+ }
316
+
317
+ /* Smooth transitions for theme switching */
318
+ * {
319
+ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
320
+ }
321
+
322
+ /* Theme toggle button */
323
+ .theme-toggle {
324
+ position: fixed;
325
+ top: 10px;
326
+ right: 10px;
327
+ padding: 8px 12px;
328
+ font-size: 12px;
329
+ background: var(--bg-highlight);
330
+ color: var(--text-secondary);
331
+ border: 1px solid var(--border-color);
332
+ border-radius: 6px;
333
+ cursor: pointer;
334
+ z-index: 1000;
335
+ transition: all 0.2s ease;
336
+ user-select: none;
337
+ }
338
+
339
+ .theme-toggle:hover {
340
+ background: var(--accent-primary);
341
+ color: var(--bg-primary);
342
+ transform: scale(1.05);
343
+ }
344
+
345
+ .theme-toggle:active {
346
+ transform: scale(0.95);
347
+ }
348
+
349
+ /* Manual theme overrides */
350
+ [data-theme="light"] {
351
+ --bg-primary: var(--base3);
352
+ --bg-secondary: var(--base2);
353
+ --bg-highlight: var(--base2);
354
+ --text-primary: var(--base00);
355
+ --text-secondary: var(--base01);
356
+ --text-emphasis: var(--base01);
357
+ --border-color: var(--base1);
358
+ --accent-primary: var(--blue);
359
+ --accent-secondary: var(--cyan);
360
+ --code-bg: var(--base2);
361
+ --code-text: var(--base01);
362
+ }
363
+
364
+ [data-theme="dark"] {
365
+ --bg-primary: var(--base03);
366
+ --bg-secondary: var(--base02);
367
+ --bg-highlight: var(--base02);
368
+ --text-primary: var(--base0);
369
+ --text-secondary: var(--base1);
370
+ --text-emphasis: var(--base1);
371
+ --border-color: var(--base01);
372
+ --accent-primary: var(--blue);
373
+ --accent-secondary: var(--cyan);
374
+ --code-bg: var(--base02);
375
+ --code-text: var(--base0);
376
+ }
377
+ </style>
378
+ </head>
379
+ <body>
380
+ <button class="theme-toggle" id="theme-toggle">
381
+ <span id="theme-icon">☀️</span>
382
+ <span id="theme-text">Light Mode</span>
383
+ </button>
384
+
385
+ <main class="conversation-content">
386
+ <%= content %>
387
+ </main>
388
+
389
+ <script>
390
+ (function() {
391
+ 'use strict';
392
+
393
+ const themeToggle = document.getElementById('theme-toggle');
394
+ const themeIcon = document.getElementById('theme-icon');
395
+ const themeText = document.getElementById('theme-text');
396
+
397
+ // Get stored theme preference or default to system preference
398
+ function getInitialTheme() {
399
+ const stored = localStorage.getItem('solarized-theme');
400
+ if (stored) {
401
+ return stored;
402
+ }
403
+
404
+ // Check system preference
405
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
406
+ return 'dark';
407
+ }
408
+
409
+ return 'light';
410
+ }
411
+
412
+ // Update UI elements based on current theme
413
+ function updateThemeUI(theme) {
414
+ if (theme === 'dark') {
415
+ themeIcon.textContent = '🌙';
416
+ themeText.textContent = 'Dark Mode';
417
+ document.documentElement.setAttribute('data-theme', 'dark');
418
+ } else {
419
+ themeIcon.textContent = '☀️';
420
+ themeText.textContent = 'Light Mode';
421
+ document.documentElement.setAttribute('data-theme', 'light');
422
+ }
423
+ }
424
+
425
+ // Toggle theme
426
+ function toggleTheme() {
427
+ const currentTheme = document.documentElement.getAttribute('data-theme') || getInitialTheme();
428
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
429
+
430
+ updateThemeUI(newTheme);
431
+ localStorage.setItem('solarized-theme', newTheme);
432
+ }
433
+
434
+ // Initialize theme
435
+ const initialTheme = getInitialTheme();
436
+ updateThemeUI(initialTheme);
437
+
438
+ // Add click event listener
439
+ themeToggle.addEventListener('click', toggleTheme);
440
+
441
+ // Listen for system theme changes (but only if user hasn't manually set a preference)
442
+ if (window.matchMedia) {
443
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
444
+ mediaQuery.addListener(function(e) {
445
+ // Only auto-switch if user hasn't manually set a preference
446
+ if (!localStorage.getItem('solarized-theme')) {
447
+ updateThemeUI(e.matches ? 'dark' : 'light');
448
+ }
449
+ });
450
+ }
451
+
452
+ // Add keyboard support (Space or Enter to toggle)
453
+ themeToggle.addEventListener('keydown', function(e) {
454
+ if (e.key === ' ' || e.key === 'Enter') {
455
+ e.preventDefault();
456
+ toggleTheme();
457
+ }
458
+ });
459
+
460
+ // Make button focusable for accessibility
461
+ themeToggle.setAttribute('tabindex', '0');
462
+ themeToggle.setAttribute('aria-label', 'Toggle between light and dark theme');
463
+ themeToggle.setAttribute('role', 'button');
464
+ })();
465
+ </script>
466
+ </body>
467
+ </html>
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ccexport
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Marc Heiligers
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.12'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.12'
40
+ description: "A Ruby tool to export Claude Code conversations from JSONL session files
41
+ \ninto beautifully formatted Markdown and HTML files with syntax highlighting, \nsecret
42
+ detection, and multiple template options.\n"
43
+ email:
44
+ - marc@silvermerc.net
45
+ executables:
46
+ - ccexport
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".gitignore"
51
+ - CHANGELOG.md
52
+ - CLAUDE.md
53
+ - Gemfile
54
+ - Gemfile.lock
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - bin/ccexport
59
+ - ccexport.gemspec
60
+ - exe/ccexport
61
+ - lib/assets/prism-bash.js
62
+ - lib/assets/prism-json.js
63
+ - lib/assets/prism-markdown.js
64
+ - lib/assets/prism-python.js
65
+ - lib/assets/prism-typescript.js
66
+ - lib/assets/prism-yaml.js
67
+ - lib/assets/prism.css
68
+ - lib/assets/prism.js
69
+ - lib/ccexport.rb
70
+ - lib/ccexport/version.rb
71
+ - lib/claude_conversation_exporter.rb
72
+ - lib/markdown_code_block_parser.rb
73
+ - lib/secret_detector.rb
74
+ - lib/templates/default.html.erb
75
+ - lib/templates/github.html.erb
76
+ - lib/templates/solarized.html.erb
77
+ homepage: https://github.com/marcheiligers/ccexport
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ homepage_uri: https://github.com/marcheiligers/ccexport
82
+ source_code_uri: https://github.com/marcheiligers/ccexport.git
83
+ changelog_uri: https://github.com/marcheiligers/ccexport/blob/main/CHANGELOG.md
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 3.0.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 1.8.11
97
+ requirements: []
98
+ rubygems_version: 3.6.7
99
+ specification_version: 4
100
+ summary: Export and preview Claude Code conversations with syntax highlighting
101
+ test_files: []