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
bevor_cli/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
YES = Annotated[bool, typer.Option("--yes", help="Skip confirmation of action.")]
|
|
6
|
+
NO_WAIT = Annotated[
|
|
7
|
+
bool,
|
|
8
|
+
typer.Option(
|
|
9
|
+
"--no-wait", help="Return immediately without waiting for the event stream to complete."
|
|
10
|
+
),
|
|
11
|
+
]
|
|
12
|
+
JSON_OUT = Annotated[bool, typer.Option("--json", help="JSON output")]
|
|
13
|
+
FORCE = Annotated[bool, typer.Option("--force", help="Skip checks. Not recommended.")]
|
|
14
|
+
SILENT = Annotated[bool, typer.Option("--silent", help="Suppress intermediate output logs.")]
|
|
15
|
+
|
|
16
|
+
__all__ = ["YES", "JSON_OUT", "NO_WAIT", "FORCE", "SILENT"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Annotated, Literal
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
PAGE = Annotated[
|
|
6
|
+
int | None,
|
|
7
|
+
typer.Option("--page", help="Page number to use for resource that supports pagination"),
|
|
8
|
+
]
|
|
9
|
+
PAGE_SIZE = Annotated[
|
|
10
|
+
int | None,
|
|
11
|
+
typer.Option("--page-size", help="Page size to use for resource that supports pagination"),
|
|
12
|
+
]
|
|
13
|
+
ORDER = Annotated[
|
|
14
|
+
Literal["desc", "asc"] | None,
|
|
15
|
+
typer.Option("--order", help="How to order results when a resource supports pagination"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
ORDER_BY = Annotated[
|
|
19
|
+
Literal["created_at", "updated_at"] | None,
|
|
20
|
+
typer.Option(
|
|
21
|
+
"--order-by", help="What field to order results by when a resource supports pagination"
|
|
22
|
+
),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
__all__ = ["PAGE", "PAGE_SIZE", "ORDER", "ORDER_BY"]
|
bevor_cli/apps/agent.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.agent.materialze_cmd import scopes_materialize
|
|
3
|
+
from bevor_cli.commands.agent.setup_cmd import agent_setup
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(help="Audit agent workflow shorthands.", no_args_is_help=True)
|
|
6
|
+
|
|
7
|
+
app.command("setup")(agent_setup)
|
|
8
|
+
app.command("materialize")(scopes_materialize)
|
|
9
|
+
|
|
10
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.analyses.active import active_analysis
|
|
3
|
+
from bevor_cli.commands.analyses.child import create_child_analysis
|
|
4
|
+
from bevor_cli.commands.analyses.commit import commit_analysis
|
|
5
|
+
from bevor_cli.commands.analyses.create import create_analysis
|
|
6
|
+
from bevor_cli.commands.analyses.delete import delete_analysis_full, delete_analysis_version
|
|
7
|
+
from bevor_cli.commands.analyses.diff import diff_analysis
|
|
8
|
+
from bevor_cli.commands.analyses.entrypoints import list_entrypoints
|
|
9
|
+
from bevor_cli.commands.analyses.findings import list_analysis_findings
|
|
10
|
+
from bevor_cli.commands.analyses.fork import fork_analysis
|
|
11
|
+
from bevor_cli.commands.analyses.list import list_analyses
|
|
12
|
+
from bevor_cli.commands.analyses.merge import merge_analysis
|
|
13
|
+
from bevor_cli.commands.analyses.retry import retry_analysis
|
|
14
|
+
from bevor_cli.commands.analyses.run import run_analysis
|
|
15
|
+
from bevor_cli.commands.analyses.select import select_analysis
|
|
16
|
+
from bevor_cli.commands.analyses.set_head import set_analysis_head
|
|
17
|
+
from bevor_cli.commands.analyses.transitions import list_transitions
|
|
18
|
+
from bevor_cli.commands.analyses.view import view_analysis
|
|
19
|
+
|
|
20
|
+
app = typer.Typer(help="Manage analyses.", no_args_is_help=True)
|
|
21
|
+
|
|
22
|
+
app.command("list")(list_analyses)
|
|
23
|
+
app.command("view")(view_analysis)
|
|
24
|
+
app.command("select")(select_analysis)
|
|
25
|
+
app.command("recent")(active_analysis)
|
|
26
|
+
app.command("create")(create_analysis)
|
|
27
|
+
app.command("set-head")(set_analysis_head)
|
|
28
|
+
|
|
29
|
+
delete_app = typer.Typer(help="Delete an analysis container or version.", no_args_is_help=True)
|
|
30
|
+
delete_app.command("full")(delete_analysis_full)
|
|
31
|
+
delete_app.command("version")(delete_analysis_version)
|
|
32
|
+
app.add_typer(delete_app, name="delete")
|
|
33
|
+
|
|
34
|
+
app.command("run")(run_analysis)
|
|
35
|
+
app.command("retry")(retry_analysis)
|
|
36
|
+
app.command("diff")(diff_analysis)
|
|
37
|
+
app.command("commit")(commit_analysis)
|
|
38
|
+
app.command("child")(create_child_analysis)
|
|
39
|
+
app.command("fork")(fork_analysis)
|
|
40
|
+
app.command("merge")(merge_analysis)
|
|
41
|
+
app.command("findings")(list_analysis_findings)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
app.command("entrypoints")(list_entrypoints)
|
|
45
|
+
app.command("transitions")(list_transitions)
|
|
46
|
+
|
|
47
|
+
__all__ = ["app"]
|
bevor_cli/apps/chat.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.analyses.chats.get import get_chat
|
|
3
|
+
from bevor_cli.commands.analyses.chats.list import list_chats
|
|
4
|
+
from bevor_cli.commands.analyses.chats.messages import chat_messages
|
|
5
|
+
from bevor_cli.commands.analyses.chats.new import chat_new
|
|
6
|
+
from bevor_cli.commands.analyses.chats.resume import chat_resume
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="Manage analysis chats.")
|
|
9
|
+
|
|
10
|
+
app.command("list")(list_chats)
|
|
11
|
+
app.command("view")(get_chat)
|
|
12
|
+
app.command("messages")(chat_messages)
|
|
13
|
+
app.command("new")(chat_new)
|
|
14
|
+
app.command("resume")(chat_resume)
|
|
15
|
+
|
|
16
|
+
__all__ = ["app"]
|
bevor_cli/apps/codes.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.apps import nodes
|
|
3
|
+
from bevor_cli.commands.codes.list import list_code_versions
|
|
4
|
+
from bevor_cli.commands.codes.push import push_code
|
|
5
|
+
from bevor_cli.commands.codes.select import select_code
|
|
6
|
+
from bevor_cli.commands.codes.view import view_code
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(
|
|
9
|
+
help="List, inspect, and upload code versions for the active project.",
|
|
10
|
+
no_args_is_help=True,
|
|
11
|
+
)
|
|
12
|
+
app.command("list")(list_code_versions)
|
|
13
|
+
app.command("view")(view_code)
|
|
14
|
+
app.command("select")(select_code)
|
|
15
|
+
app.command("push")(push_code)
|
|
16
|
+
app.add_typer(nodes.app, name="nodes")
|
|
17
|
+
|
|
18
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.findings.acknowledge import acknowledge_finding
|
|
3
|
+
from bevor_cli.commands.findings.add import add_finding
|
|
4
|
+
from bevor_cli.commands.findings.bulk import bulk_findings
|
|
5
|
+
from bevor_cli.commands.findings.delete import delete_finding
|
|
6
|
+
from bevor_cli.commands.findings.edit import edit_finding
|
|
7
|
+
from bevor_cli.commands.findings.get import get_finding
|
|
8
|
+
from bevor_cli.commands.findings.list import list_findings
|
|
9
|
+
from bevor_cli.commands.findings.revert import revert_finding
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="Findings for the configured analysis.", no_args_is_help=True)
|
|
12
|
+
|
|
13
|
+
app.command("list")(list_findings)
|
|
14
|
+
app.command("view")(get_finding)
|
|
15
|
+
app.command("add")(add_finding)
|
|
16
|
+
app.command("bulk")(bulk_findings)
|
|
17
|
+
app.command("edit")(edit_finding)
|
|
18
|
+
app.command("delete")(delete_finding)
|
|
19
|
+
app.command("revert")(revert_finding)
|
|
20
|
+
app.command("acknowledge")(acknowledge_finding)
|
|
21
|
+
|
|
22
|
+
__all__ = ["app"]
|
bevor_cli/apps/main.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli import output
|
|
3
|
+
from bevor_cli.apps import (
|
|
4
|
+
agent,
|
|
5
|
+
analyses,
|
|
6
|
+
chat,
|
|
7
|
+
codes,
|
|
8
|
+
findings,
|
|
9
|
+
projects,
|
|
10
|
+
sessions,
|
|
11
|
+
workspaces,
|
|
12
|
+
)
|
|
13
|
+
from bevor_cli.commands.auth import login, logout, whoami
|
|
14
|
+
from bevor_cli.commands.context import run_context
|
|
15
|
+
from bevor_cli.commands.link import run_link
|
|
16
|
+
from bevor_cli.output import console
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(
|
|
19
|
+
name="bevor",
|
|
20
|
+
help="Bevor CLI — smart contract security analysis and code graph exploration.",
|
|
21
|
+
invoke_without_command=True,
|
|
22
|
+
no_args_is_help=True,
|
|
23
|
+
pretty_exceptions_enable=False,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.callback(invoke_without_command=True)
|
|
28
|
+
def _root(
|
|
29
|
+
quiet: bool = typer.Option(
|
|
30
|
+
False,
|
|
31
|
+
"--quiet",
|
|
32
|
+
help="Suppress informational output; useful for scripting.",
|
|
33
|
+
is_eager=False,
|
|
34
|
+
),
|
|
35
|
+
) -> None:
|
|
36
|
+
output.set_quiet(quiet)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command("help")
|
|
40
|
+
def help_command(ctx: typer.Context) -> None:
|
|
41
|
+
"""Show root help text (alias for --help)."""
|
|
42
|
+
root_ctx = ctx.parent or ctx
|
|
43
|
+
console.print(root_ctx.get_help())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
app.command("login")(login)
|
|
47
|
+
app.command("logout")(logout)
|
|
48
|
+
app.command("whoami")(whoami)
|
|
49
|
+
app.command("link")(run_link)
|
|
50
|
+
app.command("context")(run_context)
|
|
51
|
+
|
|
52
|
+
app.add_typer(sessions.app, name="sessions")
|
|
53
|
+
app.add_typer(workspaces.app, name="workspaces")
|
|
54
|
+
app.add_typer(projects.app, name="projects")
|
|
55
|
+
app.add_typer(codes.app, name="codes")
|
|
56
|
+
app.add_typer(analyses.app, name="analyses")
|
|
57
|
+
app.add_typer(findings.app, name="findings")
|
|
58
|
+
app.add_typer(agent.app, name="agent")
|
|
59
|
+
app.add_typer(chat.app, name="chats")
|
|
60
|
+
|
|
61
|
+
__all__ = ["app"]
|
bevor_cli/apps/nodes.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.graph.call_chain import call_chain_cmd
|
|
3
|
+
from bevor_cli.commands.graph.edges import list_node_edges
|
|
4
|
+
from bevor_cli.commands.graph.interactive import interactive_cmd
|
|
5
|
+
from bevor_cli.commands.nodes.get import get_node
|
|
6
|
+
from bevor_cli.commands.nodes.list import list_nodes
|
|
7
|
+
from bevor_cli.commands.nodes.source import show_source
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Inspect the code graph.", no_args_is_help=True)
|
|
10
|
+
app.command("list")(list_nodes)
|
|
11
|
+
app.command("view")(get_node)
|
|
12
|
+
app.command("content")(show_source)
|
|
13
|
+
app.command("edges")(list_node_edges)
|
|
14
|
+
app.command("call-chain")(call_chain_cmd)
|
|
15
|
+
app.command("interactive")(interactive_cmd)
|
|
16
|
+
|
|
17
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.projects.create import create_project
|
|
3
|
+
from bevor_cli.commands.projects.list import list_projects
|
|
4
|
+
from bevor_cli.commands.projects.select import select_project
|
|
5
|
+
from bevor_cli.commands.projects.view import view_project
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="List, view, select, and create projects.", no_args_is_help=True)
|
|
8
|
+
|
|
9
|
+
app.command("list")(list_projects)
|
|
10
|
+
app.command("view")(view_project)
|
|
11
|
+
app.command("select")(select_project)
|
|
12
|
+
app.command("create")(create_project)
|
|
13
|
+
|
|
14
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.sessions.list import sessions_list
|
|
3
|
+
from bevor_cli.commands.sessions.revoke import sessions_revoke
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(
|
|
6
|
+
help="List and revoke CLI sessions.",
|
|
7
|
+
no_args_is_help=True,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
app.command("list")(sessions_list)
|
|
11
|
+
app.command("revoke")(sessions_revoke)
|
|
12
|
+
|
|
13
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from bevor_cli.commands.workspaces.list import list_workspaces
|
|
3
|
+
from bevor_cli.commands.workspaces.pick import pick_workspace
|
|
4
|
+
from bevor_cli.commands.workspaces.select import select_workspace
|
|
5
|
+
from bevor_cli.commands.workspaces.view import view_workspace
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(
|
|
8
|
+
help="List, view, and select workspaces.",
|
|
9
|
+
no_args_is_help=True,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
app.command("list")(list_workspaces)
|
|
13
|
+
app.command("view")(view_workspace)
|
|
14
|
+
app.command("select")(select_workspace)
|
|
15
|
+
|
|
16
|
+
__all__ = ["app", "pick_workspace"]
|
bevor_cli/client.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Thin httpx wrapper that injects auth headers for the Bevor API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, cast
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import typer
|
|
9
|
+
from bevor_sdk import BevorClient
|
|
10
|
+
from bevor_sdk.exceptions import ERROR_REGISTRY, AppErrorTypes
|
|
11
|
+
from bevor_sdk.types.requests.manage import CreateProjectBodyDict
|
|
12
|
+
from bevor_sdk.types.responses import ResultsResponse
|
|
13
|
+
from bevor_sdk.types.responses.manage import (
|
|
14
|
+
ProjectSchema,
|
|
15
|
+
UserDetailedSchema,
|
|
16
|
+
WorkspaceDetailedSchema,
|
|
17
|
+
)
|
|
18
|
+
from httpx._types import QueryParamTypes, TimeoutTypes
|
|
19
|
+
|
|
20
|
+
from bevor_cli import output
|
|
21
|
+
from bevor_cli.configuration.creds import Credentials
|
|
22
|
+
from bevor_cli.settings import API_URL
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BevorInternalClient(BevorClient):
|
|
26
|
+
"""Public SDK client authenticated with the CLI's user token.
|
|
27
|
+
|
|
28
|
+
Project-level endpoints resolve scope from the injected headers, so pass ``project_id`` for anything below
|
|
29
|
+
project selection.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
creds: Credentials,
|
|
35
|
+
workspace_id: str | None = None,
|
|
36
|
+
project_id: str | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
headers = {}
|
|
39
|
+
|
|
40
|
+
if not creds.token:
|
|
41
|
+
output.error("Not Authenticated", hint="Run `bevor login`.")
|
|
42
|
+
raise typer.Exit(1)
|
|
43
|
+
|
|
44
|
+
if workspace_id:
|
|
45
|
+
headers.update({"bevor-workspace-id": workspace_id})
|
|
46
|
+
if project_id:
|
|
47
|
+
headers.update({"bevor-project-id": project_id})
|
|
48
|
+
super().__init__(api_key=creds.token, base_url=API_URL, default_headers=headers)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BevorInternalBusinessClient(httpx.Client):
|
|
52
|
+
"""User-scoped endpoints that API keys can't reach: navigation above the project, sessions."""
|
|
53
|
+
|
|
54
|
+
_TIMEOUT = httpx.Timeout(10.0)
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self, creds: Credentials, workspace_id: str | None = None, timeout: TimeoutTypes = None
|
|
58
|
+
) -> None:
|
|
59
|
+
super().__init__(base_url=API_URL, timeout=timeout or self._TIMEOUT)
|
|
60
|
+
|
|
61
|
+
if creds.token:
|
|
62
|
+
self.headers.update({"Authorization": f"Bearer {creds.token}"})
|
|
63
|
+
if workspace_id:
|
|
64
|
+
self.headers.update({"Bevor-Workspace-Id": workspace_id})
|
|
65
|
+
|
|
66
|
+
def _parse_error_body(
|
|
67
|
+
self,
|
|
68
|
+
response: httpx.Response,
|
|
69
|
+
) -> tuple[str | None, str, int]:
|
|
70
|
+
status = response.status_code
|
|
71
|
+
try:
|
|
72
|
+
body = response.json()
|
|
73
|
+
if not isinstance(body, dict):
|
|
74
|
+
return None, response.text or "no details", status
|
|
75
|
+
code_raw = body.get("code")
|
|
76
|
+
code: str | None
|
|
77
|
+
if code_raw is None:
|
|
78
|
+
code = None
|
|
79
|
+
elif isinstance(code_raw, str):
|
|
80
|
+
code = code_raw
|
|
81
|
+
else:
|
|
82
|
+
code = str(code_raw)
|
|
83
|
+
if body.get("message") is not None:
|
|
84
|
+
message = str(body.get("message"))
|
|
85
|
+
else:
|
|
86
|
+
d = body.get("detail")
|
|
87
|
+
if d is not None and not isinstance(d, str):
|
|
88
|
+
message = str(d)
|
|
89
|
+
else:
|
|
90
|
+
message = str(d) if d is not None else str(body)
|
|
91
|
+
return code, message, status
|
|
92
|
+
except Exception:
|
|
93
|
+
return None, response.text or "no details", status
|
|
94
|
+
|
|
95
|
+
def _handle_http_error(self, response: httpx.Response) -> bool:
|
|
96
|
+
if response.is_success:
|
|
97
|
+
if response.status_code == 204 or not response.content:
|
|
98
|
+
return False
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
code, message, status = self._parse_error_body(response)
|
|
102
|
+
|
|
103
|
+
if not code:
|
|
104
|
+
# couldn't parse, so raise.
|
|
105
|
+
response.raise_for_status()
|
|
106
|
+
|
|
107
|
+
exc_cls = ERROR_REGISTRY.get(cast(AppErrorTypes, code))
|
|
108
|
+
if exc_cls is None:
|
|
109
|
+
response.raise_for_status()
|
|
110
|
+
return False # just for typing...
|
|
111
|
+
|
|
112
|
+
raise exc_cls(message)
|
|
113
|
+
|
|
114
|
+
def _prune_query_params(self, params: QueryParamTypes | None = None) -> QueryParamTypes | None:
|
|
115
|
+
if not params:
|
|
116
|
+
return None
|
|
117
|
+
qp = httpx.QueryParams(params)
|
|
118
|
+
out: Dict[str, Any] = {}
|
|
119
|
+
for k, v in qp.items():
|
|
120
|
+
if v is None:
|
|
121
|
+
continue
|
|
122
|
+
if v == "":
|
|
123
|
+
continue
|
|
124
|
+
if isinstance(v, bool):
|
|
125
|
+
out[k] = "true" if v else "false"
|
|
126
|
+
else:
|
|
127
|
+
out[k] = v
|
|
128
|
+
return httpx.QueryParams(out) if out else None
|
|
129
|
+
|
|
130
|
+
def _merge_queryparams(self, params: QueryParamTypes | None = None) -> QueryParamTypes | None:
|
|
131
|
+
params = super()._merge_queryparams(params)
|
|
132
|
+
return self._prune_query_params(params)
|
|
133
|
+
|
|
134
|
+
def send(self, request: httpx.Request, **kwargs) -> httpx.Response:
|
|
135
|
+
response = super().send(request, **kwargs)
|
|
136
|
+
self._handle_http_error(response)
|
|
137
|
+
return response
|
|
138
|
+
|
|
139
|
+
def me(self) -> UserDetailedSchema:
|
|
140
|
+
data = self.get("/v1/me").json()
|
|
141
|
+
return UserDetailedSchema.model_validate(data)
|
|
142
|
+
|
|
143
|
+
def list_workspaces(self) -> ResultsResponse[WorkspaceDetailedSchema]:
|
|
144
|
+
data = self.get("/v1/me/workspaces").json()
|
|
145
|
+
return ResultsResponse[WorkspaceDetailedSchema].model_validate(data)
|
|
146
|
+
|
|
147
|
+
def get_workspace(self) -> WorkspaceDetailedSchema:
|
|
148
|
+
data = self.get("/v1/workspace").json()
|
|
149
|
+
return WorkspaceDetailedSchema.model_validate(data)
|
|
150
|
+
|
|
151
|
+
def list_projects(self) -> ResultsResponse[ProjectSchema]:
|
|
152
|
+
data = self.get("/v1/workspace/projects").json()
|
|
153
|
+
return ResultsResponse[ProjectSchema].model_validate(data)
|
|
154
|
+
|
|
155
|
+
def create_project(self, body: CreateProjectBodyDict) -> ProjectSchema:
|
|
156
|
+
data = self.post("/v1/workspace/projects", json=body).json()
|
|
157
|
+
return ProjectSchema.model_validate(data)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""``bevor analyses scopes materialize``."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from bevor_sdk.types import ID
|
|
6
|
+
from bevor_sdk.types.requests.graph import CallChainBodyDict
|
|
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 scopes_materialize() -> None:
|
|
15
|
+
"""Write each scope to ``.bevor/analyses/{analysis_id}/{scope_short_id}.raw.md`` (source + server call-chain text).
|
|
16
|
+
|
|
17
|
+
Call chain uses the graph API ``response_type=text``; used to feed skills without re-running graph commands.
|
|
18
|
+
"""
|
|
19
|
+
creds = Credentials.load()
|
|
20
|
+
creds.require_token()
|
|
21
|
+
|
|
22
|
+
state = State.load()
|
|
23
|
+
workspace_id = state.validate_workspace()
|
|
24
|
+
analysis_id = state.validate_analysis()
|
|
25
|
+
|
|
26
|
+
client = BevorInternalClient(
|
|
27
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
analysis_source = client.analyses(analysis_id)
|
|
31
|
+
|
|
32
|
+
entrypoints = analysis_source.entrypoints()
|
|
33
|
+
analysis = analysis_source.get()
|
|
34
|
+
|
|
35
|
+
if not entrypoints.results:
|
|
36
|
+
output.warning("No entrypoints for this analysis; nothing to materialize.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
node_resource = client.codes.nodes(analysis.version.code_version.id)
|
|
40
|
+
|
|
41
|
+
out_root = Path.cwd() / ".bevor" / "analyses" / str(state.analysis_id)
|
|
42
|
+
out_root.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
for entrypoint in entrypoints.results:
|
|
45
|
+
if entrypoint.pipeline_status != "failed" and entrypoint.pipeline_status != "queued":
|
|
46
|
+
# ONLY materialize scopes that should actually be processed.
|
|
47
|
+
continue
|
|
48
|
+
if entrypoint.is_analyzed:
|
|
49
|
+
# ie, it was borrowed, we don't need to materialize it.
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
source_text = node_resource.content(ID(entrypoint.node.id))
|
|
53
|
+
call_chain = node_resource.call_chain(
|
|
54
|
+
CallChainBodyDict(node_ids=[ID(entrypoint.node.id)], response_type="text")
|
|
55
|
+
)
|
|
56
|
+
cc_text = str(call_chain) if call_chain else ""
|
|
57
|
+
path = out_root / f"{entrypoint.node.short_id}.raw.md"
|
|
58
|
+
path.write_text(
|
|
59
|
+
f"{source_text}\n\ncall-chain:\n{cc_text}\n",
|
|
60
|
+
encoding="utf-8",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
output.success(
|
|
64
|
+
f"Wrote {len(entrypoints.results)} file(s) under {out_root.relative_to(Path.cwd())!s}/"
|
|
65
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""``bevor agent setup`` — print the canonical audit-agent workflow."""
|
|
2
|
+
|
|
3
|
+
from bevor_cli import output
|
|
4
|
+
from bevor_cli.commands.analyses.create import apply_create_analysis
|
|
5
|
+
from bevor_cli.commands.codes.push import push_code
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def agent_setup() -> None:
|
|
9
|
+
"""Assumes user has ran `bevor link`. After that, running the setup will sync code, generate an
|
|
10
|
+
analysis, and set the original scopes to first-party only."""
|
|
11
|
+
|
|
12
|
+
push_code(yes=True)
|
|
13
|
+
apply_create_analysis(yes=True)
|
|
14
|
+
|
|
15
|
+
output.info(
|
|
16
|
+
"Code successfully synced, analysis initialized, and scopes set.\n"
|
|
17
|
+
"Materialize the scopes using `bevor agent materialize`"
|
|
18
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""``bevor analyses recent`` — select your most recent analysis in the selected project."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from bevor_cli import output
|
|
6
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
7
|
+
from bevor_cli.client import BevorInternalClient
|
|
8
|
+
from bevor_cli.configuration.creds import Credentials
|
|
9
|
+
from bevor_cli.configuration.state import State
|
|
10
|
+
from bevor_cli.output import console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def active_analysis(json_out: JSON_OUT = False) -> None:
|
|
14
|
+
"""Select your most recent analysis in the selected project."""
|
|
15
|
+
|
|
16
|
+
creds = Credentials.load()
|
|
17
|
+
creds.require_token()
|
|
18
|
+
|
|
19
|
+
state = State.load()
|
|
20
|
+
workspace_id = state.validate_workspace()
|
|
21
|
+
|
|
22
|
+
client = BevorInternalClient(
|
|
23
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
exists = client.analyses.recent()
|
|
27
|
+
|
|
28
|
+
if not exists.exists or not exists.data:
|
|
29
|
+
output.error(
|
|
30
|
+
"You have no latest analysis in this project",
|
|
31
|
+
hint="Run `bevor analyses create` first.",
|
|
32
|
+
)
|
|
33
|
+
raise typer.Exit(1)
|
|
34
|
+
|
|
35
|
+
analysis = exists.data
|
|
36
|
+
state.set_analysis(analysis)
|
|
37
|
+
state.save()
|
|
38
|
+
|
|
39
|
+
if json_out:
|
|
40
|
+
console.print_json(analysis.model_dump_json(indent=2))
|
|
41
|
+
else:
|
|
42
|
+
output.success(f"Selected analysis {analysis.id} ({analysis.name}).")
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""``bevor analyses chats get``."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from bevor_sdk.types import ID
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from bevor_cli.annotations.confirmations import JSON_OUT
|
|
8
|
+
from bevor_cli.client import BevorInternalClient
|
|
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
|
+
|
|
14
|
+
def get_chat(chat_id: str = typer.Argument(..., help="Chat ID."), json_out: JSON_OUT = False) -> None:
|
|
15
|
+
"""Get the details of a chat."""
|
|
16
|
+
creds = Credentials.load()
|
|
17
|
+
creds.require_token()
|
|
18
|
+
|
|
19
|
+
state = State.load()
|
|
20
|
+
workspace_id = state.validate_workspace()
|
|
21
|
+
|
|
22
|
+
client = BevorInternalClient(
|
|
23
|
+
creds=creds, workspace_id=workspace_id, project_id=state.validate_project()
|
|
24
|
+
)
|
|
25
|
+
chat = client.chats.get(ID(chat_id))
|
|
26
|
+
|
|
27
|
+
if json_out:
|
|
28
|
+
console.print_json(chat.model_dump_json(indent=2))
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
32
|
+
table.add_column(style="bold")
|
|
33
|
+
table.add_column()
|
|
34
|
+
|
|
35
|
+
table.add_row("ID", chat.id)
|
|
36
|
+
table.add_row("Code", str(chat.code_id))
|
|
37
|
+
if chat.version_id:
|
|
38
|
+
table.add_row("Version", str(chat.version_id))
|
|
39
|
+
table.add_row("Created", str(chat.created_at.date()))
|