feat(cli): combined product picker with eval products + clipboard fix

- Interactive `msdl` (no flags) now lists consumer + eval products in one
  picker with an Evaluation / Enterprise separator; selecting a server
  product routes to the eval flow automatically
- copyToClipboard calls clip.exe by full SystemRoot path, bypassing
  cmd.exe PATH lookup that failed in some terminal environments
- CliHandoff card: winget primary install + msdl (no .exe) in run command

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Shekhar Vaidya 2026-06-30 07:25:03 +05:30
parent 7a267a2581
commit a9c713cd6a
3 changed files with 64 additions and 18 deletions

View file

@ -245,13 +245,10 @@ func runConsumer(productID, query, langName string, noContribute bool) error {
return fmt.Errorf("unknown product ID %q — run msdl --list to see all products", productID)
}
product = p
} else {
candidates := consumerProducts
if query != "" {
candidates = searchProducts(query)
if len(candidates) == 0 {
return fmt.Errorf("no products match %q — run msdl --list to see all products", query)
}
} else if query != "" {
candidates := searchProducts(query)
if len(candidates) == 0 {
return fmt.Errorf("no products match %q — run msdl --list to see all products", query)
}
if len(candidates) == 1 {
product = candidates[0]
@ -263,6 +260,15 @@ func runConsumer(productID, query, langName string, noContribute bool) error {
return err
}
}
} else {
isEval, selected, ep, err := pickCombined()
if err != nil {
return err
}
if isEval {
return runEval(ep.Slug)
}
product = selected
}
fmt.Fprintf(os.Stderr, "Setting up Microsoft session...\n")

View file

@ -87,6 +87,28 @@ func isTerminal() bool {
return fi.Mode()&os.ModeCharDevice != 0
}
func pickCombined() (isEval bool, p Product, ep EvalProduct, err error) {
total := len(consumerProducts) + len(evalProducts)
fmt.Fprintln(os.Stderr, "\nSelect a product:")
for i, prod := range consumerProducts {
fmt.Fprintf(os.Stderr, " %2d. %s\n", i+1, prod.Name)
}
fmt.Fprintln(os.Stderr, "\n ── Evaluation / Enterprise ──")
offset := len(consumerProducts)
for i, e := range evalProducts {
fmt.Fprintf(os.Stderr, " %2d. %s\n", offset+i+1, e.Name)
}
fmt.Fprint(os.Stderr, "\nChoice: ")
n, err := parseChoice(os.Stdin, total)
if err != nil {
return false, Product{}, EvalProduct{}, err
}
if n <= offset {
return false, consumerProducts[n-1], EvalProduct{}, nil
}
return true, Product{}, evalProducts[n-offset-1], nil
}
func pickArchitecture(links []DownloadLink) (DownloadLink, error) {
fmt.Fprintln(os.Stderr, "Select architecture:")
for i, l := range links {
@ -137,7 +159,11 @@ func copyToClipboard(text string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "clip")
clipExe := `C:\Windows\System32\clip.exe`
if sr := os.Getenv("SystemRoot"); sr != "" {
clipExe = sr + `\System32\clip.exe`
}
cmd = exec.Command(clipExe)
case "darwin":
cmd = exec.Command("pbcopy")
default:

View file

@ -14,9 +14,29 @@ interface Props {
const RELEASES_URL = 'https://minxl.ink/msdl-github-release'
function InstallSteps() {
const [copied, setCopied] = useState(false)
function handleCopy() {
navigator.clipboard.writeText('winget install starkSV.msdl')
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="space-y-1.5">
<div className="space-y-2">
<p className="text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600">1. Install</p>
<div className="rounded-lg border border-emerald-500/20 bg-emerald-500/5 p-3 space-y-2">
<div className="flex items-center justify-between">
<span className="text-[10px] font-mono font-semibold tracking-widest uppercase text-emerald-500">Windows · winget</span>
<span className="text-[10px] px-1.5 py-0.5 rounded border border-emerald-500/20 text-emerald-600 font-mono">recommended</span>
</div>
<div className="flex items-center justify-between gap-2 rounded bg-black/30 px-3 py-2">
<code className="text-[12px] font-mono text-zinc-300">winget install starkSV.msdl</code>
<button onClick={handleCopy} className="flex-shrink-0 text-zinc-500 hover:text-white transition-colors">
{copied ? <Check size={12} className="text-green-400" /> : <Copy size={12} />}
</button>
</div>
</div>
<a
href={RELEASES_URL}
target="_blank"
@ -24,14 +44,8 @@ function InstallSteps() {
className="flex items-center gap-2 px-3 py-2 rounded-lg bg-white/4 border border-white/7 text-[12px] text-zinc-400 hover:text-zinc-200 hover:border-white/12 transition-colors"
>
<ExternalLink size={12} className="flex-shrink-0 text-zinc-600" />
Download msdl from GitHub Releases
Other platforms — GitHub Releases
</a>
<p className="text-[12px] text-zinc-400 leading-relaxed pt-0.5">
Rename the downloaded file and add its folder to your PATH:<br />
<span className="text-zinc-500">Windows →</span> <code className="font-mono text-zinc-300 bg-white/5 px-1 rounded">msdl.exe</code>
<span className="mx-2 text-zinc-700">·</span>
<span className="text-zinc-500">macOS/Linux →</span> <code className="font-mono text-zinc-300 bg-white/5 px-1 rounded">msdl</code>
</p>
</div>
)
}
@ -72,8 +86,8 @@ export default function CliHandoff({ productId, langName, langDisplay, defaultOp
const [copied, setCopied] = useState(false)
const command = langName
? `msdl.exe --id ${productId} --lang "${langName}"`
: `msdl.exe --id ${productId}`
? `msdl --id ${productId} --lang "${langName}"`
: `msdl --id ${productId}`
function handleCopy() {
navigator.clipboard.writeText(command)