ldap-cli 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,246 @@
1
+ # Implementation Plan: ldap-cli
2
+
3
+ ## Overview
4
+
5
+ Implement `ldapc`, a Python CLI tool for querying LDAP directories. The implementation follows the workspace conventions (hatchling build, module-per-command, pytest testing) and builds incrementally: project scaffolding → config layer → LDAP client → formatter → CLI wiring → integration tests.
6
+
7
+ ## Tasks
8
+
9
+ - [x] 1. Set up project structure and scaffolding
10
+ - [x] 1.1 Create project directory and `pyproject.toml`
11
+ - Create `ldapc/` project root directory
12
+ - Create `pyproject.toml` with hatchling build backend, project metadata, dependencies (`ldap3`, `keyring`, `pyyaml`), dev dependencies (`pytest`, `pytest-cov`, `hypothesis`, `ruff`, `mypy`, `bandit`), and `[project.scripts]` entry for `ldapc = "ldapc.cli:main"`
13
+ - Create `ldapc/ldapc/__init__.py` with package-level imports
14
+ - Create `ldapc/ldapc/_version.py` with version extraction from CHANGELOG.md
15
+ - Create `ldapc/CHANGELOG.md` with initial `[Unreleased]` section and `[0.1.0]` entry
16
+ - Create `ldapc/README.md` with project description, installation, and usage
17
+ - Create `ldapc/LICENSE` with MIT license
18
+ - Create `ldapc/docs/README.md` as documentation index
19
+ - Create `ldapc/.gitignore` for Python projects
20
+ - _Requirements: 1.1, 1.3_
21
+
22
+ - [x] 1.2 Create exceptions module
23
+ - Create `ldapc/ldapc/exceptions.py` with `LdapcError`, `ConfigError`, `ConnectionError`, and `AuthenticationError` exception classes
24
+ - All exceptions inherit from `LdapcError`
25
+ - _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
26
+
27
+ - [x] 1.3 Create test scaffolding
28
+ - Create `ldapc/tests/__init__.py`
29
+ - Create `ldapc/tests/conftest.py` with shared fixtures (tmp config directory, mock keyring, mock LDAP server)
30
+ - Create `ldapc/pytest.ini` with coverage settings, smoke marker registration, and minimum 80% coverage threshold
31
+ - _Requirements: (testing infrastructure)_
32
+
33
+ - [x] 2. Implement configuration module
34
+ - [x] 2.1 Implement `LdapcConfig` dataclass and YAML serialization
35
+ - Create `ldapc/ldapc/config.py` with `LdapcConfig` dataclass (host, username, base_dn)
36
+ - Implement `config_to_yaml()` to serialize config to YAML string
37
+ - Implement `yaml_to_config()` to parse YAML string into config object, raising `ConfigError` on invalid input
38
+ - Implement `load_config()` to read from `~/.ldapc/config.yaml`, raising `ConfigError` if missing or invalid
39
+ - Implement `save_config()` to write config with 0600 permissions, creating `~/.ldapc/` directory if needed
40
+ - All functions must have type hints and Google-style docstrings
41
+ - _Requirements: 2.1, 2.2, 2.4, 2.6, 2.7, 3.1, 3.2, 3.3, 3.5_
42
+
43
+ - [x] 2.2 Write property test: Configuration round-trip (Property 1)
44
+ - **Property 1: Configuration round-trip**
45
+ - Generate random `LdapcConfig` objects with hypothesis `@st.composite`
46
+ - Assert `yaml_to_config(config_to_yaml(config))` produces equivalent object
47
+ - Tag: `# Feature: ldap-cli, Property 1: Configuration round-trip`
48
+ - Create `ldapc/tests/test_config_properties.py`
49
+ - **Validates: Requirements 2.4, 3.1, 3.3, 3.4**
50
+
51
+ - [x] 2.3 Write property test: Invalid YAML produces descriptive error (Property 2)
52
+ - **Property 2: Invalid YAML produces a descriptive error**
53
+ - Generate random non-YAML strings and YAML missing required fields
54
+ - Assert `yaml_to_config()` raises `ConfigError` with non-empty message
55
+ - Tag: `# Feature: ldap-cli, Property 2: Invalid YAML produces a descriptive error`
56
+ - Add to `ldapc/tests/test_config_properties.py`
57
+ - **Validates: Requirements 3.2**
58
+
59
+ - [x] 2.4 Implement keyring credential storage
60
+ - Implement `store_password(username, password)` using `keyring.set_password` with service name `ldapc`
61
+ - Implement `retrieve_password(username)` using `keyring.get_password`, raising `ConfigError` if not found
62
+ - _Requirements: 4.1, 4.2, 4.3, 4.4_
63
+
64
+ - [x] 2.5 Write property test: Password never written to disk (Property 3)
65
+ - **Property 3: Password is never written to disk**
66
+ - Generate random passwords, run `save_config`, assert password does not appear in any file under `~/.ldapc/`
67
+ - Tag: `# Feature: ldap-cli, Property 3: Password is never written to disk`
68
+ - Add to `ldapc/tests/test_config_properties.py`
69
+ - **Validates: Requirements 4.4**
70
+
71
+ - [x] 2.6 Write unit tests for configuration module
72
+ - Create `ldapc/tests/test_configure.py`
73
+ - Test: load_config with valid file, missing file, invalid YAML
74
+ - Test: save_config creates directory, sets permissions
75
+ - Test: store_password and retrieve_password with mocked keyring
76
+ - Test: ConfigError raised when keychain has no stored password
77
+ - _Requirements: 2.1, 2.2, 2.4, 2.6, 2.7, 3.1, 3.2, 3.5, 4.1, 4.2, 4.3_
78
+
79
+ - [x] 3. Checkpoint - Ensure configuration tests pass
80
+ - Ensure all tests pass, ask the user if questions arise.
81
+
82
+ - [x] 4. Implement LDAP client module
83
+ - [x] 4.1 Implement `LdapClient` class with connection management
84
+ - Create `ldapc/ldapc/ldap_client.py` with `LdapEntry` dataclass and `LdapClient` class
85
+ - Implement `__init__` to establish TLS connection with 10-second timeout
86
+ - Implement `search_users(search_term, base_dn)` to search by cn and uid
87
+ - Implement `search_groups(search_term, base_dn)` to search by cn
88
+ - Implement `close()` to unbind and close connection
89
+ - Handle connection errors, timeouts, TLS failures, and auth errors by raising appropriate exceptions
90
+ - Use context manager pattern (`__enter__`/`__exit__`) for automatic cleanup
91
+ - _Requirements: 5.1, 5.3, 5.4, 5.5, 6.1, 6.3, 6.4, 6.5, 8.1, 8.2, 8.3, 8.4_
92
+
93
+ - [x] 4.2 Write unit tests for LDAP client
94
+ - Create `ldapc/tests/test_ldap_client.py`
95
+ - Test: successful user search returns LdapEntry list (mocked ldap3)
96
+ - Test: successful group search returns LdapEntry list (mocked ldap3)
97
+ - Test: connection timeout raises ConnectionError
98
+ - Test: TLS failure raises ConnectionError
99
+ - Test: invalid credentials raises AuthenticationError
100
+ - Test: close() unbinds connection
101
+ - _Requirements: 5.1, 5.3, 5.4, 5.5, 6.1, 6.3, 6.4, 6.5, 8.1, 8.2, 8.3, 8.4_
102
+
103
+ - [x] 5. Implement formatter module
104
+ - [x] 5.1 Implement search filter builders and output formatters
105
+ - Create `ldapc/ldapc/formatter.py`
106
+ - Implement `build_user_filter(search_term)` to construct LDAP filter matching cn and uid
107
+ - Implement `build_group_filter(search_term)` to construct LDAP filter matching cn
108
+ - Implement `format_user_entries(entries)` for aligned key-value text output with delimiters between entries
109
+ - Implement `format_group_entries(entries)` for aligned key-value text output with delimiters between entries
110
+ - Implement `format_entries_json(entries)` for valid JSON output
111
+ - _Requirements: 5.1, 5.2, 6.1, 6.2, 7.1, 7.2, 7.3_
112
+
113
+ - [x] 5.2 Write property test: User search filter construction (Property 4)
114
+ - **Property 4: User search filter construction**
115
+ - Generate random non-empty search term strings
116
+ - Assert `build_user_filter(term)` produces filter containing both `cn` and `uid` references and the search term
117
+ - Tag: `# Feature: ldap-cli, Property 4: User search filter construction`
118
+ - Create `ldapc/tests/test_search_properties.py`
119
+ - **Validates: Requirements 5.1**
120
+
121
+ - [x] 5.3 Write property test: Group search filter construction (Property 5)
122
+ - **Property 5: Group search filter construction**
123
+ - Generate random non-empty search term strings
124
+ - Assert `build_group_filter(term)` produces filter containing `cn` reference and the search term
125
+ - Tag: `# Feature: ldap-cli, Property 5: Group search filter construction`
126
+ - Add to `ldapc/tests/test_search_properties.py`
127
+ - **Validates: Requirements 6.1**
128
+
129
+ - [x] 5.4 Write property test: Entry formatting contains all required fields (Property 6)
130
+ - **Property 6: Entry formatting contains all required fields**
131
+ - Generate random `LdapEntry` objects with required attributes
132
+ - Assert formatted output contains DN and all attribute values
133
+ - Tag: `# Feature: ldap-cli, Property 6: Entry formatting contains all required fields`
134
+ - Create `ldapc/tests/test_formatter_properties.py`
135
+ - **Validates: Requirements 5.2, 6.2, 7.1**
136
+
137
+ - [x] 5.5 Write property test: JSON output is valid and round-trips (Property 7)
138
+ - **Property 7: JSON output is valid and round-trips**
139
+ - Generate random lists of `LdapEntry` objects
140
+ - Assert `format_entries_json(entries)` produces valid JSON that deserializes to equivalent data
141
+ - Tag: `# Feature: ldap-cli, Property 7: JSON output is valid and round-trips`
142
+ - Add to `ldapc/tests/test_formatter_properties.py`
143
+ - **Validates: Requirements 7.3**
144
+
145
+ - [x] 5.6 Write unit tests for formatter module
146
+ - Create `ldapc/tests/test_formatter.py`
147
+ - Test: format_user_entries with single and multiple entries
148
+ - Test: format_group_entries with single and multiple entries
149
+ - Test: format_entries_json produces valid JSON
150
+ - Test: multiple entries separated by delimiter
151
+ - _Requirements: 5.2, 6.2, 7.1, 7.2, 7.3_
152
+
153
+ - [x] 6. Checkpoint - Ensure all module tests pass
154
+ - Ensure all tests pass, ask the user if questions arise.
155
+
156
+ - [x] 7. Implement CLI entry point and command wiring
157
+ - [x] 7.1 Implement argument parser and main entry point
158
+ - Create `ldapc/ldapc/cli.py` with `build_parser()` and `main()` functions
159
+ - Register `--configure`, `--user`, `--group`, `--version`, `--help`, `--json` flags
160
+ - Display help when invoked without arguments
161
+ - Map exception types to exit codes in `main()`: `ConfigError` → 2, `ConnectionError` → 1, `AuthenticationError` → 1
162
+ - Write errors to stderr, results to stdout
163
+ - _Requirements: 1.1, 1.2, 1.3, 1.4, 7.4, 9.1, 9.2, 9.3, 9.4, 9.5_
164
+
165
+ - [x] 7.2 Implement configure command flow
166
+ - In `cli.py`, implement the `--configure` handler that prompts for host, username, and password
167
+ - Use `getpass.getpass()` for password input
168
+ - Call `save_config()` and `store_password()` to persist settings
169
+ - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7_
170
+
171
+ - [x] 7.3 Implement user search command flow
172
+ - In `cli.py`, implement the `--user` handler
173
+ - Load config, retrieve password from keychain, create LdapClient, execute search, format and print results
174
+ - Handle no-results case with informative message
175
+ - Close LDAP connection in `finally` block
176
+ - Support `--json` flag for JSON output
177
+ - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 7.3, 8.2_
178
+
179
+ - [x] 7.4 Implement group search command flow
180
+ - In `cli.py`, implement the `--group` handler
181
+ - Load config, retrieve password from keychain, create LdapClient, execute search, format and print results
182
+ - Handle no-results case with informative message
183
+ - Close LDAP connection in `finally` block
184
+ - Support `--json` flag for JSON output
185
+ - _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 7.3, 8.2_
186
+
187
+ - [x] 7.5 Write unit tests for CLI module
188
+ - Create `ldapc/tests/test_cli.py`
189
+ - Test: `--help` displays help text and exits 0
190
+ - Test: `--version` displays version string and exits 0
191
+ - Test: no arguments displays help
192
+ - Test: `--user` with mocked LDAP returns formatted results
193
+ - Test: `--group` with mocked LDAP returns formatted results
194
+ - Test: `--json` flag produces JSON output
195
+ - Test: connection error exits with code 1
196
+ - Test: config error exits with code 2
197
+ - Test: errors written to stderr, results to stdout
198
+ - _Requirements: 1.1, 1.2, 1.3, 1.4, 7.4, 9.1, 9.2, 9.3, 9.4, 9.5_
199
+
200
+ - [x] 7.6 Write smoke tests
201
+ - Create `ldapc/tests/test_smoke.py`
202
+ - Mark all tests with `@pytest.mark.smoke`
203
+ - Test: `ldapc --help` exits 0
204
+ - Test: `ldapc --version` exits 0
205
+ - Test: `ldapc --user test` without config exits 2
206
+ - _Requirements: 1.1, 1.2, 1.3, 9.4, 9.5_
207
+
208
+ - [x] 8. Checkpoint - Ensure full test suite passes
209
+ - Ensure all tests pass, ask the user if questions arise.
210
+
211
+ - [x] 9. Integration tests and final wiring
212
+ - [x] 9.1 Write integration tests for keyring flow
213
+ - Create `ldapc/tests/test_keyring_integration.py`
214
+ - Test: full configure → store → retrieve password flow with mocked keyring backend
215
+ - Test: retrieve fails gracefully when no password stored
216
+ - _Requirements: 4.1, 4.2, 4.3_
217
+
218
+ - [x] 9.2 Write integration tests for LDAP connection lifecycle
219
+ - Create `ldapc/tests/test_ldap_integration.py`
220
+ - Test: connect → search → unbind lifecycle with mocked ldap3 server
221
+ - Test: connection is always closed even on error (finally block)
222
+ - _Requirements: 8.1, 8.2, 8.3, 8.4_
223
+
224
+ - [x] 9.3 Write exit code tests
225
+ - Create `ldapc/tests/test_exit_codes.py`
226
+ - Test: successful query exits 0
227
+ - Test: no results exits 0
228
+ - Test: connection error exits 1
229
+ - Test: auth error exits 1
230
+ - Test: config error exits 2
231
+ - Test: invalid argument exits 2
232
+ - _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
233
+
234
+ - [x] 10. Final checkpoint - Ensure all tests pass
235
+ - Ensure all tests pass, ask the user if questions arise.
236
+
237
+ ## Notes
238
+
239
+ - Tasks marked with `*` are optional and can be skipped for faster MVP
240
+ - Each task references specific requirements for traceability
241
+ - Checkpoints ensure incremental validation
242
+ - Property tests validate universal correctness properties from the design document
243
+ - Unit tests validate specific examples and edge cases
244
+ - The project follows workspace conventions: hatchling build, pytest, hypothesis for PBT
245
+ - Python 3.10+ with type hints on all functions and Google-style docstrings
246
+ - Virtual environment at `/Users/topazb/python/py314/`
CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2025-05-12
11
+
12
+ ### Added
13
+
14
+ - Subcommand-based CLI: `list`, `search`, `get`, `add`, `delete`, `user add group`, `user remove group`
15
+ - `ldapc list users` / `ldapc list groups` to list all entries
16
+ - `ldapc search user|group <term>` for case-insensitive substring search
17
+ - `ldapc get user|group <name>` for exact lookup
18
+ - `ldapc add user|group <name>` to create entries
19
+ - `ldapc delete user|group <name>` to remove entries
20
+ - `ldapc user add group <user> <group>` to add user to group
21
+ - `ldapc user remove group <user> <group>` to remove user from group
22
+ - `--yaml` flag for simplified YAML output (extracts cn from group DNs)
23
+ - `--skip-ssl-verify` can now be set via `LDAPC_SKIP_SSL_VERIFY` environment variable
24
+ - `--debug` flag for verbose logging output to stderr
25
+ - `--docs` flag to browse and view bundled documentation (rich text or markdown, with optional paging)
26
+ - All `.md` files (README, CHANGELOG, docs/) are now included in the wheel
27
+ - `rich` added as a runtime dependency for documentation rendering
28
+ - LDAP filter input escaping (RFC 4515) to prevent filter injection
29
+
30
+ ### Changed
31
+
32
+ - CLI switched from flat flags (`--user`, `--group`) to subcommand structure
33
+ - `--skip-ssl-verify` defaults to the value of `LDAPC_SKIP_SSL_VERIFY` env var (1/true/yes)
34
+ - LDAP search filters now include objectClass constraints for precision
35
+ - User/group container OUs are now configurable via `users_ou` and `groups_ou` in config
36
+ - Project description updated to reflect full management capabilities
37
+
38
+ ## [0.1.0] - 2025-01-01
39
+
40
+ ### Added
41
+
42
+ - Initial project scaffolding
43
+ - CLI entry point (`ldapc`)
44
+ - Configuration module with YAML persistence and keyring credential storage
45
+ - LDAP client module with user and group search
46
+ - Output formatter with text and JSON modes
47
+ - Property-based tests for correctness properties
README.md ADDED
@@ -0,0 +1,145 @@
1
+ # ldapc
2
+
3
+ A Python CLI tool for querying and managing LDAP directories. Provides a simple subcommand interface to list, search, get, add, and delete users and groups, with persistent configuration and secure credential storage via the OS keychain.
4
+
5
+ ## Features
6
+
7
+ - List all users or groups
8
+ - Search users by name/uid, groups by name (case-insensitive substring)
9
+ - Get detailed info on a specific user or group
10
+ - Add and delete users and groups
11
+ - Manage group membership (add/remove users from groups)
12
+ - Text, JSON, and YAML output formats
13
+ - Persistent configuration in `~/.ldapc/config.yaml`
14
+ - Secure credential storage via OS keychain
15
+ - TLS-secured connections with optional certificate verification skip
16
+ - Debug logging for troubleshooting
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # From PyPI
22
+ pip install ldap-cli
23
+
24
+ # From source (editable, with dev deps)
25
+ pip install -e ".[dev]"
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ # Configure (host, base DN, bind DN, password)
32
+ ldapc configure
33
+
34
+ # List all users
35
+ ldapc list users
36
+
37
+ # Search for a user
38
+ ldapc search user john
39
+
40
+ # Get user details in YAML
41
+ ldapc get user jdoe --yaml
42
+
43
+ # Skip SSL verification (self-signed certs)
44
+ ldapc list users --skip-ssl-verify
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### Configure
50
+
51
+ ```bash
52
+ ldapc configure
53
+ ```
54
+
55
+ Prompts for LDAP host URL, base DN, bind DN, and password. Config is stored in `~/.ldapc/config.yaml`; password goes to the OS keychain.
56
+
57
+ ### List
58
+
59
+ ```bash
60
+ ldapc list users # all users
61
+ ldapc list groups # all groups
62
+ ```
63
+
64
+ ### Search
65
+
66
+ ```bash
67
+ ldapc search user john # users matching "john" (case-insensitive)
68
+ ldapc search group admin # groups matching "admin"
69
+ ```
70
+
71
+ ### Get
72
+
73
+ ```bash
74
+ ldapc get user jdoe # exact user lookup
75
+ ldapc get group devs # exact group lookup
76
+ ```
77
+
78
+ ### Add / Delete
79
+
80
+ ```bash
81
+ ldapc add user newuser
82
+ ldapc add group newgroup
83
+ ldapc delete user olduser
84
+ ldapc delete group oldgroup
85
+ ```
86
+
87
+ ### Group Membership
88
+
89
+ ```bash
90
+ ldapc user add group jdoe developers # add user to group
91
+ ldapc user remove group jdoe developers # remove user from group
92
+ ```
93
+
94
+ ### Output Formats
95
+
96
+ ```bash
97
+ ldapc list users --json # JSON output
98
+ ldapc list users --yaml # YAML output (simplified, human-friendly)
99
+ ```
100
+
101
+ ### Global Flags
102
+
103
+ These flags can appear after any subcommand:
104
+
105
+ | Flag | Env Var | Description |
106
+ |------|---------|-------------|
107
+ | `--debug` | — | Enable debug logging to stderr |
108
+ | `--skip-ssl-verify` | `LDAPC_SKIP_SSL_VERIFY=1` | Skip TLS certificate verification |
109
+ | `--json` | — | Output as JSON |
110
+ | `--yaml` | — | Output as simplified YAML |
111
+
112
+ ### Help
113
+
114
+ ```bash
115
+ ldapc --help # top-level help
116
+ ldapc get --help # subcommand help
117
+ ldapc --version # show version
118
+ ```
119
+
120
+ ### Documentation
121
+
122
+ ```bash
123
+ ldapc --docs # list available doc files
124
+ ldapc --docs README.md # render README as rich text
125
+ ldapc --docs README.md --page # render with paging
126
+ ldapc --docs README.md --markdown # output raw markdown
127
+ ldapc --docs docs/README.md --page # view docs/README.md with paging
128
+ ```
129
+
130
+ ## Running Tests
131
+
132
+ ```bash
133
+ pip install -e ".[dev]"
134
+ python3 -m pytest tests/ -v
135
+ ```
136
+
137
+ With coverage:
138
+
139
+ ```bash
140
+ python3 -m pytest tests/ --cov=ldapc --cov-report=term-missing
141
+ ```
142
+
143
+ ## License
144
+
145
+ MIT — see [LICENSE](LICENSE) for details.
docs/reference.md ADDED
@@ -0,0 +1,198 @@
1
+ # ldapc Documentation
2
+
3
+ ## Contents
4
+
5
+ - [Commands](#commands) — Full command reference
6
+ - [Configuration](#configuration) — Config file and credential storage
7
+ - [Output Formats](#output-formats) — Text, JSON, and YAML
8
+ - [Environment Variables](#environment-variables) — Env var reference
9
+ - [Development](#development) — Contributing, testing, project conventions
10
+
11
+ ## Overview
12
+
13
+ `ldapc` is a Python CLI tool for querying and managing LDAP directories. It uses a subcommand structure for all operations.
14
+
15
+ ## Commands
16
+
17
+ ### configure
18
+
19
+ ```bash
20
+ ldapc configure
21
+ ```
22
+
23
+ Interactive setup. Prompts for:
24
+ - **LDAP host URL** — e.g. `ldaps://ldap.example.com:636`
25
+ - **Base DN** — e.g. `dc=ldap,dc=example,dc=com`
26
+ - **Bind DN** — e.g. `uid=admin,cn=users,dc=ldap,dc=example,dc=com`
27
+ - **Bind password** — stored in OS keychain
28
+
29
+ Config saved to `~/.ldapc/config.yaml` (mode 0600).
30
+
31
+ ### list
32
+
33
+ ```bash
34
+ ldapc list users # list all users
35
+ ldapc list groups # list all groups
36
+ ```
37
+
38
+ ### search
39
+
40
+ ```bash
41
+ ldapc search user <term> # case-insensitive substring match on cn/uid
42
+ ldapc search group <term> # case-insensitive substring match on cn
43
+ ```
44
+
45
+ ### get
46
+
47
+ ```bash
48
+ ldapc get user <name> # exact match on uid or cn
49
+ ldapc get group <name> # exact match on cn
50
+ ```
51
+
52
+ Returns exit code 1 if not found.
53
+
54
+ ### add
55
+
56
+ ```bash
57
+ ldapc add user <name> # creates inetOrgPerson under ou=users
58
+ ldapc add group <name> # creates posixGroup under ou=groups
59
+ ```
60
+
61
+ ### delete
62
+
63
+ ```bash
64
+ ldapc delete user <name>
65
+ ldapc delete group <name>
66
+ ```
67
+
68
+ ### user (group membership)
69
+
70
+ ```bash
71
+ ldapc user add group <user> <group> # add user to group
72
+ ldapc user remove group <user> <group> # remove user from group
73
+ ```
74
+
75
+ ## Output Formats
76
+
77
+ ### Default (text)
78
+
79
+ Aligned key-value format:
80
+
81
+ ```
82
+ DN : uid=jdoe,ou=users,dc=example,dc=com
83
+ CN : John Doe
84
+ Email : john@example.com
85
+ Groups : cn=developers,ou=groups,dc=example,dc=com
86
+ ```
87
+
88
+ ### JSON (`--json`)
89
+
90
+ ```json
91
+ [
92
+ {
93
+ "dn": "uid=jdoe,ou=users,dc=example,dc=com",
94
+ "attributes": {
95
+ "cn": ["John Doe"],
96
+ "mail": ["john@example.com"],
97
+ "memberOf": ["cn=developers,ou=groups,dc=example,dc=com"]
98
+ }
99
+ }
100
+ ]
101
+ ```
102
+
103
+ ### YAML (`--yaml`)
104
+
105
+ Simplified, human-friendly format. Group DNs are reduced to just the cn value:
106
+
107
+ ```yaml
108
+ ---
109
+ cn: John Doe
110
+ email: john@example.com
111
+ groups:
112
+ - developers
113
+ ```
114
+
115
+ ## Global Flags
116
+
117
+ These flags work with any subcommand and can appear anywhere after the subcommand name:
118
+
119
+ | Flag | Description |
120
+ |------|-------------|
121
+ | `--debug` | Enable debug logging (connection details, search filters, result counts) to stderr |
122
+ | `--skip-ssl-verify` | Skip TLS certificate verification |
123
+ | `--json` | Output results as JSON |
124
+ | `--yaml` | Output results as simplified YAML |
125
+
126
+ ## Environment Variables
127
+
128
+ | Variable | Description |
129
+ |----------|-------------|
130
+ | `LDAPC_SKIP_SSL_VERIFY` | Set to `1`, `true`, or `yes` to skip TLS cert verification by default |
131
+
132
+ ## Configuration
133
+
134
+ ### File location
135
+
136
+ `~/.ldapc/config.yaml` (created by `ldapc configure`)
137
+
138
+ ### Format
139
+
140
+ ```yaml
141
+ host: ldaps://ldap.example.com:636
142
+ username: uid=admin,cn=users,dc=ldap,dc=example,dc=com
143
+ base_dn: dc=ldap,dc=example,dc=com
144
+ ```
145
+
146
+ ### Credentials
147
+
148
+ Password is stored in the OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service) under service name `ldapc`.
149
+
150
+ ## Installation
151
+
152
+ ```bash
153
+ # From PyPI
154
+ pip install ldap-cli
155
+
156
+ # From source (editable, with dev deps)
157
+ pip install -e ".[dev]"
158
+ ```
159
+
160
+ ## Development
161
+
162
+ ### Running tests
163
+
164
+ ```bash
165
+ pip install -e ".[dev]"
166
+ python3 -m pytest tests/ -v
167
+ ```
168
+
169
+ ### Coverage
170
+
171
+ ```bash
172
+ python3 -m pytest tests/ --cov=ldapc --cov-report=term-missing
173
+ ```
174
+
175
+ ### Project structure
176
+
177
+ ```
178
+ ldapc/
179
+ __init__.py # Package init, exports __version__
180
+ _version.py # Version extraction from CHANGELOG.md
181
+ cli.py # CLI entry point with subcommand parser
182
+ config.py # Config file and keyring management
183
+ ldap_client.py # LdapClient class (connect, search, CRUD)
184
+ formatter.py # Output formatting (text, JSON, YAML)
185
+ exceptions.py # Exception hierarchy
186
+ tests/ # Unit tests (pytest + hypothesis)
187
+ docs/ # This documentation
188
+ ```
189
+
190
+ ### Adding a new subcommand
191
+
192
+ 1. Add the subparser in `build_parser()` in `cli.py`
193
+ 2. Create a `_handle_<command>()` function
194
+ 3. Add dispatch in `main()`
195
+ 4. Add corresponding method(s) to `LdapClient` if needed
196
+ 5. Add tests in `tests/test_cli.py`
197
+ 6. Update this documentation
198
+ 7. Add changelog entry under `[Unreleased]`