swadhinbiswas

[email protected]
2026-01-22 16:42:42 +0600 committed NaN years ago
3ac03a2
commit 3ac03a2
63 files changed
+5228 -1364
Showing 63 changed files+5228-1364
README.mdcli/README.mdcli/bin/och.tscli/src/commands/config/index.tsdocs-site/astro.config.mjsdocs-site/public/favicon.pngdocs-site/src/content/docs/administration/deploy-cloudflare.mddocs-site/src/content/docs/administration/deploy-cpanel.mddocs-site/src/content/docs/administration/deploy-cyberpanel.mddocs-site/src/content/docs/administration/deploy-docker.mddocs-site/src/content/docs/administration/deploy-nginx.mddocs-site/src/content/docs/administration/deploy-podman.mddocs-site/src/content/docs/development/database-migrations.mddocs-site/src/content/docs/development/local-dev-setup.mddocs-site/src/content/docs/development/observability.mddocs-site/src/content/docs/development/security-hardening.mddocs-site/src/content/docs/development/testing-pipelines.mddocs-site/src/content/docs/features/ai-review.mddocs-site/src/content/docs/features/automations.mddocs-site/src/content/docs/features/ci-actions.mddocs-site/src/content/docs/features/cli.mddocs-site/src/content/docs/features/developer-metrics.mddocs-site/src/content/docs/features/inbox.mddocs-site/src/content/docs/features/merge-queue.mddocs-site/src/content/docs/features/notifications.mddocs-site/src/content/docs/guides/storage-adapters.mddocs-site/src/content/docs/index.mdxdocs-site/src/content/docs/reference/cli-auth-config.mddocs-site/src/content/docs/reference/cli-automation-insights.mddocs-site/src/content/docs/reference/cli-commands.mddocs-site/src/content/docs/reference/cli-core-commands.mddocs-site/src/content/docs/reference/cli-merge-queue.mddocs-site/src/content/docs/reference/cli-overview.mddocs-site/src/content/docs/reference/cli-stack-workflows.mddocs-site/src/content/docs/tutorials/ai-powered-reviews.mddocs-site/src/content/docs/tutorials/automated-releases.mddocs-site/src/content/docs/tutorials/disaster-recovery.mddocs-site/src/content/docs/tutorials/self-hosted-docker-nginx.mddocs-site/src/content/docs/tutorials/team-onboarding.mddocs-site/src/content/docs/tutorials/your-first-stack.mddocs-site/src/content/docs/tutorials/zero-to-production.mddocs/features/ai-review.mddocs/features/merge-queue.mddocs/guides/storage-adapters.mddocs/index.mddocs/reference/cli-commands.mddocs/tutorials/ai-powered-reviews.mddocs/tutorials/your-first-stack.mdpublic/docs/index.htmlpublic/favicon.pngpublic/logo-dark.pngpublic/logo-light.pngpublic/logo.pngpublic/stack-workflow.svgsrc/components/layout/Header.astrosrc/components/repo/FileExplorer.astrosrc/components/repo/RepoActions.tsxsrc/components/repo/RepoControls.tsxsrc/components/repo/RepoHeader.astrosrc/components/ui/popover.tsxsrc/components/ui/table.tsxsrc/lib/git.tssrc/pages/[owner]/[repo]/index.astro
▼README.md
+92-20
1
1
# OpenCodeHub
2
2
3
3
<p align="center">
4
- <img src="public/logo.svg" alt="OpenCodeHub Logo" width="300">
4
+ <picture>
5
+ <source media="(prefers-color-scheme: dark)" srcset="public/logo-dark.png">
6
+ <source media="(prefers-color-scheme: light)" srcset="public/logo-light.png">
7
+ <img src="public/logo-light.png" alt="OpenCodeHub Logo" width="400"/>
8
+ </picture>
5
9
<br>
6
10
</p>
7
11
32
36
33
37
**All critical security vulnerabilities are now fixed. OpenCodeHub is production-ready!** 🎊
34
38
35
-✅ **Rate Limiting** - Prevents brute force attacks
36
-✅ **CSRF Protection** - Cross-site request forgery defense
37
-✅ **Input Validation** - SQL injection & XSS prevention
38
-✅ **Hook Authentication** - Secure git operations
39
+✅ **Rate Limiting** - Prevents brute force attacks
40
+✅ **CSRF Protection** - Cross-site request forgery defense
41
+✅ **Input Validation** - SQL injection & XSS prevention
42
+✅ **Hook Authentication** - Secure git operations
39
43
✅ **Environment Validation** - Safe configuration management
40
44
41
45
📚 **See [DEPLOYMENT.md](DEPLOYMENT.md) for complete deployment guide** with SSL setup, Nginx configuration, monitoring, database backups, and production best practices.
195
199
### CLI Commands Reference
196
200
197
201
**Authentication:**
202
+
198
203
```bash
199
204
och auth login # Interactive login
200
205
och auth login --token TOKEN # Login with token
203
208
```
204
209
205
210
**Stack Management:**
211
+
206
212
```bash
207
213
och stack create \u003cname\u003e # Create new stacked branch
208
214
och stack view # View current stack (alias: ls)
213
219
```
214
220
215
221
**Repository:**
222
+
216
223
```bash
217
224
och init # Initialize repo for OpenCodeHub
218
225
och sync # Bidirectional sync with remote
236
243
237
244
#### How It Works
238
245
239
-![Stacked PR Workflow](public/stack-workflow.svg)
246
+![Stacked PR Workflow](/stack-workflow.svg)
240
247
<br>
241
248
242
249
Each PR builds on the previous one. When #123 merges, #124 automatically rebases.
244
251
#### Creating a Stack
245
252
246
253
**Using CLI:**
254
+
247
255
```bash
248
256
# 1. Create base branch
249
257
git checkout -b feature/auth
263
271
```
264
272
265
273
**Using Web UI:**
274
+
266
275
1. Create first PR normally
267
276
2. When creating second PR, select "Stack on PR #123"
268
277
3. System tracks dependencies automatically
269
278
270
279
#### Stack Features
271
280
272
-✅ **Auto-Rebasing**: When base PR merges, dependent PRs rebase automatically
273
-✅ **Dependency Visualization**: See entire stack in PR view
274
-✅ **Smart Merge Queue**: Merges in correct order
281
+✅ **Auto-Rebasing**: When base PR merges, dependent PRs rebase automatically
282
+✅ **Dependency Visualization**: See entire stack in PR view
283
+✅ **Smart Merge Queue**: Merges in correct order
275
284
✅ **Conflict Detection**: Alerts on rebase conflicts
276
285
277
286
---
283
292
#### Setup
284
293
285
294
**1. Configure AI Provider:**
295
+
286
296
```env
287
297
# In .env
288
298
AI_PROVIDER=openai # or anthropic
293
303
294
304
**2. Enable for Repository:**
295
305
Navigate to **Repository → Settings → AI Review**
306
+
296
307
- ✅ Enable AI Review
297
308
- Set review triggers (on PR open, on push, manual)
298
309
- Configure review rules
300
311
#### What AI Reviews Catch
301
312
302
313
🔍 **Security Vulnerabilities**
314
+
303
315
- SQL injection risks
304
316
- XSS vulnerabilities
305
317
- Authentication bypasses
306
318
- Secrets in code
307
319
308
320
⚡ **Performance Issues**
321
+
309
322
- N+1 queries
310
323
- Inefficient algorithms
311
324
- Memory leaks
312
325
- Blocking operations
313
326
314
327
📝 **Code Quality**
328
+
315
329
- Best practice violations
316
330
- Code smells
317
331
- Incomplete error handling
318
332
- Missing documentation
319
333
320
334
🐛 **Potential Bugs**
335
+
321
336
- Null pointer exceptions
322
337
- Race conditions
323
338
- Off-by-one errors
329
344
AI review runs automatically on every PR based on configured triggers.
330
345
331
346
**Manual:**
347
+
332
348
```bash
333
349
# Via CLI
334
350
och review ai \u003cPR_NUMBER\u003e
339
355
#### Review Output
340
356
341
357
AI provides:
358
+
342
359
- **Severity Score**: Critical / High / Medium / Low / Info
343
360
- **Specific Line Comments**: Inline code suggestions
344
361
- **Summary Report**: Overall code quality assessment
345
362
- **Fix Suggestions**: Actionable improvements
346
363
347
364
**Example:**
365
+
348
366
```text
349
367
🔴 CRITICAL: SQL Injection Vulnerability
350
368
File: src/api/users.ts:45
351
369
Issue: User input directly concatenated into SQL query
352
370
Fix: Use parameterized queries or ORM
353
371
354
-🟡 MEDIUM: Performance Concern
372
+🟡 MEDIUM: Performance Concern
355
373
File: src/lib/data.ts:128
356
374
Issue: N+1 query in loop (1000+ iterations)
357
375
Fix: Use batch loading or eager loading
366
384
#### Features
367
385
368
386
**Stack-Aware Merging:**
387
+
369
388
- Detects PR dependencies
370
389
- Merges in correct order automatically
371
390
- Handles entire stacks atomically
372
391
373
392
**CI Optimization:**
393
+
374
394
- Batches compatible PRs
375
395
- Runs CI once for multiple PRs
376
396
- Reduces CI cost by ~60%
377
397
378
398
**Automatic Rebasing:**
399
+
379
400
- Detects conflicts early
380
401
- Auto-rebases on base changes
381
402
- Alerts author on conflicts
382
403
383
404
**Smart Ordering:**
405
+
384
406
- Priority-based queue
385
407
- Dependency resolution
386
408
- Hotfix fast-tracking
388
410
#### Using Merge Queue
389
411
390
412
**Add to Queue:**
413
+
391
414
```bash
392
415
# Via CLI
393
416
och queue add \u003cPR_NUMBER\u003e
397
420
```
398
421
399
422
**Queue Status:**
423
+
400
424
```bash
401
425
# View queue
402
426
och queue list
419
443
#### Queue Configuration
420
444
421
445
**Repository → Settings → Merge Queue:**
446
+
422
447
- **Require CI**: Only merge if all checks pass
423
448
- **Batch Window**: Wait time to batch PRs (default: 5min)
424
449
- **Max Batch Size**: Max PRs to merge together (default: 3)
433
458
#### Available Metrics
434
459
435
460
**PR Velocity:**
461
+
436
462
- PRs merged per week
437
463
- Average time to merge
438
464
- Stack success rate
439
465
- Review cycle time
440
466
441
467
**Review Efficiency:**
468
+
442
469
- Average review time
443
470
- Reviews per day
444
471
- Review quality score
445
472
- Reviewer responsiveness
446
473
447
474
**Team Performance:**
475
+
448
476
- Lines changed per PR
449
477
- PR size distribution
450
478
- Revert rate
451
479
- CI success rate
452
480
453
481
**Stack Metrics:**
482
+
454
483
- % PRs that use stacks
455
484
- Average stack depth
456
485
- Stack merge success rate
459
488
#### Viewing Metrics
460
489
461
490
**Personal Dashboard:**
491
+
462
492
```text
463
493
/metrics/me
464
494
```
465
495
466
496
**Team Dashboard (Admin):**
497
+
467
498
```text
468
499
/admin/metrics
469
500
```
470
501
471
502
**Via CLI:**
503
+
472
504
```bash
473
505
och metrics show
474
506
och metrics show --user @username
500
532
#### Setup
501
533
502
534
**1. Create Slack App:**
535
+
503
536
1. Go to https://api.slack.com/apps
504
537
2. Create New App → From Scratch
505
538
3. Add OAuth Scopes:
510
543
511
544
**2. Connect to OpenCodeHub:**
512
545
Navigate to **Admin → Integrations → Slack**
546
+
513
547
```
514
548
Workspace Name: Your Team
515
549
Bot Token: xoxb-...
518
552
519
553
**3. Map Channels:**
520
554
**Repository → Settings → Slack**
555
+
521
556
```
522
557
#engineering → All PR events
523
558
#deployments → Merges only
531
566
#### Notification Types
532
567
533
568
**PR Events:**
569
+
534
570
- 🎉 PR opened
535
571
- ✅ PR approved
536
572
- 🔀 PR merged
539
575
- 🔴 Changes requested
540
576
541
577
**CI/CD Events:**
578
+
542
579
- ✅ Build passed
543
580
- ❌ Build failed
544
581
- 🚀 Deployment started
545
582
- ✅ Deployment successful
546
583
547
584
**Code Review:**
585
+
548
586
- 🤖 AI review completed
549
587
- 🔍 Security issue found
550
588
- ⚠️ Performance concern
551
589
552
590
**Merge Queue:**
591
+
553
592
- ⏭️ Added to queue
554
593
- ✅ Merged from queue
555
594
- ⚠️ Removed (conflict)
557
596
#### Interactive Actions
558
597
559
598
Notifications include action buttons:
599
+
560
600
```
561
601
[swadhin] opened PR#125: Add login UI
562
602
Stack: #123 → #124 → #125 • files →32 • LoC +420/-18
565
605
```
566
606
567
607
Click buttons to:
608
+
568
609
- Approve/reject directly from Slack
569
610
- Request changes with comment
570
611
- Add to merge queue
611
652
```
612
653
613
654
**Output:**
655
+
614
656
```
615
657
📚 Current Stack
616
658
617
659
main (base)
618
660
└─ #301: profile-db ✅ Approved
619
- └─ #302: profile-api 🔍 In Review
661
+ └─ #302: profile-api 🔍 In Review
620
662
└─ #303: profile-ui ⏳ AI Review Running
621
663
```
622
664
657
699
658
700
# Developer B: Builds on A's work (before merge!)
659
701
git checkout api/payments
660
-git checkout -b api/payment-processing
702
+git checkout -b api/payment-processing
661
703
# ... implements payment processing ...
662
704
git commit -m "Add payment processing"
663
705
git push
682
724
683
725
---
684
726
685
-
686
727
## ⚙️ Configuration
687
728
688
729
### Critical Environment Variables
704
745
### Database Configuration
705
746
706
747
**PostgreSQL (Recommended for Production):**
748
+
707
749
```env
708
750
DATABASE_DRIVER=postgres
709
751
DATABASE_URL=postgresql://user:password@localhost:5432/opencodehub
710
752
```
711
753
712
754
**MySQL:**
755
+
713
756
```env
714
757
DATABASE_DRIVER=mysql
715
758
DATABASE_URL=mysql://user:password@localhost:3306/opencodehub
716
759
```
717
760
718
761
**SQLite (Development Only):**
762
+
719
763
```env
720
764
DATABASE_DRIVER=sqlite
721
765
DATABASE_URL=./data/opencodehub.db
726
770
Storage can be configured via Admin Panel (`/admin/storage`) or environment variables:
727
771
728
772
**Local Storage:**
773
+
729
774
```env
730
775
STORAGE_TYPE=local
731
776
STORAGE_PATH=./data/storage
732
777
```
733
778
734
779
**S3/MinIO/R2:**
780
+
735
781
```env
736
782
STORAGE_TYPE=s3
737
783
STORAGE_BUCKET=opencodehub
742
788
```
743
789
744
790
**Google Drive (via Admin Panel):**
791
+
745
792
- Navigate to `/admin/storage`
746
793
- Select "Google Drive"
747
794
- Enter Client ID, Secret, and Refresh Token
750
797
### Security Configuration
751
798
752
799
**Rate Limiting:**
800
+
753
801
```env
754
802
RATE_LIMIT_AUTH=5 # Login attempts per 15 minutes
755
803
RATE_LIMIT_API=100 # API requests per minute
758
806
```
759
807
760
808
**CSRF Protection:**
809
+
761
810
```env
762
811
CSRF_SKIP_DEV=false # Never skip in production
763
812
```
765
814
### Optional Services
766
815
767
816
**Email (SMTP):**
817
+
768
818
```env
769
819
SMTP_HOST=smtp.example.com
770
820
SMTP_PORT=587
774
824
```
775
825
776
826
**Redis (Caching & Sessions):**
827
+
777
828
```env
778
829
REDIS_URL=redis://localhost:6379
779
830
```
780
831
781
832
**Error Monitoring (Sentry):**
833
+
782
834
```env
783
835
SENTRY_DSN=https://...
784
836
ENABLE_TRACING=true
793
845
### Implemented Security Features
794
846
795
847
✅ **Authentication & Authorization**
848
+
796
849
- JWT-based sessions with automatic expiration
797
850
- Bcrypt password hashing (10 rounds)
798
851
- 2FA/TOTP support
799
852
- Role-based access control (RBAC)
800
853
801
854
✅ **Attack Prevention**
855
+
802
856
- **Rate Limiting**: Prevents brute force (5 login attempts per 15min)
803
857
- **CSRF Protection**: Double-submit cookie pattern on all state-changing operations
804
858
- **Input Validation**: Zod schemas prevent SQL injection and XSS
805
859
- **XSS Sanitization**: Markdown rendered with `rehype-sanitize`
806
860
807
861
✅ **Operational Security**
862
+
808
863
- **Audit Logging**: All admin actions logged to database
809
864
- **Hook Authentication**: Git hooks protected with shared secrets
810
865
- **Session Management**: Automatic cleanup and rotation
879
934
880
935
```yaml
881
936
# docker-compose.prod.yml
882
-version: '3.8'
937
+version: "3.8"
883
938
884
939
services:
885
940
app:
977
1032
All authenticated endpoints require a session cookie or `Authorization: Bearer <token>` header.
978
1033
979
1034
**Register:**
1035
+
980
1036
```bash
981
1037
POST /api/auth/register
982
1038
Content-Type: application/json
997
1053
```
998
1054
999
1055
**Login:**
1056
+
1000
1057
```bash
1001
1058
POST /api/auth/login
1002
1059
Content-Type: application/json
1017
1074
### Repositories
1018
1075
1019
1076
**Create Repository:**
1077
+
1020
1078
```bash
1021
1079
POST /api/repos
1022
1080
Authorization: Bearer <token>
1030
1088
```
1031
1089
1032
1090
**Branch Protection:**
1091
+
1033
1092
```bash
1034
1093
POST /api/repos/{repoId}/branch-protection
1035
1094
Authorization: Bearer <token>
1046
1105
### Admin Endpoints
1047
1106
1048
1107
**List Users (Admin Only):**
1108
+
1049
1109
```bash
1050
1110
GET /admin/users?q=search&page=1
1051
1111
Cookie: och_session=...
1052
1112
```
1053
1113
1054
1114
**Update Storage Config (Admin Only):**
1115
+
1055
1116
```bash
1056
1117
POST /api/admin/config/storage
1057
1118
Cookie: och_session=...
1068
1129
### Rate Limits
1069
1130
1070
1131
All endpoints return rate limit headers:
1132
+
1071
1133
```http
1072
1134
X-RateLimit-Limit: 100
1073
1135
X-RateLimit-Remaining: 95
1096
1158
oning Repositories
1097
1159
1098
1160
**SSH (Recommended):**
1161
+
1099
1162
```bash
1100
1163
# Add your SSH key in Settings → SSH Keys
1101
1164
git clone [email protected]:username/repo.git
1102
1165
```
1103
1166
1104
1167
**HTTPS:**
1168
+
1105
1169
```bash
1106
1170
git clone https://your-domain.com/username/repo.git
1107
1171
# Username: your-username
1137
1201
runs-on: ubuntu-latest
1138
1202
steps:
1139
1203
- uses: actions/checkout@v4
1140
-
1204
+
1141
1205
- name: Setup Node.js
1142
1206
uses: actions/setup-node@v4
1143
1207
with:
1144
- node-version: '20'
1145
-
1208
+ node-version: "20"
1209
+
1146
1210
- name: Install
1147
1211
run: npm ci
1148
-
1212
+
1149
1213
- name: Test
1150
1214
run: npm test
1151
-
1215
+
1152
1216
- name: Build
1153
1217
run: npm run build
1154
-
1218
+
1155
1219
- name: Upload Artifacts
1156
1220
uses: actions/upload-artifact@v3
1157
1221
with:
1166
1230
### Common Issues
1167
1231
1168
1232
**"Rate limit exceeded" on login:**
1233
+
1169
1234
- Default: 5 attempts per 15 minutes
1170
1235
- Solution: Wait 15 minutes or adjust `RATE_LIMIT_AUTH` in `.env`
1171
1236
1172
1237
**"CSRF token validation failed":**
1238
+
1173
1239
- Ensure cookies are enabled
1174
1240
- Check `SITE_URL` matches your actual domain
1175
1241
- Verify HTTPS in production
1176
1242
1177
1243
**"Unauthorized" on git hooks:**
1244
+
1178
1245
- Check `INTERNAL_HOOK_SECRET` is set
1179
1246
- Verify hooks have correct secret in curl headers
1180
1247
- Re-run `git init` on repositories to reinstall hooks
1181
1248
1182
1249
**Database connection errors:**
1250
+
1183
1251
- Verify `DATABASE_URL` format
1184
1252
- Check database server is running
1185
1253
- Ensure user has correct permissions
1186
1254
1187
1255
**Storage upload fails:**
1256
+
1188
1257
- Test connection in `/admin/storage`
1189
1258
- Verify S3 credentials and bucket permissions
1190
1259
- Check firewall rules for external storage
1225
1294
We are constantly improving OpenCodeHub to make it the ultimate self-hosted development platform.
1226
1295
1227
1296
### Q2 2026: Advanced AI Agents
1297
+
1228
1298
- [ ] **Auto-Fix Agents**: Autonomous agents that can silently fix lint errors and simple bugs.
1229
1299
- [ ] **Test Generation**: Automatically generate unit and integration tests for new code.
1230
1300
1231
1301
### Q3 2026: Federation & Social
1302
+
1232
1303
- [ ] **ActivityPub Support**: Follow users and repositories across different OpenCodeHub instances.
1233
1304
- [ ] **Federated Merge Requests**: Cross-instance collaboration (like Email but better).
1234
1305
1235
1306
### Q4 2026: Ecosystem Expansion
1307
+
1236
1308
- [ ] **Mobile App**: Native iOS and Android apps for reviewing code on the go.
1237
1309
- [ ] **IDE Extensions**: dedicated VS Code and JetBrains plugins.
1238
1310
1239
1311
▼cli/README.md
+15-7
1
1
<p align="center">
2
- <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo.svg" alt="OpenCodeHub CLI" width="120" />
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-dark.png">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png">
5
+ <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png" alt="OpenCodeHub CLI" width="400" />
6
+ </picture>
3
7
</p>
4
8
5
9
<h1 align="center">OpenCodeHub CLI</h1>
107
111
## 🎨 Beautiful Output Examples
108
112
109
113
### Push Command
114
+
110
115
```
111
116
ℹ Pushing to swadhinbiswas/myrepo
112
117
Branch: master
122
127
✔ Uploaded 49.17 KB in 0.52s (94.56 KB/s)
123
128
124
129
remote: Processing: 100% (90/90), done.
125
-remote:
130
+remote:
126
131
To https://opencodehub.com/swadhinbiswas/myrepo.git
127
132
abc1234..def5678 master -> master
128
133
140
145
```
141
146
142
147
### Clone Command
148
+
143
149
```
144
150
ℹ Cloning swadhinbiswas/awesome-project
145
151
164
170
```
165
171
166
172
### Create Command
173
+
167
174
```
168
175
ℹ Creating 🌐 my-new-repo
169
176
Description: An awesome new project
171
178
✔ Repository created
172
179
173
180
✨ SUCCESS! ✨
174
-
181
+
175
182
Repository swadhinbiswas/my-new-repo is ready!
176
183
177
184
╭─────────────────────────────────────────╮
204
211
runs-on: ubuntu-latest
205
212
steps:
206
213
- uses: actions/checkout@v4
207
-
214
+
208
215
- name: Setup Node
209
216
uses: actions/setup-node@v4
210
217
with:
211
- node-version: '20'
212
-
218
+ node-version: "20"
219
+
213
220
- name: Install CLI
214
221
run: npm install -g opencodehub-cli
215
-
222
+
216
223
- name: Push to OpenCodeHub
217
224
run: |
218
225
och auth login --token ${{ secrets.OCH_TOKEN }}
247
254
## 📦 What's New in v1.1.0
248
255
249
256
✨ **Production-Grade UI Overhaul**
257
+
250
258
- GitHub-like progress indicators
251
259
- Beautiful ASCII art and gradients
252
260
- Real-time upload/download speeds
253
261
▼cli/bin/och.ts
+71-21
38
38
const git = simpleGit();
39
39
const program = new Command();
40
40
41
+if (!process.stdout.isTTY) {
42
+ chalk.level = 0;
43
+}
44
+
41
45
// Custom help formatter with Dracula theme
42
46
program.configureHelp({
43
47
sortSubcommands: true,
60
64
); // Dracula comment gray
61
65
});
62
66
67
+function isHiddenCommand(cmd: Command) {
68
+ return Boolean((cmd as { hidden?: boolean }).hidden);
69
+}
70
+
71
+function buildCommandTable() {
72
+ const commands = program.commands
73
+ .filter((cmd) => !isHiddenCommand(cmd))
74
+ .map((cmd) => ({
75
+ name: cmd.name(),
76
+ desc: cmd.description() || "",
77
+ }))
78
+ .sort((a, b) => a.name.localeCompare(b.name));
79
+
80
+ const maxDescWidth = 46;
81
+ const nameWidth = Math.max(
82
+ "Command".length,
83
+ ...commands.map((c) => c.name.length),
84
+ );
85
+ const descWidth = Math.min(
86
+ maxDescWidth,
87
+ Math.max("Description".length, ...commands.map((c) => c.desc.length)),
88
+ );
89
+
90
+ const top = ` ┌${"─".repeat(nameWidth + 2)}┬${"─".repeat(descWidth + 2)}┐`;
91
+ const header =
92
+ ` │ ${chalk.hex("#50fa7b").bold("Command".padEnd(nameWidth))} ` +
93
+ `│ ${chalk.hex("#50fa7b").bold("Description".padEnd(descWidth))} │`;
94
+ const mid = ` ├${"─".repeat(nameWidth + 2)}┼${"─".repeat(descWidth + 2)}┤`;
95
+
96
+ const rows = commands.map(({ name, desc }) => {
97
+ const clipped =
98
+ desc.length > descWidth ? `${desc.slice(0, descWidth - 1)}…` : desc;
99
+ return (
100
+ ` │ ${chalk.hex("#ff79c6")(name.padEnd(nameWidth))} ` +
101
+ `│ ${chalk.hex("#f8f8f2")(clipped.padEnd(descWidth))} │`
102
+ );
103
+ });
104
+
105
+ const bottom = ` └${"─".repeat(nameWidth + 2)}┴${"─".repeat(descWidth + 2)}┘`;
106
+
107
+ return [top, header, mid, ...rows, bottom].join("\n");
108
+}
109
+
63
110
// Customize command help colors with Dracula theme
64
111
const originalHelp = program.helpInformation.bind(program);
65
112
program.helpInformation = function () {
66
113
const help = originalHelp();
114
+ const commandsIndex = help.indexOf("Commands:");
115
+ const commandsBlock =
116
+ commandsIndex >= 0 ? help.slice(0, commandsIndex) : help;
117
+
118
+ const styled = commandsBlock
119
+ // Usage line - Dracula cyan
120
+ .replace(
121
+ /^Usage: (.+)$/gm,
122
+ (_, usage) =>
123
+ chalk.hex("#8be9fd").bold("Usage: ") + chalk.hex("#f8f8f2")(usage),
124
+ )
125
+ // Section headers - Dracula green
126
+ .replace(/^Options:$/gm, "\n" + chalk.hex("#50fa7b").bold("Options:"))
127
+ // Flags - Dracula purple
128
+ .replace(/(-[a-zA-Z-]+)/g, chalk.hex("#bd93f9")("$1"))
129
+ // Arguments - Dracula yellow
130
+ .replace(/<([^>]+)>/g, chalk.hex("#f1fa8c")("<$1>"))
131
+ // Optional params - Dracula comment
132
+ .replace(/\[([^\]]+)\]/g, chalk.hex("#6272a4")("[$1]"));
67
133
68
134
return (
69
- help
70
- // Usage line - Dracula cyan
71
- .replace(
72
- /^Usage: (.+)$/gm,
73
- (_, usage) =>
74
- chalk.hex("#8be9fd").bold("Usage: ") + chalk.hex("#f8f8f2")(usage),
75
- )
76
- // Section headers - Dracula green
77
- .replace(/^Options:$/gm, "\n" + chalk.hex("#50fa7b").bold("Options:"))
78
- .replace(/^Commands:$/gm, "\n" + chalk.hex("#50fa7b").bold("Commands:"))
79
- // Flags - Dracula purple
80
- .replace(/(-[a-zA-Z-]+)/g, chalk.hex("#bd93f9")("$1"))
81
- // Arguments - Dracula yellow
82
- .replace(/<([^>]+)>/g, chalk.hex("#f1fa8c")("<$1>"))
83
- // Optional params - Dracula comment
84
- .replace(/\[([^\]]+)\]/g, chalk.hex("#6272a4")("[$1]"))
85
- // Command names at start of lines - Dracula pink
86
- .replace(
87
- /^(\s+)([a-z-]+)(\s)/gm,
88
- (match, space, cmd, after) => space + chalk.hex("#ff79c6")(cmd) + after,
89
- )
135
+ styled +
136
+ "\n" +
137
+ chalk.hex("#50fa7b").bold("\nCommands:") +
138
+ "\n" +
139
+ buildCommandTable()
90
140
);
91
141
};
92
142
93
143
▼cli/src/commands/config/index.ts
+6-6
96
96
const value = config.get(key as keyof OchConfig);
97
97
98
98
if (key === "token") {
99
- if (value) {
99
+ if (typeof value === "string" && value) {
100
100
console.log(value.slice(0, 12) + "...");
101
101
} else {
102
102
console.log(chalk.dim("(not set)"));
139
139
key === "insecure" ? value === "true" || value === "1" : value;
140
140
141
141
config.set(key as keyof OchConfig, normalizedValue as any);
142
- console.log(
143
- chalk.green(
144
- `✓ Set ${key} = ${key === "token" ? value.slice(0, 12) + "..." : value}`,
145
- ),
146
- );
142
+ const displayValue =
143
+ key === "token" && typeof normalizedValue === "string"
144
+ ? `${normalizedValue.slice(0, 12)}...`
145
+ : String(normalizedValue);
146
+ console.log(chalk.green(`✓ Set ${key} = ${displayValue}`));
147
147
});
148
148
149
149
// Config Unset
150
150
▼docs-site/astro.config.mjs
+106-57
1
1
// @ts-check
2
-import { defineConfig } from 'astro/config';
3
-import starlight from '@astrojs/starlight';
2
+import starlight from "@astrojs/starlight";
3
+import { defineConfig } from "astro/config";
4
4
5
5
// https://astro.build/config
6
6
export default defineConfig({
7
- integrations: [
8
- starlight({
9
- title: 'OpenCodeHub Docs',
10
- social: [
11
- { icon: 'github', label: 'GitHub', href: 'https://github.com/swadhinbiswas/OpencodeHub' },
12
- ],
13
- sidebar: [
14
- {
15
- label: 'Getting Started',
16
- items: [
17
- { label: 'Installation', slug: 'getting-started/installation' },
18
- { label: 'Quick Start', slug: 'getting-started/quick-start' },
19
- { label: 'First Repository', slug: 'getting-started/first-repository' },
20
- ],
21
- },
22
- {
23
- label: 'Guides',
24
- items: [
25
- { label: 'Team Workflows', slug: 'guides/team-workflows' },
26
- { label: 'Branch Protection', slug: 'guides/branch-protection' },
27
- { label: 'Webhooks', slug: 'guides/webhooks' },
28
- { label: 'Storage Adapters', slug: 'guides/storage-adapters' },
29
- ],
30
- },
31
- {
32
- label: 'Features',
33
- items: [
34
- { label: 'Stacked PRs', slug: 'features/stacked-prs' },
35
- { label: 'AI Review', slug: 'features/ai-review' },
36
- { label: 'Merge Queue', slug: 'features/merge-queue' },
37
- ],
38
- },
39
- {
40
- label: 'Tutorials',
41
- autogenerate: { directory: 'tutorials' },
42
- },
43
- {
44
- label: 'Administration',
45
- autogenerate: { directory: 'administration' },
46
- },
47
- {
48
- label: 'API Reference',
49
- autogenerate: { directory: 'api' },
50
- },
51
- {
52
- label: 'Development',
53
- autogenerate: { directory: 'development' },
54
- },
55
- {
56
- label: 'Reference',
57
- autogenerate: { directory: 'reference' },
58
- },
59
- ],
60
- }),
61
- ],
7
+ integrations: [
8
+ starlight({
9
+ title: "OpenCodeHub Docs",
10
+ logo: {
11
+ light: "../../public/logo-light.png",
12
+ dark: "../../public/logo-dark.png",
13
+ replacesTitle: false,
14
+ },
15
+ social: [
16
+ {
17
+ icon: "github",
18
+ label: "GitHub",
19
+ href: "https://github.com/swadhinbiswas/OpencodeHub",
20
+ },
21
+ ],
22
+ sidebar: [
23
+ {
24
+ label: "Getting Started",
25
+ items: [
26
+ { label: "Installation", slug: "getting-started/installation" },
27
+ { label: "Quick Start", slug: "getting-started/quick-start" },
28
+ {
29
+ label: "First Repository",
30
+ slug: "getting-started/first-repository",
31
+ },
32
+ ],
33
+ },
34
+ {
35
+ label: "Guides",
36
+ items: [
37
+ { label: "Team Workflows", slug: "guides/team-workflows" },
38
+ { label: "Branch Protection", slug: "guides/branch-protection" },
39
+ { label: "Webhooks", slug: "guides/webhooks" },
40
+ { label: "Storage Adapters", slug: "guides/storage-adapters" },
41
+ ],
42
+ },
43
+ {
44
+ label: "CLI",
45
+ items: [
46
+ { label: "CLI Overview", slug: "reference/cli-overview" },
47
+ { label: "Auth & Config", slug: "reference/cli-auth-config" },
48
+ { label: "Core Commands", slug: "reference/cli-core-commands" },
49
+ { label: "Stack Workflows", slug: "reference/cli-stack-workflows" },
50
+ { label: "Merge Queue", slug: "reference/cli-merge-queue" },
51
+ {
52
+ label: "Automation & Insights",
53
+ slug: "reference/cli-automation-insights",
54
+ },
55
+ { label: "CLI Command Reference", slug: "reference/cli-commands" },
56
+ ],
57
+ },
58
+ {
59
+ label: "Features",
60
+ items: [
61
+ { label: "Stacked PRs", slug: "features/stacked-prs" },
62
+ { label: "AI Review", slug: "features/ai-review" },
63
+ { label: "Merge Queue", slug: "features/merge-queue" },
64
+ { label: "Automation Rules", slug: "features/automations" },
65
+ { label: "PR Inbox", slug: "features/inbox" },
66
+ { label: "Developer Metrics", slug: "features/developer-metrics" },
67
+ { label: "Notifications", slug: "features/notifications" },
68
+ { label: "CI/CD Actions", slug: "features/ci-actions" },
69
+ { label: "CLI Workflows", slug: "features/cli" },
70
+ ],
71
+ },
72
+ {
73
+ label: "Tutorials",
74
+ autogenerate: { directory: "tutorials" },
75
+ },
76
+ {
77
+ label: "Deployment",
78
+ items: [
79
+ { label: "Docker", slug: "administration/deploy-docker" },
80
+ { label: "Podman", slug: "administration/deploy-podman" },
81
+ { label: "Nginx Proxy", slug: "administration/deploy-nginx" },
82
+ { label: "cPanel", slug: "administration/deploy-cpanel" },
83
+ { label: "CyberPanel", slug: "administration/deploy-cyberpanel" },
84
+ { label: "Cloudflare", slug: "administration/deploy-cloudflare" },
85
+ ],
86
+ },
87
+ {
88
+ label: "Administration",
89
+ items: [
90
+ { label: "Production Guide", slug: "administration/deployment" },
91
+ { label: "Configuration", slug: "administration/configuration" },
92
+ { label: "Monitoring", slug: "administration/monitoring" },
93
+ { label: "Security", slug: "administration/security" },
94
+ ],
95
+ },
96
+ {
97
+ label: "API Reference",
98
+ autogenerate: { directory: "api" },
99
+ },
100
+ {
101
+ label: "Development",
102
+ autogenerate: { directory: "development" },
103
+ },
104
+ {
105
+ label: "Reference",
106
+ autogenerate: { directory: "reference" },
107
+ },
108
+ ],
109
+ }),
110
+ ],
62
111
});
63
112
▼docs-site/public/favicon.png
+0-0
Binary file not shown.
▼docs-site/src/content/docs/administration/deploy-cloudflare.md
+311-0
1
+---
2
+title: "Deploy with Cloudflare"
3
+description: "Deploy OpenCodeHub using Cloudflare services (Tunnel, R2, Pages)"
4
+---
5
+
6
+Cloudflare provides several services that integrate well with OpenCodeHub. This guide covers using Cloudflare Tunnel for secure access and R2 for storage.
7
+
8
+## Cloudflare Services Overview
9
+
10
+| Service | Use Case |
11
+| --------------------- | ------------------------------------------------- |
12
+| **Cloudflare Tunnel** | Securely expose your server without opening ports |
13
+| **Cloudflare R2** | S3-compatible object storage |
14
+| **Cloudflare DNS** | Fast DNS with DDoS protection |
15
+| **Cloudflare Pages** | Deploy docs-site (static) |
16
+
17
+---
18
+
19
+## Part 1: Cloudflare Tunnel
20
+
21
+Cloudflare Tunnel (formerly Argo Tunnel) creates an encrypted tunnel from your server to Cloudflare's edge, eliminating the need to open ports.
22
+
23
+### Prerequisites
24
+
25
+- OpenCodeHub running on `localhost:3000`
26
+- Cloudflare account with a domain
27
+- `cloudflared` CLI installed
28
+
29
+### Install cloudflared
30
+
31
+```bash
32
+# Debian/Ubuntu
33
+curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
34
+sudo dpkg -i cloudflared.deb
35
+
36
+# Or via package manager
37
+curl -L https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-archive-keyring.gpg
38
+echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive-keyring.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
39
+sudo apt update && sudo apt install cloudflared
40
+```
41
+
42
+### Authenticate
43
+
44
+```bash
45
+cloudflared tunnel login
46
+# This opens a browser to authenticate with your Cloudflare account
47
+```
48
+
49
+### Create Tunnel
50
+
51
+```bash
52
+# Create tunnel
53
+cloudflared tunnel create opencodehub
54
+
55
+# Note the tunnel ID and credentials file path
56
+# Example: ~/.cloudflared/<tunnel-id>.json
57
+```
58
+
59
+### Configure Tunnel
60
+
61
+Create `~/.cloudflared/config.yml`:
62
+
63
+```yaml
64
+tunnel: <your-tunnel-id>
65
+credentials-file: /home/user/.cloudflared/<tunnel-id>.json
66
+
67
+ingress:
68
+ # Main application
69
+ - hostname: git.yourdomain.com
70
+ service: http://localhost:3000
71
+ originRequest:
72
+ noTLSVerify: true
73
+ connectTimeout: 30s
74
+ # Important for git operations
75
+ disableChunkedEncoding: true
76
+
77
+ # Catch-all (required)
78
+ - service: http_status:404
79
+```
80
+
81
+### Route DNS
82
+
83
+```bash
84
+cloudflared tunnel route dns opencodehub git.yourdomain.com
85
+```
86
+
87
+### Run Tunnel
88
+
89
+```bash
90
+# Test
91
+cloudflared tunnel run opencodehub
92
+
93
+# Install as service
94
+sudo cloudflared service install
95
+sudo systemctl start cloudflared
96
+sudo systemctl enable cloudflared
97
+```
98
+
99
+### Verify
100
+
101
+Visit `https://git.yourdomain.com` - it should load OpenCodeHub!
102
+
103
+---
104
+
105
+## Part 2: Cloudflare R2 Storage
106
+
107
+R2 is S3-compatible object storage with no egress fees.
108
+
109
+### Create R2 Bucket
110
+
111
+1. Go to Cloudflare Dashboard → **R2**
112
+2. Click **Create bucket**
113
+3. Name: `opencodehub-repos`
114
+4. Location: Choose nearest region
115
+
116
+### Create API Tokens
117
+
118
+1. Go to **R2 → Manage R2 API Tokens**
119
+2. Click **Create API Token**
120
+3. Permissions: **Object Read & Write**
121
+4. Specify bucket: `opencodehub-repos`
122
+5. Copy the Access Key ID and Secret Access Key
123
+
124
+### Configure OpenCodeHub
125
+
126
+Add to your `.env`:
127
+
128
+```bash
129
+STORAGE_TYPE=s3
130
+STORAGE_BUCKET=opencodehub-repos
131
+STORAGE_REGION=auto
132
+STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
133
+S3_ACCESS_KEY=<your-r2-access-key-id>
134
+S3_SECRET_KEY=<your-r2-secret-access-key>
135
+```
136
+
137
+Find your account ID in Cloudflare Dashboard → R2 → Overview.
138
+
139
+### Performance Note
140
+
141
+:::caution[R2 Latency]
142
+R2 has the same latency characteristics as other object storage. See [Storage Adapters - Performance Considerations](/guides/storage-adapters/#️-performance-considerations) for details and recommendations.
143
+:::
144
+
145
+---
146
+
147
+## Part 3: Cloudflare DNS
148
+
149
+### Recommended DNS Settings
150
+
151
+| Type | Name | Content | Proxy |
152
+| ----- | ---- | ------------------------------ | ---------------- |
153
+| CNAME | git | `<tunnel-id>.cfargotunnel.com` | Proxied (orange) |
154
+
155
+If using Tunnel, the CNAME is created automatically.
156
+
157
+### Security Settings
158
+
159
+1. Go to **SSL/TLS → Overview**
160
+2. Set mode to **Full (strict)**
161
+
162
+3. Go to **Security → WAF**
163
+4. Enable managed rules
164
+
165
+---
166
+
167
+## Part 4: Deploy Docs to Cloudflare Pages
168
+
169
+The documentation site can be deployed to Cloudflare Pages for free.
170
+
171
+### Connect Repository
172
+
173
+1. Go to **Workers & Pages → Create application**
174
+2. Select **Pages → Connect to Git**
175
+3. Choose your OpenCodeHub repository
176
+
177
+### Build Settings
178
+
179
+| Setting | Value |
180
+| ---------------------- | ---------------------------------------------- |
181
+| Build command | `cd docs-site && npm install && npm run build` |
182
+| Build output directory | `docs-site/dist` |
183
+| Root directory | `/` |
184
+
185
+### Environment Variables
186
+
187
+| Variable | Value |
188
+| -------------- | ----- |
189
+| `NODE_VERSION` | `20` |
190
+
191
+### Custom Domain
192
+
193
+1. Go to Pages project → **Custom domains**
194
+2. Add `docs.yourdomain.com`
195
+3. Cloudflare will configure DNS automatically
196
+
197
+---
198
+
199
+## Complete Architecture
200
+
201
+```
202
+┌─────────────────────────────────────────────────────────┐
203
+│ Cloudflare Edge │
204
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
205
+│ │ DNS │ │ WAF │ │ CDN │ │
206
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
207
+│ │ │ │
208
+│ ▼ ▼ │
209
+│ ┌─────────────────┐ ┌─────────────────┐ │
210
+│ │ Cloudflare │ │ Cloudflare │ │
211
+│ │ Tunnel │ │ Pages │ │
212
+│ │ (git.domain) │ │ (docs.domain) │ │
213
+│ └────────┬────────┘ └─────────────────┘ │
214
+└───────────┼─────────────────────────────────────────────┘
215
+ │
216
+ ▼ (encrypted tunnel)
217
+ ┌───────────────────┐
218
+ │ Your Server │
219
+ │ ┌─────────────┐ │
220
+ │ │ OpenCodeHub │ │
221
+ │ │ :3000 │ │
222
+ │ └──────┬──────┘ │
223
+ │ │ │
224
+ │ ▼ │
225
+ │ ┌─────────────┐ │
226
+ │ │ PostgreSQL │ │
227
+ │ └─────────────┘ │
228
+ └───────────────────┘
229
+ │
230
+ ▼ (S3 API)
231
+ ┌───────────────────┐
232
+ │ Cloudflare R2 │
233
+ │ (repo storage) │
234
+ └───────────────────┘
235
+```
236
+
237
+---
238
+
239
+## Benefits of Cloudflare Setup
240
+
241
+| Benefit | Description |
242
+| ------------------- | ---------------------------- |
243
+| **No open ports** | Tunnel handles all ingress |
244
+| **DDoS protection** | Free, always-on |
245
+| **Free SSL** | Automatic via Cloudflare |
246
+| **No egress fees** | R2 has zero egress costs |
247
+| **Global CDN** | Static assets cached at edge |
248
+| **Analytics** | Built-in traffic analytics |
249
+
250
+---
251
+
252
+## Troubleshooting
253
+
254
+### Tunnel Connection Drops
255
+
256
+```bash
257
+# Check tunnel status
258
+cloudflared tunnel info opencodehub
259
+
260
+# View logs
261
+sudo journalctl -u cloudflared -f
262
+```
263
+
264
+### 502 Bad Gateway
265
+
266
+1. Verify OpenCodeHub is running on port 3000
267
+2. Check tunnel config points to correct port
268
+3. Verify `localhost:3000` is accessible locally
269
+
270
+### R2 Access Denied
271
+
272
+1. Verify API token has correct bucket permissions
273
+2. Check Access Key ID and Secret are correct
274
+3. Verify endpoint URL format
275
+
276
+---
277
+
278
+## Environment Example
279
+
280
+Complete `.env` for Cloudflare setup:
281
+
282
+```bash
283
+NODE_ENV=production
284
+PORT=3000
285
+SITE_URL=https://git.yourdomain.com
286
+
287
+# Database (local PostgreSQL)
288
+DATABASE_DRIVER=postgres
289
+DATABASE_URL=postgresql://opencodehub:password@localhost:5432/opencodehub
290
+
291
+# Security
292
+JWT_SECRET=<64-char-hex>
293
+SESSION_SECRET=<64-char-hex>
294
+INTERNAL_HOOK_SECRET=<64-char-hex>
295
+
296
+# Cloudflare R2 Storage
297
+STORAGE_TYPE=s3
298
+STORAGE_BUCKET=opencodehub-repos
299
+STORAGE_REGION=auto
300
+STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
301
+S3_ACCESS_KEY=<r2-access-key>
302
+S3_SECRET_KEY=<r2-secret-key>
303
+```
304
+
305
+---
306
+
307
+## Next Steps
308
+
309
+- [Configure monitoring](/administration/monitoring/)
310
+- [Set up backups](/administration/deploy-docker/#backup--restore)
311
+- [Enable AI code review](/features/ai-review/)
0
312
▼docs-site/src/content/docs/administration/deploy-cpanel.md
+221-0
1
+---
2
+title: "Deploy with cPanel"
3
+description: "Deploy OpenCodeHub on shared hosting using cPanel's Node.js Application feature"
4
+---
5
+
6
+cPanel supports Node.js applications through its Application Manager. This guide covers deploying OpenCodeHub on cPanel-based shared hosting.
7
+
8
+:::caution[Limitations]
9
+Shared hosting has significant limitations for Git servers:
10
+
11
+- No Docker support
12
+- Limited process control
13
+- Shared resources with other users
14
+- May not support persistent processes
15
+
16
+For production use, consider a VPS or dedicated server instead.
17
+:::
18
+
19
+## Prerequisites
20
+
21
+- cPanel hosting with Node.js support (v18+)
22
+- PostgreSQL database addon (or external database)
23
+- SSH access recommended
24
+- Domain configured in cPanel
25
+
26
+---
27
+
28
+## Step 1: Create PostgreSQL Database
29
+
30
+1. Login to cPanel
31
+2. Go to **Databases → PostgreSQL Databases**
32
+3. Create a new database: `opencodehub`
33
+4. Create a new user with a strong password
34
+5. Add user to database with ALL PRIVILEGES
35
+6. Note the connection string:
36
+ ```
37
+ postgresql://username:password@localhost:5432/opencodehub
38
+ ```
39
+
40
+---
41
+
42
+## Step 2: Upload Application Files
43
+
44
+### Option A: Git Clone (if SSH available)
45
+
46
+```bash
47
48
+cd ~/
49
+git clone https://github.com/swadhinbiswas/OpencodeHub.git opencodehub
50
+```
51
+
52
+### Option B: File Manager Upload
53
+
54
+1. Download the repository as ZIP from GitHub
55
+2. Go to cPanel → **File Manager**
56
+3. Navigate to your home directory
57
+4. Upload and extract the ZIP file
58
+5. Rename to `opencodehub`
59
+
60
+---
61
+
62
+## Step 3: Create Node.js Application
63
+
64
+1. Go to cPanel → **Setup Node.js App**
65
+2. Click **Create Application**
66
+3. Configure:
67
+
68
+| Setting | Value |
69
+| ------------------------ | ----------------------- |
70
+| Node.js version | 18.x or 20.x |
71
+| Application mode | Production |
72
+| Application root | `opencodehub` |
73
+| Application URL | `git.yourdomain.com` |
74
+| Application startup file | `dist/server/entry.mjs` |
75
+
76
+4. Click **Create**
77
+
78
+---
79
+
80
+## Step 4: Configure Environment Variables
81
+
82
+In the Node.js Application page:
83
+
84
+1. Scroll to **Environment Variables**
85
+2. Add the following:
86
+
87
+```bash
88
+NODE_ENV=production
89
+PORT=3000
90
+SITE_URL=https://git.yourdomain.com
91
+
92
+# Database
93
+DATABASE_DRIVER=postgres
94
+DATABASE_URL=postgresql://cpuser_dbuser:password@localhost:5432/cpuser_opencodehub
95
+
96
+# Security (generate with: openssl rand -hex 32)
97
+JWT_SECRET=<64-char-hex>
98
+SESSION_SECRET=<64-char-hex>
99
+INTERNAL_HOOK_SECRET=<64-char-hex>
100
+
101
+# Storage
102
+STORAGE_TYPE=local
103
+STORAGE_PATH=./data/repos
104
+```
105
+
106
+3. Click **Save**
107
+
108
+---
109
+
110
+## Step 5: Install Dependencies and Build
111
+
112
+1. In the Node.js app page, click **Run NPM Install**
113
+2. Connect via SSH and run:
114
+
115
+```bash
116
+cd ~/opencodehub
117
+
118
+# Enter virtual environment
119
+source /home/username/nodevenv/opencodehub/18/bin/activate
120
+
121
+# Install dependencies
122
+npm install
123
+
124
+# Build the application
125
+npm run build
126
+
127
+# Initialize database
128
+npm run db:push
129
+
130
+# Create admin user (if script exists)
131
+npm run scripts/seed-admin.ts
132
+```
133
+
134
+---
135
+
136
+## Step 6: Start Application
137
+
138
+1. In cPanel Node.js app manager, click **Start App**
139
+2. Verify it's running by visiting your domain
140
+
141
+---
142
+
143
+## Alternative: Using PM2
144
+
145
+If your host supports PM2:
146
+
147
+```bash
148
+# Install PM2 globally
149
+npm install -g pm2
150
+
151
+# Start with PM2
152
+pm2 start dist/server/entry.mjs --name opencodehub
153
+
154
+# Save process list
155
+pm2 save
156
+
157
+# Setup startup script
158
+pm2 startup
159
+```
160
+
161
+---
162
+
163
+## Handling Git Operations
164
+
165
+:::warning[Important]
166
+cPanel's Node.js apps may not handle long-running Git operations well. For push/pull with large repos, you may experience timeouts.
167
+:::
168
+
169
+### Workarounds
170
+
171
+1. **Increase PHP/Node timeout** (if available in hosting settings)
172
+2. **Use external storage** (S3) to reduce local I/O
173
+3. **Consider VPS** for better control
174
+
175
+---
176
+
177
+## Troubleshooting
178
+
179
+### Application Won't Start
180
+
181
+1. Check error logs in cPanel → **Errors**
182
+2. Verify Node.js version compatibility
183
+3. Check if all dependencies installed
184
+4. Verify startup file path is correct
185
+
186
+### Database Connection Errors
187
+
188
+```bash
189
+# Test connection via SSH
190
+psql "postgresql://user:pass@localhost/dbname"
191
+```
192
+
193
+### 503 Service Unavailable
194
+
195
+- Application may have crashed
196
+- Check logs and restart via cPanel
197
+
198
+---
199
+
200
+## Limitations Summary
201
+
202
+| Feature | cPanel Support |
203
+| --------------- | ----------------------------- |
204
+| Web UI | ✅ Works |
205
+| Git clone/push | ⚠️ May timeout on large repos |
206
+| WebSockets | ❌ Usually not supported |
207
+| Background jobs | ❌ Limited |
208
+| Custom domains | ✅ Works |
209
+| SSL | ✅ Via AutoSSL |
210
+
211
+---
212
+
213
+## Recommended Alternative
214
+
215
+For a Git server, we strongly recommend:
216
+
217
+1. **VPS** (DigitalOcean, Linode, Vultr) - Full control
218
+2. **Docker** on any cloud provider - Easy deployment
219
+3. **Vercel/Cloudflare** - For serverless approach
220
+
221
+See [Docker Deployment Guide](/administration/deploy-docker/) for production setups.
0
222
▼docs-site/src/content/docs/administration/deploy-cyberpanel.md
+330-0
1
+---
2
+title: "Deploy with CyberPanel"
3
+description: "Deploy OpenCodeHub using CyberPanel with OpenLiteSpeed web server"
4
+---
5
+
6
+CyberPanel is a hosting control panel that uses OpenLiteSpeed (OLS) as its web server. This guide covers deploying OpenCodeHub on a CyberPanel server.
7
+
8
+## Prerequisites
9
+
10
+- CyberPanel installed on a VPS
11
+- Root SSH access
12
+- Domain pointed to your server
13
+- PostgreSQL or MySQL available
14
+
15
+---
16
+
17
+## Step 1: Create Website in CyberPanel
18
+
19
+1. Login to CyberPanel admin panel
20
+2. Go to **Websites → Create Website**
21
+3. Configure:
22
+ - Domain: `git.yourdomain.com`
23
+ - Email: your email
24
+ - PHP: None (we'll use Node.js)
25
+ - SSL: Issue SSL via Let's Encrypt
26
+
27
+---
28
+
29
+## Step 2: Install Node.js
30
+
31
+CyberPanel doesn't include Node.js by default. Install via SSH:
32
+
33
+```bash
34
+# Connect via SSH as root
35
+ssh root@your-server-ip
36
+
37
+# Install Node.js 20.x
38
+curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
39
+apt install -y nodejs
40
+
41
+# Verify
42
+node --version # Should be 20.x
43
+npm --version
44
+```
45
+
46
+---
47
+
48
+## Step 3: Install PostgreSQL
49
+
50
+```bash
51
+# Install PostgreSQL
52
+apt install -y postgresql postgresql-contrib
53
+
54
+# Create database and user
55
+sudo -u postgres psql << EOF
56
+CREATE DATABASE opencodehub;
57
+CREATE USER opencodehub WITH ENCRYPTED PASSWORD 'your-secure-password';
58
+GRANT ALL PRIVILEGES ON DATABASE opencodehub TO opencodehub;
59
+\c opencodehub
60
+GRANT ALL ON SCHEMA public TO opencodehub;
61
+EOF
62
+```
63
+
64
+---
65
+
66
+## Step 4: Clone and Setup Application
67
+
68
+```bash
69
+# Create app directory
70
+mkdir -p /home/git.yourdomain.com/app
71
+cd /home/git.yourdomain.com/app
72
+
73
+# Clone repository
74
+git clone https://github.com/swadhinbiswas/OpencodeHub.git .
75
+
76
+# Install dependencies
77
+npm install
78
+
79
+# Create environment file
80
+cat > .env << 'EOF'
81
+NODE_ENV=production
82
+PORT=3000
83
+SITE_URL=https://git.yourdomain.com
84
+
85
+# Database
86
+DATABASE_DRIVER=postgres
87
+DATABASE_URL=postgresql://opencodehub:your-secure-password@localhost:5432/opencodehub
88
+
89
+# Security (generate with: openssl rand -hex 32)
90
+JWT_SECRET=your-64-char-jwt-secret
91
+SESSION_SECRET=your-64-char-session-secret
92
+INTERNAL_HOOK_SECRET=your-64-char-hook-secret
93
+
94
+# Storage
95
+STORAGE_TYPE=local
96
+STORAGE_PATH=./data/repos
97
+EOF
98
+
99
+# Build
100
+npm run build
101
+
102
+# Initialize database
103
+npm run db:push
104
+
105
+# Create admin user
106
+npm run scripts/seed-admin.ts
107
+```
108
+
109
+---
110
+
111
+## Step 5: Configure OpenLiteSpeed
112
+
113
+CyberPanel uses OpenLiteSpeed. We need to set up reverse proxy.
114
+
115
+### Create Proxy Configuration
116
+
117
+1. SSH into server
118
+2. Edit the vhost config:
119
+
120
+```bash
121
+nano /usr/local/lsws/conf/vhosts/git.yourdomain.com/vhconf.conf
122
+```
123
+
124
+3. Add or modify the config:
125
+
126
+```apache
127
+docRoot $VH_ROOT/public_html
128
+enableGzip 1
129
+
130
+context / {
131
+ type proxy
132
+ handler localhost:3000
133
+ addDefaultCharset off
134
+}
135
+
136
+rewrite {
137
+ enable 1
138
+ autoLoadHtaccess 1
139
+}
140
+```
141
+
142
+### Alternative: Via CyberPanel UI
143
+
144
+1. Go to **Websites → List Websites**
145
+2. Click on your website → **vHost Conf**
146
+3. Add the proxy context as shown above
147
+4. Click **Save**
148
+
149
+### Restart LiteSpeed
150
+
151
+```bash
152
+systemctl restart lsws
153
+```
154
+
155
+---
156
+
157
+## Step 6: Setup Process Manager (PM2)
158
+
159
+```bash
160
+# Install PM2
161
+npm install -g pm2
162
+
163
+# Start OpenCodeHub
164
+cd /home/git.yourdomain.com/app
165
+pm2 start dist/server/entry.mjs --name opencodehub
166
+
167
+# Save process list
168
+pm2 save
169
+
170
+# Setup startup script
171
+pm2 startup systemd
172
+# Run the command it outputs
173
+```
174
+
175
+---
176
+
177
+## Step 7: Configure Firewall
178
+
179
+CyberPanel uses firewalld:
180
+
181
+```bash
182
+# Allow necessary ports (if not already open)
183
+firewall-cmd --permanent --add-port=3000/tcp
184
+firewall-cmd --reload
185
+```
186
+
187
+:::note
188
+Port 3000 only needs to be open if accessing directly. With the reverse proxy, only 80/443 need to be open.
189
+:::
190
+
191
+---
192
+
193
+## SSL Configuration
194
+
195
+CyberPanel can automatically issue SSL via Let's Encrypt:
196
+
197
+1. Go to **SSL → Manage SSL**
198
+2. Select your website
199
+3. Click **Issue SSL**
200
+
201
+The SSL will be automatically configured for OpenLiteSpeed.
202
+
203
+---
204
+
205
+## OpenLiteSpeed Optimizations
206
+
207
+### Increase Timeouts for Git Operations
208
+
209
+Edit `/usr/local/lsws/conf/vhosts/git.yourdomain.com/vhconf.conf`:
210
+
211
+```apache
212
+context / {
213
+ type proxy
214
+ handler localhost:3000
215
+ addDefaultCharset off
216
+
217
+ # Extended timeouts for git operations
218
+ extraparam {
219
+ ProxyTimeout 600
220
+ }
221
+}
222
+```
223
+
224
+### Enable WebSocket Support
225
+
226
+```apache
227
+context /ws {
228
+ type proxy
229
+ handler localhost:3000
230
+ addDefaultCharset off
231
+
232
+ extraparam {
233
+ WebSocket 1
234
+ }
235
+}
236
+```
237
+
238
+---
239
+
240
+## Monitoring with CyberPanel
241
+
242
+### View Logs
243
+
244
+1. Go to **Logs → Access Logs** or **Error Logs**
245
+2. Select your website
246
+
247
+### PM2 Monitoring
248
+
249
+```bash
250
+# View status
251
+pm2 status
252
+
253
+# View logs
254
+pm2 logs opencodehub
255
+
256
+# Monitor resources
257
+pm2 monit
258
+```
259
+
260
+---
261
+
262
+## Troubleshooting
263
+
264
+### 503 Bad Gateway
265
+
266
+1. Check if Node.js app is running: `pm2 status`
267
+2. Verify port 3000 is listening: `netstat -tlnp | grep 3000`
268
+3. Check LiteSpeed error logs: `/usr/local/lsws/logs/error.log`
269
+
270
+### Application Crashes on Restart
271
+
272
+```bash
273
+# Check PM2 logs
274
+pm2 logs opencodehub --lines 100
275
+
276
+# Restart application
277
+pm2 restart opencodehub
278
+```
279
+
280
+### Database Connection Issues
281
+
282
+```bash
283
+# Test PostgreSQL connection
284
+psql -U opencodehub -h localhost -d opencodehub
285
+```
286
+
287
+---
288
+
289
+## Updating OpenCodeHub
290
+
291
+```bash
292
+cd /home/git.yourdomain.com/app
293
+
294
+# Pull latest changes
295
+git pull origin main
296
+
297
+# Install dependencies
298
+npm install
299
+
300
+# Rebuild
301
+npm run build
302
+
303
+# Run migrations
304
+npm run db:push
305
+
306
+# Restart
307
+pm2 restart opencodehub
308
+```
309
+
310
+---
311
+
312
+## Comparison: OpenLiteSpeed vs Nginx
313
+
314
+| Feature | OpenLiteSpeed | Nginx |
315
+|---------|---------------|-------|
316
+| Performance | Excellent | Excellent |
317
+| .htaccess support | ✅ | ❌ |
318
+| WebSocket | ✅ | ✅ |
319
+| Configuration | GUI + files | Files only |
320
+| Resource usage | Low | Very low |
321
+
322
+Both work well for OpenCodeHub.
323
+
324
+---
325
+
326
+## Next Steps
327
+
328
+- [Set up monitoring](/administration/monitoring/)
329
+- [Configure backups](/administration/deploy-docker/#backup--restore)
330
+- [Enable AI code review](/features/ai-review/)
0
331
▼docs-site/src/content/docs/administration/deploy-docker.md
+303-0
1
+---
2
+title: "Deploy with Docker"
3
+description: "Complete guide to deploying OpenCodeHub using Docker and Docker Compose"
4
+---
5
+
6
+Docker is the recommended deployment method for OpenCodeHub. This guide covers everything from basic setup to production-ready configurations.
7
+
8
+## Prerequisites
9
+
10
+- Docker Engine 24.0+
11
+- Docker Compose v2.20+
12
+- 2GB RAM minimum (4GB recommended)
13
+- 20GB disk space
14
+
15
+```bash
16
+# Verify installation
17
+docker --version
18
+docker compose version
19
+```
20
+
21
+---
22
+
23
+## Quick Start (Development)
24
+
25
+For testing or development, use the minimal configuration:
26
+
27
+```bash
28
+# Clone the repository
29
+git clone https://github.com/swadhinbiswas/OpencodeHub.git
30
+cd OpenCodeHub
31
+
32
+# Copy environment file
33
+cp .env.example .env
34
+
35
+# Start with Docker Compose
36
+docker compose up -d
37
+
38
+# View logs
39
+docker compose logs -f
40
+```
41
+
42
+Access at `http://localhost:3000`
43
+
44
+---
45
+
46
+## Production Setup
47
+
48
+### 1. Create Directory Structure
49
+
50
+```bash
51
+mkdir -p /opt/opencodehub/{data,postgres,redis}
52
+cd /opt/opencodehub
53
+```
54
+
55
+### 2. Create docker-compose.yml
56
+
57
+```yaml
58
+version: '3.8'
59
+
60
+services:
61
+ app:
62
+ image: ghcr.io/swadhinbiswas/opencodehub:latest
63
+ # Or build from source:
64
+ # build: .
65
+ restart: unless-stopped
66
+ ports:
67
+ - "3000:3000"
68
+ environment:
69
+ - NODE_ENV=production
70
+ env_file:
71
+ - .env
72
+ volumes:
73
+ - ./data:/app/data
74
+ depends_on:
75
+ postgres:
76
+ condition: service_healthy
77
+ redis:
78
+ condition: service_started
79
+ healthcheck:
80
+ test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
81
+ interval: 30s
82
+ timeout: 10s
83
+ retries: 3
84
+ start_period: 40s
85
+
86
+ postgres:
87
+ image: postgres:16-alpine
88
+ restart: unless-stopped
89
+ environment:
90
+ POSTGRES_DB: opencodehub
91
+ POSTGRES_USER: opencodehub
92
+ POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
93
+ volumes:
94
+ - ./postgres:/var/lib/postgresql/data
95
+ healthcheck:
96
+ test: ["CMD-SHELL", "pg_isready -U opencodehub"]
97
+ interval: 10s
98
+ timeout: 5s
99
+ retries: 5
100
+
101
+ redis:
102
+ image: redis:7-alpine
103
+ restart: unless-stopped
104
+ command: redis-server --requirepass ${REDIS_PASSWORD}
105
+ volumes:
106
+ - redis_data:/data
107
+
108
+volumes:
109
+ redis_data:
110
+```
111
+
112
+### 3. Create Production .env
113
+
114
+```bash
115
+# Generate secure secrets
116
+JWT_SECRET=$(openssl rand -hex 32)
117
+SESSION_SECRET=$(openssl rand -hex 32)
118
+INTERNAL_HOOK_SECRET=$(openssl rand -hex 32)
119
+DATABASE_PASSWORD=$(openssl rand -hex 16)
120
+REDIS_PASSWORD=$(openssl rand -hex 16)
121
+
122
+cat > .env << EOF
123
+# Application
124
+NODE_ENV=production
125
+PORT=3000
126
+SITE_URL=https://git.yourcompany.com
127
+
128
+# Security (NEVER commit these!)
129
+JWT_SECRET=${JWT_SECRET}
130
+SESSION_SECRET=${SESSION_SECRET}
131
+INTERNAL_HOOK_SECRET=${INTERNAL_HOOK_SECRET}
132
+
133
+# Database
134
+DATABASE_DRIVER=postgres
135
+DATABASE_URL=postgresql://opencodehub:${DATABASE_PASSWORD}@postgres:5432/opencodehub
136
+DATABASE_PASSWORD=${DATABASE_PASSWORD}
137
+
138
+# Redis
139
+REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379
140
+REDIS_PASSWORD=${REDIS_PASSWORD}
141
+
142
+# Storage (use local for best performance)
143
+STORAGE_TYPE=local
144
+STORAGE_PATH=/app/data/repos
145
+EOF
146
+```
147
+
148
+### 4. Start Services
149
+
150
+```bash
151
+docker compose up -d
152
+
153
+# Verify all services are healthy
154
+docker compose ps
155
+
156
+# Initialize database
157
+docker compose exec app bun run db:push
158
+
159
+# Create admin user
160
+docker compose exec app bun run scripts/seed-admin.ts
161
+```
162
+
163
+---
164
+
165
+## Using with Nginx (Recommended)
166
+
167
+See the [Nginx Deployment Guide](/administration/deploy-nginx/) for reverse proxy configuration.
168
+
169
+### Quick Nginx Setup
170
+
171
+```nginx
172
+server {
173
+ listen 80;
174
+ server_name git.yourcompany.com;
175
+ return 301 https://$host$request_uri;
176
+}
177
+
178
+server {
179
+ listen 443 ssl http2;
180
+ server_name git.yourcompany.com;
181
+
182
+ ssl_certificate /etc/letsencrypt/live/git.yourcompany.com/fullchain.pem;
183
+ ssl_certificate_key /etc/letsencrypt/live/git.yourcompany.com/privkey.pem;
184
+
185
+ client_max_body_size 500M;
186
+
187
+ location / {
188
+ proxy_pass http://localhost:3000;
189
+ proxy_set_header Host $host;
190
+ proxy_set_header X-Real-IP $remote_addr;
191
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192
+ proxy_set_header X-Forwarded-Proto $scheme;
193
+
194
+ # WebSocket support
195
+ proxy_http_version 1.1;
196
+ proxy_set_header Upgrade $http_upgrade;
197
+ proxy_set_header Connection "upgrade";
198
+ }
199
+}
200
+```
201
+
202
+---
203
+
204
+## Docker Swarm Deployment
205
+
206
+For high availability:
207
+
208
+```yaml
209
+version: '3.8'
210
+
211
+services:
212
+ app:
213
+ image: ghcr.io/swadhinbiswas/opencodehub:latest
214
+ deploy:
215
+ replicas: 3
216
+ update_config:
217
+ parallelism: 1
218
+ delay: 10s
219
+ restart_policy:
220
+ condition: on-failure
221
+ # ... rest of config
222
+```
223
+
224
+```bash
225
+docker stack deploy -c docker-compose.yml opencodehub
226
+```
227
+
228
+---
229
+
230
+## Backup & Restore
231
+
232
+### Backup
233
+
234
+```bash
235
+#!/bin/bash
236
+DATE=$(date +%Y%m%d)
237
+BACKUP_DIR=/backups/opencodehub
238
+
239
+mkdir -p $BACKUP_DIR
240
+
241
+# Backup PostgreSQL
242
+docker compose exec -T postgres pg_dump -U opencodehub opencodehub | gzip > $BACKUP_DIR/db_$DATE.sql.gz
243
+
244
+# Backup repositories
245
+tar -czf $BACKUP_DIR/repos_$DATE.tar.gz -C /opt/opencodehub/data .
246
+
247
+# Keep last 7 days
248
+find $BACKUP_DIR -name "*.gz" -mtime +7 -delete
249
+```
250
+
251
+### Restore
252
+
253
+```bash
254
+# Restore database
255
+gunzip -c db_backup.sql.gz | docker compose exec -T postgres psql -U opencodehub opencodehub
256
+
257
+# Restore repositories
258
+tar -xzf repos_backup.tar.gz -C /opt/opencodehub/data
259
+```
260
+
261
+---
262
+
263
+## Troubleshooting
264
+
265
+### Container won't start
266
+
267
+```bash
268
+# Check logs
269
+docker compose logs app
270
+
271
+# Common issues:
272
+# 1. Database not ready - increase healthcheck start_period
273
+# 2. Port in use - change ports mapping
274
+# 3. Missing env vars - check .env file
275
+```
276
+
277
+### Database connection issues
278
+
279
+```bash
280
+# Verify PostgreSQL is accessible
281
+docker compose exec app nc -zv postgres 5432
282
+
283
+# Check database exists
284
+docker compose exec postgres psql -U opencodehub -l
285
+```
286
+
287
+### Out of disk space
288
+
289
+```bash
290
+# Clean Docker resources
291
+docker system prune -a --volumes
292
+
293
+# Check disk usage
294
+docker system df
295
+```
296
+
297
+---
298
+
299
+## Next Steps
300
+
301
+- [Configure Nginx reverse proxy](/administration/deploy-nginx/)
302
+- [Set up SSL with Let's Encrypt](/administration/deploy-nginx/#ssl-setup)
303
+- [Configure monitoring](/administration/monitoring/)
0
304
▼docs-site/src/content/docs/administration/deploy-nginx.md
+351-0
1
+---
2
+title: "Deploy with Nginx"
3
+description: "Configure Nginx as a reverse proxy for OpenCodeHub with SSL and Git optimizations"
4
+---
5
+
6
+Nginx is the recommended reverse proxy for OpenCodeHub in production. This guide covers complete configuration including SSL, Git-specific optimizations, and performance tuning.
7
+
8
+## Prerequisites
9
+
10
+- Nginx 1.18+ installed
11
+- Domain name with DNS configured
12
+- OpenCodeHub running on `localhost:3000`
13
+
14
+```bash
15
+# Install Nginx (Ubuntu/Debian)
16
+sudo apt update
17
+sudo apt install -y nginx
18
+
19
+# Install Nginx (Fedora/RHEL)
20
+sudo dnf install -y nginx
21
+
22
+# Start and enable
23
+sudo systemctl start nginx
24
+sudo systemctl enable nginx
25
+```
26
+
27
+---
28
+
29
+## Basic Configuration
30
+
31
+### Minimal Reverse Proxy
32
+
33
+```nginx
34
+# /etc/nginx/sites-available/opencodehub
35
+server {
36
+ listen 80;
37
+ server_name git.yourcompany.com;
38
+
39
+ location / {
40
+ proxy_pass http://localhost:3000;
41
+ proxy_set_header Host $host;
42
+ proxy_set_header X-Real-IP $remote_addr;
43
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44
+ proxy_set_header X-Forwarded-Proto $scheme;
45
+ }
46
+}
47
+```
48
+
49
+```bash
50
+# Enable site
51
+sudo ln -s /etc/nginx/sites-available/opencodehub /etc/nginx/sites-enabled/
52
+sudo nginx -t
53
+sudo systemctl reload nginx
54
+```
55
+
56
+---
57
+
58
+## SSL Setup with Let's Encrypt
59
+
60
+### Install Certbot
61
+
62
+```bash
63
+# Ubuntu/Debian
64
+sudo apt install -y certbot python3-certbot-nginx
65
+
66
+# Fedora/RHEL
67
+sudo dnf install -y certbot python3-certbot-nginx
68
+```
69
+
70
+### Obtain Certificate
71
+
72
+```bash
73
+sudo certbot --nginx -d git.yourcompany.com
74
+```
75
+
76
+Certbot will automatically configure SSL in your Nginx config.
77
+
78
+### Manual SSL Configuration
79
+
80
+If you prefer manual setup or have existing certificates:
81
+
82
+```nginx
83
+server {
84
+ listen 80;
85
+ server_name git.yourcompany.com;
86
+ return 301 https://$host$request_uri;
87
+}
88
+
89
+server {
90
+ listen 443 ssl http2;
91
+ server_name git.yourcompany.com;
92
+
93
+ # SSL Configuration
94
+ ssl_certificate /etc/letsencrypt/live/git.yourcompany.com/fullchain.pem;
95
+ ssl_certificate_key /etc/letsencrypt/live/git.yourcompany.com/privkey.pem;
96
+
97
+ # Modern SSL settings
98
+ ssl_protocols TLSv1.2 TLSv1.3;
99
+ ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
100
+ ssl_prefer_server_ciphers off;
101
+ ssl_session_cache shared:SSL:10m;
102
+ ssl_session_timeout 1d;
103
+ ssl_stapling on;
104
+ ssl_stapling_verify on;
105
+
106
+ location / {
107
+ proxy_pass http://localhost:3000;
108
+ proxy_set_header Host $host;
109
+ proxy_set_header X-Real-IP $remote_addr;
110
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
111
+ proxy_set_header X-Forwarded-Proto $scheme;
112
+ }
113
+}
114
+```
115
+
116
+---
117
+
118
+## Production Configuration
119
+
120
+Complete production-ready configuration:
121
+
122
+```nginx
123
+# /etc/nginx/sites-available/opencodehub
124
+
125
+# Upstream for load balancing (optional)
126
+upstream opencodehub {
127
+ server localhost:3000;
128
+ keepalive 64;
129
+}
130
+
131
+# Rate limiting zones
132
+limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
133
+limit_req_zone $binary_remote_addr zone=git:10m rate=5r/s;
134
+
135
+# HTTP to HTTPS redirect
136
+server {
137
+ listen 80;
138
+ listen [::]:80;
139
+ server_name git.yourcompany.com;
140
+ return 301 https://$host$request_uri;
141
+}
142
+
143
+# Main HTTPS server
144
+server {
145
+ listen 443 ssl http2;
146
+ listen [::]:443 ssl http2;
147
+ server_name git.yourcompany.com;
148
+
149
+ # SSL
150
+ ssl_certificate /etc/letsencrypt/live/git.yourcompany.com/fullchain.pem;
151
+ ssl_certificate_key /etc/letsencrypt/live/git.yourcompany.com/privkey.pem;
152
+ ssl_protocols TLSv1.2 TLSv1.3;
153
+ ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
154
+ ssl_prefer_server_ciphers off;
155
+
156
+ # Security headers
157
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
158
+ add_header X-Frame-Options "SAMEORIGIN" always;
159
+ add_header X-Content-Type-Options "nosniff" always;
160
+ add_header X-XSS-Protection "1; mode=block" always;
161
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
162
+
163
+ # Logging
164
+ access_log /var/log/nginx/opencodehub.access.log;
165
+ error_log /var/log/nginx/opencodehub.error.log;
166
+
167
+ # Git operations (larger limits and timeouts)
168
+ location ~ ^/[^/]+/[^/]+\.git/ {
169
+ # Rate limiting for git operations
170
+ limit_req zone=git burst=10 nodelay;
171
+
172
+ # Large file support (for push)
173
+ client_max_body_size 500M;
174
+
175
+ # Extended timeouts for large repos
176
+ proxy_read_timeout 600s;
177
+ proxy_send_timeout 600s;
178
+ proxy_connect_timeout 60s;
179
+
180
+ # Buffer settings for packfiles
181
+ proxy_buffer_size 128k;
182
+ proxy_buffers 8 256k;
183
+ proxy_busy_buffers_size 256k;
184
+
185
+ # Disable buffering for real-time output
186
+ proxy_request_buffering off;
187
+
188
+ proxy_pass http://opencodehub;
189
+ proxy_set_header Host $host;
190
+ proxy_set_header X-Real-IP $remote_addr;
191
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
192
+ proxy_set_header X-Forwarded-Proto $scheme;
193
+ }
194
+
195
+ # API endpoints
196
+ location /api/ {
197
+ limit_req zone=api burst=20 nodelay;
198
+
199
+ proxy_pass http://opencodehub;
200
+ proxy_set_header Host $host;
201
+ proxy_set_header X-Real-IP $remote_addr;
202
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
203
+ proxy_set_header X-Forwarded-Proto $scheme;
204
+ }
205
+
206
+ # WebSocket support (for live updates)
207
+ location /ws {
208
+ proxy_pass http://opencodehub;
209
+ proxy_http_version 1.1;
210
+ proxy_set_header Upgrade $http_upgrade;
211
+ proxy_set_header Connection "upgrade";
212
+ proxy_set_header Host $host;
213
+ proxy_read_timeout 86400;
214
+ }
215
+
216
+ # Static files (if serving separately)
217
+ location /_astro/ {
218
+ proxy_pass http://opencodehub;
219
+ proxy_cache_valid 200 1d;
220
+ add_header Cache-Control "public, max-age=86400";
221
+ }
222
+
223
+ # Default location
224
+ location / {
225
+ proxy_pass http://opencodehub;
226
+ proxy_set_header Host $host;
227
+ proxy_set_header X-Real-IP $remote_addr;
228
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
229
+ proxy_set_header X-Forwarded-Proto $scheme;
230
+
231
+ # WebSocket support
232
+ proxy_http_version 1.1;
233
+ proxy_set_header Upgrade $http_upgrade;
234
+ proxy_set_header Connection "upgrade";
235
+ }
236
+}
237
+```
238
+
239
+---
240
+
241
+## Performance Tuning
242
+
243
+### Worker Configuration
244
+
245
+```nginx
246
+# /etc/nginx/nginx.conf
247
+
248
+worker_processes auto;
249
+worker_rlimit_nofile 65535;
250
+
251
+events {
252
+ worker_connections 4096;
253
+ use epoll;
254
+ multi_accept on;
255
+}
256
+
257
+http {
258
+ # Basic settings
259
+ sendfile on;
260
+ tcp_nopush on;
261
+ tcp_nodelay on;
262
+ keepalive_timeout 65;
263
+ types_hash_max_size 2048;
264
+
265
+ # Gzip compression
266
+ gzip on;
267
+ gzip_vary on;
268
+ gzip_proxied any;
269
+ gzip_comp_level 6;
270
+ gzip_types text/plain text/css text/xml application/json application/javascript;
271
+
272
+ # Include site configs
273
+ include /etc/nginx/sites-enabled/*;
274
+}
275
+```
276
+
277
+---
278
+
279
+## Common Issues
280
+
281
+### 413 Request Entity Too Large
282
+
283
+Increase `client_max_body_size`:
284
+
285
+```nginx
286
+location ~ ^/[^/]+/[^/]+\.git/ {
287
+ client_max_body_size 500M;
288
+ # ...
289
+}
290
+```
291
+
292
+### 504 Gateway Timeout
293
+
294
+Increase proxy timeouts for large operations:
295
+
296
+```nginx
297
+proxy_read_timeout 600s;
298
+proxy_send_timeout 600s;
299
+```
300
+
301
+### WebSocket Connection Drops
302
+
303
+Ensure WebSocket headers are set:
304
+
305
+```nginx
306
+proxy_http_version 1.1;
307
+proxy_set_header Upgrade $http_upgrade;
308
+proxy_set_header Connection "upgrade";
309
+```
310
+
311
+---
312
+
313
+## Load Balancing Multiple Instances
314
+
315
+```nginx
316
+upstream opencodehub {
317
+ least_conn;
318
+ server 10.0.0.1:3000 weight=5;
319
+ server 10.0.0.2:3000 weight=5;
320
+ server 10.0.0.3:3000 backup;
321
+ keepalive 64;
322
+}
323
+```
324
+
325
+---
326
+
327
+## Monitoring
328
+
329
+### Enable Nginx Status
330
+
331
+```nginx
332
+server {
333
+ listen 127.0.0.1:8080;
334
+
335
+ location /nginx_status {
336
+ stub_status on;
337
+ allow 127.0.0.1;
338
+ deny all;
339
+ }
340
+}
341
+```
342
+
343
+Access metrics: `curl http://127.0.0.1:8080/nginx_status`
344
+
345
+---
346
+
347
+## Next Steps
348
+
349
+- [Configure OpenCodeHub for production](/administration/deployment/)
350
+- [Set up monitoring with Grafana](/administration/monitoring/)
351
+- [Configure backups](/administration/deploy-docker/#backup--restore)
0
352
▼docs-site/src/content/docs/administration/deploy-podman.md
+324-0
1
+---
2
+title: "Deploy with Podman"
3
+description: "Deploy OpenCodeHub using Podman as a rootless, daemonless Docker alternative"
4
+---
5
+
6
+Podman is a drop-in replacement for Docker that runs without a daemon and supports rootless containers. This guide covers deploying OpenCodeHub with Podman.
7
+
8
+## Why Podman?
9
+
10
+| Feature | Docker | Podman |
11
+|---------|--------|--------|
12
+| Daemon | Required | Daemonless |
13
+| Root access | Required by default | Rootless by default |
14
+| Systemd integration | Limited | Native |
15
+| Security | Good | Better (no root daemon) |
16
+| CLI compatibility | Native | Docker-compatible |
17
+
18
+---
19
+
20
+## Prerequisites
21
+
22
+```bash
23
+# Install Podman (Ubuntu/Debian)
24
+sudo apt update
25
+sudo apt install -y podman podman-compose
26
+
27
+# Install Podman (Fedora/RHEL/CentOS)
28
+sudo dnf install -y podman podman-compose
29
+
30
+# Verify installation
31
+podman --version
32
+podman-compose --version
33
+```
34
+
35
+---
36
+
37
+## Quick Start
38
+
39
+```bash
40
+# Clone repository
41
+git clone https://github.com/swadhinbiswas/OpencodeHub.git
42
+cd OpenCodeHub
43
+
44
+# Copy environment
45
+cp .env.example .env
46
+
47
+# Start with Podman Compose
48
+podman-compose up -d
49
+
50
+# View logs
51
+podman-compose logs -f
52
+```
53
+
54
+---
55
+
56
+## Production Setup (Rootless)
57
+
58
+### 1. Create User and Directories
59
+
60
+```bash
61
+# Create dedicated user (optional but recommended)
62
+sudo useradd -r -m -d /opt/opencodehub opencodehub
63
+sudo -u opencodehub mkdir -p /opt/opencodehub/{data,postgres,redis}
64
+
65
+# Enable lingering (keeps user services running after logout)
66
+sudo loginctl enable-linger opencodehub
67
+```
68
+
69
+### 2. Create Pod Configuration
70
+
71
+Podman pods group containers like Kubernetes:
72
+
73
+```bash
74
+# Create a pod for all services
75
+podman pod create \
76
+ --name opencodehub \
77
+ --publish 3000:3000 \
78
+ --network bridge
79
+```
80
+
81
+### 3. Create Containers
82
+
83
+```bash
84
+# PostgreSQL
85
+podman run -d \
86
+ --pod opencodehub \
87
+ --name och-postgres \
88
+ -e POSTGRES_DB=opencodehub \
89
+ -e POSTGRES_USER=opencodehub \
90
+ -e POSTGRES_PASSWORD=securepassword \
91
+ -v /opt/opencodehub/postgres:/var/lib/postgresql/data:Z \
92
+ postgres:16-alpine
93
+
94
+# Redis
95
+podman run -d \
96
+ --pod opencodehub \
97
+ --name och-redis \
98
+ redis:7-alpine \
99
+ redis-server --requirepass redispassword
100
+
101
+# Wait for DB to be ready
102
+sleep 10
103
+
104
+# OpenCodeHub App
105
+podman run -d \
106
+ --pod opencodehub \
107
+ --name och-app \
108
+ --env-file /opt/opencodehub/.env \
109
+ -v /opt/opencodehub/data:/app/data:Z \
110
+ ghcr.io/swadhinbiswas/opencodehub:latest
111
+```
112
+
113
+### 4. Production .env
114
+
115
+```bash
116
+cat > /opt/opencodehub/.env << 'EOF'
117
+NODE_ENV=production
118
+PORT=3000
119
+SITE_URL=https://git.yourcompany.com
120
+
121
+# Secrets
122
+JWT_SECRET=your-64-char-hex-secret
123
+SESSION_SECRET=your-64-char-hex-secret
124
+INTERNAL_HOOK_SECRET=your-64-char-hex-secret
125
+
126
+# Database (localhost because same pod)
127
+DATABASE_DRIVER=postgres
128
+DATABASE_URL=postgresql://opencodehub:securepassword@localhost:5432/opencodehub
129
+
130
+# Redis
131
+REDIS_URL=redis://:redispassword@localhost:6379
132
+
133
+# Storage
134
+STORAGE_TYPE=local
135
+STORAGE_PATH=/app/data/repos
136
+EOF
137
+```
138
+
139
+---
140
+
141
+## Systemd Integration
142
+
143
+The key advantage of Podman is native systemd support.
144
+
145
+### Generate Systemd Units
146
+
147
+```bash
148
+# Generate unit files for the pod
149
+podman generate systemd --new --files --name opencodehub
150
+
151
+# Move to user systemd directory
152
+mkdir -p ~/.config/systemd/user/
153
+mv *.service ~/.config/systemd/user/
154
+
155
+# Reload and enable
156
+systemctl --user daemon-reload
157
+systemctl --user enable pod-opencodehub.service
158
+systemctl --user start pod-opencodehub.service
159
+```
160
+
161
+### Verify Service Status
162
+
163
+```bash
164
+systemctl --user status pod-opencodehub.service
165
+systemctl --user status container-och-app.service
166
+```
167
+
168
+### Auto-start on Boot
169
+
170
+```bash
171
+# Enable lingering for the user
172
+sudo loginctl enable-linger $(whoami)
173
+
174
+# Now services start at boot, not login
175
+```
176
+
177
+---
178
+
179
+## Using podman-compose
180
+
181
+If you prefer the Compose workflow:
182
+
183
+```yaml
184
+# podman-compose.yml
185
+version: '3.8'
186
+
187
+services:
188
+ app:
189
+ image: ghcr.io/swadhinbiswas/opencodehub:latest
190
+ ports:
191
+ - "3000:3000"
192
+ env_file:
193
+ - .env
194
+ volumes:
195
+ - ./data:/app/data:Z
196
+ depends_on:
197
+ - postgres
198
+ - redis
199
+
200
+ postgres:
201
+ image: postgres:16-alpine
202
+ environment:
203
+ POSTGRES_DB: opencodehub
204
+ POSTGRES_USER: opencodehub
205
+ POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
206
+ volumes:
207
+ - ./postgres:/var/lib/postgresql/data:Z
208
+
209
+ redis:
210
+ image: redis:7-alpine
211
+ command: redis-server --requirepass ${REDIS_PASSWORD}
212
+```
213
+
214
+```bash
215
+podman-compose up -d
216
+```
217
+
218
+:::note[SELinux Note]
219
+The `:Z` suffix on volume mounts is required for SELinux systems (Fedora, RHEL) to set the correct context.
220
+:::
221
+
222
+---
223
+
224
+## Networking
225
+
226
+### Using CNI Plugins
227
+
228
+```bash
229
+# Create a dedicated network
230
+podman network create opencodehub-net
231
+
232
+# Use in pod
233
+podman pod create \
234
+ --name opencodehub \
235
+ --network opencodehub-net \
236
+ --publish 3000:3000
237
+```
238
+
239
+### Exposing via Nginx
240
+
241
+```bash
242
+# On host, configure Nginx to proxy to localhost:3000
243
+sudo apt install nginx
244
+```
245
+
246
+See [Nginx Deployment Guide](/administration/deploy-nginx/) for configuration.
247
+
248
+---
249
+
250
+## Updating Containers
251
+
252
+```bash
253
+# Pull latest image
254
+podman pull ghcr.io/swadhinbiswas/opencodehub:latest
255
+
256
+# Recreate container
257
+podman stop och-app
258
+podman rm och-app
259
+podman run -d \
260
+ --pod opencodehub \
261
+ --name och-app \
262
+ --env-file /opt/opencodehub/.env \
263
+ -v /opt/opencodehub/data:/app/data:Z \
264
+ ghcr.io/swadhinbiswas/opencodehub:latest
265
+```
266
+
267
+### With Systemd
268
+
269
+```bash
270
+systemctl --user restart container-och-app.service
271
+```
272
+
273
+---
274
+
275
+## Troubleshooting
276
+
277
+### Permission Denied Errors
278
+
279
+```bash
280
+# Fix SELinux context
281
+sudo chcon -R -t container_file_t /opt/opencodehub/data
282
+
283
+# Or use :Z suffix on volumes
284
+-v /path:/container/path:Z
285
+```
286
+
287
+### Container Can't Reach Database
288
+
289
+```bash
290
+# In a pod, use localhost, not container names
291
+DATABASE_URL=postgresql://user:pass@localhost:5432/db
292
+```
293
+
294
+### Rootless Networking Issues
295
+
296
+```bash
297
+# Check slirp4netns is installed
298
+sudo apt install slirp4netns
299
+
300
+# Or use pasta (newer, faster)
301
+sudo apt install passt
302
+```
303
+
304
+---
305
+
306
+## Comparison with Docker
307
+
308
+| Command | Docker | Podman |
309
+|---------|--------|--------|
310
+| Run container | `docker run` | `podman run` |
311
+| Compose | `docker compose` | `podman-compose` |
312
+| Build image | `docker build` | `podman build` |
313
+| View logs | `docker logs` | `podman logs` |
314
+| Systemd units | Manual | `podman generate systemd` |
315
+
316
+Most Docker commands work identically with Podman.
317
+
318
+---
319
+
320
+## Next Steps
321
+
322
+- [Configure Nginx reverse proxy](/administration/deploy-nginx/)
323
+- [Set up monitoring](/administration/monitoring/)
324
+- [Backup and restore](/administration/deploy-docker/#backup--restore)
0
325
▼docs-site/src/content/docs/development/database-migrations.md
+38-0
1
+---
2
+title: "Database Migrations"
3
+description: "How to generate, apply, and inspect schema changes."
4
+---
5
+
6
+OpenCodeHub uses Drizzle for schema management.
7
+
8
+## Generate migrations
9
+
10
+```bash
11
+npm run db:generate
12
+```
13
+
14
+Generates SQL migrations from the current schema.
15
+
16
+## Apply migrations
17
+
18
+```bash
19
+npm run db:migrate
20
+```
21
+
22
+Applies pending migrations to the configured database.
23
+
24
+## Push schema (dev)
25
+
26
+```bash
27
+npm run db:push
28
+```
29
+
30
+Pushes schema changes directly (useful for local development).
31
+
32
+## Inspect schema
33
+
34
+```bash
35
+npm run db:studio
36
+```
37
+
38
+Opens the Drizzle Studio UI for inspecting tables.
0
39
▼docs-site/src/content/docs/development/local-dev-setup.md
+52-0
1
+---
2
+title: "Local Development Setup"
3
+description: "Set up a productive local dev environment for OpenCodeHub."
4
+---
5
+
6
+## Prerequisites
7
+
8
+- Node.js 18+ (project `engines` specify Node 20+ for runtime)
9
+- Git 2.30+
10
+- PostgreSQL/MySQL/SQLite (PostgreSQL recommended)
11
+
12
+## Install dependencies
13
+
14
+```bash
15
+npm install
16
+```
17
+
18
+## Environment
19
+
20
+```bash
21
+cp .env.example .env
22
+```
23
+
24
+Set required values like `JWT_SECRET`, `SESSION_SECRET`, `INTERNAL_HOOK_SECRET`, and `SITE_URL`.
25
+
26
+## Database
27
+
28
+```bash
29
+npm run db:push
30
+```
31
+
32
+## Seed admin user
33
+
34
+```bash
35
+bun run scripts/seed-admin.ts
36
+```
37
+
38
+## Run dev server
39
+
40
+```bash
41
+npm run dev
42
+```
43
+
44
+Open the app at `http://localhost:3000`.
45
+
46
+## Useful scripts
47
+
48
+- `npm run db:generate`
49
+- `npm run db:migrate`
50
+- `npm run db:studio`
51
+- `npm run lint`
52
+- `npm run test`
0
53
▼docs-site/src/content/docs/development/observability.md
+29-0
1
+---
2
+title: "Observability"
3
+description: "Tracing, metrics, and logging for OpenCodeHub."
4
+---
5
+
6
+OpenCodeHub ships with built‑in metrics and optional OpenTelemetry tracing.
7
+
8
+## Metrics (Prometheus)
9
+
10
+The server exposes Prometheus‑compatible metrics via:
11
+
12
+- `GET /api/metrics`
13
+
14
+Default metrics include HTTP duration and active runner counts.
15
+
16
+## Tracing (OpenTelemetry)
17
+
18
+Enable tracing by setting:
19
+
20
+```bash
21
+ENABLE_TRACING=true
22
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
23
+```
24
+
25
+The system uses the OpenTelemetry Node SDK and auto‑instrumentations to emit traces.
26
+
27
+## Logging
28
+
29
+Server logs are emitted using `pino`. In development, use `pino-pretty` or your preferred log formatter.
0
30
▼docs-site/src/content/docs/development/security-hardening.md
+28-0
1
+---
2
+title: "Security Hardening"
3
+description: "Security settings and production safeguards."
4
+---
5
+
6
+OpenCodeHub includes multiple built‑in security protections and requires strong secrets in production.
7
+
8
+## Required secrets
9
+
10
+Set strong values in `.env`:
11
+
12
+- `JWT_SECRET` (minimum 32 chars)
13
+- `SESSION_SECRET` (minimum 32 chars)
14
+- `INTERNAL_HOOK_SECRET`
15
+
16
+## Built‑in protections
17
+
18
+- Rate limiting for authentication
19
+- CSRF protection for state‑changing requests
20
+- Input validation for API payloads
21
+- Hook authentication for internal Git hooks
22
+
23
+## Recommendations
24
+
25
+- Rotate tokens periodically.
26
+- Use HTTPS in production.
27
+- Enable and monitor audit logs.
28
+- Restrict access to runner and storage secrets.
0
29
▼docs-site/src/content/docs/development/testing-pipelines.md
+29-0
1
+---
2
+title: "Testing & CI"
3
+description: "Run tests locally and understand the pipeline model."
4
+---
5
+
6
+## Test commands
7
+
8
+```bash
9
+npm run test
10
+npm run test:coverage
11
+```
12
+
13
+## Type checking & lint
14
+
15
+```bash
16
+npm run typecheck
17
+npm run lint
18
+```
19
+
20
+## CI pipelines
21
+
22
+OpenCodeHub supports GitHub Actions–compatible workflows under `.github/workflows/`. Use the Actions UI to inspect runs and view logs.
23
+
24
+For CLI access:
25
+
26
+```bash
27
+och ci list
28
+och ci view <run-id> --jobs
29
+```
0
30
▼docs-site/src/content/docs/features/ai-review.md
+81-88
2
2
title: "AI Code Review"
3
3
---
4
4
5
-
6
5
> Get instant, intelligent code reviews powered by GPT-4 or Claude
7
6
8
7
AI Code Review uses advanced language models to automatically review your pull requests, catching bugs, security issues, and code quality problems before they reach production.
27
26
### What AI Review Catches
28
27
29
28
🔒 **Security Vulnerabilities**
29
+
30
30
- SQL injection risks
31
31
- XSS (Cross-Site Scripting) vectors
32
32
- Authentication bypasses
34
34
- Insecure dependencies
35
35
36
36
⚡ **Performance Issues**
37
+
37
38
- N+1 database queries
38
39
- Inefficient algorithms (O(n²) → O(n log n))
39
40
- Memory leaks
40
41
- Blocking operations in async code
41
42
- Missing indexes
42
43
43
-📝 **Code Quality**
44
+📝 **Code Quality**
45
+
44
46
- Best practice violations
45
47
- Code smells (long functions, deep nesting)
46
48
- Incomplete error handling
48
50
- Poor naming conventions
49
51
50
52
🐛 **Potential Bugs**
53
+
51
54
- Null/undefined pointer exceptions
52
55
- Race conditions
53
56
- Off-by-one errors
81
84
1. Get API key from [platform.openai.com](https://platform.openai.com/api-keys)
82
85
83
86
2. Add to `.env`:
84
- ```env
85
- AI_PROVIDER=openai
86
- OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx
87
- OPENAI_MODEL=gpt-4-turbo-preview # or gpt-4, gpt-3.5-turbo
88
- ```
87
+
88
+```bash
89
+ AI_PROVIDER=openai
90
+ OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx
91
+ OPENAI_MODEL=gpt-4-turbo-preview # or gpt-4, gpt-3.5-turbo
92
+```
89
93
90
94
3. Restart OpenCodeHub
91
95
94
98
1. Get API key from [console.anthropic.com](https://console.anthropic.com/)
95
99
96
100
2. Add to `.env`:
97
- ```env
98
- AI_PROVIDER=anthropic
99
- ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx
100
- ANTHROPIC_MODEL=claude-3-opus-20240229 # or claude-3-sonnet
101
- ```
101
+
102
+```bash
103
+ AI_PROVIDER=anthropic
104
+ ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx
105
+ ANTHROPIC_MODEL=claude-3-opus-20240229 # or claude-3-sonnet
106
+```
102
107
103
108
3. Restart OpenCodeHub
104
109
105
110
**Option 3: Azure OpenAI**
106
111
107
-```env
112
+```bash
108
113
AI_PROVIDER=azure-openai
109
114
AZURE_OPENAI_API_KEY=xxxxx
110
115
AZURE_OPENAI_ENDPOINT=https://yourinstance.openai.azure.com/
114
119
**Option 4: Self-Hosted (Coming Soon)**
115
120
116
121
OpenCodeHub will support local models via Ollama:
117
-```env
122
+
123
+```bash
118
124
AI_PROVIDER=ollama
119
125
OLLAMA_ENDPOINT=http://localhost:11434
120
126
OLLAMA_MODEL=codellama:13b
173
179
```
174
180
175
181
You'll see:
182
+
176
183
- ✅ **Status check**: "AI Review: In Progress" → "AI Review: Complete"
177
184
- 💬 **Inline comments**: On specific lines with issues
178
185
- 📊 **Summary comment**: Overall assessment
182
189
Request AI review on-demand:
183
190
184
191
**Via Web UI:**
192
+
185
193
1. Open PR page
186
194
2. Click **"Request AI Review"** button
187
195
3. Wait ~30-60 seconds
188
196
4. Review appears as comments
189
197
190
198
**Via CLI:**
199
+
191
200
```bash
192
201
# Review specific PR
193
202
och review ai 125
194
203
195
-# Review with custom prompt
196
-och review ai 125 --focus security
197
-
198
-# Re-review after changes
199
-och review ai 125 --force
204
+# Wait for the review to complete
205
+och review ai 125 --wait
200
206
```
201
207
202
208
**Via API:**
209
+
203
210
```bash
204
-curl -X POST https://git.yourcompany.com/api/prs/125/ai-review \
211
+curl -X POST https://git.yourcompany.com/api/repos/OWNER/REPO/pulls/125/ai-review \
205
212
-H "Authorization: Bearer $TOKEN"
206
213
```
207
214
213
220
214
221
AI assigns severity to each finding:
215
222
216
-| Severity | Icon | Meaning | Example |
217
-|----------|------|---------|---------|
218
-| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key |
219
-| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling |
220
-| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification |
223
+| Severity | Icon | Meaning | Example |
224
+| --------------- | ---------- | -------------------------------------- | ----------------------------------------- |
225
+| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key |
226
+| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling |
227
+| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification |
221
228
222
229
### Example Review Comment
223
230
224
-```markdown
231
+````markdown
225
232
🔴 CRITICAL: SQL Injection Vulnerability
226
233
227
-**File:** `src/api/users.ts`
234
+**File:** `src/api/users.ts`
228
235
**Line:** 45
229
236
230
-**Issue:**
237
+**Issue:**
231
238
User input is directly concatenated into SQL query without sanitization.
232
239
233
240
**Current Code:**
241
+
234
242
```typescript
235
243
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
236
244
```
245
+````
237
246
238
247
**Risk:**
239
248
An attacker could inject malicious SQL:
249
+
240
250
```
241
251
userEmail = "' OR '1'='1"
242
252
→ Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1'
245
255
246
256
**Fix:**
247
257
Use parameterized queries or ORM:
258
+
248
259
```typescript
249
260
const query = `SELECT * FROM users WHERE email = ?`;
250
261
const results = await db.query(query, [userEmail]);
254
265
```
255
266
256
267
**References:**
268
+
257
269
- [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection)
258
270
- [Node.js SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
259
-```
271
+
272
+````
260
273
261
274
### Summary Comment
262
275
288
301
- ✅ Comprehensive TypeScript types
289
302
290
303
**Estimated fix time:** 2-3 hours
291
-```
304
+````
292
305
293
306
---
294
307
316
329
317
330
# Focus areas (weighted 1-10)
318
331
focus:
319
- security: 10 # Highest priority
332
+ security: 10 # Highest priority
320
333
performance: 8
321
334
bugs: 8
322
335
code_quality: 5
326
339
report:
327
340
critical: always
328
341
warning: always
329
- info: on_request # Only if --verbose flag used
342
+ info: on_request # Only if --verbose flag used
330
343
331
344
# Custom rules
332
345
custom_rules:
334
347
pattern: 'console\\.log\\('
335
348
severity: warning
336
349
message: "Remove console.log before merging"
337
-
350
+
338
351
- name: "Require error handling"
339
352
pattern: 'await.*\\(.*\\)'
340
353
check: must_have_try_catch
347
360
2. Database query efficiency
348
361
3. Input validation
349
362
4. Proper error handling
350
-
363
+
351
364
Our coding standards:
352
365
- TypeScript strict mode
353
366
- Functional programming preferred
357
370
358
371
### Custom Prompts
359
372
360
-**Focus on specific aspects:**
361
-
362
-```bash
363
-# Security-focused review
364
-och review ai 125 --focus security
365
-
366
-# Performance-focused review
367
-och review ai 125 --focus performance
368
-
369
-# Architecture review
370
-och review ai 125 --prompt "Review system architecture and design patterns"
371
-```
373
+Use **Settings → AI Review Rules** to add repository‑specific prompts and checks. Rules are applied automatically when AI reviews run.
372
374
373
375
### Language-Specific Reviews
374
376
389
391
AI review costs depend on provider and model:
390
392
391
393
**OpenAI (GPT-4 Turbo):**
394
+
392
395
- ~$0.01 per 1K tokens (input)
393
396
- ~$0.03 per 1K tokens (output)
394
397
- **Typical PR**: 500-2000 tokens = **$0.02-0.10 per review**
395
398
396
399
**Anthropic (Claude 3 Opus):**
400
+
397
401
- ~$0.015 per 1K tokens (input)
398
402
- ~$0.075 per 1K tokens (output)
399
403
- **Typical PR**: **$0.03-0.15 per review**
400
404
401
405
**Example monthly cost:**
406
+
402
407
- 50 PRs/month × $0.05/review = **$2.50/month**
403
408
- 500 PRs/month × $0.05/review = **$25/month**
404
409
405
410
### Cost Optimization
406
411
407
-**1. Limit file count:**
408
-```yaml
409
-max_files_per_review: 10 # Review only 10 largest changed files
410
-```
412
+To reduce cost:
411
413
412
-**2. Use cheaper models for simple changes:**
413
-```yaml
414
-rules:
415
- - if: changed_files < 3
416
- model: gpt-3.5-turbo # 10x cheaper
417
- - if: changed_files >= 3
418
- model: gpt-4-turbo
419
-```
414
+- Keep PRs small and focused.
415
+- Avoid very large diffs in a single review.
420
416
421
417
**3. Skip test files:**
418
+
422
419
```yaml
423
420
exclude:
424
421
- "**/*.test.*"
426
423
```
427
424
428
425
**4. Review only on-request:**
426
+
429
427
```yaml
430
-trigger: manual # Don't auto-review every push
428
+trigger: manual # Don't auto-review every push
431
429
```
432
430
433
431
**5. Use severity threshold:**
432
+
434
433
```yaml
435
-severity_threshold: warning # Skip "info" level comments
434
+severity_threshold: warning # Skip "info" level comments
436
435
```
437
436
438
437
---
442
441
### 💡 When to Use AI Review
443
442
444
443
**✅ Good Use Cases:**
444
+
445
445
- Security-sensitive code (auth, payments)
446
446
- Complex algorithms
447
447
- Database queries
449
449
- Third-party integrations
450
450
451
451
**❌ Less Useful:**
452
+
452
453
- Generated code
453
454
- Configuration files
454
455
- Simple refactoring
457
458
### 💡 Responding to AI Feedback
458
459
459
460
**1. Don't auto-fix everything**
461
+
460
462
- AI can be wrong
461
463
- Context matters
462
464
- Use judgment
463
465
464
466
**2. Engage in discussion**
467
+
465
468
```markdown
466
-@ai-review That's a good catch, but in this case we're using a
469
+@ai-review That's a good catch, but in this case we're using a
467
470
trusted source and the data is already sanitized earlier in the pipeline.
468
471
469
472
See line 23 where we validate input.
470
473
```
471
474
472
475
**3. Track false positives**
476
+
473
477
```yaml
474
478
# .opencodehub/ai-review.yml
475
479
ignore:
489
493
```
490
494
491
495
This workflow:
496
+
492
497
- Reduces human reviewer burden
493
498
- Catches more bugs
494
499
- Faster feedback cycles
502
507
**Cause:** Large PR (many files/lines).
503
508
504
509
**Solution:**
510
+
505
511
```bash
506
512
# Check review status
507
513
och review status 125
508
514
509
-# Cancel and retry
510
-och review cancel 125
511
-och review ai 125 --max-files 5
515
+# Retry the review
516
+och review ai 125
512
517
```
513
518
514
519
### "AI review failed"
515
520
516
521
**Causes:**
522
+
517
523
1. API key invalid/expired
518
524
2. Rate limit exceeded
519
525
3. Timeout (very large PR)
520
526
521
527
**Solutions:**
528
+
522
529
```bash
523
530
# 1. Verify API key
524
531
echo $OPENAI_API_KEY
527
534
curl https://api.openai.com/v1/usage \
528
535
-H "Authorization: Bearer $OPENAI_API_KEY"
529
536
530
-# 3. Split large PR or review manually:
531
-och review ai 125 --files src/api/users.ts src/api/auth.ts
537
+# 3. Split large PR or review manually.
532
538
```
533
539
534
540
### "Too many false positives"
535
541
536
-**Solution:** Tune detection thresholds:
537
-
538
-```yaml
539
-# .opencodehub/ai-review.yml
540
-sensitivity:
541
- security: high # Strict
542
- performance: medium
543
- code_quality: low # Lenient
544
-```
542
+**Solution:** Use **Settings → AI Review Rules** to refine prompts and reduce false positives.
545
543
546
544
### "Missing obvious bugs"
547
545
548
546
**Cause:** AI missed context or edge case.
549
547
550
-**Solution:** Add custom instructions:
551
-
552
-```yaml
553
-instructions: >
554
- Our codebase uses a custom ORM. When reviewing database code:
555
- 1. `db.raw()` is already parameterized
556
- 2. All user input goes through `sanitize()` middleware
557
- 3. Check for missing `await` on async functions
558
-```
548
+**Solution:** Add custom instructions in **Settings → AI Review Rules** so reviews incorporate your conventions.
559
549
560
550
---
561
551
576
566
run: och review ai ${{ github.event.pull_request.number }}
577
567
env:
578
568
OCH_TOKEN: ${{ secrets.OCH_TOKEN }}
579
-
569
+
580
570
- name: Check for critical issues
581
571
run: |
582
- CRITICAL=$(och review status ${{ github.event.pull_request.number }} --json | jq '.critical_count')
583
- if [ "$CRITICAL" -gt 0 ]; then
572
+ REVIEW=$(curl -s \
573
+ -H "Authorization: Bearer $OCH_TOKEN" \
574
+ https://git.yourcompany.com/api/repos/OWNER/REPO/pulls/${{ github.event.pull_request.number }}/ai-review/latest)
575
+ SEVERITY=$(echo "$REVIEW" | jq -r '.data.overallSeverity')
576
+ if [ "$SEVERITY" = "critical" ]; then
584
577
echo "❌ Critical issues found"
585
578
exit 1
586
579
fi
592
585
593
586
- [Stacked Pull Requests](stacked-prs.md)
594
587
- [Smart Merge Queue](merge-queue.md)
595
-- [CI/CD Integration](ci-cd.md)
588
+- [CI/CD Actions](ci-actions.md)
596
589
597
590
---
598
591
599
592
▼docs-site/src/content/docs/features/automations.md
+59-0
1
+---
2
+title: "Automation Rules"
3
+description: "Automate PR workflows with triggers, conditions, and actions."
4
+---
5
+
6
+Automation rules let you react to repository events and apply consistent workflow policies.
7
+
8
+## Triggers
9
+
10
+Supported trigger types include:
11
+
12
+- `pr_opened`
13
+- `pr_updated`
14
+- `pr_review_requested`
15
+- `pr_approved`
16
+- `pr_changes_requested`
17
+- `pr_merged`
18
+- `pr_closed`
19
+- `ci_passed`
20
+- `ci_failed`
21
+- `label_added`
22
+- `label_removed`
23
+- `comment_added`
24
+- `stack_created`
25
+- `stack_updated`
26
+
27
+## Actions
28
+
29
+Supported actions include:
30
+
31
+- `add_label`
32
+- `remove_label`
33
+- `assign_reviewer`
34
+- `assign_user`
35
+- `add_to_merge_queue`
36
+- `remove_from_merge_queue`
37
+- `add_comment`
38
+- `trigger_ai_review`
39
+- `notify_slack`
40
+- `close_pr`
41
+- `request_changes`
42
+
43
+## Example rules
44
+
45
+- When a PR is opened, add `needs-review` and trigger AI review.
46
+- When CI passes, add the PR to the merge queue with `squash`.
47
+- When a label is added, assign a reviewer.
48
+
49
+## Manage rules
50
+
51
+You can configure rules in the UI under **Settings → Automations** or via the CLI:
52
+
53
+```bash
54
+och automate list
55
+och automate create
56
+och automate enable "AI Review on PR"
57
+och automate disable "AI Review on PR"
58
+och automate delete "AI Review on PR"
59
+```
0
60
▼docs-site/src/content/docs/features/ci-actions.md
+50-0
1
+---
2
+title: "CI/CD Actions"
3
+description: "GitHub Actions–compatible workflows and self‑hosted runners."
4
+---
5
+
6
+OpenCodeHub supports GitHub Actions–style workflows defined in `.github/workflows/*.yml`.
7
+
8
+## Example workflow
9
+
10
+```yaml
11
+name: CI
12
+on: [push, pull_request]
13
+jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - run: npm install
19
+ - run: npm test
20
+```
21
+
22
+## View runs
23
+
24
+- Global runs: `/actions`
25
+- Repo runs: `/owner/repo/actions`
26
+
27
+## Runner settings
28
+
29
+Set these environment flags to enable runners:
30
+
31
+```bash
32
+RUNNER_ENABLED=true
33
+RUNNER_DOCKER_SOCKET=/var/run/docker.sock
34
+RUNNER_CONCURRENT_JOBS=2
35
+RUNNER_TIMEOUT=3600
36
+```
37
+
38
+And ensure actions are enabled:
39
+
40
+```bash
41
+ENABLE_ACTIONS=true
42
+```
43
+
44
+## CLI workflows
45
+
46
+```bash
47
+och ci list --branch main
48
+och ci view <run-id> --jobs
49
+och ci trace <job-id> --follow
50
+```
0
51
▼docs-site/src/content/docs/features/cli.md
+20-0
1
+---
2
+title: "CLI Workflows"
3
+description: "Stack‑first workflows, reviews, and automation from the terminal."
4
+---
5
+
6
+The `och` CLI is the fastest way to manage stacks, PRs, and queue workflows.
7
+
8
+## Highlights
9
+
10
+- Stack creation and submission (`och stack create`, `och stack submit`)
11
+- Merge queue operations (`och queue add`, `och queue list`)
12
+- AI review and approvals (`och review ai`, `och review approve`)
13
+- Repository‑level automations (`och automate create`)
14
+- Metrics and insights (`och metrics show`, `och insights team`)
15
+
16
+## Start here
17
+
18
+- [reference/cli-overview](../reference/cli-overview)
19
+- [reference/cli-stack-workflows](../reference/cli-stack-workflows)
20
+- [reference/cli-merge-queue](../reference/cli-merge-queue)
0
21
▼docs-site/src/content/docs/features/developer-metrics.md
+38-0
1
+---
2
+title: "Developer Metrics"
3
+description: "Track PR velocity, review efficiency, and team health."
4
+---
5
+
6
+OpenCodeHub tracks PR and review metrics so teams can measure flow and spot bottlenecks.
7
+
8
+## What’s tracked
9
+
10
+- PRs opened and merged
11
+- Time to first review
12
+- Time to merge
13
+- Review rounds (number of change‑request cycles)
14
+- Review counts (approvals and changes requested)
15
+- PR size (lines added/removed, files changed)
16
+- Stack awareness (stack position and stacked vs non‑stacked)
17
+
18
+## Where metrics appear
19
+
20
+- **User insights**: your personal throughput and review cadence.
21
+- **Repository metrics**: activity trends, top authors, and top reviewers.
22
+
23
+## CLI access
24
+
25
+```bash
26
+och metrics show --weeks 4
27
+och metrics show --repo --weeks 8
28
+
29
+och insights show --period 4w
30
+och insights team --period 4w --limit 10
31
+och insights repo
32
+```
33
+
34
+## Tips
35
+
36
+- Use smaller PRs to reduce time to first review.
37
+- Automate AI review for early feedback.
38
+- Monitor review rounds to detect flaky CI or unclear PR scope.
0
39
▼docs-site/src/content/docs/features/inbox.md
+33-0
1
+---
2
+title: "PR Inbox"
3
+description: "Organize reviews with inbox sections and filters."
4
+---
5
+
6
+The PR inbox is a focused view for reviewing and triaging pull requests.
7
+
8
+## Inbox sections
9
+
10
+Sections group PRs using reusable filters. Default filters include:
11
+
12
+- **Needs my review**
13
+- **Authored by me**
14
+- **CI passing**
15
+- **Ready to merge**
16
+- **Has conflicts**
17
+
18
+## CLI support
19
+
20
+```bash
21
+och inbox list
22
+och inbox section list
23
+och inbox section create
24
+och inbox section delete "Needs Review"
25
+```
26
+
27
+Use `och inbox list --section "Section Name"` to filter by a section.
28
+
29
+## Best practices
30
+
31
+- Keep a **Needs my review** section pinned.
32
+- Create a **Ready to merge** section for final checks.
33
+- Use **Has conflicts** to surface blocked work quickly.
0
34
▼docs-site/src/content/docs/features/merge-queue.md
+73-191
2
2
title: "Smart Merge Queue"
3
3
---
4
4
5
-
6
5
> Automate merging with stack-aware CI optimization and conflict resolution
7
6
8
7
The Smart Merge Queue automatically merges approved pull requests in the correct order, running CI efficiently and handling rebases automatically.
9
8
10
-## �� Table of Contents
9
+## Table of Contents
11
10
12
11
- [Overview](#overview)
13
12
- [How It Works](#how-it-works)
24
23
### The Problem
25
24
26
25
Manual merging is:
26
+
27
27
- ⏰ **Slow** - Wait for CI, merge, repeat
28
28
- 🐛 **Error-prone** - Merge conflicts, wrong order
29
29
- 💸 **Expensive** - Run CI for every PR individually
33
33
34
34
Smart Merge Queue:
35
35
36
-✅ **Automatic Merging** - Set it and forget it
37
-✅ **Stack-Aware** - Merges in dependency order
38
-✅ **CI Optimization** - Batches compatible PRs
39
-✅ **Conflict Resolution** - Auto-rebases or alerts
40
-✅ **Priority Handling** - Hotfixes jump the queue
36
+✅ **Automatic Merging** - Set it and forget it
37
+✅ **Stack-Aware** - Merges in dependency order
38
+✅ **CI Validation** - Ensures checks pass for latest commits
39
+✅ **Priority Handling** - Higher priority merges sooner
41
40
42
41
### Real-World Impact
43
42
44
43
**Without Queue:**
44
+
45
45
```
46
-PR #123 → Wait for CI (5 min) → Merge →
46
+PR #123 → Wait for CI (5 min) → Merge →
47
47
PR #124 → Rebase → Wait for CI (5 min) → Merge →
48
48
PR #125 → Rebase → Wait for CI (5 min) → Merge
49
49
Total: ~20 minutes, 3 CI runs
50
50
```
51
51
52
52
**With Queue:**
53
+
53
54
```
54
55
Add #123, #124, #125 to queue →
55
56
Queue detects dependencies →
56
57
Merges #123 →
57
-Auto-rebases #124 →
58
-Batches #124 + #125 for CI (if compatible) →
59
-Merges both
60
-Total: ~8 minutes, 1-2 CI runs 🎉
58
+Validates CI for #124 →
59
+Merges #124 →
60
+Validates CI for #125 →
61
+Merges #125
62
+Total: fewer manual steps and a consistent, ordered merge flow 🎉
61
63
```
62
64
63
65
---
74
76
3. Queue checks:
75
77
- All approvals? ✓
76
78
- CI passing? ✓
77
- - Conflicts? Auto-rebase
79
+ - Conflicts? Block and report
78
80
- Dependencies? Order correctly
79
81
↓
80
82
4. Run final CI check
105
107
All automatic!
106
108
```
107
109
108
-### CI Optimization
110
+### CI Validation
109
111
110
-**Batching:**
111
-```
112
-PR #126: Fix typo in docs (no code changes)
113
-PR #127: Update README
114
-PR #128: Fix CSS bug
115
-
116
-Queue batches #126 + #127 (docs only)
117
-→ Single CI run
118
-→ Merge both together
119
-→ 50% CI cost savings!
120
-```
112
+Before merging, the queue verifies:
121
113
122
-**Smart Scheduling:**
123
-- Long CI jobs run in parallel
124
-- Short jobs batch together
125
-- Stack dependencies respected
114
+- All required approvals are present
115
+- Latest CI results for the PR’s head SHA are successful
116
+- Stack dependencies are merged in order
126
117
127
118
---
128
119
149
140
# Add single PR
150
141
och queue add 125
151
142
152
-# Add multiple PRs
153
-och queue add 123 124 125
154
-
155
-# Add with priority (hotfix)
156
-och queue add 126 --priority high
157
-
158
-# Add when approved
159
-och queue add 127 --when approved
143
+# Add with numeric priority (higher = sooner)
144
+och queue add 126 --priority 5
160
145
```
161
146
162
147
**Via API:**
163
148
164
149
```bash
165
-curl -X POST https://git.yourcompany.com/api/queue \
150
+curl -X POST https://git.yourcompany.com/api/repos/OWNER/REPO/merge-queue \
166
151
-H "Authorization: Bearer $TOKEN" \
167
- -d '{"pr_id": 125, "priority": "normal"}'
152
+ -d '{"pullRequestNumber": 125, "priority": 0, "mergeMethod": "merge"}'
168
153
```
169
154
170
155
### Viewing Queue Status
173
158
Navigate to `/queue` or click "Merge Queue" in repository navigation
174
159
175
160
**CLI:**
161
+
176
162
```bash
177
163
# View entire queue
178
164
och queue list
179
-
180
-# Output:
181
-# Position PR Title Status ETA
182
-# 1 #125 Login UI 🏃 Running CI 2 min
183
-# 2 #124 Auth service ⏳ Waiting 5 min
184
-# 3 #123 Database schema ✅ Ready 7 min
185
-# 4 #126 Hotfix: XSS 🔥 Priority 1 min
186
-
187
-# Watch queue in real-time
188
-och queue watch
189
165
```
190
166
191
167
**API:**
168
+
192
169
```bash
193
-curl https://git.yourcompany.com/api/queue \
170
+curl https://git.yourcompany.com/api/repos/OWNER/REPO/merge-queue \
194
171
-H "Authorization: Bearer $TOKEN"
195
172
```
196
173
198
175
199
176
## Queue Management
200
177
201
-### Priority Levels
178
+### Priority
202
179
203
-| Priority | Use Case | Behavior |
204
-|----------|----------|----------|
205
-| 🔥 **Critical** | Production outage | Jumps to front, blocks other merges |
206
-| ⚡ **High** | Security fix, hotfix | Merges before normal PRs |
207
-| 📄 **Normal** | Regular features | Default, FIFO order |
208
-| 🐌 **Low** | Refactoring, docs | Merges when queue is empty |
180
+Priority is numeric. Higher values are processed sooner. Default is `0`.
209
181
210
-**Setting priority:**
211
182
```bash
212
-# CLI
213
-och queue add 126 --priority critical
214
-
215
-# Web UI: Select priority dropdown when adding to queue
183
+och queue add 126 --priority 5
216
184
```
217
185
218
186
### Removing from Queue
220
188
```bash
221
189
# Remove specific PR
222
190
och queue remove 125
223
-
224
-# Remove all your PRs
225
-och queue remove --mine
226
-
227
-# Clear entire queue (admin only)
228
-och queue clear
229
191
```
230
192
231
193
### Pausing the Queue
232
194
233
-```bash
234
-# Pause (emergency)
235
-och queue pause --reason "Investigating production issue"
236
-
237
-# Resume
238
-och queue resume
239
-```
195
+Pausing and resuming the queue is managed in the web UI.
240
196
241
197
---
242
198
244
200
245
201
### Repository Settings
246
202
247
-**Repository → Settings → Merge Queue**
203
+Queue behavior is enforced by:
248
204
249
-```yaml
250
-# Merge strategy
251
-merge_method: squash # or merge, rebase
252
-
253
-# CI requirements
254
-require_ci: true
255
-required_checks:
256
- - "test"
257
- - "lint"
258
- - "build"
259
-
260
-# Approval requirements
261
-require_approvals: 1
262
-dismiss_stale_reviews: true
263
-
264
-# Batching
265
-enable_batching: true
266
-batch_window: 5m # Wait 5 min to batch PRs
267
-max_batch_size: 3 # Max 3 PRs per batch
268
-
269
-# Rebasing
270
-auto_rebase: true # Auto-rebase on conflicts
271
-rebase_strategy: merge # or rebase, squash
272
-
273
-# Priorities
274
-allow_priority_override: true # Let users set priority
275
-max_queue_size: 50 # Max PRs in queue
276
-```
205
+- **Branch protection rules** (required approvals, required checks)
206
+- **CI status** for the PR’s latest commit
207
+- **Per‑entry merge method** chosen when adding to the queue
208
+
209
+Configuration is managed in the repository settings UI.
277
210
278
211
### Branch Protection Rules
279
212
288
221
- lint
289
222
required_approvals: 1
290
223
allow_force_push: false
291
- allow_queue_bypass: false # Even admins use queue!
224
+ allow_queue_bypass: false # Even admins use queue!
292
225
```
293
226
294
227
### Webhook Notifications
297
230
298
231
```bash
299
232
# Configure webhook
300
-curl -X POST https://git.yourcompany.com/api/webhooks \
233
+curl -X POST https://git.yourcompany.com/api/repos/OWNER/REPO/webhooks \
301
234
-d '{
302
235
"url": "https://yourapp.com/webhook",
303
236
"events": ["merge_queue.merged", "merge_queue.failed"],
306
239
```
307
240
308
241
**Payload:**
242
+
309
243
```json
310
244
{
311
245
"event": "merge_queue.merged",
318
252
"position": 1,
319
253
"wait_time_seconds": 180
320
254
},
321
- "timestamp": "2024-01-01T12:00:00Z"
255
+ "timestamp": "2024-01-01T12:00:00Z"
322
256
}
323
257
```
324
258
329
263
### 💡 When to Use the Queue
330
264
331
265
**✅ Always use for:**
266
+
332
267
- Feature branches → main
333
268
- Release branches
334
269
- Any PR affecting production
335
270
336
271
**❌ Don't use for:**
272
+
337
273
- Draft PRs
338
-- WIP branches
274
+- WIP branches
339
275
- Experimental features
340
276
341
277
### 💡 Optimizing Queue Performance
342
278
343
279
**1. Fast CI:**
344
-```yaml
345
-# Run quick tests in queue
346
-queue_ci:
347
- - unit_tests # 2 min
348
-
349
-# Run slow tests post-merge
350
-post_merge_ci:
351
- - integration_tests # 10 min
352
- - e2e_tests # 15 min
353
-```
280
+Keep queue checks short and focused. Run slower or optional suites after merge.
354
281
355
-**2. Parallel CIs:**
356
-```yaml
357
-# Run multiple jobs in parallel
358
-ci:
359
- - test (3 min)
360
- - lint (1 min)
361
- - build (2 min)
362
-# Total: 3 min (not 6 min!)
363
-```
364
-
365
-**3. Smart batching:**
366
-```bash
367
-# Group related PRs
368
-och queue add 123 124 125 --batch-id feature-auth
282
+**2. Parallel CI:**
283
+Split workflows into parallel jobs to reduce total wall‑clock time.
369
284
370
-# Queue runs CI once for entire batch
371
-```
285
+**3. Small PRs:**
286
+Smaller, focused PRs reduce queue time and review delays.
372
287
373
288
### 💡 Handling Conflicts
374
289
375
-**Automatic resolution:**
376
-```yaml
377
-auto_rebase: true
378
-rebase_strategy: merge # Creates merge commit
379
-
380
-# Or:
381
-rebase_strategy: rebase # Cleaner history
382
-```
383
-
384
290
**Manual resolution:**
291
+
385
292
```bash
386
-# If auto-rebase fails, you'll get notification
293
+# If a conflict blocks the queue, you'll get a notification
387
294
# Fix locally:
388
295
git checkout feature-branch
389
296
git fetch origin
397
304
### 💡 Queue Monitoring
398
305
399
306
**Set up alerts:**
307
+
400
308
```yaml
401
309
# Slack notification if queue stalled
402
310
alerts:
403
311
- type: queue_stalled
404
312
threshold: 30m
405
313
channel: "#eng-deployments"
406
-
314
+
407
315
- type: merge_failed
408
316
channel: "#eng-alerts"
409
317
```
410
318
411
319
**Dashboard example:**
320
+
412
321
```bash
413
322
# View queue health
414
323
och queue stats
428
337
### "PR stuck in queue"
429
338
430
339
**Causes:**
340
+
431
341
1. CI failing
432
342
2. Waiting for approval
433
343
3. Merge conflict
434
344
4. Dependency not merged yet
435
345
436
346
**Solutions:**
347
+
437
348
```bash
438
349
# Check status
439
350
och queue status 125
452
363
**Cause:** Complex merge conflicts.
453
364
454
365
**Solution:**
366
+
455
367
```bash
456
368
# Remove from queue
457
369
och queue remove 125
469
381
### "Queue not processing"
470
382
471
383
**Causes:**
472
-1. Queue paused
473
-2. CI service down
474
-3. Rate limit reached
475
384
476
-**Solutions:**
477
-```bash
478
-# Check queue status
479
-och queue info
385
+1. CI checks failing or still running
386
+2. Required approvals missing
387
+3. The PR is not in the queue
480
388
481
-# Resume if paused
482
-och queue resume
389
+**Solutions:**
483
390
484
-# Check CI status
485
-curl https://ci-service/status
391
+```bash
392
+# Check the queue
393
+och queue list
486
394
487
-# View queue system logs (admin)
488
-och admin logs queue
395
+# Inspect the PR
396
+och pr view 125
489
397
```
490
398
491
399
### "Wrong merge order"
492
400
493
-**Cause:** Dependencies not detected.
401
+**Cause:** Stack dependency is missing or incorrect.
494
402
495
403
**Solution:**
496
-```bash
497
-# Manually set dependency
498
-och pr edit 125 --depends-on 124
499
404
500
-# Queue will automatically reorder
501
-```
405
+- Ensure PRs are created as a stack (base PR first, then dependents).
406
+- Re‑submit the stack with the CLI if needed.
502
407
503
408
---
504
409
505
-## Advanced Features
410
+## Roadmap ideas
506
411
507
-### Merge Train
508
-
509
-For high-velocity teams:
510
-
511
-```yaml
512
-# Merge up to 5 PRs simultaneously
513
-merge_train:
514
- enabled: true
515
- max_concurrent: 5
516
-
517
-# Creates temporary integration branches
518
-# Runs CI in parallel
519
-# Merges in rapid succession
520
-```
521
-
522
-### Scheduled Merges
523
-
524
-```bash
525
-# Merge during business hours only
526
-och queue add 125 --schedule "Mon-Fri 9am-5pm EST"
527
-
528
-# Merge during maintenance window
529
-och queue add 126 --schedule "Sat 2am-4am EST"
530
-```
412
+Future improvements may include merge trains and scheduled merges. If these are important for your team, open an issue with your use case.
531
413
532
414
### Queue Hooks
533
415
541
423
// Update JIRA ticket
542
424
// Trigger deployment
543
425
},
544
-
426
+
545
427
afterMerge(pr) {
546
428
// Create release notes
547
429
// Update documentation
548
- }
430
+ },
549
431
};
550
432
```
551
433
552
434
▼docs-site/src/content/docs/features/notifications.md
+55-0
1
+---
2
+title: "Notifications"
3
+description: "Stay informed across in‑app, email, Slack, and browser channels."
4
+---
5
+
6
+OpenCodeHub supports multi‑channel notifications with per‑event preferences.
7
+
8
+## Channels
9
+
10
+- In‑app notifications
11
+- Email
12
+- Slack
13
+- Browser push
14
+
15
+## Event types
16
+
17
+Common events include:
18
+
19
+- `mention`
20
+- `assign`
21
+- `review_request`
22
+- `review_submitted`
23
+- `pr_approved`
24
+- `pr_changes_requested`
25
+- `pr_merged`
26
+- `pr_closed`
27
+- `comment`
28
+- `ci_passed`
29
+- `ci_failed`
30
+- `merge_queue`
31
+- `stack_update`
32
+- `ai_review`
33
+- `star`
34
+- `watching`
35
+
36
+## Quiet hours & digest
37
+
38
+You can define quiet hours and digest schedules to reduce noise. These settings are available in user notification preferences and can be managed via the CLI.
39
+
40
+```bash
41
+och notify settings --show
42
+```
43
+
44
+## CLI usage
45
+
46
+```bash
47
+och notify list --unread
48
+och notify read --all
49
+```
50
+
51
+## Tips
52
+
53
+- Turn on Slack for `review_request` and `merge_queue` events.
54
+- Use quiet hours to avoid off‑hours alerting.
55
+- Combine with automation rules for consistent team practices.
0
56
▼docs-site/src/content/docs/guides/storage-adapters.md
+130-7
2
2
title: "Storage Adapters"
3
3
---
4
4
5
-
6
5
OpenCodeHub supports a pluggable storage system, allowing you to store repository data (git objects, LFS files, artifacts) on various backends.
7
6
8
7
## Supported Adapters
19
18
Data is stored in the `data/` directory relative to the application root.
20
19
21
20
**Configuration:**
22
-```env
21
+
22
+```bash
23
23
STORAGE_TYPE=local
24
24
STORAGE_PATH=./data/storage # Optional, default is ./data
25
25
```
31
31
Store data in any S3-compatible bucket. This is recommended for production scalablity.
32
32
33
33
**Configuration:**
34
-```env
34
+
35
+```bash
35
36
STORAGE_TYPE=s3
36
37
STORAGE_BUCKET=my-opencodehub-bucket
37
38
STORAGE_REGION=us-east-1 # or auto
43
44
### Examples
44
45
45
46
**MinIO (Self-hosted):**
46
-```env
47
+
48
+```bash
47
49
STORAGE_ENDPOINT=http://minio:9000
48
50
STORAGE_REGION=us-east-1
49
51
S3_FORCE_PATH_STYLE=true
50
52
```
51
53
52
54
**Cloudflare R2:**
53
-```env
55
+
56
+```bash
54
57
STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
55
58
STORAGE_REGION=auto
56
59
```
69
72
70
73
### Configuration
71
74
72
-```env
75
+```bash
73
76
STORAGE_TYPE=gdrive
74
77
GOOGLE_CLIENT_ID=your-client-id
75
78
GOOGLE_CLIENT_SECRET=your-client-secret
78
81
```
79
82
80
83
**How to get Credentials:**
84
+
81
85
1. Go to [Google Cloud Console](https://console.cloud.google.com).
82
86
2. Create a project -> Enable **Google Drive API**.
83
87
3. Create **OAuth Client ID**.
88
92
## 🔷 Azure Blob Storage
89
93
90
94
**Configuration:**
91
-```env
95
+
96
+```bash
92
97
STORAGE_TYPE=azure
93
98
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
94
99
AZURE_CONTAINER_NAME=opencodehub
95
100
```
101
+
102
+---
103
+
104
+## ⚠️ Performance Considerations
105
+
106
+:::caution[Important for Production]
107
+Object storage (S3/R2/MinIO) introduces **significant latency** compared to local disk storage. This is a fundamental architectural trade-off you should understand before deploying.
108
+:::
109
+
110
+### Latency Comparison
111
+
112
+| Operation | Local SSD | S3/R2 Object Storage |
113
+| ----------------------- | --------- | -------------------- |
114
+| Repository page load | ~50-100ms | **5-30 seconds** |
115
+| Clone (small repo) | ~100ms | **2-10 seconds** |
116
+| Push (10 files) | ~200ms | **1-5 seconds** |
117
+| File browser navigation | ~30ms | **500ms-2s** |
118
+
119
+### Why Does This Happen?
120
+
121
+When using object storage, every Git operation requires **multiple HTTP requests**:
122
+
123
+1. **List objects** - 1 API call
124
+2. **Download each file** - 40+ API calls for a typical repo
125
+3. **Each request has ~50-100ms latency** (network + S3 processing)
126
+
127
+A repository page load might download:
128
+
129
+- `HEAD`, `config`, `refs/heads/*` (branch info)
130
+- `objects/pack/*.idx`, `*.pack` (git objects)
131
+- All hook files, info files, etc.
132
+
133
+**40 files × 100ms = 4+ seconds minimum**
134
+
135
+### Recommendations by Use Case
136
+
137
+#### 🏢 Production (Self-Hosted)
138
+
139
+**Best:** Local SSD storage + S3 for backups only
140
+
141
+```bash
142
+STORAGE_TYPE=local
143
+STORAGE_PATH=/var/lib/opencodehub/repos
144
+```
145
+
146
+Use a cron job or background process to sync to S3 for disaster recovery.
147
+
148
+#### ☁️ Serverless (Vercel, Cloudflare Workers)
149
+
150
+**Trade-off:** Accept latency or implement caching
151
+
152
+```bash
153
+STORAGE_TYPE=s3
154
+# Enable local cache (coming soon)
155
+ENABLE_REPO_CACHE=true
156
+CACHE_TTL_SECONDS=300
157
+```
158
+
159
+#### 💰 Budget-Conscious (Google Drive)
160
+
161
+**Trade-off:** Slower but very cheap
162
+
163
+```bash
164
+STORAGE_TYPE=gdrive
165
+```
166
+
167
+Google Drive has similar latency characteristics to S3.
168
+
169
+### How GitHub/GitLab Solve This
170
+
171
+| Company | Approach |
172
+| ------------- | ------------------------------------------------- |
173
+| **GitHub** | Repos on local NVMe clusters, S3 for backups only |
174
+| **GitLab** | Gitaly (dedicated Git servers with local storage) |
175
+| **Bitbucket** | Sharded storage servers, repos cached in memory |
176
+
177
+**Key insight:** Major Git hosts never serve live traffic from object storage. They use local disk + memory caching for hot repos.
178
+
179
+### Future Improvements (Roadmap)
180
+
181
+We're working on:
182
+
183
+- **Local disk cache** with TTL-based invalidation
184
+- **Parallel S3 downloads** (70% faster initial loads)
185
+- **Redis metadata cache** (instant branch/tree info)
186
+- **Gitaly-like architecture** for enterprise deployments
187
+
188
+---
189
+
190
+## 🔄 Migrating Between Storage Types
191
+
192
+To migrate from local to S3:
193
+
194
+```bash
195
+# 1. Backup current repos
196
+tar -czf repos-backup.tar.gz data/repos/
197
+
198
+# 2. Upload to S3
199
+aws s3 sync data/repos/ s3://your-bucket/repos/
200
+
201
+# 3. Update .env
202
+STORAGE_TYPE=s3
203
+STORAGE_BUCKET=your-bucket
204
+
205
+# 4. Clear local cache
206
+rm -rf .tmp/repos/
207
+```
208
+
209
+To migrate from S3 to local:
210
+
211
+```bash
212
+# 1. Download from S3
213
+aws s3 sync s3://your-bucket/repos/ data/repos/
214
+
215
+# 2. Update .env
216
+STORAGE_TYPE=local
217
+STORAGE_PATH=./data/repos
218
+```
96
219
▼docs-site/src/content/docs/index.mdx
+68-1
16
16
variant: minimal
17
17
---
18
18
19
-import { Card, CardGrid } from '@astrojs/starlight/components';
19
+import { Card, CardGrid, Tabs, TabItem } from '@astrojs/starlight/components';
20
20
21
21
## Why OpenCodeHub?
22
22
35
35
</Card>
36
36
</CardGrid>
37
37
38
+## Architecture Overview
39
+
40
+```
41
+┌─────────────────────────────────────────────────────────────────┐
42
+│ OpenCodeHub │
43
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
44
+│ │ Astro │ │ Git │ │ CLI │ │
45
+│ │ Web UI │ │ Server │ │ (och) │ │
46
+│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
47
+│ └────────────────┼────────────────┘ │
48
+│ ▼ │
49
+│ ┌───────────────────────┐ │
50
+│ │ REST/GraphQL API │ │
51
+│ └───────────┬───────────┘ │
52
+│ ▼ │
53
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
54
+│ │ PostgreSQL │ │ Redis │ │ Storage │ │
55
+│ │ (Turso) │ │ (optional) │ │ (S3/Local) │ │
56
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
57
+└─────────────────────────────────────────────────────────────────┘
58
+```
59
+
60
+## Deploy Anywhere
61
+
62
+<CardGrid>
63
+ <Card title="🐳 Docker" icon="add-document">
64
+ The recommended way to deploy. Full Docker Compose setup with PostgreSQL and Redis.
65
+ [Deploy with Docker →](/administration/deploy-docker/)
66
+ </Card>
67
+ <Card title="🦭 Podman" icon="add-document">
68
+ Rootless containers with native systemd integration. Ideal for security-focused deployments.
69
+ [Deploy with Podman →](/administration/deploy-podman/)
70
+ </Card>
71
+ <Card title="☁️ Cloudflare" icon="add-document">
72
+ Use Cloudflare Tunnel + R2 for zero-port exposure and S3-compatible storage.
73
+ [Deploy with Cloudflare →](/administration/deploy-cloudflare/)
74
+ </Card>
75
+ <Card title="🌐 Nginx" icon="add-document">
76
+ Production-ready reverse proxy config with SSL and Git-specific optimizations.
77
+ [Configure Nginx →](/administration/deploy-nginx/)
78
+ </Card>
79
+</CardGrid>
80
+
81
+## Tech Stack
82
+
83
+| Layer | Technology |
84
+|-------|------------|
85
+| **Frontend** | Astro, React, Tailwind CSS, shadcn/ui |
86
+| **Backend** | Node.js, Hono (API), simple-git |
87
+| **Database** | PostgreSQL, Turso (LibSQL), SQLite |
88
+| **Storage** | Local disk, S3, R2, Google Drive |
89
+| **Auth** | JWT, 2FA, OAuth (GitHub, Google) |
90
+| **CI/CD** | GitHub Actions compatible runners |
91
+
38
92
## Quick Links
39
93
40
94
<CardGrid>
55
109
[Start contributing →](/development/contributing/)
56
110
</Card>
57
111
</CardGrid>
112
+
113
+## Storage Options
114
+
115
+:::tip[Performance Recommendation]
116
+For best performance, use **local SSD storage** in production. Object storage (S3/R2) introduces latency. See [Storage Adapters](/guides/storage-adapters/#️-performance-considerations) for details.
117
+:::
118
+
119
+| Storage Type | Performance | Cost | Best For |
120
+|--------------|-------------|------|----------|
121
+| **Local SSD** | ⚡ Fastest | $$ | Production, self-hosted |
122
+| **S3/R2** | 🐢 Slow | $ | Backup, serverless |
123
+| **Google Drive** | 🐢 Slow | Free | Personal projects |
124
+
58
125
▼docs-site/src/content/docs/reference/cli-auth-config.md
+79-0
1
+---
2
+title: "Auth & Config"
3
+description: "Login flow, token handling, and CLI configuration."
4
+---
5
+
6
+This page covers how authentication and local configuration work in the OpenCodeHub CLI.
7
+
8
+## Authentication
9
+
10
+### Login
11
+
12
+```bash
13
+och auth login --url http://localhost:3000
14
+```
15
+
16
+Options:
17
+
18
+- `--url <url>`: Instance URL (defaults to `http://localhost:3000`)
19
+- `--with-token`: Prompt for a personal access token
20
+- `--token <token>`: Non‑interactive token login
21
+
22
+Tokens are validated via `GET /api/user` and are expected to start with the `och_` prefix.
23
+
24
+### Status
25
+
26
+```bash
27
+och auth status
28
+```
29
+
30
+Shows the authenticated user, server, and token preview.
31
+
32
+### Logout
33
+
34
+```bash
35
+och auth logout
36
+```
37
+
38
+Clears the stored token from your local config.
39
+
40
+## Configuration
41
+
42
+The CLI persists settings locally. View the location at any time:
43
+
44
+```bash
45
+och config path
46
+```
47
+
48
+### Common keys
49
+
50
+- `serverUrl`: OpenCodeHub server URL
51
+- `token`: Personal access token
52
+- `defaultBranch`: Default base branch for PRs (default: `main`)
53
+- `editor`: Editor for interactive input
54
+- `pager`: Pager for long output
55
+
56
+### List & edit
57
+
58
+```bash
59
+och config list
60
+och config get serverUrl
61
+och config set serverUrl https://git.example.com
62
+och config unset editor
63
+```
64
+
65
+## Identity shortcut
66
+
67
+```bash
68
+och whoami
69
+```
70
+
71
+A convenience command that validates your token and prints the username.
72
+
73
+## Shell completions
74
+
75
+```bash
76
+och completion bash
77
+```
78
+
79
+Supported shells: `bash`, `zsh`, `fish`.
0
80
▼docs-site/src/content/docs/reference/cli-automation-insights.md
+59-0
1
+---
2
+title: "Automation & Insights"
3
+description: "Automations, notifications, inbox, and metrics from the CLI."
4
+---
5
+
6
+## Automation rules
7
+
8
+Automation rules run when triggers fire (like PR opened or CI passed). Use the CLI to manage them per repository.
9
+
10
+```bash
11
+och automate list
12
+och automate create
13
+och automate enable "AI Review on PR"
14
+och automate disable "AI Review on PR"
15
+och automate delete "AI Review on PR"
16
+```
17
+
18
+> `och automate create` is interactive and guides you through triggers and actions.
19
+
20
+## Notifications
21
+
22
+```bash
23
+och notify list --unread
24
+och notify read --all
25
+och notify settings --show
26
+```
27
+
28
+The CLI can list and mark notifications, as well as toggle preferences and quiet hours interactively.
29
+
30
+## PR inbox
31
+
32
+```bash
33
+och inbox list
34
+och inbox list --section "Needs Review"
35
+och inbox section list
36
+och inbox section create
37
+och inbox section delete "Needs Review"
38
+```
39
+
40
+Sections support filters like:
41
+
42
+- Needs my review
43
+- Authored by me
44
+- CI passing
45
+- Ready to merge
46
+- Has conflicts
47
+
48
+## Metrics & insights
49
+
50
+```bash
51
+och metrics show --weeks 4
52
+och metrics show --repo --weeks 8
53
+
54
+och insights show --period 4w
55
+och insights team --period 4w --limit 10
56
+och insights repo
57
+```
58
+
59
+Use `metrics` for structured summaries and `insights` for leaderboard‑style views.
0
60
▼docs-site/src/content/docs/reference/cli-commands.md
+22-3
2
2
title: "CLI Command Reference"
3
3
---
4
4
5
-
6
5
The `opencodehub-cli` (or `och`) is your primary tool for interacting with OpenCodeHub from the terminal.
7
6
8
7
## Installation
24
23
Manage authentication state.
25
24
26
25
### `och auth login`
26
+
27
27
Authenticates with an OpenCodeHub instance.
28
+
28
29
- **Interactive Mode**: Prompts for URL and credentials.
29
30
- **Flags**:
30
31
- `--url <url>`: OCH instance URL.
31
32
- `--token <token>`: Bypass browser login with existing token.
32
33
33
34
### `och auth status`
35
+
34
36
Check current login status and user.
35
37
36
38
### `och auth logout`
39
+
37
40
Clear stored credentials.
38
41
39
42
---
43
46
Manage repositories.
44
47
45
48
### `och clone <repo>`
49
+
46
50
Clone a repository.
51
+
47
52
- **Example**: `och clone swadhin/my-project`
48
53
- **Behavior**: Automatically sets up remote `origin`.
49
54
50
55
### `och create [name]`
56
+
51
57
Create a new repository.
58
+
52
59
- **Flags**:
53
60
- `--public`: Make repository public.
54
61
- `--private`: Make repository private.
55
62
- `--desc <text>`: Description.
56
63
57
64
### `och push`
65
+
58
66
Push changes to the remote.
67
+
59
68
- **Features**: Shows a progress bar and PR link on success.
60
69
61
70
---
65
74
Manage Stacked Pull Requests.
66
75
67
76
### `och stack create <branch-name>`
77
+
68
78
Create a new branch stacked on top of the current one.
79
+
69
80
- **Example**: `och stack create feature-b` (when on `feature-a`).
70
81
71
82
### `och stack submit`
83
+
72
84
Submit all branches in the current stack as Pull Requests.
85
+
73
86
- **Behavior**:
74
87
- Pushes all branches.
75
88
- Creates/Updates PRs.
76
89
- Sets correct base branches (`feature-a` -> `main`, `feature-b` -> `feature-a`).
77
90
78
91
### `och stack sync`
92
+
79
93
Rebase the entire stack.
94
+
80
95
- **Use Case**: When `main` has updated or a lower PR changed.
81
96
82
97
### `och stack view`
98
+
83
99
Visualize the current stack in ASCII tree format.
84
100
85
101
---
89
105
Manage Pull Requests.
90
106
91
107
### `och pr list`
108
+
92
109
List open PRs for current repo.
93
110
94
111
### `och pr checkout <number>`
112
+
95
113
Checkout a PR by its number.
96
114
97
-### `och pr status [number]`
98
-Show CI status and review state of a PR.
115
+### `och pr view <number>`
116
+
117
+Show details for a PR including state and branches.
99
118
▼docs-site/src/content/docs/reference/cli-core-commands.md
+111-0
1
+---
2
+title: "Core Commands"
3
+description: "Repository, PR, issue, CI, and release workflows from the CLI."
4
+---
5
+
6
+This page summarizes the most common non‑stack workflows.
7
+
8
+## Repository
9
+
10
+```bash
11
+och repo create my-repo --private --description "My repo"
12
+och repo clone owner/my-repo
13
+och repo list
14
+```
15
+
16
+Shorthand:
17
+
18
+```bash
19
+och init --url http://localhost:3000
20
+och push --branch main
21
+```
22
+
23
+## Pull requests
24
+
25
+```bash
26
+och pr create --base main --title "Fix auth" --body "Adds token checks"
27
+och pr list --state open
28
+och pr view 42
29
+och pr merge 42 --squash
30
+och pr checkout 42
31
+och pr diff 42
32
+och pr ready 42
33
+```
34
+
35
+## Issues
36
+
37
+```bash
38
+och issue create --title "Crash on login" --body "Steps to reproduce..."
39
+och issue list --state open
40
+och issue view 12
41
+och issue comment 12 --body "I can reproduce"
42
+och issue close 12 --comment "Fixed in #34"
43
+och issue reopen 12
44
+```
45
+
46
+## Branches
47
+
48
+```bash
49
+och branch list
50
+och branch create feature/notes --checkout
51
+och branch rename feature/notes feature/release-notes
52
+och branch delete feature/notes --remote
53
+```
54
+
55
+## CI/CD
56
+
57
+```bash
58
+och ci list --branch main
59
+och ci view 101 --jobs
60
+och ci trace 809 --follow
61
+```
62
+
63
+## Releases
64
+
65
+```bash
66
+och release create v1.2.0 --title "1.2.0" --generate-notes
67
+och release list
68
+och release view v1.2.0
69
+och release delete v1.2.0
70
+```
71
+
72
+## Search
73
+
74
+```bash
75
+och search repos "opencode"
76
+och search issues "crash" --state open
77
+och search prs "refactor" --state open
78
+```
79
+
80
+## Secrets
81
+
82
+```bash
83
+# repo secret
84
+printf "s3cr3t" | och secret set API_TOKEN
85
+
86
+# environment secret
87
+printf "s3cr3t" | och secret set API_TOKEN --env production
88
+
89
+# org secret
90
+printf "s3cr3t" | och secret set API_TOKEN --org my-org
91
+
92
+och secret list
93
+och secret delete API_TOKEN --yes
94
+```
95
+
96
+## SSH keys
97
+
98
+```bash
99
+och ssh-key add --file ~/.ssh/id_ed25519.pub
100
+och ssh-key list
101
+och ssh-key delete <key-id>
102
+```
103
+
104
+## Direct API calls
105
+
106
+```bash
107
+och api /user
108
+och api /repos/owner/name -X PATCH -F "description=New description"
109
+```
110
+
111
+> All commands support `--help` for full options.
0
112
▼docs-site/src/content/docs/reference/cli-merge-queue.md
+45-0
1
+---
2
+title: "Merge Queue"
3
+description: "Manage the merge queue from the CLI."
4
+---
5
+
6
+The merge queue ensures PRs are merged in a stable, CI‑validated order. The CLI targets the current repository based on your `origin` remote.
7
+
8
+## Add a PR
9
+
10
+```bash
11
+och queue add 128 --priority 5 --method squash
12
+```
13
+
14
+Options:
15
+
16
+- `--priority <n>`: Higher number moves the PR up the queue.
17
+- `--method <merge|squash|rebase>`: Merge strategy.
18
+
19
+## List the queue
20
+
21
+```bash
22
+och queue list
23
+```
24
+
25
+Queue item statuses:
26
+
27
+- `pending`: queued
28
+- `running_ci`: CI is running
29
+- `ready`: ready to merge
30
+- `merging`: merge in progress
31
+- `failed`: CI failed or merge blocked
32
+
33
+## Check current PR status
34
+
35
+```bash
36
+och queue status
37
+```
38
+
39
+Automatically detects the PR for the current branch and reports its queue position.
40
+
41
+## Remove from the queue
42
+
43
+```bash
44
+och queue remove 128
45
+```
0
46
▼docs-site/src/content/docs/reference/cli-overview.md
+56-0
1
+---
2
+title: "CLI Overview"
3
+description: "How the OpenCodeHub CLI is structured and where to start."
4
+---
5
+
6
+The OpenCodeHub CLI (`och`) is a structured, command‑group based tool. Most workflows start with authentication and repository initialization, then move into PR, stack, and CI operations.
7
+
8
+## Installation
9
+
10
+```bash
11
+npm install -g opencodehub-cli
12
+```
13
+
14
+## Global flags
15
+
16
+- `--help`, `-h`: Show help
17
+- `--version`, `-v`: Print version
18
+- `--debug`: Verbose diagnostics
19
+
20
+## Command matrix
21
+
22
+| Group | Purpose | Subcommands (common) |
23
+| ---------- | ----------------------------- | ----------------------------------------------------------------------- |
24
+| `auth` | Authenticate with an instance | `login`, `logout`, `status` |
25
+| `config` | CLI configuration | `list`, `get`, `set`, `unset`, `path`, `reset` |
26
+| `repo` | Repository actions | `create`, `clone`, `list`, `push` |
27
+| `pr` | Pull requests | `create`, `list`, `view`, `merge`, `checkout`, `close`, `diff`, `ready` |
28
+| `issue` | Issues | `create`, `list`, `view`, `close`, `reopen`, `comment` |
29
+| `stack` | Stacked PR workflow | `create`, `submit`, `view`, `sync`, `status`, `rebase` |
30
+| `queue` | Merge queue | `add`, `list`, `status`, `remove` |
31
+| `review` | Reviews & AI review | `ai`, `status`, `approve`, `request-changes`, `comment` |
32
+| `ci` | CI/CD runs | `list`, `view`, `trace` |
33
+| `metrics` | Metrics summary | `show` |
34
+| `insights` | Metrics + leaderboard | `show`, `team`, `repo` |
35
+| `inbox` | PR inbox | `list`, `section` |
36
+| `notify` | Notifications | `list`, `read`, `settings` |
37
+| `automate` | Automation rules | `list`, `create`, `enable`, `disable`, `delete` |
38
+| `branch` | Git branch management | `list`, `create`, `checkout`, `rename`, `delete`, `track` |
39
+| `release` | Releases | `create`, `list`, `view`, `delete` |
40
+| `search` | Search across entities | `repos`, `issues`, `prs` |
41
+| `secret` | Workflow secrets | `list`, `set`, `delete` |
42
+| `ssh-key` | SSH keys | `list`, `add`, `delete` |
43
+| `api` | Raw API calls | Single command with `--method` |
44
+| Shorthand | Convenience commands | `init`, `sync`, `status`, `whoami`, `push`, `completion` |
45
+
46
+> Some subcommands are interactive by design. Use `--help` on any group for the authoritative list.
47
+
48
+## Where configuration lives
49
+
50
+Run `och config path` to see the exact config file location for your OS.
51
+
52
+## Next steps
53
+
54
+- See [reference/cli-auth-config](../reference/cli-auth-config) for login and config details.
55
+- See [reference/cli-core-commands](../reference/cli-core-commands) for repo, PR, and issue workflows.
56
+- See [reference/cli-stack-workflows](../reference/cli-stack-workflows) for the stacked PR flow.
0
57
▼docs-site/src/content/docs/reference/cli-stack-workflows.md
+75-0
1
+---
2
+title: "Stack Workflows"
3
+description: "Create, submit, and sync stacked PRs from the CLI."
4
+---
5
+
6
+Stacked PRs use branches prefixed with `stack/` and are designed for incremental review.
7
+
8
+## Create a stack
9
+
10
+```bash
11
+# On your base branch
12
+och stack create auth-schema
13
+
14
+# Add changes and commit
15
+# ...
16
+
17
+# Create the next layer
18
+och stack create auth-service
19
+```
20
+
21
+`och stack create` creates `stack/<name>` and keeps it tied to the current stack history.
22
+
23
+## Submit a stack
24
+
25
+```bash
26
+och stack submit
27
+```
28
+
29
+- Pushes all `stack/*` branches.
30
+- Creates or updates PRs through the OpenCodeHub API when available.
31
+- Uses `main` as the default base branch.
32
+
33
+Use `--draft` to create draft PRs, and `--message` to prefix PR titles.
34
+
35
+## View stack
36
+
37
+```bash
38
+och stack view
39
+```
40
+
41
+Outputs a simple ASCII stack tree with the current branch highlighted.
42
+
43
+## Sync stack
44
+
45
+```bash
46
+och stack sync
47
+```
48
+
49
+Syncs stack branches with the remote. Use:
50
+
51
+- `--push` to only push
52
+- `--pull` to only pull (rebase)
53
+- `--force` for force-with-lease pushes
54
+
55
+## Status
56
+
57
+```bash
58
+och stack status
59
+```
60
+
61
+Shows ahead/behind and sync status for each `stack/*` branch.
62
+
63
+## Rebase stack
64
+
65
+```bash
66
+och stack rebase --base main
67
+```
68
+
69
+Rebases the current stack on the chosen base branch.
70
+
71
+## Tips
72
+
73
+- Keep each stack layer small and reviewable.
74
+- Avoid mixing unrelated changes across layers.
75
+- When a lower PR merges, rebase or sync the upper layers.
0
76
▼docs-site/src/content/docs/tutorials/ai-powered-reviews.md
+4-3
2
2
title: "Tutorial: AI-Powered Code Reviews"
3
3
---
4
4
5
-
6
5
OpenCodeHub integrates with LLMs (GPT-4, Claude) to provide automated, intelligent code reviews on every Pull Request. This tutorial guides you through setting it up.
7
6
8
7
## Prerequisites
15
14
As an administrator, you need to configure the AI provider in your environment variables.
16
15
17
16
**For OpenAI:**
18
-```env
17
+
18
+```bash
19
19
AI_PROVIDER=openai
20
20
OPENAI_API_KEY=sk-proj-...
21
21
```
22
22
23
23
**For Anthropic:**
24
-```env
24
+
25
+```bash
25
26
AI_PROVIDER=anthropic
26
27
ANTHROPIC_API_KEY=sk-ant-...
27
28
```
28
29
▼docs-site/src/content/docs/tutorials/automated-releases.md
+36-0
1
+---
2
+title: "Automated Releases"
3
+description: "Tag, generate notes, and publish releases with the CLI."
4
+---
5
+
6
+This tutorial shows a simple release flow using the CLI.
7
+
8
+## 1) Create a tag and release
9
+
10
+```bash
11
+och release create v1.2.0 --title "1.2.0" --generate-notes
12
+```
13
+
14
+Use `--draft` for internal review or `--prerelease` for pre‑release builds.
15
+
16
+## 2) Verify release
17
+
18
+```bash
19
+och release view v1.2.0
20
+```
21
+
22
+## 3) List releases
23
+
24
+```bash
25
+och release list
26
+```
27
+
28
+## 4) Remove a release (if needed)
29
+
30
+```bash
31
+och release delete v1.2.0
32
+```
33
+
34
+## Optional: tie to CI
35
+
36
+Create a `.github/workflows/release.yml` that tags and calls the CLI once tests pass.
0
37
▼docs-site/src/content/docs/tutorials/disaster-recovery.md
+42-0
1
+---
2
+title: "Disaster Recovery"
3
+description: "Back up and restore OpenCodeHub data safely."
4
+---
5
+
6
+This guide outlines a conservative recovery strategy that works across storage backends.
7
+
8
+## 1) Back up the database
9
+
10
+Use your database’s native backup tooling to snapshot the OpenCodeHub database. Examples:
11
+
12
+- PostgreSQL: `pg_dump`
13
+- MySQL: `mysqldump`
14
+
15
+## 2) Back up Git data and storage
16
+
17
+Back up the directories used by your instance:
18
+
19
+- `GIT_REPOS_PATH` (default `./data/repositories`)
20
+- `STORAGE_PATH` (default `./data/storage`)
21
+
22
+If you use S3/R2/MinIO, snapshot the bucket or enable versioning.
23
+
24
+## 3) Restore
25
+
26
+1. Restore the database backup.
27
+2. Restore repository and storage data to the configured paths.
28
+3. Run migrations to match the code version:
29
+
30
+```bash
31
+npm run db:migrate
32
+```
33
+
34
+## 4) Validate
35
+
36
+- Open the UI and check a repository.
37
+- Confirm that PRs, issues, and CI history load.
38
+- Perform a test clone to verify Git data integrity.
39
+
40
+## 5) Automate
41
+
42
+Use a scheduled backup job and store snapshots off‑site.
0
43
▼docs-site/src/content/docs/tutorials/self-hosted-docker-nginx.md
+36-0
1
+---
2
+title: "Self‑Hosted with Docker + Nginx"
3
+description: "Deploy OpenCodeHub behind a reverse proxy with HTTPS."
4
+---
5
+
6
+This tutorial combines the Docker deployment guide with an Nginx reverse proxy setup.
7
+
8
+## 1) Start services with Docker
9
+
10
+```bash
11
+cp .env.example .env
12
+# Edit .env with production values
13
+
14
+docker-compose up -d
15
+```
16
+
17
+## 2) Configure Nginx
18
+
19
+Follow the [administration/deploy-nginx](../administration/deploy-nginx) guide to configure TLS termination and proxy rules.
20
+
21
+## 3) Seed admin user
22
+
23
+```bash
24
+docker-compose exec app bun run scripts/seed-admin.ts
25
+```
26
+
27
+## 4) Validate
28
+
29
+- Open your instance URL
30
+- Create a repository
31
+- Test `git clone` over HTTP or SSH
32
+
33
+## Tips
34
+
35
+- Enable `ENABLE_ACTIONS=true` if you plan to run workflows.
36
+- Set `RUNNER_ENABLED=true` and bind the Docker socket for runners.
0
37
▼docs-site/src/content/docs/tutorials/team-onboarding.md
+51-0
1
+---
2
+title: "Team Onboarding"
3
+description: "Invite teammates, protect branches, and set review standards."
4
+---
5
+
6
+This tutorial helps you bring a team onto a new OpenCodeHub instance.
7
+
8
+## 1) Create a repository
9
+
10
+Create a new repository from the UI and push your initial code.
11
+
12
+## 2) Invite collaborators
13
+
14
+In repository **Settings → Collaborators**, add team members and set access levels.
15
+
16
+## 3) Set branch protection
17
+
18
+Configure protections to enforce reviews and CI:
19
+
20
+- Required approvals
21
+- Block merges when CI fails
22
+- Protect `main`
23
+
24
+See [guides/branch-protection](../guides/branch-protection).
25
+
26
+## 4) Enable stacked PRs
27
+
28
+Share the stack workflow with your team:
29
+
30
+```bash
31
+npm install -g opencodehub-cli
32
+och auth login --url https://your-instance
33
+```
34
+
35
+Then point them to [tutorials/your-first-stack](../tutorials/your-first-stack).
36
+
37
+## 5) Automate reviews
38
+
39
+Add an automation to trigger AI review on PR open:
40
+
41
+```bash
42
+och automate create
43
+```
44
+
45
+## 6) Align notifications
46
+
47
+Encourage reviewers to enable notifications for review requests:
48
+
49
+```bash
50
+och notify settings --show
51
+```
0
52
▼docs-site/src/content/docs/tutorials/your-first-stack.md
+52-58
2
2
title: "Tutorial: Your First Stacked PR"
3
3
---
4
4
5
-
6
5
> Learn how to split a large feature into reviewable chunks in 15 minutes
7
6
8
7
This hands-on tutorial teaches you how to use Stacked Pull Requests to break down a large feature into small, focused PRs that ship faster.
14
13
- Navigate the review process
15
14
- Merge the stack successfully
16
15
17
-**Time:** 15-20 minutes
16
+**Time:** 15-20 minutes
18
17
**Difficulty:** Beginner
19
18
20
19
---
30
29
- [ ] Basic Git knowledge (branch, commit, push)
31
30
32
31
**First time with OCH CLI?**
32
+
33
33
```bash
34
34
# Install
35
35
npm install -g opencodehub-cli
127
127
# Output:
128
128
# ✓ Pushing stack/auth-database
129
129
# ✓ Creating pull request
130
-#
130
+#
131
131
# PR #123 created: feat: add users table migration
132
132
# https://git.yourcompany.com/yourorg/your-repo/pulls/123
133
133
```
135
135
**2.5. Verify in web UI:**
136
136
137
137
Open the PR link. You should see:
138
+
138
139
- Title: "feat: add users table migration"
139
140
- Files changed: `db/migrations/001_create_users_table.sql`
140
141
- Status: Draft or Ready for Review
165
166
export class AuthService {
166
167
async register(email: string, password: string) {
167
168
const hash = await bcrypt.hash(password, 10);
168
-
169
+
169
170
const user = await db.users.create({
170
171
email,
171
172
password_hash: hash
172
173
});
173
-
174
+
174
175
return { id: user.id, email: user.email };
175
176
}
176
-
177
+
177
178
async login(email: string, password: string) {
178
179
const user = await db.users.findByEmail(email);
179
-
180
+
180
181
if (!user) {
181
182
throw new Error('Invalid credentials');
182
183
}
183
-
184
+
184
185
const valid = await bcrypt.compare(password, user.password_hash);
185
-
186
+
186
187
if (!valid) {
187
188
throw new Error('Invalid credentials');
188
189
}
189
-
190
+
190
191
const token = jwt.sign(
191
192
{ userId: user.id, email: user.email },
192
193
process.env.JWT_SECRET!,
193
194
{ expiresIn: '7d' }
194
195
);
195
-
196
+
196
197
return { token, user: { id: user.id, email: user.email } };
197
198
}
198
199
}
267
268
router.post('/register', async (req, res) => {
268
269
try {
269
270
const { email, password } = req.body;
270
-
271
+
271
272
// Validation
272
273
if (!email || !password) {
273
274
return res.status(400).json({ error: 'Email and password required' });
274
275
}
275
-
276
+
276
277
const user = await authService.register(email, password);
277
-
278
+
278
279
res.status(201).json({ user });
279
280
} catch (error) {
280
281
res.status(500).json({ error: error.message });
284
285
router.post('/login', async (req, res) => {
285
286
try {
286
287
const { email, password } = req.body;
287
-
288
+
288
289
const result = await authService.login(email, password);
289
-
290
+
290
291
res.json(result);
291
292
} catch (error) {
292
293
res.status(401).json({ error: error.message });
314
315
315
316
# Output:
316
317
# ✓ Pushing stack/auth-api
317
-# ✓ Creating pull request
318
+# ✓ Creating pull request
318
319
# ✓ Linking to base PR #124
319
320
#
320
321
# PR #125 created: feat: add authentication API endpoints
356
357
**5.2. Request reviews:**
357
358
358
359
In the web UI for PR #123:
360
+
359
361
1. Click "Reviewers" → Select team members
360
362
2. Add comment: "This is the first PR in a stack of 3. Reviewing these in order would be helpful!"
361
363
373
375
test('creates users table', async () => {
374
376
// Run migration
375
377
await db.migrate();
376
-
378
+
377
379
// Verify table exists
378
380
const tables = await db.raw(`
379
- SELECT table_name
380
- FROM information_schema.tables
381
+ SELECT table_name
382
+ FROM information_schema.tables
381
383
WHERE table_name = 'users'
382
384
`);
383
-
385
+
384
386
expect(tables.length).toBe(1);
385
387
});
386
388
EOF
395
397
**5.4. Monitor review progress:**
396
398
397
399
```bash
398
-# Check PR status
399
-och pr status 123
400
+# View PR details
401
+och pr view 123
400
402
401
403
# Output:
402
-# PR #123: feat: add users table migration
403
-# Status: 🔍 In Review
404
-# Approvals: 1/1 required
405
-# CI: ✅ Passing
406
-# Comments: 2
404
+# #123 feat: add users table migration
405
+# State: OPEN
406
+# Author: @you
407
+# Branch: stack/auth-database → main
408
+# Created: 1/22/2026
407
409
```
408
410
409
411
---
415
417
**6.1. Use the merge queue:**
416
418
417
419
```bash
418
-# Add all PRs to queue
419
-och queue add 123 124 125
420
-
421
-# Or add individually as they get approved:
420
+# Add individually as they get approved:
422
421
och queue add 123 # Merges first
423
-# PR #124 auto-rebases onto new main
422
+# PR #124 becomes next in line after #123 merges
424
423
och queue add 124 # Merges second
425
-# PR #125 auto-rebases onto new main
424
+# PR #125 becomes next in line after #124 merges
426
425
och queue add 125 # Merges third
427
426
```
428
427
429
428
**6.2. Watch it merge automatically:**
430
429
431
430
```bash
432
-# Monitor queue
433
-och queue watch
434
-
435
-# Output (live updates):
436
-# Position PR Status ETA
437
-# 1 #123 🏃 Running CI 1 min
438
-# 2 #124 ⏳ Waiting 3 min
439
-# 3 #125 ⏳ Waiting 5 min
440
-#
441
-# [1 min later]
442
-# ✅ PR #123 merged!
443
-# 🔄 Rebasing PR #124...
444
-# 🏃 Running CI for #124...
445
-#
446
-# [2 min later]
447
-# ✅ PR #124 merged!
448
-# 🔄 Rebasing PR #125...
431
+# Check queue status
432
+och queue list
433
+
434
+# Output (refresh as needed):
435
+# Pos PR Title Status
436
+# 1 #123 Add users table migration running_ci
437
+# 2 #124 Auth service pending
438
+# 3 #125 Auth API pending
449
439
```
450
440
451
441
**6.3. Celebrate! 🎉**
459
449
460
450
# Output:
461
451
# abc123 feat: add authentication API endpoints
462
-# def456 feat: implement authentication service
452
+# def456 feat: implement authentication service
463
453
# ghi789 feat: add users table migration
464
454
```
465
455
469
459
470
460
## What You've Learned
471
461
472
-✅ How to create a stack of dependent PRs
473
-✅ Using `och stack create` for easy stacking
474
-✅ Submitting stacks with `och stack submit`
475
-✅ Viewing stack visualization
476
-✅ Using the merge queue for automatic merging
462
+✅ How to create a stack of dependent PRs
463
+✅ Using `och stack create` for easy stacking
464
+✅ Submitting stacks with `och stack submit`
465
+✅ Viewing stack visualization
466
+✅ Using the merge queue for automatic merging
477
467
✅ How auto-rebasing works
478
468
479
469
## Key Takeaways
480
470
481
471
**Stacked PRs are better because:**
472
+
482
473
- Each PR was ~50-100 lines (easy to review)
483
474
- Reviews happened in parallel (faster)
484
475
- Each merge was low-risk (incremental)
485
476
- Clear history (logical progression)
486
477
487
478
**vs. One Large PR:**
479
+
488
480
- Would be ~200-300 lines
489
481
- Single review bottleneck
490
482
- High-risk merge
497
489
### Practice More
498
490
499
491
Try stacking your next feature:
492
+
500
493
- 🎨 Frontend feature → Split UI, logic, styles
501
494
- 🔧 Backend feature → Split models, services, controllers
502
495
- 📊 Data pipeline → Split ingestion, processing, output
504
497
### Advanced Techniques
505
498
506
499
Learn more about:
500
+
507
501
- [Parallel stacks](../features/stacked-prs.md#parallel-stacks)
508
502
- [Stack reordering](../features/stacked-prs.md#reordering)
509
503
- [AI code review](../features/ai-review.md) on stacks
510
-- [Advanced CLI commands](../../cli/README.md)
504
+- [CLI overview](../reference/cli-overview)
511
505
512
506
### Join the Community
513
507
514
508
▼docs-site/src/content/docs/tutorials/zero-to-production.md
+57-0
1
+---
2
+title: "From Zero to Production"
3
+description: "End‑to‑end setup from local dev to a production‑ready instance."
4
+---
5
+
6
+This tutorial walks from a clean clone to a production‑ready deployment.
7
+
8
+## 1) Local setup
9
+
10
+```bash
11
+git clone https://github.com/swadhinbiswas/OpencodeHub.git
12
+cd OpenCodeHub
13
+npm install
14
+cp .env.example .env
15
+```
16
+
17
+Update the required secrets in `.env` (`JWT_SECRET`, `SESSION_SECRET`, `INTERNAL_HOOK_SECRET`, `SITE_URL`).
18
+
19
+## 2) Database migrations
20
+
21
+```bash
22
+npm run db:push
23
+```
24
+
25
+## 3) Create admin user
26
+
27
+```bash
28
+bun run scripts/seed-admin.ts
29
+```
30
+
31
+## 4) Start the server
32
+
33
+```bash
34
+npm run dev
35
+```
36
+
37
+Open the app at `http://localhost:3000` and log in.
38
+
39
+## 5) Production deployment
40
+
41
+For production, use Docker:
42
+
43
+```bash
44
+docker-compose up -d
45
+```
46
+
47
+Then seed the admin user inside the container:
48
+
49
+```bash
50
+docker-compose exec app bun run scripts/seed-admin.ts
51
+```
52
+
53
+## Next steps
54
+
55
+- [administration/deployment](../administration/deployment)
56
+- [administration/configuration](../administration/configuration)
57
+- [administration/deploy-docker](../administration/deploy-docker)
0
58
▼docs/features/ai-review.md
+62-23
1
+---
2
+title: "Legacy: AI Code Review"
3
+slug: "legacy/features/ai-review"
4
+---
5
+
1
6
# AI Code Review
2
7
3
8
> Get instant, intelligent code reviews powered by GPT-4 or Claude
24
29
### What AI Review Catches
25
30
26
31
🔒 **Security Vulnerabilities**
32
+
27
33
- SQL injection risks
28
34
- XSS (Cross-Site Scripting) vectors
29
35
- Authentication bypasses
31
37
- Insecure dependencies
32
38
33
39
⚡ **Performance Issues**
40
+
34
41
- N+1 database queries
35
42
- Inefficient algorithms (O(n²) → O(n log n))
36
43
- Memory leaks
37
44
- Blocking operations in async code
38
45
- Missing indexes
39
46
40
-📝 **Code Quality**
47
+📝 **Code Quality**
48
+
41
49
- Best practice violations
42
50
- Code smells (long functions, deep nesting)
43
51
- Incomplete error handling
45
53
- Poor naming conventions
46
54
47
55
🐛 **Potential Bugs**
56
+
48
57
- Null/undefined pointer exceptions
49
58
- Race conditions
50
59
- Off-by-one errors
78
87
1. Get API key from [platform.openai.com](https://platform.openai.com/api-keys)
79
88
80
89
2. Add to `.env`:
90
+
81
91
```env
82
92
AI_PROVIDER=openai
83
93
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx
91
101
1. Get API key from [console.anthropic.com](https://console.anthropic.com/)
92
102
93
103
2. Add to `.env`:
104
+
94
105
```env
95
106
AI_PROVIDER=anthropic
96
107
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx
111
122
**Option 4: Self-Hosted (Coming Soon)**
112
123
113
124
OpenCodeHub will support local models via Ollama:
125
+
114
126
```env
115
127
AI_PROVIDER=ollama
116
128
OLLAMA_ENDPOINT=http://localhost:11434
170
182
```
171
183
172
184
You'll see:
185
+
173
186
- ✅ **Status check**: "AI Review: In Progress" → "AI Review: Complete"
174
187
- 💬 **Inline comments**: On specific lines with issues
175
188
- 📊 **Summary comment**: Overall assessment
179
192
Request AI review on-demand:
180
193
181
194
**Via Web UI:**
195
+
182
196
1. Open PR page
183
197
2. Click **"Request AI Review"** button
184
198
3. Wait ~30-60 seconds
185
199
4. Review appears as comments
186
200
187
201
**Via CLI:**
202
+
188
203
```bash
189
204
# Review specific PR
190
205
och review ai 125
197
212
```
198
213
199
214
**Via API:**
215
+
200
216
```bash
201
217
curl -X POST https://git.yourcompany.com/api/prs/125/ai-review \
202
218
-H "Authorization: Bearer $TOKEN"
210
226
211
227
AI assigns severity to each finding:
212
228
213
-| Severity | Icon | Meaning | Example |
214
-|----------|------|---------|---------|
215
-| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key |
216
-| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling |
217
-| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification |
229
+| Severity | Icon | Meaning | Example |
230
+| --------------- | ---------- | -------------------------------------- | ----------------------------------------- |
231
+| 🔴 **CRITICAL** | Must fix | Security vulnerability, data loss risk | SQL injection, exposed API key |
232
+| 🟡 **WARNING** | Should fix | Performance issue, bad practice | N+1 query, missing error handling |
233
+| 🔵 **INFO** | Consider | Suggestion, refactoring opportunity | Better variable name, code simplification |
218
234
219
235
### Example Review Comment
220
236
221
-```markdown
237
+````markdown
222
238
🔴 CRITICAL: SQL Injection Vulnerability
223
239
224
-**File:** `src/api/users.ts`
240
+**File:** `src/api/users.ts`
225
241
**Line:** 45
226
242
227
-**Issue:**
243
+**Issue:**
228
244
User input is directly concatenated into SQL query without sanitization.
229
245
230
246
**Current Code:**
247
+
231
248
```typescript
232
249
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
233
250
```
251
+````
234
252
235
253
**Risk:**
236
254
An attacker could inject malicious SQL:
255
+
237
256
```
238
257
userEmail = "' OR '1'='1"
239
258
→ Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1'
242
261
243
262
**Fix:**
244
263
Use parameterized queries or ORM:
264
+
245
265
```typescript
246
266
const query = `SELECT * FROM users WHERE email = ?`;
247
267
const results = await db.query(query, [userEmail]);
251
271
```
252
272
253
273
**References:**
274
+
254
275
- [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection)
255
276
- [Node.js SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
256
-```
277
+
278
+````
257
279
258
280
### Summary Comment
259
281
285
307
- ✅ Comprehensive TypeScript types
286
308
287
309
**Estimated fix time:** 2-3 hours
288
-```
310
+````
289
311
290
312
---
291
313
313
335
314
336
# Focus areas (weighted 1-10)
315
337
focus:
316
- security: 10 # Highest priority
338
+ security: 10 # Highest priority
317
339
performance: 8
318
340
bugs: 8
319
341
code_quality: 5
323
345
report:
324
346
critical: always
325
347
warning: always
326
- info: on_request # Only if --verbose flag used
348
+ info: on_request # Only if --verbose flag used
327
349
328
350
# Custom rules
329
351
custom_rules:
331
353
pattern: 'console\\.log\\('
332
354
severity: warning
333
355
message: "Remove console.log before merging"
334
-
356
+
335
357
- name: "Require error handling"
336
358
pattern: 'await.*\\(.*\\)'
337
359
check: must_have_try_catch
344
366
2. Database query efficiency
345
367
3. Input validation
346
368
4. Proper error handling
347
-
369
+
348
370
Our coding standards:
349
371
- TypeScript strict mode
350
372
- Functional programming preferred
386
408
AI review costs depend on provider and model:
387
409
388
410
**OpenAI (GPT-4 Turbo):**
411
+
389
412
- ~$0.01 per 1K tokens (input)
390
413
- ~$0.03 per 1K tokens (output)
391
414
- **Typical PR**: 500-2000 tokens = **$0.02-0.10 per review**
392
415
393
416
**Anthropic (Claude 3 Opus):**
417
+
394
418
- ~$0.015 per 1K tokens (input)
395
419
- ~$0.075 per 1K tokens (output)
396
420
- **Typical PR**: **$0.03-0.15 per review**
397
421
398
422
**Example monthly cost:**
423
+
399
424
- 50 PRs/month × $0.05/review = **$2.50/month**
400
425
- 500 PRs/month × $0.05/review = **$25/month**
401
426
402
427
### Cost Optimization
403
428
404
429
**1. Limit file count:**
430
+
405
431
```yaml
406
-max_files_per_review: 10 # Review only 10 largest changed files
432
+max_files_per_review: 10 # Review only 10 largest changed files
407
433
```
408
434
409
435
**2. Use cheaper models for simple changes:**
436
+
410
437
```yaml
411
438
rules:
412
439
- if: changed_files < 3
413
- model: gpt-3.5-turbo # 10x cheaper
440
+ model: gpt-3.5-turbo # 10x cheaper
414
441
- if: changed_files >= 3
415
442
model: gpt-4-turbo
416
443
```
417
444
418
445
**3. Skip test files:**
446
+
419
447
```yaml
420
448
exclude:
421
449
- "**/*.test.*"
423
451
```
424
452
425
453
**4. Review only on-request:**
454
+
426
455
```yaml
427
-trigger: manual # Don't auto-review every push
456
+trigger: manual # Don't auto-review every push
428
457
```
429
458
430
459
**5. Use severity threshold:**
460
+
431
461
```yaml
432
-severity_threshold: warning # Skip "info" level comments
462
+severity_threshold: warning # Skip "info" level comments
433
463
```
434
464
435
465
---
439
469
### 💡 When to Use AI Review
440
470
441
471
**✅ Good Use Cases:**
472
+
442
473
- Security-sensitive code (auth, payments)
443
474
- Complex algorithms
444
475
- Database queries
446
477
- Third-party integrations
447
478
448
479
**❌ Less Useful:**
480
+
449
481
- Generated code
450
482
- Configuration files
451
483
- Simple refactoring
454
486
### 💡 Responding to AI Feedback
455
487
456
488
**1. Don't auto-fix everything**
489
+
457
490
- AI can be wrong
458
491
- Context matters
459
492
- Use judgment
460
493
461
494
**2. Engage in discussion**
495
+
462
496
```markdown
463
-@ai-review That's a good catch, but in this case we're using a
497
+@ai-review That's a good catch, but in this case we're using a
464
498
trusted source and the data is already sanitized earlier in the pipeline.
465
499
466
500
See line 23 where we validate input.
467
501
```
468
502
469
503
**3. Track false positives**
504
+
470
505
```yaml
471
506
# .opencodehub/ai-review.yml
472
507
ignore:
486
521
```
487
522
488
523
This workflow:
524
+
489
525
- Reduces human reviewer burden
490
526
- Catches more bugs
491
527
- Faster feedback cycles
499
535
**Cause:** Large PR (many files/lines).
500
536
501
537
**Solution:**
538
+
502
539
```bash
503
540
# Check review status
504
541
och review status 125
511
548
### "AI review failed"
512
549
513
550
**Causes:**
551
+
514
552
1. API key invalid/expired
515
553
2. Rate limit exceeded
516
554
3. Timeout (very large PR)
517
555
518
556
**Solutions:**
557
+
519
558
```bash
520
559
# 1. Verify API key
521
560
echo $OPENAI_API_KEY
535
574
```yaml
536
575
# .opencodehub/ai-review.yml
537
576
sensitivity:
538
- security: high # Strict
577
+ security: high # Strict
539
578
performance: medium
540
- code_quality: low # Lenient
579
+ code_quality: low # Lenient
541
580
```
542
581
543
582
### "Missing obvious bugs"
573
612
run: och review ai ${{ github.event.pull_request.number }}
574
613
env:
575
614
OCH_TOKEN: ${{ secrets.OCH_TOKEN }}
576
-
615
+
577
616
- name: Check for critical issues
578
617
run: |
579
618
CRITICAL=$(och review status ${{ github.event.pull_request.number }} --json | jq '.critical_count')
580
619
▼docs/features/merge-queue.md
+60-31
1
+---
2
+title: "Legacy: Smart Merge Queue"
3
+slug: "legacy/features/merge-queue"
4
+---
5
+
1
6
# Smart Merge Queue
2
7
3
8
> Automate merging with stack-aware CI optimization and conflict resolution
21
26
### The Problem
22
27
23
28
Manual merging is:
29
+
24
30
- ⏰ **Slow** - Wait for CI, merge, repeat
25
31
- 🐛 **Error-prone** - Merge conflicts, wrong order
26
32
- 💸 **Expensive** - Run CI for every PR individually
30
36
31
37
Smart Merge Queue:
32
38
33
-✅ **Automatic Merging** - Set it and forget it
34
-✅ **Stack-Aware** - Merges in dependency order
35
-✅ **CI Optimization** - Batches compatible PRs
36
-✅ **Conflict Resolution** - Auto-rebases or alerts
39
+✅ **Automatic Merging** - Set it and forget it
40
+✅ **Stack-Aware** - Merges in dependency order
41
+✅ **CI Optimization** - Batches compatible PRs
42
+✅ **Conflict Resolution** - Auto-rebases or alerts
37
43
✅ **Priority Handling** - Hotfixes jump the queue
38
44
39
45
### Real-World Impact
40
46
41
47
**Without Queue:**
48
+
42
49
```
43
-PR #123 → Wait for CI (5 min) → Merge →
50
+PR #123 → Wait for CI (5 min) → Merge →
44
51
PR #124 → Rebase → Wait for CI (5 min) → Merge →
45
52
PR #125 → Rebase → Wait for CI (5 min) → Merge
46
53
Total: ~20 minutes, 3 CI runs
47
54
```
48
55
49
56
**With Queue:**
57
+
50
58
```
51
59
Add #123, #124, #125 to queue →
52
60
Queue detects dependencies →
105
113
### CI Optimization
106
114
107
115
**Batching:**
116
+
108
117
```
109
118
PR #126: Fix typo in docs (no code changes)
110
119
PR #127: Update README
117
126
```
118
127
119
128
**Smart Scheduling:**
129
+
120
130
- Long CI jobs run in parallel
121
131
- Short jobs batch together
122
132
- Stack dependencies respected
170
180
Navigate to `/queue` or click "Merge Queue" in repository navigation
171
181
172
182
**CLI:**
183
+
173
184
```bash
174
185
# View entire queue
175
186
och queue list
186
197
```
187
198
188
199
**API:**
200
+
189
201
```bash
190
202
curl https://git.yourcompany.com/api/queue \
191
203
-H "Authorization: Bearer $TOKEN"
197
209
198
210
### Priority Levels
199
211
200
-| Priority | Use Case | Behavior |
201
-|----------|----------|----------|
202
-| 🔥 **Critical** | Production outage | Jumps to front, blocks other merges |
203
-| ⚡ **High** | Security fix, hotfix | Merges before normal PRs |
204
-| 📄 **Normal** | Regular features | Default, FIFO order |
205
-| 🐌 **Low** | Refactoring, docs | Merges when queue is empty |
212
+| Priority | Use Case | Behavior |
213
+| --------------- | -------------------- | ----------------------------------- |
214
+| 🔥 **Critical** | Production outage | Jumps to front, blocks other merges |
215
+| ⚡ **High** | Security fix, hotfix | Merges before normal PRs |
216
+| 📄 **Normal** | Regular features | Default, FIFO order |
217
+| 🐌 **Low** | Refactoring, docs | Merges when queue is empty |
206
218
207
219
**Setting priority:**
220
+
208
221
```bash
209
222
# CLI
210
223
och queue add 126 --priority critical
245
258
246
259
```yaml
247
260
# Merge strategy
248
-merge_method: squash # or merge, rebase
261
+merge_method: squash # or merge, rebase
249
262
250
263
# CI requirements
251
264
require_ci: true
252
265
required_checks:
253
266
- "test"
254
- - "lint"
267
+ - "lint"
255
268
- "build"
256
269
257
-# Approval requirements
270
+# Approval requirements
258
271
require_approvals: 1
259
272
dismiss_stale_reviews: true
260
273
261
274
# Batching
262
275
enable_batching: true
263
-batch_window: 5m # Wait 5 min to batch PRs
264
-max_batch_size: 3 # Max 3 PRs per batch
276
+batch_window: 5m # Wait 5 min to batch PRs
277
+max_batch_size: 3 # Max 3 PRs per batch
265
278
266
279
# Rebasing
267
-auto_rebase: true # Auto-rebase on conflicts
268
-rebase_strategy: merge # or rebase, squash
280
+auto_rebase: true # Auto-rebase on conflicts
281
+rebase_strategy: merge # or rebase, squash
269
282
270
283
# Priorities
271
-allow_priority_override: true # Let users set priority
272
-max_queue_size: 50 # Max PRs in queue
284
+allow_priority_override: true # Let users set priority
285
+max_queue_size: 50 # Max PRs in queue
273
286
```
274
287
275
288
### Branch Protection Rules
285
298
- lint
286
299
required_approvals: 1
287
300
allow_force_push: false
288
- allow_queue_bypass: false # Even admins use queue!
301
+ allow_queue_bypass: false # Even admins use queue!
289
302
```
290
303
291
304
### Webhook Notifications
303
316
```
304
317
305
318
**Payload:**
319
+
306
320
```json
307
321
{
308
322
"event": "merge_queue.merged",
315
329
"position": 1,
316
330
"wait_time_seconds": 180
317
331
},
318
- "timestamp": "2024-01-01T12:00:00Z"
332
+ "timestamp": "2024-01-01T12:00:00Z"
319
333
}
320
334
```
321
335
326
340
### 💡 When to Use the Queue
327
341
328
342
**✅ Always use for:**
343
+
329
344
- Feature branches → main
330
345
- Release branches
331
346
- Any PR affecting production
332
347
333
348
**❌ Don't use for:**
349
+
334
350
- Draft PRs
335
-- WIP branches
351
+- WIP branches
336
352
- Experimental features
337
353
338
354
### 💡 Optimizing Queue Performance
339
355
340
356
**1. Fast CI:**
357
+
341
358
```yaml
342
359
# Run quick tests in queue
343
360
queue_ci:
344
- - unit_tests # 2 min
345
-
361
+ - unit_tests # 2 min
362
+
346
363
# Run slow tests post-merge
347
364
post_merge_ci:
348
- - integration_tests # 10 min
349
- - e2e_tests # 15 min
365
+ - integration_tests # 10 min
366
+ - e2e_tests # 15 min
350
367
```
351
368
352
369
**2. Parallel CIs:**
370
+
353
371
```yaml
354
372
# Run multiple jobs in parallel
355
373
ci:
360
378
```
361
379
362
380
**3. Smart batching:**
381
+
363
382
```bash
364
383
# Group related PRs
365
384
och queue add 123 124 125 --batch-id feature-auth
370
389
### 💡 Handling Conflicts
371
390
372
391
**Automatic resolution:**
392
+
373
393
```yaml
374
394
auto_rebase: true
375
395
rebase_strategy: merge # Creates merge commit
379
399
```
380
400
381
401
**Manual resolution:**
402
+
382
403
```bash
383
404
# If auto-rebase fails, you'll get notification
384
405
# Fix locally:
394
415
### 💡 Queue Monitoring
395
416
396
417
**Set up alerts:**
418
+
397
419
```yaml
398
420
# Slack notification if queue stalled
399
421
alerts:
400
422
- type: queue_stalled
401
423
threshold: 30m
402
424
channel: "#eng-deployments"
403
-
425
+
404
426
- type: merge_failed
405
427
channel: "#eng-alerts"
406
428
```
407
429
408
430
**Dashboard example:**
431
+
409
432
```bash
410
433
# View queue health
411
434
och queue stats
425
448
### "PR stuck in queue"
426
449
427
450
**Causes:**
451
+
428
452
1. CI failing
429
453
2. Waiting for approval
430
454
3. Merge conflict
431
455
4. Dependency not merged yet
432
456
433
457
**Solutions:**
458
+
434
459
```bash
435
460
# Check status
436
461
och queue status 125
449
474
**Cause:** Complex merge conflicts.
450
475
451
476
**Solution:**
477
+
452
478
```bash
453
479
# Remove from queue
454
480
och queue remove 125
466
492
### "Queue not processing"
467
493
468
494
**Causes:**
495
+
469
496
1. Queue paused
470
497
2. CI service down
471
498
3. Rate limit reached
472
499
473
500
**Solutions:**
501
+
474
502
```bash
475
503
# Check queue status
476
504
och queue info
490
518
**Cause:** Dependencies not detected.
491
519
492
520
**Solution:**
521
+
493
522
```bash
494
523
# Manually set dependency
495
524
och pr edit 125 --depends-on 124
510
539
merge_train:
511
540
enabled: true
512
541
max_concurrent: 5
513
-
542
+
514
543
# Creates temporary integration branches
515
544
# Runs CI in parallel
516
545
# Merges in rapid succession
538
567
// Update JIRA ticket
539
568
// Trigger deployment
540
569
},
541
-
570
+
542
571
afterMerge(pr) {
543
572
// Create release notes
544
573
// Update documentation
545
- }
574
+ },
546
575
};
547
576
```
548
577
549
578
▼docs/guides/storage-adapters.md
+11-0
1
+---
2
+title: "Legacy: Storage Adapters"
3
+slug: "legacy/guides/storage-adapters"
4
+---
5
+
1
6
# Storage Adapters
2
7
3
8
OpenCodeHub supports a pluggable storage system, allowing you to store repository data (git objects, LFS files, artifacts) on various backends.
16
21
Data is stored in the `data/` directory relative to the application root.
17
22
18
23
**Configuration:**
24
+
19
25
```env
20
26
STORAGE_TYPE=local
21
27
STORAGE_PATH=./data/storage # Optional, default is ./data
28
34
Store data in any S3-compatible bucket. This is recommended for production scalablity.
29
35
30
36
**Configuration:**
37
+
31
38
```env
32
39
STORAGE_TYPE=s3
33
40
STORAGE_BUCKET=my-opencodehub-bucket
40
47
### Examples
41
48
42
49
**MinIO (Self-hosted):**
50
+
43
51
```env
44
52
STORAGE_ENDPOINT=http://minio:9000
45
53
STORAGE_REGION=us-east-1
47
55
```
48
56
49
57
**Cloudflare R2:**
58
+
50
59
```env
51
60
STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
52
61
STORAGE_REGION=auto
75
84
```
76
85
77
86
**How to get Credentials:**
87
+
78
88
1. Go to [Google Cloud Console](https://console.cloud.google.com).
79
89
2. Create a project -> Enable **Google Drive API**.
80
90
3. Create **OAuth Client ID**.
85
95
## 🔷 Azure Blob Storage
86
96
87
97
**Configuration:**
98
+
88
99
```env
89
100
STORAGE_TYPE=azure
90
101
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
91
102
▼docs/index.md
+31-28
1
1
# OpenCodeHub Documentation
2
2
3
3
<p align="center">
4
- <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo.svg" alt="OpenCodeHub Logo" width="300">
4
+ <picture>
5
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-dark.png">
6
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png">
7
+ <img src="https://raw.githubusercontent.com/swadhinbiswas/OpenCodeHub/main/public/logo-light.png" alt="OpenCodeHub Logo" width="400">
8
+ </picture>
5
9
</p>
6
10
7
11
> Everything you need to know about OpenCodeHub - from getting started to advanced features
17
21
<h3>🎯 Getting Started</h3>
18
22
<p>Install and set up OpenCodeHub in 5 minutes</p>
19
23
</a>
20
-
24
+
21
25
<a href="#features">
22
26
<h3>✨ Features</h3>
23
27
<p>Learn about stacked PRs, AI review, and more</p>
24
28
</a>
25
-
29
+
26
30
<a href="#tutorials">
27
31
<h3>📚 Tutorials</h3>
28
32
<p>Hands-on guides to master OpenCodeHub</p>
29
33
</a>
30
-
34
+
31
35
<a href="#api">
32
36
<h3>🔌 API Reference</h3>
33
37
<p>Integrate with OpenCodeHub programmatically</p>
50
54
51
55
Learn about OpenCodeHub's powerful features:
52
56
53
-- **[Stacked Pull Requests](features/stacked-prs.md)** ⭐
57
+- **[Stacked Pull Requests](features/stacked-prs.md)** ⭐
54
58
Break large changes into reviewable stacks. Ship code faster.
55
-
56
-- **[AI Code Review](features/ai-review.md)** 🤖
59
+
60
+- **[AI Code Review](features/ai-review.md)** 🤖
57
61
Get instant feedback from GPT-4 or Claude. Catch bugs automatically.
58
-
59
-- **[Smart Merge Queue](features/merge-queue.md)** 🔀
62
+
63
+- **[Smart Merge Queue](features/merge-queue.md)** 🔀
60
64
Automate merging with stack-aware CI optimization.
61
-
62
-- **[Developer Metrics](features/developer-metrics.md)** 📊
65
+
66
+- **[Developer Metrics](features/developer-metrics.md)** 📊
63
67
Track velocity, review efficiency, and team performance.
64
-
65
-- **[Slack Integration](features/slack-integration.md)** 💬
68
+
69
+- **[Slack Integration](features/slack-integration.md)** 💬
66
70
Get actionable notifications in Slack. Review without leaving chat.
67
-
68
-- **[CI/CD Pipelines](features/ci-cd.md)** ⚙️
71
+
72
+- **[CI/CD Pipelines](features/ci-cd.md)** ⚙️
69
73
GitHub Actions-compatible workflows. Build, test, deploy.
70
74
71
75
### Hands-On Tutorials
72
76
73
77
Step-by-step guides to master OpenCodeHub:
74
78
75
-- **[Your First Stacked PR](tutorials/your-first-stack.md)** 🎓
79
+- **[Your First Stacked PR](tutorials/your-first-stack.md)** 🎓
76
80
15-minute tutorial: Create your first stack of PRs
77
-
78
-- **[AI-Powered Code Reviews](tutorials/ai-powered-reviews.md)**
81
+
82
+- **[AI-Powered Code Reviews](tutorials/ai-powered-reviews.md)**
79
83
Set up and use AI review for your projects
80
-
81
-- **[Automated Deployment](tutorials/automated-deployment.md)**
84
+
85
+- **[Automated Deployment](tutorials/automated-deployment.md)**
82
86
Build a complete CI/CD pipeline from scratch
83
87
84
88
### Guides
122
126
- **[Glossary](reference/glossary.md)** - Project terminology
123
127
- **[Troubleshooting](support/troubleshooting.md)** - Fix common issues
124
128
125
-
126
129
---
127
130
128
131
## 🎯 Popular Topics
267
270
268
271
## 🌟 Why OpenCodeHub?
269
272
270
-| Feature | GitHub | GitLab | OpenCodeHub |
271
-|---------|--------|--------|-------------|
272
-| Stacked PRs | ❌ | ❌ | ✅ Native support |
273
-| AI Code Review | ⚠️ Copilot only | ❌ | ✅ GPT-4 & Claude |
274
-| Smart Merge Queue | ⚠️ Basic | ⚠️ Basic | ✅ Stack-aware |
275
-| Self-Hosted | ❌ | ✅ Complex | ✅ Simple |
276
-| Cost | $$$ | $$$ | Free |
273
+| Feature | GitHub | GitLab | OpenCodeHub |
274
+| ----------------- | --------------- | ---------- | ----------------- |
275
+| Stacked PRs | ❌ | ❌ | ✅ Native support |
276
+| AI Code Review | ⚠️ Copilot only | ❌ | ✅ GPT-4 & Claude |
277
+| Smart Merge Queue | ⚠️ Basic | ⚠️ Basic | ✅ Stack-aware |
278
+| Self-Hosted | ❌ | ✅ Complex | ✅ Simple |
279
+| Cost | $$$ | $$$ | Free |
277
280
278
281
---
279
282
280
283
▼docs/reference/cli-commands.md
+25-0
1
+---
2
+title: "Legacy: CLI Command Reference"
3
+slug: "legacy/reference/cli-commands"
4
+---
5
+
1
6
# CLI Command Reference
2
7
3
8
The `opencodehub-cli` (or `och`) is your primary tool for interacting with OpenCodeHub from the terminal.
21
26
Manage authentication state.
22
27
23
28
### `och auth login`
29
+
24
30
Authenticates with an OpenCodeHub instance.
31
+
25
32
- **Interactive Mode**: Prompts for URL and credentials.
26
33
- **Flags**:
27
34
- `--url <url>`: OCH instance URL.
28
35
- `--token <token>`: Bypass browser login with existing token.
29
36
30
37
### `och auth status`
38
+
31
39
Check current login status and user.
32
40
33
41
### `och auth logout`
42
+
34
43
Clear stored credentials.
35
44
36
45
---
40
49
Manage repositories.
41
50
42
51
### `och clone <repo>`
52
+
43
53
Clone a repository.
54
+
44
55
- **Example**: `och clone swadhin/my-project`
45
56
- **Behavior**: Automatically sets up remote `origin`.
46
57
47
58
### `och create [name]`
59
+
48
60
Create a new repository.
61
+
49
62
- **Flags**:
50
63
- `--public`: Make repository public.
51
64
- `--private`: Make repository private.
52
65
- `--desc <text>`: Description.
53
66
54
67
### `och push`
68
+
55
69
Push changes to the remote.
70
+
56
71
- **Features**: Shows a progress bar and PR link on success.
57
72
58
73
---
62
77
Manage Stacked Pull Requests.
63
78
64
79
### `och stack create <branch-name>`
80
+
65
81
Create a new branch stacked on top of the current one.
82
+
66
83
- **Example**: `och stack create feature-b` (when on `feature-a`).
67
84
68
85
### `och stack submit`
86
+
69
87
Submit all branches in the current stack as Pull Requests.
88
+
70
89
- **Behavior**:
71
90
- Pushes all branches.
72
91
- Creates/Updates PRs.
73
92
- Sets correct base branches (`feature-a` -> `main`, `feature-b` -> `feature-a`).
74
93
75
94
### `och stack sync`
95
+
76
96
Rebase the entire stack.
97
+
77
98
- **Use Case**: When `main` has updated or a lower PR changed.
78
99
79
100
### `och stack view`
101
+
80
102
Visualize the current stack in ASCII tree format.
81
103
82
104
---
86
108
Manage Pull Requests.
87
109
88
110
### `och pr list`
111
+
89
112
List open PRs for current repo.
90
113
91
114
### `och pr checkout <number>`
115
+
92
116
Checkout a PR by its number.
93
117
94
118
### `och pr status [number]`
119
+
95
120
Show CI status and review state of a PR.
96
121
▼docs/tutorials/ai-powered-reviews.md
+7-0
1
+---
2
+title: "Legacy: AI-Powered Code Reviews"
3
+slug: "legacy/tutorials/ai-powered-reviews"
4
+---
5
+
1
6
# Tutorial: AI-Powered Code Reviews
2
7
3
8
OpenCodeHub integrates with LLMs (GPT-4, Claude) to provide automated, intelligent code reviews on every Pull Request. This tutorial guides you through setting it up.
12
17
As an administrator, you need to configure the AI provider in your environment variables.
13
18
14
19
**For OpenAI:**
20
+
15
21
```env
16
22
AI_PROVIDER=openai
17
23
OPENAI_API_KEY=sk-proj-...
18
24
```
19
25
20
26
**For Anthropic:**
27
+
21
28
```env
22
29
AI_PROVIDER=anthropic
23
30
ANTHROPIC_API_KEY=sk-ant-...
24
31
▼docs/tutorials/your-first-stack.md
+39-27
1
+---
2
+title: "Legacy: Your First Stacked PR"
3
+slug: "legacy/tutorials/your-first-stack"
4
+---
5
+
1
6
# Tutorial: Your First Stacked PR
2
7
3
8
> Learn how to split a large feature into reviewable chunks in 15 minutes
11
16
- Navigate the review process
12
17
- Merge the stack successfully
13
18
14
-**Time:** 15-20 minutes
19
+**Time:** 15-20 minutes
15
20
**Difficulty:** Beginner
16
21
17
22
---
27
32
- [ ] Basic Git knowledge (branch, commit, push)
28
33
29
34
**First time with OCH CLI?**
35
+
30
36
```bash
31
37
# Install
32
38
npm install -g opencodehub-cli
124
130
# Output:
125
131
# ✓ Pushing stack/auth-database
126
132
# ✓ Creating pull request
127
-#
133
+#
128
134
# PR #123 created: feat: add users table migration
129
135
# https://git.yourcompany.com/yourorg/your-repo/pulls/123
130
136
```
132
138
**2.5. Verify in web UI:**
133
139
134
140
Open the PR link. You should see:
141
+
135
142
- Title: "feat: add users table migration"
136
143
- Files changed: `db/migrations/001_create_users_table.sql`
137
144
- Status: Draft or Ready for Review
162
169
export class AuthService {
163
170
async register(email: string, password: string) {
164
171
const hash = await bcrypt.hash(password, 10);
165
-
172
+
166
173
const user = await db.users.create({
167
174
email,
168
175
password_hash: hash
169
176
});
170
-
177
+
171
178
return { id: user.id, email: user.email };
172
179
}
173
-
180
+
174
181
async login(email: string, password: string) {
175
182
const user = await db.users.findByEmail(email);
176
-
183
+
177
184
if (!user) {
178
185
throw new Error('Invalid credentials');
179
186
}
180
-
187
+
181
188
const valid = await bcrypt.compare(password, user.password_hash);
182
-
189
+
183
190
if (!valid) {
184
191
throw new Error('Invalid credentials');
185
192
}
186
-
193
+
187
194
const token = jwt.sign(
188
195
{ userId: user.id, email: user.email },
189
196
process.env.JWT_SECRET!,
190
197
{ expiresIn: '7d' }
191
198
);
192
-
199
+
193
200
return { token, user: { id: user.id, email: user.email } };
194
201
}
195
202
}
264
271
router.post('/register', async (req, res) => {
265
272
try {
266
273
const { email, password } = req.body;
267
-
274
+
268
275
// Validation
269
276
if (!email || !password) {
270
277
return res.status(400).json({ error: 'Email and password required' });
271
278
}
272
-
279
+
273
280
const user = await authService.register(email, password);
274
-
281
+
275
282
res.status(201).json({ user });
276
283
} catch (error) {
277
284
res.status(500).json({ error: error.message });
281
288
router.post('/login', async (req, res) => {
282
289
try {
283
290
const { email, password } = req.body;
284
-
291
+
285
292
const result = await authService.login(email, password);
286
-
293
+
287
294
res.json(result);
288
295
} catch (error) {
289
296
res.status(401).json({ error: error.message });
311
318
312
319
# Output:
313
320
# ✓ Pushing stack/auth-api
314
-# ✓ Creating pull request
321
+# ✓ Creating pull request
315
322
# ✓ Linking to base PR #124
316
323
#
317
324
# PR #125 created: feat: add authentication API endpoints
353
360
**5.2. Request reviews:**
354
361
355
362
In the web UI for PR #123:
363
+
356
364
1. Click "Reviewers" → Select team members
357
365
2. Add comment: "This is the first PR in a stack of 3. Reviewing these in order would be helpful!"
358
366
370
378
test('creates users table', async () => {
371
379
// Run migration
372
380
await db.migrate();
373
-
381
+
374
382
// Verify table exists
375
383
const tables = await db.raw(`
376
- SELECT table_name
377
- FROM information_schema.tables
384
+ SELECT table_name
385
+ FROM information_schema.tables
378
386
WHERE table_name = 'users'
379
387
`);
380
-
388
+
381
389
expect(tables.length).toBe(1);
382
390
});
383
391
EOF
440
448
# 🔄 Rebasing PR #124...
441
449
# 🏃 Running CI for #124...
442
450
#
443
-# [2 min later]
451
+# [2 min later]
444
452
# ✅ PR #124 merged!
445
453
# 🔄 Rebasing PR #125...
446
454
```
456
464
457
465
# Output:
458
466
# abc123 feat: add authentication API endpoints
459
-# def456 feat: implement authentication service
467
+# def456 feat: implement authentication service
460
468
# ghi789 feat: add users table migration
461
469
```
462
470
466
474
467
475
## What You've Learned
468
476
469
-✅ How to create a stack of dependent PRs
470
-✅ Using `och stack create` for easy stacking
471
-✅ Submitting stacks with `och stack submit`
472
-✅ Viewing stack visualization
473
-✅ Using the merge queue for automatic merging
477
+✅ How to create a stack of dependent PRs
478
+✅ Using `och stack create` for easy stacking
479
+✅ Submitting stacks with `och stack submit`
480
+✅ Viewing stack visualization
481
+✅ Using the merge queue for automatic merging
474
482
✅ How auto-rebasing works
475
483
476
484
## Key Takeaways
477
485
478
486
**Stacked PRs are better because:**
487
+
479
488
- Each PR was ~50-100 lines (easy to review)
480
489
- Reviews happened in parallel (faster)
481
490
- Each merge was low-risk (incremental)
482
491
- Clear history (logical progression)
483
492
484
493
**vs. One Large PR:**
494
+
485
495
- Would be ~200-300 lines
486
496
- Single review bottleneck
487
497
- High-risk merge
494
504
### Practice More
495
505
496
506
Try stacking your next feature:
507
+
497
508
- 🎨 Frontend feature → Split UI, logic, styles
498
509
- 🔧 Backend feature → Split models, services, controllers
499
510
- 📊 Data pipeline → Split ingestion, processing, output
501
512
### Advanced Techniques
502
513
503
514
Learn more about:
515
+
504
516
- [Parallel stacks](../features/stacked-prs.md#parallel-stacks)
505
517
- [Stack reordering](../features/stacked-prs.md#reordering)
506
518
- [AI code review](../features/ai-review.md) on stacks
507
519
▼public/docs/index.html
+447-305
1
-<!DOCTYPE html>
1
+<!doctype html>
2
2
<html lang="en">
3
-
4
-<head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
6
<title>OpenCodeHub Documentation</title>
8
- <meta name="description"
9
- content="Complete documentation for OpenCodeHub - Self-hosted Git platform with stacked PRs, AI review, and CI/CD">
10
- <link rel="icon" type="image/svg+xml" href="../public/logo.svg">
11
- <link rel="preconnect" href="https://fonts.googleapis.com">
12
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
7
+ <meta
8
+ name="description"
9
+ content="Complete documentation for OpenCodeHub - Self-hosted Git platform with stacked PRs, AI review, and CI/CD"
10
+ />
11
+ <link rel="icon" type="image/svg+xml" href="../public/logo.svg" />
12
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
13
14
<link
14
- href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
15
- rel="stylesheet">
16
- <link rel="stylesheet" href="styles.css">
17
-</head>
15
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
16
+ rel="stylesheet"
17
+ />
18
+ <link rel="stylesheet" href="styles.css" />
19
+ </head>
18
20
19
-<body>
21
+ <body>
20
22
<!-- Header -->
21
23
<header class="header">
22
- <a href="index.html" class="header-logo">
23
- <img src="../public/logo.svg" alt="OpenCodeHub" height="40">
24
+ <a href="index.html" class="header-logo">
25
+ <img src="../public/logo.svg" alt="OpenCodeHub" height="40" />
26
+ </a>
27
+ <nav class="header-nav">
28
+ <a href="getting-started.html">Getting Started</a>
29
+ <a href="deployment.html">Deployment</a>
30
+ <a href="cli.html">CLI</a>
31
+ <a href="api.html">API</a>
32
+ <a
33
+ href="https://github.com/swadhinbiswas/OpencodeHub"
34
+ class="btn-github"
35
+ target="_blank"
36
+ >
37
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
38
+ <path
39
+ d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"
40
+ />
41
+ </svg>
42
+ GitHub
24
43
</a>
25
- <nav class="header-nav">
26
- <a href="getting-started.html">Getting Started</a>
27
- <a href="deployment.html">Deployment</a>
28
- <a href="cli.html">CLI</a>
29
- <a href="api.html">API</a>
30
- <a href="https://github.com/swadhinbiswas/OpencodeHub" class="btn-github" target="_blank">
31
- <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
32
- <path
33
- d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
34
- </svg>
35
- GitHub
36
- </a>
37
- </nav>
44
+ </nav>
38
45
</header>
39
46
40
47
<div class="layout">
41
- <!-- Sidebar -->
42
- <aside class="sidebar">
43
- <div class="sidebar-section">
44
- <div class="sidebar-title">Getting Started</div>
45
- <nav class="sidebar-nav">
46
- <a href="getting-started.html" class="sidebar-link active">
47
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
48
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
49
- </svg>
50
- Quick Start
51
- </a>
52
- <a href="getting-started.html#installation" class="sidebar-link">
53
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
54
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
55
- </svg>
56
- Installation
57
- </a>
58
- <a href="getting-started.html#configuration" class="sidebar-link">
59
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
60
- <circle cx="12" cy="12" r="3" />
61
- <path
62
- d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" />
63
- </svg>
64
- Configuration
65
- </a>
66
- </nav>
67
- </div>
68
-
69
- <div class="sidebar-section">
70
- <div class="sidebar-title">Deployment</div>
71
- <nav class="sidebar-nav">
72
- <a href="deployment.html" class="sidebar-link">
73
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
74
- <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
75
- <path d="M8 21h8M12 17v4" />
76
- </svg>
77
- Overview
78
- </a>
79
- <a href="deployment.html#docker" class="sidebar-link">
80
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
81
- <path
82
- d="M22 12.5c0 .5-.5 1-1 1h-2v-2h2c.5 0 1 .5 1 1zM2 12.5c0-.5.5-1 1-1h2v2H3c-.5 0-1-.5-1-1z" />
83
- <rect x="6" y="9" width="12" height="6" rx="1" />
84
- <rect x="8" y="11" width="2" height="2" />
85
- <rect x="11" y="11" width="2" height="2" />
86
- <rect x="14" y="11" width="2" height="2" />
87
- </svg>
88
- Docker
89
- </a>
90
- <a href="deployment.html#vercel" class="sidebar-link">
91
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
92
- <polygon points="12 2 2 22 22 22" />
93
- </svg>
94
- Vercel
95
- </a>
96
- <a href="deployment.html#vps" class="sidebar-link">
97
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
98
- <rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
99
- <rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
100
- <line x1="6" y1="6" x2="6.01" y2="6" />
101
- <line x1="6" y1="18" x2="6.01" y2="18" />
102
- </svg>
103
- VPS / Linux
104
- </a>
105
- </nav>
106
- </div>
48
+ <!-- Sidebar -->
49
+ <aside class="sidebar">
50
+ <div class="sidebar-section">
51
+ <div class="sidebar-title">Getting Started</div>
52
+ <nav class="sidebar-nav">
53
+ <a href="getting-started.html" class="sidebar-link active">
54
+ <svg
55
+ viewBox="0 0 24 24"
56
+ fill="none"
57
+ stroke="currentColor"
58
+ stroke-width="2"
59
+ >
60
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
61
+ </svg>
62
+ Quick Start
63
+ </a>
64
+ <a href="getting-started.html#installation" class="sidebar-link">
65
+ <svg
66
+ viewBox="0 0 24 24"
67
+ fill="none"
68
+ stroke="currentColor"
69
+ stroke-width="2"
70
+ >
71
+ <path
72
+ d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"
73
+ />
74
+ </svg>
75
+ Installation
76
+ </a>
77
+ <a href="getting-started.html#configuration" class="sidebar-link">
78
+ <svg
79
+ viewBox="0 0 24 24"
80
+ fill="none"
81
+ stroke="currentColor"
82
+ stroke-width="2"
83
+ >
84
+ <circle cx="12" cy="12" r="3" />
85
+ <path
86
+ d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"
87
+ />
88
+ </svg>
89
+ Configuration
90
+ </a>
91
+ </nav>
92
+ </div>
107
93
108
- <div class="sidebar-section">
109
- <div class="sidebar-title">CLI</div>
110
- <nav class="sidebar-nav">
111
- <a href="cli.html" class="sidebar-link">
112
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
113
- <polyline points="4 17 10 11 4 5" />
114
- <line x1="12" y1="19" x2="20" y2="19" />
115
- </svg>
116
- CLI Reference
117
- </a>
118
- <a href="cli.html#stacked-prs" class="sidebar-link">
119
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
120
- <path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2" />
121
- <rect x="8" y="2" width="8" height="4" rx="1" ry="1" />
122
- </svg>
123
- Stacked PRs
124
- </a>
125
- </nav>
126
- </div>
94
+ <div class="sidebar-section">
95
+ <div class="sidebar-title">Deployment</div>
96
+ <nav class="sidebar-nav">
97
+ <a href="deployment.html" class="sidebar-link">
98
+ <svg
99
+ viewBox="0 0 24 24"
100
+ fill="none"
101
+ stroke="currentColor"
102
+ stroke-width="2"
103
+ >
104
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
105
+ <path d="M8 21h8M12 17v4" />
106
+ </svg>
107
+ Overview
108
+ </a>
109
+ <a href="deployment.html#docker" class="sidebar-link">
110
+ <svg
111
+ viewBox="0 0 24 24"
112
+ fill="none"
113
+ stroke="currentColor"
114
+ stroke-width="2"
115
+ >
116
+ <path
117
+ d="M22 12.5c0 .5-.5 1-1 1h-2v-2h2c.5 0 1 .5 1 1zM2 12.5c0-.5.5-1 1-1h2v2H3c-.5 0-1-.5-1-1z"
118
+ />
119
+ <rect x="6" y="9" width="12" height="6" rx="1" />
120
+ <rect x="8" y="11" width="2" height="2" />
121
+ <rect x="11" y="11" width="2" height="2" />
122
+ <rect x="14" y="11" width="2" height="2" />
123
+ </svg>
124
+ Docker
125
+ </a>
126
+ <a href="deployment.html#vercel" class="sidebar-link">
127
+ <svg
128
+ viewBox="0 0 24 24"
129
+ fill="none"
130
+ stroke="currentColor"
131
+ stroke-width="2"
132
+ >
133
+ <polygon points="12 2 2 22 22 22" />
134
+ </svg>
135
+ Vercel
136
+ </a>
137
+ <a href="deployment.html#vps" class="sidebar-link">
138
+ <svg
139
+ viewBox="0 0 24 24"
140
+ fill="none"
141
+ stroke="currentColor"
142
+ stroke-width="2"
143
+ >
144
+ <rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
145
+ <rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
146
+ <line x1="6" y1="6" x2="6.01" y2="6" />
147
+ <line x1="6" y1="18" x2="6.01" y2="18" />
148
+ </svg>
149
+ VPS / Linux
150
+ </a>
151
+ </nav>
152
+ </div>
127
153
128
- <div class="sidebar-section">
129
- <div class="sidebar-title">API</div>
130
- <nav class="sidebar-nav">
131
- <a href="api.html" class="sidebar-link">
132
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
133
- <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
134
- <polyline points="14 2 14 8 20 8" />
135
- <line x1="16" y1="13" x2="8" y2="13" />
136
- <line x1="16" y1="17" x2="8" y2="17" />
137
- <polyline points="10 9 9 9 8 9" />
138
- </svg>
139
- REST API
140
- </a>
141
- <a href="api.html#webhooks" class="sidebar-link">
142
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
143
- <path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" />
144
- <path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" />
145
- </svg>
146
- Webhooks
147
- </a>
148
- </nav>
149
- </div>
150
- </aside>
154
+ <div class="sidebar-section">
155
+ <div class="sidebar-title">CLI</div>
156
+ <nav class="sidebar-nav">
157
+ <a href="cli.html" class="sidebar-link">
158
+ <svg
159
+ viewBox="0 0 24 24"
160
+ fill="none"
161
+ stroke="currentColor"
162
+ stroke-width="2"
163
+ >
164
+ <polyline points="4 17 10 11 4 5" />
165
+ <line x1="12" y1="19" x2="20" y2="19" />
166
+ </svg>
167
+ CLI Reference
168
+ </a>
169
+ <a href="cli.html#stacked-prs" class="sidebar-link">
170
+ <svg
171
+ viewBox="0 0 24 24"
172
+ fill="none"
173
+ stroke="currentColor"
174
+ stroke-width="2"
175
+ >
176
+ <path
177
+ d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"
178
+ />
179
+ <rect x="8" y="2" width="8" height="4" rx="1" ry="1" />
180
+ </svg>
181
+ Stacked PRs
182
+ </a>
183
+ </nav>
184
+ </div>
151
185
152
- <!-- Main Content -->
153
- <main class="main">
154
- <div class="content">
155
- <!-- Hero Section -->
156
- <section class="hero animate-fade-in">
157
- <h1>OpenCodeHub Documentation</h1>
158
- <p class="hero-subtitle">
159
- Everything you need to deploy, configure, and master OpenCodeHub -
160
- the self-hosted Git platform with stacked PRs, AI review, and CI/CD.
161
- </p>
162
- <div class="hero-buttons">
163
- <a href="getting-started.html" class="btn btn-primary">
164
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
165
- stroke-width="2">
166
- <polygon points="5 3 19 12 5 21 5 3" />
167
- </svg>
168
- Get Started
169
- </a>
170
- <a href="deployment.html" class="btn btn-secondary">
171
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
172
- stroke-width="2">
173
- <path
174
- d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" />
175
- </svg>
176
- Deploy Now
177
- </a>
178
- </div>
179
- </section>
186
+ <div class="sidebar-section">
187
+ <div class="sidebar-title">API</div>
188
+ <nav class="sidebar-nav">
189
+ <a href="api.html" class="sidebar-link">
190
+ <svg
191
+ viewBox="0 0 24 24"
192
+ fill="none"
193
+ stroke="currentColor"
194
+ stroke-width="2"
195
+ >
196
+ <path
197
+ d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"
198
+ />
199
+ <polyline points="14 2 14 8 20 8" />
200
+ <line x1="16" y1="13" x2="8" y2="13" />
201
+ <line x1="16" y1="17" x2="8" y2="17" />
202
+ <polyline points="10 9 9 9 8 9" />
203
+ </svg>
204
+ REST API
205
+ </a>
206
+ <a href="api.html#webhooks" class="sidebar-link">
207
+ <svg
208
+ viewBox="0 0 24 24"
209
+ fill="none"
210
+ stroke="currentColor"
211
+ stroke-width="2"
212
+ >
213
+ <path
214
+ d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"
215
+ />
216
+ <path
217
+ d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"
218
+ />
219
+ </svg>
220
+ Webhooks
221
+ </a>
222
+ </nav>
223
+ </div>
224
+ </aside>
180
225
181
- <!-- Quick Install -->
182
- <section>
183
- <h2>⚡ Quick Install</h2>
184
- <p>Get OpenCodeHub running in minutes with Docker:</p>
226
+ <!-- Main Content -->
227
+ <main class="main">
228
+ <div class="content">
229
+ <!-- Hero Section -->
230
+ <section class="hero animate-fade-in">
231
+ <h1>OpenCodeHub Documentation</h1>
232
+ <p class="hero-subtitle">
233
+ Everything you need to deploy, configure, and master OpenCodeHub -
234
+ the self-hosted Git platform with stacked PRs, AI review, and
235
+ CI/CD.
236
+ </p>
237
+ <div class="hero-buttons">
238
+ <a href="getting-started.html" class="btn btn-primary">
239
+ <svg
240
+ width="18"
241
+ height="18"
242
+ viewBox="0 0 24 24"
243
+ fill="none"
244
+ stroke="currentColor"
245
+ stroke-width="2"
246
+ >
247
+ <polygon points="5 3 19 12 5 21 5 3" />
248
+ </svg>
249
+ Get Started
250
+ </a>
251
+ <a href="deployment.html" class="btn btn-secondary">
252
+ <svg
253
+ width="18"
254
+ height="18"
255
+ viewBox="0 0 24 24"
256
+ fill="none"
257
+ stroke="currentColor"
258
+ stroke-width="2"
259
+ >
260
+ <path
261
+ d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"
262
+ />
263
+ </svg>
264
+ Deploy Now
265
+ </a>
266
+ </div>
267
+ </section>
185
268
186
- <div class="command-line">
187
- <span class="command-prompt">$</span>
188
- <span class="command-text">git clone https://github.com/swadhinbiswas/OpencodeHub && cd
189
- OpenCodeHub</span>
190
- <button class="command-copy"
191
- onclick="copyCommand(this, 'git clone https://github.com/swadhinbiswas/OpencodeHub && cd OpenCodeHub')">
192
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
193
- stroke-width="2">
194
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
195
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
196
- </svg>
197
- </button>
198
- </div>
269
+ <!-- Quick Install -->
270
+ <section>
271
+ <h2>⚡ Quick Install</h2>
272
+ <p>Get OpenCodeHub running in minutes with Docker:</p>
199
273
200
- <div class="command-line">
201
- <span class="command-prompt">$</span>
202
- <span class="command-text">docker-compose up -d</span>
203
- <button class="command-copy" onclick="copyCommand(this, 'docker-compose up -d')">
204
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
205
- stroke-width="2">
206
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
207
- <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
208
- </svg>
209
- </button>
210
- </div>
274
+ <div class="command-line">
275
+ <span class="command-prompt">$</span>
276
+ <span class="command-text"
277
+ >git clone https://github.com/swadhinbiswas/OpencodeHub && cd
278
+ OpenCodeHub</span
279
+ >
280
+ <button
281
+ class="command-copy"
282
+ onclick="
283
+ copyCommand(
284
+ this,
285
+ 'git clone https://github.com/swadhinbiswas/OpencodeHub && cd OpenCodeHub',
286
+ )
287
+ "
288
+ >
289
+ <svg
290
+ width="16"
291
+ height="16"
292
+ viewBox="0 0 24 24"
293
+ fill="none"
294
+ stroke="currentColor"
295
+ stroke-width="2"
296
+ >
297
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
298
+ <path
299
+ d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"
300
+ />
301
+ </svg>
302
+ </button>
303
+ </div>
211
304
212
- <p>Then visit <code>http://localhost:3000</code> in your browser.</p>
213
- </section>
305
+ <div class="command-line">
306
+ <span class="command-prompt">$</span>
307
+ <span class="command-text">docker-compose up -d</span>
308
+ <button
309
+ class="command-copy"
310
+ onclick="copyCommand(this, 'docker-compose up -d')"
311
+ >
312
+ <svg
313
+ width="16"
314
+ height="16"
315
+ viewBox="0 0 24 24"
316
+ fill="none"
317
+ stroke="currentColor"
318
+ stroke-width="2"
319
+ >
320
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
321
+ <path
322
+ d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"
323
+ />
324
+ </svg>
325
+ </button>
326
+ </div>
214
327
215
- <!-- Features -->
216
- <section>
217
- <h2>✨ Key Features</h2>
218
- <div class="feature-grid">
219
- <div class="card">
220
- <h3 class="card-title">
221
- <span>📚</span> Stacked Pull Requests
222
- </h3>
223
- <p class="card-description">
224
- Break large changes into reviewable chunks. Ship faster with dependent PRs that
225
- automatically update.
226
- </p>
227
- </div>
228
- <div class="card">
229
- <h3 class="card-title">
230
- <span>🤖</span> AI Code Review
231
- </h3>
232
- <p class="card-description">
233
- Instant feedback from GPT-4 or Claude. Catch security issues, bugs, and code smells
234
- automatically.
235
- </p>
236
- </div>
237
- <div class="card">
238
- <h3 class="card-title">
239
- <span>🔀</span> Smart Merge Queue
240
- </h3>
241
- <p class="card-description">
242
- Stack-aware CI optimization. Batch PRs, auto-rebase, and merge in the correct order.
243
- </p>
244
- </div>
245
- <div class="card">
246
- <h3 class="card-title">
247
- <span>⚡</span> CI/CD Pipelines
248
- </h3>
249
- <p class="card-description">
250
- GitHub Actions-compatible workflows. Build, test, and deploy with self-hosted runners.
251
- </p>
252
- </div>
253
- <div class="card">
254
- <h3 class="card-title">
255
- <span>🔒</span> Enterprise Security
256
- </h3>
257
- <p class="card-description">
258
- Rate limiting, CSRF protection, audit logging, 2FA, and encrypted secrets management.
259
- </p>
260
- </div>
261
- <div class="card">
262
- <h3 class="card-title">
263
- <span>💻</span> Powerful CLI
264
- </h3>
265
- <p class="card-description">
266
- Stack-first workflows from your terminal. Manage PRs, sync stacks, and more.
267
- </p>
268
- </div>
269
- </div>
270
- </section>
328
+ <p>
329
+ Then visit <code>http://localhost:3000</code> in your browser.
330
+ </p>
331
+ </section>
271
332
272
- <!-- Documentation Sections -->
273
- <section>
274
- <h2>📖 Documentation</h2>
333
+ <!-- Features -->
334
+ <section>
335
+ <h2>✨ Key Features</h2>
336
+ <div class="feature-grid">
337
+ <div class="card">
338
+ <h3 class="card-title">
339
+ <span>📚</span> Stacked Pull Requests
340
+ </h3>
341
+ <p class="card-description">
342
+ Break large changes into reviewable chunks. Ship faster with
343
+ dependent PRs that automatically update.
344
+ </p>
345
+ </div>
346
+ <div class="card">
347
+ <h3 class="card-title"><span>🤖</span> AI Code Review</h3>
348
+ <p class="card-description">
349
+ Instant feedback from GPT-4 or Claude. Catch security issues,
350
+ bugs, and code smells automatically.
351
+ </p>
352
+ </div>
353
+ <div class="card">
354
+ <h3 class="card-title"><span>🔀</span> Smart Merge Queue</h3>
355
+ <p class="card-description">
356
+ Stack-aware CI optimization. Batch PRs, auto-rebase, and merge
357
+ in the correct order.
358
+ </p>
359
+ </div>
360
+ <div class="card">
361
+ <h3 class="card-title"><span>⚡</span> CI/CD Pipelines</h3>
362
+ <p class="card-description">
363
+ GitHub Actions-compatible workflows. Build, test, and deploy
364
+ with self-hosted runners.
365
+ </p>
366
+ </div>
367
+ <div class="card">
368
+ <h3 class="card-title"><span>🔒</span> Enterprise Security</h3>
369
+ <p class="card-description">
370
+ Rate limiting, CSRF protection, audit logging, 2FA, and
371
+ encrypted secrets management.
372
+ </p>
373
+ </div>
374
+ <div class="card">
375
+ <h3 class="card-title"><span>💻</span> Powerful CLI</h3>
376
+ <p class="card-description">
377
+ Stack-first workflows from your terminal. Manage PRs, sync
378
+ stacks, and more.
379
+ </p>
380
+ </div>
381
+ </div>
382
+ </section>
275
383
276
- <div class="card" style="cursor: pointer" onclick="location.href='getting-started.html'">
277
- <h3 class="card-title">🚀 Getting Started</h3>
278
- <p class="card-description">
279
- Install OpenCodeHub, create your first repository, and configure your environment.
280
- </p>
281
- </div>
384
+ <!-- Documentation Sections -->
385
+ <section>
386
+ <h2>📖 Documentation</h2>
282
387
283
- <div class="card" style="cursor: pointer" onclick="location.href='deployment.html'">
284
- <h3 class="card-title">🌐 Deployment Guide</h3>
285
- <p class="card-description">
286
- Deploy to Docker, Vercel, VPS, or any cloud provider with production-ready configurations.
287
- </p>
288
- </div>
388
+ <div
389
+ class="card"
390
+ style="cursor: pointer"
391
+ onclick="location.href = 'getting-started.html'"
392
+ >
393
+ <h3 class="card-title">🚀 Getting Started</h3>
394
+ <p class="card-description">
395
+ Install OpenCodeHub, create your first repository, and configure
396
+ your environment.
397
+ </p>
398
+ </div>
289
399
290
- <div class="card" style="cursor: pointer" onclick="location.href='cli.html'">
291
- <h3 class="card-title">💻 CLI Reference</h3>
292
- <p class="card-description">
293
- Complete command reference for the OpenCodeHub CLI. Stack management, authentication, and
294
- more.
295
- </p>
296
- </div>
400
+ <div
401
+ class="card"
402
+ style="cursor: pointer"
403
+ onclick="location.href = 'deployment.html'"
404
+ >
405
+ <h3 class="card-title">🌐 Deployment Guide</h3>
406
+ <p class="card-description">
407
+ Deploy to Docker, Vercel, VPS, or any cloud provider with
408
+ production-ready configurations.
409
+ </p>
410
+ </div>
297
411
298
- <div class="card" style="cursor: pointer" onclick="location.href='api.html'">
299
- <h3 class="card-title">🔌 API Documentation</h3>
300
- <p class="card-description">
301
- REST API reference, webhook events, and authentication for programmatic access.
302
- </p>
303
- </div>
304
- </section>
412
+ <div
413
+ class="card"
414
+ style="cursor: pointer"
415
+ onclick="location.href = 'cli.html'"
416
+ >
417
+ <h3 class="card-title">💻 CLI Reference</h3>
418
+ <p class="card-description">
419
+ Complete command reference for the OpenCodeHub CLI. Stack
420
+ management, authentication, and more.
421
+ </p>
422
+ </div>
305
423
306
- <!-- Footer -->
307
- <footer
308
- style="margin-top: 4rem; padding-top: 2rem; border-top: 1px solid var(--border-primary); text-align: center; color: var(--text-muted);">
309
- <p>
310
- Made with ❤️ by <a href="https://github.com/swadhinbiswas">Swadhin Biswas</a> •
311
- <a href="https://github.com/swadhinbiswas/OpencodeHub">GitHub</a> •
312
- <a href="https://github.com/swadhinbiswas/OpencodeHub/blob/main/LICENSE">MIT License</a>
313
- </p>
314
- </footer>
424
+ <div
425
+ class="card"
426
+ style="cursor: pointer"
427
+ onclick="location.href = 'api.html'"
428
+ >
429
+ <h3 class="card-title">🔌 API Documentation</h3>
430
+ <p class="card-description">
431
+ REST API reference, webhook events, and authentication for
432
+ programmatic access.
433
+ </p>
315
434
</div>
316
- </main>
435
+ </section>
436
+
437
+ <!-- Footer -->
438
+ <footer
439
+ style="
440
+ margin-top: 4rem;
441
+ padding-top: 2rem;
442
+ border-top: 1px solid var(--border-primary);
443
+ text-align: center;
444
+ color: var(--text-muted);
445
+ "
446
+ >
447
+ <p>
448
+ Made with ❤️ by
449
+ <a href="https://github.com/swadhinbiswas">Swadhin Biswas</a> •
450
+ <a href="https://github.com/swadhinbiswas/OpencodeHub">GitHub</a>
451
+ •
452
+ <a
453
+ href="https://github.com/swadhinbiswas/OpencodeHub/blob/main/LICENSE"
454
+ >MIT License</a
455
+ >
456
+ </p>
457
+ </footer>
458
+ </div>
459
+ </main>
317
460
</div>
318
461
319
462
<script>
320
- function copyCommand(button, text) {
321
- navigator.clipboard.writeText(text);
322
- button.classList.add('copied');
323
- setTimeout(() => button.classList.remove('copied'), 2000);
324
- }
463
+ function copyCommand(button, text) {
464
+ navigator.clipboard.writeText(text);
465
+ button.classList.add("copied");
466
+ setTimeout(() => button.classList.remove("copied"), 2000);
467
+ }
325
468
326
- // Update active sidebar link based on current page
327
- document.querySelectorAll('.sidebar-link').forEach(link => {
328
- if (link.href === window.location.href) {
329
- link.classList.add('active');
330
- }
331
- });
469
+ // Update active sidebar link based on current page
470
+ document.querySelectorAll(".sidebar-link").forEach((link) => {
471
+ if (link.href === window.location.href) {
472
+ link.classList.add("active");
473
+ }
474
+ });
332
475
</script>
333
-</body>
334
-
335
-</html>
476
+ </body>
477
+</html>
336
478
▼public/favicon.png
+0-0
Binary file not shown.
▼public/logo-dark.png
+0-0
Binary file not shown.
▼public/logo-light.png
+0-0
Binary file not shown.
▼public/logo.png
+0-0
Binary file not shown.
▼public/stack-workflow.svg
+113-42
1
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300">
1
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 400">
2
2
<defs>
3
3
<style>
4
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&amp;display=swap');
4
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&amp;display=swap');
5
5
</style>
6
- <!-- Gradients -->
7
- <linearGradient id="grad-main" x1="0%" y1="0%" x2="100%" y2="0%">
8
- <stop offset="0%" style="stop-color:#22c55e;stop-opacity:1" />
9
- <stop offset="100%" style="stop-color:#16a34a;stop-opacity:1" />
6
+
7
+ <!-- Modern Gradients matching logo colors -->
8
+ <linearGradient id="grad-main" x1="0%" y1="0%" x2="100%" y2="100%">
9
+ <stop offset="0%" style="stop-color:#50fa7b;stop-opacity:1" />
10
+ <stop offset="100%" style="stop-color:#2dd4bf;stop-opacity:1" />
11
+ </linearGradient>
12
+
13
+ <linearGradient id="grad-feat1" x1="0%" y1="0%" x2="100%" y2="100%">
14
+ <stop offset="0%" style="stop-color:#8be9fd;stop-opacity:1" />
15
+ <stop offset="100%" style="stop-color:#60a5fa;stop-opacity:1" />
10
16
</linearGradient>
11
- <linearGradient id="grad-feat1" x1="0%" y1="0%" x2="100%" y2="0%">
12
- <stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
13
- <stop offset="100%" style="stop-color:#2563eb;stop-opacity:1" />
17
+
18
+ <linearGradient id="grad-feat2" x1="0%" y1="0%" x2="100%" y2="100%">
19
+ <stop offset="0%" style="stop-color:#bd93f9;stop-opacity:1" />
20
+ <stop offset="100%" style="stop-color:#a78bfa;stop-opacity:1" />
14
21
</linearGradient>
15
- <linearGradient id="grad-feat2" x1="0%" y1="0%" x2="100%" y2="0%">
16
- <stop offset="0%" style="stop-color:#a855f7;stop-opacity:1" />
17
- <stop offset="100%" style="stop-color:#9333ea;stop-opacity:1" />
22
+
23
+ <linearGradient id="grad-feat3" x1="0%" y1="0%" x2="100%" y2="100%">
24
+ <stop offset="0%" style="stop-color:#ff79c6;stop-opacity:1" />
25
+ <stop offset="100%" style="stop-color:#f472b6;stop-opacity:1" />
18
26
</linearGradient>
19
-
27
+
28
+ <!-- Enhanced shadow -->
20
29
<filter id="shadow">
21
- <feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000" flood-opacity="0.2"/>
30
+ <feDropShadow dx="0" dy="6" stdDeviation="8" flood-color="#000" flood-opacity="0.25"/>
22
31
</filter>
23
-
24
- <marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
25
- <path d="M0,0 L0,6 L9,3 z" fill="#64748b" />
32
+
33
+ <filter id="glow">
34
+ <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
35
+ <feMerge>
36
+ <feMergeNode in="coloredBlur"/>
37
+ <feMergeNode in="SourceGraphic"/>
38
+ </feMerge>
39
+ </filter>
40
+
41
+ <!-- Arrow markers -->
42
+ <marker id="arrow" markerWidth="12" markerHeight="12" refX="10" refY="4" orient="auto">
43
+ <path d="M0,0 L0,8 L10,4 z" fill="#8be9fd" />
44
+ </marker>
45
+
46
+ <marker id="arrow-down" markerWidth="12" markerHeight="12" refX="4" refY="10" orient="auto">
47
+ <path d="M0,0 L8,0 L4,10 z" fill="#ff79c6" opacity="0.6" />
26
48
</marker>
27
49
</defs>
28
50
29
- <!-- Background (optional) -->
30
- <!-- <rect width="800" height="300" fill="#f8fafc" /> -->
51
+ <!-- Background with subtle gradient -->
52
+ <rect width="900" height="400" fill="#1a1b26" opacity="0.02" />
31
53
32
- <!-- Title -->
33
- <text x="400" y="40" font-family="Inter, sans-serif" font-size="20" font-weight="600" text-anchor="middle" fill="#334155">Stacked PR Workflow</text>
54
+ <!-- Title with modern styling -->
55
+ <text x="450" y="45" font-family="Inter, sans-serif" font-size="26" font-weight="700" text-anchor="middle" fill="#2dd4bf">
56
+ Stacked PR Workflow
57
+ </text>
58
+ <text x="450" y="70" font-family="Inter, sans-serif" font-size="14" text-anchor="middle" fill="#64748b">
59
+ Build dependent features in parallel with confidence
60
+ </text>
34
61
35
62
<!-- Workflow Steps -->
36
- <g transform="translate(50, 80)">
37
-
38
- <!-- Step 1: Main (Base) -->
39
- <g transform="translate(0, 50)">
40
- <rect x="0" y="0" width="160" height="80" rx="12" fill="url(#grad-main)" filter="url(#shadow)"/>
41
- <text x="80" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">main</text>
42
- <text x="80" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.9">Production Ready</text>
63
+ <g transform="translate(50, 120)">
64
+
65
+ <!-- Step 1: Main Branch -->
66
+ <g transform="translate(0, 40)">
67
+ <rect x="0" y="0" width="180" height="90" rx="16" fill="url(#grad-main)" filter="url(#shadow)" opacity="0.95"/>
68
+ <rect x="2" y="2" width="176" height="86" rx="15" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.2"/>
69
+ <text x="90" y="40" font-family="Inter, sans-serif" font-size="18" font-weight="700" text-anchor="middle" fill="white">main</text>
70
+ <text x="90" y="60" font-family="Inter, sans-serif" font-size="13" text-anchor="middle" fill="white" opacity="0.9">Base Branch</text>
71
+ <circle cx="15" cy="15" r="5" fill="#50fa7b" filter="url(#glow)"/>
43
72
</g>
44
73
45
74
<!-- Arrow 1 -->
46
- <path d="M170 90 L230 90" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)" fill="none"/>
75
+ <path d="M190 85 L250 85" stroke="#8be9fd" stroke-width="3" marker-end="url(#arrow)" fill="none" opacity="0.8"/>
76
+ <text x="220" y="78" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="#64748b">depends on</text>
47
77
48
78
<!-- Step 2: Feature 1 -->
49
- <g transform="translate(240, 50)">
50
- <rect x="0" y="0" width="160" height="80" rx="12" fill="url(#grad-feat1)" filter="url(#shadow)"/>
51
- <text x="80" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/api-core</text>
52
- <text x="80" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.9">PR #1 (Base: main)</text>
79
+ <g transform="translate(260, 40)">
80
+ <rect x="0" y="0" width="180" height="90" rx="16" fill="url(#grad-feat1)" filter="url(#shadow)" opacity="0.95"/>
81
+ <rect x="2" y="2" width="176" height="86" rx="15" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.2"/>
82
+ <text x="90" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/api-core</text>
83
+ <text x="90" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.85">PR #123</text>
84
+ <text x="90" y="72" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="white" opacity="0.7">Base: main</text>
85
+ <circle cx="15" cy="15" r="5" fill="#8be9fd" filter="url(#glow)"/>
53
86
</g>
54
87
55
88
<!-- Arrow 2 -->
56
- <path d="M410 90 L470 90" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)" fill="none"/>
89
+ <path d="M450 85 L510 85" stroke="#8be9fd" stroke-width="3" marker-end="url(#arrow)" fill="none" opacity="0.8"/>
90
+ <text x="480" y="78" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="#64748b">depends on</text>
91
+
92
+ <!-- Step 3: Feature 2 (Stacked on Feature 1) -->
93
+ <g transform="translate(520, 40)">
94
+ <rect x="0" y="0" width="180" height="90" rx="16" fill="url(#grad-feat2)" filter="url(#shadow)" opacity="0.95"/>
95
+ <rect x="2" y="2" width="176" height="86" rx="15" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.2"/>
96
+ <text x="90" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/ui-login</text>
97
+ <text x="90" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.85">PR #124</text>
98
+ <text x="90" y="72" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="white" opacity="0.7">Base: PR #123</text>
99
+ <circle cx="15" cy="15" r="5" fill="#bd93f9" filter="url(#glow)"/>
100
+ </g>
101
+
102
+ <!-- Stack Dependency Visualization -->
103
+ <g transform="translate(0, 165)">
104
+ <!-- Dependency lines -->
105
+ <path d="M90 0 L90 30" stroke="#ff79c6" stroke-width="2" stroke-dasharray="6,4" marker-end="url(#arrow-down)" opacity="0.5"/>
106
+ <path d="M350 0 L350 30" stroke="#ff79c6" stroke-width="2" stroke-dasharray="6,4" marker-end="url(#arrow-down)" opacity="0.5"/>
107
+ <path d="M610 0 L610 30" stroke="#ff79c6" stroke-width="2" stroke-dasharray="6,4" marker-end="url(#arrow-down)" opacity="0.5"/>
108
+
109
+ <!-- Stack info box -->
110
+ <rect x="180" y="35" width="340" height="55" rx="12" fill="#1a1b26" fill-opacity="0.05" stroke="#8be9fd" stroke-width="2" stroke-dasharray="8,4" opacity="0.6"/>
111
+ <text x="350" y="58" font-family="Inter, sans-serif" font-size="13" font-weight="600" text-anchor="middle" fill="#8be9fd">
112
+ ⚡ Automatic Change Propagation
113
+ </text>
114
+ <text x="350" y="77" font-family="Inter, sans-serif" font-size="11" text-anchor="middle" fill="#64748b">
115
+ Updates to PR #123 automatically sync to PR #124
116
+ </text>
117
+ </g>
118
+
119
+ </g>
57
120
58
- <!-- Step 3: Feature 2 (Stacked) -->
59
- <g transform="translate(480, 50)">
60
- <rect x="0" y="0" width="160" height="80" rx="12" fill="url(#grad-feat2)" filter="url(#shadow)"/>
61
- <text x="80" y="35" font-family="Inter, sans-serif" font-size="16" font-weight="600" text-anchor="middle" fill="white">feat/ui-login</text>
62
- <text x="80" y="55" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="white" opacity="0.9">PR #2 (Base: PR #1)</text>
121
+ <!-- Benefits Section -->
122
+ <g transform="translate(50, 320)">
123
+ <!-- Benefit 1 -->
124
+ <g>
125
+ <circle cx="10" cy="10" r="6" fill="#50fa7b" opacity="0.8"/>
126
+ <text x="25" y="15" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#334155">Parallel Development</text>
63
127
</g>
64
128
65
- <!-- Connection Lines for "Stack" concept -->
66
- <path d="M320 140 L320 160 L560 160 L560 140" stroke="#cbd5e1" stroke-width="2" stroke-dasharray="5,5" fill="none"/>
67
- <text x="440" y="180" font-family="Inter, sans-serif" font-size="12" text-anchor="middle" fill="#64748b">Changes propagate automatically down the stack</text>
129
+ <!-- Benefit 2 -->
130
+ <g transform="translate(250, 0)">
131
+ <circle cx="10" cy="10" r="6" fill="#8be9fd" opacity="0.8"/>
132
+ <text x="25" y="15" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#334155">Faster Reviews</text>
133
+ </g>
68
134
135
+ <!-- Benefit 3 -->
136
+ <g transform="translate(500, 0)">
137
+ <circle cx="10" cy="10" r="6" fill="#bd93f9" opacity="0.8"/>
138
+ <text x="25" y="15" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#334155">Auto-sync Changes</text>
139
+ </g>
69
140
</g>
70
141
</svg>
71
142
▼src/components/layout/Header.astro
+5-4
9
9
<div class="container h-16 flex items-center justify-between gap-4">
10
10
<!-- Logo -->
11
11
<a href="/" class="flex items-center gap-2 transition-opacity hover:opacity-80">
12
- <img src="/logo.svg" alt="OpenCodeHub" class="h-9 w-auto" />
12
+ <img src="/logo-dark.png" alt="OpenCodeHub" class="h-9 w-auto dark:hidden" />
13
+ <img src="/logo-light.png" alt="OpenCodeHub" class="h-9 w-auto hidden dark:block" />
13
14
</a>
14
15
15
16
<!-- Navigation -->
160
161
<a href="/actions" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">Actions</a>
161
162
<a href="/cli" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">CLI</a>
162
163
<a href="/admin" class="block px-3 py-2 text-sm font-medium text-purple-400 hover:bg-purple-500/10 rounded-lg transition-colors">Mission Control</a>
163
-
164
+
164
165
<div class="pt-2">
165
166
<button
166
167
class="w-full flex items-center h-10 rounded-lg border border-input bg-background/50 px-3 text-sm text-muted-foreground text-left"
172
173
</div>
173
174
</nav>
174
175
</div>
175
-
176
+
176
177
<GlobalSearch client:idle />
177
178
</header>
178
179
226
227
});
227
228
// Also handle keydown k if focused (though usually we intercept global k)
228
229
}
229
-
230
+
230
231
if (mobileSearchTrigger) {
231
232
mobileSearchTrigger.addEventListener('click', openSearch);
232
233
}
233
234
▼src/components/repo/FileExplorer.astro
+110-81
1
1
---
2
2
import { FileText, Folder } from "lucide-react";
3
+import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
3
4
4
5
interface Props {
5
6
owner: string;
26
27
};
27
28
---
28
29
29
-<div class="rounded-lg border">
30
- <div class="divide-y">
31
- {
32
- path && (
33
- <div class="flex items-center gap-4 px-4 py-2 hover:bg-muted/30">
34
- <div class="w-4 flex justify-center">
35
- <span class="text-muted-foreground">..</span>
36
- </div>
37
- <span class="flex-1 text-sm">
38
- <a
39
- href={`/${owner}/${repo}/tree/${branch}/${path.split("/").slice(0, -1).join("/")}`}
40
- class="hover:underline hover:text-primary font-bold text-blue-500"
41
- >
42
- ..
43
- </a>
44
- </span>
45
- </div>
46
- )
47
- }
48
- {
49
- files.length > 0 ? (
50
- files.map((file) => (
51
- <div class="flex items-center gap-4 px-4 py-2 hover:bg-muted/30 group">
52
- <div class="w-4 flex justify-center">
53
- <div class="w-4 flex justify-center">
54
- {file.type === "directory" ? (
55
- <svg
56
- class="h-4 w-4 text-blue-500 fill-blue-500"
57
- viewBox="0 0 16 16"
58
- fill="currentColor"
59
- >
60
- <path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z" />
61
- </svg>
62
- ) : (
63
- (() => {
64
- const ext = file.name.split('.').pop()?.toLowerCase();
65
- let iconClass = "text-muted-foreground";
66
- if (['js', 'jsx', 'ts', 'tsx', 'cjs', 'mjs'].includes(ext || '')) iconClass = "text-yellow-500";
67
- else if (['html', 'htm'].includes(ext || '')) iconClass = "text-orange-600";
68
- else if (['css', 'scss', 'sass', 'less'].includes(ext || '')) iconClass = "text-blue-600";
69
- else if (['json', 'yml', 'yaml', 'toml'].includes(ext || '')) iconClass = "text-yellow-600";
70
- else if (['md', 'txt'].includes(ext || '')) iconClass = "text-gray-400";
71
- else if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext || '')) iconClass = "text-purple-500";
72
- else if (['py'].includes(ext || '')) iconClass = "text-blue-500";
73
- else if (['go'].includes(ext || '')) iconClass = "text-cyan-500";
74
- else if (['rs'].includes(ext || '')) iconClass = "text-orange-500";
75
- else if (['java'].includes(ext || '')) iconClass = "text-red-500";
76
- else if (['c', 'cpp', 'h', 'hpp'].includes(ext || '')) iconClass = "text-blue-700";
77
- else if (['sh', 'bash', 'zsh'].includes(ext || '')) iconClass = "text-green-500";
78
-
79
- return <FileText className={`h-4 w-4 ${iconClass}`} />;
80
- })()
81
- )}
82
- </div>
83
- </div>
84
- <span class="flex-1 text-sm">
30
+<div class="w-full">
31
+ <table class="w-full text-sm">
32
+ <tbody class="divide-y divide-border">
33
+ {
34
+ path && (
35
+ <tr class="hover:bg-muted/50 transition-colors">
36
+ <td class="p-4 py-2 w-10">
37
+ <span class="text-muted-foreground">..</span>
38
+ </td>
39
+ <td class="p-4 py-2 font-medium">
85
40
<a
86
- href={getUrl(file)}
87
- class="hover:underline hover:text-primary text-foreground"
41
+ href={`/${owner}/${repo}/tree/${branch}/${path.split("/").slice(0, -1).join("/")}`}
42
+ class="hover:underline hover:text-primary text-blue-500 block"
88
43
>
89
- {file.name}
44
+ ..
90
45
</a>
91
- </span>
92
- <span class="hidden sm:block text-sm text-muted-foreground truncate max-w-[300px]">
93
- {file.lastCommit ? (
46
+ </td>
47
+ <td class="p-4 py-2" />
48
+ <td class="p-4 py-2" />
49
+ </tr>
50
+ )
51
+ }
52
+ {
53
+ files.length > 0 ? (
54
+ files.map((file) => (
55
+ <tr class="hover:bg-muted/50 transition-colors group">
56
+ <td class="p-4 py-2.5 w-10">
57
+ {file.type === "directory" ? (
58
+ <svg
59
+ class="h-4 w-4 text-blue-500 fill-blue-500"
60
+ viewBox="0 0 16 16"
61
+ fill="currentColor"
62
+ >
63
+ <path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z" />
64
+ </svg>
65
+ ) : (
66
+ (() => {
67
+ const ext = file.name.split(".").pop()?.toLowerCase();
68
+ let iconClass = "text-muted-foreground";
69
+ if (
70
+ ["js", "jsx", "ts", "tsx", "cjs", "mjs"].includes(
71
+ ext || "",
72
+ )
73
+ )
74
+ iconClass = "text-yellow-500";
75
+ else if (["html", "htm"].includes(ext || ""))
76
+ iconClass = "text-orange-600";
77
+ else if (
78
+ ["css", "scss", "sass", "less"].includes(ext || "")
79
+ )
80
+ iconClass = "text-blue-600";
81
+ else if (
82
+ ["json", "yml", "yaml", "toml"].includes(ext || "")
83
+ )
84
+ iconClass = "text-yellow-600";
85
+ else if (["md", "txt"].includes(ext || ""))
86
+ iconClass = "text-gray-400";
87
+ else if (
88
+ ["png", "jpg", "jpeg", "gif", "svg", "webp"].includes(
89
+ ext || "",
90
+ )
91
+ )
92
+ iconClass = "text-purple-500";
93
+ else if (["py"].includes(ext || ""))
94
+ iconClass = "text-blue-500";
95
+ else if (["go"].includes(ext || ""))
96
+ iconClass = "text-cyan-500";
97
+ else if (["rs"].includes(ext || ""))
98
+ iconClass = "text-orange-500";
99
+ else if (["java"].includes(ext || ""))
100
+ iconClass = "text-red-500";
101
+ else if (["c", "cpp", "h", "hpp"].includes(ext || ""))
102
+ iconClass = "text-blue-700";
103
+ else if (["sh", "bash", "zsh"].includes(ext || ""))
104
+ iconClass = "text-green-500";
105
+
106
+ return <FileText className={`h-4 w-4 ${iconClass}`} />;
107
+ })()
108
+ )}
109
+ </td>
110
+ <td class="p-4 py-2.5">
94
111
<a
95
- href={`/${owner}/${repo}/commit/${file.lastCommit.hash}`}
96
- class="hover:text-primary hover:underline"
112
+ href={getUrl(file)}
113
+ class="hover:underline hover:text-primary text-foreground font-medium block"
97
114
>
98
- {file.lastCommit.message}
115
+ {file.name}
99
116
</a>
100
- ) : (
101
- <span class="animate-pulse bg-muted h-4 w-24 rounded inline-block" />
102
- )}
103
- </span>
104
- <span class="text-sm text-muted-foreground whitespace-nowrap w-24 text-right">
105
- {file.lastCommit?.date || ""}
106
- </span>
107
- </div>
108
- ))
109
- ) : (
110
- <div class="p-8 text-center text-muted-foreground">
111
- <p>This directory is empty.</p>
112
- </div>
113
- )
114
- }
115
- </div>
117
+ </td>
118
+ <td class="p-4 py-2.5 text-muted-foreground max-w-md truncate">
119
+ {file.lastCommit ? (
120
+ <a
121
+ href={`/${owner}/${repo}/commit/${file.lastCommit.hash}`}
122
+ class="hover:text-primary hover:underline"
123
+ >
124
+ {file.lastCommit.message}
125
+ </a>
126
+ ) : (
127
+ <span class="animate-pulse bg-muted h-4 w-24 rounded inline-block" />
128
+ )}
129
+ </td>
130
+ <td class="p-4 py-2.5 text-right text-muted-foreground whitespace-nowrap text-xs">
131
+ {file.lastCommit?.date || ""}
132
+ </td>
133
+ </tr>
134
+ ))
135
+ ) : (
136
+ <tr>
137
+ <td colspan={4} class="p-8 text-center text-muted-foreground">
138
+ This directory is empty.
139
+ </td>
140
+ </tr>
141
+ )
142
+ }
143
+ </tbody>
144
+ </table>
116
145
</div>
117
146
▼src/components/repo/RepoActions.tsx
+129-0
1
+
2
+import * as React from "react";
3
+import {
4
+ Eye,
5
+ GitFork,
6
+ Star,
7
+ ChevronDown,
8
+ Loader2
9
+} from "lucide-react";
10
+import { Button } from "@/components/ui/button";
11
+
12
+interface RepoActionsProps {
13
+ owner: string;
14
+ repo: string;
15
+ watchers: number;
16
+ forks: number;
17
+ stars: number;
18
+ isLoggedIn: boolean;
19
+}
20
+
21
+export function RepoActions({
22
+ owner,
23
+ repo,
24
+ watchers,
25
+ forks,
26
+ stars,
27
+ isLoggedIn
28
+}: RepoActionsProps) {
29
+ const [isForking, setIsForking] = React.useState(false);
30
+
31
+ const handleFork = async () => {
32
+ if (!isLoggedIn) {
33
+ window.location.href = "/login";
34
+ return;
35
+ }
36
+
37
+ setIsForking(true);
38
+ try {
39
+ const response = await fetch(`/api/repos/${owner}/${repo}/fork`, {
40
+ method: "POST",
41
+ });
42
+
43
+ const data = await response.json();
44
+
45
+ if (response.ok) {
46
+ window.location.href = data.fork.url;
47
+ } else if (response.status === 409) {
48
+ // Already forked - redirect to existing fork
49
+ window.location.href = `/${data.fork.owner}/${data.fork.name}`;
50
+ } else {
51
+ alert(data.message || "Failed to fork repository");
52
+ setIsForking(false);
53
+ }
54
+ } catch (error) {
55
+ console.error("Fork error:", error);
56
+ alert("Failed to fork repository");
57
+ setIsForking(false);
58
+ }
59
+ };
60
+
61
+ return (
62
+ <div className="flex items-center gap-2 flex-wrap">
63
+ {/* Watch */}
64
+ <div className="flex items-center rounded-lg border bg-background overflow-hidden shadow-sm">
65
+ <Button
66
+ variant="ghost"
67
+ size="sm"
68
+ className="rounded-none border-r px-3 hover:bg-muted"
69
+ >
70
+ <Eye className="mr-2 h-4 w-4 text-muted-foreground" />
71
+ Watch
72
+ <span className="ml-2 rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-xs font-medium">
73
+ {watchers}
74
+ </span>
75
+ </Button>
76
+ <Button
77
+ variant="ghost"
78
+ size="sm"
79
+ className="px-2 rounded-none hover:bg-muted"
80
+ >
81
+ <ChevronDown className="h-4 w-4 text-muted-foreground" />
82
+ </Button>
83
+ </div>
84
+
85
+ {/* Fork */}
86
+ <div className="flex items-center rounded-lg border bg-background overflow-hidden shadow-sm">
87
+ <Button
88
+ variant="ghost"
89
+ size="sm"
90
+ onClick={handleFork}
91
+ disabled={isForking}
92
+ className="rounded-none px-3 hover:bg-muted"
93
+ >
94
+ {isForking ? (
95
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
96
+ ) : (
97
+ <GitFork className="mr-2 h-4 w-4 text-muted-foreground" />
98
+ )}
99
+ Fork
100
+ <span className="ml-2 rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-xs font-medium">
101
+ {forks}
102
+ </span>
103
+ </Button>
104
+ </div>
105
+
106
+ {/* Star */}
107
+ <div className="flex items-center rounded-lg border bg-background overflow-hidden shadow-sm">
108
+ <Button
109
+ variant="ghost"
110
+ size="sm"
111
+ className="rounded-none border-r px-3 hover:bg-muted"
112
+ >
113
+ <Star className="mr-2 h-4 w-4 text-muted-foreground" />
114
+ Star
115
+ <span className="ml-2 rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-xs font-medium">
116
+ {stars}
117
+ </span>
118
+ </Button>
119
+ <Button
120
+ variant="ghost"
121
+ size="sm"
122
+ className="px-2 rounded-none hover:bg-muted"
123
+ >
124
+ <ChevronDown className="h-4 w-4 text-muted-foreground" />
125
+ </Button>
126
+ </div>
127
+ </div>
128
+ );
129
+}
0
130
▼src/components/repo/RepoControls.tsx
+181-0
1
+
2
+import * as React from "react";
3
+import {
4
+ GitBranch,
5
+ ChevronDown,
6
+ Copy,
7
+ Check,
8
+ Download,
9
+ Code
10
+} from "lucide-react";
11
+
12
+import { cn } from "@/lib/utils";
13
+import { Button } from "@/components/ui/button";
14
+import {
15
+ Command,
16
+ CommandEmpty,
17
+ CommandGroup,
18
+ CommandInput,
19
+ CommandItem,
20
+ CommandList,
21
+} from "@/components/ui/command";
22
+import {
23
+ Popover,
24
+ PopoverContent,
25
+ PopoverTrigger,
26
+} from "@/components/ui/popover";
27
+import {
28
+ Tabs,
29
+ TabsContent,
30
+ TabsList,
31
+ TabsTrigger,
32
+} from "@/components/ui/tabs";
33
+import { Input } from "@/components/ui/input";
34
+
35
+interface RepoControlsProps {
36
+ owner: string;
37
+ repo: string;
38
+ currentBranch: string;
39
+ branches: string[];
40
+ httpCloneUrl: string;
41
+ sshCloneUrl: string;
42
+}
43
+
44
+export function RepoControls({
45
+ owner,
46
+ repo,
47
+ currentBranch,
48
+ branches,
49
+ httpCloneUrl,
50
+ sshCloneUrl,
51
+}: RepoControlsProps) {
52
+ const [openBranch, setOpenBranch] = React.useState(false);
53
+ const [openClone, setOpenClone] = React.useState(false);
54
+ const [copied, setCopied] = React.useState(false);
55
+
56
+ const copyToClipboard = async (text: string) => {
57
+ await navigator.clipboard.writeText(text);
58
+ setCopied(true);
59
+ setTimeout(() => setCopied(false), 2000);
60
+ };
61
+
62
+ return (
63
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
64
+ <div className="flex items-center gap-2">
65
+ {/* Branch Selector */}
66
+ <Popover open={openBranch} onOpenChange={setOpenBranch}>
67
+ <PopoverTrigger asChild>
68
+ <Button
69
+ variant="outline"
70
+ role="combobox"
71
+ aria-expanded={openBranch}
72
+ className="justify-between min-w-[150px]"
73
+ >
74
+ <div className="flex items-center gap-2">
75
+ <GitBranch className="h-4 w-4 opacity-50" />
76
+ {currentBranch}
77
+ </div>
78
+ <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
79
+ </Button>
80
+ </PopoverTrigger>
81
+ <PopoverContent className="w-[200px] p-0" align="start">
82
+ <Command>
83
+ <CommandInput placeholder="Search branch..." />
84
+ <CommandList>
85
+ <CommandEmpty>No branch found.</CommandEmpty>
86
+ <CommandGroup heading="Branches">
87
+ {branches.map((branch) => (
88
+ <CommandItem
89
+ key={branch}
90
+ value={branch}
91
+ onSelect={() => {
92
+ window.location.href = `/${owner}/${repo}/tree/${branch}`;
93
+ setOpenBranch(false);
94
+ }}
95
+ >
96
+ <Check
97
+ className={cn(
98
+ "mr-2 h-4 w-4",
99
+ currentBranch === branch ? "opacity-100" : "opacity-0"
100
+ )}
101
+ />
102
+ {branch}
103
+ </CommandItem>
104
+ ))}
105
+ </CommandGroup>
106
+ </CommandList>
107
+ </Command>
108
+ </PopoverContent>
109
+ </Popover>
110
+ </div>
111
+
112
+ <div className="flex items-center gap-2">
113
+ {/* Clone Button */}
114
+ <Popover open={openClone} onOpenChange={setOpenClone}>
115
+ <PopoverTrigger asChild>
116
+ <Button className="gap-2 bg-green-600 hover:bg-green-700 text-white">
117
+ <Code className="h-4 w-4" />
118
+ Code
119
+ <ChevronDown className="h-4 w-4" />
120
+ </Button>
121
+ </PopoverTrigger>
122
+ <PopoverContent className="w-[350px] p-4" align="end">
123
+ <Tabs defaultValue="https" className="w-full">
124
+ <TabsList className="w-full grid grid-cols-2">
125
+ <TabsTrigger value="https">HTTPS</TabsTrigger>
126
+ <TabsTrigger value="ssh">SSH</TabsTrigger>
127
+ </TabsList>
128
+
129
+ <div className="mt-4">
130
+ <TabsContent value="https" className="mt-0">
131
+ <div className="flex items-center gap-2">
132
+ <Input readOnly value={httpCloneUrl} className="h-9 font-mono text-xs" />
133
+ <Button
134
+ size="icon"
135
+ variant="outline"
136
+ className="h-9 w-9 shrink-0"
137
+ onClick={() => copyToClipboard(httpCloneUrl)}
138
+ >
139
+ {copied ? (
140
+ <Check className="h-4 w-4 text-green-500" />
141
+ ) : (
142
+ <Copy className="h-4 w-4" />
143
+ )}
144
+ </Button>
145
+ </div>
146
+ </TabsContent>
147
+ <TabsContent value="ssh" className="mt-0">
148
+ <div className="flex items-center gap-2">
149
+ <Input readOnly value={sshCloneUrl} className="h-9 font-mono text-xs" />
150
+ <Button
151
+ size="icon"
152
+ variant="outline"
153
+ className="h-9 w-9 shrink-0"
154
+ onClick={() => copyToClipboard(sshCloneUrl)}
155
+ >
156
+ {copied ? (
157
+ <Check className="h-4 w-4 text-green-500" />
158
+ ) : (
159
+ <Copy className="h-4 w-4" />
160
+ )}
161
+ </Button>
162
+ </div>
163
+ </TabsContent>
164
+ </div>
165
+
166
+ <div className="mt-4 border-t pt-4">
167
+ <a
168
+ href={`/${owner}/${repo}/archive/refs/heads/${currentBranch}.zip`}
169
+ className="flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors"
170
+ >
171
+ <Download className="h-4 w-4" />
172
+ Download ZIP
173
+ </a>
174
+ </div>
175
+ </Tabs>
176
+ </PopoverContent>
177
+ </Popover>
178
+ </div>
179
+ </div>
180
+ );
181
+}
0
182
▼src/components/repo/RepoHeader.astro
+128-107
1
1
---
2
2
import {
3
- GitBranch,
4
- Star,
5
- GitFork,
6
- Eye,
7
3
Code,
8
4
FileText,
9
5
Settings,
10
- Lock,
11
- Globe,
12
- PlayCircle,
13
- Book,
14
6
Shield,
15
7
BarChart2,
16
8
GitPullRequest,
17
9
Layout,
18
- ChevronDown,
10
+ PlayCircle,
11
+ Book,
19
12
} from "lucide-react";
13
+import { Badge } from "@/components/ui/badge";
14
+import { RepoActions } from "@/components/repo/RepoActions";
20
15
21
16
interface Props {
22
17
repo: {
30
25
stars?: number;
31
26
openIssueCount?: number;
32
27
openPrCount?: number;
28
+ website?: string | null;
29
+ license?: string | null;
30
+ updatedAt?: Date | string | number | null;
33
31
};
34
32
activeTab:
35
33
| "code"
44
42
}
45
43
46
44
const { repo, activeTab } = Astro.props;
45
+const isLoggedIn = !!Astro.locals.user;
46
+
47
+// Helper for time ago
48
+function timeAgo(date: Date | string | number) {
49
+ if (!date) return "";
50
+ const seconds = Math.floor(
51
+ (new Date().getTime() - new Date(date).getTime()) / 1000,
52
+ );
53
+ let interval = seconds / 31536000;
54
+ if (interval > 1) return Math.floor(interval) + "y ago";
55
+ interval = seconds / 2592000;
56
+ if (interval > 1) return Math.floor(interval) + "mo ago";
57
+ interval = seconds / 86400;
58
+ if (interval > 1) return Math.floor(interval) + "d ago";
59
+ interval = seconds / 3600;
60
+ if (interval > 1) return Math.floor(interval) + "h ago";
61
+ interval = seconds / 60;
62
+ if (interval > 1) return Math.floor(interval) + "m ago";
63
+ return Math.floor(seconds) + "s ago";
64
+}
47
65
48
66
const tabs = [
49
67
{
105
123
];
106
124
---
107
125
108
-<!-- Enhanced Repo Header -->
109
126
<div class="mb-6">
110
127
<!-- Top Section: Repo Name & Action Buttons -->
111
- <div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4 py-4 border-b border-white/10">
128
+ <div
129
+ class="flex flex-col lg:flex-row lg:items-start justify-between gap-4 py-8 border-b"
130
+ >
112
131
<!-- Repo Info -->
113
- <div class="space-y-2">
114
- <!-- Repo Name - BIGGER and more prominent -->
132
+ <div class="space-y-4 max-w-3xl">
133
+ <!-- Repo Name -->
115
134
<div class="flex items-center gap-3 flex-wrap">
116
- <a
117
- href={`/${repo.owner}`}
118
- class="text-xl font-semibold text-cyan-400 hover:underline transition-colors"
135
+ <a
136
+ href={`/${repo.owner}`}
137
+ class="text-xl text-muted-foreground hover:text-foreground hover:underline transition-colors"
119
138
>
120
139
{repo.owner}
121
140
</a>
122
- <span class="text-xl text-gray-500">/</span>
141
+ <span class="text-xl text-muted-foreground">/</span>
123
142
<a
124
143
href={`/${repo.owner}/${repo.name}`}
125
- class="text-xl font-bold text-white hover:text-cyan-400 transition-colors"
144
+ class="text-xl font-bold hover:text-primary hover:underline transition-colors"
126
145
>
127
146
{repo.name}
128
147
</a>
129
- <!-- Visibility Badge -->
130
- <span
131
- class="inline-flex items-center gap-1 rounded-full border border-white/20 bg-white/5 px-2.5 py-1 text-xs font-medium text-gray-400"
148
+ <Badge
149
+ variant="secondary"
150
+ className="rounded-full px-2.5 font-normal text-muted-foreground border-muted-foreground/30"
132
151
>
133
- {
134
- repo.visibility === "public" ? (
135
- <>
136
- <Globe className="h-3 w-3 text-green-400" />
137
- <span>Public</span>
138
- </>
139
- ) : (
140
- <>
141
- <Lock className="h-3 w-3 text-orange-400" />
142
- <span>Private</span>
143
- </>
144
- )
145
- }
146
- </span>
152
+ {repo.visibility}
153
+ </Badge>
147
154
</div>
148
-
155
+
149
156
<!-- Description -->
150
157
{
151
158
repo.description && (
152
- <p class="text-gray-400 text-sm max-w-2xl">{repo.description}</p>
159
+ <p class="text-base text-foreground/80 leading-relaxed">
160
+ {repo.description}
161
+ </p>
153
162
)
154
163
}
155
-
164
+
165
+ <!-- Metadata Row -->
166
+ <div
167
+ class="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-muted-foreground"
168
+ >
169
+ {
170
+ repo.website && (
171
+ <a
172
+ href={repo.website}
173
+ target="_blank"
174
+ rel="noopener noreferrer"
175
+ class="flex items-center gap-1.5 hover:text-primary transition-colors"
176
+ >
177
+ <svg
178
+ class="h-4 w-4"
179
+ fill="none"
180
+ viewBox="0 0 24 24"
181
+ stroke="currentColor"
182
+ >
183
+ <path
184
+ stroke-linecap="round"
185
+ stroke-linejoin="round"
186
+ stroke-width="2"
187
+ d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
188
+ />
189
+ </svg>
190
+ {repo.website}
191
+ </a>
192
+ )
193
+ }
194
+ {
195
+ repo.license && (
196
+ <div class="flex items-center gap-1.5">
197
+ <svg
198
+ class="h-4 w-4"
199
+ fill="none"
200
+ viewBox="0 0 24 24"
201
+ stroke="currentColor"
202
+ >
203
+ <path
204
+ stroke-linecap="round"
205
+ stroke-linejoin="round"
206
+ stroke-width="2"
207
+ d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3"
208
+ />
209
+ </svg>
210
+ {repo.license} license
211
+ </div>
212
+ )
213
+ }
214
+ {repo.updatedAt && <div>Updated {timeAgo(repo.updatedAt)}</div>}
215
+ </div>
216
+
156
217
<!-- Topics -->
157
218
{
158
219
repo.topics && repo.topics.length > 0 && (
159
- <div class="flex flex-wrap gap-2 mt-2">
220
+ <div class="flex flex-wrap gap-2">
160
221
{repo.topics.map((topic: string) => (
161
- <a
162
- href={`/topics/${topic}`}
163
- class="inline-flex items-center rounded-full bg-cyan-500/10 border border-cyan-500/20 px-2.5 py-0.5 text-xs font-medium text-cyan-400 hover:bg-cyan-500/20 transition-colors"
222
+ <Badge
223
+ variant="secondary"
224
+ className="rounded-full hover:bg-primary/20 hover:text-primary cursor-pointer transition-colors"
164
225
>
165
- {topic}
166
- </a>
226
+ <a href={`/topics/${topic}`}>{topic}</a>
227
+ </Badge>
167
228
))}
168
229
</div>
169
230
)
170
231
}
171
232
</div>
172
-
233
+
173
234
<!-- Action Buttons -->
174
- <div class="flex items-center gap-2 flex-wrap">
175
- <!-- Watch -->
176
- <div class="flex items-center rounded-lg border border-white/10 bg-white/5 overflow-hidden">
177
- <button
178
- class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/10 transition-colors"
179
- >
180
- <Eye className="h-4 w-4" />
181
- Watch
182
- <span class="ml-1 rounded-full bg-white/10 px-2 py-0.5 text-xs text-cyan-400">
183
- {repo.watchers || 0}
184
- </span>
185
- </button>
186
- <div class="w-px h-6 bg-white/10"></div>
187
- <button class="px-2 py-2 hover:bg-white/10 transition-colors text-gray-400">
188
- <ChevronDown className="h-4 w-4" />
189
- </button>
190
- </div>
191
-
192
- <!-- Fork -->
193
- <div class="flex items-center rounded-lg border border-white/10 bg-white/5 overflow-hidden">
194
- <button
195
- id="fork-btn"
196
- data-owner={repo.owner}
197
- data-repo={repo.name}
198
- class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/10 transition-colors"
199
- >
200
- <GitFork className="h-4 w-4" />
201
- Fork
202
- <span class="ml-1 rounded-full bg-white/10 px-2 py-0.5 text-xs text-purple-400">
203
- {repo.forks || 0}
204
- </span>
205
- </button>
206
- </div>
207
-
208
- <!-- Star -->
209
- <div class="flex items-center rounded-lg border border-white/10 bg-white/5 overflow-hidden">
210
- <button
211
- class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/10 transition-colors"
212
- >
213
- <Star className="h-4 w-4" />
214
- Star
215
- <span class="ml-1 rounded-full bg-white/10 px-2 py-0.5 text-xs text-yellow-400">
216
- {repo.stars || 0}
217
- </span>
218
- </button>
219
- <div class="w-px h-6 bg-white/10"></div>
220
- <button class="px-2 py-2 hover:bg-white/10 transition-colors text-gray-400">
221
- <ChevronDown className="h-4 w-4" />
222
- </button>
223
- </div>
224
- </div>
235
+ <RepoActions
236
+ client:load
237
+ owner={repo.owner}
238
+ repo={repo.name}
239
+ watchers={repo.watchers || 0}
240
+ forks={repo.forks || 0}
241
+ stars={repo.stars || 0}
242
+ isLoggedIn={isLoggedIn}
243
+ />
225
244
</div>
226
245
227
246
<!-- Navigation Tabs -->
228
- <div class="overflow-x-auto -mx-4 px-4">
229
- <nav class="flex gap-1 pt-3 min-w-max">
247
+ <div class="overflow-x-auto -mx-4 px-4 pt-4">
248
+ <nav class="flex gap-1 min-w-max border-b border-transparent">
230
249
{
231
250
tabs.map((tab) => (
232
251
<a
233
252
href={tab.href}
234
253
class:list={[
235
- "inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all",
254
+ "inline-flex items-center gap-2 px-4 py-2.5 rounded-t-lg text-sm font-medium border-b-2 transition-all",
236
255
activeTab === tab.id
237
- ? "bg-white/10 text-white border border-white/10"
238
- : "text-gray-400 hover:text-white hover:bg-white/5",
256
+ ? "border-primary text-foreground bg-muted/40"
257
+ : "border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground/30",
239
258
]}
240
259
>
241
260
<tab.icon className="h-4 w-4" />
242
261
{tab.label}
243
262
{tab.count !== undefined && tab.count > 0 && (
244
- <span class:list={[
245
- "rounded-full px-2 py-0.5 text-xs font-medium",
246
- activeTab === tab.id
247
- ? "bg-cyan-500/20 text-cyan-400"
248
- : "bg-white/10 text-gray-400"
249
- ]}>
263
+ <span
264
+ class:list={[
265
+ "rounded-full px-2 py-0.5 text-xs font-medium",
266
+ activeTab === tab.id
267
+ ? "bg-primary/10 text-primary"
268
+ : "bg-muted text-muted-foreground",
269
+ ]}
270
+ >
250
271
{tab.count}
251
272
</span>
252
273
)}
253
274
▼src/components/ui/popover.tsx
+29-0
1
+import * as React from "react"
2
+import * as PopoverPrimitive from "@radix-ui/react-popover"
3
+
4
+import { cn } from "@/lib/utils"
5
+
6
+const Popover = PopoverPrimitive.Root
7
+
8
+const PopoverTrigger = PopoverPrimitive.Trigger
9
+
10
+const PopoverContent = React.forwardRef<
11
+ React.ElementRef<typeof PopoverPrimitive.Content>,
12
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
13
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14
+ <PopoverPrimitive.Portal>
15
+ <PopoverPrimitive.Content
16
+ ref={ref}
17
+ align={align}
18
+ sideOffset={sideOffset}
19
+ className={cn(
20
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ </PopoverPrimitive.Portal>
26
+))
27
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
28
+
29
+export { Popover, PopoverTrigger, PopoverContent }
0
30
▼src/components/ui/table.tsx
+116-0
1
+import * as React from "react"
2
+import { cn } from "@/lib/utils"
3
+
4
+const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.HTMLAttributes<HTMLTableElement>
7
+>(({ className, ...props }, ref) => (
8
+ <div className="relative w-full overflow-auto">
9
+ <table
10
+ ref={ref}
11
+ className={cn("w-full caption-bottom text-sm", className)}
12
+ {...props}
13
+ />
14
+ </div>
15
+))
16
+Table.displayName = "Table"
17
+
18
+const TableHeader = React.forwardRef<
19
+ HTMLTableSectionElement,
20
+ React.HTMLAttributes<HTMLTableSectionElement>
21
+>(({ className, ...props }, ref) => (
22
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
23
+))
24
+TableHeader.displayName = "TableHeader"
25
+
26
+const TableBody = React.forwardRef<
27
+ HTMLTableSectionElement,
28
+ React.HTMLAttributes<HTMLTableSectionElement>
29
+>(({ className, ...props }, ref) => (
30
+ <tbody
31
+ ref={ref}
32
+ className={cn("[&_tr:last-child]:border-0", className)}
33
+ {...props}
34
+ />
35
+))
36
+TableBody.displayName = "TableBody"
37
+
38
+const TableFooter = React.forwardRef<
39
+ HTMLTableSectionElement,
40
+ React.HTMLAttributes<HTMLTableSectionElement>
41
+>(({ className, ...props }, ref) => (
42
+ <tfoot
43
+ ref={ref}
44
+ className={cn(
45
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+))
51
+TableFooter.displayName = "TableFooter"
52
+
53
+const TableRow = React.forwardRef<
54
+ HTMLTableRowElement,
55
+ React.HTMLAttributes<HTMLTableRowElement>
56
+>(({ className, ...props }, ref) => (
57
+ <tr
58
+ ref={ref}
59
+ className={cn(
60
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+))
66
+TableRow.displayName = "TableRow"
67
+
68
+const TableHead = React.forwardRef<
69
+ HTMLTableCellElement,
70
+ React.ThHTMLAttributes<HTMLTableCellElement>
71
+>(({ className, ...props }, ref) => (
72
+ <th
73
+ ref={ref}
74
+ className={cn(
75
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
76
+ className
77
+ )}
78
+ {...props}
79
+ />
80
+))
81
+TableHead.displayName = "TableHead"
82
+
83
+const TableCell = React.forwardRef<
84
+ HTMLTableCellElement,
85
+ React.TdHTMLAttributes<HTMLTableCellElement>
86
+>(({ className, ...props }, ref) => (
87
+ <td
88
+ ref={ref}
89
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
90
+ {...props}
91
+ />
92
+))
93
+TableCell.displayName = "TableCell"
94
+
95
+const TableCaption = React.forwardRef<
96
+ HTMLTableCaptionElement,
97
+ React.HTMLAttributes<HTMLTableCaptionElement>
98
+>(({ className, ...props }, ref) => (
99
+ <caption
100
+ ref={ref}
101
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
102
+ {...props}
103
+ />
104
+))
105
+TableCaption.displayName = "TableCaption"
106
+
107
+export {
108
+ Table,
109
+ TableHeader,
110
+ TableBody,
111
+ TableFooter,
112
+ TableHead,
113
+ TableRow,
114
+ TableCell,
115
+ TableCaption,
116
+}
0
117
▼src/lib/git.ts
+22-0
606
606
}
607
607
}
608
608
609
+/**
610
+ * Get total commit count
611
+ */
612
+export async function getCommitCount(
613
+ repoPath: string,
614
+ ref: string = "HEAD"
615
+): Promise<number> {
616
+ const git = getGit(repoPath);
617
+
618
+ try {
619
+ // Check if repository is empty first
620
+ if (await isRepoEmpty(repoPath)) {
621
+ return 0;
622
+ }
623
+
624
+ const count = await git.raw(["rev-list", "--count", ref]);
625
+ return parseInt(count.trim(), 10);
626
+ } catch (error) {
627
+ return 0;
628
+ }
629
+}
630
+
609
631
/**
610
632
* Get a single commit
611
633
*/
612
634
▼src/pages/[owner]/[repo]/index.astro
+75-254
19
19
getCommits,
20
20
getFileContent,
21
21
getBranches,
22
+ getCommitCount,
22
23
} from "@/lib/git";
23
24
import { resolveRepoPath } from "@/lib/git-storage";
24
25
import { canReadRepo } from "@/lib/permissions";
25
26
import { renderMarkdown } from "@/lib/markdown";
26
27
import RepoHeader from "@/components/repo/RepoHeader.astro";
27
28
import FileExplorer from "@/components/repo/FileExplorer.astro";
29
+import { RepoControls } from "@/components/repo/RepoControls";
30
+import { Card } from "@/components/ui/card";
28
31
29
32
const { owner: ownerName, repo: repoName } = Astro.params;
30
33
const db = getDatabase();
77
80
const currentPath = "";
78
81
let files: any[] = [];
79
82
let latestCommit: any = null;
83
+let totalCommits = 0;
80
84
let readme = "";
81
85
82
86
// Format dates helper
148
152
latestCommit = commits[0];
149
153
}
150
154
155
+ // Get total commit count
156
+ totalCommits = await getCommitCount(resolvedLocalPath, currentBranch);
157
+
151
158
// Get README
152
159
const readmeFile = files.find((f) => f.name.toLowerCase() === "readme.md");
153
160
if (readmeFile) {
203
210
<!-- Main Content -->
204
211
<div class="lg:col-span-3 space-y-4">
205
212
<!-- Branch/Clone Bar -->
206
- <div
207
- class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
208
- >
209
- <div class="flex items-center gap-2">
210
- <!-- Branch Selector -->
211
- <div class="relative">
212
- <button
213
- class="inline-flex items-center gap-2 rounded-md border bg-background px-3 py-1.5 text-sm font-medium shadow-sm hover:bg-accent"
214
- id="branch-btn"
215
- >
216
- <GitBranch className="h-4 w-4" />
217
- {currentBranch}
218
- <ChevronDown className="h-4 w-4" />
219
- </button>
220
- <div
221
- id="branch-dropdown"
222
- class="hidden absolute left-0 top-full mt-1 w-64 rounded-md border bg-popover p-2 shadow-lg z-10"
223
- >
224
- <input
225
- type="search"
226
- placeholder="Find a branch..."
227
- class="w-full mb-2 h-8 rounded-md border bg-background px-3 text-sm"
228
- />
229
- <div class="max-h-64 overflow-y-auto">
230
- <div
231
- class="text-xs font-semibold text-muted-foreground px-2 py-1"
232
- >
233
- Branches
213
+ <RepoControls
214
+ client:load
215
+ owner={repo.owner}
216
+ repo={repo.name}
217
+ currentBranch={currentBranch}
218
+ branches={branches}
219
+ httpCloneUrl={repo.httpCloneUrl ||
220
+ `http://localhost:3000/${repo.owner}/${repo.name}.git`}
221
+ sshCloneUrl={repo.sshCloneUrl ||
222
+ `git@localhost:${repo.owner}/${repo.name}.git`}
223
+ />
224
+
225
+ <!-- File Browser -->
226
+ <div class="space-y-4">
227
+ <!-- Latest Commit -->
228
+ {
229
+ latestCommit && latestCommit.authorName ? (
230
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 rounded-lg border bg-card p-4 text-sm shadow-sm">
231
+ <div class="flex items-center gap-3 overflow-hidden">
232
+ <div class="h-8 w-8 shrink-0 rounded-full bg-muted flex items-center justify-center border text-xs font-medium">
233
+ {latestCommit.authorName[0]?.toUpperCase()}
234
234
</div>
235
- {
236
- branches.map((b) => (
235
+ <div class="flex flex-col min-w-0">
236
+ <div class="flex items-center gap-2 truncate">
237
237
<a
238
- href={`/${repo.owner}/${repo.name}/tree/${b}`}
239
- class={`flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent ${b === currentBranch ? "bg-accent" : ""}`}
238
+ href="#"
239
+ class="font-semibold hover:text-primary hover:underline truncate"
240
240
>
241
- {b === currentBranch && (
242
- <span class="text-primary">✓</span>
243
- )}
244
- {b}
241
+ {latestCommit.authorName}
245
242
</a>
246
- ))
247
- }
248
- </div>
249
- </div>
250
- </div>
251
- </div>
252
- <div class="flex items-center gap-2">
253
- <!-- Clone Button -->
254
- <div class="relative">
255
- <button
256
- class="inline-flex items-center gap-2 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90"
257
- id="clone-btn"
258
- >
259
- <Code className="h-4 w-4" />
260
- Code
261
- <ChevronDown className="h-4 w-4" />
262
- </button>
263
- <div
264
- id="clone-dropdown"
265
- class="hidden absolute right-0 top-full mt-1 w-80 rounded-md border bg-popover p-4 shadow-lg z-10"
266
- >
267
- <div class="space-y-4">
268
- <div>
269
- <div class="flex items-center gap-2 mb-2">
270
- <button
271
- id="https-tab"
272
- class="text-sm font-medium border-b-2 border-primary pb-1"
273
- >HTTPS</button
274
- >
275
- <button
276
- id="ssh-tab"
277
- class="text-sm font-medium text-muted-foreground pb-1 border-b-2 border-transparent"
278
- >SSH</button
279
- >
243
+ <span class="text-muted-foreground truncate hidden sm:inline">
244
+ {latestCommit.message}
245
+ </span>
280
246
</div>
281
- <div class="flex items-center gap-2">
282
- <input
283
- type="text"
284
- readonly
285
- id="clone-url"
286
- value={repo.httpCloneUrl ||
287
- `http://localhost:3000/${repo.owner}/${repo.name}.git`}
288
- data-https={repo.httpCloneUrl ||
289
- `http://localhost:3000/${repo.owner}/${repo.name}.git`}
290
- data-ssh={repo.sshCloneUrl ||
291
- `git@localhost:${repo.owner}/${repo.name}.git`}
292
- class="flex-1 h-8 rounded-md border bg-muted px-2 text-xs font-mono"
293
- />
294
- <button
295
- id="copy-url-btn"
296
- class="inline-flex items-center justify-center h-8 w-8 rounded-md border hover:bg-accent"
297
- title="Copy to clipboard"
298
- >
299
- <Copy className="h-4 w-4" />
300
- </button>
247
+ <div class="flex sm:hidden text-muted-foreground truncate text-xs">
248
+ {latestCommit.message}
249
+ </div>
250
+ <div class="flex items-center gap-2 text-xs text-muted-foreground">
251
+ <span>committed {timeAgo(latestCommit.authorDate)}</span>
301
252
</div>
302
- </div>
303
- <div class="border-t pt-4 space-y-2">
304
- <a
305
- href={`/${repo.owner}/${repo.name}/archive/refs/heads/${currentBranch}.zip`}
306
- class="flex items-center gap-2 text-sm hover:text-primary"
307
- >
308
- <Download className="h-4 w-4" />
309
- Download ZIP
310
- </a>
311
253
</div>
312
254
</div>
313
- </div>
314
- </div>
315
- </div>
316
- </div>
317
255
318
- <!-- File Browser -->
319
- <div class="space-y-4">
320
- <!-- Latest Commit -->
321
- {
322
- latestCommit && latestCommit.authorName ? (
323
- <div class="flex items-center gap-4 rounded-lg border bg-muted/30 p-4">
324
- <div class="h-8 w-8 rounded-full bg-muted flex items-center justify-center">
325
- <span class="text-sm font-medium">
326
- {latestCommit.authorName[0].toUpperCase()}
327
- </span>
328
- </div>
329
- <div class="flex-1 min-w-0">
330
- <div class="flex items-center gap-2 text-sm">
331
- <a href="#" class="font-medium hover:text-primary">
332
- {latestCommit.authorName}
333
- </a>
334
- <a href="#" class="truncate hover:text-primary">
335
- {latestCommit.message}
336
- </a>
337
- </div>
338
- <div class="flex items-center gap-2 text-xs text-muted-foreground">
339
- <a href="#" class="font-mono hover:text-primary">
256
+ <div class="flex items-center gap-3 shrink-0 ml-11 sm:ml-0">
257
+ <div class="flex items-center border rounded-md overflow-hidden bg-muted/30">
258
+ <a
259
+ href={`/${repo.owner}/${repo.name}/commit/${latestCommit.sha}`}
260
+ class="px-3 py-1.5 hover:bg-muted hover:text-primary font-mono text-xs transition-colors border-r"
261
+ >
340
262
{latestCommit.sha.substring(0, 7)}
341
263
</a>
342
- <span>·</span>
343
- <span>committed {timeAgo(latestCommit.authorDate)}</span>
264
+ <a
265
+ href={`/${repo.owner}/${repo.name}/commits/${currentBranch}`}
266
+ class="px-3 py-1.5 hover:bg-muted hover:text-primary text-xs flex items-center gap-1.5 transition-colors"
267
+ >
268
+ <Clock className="h-3.5 w-3.5" />
269
+ <span class="font-medium">{totalCommits}</span>
270
+ <span class="sr-only">commits</span>
271
+ </a>
344
272
</div>
345
273
</div>
346
- <a
347
- href={`/${repo.owner}/${repo.name}/commits/${currentBranch}`}
348
- class="text-sm text-muted-foreground hover:text-primary whitespace-nowrap"
349
- >
350
- <GitCommit className="h-4 w-4 inline mr-1" />
351
- History
352
- </a>
353
274
</div>
354
275
) : (
355
- <div class="p-4 text-sm text-muted-foreground border rounded-lg">
356
- No commits yet.
357
- </div>
276
+ <Card className="p-8 text-center text-muted-foreground border-dashed">
277
+ <div class="flex flex-col items-center gap-2">
278
+ <GitBranch className="h-8 w-8 opacity-50 mb-2" />
279
+ <p>No commits yet.</p>
280
+ </div>
281
+ </Card>
358
282
)
359
283
}
360
284
361
285
<!-- File List -->
362
- <FileExplorer
363
- owner={repo.owner}
364
- repo={repo.name}
365
- branch={currentBranch}
366
- path={currentPath}
367
- files={files}
368
- />
286
+ <Card className="overflow-hidden">
287
+ <FileExplorer
288
+ owner={repo.owner}
289
+ repo={repo.name}
290
+ branch={currentBranch}
291
+ path={currentPath}
292
+ files={files}
293
+ />
294
+ </Card>
369
295
370
296
<!-- README -->
371
297
{
372
298
readme && (
373
- <div class="rounded-lg border mt-8">
299
+ <Card className="mt-8 overflow-hidden">
374
300
<div class="flex items-center gap-2 border-b px-4 py-3 bg-muted/30">
375
- <FileText className="h-4 w-4" />
376
- <span class="font-medium">README.md</span>
301
+ <FileText className="h-4 w-4 text-muted-foreground" />
302
+ <span class="font-medium text-sm">README.md</span>
377
303
</div>
378
304
<div
379
- class="p-6 prose dark:prose-invert max-w-none"
305
+ class="p-6 prose prose-sm dark:prose-invert max-w-none prose-headings:border-b prose-headings:pb-2 prose-img:rounded-lg"
380
306
set:html={readme}
381
307
/>
382
- </div>
308
+ </Card>
383
309
)
384
310
}
385
311
</div>
524
450
</div>
525
451
</div>
526
452
</BaseLayout>
527
-
528
-<script>
529
- // Branch dropdown
530
- const branchBtn = document.getElementById("branch-btn");
531
- const branchDropdown = document.getElementById("branch-dropdown");
532
-
533
- branchBtn?.addEventListener("click", (e) => {
534
- e.stopPropagation();
535
- branchDropdown?.classList.toggle("hidden");
536
- document.getElementById("clone-dropdown")?.classList.add("hidden");
537
- });
538
-
539
- // Clone dropdown
540
- const cloneBtn = document.getElementById("clone-btn");
541
- const cloneDropdown = document.getElementById("clone-dropdown");
542
-
543
- cloneBtn?.addEventListener("click", (e) => {
544
- e.stopPropagation();
545
- cloneDropdown?.classList.toggle("hidden");
546
- branchDropdown?.classList.add("hidden");
547
- });
548
-
549
- // Close dropdowns when clicking outside
550
- document.addEventListener("click", () => {
551
- branchDropdown?.classList.add("hidden");
552
- cloneDropdown?.classList.add("hidden");
553
- });
554
-
555
- // HTTPS/SSH tab switching
556
- const httpsTab = document.getElementById("https-tab");
557
- const sshTab = document.getElementById("ssh-tab");
558
- const cloneUrl = document.getElementById("clone-url") as HTMLInputElement;
559
-
560
- httpsTab?.addEventListener("click", (e) => {
561
- e.stopPropagation();
562
- if (cloneUrl) {
563
- cloneUrl.value = cloneUrl.dataset.https || "";
564
- }
565
- httpsTab.classList.add("border-primary");
566
- httpsTab.classList.remove("text-muted-foreground", "border-transparent");
567
- sshTab?.classList.remove("border-primary");
568
- sshTab?.classList.add("text-muted-foreground", "border-transparent");
569
- });
570
-
571
- sshTab?.addEventListener("click", (e) => {
572
- e.stopPropagation();
573
- if (cloneUrl) {
574
- cloneUrl.value = cloneUrl.dataset.ssh || "";
575
- }
576
- sshTab.classList.add("border-primary");
577
- sshTab.classList.remove("text-muted-foreground", "border-transparent");
578
- httpsTab?.classList.remove("border-primary");
579
- httpsTab?.classList.add("text-muted-foreground", "border-transparent");
580
- });
581
-
582
- // Copy URL to clipboard
583
- const copyBtn = document.getElementById("copy-url-btn");
584
- copyBtn?.addEventListener("click", async (e) => {
585
- e.stopPropagation();
586
- if (cloneUrl) {
587
- await navigator.clipboard.writeText(cloneUrl.value);
588
- copyBtn.innerHTML =
589
- '<svg class="h-4 w-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>';
590
- setTimeout(() => {
591
- copyBtn.innerHTML =
592
- '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><rect x="9" y="9" width="13" height="13" rx="2" ry="2" stroke-width="2"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
593
- }, 2000);
594
- }
595
- });
596
-
597
- // Fork button
598
- const forkBtn = document.getElementById("fork-btn") as HTMLButtonElement;
599
- forkBtn?.addEventListener("click", async () => {
600
- const owner = forkBtn.dataset.owner;
601
- const repo = forkBtn.dataset.repo;
602
-
603
- forkBtn.disabled = true;
604
- forkBtn.innerHTML =
605
- '<svg class="animate-spin h-4 w-4 mr-2" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Forking...';
606
-
607
- try {
608
- const response = await fetch(`/api/repos/${owner}/${repo}/fork`, {
609
- method: "POST",
610
- });
611
-
612
- const data = await response.json();
613
-
614
- if (response.ok) {
615
- window.location.href = data.fork.url;
616
- } else if (response.status === 409) {
617
- // Already forked - redirect to existing fork
618
- window.location.href = `/${data.fork.owner}/${data.fork.name}`;
619
- } else {
620
- alert(data.message || "Failed to fork repository");
621
- forkBtn.disabled = false;
622
- forkBtn.innerHTML =
623
- '<svg class="h-4 w-4 mr-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 5C7 3.89543 7.89543 3 9 3C10.1046 3 11 3.89543 11 5C11 5.74028 10.5978 6.38663 10 6.73244V14.0396H14.5M17 5C17 3.89543 16.1046 3 15 3C13.8954 3 13 3.89543 13 5C13 5.74028 13.4022 6.38663 14 6.73244V8.5M14.5 14.0396C14.5 15.1441 15.3954 16.0396 16.5 16.0396C17.6046 16.0396 18.5 15.1441 18.5 14.0396C18.5 12.935 17.6046 12.0396 16.5 12.0396C15.3954 12.0396 14.5 12.935 14.5 14.0396Z"></path></svg>Fork';
624
- }
625
- } catch (error) {
626
- console.error("Fork error:", error);
627
- alert("Failed to fork repository");
628
- forkBtn.disabled = false;
629
- }
630
- });
631
-</script>
632
453