Skip to main content

objectiveai_sdk/cli/command/db/
mod.rs

1//! `db` — direct database access at the CLI level.
2//!
3//! Leaves:
4//! - `query` — execute arbitrary single-statement read-only SQL
5//!   with a required timeout and an optional per-response token
6//!   budget.
7//! - `spawn` — start the `objectiveai-db` postgres vehicle using
8//!   `config db` connection settings.
9//! - `kill` — stop the postmaster started by `db spawn`.
10
11use crate::cli::command::CommandRequest;
12
13pub mod config;
14pub mod kill;
15pub mod query;
16pub mod spawn;
17
18#[derive(clap::Subcommand)]
19pub enum Command {
20    Config {
21        #[command(subcommand)]
22        command: config::Command,
23    },
24    /// Stop the postmaster started by `db spawn`.
25    /// Idempotent — succeeds with count = 0 if none was running.
26    Kill(kill::Command),
27    /// Execute an arbitrary single-statement read-only SQL query
28    /// against the configured postgres pool.
29    Query(query::Command),
30    /// Start the `objectiveai-db` server in the background using
31    /// `config db` connection settings. Errors if something is
32    /// already listening there.
33    Spawn(spawn::Command),
34}
35
36#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
37#[serde(untagged)]
38#[schemars(rename = "cli.command.db.Request")]
39pub enum Request {
40    #[schemars(title = "Config")]
41    Config(config::Request),
42    #[schemars(title = "Kill")]
43    Kill(kill::Request),
44    #[schemars(title = "KillRequestSchema")]
45    KillRequestSchema(kill::request_schema::Request),
46    #[schemars(title = "KillResponseSchema")]
47    KillResponseSchema(kill::response_schema::Request),
48    #[schemars(title = "Query")]
49    Query(query::Request),
50    #[schemars(title = "QueryRequestSchema")]
51    QueryRequestSchema(query::request_schema::Request),
52    #[schemars(title = "QueryResponseSchema")]
53    QueryResponseSchema(query::response_schema::Request),
54    #[schemars(title = "Spawn")]
55    Spawn(spawn::Request),
56    #[schemars(title = "SpawnRequestSchema")]
57    SpawnRequestSchema(spawn::request_schema::Request),
58    #[schemars(title = "SpawnResponseSchema")]
59    SpawnResponseSchema(spawn::response_schema::Request),
60}
61
62// Exempt from json-schema coverage: tier aggregate (see the root
63// `ResponseItem` in command.rs - TS7056).
64#[objectiveai_sdk_macros::json_schema_ignore]
65#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
66#[schemars(rename = "cli.command.db.ResponseItem")]
67#[serde(untagged)]
68pub enum ResponseItem {
69    #[schemars(title = "Config")]
70    Config(config::Response),
71    #[schemars(title = "Kill")]
72    Kill(kill::Response),
73    #[schemars(title = "KillRequestSchema")]
74    KillRequestSchema(kill::request_schema::Response),
75    #[schemars(title = "KillResponseSchema")]
76    KillResponseSchema(kill::response_schema::Response),
77    #[schemars(title = "Query")]
78    Query(query::Response),
79    #[schemars(title = "QueryRequestSchema")]
80    QueryRequestSchema(query::request_schema::Response),
81    #[schemars(title = "QueryResponseSchema")]
82    QueryResponseSchema(query::response_schema::Response),
83    #[schemars(title = "Spawn")]
84    Spawn(spawn::Response),
85    #[schemars(title = "SpawnRequestSchema")]
86    SpawnRequestSchema(spawn::request_schema::Response),
87    #[schemars(title = "SpawnResponseSchema")]
88    SpawnResponseSchema(spawn::response_schema::Response),
89}
90
91#[cfg(feature = "mcp")]
92impl crate::cli::command::CommandResponse for ResponseItem {
93    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
94        match self {
95            ResponseItem::Config(v) => v.into_mcp(),
96            ResponseItem::Kill(v) => v.into_mcp(),
97            ResponseItem::KillRequestSchema(v) => v.into_mcp(),
98            ResponseItem::KillResponseSchema(v) => v.into_mcp(),
99            ResponseItem::Query(v) => v.into_mcp(),
100            ResponseItem::QueryRequestSchema(v) => v.into_mcp(),
101            ResponseItem::QueryResponseSchema(v) => v.into_mcp(),
102            ResponseItem::Spawn(v) => v.into_mcp(),
103            ResponseItem::SpawnRequestSchema(v) => v.into_mcp(),
104            ResponseItem::SpawnResponseSchema(v) => v.into_mcp(),
105        }
106    }
107}
108
109impl TryFrom<Command> for Request {
110    type Error = crate::cli::command::FromArgsError;
111    fn try_from(command: Command) -> Result<Self, Self::Error> {
112        match command {
113            Command::Config { command } =>
114                Ok(Request::Config(config::Request::try_from(command)?)),
115            Command::Kill(cmd) => match cmd.schema {
116                None => Ok(Request::Kill(kill::Request::try_from(cmd.args)?)),
117                Some(kill::Schema::RequestSchema(args)) => Ok(
118                    Request::KillRequestSchema(kill::request_schema::Request::try_from(args)?),
119                ),
120                Some(kill::Schema::ResponseSchema(args)) => Ok(
121                    Request::KillResponseSchema(kill::response_schema::Request::try_from(args)?),
122                ),
123            },
124            Command::Query(cmd) => match cmd.schema {
125                None => Ok(Request::Query(query::Request::try_from(cmd.args)?)),
126                Some(query::Schema::RequestSchema(args)) => Ok(
127                    Request::QueryRequestSchema(query::request_schema::Request::try_from(args)?),
128                ),
129                Some(query::Schema::ResponseSchema(args)) => Ok(
130                    Request::QueryResponseSchema(query::response_schema::Request::try_from(args)?),
131                ),
132            },
133            Command::Spawn(cmd) => match cmd.schema {
134                None => Ok(Request::Spawn(spawn::Request::try_from(cmd.args)?)),
135                Some(spawn::Schema::RequestSchema(args)) => Ok(
136                    Request::SpawnRequestSchema(spawn::request_schema::Request::try_from(args)?),
137                ),
138                Some(spawn::Schema::ResponseSchema(args)) => Ok(
139                    Request::SpawnResponseSchema(spawn::response_schema::Request::try_from(args)?),
140                ),
141            },
142        }
143    }
144}
145
146impl CommandRequest for Request {
147    fn into_command(&self) -> Vec<String> {
148        match self {
149            Request::Config(inner) => inner.into_command(),
150            Request::Kill(inner) => inner.into_command(),
151            Request::KillRequestSchema(inner) => inner.into_command(),
152            Request::KillResponseSchema(inner) => inner.into_command(),
153            Request::Query(inner) => inner.into_command(),
154            Request::QueryRequestSchema(inner) => inner.into_command(),
155            Request::QueryResponseSchema(inner) => inner.into_command(),
156            Request::Spawn(inner) => inner.into_command(),
157            Request::SpawnRequestSchema(inner) => inner.into_command(),
158            Request::SpawnResponseSchema(inner) => inner.into_command(),
159        }
160    }
161
162    fn request_base(&self) -> &crate::cli::command::RequestBase {
163        match self {
164            Request::Config(inner) => inner.request_base(),
165            Request::Kill(inner) => inner.request_base(),
166            Request::KillRequestSchema(inner) => inner.request_base(),
167            Request::KillResponseSchema(inner) => inner.request_base(),
168            Request::Query(inner) => inner.request_base(),
169            Request::QueryRequestSchema(inner) => inner.request_base(),
170            Request::QueryResponseSchema(inner) => inner.request_base(),
171            Request::Spawn(inner) => inner.request_base(),
172            Request::SpawnRequestSchema(inner) => inner.request_base(),
173            Request::SpawnResponseSchema(inner) => inner.request_base(),
174        }
175    }
176
177    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
178        match self {
179            Request::Config(inner) => inner.request_base_mut(),
180            Request::Kill(inner) => inner.request_base_mut(),
181            Request::KillRequestSchema(inner) => inner.request_base_mut(),
182            Request::KillResponseSchema(inner) => inner.request_base_mut(),
183            Request::Query(inner) => inner.request_base_mut(),
184            Request::QueryRequestSchema(inner) => inner.request_base_mut(),
185            Request::QueryResponseSchema(inner) => inner.request_base_mut(),
186            Request::Spawn(inner) => inner.request_base_mut(),
187            Request::SpawnRequestSchema(inner) => inner.request_base_mut(),
188            Request::SpawnResponseSchema(inner) => inner.request_base_mut(),
189        }
190    }
191}
192
193#[cfg(feature = "cli-executor")]
194pub async fn execute<E: crate::cli::command::CommandExecutor>(
195    executor: &E,
196    request: Request,
197    agent_arguments: Option<&crate::cli::command::AgentArguments>,
198) -> Result<
199    std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>>,
200    E::Error,
201> {
202    use futures::StreamExt;
203    let stream: std::pin::Pin<
204        Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>,
205    > = match request {
206            Request::Config(req) => {
207                let inner = config::execute(executor, req, agent_arguments).await?;
208                Box::pin(inner.map(|r| r.map(ResponseItem::Config)))
209            }
210        Request::Kill(req) => {
211            let value = kill::execute(executor, req, agent_arguments).await?;
212            Box::pin(crate::cli::command::StreamOnce::new(Ok(
213                ResponseItem::Kill(value),
214            )))
215        }
216        Request::KillRequestSchema(req) => {
217            let value = kill::request_schema::execute(executor, req, agent_arguments).await?;
218            Box::pin(crate::cli::command::StreamOnce::new(Ok(
219                ResponseItem::KillRequestSchema(value),
220            )))
221        }
222        Request::KillResponseSchema(req) => {
223            let value = kill::response_schema::execute(executor, req, agent_arguments).await?;
224            Box::pin(crate::cli::command::StreamOnce::new(Ok(
225                ResponseItem::KillResponseSchema(value),
226            )))
227        }
228        Request::Query(req) => {
229            let value = query::execute(executor, req, agent_arguments).await?;
230            Box::pin(crate::cli::command::StreamOnce::new(Ok(
231                ResponseItem::Query(value),
232            )))
233        }
234        Request::QueryRequestSchema(req) => {
235            let value = query::request_schema::execute(executor, req, agent_arguments).await?;
236            Box::pin(crate::cli::command::StreamOnce::new(Ok(
237                ResponseItem::QueryRequestSchema(value),
238            )))
239        }
240        Request::QueryResponseSchema(req) => {
241            let value = query::response_schema::execute(executor, req, agent_arguments).await?;
242            Box::pin(crate::cli::command::StreamOnce::new(Ok(
243                ResponseItem::QueryResponseSchema(value),
244            )))
245        }
246        Request::Spawn(req) => {
247            let value = spawn::execute(executor, req, agent_arguments).await?;
248            Box::pin(crate::cli::command::StreamOnce::new(Ok(
249                ResponseItem::Spawn(value),
250            )))
251        }
252        Request::SpawnRequestSchema(req) => {
253            let value = spawn::request_schema::execute(executor, req, agent_arguments).await?;
254            Box::pin(crate::cli::command::StreamOnce::new(Ok(
255                ResponseItem::SpawnRequestSchema(value),
256            )))
257        }
258        Request::SpawnResponseSchema(req) => {
259            let value = spawn::response_schema::execute(executor, req, agent_arguments).await?;
260            Box::pin(crate::cli::command::StreamOnce::new(Ok(
261                ResponseItem::SpawnResponseSchema(value),
262            )))
263        }
264    };
265    Ok(stream)
266}
267
268#[cfg(feature = "cli-executor")]
269pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
270    executor: &E,
271    request: Request,
272    transform: crate::cli::command::Transform,
273    agent_arguments: Option<&crate::cli::command::AgentArguments>,
274) -> Result<
275    std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>>,
276    E::Error,
277> {
278    let stream: std::pin::Pin<
279        Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>,
280    > = match request {
281            Request::Config(req) => {
282                let inner = config::execute_transform(executor, req, transform, agent_arguments).await?;
283                Box::pin(inner)
284            }
285        Request::Kill(req) => {
286            let value = kill::execute_transform(executor, req, transform, agent_arguments).await?;
287            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
288        }
289        Request::KillRequestSchema(req) => {
290            let value =
291                kill::request_schema::execute_transform(executor, req, transform, agent_arguments).await?;
292            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
293        }
294        Request::KillResponseSchema(req) => {
295            let value =
296                kill::response_schema::execute_transform(executor, req, transform, agent_arguments).await?;
297            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
298        }
299        Request::Query(req) => {
300            let value = query::execute_transform(executor, req, transform, agent_arguments).await?;
301            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
302        }
303        Request::QueryRequestSchema(req) => {
304            let value =
305                query::request_schema::execute_transform(executor, req, transform, agent_arguments).await?;
306            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
307        }
308        Request::QueryResponseSchema(req) => {
309            let value =
310                query::response_schema::execute_transform(executor, req, transform, agent_arguments).await?;
311            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
312        }
313        Request::Spawn(req) => {
314            let value = spawn::execute_transform(executor, req, transform, agent_arguments).await?;
315            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
316        }
317        Request::SpawnRequestSchema(req) => {
318            let value =
319                spawn::request_schema::execute_transform(executor, req, transform, agent_arguments).await?;
320            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
321        }
322        Request::SpawnResponseSchema(req) => {
323            let value =
324                spawn::response_schema::execute_transform(executor, req, transform, agent_arguments).await?;
325            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
326        }
327    };
328    Ok(stream)
329}