powerbase-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.
- powerbase_cli/__init__.py +5 -0
- powerbase_cli/__main__.py +5 -0
- powerbase_cli/api.py +348 -0
- powerbase_cli/certs/powerbase-test-ca.pem +21 -0
- powerbase_cli/cli.py +7 -0
- powerbase_cli/commands/__init__.py +3 -0
- powerbase_cli/commands/agent.py +192 -0
- powerbase_cli/commands/auth.py +169 -0
- powerbase_cli/commands/branch.py +86 -0
- powerbase_cli/commands/config_cmd.py +73 -0
- powerbase_cli/commands/context.py +84 -0
- powerbase_cli/commands/database.py +129 -0
- powerbase_cli/commands/instance.py +81 -0
- powerbase_cli/commands/org.py +18 -0
- powerbase_cli/commands/parser.py +114 -0
- powerbase_cli/commands/publish.py +44 -0
- powerbase_cli/commands/sandbox.py +90 -0
- powerbase_cli/commands/shared.py +152 -0
- powerbase_cli/commands/sql.py +32 -0
- powerbase_cli/config.py +239 -0
- powerbase_cli/session.py +141 -0
- powerbase_cli/transport.py +130 -0
- powerbase_cli-0.1.0.dist-info/METADATA +318 -0
- powerbase_cli-0.1.0.dist-info/RECORD +27 -0
- powerbase_cli-0.1.0.dist-info/WHEEL +5 -0
- powerbase_cli-0.1.0.dist-info/entry_points.txt +2 -0
- powerbase_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from .shared import AuthSession, AuthState, build_api, now_iso, render_output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle_auth_login(args: argparse.Namespace) -> int:
|
|
10
|
+
store, config, _, _, api = build_api(args)
|
|
11
|
+
data = api.start_cli_login()
|
|
12
|
+
login_id = data["login_id"]
|
|
13
|
+
timeout_sec = int(args.login_timeout)
|
|
14
|
+
if timeout_sec < 0:
|
|
15
|
+
raise RuntimeError("--timeout must be >= 0 (0 means wait indefinitely).")
|
|
16
|
+
deadline: float | None = None
|
|
17
|
+
if timeout_sec > 0:
|
|
18
|
+
deadline = time.monotonic() + timeout_sec
|
|
19
|
+
print(f"Open this URL in your browser:\n{data['login_url']}\n")
|
|
20
|
+
if deadline is not None:
|
|
21
|
+
print(f"Waiting for authorization (timeout {timeout_sec}s)...")
|
|
22
|
+
else:
|
|
23
|
+
print("Waiting for authorization (no timeout)...")
|
|
24
|
+
while True:
|
|
25
|
+
polled = api.poll_cli_login(login_id)
|
|
26
|
+
status = polled.get("status")
|
|
27
|
+
if status == "pending":
|
|
28
|
+
if deadline is not None:
|
|
29
|
+
remaining = deadline - time.monotonic()
|
|
30
|
+
if remaining <= 0:
|
|
31
|
+
raise RuntimeError(
|
|
32
|
+
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)."
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
remaining = float("inf")
|
|
38
|
+
|
|
39
|
+
poll_sec = max(0.0, float(int(polled.get("poll_interval", 2))))
|
|
40
|
+
if poll_sec > 0:
|
|
41
|
+
sleep_sec = min(poll_sec, remaining)
|
|
42
|
+
elif deadline is not None:
|
|
43
|
+
# poll_interval 0 would spin; cap backoff when a deadline is set
|
|
44
|
+
sleep_sec = min(1.0, remaining)
|
|
45
|
+
else:
|
|
46
|
+
sleep_sec = 0.0
|
|
47
|
+
|
|
48
|
+
if sleep_sec > 0:
|
|
49
|
+
time.sleep(sleep_sec)
|
|
50
|
+
continue
|
|
51
|
+
if status == "approved":
|
|
52
|
+
auth = AuthState(
|
|
53
|
+
source="login",
|
|
54
|
+
base_url=config.base_url,
|
|
55
|
+
anon_key=config.anon_key,
|
|
56
|
+
session=AuthSession(**polled["session"]),
|
|
57
|
+
user=polled.get("user"),
|
|
58
|
+
updated_at=now_iso(),
|
|
59
|
+
)
|
|
60
|
+
store.save_auth(auth)
|
|
61
|
+
render_output(args, {"status": "approved", "saved_to": str(store.auth_path), "user": auth.user})
|
|
62
|
+
return 0
|
|
63
|
+
raise RuntimeError(f"Login failed with status: {status}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def handle_auth_status(args: argparse.Namespace) -> int:
|
|
67
|
+
store, _, _, session_manager, _ = build_api(args)
|
|
68
|
+
auth = session_manager.get_auth_state()
|
|
69
|
+
data = {
|
|
70
|
+
"authenticated": bool(auth),
|
|
71
|
+
"source": auth.source if auth else None,
|
|
72
|
+
"auth_file": str(store.auth_path),
|
|
73
|
+
"has_refresh_token": bool(auth and auth.session.refresh_token),
|
|
74
|
+
"seconds_until_expiry": session_manager.seconds_until_expiry(auth),
|
|
75
|
+
"user": auth.user if auth else None,
|
|
76
|
+
}
|
|
77
|
+
render_output(args, data)
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def handle_auth_refresh(args: argparse.Namespace) -> int:
|
|
82
|
+
_, _, _, session_manager, _ = build_api(args)
|
|
83
|
+
auth = session_manager.refresh()
|
|
84
|
+
render_output(args, {"status": "refreshed", "user": auth.user, "expires_at": auth.session.expires_at})
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def handle_auth_logout(args: argparse.Namespace) -> int:
|
|
89
|
+
store, _, _, _, _ = build_api(args)
|
|
90
|
+
store.clear_auth()
|
|
91
|
+
render_output(args, {"status": "logged_out", "auth_file": str(store.auth_path)})
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def handle_auth_token_set(args: argparse.Namespace) -> int:
|
|
96
|
+
store, config, _, _, _ = build_api(args)
|
|
97
|
+
auth = AuthState(
|
|
98
|
+
source="manual",
|
|
99
|
+
base_url=config.base_url,
|
|
100
|
+
anon_key=config.anon_key,
|
|
101
|
+
session=AuthSession(
|
|
102
|
+
access_token=args.access_token_value,
|
|
103
|
+
refresh_token=args.refresh_token_value,
|
|
104
|
+
expires_at=args.expires_at,
|
|
105
|
+
),
|
|
106
|
+
updated_at=now_iso(),
|
|
107
|
+
)
|
|
108
|
+
store.save_auth(auth)
|
|
109
|
+
render_output(args, {"status": "saved", "auth_file": str(store.auth_path)})
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def register_auth_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
114
|
+
auth = subparsers.add_parser(
|
|
115
|
+
"auth",
|
|
116
|
+
help="Manage login state.",
|
|
117
|
+
description="Log in, inspect, refresh, or clear the saved Powerbase session.",
|
|
118
|
+
)
|
|
119
|
+
auth_sub = auth.add_subparsers(dest="auth_command")
|
|
120
|
+
p = auth_sub.add_parser(
|
|
121
|
+
"login",
|
|
122
|
+
help="Start browser login.",
|
|
123
|
+
description=(
|
|
124
|
+
"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."
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
p.add_argument(
|
|
130
|
+
"--timeout",
|
|
131
|
+
dest="login_timeout",
|
|
132
|
+
type=int,
|
|
133
|
+
default=300,
|
|
134
|
+
metavar="SECONDS",
|
|
135
|
+
help=(
|
|
136
|
+
"Stop polling after this many seconds if login is not approved (default: 300). "
|
|
137
|
+
"Pass 0 to wait indefinitely."
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
p.set_defaults(handler=handle_auth_login)
|
|
141
|
+
p = auth_sub.add_parser(
|
|
142
|
+
"status",
|
|
143
|
+
help="Show current auth status.",
|
|
144
|
+
description=(
|
|
145
|
+
"Show the active authentication source and whether refresh is available. "
|
|
146
|
+
"Reads env vars first, then ~/.config/powerbase/auth.json."
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
p.set_defaults(handler=handle_auth_status)
|
|
150
|
+
p = auth_sub.add_parser(
|
|
151
|
+
"refresh",
|
|
152
|
+
help="Refresh the current session.",
|
|
153
|
+
description="Refresh the saved session using the refresh token from auth.json.",
|
|
154
|
+
)
|
|
155
|
+
p.set_defaults(handler=handle_auth_refresh)
|
|
156
|
+
p = auth_sub.add_parser("logout", help="Clear the saved auth session.", description="Delete the saved auth.json session.")
|
|
157
|
+
p.set_defaults(handler=handle_auth_logout)
|
|
158
|
+
p = auth_sub.add_parser(
|
|
159
|
+
"token-set",
|
|
160
|
+
help="Save tokens into auth.json.",
|
|
161
|
+
description=(
|
|
162
|
+
"Save an access token and optional refresh token into ~/.config/powerbase/auth.json. "
|
|
163
|
+
"If you skip the refresh token, the CLI cannot auto-refresh when the access token expires."
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
p.add_argument("--access-token", dest="access_token_value", required=True, help="Access token to save.")
|
|
167
|
+
p.add_argument("--refresh-token", dest="refresh_token_value", help="Refresh token to save.")
|
|
168
|
+
p.add_argument("--expires-at", type=int, help="Unix timestamp when the access token expires.")
|
|
169
|
+
p.set_defaults(handler=handle_auth_token_set)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .shared import build_api, now_iso, render_output, require_instance, resolve_branch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_branch_list(args: argparse.Namespace) -> int:
|
|
9
|
+
_, _, context, _, api = build_api(args)
|
|
10
|
+
render_output(args, api.list_branches(require_instance(args.instance_id, context)))
|
|
11
|
+
return 0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def handle_branch_create(args: argparse.Namespace) -> int:
|
|
15
|
+
store, _, context, _, api = build_api(args)
|
|
16
|
+
instance_id = require_instance(args.instance_id, context)
|
|
17
|
+
source_branch = args.source_branch or resolve_branch(None, context) or "main"
|
|
18
|
+
created = api.create_branch(instance_id, args.branch_name, source_branch)
|
|
19
|
+
branch_slug = created.get("branchSlug") or created.get("branch_slug") or args.branch_name
|
|
20
|
+
api.switch_branch(instance_id, branch_slug)
|
|
21
|
+
context.instance_id = instance_id
|
|
22
|
+
context.branch = branch_slug
|
|
23
|
+
context.updated_at = now_iso()
|
|
24
|
+
store.save_context(context)
|
|
25
|
+
render_output(args, {"created": created, "switched_to": branch_slug})
|
|
26
|
+
return 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def handle_branch_switch(args: argparse.Namespace) -> int:
|
|
30
|
+
store, _, context, _, api = build_api(args)
|
|
31
|
+
instance_id = require_instance(args.instance_id, context)
|
|
32
|
+
render_output(args, api.switch_branch(instance_id, args.branch_name))
|
|
33
|
+
context.instance_id = instance_id
|
|
34
|
+
context.branch = args.branch_name
|
|
35
|
+
context.updated_at = now_iso()
|
|
36
|
+
store.save_context(context)
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def handle_branch_delete(args: argparse.Namespace) -> int:
|
|
41
|
+
_, _, context, _, api = build_api(args)
|
|
42
|
+
instance_id = require_instance(args.instance_id, context)
|
|
43
|
+
render_output(args, api.delete_branch(instance_id, args.branch_name))
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_branch_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
48
|
+
branch = subparsers.add_parser("branch", help="Branch commands.", description="Manage instance branches.")
|
|
49
|
+
branch_sub = branch.add_subparsers(dest="branch_command")
|
|
50
|
+
p = branch_sub.add_parser("list", help="List branches.", description="List branches for one instance.")
|
|
51
|
+
p.add_argument("--instance-id", help="Instance ID. Falls back to context.json.")
|
|
52
|
+
p.set_defaults(handler=handle_branch_list)
|
|
53
|
+
p = branch_sub.add_parser(
|
|
54
|
+
"create",
|
|
55
|
+
help="Create and switch a branch.",
|
|
56
|
+
description=(
|
|
57
|
+
"Create a branch through the platform API, then switch the sandbox to it. "
|
|
58
|
+
"If --source-branch is omitted, the saved context branch is used. The "
|
|
59
|
+
"returned `switched_to` value is the normalized branch slug; use that "
|
|
60
|
+
"slug for later commands such as `branch delete`."
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
p.add_argument("branch_name", help="Branch slug to create.")
|
|
64
|
+
p.add_argument("--instance-id", help="Instance ID. Falls back to context.json.")
|
|
65
|
+
p.add_argument("--source-branch", help="Source branch slug. Example: main")
|
|
66
|
+
p.set_defaults(handler=handle_branch_create)
|
|
67
|
+
p = branch_sub.add_parser(
|
|
68
|
+
"switch",
|
|
69
|
+
help="Switch the sandbox branch.",
|
|
70
|
+
description="Switch the sandbox branch and update context.json.",
|
|
71
|
+
)
|
|
72
|
+
p.add_argument("branch_name", help="Branch slug to switch to.")
|
|
73
|
+
p.add_argument("--instance-id", help="Instance ID. Falls back to context.json.")
|
|
74
|
+
p.set_defaults(handler=handle_branch_switch)
|
|
75
|
+
p = branch_sub.add_parser(
|
|
76
|
+
"delete",
|
|
77
|
+
help="Delete a branch.",
|
|
78
|
+
description=(
|
|
79
|
+
"Delete a branch from the instance. Pass the normalized branch slug, not "
|
|
80
|
+
"necessarily the raw input you originally used with `branch create`. For "
|
|
81
|
+
"example, `feature/foo` may need to be deleted as `feature-foo`."
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
p.add_argument("branch_name", help="Branch slug to delete.")
|
|
85
|
+
p.add_argument("--instance-id", help="Instance ID. Falls back to context.json.")
|
|
86
|
+
p.set_defaults(handler=handle_branch_delete)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .shared import asdict, build_store, render_output, resolve_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_config_list(args: argparse.Namespace) -> int:
|
|
9
|
+
store = build_store(args)
|
|
10
|
+
file_config = store.load_config()
|
|
11
|
+
effective = resolve_config(args, store)
|
|
12
|
+
render_output(
|
|
13
|
+
args,
|
|
14
|
+
{
|
|
15
|
+
"effective": asdict(effective),
|
|
16
|
+
"file": asdict(file_config),
|
|
17
|
+
"config_file": str(store.config_path),
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
return 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def handle_config_get(args: argparse.Namespace) -> int:
|
|
24
|
+
store = build_store(args)
|
|
25
|
+
effective = resolve_config(args, store)
|
|
26
|
+
value = getattr(effective, args.key.replace("-", "_"))
|
|
27
|
+
render_output(args, {args.key: value})
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def handle_config_set(args: argparse.Namespace) -> int:
|
|
32
|
+
store = build_store(args)
|
|
33
|
+
config = store.load_config()
|
|
34
|
+
setattr(config, args.key.replace("-", "_"), args.value)
|
|
35
|
+
store.save_config(config)
|
|
36
|
+
render_output(args, {"status": "saved", "config_file": str(store.config_path), args.key: args.value})
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def register_config_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
41
|
+
config = subparsers.add_parser("config", help="Read or update config.", description="Manage config.toml values.")
|
|
42
|
+
config_sub = config.add_subparsers(dest="config_command")
|
|
43
|
+
p = config_sub.add_parser(
|
|
44
|
+
"list",
|
|
45
|
+
help="List effective config.",
|
|
46
|
+
description=(
|
|
47
|
+
"Show effective config and file-backed values. Remember that command-line "
|
|
48
|
+
"flags such as `--json` override values saved in config.toml."
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
p.set_defaults(handler=handle_config_list)
|
|
52
|
+
p = config_sub.add_parser(
|
|
53
|
+
"get",
|
|
54
|
+
help="Get one config key.",
|
|
55
|
+
description=(
|
|
56
|
+
"Read one effective config key after applying flags, environment variables, "
|
|
57
|
+
"config.toml, and defaults. For example, passing `--json` still makes the "
|
|
58
|
+
"effective output value `json` even if config.toml says `text`."
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
p.add_argument("key", choices=["base_url", "anon_key", "output"])
|
|
62
|
+
p.set_defaults(handler=handle_config_get)
|
|
63
|
+
p = config_sub.add_parser(
|
|
64
|
+
"set",
|
|
65
|
+
help="Set one config key.",
|
|
66
|
+
description=(
|
|
67
|
+
"Write one value into config.toml. Later commands can still override the "
|
|
68
|
+
"saved value with command-line flags such as `--json`."
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
p.add_argument("key", choices=["base_url", "anon_key", "output"])
|
|
72
|
+
p.add_argument("value")
|
|
73
|
+
p.set_defaults(handler=handle_config_set)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .shared import asdict, build_store, now_iso, render_output, resolve_context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_context_show(args: argparse.Namespace) -> int:
|
|
9
|
+
store = build_store(args)
|
|
10
|
+
file_context = store.load_context()
|
|
11
|
+
effective = resolve_context(store)
|
|
12
|
+
render_output(
|
|
13
|
+
args,
|
|
14
|
+
{"effective": asdict(effective), "file": asdict(file_context), "context_file": str(store.context_path)},
|
|
15
|
+
)
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _save_context_update(
|
|
20
|
+
args: argparse.Namespace,
|
|
21
|
+
*,
|
|
22
|
+
instance_id: str | None = None,
|
|
23
|
+
org_id: str | None = None,
|
|
24
|
+
branch: str | None = None,
|
|
25
|
+
) -> int:
|
|
26
|
+
store = build_store(args)
|
|
27
|
+
context = store.load_context()
|
|
28
|
+
if instance_id is not None:
|
|
29
|
+
context.instance_id = instance_id
|
|
30
|
+
if org_id is not None:
|
|
31
|
+
context.org_id = org_id
|
|
32
|
+
if branch is not None:
|
|
33
|
+
context.branch = branch
|
|
34
|
+
context.updated_at = now_iso()
|
|
35
|
+
store.save_context(context)
|
|
36
|
+
render_output(args, {"status": "saved", "context": asdict(context), "context_file": str(store.context_path)})
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def handle_context_clear(args: argparse.Namespace) -> int:
|
|
41
|
+
store = build_store(args)
|
|
42
|
+
context = store.clear_context(instance=args.instance, org=args.org, branch=args.branch)
|
|
43
|
+
render_output(args, {"status": "cleared", "context": asdict(context), "context_file": str(store.context_path)})
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_context_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
48
|
+
context = subparsers.add_parser(
|
|
49
|
+
"context",
|
|
50
|
+
help="Manage default org, instance, and branch.",
|
|
51
|
+
description="Read or update ~/.config/powerbase/context.json for later commands.",
|
|
52
|
+
)
|
|
53
|
+
context_sub = context.add_subparsers(dest="context_command")
|
|
54
|
+
p = context_sub.add_parser("show", help="Show current context.", description="Show effective and saved context values.")
|
|
55
|
+
p.set_defaults(handler=handle_context_show)
|
|
56
|
+
p = context_sub.add_parser(
|
|
57
|
+
"use-instance",
|
|
58
|
+
help="Set the default instance.",
|
|
59
|
+
description=(
|
|
60
|
+
"Set the default instance used by later commands. The instance ID usually comes from "
|
|
61
|
+
"`powerbase instance list` or `powerbase context show`."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
p.add_argument("instance_id")
|
|
65
|
+
p.set_defaults(handler=lambda args: _save_context_update(args, instance_id=args.instance_id))
|
|
66
|
+
p = context_sub.add_parser("use-org", help="Set the default org.", description="Set the default organization ID.")
|
|
67
|
+
p.add_argument("org_id")
|
|
68
|
+
p.set_defaults(handler=lambda args: _save_context_update(args, org_id=args.org_id))
|
|
69
|
+
p = context_sub.add_parser(
|
|
70
|
+
"use-branch",
|
|
71
|
+
help="Set the default branch.",
|
|
72
|
+
description="Set the default branch slug, such as main or feature/my-change.",
|
|
73
|
+
)
|
|
74
|
+
p.add_argument("branch")
|
|
75
|
+
p.set_defaults(handler=lambda args: _save_context_update(args, branch=args.branch))
|
|
76
|
+
p = context_sub.add_parser(
|
|
77
|
+
"clear",
|
|
78
|
+
help="Clear saved context.",
|
|
79
|
+
description="Clear saved context values. If no flag is passed, all context values are cleared.",
|
|
80
|
+
)
|
|
81
|
+
p.add_argument("--instance", action="store_true", help="Clear only the saved instance_id.")
|
|
82
|
+
p.add_argument("--org", action="store_true", help="Clear only the saved org_id.")
|
|
83
|
+
p.add_argument("--branch", action="store_true", help="Clear only the saved branch.")
|
|
84
|
+
p.set_defaults(handler=handle_context_clear)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .shared import build_api, render_output
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_database_list(args: argparse.Namespace) -> int:
|
|
9
|
+
_, _, _, _, api = build_api(args)
|
|
10
|
+
render_output(args, api.list_databases())
|
|
11
|
+
return 0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def handle_database_get(args: argparse.Namespace) -> int:
|
|
15
|
+
_, _, _, _, api = build_api(args)
|
|
16
|
+
render_output(args, api.get_database(args.database_id))
|
|
17
|
+
return 0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def handle_database_create(args: argparse.Namespace) -> int:
|
|
21
|
+
_, _, _, _, api = build_api(args)
|
|
22
|
+
render_output(args, api.create_database(args.name, args.dsn, args.description, args.db_type))
|
|
23
|
+
return 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def handle_database_update(args: argparse.Namespace) -> int:
|
|
27
|
+
_, _, _, _, api = build_api(args)
|
|
28
|
+
render_output(
|
|
29
|
+
args,
|
|
30
|
+
api.update_database(args.database_id, args.name, args.dsn, args.description, args.db_type),
|
|
31
|
+
)
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def handle_database_delete(args: argparse.Namespace) -> int:
|
|
36
|
+
_, _, _, _, api = build_api(args)
|
|
37
|
+
render_output(args, api.delete_database(args.database_id))
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register_database_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
42
|
+
database = subparsers.add_parser(
|
|
43
|
+
"database",
|
|
44
|
+
help="Registered database connection commands.",
|
|
45
|
+
description=(
|
|
46
|
+
"Discover or manage reusable database connections used by instances. "
|
|
47
|
+
"This command group is the advanced bring-your-own-database path. "
|
|
48
|
+
"Use `powerbase instance create` without `--database-id` for the default "
|
|
49
|
+
"managed Powerbase database flow."
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
database_sub = database.add_subparsers(dest="database_command")
|
|
53
|
+
p = database_sub.add_parser(
|
|
54
|
+
"list",
|
|
55
|
+
help="List registered database connections.",
|
|
56
|
+
description=(
|
|
57
|
+
"List registered database connections visible to the current user. "
|
|
58
|
+
"Use this before `powerbase instance create --database-id ...` when you "
|
|
59
|
+
"intentionally want the advanced bring-your-own-database flow."
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
p.set_defaults(handler=handle_database_list)
|
|
63
|
+
p = database_sub.add_parser(
|
|
64
|
+
"get",
|
|
65
|
+
help="Get one registered database connection.",
|
|
66
|
+
description=(
|
|
67
|
+
"Show one registered database connection by ID. The database ID usually "
|
|
68
|
+
"comes from `powerbase database list`."
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
p.add_argument("database_id", help="Database ID from `powerbase database list`.")
|
|
72
|
+
p.set_defaults(handler=handle_database_get)
|
|
73
|
+
p = database_sub.add_parser(
|
|
74
|
+
"create",
|
|
75
|
+
help="Register an existing database connection.",
|
|
76
|
+
description=(
|
|
77
|
+
"Register an existing database DSN for advanced bring-your-own-database "
|
|
78
|
+
"workflows. This does not provision a new physical database. Use the "
|
|
79
|
+
"returned ID with `powerbase instance create --database-id ...`. Prefer "
|
|
80
|
+
"plain `powerbase instance create` for the default managed Powerbase "
|
|
81
|
+
"database flow."
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
p.add_argument("--name", required=True, help="Database display name.")
|
|
85
|
+
p.add_argument(
|
|
86
|
+
"--dsn",
|
|
87
|
+
required=True,
|
|
88
|
+
help=(
|
|
89
|
+
"Existing database DSN or connection string. Treat this as sensitive: "
|
|
90
|
+
"credentials are sent to and stored by the Powerbase platform."
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
p.add_argument("--description", help="Optional description shown in the console.")
|
|
94
|
+
p.add_argument(
|
|
95
|
+
"--db-type",
|
|
96
|
+
help=(
|
|
97
|
+
"Optional database type, such as mysql. Advanced bring-your-own-database "
|
|
98
|
+
"flows may require SeekDB-compatible behavior for branch operations."
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
p.set_defaults(handler=handle_database_create)
|
|
102
|
+
p = database_sub.add_parser(
|
|
103
|
+
"update",
|
|
104
|
+
help="Update one registered database connection.",
|
|
105
|
+
description=(
|
|
106
|
+
"Update one registered database connection by ID. Pass only the fields "
|
|
107
|
+
"you want to change."
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
p.add_argument("database_id", help="Database ID from `powerbase database list`.")
|
|
111
|
+
p.add_argument("--name", help="Updated database display name.")
|
|
112
|
+
p.add_argument(
|
|
113
|
+
"--dsn",
|
|
114
|
+
help="Updated DSN or connection string for an existing external database.",
|
|
115
|
+
)
|
|
116
|
+
p.add_argument("--description", help="Updated description.")
|
|
117
|
+
p.add_argument("--db-type", help="Updated database type.")
|
|
118
|
+
p.set_defaults(handler=handle_database_update)
|
|
119
|
+
p = database_sub.add_parser(
|
|
120
|
+
"delete",
|
|
121
|
+
help="Delete one registered database connection.",
|
|
122
|
+
description=(
|
|
123
|
+
"Delete one registered database connection by ID. This removes the "
|
|
124
|
+
"Powerbase-side record only; it does not delete the physical database. "
|
|
125
|
+
"Discover IDs with `powerbase database list` first."
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
p.add_argument("database_id", help="Database ID from `powerbase database list`.")
|
|
129
|
+
p.set_defaults(handler=handle_database_delete)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .shared import build_api, render_output, require_instance
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_instance_list(args: argparse.Namespace) -> int:
|
|
9
|
+
_, _, _, _, api = build_api(args)
|
|
10
|
+
render_output(args, api.list_instances())
|
|
11
|
+
return 0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def handle_instance_get(args: argparse.Namespace) -> int:
|
|
15
|
+
_, _, context, _, api = build_api(args)
|
|
16
|
+
render_output(args, api.get_instance(require_instance(args.instance_id, context)))
|
|
17
|
+
return 0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def handle_instance_create(args: argparse.Namespace) -> int:
|
|
21
|
+
_, _, _, _, api = build_api(args)
|
|
22
|
+
render_output(args, api.create_instance(args.name, args.database_id, args.org_id))
|
|
23
|
+
return 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def handle_instance_delete(args: argparse.Namespace) -> int:
|
|
27
|
+
_, _, context, _, api = build_api(args)
|
|
28
|
+
render_output(args, api.delete_instance(require_instance(args.instance_id, context)))
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def register_instance_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
33
|
+
instance = subparsers.add_parser(
|
|
34
|
+
"instance",
|
|
35
|
+
help="Instance commands.",
|
|
36
|
+
description=(
|
|
37
|
+
"Read or change Powerbase instances. By default, `powerbase instance create` "
|
|
38
|
+
"uses the managed Powerbase database flow. Pass `--database-id` only when "
|
|
39
|
+
"you intentionally want the advanced bring-your-own-database path."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
instance_sub = instance.add_subparsers(dest="instance_command")
|
|
43
|
+
p = instance_sub.add_parser("list", help="List instances.", description="List instances visible to the current user.")
|
|
44
|
+
p.set_defaults(handler=handle_instance_list)
|
|
45
|
+
p = instance_sub.add_parser(
|
|
46
|
+
"get",
|
|
47
|
+
help="Get one instance.",
|
|
48
|
+
description="Show one instance. If --instance-id is omitted, the saved context instance_id is used.",
|
|
49
|
+
)
|
|
50
|
+
p.add_argument("--instance-id", help="Instance ID. Source: `powerbase instance list` or `powerbase context show`.")
|
|
51
|
+
p.set_defaults(handler=handle_instance_get)
|
|
52
|
+
p = instance_sub.add_parser(
|
|
53
|
+
"create",
|
|
54
|
+
help="Create an instance.",
|
|
55
|
+
description=(
|
|
56
|
+
"Create a new instance. Default behavior: if `--database-id` is omitted, "
|
|
57
|
+
"Powerbase creates or attaches the managed platform database for the "
|
|
58
|
+
"instance. Advanced behavior: pass `--database-id` to bind the instance "
|
|
59
|
+
"to an already registered external database connection. The create call "
|
|
60
|
+
"returns as soon as provisioning starts; `instance get`, `branch list`, "
|
|
61
|
+
"and sandbox operations may need a short retry window before the new "
|
|
62
|
+
"instance becomes fully readable."
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
p.add_argument("--name", help="Optional instance name.")
|
|
66
|
+
p.add_argument(
|
|
67
|
+
"--database-id",
|
|
68
|
+
help=(
|
|
69
|
+
"Optional registered database ID for the advanced bring-your-own-database "
|
|
70
|
+
"flow. Omit this to use the default managed Powerbase database flow."
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
p.add_argument("--org-id", help="Optional organization ID.")
|
|
74
|
+
p.set_defaults(handler=handle_instance_create)
|
|
75
|
+
p = instance_sub.add_parser(
|
|
76
|
+
"delete",
|
|
77
|
+
help="Delete an instance.",
|
|
78
|
+
description="Delete one instance. If --instance-id is omitted, the saved context instance_id is used.",
|
|
79
|
+
)
|
|
80
|
+
p.add_argument("--instance-id", help="Instance ID to delete.")
|
|
81
|
+
p.set_defaults(handler=handle_instance_delete)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .shared import build_api, render_output
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_org_list(args: argparse.Namespace) -> int:
|
|
9
|
+
_, _, _, _, api = build_api(args)
|
|
10
|
+
render_output(args, api.list_orgs())
|
|
11
|
+
return 0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_org_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
15
|
+
org = subparsers.add_parser("org", help="Organization commands.", description="Read Powerbase organizations.")
|
|
16
|
+
org_sub = org.add_subparsers(dest="org_command")
|
|
17
|
+
p = org_sub.add_parser("list", help="List organizations.", description="List organizations available to the current user.")
|
|
18
|
+
p.set_defaults(handler=handle_org_list)
|