Skip to content

Commit 13a1385

Browse files
committed
Merge branch 'main' into snapshot_revise
2 parents a9801c0 + 864a27b commit 13a1385

File tree

27 files changed

+960
-308
lines changed

27 files changed

+960
-308
lines changed

.github/CODEOWNERS

Lines changed: 0 additions & 1 deletion
This file was deleted.

Cargo.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ authors = ["Amazon Q CLI Team ([email protected])", "Chay Nabors (nabochay@amazon
88
edition = "2024"
99
homepage = "https://aws.amazon.com/q/"
1010
publish = false
11-
version = "1.15.0"
11+
version = "1.16.0"
1212
license = "MIT OR Apache-2.0"
1313

1414
[workspace.dependencies]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ cargo install typos-cli
4747

4848
## Project Layout
4949

50-
- [`chat_cli`](crates/chat_cli/) - the `q` CLI, allows users to interface with Amazon Q Developer from
50+
- [`chat_cli`](crates/chat-cli/) - the `q` CLI, allows users to interface with Amazon Q Developer from
5151
the command line
5252
- [`scripts/`](scripts/) - Contains ops and build related scripts
5353
- [`crates/`](crates/) - Contains all rust crates

crates/chat-cli/build.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,10 @@ fn download_feed_json() {
355355
.args([
356356
"-H",
357357
"Accept: application/vnd.github.v3.raw",
358-
"-s", // silent
359-
"-f", // fail on HTTP errors
358+
"-f", // fail on HTTP errors
359+
"-s", // silent
360+
"-v", // verbose output printed to stderr
361+
"--show-error", // print error message to stderr (since -s is used)
360362
"https://api.github.com/repos/aws/amazon-q-developer-cli-autocomplete/contents/feed.json",
361363
])
362364
.output();
@@ -371,9 +373,9 @@ fn download_feed_json() {
371373
},
372374
Ok(result) => {
373375
let error_msg = if !result.stderr.is_empty() {
374-
format!("HTTP error: {}", String::from_utf8_lossy(&result.stderr))
376+
format!("{}", String::from_utf8_lossy(&result.stderr))
375377
} else {
376-
"HTTP error occurred".to_string()
378+
"An unknown error occurred".to_string()
377379
};
378380
panic!("Failed to download feed.json: {}", error_msg);
379381
},

crates/chat-cli/src/cli/agent/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ impl Agents {
815815
// This "static" way avoids needing to construct a tool instance.
816816
fn default_permission_label(&self, tool_name: &str) -> String {
817817
let label = match tool_name {
818-
"fs_read" => "trusted".dark_green().bold(),
818+
"fs_read" => "trust working directory".dark_grey(),
819819
"fs_write" => "not trusted".dark_grey(),
820820
#[cfg(not(windows))]
821821
"execute_bash" => "trust read-only commands".dark_grey(),
@@ -1142,9 +1142,9 @@ mod tests {
11421142

11431143
let label = agents.display_label("fs_read", &ToolOrigin::Native);
11441144
// With no active agent, it should fall back to default permissions
1145-
// fs_read has a default of "trusted"
1145+
// fs_read has a default of "trust working directory"
11461146
assert!(
1147-
label.contains("trusted"),
1147+
label.contains("trust working directory"),
11481148
"fs_read should show default trusted permission, instead found: {}",
11491149
label
11501150
);
@@ -1173,7 +1173,7 @@ mod tests {
11731173
// Test default permissions for known tools
11741174
let fs_read_label = agents.display_label("fs_read", &ToolOrigin::Native);
11751175
assert!(
1176-
fs_read_label.contains("trusted"),
1176+
fs_read_label.contains("trust working directory"),
11771177
"fs_read should be trusted by default, instead found: {}",
11781178
fs_read_label
11791179
);

crates/chat-cli/src/cli/agent/root_command_args.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ pub enum AgentSubcommands {
4646
#[arg(long, short)]
4747
from: Option<String>,
4848
},
49+
/// Edit an existing agent config
50+
Edit {
51+
/// Name of the agent to edit
52+
#[arg(long, short)]
53+
name: String,
54+
},
4955
/// Validate a config with the given path
5056
Validate {
5157
#[arg(long, short)]
@@ -138,6 +144,38 @@ impl AgentArgs {
138144
path_with_file_name.display()
139145
)?;
140146
},
147+
Some(AgentSubcommands::Edit { name }) => {
148+
let _agents = Agents::load(os, None, true, &mut stderr, mcp_enabled).await.0;
149+
let (_agent, path_with_file_name) = Agent::get_agent_by_name(os, &name).await?;
150+
151+
let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
152+
let mut cmd = std::process::Command::new(editor_cmd);
153+
154+
let status = cmd.arg(&path_with_file_name).status()?;
155+
if !status.success() {
156+
bail!("Editor process did not exit with success");
157+
}
158+
159+
let Ok(content) = os.fs.read(&path_with_file_name).await else {
160+
bail!(
161+
"Post edit validation failed. Error opening {}. Aborting",
162+
path_with_file_name.display()
163+
);
164+
};
165+
if let Err(e) = serde_json::from_slice::<Agent>(&content) {
166+
bail!(
167+
"Post edit validation failed for agent '{name}' at path: {}. Malformed config detected: {e}",
168+
path_with_file_name.display()
169+
);
170+
}
171+
172+
writeln!(
173+
stderr,
174+
"\n✏️ Edited agent {} '{}'\n",
175+
name,
176+
path_with_file_name.display()
177+
)?;
178+
},
141179
Some(AgentSubcommands::Validate { path }) => {
142180
let mut global_mcp_config = None::<McpServerConfig>;
143181
let agent = Agent::load(os, path.as_str(), &mut global_mcp_config, mcp_enabled, &mut stderr).await;
@@ -386,4 +424,24 @@ mod tests {
386424
})
387425
);
388426
}
427+
428+
#[test]
429+
fn test_agent_subcommand_edit() {
430+
assert_parse!(
431+
["agent", "edit", "--name", "existing_agent"],
432+
RootSubcommand::Agent(AgentArgs {
433+
cmd: Some(AgentSubcommands::Edit {
434+
name: "existing_agent".to_string(),
435+
})
436+
})
437+
);
438+
assert_parse!(
439+
["agent", "edit", "-n", "existing_agent"],
440+
RootSubcommand::Agent(AgentArgs {
441+
cmd: Some(AgentSubcommands::Edit {
442+
name: "existing_agent".to_string(),
443+
})
444+
})
445+
);
446+
}
389447
}

crates/chat-cli/src/cli/chat/cli/experiment.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,16 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result<Opt
108108
sel
109109
},
110110
// Ctrl‑C -> Err(Interrupted)
111-
Err(dialoguer::Error::IO(ref e)) if e.kind() == std::io::ErrorKind::Interrupted => return Ok(None),
111+
Err(dialoguer::Error::IO(ref e)) if e.kind() == std::io::ErrorKind::Interrupted => {
112+
// Move to beginning of line and clear everything from warning message down
113+
queue!(
114+
session.stderr,
115+
crossterm::cursor::MoveToColumn(0),
116+
crossterm::cursor::MoveUp(experiment_labels.len() as u16 + 3),
117+
crossterm::terminal::Clear(crossterm::terminal::ClearType::FromCursorDown),
118+
)?;
119+
return Ok(None);
120+
},
112121
Err(e) => return Err(ChatError::Custom(format!("Failed to choose experiment: {e}").into())),
113122
};
114123

@@ -161,6 +170,13 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result<Opt
161170
style::SetForegroundColor(Color::Reset),
162171
style::SetBackgroundColor(Color::Reset),
163172
)?;
173+
} else {
174+
// ESC was pressed - clear the warning message
175+
queue!(
176+
session.stderr,
177+
crossterm::cursor::MoveUp(3), // Move up past selection + 2 disclaimer lines
178+
crossterm::terminal::Clear(crossterm::terminal::ClearType::FromCursorDown),
179+
)?;
164180
}
165181

166182
execute!(session.stderr, style::ResetColor)?;

crates/chat-cli/src/cli/chat/cli/mcp.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ impl McpArgs {
5454
let msg = msg
5555
.iter()
5656
.map(|record| match record {
57-
LoadingRecord::Err(content) | LoadingRecord::Warn(content) | LoadingRecord::Success(content) => {
58-
content.clone()
59-
},
57+
LoadingRecord::Err(timestamp, content)
58+
| LoadingRecord::Warn(timestamp, content)
59+
| LoadingRecord::Success(timestamp, content) => format!("[{timestamp}]: {content}"),
6060
})
6161
.collect::<Vec<_>>()
6262
.join("\n--- tools refreshed ---\n");

crates/chat-cli/src/cli/chat/cli/profile.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ pub enum AgentSubcommand {
7777
#[arg(long, short)]
7878
from: Option<String>,
7979
},
80+
/// Edit an existing agent configuration
81+
Edit {
82+
/// Name of the agent to edit
83+
#[arg(long, short)]
84+
name: String,
85+
},
8086
/// Generate an agent configuration using AI
8187
Generate {},
8288
/// Delete the specified agent
@@ -242,6 +248,64 @@ impl AgentSubcommand {
242248
)?;
243249
},
244250

251+
Self::Edit { name } => {
252+
let (_agent, path_with_file_name) = Agent::get_agent_by_name(os, &name)
253+
.await
254+
.map_err(|e| ChatError::Custom(Cow::Owned(e.to_string())))?;
255+
256+
let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
257+
let mut cmd = std::process::Command::new(editor_cmd);
258+
259+
let status = cmd.arg(&path_with_file_name).status()?;
260+
if !status.success() {
261+
return Err(ChatError::Custom("Editor process did not exit with success".into()));
262+
}
263+
264+
let updated_agent = Agent::load(
265+
os,
266+
&path_with_file_name,
267+
&mut None,
268+
session.conversation.mcp_enabled,
269+
&mut session.stderr,
270+
)
271+
.await;
272+
match updated_agent {
273+
Ok(agent) => {
274+
session.conversation.agents.agents.insert(agent.name.clone(), agent);
275+
},
276+
Err(e) => {
277+
execute!(
278+
session.stderr,
279+
style::SetForegroundColor(Color::Red),
280+
style::Print("Error: "),
281+
style::ResetColor,
282+
style::Print(&e),
283+
style::Print("\n"),
284+
)?;
285+
286+
return Err(ChatError::Custom(
287+
format!("Post edit validation failed for agent '{name}'. Malformed config detected: {e}")
288+
.into(),
289+
));
290+
},
291+
}
292+
293+
execute!(
294+
session.stderr,
295+
style::SetForegroundColor(Color::Green),
296+
style::Print("Agent "),
297+
style::SetForegroundColor(Color::Cyan),
298+
style::Print(name),
299+
style::SetForegroundColor(Color::Green),
300+
style::Print(" has been edited successfully"),
301+
style::SetForegroundColor(Color::Reset),
302+
style::Print("\n"),
303+
style::SetForegroundColor(Color::Yellow),
304+
style::Print("Changes take effect on next launch"),
305+
style::SetForegroundColor(Color::Reset)
306+
)?;
307+
},
308+
245309
Self::Generate {} => {
246310
let agent_name = match crate::util::input("Enter agent name: ", None) {
247311
Ok(input) => input.trim().to_string(),
@@ -440,6 +504,7 @@ impl AgentSubcommand {
440504
match self {
441505
Self::List => "list",
442506
Self::Create { .. } => "create",
507+
Self::Edit { .. } => "edit",
443508
Self::Generate { .. } => "generate",
444509
Self::Delete { .. } => "delete",
445510
Self::Set { .. } => "set",

0 commit comments

Comments
 (0)