learn_bash_from_session_data 1.0.5 → 1.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/bin/learn-bash.js +17 -14
- package/package.json +7 -3
- package/scripts/html_generator.py +143 -36
- package/scripts/knowledge_base.py +5624 -1593
- package/scripts/quiz_generator.py +193 -65
- package/bash-learner-output/run-2026-02-05-154214/index.html +0 -3848
- package/bash-learner-output/run-2026-02-05-154214/summary.json +0 -148
- package/bash-learner-output/run-2026-02-05-155427/index.html +0 -3900
- package/bash-learner-output/run-2026-02-05-155427/summary.json +0 -157
- package/bash-learner-output/run-2026-02-05-155949/index.html +0 -4514
- package/bash-learner-output/run-2026-02-05-155949/summary.json +0 -163
package/bin/learn-bash.js
CHANGED
|
@@ -240,26 +240,29 @@ function checkPython() {
|
|
|
240
240
|
*/
|
|
241
241
|
function openInBrowser(filePath) {
|
|
242
242
|
const platform = os.platform();
|
|
243
|
+
const isWindows = platform === 'win32' ||
|
|
244
|
+
process.env.MSYSTEM != null ||
|
|
245
|
+
(process.env.OSTYPE && process.env.OSTYPE.includes('msys')) ||
|
|
246
|
+
isWSL();
|
|
243
247
|
let cmd;
|
|
244
248
|
let args;
|
|
245
249
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Linux and others
|
|
257
|
-
cmd = 'xdg-open';
|
|
258
|
-
args = [filePath];
|
|
250
|
+
if (platform === 'darwin') {
|
|
251
|
+
cmd = 'open';
|
|
252
|
+
args = [filePath];
|
|
253
|
+
} else if (isWindows) {
|
|
254
|
+
// Works from native Windows, MSYS, Git Bash, and WSL
|
|
255
|
+
cmd = process.env.COMSPEC || 'cmd.exe';
|
|
256
|
+
args = ['/c', 'start', '', filePath.replace(/\//g, '\\')];
|
|
257
|
+
} else {
|
|
258
|
+
cmd = 'xdg-open';
|
|
259
|
+
args = [filePath];
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
try {
|
|
262
|
-
spawn(cmd, args, { detached: true, stdio: 'ignore' })
|
|
263
|
+
const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
|
|
264
|
+
child.on('error', () => {}); // Suppress async spawn errors
|
|
265
|
+
child.unref();
|
|
263
266
|
return true;
|
|
264
267
|
} catch (e) {
|
|
265
268
|
return false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "learn_bash_from_session_data",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Learn bash from your Claude Code sessions - extracts commands and generates interactive HTML lessons",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "Learn bash from your Claude Code sessions - extracts commands and generates interactive HTML lessons with 400+ commands, quizzes, and comprehensive coverage",
|
|
5
5
|
"main": "bin/learn-bash.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"learn-bash": "bin/learn-bash.js",
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"claude",
|
|
17
17
|
"cli",
|
|
18
18
|
"terminal",
|
|
19
|
-
"shell"
|
|
19
|
+
"shell",
|
|
20
|
+
"quiz",
|
|
21
|
+
"commands",
|
|
22
|
+
"interactive",
|
|
23
|
+
"education"
|
|
20
24
|
],
|
|
21
25
|
"author": "",
|
|
22
26
|
"license": "MIT",
|
|
@@ -12,6 +12,16 @@ from pathlib import Path
|
|
|
12
12
|
import html
|
|
13
13
|
import json
|
|
14
14
|
|
|
15
|
+
try:
|
|
16
|
+
from scripts.knowledge_base import COMMAND_DB, get_flags_for_command, get_command_info
|
|
17
|
+
except ImportError:
|
|
18
|
+
try:
|
|
19
|
+
from knowledge_base import COMMAND_DB, get_flags_for_command, get_command_info
|
|
20
|
+
except ImportError:
|
|
21
|
+
COMMAND_DB = {}
|
|
22
|
+
def get_flags_for_command(cmd): return {}
|
|
23
|
+
def get_command_info(cmd): return None
|
|
24
|
+
|
|
15
25
|
|
|
16
26
|
def _generate_html_impl(analysis_result: dict[str, Any], quizzes: list[dict[str, Any]]) -> str:
|
|
17
27
|
"""
|
|
@@ -376,7 +386,7 @@ def render_commands_tab(commands: list[dict]) -> str:
|
|
|
376
386
|
# Syntax highlighted command
|
|
377
387
|
highlighted = _syntax_highlight(cmd.get("full_command", ""))
|
|
378
388
|
|
|
379
|
-
# Flags breakdown
|
|
389
|
+
# Flags breakdown with descriptions
|
|
380
390
|
flags = cmd.get("flags", [])
|
|
381
391
|
flags_html = ""
|
|
382
392
|
if flags:
|
|
@@ -384,9 +394,27 @@ def render_commands_tab(commands: list[dict]) -> str:
|
|
|
384
394
|
for flag in flags:
|
|
385
395
|
flag_name = html.escape(flag.get("flag", ""))
|
|
386
396
|
flag_desc = html.escape(flag.get("description", ""))
|
|
387
|
-
|
|
397
|
+
if flag_desc:
|
|
398
|
+
flags_html += f'<li><code class="flag">{flag_name}</code> <span class="flag-desc">{flag_desc}</span></li>'
|
|
399
|
+
else:
|
|
400
|
+
flags_html += f'<li><code class="flag">{flag_name}</code></li>'
|
|
388
401
|
flags_html += '</ul></div>'
|
|
389
402
|
|
|
403
|
+
# Subcommand description
|
|
404
|
+
subcommand_desc = cmd.get("subcommand_desc", "")
|
|
405
|
+
subcmd_html = ""
|
|
406
|
+
if subcommand_desc:
|
|
407
|
+
subcmd_html = f'<div class="subcmd-section"><span class="subcmd-label">Subcommand:</span> {html.escape(subcommand_desc)}</div>'
|
|
408
|
+
|
|
409
|
+
# Common patterns / examples from knowledge base
|
|
410
|
+
common_patterns = cmd.get("common_patterns", [])
|
|
411
|
+
patterns_html = ""
|
|
412
|
+
if common_patterns:
|
|
413
|
+
patterns_html = '<div class="patterns-section"><h5>Common Patterns:</h5><ul class="patterns-list">'
|
|
414
|
+
for pattern in common_patterns[:5]:
|
|
415
|
+
patterns_html += f'<li><code>{html.escape(pattern)}</code></li>'
|
|
416
|
+
patterns_html += '</ul></div>'
|
|
417
|
+
|
|
390
418
|
# Output preview
|
|
391
419
|
output_preview = cmd.get("output_preview", "")
|
|
392
420
|
output_html = ""
|
|
@@ -418,7 +446,9 @@ def render_commands_tab(commands: list[dict]) -> str:
|
|
|
418
446
|
<h5>Description:</h5>
|
|
419
447
|
<p>{description}</p>
|
|
420
448
|
</div>
|
|
449
|
+
{subcmd_html}
|
|
421
450
|
{flags_html}
|
|
451
|
+
{patterns_html}
|
|
422
452
|
{output_html}
|
|
423
453
|
</div>
|
|
424
454
|
</div>'''
|
|
@@ -512,13 +542,37 @@ def render_lessons_tab(categories: dict, commands: list[dict]) -> str:
|
|
|
512
542
|
complexity = cmd.get("complexity", "simple")
|
|
513
543
|
highlighted = _syntax_highlight(cmd.get("full_command", ""))
|
|
514
544
|
|
|
545
|
+
# Get flags and patterns for this command from COMMAND_DB
|
|
546
|
+
cmd_flags = cmd.get("flags", [])
|
|
547
|
+
lesson_flags_html = ""
|
|
548
|
+
if cmd_flags:
|
|
549
|
+
lesson_flags_html = '<div class="lesson-flags"><strong>Flags used:</strong> '
|
|
550
|
+
flag_parts = []
|
|
551
|
+
for flag in cmd_flags:
|
|
552
|
+
fname = html.escape(flag.get("flag", "") if isinstance(flag, dict) else str(flag))
|
|
553
|
+
fdesc = html.escape(flag.get("description", "") if isinstance(flag, dict) else "")
|
|
554
|
+
if fdesc:
|
|
555
|
+
flag_parts.append(f'<code class="flag">{fname}</code> ({fdesc})')
|
|
556
|
+
else:
|
|
557
|
+
flag_parts.append(f'<code class="flag">{fname}</code>')
|
|
558
|
+
lesson_flags_html += ', '.join(flag_parts) + '</div>'
|
|
559
|
+
|
|
560
|
+
# Subcommand info
|
|
561
|
+
subcmd_desc = cmd.get("subcommand_desc", "")
|
|
562
|
+
lesson_subcmd = ""
|
|
563
|
+
if subcmd_desc:
|
|
564
|
+
lesson_subcmd = f'<div class="lesson-subcmd"><em>{html.escape(subcmd_desc)}</em></div>'
|
|
565
|
+
|
|
515
566
|
cat_commands_html += f'''
|
|
516
567
|
<div class="lesson-command">
|
|
517
568
|
<div class="lesson-command-header">
|
|
518
569
|
<code class="cmd">{base_cmd}</code>
|
|
570
|
+
<span class="lesson-complexity complexity-{complexity}">{complexity}</span>
|
|
519
571
|
</div>
|
|
520
572
|
<pre class="syntax-highlighted">{highlighted}</pre>
|
|
521
573
|
<p class="lesson-description">{description}</p>
|
|
574
|
+
{lesson_subcmd}
|
|
575
|
+
{lesson_flags_html}
|
|
522
576
|
</div>'''
|
|
523
577
|
|
|
524
578
|
# Patterns observed
|
|
@@ -559,20 +613,17 @@ def render_lessons_tab(categories: dict, commands: list[dict]) -> str:
|
|
|
559
613
|
def _get_category_concept(category: str) -> str:
|
|
560
614
|
"""Get concept overview for a category."""
|
|
561
615
|
concepts = {
|
|
562
|
-
"File
|
|
563
|
-
"Text Processing": "Tools for searching, filtering,
|
|
564
|
-
"
|
|
565
|
-
"
|
|
566
|
-
"
|
|
567
|
-
"
|
|
568
|
-
"
|
|
569
|
-
"
|
|
570
|
-
"
|
|
571
|
-
"
|
|
572
|
-
"
|
|
573
|
-
"Compression": "Tools for compressing, archiving, and extracting files.",
|
|
574
|
-
"Search": "Commands for finding files, searching content, and locating resources.",
|
|
575
|
-
"Permissions": "Tools for managing file permissions, ownership, and access control.",
|
|
616
|
+
"File System": "Commands for navigating, viewing, creating, and managing files and directories in the filesystem.",
|
|
617
|
+
"Text Processing": "Tools for viewing, searching, filtering, and transforming text content in files and streams.",
|
|
618
|
+
"Git": "Version control system commands for tracking changes, managing branches, and collaborating on code.",
|
|
619
|
+
"Package Management": "Package managers for installing, updating, and managing software dependencies across languages and platforms.",
|
|
620
|
+
"Process & System": "Commands for monitoring, managing, and controlling running processes and system resources.",
|
|
621
|
+
"Networking": "Commands for network operations, file transfers, remote access, and connectivity diagnostics.",
|
|
622
|
+
"Permissions": "Commands for managing file ownership, access permissions, and user/group administration.",
|
|
623
|
+
"Compression": "Commands for compressing, archiving, and extracting files using various algorithms.",
|
|
624
|
+
"Search & Navigation": "Commands for finding files, searching content, and navigating the filesystem efficiently.",
|
|
625
|
+
"Development": "Development tools for building, testing, compiling, and running code across languages.",
|
|
626
|
+
"Shell Builtins": "Built-in shell commands for scripting, variable management, and interactive shell use.",
|
|
576
627
|
}
|
|
577
628
|
return concepts.get(category, f"Commands related to {category.lower()} operations and utilities.")
|
|
578
629
|
|
|
@@ -580,20 +631,17 @@ def _get_category_concept(category: str) -> str:
|
|
|
580
631
|
def _get_category_icon(category: str) -> str:
|
|
581
632
|
"""Get icon for a category."""
|
|
582
633
|
icons = {
|
|
583
|
-
"File
|
|
634
|
+
"File System": "📁",
|
|
584
635
|
"Text Processing": "📄",
|
|
585
|
-
"
|
|
586
|
-
"Network": "🌐",
|
|
636
|
+
"Git": "📊",
|
|
587
637
|
"Package Management": "📦",
|
|
588
|
-
"
|
|
589
|
-
"
|
|
590
|
-
"User Management": "👤",
|
|
591
|
-
"Disk Management": "💿",
|
|
592
|
-
"Shell Scripting": "❯",
|
|
593
|
-
"Development": "💻",
|
|
594
|
-
"Compression": "📦",
|
|
595
|
-
"Search": "🔍",
|
|
638
|
+
"Process & System": "⚙",
|
|
639
|
+
"Networking": "🌐",
|
|
596
640
|
"Permissions": "🔒",
|
|
641
|
+
"Compression": "📦",
|
|
642
|
+
"Search & Navigation": "🔍",
|
|
643
|
+
"Development": "💻",
|
|
644
|
+
"Shell Builtins": "❯",
|
|
597
645
|
}
|
|
598
646
|
return icons.get(category, "📌")
|
|
599
647
|
|
|
@@ -1390,6 +1438,22 @@ def get_inline_css() -> str:
|
|
|
1390
1438
|
color: #34a853;
|
|
1391
1439
|
}
|
|
1392
1440
|
|
|
1441
|
+
.flag-desc { color: #6c757d; margin-left: 4px; }
|
|
1442
|
+
.flags-list li { margin: 4px 0; line-height: 1.5; }
|
|
1443
|
+
.subcmd-section { background: #f0f7ff; padding: 8px 12px; border-radius: 6px; margin: 8px 0; border-left: 3px solid #4a9eff; }
|
|
1444
|
+
.subcmd-label { font-weight: 600; color: #4a9eff; }
|
|
1445
|
+
.patterns-section { margin: 8px 0; }
|
|
1446
|
+
.patterns-section h5 { margin: 4px 0; color: #666; font-size: 0.85em; }
|
|
1447
|
+
.patterns-list { list-style: none; padding: 0; margin: 4px 0; }
|
|
1448
|
+
.patterns-list li { padding: 3px 0; }
|
|
1449
|
+
.patterns-list code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 0.85em; }
|
|
1450
|
+
.lesson-flags { margin: 6px 0; font-size: 0.9em; color: #555; }
|
|
1451
|
+
.lesson-subcmd { font-size: 0.9em; color: #4a9eff; margin: 4px 0; }
|
|
1452
|
+
.lesson-complexity { font-size: 0.75em; padding: 2px 8px; border-radius: 10px; margin-left: 8px; }
|
|
1453
|
+
.complexity-simple { background: #e8f5e9; color: #2e7d32; }
|
|
1454
|
+
.complexity-intermediate { background: #fff3e0; color: #e65100; }
|
|
1455
|
+
.complexity-advanced { background: #fce4ec; color: #c62828; }
|
|
1456
|
+
|
|
1393
1457
|
.syntax-highlighted .string {
|
|
1394
1458
|
color: #ff6d01;
|
|
1395
1459
|
}
|
|
@@ -2113,27 +2177,70 @@ def generate_html_files(
|
|
|
2113
2177
|
# Transform commands to expected format
|
|
2114
2178
|
formatted_commands = []
|
|
2115
2179
|
for cmd in analyzed_commands:
|
|
2116
|
-
|
|
2180
|
+
cmd_str = cmd.get('command', '')
|
|
2181
|
+
base_cmd = cmd.get('base_command', cmd_str.split()[0] if cmd_str else '')
|
|
2182
|
+
complexity_score = cmd.get('complexity', 1)
|
|
2183
|
+
|
|
2184
|
+
# Look up COMMAND_DB info for this command
|
|
2185
|
+
cmd_info = COMMAND_DB.get(base_cmd, {})
|
|
2186
|
+
kb_flags = get_flags_for_command(base_cmd)
|
|
2187
|
+
|
|
2188
|
+
# Convert flags to expected format WITH descriptions from knowledge base
|
|
2117
2189
|
raw_flags = cmd.get('flags', [])
|
|
2118
2190
|
formatted_flags = []
|
|
2119
2191
|
for f in raw_flags:
|
|
2120
|
-
if isinstance(f, dict):
|
|
2121
|
-
|
|
2192
|
+
if isinstance(f, dict) and 'flag' in f:
|
|
2193
|
+
# Already formatted - but enrich description if empty
|
|
2194
|
+
flag_name = f.get('flag', '')
|
|
2195
|
+
flag_desc = f.get('description', '')
|
|
2196
|
+
if not flag_desc and flag_name in kb_flags:
|
|
2197
|
+
flag_desc = kb_flags[flag_name]
|
|
2198
|
+
formatted_flags.append({'flag': flag_name, 'description': flag_desc})
|
|
2122
2199
|
elif isinstance(f, str):
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2200
|
+
# Raw flag string - look up description from knowledge base
|
|
2201
|
+
flag_desc = kb_flags.get(f, '')
|
|
2202
|
+
# For combined flags like -la, try individual characters
|
|
2203
|
+
if not flag_desc and len(f) > 2 and f.startswith('-') and not f.startswith('--'):
|
|
2204
|
+
char_descs = []
|
|
2205
|
+
for char in f[1:]:
|
|
2206
|
+
single = f'-{char}'
|
|
2207
|
+
if single in kb_flags:
|
|
2208
|
+
char_descs.append(f'{single}: {kb_flags[single]}')
|
|
2209
|
+
if char_descs:
|
|
2210
|
+
flag_desc = '; '.join(char_descs)
|
|
2211
|
+
formatted_flags.append({'flag': f, 'description': flag_desc})
|
|
2212
|
+
|
|
2213
|
+
# Get educational description from COMMAND_DB if session description is empty/generic
|
|
2214
|
+
session_desc = cmd.get('description', '')
|
|
2215
|
+
kb_desc = cmd_info.get('description', '')
|
|
2216
|
+
description = session_desc if session_desc else kb_desc
|
|
2217
|
+
|
|
2218
|
+
# Get subcommand info (for commands like git, docker, npm)
|
|
2219
|
+
subcommands = cmd_info.get('subcommands', {})
|
|
2220
|
+
# Try to identify the subcommand from the full command
|
|
2221
|
+
cmd_tokens = cmd_str.split() if cmd_str else []
|
|
2222
|
+
subcommand_desc = ''
|
|
2223
|
+
if subcommands and len(cmd_tokens) > 1:
|
|
2224
|
+
for token in cmd_tokens[1:]:
|
|
2225
|
+
if not token.startswith('-') and token in subcommands:
|
|
2226
|
+
subcommand_desc = subcommands[token]
|
|
2227
|
+
break
|
|
2228
|
+
|
|
2229
|
+
# Get common patterns from COMMAND_DB
|
|
2230
|
+
common_patterns = cmd_info.get('common_patterns', [])
|
|
2127
2231
|
|
|
2128
2232
|
formatted_commands.append({
|
|
2129
|
-
'base_command':
|
|
2233
|
+
'base_command': base_cmd,
|
|
2130
2234
|
'full_command': cmd_str,
|
|
2131
2235
|
'category': cmd.get('category', 'Other'),
|
|
2132
2236
|
'complexity': complexity_to_label(complexity_score),
|
|
2133
2237
|
'complexity_score': complexity_score,
|
|
2134
2238
|
'frequency': frequency_map.get(cmd_str, 1),
|
|
2135
|
-
'description':
|
|
2239
|
+
'description': description,
|
|
2136
2240
|
'flags': formatted_flags,
|
|
2241
|
+
'subcommand_desc': subcommand_desc,
|
|
2242
|
+
'common_patterns': common_patterns[:6],
|
|
2243
|
+
'args': cmd.get('args', []),
|
|
2137
2244
|
'is_new': False,
|
|
2138
2245
|
})
|
|
2139
2246
|
|