Imported from crstian19/aceplay (
AGENTS.md). Install upstream withnpx skills add crstian19/aceplay. Copyright stays with the author.
AGENTS.md - Aceplay Development Guide
This document provides guidelines for AI agents working on the Aceplay project.
Project Overview
Aceplay is a modern Go reimplementation of acestream-launcher. It automatically starts acestream-engine if not running and plays Ace Stream content using the user's preferred video player.
Tech Stack:
- Go 1.24.2 (minimum 1.22)
- Charm ecosystem (Bubble Tea, Huh, Lipgloss, Log)
- Cobra CLI framework
- Viper for configuration
- Resty for HTTP requests
- Testify for testing
Build, Test, and Lint Commands
Building
# Build binary for current platform
make build
# or
go build -o build/aceplay ./cmd
# Build with version info
make build
# Binary includes version, commit, and date from git
# Build for all platforms
make build-all
# Cross-compile manually
GOOS=linux GOARCH=amd64 go build -o build/aceplay ./cmd
Testing
# Run all tests
make test
# or
go test -v -race ./...
# Run a single test
go test -v -run TestFunctionName ./pkg/acestream
# Run tests in specific package
go test -v ./internal/config/...
# Run short tests
make test-short
# Run with coverage
make test-coverage
go test -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
Linting and Code Quality
The linter set lives in .golangci.yml (golangci-lint v2). CI runs exactly the same
config, so a clean make lint means a green pipeline.
# Format code (gofumpt + gci, same as CI checks)
make fmt
# Fail if anything is unformatted — this is what CI runs
make fmt-check
# or
golangci-lint fmt --diff
# Run the linters (govet/staticcheck/gosec/revive/... per .golangci.yml)
make lint
# Install: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh
# Run all checks (fmt, vet, lint, test)
make check
Do not "fix" lint by running
go fmt ./...in CI: it rewrites files and always exits 0, so it can never fail a build. Usegolangci-lint fmt --diff.
Development
# Install dependencies
make deps
# or
go mod download
go mod tidy
# Run the application
make run
# or
go run ./cmd
# Install binary to system
sudo make install
# Clean build artifacts
make clean
Code Style Guidelines
General Principles
- Write clean, readable Go code
- Follow standard Go conventions
- Keep functions focused and small
- Use meaningful variable and function names
- Add comments for exported functions and types
Imports
Organize imports in three groups (standard library first, then external):
import (
"context"
"fmt"
"os"
"time"
"charm.land/lipgloss/v2"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)
Import grouping is enforced by gci (standard / third-party / github.com/crstian19/aceplay); run make fmt instead of sorting by hand.
Naming Conventions
- Variables:
camelCase(e.g.,configPath,isFirstRun) - Constants:
PascalCaseorcamelCasefor unexported (e.g.,DefaultPlayer,maxRetries) - Functions:
PascalCasefor exported,camelCasefor unexported (e.g.,LoadConfig,isValidURL) - Types/Structs:
PascalCase(e.g.,Config,EngineConfig) - Interfaces:
PascalCasewithersuffix (e.g.,Reader,Writer)
Error Handling
- Always handle errors explicitly
- Return meaningful error messages with context
- Use
fmt.Errorf("...: %w", err)for wrapping errors - Avoid ignoring errors with
_
// Good
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
// Good - early return on error
cfg, err := Load(configPath)
if err != nil {
return fmt.Errorf("error loading configuration: %w", err)
}
Struct Tags
Use struct tags for configuration and serialization:
type Config struct {
Player string `mapstructure:"player"`
Engine EngineConfig `mapstructure:"engine"`
Timeout time.Duration `mapstructure:"timeout"`
}
Testing
- Use
testifyassertions (assert,require) - Test file naming:
module_test.go - Table-driven tests for multiple test cases
func TestFunctionName(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{"case 1", "input1", "expected1"},
{"case 2", "input2", "expected2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := MyFunction(tt.input)
assert.Equal(t, tt.want, result)
})
}
}
Logging
Use the Charm log package:
import "github.com/charmbracelet/log"
log.Info("starting application")
log.Errorf("failed to connect: %v", err)
CLI Commands
- Use Cobra for CLI commands
- Group related functionality
- Provide helpful usage and descriptions
var rootCmd = &cobra.Command{
Use: "aceplay",
Short: "Play Ace Stream content",
Long: `Aceplay is a modern CLI for playing Ace Stream content.`,
}
var playCmd = &cobra.Command{
Use: "play [acestream-url]",
Short: "Play an Ace Stream URL",
Args: cobra.ExactArgs(1),
RunE: runPlay,
}
Configuration
- Use Viper for configuration management
- Support YAML config files
- Provide sensible defaults
- Allow environment variable overrides
Dependencies
- Keep dependencies minimal
- Run
go mod tidyafter adding dependencies - Avoid pulling unused dependencies
Project Structure
aceplay/
├── cmd/ # CLI entry point
│ └── main.go # Main command
├── internal/
│ ├── acestream/ # Ace Stream client
│ ├── config/ # Configuration management
│ ├── player/ # Video player integration
│ ├── ui/ # UI components (Bubble Tea, Huh)
│ └── notify/ # Desktop notifications
├── pkg/ # Reusable packages
├── scripts/ # Build/release scripts
├── Makefile # Build commands
└── go.mod # Dependencies
Common Tasks
Adding a New Command
- Create command in appropriate package
- Register with Cobra in
cmd/aceplay/main.go - Add tests
Adding Configuration
- Add field to
Configstruct ininternal/config/config.go - Add default value in
NewConfig() - Use in code via
cfg.FieldName
Adding Tests
- Create
*_test.gofile in same package - Use table-driven tests
- Run with
go test -v -run TestName ./package/...
Releasing a New Version
Prerequisites
- Ensure all changes are committed on
main - All tests pass:
make test - Build works:
make build
Steps
-
Update CHANGELOG.md with the new version:
# Add new section at the top (after the header comments) ## [X.Y.Z] - YYYY-MM-DD ### Changed - ... ### Fixed - ... -
Commit the changelog:
git add CHANGELOG.md git commit -m "docs: update changelog for vX.Y.Z" -
Create and push the tag:
git tag -a vX.Y.Z -m "Release vX.Y.Z" git push origin main git push origin vX.Y.Z -
Monitor the release workflow:
- GoReleaser creates the GitHub release with binaries
- AUR workflow publishes to Arch Linux AUR
- Check: https://github.com/crstian19/aceplay/actions
Updating Charm Ecosystem Dependencies (Major Versions)
When charm.land libraries release v2 (breaking changes), additional steps are required:
Import Path Changes
| Library | Old Import | New Import |
|---|---|---|
| huh | github.com/charmbracelet/huh |
charm.land/huh/v2 |
| log | github.com/charmbracelet/log |
charm.land/log/v2 |
| lipgloss | charm.land/lipgloss/v2 |
(same) |
Code Changes Required
1. Update go.mod:
- github.com/charmbracelet/huh v0.8.0
- github.com/charmbracelet/log v0.4.2
+ charm.land/huh/v2 v2.0.x
+ charm.land/log/v2 v2.0.0
2. Update imports in Go files:
- "github.com/charmbracelet/huh"
+ "charm.land/huh/v2"
- "github.com/charmbracelet/log"
+ "charm.land/log/v2"
+ "github.com/charmbracelet/colorprofile"
3. Update API calls for log v2:
- log.NewWithOptions(os.Stderr, log.Options{...})
+ log.NewWithOptions(colorprofile.NewWriter(os.Stderr, os.Environ()), log.Options{...})
4. Run go mod tidy:
go mod tidy
5. Verify:
go build ./...
make test
Common Issues
- Renovate only updates go.mod: When Renovate creates PRs for major version bumps, it only updates
go.modbut NOT the source files. You must manually update the imports and rungo mod tidy. - Import path mismatch: Some libraries changed from
github.com/charmbracelet/*tocharm.land/*/*. Check the new import path in go.mod after runninggo mod tidy.