CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

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).

Architecture

This is a simple Go web application using the Skykit framework pattern:

  • Framework: theskyscape.com/repo/skykit - External dependency providing MVC framework, database connectivity, and server infrastructure
  • Pattern: MVC architecture with embedded views (HTMX/HATEOAS)
  • Frontend: HTMX + Tailwind CSS + DaisyUI (dark theme)
  • Database: LibSQL via Skykit's ConnectDB() helper

Project Structure

hacknight/
├── 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

Skykit Framework Pattern

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:

  • Controllers have factory functions returning (name string, handler skykit.Handler)
  • Embed skykit.Controller base struct
  • Implement Setup(app *skykit.Application) for route registration
  • Implement Handle(r *http.Request) skykit.Handler with value receiver
  • Views are embedded using //go:embed all:views and passed to skykit.Serve()

Friendli.ai Integration

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:

  • Chat completions API (conversational AI)
  • Streaming and non-streaming support
  • OpenAI-compatible API structure
  • Support for 468,834+ open-source models from Hugging Face
  • Tool/function calling capabilities

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:

  • Llama 3.1 (8B, 70B, 405B)
  • Llama 3.2 Vision (11B, 90B)
  • Mistral 7B / 7B Instruct
  • CodeLlama variants
  • 468,834+ other Hugging Face models

API Reference:

  • friendli.NewClient(apiKey) - Create client with API key
  • client.Chat.CreateCompletion(ctx, req) - Non-streaming chat
  • client.Chat.CreateCompletionStream(ctx, req) - Streaming chat
  • stream.Recv() - Read next chunk from stream
  • stream.Close() - Close stream and cleanup
  • stream.CollectContent() - Read entire stream as string
  • stream.StreamToChannel(chunkCh, errCh) - Stream to channels

Error 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
        }
    }
}

Development Workflow

Running the Application

# 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.

Dependencies

Install/update dependencies:

go mod download
go mod tidy

The application depends on theskyscape.com/repo/skykit which provides:

  • MVC framework and routing
  • Database connectivity (LibSQL)
  • Server infrastructure and configuration
  • Template rendering

Adding New Features

Adding a Controller:

  1. Create new file in controllers/
  2. Follow the Skykit controller pattern (factory function, embed skykit.Controller)
  3. Implement Setup() and Handle() methods
  4. Register in main.go using skykit.WithController()

Adding a View:

  1. Create HTML file in views/ directory
  2. Views are automatically embedded via //go:embed all:views
  3. Reference in controller using c.Serve("filename.html", data)
  4. Use HTMX for dynamic features

Database Access:

// Access the global DB connection
import "hacknight/models"

// Use models.DB in your controllers
result, err := models.DB.Query("SELECT * FROM table")

Frontend Stack

  • HTMX 2.0.8 - Dynamic HTML without JavaScript
  • Tailwind CSS 4 - Utility-first styling
  • DaisyUI 5 - Component library (using "dark" theme)

Views should use data-theme="dark" on the <html> tag for consistent theming.

Environment Variables

  • HOST_PREFIX - Optional host prefix for routing (used in multi-app deployments)

Configuration

Friendli API Key (Database-Stored)

The Friendli API key is now configured through the web UI and stored in the database:

  1. Navigate to any project page
  2. If not configured, click "Configure API Key" button in navbar (yellow warning button)
  3. Enter your API key in the modal dialog
  4. Key is saved to the settings table in the database
  5. No restart required - changes take effect immediately

Obtaining an API Key:

Benefits:

  • No environment variable configuration needed
  • Can be changed through UI without server restart
  • Persisted in database across restarts

Module Information

  • Module name: hacknight
  • Go version: 1.23.2
  • Main dependency: theskyscape.com/repo/skykit v0.0.0-20251111223751-bfc3029ae897
cdf282b

Adding demo video

Connor McCutcheon
@connor
0 stars

Basic AI powered project manager

Sign in to comment Sign In