@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.
- package/CHANGELOG.md +35 -0
- package/README.md +4 -0
- package/cli.js +34 -4
- package/contenuto/lib/mcp-locale/ambiente/.python-version +1 -0
- package/contenuto/lib/mcp-locale/ambiente/pyproject.toml +23 -0
- package/contenuto/lib/mcp-locale/ambiente/uv.lock +1103 -0
- package/contenuto/lib/mcp-locale/cartella/CLAUDE.md +52 -0
- package/contenuto/lib/mcp-locale/cartella/LEGGIMI.md +163 -0
- package/contenuto/lib/mcp-locale/cartella/imposta-password.ps1 +40 -0
- package/contenuto/lib/mcp-locale/cartella/installa.ps1 +286 -0
- package/contenuto/lib/mcp-locale/costruisci.py +495 -0
- package/contenuto/lib/mcp-locale/prova.py +93 -0
- package/contenuto/lib/mcp-locale/server/modello_locale.py +167 -0
- package/contenuto/lib/mcp-locale/server/noeva_mcp.py +203 -0
- package/contenuto/lib/modello/consegna.py +134 -0
- package/contenuto/lib/modello/pubblica.py +131 -0
- package/contenuto/lib/snapshot/ascolta.py +213 -0
- package/contenuto/lib/snapshot/impacchetta.py +184 -0
- package/contenuto/lib/snapshot/noeva_snapshot.py +107 -0
- package/contenuto/lib/snapshot/pubblica.py +35 -15
- package/contenuto/lib/snapshot/worker/installa.ps1 +340 -0
- package/core.js +18 -2
- package/modello/repository/github/workflows/ci.yml +53 -3
- package/modello/repository/github/workflows/rilascio-windows.yml +207 -0
- package/modello/repository/gitignore +2 -1
- package/package.json +2 -1
- package/pubblica.js +205 -0
|
@@ -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
|