gh-here 2.0.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.
@@ -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
+ 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 it globally:
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 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
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
+ - 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
- | 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 |
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
- | `/` / `s` | Focus search |
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 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`)
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
- - **express** - Web server
136
- - **highlight.js** - Syntax highlighting
137
- - **marked** - Markdown rendering
138
- - **@primer/octicons** - GitHub icons
64
+ - express
65
+ - highlight.js
66
+ - marked
67
+ - @primer/octicons
package/SAMPLE.md ADDED
@@ -0,0 +1,287 @@
1
+ # Syntax Highlighting Test
2
+
3
+ This file contains code samples in various languages to test syntax highlighting in markdown rendering.
4
+
5
+ ## JavaScript
6
+
7
+ ```javascript
8
+ function greet(name) {
9
+ const message = `Hello, ${name}!`;
10
+ console.log(message);
11
+ return message;
12
+ }
13
+
14
+ class Person {
15
+ constructor(name, age) {
16
+ this.name = name;
17
+ this.age = age;
18
+ }
19
+
20
+ introduce() {
21
+ return `My name is ${this.name} and I'm ${this.age} years old.`;
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## Python
27
+
28
+ ```python
29
+ def calculate_fibonacci(n):
30
+ """Calculate the nth Fibonacci number."""
31
+ if n <= 1:
32
+ return n
33
+ return calculate_fibonacci(n - 1) + calculate_fibonacci(n - 2)
34
+
35
+ class Animal:
36
+ def __init__(self, name, species):
37
+ self.name = name
38
+ self.species = species
39
+
40
+ def make_sound(self):
41
+ return f"{self.name} the {self.species} makes a sound"
42
+ ```
43
+
44
+ ## Go
45
+
46
+ ```go
47
+ package main
48
+
49
+ import (
50
+ "fmt"
51
+ "time"
52
+ )
53
+
54
+ type User struct {
55
+ Name string
56
+ Email string
57
+ CreatedAt time.Time
58
+ }
59
+
60
+ func (u *User) SendEmail(message string) error {
61
+ fmt.Printf("Sending email to %s: %s\n", u.Email, message)
62
+ return nil
63
+ }
64
+
65
+ func main() {
66
+ user := &User{
67
+ Name: "John Doe",
68
+ Email: "john@example.com",
69
+ CreatedAt: time.Now(),
70
+ }
71
+ user.SendEmail("Welcome!")
72
+ }
73
+ ```
74
+
75
+ ## Rust
76
+
77
+ ```rust
78
+ use std::collections::HashMap;
79
+
80
+ fn main() {
81
+ let mut scores = HashMap::new();
82
+ scores.insert(String::from("Blue"), 10);
83
+ scores.insert(String::from("Red"), 50);
84
+
85
+ for (key, value) in &scores {
86
+ println!("{}: {}", key, value);
87
+ }
88
+ }
89
+
90
+ struct Rectangle {
91
+ width: u32,
92
+ height: u32,
93
+ }
94
+
95
+ impl Rectangle {
96
+ fn area(&self) -> u32 {
97
+ self.width * self.height
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## TypeScript
103
+
104
+ ```typescript
105
+ interface User {
106
+ id: number;
107
+ name: string;
108
+ email: string;
109
+ }
110
+
111
+ async function fetchUser(id: number): Promise<User> {
112
+ const response = await fetch(`/api/users/${id}`);
113
+ const data: User = await response.json();
114
+ return data;
115
+ }
116
+
117
+ class UserService {
118
+ private users: Map<number, User> = new Map();
119
+
120
+ addUser(user: User): void {
121
+ this.users.set(user.id, user);
122
+ }
123
+
124
+ getUser(id: number): User | undefined {
125
+ return this.users.get(id);
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Bash
131
+
132
+ ```bash
133
+ #!/bin/bash
134
+
135
+ # Function to backup files
136
+ backup_files() {
137
+ local source_dir=$1
138
+ local backup_dir=$2
139
+
140
+ if [ ! -d "$backup_dir" ]; then
141
+ mkdir -p "$backup_dir"
142
+ fi
143
+
144
+ tar -czf "$backup_dir/backup-$(date +%Y%m%d).tar.gz" "$source_dir"
145
+ echo "Backup completed successfully!"
146
+ }
147
+
148
+ backup_files "/home/user/documents" "/home/user/backups"
149
+ ```
150
+
151
+ ## SQL
152
+
153
+ ```sql
154
+ -- Create users table
155
+ CREATE TABLE users (
156
+ id SERIAL PRIMARY KEY,
157
+ username VARCHAR(50) UNIQUE NOT NULL,
158
+ email VARCHAR(100) UNIQUE NOT NULL,
159
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
160
+ );
161
+
162
+ -- Insert some data
163
+ INSERT INTO users (username, email) VALUES
164
+ ('john_doe', 'john@example.com'),
165
+ ('jane_smith', 'jane@example.com');
166
+
167
+ -- Query with JOIN
168
+ SELECT u.username, o.order_date, o.total
169
+ FROM users u
170
+ INNER JOIN orders o ON u.id = o.user_id
171
+ WHERE o.total > 100
172
+ ORDER BY o.order_date DESC;
173
+ ```
174
+
175
+ ## JSON
176
+
177
+ ```json
178
+ {
179
+ "name": "gh-here",
180
+ "version": "2.0.0",
181
+ "description": "A local GitHub-like file browser",
182
+ "dependencies": {
183
+ "express": "^4.18.2",
184
+ "highlight.js": "^11.9.0",
185
+ "marked": "^12.0.0"
186
+ },
187
+ "scripts": {
188
+ "start": "node bin/gh-here.js",
189
+ "test": "jest"
190
+ }
191
+ }
192
+ ```
193
+
194
+ ## CSS
195
+
196
+ ```css
197
+ :root {
198
+ --primary-color: #0366d6;
199
+ --background-color: #ffffff;
200
+ --text-color: #24292e;
201
+ }
202
+
203
+ .container {
204
+ max-width: 1200px;
205
+ margin: 0 auto;
206
+ padding: 20px;
207
+ }
208
+
209
+ .button {
210
+ display: inline-block;
211
+ padding: 10px 20px;
212
+ background-color: var(--primary-color);
213
+ color: white;
214
+ border-radius: 5px;
215
+ transition: background-color 0.3s ease;
216
+ }
217
+
218
+ .button:hover {
219
+ background-color: #0256c7;
220
+ cursor: pointer;
221
+ }
222
+ ```
223
+
224
+ ## HTML
225
+
226
+ ```html
227
+ <!DOCTYPE html>
228
+ <html lang="en">
229
+ <head>
230
+ <meta charset="UTF-8">
231
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
232
+ <title>Sample Page</title>
233
+ <link rel="stylesheet" href="styles.css">
234
+ </head>
235
+ <body>
236
+ <header>
237
+ <h1>Welcome to My Site</h1>
238
+ <nav>
239
+ <ul>
240
+ <li><a href="#home">Home</a></li>
241
+ <li><a href="#about">About</a></li>
242
+ <li><a href="#contact">Contact</a></li>
243
+ </ul>
244
+ </nav>
245
+ </header>
246
+ <main>
247
+ <p>This is the main content area.</p>
248
+ </main>
249
+ <script src="script.js"></script>
250
+ </body>
251
+ </html>
252
+ ```
253
+
254
+ ## Java
255
+
256
+ ```java
257
+ import java.util.ArrayList;
258
+ import java.util.List;
259
+
260
+ public class BankAccount {
261
+ private String accountNumber;
262
+ private double balance;
263
+ private List<Transaction> transactions;
264
+
265
+ public BankAccount(String accountNumber, double initialBalance) {
266
+ this.accountNumber = accountNumber;
267
+ this.balance = initialBalance;
268
+ this.transactions = new ArrayList<>();
269
+ }
270
+
271
+ public void deposit(double amount) {
272
+ if (amount > 0) {
273
+ balance += amount;
274
+ transactions.add(new Transaction("DEPOSIT", amount));
275
+ }
276
+ }
277
+
278
+ public boolean withdraw(double amount) {
279
+ if (amount > 0 && balance >= amount) {
280
+ balance -= amount;
281
+ transactions.add(new Transaction("WITHDRAWAL", amount));
282
+ return true;
283
+ }
284
+ return false;
285
+ }
286
+ }
287
+ ```
@@ -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
+ };