use crate::agent;
use crate::cmd::list::{ListArgs, run_list};
use crate::output::{CliError, OutputMode, render_error};
use crate::validate;
use clap::Args;
use std::path::Path;
#[derive(Args, Debug, Clone)]
pub struct MineArgs {
#[command(flatten)]
pub list: ListArgs,
}
fn build_mine_filter(args: &MineArgs, resolved_agent: String) -> ListArgs {
let mut list = args.list.clone();
list.assignee = Some(resolved_agent);
list
}
pub fn run_mine(
args: &MineArgs,
agent_flag: Option<&str>,
output: OutputMode,
project_root: &Path,
) -> anyhow::Result<()> {
let agent = match agent::require_agent(agent_flag) {
Ok(a) => a,
Err(e) => {
render_error(
output,
&CliError::with_details(
&e.message,
"Set --agent, BONES_AGENT, AGENT, or USER (interactive only)",
e.code,
),
)?;
anyhow::bail!(e.message);
}
};
if let Err(e) = validate::validate_agent(&agent) {
render_error(output, &e.to_cli_error())?;
anyhow::bail!(e.reason);
}
let list_args = build_mine_filter(args, agent);
run_list(&list_args, output, project_root)
}
#[cfg(test)]
mod tests {
use super::*;
use bones_core::db::migrations;
use clap::Parser;
use rusqlite::Connection;
#[derive(Parser)]
struct Wrapper {
#[command(flatten)]
args: MineArgs,
}
#[test]
fn mine_args_parse_with_list_filters() {
let parsed = Wrapper::parse_from([
"test", "--state", "doing", "--kind", "task", "--label", "backend", "--sort",
"priority",
]);
assert_eq!(parsed.args.list.state.as_deref(), Some("doing"));
assert_eq!(parsed.args.list.kind.as_deref(), Some("task"));
assert_eq!(parsed.args.list.label, vec!["backend"]);
assert_eq!(parsed.args.list.sort, "priority");
}
#[test]
fn build_mine_filter_overrides_assignee() {
let args = MineArgs {
list: ListArgs {
state: Some("open".to_string()),
all_states: false,
all: false,
kind: None,
label: vec![],
tag: vec![],
labels: vec![],
tags: vec![],
urgency: None,
assignee: Some("someone-else".to_string()),
parent: None,
since: None,
until: None,
limit: 50,
offset: 0,
sort: "updated".to_string(),
},
};
let filtered = build_mine_filter(&args, "alice".to_string());
assert_eq!(filtered.assignee.as_deref(), Some("alice"));
assert_eq!(filtered.state.as_deref(), Some("open"));
}
#[test]
fn run_mine_smoke() {
let dir = tempfile::tempdir().expect("tempdir");
let bones_dir = dir.path().join(".bones");
std::fs::create_dir_all(&bones_dir).expect("create .bones");
let db_path = bones_dir.join("bones.db");
let mut conn = Connection::open(&db_path).expect("open db");
migrations::migrate(&mut conn).expect("migrate");
conn.execute(
"INSERT INTO items (item_id, title, kind, state, urgency, is_deleted, search_labels, created_at_us, updated_at_us) \
VALUES ('bn-001', 'Mine', 'task', 'open', 'default', 0, '', 1000, 1000)",
[],
)
.expect("insert item");
conn.execute(
"INSERT INTO item_assignees (item_id, agent, created_at_us) VALUES ('bn-001', 'alice', 1000)",
[],
)
.expect("insert assignee");
let args = MineArgs {
list: ListArgs {
state: None,
all_states: false,
all: false,
kind: None,
label: vec![],
tag: vec![],
labels: vec![],
tags: vec![],
urgency: None,
assignee: None,
parent: None,
since: None,
until: None,
limit: 50,
offset: 0,
sort: "updated".to_string(),
},
};
run_mine(&args, Some("alice"), OutputMode::Json, dir.path()).expect("run mine");
}
}