28 KiB
AEVS CLI — Implementation Plan
Пошаговый план для агента-разработчика.
Общая информация
Проект: AEVS CLI — инструмент синхронизации .env файлов между машинами одного пользователя.
Технологии:
- Go (golang)
- CLI:
github.com/spf13/cobra - S3:
github.com/aws/aws-sdk-go-v2 - YAML:
gopkg.in/yaml.v3
Документация: .docs/02-opus-cli-docs/
Фаза 1: Инициализация проекта
Шаг 1.1: Создание структуры проекта
Создай следующую структуру директорий:
aevs/
├── cmd/
│ └── aevs/
│ └── main.go # точка входа
├── internal/
│ ├── cli/
│ │ ├── root.go # root command
│ │ ├── config.go # aevs config
│ │ ├── init.go # aevs init
│ │ ├── push.go # aevs push
│ │ ├── pull.go # aevs pull
│ │ ├── list.go # aevs list
│ │ └── status.go # aevs status
│ ├── config/
│ │ ├── global.go # GlobalConfig, загрузка/сохранение
│ │ ├── project.go # ProjectConfig
│ │ └── constants.go # константы
│ ├── storage/
│ │ ├── storage.go # Storage interface
│ │ └── s3.go # S3 implementation
│ ├── archiver/
│ │ └── archiver.go # tar.gz создание/распаковка
│ ├── scanner/
│ │ └── scanner.go # сканирование .env файлов
│ └── types/
│ └── types.go # Metadata, FileStatus, etc.
├── go.mod
├── go.sum
└── README.md
Чеклист:
- Создана директория
cmd/aevs/ - Создана директория
internal/cli/ - Создана директория
internal/config/ - Создана директория
internal/storage/ - Создана директория
internal/archiver/ - Создана директория
internal/scanner/ - Создана директория
internal/types/ - Проверен другой моделью и составлен следующий шаг
Шаг 1.2: Инициализация Go модуля
go mod init github.com/user/aevs
Чеклист:
- Выполнена команда
go mod init - Создан файл
go.mod - Проверен другой моделью и составлен следующий шаг
Шаг 1.3: Добавление зависимостей
go get github.com/spf13/cobra@latest
go get github.com/aws/aws-sdk-go-v2/config@latest
go get github.com/aws/aws-sdk-go-v2/service/s3@latest
go get github.com/aws/aws-sdk-go-v2/credentials@latest
go get gopkg.in/yaml.v3@latest
Чеклист:
- Добавлена зависимость
github.com/spf13/cobra - Добавлена зависимость
github.com/aws/aws-sdk-go-v2/config - Добавлена зависимость
github.com/aws/aws-sdk-go-v2/service/s3 - Добавлена зависимость
github.com/aws/aws-sdk-go-v2/credentials - Добавлена зависимость
gopkg.in/yaml.v3 - Создан файл
go.sum - Проверен другой моделью и составлен следующий шаг
Фаза 2: Types & Config
Шаг 2.1: Создай internal/types/types.go
Реализуй типы из документации (см. .docs/02-opus-cli-docs/04-types.md):
Metadata— метаданные в storageProjectInfo— для listFileStatus— для statusSyncStatus— константы статусов
Чеклист:
- Создан файл
internal/types/types.go - Реализован тип
Metadata - Реализован тип
ProjectInfo - Реализован тип
FileStatus - Реализован тип
SyncStatusс константами - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 2.2: Создай internal/config/constants.go
Константы из документации:
DefaultConfigDir = ".config/aevs"DefaultGlobalConfigFile = "config.yaml"DefaultProjectConfigFile = "aevs.yaml"ArchiveFileName = "envs.tar.gz"MetadataFileName = "metadata.json"- и т.д.
Чеклист:
- Создан файл
internal/config/constants.go - Определена константа
DefaultConfigDir - Определена константа
DefaultGlobalConfigFile - Определена константа
DefaultProjectConfigFile - Определена константа
ArchiveFileName - Определена константа
MetadataFileName - Определены дефолтные значения для S3
- Проверен другой моделью и составлен следующий шаг
Шаг 2.3: Создай internal/config/global.go
type GlobalConfig struct {
Storage StorageConfig `yaml:"storage"`
}
type StorageConfig struct {
Type string `yaml:"type"`
Endpoint string `yaml:"endpoint"`
Region string `yaml:"region"`
Bucket string `yaml:"bucket"`
AccessKey string `yaml:"access_key"`
SecretKey string `yaml:"secret_key"`
}
Функции:
LoadGlobalConfig() (*GlobalConfig, error)— загрузка из~/.config/aevs/config.yamlSaveGlobalConfig(cfg *GlobalConfig) error— сохранение с правами0600GlobalConfigPath() string— путь к конфигуGlobalConfigExists() bool— проверка существования
Чеклист:
- Создан файл
internal/config/global.go - Реализован тип
GlobalConfig - Реализован тип
StorageConfig - Реализована функция
LoadGlobalConfig() - Реализована функция
SaveGlobalConfig()с правами0600 - Реализована функция
GlobalConfigPath() - Реализована функция
GlobalConfigExists() - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 2.4: Создай internal/config/project.go
type ProjectConfig struct {
Project string `yaml:"project"`
Files []string `yaml:"files"`
}
Функции:
LoadProjectConfig(path string) (*ProjectConfig, error)SaveProjectConfig(path string, cfg *ProjectConfig) errorValidateProjectName(name string) error— проверка[a-z0-9_-]
Чеклист:
- Создан файл
internal/config/project.go - Реализован тип
ProjectConfig - Реализована функция
LoadProjectConfig() - Реализована функция
SaveProjectConfig() - Реализована функция
ValidateProjectName()с regex[a-z0-9_-] - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Фаза 3: Storage Layer
Шаг 3.1: Создай internal/storage/storage.go
Интерфейс Storage:
type Storage interface {
Upload(ctx context.Context, key string, data io.Reader, size int64) error
Download(ctx context.Context, key string) (io.ReadCloser, error)
Delete(ctx context.Context, key string) error
Exists(ctx context.Context, key string) (bool, error)
List(ctx context.Context, prefix string) ([]string, error)
ListProjects(ctx context.Context) ([]string, error)
TestConnection(ctx context.Context) error
}
Чеклист:
- Создан файл
internal/storage/storage.go - Определён интерфейс
Storage - Метод
Uploadв интерфейсе - Метод
Downloadв интерфейсе - Метод
Deleteв интерфейсе - Метод
Existsв интерфейсе - Метод
Listв интерфейсе - Метод
ListProjectsв интерфейсе - Метод
TestConnectionв интерфейсе - Проверен другой моделью и составлен следующий шаг
Шаг 3.2: Создай internal/storage/s3.go
S3 реализация интерфейса Storage:
type S3Storage struct {
client *s3.Client
bucket string
}
func NewS3Storage(cfg *config.StorageConfig) (*S3Storage, error)
Методы:
Upload—s3.PutObjectDownload—s3.GetObjectDelete—s3.DeleteObjectExists—s3.HeadObjectList—s3.ListObjectsV2ListProjects— list с delimiter/TestConnection—s3.ListBucketsилиHeadBucket
Важно: Используй aws.Config с кастомным endpoint для поддержки MinIO, R2, Spaces.
Чеклист:
- Создан файл
internal/storage/s3.go - Реализована структура
S3Storage - Реализована функция
NewS3Storage()с кастомным endpoint - Реализован метод
Upload() - Реализован метод
Download() - Реализован метод
Delete() - Реализован метод
Exists() - Реализован метод
List() - Реализован метод
ListProjects()с delimiter - Реализован метод
TestConnection() - S3Storage реализует интерфейс Storage
- Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Фаза 4: Scanner & Archiver
Шаг 4.1: Создай internal/scanner/scanner.go
Сканирование директории на .env файлы:
func Scan(rootDir string) ([]string, error)
Паттерны для включения:
.env.env.**.env
Исключения (директории):
node_modules,.git,vendor,venv,.venv,__pycache__,.idea,.vscode,dist,build
Исключения (файлы):
.env.example,.env.sample,.env.template
Чеклист:
- Создан файл
internal/scanner/scanner.go - Определены паттерны включения
- Определены исключаемые директории
- Определены исключаемые файлы
- Реализована функция
Scan() - Функция рекурсивно обходит директории
- Функция корректно фильтрует файлы
- Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 4.2: Создай internal/archiver/archiver.go
func Create(files []string, rootDir string) (io.Reader, int64, error)
func Extract(archive io.Reader, destDir string) ([]string, error)
func List(archive io.Reader) ([]string, error)
Create— создаёт tar.gz архив из списка файловExtract— распаковывает архив в директорию, создаёт недостающие папкиList— возвращает список файлов в архиве без распаковки
Чеклист:
- Создан файл
internal/archiver/archiver.go - Реализована функция
Create()(tar.gz) - Реализована функция
Extract()с созданием директорий - Реализована функция
List() - Пути в архиве сохраняются относительно rootDir
- Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Фаза 5: CLI Commands
Шаг 5.1: Создай internal/cli/root.go
Root command с cobra:
var rootCmd = &cobra.Command{
Use: "aevs",
Short: "Sync .env files between machines",
}
func Execute() error {
return rootCmd.Execute()
}
Глобальные флаги:
--verbose,-v— verbose output--debug— debug output
Чеклист:
- Создан файл
internal/cli/root.go - Определён
rootCmdс cobra - Реализована функция
Execute() - Добавлен флаг
--verbose/-v - Добавлен флаг
--debug - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 5.2: Создай internal/cli/config.go — команда aevs config
Интерактивная настройка credentials:
- Запросить storage type (default:
s3) - Запросить endpoint (default:
https://s3.amazonaws.com) - Запросить region (default:
us-east-1) - Запросить bucket name
- Запросить access key
- Запросить secret key (скрытый ввод)
- Проверить подключение (
TestConnection) - Сохранить конфиг
Чеклист:
- Создан файл
internal/cli/config.go - Определён
configCmdс cobra - Реализован интерактивный ввод storage type
- Реализован интерактивный ввод endpoint
- Реализован интерактивный ввод region
- Реализован интерактивный ввод bucket name
- Реализован интерактивный ввод access key
- Реализован скрытый ввод secret key
- Вызывается
TestConnection()для проверки - Конфиг сохраняется через
SaveGlobalConfig() - Команда зарегистрирована в
rootCmd - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 5.3: Создай internal/cli/init.go — команда aevs init
aevs init [project-name]
Флаги:
--force,-f— перезаписать существующий конфиг
Логика:
- Проверить существование
aevs.yaml - Если существует и нет
--force:- Сканировать на новые файлы
- Предложить добавить
- Если не существует:
- Сканировать директорию
- Определить имя проекта (аргумент или имя папки)
- Создать
aevs.yaml
Чеклист:
- Создан файл
internal/cli/init.go - Определён
initCmdс cobra - Добавлен флаг
--force/-f - Реализована проверка существования
aevs.yaml - Реализовано сканирование директории через
scanner.Scan() - Реализовано определение имени проекта (аргумент или имя папки)
- Реализовано добавление новых файлов в существующий конфиг
- Создаётся
aevs.yamlчерезSaveProjectConfig() - Команда зарегистрирована в
rootCmd - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 5.4: Создай internal/cli/push.go — команда aevs push
Флаги:
--config,-c— путь к конфигу--dry-run— показать что будет загружено
Логика:
- Загрузить глобальный конфиг
- Загрузить проектный конфиг
- Проверить существование всех файлов
- Создать tar.gz архив
- Загрузить архив в
{project}/envs.tar.gz - Создать и загрузить metadata.json
- Вывести результат
Чеклист:
- Создан файл
internal/cli/push.go - Определён
pushCmdс cobra - Добавлен флаг
--config/-c - Добавлен флаг
--dry-run - Реализована загрузка глобального конфига
- Реализована загрузка проектного конфига
- Реализована проверка существования файлов
- Реализовано создание архива через
archiver.Create() - Реализована загрузка архива через
storage.Upload() - Реализовано создание и загрузка
metadata.json - Реализован режим
--dry-run - Команда зарегистрирована в
rootCmd - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 5.5: Создай internal/cli/pull.go — команда aevs pull
aevs pull [project-name]
Флаги:
--config,-c--force,-f— перезаписать без подтверждения--dry-run
Логика:
- Определить имя проекта (аргумент или из
aevs.yaml) - Скачать metadata.json
- Скачать envs.tar.gz
- Для каждого файла:
- Если не существует — создать
- Если существует и отличается — спросить (или --force)
- Вывести результат
Обработка конфликтов:
[o]verwrite— перезаписать[s]kip— пропустить[d]iff— показать diff[O]verwrite all[S]kip all
Чеклист:
- Создан файл
internal/cli/pull.go - Определён
pullCmdс cobra - Добавлен флаг
--config/-c - Добавлен флаг
--force/-f - Добавлен флаг
--dry-run - Реализовано определение имени проекта
- Реализовано скачивание metadata.json
- Реализовано скачивание envs.tar.gz
- Реализована распаковка через
archiver.Extract() - Реализована обработка конфликтов (o/s/d/O/S)
- Реализован режим
--force - Реализован режим
--dry-run - Команда зарегистрирована в
rootCmd - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 5.6: Создай internal/cli/list.go — команда aevs list
Флаги:
--json— вывод в JSON
Логика:
- Загрузить глобальный конфиг
- Получить список проектов (
ListProjects) - Для каждого проекта загрузить metadata.json
- Вывести таблицу или JSON
Чеклист:
- Создан файл
internal/cli/list.go - Определён
listCmdс cobra - Добавлен флаг
--json - Реализована загрузка глобального конфига
- Реализовано получение списка проектов через
ListProjects() - Реализована загрузка metadata.json для каждого проекта
- Реализован вывод в виде таблицы
- Реализован вывод в формате JSON
- Команда зарегистрирована в
rootCmd - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 5.7: Создай internal/cli/status.go — команда aevs status
Флаги:
--config,-c
Логика:
- Загрузить оба конфига
- Скачать metadata.json из storage
- Для каждого файла:
- Сравнить размер и хеш локального vs remote
- Определить статус
- Вывести таблицу статусов
Чеклист:
- Создан файл
internal/cli/status.go - Определён
statusCmdс cobra - Добавлен флаг
--config/-c - Реализована загрузка обоих конфигов
- Реализовано скачивание metadata.json
- Реализовано сравнение локальных и remote файлов
- Реализовано определение статуса каждого файла
- Реализован вывод таблицы статусов
- Команда зарегистрирована в
rootCmd - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Фаза 6: Error Handling
Шаг 6.1: Создай internal/errors/errors.go
Определённые ошибки из документации:
var (
ErrNoGlobalConfig = errors.New("no storage configured; run 'aevs config' first")
ErrNoProjectConfig = errors.New("no aevs.yaml found; run 'aevs init' first")
ErrConfigExists = errors.New("aevs.yaml already exists; use --force to overwrite")
ErrInvalidProject = errors.New("invalid project name; use only a-z, 0-9, -, _")
ErrProjectNotFound = errors.New("project not found in storage")
ErrAccessDenied = errors.New("access denied; check your credentials")
ErrBucketNotFound = errors.New("bucket not found")
ErrFileNotFound = errors.New("file not found")
ErrNoEnvFiles = errors.New("no env files found")
)
Чеклист:
- Создан файл
internal/errors/errors.go - Определена ошибка
ErrNoGlobalConfig - Определена ошибка
ErrNoProjectConfig - Определена ошибка
ErrConfigExists - Определена ошибка
ErrInvalidProject - Определена ошибка
ErrProjectNotFound - Определена ошибка
ErrAccessDenied - Определена ошибка
ErrBucketNotFound - Определена ошибка
ErrFileNotFound - Определена ошибка
ErrNoEnvFiles - Код компилируется без ошибок
- Проверен другой моделью и составлен следующий шаг
Шаг 6.2: Exit codes
Реализуй exit codes:
0— Success1— General error2— Configuration error3— File system error4— Storage error5— Network error130— Interrupted
Чеклист:
- Определены константы exit codes
- Реализована функция для определения exit code по типу ошибки
- Exit codes используются в
main.go - Проверен другой моделью и составлен следующий шаг
Фаза 7: Entry Point
Шаг 7.1: Создай cmd/aevs/main.go
package main
import (
"os"
"aevs/internal/cli"
)
func main() {
if err := cli.Execute(); err != nil {
os.Exit(1)
}
}
Чеклист:
- Создан файл
cmd/aevs/main.go - Импортирован пакет
cli - Вызывается
cli.Execute() - Используются корректные exit codes
- Приложение компилируется:
go build ./cmd/aevs - Приложение запускается:
./aevs --help - Проверен другой моделью и составлен следующий шаг
Фаза 8: Тестирование
Шаг 8.1: Unit tests
Создай тесты для:
config/— загрузка/сохранение конфиговscanner/— сканирование файловarchiver/— создание/распаковка архивовstorage/— mock тесты для S3
Чеклист:
- Создан файл
internal/config/global_test.go - Создан файл
internal/config/project_test.go - Создан файл
internal/scanner/scanner_test.go - Создан файл
internal/archiver/archiver_test.go - Создан файл
internal/storage/s3_test.go(mock) - Все тесты проходят:
go test ./... - Проверен другой моделью и составлен следующий шаг
Шаг 8.2: Integration tests
Тесты с реальным S3 (или MinIO в Docker):
- Полный цикл: config → init → push → pull → status → list
Чеклист:
- Настроен MinIO в Docker для тестов
- Создан файл интеграционных тестов
- Тест полного цикла работает
- Тесты можно запустить локально
- Проверен другой моделью и составлен следующий шаг
Фаза 9: Build & Release
Шаг 9.1: Makefile
.PHONY: build test clean
build:
go build -o bin/aevs ./cmd/aevs
test:
go test ./...
clean:
rm -rf bin/
Чеклист:
- Создан файл
Makefile - Добавлена цель
build - Добавлена цель
test - Добавлена цель
clean make buildработаетmake testработает- Проверен другой моделью и составлен следующий шаг
Шаг 9.2: README.md
Создай README с:
- Описанием проекта
- Установкой
- Quick start
- Командами
Чеклист:
- Создан файл
README.md - Добавлено описание проекта
- Добавлены инструкции по установке
- Добавлен раздел Quick Start
- Добавлено описание всех команд
- Добавлены примеры использования
- Проверен другой моделью и составлен следующий шаг
Порядок реализации (приоритет)
- Фаза 1 — структура проекта
- Фаза 2 — types & config
- Фаза 4 — scanner & archiver (можно тестировать локально)
- Фаза 3 — storage layer
- Фаза 5 — CLI commands в порядке:
root.goconfig.go(нужен для всего остального)init.gopush.gopull.golist.gostatus.go
- Фаза 6 — error handling (можно делать параллельно)
- Фаза 7 — entry point
- Фаза 8 — тесты
- Фаза 9 — build & docs
Ссылки на документацию
- 01-overview.md — обзор и архитектура
- 02-configuration.md — конфигурация
- 03-commands.md — детали команд
- 04-types.md — Go типы
- 05-scenarios.md — сценарии использования
- 06-errors.md — обработка ошибок