MVP LlmRun: CLI/GUI che lancia claude -p nella cartella dell'exe, con docs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2718c3d2fb
commit
7d557b906f
7 files changed
+158
-22
No files matched your search
@@ -0,0 +1,2 @@
|
||||
bin/
|
||||
obj/
|
||||
@@ -2,30 +2,32 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Stato del progetto
|
||||
## Progetto
|
||||
|
||||
Repository vuoto (nessun codice, nessun git). Obiettivo: MVP di un eseguibile C# che, "sotto il cofano", invoca Claude Code (CLI `claude`) da terminale.
|
||||
LlmRun: eseguibile C# (.NET 10, Windows) che invoca Claude Code (`claude -p`) da terminale. Senza argomenti apre una GUI WinForms, con argomenti funziona da CLI. Non è un repo git.
|
||||
|
||||
## Approccio doc-first
|
||||
## Workflow doc-first (obbligatorio)
|
||||
|
||||
Prima si aggiorna la documentazione, poi il codice. Specifica e criteri di accettazione: [docs/MVP.md](docs/MVP.md). Fasi e stato: [docs/ROADMAP.md](docs/ROADMAP.md). Se il codice diverge dall'MVP, aggiornare prima i doc.
|
||||
- Specifica e criteri di accettazione: [docs/MVP.md](docs/MVP.md). Fasi e stato: [docs/ROADMAP.md](docs/ROADMAP.md). Uso: [README.md](README.md).
|
||||
- Ad ogni richiesta di modifica: aggiornare prima i doc interessati (MVP.md, ROADMAP.md con le spunte, README.md se cambia l'uso), poi il codice, poi questo file se cambiano comandi, design o vincoli. Nessuna modifica deve lasciare i doc disallineati.
|
||||
|
||||
Design deciso: un solo `Program.cs`, nessuna dipendenza, lancio di `claude -p <prompt>` con `ProcessStartInfo.ArgumentList`, output in streaming, exit code propagato.
|
||||
## Comandi
|
||||
|
||||
## Requisito chiave
|
||||
|
||||
Claude Code deve essere lanciato con working directory = la directory in cui risiede l'eseguibile, non la current directory del chiamante. In C# usare `AppContext.BaseDirectory` (non `Environment.CurrentDirectory`) come `ProcessStartInfo.WorkingDirectory`.
|
||||
|
||||
Nota: con single-file publish `Assembly.Location` è vuoto, quindi `AppContext.BaseDirectory` è la scelta corretta.
|
||||
|
||||
## Comandi (da .NET SDK, una volta creato il progetto)
|
||||
|
||||
- Build: `dotnet build`
|
||||
- Run: `dotnet run`
|
||||
- Build: `dotnet build` (LlmRun.csproj, net10.0-windows, WinForms)
|
||||
- GUI: `dotnet run`; CLI: `dotnet run -- "prompt"`
|
||||
- Publish single-file: `dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true`
|
||||
- Test: non esistono ancora.
|
||||
|
||||
Aggiornare questa sezione quando esisteranno il `.csproj` e i test.
|
||||
## Architettura
|
||||
|
||||
## Ambiente
|
||||
Tutto in [Program.cs](Program.cs): top-level statements per la CLI e la classe `Gui` (Form) per la GUI. Entrambe costruiscono un `ProcessStartInfo` per `claude -p <prompt>` con `ArgumentList` (niente quoting manuale). Nessuna dipendenza esterna.
|
||||
|
||||
Windows. `claude` è installato come script npm (`claude.cmd`): con `Process.Start` e `UseShellExecute=false` può servire lanciare via `cmd.exe /c claude ...` oppure risolvere il percorso completo, altrimenti il processo non viene trovato.
|
||||
- CLI: stdout/stderr ereditati (streaming), exit code propagato, 127 se `claude` non è nel PATH.
|
||||
- GUI: output rediretto e mostrato riga per riga.
|
||||
|
||||
## Vincoli non ovvi
|
||||
|
||||
- La working directory di `claude` deve essere la cartella dell'exe: usare `AppContext.BaseDirectory`, mai `Environment.CurrentDirectory` né `Assembly.Location` (vuoto in single-file).
|
||||
- Lo stdin di `claude` va redirezionato e chiuso subito, altrimenti aspetta 3s.
|
||||
- `claude` è un .exe nativo (`~/.local/bin`), avviabile direttamente. Se fosse `claude.cmd` serve `cmd.exe /c`.
|
||||
- La logica di avvio processo è duplicata tra CLI e GUI (volutamente, per restare in un file solo): se cambia, modificarla in entrambi i punti.
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new Gui());
|
||||
return 0;
|
||||
}
|
||||
|
||||
var psi = NewClaude(string.Join(' ', args));
|
||||
try
|
||||
{
|
||||
using var p = Process.Start(psi)!; // stdout/stderr ereditati: streaming in tempo reale
|
||||
p.StandardInput.Close();
|
||||
p.WaitForExit();
|
||||
return p.ExitCode;
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception)
|
||||
{
|
||||
Console.Error.WriteLine("'claude' non trovato nel PATH.");
|
||||
return 127;
|
||||
}
|
||||
|
||||
static ProcessStartInfo NewClaude(string prompt)
|
||||
{
|
||||
var psi = new ProcessStartInfo("claude")
|
||||
{
|
||||
WorkingDirectory = AppContext.BaseDirectory,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true, // evita l'attesa di stdin di claude
|
||||
};
|
||||
psi.ArgumentList.Add("-p");
|
||||
psi.ArgumentList.Add(prompt);
|
||||
return psi;
|
||||
}
|
||||
|
||||
class Gui : Form
|
||||
{
|
||||
readonly TextBox prompt = new() { Dock = DockStyle.Top, Multiline = true, Height = 80, AcceptsReturn = true };
|
||||
readonly Button send = new() { Dock = DockStyle.Top, Text = "Invia a Claude", Height = 32 };
|
||||
readonly TextBox output = new() { Dock = DockStyle.Fill, Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical };
|
||||
|
||||
public Gui()
|
||||
{
|
||||
Text = "LlmRun";
|
||||
Width = 700; Height = 500;
|
||||
Controls.Add(output); Controls.Add(send); Controls.Add(prompt); // Fill per primo: l'ordine inverso dei Dock
|
||||
send.Click += async (_, _) => await Run();
|
||||
}
|
||||
|
||||
async Task Run()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prompt.Text)) return;
|
||||
send.Enabled = false;
|
||||
output.Clear();
|
||||
try
|
||||
{
|
||||
var psi = Program_NewClaude(prompt.Text);
|
||||
using var p = Process.Start(psi)!;
|
||||
p.StandardInput.Close();
|
||||
void Append(string? l) { if (l != null) BeginInvoke(() => output.AppendText(l + Environment.NewLine)); }
|
||||
p.OutputDataReceived += (_, e) => Append(e.Data);
|
||||
p.ErrorDataReceived += (_, e) => Append(e.Data);
|
||||
p.BeginOutputReadLine(); p.BeginErrorReadLine();
|
||||
await p.WaitForExitAsync();
|
||||
output.AppendText($"[exit {p.ExitCode}]");
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception) { output.Text = "'claude' non trovato nel PATH."; }
|
||||
finally { send.Enabled = true; }
|
||||
}
|
||||
|
||||
static ProcessStartInfo Program_NewClaude(string prompt)
|
||||
{
|
||||
var psi = new ProcessStartInfo("claude")
|
||||
{
|
||||
WorkingDirectory = AppContext.BaseDirectory,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
psi.ArgumentList.Add("-p");
|
||||
psi.ArgumentList.Add(prompt);
|
||||
return psi;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# LlmRun
|
||||
|
||||
Eseguibile C# (.NET 10, Windows) che lancia Claude Code (`claude -p`) nella cartella in cui risiede l'eseguibile.
|
||||
|
||||
## Requisiti
|
||||
- .NET SDK 10
|
||||
- `claude` (Claude Code) nel PATH
|
||||
|
||||
## Uso
|
||||
|
||||
GUI (nessun argomento): si apre una finestra con campo prompt, pulsante e output.
|
||||
```
|
||||
dotnet run
|
||||
```
|
||||
|
||||
CLI (con argomenti): il prompt viene stampato su terminale, l'exit code è quello di `claude`.
|
||||
```
|
||||
dotnet run -- "spiega questo progetto"
|
||||
```
|
||||
|
||||
Eseguibile già compilato:
|
||||
```
|
||||
dotnet build
|
||||
bin\Debug\net10.0-windows\LlmRun.exe
|
||||
```
|
||||
|
||||
## Publish single-file
|
||||
```
|
||||
dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true
|
||||
```
|
||||
|
||||
Documentazione: [docs/MVP.md](docs/MVP.md), [docs/ROADMAP.md](docs/ROADMAP.md).
|
||||
+5
-2
@@ -16,14 +16,17 @@ Tutti gli argomenti sono uniti in un unico prompt.
|
||||
4. Esce con lo stesso exit code di `claude`.
|
||||
5. Se `claude` non è trovato: messaggio chiaro ed exit code 127.
|
||||
|
||||
## GUI
|
||||
Senza argomenti si apre una finestra WinForms (prompt, pulsante, output) che usa lo stesso lancio di `claude -p`. Con argomenti resta la CLI.
|
||||
|
||||
## Scelte tecniche
|
||||
- .NET 8+, un solo `Program.cs`, nessuna dipendenza esterna.
|
||||
- `ProcessStartInfo.ArgumentList` per passare il prompt (niente problemi di quoting).
|
||||
- Windows: `claude` è `claude.cmd`, quindi si avvia `cmd.exe /c claude ...`.
|
||||
- `claude` è un .exe nativo (`~/.local/bin`): si avvia direttamente, senza `cmd /c`. Lo stdin è chiuso subito per evitare l'attesa di 3s di claude.
|
||||
- Working dir: `AppContext.BaseDirectory`, non la cwd del chiamante.
|
||||
|
||||
## Fuori scope (MVP)
|
||||
Modalità interattiva, sessioni/`--resume`, config file, logging su file, test automatici, supporto Linux/macOS.
|
||||
Modalità interattiva (chat), sessioni/`--resume`, config file, logging su file, test automatici, supporto Linux/macOS.
|
||||
|
||||
## Criteri di accettazione
|
||||
- [ ] Eseguito da un'altra cartella, `claude` parte nella cartella dell'exe (verificabile chiedendo "pwd").
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ Dettagli dell'MVP in [MVP.md](MVP.md).
|
||||
- [ ] Chiudere le domande aperte in MVP.md
|
||||
|
||||
## Fase 1 — MVP
|
||||
- [ ] `dotnet new console`, `Program.cs` unico
|
||||
- [ ] Lancio di `claude -p` nella cartella dell'exe, streaming, exit code
|
||||
- [x] `dotnet new console`, `Program.cs` unico
|
||||
- [x] Lancio di `claude -p` nella cartella dell'exe, streaming, exit code
|
||||
- [ ] Publish single-file win-x64 e verifica dei criteri di accettazione
|
||||
|
||||
## Fase 2 — Robustezza
|
||||
|
||||
Reference in new issue
Block a user