Metadata-Version: 2.4
Name: syncware
Version: 0.0.7
Summary: Sync selective folders from Git repositories to a target directory
Author-email: Your Name <you@example.com>
License: MIT
Keywords: cli,folder,sync,vendor
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: pyyaml
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# syncware

A CLI tool for syncing selective folders from local Git repositories to a target directory. Think `go mod vendor` or `npm install`, but for folder-based content.

## Installation

```bash
pip install syncware
```

## Development

Clone the repository and install with dev dependencies using [uv](https://github.com/astral-sh/uv):

```bash
git clone https://github.com/youruser/syncware.git
cd syncware
uv sync --group dev
```

This installs `build`, `twine`, and `pytest` for building and testing.

## Quick Start

```bash
# Initialize a new project
syncware init

# Edit syncware.yml with your repositories

# List configured folders
syncware list

# Sync folders to target
syncware sync

# Preview sync (dry run)
syncware sync --dry

# Update repos and sync
syncware update

# Generate lockfile (captures exact versions)
syncware lockfile

# Verify folders against lockfile
syncware verify

# Auto-fix drift
syncware verify --fix
```

## Verbosity

```bash
syncware -v list      # INFO level (default)
syncware -vv list     # DEBUG level
```

## Configuration

Create a `syncware.yml` file in your project root:

```yaml
repos:
  - name: myrepo
    path: /path/to/local/repo
    source: path/to/folders
    items:
      - folder1
      - file.txt
      - "*"  # or all items (files and subdirectories)
    target: ./output  # optional, overrides global target

target: ./output  # global target (default)
```

### Default Target

If no `target` is specified globally or per-repo, it defaults to `./output`:

```bash
# Both of these are equivalent:
syncware sync              # uses ./output as default
target: ./output
syncware sync             # explicitly set
```

### Per-Repo Target

Each repo can have its own target:

```yaml
repos:
  - name: frontend
    path: ./packages/frontend
    source: dist
    items: ["ui", "icons"]
    target: ./public/assets

  - name: backend
    path: ./packages/backend
    source: configs
    items: ["nginx", "docker"]
    target: ./deploy/config

target: ./common/output  # used by repos without own target
```

### Multiple Sources Per Repo

A single repo entry can sync from multiple source directories, each with its own `items` and optional per-source `target`:

```yaml
repos:
  - name: myrepo
    path: /path/to/repo
    sources:
      - source: skills
        items: ["folder1", "folder2"]
        target: ./output1  # optional, overrides repo/global target
      - source: docs
        items: ["readme", "api"]
        target: ./output2
```

This is equivalent to:

```yaml
# Legacy single-source format (still fully supported)
repos:
  - name: myrepo
    path: /path/to/repo
    source: skills
    items: ["folder1", "folder2"]
    target: ./output1
```

### YAML Variables (Anchor & Alias)

YAML anchors and aliases let you define a value once and reuse it across multiple repos:

```yaml
# Define reusable values
shared_target: &shared_target ~/Downloads/output
backup_target: &backup_target ./backup

repos:
  - name: frontend
    path: ./packages/frontend
    source: dist
    items: ["ui", "icons"]
    target: *shared_target

  - name: backend
    path: ./packages/backend
    source: configs
    items: ["nginx", "docker"]
    target: *shared_target

  - name: docs
    path: ./docs
    source: build
    items: ["html"]
    target: *backup_target
```

### Clean Item

When `clean_item_content: true`, synchronizes target folders to exactly match source by removing any extra files/directories that exist in target but not in source. Can be set globally or per-repo:

```yaml
clean_item_content: true  # global setting

repos:
  - name: myrepo
    path: ./repo
    source: dist
    items: ["ui", "icons"]
    clean_item_content: false  # repo-level override
```

### Ignore Hidden Files and Folders

By default, all files are synced including hidden ones (starting with `.`). Set `ignore_hidden_file` or `ignore_hidden_folder` to `true` to exclude them:

```yaml
ignore_hidden_file: true   # skip hidden files (starting with .)
ignore_hidden_folder: true  # skip hidden directories (starting with .)

repos:
  - name: myrepo
    path: ./repo
    source: dist
    items: ["ui", "icons"]
    ignore_hidden_file: false  # repo-level override
```

### Keep Existing Item

When `keep_existing_item: false`, removes folders in the target root that aren't configured to sync. Default is `true` (keeps extra folders):

```yaml
keep_existing_item: false  # global setting

repos:
  - name: myrepo
    path: ./repo
    source: dist
    items: ["ui", "icons"]
    keep_existing_item: true  # repo-level override
```

### Multiple Targets

Both global and per-repo targets can specify multiple destinations:

```yaml
# Global multiple targets
target: ["./output1", "./output2"]

repos:
  - name: myrepo
    path: /path/to/repo
    source: dist
    items: ["ui", "icons"]
    # folder1 and folder2 sync to BOTH ./output1 and ./output2
```

```yaml
# Per-repo multiple targets
repos:
  - name: frontend
    path: ./packages/frontend
    source: dist
    items: ["ui", "icons"]
    target: ["./public/assets", "./backup/assets"]
    # ui and icons sync to BOTH ./public/assets and ./backup/assets
```

### Remote Repositories

You can also use remote Git URLs (GitHub, GitLab, etc.):

```yaml
repos:
  - name: claude
    path: https://github.com/user/repo.git
    source: skills
    items: ["docx", "xlsx"]
```

Remote repos are cloned to `~/.cache/syncware/<name>` and reused on subsequent runs.

### Config Fields

| Field | Description |
|-------|-------------|
| `repos` | List of source repositories |
| `repos[].name` | Namespace identifier |
| `repos[].path` | Local path or Git URL |
| `repos[].source` | Subdirectory containing folders to sync (single-source format) |
| `repos[].sources` | List of source entries, each with `source`, `items`, optional `target` (multi-source format) |
| `repos[].items` | List of folder or file names, or `"*"` for all |
| `repos[].target` | Per-repo destination (string or list, overrides global) |
| `repos[].clean_item_content` | Remove extra files in synced folders not in source (overrides global) |
| `clean_item_content` | Global clean setting (removes extra files in synced folders) |
| `repos[].ignore_hidden_file` | Skip hidden files starting with `.` (overrides global) |
| `ignore_hidden_file` | Global ignore hidden files setting |
| `repos[].ignore_hidden_folder` | Skip hidden directories starting with `.` (overrides global) |
| `ignore_hidden_folder` | Global ignore hidden folders setting |
| `repos[].keep_existing_item` | Keep extra folders in target root not in config (overrides global) |
| `keep_existing_item` | Global keep setting (removes extra folders in target root) |
| `target` | Global destination directory (string or list) |

## How It Works

```
<repo.path>/<source>/<folder> → copied to <repo.target>/<folder>
                                  or <global.target>/<folder>
                                  (or to ALL targets if multiple)
```

## Lockfile

The lockfile (`syncware.lock.yml`) ensures reproducible syncs:

```bash
syncware lockfile          # Generate lockfile
syncware verify           # Check for drift
syncware verify --fix     # Auto-fix drift
```

**What gets locked:**
- Git commit hash of each source repository
- Content hash (SHA256) of each synced folder

**Verification detects:**
- `DRIFT` - Folder content was modified locally
- `UPDATE_AVAILABLE` - Source repo has new commits
- `MISSING` - Source folder was deleted

## Use Cases

- **Skills/Plugins** - Sync skill folders from a central repo
- **Config Management** - Keep config folders in sync across machines
- **Documentation** - Maintain a set of doc folders from different sources
- **Assets** - Bundle asset folders from multiple repositories

## Comparison

| Tool | Use Case |
|------|----------|
| `syncware` | Selective folder sync from local or remote repos |
| `go mod vendor` | Vendor Go dependencies |
| `npm install` | Install package dependencies |
| `rsync` | General file/folder synchronization |
