learn_bash_from_session_data 1.0.7 → 1.0.9

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
@@ -1,6 +1,6 @@
1
1
  # learn_bash_from_session_data
2
2
 
3
- Learn bash from your Claude Code sessions. Extracts commands you've used, categorizes them, and generates an interactive HTML learning resource with quizzes.
3
+ Turn your Claude Code sessions into personalized bash lessons. This tool extracts every command you've run, enriches them with descriptions and flag breakdowns from a 402-command knowledge base, generates interactive quizzes, and produces a self-contained HTML learning resource.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,37 +8,160 @@ Learn bash from your Claude Code sessions. Extracts commands you've used, catego
8
8
  npm install -g learn_bash_from_session_data
9
9
  ```
10
10
 
11
- ## Usage
11
+ **Requirements:** Node.js >= 14.0.0 and Python >= 3.8
12
+
13
+ ## Quick Start
12
14
 
13
15
  ```bash
14
- # Analyze all sessions from current project
16
+ # Generate a lesson from your current project's sessions
15
17
  learn-bash
16
18
 
17
- # Analyze last 5 sessions
19
+ # Process the last 5 sessions only
18
20
  learn-bash -n 5
19
21
 
20
- # List available projects
22
+ # List all available Claude Code projects
21
23
  learn-bash --list
24
+ ```
25
+
26
+ The generated HTML opens automatically in your browser.
27
+
28
+ ## CLI Reference
29
+
30
+ ```
31
+ learn-bash [options]
32
+ ```
33
+
34
+ | Flag | Short | Description |
35
+ |------|-------|-------------|
36
+ | `--sessions <count>` | `-n` | Number of recent sessions to process (default: all) |
37
+ | `--file <path>` | `-f` | Process a specific session JSONL file |
38
+ | `--output <path>` | `-o` | Output directory path (default: `./bash-learner-output/`) |
39
+ | `--project <name>` | `-p` | Process sessions from a specific project by name |
40
+ | `--list` | `-l` | List available Claude Code projects with session counts |
41
+ | `--no-open` | | Don't auto-open the generated HTML in browser |
42
+ | `--help` | `-h` | Show help message |
43
+
44
+ ### Examples
45
+
46
+ ```bash
47
+ # Process a specific session file
48
+ learn-bash --file ~/.claude/projects/my-project/abc123.jsonl
49
+
50
+ # Output to a custom location without opening browser
51
+ learn-bash -o ./my-lessons --no-open
22
52
 
23
- # Process specific session file
24
- learn-bash --file ~/.claude/projects/.../session.jsonl
53
+ # Process sessions from a named project
54
+ learn-bash --project "C--Users-me-my-app"
25
55
 
26
- # Custom output path
27
- learn-bash --output ./my-lessons.html
56
+ # Process last 3 sessions, custom output
57
+ learn-bash -n 3 -o ./review
28
58
  ```
29
59
 
30
- ## Features
60
+ ## What You Get
61
+
62
+ The tool generates a single interactive HTML file with four sections:
63
+
64
+ ### Commands Tab
65
+ Every bash command you used, organized by category with:
66
+ - Syntax-highlighted full command display
67
+ - Flag breakdowns with descriptions (e.g., `-l` = "Long format listing with permissions, size, dates")
68
+ - Subcommand explanations (e.g., `git add` = "Stage file contents for commit")
69
+ - Common usage patterns from the knowledge base
70
+ - Search, sort (by frequency, complexity, category, name), and category filtering
71
+
72
+ ### Lessons Tab
73
+ Step-by-step walkthrough of commands grouped by category, with flag details and complexity indicators. Designed for sequential learning.
74
+
75
+ ### Quiz Tab
76
+ 20 auto-generated questions in four types:
77
+
78
+ | Type | Weight | What It Tests |
79
+ |------|--------|---------------|
80
+ | What does this do? | 40% | Identify a command's purpose from its syntax |
81
+ | Which flag? | 25% | Match a flag to its behavior |
82
+ | Build the command | 20% | Construct the correct command for a task |
83
+ | Spot the difference | 15% | Compare two similar commands |
84
+
85
+ Quizzes are **session-adaptive** (based on commands you actually used), **randomized** (different questions and answer order each run), and use plausible distractors drawn from 402 real commands.
86
+
87
+ ### Summary Tab
88
+ Statistics on your session: total commands, category distribution, complexity breakdown, most-used commands.
89
+
90
+ ## Knowledge Base
31
91
 
32
- - **Command Extraction**: Parses Claude Code session JSONL files
33
- - **Categorization**: Groups commands by category (Git, File System, Text Processing, etc.)
34
- - **Complexity Scoring**: Rates commands from 1-5 based on complexity
35
- - **Interactive Quizzes**: Test your knowledge with auto-generated quizzes
36
- - **Self-Contained HTML**: No external dependencies, works offline
92
+ The built-in knowledge base powers descriptions, flag lookups, and quiz generation:
37
93
 
38
- ## Requirements
94
+ | Metric | Count |
95
+ |--------|-------|
96
+ | Commands documented | 402 |
97
+ | Flag definitions | 1,961 |
98
+ | Common usage patterns | 1,357 |
99
+ | Categories | 11 |
100
+ | Bash operators | 16 |
101
+ | Bash concepts | 6 |
39
102
 
40
- - Node.js >= 14.0.0
41
- - Python >= 3.8
103
+ ### Categories
104
+
105
+ File System, Text Processing, Git, Package Management, Process & System, Networking, Permissions, Compression, Search & Navigation, Development, Shell Builtins
106
+
107
+ ## How It Works
108
+
109
+ ```
110
+ Claude Code session (.jsonl)
111
+ |
112
+ v
113
+ [Parser] --> Extract bash tool_use blocks
114
+ |
115
+ v
116
+ [Extractor] --> Split compound commands (pipes, &&, ;)
117
+ |
118
+ v
119
+ [Analyzer] --> Categorize, score complexity (1-5), count frequency
120
+ |
121
+ v
122
+ [Knowledge Base] --> Enrich with 402 commands, 1961 flags, 1357 patterns
123
+ |
124
+ v
125
+ [Quiz Generator] --> 20 randomized, session-adaptive questions
126
+ |
127
+ v
128
+ [HTML Generator] --> Self-contained interactive HTML (no dependencies)
129
+ ```
130
+
131
+ ## Session File Location
132
+
133
+ Claude Code stores sessions at:
134
+
135
+ | Platform | Path |
136
+ |----------|------|
137
+ | macOS/Linux | `~/.claude/projects/` |
138
+ | Windows | `%USERPROFILE%\.claude\projects\` |
139
+ | WSL | Auto-detected from `/mnt/c/Users/<name>/.claude/projects/` |
140
+
141
+ Each project directory contains `.jsonl` session files that this tool reads.
142
+
143
+ ## Programmatic Usage
144
+
145
+ You can also run the Python pipeline directly:
146
+
147
+ ```bash
148
+ python scripts/main.py --sessions 5 --output ./output
149
+ ```
150
+
151
+ Or import modules in Python:
152
+
153
+ ```python
154
+ from scripts.knowledge_base import COMMAND_DB, get_flags_for_command, get_command_info
155
+ from scripts.quiz_generator import generate_quiz_set
156
+ from scripts.analyzer import analyze_commands
157
+
158
+ # Look up a command
159
+ info = get_command_info("grep")
160
+ flags = get_flags_for_command("grep")
161
+
162
+ # Generate quizzes from analyzed commands
163
+ quizzes = generate_quiz_set(analyzed_commands, count=10)
164
+ ```
42
165
 
43
166
  ## License
44
167
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "learn_bash_from_session_data",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
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": {
@@ -433,7 +433,7 @@ def render_commands_tab(commands: list[dict]) -> str:
433
433
  <span class="category-badge">{category}</span>
434
434
  </div>
435
435
  <div class="command-meta">
436
- <span class="frequency">Used {frequency}x</span>
436
+ <span class="cmd-preview">{' '.join(description.split())[:60]}{'...' if len(' '.join(description.split())) > 60 else ''}</span>
437
437
  <span class="expand-icon">&#9660;</span>
438
438
  </div>
439
439
  </div>
@@ -1358,9 +1358,13 @@ def get_inline_css() -> str:
1358
1358
  gap: 16px;
1359
1359
  }
1360
1360
 
1361
- .frequency {
1362
- font-size: 0.85rem;
1361
+ .cmd-preview {
1362
+ font-size: 0.8rem;
1363
1363
  color: var(--text-secondary);
1364
+ max-width: 400px;
1365
+ overflow: hidden;
1366
+ text-overflow: ellipsis;
1367
+ white-space: nowrap;
1364
1368
  }
1365
1369
 
1366
1370
  .expand-icon {
@@ -2181,25 +2185,54 @@ def generate_html_files(
2181
2185
  base_cmd = cmd.get('base_command', cmd_str.split()[0] if cmd_str else '')
2182
2186
  complexity_score = cmd.get('complexity', 1)
2183
2187
 
2188
+ # Filter out non-bash entries (Python/JS code fragments, single chars, status text)
2189
+ if not base_cmd or len(base_cmd) < 2:
2190
+ continue
2191
+ # Skip entries that look like code fragments (contain parens, equals, dots as methods)
2192
+ if any(c in base_cmd for c in ('(', ')', '=', '{', '}')) and not base_cmd.startswith('.'):
2193
+ continue
2194
+ # Skip entries that are clearly not commands (capitalized status words, text fragments)
2195
+ if base_cmd[0].isupper() and base_cmd.isalpha() and base_cmd not in ('PATH', 'HOME'):
2196
+ continue
2197
+ # Skip common text fragments that get misidentified as commands
2198
+ junk_tokens = {'version', 'total', 'package', 'success', 'error', 'reading',
2199
+ 'editing', 'done', 'warning', 'info', 'note', 'output'}
2200
+ if base_cmd.lower() in junk_tokens:
2201
+ continue
2202
+
2203
+ # Tokenize the command for subcommand/description generation
2204
+ cmd_tokens = cmd_str.split() if cmd_str else []
2205
+
2184
2206
  # Look up COMMAND_DB info for this command
2185
2207
  cmd_info = COMMAND_DB.get(base_cmd, {})
2186
2208
  kb_flags = get_flags_for_command(base_cmd)
2187
2209
 
2188
2210
  # Convert flags to expected format WITH descriptions from knowledge base
2211
+ # Filter out non-flag tokens: bare dashes, numeric args (-5, -30), trailing colons
2212
+ import re
2189
2213
  raw_flags = cmd.get('flags', [])
2190
2214
  formatted_flags = []
2215
+ seen_flags = set()
2191
2216
  for f in raw_flags:
2217
+ flag_name = f.get('flag', '') if isinstance(f, dict) else f
2218
+ # Skip bare dash, numeric-only flags (-5, -30), and artifact flags with colons
2219
+ if not flag_name or flag_name == '-' or flag_name.endswith(':'):
2220
+ continue
2221
+ if re.match(r'^-\d+$', flag_name):
2222
+ continue
2223
+ # Deduplicate flags within same command
2224
+ if flag_name in seen_flags:
2225
+ continue
2226
+ seen_flags.add(flag_name)
2227
+
2192
2228
  if isinstance(f, dict) and 'flag' in f:
2193
- # Already formatted - but enrich description if empty
2194
- flag_name = f.get('flag', '')
2195
2229
  flag_desc = f.get('description', '')
2196
2230
  if not flag_desc and flag_name in kb_flags:
2197
2231
  flag_desc = kb_flags[flag_name]
2198
2232
  formatted_flags.append({'flag': flag_name, 'description': flag_desc})
2199
2233
  elif isinstance(f, str):
2200
- # Raw flag string - look up description from knowledge base
2201
2234
  flag_desc = kb_flags.get(f, '')
2202
- # For combined flags like -la, try individual characters
2235
+ # For combined flags like -la, decompose into individual flags
2203
2236
  if not flag_desc and len(f) > 2 and f.startswith('-') and not f.startswith('--'):
2204
2237
  char_descs = []
2205
2238
  for char in f[1:]:
@@ -2208,17 +2241,117 @@ def generate_html_files(
2208
2241
  char_descs.append(f'{single}: {kb_flags[single]}')
2209
2242
  if char_descs:
2210
2243
  flag_desc = '; '.join(char_descs)
2244
+ # For find-style flags (-name, -type, -path, -maxdepth), add descriptions
2245
+ if not flag_desc:
2246
+ find_flags = {
2247
+ '-name': 'Match files by name pattern',
2248
+ '-type': 'Filter by file type (f=file, d=directory)',
2249
+ '-path': 'Match files by path pattern',
2250
+ '-maxdepth': 'Limit directory recursion depth',
2251
+ '-mindepth': 'Set minimum directory depth',
2252
+ '-exec': 'Execute command on each match',
2253
+ '-not': 'Negate the following expression',
2254
+ '-size': 'Match files by size',
2255
+ '-mtime': 'Match by modification time',
2256
+ '-perm': 'Match by file permissions',
2257
+ '-ls': 'List matched files in ls -l format',
2258
+ '-delete': 'Delete matched files',
2259
+ '-print': 'Print matched file paths',
2260
+ }
2261
+ flag_desc = find_flags.get(f, '')
2262
+ # For common CLI flags without KB entries
2263
+ if not flag_desc:
2264
+ common_flags = {
2265
+ '--help': 'Show help and usage information',
2266
+ '--version': 'Show version number',
2267
+ '--verbose': 'Enable verbose output',
2268
+ '--dry-run': 'Preview changes without executing',
2269
+ '--output': 'Specify output file or directory',
2270
+ '--open': 'Open result in default application',
2271
+ '--stat': 'Show diffstat summary of changes',
2272
+ '--sessions': 'Number of sessions to process',
2273
+ '--title': 'Set custom title',
2274
+ '--no-open': 'Skip auto-opening in browser',
2275
+ '--from': 'Specify input source path',
2276
+ '-s': 'Silent/short output mode',
2277
+ '-n': 'Numeric/count or line number',
2278
+ '-c': 'Execute command string or count',
2279
+ '-g': 'Global scope',
2280
+ '-p': 'Preserve attributes or port',
2281
+ '-o': 'Output file',
2282
+ '-P': 'No dereference (physical path)',
2283
+ }
2284
+ flag_desc = common_flags.get(f, '')
2211
2285
  formatted_flags.append({'flag': f, 'description': flag_desc})
2212
2286
 
2213
- # Get educational description from COMMAND_DB if session description is empty/generic
2287
+ # Generate a contextual description that differentiates commands with the same base
2214
2288
  session_desc = cmd.get('description', '')
2215
2289
  kb_desc = cmd_info.get('description', '')
2216
- description = session_desc if session_desc else kb_desc
2290
+
2291
+ # Build a specific description from the actual command content
2292
+ args_list = cmd.get('args', [])
2293
+ flag_list = [fl.get('flag', '') if isinstance(fl, dict) else str(fl) for fl in formatted_flags]
2294
+ contextual_desc = ''
2295
+
2296
+ # For inline code execution (python -c, bash -c), summarize the code snippet
2297
+ if base_cmd in ('python', 'python3', 'bash', 'sh', 'node') and '-c' in flag_list:
2298
+ # Extract the inline code from the full command after -c
2299
+ c_idx = cmd_str.find('-c')
2300
+ if c_idx >= 0:
2301
+ raw_code = cmd_str[c_idx + 2:].strip().strip('"').strip("'")
2302
+ # Split on actual newlines before collapsing
2303
+ code_lines = [l.strip() for l in raw_code.splitlines() if l.strip()]
2304
+ # Find first non-import line for a distinctive preview
2305
+ action_lines = [l for l in code_lines if not l.startswith(('import ', 'from ', '#'))]
2306
+ if action_lines:
2307
+ code_part = ' '.join(action_lines[0].split())[:60]
2308
+ elif code_lines:
2309
+ # All imports - show what's being imported
2310
+ code_part = ' '.join(code_lines[0].split())[:60]
2311
+ else:
2312
+ code_part = ''
2313
+ if code_part:
2314
+ contextual_desc = f"{base_cmd} -c: {code_part}{'...' if len(code_part) >= 60 else ''}"
2315
+
2316
+ # For commands with subcommands (git, npm, docker, etc.), use subcommand context
2317
+ if not contextual_desc and cmd_tokens and len(cmd_tokens) > 1:
2318
+ subcmd_token = next((t for t in cmd_tokens[1:] if not t.startswith('-') and not t.startswith('"') and not t.startswith("'")), '')
2319
+ if subcmd_token and subcmd_token != base_cmd:
2320
+ subcmd_info = cmd_info.get('subcommands', {}).get(subcmd_token, '')
2321
+ if subcmd_info:
2322
+ contextual_desc = f"{base_cmd} {subcmd_token}: {subcmd_info}"
2323
+ else:
2324
+ contextual_desc = f"{base_cmd} {subcmd_token}"
2325
+ # Add meaningful args (skip very long ones, quotes, code)
2326
+ short_args = [a for a in args_list if len(str(a)) < 40 and a != subcmd_token and not a.startswith('"')]
2327
+ if short_args:
2328
+ contextual_desc += f" ({', '.join(short_args[:3])})"
2329
+
2330
+ # For commands with flags but no subcommand, describe with flags
2331
+ if not contextual_desc and flag_list:
2332
+ flag_summary = ', '.join(flag_list[:3])
2333
+ short_args = [a for a in args_list if len(str(a)) < 40]
2334
+ if short_args:
2335
+ contextual_desc = f"{base_cmd} {flag_summary} on {', '.join(short_args[:2])}"
2336
+ else:
2337
+ contextual_desc = f"{base_cmd} with {flag_summary}"
2338
+
2339
+ # For simple commands with just args
2340
+ if not contextual_desc and args_list:
2341
+ short_args = [a for a in args_list if len(str(a)) < 40]
2342
+ if short_args:
2343
+ contextual_desc = f"{base_cmd} {' '.join(short_args[:3])}"
2344
+
2345
+ # Priority: contextual > session > knowledge base
2346
+ if contextual_desc:
2347
+ description = contextual_desc
2348
+ elif session_desc:
2349
+ description = session_desc
2350
+ else:
2351
+ description = kb_desc if kb_desc else f"Run {base_cmd} command"
2217
2352
 
2218
2353
  # Get subcommand info (for commands like git, docker, npm)
2219
2354
  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
2355
  subcommand_desc = ''
2223
2356
  if subcommands and len(cmd_tokens) > 1:
2224
2357
  for token in cmd_tokens[1:]:
@@ -91,6 +91,8 @@ CATEGORY_MAPPINGS: Dict[str, Set[str]] = {
91
91
  "history", "fc", "true", "false", "test", "[", "[[", "exit",
92
92
  "return", "break", "continue", "shift", "getopts", "trap",
93
93
  "ulimit", "times", "let", ":", "compgen", "complete", "compopt",
94
+ "cmd.exe", "cmd", "start", "where", "type",
95
+ "session-slides", "learn-bash", "bash-learner", "claude",
94
96
  },
95
97
  }
96
98