Sync project-local dot folders into a central portfolio and restore them on demand.
Project description
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
.gitignorecontaining*so their contents stay untracked. - The portfolio copy excludes nested
.gitdirectories, top-level target.gitignorefiles, 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:
pipx install lazyfolders
or:
python3 -m pip install lazyfolders
For local development from this repository:
python3 -m pip install -e .
After installation, the command is:
lazyfolders --help
You can also run the package module directly while developing:
PYTHONPATH=src python3 -m lazy_folders --help
Requirements
- Python 3.10 or newer.
gitis optional but recommended. When the current directory is inside a Git repository,lazyfoldersuses the repository root and remote URL to determine the portfolio project name.treeis 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:
~/lazy-dot-folders/
|-- my-app/
| |-- .lazyfolders.yml
| |-- .agents/
| `-- .notes/
`-- template-python/
`-- .agents/
The default portfolio path is:
~/lazy-dot-folders
The configured path is stored at:
$XDG_CONFIG_HOME/lazyfolders/config.yml
or, when XDG_CONFIG_HOME is not set:
~/.config/lazyfolders/config.yml
Project Identity
When a command runs inside a Git repository, project identity is resolved in this order:
- Prefer the
upstreamremote when present. - Otherwise prefer the
originremote when present. - Otherwise use any remaining remote name in sorted order.
- If no remote exists, use the Git repository root folder name.
- 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:
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:
lazyfolders init
Initialize with a custom portfolio directory:
lazyfolders init ~/dev/lazy-folder-portfolio
Add a local folder from the current project:
lazyfolders add .notes
Restore saved folders into the current project:
lazyfolders pull
Push local changes back to the portfolio:
lazyfolders push .notes
List known project entries:
lazyfolders projects
List saved folders for the current project:
lazyfolders list
Commands
init
Create the portfolio directory and save its path in config.
lazyfolders init [portfolio_path]
Examples:
lazyfolders init
lazyfolders init ~/lazy-dot-folders
add
Copy one local target folder into the current project's portfolio entry.
lazyfolders add TARGET_FOLDER [--overwrite] [--yes]
Example:
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:
lazyfolders add .agents --overwrite
For non-interactive use:
lazyfolders add .agents --overwrite --yes
pull
Copy saved folders from the portfolio into the current project.
lazyfolders pull [TARGET_FOLDER] [--use-template PROJECT] [--overwrite] [--yes]
Restore all saved folders for the current project:
lazyfolders pull
Restore one folder:
lazyfolders pull .notes
Restore from another portfolio project as a template:
lazyfolders pull .agents --use-template template-python --yes
Replace same-path local files:
lazyfolders pull .notes --overwrite --yes
push
Copy local target folders back into the portfolio.
lazyfolders push [TARGET_FOLDER] [--to-project PROJECT] [--overwrite] [--yes]
Push one folder to the current project's portfolio entry:
lazyfolders push .notes
Push every locally available folder that is already known by the current portfolio project:
lazyfolders push
Push into a template project:
lazyfolders push .agents --to-project template-python --yes
Replace same-path portfolio files:
lazyfolders push .agents --overwrite --yes
list
List saved target folders for a portfolio project.
lazyfolders list [--project PROJECT] [--folder TARGET_FOLDER]
List folders for the current project:
lazyfolders list
List folders for another project:
lazyfolders list --project template-python
Show a tree for one folder:
lazyfolders list --folder .agents
projects
List known portfolio project folders.
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
--overwriteis passed. - Nested
.gitignorefiles, except for the top-level.gitignoreof the target folder.
Skipped:
- Existing destination files when
--overwriteis not passed. - Any
.gitdirectory at any depth. .lazyfolders.ymlmetadata files.- The top-level
.gitignoreinside 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:
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:
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install -e .
Run the shell test suite:
bash tests/run.sh
Build distribution artifacts:
python3 -m pip install build twine
python3 -m build
twine check dist/*
Publish to TestPyPI:
twine upload --repository testpypi dist/*
Publish to PyPI:
twine upload dist/*
Repository Layout
.
|-- pyproject.toml
|-- README.md
|-- LICENSE
|-- src/
| `-- lazy_folders/
| |-- __init__.py
| `-- __main__.py
`-- tests/
|-- run.sh
`-- test_lazy_folders_*.sh
License
MIT License. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lazyfolders-0.1.0.tar.gz.
File metadata
- Download URL: lazyfolders-0.1.0.tar.gz
- Upload date:
- Size: 12.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
110dbb1e09a0e7872653b29af571a6194303d09d0538955f3db9e1933680dc4c
|
|
| MD5 |
0892393633ad8861407663a8b7051c8c
|
|
| BLAKE2b-256 |
144ebab832d1f191647312ff6b9a3fb91c32daeaf68c084674cebfaa4b4faf77
|
Provenance
The following attestation bundles were made for lazyfolders-0.1.0.tar.gz:
Publisher:
publish-pypi.yml on bouli/lazyfolders
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lazyfolders-0.1.0.tar.gz -
Subject digest:
110dbb1e09a0e7872653b29af571a6194303d09d0538955f3db9e1933680dc4c - Sigstore transparency entry: 1930506101
- Sigstore integration time:
-
Permalink:
bouli/lazyfolders@750b7587ed111772d1d90d146d1087a90c379995 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bouli
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@750b7587ed111772d1d90d146d1087a90c379995 -
Trigger Event:
push
-
Statement type:
File details
Details for the file lazyfolders-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lazyfolders-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db9555d552f86450b84dc1dac8b196c0851c4eea3e1098da449d05f305d3067b
|
|
| MD5 |
0758a18aa11f1939239cdcd52714f953
|
|
| BLAKE2b-256 |
f3c36f77f7c1b6b3bc60facf7e83dcf6c297041a66084010b939239aa42e8d80
|
Provenance
The following attestation bundles were made for lazyfolders-0.1.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on bouli/lazyfolders
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lazyfolders-0.1.0-py3-none-any.whl -
Subject digest:
db9555d552f86450b84dc1dac8b196c0851c4eea3e1098da449d05f305d3067b - Sigstore transparency entry: 1930506182
- Sigstore integration time:
-
Permalink:
bouli/lazyfolders@750b7587ed111772d1d90d146d1087a90c379995 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bouli
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@750b7587ed111772d1d90d146d1087a90c379995 -
Trigger Event:
push
-
Statement type: