gh-here 2.1.0 → 3.0.1

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.
@@ -1,21 +1,30 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(if [ -f scripts/lint ])",
5
- "Bash(then scripts/lint)",
6
- "Bash(elif [ -f script/lint ])",
7
- "Bash(then script/lint)",
8
- "Bash(elif [ -f package.json ])",
9
- "Bash(then npm run lint)",
10
- "Bash(else echo \"No linting script found\")",
11
- "Bash(fi)",
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(gh pr view:*)"
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
- A local GitHub-like file browser for viewing and exploring codebases in your browser. Launch it in any folder to get a beautiful web-based directory browser with syntax highlighting and powerful navigation features.
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
+ A fast, local GitHub-style file browser for exploring codebases. Browse directories with a file tree, view files with syntax highlighting, and explore git diffs - all in your browser.
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 it globally:
11
+ Or install globally:
20
12
 
21
13
  ```bash
22
14
  npm install -g gh-here
@@ -24,102 +16,44 @@ 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 server and open browser
32
- gh-here --port=8080 # Start server on port 8080
33
- gh-here --open --browser=safari # Start server and open in Safari
34
- gh-here --open --browser=arc # Start server and open in Arc
21
+ gh-here --open # Start and open browser (default)
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
- ### 📁 File Browsing
42
- - Beautiful directory browsing with specific file type icons
43
- - README preview with beautiful markdown rendering
44
- - Language statistics for project overview
45
- - Quick actions (copy path, download files, edit, rename, delete)
46
- - .gitignore support with toggle functionality
47
- - File and folder creation, editing, renaming, and deletion
48
-
49
- ### 🎨 Code Viewing & Editing
50
- - VS Code-quality Monaco Editor with advanced syntax highlighting for 30+ languages
51
- - GitHub-style line numbers with selection (click, shift-click, ctrl-click)
52
- - Professional in-browser file editing with auto-save to localStorage
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
+ ### Core
29
+ - **File Tree Sidebar** - Navigate your repository structure with an interactive file tree
30
+ - **Context-Aware Search** - Global repository search or filter the file tree
31
+ - **Client-Side Navigation** - Fast page transitions without full reloads
32
+ - **Syntax Highlighting** - Support for 30+ languages via highlight.js
33
+ - **Git Integration** - Status indicators and diff viewer with line numbers
34
+
35
+ ### UI/UX
36
+ - **Gitignore Toggle** - Show/hide gitignored files (persists in localStorage)
37
+ - **Dark/Light Themes** - Toggle between themes
38
+ - **README Preview** - Automatic markdown rendering
39
+ - **Language Statistics** - See breakdown of languages in your repo
40
+ - **File Operations** - Copy file paths, download files, view raw content
94
41
 
95
42
  ## Keyboard Shortcuts
96
43
 
97
- | Shortcut | Action |
98
- |----------|--------|
99
- | `j` / `↓` | Move down |
100
- | `k` / `↑` | Move up |
101
- | `Enter` / `o` | Open file/folder |
102
- | `e` | Edit focused file |
103
- | `c` | Create new file |
104
- | `h` | Go up directory |
105
- | `/` / `s` | Focus search |
106
- | `Ctrl/Cmd + K` | Focus search |
107
- | `Ctrl/Cmd + G` | Go to top |
108
- | `Shift + G` | Go to bottom |
109
- | `t` | Toggle theme |
110
- | `i` | Toggle .gitignore filter |
111
- | `d` | Show diff for focused file |
112
- | `r` | Refresh page |
113
- | `?` | Show keyboard shortcuts |
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`)
44
+ | Key | Action |
45
+ |-----|--------|
46
+ | `Cmd/Ctrl + K` | Focus search |
47
+ | `Escape` | Close search/modals |
48
+
49
+ ## How It Works
50
+
51
+ gh-here runs a local Express server that serves a read-only view of your codebase. It:
52
+
53
+ 1. Respects your `.gitignore` (optional toggle to show ignored files)
54
+ 2. Shows git status and diffs for modified files
55
+ 3. Provides a familiar GitHub-like interface
56
+ 4. Works entirely offline - no data leaves your machine
123
57
 
124
58
  ## Development
125
59
 
@@ -128,11 +62,13 @@ npm install
128
62
  npm start
129
63
  ```
130
64
 
131
- Navigate to `http://localhost:5555` to view the interface.
132
-
133
65
  ## Dependencies
134
66
 
135
- - **express** - Web server
136
- - **highlight.js** - Syntax highlighting
137
- - **marked** - Markdown rendering
138
- - **@primer/octicons** - GitHub icons
67
+ - express - Web server
68
+ - highlight.js - Syntax highlighting
69
+ - marked - Markdown rendering
70
+ - @primer/octicons - GitHub icons
71
+
72
+ ## License
73
+
74
+ MIT
@@ -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
- 'py': 'python',
196
- 'java': 'java',
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
- 'go': 'go',
209
- 'rs': 'rust',
210
- 'cpp': 'cpp',
211
- 'cxx': 'cpp',
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
- 'dockerfile': 'dockerfile',
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[ext];
260
+ return langMap[normalized];
230
261
  }
231
262
 
232
263
  function getLanguageColor(language) {