Skip to content

Commit 146f831

Browse files
committed
Merge PR #261: fix: batch fixes for issues #2508-#2540
2 parents 3c1942b + e0da850 commit 146f831

File tree

6 files changed

+524
-1
lines changed

6 files changed

+524
-1
lines changed

cortex-cli/src/acp_cmd.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ pub struct AcpCli {
4141
/// Agent to use.
4242
#[arg(long = "agent")]
4343
pub agent: Option<String>,
44+
45+
/// Tools to allow (whitelist). Can be specified multiple times.
46+
/// Only these tools will be available to the agent.
47+
#[arg(long = "allow-tool", action = clap::ArgAction::Append)]
48+
pub allow_tools: Vec<String>,
49+
50+
/// Tools to deny (blacklist). Can be specified multiple times.
51+
/// These tools will be blocked from use.
52+
#[arg(long = "deny-tool", action = clap::ArgAction::Append)]
53+
pub deny_tools: Vec<String>,
4454
}
4555

4656
impl AcpCli {
@@ -58,6 +68,17 @@ impl AcpCli {
5868
config.model = resolve_model_alias(model).to_string();
5969
}
6070

71+
// Report tool restrictions (will be applied when server initializes session)
72+
if !self.allow_tools.is_empty() {
73+
eprintln!("Tool whitelist: {:?}", self.allow_tools);
74+
// Note: Tool restrictions are passed via server configuration
75+
}
76+
77+
if !self.deny_tools.is_empty() {
78+
eprintln!("Tool blacklist: {:?}", self.deny_tools);
79+
// Note: Tool restrictions are passed via server configuration
80+
}
81+
6182
// Decide transport mode
6283
if self.stdio || self.port == 0 {
6384
// Use stdio transport

cortex-cli/src/agent_cmd.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub enum AgentSubcommand {
3636

3737
/// Remove a user-defined agent.
3838
Remove(RemoveArgs),
39+
40+
/// Install an agent from the registry.
41+
Install(InstallArgs),
3942
}
4043

4144
/// Arguments for list command.
@@ -147,6 +150,21 @@ pub struct RemoveArgs {
147150
pub force: bool,
148151
}
149152

153+
/// Arguments for install command.
154+
#[derive(Debug, Parser)]
155+
pub struct InstallArgs {
156+
/// Name or URL of the agent to install from registry.
157+
pub name: String,
158+
159+
/// Force overwrite if agent already exists.
160+
#[arg(short, long)]
161+
pub force: bool,
162+
163+
/// Registry URL to install from (defaults to official registry).
164+
#[arg(long)]
165+
pub registry: Option<String>,
166+
}
167+
150168
/// Agent operation mode.
151169
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
152170
#[serde(rename_all = "lowercase")]
@@ -317,6 +335,7 @@ impl AgentCli {
317335
AgentSubcommand::Create(args) => run_create(args).await,
318336
AgentSubcommand::Edit(args) => run_edit(args).await,
319337
AgentSubcommand::Remove(args) => run_remove(args).await,
338+
AgentSubcommand::Install(args) => run_install(args).await,
320339
}
321340
}
322341
}
@@ -1593,6 +1612,89 @@ async fn run_remove(args: RemoveArgs) -> Result<()> {
15931612
Ok(())
15941613
}
15951614

1615+
/// Install agent from registry.
1616+
async fn run_install(args: InstallArgs) -> Result<()> {
1617+
let registry_url = args
1618+
.registry
1619+
.as_deref()
1620+
.unwrap_or("https://registry.cortex.foundation");
1621+
1622+
println!("Installing agent '{}' from registry...", args.name);
1623+
println!(" Registry: {}", registry_url);
1624+
1625+
// Construct the agent download URL
1626+
let agent_url = format!("{}/agents/{}.md", registry_url, args.name);
1627+
1628+
// Download the agent file
1629+
println!(" Fetching: {}", agent_url);
1630+
1631+
let response = reqwest::get(&agent_url).await;
1632+
1633+
let content = match response {
1634+
Ok(resp) => {
1635+
if !resp.status().is_success() {
1636+
bail!(
1637+
"Agent '{}' not found in registry (HTTP {}).\n\n\
1638+
Available options:\n\
1639+
1. Check the agent name is correct\n\
1640+
2. Use --registry to specify a different registry URL\n\
1641+
3. Create a custom agent with: cortex agent create {}",
1642+
args.name,
1643+
resp.status(),
1644+
args.name
1645+
);
1646+
}
1647+
resp.text()
1648+
.await
1649+
.with_context(|| "Failed to read agent content")?
1650+
}
1651+
Err(e) => {
1652+
bail!(
1653+
"Failed to connect to registry: {}\n\n\
1654+
Check your network connection or try a different registry with --registry",
1655+
e
1656+
);
1657+
}
1658+
};
1659+
1660+
// Validate the content has proper frontmatter
1661+
if !content.trim().starts_with("---") {
1662+
bail!("Invalid agent file format. Expected markdown with YAML frontmatter.");
1663+
}
1664+
1665+
// Get target path
1666+
let agents_dir = get_agents_dir()?;
1667+
std::fs::create_dir_all(&agents_dir)?;
1668+
1669+
let agent_file = agents_dir.join(format!("{}.md", args.name));
1670+
1671+
// Check if already exists
1672+
if agent_file.exists() && !args.force {
1673+
bail!(
1674+
"Agent '{}' already exists at {}.\n\
1675+
Use --force to overwrite.",
1676+
args.name,
1677+
agent_file.display()
1678+
);
1679+
}
1680+
1681+
// Write the agent file
1682+
std::fs::write(&agent_file, &content)
1683+
.with_context(|| format!("Failed to write agent file: {}", agent_file.display()))?;
1684+
1685+
println!();
1686+
println!("Agent '{}' installed successfully!", args.name);
1687+
println!(" Location: {}", agent_file.display());
1688+
println!();
1689+
println!(" Use 'cortex agent show {}' to view details.", args.name);
1690+
println!(
1691+
" Use 'cortex -a {}' to start a session with this agent.",
1692+
args.name
1693+
);
1694+
1695+
Ok(())
1696+
}
1697+
15961698
/// Generate agent using AI.
15971699
async fn run_generate(args: CreateArgs) -> Result<()> {
15981700
use cortex_engine::agent::{AgentGenerator, GeneratedAgent};

cortex-cli/src/github_cmd.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ pub enum GitHubSubcommand {
2828

2929
/// Check GitHub Actions installation status.
3030
Status(StatusArgs),
31+
32+
/// Uninstall/remove the Cortex GitHub workflow.
33+
Uninstall(UninstallArgs),
34+
35+
/// Update the Cortex GitHub workflow to the latest version.
36+
Update(UpdateArgs),
3137
}
3238

3339
/// Arguments for install command.
@@ -100,13 +106,51 @@ pub struct StatusArgs {
100106
pub json: bool,
101107
}
102108

109+
/// Arguments for uninstall command.
110+
#[derive(Debug, Parser)]
111+
pub struct UninstallArgs {
112+
/// Path to the repository root (defaults to current directory).
113+
#[arg(short, long)]
114+
pub path: Option<PathBuf>,
115+
116+
/// Workflow name to remove (defaults to "cortex").
117+
#[arg(long, default_value = "cortex")]
118+
pub workflow_name: String,
119+
120+
/// Force removal without confirmation.
121+
#[arg(short, long)]
122+
pub force: bool,
123+
}
124+
125+
/// Arguments for update command.
126+
#[derive(Debug, Parser)]
127+
pub struct UpdateArgs {
128+
/// Path to the repository root (defaults to current directory).
129+
#[arg(short, long)]
130+
pub path: Option<PathBuf>,
131+
132+
/// Workflow name to update (defaults to "cortex").
133+
#[arg(long, default_value = "cortex")]
134+
pub workflow_name: String,
135+
136+
/// Include PR review automation.
137+
#[arg(long, default_value_t = true)]
138+
pub pr_review: bool,
139+
140+
/// Include issue automation.
141+
#[arg(long, default_value_t = true)]
142+
pub issue_automation: bool,
143+
}
144+
103145
impl GitHubCli {
104146
/// Run the GitHub command.
105147
pub async fn run(self) -> Result<()> {
106148
match self.subcommand {
107149
GitHubSubcommand::Install(args) => run_install(args).await,
108150
GitHubSubcommand::Run(args) => run_github_agent(args).await,
109151
GitHubSubcommand::Status(args) => run_status(args).await,
152+
GitHubSubcommand::Uninstall(args) => run_uninstall(args).await,
153+
GitHubSubcommand::Update(args) => run_update(args).await,
110154
}
111155
}
112156
}
@@ -614,6 +658,154 @@ async fn run_status(args: StatusArgs) -> Result<()> {
614658
Ok(())
615659
}
616660

661+
/// Uninstall/remove GitHub Actions workflow.
662+
async fn run_uninstall(args: UninstallArgs) -> Result<()> {
663+
use std::io::{self, Write};
664+
665+
let repo_path = args.path.unwrap_or_else(|| PathBuf::from("."));
666+
667+
// Validate path exists
668+
if !repo_path.exists() {
669+
bail!("Path does not exist: {}", repo_path.display());
670+
}
671+
672+
// Check for workflow files
673+
let workflows_dir = repo_path.join(".github").join("workflows");
674+
675+
// Try multiple possible workflow file names
676+
let possible_names = vec![
677+
format!("{}.yml", args.workflow_name),
678+
format!("{}.yaml", args.workflow_name),
679+
];
680+
681+
let mut found_workflow: Option<PathBuf> = None;
682+
for name in &possible_names {
683+
let path = workflows_dir.join(name);
684+
if path.exists() {
685+
// Verify it's a Cortex workflow
686+
if let Ok(content) = std::fs::read_to_string(&path) {
687+
if content.contains("Cortex") || content.contains("cortex") {
688+
found_workflow = Some(path);
689+
break;
690+
}
691+
}
692+
}
693+
}
694+
695+
let workflow_path = match found_workflow {
696+
Some(p) => p,
697+
None => {
698+
bail!(
699+
"Cortex workflow '{}' not found in {}.\n\
700+
Use `cortex github status` to check installation status.",
701+
args.workflow_name,
702+
workflows_dir.display()
703+
);
704+
}
705+
};
706+
707+
// Confirm removal unless --force
708+
if !args.force {
709+
print!(
710+
"Remove Cortex workflow '{}'? [y/N]: ",
711+
workflow_path.display()
712+
);
713+
io::stdout().flush()?;
714+
715+
let mut input = String::new();
716+
io::stdin().read_line(&mut input)?;
717+
718+
if !input.trim().eq_ignore_ascii_case("y") {
719+
println!("Cancelled.");
720+
return Ok(());
721+
}
722+
}
723+
724+
// Remove the workflow file
725+
std::fs::remove_file(&workflow_path)
726+
.with_context(|| format!("Failed to remove workflow: {}", workflow_path.display()))?;
727+
728+
println!("Cortex workflow removed successfully!");
729+
println!(" Removed: {}", workflow_path.display());
730+
println!();
731+
println!(
732+
"Note: You may also want to remove the CORTEX_API_KEY secret from your repository settings."
733+
);
734+
735+
Ok(())
736+
}
737+
738+
/// Update GitHub Actions workflow to latest version.
739+
async fn run_update(args: UpdateArgs) -> Result<()> {
740+
use cortex_engine::github::{WorkflowConfig, generate_workflow};
741+
742+
let repo_path = args.path.unwrap_or_else(|| PathBuf::from("."));
743+
744+
// Validate path exists
745+
if !repo_path.exists() {
746+
bail!("Path does not exist: {}", repo_path.display());
747+
}
748+
749+
let workflows_dir = repo_path.join(".github").join("workflows");
750+
751+
// Try to find existing workflow
752+
let possible_names = vec![
753+
format!("{}.yml", args.workflow_name),
754+
format!("{}.yaml", args.workflow_name),
755+
];
756+
757+
let mut existing_path: Option<PathBuf> = None;
758+
for name in &possible_names {
759+
let path = workflows_dir.join(name);
760+
if path.exists() {
761+
existing_path = Some(path);
762+
break;
763+
}
764+
}
765+
766+
let workflow_file = match existing_path {
767+
Some(p) => p,
768+
None => {
769+
bail!(
770+
"Cortex workflow '{}' not found. Use `cortex github install` first.",
771+
args.workflow_name
772+
);
773+
}
774+
};
775+
776+
// Generate updated workflow
777+
let config = WorkflowConfig {
778+
name: args.workflow_name.clone(),
779+
pr_review: args.pr_review,
780+
issue_automation: args.issue_automation,
781+
};
782+
783+
let workflow_content = generate_workflow(&config);
784+
785+
// Write updated workflow
786+
std::fs::write(&workflow_file, &workflow_content)
787+
.with_context(|| format!("Failed to write workflow file: {}", workflow_file.display()))?;
788+
789+
println!("Cortex workflow updated successfully!");
790+
println!(" Path: {}", workflow_file.display());
791+
println!();
792+
println!("Features enabled:");
793+
if args.pr_review {
794+
println!(" • PR review automation");
795+
}
796+
if args.issue_automation {
797+
println!(" • Issue automation");
798+
}
799+
println!();
800+
println!("Next steps:");
801+
println!(" 1. Commit and push the updated workflow:");
802+
println!(" git add {}", workflow_file.display());
803+
println!(" git commit -m \"Update Cortex workflow\"");
804+
println!(" git push");
805+
806+
Ok(())
807+
}
808+
617809
/// Installation status information.
618810
#[derive(Debug, Default, serde::Serialize)]
619811
struct InstallationStatus {

0 commit comments

Comments
 (0)