labtab 0.2.1__tar.gz → 0.2.2__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.
- {labtab-0.2.1/src/labtab.egg-info → labtab-0.2.2}/PKG-INFO +5 -2
- {labtab-0.2.1 → labtab-0.2.2}/README.md +4 -1
- {labtab-0.2.1 → labtab-0.2.2}/pyproject.toml +1 -1
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/__init__.py +1 -1
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/auth.py +23 -0
- {labtab-0.2.1 → labtab-0.2.2/src/labtab.egg-info}/PKG-INFO +5 -2
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_auth.py +64 -0
- {labtab-0.2.1 → labtab-0.2.2}/.gitignore +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/LICENSE +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/RELEASING.md +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/setup.cfg +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/auth.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/cli.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/client.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/__init__.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/_common.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/account.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/ai.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/api_keys.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/audit_logs.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/deadlines.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/export.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/grants.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/home.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/labs.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/letters.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/literature.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/meetings.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/notes.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/notifications.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/papers.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/people.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/posters.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/projects.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/results.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/search.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/shares.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/talks.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/tasks.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/commands/wiki.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/config.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/datetimes.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/errors.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/models/__init__.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/models/api.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/models/generated.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/output.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab/py.typed +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab.egg-info/SOURCES.txt +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab.egg-info/dependency_links.txt +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab.egg-info/entry_points.txt +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab.egg-info/requires.txt +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/src/labtab.egg-info/top_level.txt +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/__init__.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/conftest.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_auth.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_client.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_account.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_ai.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_api_keys.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_audit_logs.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_deadlines.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_export.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_grants.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_home.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_labs.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_letters.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_literature.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_meetings.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_meetings_create.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_notes.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_notifications.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_papers.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_papers_import.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_people.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_posters.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_projects.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_results.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_search.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_shares.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_talks.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_tasks.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_tasks_complete.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_tasks_create.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_commands_wiki.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_config.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_datetimes.py +0 -0
- {labtab-0.2.1 → labtab-0.2.2}/tests/test_models_generated.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labtab
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Command-line interface for LabTab (Wetware Ltd). Manage your lab from the terminal.
|
|
5
5
|
Author-email: Wetware Ltd <support@wetware.ai>
|
|
6
6
|
License: MIT
|
|
@@ -59,6 +59,9 @@ labtab --show-completion
|
|
|
59
59
|
# Pair your terminal with your LabTab account (opens a browser window)
|
|
60
60
|
labtab auth login
|
|
61
61
|
|
|
62
|
+
# Later logins reuse the API key in ~/.config/labtab/config.toml; pair again only when needed
|
|
63
|
+
labtab auth login --force
|
|
64
|
+
|
|
62
65
|
# Pair with a read-only key instead - mutating commands will be rejected
|
|
63
66
|
labtab auth login --read-only
|
|
64
67
|
|
|
@@ -199,7 +202,7 @@ permission check.
|
|
|
199
202
|
| `labtab notes create <type> <id>` | Create a note from `--text` or `--file` |
|
|
200
203
|
| `labtab notes update <id>` | Replace note content from `--text` or `--file` |
|
|
201
204
|
| `labtab notes export <type> <id>` | Export entity notes as Markdown or text |
|
|
202
|
-
| `labtab auth login` | Pair
|
|
205
|
+
| `labtab auth login` | Pair once, then reuse the locally stored key; `--force` replaces it and `--read-only` requests a key that cannot modify data |
|
|
203
206
|
| `labtab auth logout` | Forget the local API key |
|
|
204
207
|
| `labtab auth whoami` | Show the signed-in user and default lab |
|
|
205
208
|
| `labtab auth set-base-url` | Update the stored API base URL |
|
|
@@ -33,6 +33,9 @@ labtab --show-completion
|
|
|
33
33
|
# Pair your terminal with your LabTab account (opens a browser window)
|
|
34
34
|
labtab auth login
|
|
35
35
|
|
|
36
|
+
# Later logins reuse the API key in ~/.config/labtab/config.toml; pair again only when needed
|
|
37
|
+
labtab auth login --force
|
|
38
|
+
|
|
36
39
|
# Pair with a read-only key instead - mutating commands will be rejected
|
|
37
40
|
labtab auth login --read-only
|
|
38
41
|
|
|
@@ -173,7 +176,7 @@ permission check.
|
|
|
173
176
|
| `labtab notes create <type> <id>` | Create a note from `--text` or `--file` |
|
|
174
177
|
| `labtab notes update <id>` | Replace note content from `--text` or `--file` |
|
|
175
178
|
| `labtab notes export <type> <id>` | Export entity notes as Markdown or text |
|
|
176
|
-
| `labtab auth login` | Pair
|
|
179
|
+
| `labtab auth login` | Pair once, then reuse the locally stored key; `--force` replaces it and `--read-only` requests a key that cannot modify data |
|
|
177
180
|
| `labtab auth logout` | Forget the local API key |
|
|
178
181
|
| `labtab auth whoami` | Show the signed-in user and default lab |
|
|
179
182
|
| `labtab auth set-base-url` | Update the stored API base URL |
|
|
@@ -92,11 +92,33 @@ def login(
|
|
|
92
92
|
"--read-only",
|
|
93
93
|
help="Request a read-only API key - mutating commands will be rejected.",
|
|
94
94
|
),
|
|
95
|
+
force: bool = typer.Option(
|
|
96
|
+
False,
|
|
97
|
+
"--force",
|
|
98
|
+
help="Pair again and replace the API key already stored on this machine.",
|
|
99
|
+
),
|
|
95
100
|
json_mode: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
96
101
|
) -> None:
|
|
97
102
|
"""Pair this terminal with your LabTab account."""
|
|
98
103
|
|
|
99
104
|
config = Config.load()
|
|
105
|
+
if config.api_key and not force:
|
|
106
|
+
if json_mode:
|
|
107
|
+
dump_json(
|
|
108
|
+
{
|
|
109
|
+
"ok": True,
|
|
110
|
+
"user_email": config.user_email,
|
|
111
|
+
"default_lab_slug": config.default_lab_slug,
|
|
112
|
+
"reused": True,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
email = config.user_email or "your account"
|
|
118
|
+
print_info(f"Already signed in as {email}; using the stored API key.")
|
|
119
|
+
print_info("Run `labtab auth login --force` to pair again.")
|
|
120
|
+
return
|
|
121
|
+
|
|
100
122
|
client = LabtabClient.from_config(config)
|
|
101
123
|
console = Console()
|
|
102
124
|
requested_scopes = ["read"] if read_only else None
|
|
@@ -150,6 +172,7 @@ def login(
|
|
|
150
172
|
"user_email": result.user_email,
|
|
151
173
|
"default_lab_slug": result.default_lab_slug,
|
|
152
174
|
"read_only": read_only,
|
|
175
|
+
"reused": False,
|
|
153
176
|
},
|
|
154
177
|
)
|
|
155
178
|
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labtab
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Command-line interface for LabTab (Wetware Ltd). Manage your lab from the terminal.
|
|
5
5
|
Author-email: Wetware Ltd <support@wetware.ai>
|
|
6
6
|
License: MIT
|
|
@@ -59,6 +59,9 @@ labtab --show-completion
|
|
|
59
59
|
# Pair your terminal with your LabTab account (opens a browser window)
|
|
60
60
|
labtab auth login
|
|
61
61
|
|
|
62
|
+
# Later logins reuse the API key in ~/.config/labtab/config.toml; pair again only when needed
|
|
63
|
+
labtab auth login --force
|
|
64
|
+
|
|
62
65
|
# Pair with a read-only key instead - mutating commands will be rejected
|
|
63
66
|
labtab auth login --read-only
|
|
64
67
|
|
|
@@ -199,7 +202,7 @@ permission check.
|
|
|
199
202
|
| `labtab notes create <type> <id>` | Create a note from `--text` or `--file` |
|
|
200
203
|
| `labtab notes update <id>` | Replace note content from `--text` or `--file` |
|
|
201
204
|
| `labtab notes export <type> <id>` | Export entity notes as Markdown or text |
|
|
202
|
-
| `labtab auth login` | Pair
|
|
205
|
+
| `labtab auth login` | Pair once, then reuse the locally stored key; `--force` replaces it and `--read-only` requests a key that cannot modify data |
|
|
203
206
|
| `labtab auth logout` | Forget the local API key |
|
|
204
207
|
| `labtab auth whoami` | Show the signed-in user and default lab |
|
|
205
208
|
| `labtab auth set-base-url` | Update the stored API base URL |
|
|
@@ -91,6 +91,70 @@ def test_login_happy_path(
|
|
|
91
91
|
assert reloaded.default_lab_slug == "demo-lab"
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
def test_login_reuses_stored_key_without_starting_pairing(
|
|
95
|
+
invoke,
|
|
96
|
+
httpx_mock: HTTPXMock,
|
|
97
|
+
signed_in_config: Path,
|
|
98
|
+
) -> None:
|
|
99
|
+
result = invoke("auth", "login")
|
|
100
|
+
|
|
101
|
+
assert result.exit_code == 0, result.output
|
|
102
|
+
assert "Already signed in as pi@example.com" in result.stdout
|
|
103
|
+
assert "using the stored API key" in result.stdout
|
|
104
|
+
assert "--force" in result.stdout
|
|
105
|
+
assert httpx_mock.get_requests() == []
|
|
106
|
+
assert Config.load().api_key == "lab_api_test"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_login_reuses_stored_key_in_json_mode(
|
|
110
|
+
invoke,
|
|
111
|
+
httpx_mock: HTTPXMock,
|
|
112
|
+
signed_in_config: Path,
|
|
113
|
+
) -> None:
|
|
114
|
+
result = invoke("auth", "login", "--json")
|
|
115
|
+
|
|
116
|
+
assert result.exit_code == 0, result.output
|
|
117
|
+
assert json.loads(result.stdout) == {
|
|
118
|
+
"ok": True,
|
|
119
|
+
"user_email": "pi@example.com",
|
|
120
|
+
"default_lab_slug": "smith-neuro-lab",
|
|
121
|
+
"reused": True,
|
|
122
|
+
}
|
|
123
|
+
assert httpx_mock.get_requests() == []
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_login_force_replaces_stored_key(
|
|
127
|
+
invoke,
|
|
128
|
+
httpx_mock: HTTPXMock,
|
|
129
|
+
signed_in_config: Path,
|
|
130
|
+
) -> None:
|
|
131
|
+
httpx_mock.add_response(
|
|
132
|
+
url="https://api.test/v1/cli/pair/start/",
|
|
133
|
+
method="POST",
|
|
134
|
+
json={
|
|
135
|
+
"pairing_code": "NEWK-EY12",
|
|
136
|
+
"confirm_url": "https://labtab.app/cli/confirm?code=NEWK-EY12",
|
|
137
|
+
"expires_in": 600,
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
httpx_mock.add_response(
|
|
141
|
+
url="https://api.test/v1/cli/pair/poll/?code=NEWK-EY12",
|
|
142
|
+
json={
|
|
143
|
+
"status": "confirmed",
|
|
144
|
+
"api_key": "lab_api_replacement",
|
|
145
|
+
"user_email": "pi@example.com",
|
|
146
|
+
"default_lab_slug": "smith-neuro-lab",
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
result = invoke("auth", "login", "--force", "--no-browser", "--json")
|
|
151
|
+
|
|
152
|
+
assert result.exit_code == 0, result.output
|
|
153
|
+
assert json.loads(result.stdout)["reused"] is False
|
|
154
|
+
assert Config.load().api_key == "lab_api_replacement"
|
|
155
|
+
assert len(httpx_mock.get_requests(method="POST")) == 1
|
|
156
|
+
|
|
157
|
+
|
|
94
158
|
def test_login_read_only_sends_requested_scopes(
|
|
95
159
|
invoke,
|
|
96
160
|
httpx_mock: HTTPXMock,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|