Handoff is both a command-line tool and a Go library for collecting and formatting code from multiple files. It's designed to make it easy to share code with AI assistants, documentation generators, or other tools that work with source code.
- Features
- Installation
- Usage
- Output Format
- Output Statistics
- Git Integration
- Development and Contributing
- License
- Dual Interface: Use as a command-line tool or import as a Go library
- Flexible File Collection: Process individual files or entire directories
- Smart Filtering: Include/exclude files by extension or name
- Git-Aware: Respects .gitignore rules to skip irrelevant files, with optional bypass for processing gitignored content
- Format Customization: Customize output with templates
- Multiple Output Options: Copy to clipboard or write to file
- Content Statistics: Get detailed stats about processed content
- Binary Detection: Automatically skips binary files
- Safety Features: File overwrite protection
- Go 1.22.3 or higher
# Clone the repository
git clone https://github.com/phrazzld/handoff.git
cd handoff
# Build the binary
go build
# Optionally, install it to your GOPATH
go install# Add to your Go project
go get github.com/phrazzld/handoff/lib
# Import in your code
import "github.com/phrazzld/handoff/lib"./handoff [options] [path1] [path2] ...-verbose: Enable verbose output-dry-run: Preview what would be copied without actually copying-output: Write output to the specified file instead of clipboard (e.g.,HANDOFF.md)-force: Allow overwriting existing files when using-outputflag-include: Comma-separated list of file extensions to include (e.g.,.txt,.go)-exclude: Comma-separated list of file extensions to exclude (e.g.,.exe,.bin)-exclude-names: Comma-separated list of file names to exclude (e.g.,package-lock.json,yarn.lock)-ignore-gitignore: Process files even if they are gitignored (bypasses .gitignore rules; default: false)-format: Custom format for output. Use{path}and{content}as placeholders
# Copy all files in the current directory
./handoff .
# Copy only .go files from the src directory
./handoff -include=.go src/
# Copy specific files
./handoff main.go utils.go config.go
# Use a custom format
./handoff -format="File: {path}\n```go\n{content}\n```\n\n" .
# Write output to a file instead of clipboard
./handoff -output=HANDOFF.md .
# Write output to a file, overwriting if it exists
./handoff -output=HANDOFF.md -force .
# Preview content that would be written to file
./handoff -output=HANDOFF.md -dry-run .
# Process files including those that are gitignored
./handoff -ignore-gitignore .
# Process specific gitignored documentation files
./handoff -ignore-gitignore docs/internal-notes.md README-private.md
# Combine with verbose output to see which gitignored files are being processed
./handoff -ignore-gitignore -verbose .Handoff's core functionality is available as a library for integration with your Go applications:
package main
import (
"fmt"
"github.com/phrazzld/handoff/lib"
)
func main() {
// Create a configuration with functional options
config := lib.NewConfig(
lib.WithVerbose(true), // Enable verbose logging
lib.WithInclude(".go,.md,.txt"), // Only include specific file types
lib.WithExclude(".exe,.bin,.jpg,.png"), // Exclude binary and image files
lib.WithExcludeNames("node_modules,package-lock.json"), // Skip specific files/directories
lib.WithIgnoreGitignore(true), // Process files even if gitignored
lib.WithFormat("## {path}\n```\n{content}\n```\n\n"), // Custom output format
)
// Process project files (the main API entry point)
content, stats, err := lib.ProcessProject([]string{"./my-project"}, config)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Use the content - write to a file with overwrite protection
if err := lib.WriteToFile(content, "output.md", true); err != nil {
fmt.Printf("Error writing to file: %v\n", err)
}
// Display statistics returned from ProcessProject
fmt.Printf("Processed %d/%d files\n", stats.FilesProcessed, stats.FilesTotal)
fmt.Printf("Content stats: %d lines, %d chars, ~%d tokens\n",
stats.Lines, stats.Chars, stats.Tokens)
// You can also calculate statistics for any content directly
chars, lines, tokens := lib.CalculateStatistics("Some content to analyze")
fmt.Printf("Analysis: %d chars, %d lines, ~%d tokens\n", chars, lines, tokens)
// Wrap any content in context tags if needed
wrappedContent := lib.WrapInContext("Content to be wrapped in context tags")
fmt.Println(wrappedContent)
}For more details on the library API, see the lib/README.md documentation.
The copied content will be formatted as:
<context>
<path/to/file.txt>
```
content of file.txt
```
</path/to/file.txt>
<path/to/another/file.go>
```
content of file.go
```
</path/to/another/file.go>
</context>
You can customize this format using the -format flag with {path} and {content} placeholders.
After processing files, Handoff displays useful statistics about the copied content:
Handoff complete:
- Files: 3
- Lines: 45
- Characters: 1024
- Estimated tokens: 256
These statistics are particularly helpful for:
- Understanding how much content you're sharing
- Estimating LLM token usage when pasting into AI tools
- Monitoring the size of your clipboard or file content
When multiple output options are specified, Handoff follows this precedence:
-dry-run: Highest priority - outputs to screen only, no clipboard/file modifications-output: Medium priority - writes to the specified file- Clipboard: Default behavior - copies to clipboard when no other output option is specified
When using the -output flag, Handoff includes built-in protection against accidental file overwrites:
- If the specified output file already exists, Handoff will refuse to overwrite it
- To allow overwriting an existing file, use the
-forceflag - This ensures you don't accidentally lose content in existing files
Handoff provides flexible Git-aware file processing:
- In Git repositories, files ignored by Git (via
.gitignore) will not be included - In non-Git directories, hidden files (starting with
.) will be skipped - This ensures that binary files, build artifacts, and other irrelevant files are not copied
Use the -ignore-gitignore flag when you need to process files that would normally be excluded:
- Useful for documentation files, configuration templates, or context files that are intentionally gitignored
- Common scenarios include processing
glance.mdfiles, internal notes, or draft documentation - When combined with
-verbose, you'll see explicit confirmation of which gitignored files are being processed
/lib: The library package (importable asgithub.com/phrazzld/handoff/lib)- Root directory: CLI tool implementation
This project follows Semantic Versioning. The current version is v0.1.0.
For more details on the versioning strategy, see VERSIONING.md.
# Run all tests
go test ./...
# Run library tests with coverage
go test -coverprofile=coverage.out ./lib/...
go tool cover -func=coverage.outThe library package maintains a minimum test coverage threshold of 85%, enforced via GitHub Actions.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.