claude-code-watch 0.0.5 → 0.0.7

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 CHANGED
@@ -18,7 +18,7 @@ Claude Code writes detailed JSONL logs under `~/.claude/projects/` as it works
18
18
  ## Quick Start
19
19
 
20
20
  ```bash
21
- npx claude-watch
21
+ npx claude-code-watch
22
22
  ```
23
23
 
24
24
  This starts the dashboard at `http://localhost:23000` and opens it in your browser.
@@ -28,19 +28,21 @@ It will auto-discover active Claude Code sessions from `~/.claude/projects/` and
28
28
  ## Installation
29
29
 
30
30
  ```bash
31
- npm install -g claude-watch
31
+ npm install -g claude-code-watch
32
32
  ```
33
33
 
34
34
  Then run:
35
35
 
36
36
  ```bash
37
- claude-watch
37
+ claude-code-watch
38
38
  ```
39
39
 
40
40
  ## Usage
41
41
 
42
42
  ```
43
- claude-watch [OPTIONS]
43
+ claude-code-watch [OPTIONS]
44
+
45
+ Shorter alias: `cc-watch` (equivalent to `claude-code-watch`).
44
46
 
45
47
  OPTIONS:
46
48
  -p, --port <port> HTTP port (default: 23000)
@@ -62,25 +64,25 @@ OPTIONS:
62
64
 
63
65
  ```bash
64
66
  # List recent sessions
65
- claude-watch -l
67
+ claude-code-watch -l
66
68
 
67
69
  # List active sessions from last 10 minutes
68
- claude-watch -a -w 10m
70
+ claude-code-watch -a -w 10m
69
71
 
70
72
  # Watch a specific session
71
- claude-watch -s abc123-def456
73
+ claude-code-watch -s abc123-def456
72
74
 
73
75
  # Live-only mode (don't replay history)
74
- claude-watch -n
76
+ claude-code-watch -n
75
77
 
76
78
  # Custom port and host
77
- claude-watch -p 8080 -h 0.0.0.0
79
+ claude-code-watch -p 8080 -h 0.0.0.0
78
80
 
79
81
  # Limit tree to 5 most recent sessions, auto-collapse after 2m of inactivity
80
- claude-watch -m 5 -c 2m
82
+ claude-code-watch -m 5 -c 2m
81
83
 
82
84
  # Debug mode: see every unknown JSONL line type
83
- claude-watch -D
85
+ claude-code-watch -D
84
86
  ```
85
87
 
86
88
  ## How It Works
package/README.zh-CN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # claude-watch
2
2
 
3
- claude-watch — 一个 Claude Code 的实时 Web 监控仪表盘。
3
+ claude-code-watch — 一个 Claude Code 的实时 Web 监控仪表盘。短命令 `cc-watch`。
4
4
 
5
5
  ## 核心作用
6
6
 
@@ -2,10 +2,13 @@
2
2
 
3
3
  'use strict';
4
4
 
5
+ const https = require('https');
6
+ const cp = require('child_process');
7
+
5
8
  const { startServer } = require('../src/server/server');
6
9
  const { listSessions, listActiveSessions } = require('../src/watcher/watcher');
7
10
 
8
- const VERSION = '0.0.1';
11
+ const { version: VERSION } = require('../package.json');
9
12
 
10
13
  function printHelp() {
11
14
  console.log(`claude-watch v${VERSION}
@@ -15,6 +18,7 @@ to a web browser.
15
18
 
16
19
  USAGE:
17
20
  claude-watch [OPTIONS]
21
+ claude-watch update Check for updates and install latest
18
22
 
19
23
  OPTIONS:
20
24
  -p, --port <port> HTTP port (default: 23000)
@@ -28,6 +32,7 @@ OPTIONS:
28
32
  -c <dur> Auto-collapse sessions inactive for this duration (e.g. 2m)
29
33
  -D Debug: show raw type:subtype for every JSONL line we'd drop
30
34
  --poll <ms> Polling interval in milliseconds (default: 500)
35
+ --no-open Do not auto-open browser on start
31
36
  -v Show version
32
37
  --help Show this help
33
38
 
@@ -36,6 +41,93 @@ ENVIRONMENT:
36
41
  `);
37
42
  }
38
43
 
44
+ function compareVersions(a, b) {
45
+ const pa = a.split('.').map(Number);
46
+ const pb = b.split('.').map(Number);
47
+ for (let i = 0; i < 3; i++) {
48
+ if (pa[i] > pb[i]) return 1;
49
+ if (pa[i] < pb[i]) return -1;
50
+ }
51
+ return 0;
52
+ }
53
+
54
+ function fetchLatestVersion() {
55
+ return new Promise((resolve, reject) => {
56
+ const opts = {
57
+ hostname: 'registry.npmjs.org',
58
+ path: '/claude-code-watch/latest',
59
+ timeout: 5000,
60
+ };
61
+
62
+ const req = https.get(opts, (res) => {
63
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
64
+ let data = '';
65
+ res.on('data', (chunk) => { data += chunk; });
66
+ res.on('end', () => {
67
+ try {
68
+ const json = JSON.parse(data);
69
+ resolve(json.version);
70
+ } catch (err) {
71
+ reject(err);
72
+ }
73
+ });
74
+ });
75
+
76
+ req.on('error', reject);
77
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
78
+ req.end();
79
+ });
80
+ }
81
+
82
+ function checkForUpdate() {
83
+ fetchLatestVersion().then((latest) => {
84
+ if (compareVersions(latest, VERSION) > 0) {
85
+ console.log(`\n New version available: v${latest} (current: v${VERSION})`);
86
+ console.log(' Updating in background...\n');
87
+ const child = cp.spawn('npm', ['install', '-g', 'claude-code-watch@latest'], {
88
+ stdio: 'ignore',
89
+ detached: true,
90
+ });
91
+ child.unref();
92
+ child.on('exit', (code) => {
93
+ if (code === 0) {
94
+ console.log(` Updated to v${latest}. Changes take effect on next start.\n`);
95
+ }
96
+ });
97
+ }
98
+ }).catch(() => { /* network unavailable, skip */ });
99
+ }
100
+
101
+ async function runUpdate() {
102
+ console.log(` Current version: v${VERSION}`);
103
+ console.log(' Checking for latest version...\n');
104
+
105
+ let latest;
106
+ try {
107
+ latest = await fetchLatestVersion();
108
+ } catch (err) {
109
+ console.error(` Failed to check for updates: ${err.message}`);
110
+ process.exit(1);
111
+ }
112
+
113
+ if (compareVersions(latest, VERSION) <= 0) {
114
+ console.log(` Already up to date (v${VERSION}).`);
115
+ return;
116
+ }
117
+
118
+ console.log(` Latest version: v${latest}`);
119
+ console.log(' Running npm install -g claude-code-watch@latest...\n');
120
+
121
+ const { execSync } = require('child_process');
122
+ try {
123
+ execSync('npm install -g claude-code-watch@latest', { stdio: 'inherit' });
124
+ console.log(`\n Updated to v${latest}. Restart to use the new version.`);
125
+ } catch {
126
+ console.error('\n Update failed. Try manually: npm install -g claude-code-watch@latest');
127
+ process.exit(1);
128
+ }
129
+ }
130
+
39
131
  function parseDuration(s) {
40
132
  const match = s.match(/^(\d+)(ms|s|m|h)$/);
41
133
  if (!match) throw new Error(`Invalid duration: ${s}`);
@@ -62,6 +154,7 @@ async function main() {
62
154
  maxSessions: 0,
63
155
  collapseAfter: 0,
64
156
  debugAll: false,
157
+ openBrowser: true,
65
158
  };
66
159
 
67
160
  // First pass: collect all option values
@@ -74,7 +167,7 @@ async function main() {
74
167
  options.skipHistory = true;
75
168
  break;
76
169
  case '-p':
77
- case '--port':
170
+ case '--port': {
78
171
  if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
79
172
  console.error(`Error: ${args[i]} requires a port number`);
80
173
  process.exit(1);
@@ -86,6 +179,7 @@ async function main() {
86
179
  }
87
180
  options.port = pv;
88
181
  break;
182
+ }
89
183
  case '-h':
90
184
  case '--host':
91
185
  if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
@@ -117,6 +211,9 @@ async function main() {
117
211
  case '--poll':
118
212
  options.pollMs = parseInt(args[++i], 10) || 500;
119
213
  break;
214
+ case '--no-open':
215
+ options.openBrowser = false;
216
+ break;
120
217
  default:
121
218
  break;
122
219
  }
@@ -169,6 +266,14 @@ async function main() {
169
266
  case '--help':
170
267
  printHelp();
171
268
  return;
269
+ case 'update':
270
+ await runUpdate();
271
+ return;
272
+ // Skip option flags already handled in first pass
273
+ case '-s': case '-n': case '-p': case '--port':
274
+ case '-h': case '--host': case '-w': case '-c':
275
+ case '-m': case '-D': case '--poll': case '--no-open':
276
+ break;
172
277
  default:
173
278
  if (args[i].startsWith('-')) {
174
279
  console.error(`Unknown option: ${args[i]}`);
@@ -178,6 +283,7 @@ async function main() {
178
283
  }
179
284
  }
180
285
 
286
+ checkForUpdate();
181
287
  startServer(options);
182
288
  }
183
289
 
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "claude-code-watch",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Web-based real-time monitor for Claude Code.",
5
5
  "main": "./src/server/server.js",
6
6
  "bin": {
7
- "claude-code-watch": "./bin/claude-watch.js"
7
+ "claude-code-watch": "bin/claude-watch.js",
8
+ "cc-watch": "bin/claude-watch.js"
8
9
  },
9
10
  "scripts": {
10
11
  "start": "node bin/claude-watch.js",
11
- "dev": "node --watch bin/claude-watch.js",
12
+ "dev": "node --watch bin/claude-watch.js --no-open",
12
13
  "test": "node --test tests/all.test.js tests/watcher.test.js tests/server.test.js"
13
14
  },
14
15
  "files": [
@@ -26,7 +27,7 @@
26
27
  "license": "MIT",
27
28
  "repository": {
28
29
  "type": "git",
29
- "url": "https://github.com/shuxuecode/claude-watch"
30
+ "url": "git+https://github.com/shuxuecode/claude-watch.git"
30
31
  },
31
32
  "engines": {
32
33
  "node": ">=18.0.0"
package/public/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>claude-watch</title>
7
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
7
+ <link rel="stylesheet" href="vendor/github-dark.min.css">
8
8
  <style>
9
9
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
10
 
@@ -30,6 +30,28 @@
30
30
  --orange: #fb923c;
31
31
  }
32
32
 
33
+ :root[data-theme="light"] {
34
+ --bg: #f8f9fa;
35
+ --bg2: #e9ecef;
36
+ --bg3: #ced4da;
37
+ --border: #adb5bd;
38
+ --text: #495057;
39
+ --dim: #868e96;
40
+ --white: #212529;
41
+ --purple: #6741d9;
42
+ --purple2: #5b21b6;
43
+ --blue: #2563eb;
44
+ --magenta: #9333ea;
45
+ --yellow: #d97706;
46
+ --yellow2: #92400e;
47
+ --green: #059669;
48
+ --cyan: #0891b2;
49
+ --red: #dc2626;
50
+ --red2: #b91c1c;
51
+ --gray: #6b7280;
52
+ --orange: #ea580c;
53
+ }
54
+
33
55
  body {
34
56
  background: var(--bg);
35
57
  color: var(--text);
@@ -218,6 +240,21 @@ body {
218
240
 
219
241
  /* Override highlight.js background to match our theme */
220
242
  .hljs { background: #0d1117 !important; }
243
+
244
+ /* Light theme overrides */
245
+ :root[data-theme="light"] .btn.on { background: var(--purple); border-color: var(--purple); color: #fff; }
246
+ :root[data-theme="light"] .btn.on:hover { background: var(--purple2); color: #fff; }
247
+ :root[data-theme="light"] .btn.on:hover::after { background: var(--purple2); color: #fff; }
248
+ :root[data-theme="light"] .hljs { background: #f0f0f0 !important; }
249
+ :root[data-theme="light"] .tree-node:hover { background: rgba(0,0,0,0.06); }
250
+ :root[data-theme="light"] .tree-node.selected { background: rgba(124,58,237,0.2); }
251
+ :root[data-theme="light"] .tree-node .active-dot.off { color: #bbb; }
252
+ :root[data-theme="light"] #tree-resize-handle:hover,
253
+ :root[data-theme="light"] #tree-resize-handle.active { background: var(--purple); }
254
+ :root[data-theme="light"] .stream-line.text { color: var(--text); }
255
+
256
+ /* Theme toggle button */
257
+ #btn-theme { font-size: 14px; }
221
258
  </style>
222
259
  </head>
223
260
  <body>
@@ -233,6 +270,7 @@ body {
233
270
  <span class="sep">│</span>
234
271
  <span id="session-info">Connecting...</span>
235
272
  <div class="auto">
273
+ <button class="btn btn-icon" id="btn-theme" onclick="toggleTheme()" data-tooltip="Toggle theme">🌙</button>
236
274
  <button class="btn on" id="btn-autodisco" onclick="toggleAutoDiscovery()" data-tooltip="Auto-discover">🔍 Auto</button>
237
275
  <span class="sep">│</span>
238
276
  <span id="token-info"></span>
@@ -269,8 +307,9 @@ body {
269
307
  <span class="sep">│</span>
270
308
  </div>
271
309
 
272
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
273
- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script>
310
+ <script src="vendor/highlight.min.js"></script>
311
+ <script src="vendor/marked.min.js"></script>
312
+ <script src="vendor/purify.min.js"></script>
274
313
  <script>
275
314
  // ══════════════════════════════════════════════════════════════════════════════
276
315
  // DOM refs
@@ -290,6 +329,8 @@ let ws = null;
290
329
  let reconnectTimer = null;
291
330
  let reconnectDelay = 1000;
292
331
  const MaxReconnectDelay = 30000;
332
+ const MaxReconnectAttempts = 20;
333
+ let reconnectAttempts = 0;
293
334
  let showTree = true;
294
335
  let autoScroll = true;
295
336
  let lastMsgTime = 0;
@@ -299,8 +340,40 @@ let sessions = [];
299
340
  let treeNodes = [];
300
341
  let treeCursor = 0;
301
342
  let streamItems = [];
302
- let seenToolIDs = new Set();
343
+ const seenToolIDsKeys = [];
344
+ const seenToolIDsSet = new Set();
345
+ const seenToolIDsMax = 5000;
346
+
347
+ function seenToolIDsHas(key) {
348
+ return seenToolIDsSet.has(key);
349
+ }
350
+ function seenToolIDsAdd(key) {
351
+ seenToolIDsSet.add(key);
352
+ seenToolIDsKeys.push(key);
353
+ if (seenToolIDsKeys.length > seenToolIDsMax) {
354
+ const evictCount = seenToolIDsKeys.length >> 1;
355
+ for (let i = 0; i < evictCount; i++) {
356
+ seenToolIDsSet.delete(seenToolIDsKeys[i]);
357
+ }
358
+ seenToolIDsKeys.splice(0, evictCount);
359
+ }
360
+ }
361
+ const toolNameMapMax = 2000;
303
362
  let toolNameMap = new Map(); // toolID -> toolName
363
+ let toolNameMapKeys = [];
364
+
365
+ function toolNameMapSet(toolID, toolName) {
366
+ if (toolNameMap.has(toolID)) return;
367
+ toolNameMap.set(toolID, toolName);
368
+ toolNameMapKeys.push(toolID);
369
+ if (toolNameMapKeys.length > toolNameMapMax) {
370
+ const evictCount = toolNameMapKeys.length >> 1;
371
+ for (let i = 0; i < evictCount; i++) {
372
+ toolNameMap.delete(toolNameMapKeys[i]);
373
+ }
374
+ toolNameMapKeys.splice(0, evictCount);
375
+ }
376
+ }
304
377
  let filters = new Map();
305
378
 
306
379
  let showThinking = true;
@@ -314,6 +387,16 @@ let renderPending = false;
314
387
  let totalInput = 0, totalOutput = 0, totalCacheCreate = 0, totalCacheRead = 0;
315
388
  let contextData = {};
316
389
 
390
+ function computeTokensFromContext() {
391
+ totalInput = 0; totalOutput = 0; totalCacheCreate = 0; totalCacheRead = 0;
392
+ for (const ctx of Object.values(contextData)) {
393
+ totalInput += ctx.inputTokens || 0;
394
+ totalOutput += ctx.outputTokens || 0;
395
+ totalCacheCreate += ctx.cacheCreationTokens || 0;
396
+ totalCacheRead += ctx.cacheReadTokens || 0;
397
+ }
398
+ }
399
+
317
400
  let collapseAfter = 0;
318
401
  let collapseTimer = null;
319
402
  let activeRefreshTimer = null;
@@ -349,7 +432,7 @@ marked.setOptions({ renderer: mdRenderer, breaks: true, gfm: true });
349
432
 
350
433
  function mdRender(text) {
351
434
  try {
352
- return marked.parse(text);
435
+ return DOMPurify.sanitize(marked.parse(text));
353
436
  } catch {
354
437
  return esc(text);
355
438
  }
@@ -367,10 +450,16 @@ function connect() {
367
450
  sessionInfo.textContent = 'Connected';
368
451
  lastMsgTime = Date.now();
369
452
  reconnectDelay = 1000;
453
+ reconnectAttempts = 0;
370
454
  startStaleCheck();
371
455
  startActiveRefresh();
372
456
  };
373
457
  ws.onclose = () => {
458
+ reconnectAttempts++;
459
+ if (reconnectAttempts >= MaxReconnectAttempts) {
460
+ sessionInfo.textContent = 'Disconnected. Please refresh to reconnect.';
461
+ return;
462
+ }
374
463
  sessionInfo.textContent = 'Disconnected, reconnecting...';
375
464
  stopStaleCheck();
376
465
  if (activeRefreshTimer) { clearInterval(activeRefreshTimer); activeRefreshTimer = null; }
@@ -544,10 +633,8 @@ function handleItemBatch(items) {
544
633
  }
545
634
 
546
635
  function pushItem(item) {
547
- if (item.inputTokens > 0) totalInput += item.inputTokens;
548
- if (item.outputTokens > 0) totalOutput += item.outputTokens;
549
- if (item.cacheCreationTokens > 0) totalCacheCreate += item.cacheCreationTokens;
550
- if (item.cacheReadTokens > 0) totalCacheRead += item.cacheReadTokens;
636
+ // Token counts are sourced exclusively from server context messages
637
+ // to avoid divergence between frontend accumulation and server tracking
551
638
 
552
639
  if (item.model) {
553
640
  const s = sessions.find(s => s.id === item.sessionID);
@@ -555,14 +642,13 @@ function pushItem(item) {
555
642
  }
556
643
 
557
644
  if (item.type === 'tool_input' && item.toolID && item.toolName) {
558
- toolNameMap.set(item.toolID, item.toolName);
645
+ toolNameMapSet(item.toolID, item.toolName);
559
646
  }
560
647
 
561
648
  if (item.toolID) {
562
649
  const key = `${item.toolID}:${item.type}`;
563
- if (seenToolIDs.has(key)) return;
564
- seenToolIDs.add(key);
565
- if (seenToolIDs.size > 5000) seenToolIDs.clear();
650
+ if (seenToolIDsHas(key)) return;
651
+ seenToolIDsAdd(key);
566
652
  }
567
653
 
568
654
  streamItems.push(item);
@@ -639,7 +725,6 @@ function getNodeHTML(node, idx) {
639
725
  if (node.type === 'session') {
640
726
  const displayName = node.title || folderName(node.projectPath) || node.id.slice(0, 14);
641
727
  const parts = [];
642
- if (node.folder) parts.push(`📂 ${esc(node.folder)}`);
643
728
  if (node.model) parts.push(`🧠 ${esc(node.model)}`);
644
729
  const activeDot = isSessionActive(node) ? '<span class="active-dot on">🟢</span>' : '<span class="active-dot off">⚪</span>';
645
730
  const subInfo = parts.length > 0 ? ` <span style="color:#6b7280;font-size:10px">${parts.join(' · ')}</span>` : '';
@@ -881,6 +966,7 @@ function refreshButtons() {
881
966
  sessionInfo.textContent = info;
882
967
 
883
968
  // Token info
969
+ computeTokensFromContext();
884
970
  let tokStr = '';
885
971
  if (totalInput > 0 || totalOutput > 0) {
886
972
  tokStr = `${fmtTok(totalInput)} in / ${fmtTok(totalOutput)} out`;
@@ -1155,7 +1241,7 @@ function folderName(projectPath) {
1155
1241
  }
1156
1242
 
1157
1243
  function esc(s) {
1158
- return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1244
+ return (s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;');
1159
1245
  }
1160
1246
 
1161
1247
  function fmtDur(ms) {
@@ -1189,6 +1275,37 @@ function scheduleRender() {
1189
1275
  }
1190
1276
  }
1191
1277
 
1278
+ // ══════════════════════════════════════════════════════════════════════════════
1279
+ // Theme toggle
1280
+ // ══════════════════════════════════════════════════════════════════════════════
1281
+
1282
+ function applyTheme(theme) {
1283
+ document.documentElement.setAttribute('data-theme', theme);
1284
+ const btn = document.getElementById('btn-theme');
1285
+ if (btn) {
1286
+ btn.textContent = theme === 'dark' ? '🌙' : '☀️';
1287
+ btn.setAttribute('data-tooltip', theme === 'dark' ? 'Switch to light' : 'Switch to dark');
1288
+ }
1289
+ // Swap highlight.js stylesheet for theme
1290
+ const hlLink = document.querySelector('link[rel="stylesheet"][href*="github"]');
1291
+ if (hlLink) {
1292
+ hlLink.href = theme === 'dark' ? 'vendor/github-dark.min.css' : 'vendor/github-light.min.css';
1293
+ }
1294
+ }
1295
+
1296
+ function toggleTheme() {
1297
+ const current = document.documentElement.getAttribute('data-theme') || 'dark';
1298
+ const next = current === 'dark' ? 'light' : 'dark';
1299
+ localStorage.setItem('theme', next);
1300
+ applyTheme(next);
1301
+ }
1302
+
1303
+ // Apply saved theme on load (default dark)
1304
+ (function() {
1305
+ const saved = localStorage.getItem('theme');
1306
+ applyTheme(saved || 'dark');
1307
+ })();
1308
+
1192
1309
  // ══════════════════════════════════════════════════════════════════════════════
1193
1310
  // Init
1194
1311
  // ══════════════════════════════════════════════════════════════════════════════
@@ -0,0 +1,10 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2
+ Theme: GitHub Dark
3
+ Description: Dark theme as seen on github.com
4
+ Author: github.com
5
+ Maintainer: @Hirse
6
+ Updated: 2021-05-15
7
+
8
+ Outdated base version: https://github.com/primer/github-syntax-dark
9
+ Current colors taken from GitHub's CSS
10
+ */.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
@@ -0,0 +1,10 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2
+ Theme: GitHub
3
+ Description: Light theme as seen on github.com
4
+ Author: github.com
5
+ Maintainer: @Hirse
6
+ Updated: 2021-05-15
7
+
8
+ Outdated base version: https://github.com/primer/github-syntax-light
9
+ Current colors taken from GitHub's CSS
10
+ */.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}