Skip to content

Commit 00ce93d

Browse files
committed
implemented API_key and improved web view
1 parent c944b8e commit 00ce93d

17 files changed

Lines changed: 1061 additions & 239 deletions

.env.example

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@ DB_PORT=5432
44
DB_USER=your_username
55
DB_PASS=your_password
66
DB_NAME=your_database
7-
TB_NAME=scmd
7+
TB_NAME=data
8+
ACCESS_TB=access
89

910
# Gemini API Configuration (Primary - Recommended)
1011
GEMINIAPI=your_gemini_api_key_here
1112
GEMINIMODEL=gemini-2.5-flash-lite
1213
GEMINI_EMBEDDING_MODEL=gemini-embedding-001
1314

15+
# Generate a 32-char API key for an email and store it in the access table
16+
# Usage: scmd-windows-amd64.exe --create-api [email]
17+
API_ACCESS=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
18+
1419
# Ollama Configuration (Fallback - optional)
15-
OLLAMA=localhost # Ollama server IP or hostname
16-
MODEL=llama2 # Ollama model name
20+
OLLAMA=localhost # Ollama server IP or hostname
21+
MODEL=qwen2.5-coder:1.5b # Ollama model name
1722

1823
# Embedding Configuration (for vector search)
1924
# For 384 dimensions: all-MiniLM-L6-v2

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@
1515
*scmd-Linux-x86_64
1616
*.env
1717
.local
18-
.kiro/
18+
.kiro/
19+
build/
20+
*.exe

api_key_gen.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package main
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/hex"
6+
"fmt"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
)
12+
13+
// aiAccessGranted is the package-level gate for AI and embedding features.
14+
// It is set once during startup by ValidateAPIAccess().
15+
var aiAccessGranted = false
16+
17+
// CreateAPIKey generates a 32-character random API key, stores it in the
18+
// access table paired with the given email, and prints it to the screen.
19+
// Usage: scmd --create-api "user@example.com"
20+
func CreateAPIKey(email string) {
21+
email = strings.TrimSpace(email)
22+
if email == "" {
23+
log.Fatal("Usage: scmd --create-api \"user@example.com\"")
24+
}
25+
26+
// Ensure the database is initialised
27+
if err := InitDB(); err != nil {
28+
log.Fatalf("Database connection failed: %v", err)
29+
}
30+
defer CloseDB()
31+
32+
// Generate 16 random bytes → 32 hex characters
33+
raw := make([]byte, 16)
34+
if _, err := rand.Read(raw); err != nil {
35+
log.Fatalf("Failed to generate random key: %v", err)
36+
}
37+
apiKey := strings.ToUpper(hex.EncodeToString(raw))
38+
39+
accessTbl := os.Getenv("ACCESS_TB")
40+
if accessTbl == "" {
41+
accessTbl = "access"
42+
}
43+
44+
// Upsert: if the email already exists update the key, otherwise insert
45+
query := fmt.Sprintf(`
46+
INSERT INTO %s (email, api_key)
47+
VALUES ($1, $2)
48+
ON CONFLICT (email) DO UPDATE SET api_key = EXCLUDED.api_key`, accessTbl)
49+
50+
if _, err := db.Exec(query, email, apiKey); err != nil {
51+
log.Fatalf("Failed to store API key: %v", err)
52+
}
53+
54+
fmt.Println()
55+
fmt.Println("======================================================")
56+
fmt.Printf(" Email : %s\n", email)
57+
fmt.Printf(" API Key : %s\n", apiKey)
58+
fmt.Println()
59+
60+
// Automatically write / update API_ACCESS in the .env file
61+
if err := writeAPIAccessToEnv(apiKey); err != nil {
62+
fmt.Printf(" ⚠ Could not update .env automatically: %v\n", err)
63+
fmt.Println(" Add this line manually to your .env file:")
64+
fmt.Printf(" API_ACCESS=%s\n", apiKey)
65+
} else {
66+
fmt.Println(" ✅ API_ACCESS has been written to your .env file.")
67+
}
68+
fmt.Println("======================================================")
69+
fmt.Println()
70+
}
71+
72+
// writeAPIAccessToEnv updates or appends API_ACCESS=<key> in the .env file
73+
// found in the current working directory.
74+
func writeAPIAccessToEnv(apiKey string) error {
75+
// Locate the .env file
76+
cwd, err := os.Getwd()
77+
if err != nil {
78+
return err
79+
}
80+
envPath := []string{cwd}
81+
82+
// Also check executable directory as fallback
83+
if execPath, err2 := os.Executable(); err2 == nil {
84+
envPath = append(envPath, filepath.Dir(execPath))
85+
}
86+
87+
var filePath string
88+
for _, dir := range envPath {
89+
p := filepath.Join(dir, ".env")
90+
if _, statErr := os.Stat(p); statErr == nil {
91+
filePath = p
92+
break
93+
}
94+
}
95+
if filePath == "" {
96+
return fmt.Errorf(".env file not found")
97+
}
98+
99+
// Read existing content
100+
raw, err := os.ReadFile(filePath)
101+
if err != nil {
102+
return err
103+
}
104+
105+
lines := strings.Split(string(raw), "\n")
106+
newLine := "API_ACCESS=" + apiKey
107+
found := false
108+
109+
for i, line := range lines {
110+
trimmed := strings.TrimSpace(line)
111+
if strings.HasPrefix(trimmed, "API_ACCESS=") {
112+
lines[i] = newLine
113+
found = true
114+
break
115+
}
116+
}
117+
118+
if !found {
119+
lines = append(lines, newLine)
120+
}
121+
122+
return os.WriteFile(filePath, []byte(strings.Join(lines, "\n")), 0644)
123+
}
124+
125+
// ValidateAPIAccess checks whether the API_ACCESS key in the .env file
126+
// matches an entry in the access table. Sets aiAccessGranted accordingly.
127+
// Returns true if access is granted or if no access table / key is configured
128+
// (so existing installs without the access system still work).
129+
func ValidateAPIAccess() bool {
130+
apiKey := strings.TrimSpace(os.Getenv("API_ACCESS"))
131+
132+
// If API_ACCESS is not set in .env, allow unrestricted access (backward-compatible)
133+
if apiKey == "" {
134+
aiAccessGranted = true
135+
return true
136+
}
137+
138+
if db == nil {
139+
log.Println("⚠ API access validation skipped: database not connected")
140+
aiAccessGranted = false
141+
return false
142+
}
143+
144+
accessTbl := os.Getenv("ACCESS_TB")
145+
if accessTbl == "" {
146+
accessTbl = "access"
147+
}
148+
149+
var count int
150+
query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE api_key = $1", accessTbl)
151+
err := db.QueryRow(query, apiKey).Scan(&count)
152+
if err != nil {
153+
log.Printf("⚠ API access check failed: %v", err)
154+
aiAccessGranted = false
155+
return false
156+
}
157+
158+
if count > 0 {
159+
aiAccessGranted = true
160+
log.Println("✓ API access validated")
161+
return true
162+
}
163+
164+
log.Println("✗ API access denied: key not found in database")
165+
aiAccessGranted = false
166+
return false
167+
}
168+
169+
// requireAIAccess returns true if AI features are unlocked.
170+
// Call this at the top of any AI/embedding function.
171+
func requireAIAccess() bool {
172+
if !aiAccessGranted {
173+
fmt.Println("⛔ AI features are locked. Add a valid API_ACCESS key to your .env")
174+
fmt.Println(" Generate one with: scmd --create-api \"your@email.com\"")
175+
}
176+
return aiAccessGranted
177+
}

build.ps1

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Terminal Intelligence Build Script for Windows
2+
param(
3+
[string]$Target = "build"
4+
)
5+
6+
$BINARY_NAME = "scmd"
7+
$VERSION = "2.0.1"
8+
$BUILD_DIR = "build"
9+
10+
# Get build number from git
11+
try {
12+
$gitCount = git rev-list --count HEAD 2>$null
13+
if ($LASTEXITCODE -eq 0) {
14+
$BUILD_NUMBER = [int]$gitCount + 1
15+
}
16+
else {
17+
$BUILD_NUMBER = 1
18+
}
19+
}
20+
catch {
21+
$BUILD_NUMBER = 1
22+
}
23+
24+
$LDFLAGS = "-s -w -X main.version=$VERSION -X main.buildNumber=$BUILD_NUMBER"
25+
26+
function Ensure-BuildDir {
27+
if (!(Test-Path $BUILD_DIR)) {
28+
New-Item -ItemType Directory -Path $BUILD_DIR | Out-Null
29+
}
30+
}
31+
32+
function Build-Current {
33+
Write-Host "Building $BINARY_NAME for current platform..." -ForegroundColor Cyan
34+
Ensure-BuildDir
35+
go build -ldflags="$LDFLAGS" -o "$BUILD_DIR/$BINARY_NAME.exe" .
36+
if ($LASTEXITCODE -eq 0) {
37+
Write-Host "Build complete: $BUILD_DIR/$BINARY_NAME.exe" -ForegroundColor Green
38+
}
39+
else {
40+
Write-Host "Build failed!" -ForegroundColor Red
41+
exit 1
42+
}
43+
}
44+
45+
function Build-Windows {
46+
Write-Host "Building $BINARY_NAME for Windows..." -ForegroundColor Cyan
47+
Ensure-BuildDir
48+
$env:GOOS = "windows"
49+
$env:GOARCH = "amd64"
50+
go build -ldflags="$LDFLAGS" -o "$BUILD_DIR/$BINARY_NAME-windows-amd64.exe" .
51+
Write-Host "Build complete: $BUILD_DIR/$BINARY_NAME-windows-amd64.exe" -ForegroundColor Green
52+
}
53+
54+
function Build-Linux {
55+
Write-Host "Building $BINARY_NAME for Linux..." -ForegroundColor Cyan
56+
Ensure-BuildDir
57+
$env:GOOS = "linux"
58+
$env:GOARCH = "amd64"
59+
go build -ldflags="$LDFLAGS" -o "$BUILD_DIR/$BINARY_NAME-linux-amd64" .
60+
$env:GOARCH = "arm64"
61+
go build -ldflags="$LDFLAGS" -o "$BUILD_DIR/$BINARY_NAME-linux-aarch64" .
62+
Write-Host "Build complete: $BUILD_DIR/$BINARY_NAME-linux-amd64 and $BUILD_DIR/$BINARY_NAME-linux-aarch64" -ForegroundColor Green
63+
}
64+
65+
function Build-Darwin {
66+
Write-Host "Building $BINARY_NAME for macOS..." -ForegroundColor Cyan
67+
Ensure-BuildDir
68+
$env:GOOS = "darwin"
69+
$env:GOARCH = "amd64"
70+
go build -ldflags="$LDFLAGS" -o "$BUILD_DIR/$BINARY_NAME-darwin-amd64" .
71+
$env:GOARCH = "arm64"
72+
go build -ldflags="$LDFLAGS" -o "$BUILD_DIR/$BINARY_NAME-darwin-arm64" .
73+
Write-Host "Build complete: $BUILD_DIR/$BINARY_NAME-darwin-amd64 and $BUILD_DIR/$BINARY_NAME-darwin-arm64" -ForegroundColor Green
74+
}
75+
76+
function Build-All {
77+
Build-Windows
78+
Build-Linux
79+
Build-Darwin
80+
Write-Host "All platform builds complete!" -ForegroundColor Green
81+
}
82+
83+
function Run-Tests {
84+
Write-Host "Running tests..." -ForegroundColor Cyan
85+
go test ./... -v
86+
}
87+
88+
function Run-Clean {
89+
Write-Host "Cleaning build artifacts..." -ForegroundColor Cyan
90+
if (Test-Path $BUILD_DIR) {
91+
Remove-Item -Recurse -Force $BUILD_DIR
92+
}
93+
if (Test-Path "coverage.out") {
94+
Remove-Item "coverage.out"
95+
}
96+
if (Test-Path "coverage.html") {
97+
Remove-Item "coverage.html"
98+
}
99+
Write-Host "Clean complete" -ForegroundColor Green
100+
}
101+
102+
function Show-Help {
103+
Write-Host "Terminal Intelligence (TI) Build Script" -ForegroundColor Cyan
104+
Write-Host ""
105+
Write-Host "Usage: .\build.ps1 [target]" -ForegroundColor Yellow
106+
Write-Host ""
107+
Write-Host "Targets:" -ForegroundColor Yellow
108+
Write-Host " build - Build for current platform (default)"
109+
Write-Host " windows - Build for Windows (amd64)"
110+
Write-Host " linux - Build for Linux (amd64 and arm64)"
111+
Write-Host " darwin - Build for macOS (amd64 and arm64)"
112+
Write-Host " all - Build for all platforms"
113+
Write-Host " test - Run all tests"
114+
Write-Host " clean - Remove build artifacts"
115+
Write-Host " help - Show this help message"
116+
}
117+
118+
# Execute target
119+
switch ($Target.ToLower()) {
120+
"build" { Build-Current }
121+
"windows" { Build-Windows }
122+
"linux" { Build-Linux }
123+
"darwin" { Build-Darwin }
124+
"all" { Build-All }
125+
"test" { Run-Tests }
126+
"clean" { Run-Clean }
127+
"help" { Show-Help }
128+
default {
129+
Write-Host "Unknown target: $Target" -ForegroundColor Red
130+
Show-Help
131+
exit 1
132+
}
133+
}

0 commit comments

Comments
 (0)