projectmemory-cli 0.1.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.
- projectmemory_cli/__init__.py +3 -0
- projectmemory_cli/api/__init__.py +1 -0
- projectmemory_cli/api/auth.py +94 -0
- projectmemory_cli/api/client.py +135 -0
- projectmemory_cli/api/errors.py +124 -0
- projectmemory_cli/api/sessions.py +25 -0
- projectmemory_cli/app.py +80 -0
- projectmemory_cli/commands/__init__.py +1 -0
- projectmemory_cli/commands/auth.py +163 -0
- projectmemory_cli/commands/commits.py +147 -0
- projectmemory_cli/commands/config.py +56 -0
- projectmemory_cli/commands/doctor.py +375 -0
- projectmemory_cli/commands/issues.py +234 -0
- projectmemory_cli/commands/memory.py +235 -0
- projectmemory_cli/commands/next_action.py +282 -0
- projectmemory_cli/commands/notes.py +231 -0
- projectmemory_cli/commands/projects.py +325 -0
- projectmemory_cli/commands/resume.py +240 -0
- projectmemory_cli/commands/sessions.py +277 -0
- projectmemory_cli/commands/tasks.py +435 -0
- projectmemory_cli/config.py +101 -0
- projectmemory_cli/credentials.py +98 -0
- projectmemory_cli/git.py +124 -0
- projectmemory_cli/main.py +4 -0
- projectmemory_cli/models/__init__.py +1 -0
- projectmemory_cli/models/api.py +226 -0
- projectmemory_cli/output.py +227 -0
- projectmemory_cli/project_context.py +244 -0
- projectmemory_cli-0.1.0.dist-info/METADATA +239 -0
- projectmemory_cli-0.1.0.dist-info/RECORD +32 -0
- projectmemory_cli-0.1.0.dist-info/WHEEL +4 -0
- projectmemory_cli-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Project context resolution and session guard.
|
|
2
|
+
|
|
3
|
+
Resolution order for current project:
|
|
4
|
+
1. Explicit project argument (--project flag or positional name/id)
|
|
5
|
+
2. Current directory / git remote mapping
|
|
6
|
+
3. Stored current_project_id in config
|
|
7
|
+
4. Interactive selection
|
|
8
|
+
|
|
9
|
+
The session guard always fetches fresh state from the backend —
|
|
10
|
+
local cache is never treated as authoritative.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from platformdirs import user_config_dir
|
|
20
|
+
|
|
21
|
+
from projectmemory_cli import config, output
|
|
22
|
+
from projectmemory_cli.api.client import APIClient
|
|
23
|
+
from projectmemory_cli.api.errors import (
|
|
24
|
+
APIError,
|
|
25
|
+
AuthenticationError,
|
|
26
|
+
NoActiveSessionError,
|
|
27
|
+
)
|
|
28
|
+
from projectmemory_cli.models.api import ActiveSession, ProjectSummary
|
|
29
|
+
|
|
30
|
+
APP_NAME = "projectmemory"
|
|
31
|
+
REPO_MAP_FILE = Path(user_config_dir(APP_NAME)) / "repo_map.json"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Repository mapping
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
def _load_repo_map() -> dict:
|
|
39
|
+
if not REPO_MAP_FILE.exists():
|
|
40
|
+
return {}
|
|
41
|
+
try:
|
|
42
|
+
return json.loads(REPO_MAP_FILE.read_text())
|
|
43
|
+
except Exception:
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _save_repo_map(data: dict) -> None:
|
|
48
|
+
REPO_MAP_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
REPO_MAP_FILE.write_text(json.dumps(data, indent=2))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def save_repo_mapping(path: str, project_id: int, project_name: str) -> None:
|
|
53
|
+
data = _load_repo_map()
|
|
54
|
+
data[path] = {"project_id": project_id, "project_name": project_name}
|
|
55
|
+
_save_repo_map(data)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def remove_repo_mapping(path: str) -> bool:
|
|
59
|
+
data = _load_repo_map()
|
|
60
|
+
if path in data:
|
|
61
|
+
del data[path]
|
|
62
|
+
_save_repo_map(data)
|
|
63
|
+
return True
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_mapped_project_id_for_cwd() -> Optional[tuple[int, str]]:
|
|
68
|
+
"""Return (project_id, project_name) if CWD or a git root is mapped."""
|
|
69
|
+
from projectmemory_cli import git
|
|
70
|
+
data = _load_repo_map()
|
|
71
|
+
if not data:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
cwd = str(Path.cwd())
|
|
75
|
+
if cwd in data:
|
|
76
|
+
entry = data[cwd]
|
|
77
|
+
return entry["project_id"], entry["project_name"]
|
|
78
|
+
|
|
79
|
+
# Check git repo root
|
|
80
|
+
root = git.get_repo_root()
|
|
81
|
+
if root:
|
|
82
|
+
root_str = str(root)
|
|
83
|
+
if root_str in data:
|
|
84
|
+
entry = data[root_str]
|
|
85
|
+
return entry["project_id"], entry["project_name"]
|
|
86
|
+
|
|
87
|
+
# Check by git remote
|
|
88
|
+
remote_name = git.normalize_github_repo(git.get_remote_url(root))
|
|
89
|
+
if remote_name:
|
|
90
|
+
key = f"git_remote:{remote_name}"
|
|
91
|
+
if key in data:
|
|
92
|
+
entry = data[key]
|
|
93
|
+
return entry["project_id"], entry["project_name"]
|
|
94
|
+
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# Project resolution
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
def fetch_projects(client: APIClient) -> list[ProjectSummary]:
|
|
103
|
+
data = client.get("/api/cli/projects/")
|
|
104
|
+
return [ProjectSummary.model_validate(p) for p in data.get("projects", [])]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def find_project_by_name_or_id(
|
|
108
|
+
projects: list[ProjectSummary], query: str
|
|
109
|
+
) -> Optional[ProjectSummary]:
|
|
110
|
+
"""Find by exact ID match first, then case-insensitive name."""
|
|
111
|
+
query = query.strip()
|
|
112
|
+
# Try numeric ID
|
|
113
|
+
try:
|
|
114
|
+
pid = int(query)
|
|
115
|
+
for p in projects:
|
|
116
|
+
if p.id == pid:
|
|
117
|
+
return p
|
|
118
|
+
except ValueError:
|
|
119
|
+
pass
|
|
120
|
+
# Name match (case-insensitive, partial)
|
|
121
|
+
ql = query.lower()
|
|
122
|
+
for p in projects:
|
|
123
|
+
if p.name.lower() == ql:
|
|
124
|
+
return p
|
|
125
|
+
# Partial
|
|
126
|
+
matches = [p for p in projects if ql in p.name.lower()]
|
|
127
|
+
if len(matches) == 1:
|
|
128
|
+
return matches[0]
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def select_project_interactive(projects: list[ProjectSummary]) -> Optional[ProjectSummary]:
|
|
133
|
+
"""Present a questionary menu to pick a project."""
|
|
134
|
+
import questionary
|
|
135
|
+
if not projects:
|
|
136
|
+
return None
|
|
137
|
+
choices = [
|
|
138
|
+
questionary.Choice(
|
|
139
|
+
title=f"{p.name}{' [active session]' if p.has_active_session else ''}",
|
|
140
|
+
value=p,
|
|
141
|
+
)
|
|
142
|
+
for p in projects
|
|
143
|
+
]
|
|
144
|
+
try:
|
|
145
|
+
result = questionary.select(
|
|
146
|
+
"Select project:",
|
|
147
|
+
choices=choices,
|
|
148
|
+
).ask()
|
|
149
|
+
return result
|
|
150
|
+
except (KeyboardInterrupt, EOFError):
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def resolve_project(
|
|
155
|
+
client: APIClient,
|
|
156
|
+
*,
|
|
157
|
+
arg: Optional[str] = None,
|
|
158
|
+
interactive: bool = True,
|
|
159
|
+
) -> Optional[ProjectSummary]:
|
|
160
|
+
"""Full project resolution pipeline."""
|
|
161
|
+
|
|
162
|
+
projects: list[ProjectSummary] | None = None
|
|
163
|
+
|
|
164
|
+
def _projects() -> list[ProjectSummary]:
|
|
165
|
+
nonlocal projects
|
|
166
|
+
if projects is None:
|
|
167
|
+
projects = fetch_projects(client)
|
|
168
|
+
return projects
|
|
169
|
+
|
|
170
|
+
# 1. Explicit argument
|
|
171
|
+
if arg:
|
|
172
|
+
ps = _projects()
|
|
173
|
+
found = find_project_by_name_or_id(ps, arg)
|
|
174
|
+
if found:
|
|
175
|
+
return found
|
|
176
|
+
output.error(f"Project not found: {arg!r}")
|
|
177
|
+
raise SystemExit(5)
|
|
178
|
+
|
|
179
|
+
# 2. Current directory / git mapping
|
|
180
|
+
mapped = get_mapped_project_id_for_cwd()
|
|
181
|
+
if mapped:
|
|
182
|
+
project_id, _ = mapped
|
|
183
|
+
ps = _projects()
|
|
184
|
+
found = find_project_by_name_or_id(ps, str(project_id))
|
|
185
|
+
if found:
|
|
186
|
+
return found
|
|
187
|
+
|
|
188
|
+
# 3. Stored current project
|
|
189
|
+
stored_id = config.get_current_project_id()
|
|
190
|
+
if stored_id:
|
|
191
|
+
ps = _projects()
|
|
192
|
+
found = find_project_by_name_or_id(ps, stored_id)
|
|
193
|
+
if found:
|
|
194
|
+
return found
|
|
195
|
+
|
|
196
|
+
# 4. Interactive selection
|
|
197
|
+
if interactive:
|
|
198
|
+
ps = _projects()
|
|
199
|
+
if not ps:
|
|
200
|
+
output.error("You have no projects. Run: pm project create")
|
|
201
|
+
raise SystemExit(5)
|
|
202
|
+
return select_project_interactive(ps)
|
|
203
|
+
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Session guard
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
def get_active_session_for_project(client: APIClient, project_id: int) -> Optional[ActiveSession]:
|
|
212
|
+
"""Fetch the active session for a project from the backend (never cached)."""
|
|
213
|
+
try:
|
|
214
|
+
data = client.get("/api/cli/session/status/")
|
|
215
|
+
except AuthenticationError:
|
|
216
|
+
raise
|
|
217
|
+
except APIError:
|
|
218
|
+
return None
|
|
219
|
+
sessions = data.get("active_sessions", [])
|
|
220
|
+
for s in sessions:
|
|
221
|
+
if s.get("project") == project_id:
|
|
222
|
+
return ActiveSession.model_validate(s)
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def guard_project_session(
|
|
227
|
+
client: APIClient,
|
|
228
|
+
*,
|
|
229
|
+
project_arg: Optional[str] = None,
|
|
230
|
+
) -> tuple[ProjectSummary, ActiveSession]:
|
|
231
|
+
"""Resolve project and verify active session. Exit with clear message on failure."""
|
|
232
|
+
project = resolve_project(client, arg=project_arg)
|
|
233
|
+
if not project:
|
|
234
|
+
output.error("No current project.")
|
|
235
|
+
output.hint("Run:\n pm resume")
|
|
236
|
+
raise SystemExit(1)
|
|
237
|
+
|
|
238
|
+
session = get_active_session_for_project(client, project.id)
|
|
239
|
+
if not session:
|
|
240
|
+
output.error(f"No active session for {project.name}.")
|
|
241
|
+
output.hint("Run:\n pm resume")
|
|
242
|
+
raise SystemExit(8)
|
|
243
|
+
|
|
244
|
+
return project, session
|
|
@@ -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,32 @@
|
|
|
1
|
+
projectmemory_cli/__init__.py,sha256=_mSPeKukKlAdhOhlyV7KDX3nbP8dTqDsNKZyypYB17o,57
|
|
2
|
+
projectmemory_cli/app.py,sha256=SJEYEj0lh6RozWj80C2AnjsyceQojEDelIKEl5gpKag,2742
|
|
3
|
+
projectmemory_cli/config.py,sha256=G764kH7NtINJsGwivL6qsGO6h98N53XJeIqlSMf5bVw,2591
|
|
4
|
+
projectmemory_cli/credentials.py,sha256=tZwBgJRa8IkN9oYs8hWwyMfVs9qpeX4XTA-VLtRY6as,2400
|
|
5
|
+
projectmemory_cli/git.py,sha256=eu3i6K1T_n1PCrVUnsr2ZLUVFy_fjQXPfgKhUxDvoD4,3693
|
|
6
|
+
projectmemory_cli/main.py,sha256=gQZnlXah8alZblfSkMmd-ZmeTG-EGQJ2zO1vWJi0ZsU,174
|
|
7
|
+
projectmemory_cli/output.py,sha256=5U5QKQgObXt7tkFJR9CZsnbETw8eiWiq8J3H2Lcm-ho,6083
|
|
8
|
+
projectmemory_cli/project_context.py,sha256=i5sv3E-5EyN3Ig-xR3k0aM8L6vFRt0FACxZNLoqrzvE,7151
|
|
9
|
+
projectmemory_cli/api/__init__.py,sha256=d7XMEse6wwtLORqrQmnhaKMI8KM3lJSzRVE7HVskq1w,19
|
|
10
|
+
projectmemory_cli/api/auth.py,sha256=VAkHwcBezEL7Q8ftw_M3XhtKLM67e2forJK4s_24PgM,2996
|
|
11
|
+
projectmemory_cli/api/client.py,sha256=ZXnYj1nBNt_6vQIKr5ewUoBOyHNBTAHgEQ0ZAn1O-KU,4328
|
|
12
|
+
projectmemory_cli/api/errors.py,sha256=38bgoIFaJGjg5AoJpBim-lKUUGI1EHHEePIfhWf9HMQ,4249
|
|
13
|
+
projectmemory_cli/api/sessions.py,sha256=_61u2Ln3m74ZMjEowZ14ChV1jb3H5PV5QAVcBL_4mJ4,894
|
|
14
|
+
projectmemory_cli/commands/__init__.py,sha256=zltFPaCZgkeTdOH1YWrUEqqBF9Dg6tokgAFcmqP4_n4,24
|
|
15
|
+
projectmemory_cli/commands/auth.py,sha256=xhKPerPKqpagFGV0ohCYxa-J-_WsDnD_6rvWTaJdXSA,4949
|
|
16
|
+
projectmemory_cli/commands/commits.py,sha256=t-MdXCdz3D4ppB8bg7HRzDziVYGyIvquqS3nEgh72kg,5596
|
|
17
|
+
projectmemory_cli/commands/config.py,sha256=7qybOO4Gu-8V6z3beX4WgT_QMdvxVZnmnU7DyDECTSk,1693
|
|
18
|
+
projectmemory_cli/commands/doctor.py,sha256=dcBWnpiA0-F-LW_sVNL5_WLnhAoTGzuD8imH_lpoeO8,15454
|
|
19
|
+
projectmemory_cli/commands/issues.py,sha256=nTZqFGtXnSO6RkBGdB0YqrSj80KIcpmKT0C1KrcVIkw,8143
|
|
20
|
+
projectmemory_cli/commands/memory.py,sha256=oR_IGCsbT0S-Jmd4cOeIulGDmNBG2FQ-HZppEr7R5r8,8089
|
|
21
|
+
projectmemory_cli/commands/next_action.py,sha256=DpoIg74NCCq009I6o6NaE3WZ7PVEKsNe2cHR5kpzIdk,10286
|
|
22
|
+
projectmemory_cli/commands/notes.py,sha256=RfNSiqX-Qqej6wGz22Pm50r8E9rkOxK231Tj-qtyGq0,7769
|
|
23
|
+
projectmemory_cli/commands/projects.py,sha256=X1-DHFB34IR8kz-oK8EonFXj-gatgEtkQH6tl9EaIqo,11073
|
|
24
|
+
projectmemory_cli/commands/resume.py,sha256=623w3_dphwBPxl9Fp2tXCIYFCJ4v5M2tGxBjqs4_g18,8819
|
|
25
|
+
projectmemory_cli/commands/sessions.py,sha256=_RmAKhiTX1PQlVrBi651gjjcvA5aYYrVeq63So29hEM,10446
|
|
26
|
+
projectmemory_cli/commands/tasks.py,sha256=MfN9Vqij7myr8Hd9I-32eV9oslg8qItLfT1McOB1qwg,15732
|
|
27
|
+
projectmemory_cli/models/__init__.py,sha256=Olg44Lfn1NJZ590xdEv7rZrS47PxSUnCXo9pOnSJm1s,41
|
|
28
|
+
projectmemory_cli/models/api.py,sha256=q8-lAaH7mimedjYRAPgvWMwQ6EaOVwt1U_K9TImtuM4,5800
|
|
29
|
+
projectmemory_cli-0.1.0.dist-info/METADATA,sha256=wSoHLLMEtzrWEs28KTQaN4jb3is43ScHDhuQWsofnJU,7156
|
|
30
|
+
projectmemory_cli-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
31
|
+
projectmemory_cli-0.1.0.dist-info/entry_points.txt,sha256=PFSO5C1l54v5RTc5LnHZUB3JKLhsCSXIMQwEPGW70Hg,93
|
|
32
|
+
projectmemory_cli-0.1.0.dist-info/RECORD,,
|