projectmemory-cli 0.1.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 (38) hide show
  1. projectmemory_cli-0.1.0/PKG-INFO +239 -0
  2. projectmemory_cli-0.1.0/README.md +216 -0
  3. projectmemory_cli-0.1.0/projectmemory_cli/__init__.py +3 -0
  4. projectmemory_cli-0.1.0/projectmemory_cli/api/__init__.py +1 -0
  5. projectmemory_cli-0.1.0/projectmemory_cli/api/auth.py +94 -0
  6. projectmemory_cli-0.1.0/projectmemory_cli/api/client.py +135 -0
  7. projectmemory_cli-0.1.0/projectmemory_cli/api/errors.py +124 -0
  8. projectmemory_cli-0.1.0/projectmemory_cli/api/sessions.py +25 -0
  9. projectmemory_cli-0.1.0/projectmemory_cli/app.py +80 -0
  10. projectmemory_cli-0.1.0/projectmemory_cli/commands/__init__.py +1 -0
  11. projectmemory_cli-0.1.0/projectmemory_cli/commands/auth.py +163 -0
  12. projectmemory_cli-0.1.0/projectmemory_cli/commands/commits.py +147 -0
  13. projectmemory_cli-0.1.0/projectmemory_cli/commands/config.py +56 -0
  14. projectmemory_cli-0.1.0/projectmemory_cli/commands/doctor.py +375 -0
  15. projectmemory_cli-0.1.0/projectmemory_cli/commands/issues.py +234 -0
  16. projectmemory_cli-0.1.0/projectmemory_cli/commands/memory.py +235 -0
  17. projectmemory_cli-0.1.0/projectmemory_cli/commands/next_action.py +282 -0
  18. projectmemory_cli-0.1.0/projectmemory_cli/commands/notes.py +231 -0
  19. projectmemory_cli-0.1.0/projectmemory_cli/commands/projects.py +325 -0
  20. projectmemory_cli-0.1.0/projectmemory_cli/commands/resume.py +240 -0
  21. projectmemory_cli-0.1.0/projectmemory_cli/commands/sessions.py +277 -0
  22. projectmemory_cli-0.1.0/projectmemory_cli/commands/tasks.py +435 -0
  23. projectmemory_cli-0.1.0/projectmemory_cli/config.py +101 -0
  24. projectmemory_cli-0.1.0/projectmemory_cli/credentials.py +98 -0
  25. projectmemory_cli-0.1.0/projectmemory_cli/git.py +124 -0
  26. projectmemory_cli-0.1.0/projectmemory_cli/main.py +4 -0
  27. projectmemory_cli-0.1.0/projectmemory_cli/models/__init__.py +1 -0
  28. projectmemory_cli-0.1.0/projectmemory_cli/models/api.py +226 -0
  29. projectmemory_cli-0.1.0/projectmemory_cli/output.py +227 -0
  30. projectmemory_cli-0.1.0/projectmemory_cli/project_context.py +244 -0
  31. projectmemory_cli-0.1.0/pyproject.toml +54 -0
  32. projectmemory_cli-0.1.0/tests/conftest.py +20 -0
  33. projectmemory_cli-0.1.0/tests/test_auth.py +119 -0
  34. projectmemory_cli-0.1.0/tests/test_doctor.py +14 -0
  35. projectmemory_cli-0.1.0/tests/test_errors.py +75 -0
  36. projectmemory_cli-0.1.0/tests/test_git.py +32 -0
  37. projectmemory_cli-0.1.0/tests/test_guard.py +85 -0
  38. projectmemory_cli-0.1.0/tests/test_output.py +31 -0
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: projectmemory-cli
3
+ Version: 0.1.0
4
+ Summary: Project Memory CLI — developer context in your terminal
5
+ License: MIT
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: httpx>=0.27
8
+ Requires-Dist: keyring>=25
9
+ Requires-Dist: platformdirs>=4
10
+ Requires-Dist: pydantic>=2
11
+ Requires-Dist: questionary>=2
12
+ Requires-Dist: rich>=13
13
+ Requires-Dist: tomli-w>=1
14
+ Requires-Dist: typer[all]>=0.12
15
+ Provides-Extra: dev
16
+ Requires-Dist: mypy>=1.10; extra == 'dev'
17
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
18
+ Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
19
+ Requires-Dist: pytest-mock>=3; extra == 'dev'
20
+ Requires-Dist: pytest>=8; extra == 'dev'
21
+ Requires-Dist: ruff>=0.4; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Project Memory CLI
25
+
26
+ Developer context in your terminal — a Python CLI client for [projectmemory.app](https://projectmemory.app).
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install projectmemory-cli
32
+ ```
33
+
34
+ Or from source:
35
+
36
+ ```bash
37
+ git clone https://github.com/your-org/projectmemory-cli
38
+ cd projectmemory-cli
39
+ pip install -e ".[dev]"
40
+ ```
41
+
42
+ Both `pm` and `projectmemory` are registered as executable names.
43
+
44
+ ## Quick Start
45
+
46
+ ```bash
47
+ # Authenticate (opens browser)
48
+ pm login
49
+
50
+ # Create a project and start its first session
51
+ pm project create
52
+
53
+ # Or resume an existing project/session
54
+ pm resume "My App"
55
+
56
+ # See your current context
57
+ pm memory show
58
+
59
+ # Work with tasks
60
+ pm task list
61
+ pm task add "Implement login"
62
+ pm task done 42 # completes task, then optionally links a commit
63
+ pm done # interactive task completion shortcut
64
+
65
+ # Capture notes
66
+ pm capture "Don't forget to update the Stripe webhook URL"
67
+
68
+ # Report an issue
69
+ pm issue add "Login fails on mobile Safari"
70
+
71
+ # End your session with the shared backend review
72
+ pm session end
73
+ ```
74
+
75
+ ## Commands
76
+
77
+ ### Authentication
78
+ | Command | Description |
79
+ |---|---|
80
+ | `pm login` | Authenticate via browser |
81
+ | `pm logout` | Revoke token |
82
+ | `pm whoami` | Show current user |
83
+ | `pm auth status` | Check auth status |
84
+ | `pm doctor` | Diagnose CLI config, auth, backend, project, session, and Git state |
85
+
86
+ ### Projects
87
+ | Command | Description |
88
+ |---|---|
89
+ | `pm project list` | List all projects |
90
+ | `pm project create` | Create a project (interactive) |
91
+ | `pm project current` | Show current project + session |
92
+ | `pm project link` | Link this directory to a project |
93
+ | `pm project unlink` | Remove directory mapping |
94
+ | `pm web` | Open project in browser |
95
+
96
+ ### Work Sessions
97
+ | Command | Description |
98
+ |---|---|
99
+ | `pm resume [project]` | Start or resume a session |
100
+ | `pm session status` | Show all active sessions |
101
+ | `pm session end` | End the current session |
102
+
103
+ ### Project Memory
104
+ | Command | Description |
105
+ |---|---|
106
+ | `pm memory show` | Display all memory fields |
107
+ | `pm memory status set [text]` | Update Current Status |
108
+ | `pm memory context set [text]` | Update Important Context |
109
+ | `pm memory update` | Interactive memory update, including open issues |
110
+
111
+ ### Next Action
112
+ | Command | Description |
113
+ |---|---|
114
+ | `pm next` | Show current Next Action |
115
+ | `pm next set [text]` | Set Next Action (interactive menu) |
116
+ | `pm next task <id>` | Link a task as Next Action |
117
+ | `pm next issue <id>` | Link an issue as Next Action |
118
+ | `pm next clear` | Clear Next Action |
119
+
120
+ ### Tasks
121
+ | Command | Description |
122
+ |---|---|
123
+ | `pm task list` | List tasks; supports `--open`, `--completed`, `--snoozed`, `--all` |
124
+ | `pm task add [title]` | Create a task |
125
+ | `pm task done <id>` | Complete task + optional commit link |
126
+ | `pm done` | Interactive task completion shortcut |
127
+ | `pm task snooze <id>` | Snooze task |
128
+ | `pm task reopen <id>` | Reopen task |
129
+ | `pm task edit <id>` | Edit task |
130
+ | `pm task delete <id>` | Delete task |
131
+
132
+ ### Notes
133
+ | Command | Description |
134
+ |---|---|
135
+ | `pm note list` | List notes |
136
+ | `pm note add [content]` | Create a note; supports `--title`, `--editor`, `--stdin` |
137
+ | `pm note search <query>` | Search notes |
138
+ | `pm capture [text]` | Quick note shortcut |
139
+ | `pm note edit <id>` | Edit a note |
140
+ | `pm note delete <id>` | Delete a note |
141
+
142
+ ### Issues
143
+ | Command | Description |
144
+ |---|---|
145
+ | `pm issue list` | List issues; supports `--open`, `--resolved`, `--all` |
146
+ | `pm issue add [description]` | Report an issue |
147
+ | `pm issue resolve <id>` | Mark issue as resolved |
148
+ | `pm issue reopen <id>` | Reopen issue |
149
+ | `pm issue edit <id>` | Edit issue |
150
+ | `pm issue delete <id>` | Delete issue |
151
+
152
+ ### Commits
153
+ | Command | Description |
154
+ |---|---|
155
+ | `pm commit link` | Interactive recovery flow for linking a commit to a completed task |
156
+ | `pm commit link HEAD --task 24` | Directly link a commit/ref to a completed task |
157
+
158
+ ## Configuration
159
+
160
+ Configuration is stored at `~/.config/projectmemory/config.toml`.
161
+
162
+ The default `api_url` is the Project Memory site root, for example `https://projectmemory.app`. The CLI adds API paths such as `/api/cli/...` internally.
163
+
164
+ ```bash
165
+ pm config show # show all settings
166
+ pm config set api_url https://your-instance.example.com
167
+ pm config reset # restore defaults
168
+ ```
169
+
170
+ ### Environment variables
171
+
172
+ | Variable | Description |
173
+ |---|---|
174
+ | `PROJECTMEMORY_API_URL` | API base URL |
175
+ | `PROJECTMEMORY_TOKEN` | Token override (skip keyring) |
176
+ | `PROJECTMEMORY_PROJECT` | Current project ID override |
177
+ | `PROJECTMEMORY_NO_COLOR` | Disable color output |
178
+
179
+
180
+ ## Diagnostics
181
+
182
+ Run a read-only diagnostic report when troubleshooting local configuration or support issues:
183
+
184
+ ```bash
185
+ pm doctor
186
+ pm doctor --json
187
+ pm doctor --verbose
188
+ pm doctor --no-network
189
+ ```
190
+
191
+ `pm doctor` never prints authentication tokens, refresh tokens, passwords, private keys, or OAuth secrets. It exits with `0` when healthy, `1` when warnings are present, and `2` when critical issues prevent normal CLI use.
192
+
193
+ ## Directory Linking
194
+
195
+ Link the current git repository to a project so `pm` knows which project to use automatically:
196
+
197
+ ```bash
198
+ cd ~/code/my-app
199
+ pm project link "My App"
200
+
201
+ # Now pm commands in this directory use "My App" automatically
202
+ pm task list # no --project needed
203
+ ```
204
+
205
+ ## How Sessions Work
206
+
207
+ The CLI shares the session model with the web app. Sessions must be active for write operations (creating tasks, notes, issues, updating memory).
208
+
209
+ - `pm resume` starts or resumes the backend session for the selected project
210
+ - `pm session end` shows the shared backend Session Review and finalizes that exact session
211
+ - If the web app ends your session, the CLI will show a clear error and ask you to `pm resume` again
212
+ - A user can have active sessions in multiple projects, but the CLI has one current project
213
+ - Switching projects can end the current project session, keep it active, or cancel
214
+
215
+ ## Development
216
+
217
+ ```bash
218
+ # Install with dev dependencies
219
+ pip install -e ".[dev]"
220
+
221
+ # Run tests
222
+ pytest
223
+
224
+ # Run with coverage
225
+ pytest --cov=projectmemory_cli --cov-report=term-missing
226
+
227
+ # Lint
228
+ ruff check projectmemory_cli/
229
+
230
+ # Type check
231
+ mypy projectmemory_cli/
232
+ ```
233
+
234
+ ## Security
235
+
236
+ - Tokens are stored in the OS keyring (or `~/.config/projectmemory/credentials.json` with `chmod 600` as fallback)
237
+ - Passwords are never stored
238
+ - The `PROJECTMEMORY_TOKEN` environment variable overrides stored credentials (read-only)
239
+ - Tokens can be revoked remotely via `pm logout` or from the web app at projectmemory.app/settings
@@ -0,0 +1,216 @@
1
+ # Project Memory CLI
2
+
3
+ Developer context in your terminal — a Python CLI client for [projectmemory.app](https://projectmemory.app).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install projectmemory-cli
9
+ ```
10
+
11
+ Or from source:
12
+
13
+ ```bash
14
+ git clone https://github.com/your-org/projectmemory-cli
15
+ cd projectmemory-cli
16
+ pip install -e ".[dev]"
17
+ ```
18
+
19
+ Both `pm` and `projectmemory` are registered as executable names.
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ # Authenticate (opens browser)
25
+ pm login
26
+
27
+ # Create a project and start its first session
28
+ pm project create
29
+
30
+ # Or resume an existing project/session
31
+ pm resume "My App"
32
+
33
+ # See your current context
34
+ pm memory show
35
+
36
+ # Work with tasks
37
+ pm task list
38
+ pm task add "Implement login"
39
+ pm task done 42 # completes task, then optionally links a commit
40
+ pm done # interactive task completion shortcut
41
+
42
+ # Capture notes
43
+ pm capture "Don't forget to update the Stripe webhook URL"
44
+
45
+ # Report an issue
46
+ pm issue add "Login fails on mobile Safari"
47
+
48
+ # End your session with the shared backend review
49
+ pm session end
50
+ ```
51
+
52
+ ## Commands
53
+
54
+ ### Authentication
55
+ | Command | Description |
56
+ |---|---|
57
+ | `pm login` | Authenticate via browser |
58
+ | `pm logout` | Revoke token |
59
+ | `pm whoami` | Show current user |
60
+ | `pm auth status` | Check auth status |
61
+ | `pm doctor` | Diagnose CLI config, auth, backend, project, session, and Git state |
62
+
63
+ ### Projects
64
+ | Command | Description |
65
+ |---|---|
66
+ | `pm project list` | List all projects |
67
+ | `pm project create` | Create a project (interactive) |
68
+ | `pm project current` | Show current project + session |
69
+ | `pm project link` | Link this directory to a project |
70
+ | `pm project unlink` | Remove directory mapping |
71
+ | `pm web` | Open project in browser |
72
+
73
+ ### Work Sessions
74
+ | Command | Description |
75
+ |---|---|
76
+ | `pm resume [project]` | Start or resume a session |
77
+ | `pm session status` | Show all active sessions |
78
+ | `pm session end` | End the current session |
79
+
80
+ ### Project Memory
81
+ | Command | Description |
82
+ |---|---|
83
+ | `pm memory show` | Display all memory fields |
84
+ | `pm memory status set [text]` | Update Current Status |
85
+ | `pm memory context set [text]` | Update Important Context |
86
+ | `pm memory update` | Interactive memory update, including open issues |
87
+
88
+ ### Next Action
89
+ | Command | Description |
90
+ |---|---|
91
+ | `pm next` | Show current Next Action |
92
+ | `pm next set [text]` | Set Next Action (interactive menu) |
93
+ | `pm next task <id>` | Link a task as Next Action |
94
+ | `pm next issue <id>` | Link an issue as Next Action |
95
+ | `pm next clear` | Clear Next Action |
96
+
97
+ ### Tasks
98
+ | Command | Description |
99
+ |---|---|
100
+ | `pm task list` | List tasks; supports `--open`, `--completed`, `--snoozed`, `--all` |
101
+ | `pm task add [title]` | Create a task |
102
+ | `pm task done <id>` | Complete task + optional commit link |
103
+ | `pm done` | Interactive task completion shortcut |
104
+ | `pm task snooze <id>` | Snooze task |
105
+ | `pm task reopen <id>` | Reopen task |
106
+ | `pm task edit <id>` | Edit task |
107
+ | `pm task delete <id>` | Delete task |
108
+
109
+ ### Notes
110
+ | Command | Description |
111
+ |---|---|
112
+ | `pm note list` | List notes |
113
+ | `pm note add [content]` | Create a note; supports `--title`, `--editor`, `--stdin` |
114
+ | `pm note search <query>` | Search notes |
115
+ | `pm capture [text]` | Quick note shortcut |
116
+ | `pm note edit <id>` | Edit a note |
117
+ | `pm note delete <id>` | Delete a note |
118
+
119
+ ### Issues
120
+ | Command | Description |
121
+ |---|---|
122
+ | `pm issue list` | List issues; supports `--open`, `--resolved`, `--all` |
123
+ | `pm issue add [description]` | Report an issue |
124
+ | `pm issue resolve <id>` | Mark issue as resolved |
125
+ | `pm issue reopen <id>` | Reopen issue |
126
+ | `pm issue edit <id>` | Edit issue |
127
+ | `pm issue delete <id>` | Delete issue |
128
+
129
+ ### Commits
130
+ | Command | Description |
131
+ |---|---|
132
+ | `pm commit link` | Interactive recovery flow for linking a commit to a completed task |
133
+ | `pm commit link HEAD --task 24` | Directly link a commit/ref to a completed task |
134
+
135
+ ## Configuration
136
+
137
+ Configuration is stored at `~/.config/projectmemory/config.toml`.
138
+
139
+ The default `api_url` is the Project Memory site root, for example `https://projectmemory.app`. The CLI adds API paths such as `/api/cli/...` internally.
140
+
141
+ ```bash
142
+ pm config show # show all settings
143
+ pm config set api_url https://your-instance.example.com
144
+ pm config reset # restore defaults
145
+ ```
146
+
147
+ ### Environment variables
148
+
149
+ | Variable | Description |
150
+ |---|---|
151
+ | `PROJECTMEMORY_API_URL` | API base URL |
152
+ | `PROJECTMEMORY_TOKEN` | Token override (skip keyring) |
153
+ | `PROJECTMEMORY_PROJECT` | Current project ID override |
154
+ | `PROJECTMEMORY_NO_COLOR` | Disable color output |
155
+
156
+
157
+ ## Diagnostics
158
+
159
+ Run a read-only diagnostic report when troubleshooting local configuration or support issues:
160
+
161
+ ```bash
162
+ pm doctor
163
+ pm doctor --json
164
+ pm doctor --verbose
165
+ pm doctor --no-network
166
+ ```
167
+
168
+ `pm doctor` never prints authentication tokens, refresh tokens, passwords, private keys, or OAuth secrets. It exits with `0` when healthy, `1` when warnings are present, and `2` when critical issues prevent normal CLI use.
169
+
170
+ ## Directory Linking
171
+
172
+ Link the current git repository to a project so `pm` knows which project to use automatically:
173
+
174
+ ```bash
175
+ cd ~/code/my-app
176
+ pm project link "My App"
177
+
178
+ # Now pm commands in this directory use "My App" automatically
179
+ pm task list # no --project needed
180
+ ```
181
+
182
+ ## How Sessions Work
183
+
184
+ The CLI shares the session model with the web app. Sessions must be active for write operations (creating tasks, notes, issues, updating memory).
185
+
186
+ - `pm resume` starts or resumes the backend session for the selected project
187
+ - `pm session end` shows the shared backend Session Review and finalizes that exact session
188
+ - If the web app ends your session, the CLI will show a clear error and ask you to `pm resume` again
189
+ - A user can have active sessions in multiple projects, but the CLI has one current project
190
+ - Switching projects can end the current project session, keep it active, or cancel
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ # Install with dev dependencies
196
+ pip install -e ".[dev]"
197
+
198
+ # Run tests
199
+ pytest
200
+
201
+ # Run with coverage
202
+ pytest --cov=projectmemory_cli --cov-report=term-missing
203
+
204
+ # Lint
205
+ ruff check projectmemory_cli/
206
+
207
+ # Type check
208
+ mypy projectmemory_cli/
209
+ ```
210
+
211
+ ## Security
212
+
213
+ - Tokens are stored in the OS keyring (or `~/.config/projectmemory/credentials.json` with `chmod 600` as fallback)
214
+ - Passwords are never stored
215
+ - The `PROJECTMEMORY_TOKEN` environment variable overrides stored credentials (read-only)
216
+ - Tokens can be revoked remotely via `pm logout` or from the web app at projectmemory.app/settings
@@ -0,0 +1,3 @@
1
+ """Project Memory CLI package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """API package."""
@@ -0,0 +1,94 @@
1
+ """Device authorization flow API calls."""
2
+ from __future__ import annotations
3
+
4
+ import time
5
+ from dataclasses import dataclass
6
+
7
+ from projectmemory_cli.api.client import APIClient
8
+ from projectmemory_cli.api.errors import APIError
9
+
10
+
11
+ @dataclass
12
+ class DeviceAuthStart:
13
+ device_code: str
14
+ user_code: str
15
+ verification_uri: str
16
+ verification_uri_complete: str
17
+ expires_in: int
18
+ interval: int
19
+ authorization_id: int
20
+
21
+
22
+ @dataclass
23
+ class DeviceAuthResult:
24
+ access_token: str
25
+ token_type: str
26
+ token_id: int
27
+ user: dict
28
+
29
+
30
+ def start_device_auth(client: APIClient, device_name: str = "Project Memory CLI") -> DeviceAuthStart:
31
+ data = client.post("/api/cli/auth/device/", json={"device_name": device_name})
32
+ return DeviceAuthStart(
33
+ device_code=data["device_code"],
34
+ user_code=data["user_code"],
35
+ verification_uri=data["verification_uri"],
36
+ verification_uri_complete=data["verification_uri_complete"],
37
+ expires_in=data["expires_in"],
38
+ interval=data["interval"],
39
+ authorization_id=data["authorization_id"],
40
+ )
41
+
42
+
43
+ def poll_device_auth(
44
+ client: APIClient,
45
+ device_code: str,
46
+ *,
47
+ interval: int = 5,
48
+ timeout: int = 900,
49
+ on_pending: object = None,
50
+ ) -> DeviceAuthResult:
51
+ """Poll until the authorization is approved, denied, or expired.
52
+
53
+ Calls `on_pending()` (if callable) on each pending response.
54
+ Raises APIError on denial, expiry, or timeout.
55
+ """
56
+ deadline = time.time() + timeout
57
+ while time.time() < deadline:
58
+ try:
59
+ data = client.post("/api/cli/auth/device/poll/", json={"device_code": device_code})
60
+ return DeviceAuthResult(
61
+ access_token=data["access_token"],
62
+ token_type=data["token_type"],
63
+ token_id=data["token_id"],
64
+ user=data["user"],
65
+ )
66
+ except APIError as exc:
67
+ err_data = getattr(exc, "data", {}) or {}
68
+ err_code = err_data.get("error", "")
69
+ if err_code == "authorization_pending":
70
+ if callable(on_pending):
71
+ on_pending() # type: ignore[call-arg]
72
+ time.sleep(interval)
73
+ continue
74
+ if err_code == "expired_token":
75
+ raise APIError("Authorization code expired. Run `pm login` to try again.")
76
+ if err_code == "access_denied":
77
+ raise APIError("Authorization was denied in the browser.")
78
+ if err_code == "authorization_consumed":
79
+ raise APIError("This authorization code was already used.")
80
+ raise
81
+ raise APIError("Authorization timed out. Run `pm login` to try again.")
82
+
83
+
84
+ def revoke_token(client: APIClient) -> bool:
85
+ data = client.post("/api/cli/auth/logout/")
86
+ return bool(data.get("revoked"))
87
+
88
+
89
+ def whoami(client: APIClient) -> dict:
90
+ return client.get("/api/cli/whoami/")
91
+
92
+
93
+ def auth_status(client: APIClient) -> dict:
94
+ return client.get("/api/cli/auth/status/")
@@ -0,0 +1,135 @@
1
+ """HTTPX-based API client.
2
+
3
+ Injects:
4
+ - Authorization: Bearer <token>
5
+ - X-Project-Memory-Session: <session_id> (when provided)
6
+ - User-Agent: projectmemory-cli/<version>
7
+ - Content-Type: application/json
8
+
9
+ All requests go through _request() which translates HTTP errors into
10
+ structured APIError subclasses via api.errors.raise_for_response().
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+ import httpx
17
+
18
+ from projectmemory_cli import output
19
+ from projectmemory_cli.api.errors import NetworkError, raise_for_response
20
+
21
+ _VERSION = "0.1.0"
22
+ _TIMEOUT = 30.0
23
+
24
+
25
+ class APIClient:
26
+ def __init__(
27
+ self,
28
+ base_url: str,
29
+ token: str | None = None,
30
+ session_id: int | str | None = None,
31
+ debug: bool = False,
32
+ ):
33
+ self.base_url = base_url.rstrip("/")
34
+ self.token = token
35
+ self.session_id = session_id
36
+ self.debug = debug
37
+
38
+ def _headers(self, extra: dict | None = None) -> dict[str, str]:
39
+ headers: dict[str, str] = {
40
+ "User-Agent": f"projectmemory-cli/{_VERSION}",
41
+ "Accept": "application/json",
42
+ }
43
+ if self.token:
44
+ headers["Authorization"] = f"Bearer {self.token}"
45
+ if self.session_id is not None:
46
+ headers["X-Project-Memory-Session"] = str(self.session_id)
47
+ if extra:
48
+ headers.update(extra)
49
+ return headers
50
+
51
+ def _log_request(self, method: str, url: str, body: Any = None) -> None:
52
+ if not self.debug:
53
+ return
54
+ token_hint = " [token=pmcli_***]" if self.token else ""
55
+ output.debug(f"{method} {url}{token_hint}")
56
+ if body:
57
+ import json
58
+ output.debug(f" body={json.dumps(body, default=str)[:300]}")
59
+
60
+ def _log_response(self, resp: httpx.Response) -> None:
61
+ if self.debug:
62
+ output.debug(f" -> {resp.status_code}")
63
+
64
+ def _request(
65
+ self,
66
+ method: str,
67
+ path: str,
68
+ *,
69
+ json: Any = None,
70
+ params: dict | None = None,
71
+ project_name: str = "",
72
+ ) -> Any:
73
+ url = f"{self.base_url}{path}"
74
+ self._log_request(method, url, json)
75
+ try:
76
+ with httpx.Client(timeout=_TIMEOUT) as client:
77
+ resp = client.request(
78
+ method,
79
+ url,
80
+ headers=self._headers(),
81
+ json=json,
82
+ params=params,
83
+ )
84
+ except httpx.TimeoutException:
85
+ raise NetworkError("Request timed out. Check your connection.")
86
+ except httpx.ConnectError:
87
+ raise NetworkError(
88
+ "Cannot reach the Project Memory API.\n"
89
+ "Check your connection or try again later."
90
+ )
91
+ except httpx.RequestError as exc:
92
+ raise NetworkError(f"Network error: {exc}")
93
+
94
+ self._log_response(resp)
95
+
96
+ if resp.status_code == 204:
97
+ return {}
98
+
99
+ try:
100
+ data = resp.json()
101
+ except Exception:
102
+ data = {"detail": resp.text[:500]}
103
+
104
+ if not resp.is_success:
105
+ raise_for_response(resp.status_code, data, project_name=project_name)
106
+
107
+ return data
108
+
109
+ def get(self, path: str, *, params: dict | None = None, project_name: str = "") -> Any:
110
+ return self._request("GET", path, params=params, project_name=project_name)
111
+
112
+ def post(self, path: str, *, json: Any = None, project_name: str = "") -> Any:
113
+ return self._request("POST", path, json=json or {}, project_name=project_name)
114
+
115
+ def patch(self, path: str, *, json: Any = None, project_name: str = "") -> Any:
116
+ return self._request("PATCH", path, json=json or {}, project_name=project_name)
117
+
118
+ def delete(self, path: str, *, project_name: str = "") -> Any:
119
+ return self._request("DELETE", path, project_name=project_name)
120
+
121
+ @classmethod
122
+ def from_env(
123
+ cls,
124
+ *,
125
+ session_id: int | str | None = None,
126
+ debug: bool = False,
127
+ ) -> "APIClient":
128
+ """Build a client using current config + stored credentials."""
129
+ from projectmemory_cli import config, credentials
130
+ return cls(
131
+ base_url=config.get_api_url(),
132
+ token=credentials.get_token(),
133
+ session_id=session_id,
134
+ debug=debug,
135
+ )