This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
hacknight is a demonstration application built for the HackNight event by GitHub. It's a simple web application built using the Skykit framework (from The Skyscape project).
This is a simple Go web application using the Skykit framework pattern:
theskyscape.com/repo/skykit - External dependency providing MVC framework, database connectivity, and server infrastructureConnectDB() helperhacknight/
├── main.go # Application entry point, embeds views, configures Skykit
├── controllers/ # HTTP request handlers
│ └── home.go # Home controller with homepage route
├── models/ # Data models and database connection
│ └── database.go # Global DB connection via Skykit
├── internal/ # Internal packages
│ └── friendli/ # Friendli.ai API client library
│ ├── client.go # Core HTTP client with authentication
│ ├── chat.go # Chat completions service
│ ├── stream.go # Streaming response handler
│ ├── types.go # Request/response types
│ └── errors.go # Error types and handling
├── views/ # HTML templates (embedded in binary)
│ └── homepage.html # Landing page
├── go.mod # Module dependencies
└── go.sum # Dependency checksums
This application follows the Skykit framework conventions:
Application Entry Point:
// main.go
//go:embed all:views
var views embed.FS
func main() {
skykit.Serve(views,
skykit.WithValue("hostPrefix", os.Getenv("HOST_PREFIX")),
skykit.WithController(controllers.Home()),
)
}
Controller Pattern:
// controllers/home.go
func Home() (string, skykit.Handler) {
return "home", &HomeController{}
}
type HomeController struct {
skykit.Controller // Embed base controller
}
func (c *HomeController) Setup(app *skykit.Application) {
c.Controller.Setup(app)
http.Handle("/", c.Serve("homepage.html", nil))
}
func (c HomeController) Handle(r *http.Request) skykit.Handler {
c.Request = r
return &c
}
Key Conventions:
(name string, handler skykit.Handler)skykit.Controller base structSetup(app *skykit.Application) for route registrationHandle(r *http.Request) skykit.Handler with value receiver//go:embed all:views and passed to skykit.Serve()The project includes an internal library for interacting with Friendli.ai (https://friendli.ai), an AI infrastructure platform for deploying and running LLMs. The library is located at internal/friendli/ and provides a Go client for Friendli.ai's serverless API.
Key Features:
Basic Usage (Non-Streaming):
import (
"hacknight/internal/friendli"
"hacknight/models"
)
// Get API key from database
apiKey, err := models.GetFriendliAPIKey()
if err != nil || apiKey == "" {
// API key not configured
}
// Initialize client
client, err := friendli.NewClient(apiKey)
if err != nil {
// handle error
}
// Create a chat completion request
req := friendli.NewChatCompletionRequest(
"meta-llama-3.1-8b-instruct",
[]friendli.Message{
friendli.NewSystemMessage("You are a helpful assistant"),
friendli.NewUserMessage("Hello!"),
},
).WithTemperature(0.7).WithMaxTokens(200)
// Get response
resp, err := client.Chat.CreateCompletion(context.Background(), req)
if err != nil {
// handle error
}
// Access the response
message := resp.Choices[0].Message.Content
Streaming Usage:
// Create streaming request
req := friendli.NewChatCompletionRequest(
"meta-llama-3.1-8b-instruct",
[]friendli.Message{
friendli.NewUserMessage("Tell me a story"),
},
)
// Get streaming response
stream, err := client.Chat.CreateCompletionStream(context.Background(), req)
if err != nil {
// handle error
}
defer stream.Close()
// Read chunks
for {
chunk, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
// handle error
}
// Process chunk
if len(chunk.Choices) > 0 {
content := chunk.Choices[0].Delta.Content
fmt.Print(content)
}
}
Helper Methods:
// Collect entire stream into a string
content, err := stream.CollectContent()
// Or collect all chunks
chunks, err := stream.CollectAll()
// Stream to channels (useful for HTMX SSE)
chunkCh := make(chan *friendli.ChatCompletionChunk, 10)
errCh := make(chan error, 1)
go stream.StreamToChannel(chunkCh, errCh)
Using in Controllers:
// Get API key from database in handler (allows dynamic updates)
func (c *ProjectsController) handleAIFeature(w http.ResponseWriter, r *http.Request) {
// Get API key from database
apiKey, err := models.GetFriendliAPIKey()
if err != nil || apiKey == "" {
c.Render(w, r, "error-message.html", "API key not configured")
return
}
// Initialize Friendli client
client, err := friendli.NewClient(apiKey)
if err != nil {
c.Render(w, r, "error-message.html", fmt.Sprintf("Failed to create AI client: %v", err))
return
}
// Use client for streaming or non-streaming requests
// See actual implementation in controllers/projects.go:
// - handleAIGenerateTasks() for streaming task generation (line ~260)
// - handleAIProjectSummary() for streaming project analysis (line ~370)
}
Note: The application now manages API keys through the database Settings model.
See models/settings.go and controllers/projects.go for the complete implementation.
Available Models:
API Reference:
friendli.NewClient(apiKey) - Create client with API keyclient.Chat.CreateCompletion(ctx, req) - Non-streaming chatclient.Chat.CreateCompletionStream(ctx, req) - Streaming chatstream.Recv() - Read next chunk from streamstream.Close() - Close stream and cleanupstream.CollectContent() - Read entire stream as stringstream.StreamToChannel(chunkCh, errCh) - Stream to channelsError Handling:
if err != nil {
if apiErr, ok := err.(*friendli.APIError); ok {
if apiErr.IsRateLimitError() {
// handle rate limit
} else if apiErr.IsAuthenticationError() {
// handle auth error
} else if apiErr.IsValidationError() {
// handle validation error
}
}
}
# Set optional environment variables
export HOST_PREFIX="" # Optional: Host prefix for routing
# Run the application
go run .
# Build binary
go build -o app .
Note: Skykit handles port configuration and other server settings internally.
Install/update dependencies:
go mod download
go mod tidy
The application depends on theskyscape.com/repo/skykit which provides:
Adding a Controller:
controllers/skykit.Controller)Setup() and Handle() methodsmain.go using skykit.WithController()Adding a View:
views/ directory//go:embed all:viewsc.Serve("filename.html", data)Database Access:
// Access the global DB connection
import "hacknight/models"
// Use models.DB in your controllers
result, err := models.DB.Query("SELECT * FROM table")
Views should use data-theme="dark" on the <html> tag for consistent theming.
HOST_PREFIX - Optional host prefix for routing (used in multi-app deployments)The Friendli API key is now configured through the web UI and stored in the database:
settings table in the databaseObtaining an API Key:
flp_XXXBenefits:
hacknighttheskyscape.com/repo/skykit v0.0.0-20251111223751-bfc3029ae897Adding demo video
Basic AI powered project manager