Skip to content

Commit 9c771ee

Browse files
committed
refactor: remove qsv-skill-gen binary, and make it an option in qsv gated behind mcp feature flag
1 parent 7b75a07 commit 9c771ee

File tree

5 files changed

+102
-77
lines changed

5 files changed

+102
-77
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,6 @@ doctest = false
5050
path = "src/maindp.rs"
5151
required-features = ["datapusher_plus"]
5252

53-
[[bin]]
54-
name = "qsv-skill-gen"
55-
test = false
56-
bench = false
57-
doctest = false
58-
path = "src/bin/qsv-skill-gen.rs"
59-
6053
[[test]]
6154
name = "tests"
6255
path = "tests/tests.rs"
@@ -99,6 +92,7 @@ censor = { version = "0.3", optional = true }
9992
chrono = { version = "0.4", default-features = false }
10093
chrono-tz = "0.10"
10194
console = { version = "0.16", optional = true }
95+
const_format = "0.2"
10296
cpc = { version = "3", optional = true }
10397
crc32fast = { version = "1.4", optional = true }
10498
crossbeam-channel = "0.5"
@@ -379,6 +373,7 @@ distrib_features = [
379373
"foreach",
380374
"geocode",
381375
"luau",
376+
"mcp",
382377
"polars",
383378
"python",
384379
"to",
@@ -416,6 +411,7 @@ geocode = [
416411
"sled",
417412
]
418413
luau = ["mlua", "sanitize-filename"]
414+
mcp = []
419415
polars = ["dep:polars", "bytemuck"]
420416

421417
prompt = ["rfd"]

src/bin/README.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/main.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ mod cmd;
5555
mod config;
5656
mod index;
5757
mod lookup;
58+
#[cfg(feature = "mcp")]
59+
mod mcp_skills_gen;
5860
mod odhtcache;
5961
mod select;
6062
mod util;
6163

62-
static USAGE: &str = r#"
64+
const USAGE_COMMON: &str = r#"
6365
Usage:
6466
qsv <command> [<args>...]
6567
qsv [options]
@@ -68,11 +70,27 @@ Options:
6870
--list List all commands available.
6971
--envlist List all qsv-relevant environment variables.
7072
-u, --update Update qsv to the latest release from GitHub.
71-
-U, --updatenow Update qsv to the latest release from GitHub without confirming.
72-
-h, --help Display this message
73+
-U, --updatenow Update qsv to the latest release from GitHub without confirming."#;
74+
75+
#[cfg(feature = "mcp")]
76+
const USAGE_MCP: &str = " --update-mcp-skills Regenerate MCP skills JSON files for Claude Desktop.";
77+
78+
const USAGE_FOOTER: &str = " -h, --help Display this message
7379
<command> -h Display the command help message
74-
-v, --version Print version info, mem allocator, features installed,
75-
max_jobs, num_cpus, build info then exit"#;
80+
-v, --version Print version info, mem allocator, features installed,
81+
max_jobs, num_cpus, build info then exit";
82+
83+
#[cfg(feature = "mcp")]
84+
const USAGE: &str = const_format::concatcp!(
85+
USAGE_COMMON,
86+
"\n",
87+
USAGE_MCP,
88+
"\n",
89+
USAGE_FOOTER
90+
);
91+
92+
#[cfg(not(feature = "mcp"))]
93+
const USAGE: &str = const_format::concatcp!(USAGE_COMMON, "\n", USAGE_FOOTER);
7694

7795
#[derive(Deserialize)]
7896
struct Args {
@@ -81,6 +99,8 @@ struct Args {
8199
flag_envlist: bool,
82100
flag_update: bool,
83101
flag_updatenow: bool,
102+
#[cfg(feature = "mcp")]
103+
flag_update_mcp_skills: bool,
84104
}
85105

86106
fn main() -> QsvExitCode {
@@ -250,6 +270,19 @@ fn main() -> QsvExitCode {
250270
util::log_end(qsv_args, now);
251271
return QsvExitCode::Good;
252272
}
273+
#[cfg(feature = "mcp")]
274+
{
275+
if args.flag_update_mcp_skills {
276+
util::log_end(qsv_args, now);
277+
match mcp_skills_gen::generate_mcp_skills() {
278+
Ok(()) => return QsvExitCode::Good,
279+
Err(e) => {
280+
werr!("MCP skills generation error: {e}");
281+
return QsvExitCode::Bad;
282+
},
283+
}
284+
}
285+
}
253286
if args.flag_update || args.flag_updatenow {
254287
util::log_end(qsv_args, now);
255288
if let Err(e) = util::qsv_check_for_update(false, args.flag_updatenow) {
Lines changed: 40 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,7 @@
1-
#![cfg_attr(
2-
clippy,
3-
allow(
4-
clippy::cast_possible_truncation,
5-
clippy::cast_possible_wrap,
6-
clippy::cast_sign_loss,
7-
// things are often more readable this way
8-
clippy::needless_raw_string_hashes,
9-
clippy::cast_lossless,
10-
clippy::module_name_repetitions,
11-
clippy::type_complexity,
12-
clippy::zero_prefixed_literal,
13-
clippy::unused_self,
14-
clippy::if_same_then_else,
15-
clippy::needless_continue,
16-
// correctly used
17-
clippy::enum_glob_use,
18-
clippy::result_unit_err,
19-
// not practical
20-
clippy::similar_names,
21-
clippy::too_many_lines,
22-
clippy::struct_excessive_bools,
23-
// preference
24-
clippy::doc_markdown,
25-
clippy::unnecessary_wraps,
26-
clippy::ref_as_ptr,
27-
// false positive
28-
clippy::needless_doctest_main,
29-
// noisy
30-
clippy::missing_errors_doc,
31-
clippy::use_self,
32-
clippy::cognitive_complexity,
33-
clippy::option_if_let_else,
34-
clippy::implicit_clone,
35-
),
36-
warn(
37-
clippy::missing_asserts_for_indexing,
38-
)
39-
)]
40-
41-
// qsv-skill-gen: Generate Agent Skills from qsv command USAGE text
1+
// qsv MCP Skills Generator - Generate Agent Skills from qsv command USAGE text
422
//
43-
// This tool parses USAGE text from qsv commands and generates Agent Skill
44-
// definitions in JSON format for use with the Claude Agent SDK.
3+
// This module parses USAGE text from qsv commands and generates Agent Skill
4+
// definitions in JSON format for use with Claude Desktop (MCP) and the Claude Agent SDK.
455
//
466
// Uses qsv-docopt Parser for robust USAGE text parsing.
477

@@ -53,6 +13,8 @@ use std::{
5313
use qsv_docopt::parse::{Argument as DocoptArgument, Atom, Parser};
5414
use serde::{Deserialize, Serialize};
5515

16+
use crate::CliResult;
17+
5618
#[derive(Debug, Serialize, Deserialize)]
5719
struct SkillDefinition {
5820
name: String,
@@ -565,7 +527,9 @@ fn extract_usage_from_file(file_path: &Path) -> Result<String, String> {
565527
Ok(after_start[..usage_end].to_string())
566528
}
567529

568-
fn main() -> Result<(), Box<dyn std::error::Error>> {
530+
/// Public function to generate MCP skills JSON files
531+
/// Called via `qsv --update-mcp-skills` flag
532+
pub fn generate_mcp_skills() -> CliResult<()> {
569533
// Get all commands from src/cmd/*.rs (excluding mod.rs and duplicates)
570534
let commands = vec![
571535
"apply",
@@ -636,22 +600,35 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
636600
"validate",
637601
];
638602

603+
// Determine repository root - look for Cargo.toml
604+
let mut repo_root = std::env::current_dir()?;
605+
loop {
606+
if repo_root.join("Cargo.toml").exists() && repo_root.join("src/cmd").exists() {
607+
break;
608+
}
609+
if !repo_root.pop() {
610+
return fail_clierror!("Could not find qsv repository root (no Cargo.toml with src/cmd)");
611+
}
612+
}
613+
639614
// Create output directory
640-
let output_dir = PathBuf::from(".claude/skills/qsv");
615+
let output_dir = repo_root.join(".claude/skills/qsv");
641616
fs::create_dir_all(&output_dir)?;
642617

643-
println!("QSV Agent Skill Generator (using qsv-docopt)");
644-
println!("=============================================");
645-
println!("Generating {} skills...\n", commands.len());
618+
eprintln!("QSV MCP Skills Generator (via qsv --update-mcp-skills)");
619+
eprintln!("=======================================================");
620+
eprintln!("Repository: {}", repo_root.display());
621+
eprintln!("Output: {}", output_dir.display());
622+
eprintln!("Generating {} skills...\n", commands.len());
646623

647624
let mut success_count = 0;
648625
let mut error_count = 0;
649626

650627
for cmd_name in &commands {
651-
println!("Processing: {cmd_name}");
628+
eprintln!("Processing: {cmd_name}");
652629

653630
// Find command file
654-
let cmd_file = PathBuf::from(format!("src/cmd/{cmd_name}.rs"));
631+
let cmd_file = repo_root.join(format!("src/cmd/{cmd_name}.rs"));
655632

656633
if !cmd_file.exists() {
657634
eprintln!(" ❌ File not found: {}", cmd_file.display());
@@ -685,22 +662,28 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
685662
let json = serde_json::to_string_pretty(&skill)?;
686663
fs::write(&output_file, json)?;
687664

688-
println!(" ✅ Generated: {}", output_file.display());
689-
println!(" - {} arguments", skill.command.args.len());
690-
println!(" - {} options", skill.command.options.len());
691-
println!();
665+
eprintln!(" ✅ Generated: {}", output_file.display());
666+
eprintln!(" - {} arguments", skill.command.args.len());
667+
eprintln!(" - {} options", skill.command.options.len());
668+
eprintln!();
692669

693670
success_count += 1;
694671
}
695672

696-
println!("\nSkill generation complete!");
697-
println!("📁 Output directory: {}", output_dir.display());
698-
println!(
673+
eprintln!("\nMCP Skills generation complete!");
674+
eprintln!("📁 Output directory: {}", output_dir.display());
675+
eprintln!(
699676
"📊 Summary: {} succeeded, {} failed out of {} total",
700677
success_count,
701678
error_count,
702679
commands.len()
703680
);
704681

682+
if error_count > 0 {
683+
return fail_clierror!("{} skill(s) failed to generate", error_count);
684+
}
685+
686+
eprintln!("\n💡 Restart Claude Desktop to load the updated skills.");
687+
705688
Ok(())
706689
}

0 commit comments

Comments
 (0)