config: let profiles carry agent CLI args
Add an `args` field to Profile, parsed from one or more `args` nodes in a
profile block, so a profile can pin flags the agent needs (pi selects
Bedrock via --provider, which has no env equivalent). Entries concatenate
like env/mounts blocks; args ride with the wholesale profile-merge rule.
Assisted-by: Claude Opus 4.8 via pi
diff --git a/src/config.rs b/src/config.rs
index f6d9927..2559361 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -104,6 +104,7 @@ pub struct Profile {
pub agent: Agent,
pub env: Vec<EnvVar>,
pub mounts: Vec<Mount>,
+ pub args: Vec<String>,
}
impl Profile {
@@ -118,6 +119,7 @@ impl Profile {
agent,
env: Vec::new(),
mounts: Vec::new(),
+ args: Vec::new(),
})
.collect()
}
@@ -567,11 +569,13 @@ fn parse_profile(node: &KdlNode) -> Result<ProfileNode> {
let mut agent = None;
let mut env = Vec::new();
let mut mounts = Vec::new();
+ let mut args = Vec::new();
for child in children.nodes() {
match child.name().value() {
"agent" => agent = Some(Agent::parse(&single_string_arg(child)?)?),
"env" => env.extend(parse_env(child)?),
"mounts" => mounts.extend(parse_mounts(child)?),
+ "args" => args.extend(parse_args(child)?),
other => bail!("unknown `profile` field `{other}`"),
}
}
@@ -582,9 +586,31 @@ fn parse_profile(node: &KdlNode) -> Result<ProfileNode> {
name,
env,
mounts,
+ args,
}))
}
+/// Parse an `args` node: bare string entries passed verbatim to the agent
+/// binary (`args "--provider" "amazon-bedrock"`). No properties, no block.
+fn parse_args(node: &KdlNode) -> Result<Vec<String>> {
+ if node.children().is_some() {
+ bail!("`args` takes inline string values (args \"--flag\" \"value\"), not a block");
+ }
+ node.entries()
+ .iter()
+ .map(|entry| {
+ if entry.name().is_some() {
+ bail!("`args` takes bare string values, not properties");
+ }
+ entry
+ .value()
+ .as_string()
+ .map(str::to_string)
+ .ok_or_else(|| miette!("`args` takes string values, got {}", entry.value()))
+ })
+ .collect()
+}
+
/// Parse a `mounts` block. Exactly one syntax, mirroring `env`: a block with
/// one child node per mount, whose name is the host source path, with
/// optional `target` and `writable` properties.
@@ -808,6 +834,7 @@ mod tests {
agent: Agent::Pi,
env: Vec::new(),
mounts: Vec::new(),
+ args: Vec::new(),
},
}
}
@@ -1252,6 +1279,45 @@ mod tests {
assert_eq!(p.mounts[0].source, "~/.aws");
}
+ #[test]
+ fn parse_profile_with_args() {
+ let raw = parse_config(
+ r#"
+ profile "pi-bedrock" {
+ agent "pi"
+ args "--provider" "amazon-bedrock"
+ }
+ "#,
+ )
+ .unwrap();
+ let p = &raw.profiles[0];
+ assert_eq!(p.args, vec!["--provider", "amazon-bedrock"]);
+ }
+
+ #[test]
+ fn parse_profile_concatenates_multiple_args_nodes() {
+ let raw = parse_config(
+ r#"
+ profile "x" {
+ agent "pi"
+ args "--provider" "amazon-bedrock"
+ args "--model" "some-model"
+ }
+ "#,
+ )
+ .unwrap();
+ assert_eq!(
+ raw.profiles[0].args,
+ vec!["--provider", "amazon-bedrock", "--model", "some-model"]
+ );
+ }
+
+ #[test]
+ fn parse_profile_rejects_non_string_args() {
+ let result = parse_config("profile \"x\" {\n agent \"pi\"\n args 42\n}");
+ assert!(result.is_err());
+ }
+
#[test]
fn parse_profile_selection() {
let raw = parse_config(r#"profile "pi-glm""#).unwrap();