summaryrefslogtreecommitdiff
path: root/makima/src/daemon/cli
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-01 01:35:18 +0000
committersoryu <soryu@soryu.co>2026-02-01 01:35:18 +0000
commitc2750f86ebd6ac5c04b70dd8249501262d6dd07c (patch)
treeecd4a32d74adc3479cdc30c94843a6447bf44649 /makima/src/daemon/cli
parent7567153e6281b94e39e52be5d060b381ed69597d (diff)
downloadsoryu-c2750f86ebd6ac5c04b70dd8249501262d6dd07c.tar.gz
soryu-c2750f86ebd6ac5c04b70dd8249501262d6dd07c.zip
[WIP] Heartbeat checkpoint - 2026-02-01 01:35:18 UTC
Diffstat (limited to 'makima/src/daemon/cli')
-rw-r--r--makima/src/daemon/cli/contract.rs208
-rw-r--r--makima/src/daemon/cli/mod.rs21
2 files changed, 228 insertions, 1 deletions
diff --git a/makima/src/daemon/cli/contract.rs b/makima/src/daemon/cli/contract.rs
index a443b85..9d6784d 100644
--- a/makima/src/daemon/cli/contract.rs
+++ b/makima/src/daemon/cli/contract.rs
@@ -1,6 +1,6 @@
//! Contract subcommand - task-contract interaction commands.
-use clap::Args;
+use clap::{Args, Subcommand};
use uuid::Uuid;
/// Common arguments for contract commands.
@@ -85,3 +85,209 @@ pub struct CreateFileArgs {
/// Name of the new file
pub name: String,
}
+
+// ============================================================================
+// Contracts management commands (makima contracts ...)
+// ============================================================================
+
+/// Common arguments for contracts management commands.
+#[derive(Args, Debug, Clone)]
+pub struct ContractsCommonArgs {
+ /// API URL
+ #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp", global = true)]
+ pub api_url: String,
+
+ /// API key for authentication
+ #[arg(long, env = "MAKIMA_API_KEY", global = true)]
+ pub api_key: String,
+}
+
+/// Contracts management subcommands.
+#[derive(Subcommand, Debug)]
+pub enum ContractsSubcommand {
+ /// List all contracts with optional filters
+ List(ListArgs),
+
+ /// Clean up old contracts, archived contracts, and orphaned worktrees
+ Cleanup(CleanupArgs),
+}
+
+/// Output format for list command.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum ListOutputFormat {
+ #[default]
+ Table,
+ Json,
+ Compact,
+}
+
+impl std::str::FromStr for ListOutputFormat {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "table" => Ok(ListOutputFormat::Table),
+ "json" => Ok(ListOutputFormat::Json),
+ "compact" => Ok(ListOutputFormat::Compact),
+ _ => Err(format!("Invalid format '{}'. Valid options: table, json, compact", s)),
+ }
+ }
+}
+
+impl std::fmt::Display for ListOutputFormat {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ListOutputFormat::Table => write!(f, "table"),
+ ListOutputFormat::Json => write!(f, "json"),
+ ListOutputFormat::Compact => write!(f, "compact"),
+ }
+ }
+}
+
+/// Arguments for the list subcommand.
+#[derive(Args, Debug)]
+pub struct ListArgs {
+ #[command(flatten)]
+ pub common: ContractsCommonArgs,
+
+ /// Filter by status (active, completed, failed)
+ #[arg(long, value_delimiter = ',')]
+ pub status: Option<Vec<String>>,
+
+ /// Show only stale contracts (no activity within threshold)
+ #[arg(long)]
+ pub stale: bool,
+
+ /// Stale threshold duration (e.g., "30m", "1h", "2d"). Default: 30m
+ #[arg(long, default_value = "30m")]
+ pub threshold: String,
+
+ /// Show contracts waiting for user input
+ #[arg(long)]
+ pub waiting: bool,
+
+ /// Filter by phase (e.g., "plan", "execute", "review")
+ #[arg(long)]
+ pub phase: Option<String>,
+
+ /// Output format: table, json, or compact
+ #[arg(long, short = 'f', default_value = "table")]
+ pub format: String,
+
+ /// Limit number of results
+ #[arg(long, short = 'n')]
+ pub limit: Option<usize>,
+}
+
+impl ListArgs {
+ /// Parse the format string into a ListOutputFormat enum.
+ pub fn parse_format(&self) -> Result<ListOutputFormat, String> {
+ self.format.parse()
+ }
+
+ /// Parse the threshold duration into seconds.
+ pub fn parse_threshold(&self) -> Result<u64, String> {
+ parse_duration(&self.threshold)
+ }
+}
+
+/// Arguments for the cleanup subcommand.
+#[derive(Args, Debug)]
+pub struct CleanupArgs {
+ #[command(flatten)]
+ pub common: ContractsCommonArgs,
+
+ /// Archive completed/failed contracts older than the threshold
+ #[arg(long)]
+ pub archive: bool,
+
+ /// Delete archived contracts older than the threshold
+ #[arg(long)]
+ pub delete_archived: bool,
+
+ /// Clean up orphaned worktrees (worktrees without associated contracts)
+ #[arg(long)]
+ pub worktrees: bool,
+
+ /// Run all cleanup operations (archive, delete-archived, worktrees)
+ #[arg(long)]
+ pub all: bool,
+
+ /// Age threshold for cleanup operations (e.g., "7d", "24h", "30d")
+ /// Default: 7d (7 days)
+ #[arg(long, default_value = "7d")]
+ pub older_than: String,
+
+ /// Show what would be affected without making changes
+ #[arg(long)]
+ pub dry_run: bool,
+
+ /// Skip confirmation prompts for destructive operations
+ #[arg(long)]
+ pub force: bool,
+}
+
+impl CleanupArgs {
+ /// Parse the older_than duration string into seconds.
+ /// Supports formats like "7d", "24h", "30m", "60s".
+ pub fn parse_older_than(&self) -> Result<u64, String> {
+ parse_duration(&self.older_than)
+ }
+
+ /// Returns true if any cleanup operation is selected.
+ pub fn has_any_operation(&self) -> bool {
+ self.archive || self.delete_archived || self.worktrees || self.all
+ }
+}
+
+/// Parse a duration string like "7d", "24h", "30m", "60s" into seconds.
+pub fn parse_duration(s: &str) -> Result<u64, String> {
+ let s = s.trim();
+ if s.is_empty() {
+ return Err("Empty duration string".to_string());
+ }
+
+ let (num_str, unit) = if s.ends_with('d') || s.ends_with('D') {
+ (&s[..s.len() - 1], 'd')
+ } else if s.ends_with('h') || s.ends_with('H') {
+ (&s[..s.len() - 1], 'h')
+ } else if s.ends_with('m') || s.ends_with('M') {
+ (&s[..s.len() - 1], 'm')
+ } else if s.ends_with('s') || s.ends_with('S') {
+ (&s[..s.len() - 1], 's')
+ } else {
+ // Default to days if no unit
+ (s, 'd')
+ };
+
+ let num: u64 = num_str
+ .parse()
+ .map_err(|_| format!("Invalid number in duration: {}", num_str))?;
+
+ let seconds = match unit {
+ 'd' => num * 24 * 60 * 60,
+ 'h' => num * 60 * 60,
+ 'm' => num * 60,
+ 's' => num,
+ _ => unreachable!(),
+ };
+
+ Ok(seconds)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_duration() {
+ assert_eq!(parse_duration("7d").unwrap(), 7 * 24 * 60 * 60);
+ assert_eq!(parse_duration("24h").unwrap(), 24 * 60 * 60);
+ assert_eq!(parse_duration("30m").unwrap(), 30 * 60);
+ assert_eq!(parse_duration("60s").unwrap(), 60);
+ assert_eq!(parse_duration("7D").unwrap(), 7 * 24 * 60 * 60);
+ assert_eq!(parse_duration("7").unwrap(), 7 * 24 * 60 * 60); // defaults to days
+ assert!(parse_duration("").is_err());
+ assert!(parse_duration("abc").is_err());
+ }
+}
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index c848e8e..8538afa 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -65,6 +65,27 @@ pub enum Commands {
/// Red team commands for adversarial monitoring
#[command(name = "red-team", subcommand)]
RedTeam(RedTeamCommand),
+
+ /// Contract management commands (list, cleanup)
+ #[command(subcommand)]
+ Contracts(ContractsCommand),
+}
+
+/// Contracts management subcommands for multi-contract operations.
+#[derive(Subcommand, Debug)]
+pub enum ContractsCommand {
+ /// List all contracts with optional filters
+ ///
+ /// Examples:
+ /// makima contracts list # List all contracts
+ /// makima contracts list --status active # List only active contracts
+ /// makima contracts list --stale # Show stale contracts
+ /// makima contracts list --waiting # Show contracts waiting for input
+ /// makima contracts list --format json # Output as JSON
+ List(contract::ListArgs),
+
+ /// Clean up old contracts, archived contracts, and orphaned worktrees
+ Cleanup(contract::CleanupArgs),
}
/// Config subcommands for CLI configuration.