Sprint B.5: add-LICENSE action (MIT/Apache-2.0) in [m] menu + vulp repo license

- src/license/: embedded templates, holder resolution (config > git user.name
  > account username), write path shared by TUI and CLI
- TUI: bulk-aware [m] entry over action_targets(), kind select + confirm,
  point-updates has_license/is_dirty, logs to activity (new License kind)
- CLI: vulp repo license <repo>...|--all [--kind] [--holder] [--json],
  skips already-licensed repos, exit 2/4 contract kept
- config: license_holder + default_license fields (TUI-editable)
- never overwrites an existing license, never auto-commits
This commit is contained in:
dualfroz committed 2026-07-06 15:37:25 +02:00
1 parent 0468c032c6
commit f1432d1cd3
15 files changed
+506 -7

No files matched your search

+15
View File
@@ -37,8 +37,17 @@ pub struct AppConfig {
/// Count lines-of-code during workspace scan (slow for large repos; off by default).
#[serde(default)]
pub show_locs: bool,
/// Copyright holder written into generated LICENSE files
/// (empty = fall back to `git config user.name`, then `username`).
#[serde(default)]
pub license_holder: String,
/// Default template for the add-LICENSE action: "mit" or "apache-2.0".
#[serde(default = "default_license_kind")]
pub default_license: String,
}
fn default_license_kind() -> String { "mit".into() }
/// Picks the first available clipboard tool on PATH as the default
/// `clipboard_cmd`, so a fresh install works without manual configuration
/// on most systems.
@@ -71,6 +80,8 @@ impl Default for AppConfig {
workspace_groups: vec![],
clipboard_cmd: default_clipboard_cmd(),
show_locs: false,
license_holder: String::new(),
default_license: default_license_kind(),
}
}
}
@@ -94,6 +105,8 @@ impl AppConfig {
("timezone", self.timezone.clone()),
("vsc_data_dir", self.vsc_data_dir.to_string_lossy().into()),
("workspace_groups", self.workspace_groups.join(", ")),
("license_holder", self.license_holder.clone()),
("default_license", self.default_license.clone()),
]
}
@@ -115,6 +128,8 @@ impl AppConfig {
"share_stats" => self.share_stats = value == "true",
"timezone" => self.timezone = value,
"vsc_data_dir" => self.vsc_data_dir = PathBuf::from(&value),
"license_holder" => self.license_holder = value,
"default_license" => self.default_license = value,
"workspace_groups" => {
self.workspace_groups = value
.split(',')