Skip to content

feat: nested org collections + org-aware personal folders#34

Merged
kjanat merged 5 commits into
masterfrom
issue-33
Jun 21, 2026
Merged

feat: nested org collections + org-aware personal folders#34
kjanat merged 5 commits into
masterfrom
issue-33

Conversation

@kjanat

@kjanat kjanat commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Closes #33.

Bitwarden has no "org folder" — the org-side equivalent of a folder is a collection, and nesting is just a /-naming convention. So an org import today double-files: items land in the org collection and a redundant personal-folder tree gets built on top.

What this does

  • --bitwarden-collection nested — derive collections from the full KeePass path (Work/Servers) instead of auto's top-level-only segment (Work). Bitwarden renders the / as nested collections.
  • --folder / --no-folder (KP2BW_CREATE_FOLDERS) — toggle personal-folder creation. Default is on, but off when --bitwarden-org is set, so org imports are collections-only unless --folder opts back in.

Both are opt-in and non-breaking for personal-vault imports. The only behavior change is org imports no longer also build a personal folder tree (the exact complaint in #33).

Idempotency

  • Items: unchanged — matched by the stable KP2BW_ID stamp (folder-independent).
  • Collections: matched by org + name, seeded from existing collections on connect, so an unchanged tree is a no-op and nested collections aren't duplicated across entries or re-runs.

Notes

  • Free Bitwarden cloud orgs cap at 2 collections; a large tree migrated as collections needs a paid plan. Vaultwarden self-hosted has no cap.
  • Nesting is name-based, so a KeePass group name containing / creates accidental nesting (pre-existing in folder mode too).

Tests

  • nested mode uses the full path; auto stays top-level only
  • --no-folder batch creates items with folderId=None and never calls create_folder
  • --bitwarden-org flips the personal-folder default off; no org keeps it on
  • KP2BW_CREATE_FOLDERS=0 env override

All script tests + ruff + ty + dprint clean.

kjanat added 2 commits June 17, 2026 21:57
Add nested organization collection mode and `--no-folder` support so
imports can target organization collections without creating personal
folders.

Closes #33.
org imports double-file (folderId + collectionIds); folders only duplicate the
collection tree in the personal vault, so default them off when an org is set.
--folder (BooleanOptionalAction) / KP2BW_CREATE_FOLDERS=1 restores them.

- cli: org-aware create_folders default; --no-folder -> --folder/--no-folder
- convert: note legacy-claim folder mismatch under --no-folder
- docs+test: org default-off behavior

ref #33
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Version 3.7.0 adds two related features to sort out the mess of personal folders versus collections. First, --bitwarden-collection nested mode now maps the full KeePass folder path hierarchy to nested Bitwarden collections (previously only auto, which used top-level folder names, was supported). Second, a new --folder/--no-folder flag (also KP2BW_CREATE_FOLDERS) controls whether personal-vault folders are created during import. Under --bitwarden-org, personal folder creation now defaults to off—users must pass --folder to restore the previous behaviour if they want it. The _resolve_collection method, Converter constructor, and BitwardenServeClient.create_items_batch are all updated accordingly, with new tests covering the no-folder mode, CLI env defaults, and nested collection resolution.


Sequence Diagram

sequenceDiagram
    participant User as User CLI
    participant Parser as Argument Parser
    participant Resolver as _resolve_bool_option
    participant Converter as Converter
    participant BatchClient as BitwardenServeClient
    
    User->>Parser: --bitwarden-org <org> [--folder]
    Parser->>Resolver: resolve create_folders (default: not bw_org)
    Resolver-->>Parser: create_folders boolean
    Parser->>Converter: Converter(..., create_folders=create_folders)
    Converter->>BatchClient: create_items_batch(..., create_folders=create_folders)
    alt create_folders = True
        BatchClient->>BatchClient: collect folder names
        BatchClient->>BatchClient: create folders in personal vault
        BatchClient->>BatchClient: bind folderId to items
    else create_folders = False
        BatchClient->>BatchClient: skip folder operations
        BatchClient->>BatchClient: bind folderId = None
    end
    BatchClient->>BatchClient: create items with collection IDs applied
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs

  • kjanat/kp2bw#25: Directly modifies the same BitwardenServeClient.create_items_batch method signature and batch-creation behaviour that this PR extends with the create_folders parameter.

Poem

⚓ Hoist the anchor, the personal folders be gone from yer vault, matey!
🗂️ Nested collections map the full hierarchy, ain't that state-ly?
--folder waves the flag when ye want yer tree restored,
But organisational imports sail free by default, gettin' items to the collection hoard!
No more double-filing into both collections AND folders—finally sorted! 🏴‍☠️✨

🚥 Pre-merge checks | ✅ 6 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Changelog Update ⚠️ Warning Version bumped to 3.7.0 but CHANGELOG.md entries placed under [Unreleased] instead of required [3.7.0] - YYYY-MM-DD section header. Move CHANGELOG entries from [Unreleased] to new [3.7.0] - YYYY-MM-DD section header matching pyproject.toml version.
Agents.Md Documentation Updated ⚠️ Warning PR modifies CLI interfaces (--folder/--no-folder flags), architecture (AUTO_COLLECTION_MODE, NESTED_COLLECTION_MODE constants, create_folders parameter), and behavior (BitwardenServeClient.create_i... Update src/kp2bw/AGENTS.md to document new collection modes (auto vs nested), folder creation toggle, --folder/--no-folder CLI flag, create_folders parameter, and org-import folder default behaviour.
✅ Passed checks (6 passed)
Check name Status Explanation
Description check ✅ Passed Description is well-detailed and directly related to the changeset, explaining the nested collections feature, folder toggling, and addressing the double-filing issue from #33.
Linked Issues check ✅ Passed Changes fully address #33: new --bitwarden-collection nested mode creates collections from full paths, --folder toggle prevents redundant personal folders in org imports, and collection matching prevents duplication across re-runs.
Out of Scope Changes check ✅ Passed All changes directly support the two core features: nested collections, folder toggling, environment variable handling, and comprehensive test coverage for both modes and defaults.
Docstring Coverage ✅ Passed Docstring coverage is 71.43% which is sufficient. The required threshold is 30.00%.
Semver Version Bump Validation ✅ Passed Source code modified (bw_serve.py, cli.py, convert.py) with backward-compatible additions: new optional parameters with defaults, new constants, new CLI flags. Version bumped 3.6.0→3.7.0 (MINOR) co...
Title check ✅ Passed Title follows conventional commit format with 'feat:' prefix, uses lowercase verb, is concise (57 chars), and accurately describes the dual features: nested collections and folder toggle.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown

📦 Test this PR (archived)

Status: ✅ Merged

This PR has been merged. You can still test the final state:

uvx -p 3.14 --from 'git+https://github.com/kjanat/kp2bw@3045bd7aa33c4ebc7f6e4cef83520580014fc4c8' kp2bw --help

📋 PR Details

Field Value
Final commit 3045bd7
Commit message feat: nested org collections + org-aware personal folders (#34)
Branch issue-33

Merge commit: 3045bd7

📋 Example usage
# Show version
uvx -p 3.14 --from 'git+https://github.com/kjanat/kp2bw@3045bd7aa33c4ebc7f6e4cef83520580014fc4c8' kp2bw --version
# Migrate KeePass DB (interactive password prompts)
uvx -p 3.14 --from 'git+https://github.com/kjanat/kp2bw@3045bd7aa33c4ebc7f6e4cef83520580014fc4c8' kp2bw vault.kdbx
# Non-interactive with env vars
KP2BW_KEEPASS_PASSWORD=kppass KP2BW_BITWARDEN_PASSWORD=bwpass uvx -p 3.14 --from 'git+https://github.com/kjanat/kp2bw@3045bd7aa33c4ebc7f6e4cef83520580014fc4c8' kp2bw vault.kdbx -y

🤖 Archived on merge

@kjanat kjanat self-assigned this Jun 21, 2026
@kjanat kjanat added enhancement New feature or request Feature documentation Improvements or additions to documentation labels Jun 21, 2026
@kjanat kjanat added the cr:review Allow CodeRabbit review label Jun 21, 2026
@coderabbitai coderabbitai Bot added the bug Something isn't working label Jun 21, 2026
coderabbitai[bot]

This comment was marked as resolved.

kjanat added 2 commits June 21, 2026 23:54
ty 0.0.51 widens an unannotated {**os.environ} after env.pop(), so
subprocess.Popen's overload stops resolving to Popen[bytes]. Annotate
env: dict[str, str] to keep it stable.

Also address CodeRabbit: _uris returns list[tuple[str, UriMatchValue]],
not object.
@socket-security

Copy link
Copy Markdown

@kjanat kjanat changed the title feat: nested org collections + org-aware personal folders (#33) feat: nested org collections + org-aware personal folders Jun 21, 2026
@kjanat kjanat merged commit 3045bd7 into master Jun 21, 2026
14 checks passed
@kjanat kjanat deleted the issue-33 branch June 21, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working cr:review Allow CodeRabbit review documentation Improvements or additions to documentation enhancement New feature or request Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Organisation and (Nested) Collections

1 participant