617 lines
20 KiB
Rust
617 lines
20 KiB
Rust
use crate::ai_agents::AiAgentPermissionMode;
|
|
use crate::claude_cli::ChatStreamRequest;
|
|
use crate::cli_agent_runtime::AgentStreamRequest;
|
|
|
|
const CLAUDE_SAFE_AGENT_TOOLS: &str = "Read,Edit,MultiEdit,Write,Glob,Grep,LS";
|
|
const CLAUDE_POWER_USER_AGENT_TOOLS: &str = "Read,Edit,MultiEdit,Write,Glob,Grep,LS,Bash";
|
|
const CLAUDE_CHAT_DISALLOWED_TOOLS_COMPAT: &str =
|
|
"Bash,Glob,Grep,Read,Edit,Write,NotebookEdit,WebFetch,WebSearch,TodoWrite,Task,MultiEdit,LS";
|
|
const CLAUDE_SAFE_DISALLOWED_TOOLS_COMPAT: &str =
|
|
"Bash,NotebookEdit,WebFetch,WebSearch,TodoWrite,Task";
|
|
const CLAUDE_POWER_USER_DISALLOWED_TOOLS_COMPAT: &str =
|
|
"NotebookEdit,WebFetch,WebSearch,TodoWrite,Task";
|
|
const WINDOWS_COMMAND_LINE_STDIN_THRESHOLD: usize = 24 * 1024;
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct ClaudeInvocation {
|
|
pub(crate) args: Vec<String>,
|
|
pub(crate) fallback_args: Vec<Vec<String>>,
|
|
pub(crate) stdin_text: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
enum PromptSource {
|
|
Argument,
|
|
Stdin,
|
|
}
|
|
|
|
pub(crate) fn chat(req: &ChatStreamRequest) -> ClaudeInvocation {
|
|
chat_with_windows_limit(req, cfg!(windows))
|
|
}
|
|
|
|
pub(crate) fn agent(req: &AgentStreamRequest) -> Result<ClaudeInvocation, String> {
|
|
agent_with_windows_limit(req, cfg!(windows))
|
|
}
|
|
|
|
fn chat_with_windows_limit(
|
|
req: &ChatStreamRequest,
|
|
enforce_windows_limit: bool,
|
|
) -> ClaudeInvocation {
|
|
let args = chat_args(req);
|
|
let fallback_args = vec![chat_args_compat(req)];
|
|
if should_pipe_prompt_for_windows(enforce_windows_limit, &args, &fallback_args) {
|
|
return ClaudeInvocation {
|
|
args: chat_args_with_tool_policy(req, "--tools", String::new(), PromptSource::Stdin),
|
|
fallback_args: vec![chat_args_with_tool_policy(
|
|
req,
|
|
"--disallowedTools",
|
|
CLAUDE_CHAT_DISALLOWED_TOOLS_COMPAT.into(),
|
|
PromptSource::Stdin,
|
|
)],
|
|
stdin_text: Some(stdin_prompt(&req.message, req.system_prompt.as_deref())),
|
|
};
|
|
}
|
|
|
|
ClaudeInvocation {
|
|
args,
|
|
fallback_args,
|
|
stdin_text: None,
|
|
}
|
|
}
|
|
|
|
fn agent_with_windows_limit(
|
|
req: &AgentStreamRequest,
|
|
enforce_windows_limit: bool,
|
|
) -> Result<ClaudeInvocation, String> {
|
|
let args = agent_args(req)?;
|
|
let fallback_args = vec![
|
|
agent_args_without_session_persistence(req)?,
|
|
agent_args_compat(req)?,
|
|
];
|
|
if should_pipe_prompt_for_windows(enforce_windows_limit, &args, &fallback_args) {
|
|
return Ok(ClaudeInvocation {
|
|
args: agent_args_with_tool_policy(
|
|
req,
|
|
AgentToolPolicy::strict(req.permission_mode),
|
|
PromptSource::Stdin,
|
|
)?,
|
|
fallback_args: vec![
|
|
agent_args_with_tool_policy(
|
|
req,
|
|
AgentToolPolicy::strict_without_session_persistence(req.permission_mode),
|
|
PromptSource::Stdin,
|
|
)?,
|
|
agent_args_with_tool_policy(
|
|
req,
|
|
AgentToolPolicy::compat(req.permission_mode),
|
|
PromptSource::Stdin,
|
|
)?,
|
|
],
|
|
stdin_text: Some(stdin_prompt(&req.message, req.system_prompt.as_deref())),
|
|
});
|
|
}
|
|
|
|
Ok(ClaudeInvocation {
|
|
args,
|
|
fallback_args,
|
|
stdin_text: None,
|
|
})
|
|
}
|
|
|
|
fn chat_args(req: &ChatStreamRequest) -> Vec<String> {
|
|
chat_args_with_tool_policy(req, "--tools", String::new(), PromptSource::Argument)
|
|
}
|
|
|
|
fn chat_args_compat(req: &ChatStreamRequest) -> Vec<String> {
|
|
chat_args_with_tool_policy(
|
|
req,
|
|
"--disallowedTools",
|
|
CLAUDE_CHAT_DISALLOWED_TOOLS_COMPAT.into(),
|
|
PromptSource::Argument,
|
|
)
|
|
}
|
|
|
|
fn chat_args_with_tool_policy(
|
|
req: &ChatStreamRequest,
|
|
tool_flag: &str,
|
|
tool_value: String,
|
|
prompt_source: PromptSource,
|
|
) -> Vec<String> {
|
|
let mut args: Vec<String> = vec!["-p".into()];
|
|
if prompt_source == PromptSource::Argument {
|
|
args.push(req.message.clone());
|
|
}
|
|
args.extend([
|
|
"--output-format".into(),
|
|
"stream-json".into(),
|
|
"--verbose".into(),
|
|
"--include-partial-messages".into(),
|
|
tool_flag.into(),
|
|
tool_value,
|
|
]);
|
|
|
|
if prompt_source == PromptSource::Argument {
|
|
append_non_empty_arg_pair(&mut args, "--system-prompt", req.system_prompt.as_deref());
|
|
}
|
|
if let Some(ref session_id) = req.session_id {
|
|
args.push("--resume".into());
|
|
args.push(session_id.clone());
|
|
}
|
|
|
|
args
|
|
}
|
|
|
|
fn agent_args(req: &AgentStreamRequest) -> Result<Vec<String>, String> {
|
|
agent_args_with_tool_policy(
|
|
req,
|
|
AgentToolPolicy::strict(req.permission_mode),
|
|
PromptSource::Argument,
|
|
)
|
|
}
|
|
|
|
fn agent_args_without_session_persistence(req: &AgentStreamRequest) -> Result<Vec<String>, String> {
|
|
agent_args_with_tool_policy(
|
|
req,
|
|
AgentToolPolicy::strict_without_session_persistence(req.permission_mode),
|
|
PromptSource::Argument,
|
|
)
|
|
}
|
|
|
|
fn agent_args_compat(req: &AgentStreamRequest) -> Result<Vec<String>, String> {
|
|
agent_args_with_tool_policy(
|
|
req,
|
|
AgentToolPolicy::compat(req.permission_mode),
|
|
PromptSource::Argument,
|
|
)
|
|
}
|
|
|
|
fn agent_args_with_tool_policy(
|
|
req: &AgentStreamRequest,
|
|
policy: AgentToolPolicy,
|
|
prompt_source: PromptSource,
|
|
) -> Result<Vec<String>, String> {
|
|
let mut args: Vec<String> = vec!["-p".into()];
|
|
if prompt_source == PromptSource::Argument {
|
|
args.push(req.message.clone());
|
|
}
|
|
args.extend([
|
|
"--output-format".into(),
|
|
"stream-json".into(),
|
|
"--verbose".into(),
|
|
"--include-partial-messages".into(),
|
|
"--mcp-config".into(),
|
|
mcp_config(&req.vault_path, &req.vault_paths)?,
|
|
"--strict-mcp-config".into(),
|
|
"--permission-mode".into(),
|
|
"acceptEdits".into(),
|
|
policy.tool_flag.into(),
|
|
policy.tool_value.into(),
|
|
]);
|
|
|
|
if policy.include_session_persistence_flag {
|
|
args.push("--no-session-persistence".into());
|
|
}
|
|
if let Some(allowed_tools) = policy.preapproved_tools {
|
|
args.push("--allowedTools".into());
|
|
args.push(allowed_tools.into());
|
|
}
|
|
if let Some(disallowed_tools) = policy.disallowed_tools {
|
|
args.push("--disallowedTools".into());
|
|
args.push(disallowed_tools.into());
|
|
}
|
|
if prompt_source == PromptSource::Argument {
|
|
append_non_empty_arg_pair(
|
|
&mut args,
|
|
"--append-system-prompt",
|
|
req.system_prompt.as_deref(),
|
|
);
|
|
}
|
|
|
|
Ok(args)
|
|
}
|
|
|
|
fn append_non_empty_arg_pair(args: &mut Vec<String>, flag: &str, value: Option<&str>) {
|
|
if let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) {
|
|
args.push(flag.into());
|
|
args.push(value.into());
|
|
}
|
|
}
|
|
|
|
fn stdin_prompt(message: &str, system_prompt: Option<&str>) -> String {
|
|
crate::cli_agent_runtime::build_prompt(message, system_prompt)
|
|
}
|
|
|
|
fn mcp_config(vault_path: &str, vault_paths: &[String]) -> Result<String, String> {
|
|
let mcp_server_path = crate::cli_agent_runtime::mcp_server_path_string()?;
|
|
let vault_paths = crate::cli_agent_runtime::active_vault_paths_json(vault_path, vault_paths);
|
|
let config = serde_json::json!({
|
|
"mcpServers": {
|
|
"tolaria": {
|
|
"command": "node",
|
|
"args": [mcp_server_path],
|
|
"env": {
|
|
"VAULT_PATH": vault_path,
|
|
"VAULT_PATHS": vault_paths
|
|
}
|
|
}
|
|
}
|
|
});
|
|
serde_json::to_string(&config).map_err(|e| format!("Failed to serialise MCP config: {e}"))
|
|
}
|
|
|
|
fn should_pipe_prompt_for_windows(
|
|
enforce_windows_limit: bool,
|
|
args: &[String],
|
|
fallback_args: &[Vec<String>],
|
|
) -> bool {
|
|
enforce_windows_limit
|
|
&& std::iter::once(args)
|
|
.chain(fallback_args.iter().map(Vec::as_slice))
|
|
.any(args_exceed_windows_stdin_threshold)
|
|
}
|
|
|
|
fn args_exceed_windows_stdin_threshold(args: &[String]) -> bool {
|
|
windows_command_line_utf16_units(args) >= WINDOWS_COMMAND_LINE_STDIN_THRESHOLD
|
|
}
|
|
|
|
fn windows_command_line_utf16_units(args: &[String]) -> usize {
|
|
args.iter().map(|arg| arg.encode_utf16().count() + 3).sum()
|
|
}
|
|
|
|
struct AgentToolPolicy {
|
|
tool_flag: &'static str,
|
|
tool_value: &'static str,
|
|
include_session_persistence_flag: bool,
|
|
preapproved_tools: Option<&'static str>,
|
|
disallowed_tools: Option<&'static str>,
|
|
}
|
|
|
|
impl AgentToolPolicy {
|
|
fn strict(permission_mode: AiAgentPermissionMode) -> Self {
|
|
Self {
|
|
tool_flag: "--tools",
|
|
tool_value: agent_tools(permission_mode),
|
|
include_session_persistence_flag: true,
|
|
preapproved_tools: preapproved_agent_tools(permission_mode),
|
|
disallowed_tools: None,
|
|
}
|
|
}
|
|
|
|
fn strict_without_session_persistence(permission_mode: AiAgentPermissionMode) -> Self {
|
|
Self {
|
|
include_session_persistence_flag: false,
|
|
..Self::strict(permission_mode)
|
|
}
|
|
}
|
|
|
|
fn compat(permission_mode: AiAgentPermissionMode) -> Self {
|
|
Self {
|
|
tool_flag: "--allowedTools",
|
|
tool_value: agent_tools(permission_mode),
|
|
include_session_persistence_flag: false,
|
|
preapproved_tools: None,
|
|
disallowed_tools: Some(disallowed_agent_tools_compat(permission_mode)),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn agent_tools(permission_mode: AiAgentPermissionMode) -> &'static str {
|
|
match permission_mode {
|
|
AiAgentPermissionMode::Safe => CLAUDE_SAFE_AGENT_TOOLS,
|
|
AiAgentPermissionMode::PowerUser => CLAUDE_POWER_USER_AGENT_TOOLS,
|
|
}
|
|
}
|
|
|
|
fn preapproved_agent_tools(permission_mode: AiAgentPermissionMode) -> Option<&'static str> {
|
|
match permission_mode {
|
|
AiAgentPermissionMode::Safe => None,
|
|
AiAgentPermissionMode::PowerUser => Some("Bash"),
|
|
}
|
|
}
|
|
|
|
fn disallowed_agent_tools_compat(permission_mode: AiAgentPermissionMode) -> &'static str {
|
|
match permission_mode {
|
|
AiAgentPermissionMode::Safe => CLAUDE_SAFE_DISALLOWED_TOOLS_COMPAT,
|
|
AiAgentPermissionMode::PowerUser => CLAUDE_POWER_USER_DISALLOWED_TOOLS_COMPAT,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
macro_rules! assert_args_contain {
|
|
($args:expr, [$($value:expr),+ $(,)?] $(,)?) => {
|
|
$(
|
|
assert!($args.contains(&$value.to_string()), "missing {}", $value);
|
|
)+
|
|
};
|
|
}
|
|
|
|
macro_rules! assert_args_lack {
|
|
($args:expr, [$($value:expr),+ $(,)?] $(,)?) => {
|
|
$(
|
|
assert!(!$args.iter().any(|arg| arg == &$value.to_string()), "unexpected {}", $value);
|
|
)+
|
|
};
|
|
}
|
|
|
|
macro_rules! assert_no_arg_contains {
|
|
($args:expr, $fragment:expr $(,)?) => {
|
|
assert!(!$args.iter().any(|arg| arg.contains($fragment)));
|
|
};
|
|
}
|
|
|
|
macro_rules! chat_request {
|
|
($message:expr, None, None $(,)?) => {
|
|
ChatStreamRequest {
|
|
message: $message.into(),
|
|
system_prompt: None,
|
|
session_id: None,
|
|
}
|
|
};
|
|
($message:expr, Some($system_prompt:expr), None $(,)?) => {
|
|
ChatStreamRequest {
|
|
message: $message.into(),
|
|
system_prompt: Some($system_prompt.to_string()),
|
|
session_id: None,
|
|
}
|
|
};
|
|
($message:expr, None, Some($session_id:expr) $(,)?) => {
|
|
ChatStreamRequest {
|
|
message: $message.into(),
|
|
system_prompt: None,
|
|
session_id: Some($session_id.to_string()),
|
|
}
|
|
};
|
|
($message:expr, Some($system_prompt:expr), Some($session_id:expr) $(,)?) => {
|
|
ChatStreamRequest {
|
|
message: $message.into(),
|
|
system_prompt: Some($system_prompt.to_string()),
|
|
session_id: Some($session_id.to_string()),
|
|
}
|
|
};
|
|
}
|
|
|
|
fn agent_request(
|
|
message: &str,
|
|
system_prompt: Option<&str>,
|
|
permission_mode: AiAgentPermissionMode,
|
|
) -> AgentStreamRequest {
|
|
AgentStreamRequest {
|
|
message: message.into(),
|
|
system_prompt: system_prompt.map(str::to_string),
|
|
vault_path: "/tmp/vault".into(),
|
|
vault_paths: Vec::new(),
|
|
permission_mode,
|
|
}
|
|
}
|
|
|
|
fn arg_value_after<'a>(args: &'a [String], name: &str) -> Option<&'a str> {
|
|
let index = args.iter().position(|arg| arg == name)?;
|
|
args.get(index + 1).map(String::as_str)
|
|
}
|
|
|
|
#[test]
|
|
fn chat_args_basic() {
|
|
let args = chat_args(&chat_request!("hello", None, None));
|
|
|
|
assert_args_contain!(args, ["-p", "hello", "stream-json"]);
|
|
assert_args_lack!(args, ["--system-prompt", "--resume"]);
|
|
}
|
|
|
|
#[test]
|
|
fn chat_args_with_system_prompt() {
|
|
let args = chat_args(&chat_request!("hi", Some("You are helpful."), None));
|
|
|
|
assert_args_contain!(args, ["--system-prompt", "You are helpful."]);
|
|
}
|
|
|
|
#[test]
|
|
fn chat_args_empty_system_prompt_is_skipped() {
|
|
let args = chat_args(&chat_request!("hi", Some(""), None));
|
|
|
|
assert!(!args.contains(&"--system-prompt".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn chat_args_with_session_id() {
|
|
let args = chat_args(&chat_request!("continue", None, Some("sess-abc")));
|
|
|
|
assert_args_contain!(args, ["--resume", "sess-abc"]);
|
|
}
|
|
|
|
#[test]
|
|
fn chat_args_compat_disallows_builtin_tools_when_tools_flag_is_unavailable() {
|
|
let args = chat_args_compat(&chat_request!("hello", None, None));
|
|
|
|
assert_args_contain!(args, ["--disallowedTools"]);
|
|
assert_args_lack!(args, ["--tools"]);
|
|
assert!(arg_value_after(&args, "--disallowedTools")
|
|
.is_some_and(|tools| tools.contains("NotebookEdit")));
|
|
}
|
|
|
|
#[test]
|
|
fn oversized_windows_invocations_pipe_prompt_to_stdin() {
|
|
let long_message = "x".repeat(WINDOWS_COMMAND_LINE_STDIN_THRESHOLD);
|
|
let chat_req = chat_request!(&long_message, Some("Use context."), Some("sess-abc"));
|
|
let chat_invocation = chat_with_windows_limit(&chat_req, true);
|
|
|
|
assert_eq!(chat_invocation.args.first().map(String::as_str), Some("-p"));
|
|
assert_args_lack!(
|
|
chat_invocation.args,
|
|
[long_message.as_str(), "--system-prompt", "Use context."]
|
|
);
|
|
assert_args_contain!(chat_invocation.args, ["--resume", "sess-abc"]);
|
|
assert!(chat_invocation
|
|
.fallback_args
|
|
.iter()
|
|
.flatten()
|
|
.all(|arg| arg != &long_message));
|
|
let chat_stdin = chat_invocation.stdin_text.as_deref().unwrap();
|
|
assert!(chat_stdin.contains("System instructions:\nUse context."));
|
|
assert!(chat_stdin.contains("User request:\n"));
|
|
assert!(chat_stdin.contains(&long_message));
|
|
|
|
let agent_req = agent_request(
|
|
&long_message,
|
|
Some("Read the active vault first."),
|
|
AiAgentPermissionMode::Safe,
|
|
);
|
|
let agent_invocation = agent_with_windows_limit(&agent_req, true).unwrap();
|
|
|
|
assert_eq!(
|
|
agent_invocation.args.first().map(String::as_str),
|
|
Some("-p")
|
|
);
|
|
assert_args_lack!(
|
|
agent_invocation.args,
|
|
[
|
|
long_message.as_str(),
|
|
"--append-system-prompt",
|
|
"Read the active vault first."
|
|
]
|
|
);
|
|
assert_args_contain!(
|
|
agent_invocation.args,
|
|
["--mcp-config", "--strict-mcp-config"]
|
|
);
|
|
assert!(agent_invocation
|
|
.fallback_args
|
|
.iter()
|
|
.flatten()
|
|
.all(|arg| arg != &long_message));
|
|
let agent_stdin = agent_invocation.stdin_text.as_deref().unwrap();
|
|
assert!(agent_stdin.contains("System instructions:\nRead the active vault first."));
|
|
assert!(agent_stdin.contains("User request:\n"));
|
|
assert!(agent_stdin.contains(&long_message));
|
|
}
|
|
|
|
#[test]
|
|
fn agent_args_with_system_prompt() {
|
|
if let Ok(args) = agent_args(&agent_request(
|
|
"do it",
|
|
Some("Act as expert."),
|
|
AiAgentPermissionMode::Safe,
|
|
)) {
|
|
assert_args_contain!(args, ["--append-system-prompt", "Act as expert."]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn agent_args_empty_system_prompt_is_skipped() {
|
|
if let Ok(args) = agent_args(&agent_request("x", Some(""), AiAgentPermissionMode::Safe)) {
|
|
assert!(!args.contains(&"--append-system-prompt".to_string()));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn agent_args_without_session_persistence_keeps_tools_allowlist() {
|
|
let args = agent_args_without_session_persistence(&agent_request(
|
|
"Rename the note",
|
|
None,
|
|
AiAgentPermissionMode::Safe,
|
|
))
|
|
.unwrap();
|
|
|
|
assert_args_contain!(args, ["--tools", "Read,Edit,MultiEdit,Write,Glob,Grep,LS"]);
|
|
assert_args_lack!(args, ["--no-session-persistence"]);
|
|
}
|
|
|
|
#[test]
|
|
fn agent_args_compat_uses_allowed_and_disallowed_tools_without_removed_flags() {
|
|
let args = agent_args_compat(&agent_request(
|
|
"Rename the note",
|
|
None,
|
|
AiAgentPermissionMode::Safe,
|
|
))
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
arg_value_after(&args, "--allowedTools"),
|
|
Some("Read,Edit,MultiEdit,Write,Glob,Grep,LS")
|
|
);
|
|
assert_args_contain!(args, ["--disallowedTools"]);
|
|
assert_args_lack!(args, ["--tools"]);
|
|
assert_args_lack!(args, ["--no-session-persistence"]);
|
|
}
|
|
|
|
#[test]
|
|
fn agent_args_use_safe_mode_without_bash_by_default() {
|
|
let args = agent_args(&agent_request(
|
|
"Rename the note",
|
|
None,
|
|
AiAgentPermissionMode::Safe,
|
|
))
|
|
.unwrap();
|
|
|
|
assert_args_contain!(
|
|
args,
|
|
["--strict-mcp-config", "--permission-mode", "acceptEdits"]
|
|
);
|
|
assert_args_contain!(args, ["Read,Edit,MultiEdit,Write,Glob,Grep,LS"]);
|
|
assert_no_arg_contains!(args, "Bash");
|
|
assert_args_lack!(args, ["--allowedTools"]);
|
|
assert_args_lack!(args, ["--dangerously-skip-permissions"]);
|
|
}
|
|
|
|
#[test]
|
|
fn agent_args_allow_bash_in_power_user_mode_without_dangerous_bypass() {
|
|
let args = agent_args(&agent_request(
|
|
"Rename the note",
|
|
None,
|
|
AiAgentPermissionMode::PowerUser,
|
|
))
|
|
.unwrap();
|
|
|
|
assert_args_contain!(args, ["--strict-mcp-config"]);
|
|
assert_args_contain!(args, ["Read,Edit,MultiEdit,Write,Glob,Grep,LS,Bash"]);
|
|
assert_args_lack!(args, ["--dangerously-skip-permissions"]);
|
|
}
|
|
|
|
#[test]
|
|
fn agent_args_preapprove_bash_for_power_user_runs() {
|
|
let args = agent_args(&agent_request(
|
|
"Run a local script",
|
|
None,
|
|
AiAgentPermissionMode::PowerUser,
|
|
))
|
|
.unwrap();
|
|
|
|
assert_eq!(arg_value_after(&args, "--allowedTools"), Some("Bash"));
|
|
}
|
|
|
|
#[test]
|
|
fn agent_invocation_keeps_short_windows_prompt_on_args() {
|
|
let req = agent_request(
|
|
"summarize the inbox",
|
|
Some("Read the active vault first."),
|
|
AiAgentPermissionMode::Safe,
|
|
);
|
|
let invocation = agent_with_windows_limit(&req, true).unwrap();
|
|
|
|
assert_args_contain!(
|
|
invocation.args,
|
|
["-p", "summarize the inbox", "--append-system-prompt"]
|
|
);
|
|
assert!(invocation.stdin_text.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn mcp_config_is_valid_json() {
|
|
let extra_vaults = vec!["/tmp/secondary-vault".to_string()];
|
|
if let Ok(config_str) = mcp_config("/tmp/test-vault", &extra_vaults) {
|
|
let parsed: serde_json::Value = serde_json::from_str(&config_str).unwrap();
|
|
assert!(parsed["mcpServers"]["tolaria"]["command"].is_string());
|
|
assert_eq!(
|
|
parsed["mcpServers"]["tolaria"]["env"]["VAULT_PATH"],
|
|
"/tmp/test-vault"
|
|
);
|
|
assert_eq!(
|
|
parsed["mcpServers"]["tolaria"]["env"]["VAULT_PATHS"],
|
|
"[\"/tmp/test-vault\",\"/tmp/secondary-vault\"]"
|
|
);
|
|
}
|
|
}
|
|
}
|