gh-here 2.1.0 → 3.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/.claude/settings.local.json +20 -11
- package/README.md +30 -101
- package/lib/constants.js +38 -0
- package/lib/error-handler.js +55 -0
- package/lib/file-tree-builder.js +81 -0
- package/lib/file-utils.js +43 -12
- package/lib/renderers.js +423 -194
- package/lib/server.js +120 -32
- package/lib/validation.js +77 -0
- package/package.json +1 -1
- package/public/app.js +199 -1825
- package/public/app.js.backup +1902 -0
- package/public/js/clipboard-utils.js +45 -0
- package/public/js/constants.js +60 -0
- package/public/js/draft-manager.js +36 -0
- package/public/js/editor-manager.js +159 -0
- package/public/js/file-tree.js +321 -0
- package/public/js/keyboard-handler.js +41 -0
- package/public/js/modal-manager.js +70 -0
- package/public/js/navigation.js +254 -0
- package/public/js/notification.js +23 -0
- package/public/js/search-handler.js +238 -0
- package/public/js/theme-manager.js +108 -0
- package/public/js/utils.js +123 -0
- package/public/styles.css +874 -570
- package/.channels_cache_v2.json +0 -10882
- package/.users_cache.json +0 -16187
- package/blog-post.md +0 -100
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(
|
|
5
|
-
"Bash(
|
|
6
|
-
"Bash(
|
|
7
|
-
"Bash(
|
|
8
|
-
"Bash(
|
|
9
|
-
"Bash(
|
|
10
|
-
"Bash(
|
|
11
|
-
"Bash(
|
|
12
|
-
"Bash(git checkout:*)",
|
|
4
|
+
"Bash(chmod:*)",
|
|
5
|
+
"Bash(npm install)",
|
|
6
|
+
"Bash(npm start)",
|
|
7
|
+
"Bash(pkill:*)",
|
|
8
|
+
"Bash(node:*)",
|
|
9
|
+
"Bash(curl:*)",
|
|
10
|
+
"Bash(npm install:*)",
|
|
11
|
+
"Bash(git init:*)",
|
|
13
12
|
"Bash(git add:*)",
|
|
14
13
|
"Bash(git commit:*)",
|
|
14
|
+
"Bash(git rm:*)",
|
|
15
|
+
"Bash(ssh:*)",
|
|
16
|
+
"Bash(git restore:*)",
|
|
17
|
+
"Bash(npx gh-here:*)",
|
|
18
|
+
"Bash(git checkout:*)",
|
|
15
19
|
"Bash(git push:*)",
|
|
16
|
-
"Bash(
|
|
20
|
+
"Bash(git pull:*)",
|
|
21
|
+
"Bash(npm publish:*)",
|
|
22
|
+
"Bash(timeout:*)",
|
|
23
|
+
"Bash(npm version:*)",
|
|
24
|
+
"Bash(gh release create:*)",
|
|
25
|
+
"Bash(tree:*)"
|
|
17
26
|
],
|
|
18
27
|
"deny": [],
|
|
19
28
|
"ask": []
|
|
20
29
|
}
|
|
21
|
-
}
|
|
30
|
+
}
|
package/README.md
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
# gh-here
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Why?
|
|
6
|
-
|
|
7
|
-
TUIs (Terminal User Interfaces) like Claude Code, Google Gemini CLI, and Cursor have become very popular tools for working on codebases, but they don't provide a visual view into the directories and files themselves. gh-here exists to fill that gap, so you can easily explore your project files in a familiar GitHub-esque browser GUI.
|
|
8
|
-
|
|
9
|
-
<!-- Test change for commit interface -->
|
|
3
|
+
Local GitHub-style file browser for viewing codebases. Browse directories, view files with syntax highlighting, and explore git diffs.
|
|
10
4
|
|
|
11
5
|
## Installation
|
|
12
6
|
|
|
13
|
-
Run gh-here directly with npx (no installation required):
|
|
14
|
-
|
|
15
7
|
```bash
|
|
16
8
|
npx gh-here
|
|
17
9
|
```
|
|
18
10
|
|
|
19
|
-
Or install
|
|
11
|
+
Or install globally:
|
|
20
12
|
|
|
21
13
|
```bash
|
|
22
14
|
npm install -g gh-here
|
|
@@ -24,102 +16,41 @@ npm install -g gh-here
|
|
|
24
16
|
|
|
25
17
|
## Usage
|
|
26
18
|
|
|
27
|
-
Navigate to any directory and run:
|
|
28
|
-
|
|
29
19
|
```bash
|
|
30
20
|
gh-here # Start server on available port
|
|
31
|
-
gh-here --open # Start
|
|
32
|
-
gh-here --port=8080 #
|
|
33
|
-
gh-here --open --browser=safari #
|
|
34
|
-
gh-here --open --browser=arc # Start server and open in Arc
|
|
21
|
+
gh-here --open # Start and open browser
|
|
22
|
+
gh-here --port=8080 # Use specific port
|
|
23
|
+
gh-here --open --browser=safari # Open in Safari
|
|
35
24
|
```
|
|
36
25
|
|
|
37
|
-
The app will automatically find an available port starting from 5555 and serve your current directory with a GitHub-like interface.
|
|
38
|
-
|
|
39
26
|
## Features
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
- README preview with
|
|
44
|
-
- Language statistics
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- Draft management with persistence across sessions
|
|
54
|
-
- Raw and rendered markdown views
|
|
55
|
-
- Shareable URLs with line selections (`#L10-L20`)
|
|
56
|
-
- Monaco Editor features: IntelliSense, bracket matching, folding
|
|
57
|
-
|
|
58
|
-
### 🔀 Git Integration
|
|
59
|
-
- Automatic git repository detection
|
|
60
|
-
- Clean git status indicators with colored dots in dedicated column
|
|
61
|
-
- Smart status detection for files within untracked directories
|
|
62
|
-
- Professional inline diff viewer with syntax highlighting
|
|
63
|
-
- View/Diff/Edit mode toggle for files with changes
|
|
64
|
-
- Beautiful raw git diff display with color coding
|
|
65
|
-
- Git branch display in navigation header
|
|
66
|
-
- Visual status indicators: modified (dot), untracked (purple dot), added, deleted
|
|
67
|
-
|
|
68
|
-
### ⌨️ Keyboard Navigation
|
|
69
|
-
- `j`/`k` or arrow keys to navigate files
|
|
70
|
-
- `Enter` or `o` to open files/folders
|
|
71
|
-
- `e` to edit focused file
|
|
72
|
-
- `c` to create new file
|
|
73
|
-
- `/` or `s` to focus search
|
|
74
|
-
- `h` to go up directory
|
|
75
|
-
- `t` to toggle theme
|
|
76
|
-
- `i` to toggle .gitignore filter
|
|
77
|
-
- `d` to show diff for focused file (if git changes)
|
|
78
|
-
- `r` to refresh
|
|
79
|
-
- `?` to show keyboard shortcuts
|
|
80
|
-
- `Ctrl/Cmd + S` to save file (in editor)
|
|
81
|
-
- `Esc` to close editor/dialogs
|
|
82
|
-
|
|
83
|
-
### 🌙 Themes & UI
|
|
84
|
-
- GitHub dark and light themes
|
|
85
|
-
- Smart header path (shows "gh-here" at root, path when browsing)
|
|
86
|
-
- Search functionality with keyboard shortcuts
|
|
87
|
-
- Breadcrumb navigation
|
|
88
|
-
- Error handling and loading states
|
|
89
|
-
- Notification system for user feedback
|
|
90
|
-
|
|
91
|
-
## Language Support
|
|
92
|
-
|
|
93
|
-
Supports syntax highlighting for 30+ languages including JavaScript, TypeScript, Python, Go, Rust, Java, C/C++, and many more through Monaco Editor integration.
|
|
28
|
+
- Directory browsing with file icons
|
|
29
|
+
- Syntax highlighting for 30+ languages
|
|
30
|
+
- README preview with markdown rendering
|
|
31
|
+
- Language statistics
|
|
32
|
+
- Git status indicators
|
|
33
|
+
- Diff viewer with line numbers
|
|
34
|
+
- Search files
|
|
35
|
+
- Copy/download files
|
|
36
|
+
- Rename/delete files and folders
|
|
37
|
+
- .gitignore filtering
|
|
38
|
+
- Dark/light themes
|
|
39
|
+
- Keyboard navigation
|
|
94
40
|
|
|
95
41
|
## Keyboard Shortcuts
|
|
96
42
|
|
|
97
|
-
|
|
|
98
|
-
|
|
99
|
-
| `j`
|
|
100
|
-
| `
|
|
101
|
-
| `Enter` / `o` | Open file/folder |
|
|
102
|
-
| `e` | Edit focused file |
|
|
103
|
-
| `c` | Create new file |
|
|
43
|
+
| Key | Action |
|
|
44
|
+
|-----|--------|
|
|
45
|
+
| `j`/`k` or arrows | Navigate files |
|
|
46
|
+
| `Enter` or `o` | Open file/folder |
|
|
104
47
|
| `h` | Go up directory |
|
|
105
|
-
| `/`
|
|
106
|
-
| `Ctrl/Cmd + K` | Focus search |
|
|
107
|
-
| `Ctrl/Cmd + G` | Go to top |
|
|
108
|
-
| `Shift + G` | Go to bottom |
|
|
48
|
+
| `/` or `s` | Focus search |
|
|
109
49
|
| `t` | Toggle theme |
|
|
110
|
-
| `i` | Toggle .gitignore
|
|
111
|
-
| `d` | Show diff
|
|
112
|
-
| `r` | Refresh
|
|
113
|
-
| `?` | Show
|
|
114
|
-
| `Ctrl/Cmd + S` | Save file (in editor) |
|
|
115
|
-
| `Esc` | Close editor/dialogs |
|
|
116
|
-
|
|
117
|
-
## Line Selection (Code Files)
|
|
118
|
-
|
|
119
|
-
- **Click line number**: Select single line
|
|
120
|
-
- **Shift + Click**: Select range
|
|
121
|
-
- **Ctrl/Cmd + Click**: Multi-select lines
|
|
122
|
-
- **URLs**: Automatically updated (`#L5`, `#L10-L20`, `#L1,L5-L10`)
|
|
50
|
+
| `i` | Toggle .gitignore |
|
|
51
|
+
| `d` | Show diff (if git changes) |
|
|
52
|
+
| `r` | Refresh |
|
|
53
|
+
| `?` | Show shortcuts |
|
|
123
54
|
|
|
124
55
|
## Development
|
|
125
56
|
|
|
@@ -128,11 +59,9 @@ npm install
|
|
|
128
59
|
npm start
|
|
129
60
|
```
|
|
130
61
|
|
|
131
|
-
Navigate to `http://localhost:5555` to view the interface.
|
|
132
|
-
|
|
133
62
|
## Dependencies
|
|
134
63
|
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
64
|
+
- express
|
|
65
|
+
- highlight.js
|
|
66
|
+
- marked
|
|
67
|
+
- @primer/octicons
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend constants and configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
HTTP_STATUS: {
|
|
7
|
+
OK: 200,
|
|
8
|
+
BAD_REQUEST: 400,
|
|
9
|
+
FORBIDDEN: 403,
|
|
10
|
+
NOT_FOUND: 404,
|
|
11
|
+
CONFLICT: 409,
|
|
12
|
+
INTERNAL_ERROR: 500
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
ERROR_MESSAGES: {
|
|
16
|
+
NOT_GIT_REPO: 'Not a git repository',
|
|
17
|
+
COMMIT_MESSAGE_REQUIRED: 'Commit message is required',
|
|
18
|
+
NO_FILES_SELECTED: 'No files selected',
|
|
19
|
+
FILE_PATH_REQUIRED: 'File path is required',
|
|
20
|
+
ACCESS_DENIED: 'Access denied',
|
|
21
|
+
FILE_NOT_FOUND: 'File not found',
|
|
22
|
+
ITEM_NOT_FOUND: 'Item not found',
|
|
23
|
+
ITEM_ALREADY_EXISTS: 'Item already exists',
|
|
24
|
+
CANNOT_EDIT_BINARY: 'Cannot edit binary files',
|
|
25
|
+
CANNOT_DOWNLOAD_DIRECTORIES: 'Cannot download directories'
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
GIT_STATUS_MAP: {
|
|
29
|
+
'A': 'added',
|
|
30
|
+
'M': 'modified',
|
|
31
|
+
'D': 'deleted',
|
|
32
|
+
'R': 'renamed',
|
|
33
|
+
'??': 'untracked',
|
|
34
|
+
'MM': 'mixed',
|
|
35
|
+
'AM': 'mixed',
|
|
36
|
+
'AD': 'mixed'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized error handling
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { HTTP_STATUS, ERROR_MESSAGES } = require('./constants');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Formats error response
|
|
9
|
+
*/
|
|
10
|
+
function formatErrorResponse(error, statusCode = HTTP_STATUS.INTERNAL_ERROR) {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
error: error.message || error,
|
|
14
|
+
statusCode
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sends error response
|
|
20
|
+
*/
|
|
21
|
+
function sendError(res, message, statusCode = HTTP_STATUS.INTERNAL_ERROR) {
|
|
22
|
+
res.status(statusCode).json({
|
|
23
|
+
success: false,
|
|
24
|
+
error: message
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Handles async route errors
|
|
30
|
+
*/
|
|
31
|
+
function asyncHandler(fn) {
|
|
32
|
+
return (req, res, next) => {
|
|
33
|
+
Promise.resolve(fn(req, res, next)).catch(error => {
|
|
34
|
+
console.error('Route error:', error);
|
|
35
|
+
sendError(res, 'Internal server error', HTTP_STATUS.INTERNAL_ERROR);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Logs error with context
|
|
42
|
+
*/
|
|
43
|
+
function logError(context, error) {
|
|
44
|
+
console.error(`[${context}] Error:`, error.message || error);
|
|
45
|
+
if (error.stack) {
|
|
46
|
+
console.error(error.stack);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
formatErrorResponse,
|
|
52
|
+
sendError,
|
|
53
|
+
asyncHandler,
|
|
54
|
+
logError
|
|
55
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds file tree structure for navigation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { isIgnoredByGitignore } = require('./gitignore');
|
|
8
|
+
|
|
9
|
+
function buildFileTree(dirPath, relativePath = '', gitignoreRules, workingDir, showGitignored = false, maxDepth = 5, currentDepth = 0) {
|
|
10
|
+
if (currentDepth >= maxDepth) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const items = fs.readdirSync(dirPath);
|
|
16
|
+
const tree = [];
|
|
17
|
+
|
|
18
|
+
for (const item of items) {
|
|
19
|
+
const fullPath = path.join(dirPath, item);
|
|
20
|
+
const itemRelativePath = relativePath ? `${relativePath}/${item}` : item;
|
|
21
|
+
|
|
22
|
+
// Always skip .git directory and node_modules
|
|
23
|
+
if (item === '.git' || item === 'node_modules') {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Skip other hidden files/folders unless showing gitignored
|
|
28
|
+
if (!showGitignored && item.startsWith('.')) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Skip gitignored items unless explicitly showing them
|
|
33
|
+
if (!showGitignored && isIgnoredByGitignore(fullPath, gitignoreRules, workingDir, false)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stats = fs.statSync(fullPath);
|
|
38
|
+
const isDirectory = stats.isDirectory();
|
|
39
|
+
|
|
40
|
+
const treeItem = {
|
|
41
|
+
name: item,
|
|
42
|
+
path: itemRelativePath,
|
|
43
|
+
isDirectory
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (isDirectory) {
|
|
47
|
+
treeItem.children = buildFileTree(
|
|
48
|
+
fullPath,
|
|
49
|
+
itemRelativePath,
|
|
50
|
+
gitignoreRules,
|
|
51
|
+
workingDir,
|
|
52
|
+
showGitignored,
|
|
53
|
+
maxDepth,
|
|
54
|
+
currentDepth + 1
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tree.push(treeItem);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Sort: directories first, then alphabetically
|
|
62
|
+
tree.sort((a, b) => {
|
|
63
|
+
if (a.isDirectory && !b.isDirectory) {
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
if (!a.isDirectory && b.isDirectory) {
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
return a.name.localeCompare(b.name);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return tree;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Error building file tree:', error);
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
buildFileTree
|
|
81
|
+
};
|
package/lib/file-utils.js
CHANGED
|
@@ -187,46 +187,77 @@ function getFileIcon(filename) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function getLanguageFromExtension(ext) {
|
|
190
|
+
if (!ext) return undefined;
|
|
191
|
+
const normalized = String(ext).toLowerCase();
|
|
190
192
|
const langMap = {
|
|
193
|
+
// JavaScript family
|
|
191
194
|
'js': 'javascript',
|
|
195
|
+
'mjs': 'javascript',
|
|
192
196
|
'jsx': 'javascript',
|
|
193
197
|
'ts': 'typescript',
|
|
194
198
|
'tsx': 'typescript',
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
|
|
200
|
+
// Web
|
|
197
201
|
'html': 'html',
|
|
202
|
+
'htm': 'html',
|
|
198
203
|
'css': 'css',
|
|
199
204
|
'scss': 'scss',
|
|
200
205
|
'sass': 'sass',
|
|
206
|
+
'less': 'less',
|
|
207
|
+
|
|
208
|
+
// Data / config
|
|
201
209
|
'json': 'json',
|
|
202
210
|
'xml': 'xml',
|
|
203
211
|
'yaml': 'yaml',
|
|
204
212
|
'yml': 'yaml',
|
|
213
|
+
|
|
214
|
+
// Shell & scripts
|
|
205
215
|
'sh': 'bash',
|
|
206
216
|
'bash': 'bash',
|
|
207
217
|
'zsh': 'bash',
|
|
208
|
-
'
|
|
209
|
-
'
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'cc': 'cpp',
|
|
218
|
+
'fish': 'bash',
|
|
219
|
+
'ps1': 'powershell',
|
|
220
|
+
|
|
221
|
+
// Compiled / systems
|
|
213
222
|
'c': 'c',
|
|
214
223
|
'h': 'c',
|
|
224
|
+
'cpp': 'cpp',
|
|
225
|
+
'cc': 'cpp',
|
|
226
|
+
'cxx': 'cpp',
|
|
215
227
|
'hpp': 'cpp',
|
|
228
|
+
'rs': 'rust',
|
|
229
|
+
'go': 'go',
|
|
230
|
+
'java': 'java',
|
|
231
|
+
'kt': 'kotlin',
|
|
232
|
+
'swift': 'swift',
|
|
233
|
+
|
|
234
|
+
// Scripting
|
|
235
|
+
'py': 'python',
|
|
216
236
|
'php': 'php',
|
|
217
237
|
'rb': 'ruby',
|
|
218
|
-
'swift': 'swift',
|
|
219
|
-
'kt': 'kotlin',
|
|
220
238
|
'dart': 'dart',
|
|
221
239
|
'r': 'r',
|
|
222
240
|
'sql': 'sql',
|
|
223
|
-
'
|
|
241
|
+
'scala': 'scala',
|
|
242
|
+
'clj': 'clojure',
|
|
243
|
+
'lua': 'lua',
|
|
244
|
+
'pl': 'perl',
|
|
245
|
+
'groovy': 'groovy',
|
|
246
|
+
|
|
247
|
+
// Markup / frameworks
|
|
224
248
|
'md': 'markdown',
|
|
225
249
|
'markdown': 'markdown',
|
|
226
250
|
'vue': 'vue',
|
|
227
|
-
'svelte': 'svelte'
|
|
251
|
+
'svelte': 'svelte',
|
|
252
|
+
|
|
253
|
+
// Misc text
|
|
254
|
+
'txt': 'plaintext',
|
|
255
|
+
'log': 'plaintext',
|
|
256
|
+
|
|
257
|
+
// Special filename-style extensions
|
|
258
|
+
'dockerfile': 'dockerfile'
|
|
228
259
|
};
|
|
229
|
-
return langMap[
|
|
260
|
+
return langMap[normalized];
|
|
230
261
|
}
|
|
231
262
|
|
|
232
263
|
function getLanguageColor(language) {
|