learn_bash_from_session_data 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 +21 -0
- package/README.md +45 -0
- package/bin/learn-bash.js +328 -0
- package/package.json +23 -0
- package/scripts/__init__.py +34 -0
- package/scripts/analyzer.py +591 -0
- package/scripts/extractor.py +411 -0
- package/scripts/html_generator.py +2029 -0
- package/scripts/knowledge_base.py +1593 -0
- package/scripts/main.py +443 -0
- package/scripts/parser.py +623 -0
- package/scripts/quiz_generator.py +1080 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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,45 @@
|
|
|
1
|
+
# learn_bash_from_session_data
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g learn_bash_from_session_data
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Analyze all sessions from current project
|
|
15
|
+
learn-bash
|
|
16
|
+
|
|
17
|
+
# Analyze last 5 sessions
|
|
18
|
+
learn-bash -n 5
|
|
19
|
+
|
|
20
|
+
# List available projects
|
|
21
|
+
learn-bash --list
|
|
22
|
+
|
|
23
|
+
# Process specific session file
|
|
24
|
+
learn-bash --file ~/.claude/projects/.../session.jsonl
|
|
25
|
+
|
|
26
|
+
# Custom output path
|
|
27
|
+
learn-bash --output ./my-lessons.html
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
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
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Node.js >= 14.0.0
|
|
41
|
+
- Python >= 3.8
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* learn-bash CLI
|
|
5
|
+
* Learn bash from your Claude Code sessions - extracts commands and generates interactive HTML lessons
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* learn-bash [options]
|
|
9
|
+
* learn-bash --list List available Claude Code projects
|
|
10
|
+
* learn-bash --sessions 5 Process last 5 sessions from current project
|
|
11
|
+
* learn-bash --file path/to/session Process a specific session file
|
|
12
|
+
* learn-bash --project "project-name" Process sessions from a specific project
|
|
13
|
+
* learn-bash --output ./my-lesson.html Specify output file location
|
|
14
|
+
* learn-bash --no-open Don't auto-open the generated HTML
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { spawn, execSync } = require('child_process');
|
|
20
|
+
const os = require('os');
|
|
21
|
+
|
|
22
|
+
// ANSI color codes for terminal output
|
|
23
|
+
const colors = {
|
|
24
|
+
reset: '\x1b[0m',
|
|
25
|
+
bright: '\x1b[1m',
|
|
26
|
+
red: '\x1b[31m',
|
|
27
|
+
green: '\x1b[32m',
|
|
28
|
+
yellow: '\x1b[33m',
|
|
29
|
+
blue: '\x1b[34m',
|
|
30
|
+
cyan: '\x1b[36m'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse command line arguments (minimist-style, no dependencies)
|
|
35
|
+
*/
|
|
36
|
+
function parseArgs(args) {
|
|
37
|
+
const result = {
|
|
38
|
+
_: [],
|
|
39
|
+
sessions: null,
|
|
40
|
+
file: null,
|
|
41
|
+
output: null,
|
|
42
|
+
list: false,
|
|
43
|
+
'no-open': false,
|
|
44
|
+
project: null,
|
|
45
|
+
help: false
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < args.length; i++) {
|
|
49
|
+
const arg = args[i];
|
|
50
|
+
const nextArg = args[i + 1];
|
|
51
|
+
|
|
52
|
+
if (arg === '--help' || arg === '-h') {
|
|
53
|
+
result.help = true;
|
|
54
|
+
} else if (arg === '--list' || arg === '-l') {
|
|
55
|
+
result.list = true;
|
|
56
|
+
} else if (arg === '--no-open') {
|
|
57
|
+
result['no-open'] = true;
|
|
58
|
+
} else if (arg === '--sessions' || arg === '-n') {
|
|
59
|
+
result.sessions = parseInt(nextArg, 10);
|
|
60
|
+
i++;
|
|
61
|
+
} else if (arg === '--file' || arg === '-f') {
|
|
62
|
+
result.file = nextArg;
|
|
63
|
+
i++;
|
|
64
|
+
} else if (arg === '--output' || arg === '-o') {
|
|
65
|
+
result.output = nextArg;
|
|
66
|
+
i++;
|
|
67
|
+
} else if (arg === '--project' || arg === '-p') {
|
|
68
|
+
result.project = nextArg;
|
|
69
|
+
i++;
|
|
70
|
+
} else if (!arg.startsWith('-')) {
|
|
71
|
+
result._.push(arg);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Print usage information
|
|
80
|
+
*/
|
|
81
|
+
function printHelp() {
|
|
82
|
+
console.log(`
|
|
83
|
+
${colors.bright}${colors.cyan}learn-bash${colors.reset} - Learn bash from your Claude Code sessions
|
|
84
|
+
|
|
85
|
+
${colors.bright}USAGE:${colors.reset}
|
|
86
|
+
learn-bash [options]
|
|
87
|
+
|
|
88
|
+
${colors.bright}OPTIONS:${colors.reset}
|
|
89
|
+
-n, --sessions <count> Number of recent sessions to process (default: all)
|
|
90
|
+
-f, --file <path> Process a specific session file
|
|
91
|
+
-o, --output <path> Output HTML file path (default: ./bash-lesson.html)
|
|
92
|
+
-p, --project <name> Process sessions from a specific project
|
|
93
|
+
-l, --list List available Claude Code projects
|
|
94
|
+
--no-open Don't auto-open the generated HTML in browser
|
|
95
|
+
-h, --help Show this help message
|
|
96
|
+
|
|
97
|
+
${colors.bright}EXAMPLES:${colors.reset}
|
|
98
|
+
${colors.green}learn-bash${colors.reset} Process all sessions from current project
|
|
99
|
+
${colors.green}learn-bash -n 5${colors.reset} Process last 5 sessions
|
|
100
|
+
${colors.green}learn-bash --list${colors.reset} List available projects
|
|
101
|
+
${colors.green}learn-bash -p "my-project"${colors.reset} Process sessions from specific project
|
|
102
|
+
${colors.green}learn-bash -f ~/.claude/sessions/abc.json${colors.reset}
|
|
103
|
+
${colors.green}learn-bash -o ./lesson.html --no-open${colors.reset}
|
|
104
|
+
|
|
105
|
+
${colors.bright}SESSION LOCATION:${colors.reset}
|
|
106
|
+
Claude Code sessions are stored at: ${colors.yellow}~/.claude/projects/${colors.reset}
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get the Claude projects directory path
|
|
112
|
+
*/
|
|
113
|
+
function getClaudeProjectsDir() {
|
|
114
|
+
const homeDir = os.homedir();
|
|
115
|
+
return path.join(homeDir, '.claude', 'projects');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* List available Claude Code projects
|
|
120
|
+
*/
|
|
121
|
+
function listProjects() {
|
|
122
|
+
const projectsDir = getClaudeProjectsDir();
|
|
123
|
+
|
|
124
|
+
if (!fs.existsSync(projectsDir)) {
|
|
125
|
+
console.log(`${colors.yellow}No Claude Code projects directory found at:${colors.reset}`);
|
|
126
|
+
console.log(` ${projectsDir}`);
|
|
127
|
+
console.log(`\n${colors.cyan}Make sure you have used Claude Code at least once.${colors.reset}`);
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const entries = fs.readdirSync(projectsDir, { withFileTypes: true });
|
|
132
|
+
const projects = entries
|
|
133
|
+
.filter(entry => entry.isDirectory())
|
|
134
|
+
.map(entry => {
|
|
135
|
+
const projectPath = path.join(projectsDir, entry.name);
|
|
136
|
+
const sessionsPath = path.join(projectPath, 'sessions');
|
|
137
|
+
let sessionCount = 0;
|
|
138
|
+
|
|
139
|
+
if (fs.existsSync(sessionsPath)) {
|
|
140
|
+
const sessions = fs.readdirSync(sessionsPath)
|
|
141
|
+
.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
|
|
142
|
+
sessionCount = sessions.length;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
name: entry.name,
|
|
147
|
+
path: projectPath,
|
|
148
|
+
sessionCount
|
|
149
|
+
};
|
|
150
|
+
})
|
|
151
|
+
.filter(p => p.sessionCount > 0);
|
|
152
|
+
|
|
153
|
+
if (projects.length === 0) {
|
|
154
|
+
console.log(`${colors.yellow}No projects with sessions found.${colors.reset}`);
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(`\n${colors.bright}${colors.cyan}Available Claude Code Projects:${colors.reset}\n`);
|
|
159
|
+
projects.forEach((project, index) => {
|
|
160
|
+
console.log(` ${colors.green}${index + 1}.${colors.reset} ${project.name}`);
|
|
161
|
+
console.log(` ${colors.yellow}Sessions:${colors.reset} ${project.sessionCount}`);
|
|
162
|
+
console.log(` ${colors.yellow}Path:${colors.reset} ${project.path}\n`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return projects;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if Python 3 is available
|
|
170
|
+
*/
|
|
171
|
+
function checkPython() {
|
|
172
|
+
const pythonCommands = ['python3', 'python'];
|
|
173
|
+
|
|
174
|
+
for (const cmd of pythonCommands) {
|
|
175
|
+
try {
|
|
176
|
+
const version = execSync(`${cmd} --version 2>&1`, { encoding: 'utf8' });
|
|
177
|
+
if (version.includes('Python 3')) {
|
|
178
|
+
return cmd;
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
// Command not found, try next
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Open a file in the default browser
|
|
190
|
+
*/
|
|
191
|
+
function openInBrowser(filePath) {
|
|
192
|
+
const platform = os.platform();
|
|
193
|
+
let cmd;
|
|
194
|
+
let args;
|
|
195
|
+
|
|
196
|
+
switch (platform) {
|
|
197
|
+
case 'darwin':
|
|
198
|
+
cmd = 'open';
|
|
199
|
+
args = [filePath];
|
|
200
|
+
break;
|
|
201
|
+
case 'win32':
|
|
202
|
+
cmd = 'cmd';
|
|
203
|
+
args = ['/c', 'start', '', filePath];
|
|
204
|
+
break;
|
|
205
|
+
default:
|
|
206
|
+
// Linux and others
|
|
207
|
+
cmd = 'xdg-open';
|
|
208
|
+
args = [filePath];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
|
|
213
|
+
return true;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Find the Python main.py script
|
|
221
|
+
*/
|
|
222
|
+
function findPythonScript() {
|
|
223
|
+
// Check relative to this script's location
|
|
224
|
+
const scriptDir = path.dirname(__filename);
|
|
225
|
+
const possiblePaths = [
|
|
226
|
+
path.join(scriptDir, '..', 'scripts', 'main.py'),
|
|
227
|
+
path.join(scriptDir, 'scripts', 'main.py'),
|
|
228
|
+
path.join(process.cwd(), 'scripts', 'main.py')
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
for (const p of possiblePaths) {
|
|
232
|
+
if (fs.existsSync(p)) {
|
|
233
|
+
return path.resolve(p);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Main entry point
|
|
242
|
+
*/
|
|
243
|
+
function main() {
|
|
244
|
+
const args = parseArgs(process.argv.slice(2));
|
|
245
|
+
|
|
246
|
+
// Show help
|
|
247
|
+
if (args.help) {
|
|
248
|
+
printHelp();
|
|
249
|
+
process.exit(0);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// List projects
|
|
253
|
+
if (args.list) {
|
|
254
|
+
listProjects();
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check Python availability
|
|
259
|
+
const pythonCmd = checkPython();
|
|
260
|
+
if (!pythonCmd) {
|
|
261
|
+
console.error(`${colors.red}${colors.bright}Error:${colors.reset} Python 3 is required but not found.`);
|
|
262
|
+
console.error(`Please install Python 3 from: ${colors.cyan}https://www.python.org/downloads/${colors.reset}`);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Find the Python script
|
|
267
|
+
const pythonScript = findPythonScript();
|
|
268
|
+
if (!pythonScript) {
|
|
269
|
+
console.error(`${colors.red}${colors.bright}Error:${colors.reset} Could not find scripts/main.py`);
|
|
270
|
+
console.error(`Make sure you're running from the package directory or it's properly installed.`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Build Python script arguments
|
|
275
|
+
const pythonArgs = [pythonScript];
|
|
276
|
+
|
|
277
|
+
if (args.sessions) {
|
|
278
|
+
pythonArgs.push('--sessions', args.sessions.toString());
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (args.file) {
|
|
282
|
+
pythonArgs.push('--file', args.file);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (args.output) {
|
|
286
|
+
pythonArgs.push('--output', args.output);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (args.project) {
|
|
290
|
+
pythonArgs.push('--project', args.project);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Default output path if not specified
|
|
294
|
+
const outputPath = args.output || path.join(process.cwd(), 'bash-lesson.html');
|
|
295
|
+
|
|
296
|
+
console.log(`\n${colors.bright}${colors.cyan}learn-bash${colors.reset} - Extracting bash commands from Claude Code sessions...\n`);
|
|
297
|
+
|
|
298
|
+
// Spawn Python process
|
|
299
|
+
const pythonProcess = spawn(pythonCmd, pythonArgs, {
|
|
300
|
+
stdio: 'inherit',
|
|
301
|
+
cwd: process.cwd()
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
pythonProcess.on('close', (code) => {
|
|
305
|
+
if (code === 0) {
|
|
306
|
+
console.log(`\n${colors.green}${colors.bright}Success!${colors.reset} Generated: ${colors.cyan}${outputPath}${colors.reset}`);
|
|
307
|
+
|
|
308
|
+
// Open in browser unless --no-open specified
|
|
309
|
+
if (!args['no-open'] && fs.existsSync(outputPath)) {
|
|
310
|
+
console.log(`${colors.yellow}Opening in browser...${colors.reset}`);
|
|
311
|
+
if (!openInBrowser(outputPath)) {
|
|
312
|
+
console.log(`${colors.yellow}Could not auto-open. Please open manually:${colors.reset} ${outputPath}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
console.error(`\n${colors.red}${colors.bright}Error:${colors.reset} Python script exited with code ${code}`);
|
|
317
|
+
process.exit(code);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
pythonProcess.on('error', (err) => {
|
|
322
|
+
console.error(`${colors.red}${colors.bright}Error:${colors.reset} Failed to start Python process:`, err.message);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Run main
|
|
328
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "learn_bash_from_session_data",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Learn bash from your Claude Code sessions - extracts commands and generates interactive HTML lessons",
|
|
5
|
+
"main": "bin/learn-bash.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"learn-bash": "./bin/learn-bash.js",
|
|
8
|
+
"bash-learner": "./bin/learn-bash.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node tests/test-runner.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["bash", "learning", "claude", "cli", "terminal", "shell"],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=14.0.0"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/bjpl/learn_bash_from_session_data"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scripts package for learn_bash_from_session_data.
|
|
3
|
+
|
|
4
|
+
Provides JSONL extraction and bash command parsing utilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .extractor import (
|
|
8
|
+
ExtractedCommand,
|
|
9
|
+
JSONLExtractor,
|
|
10
|
+
extract_commands_from_jsonl,
|
|
11
|
+
extract_commands_from_sessions,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .parser import (
|
|
15
|
+
ParsedCommand,
|
|
16
|
+
CommandCategory,
|
|
17
|
+
BashParser,
|
|
18
|
+
parse_command,
|
|
19
|
+
parse_commands,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Extractor
|
|
24
|
+
"ExtractedCommand",
|
|
25
|
+
"JSONLExtractor",
|
|
26
|
+
"extract_commands_from_jsonl",
|
|
27
|
+
"extract_commands_from_sessions",
|
|
28
|
+
# Parser
|
|
29
|
+
"ParsedCommand",
|
|
30
|
+
"CommandCategory",
|
|
31
|
+
"BashParser",
|
|
32
|
+
"parse_command",
|
|
33
|
+
"parse_commands",
|
|
34
|
+
]
|