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.
Files changed (100) hide show
  1. bevor_cli/__init__.py +0 -0
  2. bevor_cli/annotations/__init__.py +0 -0
  3. bevor_cli/annotations/confirmations.py +16 -0
  4. bevor_cli/annotations/query.py +25 -0
  5. bevor_cli/apps/agent.py +10 -0
  6. bevor_cli/apps/analyses.py +47 -0
  7. bevor_cli/apps/chat.py +16 -0
  8. bevor_cli/apps/codes.py +18 -0
  9. bevor_cli/apps/findings.py +22 -0
  10. bevor_cli/apps/main.py +61 -0
  11. bevor_cli/apps/nodes.py +17 -0
  12. bevor_cli/apps/projects.py +14 -0
  13. bevor_cli/apps/sessions.py +13 -0
  14. bevor_cli/apps/workspaces.py +16 -0
  15. bevor_cli/client.py +157 -0
  16. bevor_cli/commands/__init__.py +0 -0
  17. bevor_cli/commands/agent/__init__.py +0 -0
  18. bevor_cli/commands/agent/materialze_cmd.py +65 -0
  19. bevor_cli/commands/agent/setup_cmd.py +18 -0
  20. bevor_cli/commands/analyses/__init__.py +0 -0
  21. bevor_cli/commands/analyses/active.py +42 -0
  22. bevor_cli/commands/analyses/chats/__init__.py +0 -0
  23. bevor_cli/commands/analyses/chats/get.py +39 -0
  24. bevor_cli/commands/analyses/chats/list.py +64 -0
  25. bevor_cli/commands/analyses/chats/messages.py +41 -0
  26. bevor_cli/commands/analyses/chats/new.py +44 -0
  27. bevor_cli/commands/analyses/chats/resume.py +43 -0
  28. bevor_cli/commands/analyses/chats/session.py +295 -0
  29. bevor_cli/commands/analyses/child.py +81 -0
  30. bevor_cli/commands/analyses/commit.py +49 -0
  31. bevor_cli/commands/analyses/create.py +105 -0
  32. bevor_cli/commands/analyses/delete.py +65 -0
  33. bevor_cli/commands/analyses/diff.py +87 -0
  34. bevor_cli/commands/analyses/entrypoints.py +56 -0
  35. bevor_cli/commands/analyses/findings.py +129 -0
  36. bevor_cli/commands/analyses/fork.py +47 -0
  37. bevor_cli/commands/analyses/list.py +85 -0
  38. bevor_cli/commands/analyses/merge.py +46 -0
  39. bevor_cli/commands/analyses/retry.py +77 -0
  40. bevor_cli/commands/analyses/run.py +87 -0
  41. bevor_cli/commands/analyses/select.py +85 -0
  42. bevor_cli/commands/analyses/set_head.py +45 -0
  43. bevor_cli/commands/analyses/transitions.py +90 -0
  44. bevor_cli/commands/analyses/view.py +94 -0
  45. bevor_cli/commands/auth/__init__.py +7 -0
  46. bevor_cli/commands/auth/login.py +135 -0
  47. bevor_cli/commands/auth/logout.py +23 -0
  48. bevor_cli/commands/auth/whoami.py +21 -0
  49. bevor_cli/commands/codes/__init__.py +0 -0
  50. bevor_cli/commands/codes/list.py +90 -0
  51. bevor_cli/commands/codes/push.py +76 -0
  52. bevor_cli/commands/codes/select.py +93 -0
  53. bevor_cli/commands/codes/view.py +80 -0
  54. bevor_cli/commands/context/__init__.py +92 -0
  55. bevor_cli/commands/findings/__init__.py +0 -0
  56. bevor_cli/commands/findings/acknowledge.py +28 -0
  57. bevor_cli/commands/findings/add.py +236 -0
  58. bevor_cli/commands/findings/bulk.py +110 -0
  59. bevor_cli/commands/findings/delete.py +28 -0
  60. bevor_cli/commands/findings/edit.py +109 -0
  61. bevor_cli/commands/findings/get.py +62 -0
  62. bevor_cli/commands/findings/interactive.py +329 -0
  63. bevor_cli/commands/findings/list.py +108 -0
  64. bevor_cli/commands/findings/revert.py +28 -0
  65. bevor_cli/commands/graph/__init__.py +0 -0
  66. bevor_cli/commands/graph/call_chain.py +48 -0
  67. bevor_cli/commands/graph/edges.py +59 -0
  68. bevor_cli/commands/graph/interactive.py +207 -0
  69. bevor_cli/commands/graph/renderer.py +135 -0
  70. bevor_cli/commands/link/__init__.py +52 -0
  71. bevor_cli/commands/link/helpers.py +26 -0
  72. bevor_cli/commands/nodes/__init__.py +0 -0
  73. bevor_cli/commands/nodes/get.py +52 -0
  74. bevor_cli/commands/nodes/list.py +75 -0
  75. bevor_cli/commands/nodes/source.py +40 -0
  76. bevor_cli/commands/projects/__init__.py +0 -0
  77. bevor_cli/commands/projects/create.py +54 -0
  78. bevor_cli/commands/projects/list.py +57 -0
  79. bevor_cli/commands/projects/select.py +75 -0
  80. bevor_cli/commands/projects/view.py +49 -0
  81. bevor_cli/commands/sessions/__init__.py +0 -0
  82. bevor_cli/commands/sessions/list.py +36 -0
  83. bevor_cli/commands/sessions/revoke.py +35 -0
  84. bevor_cli/commands/workspaces/__init__.py +0 -0
  85. bevor_cli/commands/workspaces/list.py +50 -0
  86. bevor_cli/commands/workspaces/pick.py +20 -0
  87. bevor_cli/commands/workspaces/select.py +68 -0
  88. bevor_cli/commands/workspaces/view.py +48 -0
  89. bevor_cli/configuration/creds.py +52 -0
  90. bevor_cli/configuration/settings.py +54 -0
  91. bevor_cli/configuration/state.py +113 -0
  92. bevor_cli/findings_yaml.py +291 -0
  93. bevor_cli/main.py +33 -0
  94. bevor_cli/output.py +69 -0
  95. bevor_cli/select.py +64 -0
  96. bevor_cli/settings.py +4 -0
  97. bevor_cli-0.1.0.dist-info/METADATA +14 -0
  98. bevor_cli-0.1.0.dist-info/RECORD +100 -0
  99. bevor_cli-0.1.0.dist-info/WHEEL +4 -0
  100. 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"]
@@ -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"]
@@ -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"]
@@ -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()))