Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions .taskmaster/tasks/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,18 @@
"6",
"9"
],
"status": "pending",
"status": "done",
"subtasks": [
{
"id": 1,
"title": "Implement Interactive Prompt Logic Using dialoguer",
"description": "Develop interactive item-by-item confirmation prompts with color-coded diffs and support for options: yes, no, all, none, diff, quit.",
"dependencies": [],
"details": "Use the `dialoguer` crate (v0.10+) to render confirmation and selection prompts. Integrate color-coded diff display before each action. Ensure prompt options are handled correctly and user choices are remembered for the session, especially 'all'.",
"status": "pending",
"status": "done",
"testStrategy": "Simulate user input for all prompt options. Validate correct rendering of diffs and prompt flow. Test session memory for 'all' choice.",
"parentId": "undefined"
"parentId": "undefined",
"updatedAt": "2025-10-28T16:06:13.029Z"
},
{
"id": 2,
Expand All @@ -249,9 +250,10 @@
1
],
"details": "Parse CLI flags to detect non-interactive mode. Automatically confirm all actions when `--yes-all` is set. Ensure no prompts are shown and actions proceed without user intervention.",
"status": "pending",
"status": "done",
"testStrategy": "Run automated tests with flags set. Confirm no prompts are displayed and all actions execute as expected.",
"parentId": "undefined"
"parentId": "undefined",
"updatedAt": "2025-10-28T16:06:13.031Z"
},
{
"id": 3,
Expand All @@ -261,9 +263,10 @@
2
],
"details": "Detect `--dry-run` flag and modify execution flow to only simulate actions. Output should be clearly marked as a preview, showing intended changes without performing them.",
"status": "pending",
"status": "done",
"testStrategy": "Test with `--dry-run` flag. Validate that no changes occur and output is clearly marked as a preview.",
"parentId": "undefined"
"parentId": "undefined",
"updatedAt": "2025-10-28T16:06:13.034Z"
},
{
"id": 4,
Expand All @@ -273,9 +276,10 @@
1
],
"details": "Implement session state tracking to remember if the user selects 'all' or similar options, ensuring subsequent actions respect this choice until session end.",
"status": "pending",
"status": "done",
"testStrategy": "Test session behavior by selecting 'all' and verifying that subsequent items are processed without further prompts.",
"parentId": "undefined"
"parentId": "undefined",
"updatedAt": "2025-10-28T16:06:13.037Z"
},
{
"id": 5,
Expand All @@ -287,14 +291,16 @@
4
],
"details": "Use the `ctrlc` crate to intercept Ctrl+C and perform cleanup or exit gracefully. Ensure exit codes reflect success, failure, or interruption for scripting compatibility.",
"status": "pending",
"status": "done",
"testStrategy": "Simulate Ctrl+C during operation. Validate graceful shutdown and correct exit codes for all operation modes.",
"parentId": "undefined"
"parentId": "undefined",
"updatedAt": "2025-10-28T16:06:13.039Z"
}
],
"complexity": 6,
"recommendedSubtasks": 5,
"expansionPrompt": "Separate into interactive prompt logic, non-interactive automation, dry-run simulation, session state management, and signal/exit handling."
"expansionPrompt": "Separate into interactive prompt logic, non-interactive automation, dry-run simulation, session state management, and signal/exit handling.",
"updatedAt": "2025-10-28T16:06:13.039Z"
},
{
"id": "5",
Expand Down Expand Up @@ -616,9 +622,9 @@
],
"metadata": {
"version": "1.0.0",
"lastModified": "2025-10-28T12:24:00.564Z",
"lastModified": "2025-10-28T16:06:13.039Z",
"taskCount": 9,
"completedCount": 6,
"completedCount": 7,
"tags": [
"master"
]
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ repository = "https://github.com/onsails/ccsync"
# Core dependencies with tilde requirements for patch compatibility
clap = { version = "~4.5.28", features = ["derive", "cargo"] }
anyhow = "~1.0.95"
dialoguer = "~0.11.0"
ctrlc = "~3.4.7"

# Dev dependencies
assert_cmd = "~2.0.17"
Expand Down
2 changes: 2 additions & 0 deletions crates/ccsync-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ path = "src/main.rs"
ccsync = { path = "../ccsync" }
clap.workspace = true
anyhow.workspace = true
dialoguer.workspace = true
ctrlc.workspace = true

[dev-dependencies]
assert_cmd.workspace = true
Expand Down
4 changes: 0 additions & 4 deletions crates/ccsync-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ pub struct Cli {
#[arg(short, long, global = true)]
pub verbose: bool,

/// Run in non-interactive mode (skip all prompts)
#[arg(long, global = true)]
pub non_interactive: bool,

/// Accept all items in interactive mode without prompting
#[arg(long, global = true)]
pub yes_all: bool,
Expand Down
33 changes: 29 additions & 4 deletions crates/ccsync-cli/src/commands/to_global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ccsync::config::{Config, SyncDirection};
use ccsync::sync::{SyncEngine, SyncReporter};

use crate::cli::{ConfigType, ConflictMode};
use crate::interactive::InteractivePrompter;

pub struct ToGlobal;

Expand All @@ -15,6 +16,7 @@ impl ToGlobal {
conflict: &ConflictMode,
verbose: bool,
dry_run: bool,
yes_all: bool,
) -> anyhow::Result<()> {
if verbose {
println!("Executing to-global command");
Expand All @@ -39,10 +41,33 @@ impl ToGlobal {
let engine = SyncEngine::new(config, SyncDirection::ToGlobal)
.context("Failed to initialize sync engine")?;

// Execute sync (source is local, destination is global)
let result = engine
.sync(&local_path, &global_path)
.context("Sync operation failed")?;
// Execute sync with optional interactive approval (source is local, destination is global)
let result = if yes_all || dry_run {
// Non-interactive: auto-approve all or just preview
engine
.sync(&local_path, &global_path)
.context("Sync operation failed")?
} else {
// Interactive mode: prompt for each action
let mut prompter = InteractivePrompter::new();
match engine.sync_with_approver(
&local_path,
&global_path,
Some(Box::new(move |action| prompter.prompt(action))),
) {
Ok(result) => result,
Err(e) => {
// Check if this is a user abort (not a real error)
let err_msg = e.to_string();
if err_msg.contains("User aborted") {
eprintln!("\nSync cancelled by user.");
std::process::exit(0); // Clean exit, not an error
} else {
return Err(e).context("Sync operation failed");
}
}
}
};

// Display results
let summary = SyncReporter::generate_summary(&result);
Expand Down
33 changes: 29 additions & 4 deletions crates/ccsync-cli/src/commands/to_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ccsync::config::{Config, SyncDirection};
use ccsync::sync::{SyncEngine, SyncReporter};

use crate::cli::{ConfigType, ConflictMode};
use crate::interactive::InteractivePrompter;

pub struct ToLocal;

Expand All @@ -15,6 +16,7 @@ impl ToLocal {
conflict: &ConflictMode,
verbose: bool,
dry_run: bool,
yes_all: bool,
) -> anyhow::Result<()> {
if verbose {
println!("Executing to-local command");
Expand All @@ -39,10 +41,33 @@ impl ToLocal {
let engine = SyncEngine::new(config, SyncDirection::ToLocal)
.context("Failed to initialize sync engine")?;

// Execute sync
let result = engine
.sync(&global_path, &local_path)
.context("Sync operation failed")?;
// Execute sync with optional interactive approval
let result = if yes_all || dry_run {
// Non-interactive: auto-approve all or just preview
engine
.sync(&global_path, &local_path)
.context("Sync operation failed")?
} else {
// Interactive mode: prompt for each action
let mut prompter = InteractivePrompter::new();
match engine.sync_with_approver(
&global_path,
&local_path,
Some(Box::new(move |action| prompter.prompt(action))),
) {
Ok(result) => result,
Err(e) => {
// Check if this is a user abort (not a real error)
let err_msg = e.to_string();
if err_msg.contains("User aborted") {
eprintln!("\nSync cancelled by user.");
std::process::exit(0); // Clean exit, not an error
} else {
return Err(e).context("Sync operation failed");
}
}
}
};

// Display results
let summary = SyncReporter::generate_summary(&result);
Expand Down
Loading
Loading