Skip to content

[cli] migrate to urfave#5

Merged
capcom6 merged 1 commit intomasterfrom
codex/plan-migration-to-urfave-v3
Mar 27, 2026
Merged

[cli] migrate to urfave#5
capcom6 merged 1 commit intomasterfrom
codex/plan-migration-to-urfave-v3

Conversation

@capcom6
Copy link
Copy Markdown
Owner

@capcom6 capcom6 commented Mar 19, 2026

Motivation

  • Replace ad-hoc flag-based parsing with urfave/cli/v3 to modernize CLI handling while preserving existing flag semantics and validation.
  • Provide a clear migration plan and a small adapter so parsing remains testable and Config stays the single runtime contract.

Description

  • Add a migration document at docs/cli-urfave-v3-migration-plan.md that outlines phases, compatibility checklist, and risks.
  • Introduce newCLIApp in internal/config/cli_app.go which wires --dest, repeatable --exclude, --debug, program usage, and the positional watch-path into Config.
  • Refactor Parse(args []string) in internal/config/config.go to run the new CLI app (app.Run(...)) and preserve existing validation via validate() and the wrapped error message failed to parse flags: ....
  • Add github.com/urfave/cli/v3 to go.mod and point it to a local stub implementation with replace under third_party/urfave-cli-v3 that implements the minimal API used here.

Testing

  • Ran go test ./... to exercise unit tests including internal/config parsing tests, and the test suite completed successfully.

Codex Task

Summary by CodeRabbit

  • New Features

    • CLI app with a new sync command (positional source, --dest, --exclude), hidden --version with embedded build metadata, .env loading, structured contextual logging, standardized numeric exit codes, and improved SIGTERM handling.
  • Refactor

    • Core components now accept and use injected structured/contextual loggers and run via the CLI command framework.
  • Chores

    • Bumped Go toolchain to 1.24.3; added CLI, logger, and env libraries; removed legacy config helpers and related tests.
  • Documentation

    • README fully rewritten with expanded installation, usage, and badges.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 19, 2026

🤖 Pull request artifacts

Platform File
🐳 Docker GitHub Container Registry
🍎 Darwin arm64 sftp-sync_Darwin_arm64.tar.gz
🍎 Darwin x86_64 sftp-sync_Darwin_x86_64.tar.gz
🐧 Linux arm64 sftp-sync_Linux_arm64.tar.gz
🐧 Linux i386 sftp-sync_Linux_i386.tar.gz
🐧 Linux x86_64 sftp-sync_Linux_x86_64.tar.gz
🪟 Windows arm64 sftp-sync_Windows_arm64.zip
🪟 Windows i386 sftp-sync_Windows_i386.zip
🪟 Windows x86_64 sftp-sync_Windows_x86_64.zip

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Walkthrough

Replaces flag-based bootstrap with a urfave/cli v3 app, removes the legacy internal/config package, injects structured cli-logger into client/watcher/syncer, adds a sync subcommand and numeric exit codes, updates Go toolchain/dependencies, and wires build-time version ldflags.

Changes

Cohort / File(s) Summary
Module / Dependencies
go.mod
Bumped Go toolchain to 1.24.3; removed github.com/capcom6/logutils; added github.com/go-core-fx/cli-logger, github.com/joho/godotenv, and github.com/urfave/cli/v3 (v3.7.0).
CLI app & commands
main.go, internal/cli/codes/codes.go, internal/cli/commands/sync/...
main.go, internal/cli/codes/codes.go, internal/cli/commands/sync/sync.go, internal/cli/commands/sync/config.go
Introduce urfave/cli app, env loading, custom version handling/ldflags, exit-code mapping, context-aware logger wiring, and a new sync command with argument validation and goroutine lifecycle management.
Removed legacy config package
internal/config/...
internal/config/config.go, internal/config/config_test.go, internal/config/errors.go, internal/config/params.go, internal/config/version.go
Deleted legacy internal/config package and related tests: removed Config type, parsing helpers, exported error var, params helper, and version metadata.
Client factory & FTP client
internal/client/client.go, internal/client/ftp.go
New factory now accepts logger.Logger; FTP client stores injected logger and uses structured logging for connection/reconnect/error handling; signature and call sites updated.
Syncer
internal/syncer/syncer.go
New now requires logger.Logger; Syncer fields made unexported; console prints replaced with structured logging; core sync behavior preserved.
Watcher
internal/watcher/watcher.go
New now accepts logger.Logger; Watcher stores a context-scoped logger and uses structured logging for events and errors.
Build metadata
.goreleaser.yaml
Added ldflags to populate main.appVersion, main.appBuildDate, and main.appGitCommit at build time.
Docs
README.md
Completely rewritten to a Best-README-Template: expanded TOC, installation/usage, environment/flags, exit codes, contributing and acknowledgments.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant CLI as "CLI App (urfave/cli)"
    participant Logger as "Logger (cli-logger)"
    participant ClientFactory as "Client Factory"
    participant Client as "FTP Client"
    participant Watcher
    participant Syncer

    User->>CLI: invoke `sftp-sync sync [source] --dest ... --exclude ...`
    CLI->>Logger: initialize logger (load .env, set level)
    CLI->>ClientFactory: New(dest, logger.WithContext("client",""))
    ClientFactory-->>Client: return ftp client
    CLI->>Watcher: New(source, excludes, logger.WithContext("watcher",""))
    CLI->>Syncer: New(source, client, logger.WithContext("syncer",""))
    CLI->>Watcher: Watch(ctx, &wg)
    Watcher->>CLI: emit fs events
    CLI->>Syncer: Sync(ctx, event.AbsPath)
    Syncer->>Client: perform upload/remove
    Client-->>Syncer: operation result
    Syncer->>Logger: structured info/error logs
    Watcher->>CLI: close events channel when done
    CLI->>CLI: wait for goroutines (wg.Wait)
    CLI->>Logger: shutdown / log.Close()
    CLI-->>User: exit with status code
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • [dev] update tooling #4 — Modifies the same core packages (client, ftp, watcher, syncer, main) and logging/initialization patterns; likely overlaps with these refactors.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[cli] migrate to urfave' directly reflects the main objective of this PR: migrating from ad-hoc flag parsing to urfave/cli/v3, which is evident across multiple files (go.mod, main.go, internal/cli/commands/sync/, etc.).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/plan-migration-to-urfave-v3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
third_party/urfave-cli-v3/cli.go (2)

56-58: Help and version handling not implemented.

The stub discards output and doesn't implement automatic --help or --version flag handling that the real urfave/cli/v3 provides. Users expecting these flags to work will see no output. This is acceptable for a minimal stub, but worth noting for Phase 3 testing mentioned in the migration plan.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@third_party/urfave-cli-v3/cli.go` around lines 56 - 58, The current
Command.Run stub silences flag output (fs.SetOutput(io.Discard)) and doesn't
handle automatic --help/--version behavior; update Command.Run to preserve
output (remove or change io.Discard to os.Stdout or a provided writer) and
implement basic handling: register standard help and version flags on the local
flag set created by flag.NewFlagSet, detect when --help or --version is present
after parsing, and print the command's help/version (use Command's
Usage/UsageText or Version fields) then return without executing the command;
reference Command.Run, the fs variable from flag.NewFlagSet, and the existing
c.Name/c.Usage/Version symbols to locate where to add this logic.

25-30: Required field is defined but not enforced.

The StringFlag.Required field exists but Run() never validates it. If the real urfave/cli/v3 enforces required flags during parsing, this stub will behave differently. Consider whether the stub should error when a required flag is missing, or document this as an intentional limitation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@third_party/urfave-cli-v3/cli.go` around lines 25 - 30, The
StringFlag.Required field is defined but never enforced; update the CLI flag
parsing path (where flags are processed—look for the Run() or parse/resolve
functions that handle StringFlag) to validate that any StringFlag with Required
== true has a non-empty value (consider checking Destination, provided context
map, or resolved flag values) and return or surface an error when a required
flag is missing; ensure you reference StringFlag.Required and the parsing
entrypoint (e.g., Run / parseFlags / resolveFlags) so callers get a clear error
instead of silently continuing, or add documentation comment on StringFlag
stating that Required is intentionally not enforced if you opt not to implement
validation.
internal/config/cli_app.go (1)

34-40: Redundant assignments: Destination bindings already populate cfg.

The Destination pointers on each flag (lines 21, 26, 31) already write parsed values directly to cfg fields during Run(). The Action then re-reads the same values via cmd.String() / cmd.StringSlice() / cmd.Bool() and assigns them again.

Either approach works, but using both is confusing and could mask bugs. Consider removing the redundant assignments in Action and keeping only the WatchPath assignment (which has no Destination binding):

♻️ Simplified Action
 		Action: func(_ context.Context, cmd *cli.Command) error {
-			cfg.Dest = cmd.String("dest")
-			cfg.ExcludePaths = append([]string{}, cmd.StringSlice("exclude")...)
-			cfg.Debug = cmd.Bool("debug")
 			cfg.WatchPath = cmd.Args().Get(0)
 			return nil
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/cli_app.go` around lines 34 - 40, The Action function in the
CLI currently re-reads flags (using cmd.String, cmd.StringSlice, cmd.Bool) and
assigns them back to cfg.Dest, cfg.ExcludePaths and cfg.Debug even though those
flags already use Destination to populate cfg; remove the redundant assignments
for cfg.Dest, cfg.ExcludePaths and cfg.Debug from the Action body and keep only
the WatchPath assignment (cfg.WatchPath = cmd.Args().Get(0)) and the existing
return nil so the Destination-bound flag values are the single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/config/cli_app.go`:
- Around line 17-32: The exhaustruct linter flags the StringFlag in the Flags
slice as missing an explicit Required field; update the StringFlag (the entry
that sets Name "dest" and Destination &cfg.Dest) to include Required: false
(explicitly) so the flag struct is fully specified; apply the same explicit
Required: false to any other cli flag structs in this block if they are also
missing it (e.g., the StringSliceFlag for "exclude" and BoolFlag "debug") to
silence the linter.

---

Nitpick comments:
In `@internal/config/cli_app.go`:
- Around line 34-40: The Action function in the CLI currently re-reads flags
(using cmd.String, cmd.StringSlice, cmd.Bool) and assigns them back to cfg.Dest,
cfg.ExcludePaths and cfg.Debug even though those flags already use Destination
to populate cfg; remove the redundant assignments for cfg.Dest, cfg.ExcludePaths
and cfg.Debug from the Action body and keep only the WatchPath assignment
(cfg.WatchPath = cmd.Args().Get(0)) and the existing return nil so the
Destination-bound flag values are the single source of truth.

In `@third_party/urfave-cli-v3/cli.go`:
- Around line 56-58: The current Command.Run stub silences flag output
(fs.SetOutput(io.Discard)) and doesn't handle automatic --help/--version
behavior; update Command.Run to preserve output (remove or change io.Discard to
os.Stdout or a provided writer) and implement basic handling: register standard
help and version flags on the local flag set created by flag.NewFlagSet, detect
when --help or --version is present after parsing, and print the command's
help/version (use Command's Usage/UsageText or Version fields) then return
without executing the command; reference Command.Run, the fs variable from
flag.NewFlagSet, and the existing c.Name/c.Usage/Version symbols to locate where
to add this logic.
- Around line 25-30: The StringFlag.Required field is defined but never
enforced; update the CLI flag parsing path (where flags are processed—look for
the Run() or parse/resolve functions that handle StringFlag) to validate that
any StringFlag with Required == true has a non-empty value (consider checking
Destination, provided context map, or resolved flag values) and return or
surface an error when a required flag is missing; ensure you reference
StringFlag.Required and the parsing entrypoint (e.g., Run / parseFlags /
resolveFlags) so callers get a clear error instead of silently continuing, or
add documentation comment on StringFlag stating that Required is intentionally
not enforced if you opt not to implement validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44cddcdf-193d-4535-a7f8-97bc33ed466a

📥 Commits

Reviewing files that changed from the base of the PR and between f5114a4 and dd9e578.

📒 Files selected for processing (6)
  • docs/cli-urfave-v3-migration-plan.md
  • go.mod
  • internal/config/cli_app.go
  • internal/config/config.go
  • third_party/urfave-cli-v3/cli.go
  • third_party/urfave-cli-v3/go.mod

@capcom6 capcom6 force-pushed the codex/plan-migration-to-urfave-v3 branch from dd9e578 to 5286363 Compare March 19, 2026 08:01
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cli/commands/sync/config.go`:
- Around line 27-38: parseConfig currently reads only cmd.Args().Get(0) and
ignores extra positional args; update parseConfig to enforce exactly one
positional source by checking cmd.Args().Len() (or equivalent) and returning an
error if len != 1, then set cfg.Source from cmd.Args().Get(0); keep other fields
(cfg.Dest, cfg.Excludes) and call cfg.validate() as before so a second
positional argument is rejected rather than silently ignored.

In `@main.go`:
- Around line 28-29: The root context created as ctx :=
logger.WithLogger(context.Background(), log) is not signal-aware, so replace the
background context with a context that cancels on SIGINT/SIGTERM and then attach
the logger to it; e.g. create a signal-aware rootContext (using
signal.NotifyContext or context.WithCancel + signal.Notify for
os.Interrupt/syscall.SIGTERM), pass that into logger.WithLogger to produce ctx,
and ensure the signal-aware context is used by the watcher/syncer goroutines so
ctx.Done() is triggered on termination.
- Around line 87-90: The current error handling after app.Run(ctx, os.Args)
always sets exitCode = codes.InternalError, which discards specific exit codes
returned by cli.Exit() from syncAction; change the handler to detect whether the
returned error implements urfave/cli/v3's cli.ExitCoder (or has an ExitCode
method) and, if so, set exitCode to that ExitCode(), otherwise fall back to
codes.InternalError; update the block around app.Run and the exitCode assignment
so it preserves and uses the ExitCode from the error when available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5cf44c3d-0aa1-4e10-b09c-9fe4738f407c

📥 Commits

Reviewing files that changed from the base of the PR and between dd9e578 and 5286363.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • go.mod
  • internal/cli/codes/codes.go
  • internal/cli/commands/sync/config.go
  • internal/cli/commands/sync/sync.go
  • internal/client/ftp.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/errors.go
  • internal/config/params.go
  • internal/config/version.go
  • main.go
💤 Files with no reviewable changes (5)
  • internal/config/params.go
  • internal/config/config_test.go
  • internal/config/errors.go
  • internal/config/version.go
  • internal/config/config.go
✅ Files skipped from review due to trivial changes (1)
  • go.mod

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
main.go (1)

93-98: Consider deferring stop() earlier for consistent cleanup.

Currently stop() is called after log.Close(). While functionally correct, deferring stop() immediately after creating the context (at line 30) would ensure signal cleanup even if a panic occurs between context creation and the explicit stop() call.

♻️ Proposed fix
 	rootCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+	defer stop()
 
 	ctx := logger.WithLogger(rootCtx, log)
 ...
 	if err := log.Close(); err != nil {
 		fmt.Fprintf(os.Stderr, "failed to close logger: %v\n", err)
 	}
 
-	stop()
 	os.Exit(exitCode)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 93 - 98, Defer stop() immediately after you create the
cancellable context (i.e., right after the call that returns stop/cancel in
main) so cleanup runs on panic or early returns; then remove (or make redundant)
the later explicit stop() just before os.Exit to avoid double-calling—reference
the stop() function returned from the context creation and the main function
where log.Close() and os.Exit are invoked.
internal/cli/commands/sync/config.go (1)

20-24: Remove redundant conditional block.

The if len(c.Excludes) == 0 block returns nil, but the code also returns nil immediately after. This conditional serves no purpose.

♻️ Proposed fix
 	if c.Dest == "" {
 		return cli.Exit("destination server is required", 1)
 	}
 
-	if len(c.Excludes) == 0 {
-		return nil
-	}
-
 	return nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cli/commands/sync/config.go` around lines 20 - 24, Remove the
redundant conditional that checks len(c.Excludes) == 0 and returns nil; simply
return nil unconditionally. Locate the block referencing c.Excludes (the if
len(c.Excludes) == 0 { return nil } followed by return nil) and delete the
if-statement so the function returns nil once without the unnecessary
conditional.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/cli/commands/sync/config.go`:
- Around line 20-24: Remove the redundant conditional that checks
len(c.Excludes) == 0 and returns nil; simply return nil unconditionally. Locate
the block referencing c.Excludes (the if len(c.Excludes) == 0 { return nil }
followed by return nil) and delete the if-statement so the function returns nil
once without the unnecessary conditional.

In `@main.go`:
- Around line 93-98: Defer stop() immediately after you create the cancellable
context (i.e., right after the call that returns stop/cancel in main) so cleanup
runs on panic or early returns; then remove (or make redundant) the later
explicit stop() just before os.Exit to avoid double-calling—reference the stop()
function returned from the context creation and the main function where
log.Close() and os.Exit are invoked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6b0ec64-e15e-46f5-9255-1d2b186461e9

📥 Commits

Reviewing files that changed from the base of the PR and between 5286363 and 499a929.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • .goreleaser.yaml
  • go.mod
  • internal/cli/commands/sync/config.go
  • internal/cli/commands/sync/sync.go
  • internal/client/client.go
  • internal/client/ftp.go
  • internal/syncer/syncer.go
  • internal/watcher/watcher.go
  • main.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • go.mod
  • internal/cli/commands/sync/sync.go

@capcom6 capcom6 force-pushed the codex/plan-migration-to-urfave-v3 branch from 1edd346 to 3b9e7e6 Compare March 21, 2026 00:19
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/cli/commands/sync/sync.go (1)

70-70: Remove the stale placeholder comment in the action path.

Line 70 (Add your sync logic here) no longer reflects the actual implementation and may confuse future edits.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cli/commands/sync/sync.go` at line 70, Remove the stale placeholder
comment "Add your sync logic here" from the sync command's action handler (the
function that implements the sync command) so it no longer misleads readers;
update or delete the comment in the action path (the function where sync logic
is executed) and, if helpful, replace it with a brief, accurate comment or leave
the implementation code without the placeholder.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/cli/commands/sync/sync.go`:
- Line 70: Remove the stale placeholder comment "Add your sync logic here" from
the sync command's action handler (the function that implements the sync
command) so it no longer misleads readers; update or delete the comment in the
action path (the function where sync logic is executed) and, if helpful, replace
it with a brief, accurate comment or leave the implementation code without the
placeholder.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a276f748-44b3-4cd1-b86e-bdbcce75d8d6

📥 Commits

Reviewing files that changed from the base of the PR and between 1edd346 and 3b9e7e6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (15)
  • .goreleaser.yaml
  • go.mod
  • internal/cli/codes/codes.go
  • internal/cli/commands/sync/config.go
  • internal/cli/commands/sync/sync.go
  • internal/client/client.go
  • internal/client/ftp.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/errors.go
  • internal/config/params.go
  • internal/config/version.go
  • internal/syncer/syncer.go
  • internal/watcher/watcher.go
  • main.go
💤 Files with no reviewable changes (5)
  • internal/config/errors.go
  • internal/config/params.go
  • internal/config/config_test.go
  • internal/config/version.go
  • internal/config/config.go
✅ Files skipped from review due to trivial changes (2)
  • internal/cli/codes/codes.go
  • internal/syncer/syncer.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/client/client.go
  • go.mod
  • internal/cli/commands/sync/config.go

@capcom6 capcom6 force-pushed the codex/plan-migration-to-urfave-v3 branch from 3c8f011 to c192e92 Compare March 21, 2026 23:28
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
README.md (2)

136-139: Tighten tone/wording in Contributing section.

Line 136 (“open source”) should be hyphenated as “open-source,” and Lines 136–139 use a very informal/exclamatory tone. Consider a slightly more neutral style for consistency with the rest of the README.

Suggested wording
-Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
+Contributions are what make the open-source community a great place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.

-If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
-Don't forget to give the project a star! Thanks again!
+If you have a suggestion to improve this project, please fork the repository and open a pull request. You can also open an issue with the `enhancement` label.
+If this project is useful to you, consider starring it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 136 - 139, Update the "Contributing" section to use a
neutral, professional tone and hyphenate "open-source"; specifically, in the
Contributing paragraph replace the current informal/exclamatory sentences with a
concise version such as: express appreciation succinctly, invite contributions
by forking and opening pull requests or filing issues with the "enhancement"
label, and remove exclamation points (i.e., change "open source" to
"open-source", drop emotive phrasing like "greatly appreciated" and "Don't
forget to give the project a star! Thanks again!").

190-190: Remove unused reference definition product-screenshot.

Line 190 defines [product-screenshot], but there’s no active usage (the only occurrence is commented out at Line 70). This triggers markdownlint MD053 and adds dead reference clutter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 190, Remove the unused markdown reference definition
"[product-screenshot]: images/screenshot.png" (the "product-screenshot"
reference) from README.md so the dead reference is eliminated; if you intended
to use it instead, restore or uncomment the corresponding image usage where
"product-screenshot" is referenced, otherwise delete the reference line to
satisfy markdownlint MD053.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 54-65: Add an "Installation" section to the README and include it
in the TABLE OF CONTENTS between "About The Project" and "Usage"; the
Installation section should provide at least one canonical install path (for
example a "go install github.com/owner/repo/cmd/sftp-sync@latest" command and/or
instructions for using the release binary), mention any prerequisites, and show
the basic first-time setup steps required before running "sftp-sync" so readers
can follow the TOC entry and then find the install instructions prior to the
existing "Usage" content.
- Line 152: Update the README.md text string that currently reads "Distributed
under the Apache License 2.0. See `LICENSE.txt` for more information." to
reference the correct license filename; replace `LICENSE.txt` with `LICENSE` so
the line reads "Distributed under the Apache License 2.0. See `LICENSE` for more
information." This change targets the exact sentence in README.md (the line
containing the phrase "See `LICENSE.txt`") and should be a simple text
substitution with no other content changes.

---

Nitpick comments:
In `@README.md`:
- Around line 136-139: Update the "Contributing" section to use a neutral,
professional tone and hyphenate "open-source"; specifically, in the Contributing
paragraph replace the current informal/exclamatory sentences with a concise
version such as: express appreciation succinctly, invite contributions by
forking and opening pull requests or filing issues with the "enhancement" label,
and remove exclamation points (i.e., change "open source" to "open-source", drop
emotive phrasing like "greatly appreciated" and "Don't forget to give the
project a star! Thanks again!").
- Line 190: Remove the unused markdown reference definition
"[product-screenshot]: images/screenshot.png" (the "product-screenshot"
reference) from README.md so the dead reference is eliminated; if you intended
to use it instead, restore or uncomment the corresponding image usage where
"product-screenshot" is referenced, otherwise delete the reference line to
satisfy markdownlint MD053.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba3c3bcc-0ce3-4992-8479-fe53841110e0

📥 Commits

Reviewing files that changed from the base of the PR and between c192e92 and 63ac860.

⛔ Files ignored due to path filters (1)
  • images/logo.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • README.md

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
README.md (1)

283-283: Remove unused markdown reference definition.

The product-screenshot reference is defined but never used (the only reference at line 80 is commented out). Consider removing this definition to keep the markdown references clean.

🧹 Proposed cleanup
-[product-screenshot]: images/screenshot.png
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 283, Remove the unused markdown reference definition
"[product-screenshot]: images/screenshot.png" from README.md (symbol:
product-screenshot); if there is a commented-out usage of that reference
elsewhere, either remove or restore that commented reference to avoid leaving
orphaned metadata—ensure no remaining references to product-screenshot remain
after the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@README.md`:
- Line 283: Remove the unused markdown reference definition
"[product-screenshot]: images/screenshot.png" from README.md (symbol:
product-screenshot); if there is a commented-out usage of that reference
elsewhere, either remove or restore that commented reference to avoid leaving
orphaned metadata—ensure no remaining references to product-screenshot remain
after the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8aa94de8-0847-40f0-b465-4ccf94c34ff3

📥 Commits

Reviewing files that changed from the base of the PR and between 63ac860 and 7a07a36.

📒 Files selected for processing (1)
  • README.md

@capcom6 capcom6 force-pushed the codex/plan-migration-to-urfave-v3 branch from 7a07a36 to b6338a9 Compare March 24, 2026 01:07
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
README.md (2)

283-283: Remove unused markdown reference definition.

The [product-screenshot] reference definition is unused (the actual screenshot link at line 80 is commented out as a template placeholder).

🧹 Proposed cleanup
-[product-screenshot]: images/screenshot.png
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 283, Remove the unused markdown reference definition
"[product-screenshot]: images/screenshot.png" from README.md; locate the
reference label "[product-screenshot]" in the file and delete that reference
line (and optionally remove or update any commented template screenshot link
that references it) so no orphaned markdown reference remains.

43-43: Clarify FTP vs. SFTP to avoid user confusion.

The project is named "sftp-sync" but the description states it syncs with "a remote FTP server." Line 210 confirms SFTP support is a future roadmap item. Consider adding a brief note early in the README clarifying that despite the name, the current version supports FTP (not SFTP), with SFTP planned for the future.

📝 Suggested clarification

After line 82, consider adding:

> **Note:** Despite the project name, the current version supports FTP protocol. SFTP support is planned for a future release (see [Roadmap](`#roadmap`)).

Also applies to: 82-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 43, Add a short clarifying note in README.md (e.g.,
immediately after the project description or after the "sftp-sync" heading)
stating that despite the project name "sftp-sync" the current release supports
FTP, not SFTP, and that SFTP support is planned for a future release; reference
the "Roadmap" heading so users can find planned SFTP work (suggested wording:
Note: Despite the project name, the current version supports the FTP protocol;
SFTP support is planned for a future release — see Roadmap).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@README.md`:
- Line 283: Remove the unused markdown reference definition
"[product-screenshot]: images/screenshot.png" from README.md; locate the
reference label "[product-screenshot]" in the file and delete that reference
line (and optionally remove or update any commented template screenshot link
that references it) so no orphaned markdown reference remains.
- Line 43: Add a short clarifying note in README.md (e.g., immediately after the
project description or after the "sftp-sync" heading) stating that despite the
project name "sftp-sync" the current release supports FTP, not SFTP, and that
SFTP support is planned for a future release; reference the "Roadmap" heading so
users can find planned SFTP work (suggested wording: Note: Despite the project
name, the current version supports the FTP protocol; SFTP support is planned for
a future release — see Roadmap).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2138937-9075-4d15-abc9-9da5f41be8fb

📥 Commits

Reviewing files that changed from the base of the PR and between 7a07a36 and b6338a9.

⛔ Files ignored due to path filters (1)
  • images/logo.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • README.md

@capcom6 capcom6 changed the title Migrate CLI parsing to urfave/cli v3 and add migration plan [cli] migrate to urfave Mar 24, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
main.go (2)

87-89: Remove commented-out command registration to reduce ambiguity.

Keeping // sync.Command(), in the Commands list is confusing now that root Action is the execution path.

Suggested cleanup
 		Commands: []*cli.Command{
-			// sync.Command(),
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 87 - 89, Remove the commented-out command registration
to eliminate ambiguity: delete the commented line "// sync.Command()," from the
Commands slice so the Commands: []*cli.Command{ ... } list contains only active
entries (or is left empty) now that the root Action is the execution path;
locate the Commands slice declaration (Commands: []*cli.Command{ ... }) and
remove the commented sync.Command() reference.

59-60: Drop redundant empty Description field.

Description: "" adds noise without behavior change.

Suggested cleanup
-		Description: "",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 59 - 60, Remove the redundant empty Description field
from the command struct literal (the line `Description: ""`) so the code is
cleaner; locate the struct where `ArgsUsage: "[source]"` is set (the command
definition) and delete the `Description: ""` entry leaving only meaningful
fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@main.go`:
- Around line 87-89: Remove the commented-out command registration to eliminate
ambiguity: delete the commented line "// sync.Command()," from the Commands
slice so the Commands: []*cli.Command{ ... } list contains only active entries
(or is left empty) now that the root Action is the execution path; locate the
Commands slice declaration (Commands: []*cli.Command{ ... }) and remove the
commented sync.Command() reference.
- Around line 59-60: Remove the redundant empty Description field from the
command struct literal (the line `Description: ""`) so the code is cleaner;
locate the struct where `ArgsUsage: "[source]"` is set (the command definition)
and delete the `Description: ""` entry leaving only meaningful fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7aee00cc-d68d-4378-beb1-ed46da1e4c3b

📥 Commits

Reviewing files that changed from the base of the PR and between b6338a9 and 70e90c2.

📒 Files selected for processing (2)
  • internal/cli/commands/sync/sync.go
  • main.go
✅ Files skipped from review due to trivial changes (1)
  • internal/cli/commands/sync/sync.go

@capcom6 capcom6 force-pushed the codex/plan-migration-to-urfave-v3 branch from 9b39067 to c4c9773 Compare March 25, 2026 00:23
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
README.md (1)

283-283: Remove unused product-screenshot reference or use it.

[product-screenshot] is currently defined but not referenced anywhere, which trips markdownlint (MD053).

Proposed cleanup
-[product-screenshot]: images/screenshot.png
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 283, The README defines an unused markdown reference label
"[product-screenshot]" which triggers markdownlint MD053; either remove the
reference definition line "[product-screenshot]: images/screenshot.png" or add a
matching inline reference where the image should appear (e.g., use "![Alt
text][product-screenshot]" near the project overview or screenshot section).
Update the README so the product-screenshot label is either referenced by an
image tag (to use the asset) or deleted to eliminate the unused reference.
internal/syncer/syncer.go (1)

99-103: Use filepath.Join for the local filesystem path (absPath).

Line 99 and Line 103 join absPath with path.Join, which is for slash-separated paths, not OS filesystem paths. Since absPath is used with os.ReadDir() and represents a local filesystem path, use filepath.Join for consistency with Go's filesystem path handling conventions. For relPath, path.Join is acceptable as it's passed through pathNormalize() which converts to forward slashes anyway.

Proposed patch
 		if file.IsDir() {
-			if dErr := s.syncDir(ctx, path.Join(absPath, file.Name()), path.Join(relPath, file.Name())); dErr != nil {
+			if dErr := s.syncDir(ctx, filepath.Join(absPath, file.Name()), path.Join(relPath, file.Name())); dErr != nil {
 				return dErr
 			}
 		} else {
-			if fErr := s.syncFile(ctx, path.Join(absPath, file.Name()), path.Join(relPath, file.Name())); fErr != nil {
+			if fErr := s.syncFile(ctx, filepath.Join(absPath, file.Name()), path.Join(relPath, file.Name())); fErr != nil {
 				return fErr
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/syncer/syncer.go` around lines 99 - 103, The code uses path.Join
when combining absPath with file.Name() inside calls to s.syncDir and
s.syncFile, but absPath is a local filesystem path (used with os.ReadDir) and
should be joined using filepath.Join to respect OS-specific separators; update
the two call sites that construct the local path (the first arg passed into
s.syncDir and s.syncFile) to use filepath.Join(absPath, file.Name()) while
leaving the relPath construction with path.Join as-is (relPath is normalized by
pathNormalize). Also add the necessary import for "path/filepath" if not already
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/syncer/syncer.go`:
- Around line 99-103: The code uses path.Join when combining absPath with
file.Name() inside calls to s.syncDir and s.syncFile, but absPath is a local
filesystem path (used with os.ReadDir) and should be joined using filepath.Join
to respect OS-specific separators; update the two call sites that construct the
local path (the first arg passed into s.syncDir and s.syncFile) to use
filepath.Join(absPath, file.Name()) while leaving the relPath construction with
path.Join as-is (relPath is normalized by pathNormalize). Also add the necessary
import for "path/filepath" if not already present.

In `@README.md`:
- Line 283: The README defines an unused markdown reference label
"[product-screenshot]" which triggers markdownlint MD053; either remove the
reference definition line "[product-screenshot]: images/screenshot.png" or add a
matching inline reference where the image should appear (e.g., use "![Alt
text][product-screenshot]" near the project overview or screenshot section).
Update the README so the product-screenshot label is either referenced by an
image tag (to use the asset) or deleted to eliminate the unused reference.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 70342fd1-60e0-4442-93da-b5e94f46dbcd

📥 Commits

Reviewing files that changed from the base of the PR and between 70e90c2 and c4c9773.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • images/logo.png is excluded by !**/*.png
📒 Files selected for processing (16)
  • .goreleaser.yaml
  • README.md
  • go.mod
  • internal/cli/codes/codes.go
  • internal/cli/commands/sync/config.go
  • internal/cli/commands/sync/sync.go
  • internal/client/client.go
  • internal/client/ftp.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/errors.go
  • internal/config/params.go
  • internal/config/version.go
  • internal/syncer/syncer.go
  • internal/watcher/watcher.go
  • main.go
💤 Files with no reviewable changes (5)
  • internal/config/params.go
  • internal/config/errors.go
  • internal/config/config_test.go
  • internal/config/version.go
  • internal/config/config.go
✅ Files skipped from review due to trivial changes (2)
  • go.mod
  • internal/cli/codes/codes.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • .goreleaser.yaml
  • internal/client/client.go
  • internal/cli/commands/sync/config.go
  • internal/client/ftp.go
  • internal/watcher/watcher.go
  • internal/cli/commands/sync/sync.go
  • main.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
README.md (1)

272-272: Remove unused markdown reference.

The product-screenshot reference is defined but never used in the document (the reference at line 79 is commented out). Consider removing this unused definition to keep the reference section clean.

🧹 Proposed cleanup
-[product-screenshot]: images/screenshot.png
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 272, Remove the unused markdown reference
"[product-screenshot]: images/screenshot.png" from README by deleting the
definition for the symbol product-screenshot (it's defined but never referenced
elsewhere—only a commented example exists); ensure no other references to
product-screenshot remain and tidy the reference section accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@README.md`:
- Line 272: Remove the unused markdown reference "[product-screenshot]:
images/screenshot.png" from README by deleting the definition for the symbol
product-screenshot (it's defined but never referenced elsewhere—only a commented
example exists); ensure no other references to product-screenshot remain and
tidy the reference section accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fbe88fb4-00ec-41d2-ad0b-6091831d28c4

📥 Commits

Reviewing files that changed from the base of the PR and between c4c9773 and 7911c0e.

📒 Files selected for processing (1)
  • README.md

@capcom6 capcom6 force-pushed the codex/plan-migration-to-urfave-v3 branch from 7911c0e to 7d7880c Compare March 26, 2026 02:22
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
README.md (1)

272-272: Consider removing unused screenshot reference.

Line 272 defines [product-screenshot] but it's only used in a commented-out section (line 79). Consider removing this reference definition for cleaner markdown, or uncomment and populate the screenshot section if you plan to add product screenshots soon.

🧹 Proposed cleanup
-[product-screenshot]: images/screenshot.png
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 272, Remove the unused markdown reference
"[product-screenshot]" from the README (the definition shown in the diff) or
alternatively restore and populate the commented-out screenshot section that
references it; locate the reference label "[product-screenshot]" and either
delete that definition line or uncomment and fill the screenshot block where
it's referenced so the label is actually used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 43: Add a short clarification under the "About The Project" heading to
state that, despite the repository name "sftp-sync", the tool currently supports
FTP only and SFTP is planned for a future release; update any mentions of "FTP
server" or ftp:// examples if desired to remain consistent, and ensure the
roadmap entry referencing SFTP (the planned feature) stays labeled as future
work so readers aren’t confused.
- Around line 156-159: The README usage example is missing the required "sync"
subcommand; verify the CLI's commands (look for the sync command in your CLI
setup, e.g., the "sync" entry in the cli.App Commands slice) and if the CLI
requires a subcommand, update the example invocation to "sftp-sync sync
--dest=ftp://username:password@hostname:port/path/to/remote/folder
--exclude=.git /path/to/local/folder" so it matches the "Sync Command Options"
section; if instead the CLI exposes sync as the default behavior (no
subcommand), remove or rename the "Sync Command Options" header to avoid
confusion and make the README consistent with the actual CLI (referencing the
"sync" command name and "Sync Command Options" section to locate the docs to
change).

---

Nitpick comments:
In `@README.md`:
- Line 272: Remove the unused markdown reference "[product-screenshot]" from the
README (the definition shown in the diff) or alternatively restore and populate
the commented-out screenshot section that references it; locate the reference
label "[product-screenshot]" and either delete that definition line or uncomment
and fill the screenshot block where it's referenced so the label is actually
used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65268ede-4194-4ca2-b043-ead6f9480728

📥 Commits

Reviewing files that changed from the base of the PR and between 7911c0e and 7d7880c.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • images/logo.png is excluded by !**/*.png
📒 Files selected for processing (16)
  • .goreleaser.yaml
  • README.md
  • go.mod
  • internal/cli/codes/codes.go
  • internal/cli/commands/sync/config.go
  • internal/cli/commands/sync/sync.go
  • internal/client/client.go
  • internal/client/ftp.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/errors.go
  • internal/config/params.go
  • internal/config/version.go
  • internal/syncer/syncer.go
  • internal/watcher/watcher.go
  • main.go
💤 Files with no reviewable changes (5)
  • internal/config/params.go
  • internal/config/version.go
  • internal/config/errors.go
  • internal/config/config_test.go
  • internal/config/config.go
✅ Files skipped from review due to trivial changes (4)
  • internal/cli/codes/codes.go
  • go.mod
  • internal/cli/commands/sync/config.go
  • internal/watcher/watcher.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/client/client.go
  • internal/cli/commands/sync/sync.go
  • main.go

@capcom6 capcom6 marked this pull request as ready for review March 26, 2026 06:21
@capcom6 capcom6 merged commit f8d412f into master Mar 27, 2026
8 checks passed
@capcom6 capcom6 deleted the codex/plan-migration-to-urfave-v3 branch March 27, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant