first commit

This commit is contained in:
2026-05-15 23:36:12 +08:00
parent 62fe8ddf4a
commit 52d6dde4aa
10 changed files with 286 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
package cmd
import (
"fmt"
"cliprepo/internal/bundle"
"github.com/spf13/cobra"
)
var bundleClipboard bool
var bundleCmd = &cobra.Command{
Use: "bundle [key]",
Short: "Bundle files by key from config",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
key := args[0]
result, err := bundle.Build(key)
if err != nil {
return err
}
if bundleClipboard {
return bundle.Copy(result)
}
fmt.Println(result)
return nil
},
}
func init() {
rootCmd.AddCommand(bundleCmd)
bundleCmd.Flags().BoolVar(&bundleClipboard, "clipboard", false, "Copy to clipboard")
}
+18
View File
@@ -0,0 +1,18 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "cliprepo",
Short: "Extract repo structure and bundle code for LLMs",
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
+40
View File
@@ -0,0 +1,40 @@
package cmd
import (
"fmt"
"cliprepo/internal/tree"
"github.com/spf13/cobra"
)
var treeOutput string
var treeClipboard bool
var treeCmd = &cobra.Command{
Use: "tree",
Short: "Print repo tree respecting .gitignore",
RunE: func(cmd *cobra.Command, args []string) error {
result, err := tree.Generate(".")
if err != nil {
return err
}
if treeClipboard {
return tree.Copy(result)
}
if treeOutput != "" {
return tree.Write(treeOutput, result)
}
fmt.Println(result)
return nil
},
}
func init() {
rootCmd.AddCommand(treeCmd)
treeCmd.Flags().StringVarP(&treeOutput, "output", "o", "", "Output file")
treeCmd.Flags().BoolVar(&treeClipboard, "clipboard", false, "Copy to clipboard")
}
+15
View File
@@ -0,0 +1,15 @@
module cliprepo
go 1.26.3
require (
github.com/atotto/clipboard v0.1.4
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/spf13/cobra v1.10.2
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
)
+25
View File
@@ -0,0 +1,25 @@
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+52
View File
@@ -0,0 +1,52 @@
package bundle
import (
"fmt"
"os"
"strings"
"cliprepo/internal/config"
"github.com/atotto/clipboard"
)
const maxFileSize = 1 * 1024 * 1024 // 1MB
func Build(key string) (string, error) {
cfg, err := config.Load()
if err != nil {
return "", err
}
files, ok := cfg.Bundles[key]
if !ok {
return "", fmt.Errorf("bundle not found: %s", key)
}
var builder strings.Builder
for _, file := range files {
info, err := os.Stat(file)
if err != nil {
continue
}
if info.Size() > maxFileSize {
continue
}
data, err := os.ReadFile(file)
if err != nil {
continue
}
builder.WriteString(fmt.Sprintf("===== %s =====\n", file))
builder.Write(data)
builder.WriteString("\n\n")
}
return builder.String(), nil
}
func Copy(content string) error {
return clipboard.WriteAll(content)
}
+22
View File
@@ -0,0 +1,22 @@
package config
import (
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Bundles map[string][]string `yaml:"bundles"`
}
func Load() (*Config, error) {
data, err := os.ReadFile("cliprepo.yaml")
if err != nil {
return nil, err
}
var cfg Config
err = yaml.Unmarshal(data, &cfg)
return &cfg, err
}
+18
View File
@@ -0,0 +1,18 @@
package gitignore
import (
"os"
"path/filepath"
ignore "github.com/sabhiram/go-gitignore"
)
func Load(root string) (*ignore.GitIgnore, error) {
path := filepath.Join(root, ".gitignore")
if _, err := os.Stat(path); os.IsNotExist(err) {
return ignore.CompileIgnoreLines(), nil
}
return ignore.CompileIgnoreFile(path)
}
+53
View File
@@ -0,0 +1,53 @@
package tree
import (
"fmt"
"os"
"path/filepath"
"strings"
"cliprepo/internal/gitignore"
"github.com/atotto/clipboard"
)
func Generate(root string) (string, error) {
ig, _ := gitignore.Load(root)
var builder strings.Builder
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == root {
builder.WriteString(".\n")
return nil
}
rel, _ := filepath.Rel(root, path)
if ig.MatchesPath(rel) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
level := strings.Count(rel, string(os.PathSeparator))
prefix := strings.Repeat("│ ", level)
builder.WriteString(fmt.Sprintf("%s├── %s\n", prefix, info.Name()))
return nil
})
return builder.String(), err
}
func Copy(content string) error {
return clipboard.WriteAll(content)
}
func Write(path, content string) error {
return os.WriteFile(path, []byte(content), 0644)
}
+7
View File
@@ -0,0 +1,7 @@
package main
import "cliprepo/cmd"
func main() {
cmd.Execute()
}