bevor-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.
- bevor_cli/__init__.py +0 -0
- bevor_cli/annotations/__init__.py +0 -0
- bevor_cli/annotations/confirmations.py +16 -0
- bevor_cli/annotations/query.py +25 -0
- bevor_cli/apps/agent.py +10 -0
- bevor_cli/apps/analyses.py +47 -0
- bevor_cli/apps/chat.py +16 -0
- bevor_cli/apps/codes.py +18 -0
- bevor_cli/apps/findings.py +22 -0
- bevor_cli/apps/main.py +61 -0
- bevor_cli/apps/nodes.py +17 -0
- bevor_cli/apps/projects.py +14 -0
- bevor_cli/apps/sessions.py +13 -0
- bevor_cli/apps/workspaces.py +16 -0
- bevor_cli/client.py +157 -0
- bevor_cli/commands/__init__.py +0 -0
- bevor_cli/commands/agent/__init__.py +0 -0
- bevor_cli/commands/agent/materialze_cmd.py +65 -0
- bevor_cli/commands/agent/setup_cmd.py +18 -0
- bevor_cli/commands/analyses/__init__.py +0 -0
- bevor_cli/commands/analyses/active.py +42 -0
- bevor_cli/commands/analyses/chats/__init__.py +0 -0
- bevor_cli/commands/analyses/chats/get.py +39 -0
- bevor_cli/commands/analyses/chats/list.py +64 -0
- bevor_cli/commands/analyses/chats/messages.py +41 -0
- bevor_cli/commands/analyses/chats/new.py +44 -0
- bevor_cli/commands/analyses/chats/resume.py +43 -0
- bevor_cli/commands/analyses/chats/session.py +295 -0
- bevor_cli/commands/analyses/child.py +81 -0
- bevor_cli/commands/analyses/commit.py +49 -0
- bevor_cli/commands/analyses/create.py +105 -0
- bevor_cli/commands/analyses/delete.py +65 -0
- bevor_cli/commands/analyses/diff.py +87 -0
- bevor_cli/commands/analyses/entrypoints.py +56 -0
- bevor_cli/commands/analyses/findings.py +129 -0
- bevor_cli/commands/analyses/fork.py +47 -0
- bevor_cli/commands/analyses/list.py +85 -0
- bevor_cli/commands/analyses/merge.py +46 -0
- bevor_cli/commands/analyses/retry.py +77 -0
- bevor_cli/commands/analyses/run.py +87 -0
- bevor_cli/commands/analyses/select.py +85 -0
- bevor_cli/commands/analyses/set_head.py +45 -0
- bevor_cli/commands/analyses/transitions.py +90 -0
- bevor_cli/commands/analyses/view.py +94 -0
- bevor_cli/commands/auth/__init__.py +7 -0
- bevor_cli/commands/auth/login.py +135 -0
- bevor_cli/commands/auth/logout.py +23 -0
- bevor_cli/commands/auth/whoami.py +21 -0
- bevor_cli/commands/codes/__init__.py +0 -0
- bevor_cli/commands/codes/list.py +90 -0
- bevor_cli/commands/codes/push.py +76 -0
- bevor_cli/commands/codes/select.py +93 -0
- bevor_cli/commands/codes/view.py +80 -0
- bevor_cli/commands/context/__init__.py +92 -0
- bevor_cli/commands/findings/__init__.py +0 -0
- bevor_cli/commands/findings/acknowledge.py +28 -0
- bevor_cli/commands/findings/add.py +236 -0
- bevor_cli/commands/findings/bulk.py +110 -0
- bevor_cli/commands/findings/delete.py +28 -0
- bevor_cli/commands/findings/edit.py +109 -0
- bevor_cli/commands/findings/get.py +62 -0
- bevor_cli/commands/findings/interactive.py +329 -0
- bevor_cli/commands/findings/list.py +108 -0
- bevor_cli/commands/findings/revert.py +28 -0
- bevor_cli/commands/graph/__init__.py +0 -0
- bevor_cli/commands/graph/call_chain.py +48 -0
- bevor_cli/commands/graph/edges.py +59 -0
- bevor_cli/commands/graph/interactive.py +207 -0
- bevor_cli/commands/graph/renderer.py +135 -0
- bevor_cli/commands/link/__init__.py +52 -0
- bevor_cli/commands/link/helpers.py +26 -0
- bevor_cli/commands/nodes/__init__.py +0 -0
- bevor_cli/commands/nodes/get.py +52 -0
- bevor_cli/commands/nodes/list.py +75 -0
- bevor_cli/commands/nodes/source.py +40 -0
- bevor_cli/commands/projects/__init__.py +0 -0
- bevor_cli/commands/projects/create.py +54 -0
- bevor_cli/commands/projects/list.py +57 -0
- bevor_cli/commands/projects/select.py +75 -0
- bevor_cli/commands/projects/view.py +49 -0
- bevor_cli/commands/sessions/__init__.py +0 -0
- bevor_cli/commands/sessions/list.py +36 -0
- bevor_cli/commands/sessions/revoke.py +35 -0
- bevor_cli/commands/workspaces/__init__.py +0 -0
- bevor_cli/commands/workspaces/list.py +50 -0
- bevor_cli/commands/workspaces/pick.py +20 -0
- bevor_cli/commands/workspaces/select.py +68 -0
- bevor_cli/commands/workspaces/view.py +48 -0
- bevor_cli/configuration/creds.py +52 -0
- bevor_cli/configuration/settings.py +54 -0
- bevor_cli/configuration/state.py +113 -0
- bevor_cli/findings_yaml.py +291 -0
- bevor_cli/main.py +33 -0
- bevor_cli/output.py +69 -0
- bevor_cli/select.py +64 -0
- bevor_cli/settings.py +4 -0
- bevor_cli-0.1.0.dist-info/METADATA +14 -0
- bevor_cli-0.1.0.dist-info/RECORD +100 -0
- bevor_cli-0.1.0.dist-info/WHEEL +4 -0
- bevor_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""``bevor code node get`` — fetch a single graph node."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from bevor_sdk.types import ID
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
11
|
+
from bevor_cli.client import BevorInternalClient
|
|
12
|
+
from bevor_cli.configuration.creds import Credentials
|
|
13
|
+
from bevor_cli.configuration.state import State
|
|
14
|
+
from bevor_cli.output import console
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_node(
|
|
18
|
+
node_id: str = typer.Argument(..., help="Node short id (or full wire id)."),
|
|
19
|
+
code_id: Optional[str] = typer.Option(
|
|
20
|
+
None, "--code", help="Code id to list for (defaults to current configuration)"
|
|
21
|
+
),
|
|
22
|
+
json_out: JSON_OUT = False,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Get the details of a node. Uses the currently configured code version, or you can pass --code."""
|
|
25
|
+
creds = Credentials.load()
|
|
26
|
+
creds.require_token()
|
|
27
|
+
|
|
28
|
+
state = State.load()
|
|
29
|
+
workspace_id = state.validate_workspace()
|
|
30
|
+
code_id = state.validate_code(priority=code_id)
|
|
31
|
+
|
|
32
|
+
client = BevorInternalClient(
|
|
33
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
node = client.codes.nodes.get(code_id, ID(node_id))
|
|
37
|
+
|
|
38
|
+
if json_out:
|
|
39
|
+
typer.echo(json.dumps(node, indent=2))
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
43
|
+
table.add_column(style="bold")
|
|
44
|
+
table.add_column()
|
|
45
|
+
|
|
46
|
+
table.add_row("ID", str(node.id))
|
|
47
|
+
table.add_row("Short id", str(node.short_id))
|
|
48
|
+
table.add_row("Name", node.name)
|
|
49
|
+
table.add_row("Type", str(node.node_type))
|
|
50
|
+
table.add_row("File", f"{node.path} ({node.file_id})")
|
|
51
|
+
|
|
52
|
+
console.print(table)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""``bevor code node list`` — list graph nodes for a code version."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from bevor_sdk.types import ID
|
|
7
|
+
from bevor_sdk.types.requests.graph import NodeQueryParams
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from bevor_cli import output
|
|
11
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
12
|
+
from bevor_cli.client import BevorInternalClient
|
|
13
|
+
from bevor_cli.configuration.creds import Credentials
|
|
14
|
+
from bevor_cli.configuration.state import State
|
|
15
|
+
from bevor_cli.output import console
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def list_nodes(
|
|
19
|
+
code_id: Optional[str] = typer.Option(
|
|
20
|
+
None, "--code", help="Code id to list for (defaults to current configuration)"
|
|
21
|
+
),
|
|
22
|
+
entrypoints_only: bool = typer.Option(
|
|
23
|
+
False,
|
|
24
|
+
"--entrypoints",
|
|
25
|
+
help="Only show auditable entry-point functions.",
|
|
26
|
+
),
|
|
27
|
+
file_id: Optional[str] = typer.Option(None, "--file", help="Filter to nodes in this file."),
|
|
28
|
+
no_deps: Optional[bool] = typer.Option(
|
|
29
|
+
None, "--no-deps", help="Filter to nodes under this directory prefix."
|
|
30
|
+
),
|
|
31
|
+
name: Optional[str] = typer.Option(None, "--name", help="Filter to nodes by name"),
|
|
32
|
+
node_type: Optional[str] = typer.Option(None, "--node-type", help="Filter to nodes by type"),
|
|
33
|
+
json_out: JSON_OUT = False,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""List all nodes. Uses the currently configured code version, or you can pass --code."""
|
|
36
|
+
creds = Credentials.load()
|
|
37
|
+
creds.require_token()
|
|
38
|
+
|
|
39
|
+
state = State.load()
|
|
40
|
+
workspace_id = state.validate_workspace()
|
|
41
|
+
code_id = state.validate_code(priority=code_id)
|
|
42
|
+
|
|
43
|
+
client = BevorInternalClient(
|
|
44
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
nodes = client.codes.nodes.list(
|
|
48
|
+
code_id,
|
|
49
|
+
NodeQueryParams(
|
|
50
|
+
file_id=ID(file_id) if file_id else None,
|
|
51
|
+
entrypoint_only=entrypoints_only,
|
|
52
|
+
no_deps=no_deps,
|
|
53
|
+
name=name,
|
|
54
|
+
node_type=node_type,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if json_out:
|
|
59
|
+
console.print_json(nodes.model_dump_json(indent=2))
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
if not nodes.results:
|
|
63
|
+
output.error("No nodes found")
|
|
64
|
+
raise typer.Exit(1)
|
|
65
|
+
|
|
66
|
+
table = Table(title=f"Nodes — version {state.code_id}", show_header=True, header_style="bold")
|
|
67
|
+
table.add_column("Short id", style="dim", no_wrap=True)
|
|
68
|
+
table.add_column("Name")
|
|
69
|
+
table.add_column("Type", style="cyan")
|
|
70
|
+
table.add_column("File", style="dim")
|
|
71
|
+
table.add_column("EP", justify="center")
|
|
72
|
+
|
|
73
|
+
for n in nodes.results:
|
|
74
|
+
table.add_row(n.short_id, n.name, n.node_type, n.path, "⚡" if n.is_entrypoint else "")
|
|
75
|
+
console.print(table)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""``bevor code node content`` — show source for a graph node."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from bevor_sdk.types import ID
|
|
7
|
+
from rich.syntax import Syntax
|
|
8
|
+
|
|
9
|
+
from bevor_cli import output
|
|
10
|
+
from bevor_cli.client import BevorInternalClient
|
|
11
|
+
from bevor_cli.configuration.creds import Credentials
|
|
12
|
+
from bevor_cli.configuration.state import State
|
|
13
|
+
from bevor_cli.output import console
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def show_source(
|
|
17
|
+
node_id: str = typer.Argument(..., help="Node short id (or full wire id)."),
|
|
18
|
+
code_id: Optional[str] = typer.Option(
|
|
19
|
+
None, "--code", help="Code id to list for (defaults to current configuration)"
|
|
20
|
+
),
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Show the source code of a node. Uses the currently configured code version, or you can pass --code."""
|
|
23
|
+
creds = Credentials.load()
|
|
24
|
+
creds.require_token()
|
|
25
|
+
|
|
26
|
+
state = State.load()
|
|
27
|
+
workspace_id = state.validate_workspace()
|
|
28
|
+
code_id = state.validate_code(priority=code_id)
|
|
29
|
+
|
|
30
|
+
client = BevorInternalClient(
|
|
31
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
content = client.codes.nodes.content(code_id, ID(node_id))
|
|
35
|
+
|
|
36
|
+
if not content:
|
|
37
|
+
output.error("No source content available.")
|
|
38
|
+
raise typer.Exit(1)
|
|
39
|
+
|
|
40
|
+
console.print(Syntax(content.strip(), "solidity", theme="monokai", line_numbers=False, padding=1))
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Optional, get_args
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
import typer
|
|
5
|
+
from bevor_sdk.types.literals import ProjectKind
|
|
6
|
+
from bevor_sdk.types.requests.manage import CreateProjectBodyDict
|
|
7
|
+
from bevor_sdk.types.responses.manage import ProjectSchema
|
|
8
|
+
from rich.prompt import Confirm, Prompt
|
|
9
|
+
|
|
10
|
+
from bevor_cli import output
|
|
11
|
+
from bevor_cli.annotations.confirmations import YES
|
|
12
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
13
|
+
from bevor_cli.configuration.creds import Credentials
|
|
14
|
+
from bevor_cli.configuration.state import State
|
|
15
|
+
from bevor_cli.select import _select
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_project(
|
|
19
|
+
name: Optional[str] = typer.Option(None, "--name", help="project name"),
|
|
20
|
+
description: Optional[str] = typer.Option(None, "--description", help="project description"),
|
|
21
|
+
kind: Optional[ProjectKind] = typer.Option(None, "--kind", help="project kind"),
|
|
22
|
+
yes: YES = False,
|
|
23
|
+
) -> ProjectSchema:
|
|
24
|
+
"""Create a project. Can pass arguments, or do so interactively."""
|
|
25
|
+
creds = Credentials.load()
|
|
26
|
+
creds.require_token()
|
|
27
|
+
|
|
28
|
+
state = State.load()
|
|
29
|
+
workspace_id = state.validate_workspace()
|
|
30
|
+
|
|
31
|
+
client = BevorInternalBusinessClient(creds=creds, workspace_id=workspace_id)
|
|
32
|
+
|
|
33
|
+
if name is None:
|
|
34
|
+
name = Prompt.ask("Project name")
|
|
35
|
+
if description is None:
|
|
36
|
+
description = Prompt.ask("Project description (optional)", default="")
|
|
37
|
+
if kind is None:
|
|
38
|
+
choices = [questionary.Choice(title=p_kind, value=p_kind) for p_kind in get_args(ProjectKind)]
|
|
39
|
+
kind = _select("Project Kind", choices=choices)
|
|
40
|
+
|
|
41
|
+
if not yes:
|
|
42
|
+
# Add this so that it's obvious which workspace they're creating the project under. Can override.
|
|
43
|
+
workspace = client.get_workspace()
|
|
44
|
+
if not Confirm.ask(f"Create project '{name}' under workspace '{workspace.name}'?"):
|
|
45
|
+
raise typer.Exit(0)
|
|
46
|
+
|
|
47
|
+
project = client.create_project(
|
|
48
|
+
CreateProjectBodyDict(name=name, description=description, kind=kind)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
state.set_project(project.id)
|
|
52
|
+
state.save()
|
|
53
|
+
output.success(f"Created project: {project.id} {project.name}")
|
|
54
|
+
return project
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""``bevor projects list``."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, List
|
|
5
|
+
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
10
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
11
|
+
from bevor_cli.configuration.creds import Credentials
|
|
12
|
+
from bevor_cli.configuration.state import State
|
|
13
|
+
from bevor_cli.output import console
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def list_projects(
|
|
17
|
+
json_out: JSON_OUT = False,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""List projects in the configured workspace."""
|
|
20
|
+
creds = Credentials.load()
|
|
21
|
+
creds.require_token()
|
|
22
|
+
|
|
23
|
+
state = State.load()
|
|
24
|
+
workspace_id = state.validate_workspace()
|
|
25
|
+
|
|
26
|
+
client = BevorInternalBusinessClient(creds=creds, workspace_id=workspace_id)
|
|
27
|
+
|
|
28
|
+
projects = client.list_projects()
|
|
29
|
+
|
|
30
|
+
if json_out:
|
|
31
|
+
console.print_json(json.dumps(projects.model_dump(mode="json"), indent=2))
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if not projects.results:
|
|
35
|
+
console.print("No projects found.")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
table = Table("NAME", "ID", "CODES", "ANALYSES", "CREATED")
|
|
39
|
+
table.columns[2].justify = "right"
|
|
40
|
+
table.columns[3].justify = "right"
|
|
41
|
+
|
|
42
|
+
for p in projects.results:
|
|
43
|
+
is_active = p.id == state.project_id
|
|
44
|
+
|
|
45
|
+
row: List[Any] = [
|
|
46
|
+
p.name,
|
|
47
|
+
p.id,
|
|
48
|
+
str(p.n_codes),
|
|
49
|
+
str(p.n_analyses),
|
|
50
|
+
str(p.created_at.date()),
|
|
51
|
+
]
|
|
52
|
+
if is_active:
|
|
53
|
+
row.append(Text("●", style="bold green"))
|
|
54
|
+
|
|
55
|
+
table.add_row(*row, style="bold green" if is_active else None)
|
|
56
|
+
|
|
57
|
+
console.print(table)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""``bevor projects select`` — set the selected project."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
import typer
|
|
7
|
+
from bevor_sdk.types.responses.manage import ProjectSchema
|
|
8
|
+
|
|
9
|
+
from bevor_cli import output
|
|
10
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
11
|
+
from bevor_cli.configuration.creds import Credentials
|
|
12
|
+
from bevor_cli.configuration.state import State
|
|
13
|
+
from bevor_cli.output import console
|
|
14
|
+
from bevor_cli.select import _CANCEL, _select
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def select_project(
|
|
18
|
+
project_id: Optional[str] = typer.Argument(
|
|
19
|
+
None, help="Project id to select. Prompts with a list when omitted."
|
|
20
|
+
),
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Select a project by id, or pick one from a list."""
|
|
23
|
+
creds = Credentials.load()
|
|
24
|
+
creds.require_token()
|
|
25
|
+
|
|
26
|
+
state = State.load()
|
|
27
|
+
workspace_id = state.validate_workspace()
|
|
28
|
+
|
|
29
|
+
client = BevorInternalBusinessClient(creds=creds, workspace_id=workspace_id)
|
|
30
|
+
|
|
31
|
+
projects = client.list_projects()
|
|
32
|
+
|
|
33
|
+
if not projects.results:
|
|
34
|
+
console.print("No projects found.")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if project_id:
|
|
38
|
+
if not any(p.id == project_id for p in projects.results):
|
|
39
|
+
output.error("project_id does not exist")
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
|
|
42
|
+
if state.project_id != project_id:
|
|
43
|
+
state.set_project(project_id)
|
|
44
|
+
state.save()
|
|
45
|
+
output.info("Updated bevor configuration")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
if len(projects.results) == 1:
|
|
49
|
+
if projects.results[0].id == state.project_id:
|
|
50
|
+
output.info("only 1 project exists, skipping")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
state.set_project(projects.results[0].id)
|
|
54
|
+
state.save()
|
|
55
|
+
output.info("only 1 project exists, updating your bevor context automatically")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
choices = [
|
|
59
|
+
questionary.Choice(
|
|
60
|
+
title=f"{p.name} {p.id}" + (" ●" if p.id == state.project_id else ""),
|
|
61
|
+
value=p,
|
|
62
|
+
)
|
|
63
|
+
for p in projects.results
|
|
64
|
+
] + [questionary.Choice(title="↵ Cancel", value=_CANCEL)]
|
|
65
|
+
|
|
66
|
+
project: ProjectSchema | None = _select("Select a project:", choices)
|
|
67
|
+
if project is None:
|
|
68
|
+
raise typer.Exit(0)
|
|
69
|
+
|
|
70
|
+
if project.id != state.project_id:
|
|
71
|
+
output.info("Clearing active code version and analysis (different project).")
|
|
72
|
+
state.set_project(project.id)
|
|
73
|
+
|
|
74
|
+
state.save()
|
|
75
|
+
console.print(f"Active project: {project.name} ({project.id})")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""``bevor projects view`` — show a project (the selected one by default)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
9
|
+
from bevor_cli.client import BevorInternalClient
|
|
10
|
+
from bevor_cli.configuration.creds import Credentials
|
|
11
|
+
from bevor_cli.configuration.state import State
|
|
12
|
+
from bevor_cli.output import console
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def view_project(
|
|
16
|
+
project_id: Optional[str] = typer.Argument(
|
|
17
|
+
None, help="Project id. Defaults to the selected project."
|
|
18
|
+
),
|
|
19
|
+
json_out: JSON_OUT = False,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Print details for a project in the selected workspace, or the selected project."""
|
|
22
|
+
creds = Credentials.load()
|
|
23
|
+
creds.require_token()
|
|
24
|
+
|
|
25
|
+
state = State.load()
|
|
26
|
+
workspace_id = state.validate_workspace()
|
|
27
|
+
project_id = state.validate_project(project_id)
|
|
28
|
+
|
|
29
|
+
client = BevorInternalClient(creds=creds, workspace_id=workspace_id, project_id=project_id)
|
|
30
|
+
|
|
31
|
+
project = client.projects.get()
|
|
32
|
+
|
|
33
|
+
if json_out:
|
|
34
|
+
console.print_json(project.model_dump_json(indent=2))
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
38
|
+
table.add_column(style="bold")
|
|
39
|
+
table.add_column()
|
|
40
|
+
|
|
41
|
+
table.add_row("Name", project.name)
|
|
42
|
+
table.add_row("ID", project.id)
|
|
43
|
+
table.add_row("Description", project.description or "")
|
|
44
|
+
table.add_row("Codes", str(project.n_codes))
|
|
45
|
+
table.add_row("Analyses", str(project.n_analyses))
|
|
46
|
+
table.add_row("Created by", project.created_by_user.username)
|
|
47
|
+
table.add_row("Created", str(project.created_at.date()))
|
|
48
|
+
|
|
49
|
+
console.print(table)
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""``bevor sessions list``."""
|
|
2
|
+
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
6
|
+
from bevor_cli.configuration.creds import Credentials
|
|
7
|
+
from bevor_cli.output import console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def sessions_list() -> None:
|
|
11
|
+
"""List active CLI sessions."""
|
|
12
|
+
creds = Credentials.load()
|
|
13
|
+
creds.require_token()
|
|
14
|
+
|
|
15
|
+
client = BevorInternalBusinessClient(creds=creds)
|
|
16
|
+
|
|
17
|
+
data = client.get("/v1/cli-tokens").json()
|
|
18
|
+
sessions = data.get("results", [])
|
|
19
|
+
|
|
20
|
+
if not sessions:
|
|
21
|
+
console.print("No active CLI sessions.")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
table = Table("ID", "Device", "IP", "Last Used", "Expires", "Current Device")
|
|
25
|
+
|
|
26
|
+
for s in sessions:
|
|
27
|
+
args = [
|
|
28
|
+
s["id"],
|
|
29
|
+
s["device_name"],
|
|
30
|
+
s["ip_address"],
|
|
31
|
+
s["last_used_at"],
|
|
32
|
+
s["expires_at"],
|
|
33
|
+
str(s["is_current"]),
|
|
34
|
+
]
|
|
35
|
+
table.add_row(*args)
|
|
36
|
+
console.print(table)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""``bevor sessions revoke``."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from bevor_cli import output
|
|
6
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
7
|
+
from bevor_cli.configuration.creds import Credentials
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def sessions_revoke(
|
|
11
|
+
token_id: str = typer.Argument(
|
|
12
|
+
...,
|
|
13
|
+
help="CLI session id to revoke (from `bevor sessions`).",
|
|
14
|
+
),
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Revoke a CLI session by id."""
|
|
17
|
+
|
|
18
|
+
creds = Credentials.load()
|
|
19
|
+
creds.require_token()
|
|
20
|
+
|
|
21
|
+
if token_id == creds.token:
|
|
22
|
+
output.error(
|
|
23
|
+
"Cannot revoke this session since it's your current one.",
|
|
24
|
+
hint="Run `bevor logout` instead.",
|
|
25
|
+
)
|
|
26
|
+
raise typer.Exit(1)
|
|
27
|
+
|
|
28
|
+
client = BevorInternalBusinessClient(creds=creds)
|
|
29
|
+
|
|
30
|
+
res = client.delete(f"/v1/cli-tokens/revoke/{token_id}").json()
|
|
31
|
+
output.success(f"Revoked CLI session {token_id}.")
|
|
32
|
+
|
|
33
|
+
if res["is_current"]:
|
|
34
|
+
creds.delete()
|
|
35
|
+
output.success("Logged out.")
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""``bevor workspace list`` — list workspaces."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, List
|
|
5
|
+
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
10
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
11
|
+
from bevor_cli.configuration.creds import Credentials
|
|
12
|
+
from bevor_cli.configuration.state import State
|
|
13
|
+
from bevor_cli.output import console
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def list_workspaces(
|
|
17
|
+
json_out: JSON_OUT = False,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""List workspaces you belong to."""
|
|
20
|
+
creds = Credentials.load()
|
|
21
|
+
creds.require_token()
|
|
22
|
+
|
|
23
|
+
state = State.load()
|
|
24
|
+
|
|
25
|
+
workspaces = BevorInternalBusinessClient(creds=creds).list_workspaces()
|
|
26
|
+
|
|
27
|
+
if json_out:
|
|
28
|
+
console.print_json(json.dumps(workspaces.model_dump(mode="json"), indent=2))
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
if not workspaces.results:
|
|
32
|
+
console.print("No workspaces found.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
table = Table("ID", "Name", "N Projects", "Role", "Created")
|
|
36
|
+
|
|
37
|
+
for workspace in workspaces.results:
|
|
38
|
+
args: List[Any] = [
|
|
39
|
+
workspace.id,
|
|
40
|
+
workspace.name,
|
|
41
|
+
str(workspace.n_projects),
|
|
42
|
+
workspace.role,
|
|
43
|
+
str(workspace.created_at.date()),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
if workspace.id == state.workspace_id:
|
|
47
|
+
args.append(Text("●", style="bold green"))
|
|
48
|
+
table.add_row(*args)
|
|
49
|
+
|
|
50
|
+
console.print(table)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Interactive workspace picker (shared with ``bevor link``)."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
import typer
|
|
7
|
+
from bevor_sdk.types.responses.manage import WorkspaceDetailedSchema
|
|
8
|
+
|
|
9
|
+
from bevor_cli.select import _select
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def pick_workspace(workspaces: List[WorkspaceDetailedSchema]) -> WorkspaceDetailedSchema:
|
|
13
|
+
choices = [
|
|
14
|
+
questionary.Choice(title=f"{workspace.name} {workspace.id}", value=workspace)
|
|
15
|
+
for workspace in workspaces
|
|
16
|
+
]
|
|
17
|
+
chosen: WorkspaceDetailedSchema | None = _select("Select a workspace:", choices)
|
|
18
|
+
if chosen is None:
|
|
19
|
+
raise typer.Exit(0)
|
|
20
|
+
return chosen
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""``bevor workspaces select`` — set the selected workspace."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from bevor_cli import output
|
|
8
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
9
|
+
from bevor_cli.configuration.creds import Credentials
|
|
10
|
+
from bevor_cli.configuration.state import State
|
|
11
|
+
from bevor_cli.output import console
|
|
12
|
+
|
|
13
|
+
from .pick import pick_workspace
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def select_workspace(
|
|
17
|
+
workspace_id: Optional[str] = typer.Argument(
|
|
18
|
+
None, help="Workspace id to select. Prompts with a list when omitted."
|
|
19
|
+
),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Select a workspace by id, or pick one from a list."""
|
|
22
|
+
|
|
23
|
+
creds = Credentials.load()
|
|
24
|
+
creds.require_token()
|
|
25
|
+
|
|
26
|
+
state = State.load()
|
|
27
|
+
|
|
28
|
+
if workspace_id and state.workspace_id == workspace_id:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
workspaces = BevorInternalBusinessClient(creds=creds).list_workspaces()
|
|
32
|
+
|
|
33
|
+
if not workspaces.results:
|
|
34
|
+
console.print("No workspaces found.")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if workspace_id:
|
|
38
|
+
if not any(workspace.id == workspace_id for workspace in workspaces.results):
|
|
39
|
+
output.error(
|
|
40
|
+
"passed workspace does not exist",
|
|
41
|
+
hint="run `bevor workspaces list` to see available ids.",
|
|
42
|
+
)
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
|
|
45
|
+
state.set_workspace(workspace_id)
|
|
46
|
+
state.save()
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if len(workspaces.results) == 1:
|
|
50
|
+
if workspaces.results[0].id == state.workspace_id:
|
|
51
|
+
output.info("only 1 workspace exists, skipping")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
state.set_workspace(workspaces.results[0].id)
|
|
55
|
+
state.save()
|
|
56
|
+
output.info("only 1 workspace exists, updating your bevor context automatically")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
workspace = pick_workspace(workspaces.results)
|
|
60
|
+
|
|
61
|
+
if state.workspace_id == workspace.id:
|
|
62
|
+
output.info("no changes to your current bevor context.")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
state.set_workspace(workspace.id)
|
|
66
|
+
state.save()
|
|
67
|
+
|
|
68
|
+
output.success(f"Active workspace: {workspace.name} ({workspace.id})")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""``bevor workspaces view`` — show a workspace (the selected one by default)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
9
|
+
from bevor_cli.client import BevorInternalBusinessClient
|
|
10
|
+
from bevor_cli.configuration.creds import Credentials
|
|
11
|
+
from bevor_cli.configuration.state import State
|
|
12
|
+
from bevor_cli.output import console
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def view_workspace(
|
|
16
|
+
workspace_id: Optional[str] = typer.Argument(
|
|
17
|
+
None, help="Workspace id. Defaults to the selected workspace."
|
|
18
|
+
),
|
|
19
|
+
json_out: JSON_OUT = False,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Print details for a workspace, or the selected one."""
|
|
22
|
+
creds = Credentials.load()
|
|
23
|
+
creds.require_token()
|
|
24
|
+
|
|
25
|
+
client = BevorInternalBusinessClient(
|
|
26
|
+
creds=creds, workspace_id=workspace_id or State.load().validate_workspace()
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
workspace = client.get_workspace()
|
|
30
|
+
|
|
31
|
+
if json_out:
|
|
32
|
+
console.print_json(workspace.model_dump_json(indent=2))
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
36
|
+
table.add_column(style="bold")
|
|
37
|
+
table.add_column()
|
|
38
|
+
|
|
39
|
+
table.add_row("Name", workspace.name)
|
|
40
|
+
table.add_row("ID", workspace.id)
|
|
41
|
+
table.add_row("My Role", workspace.role)
|
|
42
|
+
table.add_row("Projects", str(workspace.n_projects))
|
|
43
|
+
table.add_row("Members", str(workspace.n_members))
|
|
44
|
+
table.add_row("Default workspace", str(workspace.is_default))
|
|
45
|
+
table.add_row("Created", str(workspace.created_at))
|
|
46
|
+
table.add_row("Created by", workspace.created_by_user.username)
|
|
47
|
+
|
|
48
|
+
console.print(table)
|