Nuovo ADR-0005: su Windows l'eseguibile è senza console, così il doppio clic mostra solo la GUI, e la CLI riaggancia il terminale padre. L'alternativa a due file resta registrata. MVP, README e CLAUDE.md spiegano la conseguenza sull'exit code (disponibile solo se il chiamante attende il processo, con start /wait o Start-Process -Wait); TESTING.md aggiunge le righe WIN. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
10 KiB
MVP (doc-first)
Obiettivo
Eseguibile C# a riga di comando che inoltra un prompt a Claude Code (claude) lanciandolo nella directory dell'eseguibile, e ne mostra l'output.
Uso
<nome-app> "spiega questo progetto"
Tutti gli argomenti sono uniti in un unico prompt. Con --engine <id> come primo argomento (claude o codex) si sceglie il motore, per esempio <nome-app> --engine codex "spiega questo progetto"; senza, si usa Claude Code. Id sconosciuto o prompt mancante: uso su stderr ed exit code 2.
Comportamento
- Se non ci sono argomenti: stampa l'uso ed esce con codice 2.
- Avvia il motore scelto in modalità non interattiva (
claude -p <prompt>ocodex exec … -- <prompt>) conWorkingDirectory = AppContext.BaseDirectory. - Ritrasmette stdout e stderr del processo figlio in tempo reale.
- Esce con lo stesso exit code del motore. Su Windows l'eseguibile non ha finestra di console (ADR-0005):
cmde PowerShell non aspettano la fine del programma, quindi l'exit code è disponibile solo se il chiamante attende il processo (start /wait,Start-Process -Wait,| Out-Host, script). - Se l'eseguibile del motore non è trovato: messaggio chiaro ed exit code 127.
GUI (chat)
Senza argomenti si apre una finestra Avalonia (C# + XAML, nessun HTML) in stile claude.ai/ChatGPT, identica su Windows e Linux. Con argomenti resta la CLI. Decisione e motivazioni: ADR-0001.
Struttura della finestra
- Barra laterale (sinistra, comprimibile con il pulsante a tre linee): logo e nome dell'app, pulsante Nuova chat, elenco delle chat salvate (titolo = inizio del primo messaggio, data/ora dell'ultimo scambio; la chat aperta è evidenziata). Un clic su una chat la riapre; la ✕ (con conferma) la elimina. Elenco vuoto: "Nessuna chat salvata".
- Conversazione: messaggi alternati utente/Claude. Le risposte sono renderizzate in markdown: titoli, liste, tabelle, citazioni, blocchi di codice, grassetto/corsivo/barrato, codice inline, link. Il rendering è un renderer proprio (MarkdownView.cs) che trasforma l'albero di Markdig in controlli Avalonia; l'HTML grezzo è disabilitato. Il testo è selezionabile. Sulle risposte il pulsante Copia copia il markdown; sui blocchi di codice copia il codice.
- Schermata iniziale (chat vuota): solo logo, titolo e sottotitolo (nessun suggerimento precompilato).
- Composer (in basso, scheda arrotondata): campo di testo che cresce fino a ~8 righe e pulsante Invia circolare con icona.
Invioinvia,Maiusc+Inviova a capo. Hint sulla tastiera sotto la scheda. Non contiene più i selettori di modello ed effort. - Impostazioni (in alto a destra, pulsante con icona a cursori, simmetrico a quello della barra laterale): aprono un riquadro a comparsa con l'interruttore CLI (Claude Code, Codex; un motore non installato è mostrato "non installato" e non è selezionabile; disabilitato durante l'attesa), poi Modello (Predefinito, Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5, "Altro ID…" con campo libero) ed Effort (Predefinito, low, medium, high, xhigh, max). Valori iniziali: il modello più piccolo del motore (Claude Code: Haiku 5.5; Codex: GPT-5.6 Luna) ed effort
low, per risposte rapide e poco costose; sono quelli che si ritrovano all'avvio e a ogni cambio di CLI. Si chiude conEsco con un clic fuori; le scelte restano valide per i messaggi successivi (non sono ancora salvate su disco). Passati come--model/--effort; con "Predefinito" il flag viene omesso. Solo GUI. - Aggiornamenti (nello stesso riquadro, sotto le impostazioni): una riga per ogni CLI installato con nome, versione e pulsante Aggiorna. L'app controlla prima se esiste una versione più recente (all'avvio, all'apertura del riquadro se l'ultimo controllo ha più di 5 minuti, e dopo ogni aggiornamento): se c'è, la riga dice "Aggiornamento disponibile: X", il pulsante è evidenziato e un puntino compare sul pulsante Impostazioni; se no, la riga dice "Aggiornato" e nulla è evidenziato; senza rete "Controllo non riuscito". Il pulsante esegue il comando ufficiale
<cli> updatee mostra l'esito sulla riga: "Aggiornato da X a Y", "Già aggiornato (X)" o l'errore con il comando manuale adatto al metodo di installazione (npm, Homebrew, WinGet, nativo). Un solo aggiornamento alla volta; i pulsanti sono disabilitati durante una risposta. Nessun aggiornamento parte da solo. Vedi ADR-0004. - Motori (ADR-0002): una chat usa un solo motore. Cambiare CLI apre una nuova chat con quel motore; modello ed effort tornano ai valori iniziali del nuovo motore (il modello più piccolo ed effort
low) e le liste dipendono dal motore (Codex: modelli GPT-5.6 Sol/Terra/Luna, stessi livelli di effort). Aprendo una chat salvata il selettore passa al motore di quella chat. L'etichetta sulle risposte, il segnaposto del composer e il messaggio d'attesa usano il nome del motore, e l'elenco chat lo indica sotto il titolo. Con Codex la sessione è letta dall'output (thread_id) e usata per riprendere la chat; i permessi sono di sola lettura. - Contesto:
claude -p --session-id <guid>al primo messaggio,--resume <guid>ai successivi. "Nuova chat" svuota la vista e genera un nuovo guid. - Cronologia: dopo ogni scambio la chat è salvata in
<cartella dati>/<nome-app>/chats/<guid>.json, dove la cartella dati èEnvironment.SpecialFolder.LocalApplicationData(%LOCALAPPDATA%su Windows,~/.local/sharesu Linux; vedi Nome del progetto). Il file contiene guid (= id di sessione diclaude), titolo, data, flagstarted, messaggi con ruolo e testo, motore (Engine) e id di sessione del motore (SessionId); i file senza questi campi sono chat di Claude Code. Riaprendo una chat si rimostra la conversazione e si prosegue con--resume <guid>, quindiclaudericorda il contesto. Il messaggio dell'utente viene salvato subito, prima della risposta (così non si perde se l'app si chiude mentre Claude risponde); la risposta si aggiunge appena arriva. Una chat nuova e vuota non viene salvata. All'avvio l'app riapre automaticamente l'ultima conversazione (la più recente); se non ce ne sono parte con la schermata iniziale. "Nuova chat" resta sempre disponibile. Eliminare una chat cancella solo il file JSON, non la sessione diclaude. Non si può aprire/eliminare una chat mentre Claude sta rispondendo. - Limite noto: la sessione di
claudeè legata alla cartella in cui gira (quella dell'exe): se si sposta l'exe, le vecchie chat si leggono ancora ma--resumenon ritrova il contesto. - La risposta compare a fine generazione (nessuno streaming); durante l'attesa un indicatore di avanzamento e Invia disabilitato. Se
claudeesce con errore, lo stderr appare come messaggio di errore. - I link
http(s)si aprono nel browser di sistema. - Finestra orizzontale con proporzioni 16:10: larga il 75% dell'area utile dello schermo (max 1440 px logici), altezza di conseguenza (max 90% dell'area utile), centrata; minimo 900x600. Su un monitor Full HD 1920x1080 risulta circa 1440x900.
Design (UI/UX)
Sorgente di verità per l'aspetto, derivato dalla skill ui-ux-pro-max (stile "strumento per sviluppatori": slate + accento verde). I token vivono come risorse di tema in Gui/App.axaml.
- Tema: segue il sistema (chiaro/scuro) tramite
RequestedThemeVariant="Default". - Token (chiaro / scuro): sfondo
#FFFFFF/#0F172A, superficie composer#FFFFFF/#1E293B, testo#0F172A/#F8FAFC, testo secondario#475569/#94A3B8, bordi#CBD5E1/#334155, accento#15803D/#22C55E, errore#B91C1C/#F87171. Contrasto testo ≥ 4.5:1. - Tipografia: font di sistema (Segoe UI Variable/Segoe UI su Windows; Inter, Ubuntu, Cantarell, Noto Sans, DejaVu Sans su Linux; corpo 15.5, titoli fino a 24), Cascadia Code/Consolas/DejaVu Sans Mono per il codice. Nessun font incorporato (funziona offline).
- Forma: raggi 10–20, bordi sottili, pulsante d'invio circolare.
- Barra laterale: sfondo
Muted, 272 px; voce attiva con barra d'accento a sinistra; ✕ per eliminare. - Accessibilità: focus visibile (il composer prende il bordo d'accento),
AutomationProperties.Namesui pulsanti a sola icona e sul campo messaggio, target ≥ 30 px, icone vettoriali (nessuna emoji). - Non incluso: evidenziazione sintattica del codice.
Scelte tecniche
Le scelte architetturali sono negli ADR. Sintesi:
- .NET 10, un solo progetto
net10.0per Windows e Linux. Avalonia (GUI), Markdig (parsing markdown). Il codice è diviso inCore/(avvio diclaude, cronologia, nome app) eGui/(finestra, ViewModel, renderer markdown);Program.cssceglie CLI o GUI. ProcessStartInfo.ArgumentListper passare il prompt (niente problemi di quoting).claudeè un eseguibile nativo (~/.local/bin): si avvia direttamente, senzacmd /c. Lo stdin è chiuso subito per evitare l'attesa di 3s di claude.- Working dir:
AppContext.BaseDirectory, non la cwd del chiamante. - L'avvio dei motori è in un solo punto (Core/EngineRunner.cs), usato da CLI e GUI; ogni motore descrive argomenti e parser in Core/Engine.cs.
Fuori scope (MVP)
Streaming token-per-token nella GUI, annullamento di una risposta in corso, rinomina/ricerca delle chat, config file, altri motori oltre a Claude Code (Codex, Antigravity: vedi ADR-0002), logging su file, supporto macOS. I test automatici sono pianificati: TESTING.md.
Criteri di accettazione
Ogni criterio ha test automatici nella matrice di TESTING.md (CL-04, CL-08, CL-06, CL-01).
- Eseguito da un'altra cartella,
claudeparte nella cartella dell'exe (verificabile chiedendo "pwd"). - L'output appare in streaming, non solo alla fine.
- L'exit code è propagato (su Windows, quando il chiamante attende il processo: vedi sopra).
- Un prompt con virgolette e spazi arriva intatto.
Domande aperte
- Permessi: serve
--allowedToolso--permission-modeper l'uso non interattivo? - Formato output: testo semplice o
--output-format stream-json?