2026-05-09 16:05:17 -05:00
|
|
|
# CLAUDE.md
|
|
|
|
|
|
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
|
|
|
|
|
|
## Project
|
|
|
|
|
|
|
|
|
|
`drawer` is a Rust project (edition 2024). Entry point is `src/main.rs`.
|
|
|
|
|
|
|
|
|
|
## Dependencies
|
|
|
|
|
|
|
|
|
|
- `tar` — reading and writing tarballs in memory
|
|
|
|
|
- `flate2` — gzip compression, paired with `tar`
|
|
|
|
|
- `age` — encryption/decryption using SSH keys (RSA or Ed25519) loaded from disk
|
|
|
|
|
- `clap` — command line argument parsing (derive API), used for subcommands and flags
|
|
|
|
|
|
|
|
|
|
`drawer` is a command line tool for managing projects.
|
|
|
|
|
|
|
|
|
|
A drawer is a single directory with related files in for a project. Drawer files always end
|
|
|
|
|
in the `.drawer` extension, like `foo.drawer`. To create a drawer file from a directory, we
|
|
|
|
|
first tar all files in the directory, and then encrypt the resulting tarball. To open a drawer
|
|
|
|
|
file, we decrypt it and expand the tarball to a directory.
|
|
|
|
|
|
|
|
|
|
The `drawer` tool has two subcommands, `drawer open` and `drawer close`. Each of these commands
|
|
|
|
|
requires a key, the path to which can be given by either the `$DRAWER_KEY` environment variable
|
|
|
|
|
or a `-i` command line parameter.
|
|
|
|
|
|
|
|
|
|
### Open subcommand
|
|
|
|
|
|
|
|
|
|
The `drawer open` subcommand will decrypt and untar a drawer file into a path. It takes two arguments,
|
|
|
|
|
the name of a drawer file (required) and a path to expand it into (optional, defaults to a directory
|
|
|
|
|
under the current directory named the same as the drawer file). If the target path exists and is not
|
|
|
|
|
empty, print an error message, unless the `-f` flag is passed.
|
|
|
|
|
|
|
|
|
|
### Close subcommand
|
|
|
|
|
|
|
|
|
|
The `drawer close` subcommand will create a drawer file from a directory and a key. The command
|
|
|
|
|
takes two arguments, the name of a drawer file (required) and the path to a directory (optional,
|
|
|
|
|
defaults to the current directory). If the target drawer file is inside the directory, print an
|
|
|
|
|
error message saying so. Otherwise, create a tarball (in memory) of the directory contents, encrypt
|
|
|
|
|
them (again in memory), and write the drawer file to disk.
|
|
|
|
|
|
|
|
|
|
### Examples:
|
|
|
|
|
|
|
|
|
|
- `drawer close -i ~/.keys/mykey ~/Documents/blah.drawer ~/Documents/blah` (create a file, `~/Documents/blah.drawer`,
|
|
|
|
|
encrypted with `~/.keys/mykey`, containing the contents of `~/Documents/blah`)
|
|
|
|
|
- `drawer open ~/Documents/blah.drawer` (open `~/Documents/blah.drawer` into `./blah` using a key found at `$DRAWER_KEY`)
|
|
|
|
|
|
|
|
|
|
## Implementation plan
|
|
|
|
|
|
|
|
|
|
The implementation of this project will happen in several phases. Claude will do the primary coding for each phase, then
|
|
|
|
|
the user (me) will review the code, write tests, and possibly make changes, before proceeding with the next phase.
|
|
|
|
|
|
|
|
|
|
At to time should Claude delete or modify any tests whatsoever.
|
|
|
|
|
|
|
|
|
|
### Phase 1
|
|
|
|
|
|
|
|
|
|
Write code that uses `clap` to parse the command line arguments and create a `DrawerOperation` struct from them. The
|
|
|
|
|
`DrawerOperation` struct will contain:
|
|
|
|
|
|
|
|
|
|
- An `operation_type` (`OperationType`, enum of `Open` or `Close`)
|
|
|
|
|
- A `drawer_file` (`Path` for the drawer file to open or close)
|
|
|
|
|
- A `key_path` (`Path` to the key we'll use, which is either the value of the `-i` flag or the value of the `$DRAWER_KEY`
|
|
|
|
|
env var)
|
|
|
|
|
- A `target_path` (`Path` to the contents we're going to put into or take out of the drawer)
|
|
|
|
|
- A `force` (bool for whether or not `-f` was passed)
|
|
|
|
|
|
|
|
|
|
In order for this to be testable, it needs to be a function that returns a `Result<DrawerOperation, DrawerError>`. The
|
|
|
|
|
main function should call that and then do nothing with the result.
|
|
|
|
|
|
|
|
|
|
Certain things should cause the function to return `Err`:
|
|
|
|
|
|
|
|
|
|
- Not passing a drawer file
|
|
|
|
|
- Passing a subcommand other than `open` or `close`
|
|
|
|
|
- Passing any flags but `-f` or `-i`
|
|
|
|
|
- Not passing `-i` when the `$DRAWER_KEY` environment variable is undefined
|
|
|
|
|
|
2026-05-09 17:22:50 -05:00
|
|
|
### Phase 2
|
|
|
|
|
|
|
|
|
|
Phase 2 is about error handling. We want to add some variants to `DrawerError` and a `validate` method to `DrawerOperation`
|
|
|
|
|
that will catch various problems and ensure that we can do the operation correctly. Here are some error cases in no particular order:
|
|
|
|
|
|
|
|
|
|
- If the command is `Open` and the drawer file does not exist.
|
|
|
|
|
- If the command is `Close` and the target path either does not exist, is not a directory, or is not readable.
|
|
|
|
|
- If the `key` is `EnvVar` but the `$DRAWER_KEY` environment variable is not set, or is not set to a path that exists and is readable.
|
|
|
|
|
- If the `key` is `Path()` but the path does not exist or is not readable.
|
|
|
|
|
- If the command is `Open` and the target is a non-empty directory that exists, and the `force` flag is false
|
|
|
|
|
- If the command is `Close` and the target contains the drawer file (at any level; the target is any parent of the drawer file's path)
|
|
|
|
|
|
|
|
|
|
The `validate` method should take `&self` and return `Result<(), DrawerError>`. Let's have `main`, which is currently
|
|
|
|
|
creating a `DrawerOperation` and not using it, also call `validate` and print out the error if there is one. This means
|
|
|
|
|
we also need to `impl Display` on `DrawerError`.
|
|
|
|
|
|
2026-05-09 18:07:24 -05:00
|
|
|
### Phase 3
|
|
|
|
|
|
|
|
|
|
For phase 3 we want to start implementing the `close` command. We'll get the tar part working but not worry about encryption yet.
|
|
|
|
|
|
|
|
|
|
- Make a new method `perform` on `DrawerOperation` which takes `&self` and returns `Result<(), DrawerError>`.
|
|
|
|
|
- `perform` should first call `validate` and return the error if there is one.
|
|
|
|
|
- After that, it should call either `open(&self)` or `close(&self)` depending on the operation type. For now, `open` will just be a `todo!()`
|
|
|
|
|
- The new `close` method should create a vec<u8> consisting of the contents of the target directory as a tarball (use the tar create we depend on)
|
|
|
|
|
- After creating the vec, it should write it out to the drawer file path.
|
|
|
|
|
|
|
|
|
|
I'm going to add some test data to the project under `tests/` but it's just random data to test, ignore the contents of all those files.
|
|
|
|
|
|
|
|
|
|
### Phase 3a
|
|
|
|
|
|
|
|
|
|
As a minor improvement, make the `perform_close` method also gzip the contents of the tarball.
|
|
|
|
|
|
2026-05-10 18:27:55 -05:00
|
|
|
### Phase 4
|
|
|
|
|
|
|
|
|
|
For phase 4, add encryption. We'll have to add a new error type for the given key not being a valid key for `age`.
|
|
|
|
|
|
|
|
|
|
Now, after we create the tar but before we write it to disk, we need to encrypt the array, so that plaintext is never
|
|
|
|
|
written to disk.
|
|
|
|
|
|
2026-05-09 16:05:17 -05:00
|
|
|
## Commands
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cargo build # compile
|
|
|
|
|
cargo run # build and run
|
|
|
|
|
cargo test # run all tests
|
|
|
|
|
cargo test <name> # run a single test by name
|
|
|
|
|
cargo clippy # lint
|
|
|
|
|
cargo fmt # format
|
|
|
|
|
```
|