powerbase-cli 0.1.0__tar.gz → 0.1.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.
Files changed (37) hide show
  1. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/PKG-INFO +9 -5
  2. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/README.md +8 -4
  3. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/pyproject.toml +1 -1
  4. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/auth.py +40 -8
  5. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/session.py +11 -2
  6. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/transport.py +16 -1
  7. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/PKG-INFO +9 -5
  8. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_cli_commands.py +57 -2
  9. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_cli_help.py +7 -6
  10. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_session.py +12 -1
  11. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_transport.py +16 -0
  12. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/setup.cfg +0 -0
  13. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/__init__.py +0 -0
  14. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/__main__.py +0 -0
  15. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/api.py +0 -0
  16. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/certs/powerbase-test-ca.pem +0 -0
  17. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/cli.py +0 -0
  18. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/__init__.py +0 -0
  19. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/agent.py +0 -0
  20. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/branch.py +0 -0
  21. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/config_cmd.py +0 -0
  22. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/context.py +0 -0
  23. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/database.py +0 -0
  24. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/instance.py +0 -0
  25. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/org.py +0 -0
  26. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/parser.py +0 -0
  27. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/publish.py +0 -0
  28. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/sandbox.py +0 -0
  29. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/shared.py +0 -0
  30. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/sql.py +0 -0
  31. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/config.py +0 -0
  32. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/SOURCES.txt +0 -0
  33. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/dependency_links.txt +0 -0
  34. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/entry_points.txt +0 -0
  35. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/top_level.txt +0 -0
  36. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_api.py +0 -0
  37. {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: powerbase-cli
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: CLI for operating Powerbase console workflows
5
5
  Author: Powerbase
6
6
  Requires-Python: >=3.11
@@ -101,10 +101,12 @@ This will:
101
101
 
102
102
  1. request a CLI login session from Powerbase
103
103
  2. print a browser URL
104
- 3. poll until browser approval or until `--timeout` elapses (default **300** seconds, five minutes)
104
+ 3. poll until browser approval or until `--timeout` elapses (default **600** seconds, ten minutes)
105
105
  4. save the resulting session to `~/.config/powerbase/auth.json`
106
106
 
107
- Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than five minutes to complete login.
107
+ Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than ten minutes to complete login.
108
+ For agent-guided workflows, prefer `powerbase auth login --no-wait --json` so the CLI returns the `login_url` and exits immediately instead of blocking on polling.
109
+ If the browser link expires or the user is not currently signed in to the console, rerun `powerbase auth login --no-wait --json` to generate a fresh login URL before retrying.
108
110
  If an agent or wrapper script is coordinating the workflow, it is still helpful
109
111
  to tell the user: after browser approval, return to the current session so the
110
112
  remaining steps can continue.
@@ -278,13 +280,15 @@ When Openclaw or another LLM agent uses `powerbase`:
278
280
  - run discovery commands before write operations
279
281
  - set context for long multi-step tasks
280
282
  - use `auth status` before workflows that assume login
281
- - use `auth token-set` if browser login is not practical in the current environment
283
+ - prefer `auth login --no-wait --json` for browser login so the agent can hand `login_url` back to the user
284
+ - if a protected command says authentication is missing or expired, rerun `auth login --no-wait --json` to generate a fresh login URL
285
+ - do not have the agent open the browser login URL itself; the user must complete that approval step
282
286
  - remember that `--json` overrides a saved `config output text` setting for that command
283
287
 
284
288
  Recommended agent sequence:
285
289
 
286
290
  1. `powerbase auth status --json`
287
- 2. If unauthenticated, run `powerbase auth login` or `powerbase auth token-set`
291
+ 2. If unauthenticated, or if a protected command reports an auth error, run `powerbase auth login --no-wait --json`, return the `login_url` to the user, and wait for them to approve in the browser
288
292
  3. `powerbase context show --json`
289
293
  4. If no instance is selected, run `powerbase instance list --json`
290
294
  5. If no suitable instance exists, prefer `powerbase instance create --name ... --org-id ... --json`
@@ -93,10 +93,12 @@ This will:
93
93
 
94
94
  1. request a CLI login session from Powerbase
95
95
  2. print a browser URL
96
- 3. poll until browser approval or until `--timeout` elapses (default **300** seconds, five minutes)
96
+ 3. poll until browser approval or until `--timeout` elapses (default **600** seconds, ten minutes)
97
97
  4. save the resulting session to `~/.config/powerbase/auth.json`
98
98
 
99
- Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than five minutes to complete login.
99
+ Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than ten minutes to complete login.
100
+ For agent-guided workflows, prefer `powerbase auth login --no-wait --json` so the CLI returns the `login_url` and exits immediately instead of blocking on polling.
101
+ If the browser link expires or the user is not currently signed in to the console, rerun `powerbase auth login --no-wait --json` to generate a fresh login URL before retrying.
100
102
  If an agent or wrapper script is coordinating the workflow, it is still helpful
101
103
  to tell the user: after browser approval, return to the current session so the
102
104
  remaining steps can continue.
@@ -270,13 +272,15 @@ When Openclaw or another LLM agent uses `powerbase`:
270
272
  - run discovery commands before write operations
271
273
  - set context for long multi-step tasks
272
274
  - use `auth status` before workflows that assume login
273
- - use `auth token-set` if browser login is not practical in the current environment
275
+ - prefer `auth login --no-wait --json` for browser login so the agent can hand `login_url` back to the user
276
+ - if a protected command says authentication is missing or expired, rerun `auth login --no-wait --json` to generate a fresh login URL
277
+ - do not have the agent open the browser login URL itself; the user must complete that approval step
274
278
  - remember that `--json` overrides a saved `config output text` setting for that command
275
279
 
276
280
  Recommended agent sequence:
277
281
 
278
282
  1. `powerbase auth status --json`
279
- 2. If unauthenticated, run `powerbase auth login` or `powerbase auth token-set`
283
+ 2. If unauthenticated, or if a protected command reports an auth error, run `powerbase auth login --no-wait --json`, return the `login_url` to the user, and wait for them to approve in the browser
280
284
  3. `powerbase context show --json`
281
285
  4. If no instance is selected, run `powerbase instance list --json`
282
286
  5. If no suitable instance exists, prefer `powerbase instance create --name ... --org-id ... --json`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "powerbase-cli"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "CLI for operating Powerbase console workflows"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -10,6 +10,25 @@ def handle_auth_login(args: argparse.Namespace) -> int:
10
10
  store, config, _, _, api = build_api(args)
11
11
  data = api.start_cli_login()
12
12
  login_id = data["login_id"]
13
+ if getattr(args, "no_wait_login", False):
14
+ render_output(
15
+ args,
16
+ {
17
+ "status": "pending",
18
+ "login_id": login_id,
19
+ "login_url": data["login_url"],
20
+ "poll_interval": data.get("poll_interval", 2),
21
+ "next_action": "ask_user_to_open_url",
22
+ "message": (
23
+ "Ask the user to open login_url in a browser and approve the request. "
24
+ "If the user is not already signed in to the console or this link expires, "
25
+ "rerun `powerbase auth login --no-wait --json` to generate a fresh login URL. "
26
+ "After approval, rerun `powerbase auth status --json` or invoke "
27
+ "`powerbase auth login` without `--no-wait` to finish in this session."
28
+ ),
29
+ },
30
+ )
31
+ return 0
13
32
  timeout_sec = int(args.login_timeout)
14
33
  if timeout_sec < 0:
15
34
  raise RuntimeError("--timeout must be >= 0 (0 means wait indefinitely).")
@@ -30,8 +49,9 @@ def handle_auth_login(args: argparse.Namespace) -> int:
30
49
  if remaining <= 0:
31
50
  raise RuntimeError(
32
51
  f"Login timed out after {timeout_sec} seconds without browser approval. "
33
- "Complete login sooner, run `powerbase auth login` again, or pass a larger "
34
- "`--timeout` (use `--timeout 0` to wait indefinitely)."
52
+ "Complete login sooner, rerun `powerbase auth login --no-wait --json` to "
53
+ "generate a fresh login URL, or pass a larger `--timeout` "
54
+ "(use `--timeout 0` to wait indefinitely)."
35
55
  )
36
56
  else:
37
57
  remaining = float("inf")
@@ -116,24 +136,37 @@ def register_auth_commands(subparsers: argparse._SubParsersAction[argparse.Argum
116
136
  help="Manage login state.",
117
137
  description="Log in, inspect, refresh, or clear the saved Powerbase session.",
118
138
  )
119
- auth_sub = auth.add_subparsers(dest="auth_command")
139
+ auth_sub = auth.add_subparsers(
140
+ dest="auth_command",
141
+ metavar="{login,status,refresh,logout}",
142
+ )
120
143
  p = auth_sub.add_parser(
121
144
  "login",
122
145
  help="Start browser login.",
123
146
  description=(
124
147
  "Start browser-based login. This prints a login URL, polls until approval in the browser "
125
- "or until --timeout seconds elapse (default 300), then saves the session to "
126
- "~/.config/powerbase/auth.json. Use --timeout 0 to wait without a time limit."
148
+ "or until --timeout seconds elapse (default 600), then saves the session to "
149
+ "~/.config/powerbase/auth.json. Use --timeout 0 to wait without a time limit. "
150
+ "Use --no-wait to print the login URL and exit immediately without polling."
151
+ ),
152
+ )
153
+ p.add_argument(
154
+ "--no-wait",
155
+ dest="no_wait_login",
156
+ action="store_true",
157
+ help=(
158
+ "Return the login URL and exit immediately without polling for approval. "
159
+ "Useful for agent-guided flows where the user must open the URL manually."
127
160
  ),
128
161
  )
129
162
  p.add_argument(
130
163
  "--timeout",
131
164
  dest="login_timeout",
132
165
  type=int,
133
- default=300,
166
+ default=600,
134
167
  metavar="SECONDS",
135
168
  help=(
136
- "Stop polling after this many seconds if login is not approved (default: 300). "
169
+ "Stop polling after this many seconds if login is not approved (default: 600). "
137
170
  "Pass 0 to wait indefinitely."
138
171
  ),
139
172
  )
@@ -157,7 +190,6 @@ def register_auth_commands(subparsers: argparse._SubParsersAction[argparse.Argum
157
190
  p.set_defaults(handler=handle_auth_logout)
158
191
  p = auth_sub.add_parser(
159
192
  "token-set",
160
- help="Save tokens into auth.json.",
161
193
  description=(
162
194
  "Save an access token and optional refresh token into ~/.config/powerbase/auth.json. "
163
195
  "If you skip the refresh token, the CLI cannot auto-refresh when the access token expires."
@@ -39,6 +39,12 @@ class SessionManager:
39
39
  self.tls_insecure = tls_insecure
40
40
  self.ca_cert_file = ca_cert_file
41
41
 
42
+ def _login_guidance(self) -> str:
43
+ return (
44
+ "Run `powerbase auth login --no-wait --json` to generate a fresh login URL, "
45
+ "ask the user to open it in their own browser, and retry after approval."
46
+ )
47
+
42
48
  def _urlopen(self, req: request.Request):
43
49
  if self.tls_insecure:
44
50
  context = ssl.create_default_context()
@@ -81,9 +87,12 @@ class SessionManager:
81
87
  def refresh(self, auth: AuthState | None = None) -> AuthState:
82
88
  auth = auth or self.get_auth_state()
83
89
  if not auth:
84
- raise SessionError("No authentication session available.")
90
+ raise SessionError(f"No authentication session available. {self._login_guidance()}")
85
91
  if not auth.session.refresh_token:
86
- raise SessionError("Current session has no refresh token. Please log in again.")
92
+ raise SessionError(
93
+ "Current session has no refresh token or can no longer be refreshed. "
94
+ f"{self._login_guidance()}"
95
+ )
87
96
  if not self.base_url or not self.anon_key:
88
97
  raise SessionError("base_url and anon_key are required to refresh the session.")
89
98
 
@@ -27,6 +27,12 @@ class PowerbaseTransport:
27
27
  self.config = config
28
28
  self.session_manager = session_manager
29
29
 
30
+ def _login_guidance(self) -> str:
31
+ return (
32
+ "Run `powerbase auth login --no-wait --json` to generate a fresh login URL, "
33
+ "ask the user to open it in their own browser, and retry after approval."
34
+ )
35
+
30
36
  def _urlopen(self, req: request.Request):
31
37
  if self.config.tls_insecure:
32
38
  context = ssl.create_default_context()
@@ -76,7 +82,11 @@ class PowerbaseTransport:
76
82
  data = json.loads(body_text)
77
83
  except json.JSONDecodeError:
78
84
  data = {"error": body_text}
79
- return ApiError(data.get("error") or body_text or exc.reason, exc.code)
85
+ message = data.get("error") or body_text or exc.reason
86
+ if exc.code == 401:
87
+ base_message = str(message).strip() or "Authentication failed."
88
+ message = f"{base_message} {self._login_guidance()}"
89
+ return ApiError(str(message), exc.code)
80
90
 
81
91
  def invoke(
82
92
  self,
@@ -95,6 +105,11 @@ class PowerbaseTransport:
95
105
  raise ApiError("Powerbase anon key is not configured.")
96
106
 
97
107
  auth = self.session_manager.ensure_valid() if requires_auth else None
108
+ if requires_auth and not auth:
109
+ raise ApiError(
110
+ f"No authentication session available. {self._login_guidance()}",
111
+ 401,
112
+ )
98
113
  url = f"{self.config.base_url.rstrip('/')}/functions/v1/{function_path}"
99
114
  payload = None if body is None else json.dumps(body).encode("utf-8")
100
115
  request_headers = self._build_headers(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: powerbase-cli
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: CLI for operating Powerbase console workflows
5
5
  Author: Powerbase
6
6
  Requires-Python: >=3.11
@@ -101,10 +101,12 @@ This will:
101
101
 
102
102
  1. request a CLI login session from Powerbase
103
103
  2. print a browser URL
104
- 3. poll until browser approval or until `--timeout` elapses (default **300** seconds, five minutes)
104
+ 3. poll until browser approval or until `--timeout` elapses (default **600** seconds, ten minutes)
105
105
  4. save the resulting session to `~/.config/powerbase/auth.json`
106
106
 
107
- Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than five minutes to complete login.
107
+ Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than ten minutes to complete login.
108
+ For agent-guided workflows, prefer `powerbase auth login --no-wait --json` so the CLI returns the `login_url` and exits immediately instead of blocking on polling.
109
+ If the browser link expires or the user is not currently signed in to the console, rerun `powerbase auth login --no-wait --json` to generate a fresh login URL before retrying.
108
110
  If an agent or wrapper script is coordinating the workflow, it is still helpful
109
111
  to tell the user: after browser approval, return to the current session so the
110
112
  remaining steps can continue.
@@ -278,13 +280,15 @@ When Openclaw or another LLM agent uses `powerbase`:
278
280
  - run discovery commands before write operations
279
281
  - set context for long multi-step tasks
280
282
  - use `auth status` before workflows that assume login
281
- - use `auth token-set` if browser login is not practical in the current environment
283
+ - prefer `auth login --no-wait --json` for browser login so the agent can hand `login_url` back to the user
284
+ - if a protected command says authentication is missing or expired, rerun `auth login --no-wait --json` to generate a fresh login URL
285
+ - do not have the agent open the browser login URL itself; the user must complete that approval step
282
286
  - remember that `--json` overrides a saved `config output text` setting for that command
283
287
 
284
288
  Recommended agent sequence:
285
289
 
286
290
  1. `powerbase auth status --json`
287
- 2. If unauthenticated, run `powerbase auth login` or `powerbase auth token-set`
291
+ 2. If unauthenticated, or if a protected command reports an auth error, run `powerbase auth login --no-wait --json`, return the `login_url` to the user, and wait for them to approve in the browser
288
292
  3. `powerbase context show --json`
289
293
  4. If no instance is selected, run `powerbase instance list --json`
290
294
  5. If no suitable instance exists, prefer `powerbase instance create --name ... --org-id ... --json`
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  import sys
4
5
  import tempfile
5
6
  import unittest
@@ -36,6 +37,18 @@ class FakeApiPending:
36
37
  return {"status": "pending", "poll_interval": 2}
37
38
 
38
39
 
40
+ class FakeApiNoWait:
41
+ def start_cli_login(self):
42
+ return {
43
+ "login_id": "login-nowait",
44
+ "login_url": "https://console.example.com/cli-auth/login-nowait",
45
+ "poll_interval": 2,
46
+ }
47
+
48
+ def poll_cli_login(self, login_id: str):
49
+ raise AssertionError("poll_cli_login should not be called when --no-wait is set")
50
+
51
+
39
52
  class FakeApi:
40
53
  def start_cli_login(self):
41
54
  return {
@@ -101,7 +114,15 @@ class CliCommandTests(unittest.TestCase):
101
114
  with tempfile.TemporaryDirectory() as temp_dir:
102
115
  store = ConfigStore(Path(temp_dir))
103
116
  config = AppConfig(base_url="https://console.example.com", anon_key="anon")
104
- args = Namespace(config_dir=temp_dir, base_url=None, anon_key=None, json=True, login_timeout=300, insecure=False)
117
+ args = Namespace(
118
+ config_dir=temp_dir,
119
+ base_url=None,
120
+ anon_key=None,
121
+ json=True,
122
+ login_timeout=300,
123
+ no_wait_login=False,
124
+ insecure=False,
125
+ )
105
126
 
106
127
  with mock.patch("powerbase_cli.commands.auth.build_api", return_value=(store, config, None, None, FakeApi())):
107
128
  with mock.patch("powerbase_cli.commands.auth.time.sleep", return_value=None):
@@ -114,6 +135,32 @@ class CliCommandTests(unittest.TestCase):
114
135
  self.assertEqual(saved.session.access_token, "new-access")
115
136
  self.assertEqual(saved.user["id"], "user-1")
116
137
 
138
+ def test_auth_login_no_wait_returns_login_url_without_polling(self) -> None:
139
+ with tempfile.TemporaryDirectory() as temp_dir:
140
+ store = ConfigStore(Path(temp_dir))
141
+ config = AppConfig(base_url="https://console.example.com", anon_key="anon")
142
+ args = Namespace(
143
+ config_dir=temp_dir,
144
+ base_url=None,
145
+ anon_key=None,
146
+ json=True,
147
+ login_timeout=300,
148
+ no_wait_login=True,
149
+ insecure=False,
150
+ )
151
+
152
+ with mock.patch("powerbase_cli.commands.auth.build_api", return_value=(store, config, None, None, FakeApiNoWait())):
153
+ with mock.patch("sys.stdout", new=StringIO()) as stdout:
154
+ exit_code = auth_handle_auth_login(args)
155
+
156
+ self.assertEqual(exit_code, 0)
157
+ payload = json.loads(stdout.getvalue())
158
+ self.assertEqual(payload["status"], "pending")
159
+ self.assertEqual(payload["login_id"], "login-nowait")
160
+ self.assertEqual(payload["next_action"], "ask_user_to_open_url")
161
+ self.assertIn("login_url", payload)
162
+ self.assertIsNone(store.load_auth())
163
+
117
164
  def test_auth_login_times_out(self) -> None:
118
165
  clock = {"t": 0.0}
119
166
 
@@ -126,7 +173,15 @@ class CliCommandTests(unittest.TestCase):
126
173
  with tempfile.TemporaryDirectory() as temp_dir:
127
174
  store = ConfigStore(Path(temp_dir))
128
175
  config = AppConfig(base_url="https://console.example.com", anon_key="anon")
129
- args = Namespace(config_dir=temp_dir, base_url=None, anon_key=None, json=False, login_timeout=5, insecure=False)
176
+ args = Namespace(
177
+ config_dir=temp_dir,
178
+ base_url=None,
179
+ anon_key=None,
180
+ json=False,
181
+ login_timeout=5,
182
+ no_wait_login=False,
183
+ insecure=False,
184
+ )
130
185
 
131
186
  with mock.patch("powerbase_cli.commands.auth.build_api", return_value=(store, config, None, None, FakeApiPending())):
132
187
  with mock.patch("powerbase_cli.commands.auth.time.monotonic", fake_monotonic):
@@ -28,15 +28,16 @@ class CliHelpTests(unittest.TestCase):
28
28
  login_parser = auth_parser._subparsers._group_actions[0].choices["login"]
29
29
  help_text = login_parser.format_help()
30
30
  self.assertIn("--timeout", help_text)
31
- self.assertIn("300", help_text)
31
+ self.assertIn("--no-wait", help_text)
32
+ self.assertIn("600", help_text)
32
33
 
33
- def test_auth_token_set_help_mentions_refresh_token(self) -> None:
34
+ def test_auth_help_does_not_list_token_set_subcommand(self) -> None:
34
35
  parser = build_parser()
35
36
  auth_parser = parser._subparsers._group_actions[0].choices["auth"]
36
- token_parser = auth_parser._subparsers._group_actions[0].choices["token-set"]
37
- help_text = token_parser.format_help()
38
- self.assertIn("refresh token", help_text.lower())
39
- self.assertIn("auto-refresh", help_text)
37
+ help_text = auth_parser.format_help()
38
+ self.assertNotIn("token-set", help_text)
39
+ subcommands = auth_parser._subparsers._group_actions[0].choices
40
+ self.assertIn("token-set", subcommands)
40
41
 
41
42
  def test_context_use_instance_help_mentions_source(self) -> None:
42
43
  parser = build_parser()
@@ -11,7 +11,7 @@ from unittest import mock
11
11
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
12
12
 
13
13
  from powerbase_cli.config import BUNDLED_CA_CERT_SENTINEL, AuthSession, AuthState, ConfigStore
14
- from powerbase_cli.session import SessionManager
14
+ from powerbase_cli.session import SessionError, SessionManager
15
15
 
16
16
 
17
17
  class FakeResponse:
@@ -29,6 +29,17 @@ class FakeResponse:
29
29
 
30
30
 
31
31
  class SessionManagerTests(unittest.TestCase):
32
+ def test_refresh_without_auth_instructs_user_to_log_in_again(self) -> None:
33
+ with tempfile.TemporaryDirectory() as temp_dir:
34
+ store = ConfigStore(Path(temp_dir))
35
+ manager = SessionManager(store, "https://console.example.com", "anon")
36
+
37
+ with self.assertRaises(SessionError) as ctx:
38
+ manager.refresh()
39
+
40
+ self.assertIn("No authentication session available", str(ctx.exception))
41
+ self.assertIn("powerbase auth login --no-wait --json", str(ctx.exception))
42
+
32
43
  def test_refresh_updates_saved_auth_file(self) -> None:
33
44
  with tempfile.TemporaryDirectory() as temp_dir:
34
45
  store = ConfigStore(Path(temp_dir))
@@ -35,6 +35,21 @@ def build_http_error(status: int, payload: dict[str, object]) -> HTTPError:
35
35
 
36
36
 
37
37
  class PowerbaseTransportTests(unittest.TestCase):
38
+ def test_invoke_requires_login_when_session_missing(self) -> None:
39
+ with tempfile.TemporaryDirectory() as temp_dir:
40
+ store = ConfigStore(Path(temp_dir))
41
+ manager = SessionManager(store, "https://console.example.com", "anon")
42
+ transport = PowerbaseTransport(AppConfig(base_url="https://console.example.com", anon_key="anon"), manager)
43
+
44
+ with mock.patch("powerbase_cli.transport.request.urlopen") as urlopen_mock:
45
+ with self.assertRaises(ApiError) as ctx:
46
+ transport.invoke("instances", method="GET")
47
+
48
+ self.assertEqual(ctx.exception.status, 401)
49
+ self.assertIn("No authentication session available", str(ctx.exception))
50
+ self.assertIn("powerbase auth login --no-wait --json", str(ctx.exception))
51
+ self.assertEqual(urlopen_mock.call_count, 0)
52
+
38
53
  def test_invoke_uses_unverified_tls_context_when_insecure(self) -> None:
39
54
  with tempfile.TemporaryDirectory() as temp_dir:
40
55
  store = ConfigStore(Path(temp_dir))
@@ -167,6 +182,7 @@ class PowerbaseTransportTests(unittest.TestCase):
167
182
  transport.invoke("instances", method="GET")
168
183
 
169
184
  self.assertEqual(ctx.exception.status, 401)
185
+ self.assertIn("powerbase auth login --no-wait --json", str(ctx.exception))
170
186
  self.assertEqual(refresh_mock.call_count, 0)
171
187
  self.assertEqual(urlopen_mock.call_count, 1)
172
188
 
File without changes