@noeva/analytics-core 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ # NOEVA Analytics · {{cliente}}
2
+
3
+ Questa cartella serve a interrogare i dati di {{cliente}} con Claude. Chi la usa sono gli
4
+ analisti di {{cliente}}: rispondi in italiano, con numeri verificabili, senza gergo tecnico se
5
+ non lo chiedono.
6
+
7
+ ## Da dove vengono i dati
8
+
9
+ Il server MCP **{{server}}** legge in sola lettura il database di produzione: {{cosa}}.
10
+ È il modello semantico NOEVA: tabelle con nomi leggibili (`fatture_vendita`, `versamenti`,
11
+ `giacenze`, …) e le regole di business già scritte dentro o accanto.
12
+
13
+ Server SQL {{host}}, raggiungibile solo dalla rete di {{cliente}} (o in VPN).
14
+
15
+ ## Come si risponde a una domanda sui dati
16
+
17
+ 1. A inizio sessione chiama `get_instructions`: sono le regole di business (quali documenti
18
+ sono fatturato, quale data usare, i segni degli importi, le regole P1-P14 della produzione
19
+ e M1-M10 del magazzino). Valgono più di quello che sembra ovvio dai nomi delle colonne.
20
+ 2. `list_models` e `describe_model` per le tabelle e le colonne; `recall_queries` per le
21
+ domande simili già risolte.
22
+ 3. Scrivi la SQL sul modello, validala con `dry_run`, eseguila con `run_sql`.
23
+ 4. Aggrega nel database (SUM, COUNT, GROUP BY) invece di scaricare righe, e metti sempre
24
+ un `limit`: è il database di produzione, in orario di lavoro lo usa anche l'ERP.
25
+ 5. Nella risposta di' periodo, filtri e regole applicate. Se l'analista lo chiede, mostra
26
+ la query.
27
+
28
+ ## Cosa non fare
29
+
30
+ - Non inventare né stimare cifre. Se una query fallisce o un dato manca, dillo.
31
+ - Non collegarti al database per altre strade (sqlcmd, script, ODBC): solo {{server}}.
32
+ - Non scrivere mai la password in chat, in un file o in un comando. Sta nella variabile
33
+ d'ambiente `{{password_env}}` dell'utente Windows e la legge solo il server.
34
+ - Non modificare la cartella `noeva/`: è il motore (eseguibile, modello, regole). Le regole
35
+ si leggono in `noeva/modello/knowledge/rules/`, si cambiano solo con un aggiornamento
36
+ di NOEVA.
37
+
38
+ ## Se {{server}} non risponde
39
+
40
+ Se `/mcp` mostra {{server}} come *failed*, o gli strumenti non ci sono, esegui in PowerShell
41
+ dalla cartella:
42
+
43
+ ```powershell
44
+ {{exe_ps}} --verifica
45
+ ```
46
+
47
+ Dice cosa manca: la password (`imposta-password.ps1`), il driver "ODBC Driver 18 for SQL
48
+ Server", la rete verso {{host}}. Spiegalo all'analista con le istruzioni di `LEGGIMI.md`.
49
+ Dopo aver impostato la password bisogna chiudere e riaprire Claude Code.
50
+
51
+ Ripiego offline (snapshot delle dashboard): {{server_snapshot}}. È spento; si accende da
52
+ `/mcp` solo se l'analista lo chiede, e le risposte devono dire che il dato è dello snapshot.
@@ -0,0 +1,163 @@
1
+ # NOEVA Analytics · {{cliente}} — installazione su Windows 11
2
+
3
+ Questa cartella permette di fare domande sui dati di {{cliente}} a Claude, con Claude Code
4
+ (dal terminale o dalla scheda **Code** di Claude Desktop). Claude legge il database di
5
+ produzione in sola lettura attraverso **{{server}}**, un programma nella cartella `noeva\`
6
+ che Claude Code avvia da solo quando apri la cartella: non c'è niente da tenere acceso, e
7
+ dopo un riavvio del PC funziona come prima.
8
+
9
+ Versione {{versione}}.
10
+
11
+ ## Il modo più semplice: installa.ps1
12
+
13
+ Estrai lo zip dove vuoi (Desktop o Download, non in `C:\NOEVA`), apri PowerShell in quella
14
+ cartella e lancia:
15
+
16
+ ```powershell
17
+ powershell -ExecutionPolicy Bypass -File .\installa.ps1
18
+ ```
19
+
20
+ Fa tutto lui, passo per passo, e chiede solo quello che serve: controlla la rete, Claude
21
+ Code e il driver, copia la cartella in `C:\NOEVA\{{nome}}` (se c'è già la mette da parte e
22
+ riporta i tuoi file), chiede la password se manca, verifica i numeri dal vivo e alla fine
23
+ scrive l'esito in `esito-installa-<data>.txt`. Dopo un riavvio, per controllare senza
24
+ cambiare niente: `.\installa.ps1 -SoloControllo`.
25
+
26
+ I passi qui sotto sono gli stessi, a mano.
27
+
28
+ ## Cosa serve
29
+
30
+ - Windows 10 (1809 o successivo) o Windows 11, 64 bit.
31
+ - Un account Claude Pro, Max, Team o Enterprise. Il piano gratuito non include Claude Code.
32
+ - Il PC nella rete di {{cliente}} (o in VPN): deve raggiungere il server SQL {{host}}
33
+ sulla porta 1433.
34
+ - La password dell'utente `{{utente}}`, che vi diamo a parte, mai insieme alla cartella.
35
+
36
+ ## 0. La rete
37
+
38
+ Prima di installare, controlla che il PC raggiunga il database:
39
+
40
+ ```powershell
41
+ Test-NetConnection {{host}} -Port 1433
42
+ ```
43
+
44
+ Deve rispondere `TcpTestSucceeded : True`. Se no, è un problema di rete da risolvere con
45
+ l'IT: il resto non funzionerebbe.
46
+
47
+ ## 1. Claude Code
48
+
49
+ Apri **PowerShell** (non serve come amministratore) e lancia:
50
+
51
+ ```powershell
52
+ irm https://claude.ai/install.ps1 | iex
53
+ ```
54
+
55
+ Poi apri un **nuovo** PowerShell e controlla:
56
+
57
+ ```powershell
58
+ claude --version
59
+ ```
60
+
61
+ Se l'installer avvisa che `%USERPROFILE%\.local\bin` non è nel PATH e `claude` non
62
+ esiste, Claude Code è installato ma manca il PATH (non serve winget). Aggiungilo così, poi
63
+ apri un PowerShell **nuovo**:
64
+
65
+ ```powershell
66
+ [Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path','User') + ";$env:USERPROFILE\.local\bin", 'User')
67
+ ```
68
+
69
+ La prima volta che lanci `claude` chiede di accedere con l'account Claude: fallo, poi esci
70
+ con `/exit`.
71
+
72
+ Git for Windows non è necessario: senza, Claude Code usa PowerShell.
73
+
74
+ Se preferite Claude Desktop, la scheda **Code** usa la stessa cartella e la stessa
75
+ configurazione: va bene uno dei due o entrambi.
76
+
77
+ ## 2. Il driver per SQL Server
78
+
79
+ Serve **Microsoft ODBC Driver 18 for SQL Server** (x64). Scaricalo dalla pagina Microsoft
80
+ "Download ODBC Driver for SQL Server" (https://aka.ms/downloadmsodbcsql) e installalo: questo
81
+ passaggio chiede i diritti di amministratore, una volta per PC.
82
+
83
+ Per controllare, in PowerShell:
84
+
85
+ ```powershell
86
+ Get-OdbcDriver -Name "ODBC Driver 18 for SQL Server" -Platform 64-bit
87
+ ```
88
+
89
+ ## 3. La cartella
90
+
91
+ Estrai lo zip in una cartella **locale**, per esempio `C:\NOEVA\{{nome}}`. Evita Desktop e
92
+ Documenti se sono sincronizzati con OneDrive.
93
+
94
+ Windows segna i file arrivati da internet; per evitare l'avviso di SmartScreen sul
95
+ programma, una volta, dalla cartella:
96
+
97
+ ```powershell
98
+ cd C:\NOEVA\{{nome}}
99
+ Get-ChildItem -Recurse | Unblock-File
100
+ ```
101
+
102
+ ## 4. La password
103
+
104
+ Dalla cartella:
105
+
106
+ ```powershell
107
+ powershell -ExecutionPolicy Bypass -File .\imposta-password.ps1
108
+ ```
109
+
110
+ Chiede la password di `{{utente}}` (non si vede mentre scrivi), la salva nella variabile
111
+ d'ambiente `{{password_env}}` del tuo utente Windows e prova subito la connessione. Deve
112
+ finire con **tutto a posto**.
113
+
114
+ Poi **chiudi questa finestra di PowerShell e aprine una nuova**: la finestra in cui hai
115
+ lanciato lo script non vede la password, e lì `--verifica` direbbe *manca la password*.
116
+
117
+ Perché una variabile d'ambiente e non un file nella cartella: la cartella si copia, si
118
+ zippa, si manda per email e a volte finisce su OneDrive; la variabile resta sul PC, legata
119
+ al tuo utente, e sopravvive agli aggiornamenti della cartella. Ogni utente Windows che usa
120
+ la cartella imposta la sua.
121
+
122
+ ## 5. Il primo avvio
123
+
124
+ Chiudi e riapri PowerShell (la variabile si legge all'avvio), poi:
125
+
126
+ ```powershell
127
+ cd C:\NOEVA\{{nome}}
128
+ claude
129
+ ```
130
+
131
+ La prima volta Claude Code chiede se ti fidi della cartella: rispondi di sì. Poi scrivi:
132
+
133
+ ```
134
+ /mcp
135
+ ```
136
+
137
+ Devi vedere **{{server}}** come connesso. Prova con una domanda, per esempio: *«Qual è il
138
+ fatturato 2025 per famiglia di prodotto?»*
139
+
140
+ Con Claude Desktop: chiudilo del tutto e riaprilo dopo il passo 4, scheda **Code**, scegli
141
+ la cartella `C:\NOEVA\{{nome}}`.
142
+
143
+ ## Problemi
144
+
145
+ Il controllo che dice cosa manca, da PowerShell nella cartella:
146
+
147
+ ```powershell
148
+ {{exe_ps}} --verifica
149
+ ```
150
+
151
+ | Cosa vedi | Cosa fare |
152
+ |---|---|
153
+ | `manca la password di {{utente}}` | passo 4, poi chiudi e riapri Claude Code |
154
+ | `il driver 'ODBC Driver 18 for SQL Server' non e' installato` | passo 2 |
155
+ | `Login timeout expired` o `TCP Provider` | il PC non raggiunge {{host}}: rete di {{cliente}} o VPN |
156
+ | `Login failed for user '{{utente}}'` | password sbagliata: rifai il passo 4 |
157
+ | in `/mcp` {{server}} è *failed* ma `--verifica` è a posto | chiudi e riapri Claude Code dalla cartella giusta (`cd` prima di `claude`) |
158
+ | Windows blocca `{{nome}}.exe` | `Unblock-File` del passo 3; se c'è un antivirus aziendale, va messa in eccezione la cartella `noeva\` |
159
+
160
+ ## Aggiornamenti
161
+
162
+ Vi mandiamo una nuova cartella `noeva\`: si sostituisce quella vecchia. La password resta
163
+ nella variabile d'ambiente, non va reimpostata.
@@ -0,0 +1,40 @@
1
+ # Imposta la password di {{utente}} per NOEVA Analytics ({{cliente}}) e controlla la connessione.
2
+ #
3
+ # powershell -ExecutionPolicy Bypass -File .\imposta-password.ps1
4
+ #
5
+ # La password va nella variabile d'ambiente {{password_env}} del tuo utente Windows: non
6
+ # nella cartella, che si copia, si zippa e a volte finisce su OneDrive. La legge solo
7
+ # noeva\{{nome}}.exe, che Claude Code avvia. Dopo, chiudi e riapri Claude Code.
8
+
9
+ $ErrorActionPreference = 'Stop'
10
+ $qui = Split-Path -Parent $MyInvocation.MyCommand.Path
11
+
12
+ $sicura = Read-Host "Password di {{utente}}" -AsSecureString
13
+ $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sicura)
14
+ try {
15
+ $chiaro = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
16
+ } finally {
17
+ [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
18
+ }
19
+ if (-not $chiaro) {
20
+ Write-Host "Nessuna password inserita: non cambio niente."
21
+ exit 1
22
+ }
23
+
24
+ [Environment]::SetEnvironmentVariable('{{password_env}}', $chiaro, 'User')
25
+ $env:{{password_env}} = $chiaro
26
+ Write-Host "Password salvata nella variabile {{password_env}} del tuo utente Windows."
27
+ Write-Host ""
28
+
29
+ & (Join-Path $qui 'noeva\{{nome}}.exe') --verifica
30
+ $esito = $LASTEXITCODE
31
+
32
+ Write-Host ""
33
+ if ($esito -eq 0) {
34
+ Write-Host "Ora chiudi QUESTA finestra di PowerShell e aprine una nuova: qui la password non si vede,"
35
+ Write-Host "e un --verifica lanciato da qui direbbe 'manca la password'. Chiudi e riapri anche"
36
+ Write-Host "Claude Code (o Claude Desktop): leggono la variabile solo all'avvio."
37
+ } else {
38
+ Write-Host "Qualcosa non va: leggi le righe NO qui sopra e la sezione Problemi di LEGGIMI.md."
39
+ }
40
+ exit $esito
@@ -0,0 +1,286 @@
1
+ # Installa o aggiorna {{nome}} su questo PC, passo per passo, e alla fine dice com'e' andata.
2
+ #
3
+ # powershell -ExecutionPolicy Bypass -File .\installa.ps1 installa o aggiorna
4
+ # powershell -ExecutionPolicy Bypass -File .\installa.ps1 -SoloControllo controlla e basta (dopo un riavvio)
5
+ #
6
+ # Si lancia dalla cartella estratta dallo zip, dove si vuole (Desktop, Download): la copia
7
+ # in C:\NOEVA\{{nome}} la fa lo script. Se C:\NOEVA\{{nome}} c'e' gia' e' un aggiornamento:
8
+ # la cartella vecchia si mette da parte (non si cancella), i file dell'utente si riportano,
9
+ # la password resta. Nessun segreto finisce a schermo o nel file dell'esito.
10
+
11
+ param([switch]$SoloControllo)
12
+
13
+ $ErrorActionPreference = 'Stop'
14
+ try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch { }
15
+
16
+ $qui = Split-Path -Parent $MyInvocation.MyCommand.Path
17
+ $nome = '{{nome}}'
18
+ $cliente = '{{cliente}}'
19
+ $versione = '{{versione}}'
20
+ $dbHost = '{{host}}'
21
+ $dbPorta = 1433
22
+ $utenteDb = '{{utente}}'
23
+ $varPassword = '{{password_env}}'
24
+ # La variabile della chiave API NOEVA con cui il servizio scarica il modello aggiornato
25
+ # (ADR 0005, punto 6). Vuota: il modello e' solo quello della cartella.
26
+ $varChiave = '{{chiave_env}}'
27
+ $domanda = '{{domanda}}'
28
+ $destinazione = "C:\NOEVA\$nome"
29
+ $exe = Join-Path $destinazione "noeva\$nome.exe"
30
+ $modo = 'nuova installazione'
31
+ $esiti = New-Object System.Collections.ArrayList
32
+
33
+ function Passo($titolo) {
34
+ Write-Host ''
35
+ Write-Host "== $titolo" -ForegroundColor Cyan
36
+ }
37
+
38
+ function Esito($passo, $stato, $nota) {
39
+ [void]$esiti.Add([pscustomobject]@{ Passo = $passo; Stato = $stato; Nota = $nota })
40
+ $colore = @{ 'ok' = 'Green'; 'avviso' = 'Yellow'; 'NO' = 'Red'; 'saltato' = 'DarkGray' }[$stato]
41
+ Write-Host (' {0,-7} {1}' -f $stato, $nota) -ForegroundColor $colore
42
+ }
43
+
44
+ function Chiedi($domanda, [bool]$predefinito = $true) {
45
+ $suggerimento = if ($predefinito) { '(S/n)' } else { '(s/N)' }
46
+ $r = Read-Host " $domanda $suggerimento"
47
+ if (-not $r) { return $predefinito }
48
+ return $r.Trim().ToLower().StartsWith('s')
49
+ }
50
+
51
+ function ChiediSegreto($domanda) {
52
+ $sicura = Read-Host " $domanda" -AsSecureString
53
+ $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sicura)
54
+ try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
55
+ finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
56
+ }
57
+
58
+ function PortaAperta($h, $p) {
59
+ $c = New-Object Net.Sockets.TcpClient
60
+ try { return $c.BeginConnect($h, $p, $null, $null).AsyncWaitHandle.WaitOne(5000) -and $c.Connected }
61
+ catch { return $false }
62
+ finally { $c.Close() }
63
+ }
64
+
65
+ # Un eseguibile, con lo stderr insieme allo stdout e senza che PowerShell 5 lo prenda per un errore.
66
+ function Esegui($comando, [string[]]$argomenti) {
67
+ $prima = $ErrorActionPreference
68
+ $ErrorActionPreference = 'Continue'
69
+ try {
70
+ $testo = (& $comando @argomenti 2>&1 | ForEach-Object { "$_" }) -join "`n"
71
+ return [pscustomobject]@{ Codice = $LASTEXITCODE; Testo = $testo }
72
+ } finally { $ErrorActionPreference = $prima }
73
+ }
74
+
75
+ # Come Esegui, ma le righe si vedono mentre arrivano (la verifica e il giro durano minuti).
76
+ function EseguiDalVivo($comando, [string[]]$argomenti) {
77
+ $prima = $ErrorActionPreference
78
+ $ErrorActionPreference = 'Continue'
79
+ try {
80
+ $righe = New-Object System.Collections.ArrayList
81
+ & $comando @argomenti 2>&1 | ForEach-Object { $r = "$_"; [void]$righe.Add($r); Write-Host " $r" }
82
+ return [pscustomobject]@{ Codice = $LASTEXITCODE; Testo = ($righe -join "`n") }
83
+ } finally { $ErrorActionPreference = $prima }
84
+ }
85
+
86
+ function Fine {
87
+ $no = @($esiti | Where-Object { $_.Stato -eq 'NO' }).Count
88
+ $avvisi = @($esiti | Where-Object { $_.Stato -eq 'avviso' }).Count
89
+ $esitoGenerale = if ($no) { "NON COMPLETATA: $no passi da sistemare" } elseif ($avvisi) { "COMPLETATA con $avvisi avvisi" } else { 'COMPLETATA' }
90
+ $quando = Get-Date
91
+ $righe = @(
92
+ "$nome $versione · $modo · $esitoGenerale",
93
+ "PC $env:COMPUTERNAME · utente Windows $env:USERDOMAIN\$env:USERNAME · $($quando.ToString('dd/MM/yyyy HH:mm'))",
94
+ ''
95
+ ) + ($esiti | ForEach-Object { '{0,-7} {1,-22} {2}' -f $_.Stato, $_.Passo, $_.Nota })
96
+ Write-Host ''
97
+ Write-Host '==================== RIEPILOGO ====================' -ForegroundColor Cyan
98
+ $colore = if ($no) { 'Red' } elseif ($avvisi) { 'Yellow' } else { 'Green' }
99
+ Write-Host $righe[0] -ForegroundColor $colore
100
+ $righe | Select-Object -Skip 1 | ForEach-Object { Write-Host $_ }
101
+ $cartellaEsito = if (Test-Path $destinazione) { $destinazione } else { $qui }
102
+ $file = Join-Path $cartellaEsito ("esito-installa-{0}.txt" -f $quando.ToString('yyyyMMdd-HHmm'))
103
+ try {
104
+ $righe | Set-Content -Path $file -Encoding UTF8
105
+ Write-Host ''
106
+ Write-Host "Esito salvato in $file (senza password): da mandare a chi segue l'installazione."
107
+ } catch { Write-Host "Non riesco a salvare l'esito in $file" -ForegroundColor Yellow }
108
+ if ($no) { exit 1 } else { exit 0 }
109
+ }
110
+
111
+ function Ferma($passo, $nota) {
112
+ Esito $passo 'NO' $nota
113
+ Write-Host ' Mi fermo qui: sistemato questo, rilancia lo script.' -ForegroundColor Red
114
+ Fine
115
+ }
116
+
117
+ Write-Host "NOEVA Analytics per $cliente · $nome $versione" -ForegroundColor Cyan
118
+ Write-Host "PC $env:COMPUTERNAME · utente Windows $env:USERDOMAIN\$env:USERNAME"
119
+ if ($SoloControllo) { $modo = 'solo controllo'; Write-Host 'Solo controllo: non cambio niente.' }
120
+
121
+ # 1. La rete --------------------------------------------------------------------
122
+ Passo "1. Il PC vede il database ($dbHost, porta $dbPorta)"
123
+ if (PortaAperta $dbHost $dbPorta) { Esito 'rete' 'ok' "$dbHost`:$dbPorta raggiungibile" }
124
+ else { Ferma 'rete' "$dbHost`:$dbPorta non risponde: e' un problema di rete o di VPN, per l'IT" }
125
+
126
+ # 2. Claude Code ------------------------------------------------------------------
127
+ Passo '2. Claude Code'
128
+ $cartellaClaude = Join-Path $env:USERPROFILE '.local\bin'
129
+ $claude = Get-Command claude -ErrorAction SilentlyContinue
130
+ if (-not $claude -and -not (Test-Path (Join-Path $cartellaClaude 'claude.exe'))) {
131
+ if ($SoloControllo) { Esito 'Claude Code' 'NO' 'non installato' }
132
+ elseif (Chiedi 'Claude Code non e'' installato. Lo installo adesso?') {
133
+ $prima = $ErrorActionPreference
134
+ $ErrorActionPreference = 'Continue'
135
+ try { Invoke-RestMethod https://claude.ai/install.ps1 | Invoke-Expression } catch { Write-Host " $($_.Exception.Message)" }
136
+ $ErrorActionPreference = $prima
137
+ }
138
+ }
139
+ if (Test-Path (Join-Path $cartellaClaude 'claude.exe')) {
140
+ # L'installer non aggiunge la cartella al PATH dell'utente (successo il 20/10 su tutti i PC).
141
+ $pathUtente = [Environment]::GetEnvironmentVariable('Path', 'User')
142
+ if (-not (($pathUtente -split ';') -contains $cartellaClaude)) {
143
+ if ($SoloControllo) { Esito 'PATH' 'avviso' "$cartellaClaude non e' nel PATH dell'utente" }
144
+ else {
145
+ [Environment]::SetEnvironmentVariable('Path', ($pathUtente.TrimEnd(';') + ";$cartellaClaude"), 'User')
146
+ Esito 'PATH' 'ok' "aggiunto $cartellaClaude al PATH dell'utente"
147
+ }
148
+ }
149
+ if (-not (($env:Path -split ';') -contains $cartellaClaude)) { $env:Path += ";$cartellaClaude" }
150
+ }
151
+ $claude = Get-Command claude -ErrorAction SilentlyContinue
152
+ if ($claude) {
153
+ $v = Esegui $claude.Source @('--version')
154
+ Esito 'Claude Code' 'ok' ($v.Testo.Trim() -split "`n")[0]
155
+ } elseif (-not $SoloControllo) {
156
+ Ferma 'Claude Code' 'non installato: https://claude.ai/install.ps1, poi rilancia'
157
+ }
158
+
159
+ # 3. Il driver ODBC -----------------------------------------------------------------
160
+ Passo '3. Il driver ODBC per SQL Server'
161
+ $driver = @(Get-OdbcDriver -Platform 64-bit -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^ODBC Driver (18|17) for SQL Server$' } | ForEach-Object { $_.Name })
162
+ if ($driver -contains 'ODBC Driver 18 for SQL Server') { Esito 'driver' 'ok' 'ODBC Driver 18 for SQL Server' }
163
+ elseif ($driver -contains 'ODBC Driver 17 for SQL Server') {
164
+ if (-not $SoloControllo) { [Environment]::SetEnvironmentVariable('NOEVA_MSSQL_DRIVER', 'ODBC Driver 17 for SQL Server', 'User') }
165
+ $env:NOEVA_MSSQL_DRIVER = 'ODBC Driver 17 for SQL Server'
166
+ Esito 'driver' 'avviso' 'c''e'' il 17, non il 18: va bene, impostato NOEVA_MSSQL_DRIVER'
167
+ } else {
168
+ Ferma 'driver' 'manca: ODBC Driver 18 for SQL Server x64 da https://aka.ms/downloadmsodbcsql (serve un amministratore)'
169
+ }
170
+ $driverUtente = [Environment]::GetEnvironmentVariable('NOEVA_MSSQL_DRIVER', 'User')
171
+ if ($driverUtente) { $env:NOEVA_MSSQL_DRIVER = $driverUtente }
172
+
173
+ # 4. La cartella --------------------------------------------------------------------
174
+ Passo "4. La cartella $destinazione"
175
+ if ($SoloControllo) {
176
+ if (Test-Path $exe) { Esito 'cartella' 'ok' "$destinazione, con noeva\$nome.exe" } else { Ferma 'cartella' "$exe non c'e'" }
177
+ } elseif ($qui.TrimEnd('\') -ieq $destinazione) {
178
+ $modo = 'sul posto'
179
+ Esito 'cartella' 'ok' "lo script gira gia' da $destinazione"
180
+ } else {
181
+ if (Test-Path $destinazione) {
182
+ $modo = 'aggiornamento'
183
+ $aperti = @(Get-Process -Name $nome, 'claude', 'Claude' -ErrorAction SilentlyContinue)
184
+ if ($aperti.Count) {
185
+ Write-Host " Sono aperti: $((($aperti | ForEach-Object { $_.ProcessName }) | Sort-Object -Unique) -join ', '). Tengono bloccata la cartella."
186
+ if (Chiedi 'Li chiudo? (salva prima eventuali conversazioni aperte)') {
187
+ $aperti | Stop-Process -Force -ErrorAction SilentlyContinue
188
+ Start-Sleep -Seconds 2
189
+ } else { Ferma 'cartella' 'chiudi Claude Code e Claude Desktop (anche dall''icona vicino all''orologio), poi rilancia' }
190
+ }
191
+ $vecchia = "$destinazione-prima-$((Get-Date).ToString('yyyyMMdd-HHmm'))"
192
+ try { Rename-Item $destinazione (Split-Path -Leaf $vecchia) }
193
+ catch { Ferma 'cartella' "non riesco a rinominare $destinazione (ancora in uso?): $($_.Exception.Message)" }
194
+ Copy-Item $qui $destinazione -Recurse
195
+ # I file dell'utente (report, appunti) tornano nella cartella nuova; quelli della versione no.
196
+ $dellaVersione = @(Get-ChildItem -Force $qui | ForEach-Object { $_.Name })
197
+ $riportati = @()
198
+ foreach ($f in Get-ChildItem -Force $vecchia) {
199
+ if ($dellaVersione -contains $f.Name -or $f.Name -like 'esito-installa-*') { continue }
200
+ Copy-Item $f.FullName (Join-Path $destinazione $f.Name) -Recurse -Force
201
+ $riportati += $f.Name
202
+ }
203
+ $nota = "vecchia messa da parte in $vecchia"
204
+ if ($riportati.Count) { $nota += "; riportati: $($riportati -join ', ')" }
205
+ Esito 'cartella' 'ok' $nota
206
+ } else {
207
+ New-Item -ItemType Directory -Force (Split-Path $destinazione) | Out-Null
208
+ Copy-Item $qui $destinazione -Recurse
209
+ Esito 'cartella' 'ok' "copiata in $destinazione"
210
+ }
211
+ Get-ChildItem $destinazione -Recurse | Unblock-File
212
+ if (-not (Test-Path $exe)) { Ferma 'cartella' "$exe non c'e' dopo la copia" }
213
+ }
214
+
215
+ # 5. La password ----------------------------------------------------------------------
216
+ Passo "5. La password di $utenteDb"
217
+ $password = [Environment]::GetEnvironmentVariable($varPassword, 'User')
218
+ if ($password -and ($SoloControllo -or (Chiedi "La password e' gia' salvata per $env:USERNAME. La tengo?"))) {
219
+ Esito 'password' 'ok' "gia' salvata in $varPassword"
220
+ } elseif ($SoloControllo) {
221
+ Esito 'password' 'NO' "manca: rilancia senza -SoloControllo"
222
+ } else {
223
+ $password = ''
224
+ for ($i = 0; $i -lt 3 -and -not $password; $i++) { $password = ChiediSegreto "Password di $utenteDb (non si vede mentre scrivi)" }
225
+ if (-not $password) { Ferma 'password' 'nessuna password inserita' }
226
+ [Environment]::SetEnvironmentVariable($varPassword, $password, 'User')
227
+ Esito 'password' 'ok' "salvata in $varPassword dell'utente $env:USERNAME"
228
+ }
229
+ if ($password) { Set-Item "env:$varPassword" $password }
230
+ $password = $null
231
+
232
+ # 6. La chiave API NOEVA -----------------------------------------------------------------
233
+ # Senza chiave il servizio funziona lo stesso, col modello della cartella: chi la
234
+ # salta lo vede nel riepilogo, non si ferma l'installazione.
235
+ if ($varChiave) {
236
+ Passo "6. La chiave API NOEVA di $env:USERNAME (il modello aggiornato)"
237
+ $chiave = [Environment]::GetEnvironmentVariable($varChiave, 'User')
238
+ if ($chiave -and ($SoloControllo -or (Chiedi "La chiave e' gia' salvata per $env:USERNAME. La tengo?"))) {
239
+ Esito 'chiave NOEVA' 'ok' "gia' salvata in $varChiave"
240
+ } elseif ($SoloControllo) {
241
+ Esito 'chiave NOEVA' 'NO' "manca: il modello resta quello della cartella; rilancia senza -SoloControllo"
242
+ } else {
243
+ $chiave = ''
244
+ for ($i = 0; $i -lt 3 -and -not $chiave; $i++) {
245
+ $chiave = ChiediSegreto "La tua chiave API NOEVA, dal pannello NOEVA (noeva_sk_..., non si vede mentre scrivi; vuota per saltare)"
246
+ if (-not $chiave) { break }
247
+ if (-not $chiave.StartsWith('noeva_sk_')) { Write-Host ' Non sembra una chiave NOEVA (comincia con noeva_sk_).' -ForegroundColor Yellow; $chiave = '' }
248
+ }
249
+ if ($chiave) {
250
+ [Environment]::SetEnvironmentVariable($varChiave, $chiave, 'User')
251
+ Esito 'chiave NOEVA' 'ok' "salvata in $varChiave dell'utente $env:USERNAME"
252
+ } else {
253
+ Esito 'chiave NOEVA' 'NO' "saltata: il modello resta quello della cartella, senza aggiornamenti"
254
+ }
255
+ }
256
+ if ($chiave) { Set-Item "env:$varChiave" $chiave }
257
+ $chiave = $null
258
+ }
259
+
260
+ # 7. La verifica -----------------------------------------------------------------------
261
+ Passo '7. La verifica: connessione e numeri noti, dal vivo'
262
+ $v = EseguiDalVivo $exe @('--verifica')
263
+ if ($v.Codice -eq 0) { Esito 'verifica' 'ok' 'tutti i controlli tornano' }
264
+ else {
265
+ $primoNo = ($v.Testo -split "`n" | Where-Object { $_ -match '^\s*NO\s' } | Select-Object -First 1)
266
+ Esito 'verifica' 'NO' ("{0}" -f ($(if ($primoNo) { $primoNo.Trim() } else { "uscita $($v.Codice)" })))
267
+ }
268
+
269
+ # 8. Claude e la domanda di prova --------------------------------------------------------
270
+ if ($SoloControllo) {
271
+ Esito 'Claude' 'saltato' 'la prova in Claude si fa a mano'
272
+ } else {
273
+ Passo '8. Claude e la domanda di prova'
274
+ Write-Host ' Apri una finestra di PowerShell NUOVA (questa non vede le variabili appena salvate) e lancia:'
275
+ Write-Host " cd $destinazione" -ForegroundColor White
276
+ Write-Host ' claude' -ForegroundColor White
277
+ Write-Host ' Al primo avvio la persona fa il login con il suo account. Alla domanda sulla fiducia nella cartella: si''.'
278
+ Write-Host " Poi /mcp: $nome deve essere connesso. Domanda di prova:"
279
+ Write-Host " $domanda" -ForegroundColor White
280
+ if (Chiedi "In /mcp $nome e' connesso?") { Esito 'Claude /mcp' 'ok' "$nome connesso" }
281
+ else { Esito 'Claude /mcp' 'NO' "$nome non connesso: hai fatto cd nella cartella prima di claude?" }
282
+ if (Chiedi 'La domanda di prova ha risposto con numeri sensati?') { Esito 'domanda di prova' 'ok' 'risponde' }
283
+ else { Esito 'domanda di prova' 'NO' 'da guardare con chi segue l''installazione' }
284
+ }
285
+
286
+ Fine