kcfinder-client 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. kcfinder_client-0.2.0/.github/workflows/ci.yml +34 -0
  2. kcfinder_client-0.2.0/.github/workflows/publish.yml +42 -0
  3. kcfinder_client-0.2.0/.gitignore +15 -0
  4. kcfinder_client-0.2.0/.markdownlint.rb +8 -0
  5. kcfinder_client-0.2.0/CLAUDE.md +146 -0
  6. kcfinder_client-0.2.0/LICENSE +21 -0
  7. kcfinder_client-0.2.0/PKG-INFO +175 -0
  8. kcfinder_client-0.2.0/README.md +154 -0
  9. kcfinder_client-0.2.0/docs/auth.md +178 -0
  10. kcfinder_client-0.2.0/docs/bulk.md +130 -0
  11. kcfinder_client-0.2.0/docs/directories.md +172 -0
  12. kcfinder_client-0.2.0/docs/exceptions.md +223 -0
  13. kcfinder_client-0.2.0/docs/files.md +171 -0
  14. kcfinder_client-0.2.0/docs/sync.md +132 -0
  15. kcfinder_client-0.2.0/examples/async_usage.py +42 -0
  16. kcfinder_client-0.2.0/examples/sync_usage.py +64 -0
  17. kcfinder_client-0.2.0/live.py +459 -0
  18. kcfinder_client-0.2.0/pyproject.toml +60 -0
  19. kcfinder_client-0.2.0/src/kcfinder_client/__init__.py +42 -0
  20. kcfinder_client-0.2.0/src/kcfinder_client/_core.py +177 -0
  21. kcfinder_client-0.2.0/src/kcfinder_client/async_client.py +214 -0
  22. kcfinder_client-0.2.0/src/kcfinder_client/auth.py +160 -0
  23. kcfinder_client-0.2.0/src/kcfinder_client/client.py +212 -0
  24. kcfinder_client-0.2.0/src/kcfinder_client/exceptions.py +96 -0
  25. kcfinder_client-0.2.0/src/kcfinder_client/models.py +36 -0
  26. kcfinder_client-0.2.0/src/kcfinder_client/sync.py +93 -0
  27. kcfinder_client-0.2.0/tasks.py +73 -0
  28. kcfinder_client-0.2.0/tests/__init__.py +0 -0
  29. kcfinder_client-0.2.0/tests/conftest.py +13 -0
  30. kcfinder_client-0.2.0/tests/test_async_client.py +326 -0
  31. kcfinder_client-0.2.0/tests/test_auth.py +186 -0
  32. kcfinder_client-0.2.0/tests/test_client.py +203 -0
  33. kcfinder_client-0.2.0/tests/test_core.py +225 -0
  34. kcfinder_client-0.2.0/tests/test_exceptions.py +133 -0
  35. kcfinder_client-0.2.0/tests/test_models.py +55 -0
  36. kcfinder_client-0.2.0/tests/test_sync.py +266 -0
  37. kcfinder_client-0.2.0/uv.lock +639 -0
@@ -0,0 +1,34 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ check:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v6
17
+
18
+ - name: Set up Python
19
+ run: uv python install 3.14
20
+
21
+ - name: Install dependencies
22
+ run: uv sync
23
+
24
+ - name: Lint
25
+ run: uv run ruff check .
26
+
27
+ - name: Format check
28
+ run: uv run ruff format --check .
29
+
30
+ - name: Typecheck
31
+ run: uv run pyrefly check
32
+
33
+ - name: Test
34
+ run: uv run pytest -v
@@ -0,0 +1,42 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v6
19
+
20
+ - name: Set up Python
21
+ run: uv python install 3.14
22
+
23
+ - name: Install dependencies
24
+ run: uv sync
25
+
26
+ - name: Lint
27
+ run: uv run ruff check .
28
+
29
+ - name: Format check
30
+ run: uv run ruff format --check .
31
+
32
+ - name: Typecheck
33
+ run: uv run pyrefly check
34
+
35
+ - name: Test
36
+ run: uv run pytest -v
37
+
38
+ - name: Build
39
+ run: uv build
40
+
41
+ - name: Publish to PyPI
42
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ *.egg-info/
5
+ .venv/
6
+ .pyrefly/
7
+ .ruff_cache/
8
+ *.whl
9
+ *.tar.gz
10
+ .pytest_cache/
11
+ .coverage
12
+ .idea
13
+ htmlcov/
14
+ docs/superpowers/
15
+ .env
@@ -0,0 +1,8 @@
1
+ # https://github.com/markdownlint/markdownlint/blob/main/docs/creating_styles.md
2
+ # https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md
3
+ all
4
+ rule 'MD007', :indent => 3
5
+ rule 'MD013', :ignore_code_blocks => true, :tables => false
6
+ rule 'MD029', :style => :ordered
7
+ exclude_rule 'MD033'
8
+ exclude_rule 'MD034'
@@ -0,0 +1,146 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with
4
+ code in this repository.
5
+
6
+ ## Project Overview
7
+
8
+ A standalone Python client library for KCFinder, a PHP web file manager.
9
+ Wraps KCFinder's HTTP action-dispatch API (`browse.php?act=<action>`) into a
10
+ clean Python interface with both sync and async clients. Designed to work with
11
+ any KCFinder instance, with built-in support for the HarmonySite auth
12
+ extension.
13
+
14
+ ## Build & Development
15
+
16
+ Python package managed with `uv` and `pyproject.toml`. Requires Python 3.14+.
17
+
18
+ ```bash
19
+ uv sync # Install dependencies
20
+ uv run pytest # Run all tests
21
+ uv run pytest tests/test_client.py::test_list_files # Run a single test
22
+ uv run pytest -x # Stop on first failure
23
+ ```
24
+
25
+ ### Invoke Tasks
26
+
27
+ ```bash
28
+ inv test # Run pytest
29
+ inv lint # Run ruff linter
30
+ inv format # Run ruff formatter
31
+ inv tc # Run pyrefly type checker
32
+ inv mdlint # Run markdownlint on docs (requires Docker)
33
+ inv check # Lint + format + typecheck + mdlint (run this frequently)
34
+ inv scan # Security scan (bandit + pip-audit)
35
+ inv build # Build package with uv
36
+ ```
37
+
38
+ ### Live Testing
39
+
40
+ Atomic live tests for every client method against a real KCFinder server.
41
+ Requires env vars (see `live.py` for details). Credentials are loaded via
42
+ `python-dotenv` from a `.env` file.
43
+
44
+ ```bash
45
+ inv live.auth # Authenticate, confirm session
46
+ inv live.list # List files in root (--dir for subdirs)
47
+ inv live.tree # Root directory node with immediate subdirs
48
+ inv live.expand # List subdirectories (--dir for subdirs)
49
+ inv live.upload # Upload a test JPEG
50
+ inv live.download # Upload, download, verify bytes match
51
+ inv live.thumb # Upload image, get thumbnail
52
+ inv live.rename # Upload, rename, verify
53
+ inv live.delete # Upload, delete, verify gone
54
+ inv live.mkdir # Create test directory
55
+ inv live.list-dir # Create dir with file, list contents
56
+ inv live.rename-dir # Create dir, rename, verify
57
+ inv live.delete-dir # Create dir, delete, verify gone
58
+ inv live.copy # Upload, copy to dest dir
59
+ inv live.move # Upload, move to dest dir
60
+ inv live.bulk-delete # Upload 2 files, bulk delete
61
+ inv live.download-dir # Download dir as ZIP
62
+ inv live.download-sel # Download selected files as ZIP
63
+ inv live.cleanup # Remove all _test_* artifacts
64
+ inv live.all # Run all tests in order
65
+ ```
66
+
67
+ ## Package Structure
68
+
69
+ ```text
70
+ src/kcfinder_client/
71
+ ├── __init__.py # Public exports
72
+ ├── _core.py # Shared request building, response parsing, error checking
73
+ ├── async_client.py # AsyncKCFinderClient (httpx.AsyncClient)
74
+ ├── client.py # KCFinderClient (httpx.Client, sync)
75
+ ├── auth.py # BaseAuth, HarmonySiteAuth, SessionAuth, harmonysite_auth_from_env
76
+ ├── models.py # FileInfo, DirTree, SyncResult dataclasses
77
+ ├── exceptions.py # KCFinderError hierarchy
78
+ └── sync.py # SyncManager, SyncManagerSync (one-way push)
79
+
80
+ live.py # Live test tasks (inv live.*)
81
+ tasks.py # Invoke task definitions + live namespace registration
82
+ ```
83
+
84
+ ## Architecture
85
+
86
+ ### Dual Client Pattern
87
+
88
+ Two client classes share core logic via private helpers in `_core.py`:
89
+
90
+ - **`AsyncKCFinderClient`** — async methods, uses `httpx.AsyncClient`
91
+ - **`KCFinderClient`** — sync methods, uses `httpx.Client`
92
+
93
+ Both are context managers that handle auth and cleanup. All request building,
94
+ response parsing, and error detection lives in `_core.py`.
95
+
96
+ ### KCFinder Protocol
97
+
98
+ All operations POST to `browse.php?act=<action>` with form data (`dir`,
99
+ `file`, `files[]`). Key protocol details:
100
+
101
+ - **Session-based auth**: `PHPSESSID` cookie from login step
102
+ - **Required headers**: `Referer` (full browse URL) and
103
+ `X-Requested-With: XMLHttpRequest`
104
+ - **Auth query params**: HarmonySite's fork requires `bros_config` and
105
+ `brosseccheck` as query params on every request, not just the session init.
106
+ This is handled by `BaseAuth.get_query_params()`.
107
+ - **Dir param type prefix**: All `dir` params must include the file type
108
+ prefix (e.g., `images/subfolder`). The client handles this internally via
109
+ `prefix_dir()` — callers pass bare paths like `"subfolder"`.
110
+ - **Error handling**: KCFinder returns HTTP 200 even on errors — response body
111
+ must be inspected. Success is `{}` for mutating actions, JSON for queries.
112
+ Bulk action errors are arrays joined with `"; "`.
113
+ - **Thumbnail via GET**: `act=thumb` uses GET query params (dir, file), not
114
+ POST form data.
115
+ - **File scoping**: The `type=images` param scopes to `images/` under
116
+ `uploadDir` — this is the filesystem ceiling
117
+
118
+ ### Auth Strategy Pattern
119
+
120
+ Auth is pluggable via `BaseAuth` subclasses. Each strategy implements
121
+ `authenticate(session)`, `authenticate_sync(session)`, `get_referer()`, and
122
+ optionally `get_query_params()`.
123
+
124
+ - **`HarmonySiteAuth`**: Logs in via `dbaction.php`, then initializes KCFinder
125
+ session with `bros_config`/`brosseccheck` query params. These params are
126
+ also sent on every subsequent request via `get_query_params()`.
127
+ - **`SessionAuth`**: For standard KCFinder installs where the session is
128
+ pre-established
129
+ - **`harmonysite_auth_from_env()`**: Factory that reads credentials from
130
+ environment variables
131
+
132
+ ### SyncManager
133
+
134
+ One-way push sync on top of the client. Compares local vs remote files by
135
+ name + size. Supports dry run. Available in both async (`SyncManager`) and
136
+ sync (`SyncManagerSync`) variants.
137
+
138
+ ## Design Principles
139
+
140
+ - Dual sync/async clients sharing logic via `_core.py` (same pattern as
141
+ anthropic/openai SDKs)
142
+ - Auth classes take explicit args — no credential sourcing; consumers handle
143
+ that
144
+ - `harmonysite_auth_from_env()` factory for deployment contexts (n8n, CI)
145
+ - Primary consumer is the `witnessmusic` project via local path dependency
146
+ (`uv` source)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cyberops7
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: kcfinder-client
3
+ Version: 0.2.0
4
+ Summary: Python client library for KCFinder file manager
5
+ Project-URL: Changelog, https://github.com/cyberops7/kcfinder-client/releases
6
+ Project-URL: Issues, https://github.com/cyberops7/kcfinder-client/issues
7
+ Project-URL: Repository, https://github.com/cyberops7/kcfinder-client
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: file-manager,httpx,kcfinder,php
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Internet :: WWW/HTTP
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.14
19
+ Requires-Dist: httpx>=0.28
20
+ Description-Content-Type: text/markdown
21
+
22
+ # kcfinder-client
23
+
24
+ [![CI](https://github.com/cyberops7/kcfinder-client/actions/workflows/ci.yml/badge.svg)](https://github.com/cyberops7/kcfinder-client/actions/workflows/ci.yml)
25
+ ![Python 3.14+](https://img.shields.io/badge/python-3.14+-blue.svg)
26
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
27
+
28
+ A Python client library for [KCFinder](https://github.com/sunhater/kcfinder),
29
+ a PHP web file manager. Wraps KCFinder's HTTP action-dispatch API into a clean
30
+ Python interface with both async and sync clients.
31
+
32
+ > [!NOTE]
33
+ > Designed for and tested against HarmonySite's KCFinder fork.
34
+ > May work with other KCFinder instances via `SessionAuth`, but this
35
+ > has not been tested.
36
+
37
+ ## Table of Contents
38
+
39
+ - [Install](#install)
40
+ - [Quick Start](#quick-start)
41
+ - [Auth from Environment Variables](#auth-from-environment-variables)
42
+ - [Sync Files with SyncManager](#sync-files-with-syncmanager)
43
+ - [Documentation](#documentation)
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ # uv
49
+ uv add kcfinder-client
50
+
51
+ # pip
52
+ pip install kcfinder-client
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ### Async (recommended)
58
+
59
+ ```python
60
+ import asyncio
61
+ import os
62
+ from kcfinder_client import AsyncKCFinderClient, HarmonySiteAuth
63
+
64
+ auth = HarmonySiteAuth(
65
+ login_url="https://example.harmonysong.com/dbaction.php",
66
+ browse_url="https://example.harmonysong.com/kcfinder/browse.php",
67
+ username="admin",
68
+ password="secret",
69
+ bros_config=(
70
+ 'a:3:{s:9:"uploadDir";s:6:"files/";'
71
+ 's:9:"thumbsDir";s:14:"files/.thumbs/";'
72
+ 's:9:"uploadURL";s:0:"";}'
73
+ ),
74
+ )
75
+
76
+ async def main():
77
+ browse_url = "https://example.harmonysong.com/kcfinder/browse.php"
78
+ async with AsyncKCFinderClient(browse_url, auth) as client:
79
+ files = await client.list_files("banners")
80
+ for f in files:
81
+ print(f.name, f.size)
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ ### Sync
87
+
88
+ ```python
89
+ from kcfinder_client import KCFinderClient, HarmonySiteAuth
90
+
91
+ auth = HarmonySiteAuth(
92
+ login_url="https://example.harmonysong.com/dbaction.php",
93
+ browse_url="https://example.harmonysong.com/kcfinder/browse.php",
94
+ username="admin",
95
+ password="secret",
96
+ bros_config=(
97
+ 'a:3:{s:9:"uploadDir";s:6:"files/";'
98
+ 's:9:"thumbsDir";s:14:"files/.thumbs/";'
99
+ 's:9:"uploadURL";s:0:"";}'
100
+ ),
101
+ )
102
+
103
+ browse_url = "https://example.harmonysong.com/kcfinder/browse.php"
104
+ with KCFinderClient(browse_url, auth) as client:
105
+ files = client.list_files("banners")
106
+ for f in files:
107
+ print(f.name, f.size)
108
+ ```
109
+
110
+ ## Auth from Environment Variables
111
+
112
+ Load credentials from the environment rather than hardcoding them:
113
+
114
+ ```bash
115
+ export KCFINDER_LOGIN_URL=https://example.harmonysong.com/dbaction.php
116
+ export KCFINDER_BROWSE_URL=https://example.harmonysong.com/kcfinder/browse.php
117
+ export KCFINDER_USERNAME=admin
118
+ export KCFINDER_PASSWORD=secret
119
+ export KCFINDER_BROS_CONFIG='a:3:{s:9:"uploadDir";s:6:"files/";s:9:"thumbsDir";s:14:"files/.thumbs/";s:9:"uploadURL";s:0:"";}'
120
+ ```
121
+
122
+ ```python
123
+ import os
124
+ from kcfinder_client import AsyncKCFinderClient, harmonysite_auth_from_env
125
+ import asyncio
126
+
127
+ async def main():
128
+ auth = harmonysite_auth_from_env()
129
+ browse_url = os.environ["KCFINDER_BROWSE_URL"]
130
+ async with AsyncKCFinderClient(browse_url, auth) as client:
131
+ files = await client.list_files()
132
+ print(f"Found {len(files)} files")
133
+
134
+ asyncio.run(main())
135
+ ```
136
+
137
+ ## Sync Files with SyncManager
138
+
139
+ One-way push sync to make a remote directory match a local one:
140
+
141
+ ```python
142
+ import os
143
+ from pathlib import Path
144
+ from kcfinder_client import KCFinderClient, SyncManagerSync, harmonysite_auth_from_env
145
+
146
+ auth = harmonysite_auth_from_env()
147
+ browse_url = os.environ["KCFINDER_BROWSE_URL"]
148
+
149
+ with KCFinderClient(browse_url, auth) as client:
150
+ sync = SyncManagerSync(client)
151
+
152
+ # Dry run first to preview changes
153
+ result = sync.push("banners", Path("./banners"), dry_run=True)
154
+ print(f"Would upload: {result.uploaded}")
155
+ print(f"Would delete: {result.deleted}")
156
+ print(f"Would skip: {result.skipped}")
157
+
158
+ # Apply the sync
159
+ result = sync.push("banners", Path("./banners"))
160
+ ```
161
+
162
+ ## Documentation
163
+
164
+ - [Auth setup](docs/auth.md) — HarmonySiteAuth, SessionAuth, env vars
165
+ - [File operations](docs/files.md) — list, upload, download, delete, rename,
166
+ thumbnails
167
+ - [Directory operations](docs/directories.md) — tree, expand, create, rename,
168
+ delete
169
+ - [Bulk operations](docs/bulk.md) — copy, move, bulk delete, download selected
170
+ - [Sync manager](docs/sync.md) — push sync, dry run, SyncResult
171
+ - [Exceptions](docs/exceptions.md) — error handling, exception hierarchy
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,154 @@
1
+ # kcfinder-client
2
+
3
+ [![CI](https://github.com/cyberops7/kcfinder-client/actions/workflows/ci.yml/badge.svg)](https://github.com/cyberops7/kcfinder-client/actions/workflows/ci.yml)
4
+ ![Python 3.14+](https://img.shields.io/badge/python-3.14+-blue.svg)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A Python client library for [KCFinder](https://github.com/sunhater/kcfinder),
8
+ a PHP web file manager. Wraps KCFinder's HTTP action-dispatch API into a clean
9
+ Python interface with both async and sync clients.
10
+
11
+ > [!NOTE]
12
+ > Designed for and tested against HarmonySite's KCFinder fork.
13
+ > May work with other KCFinder instances via `SessionAuth`, but this
14
+ > has not been tested.
15
+
16
+ ## Table of Contents
17
+
18
+ - [Install](#install)
19
+ - [Quick Start](#quick-start)
20
+ - [Auth from Environment Variables](#auth-from-environment-variables)
21
+ - [Sync Files with SyncManager](#sync-files-with-syncmanager)
22
+ - [Documentation](#documentation)
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ # uv
28
+ uv add kcfinder-client
29
+
30
+ # pip
31
+ pip install kcfinder-client
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### Async (recommended)
37
+
38
+ ```python
39
+ import asyncio
40
+ import os
41
+ from kcfinder_client import AsyncKCFinderClient, HarmonySiteAuth
42
+
43
+ auth = HarmonySiteAuth(
44
+ login_url="https://example.harmonysong.com/dbaction.php",
45
+ browse_url="https://example.harmonysong.com/kcfinder/browse.php",
46
+ username="admin",
47
+ password="secret",
48
+ bros_config=(
49
+ 'a:3:{s:9:"uploadDir";s:6:"files/";'
50
+ 's:9:"thumbsDir";s:14:"files/.thumbs/";'
51
+ 's:9:"uploadURL";s:0:"";}'
52
+ ),
53
+ )
54
+
55
+ async def main():
56
+ browse_url = "https://example.harmonysong.com/kcfinder/browse.php"
57
+ async with AsyncKCFinderClient(browse_url, auth) as client:
58
+ files = await client.list_files("banners")
59
+ for f in files:
60
+ print(f.name, f.size)
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ ### Sync
66
+
67
+ ```python
68
+ from kcfinder_client import KCFinderClient, HarmonySiteAuth
69
+
70
+ auth = HarmonySiteAuth(
71
+ login_url="https://example.harmonysong.com/dbaction.php",
72
+ browse_url="https://example.harmonysong.com/kcfinder/browse.php",
73
+ username="admin",
74
+ password="secret",
75
+ bros_config=(
76
+ 'a:3:{s:9:"uploadDir";s:6:"files/";'
77
+ 's:9:"thumbsDir";s:14:"files/.thumbs/";'
78
+ 's:9:"uploadURL";s:0:"";}'
79
+ ),
80
+ )
81
+
82
+ browse_url = "https://example.harmonysong.com/kcfinder/browse.php"
83
+ with KCFinderClient(browse_url, auth) as client:
84
+ files = client.list_files("banners")
85
+ for f in files:
86
+ print(f.name, f.size)
87
+ ```
88
+
89
+ ## Auth from Environment Variables
90
+
91
+ Load credentials from the environment rather than hardcoding them:
92
+
93
+ ```bash
94
+ export KCFINDER_LOGIN_URL=https://example.harmonysong.com/dbaction.php
95
+ export KCFINDER_BROWSE_URL=https://example.harmonysong.com/kcfinder/browse.php
96
+ export KCFINDER_USERNAME=admin
97
+ export KCFINDER_PASSWORD=secret
98
+ export KCFINDER_BROS_CONFIG='a:3:{s:9:"uploadDir";s:6:"files/";s:9:"thumbsDir";s:14:"files/.thumbs/";s:9:"uploadURL";s:0:"";}'
99
+ ```
100
+
101
+ ```python
102
+ import os
103
+ from kcfinder_client import AsyncKCFinderClient, harmonysite_auth_from_env
104
+ import asyncio
105
+
106
+ async def main():
107
+ auth = harmonysite_auth_from_env()
108
+ browse_url = os.environ["KCFINDER_BROWSE_URL"]
109
+ async with AsyncKCFinderClient(browse_url, auth) as client:
110
+ files = await client.list_files()
111
+ print(f"Found {len(files)} files")
112
+
113
+ asyncio.run(main())
114
+ ```
115
+
116
+ ## Sync Files with SyncManager
117
+
118
+ One-way push sync to make a remote directory match a local one:
119
+
120
+ ```python
121
+ import os
122
+ from pathlib import Path
123
+ from kcfinder_client import KCFinderClient, SyncManagerSync, harmonysite_auth_from_env
124
+
125
+ auth = harmonysite_auth_from_env()
126
+ browse_url = os.environ["KCFINDER_BROWSE_URL"]
127
+
128
+ with KCFinderClient(browse_url, auth) as client:
129
+ sync = SyncManagerSync(client)
130
+
131
+ # Dry run first to preview changes
132
+ result = sync.push("banners", Path("./banners"), dry_run=True)
133
+ print(f"Would upload: {result.uploaded}")
134
+ print(f"Would delete: {result.deleted}")
135
+ print(f"Would skip: {result.skipped}")
136
+
137
+ # Apply the sync
138
+ result = sync.push("banners", Path("./banners"))
139
+ ```
140
+
141
+ ## Documentation
142
+
143
+ - [Auth setup](docs/auth.md) — HarmonySiteAuth, SessionAuth, env vars
144
+ - [File operations](docs/files.md) — list, upload, download, delete, rename,
145
+ thumbnails
146
+ - [Directory operations](docs/directories.md) — tree, expand, create, rename,
147
+ delete
148
+ - [Bulk operations](docs/bulk.md) — copy, move, bulk delete, download selected
149
+ - [Sync manager](docs/sync.md) — push sync, dry run, SyncResult
150
+ - [Exceptions](docs/exceptions.md) — error handling, exception hierarchy
151
+
152
+ ## License
153
+
154
+ MIT