Metadata-Version: 2.4
Name: lazyfolders
Version: 0.1.0
Summary: Sync project-local dot folders into a central portfolio and restore them on demand.
Project-URL: Homepage, https://github.com/cesarcardoso/lazyfolders
Project-URL: Issues, https://github.com/cesarcardoso/lazyfolders/issues
Author: Cesar Cardoso
License-Expression: MIT
License-File: LICENSE
Keywords: cli,developer-tools,dotfiles,portfolio,templates
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# lazyfolders - v0.1.0

`lazyfolders` is a small CLI for saving project-local dot folders into a central portfolio and restoring them later. It is useful for folders you want near a project while working, but do not want committed to that project, such as `.agents`, `.notes`, `.scratch`, `.prompts`, or other local workflow state.

The tool is intentionally conservative:

- No runtime Python dependencies.
- Project identity is resolved from Git remotes when available.
- Local target folders get a top-level `.gitignore` containing `*` so their contents stay untracked.
- The portfolio copy excludes nested `.git` directories, top-level target `.gitignore` files, and lazyfolders metadata.
- Existing files are skipped by default unless you pass `--overwrite`.
- Prompts fail in non-interactive shells unless you pass `--yes`.

## Installation

Install from PyPI after publishing:

```sh
pipx install lazyfolders
```

or:

```sh
python3 -m pip install lazyfolders
```

For local development from this repository:

```sh
python3 -m pip install -e .
```

After installation, the command is:

```sh
lazyfolders --help
```

You can also run the package module directly while developing:

```sh
PYTHONPATH=src python3 -m lazy_folders --help
```

## Requirements

- Python 3.10 or newer.
- `git` is optional but recommended. When the current directory is inside a Git repository, `lazyfolders` uses the repository root and remote URL to determine the portfolio project name.
- `tree` is optional. If installed, `lazyfolders list --folder ...` uses it; otherwise it prints a built-in tree-like fallback.

## Core Concepts

### Portfolio

The portfolio is a directory outside your projects. It stores one subdirectory per project:

```text
~/lazy-dot-folders/
|-- my-app/
|   |-- .lazyfolders.yml
|   |-- .agents/
|   `-- .notes/
`-- template-python/
    `-- .agents/
```

The default portfolio path is:

```text
~/lazy-dot-folders
```

The configured path is stored at:

```text
$XDG_CONFIG_HOME/lazyfolders/config.yml
```

or, when `XDG_CONFIG_HOME` is not set:

```text
~/.config/lazyfolders/config.yml
```

### Project Identity

When a command runs inside a Git repository, project identity is resolved in this order:

1. Prefer the `upstream` remote when present.
2. Otherwise prefer the `origin` remote when present.
3. Otherwise use any remaining remote name in sorted order.
4. If no remote exists, use the Git repository root folder name.
5. If the current directory is not in a Git repository, use the current directory name.

Remote URLs are normalized for collision detection, and the repository name becomes the portfolio project folder. For example:

```text
git@github.com:example/workflow-app.git -> workflow-app
https://github.com/example/workflow-app.git -> workflow-app
```

Each portfolio project gets a `.lazyfolders.yml` metadata file with the resolved identity.

## Quick Start

Initialize the portfolio:

```sh
lazyfolders init
```

Initialize with a custom portfolio directory:

```sh
lazyfolders init ~/dev/lazy-folder-portfolio
```

Add a local folder from the current project:

```sh
lazyfolders add .notes
```

Restore saved folders into the current project:

```sh
lazyfolders pull
```

Push local changes back to the portfolio:

```sh
lazyfolders push .notes
```

List known project entries:

```sh
lazyfolders projects
```

List saved folders for the current project:

```sh
lazyfolders list
```

## Commands

### `init`

Create the portfolio directory and save its path in config.

```sh
lazyfolders init [portfolio_path]
```

Examples:

```sh
lazyfolders init
lazyfolders init ~/lazy-dot-folders
```

### `add`

Copy one local target folder into the current project's portfolio entry.

```sh
lazyfolders add TARGET_FOLDER [--overwrite] [--yes]
```

Example:

```sh
lazyfolders add .agents
```

If the target folder does not contain a top-level `.gitignore`, the command asks whether to create one containing `*`. This keeps local workflow files out of the project repository.

By default, files that already exist in the portfolio are skipped. Use `--overwrite` to replace same-path files after confirmation:

```sh
lazyfolders add .agents --overwrite
```

For non-interactive use:

```sh
lazyfolders add .agents --overwrite --yes
```

### `pull`

Copy saved folders from the portfolio into the current project.

```sh
lazyfolders pull [TARGET_FOLDER] [--use-template PROJECT] [--overwrite] [--yes]
```

Restore all saved folders for the current project:

```sh
lazyfolders pull
```

Restore one folder:

```sh
lazyfolders pull .notes
```

Restore from another portfolio project as a template:

```sh
lazyfolders pull .agents --use-template template-python --yes
```

Replace same-path local files:

```sh
lazyfolders pull .notes --overwrite --yes
```

### `push`

Copy local target folders back into the portfolio.

```sh
lazyfolders push [TARGET_FOLDER] [--to-project PROJECT] [--overwrite] [--yes]
```

Push one folder to the current project's portfolio entry:

```sh
lazyfolders push .notes
```

Push every locally available folder that is already known by the current portfolio project:

```sh
lazyfolders push
```

Push into a template project:

```sh
lazyfolders push .agents --to-project template-python --yes
```

Replace same-path portfolio files:

```sh
lazyfolders push .agents --overwrite --yes
```

### `list`

List saved target folders for a portfolio project.

```sh
lazyfolders list [--project PROJECT] [--folder TARGET_FOLDER]
```

List folders for the current project:

```sh
lazyfolders list
```

List folders for another project:

```sh
lazyfolders list --project template-python
```

Show a tree for one folder:

```sh
lazyfolders list --folder .agents
```

### `projects`

List known portfolio project folders.

```sh
lazyfolders projects
```

## Copy Rules

The copy operation is a merge, not a delete-and-replace.

Copied:

- Files that exist in the source but not in the destination.
- Same-path files only when `--overwrite` is passed.
- Nested `.gitignore` files, except for the top-level `.gitignore` of the target folder.

Skipped:

- Existing destination files when `--overwrite` is not passed.
- Any `.git` directory at any depth.
- `.lazyfolders.yml` metadata files.
- The top-level `.gitignore` inside the target folder.

This means `lazyfolders` should not remove files from your portfolio or your local project. If you delete a file locally and want it removed from the portfolio too, delete it from the portfolio directly.

## Non-Interactive Use

Prompts are intentionally strict. In a non-interactive shell, a command that needs confirmation exits with an error unless `--yes` is provided.

Use this shape in scripts:

```sh
lazyfolders add .agents --overwrite --yes
lazyfolders pull --yes
lazyfolders push .notes --overwrite --yes
```

## Development

Create a virtual environment and install the package in editable mode:

```sh
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install -e .
```

Run the shell test suite:

```sh
bash tests/run.sh
```

Build distribution artifacts:

```sh
python3 -m pip install build twine
python3 -m build
twine check dist/*
```

Publish to TestPyPI:

```sh
twine upload --repository testpypi dist/*
```

Publish to PyPI:

```sh
twine upload dist/*
```

## Repository Layout

```text
.
|-- pyproject.toml
|-- README.md
|-- LICENSE
|-- src/
|   `-- lazy_folders/
|       |-- __init__.py
|       `-- __main__.py
`-- tests/
    |-- run.sh
    `-- test_lazy_folders_*.sh
```

## License

MIT License. See [LICENSE](LICENSE).
