Skip to main content

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 .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:

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.
  • 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:

~/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:

  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:

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 --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:

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lazyfolders-0.1.0.tar.gz (12.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lazyfolders-0.1.0-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

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

Hashes for lazyfolders-0.1.0.tar.gz
Algorithm Hash digest
SHA256 110dbb1e09a0e7872653b29af571a6194303d09d0538955f3db9e1933680dc4c
MD5 0892393633ad8861407663a8b7051c8c
BLAKE2b-256 144ebab832d1f191647312ff6b9a3fb91c32daeaf68c084674cebfaa4b4faf77

See more details on using hashes here.

Provenance

The following attestation bundles were made for lazyfolders-0.1.0.tar.gz:

Publisher: publish-pypi.yml on bouli/lazyfolders

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for lazyfolders-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 db9555d552f86450b84dc1dac8b196c0851c4eea3e1098da449d05f305d3067b
MD5 0758a18aa11f1939239cdcd52714f953
BLAKE2b-256 f3c36f77f7c1b6b3bc60facf7e83dcf6c297041a66084010b939239aa42e8d80

See more details on using hashes here.

Provenance

The following attestation bundles were made for lazyfolders-0.1.0-py3-none-any.whl:

Publisher: publish-pypi.yml on bouli/lazyfolders

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page