relay_knowledge/interfaces/cli/
mod.rs1mod command;
4mod files;
5mod grammar;
6mod knowledge;
7pub(crate) mod map;
8mod operations;
9mod remote;
10mod render;
11mod repo;
12mod repo_set;
13mod runtime;
14mod service;
15mod setup;
16mod spec;
17mod version;
18
19use crate::{
20 api::ServicePlanRequest,
21 domain::{FreshnessPolicy, IndexKind, ProposalState, WorkerKind},
22};
23
24pub use command::{CliDiagnostic, CliError};
25use render::{render_response, serialize_line};
26pub(crate) use runtime::run_command;
27pub use runtime::run_with_service;
28
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
31pub enum OutputFormat {
32 #[default]
33 Text,
34 Json,
35 Markdown,
36 StreamingJson,
37}
38
39impl OutputFormat {
40 fn as_str(self) -> &'static str {
41 match self {
42 Self::Text => "text",
43 Self::Json => "json",
44 Self::Markdown => "markdown",
45 Self::StreamingJson => "streaming-json",
46 }
47 }
48
49 fn is_machine_readable(self) -> bool {
50 matches!(self, Self::Json | Self::StreamingJson)
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct CliCommand {
57 pub action: CliAction,
58 pub format: OutputFormat,
59 pub remote_base_url: Option<String>,
60 pub help: bool,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum CliAction {
66 Status,
67 Ingest {
68 source_scope: String,
69 content: String,
70 entity_labels: Vec<String>,
71 },
72 Query {
73 query: String,
74 source_scope: Option<String>,
75 limit: usize,
76 freshness: FreshnessPolicy,
77 },
78 FilesIndex {
79 source_scope: Option<String>,
80 roots: Vec<String>,
81 },
82 FilesQuery {
83 query: String,
84 source_scope: Option<String>,
85 root_id: Option<String>,
86 limit: usize,
87 freshness: FreshnessPolicy,
88 },
89 FilesContentQuery {
90 query: String,
91 source_scope: Option<String>,
92 root_id: Option<String>,
93 limit: usize,
94 freshness: FreshnessPolicy,
95 },
96 GraphInspect,
97 IndexRefresh {
98 kinds: Vec<IndexKind>,
99 },
100 Map(map::MapCommand),
101 WorkerStatus {
102 kind: Option<WorkerKind>,
103 },
104 WorkerRunOnce {
105 kind: Option<WorkerKind>,
106 },
107 ProposalList {
108 state: Option<ProposalState>,
109 limit: usize,
110 },
111 ProposalShow {
112 proposal_id: String,
113 },
114 ProposalAccept {
115 proposal_id: String,
116 actor: String,
117 reason: Option<String>,
118 },
119 ProposalReject {
120 proposal_id: String,
121 actor: String,
122 reason: Option<String>,
123 },
124 ProposalSupersede {
125 proposal_id: String,
126 actor: String,
127 reason: Option<String>,
128 },
129 AuditQuery {
130 operation: Option<String>,
131 limit: usize,
132 },
133 ProviderProbe,
134 Repo(repo::RepoCommand),
135 RepoSet(repo_set::RepoSetCommand),
136 Health,
137 ServiceStatus,
138 ServicePlan(ServicePlanRequest),
139 ServiceDefinitionWrite,
140 ServiceOperatorStatus,
141 ServiceOperatorPause,
142 ServiceOperatorResume,
143 ServiceWorkerRun {
144 task_id: Option<String>,
145 },
146 ServiceRun {
147 mcp: ServiceMcpTransport,
148 web: bool,
149 },
150 SetupDoctor,
151 SetupProfile {
152 profile: setup::SetupProfile,
153 },
154 Version,
155 VersionCheck,
156 Help {
157 path: Vec<String>,
158 },
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum ServiceMcpTransport {
164 Configured,
165 StreamableHttp,
166}
167
168pub async fn run<I, S>(args: I) -> Result<String, CliError>
170where
171 I: IntoIterator<Item = S>,
172 S: Into<String>,
173{
174 let output = crate::bootstrap::cli::run_process(args, false).await?;
175 Ok(output.stdout)
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct CliProcessOutput {
181 pub stdout: String,
182 pub stderr: String,
183}
184
185pub async fn run_process<I, S>(
187 args: I,
188 interactive_text_output: bool,
189) -> Result<CliProcessOutput, CliError>
190where
191 I: IntoIterator<Item = S>,
192 S: Into<String>,
193{
194 crate::bootstrap::cli::run_process(args, interactive_text_output).await
195}
196
197pub async fn process_update_notice<I, S>(args: I, interactive_text_output: bool) -> Option<String>
199where
200 I: IntoIterator<Item = S>,
201 S: Into<String>,
202{
203 let command = CliCommand::parse(args).ok()?;
204 version::update_notice_for_process(&command, interactive_text_output).await
205}
206
207#[cfg(test)]
208use service::ensure_web_remote_bind_allowed;
209
210#[cfg(test)]
211#[path = "tests/naming.rs"]
212mod cli_naming_tests;
213
214#[cfg(test)]
215#[path = "tests/general.rs"]
216mod cli_tests;
217
218#[cfg(test)]
219#[path = "tests/parse.rs"]
220mod cli_parse_tests;
221
222#[cfg(test)]
223#[path = "tests/remote.rs"]
224mod remote_cli_tests;
225
226#[cfg(test)]
227#[path = "tests/map.rs"]
228mod cli_map_tests;
229
230#[cfg(test)]
231#[path = "tests/service.rs"]
232mod cli_service_tests;
233
234#[cfg(test)]
235#[path = "tests/version.rs"]
236mod cli_version_tests;