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,105 @@
|
|
|
1
|
+
"""``bevor analysis create`` — create a new root analysis."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from bevor_sdk.helpers.source_fingerprint import zip_source_tree
|
|
8
|
+
from bevor_sdk.types import ID
|
|
9
|
+
from bevor_sdk.types.requests.security.analyses import CreateAnalysisRootBodyDict
|
|
10
|
+
from bevor_sdk.types.responses.security import AnalysisCollisionResult, AnalysisCreatedResult
|
|
11
|
+
from rich.prompt import Confirm
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from bevor_cli import output
|
|
15
|
+
from bevor_cli.annotations.confirmations import FORCE, JSON_OUT, YES
|
|
16
|
+
from bevor_cli.client import BevorInternalClient
|
|
17
|
+
from bevor_cli.configuration.creds import Credentials
|
|
18
|
+
from bevor_cli.configuration.state import State
|
|
19
|
+
from bevor_cli.output import console
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def apply_create_analysis(
|
|
23
|
+
*,
|
|
24
|
+
name: str | None = None,
|
|
25
|
+
collision: Literal["surface", "force", "merge"] | None = None,
|
|
26
|
+
force: bool = False,
|
|
27
|
+
json_out: bool = False,
|
|
28
|
+
yes: bool = False,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Create a root analysis for the configured code version. Bevor will check whether an analysis
|
|
31
|
+
exists for the given code version or not, and early return if so. Use the --collision argument to bypass this,
|
|
32
|
+
but it's advised not to."""
|
|
33
|
+
creds = Credentials.load()
|
|
34
|
+
creds.require_token()
|
|
35
|
+
|
|
36
|
+
state = State.load()
|
|
37
|
+
workspace_id = state.validate_workspace()
|
|
38
|
+
code_id = state.validate_code()
|
|
39
|
+
|
|
40
|
+
if not force:
|
|
41
|
+
_, _, content_hash = zip_source_tree(Path.cwd())
|
|
42
|
+
if content_hash != state.code_fingerprint:
|
|
43
|
+
output.error(
|
|
44
|
+
"Working directory does not match the configured code fingerprint "
|
|
45
|
+
"(run `bevor codes push` after your latest changes).",
|
|
46
|
+
hint="Pass `--force` only if you mean to create without syncing.",
|
|
47
|
+
)
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
|
|
50
|
+
if not yes:
|
|
51
|
+
if not Confirm.ask(f"Create new analysis using code {code_id}?"):
|
|
52
|
+
raise typer.Exit(0)
|
|
53
|
+
|
|
54
|
+
client = BevorInternalClient(
|
|
55
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
56
|
+
)
|
|
57
|
+
created = client.analyses.create_root(
|
|
58
|
+
CreateAnalysisRootBodyDict(code_id=ID(code_id), name=name, on_collision=collision)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if isinstance(created, AnalysisCreatedResult):
|
|
62
|
+
if json_out:
|
|
63
|
+
console.print_json(created.analysis.model_dump_json(indent=2))
|
|
64
|
+
else:
|
|
65
|
+
output.success(
|
|
66
|
+
f"Analysis created: {created.analysis.id}. "
|
|
67
|
+
"`bevor analyses run` or `bevor findings add` next."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
state.set_analysis(created.analysis)
|
|
71
|
+
state.save()
|
|
72
|
+
elif isinstance(created, AnalysisCollisionResult):
|
|
73
|
+
if json_out:
|
|
74
|
+
console.print_json(created.model_dump_json(indent=2))
|
|
75
|
+
else:
|
|
76
|
+
output.warning(
|
|
77
|
+
f"{len(created.candidates)} analysis versions already exist for this code version. They're listed out with the proposed actions. "
|
|
78
|
+
"if you want to create the analysis anyways, use the --collision=force flag."
|
|
79
|
+
)
|
|
80
|
+
table = Table("ID", "Name", "Created", "User", "Action")
|
|
81
|
+
for candidate in created.candidates:
|
|
82
|
+
args = [
|
|
83
|
+
candidate.id,
|
|
84
|
+
candidate.analysis_meta.name,
|
|
85
|
+
str(candidate.created_at.date()),
|
|
86
|
+
candidate.analysis_meta.user.username,
|
|
87
|
+
candidate.on_collision,
|
|
88
|
+
]
|
|
89
|
+
table.add_row(*args)
|
|
90
|
+
console.print(table)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def create_analysis(
|
|
94
|
+
name: Optional[str] = typer.Option(None, "--name", help="Optional analysis name."),
|
|
95
|
+
collision: Optional[Literal["surface", "force", "merge"]] = typer.Option(
|
|
96
|
+
"surface",
|
|
97
|
+
"--collision",
|
|
98
|
+
help="What Bevor should do if analysis already exists for the given code",
|
|
99
|
+
),
|
|
100
|
+
yes: YES = False,
|
|
101
|
+
json_out: JSON_OUT = False,
|
|
102
|
+
force: FORCE = False,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Create a root analysis for the configured code version."""
|
|
105
|
+
apply_create_analysis(name=name, collision=collision, force=force, yes=yes, json_out=json_out)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""``bevor analysis delete {full|branch|version}``."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from bevor_sdk.types import ID
|
|
7
|
+
|
|
8
|
+
from bevor_cli import output
|
|
9
|
+
from bevor_cli.client import BevorInternalClient
|
|
10
|
+
from bevor_cli.configuration.creds import Credentials
|
|
11
|
+
from bevor_cli.configuration.state import State
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def delete_analysis_full() -> None:
|
|
15
|
+
"""Delete the entire currently configured analysis container"""
|
|
16
|
+
creds = Credentials.load()
|
|
17
|
+
creds.require_token()
|
|
18
|
+
|
|
19
|
+
state = State.load()
|
|
20
|
+
workspace_id = state.validate_workspace()
|
|
21
|
+
analysis_id = state.validate_analysis()
|
|
22
|
+
|
|
23
|
+
client = BevorInternalClient(
|
|
24
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
25
|
+
)
|
|
26
|
+
client.analyses.delete(analysis_id)
|
|
27
|
+
|
|
28
|
+
state.clear_analysis()
|
|
29
|
+
state.save()
|
|
30
|
+
|
|
31
|
+
output.success(f"Deleted analysis {analysis_id}. Cleared analysis from `.bevor`.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def delete_analysis_version(
|
|
35
|
+
version: Optional[str] = typer.Argument(
|
|
36
|
+
None,
|
|
37
|
+
help="Version id to delete (default: configured analysis head).",
|
|
38
|
+
),
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Delete a single analysis version (leaf)."""
|
|
41
|
+
creds = Credentials.load()
|
|
42
|
+
creds.require_token()
|
|
43
|
+
|
|
44
|
+
state = State.load()
|
|
45
|
+
workspace_id = state.validate_workspace()
|
|
46
|
+
|
|
47
|
+
client = BevorInternalClient(
|
|
48
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if not version:
|
|
52
|
+
analysis_id = state.validate_analysis()
|
|
53
|
+
analysis = client.analyses.get(analysis_id)
|
|
54
|
+
id_to_delete = analysis.version.id
|
|
55
|
+
else:
|
|
56
|
+
id_to_delete = ID(version)
|
|
57
|
+
|
|
58
|
+
result = client.analysis_versions.delete(id_to_delete)
|
|
59
|
+
|
|
60
|
+
# HEAD may have moved back to parent — refresh config if still same container.
|
|
61
|
+
if state.analysis_id == result.id:
|
|
62
|
+
state.set_analysis(result)
|
|
63
|
+
state.save()
|
|
64
|
+
|
|
65
|
+
output.success(f"Deleted version {id_to_delete}. Updated HEAD to {result.version.id}")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""``bevor analysis diff``."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from bevor_sdk.types import ID
|
|
7
|
+
from bevor_sdk.types.requests.security import VersionQueryParams
|
|
8
|
+
from bevor_sdk.types.responses.security import FindingBareSchema, FindingTransitionSchema
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
13
|
+
from bevor_cli.client import BevorInternalClient
|
|
14
|
+
from bevor_cli.commands.findings.get import LEVEL_COLORS
|
|
15
|
+
from bevor_cli.configuration.creds import Credentials
|
|
16
|
+
from bevor_cli.configuration.state import State
|
|
17
|
+
from bevor_cli.output import console
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _snapshot(item: FindingTransitionSchema) -> FindingBareSchema:
|
|
21
|
+
to_render = item.after_finding or item.before_finding
|
|
22
|
+
assert to_render
|
|
23
|
+
return to_render
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def diff_analysis(
|
|
27
|
+
version: Optional[str] = typer.Option(
|
|
28
|
+
None,
|
|
29
|
+
"--version",
|
|
30
|
+
help="Version id to diff (default: configured analysis head).",
|
|
31
|
+
),
|
|
32
|
+
staged: bool = typer.Option(
|
|
33
|
+
False,
|
|
34
|
+
"--staged",
|
|
35
|
+
help="Show staged drafts instead of version-vs-parent diffs.",
|
|
36
|
+
),
|
|
37
|
+
json_out: JSON_OUT = False,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Show finding diffs for the configured analysis head (or ``--version``). --staged flag will show
|
|
40
|
+
the diff of the staged commits. Excluding it, it'll show the diff of committed findings between the analysis
|
|
41
|
+
and its parent."""
|
|
42
|
+
creds = Credentials.load()
|
|
43
|
+
creds.require_token()
|
|
44
|
+
|
|
45
|
+
state = State.load()
|
|
46
|
+
workspace_id = state.validate_workspace()
|
|
47
|
+
|
|
48
|
+
client = BevorInternalClient(
|
|
49
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
opts = VersionQueryParams()
|
|
53
|
+
if version:
|
|
54
|
+
v_ = client.analysis_versions.get(ID(version))
|
|
55
|
+
analysis_id = v_.analysis_meta.id
|
|
56
|
+
opts["version_id"] = v_.id
|
|
57
|
+
else:
|
|
58
|
+
analysis_id = state.validate_analysis()
|
|
59
|
+
|
|
60
|
+
analysis_source = client.analyses(analysis_id)
|
|
61
|
+
|
|
62
|
+
if staged:
|
|
63
|
+
page = analysis_source.diff_staged(opts)
|
|
64
|
+
else:
|
|
65
|
+
page = analysis_source.diff_version(opts)
|
|
66
|
+
|
|
67
|
+
if json_out:
|
|
68
|
+
console.print_json(page.model_dump_json(indent=2))
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
if not page.results:
|
|
72
|
+
console.print("No diffs.")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
table = Table("Reason", "Type", "Level", "Name", "Changed")
|
|
76
|
+
for item in page.results:
|
|
77
|
+
snapshot = _snapshot(item)
|
|
78
|
+
|
|
79
|
+
table.add_row(
|
|
80
|
+
item.reason,
|
|
81
|
+
snapshot.type,
|
|
82
|
+
Text(snapshot.level, style=LEVEL_COLORS[snapshot.level]),
|
|
83
|
+
snapshot.name,
|
|
84
|
+
", ".join(item.changed_fields),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
console.print(table)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""``bevor analysis entrypoints``."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from bevor_sdk.types import ID
|
|
8
|
+
from bevor_sdk.types.requests.security import VersionQueryParams
|
|
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 list_entrypoints(
|
|
18
|
+
version: Optional[str] = typer.Option(
|
|
19
|
+
None,
|
|
20
|
+
"--version",
|
|
21
|
+
help="Version id (default: configured analysis head).",
|
|
22
|
+
),
|
|
23
|
+
json_out: JSON_OUT = False,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""List entrypoints for the configured analysis head (or ``--version``)."""
|
|
26
|
+
creds = Credentials.load()
|
|
27
|
+
creds.require_token()
|
|
28
|
+
|
|
29
|
+
state = State.load()
|
|
30
|
+
workspace_id = state.validate_workspace()
|
|
31
|
+
|
|
32
|
+
client = BevorInternalClient(
|
|
33
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
opts = VersionQueryParams()
|
|
37
|
+
if version:
|
|
38
|
+
v_ = client.analysis_versions.get(ID(version))
|
|
39
|
+
analysis_id = v_.analysis_meta.id
|
|
40
|
+
version_id = v_.id
|
|
41
|
+
opts["version_id"] = version_id
|
|
42
|
+
else:
|
|
43
|
+
analysis_id = state.validate_analysis()
|
|
44
|
+
|
|
45
|
+
page = client.analyses.entrypoints(analysis_id, opts)
|
|
46
|
+
|
|
47
|
+
if json_out:
|
|
48
|
+
console.print_json(json.dumps(page.model_dump(mode="json"), indent=2))
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if not page.results:
|
|
52
|
+
console.print("No scopes.")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
for s in page.results:
|
|
56
|
+
console.print(f"{s.node.short_id} {s.node.name} ({s.node.path}) [{s.pipeline_status}]")
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""``bevor finding list``."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from bevor_sdk import Graph
|
|
9
|
+
from bevor_sdk.helpers.converters.sarif import bevor_to_sarif_sync
|
|
10
|
+
from bevor_sdk.types import ID
|
|
11
|
+
from bevor_sdk.types.literals import FindingDraftOperation, FindingLevel, FindingType
|
|
12
|
+
from bevor_sdk.types.requests.security import VersionQueryParams
|
|
13
|
+
from bevor_sdk.types.responses.security import FindingSchema
|
|
14
|
+
|
|
15
|
+
from bevor_cli import output
|
|
16
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
17
|
+
from bevor_cli.client import BevorInternalClient
|
|
18
|
+
from bevor_cli.commands.findings.interactive import (
|
|
19
|
+
browse_findings_with_actions,
|
|
20
|
+
print_findings_table_with_ops,
|
|
21
|
+
)
|
|
22
|
+
from bevor_cli.configuration.creds import Credentials
|
|
23
|
+
from bevor_cli.configuration.state import State
|
|
24
|
+
from bevor_cli.output import console
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _on_node(finding: FindingSchema, node_id: Optional[str]) -> bool:
|
|
28
|
+
if not node_id:
|
|
29
|
+
return True
|
|
30
|
+
if str(finding.node_id) == node_id:
|
|
31
|
+
return True
|
|
32
|
+
return finding.location_id is not None and str(finding.location_id) == node_id
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _matches(
|
|
36
|
+
finding: FindingSchema,
|
|
37
|
+
*,
|
|
38
|
+
finding_type: Optional[FindingType],
|
|
39
|
+
level: Optional[FindingLevel],
|
|
40
|
+
is_acknowledged: Optional[bool],
|
|
41
|
+
operation: Optional[FindingDraftOperation],
|
|
42
|
+
created_by: Optional[str],
|
|
43
|
+
node_id: Optional[str],
|
|
44
|
+
) -> bool:
|
|
45
|
+
"""Filters the version findings endpoint does not support server-side."""
|
|
46
|
+
if finding_type and finding.type != finding_type:
|
|
47
|
+
return False
|
|
48
|
+
if level and finding.level != level:
|
|
49
|
+
return False
|
|
50
|
+
if is_acknowledged is not None and finding.is_acknowledged is not is_acknowledged:
|
|
51
|
+
return False
|
|
52
|
+
if operation and finding.operation != operation:
|
|
53
|
+
return False
|
|
54
|
+
if created_by and str(finding.created_by_user.id) != created_by:
|
|
55
|
+
return False
|
|
56
|
+
return _on_node(finding, node_id)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def list_analysis_findings(
|
|
60
|
+
version: Optional[str] = typer.Option(
|
|
61
|
+
None,
|
|
62
|
+
"--version",
|
|
63
|
+
help="Version id to list findings for (default: configured analysis head).",
|
|
64
|
+
),
|
|
65
|
+
interactive: bool = typer.Option(
|
|
66
|
+
False, "--interactive", help="Browse findings and expand the focused row."
|
|
67
|
+
),
|
|
68
|
+
json_out: JSON_OUT = False,
|
|
69
|
+
sarif: Optional[Path] = typer.Option(
|
|
70
|
+
None, "--sarif", help="Write SARIF for the configured analysis to this path."
|
|
71
|
+
),
|
|
72
|
+
) -> None:
|
|
73
|
+
"""List findings on the configured analysis, or ``--version``. Including a --sarif path will output
|
|
74
|
+
the findings into a SARIF format. --interactive will open an interactive terminal to explore further."""
|
|
75
|
+
creds = Credentials.load()
|
|
76
|
+
creds.require_token()
|
|
77
|
+
|
|
78
|
+
state = State.load()
|
|
79
|
+
workspace_id = state.validate_workspace()
|
|
80
|
+
|
|
81
|
+
if sarif and not sarif.parent.exists():
|
|
82
|
+
output.error(
|
|
83
|
+
f"Directory does not exist: {sarif.parent}",
|
|
84
|
+
hint="Create it first, or pass a different --sarif path.",
|
|
85
|
+
)
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
|
|
88
|
+
client = BevorInternalClient(
|
|
89
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
opts = VersionQueryParams()
|
|
93
|
+
if version:
|
|
94
|
+
v_ = client.analysis_versions.get(ID(version))
|
|
95
|
+
analysis_id = v_.analysis_meta.id
|
|
96
|
+
version_id = v_.id
|
|
97
|
+
opts["version_id"] = version_id
|
|
98
|
+
else:
|
|
99
|
+
analysis_id = state.validate_analysis()
|
|
100
|
+
|
|
101
|
+
result = client.analyses.list_findings(analysis_id, options=opts)
|
|
102
|
+
|
|
103
|
+
if json_out:
|
|
104
|
+
console.print_json(json.dumps([f.model_dump(mode="json") for f in result.results], indent=2))
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
if sarif:
|
|
108
|
+
analysis = client.analyses.get(analysis_id)
|
|
109
|
+
code_id = analysis.version.code_version.id
|
|
110
|
+
|
|
111
|
+
graph = Graph(client=client, code_id=code_id)
|
|
112
|
+
graph.build()
|
|
113
|
+
|
|
114
|
+
sarif_schema = bevor_to_sarif_sync(graph=graph, findings=result.results)
|
|
115
|
+
sarif.write_text(sarif_schema.model_dump_json(indent=2, exclude_none=True))
|
|
116
|
+
output.success(f"Wrote sarif outputs to {sarif.name}")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if not result.results:
|
|
120
|
+
console.print("No findings.")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
def refetch() -> List[FindingSchema]:
|
|
124
|
+
return client.analyses.list_findings(analysis_id, options=opts).results
|
|
125
|
+
|
|
126
|
+
if interactive:
|
|
127
|
+
browse_findings_with_actions(client=client, refetch=refetch, rows=result.results)
|
|
128
|
+
else:
|
|
129
|
+
print_findings_table_with_ops(result.results)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""``bevor analysis fork``."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from bevor_sdk.types import ID
|
|
7
|
+
from bevor_sdk.types.requests.security import VersionBodyDict
|
|
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
|
+
|
|
14
|
+
|
|
15
|
+
def fork_analysis(
|
|
16
|
+
version: Optional[str] = typer.Option(
|
|
17
|
+
None,
|
|
18
|
+
"--version",
|
|
19
|
+
help="Version id to fork (default: configured analysis head).",
|
|
20
|
+
),
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Fork an analysis version and set the new analysis as configured."""
|
|
23
|
+
creds = Credentials.load()
|
|
24
|
+
creds.require_token()
|
|
25
|
+
|
|
26
|
+
state = State.load()
|
|
27
|
+
workspace_id = state.validate_workspace()
|
|
28
|
+
|
|
29
|
+
client = BevorInternalClient(
|
|
30
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
body = VersionBodyDict()
|
|
34
|
+
if version:
|
|
35
|
+
v_ = client.analysis_versions.get(ID(version))
|
|
36
|
+
analysis_id = v_.analysis_meta.id
|
|
37
|
+
version_id = v_.id
|
|
38
|
+
body["version_id"] = version_id
|
|
39
|
+
else:
|
|
40
|
+
analysis_id = state.validate_analysis()
|
|
41
|
+
|
|
42
|
+
created = client.analyses.fork(analysis_id, body)
|
|
43
|
+
|
|
44
|
+
state.set_analysis(created)
|
|
45
|
+
state.save()
|
|
46
|
+
|
|
47
|
+
output.success(f"Forked to analysis {created.id}. Active analysis updated in `.bevor`.")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""``bevor analyses list``."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from bevor_sdk.types import ID
|
|
8
|
+
from bevor_sdk.types.requests.security import AnalysesQueryParams
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
12
|
+
from bevor_cli.annotations.query import ORDER, ORDER_BY, PAGE, PAGE_SIZE
|
|
13
|
+
from bevor_cli.client import BevorInternalClient
|
|
14
|
+
from bevor_cli.configuration.creds import Credentials
|
|
15
|
+
from bevor_cli.configuration.settings import Settings
|
|
16
|
+
from bevor_cli.configuration.state import State
|
|
17
|
+
from bevor_cli.output import console, info
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def list_analyses(
|
|
21
|
+
page: PAGE = None,
|
|
22
|
+
page_size: PAGE_SIZE = None,
|
|
23
|
+
order: ORDER = None,
|
|
24
|
+
order_by: ORDER_BY = None,
|
|
25
|
+
project: Optional[str] = typer.Option(
|
|
26
|
+
None,
|
|
27
|
+
"--project",
|
|
28
|
+
help="Project ID (defaults to .bevor config).",
|
|
29
|
+
),
|
|
30
|
+
user: Optional[str] = typer.Option(None, "--user", help="Analysis creator to filter by"),
|
|
31
|
+
is_owner: Optional[bool] = typer.Option(None, "--is-owner", help="Analysis filter by owner only"),
|
|
32
|
+
json_out: JSON_OUT = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""List analyses for the configured project, or use the --project flag to list for a specific project."""
|
|
35
|
+
creds = Credentials.load()
|
|
36
|
+
creds.require_token()
|
|
37
|
+
|
|
38
|
+
state = State.load()
|
|
39
|
+
settings = Settings.load()
|
|
40
|
+
|
|
41
|
+
workspace_id = state.validate_workspace()
|
|
42
|
+
|
|
43
|
+
client = BevorInternalClient(
|
|
44
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project(priority=project)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
query_params = AnalysesQueryParams()
|
|
48
|
+
if page:
|
|
49
|
+
query_params["page"] = page
|
|
50
|
+
query_params["page_size"] = page_size or settings.default_page_size
|
|
51
|
+
if order:
|
|
52
|
+
query_params["order"] = order
|
|
53
|
+
if order_by:
|
|
54
|
+
query_params["order_by"] = order_by
|
|
55
|
+
if user:
|
|
56
|
+
query_params["user_id"] = ID(user)
|
|
57
|
+
if is_owner:
|
|
58
|
+
query_params["is_owner"] = is_owner
|
|
59
|
+
|
|
60
|
+
analyses = client.analyses.list(query_params)
|
|
61
|
+
|
|
62
|
+
if not analyses.results:
|
|
63
|
+
console.print("No analyses found.")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if json_out:
|
|
67
|
+
console.print_json(
|
|
68
|
+
json.dumps([a.model_dump(mode="json") for a in analyses.results], indent=2)
|
|
69
|
+
)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
table = Table("ID", "Name", "Code", "Owner", "Created")
|
|
73
|
+
for a in analyses.results:
|
|
74
|
+
args = [a.id, a.name, a.version.code_version.id, a.user.username, str(a.created_at.date())]
|
|
75
|
+
if a.id == state.analysis_id:
|
|
76
|
+
args.append("●")
|
|
77
|
+
|
|
78
|
+
table.add_row(*args)
|
|
79
|
+
|
|
80
|
+
console.print(table)
|
|
81
|
+
|
|
82
|
+
if analyses.more:
|
|
83
|
+
info(
|
|
84
|
+
f"\nShowing {len(analyses.results)} of {analyses.total_observations} results. Use --page --page-size to navigate."
|
|
85
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""``bevor analysis merge``."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from bevor_sdk.types import ID
|
|
7
|
+
from bevor_sdk.types.requests.security import MergeAnalysisBodyDict
|
|
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
|
+
|
|
14
|
+
|
|
15
|
+
def merge_analysis(
|
|
16
|
+
from_analysis: str = typer.Argument(..., help="Source analysis container id."),
|
|
17
|
+
to_analysis: Optional[str] = typer.Option(
|
|
18
|
+
None, "--to", help="Destination analysis container id."
|
|
19
|
+
),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Merge one analysis into another. --from is required, but --to defaults to your active analysis's HEAD.
|
|
22
|
+
This will create a child analysis version of the --to, and update its HEAD."""
|
|
23
|
+
creds = Credentials.load()
|
|
24
|
+
creds.require_token()
|
|
25
|
+
|
|
26
|
+
state = State.load()
|
|
27
|
+
workspace_id = state.validate_workspace()
|
|
28
|
+
client = BevorInternalClient(
|
|
29
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
from_ = client.analyses.get(ID(from_analysis))
|
|
33
|
+
|
|
34
|
+
to_analysis_id = state.validate_analysis(priority=to_analysis)
|
|
35
|
+
to_ = client.analyses.get(to_analysis_id)
|
|
36
|
+
|
|
37
|
+
created = client.analyses.merge(
|
|
38
|
+
MergeAnalysisBodyDict(to_version_id=to_.version.id, from_version_id=from_.version.id)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
state.set_analysis(created)
|
|
42
|
+
state.save()
|
|
43
|
+
|
|
44
|
+
output.success(
|
|
45
|
+
f"Merge complete → analysis {created.id} head {created.version.id}. Config updated."
|
|
46
|
+
)
|