opencode-wyvern 0.1.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/LICENSE +21 -0
- package/README.md +84 -0
- package/bin/oc-setup.js +87 -0
- package/package.json +41 -0
- package/src/client.js +109 -0
- package/src/config.js +64 -0
- package/src/prompts.js +164 -0
- package/src/sections.js +160 -0
- package/src/server.js +343 -0
- package/src/setup.js +271 -0
- package/src/shell.js +102 -0
- package/src/uninstall.js +109 -0
- package/templates/AGENTS.md +17 -0
- package/templates/claude-mem-plugin.js +6 -0
- package/templates/client-bash.sh +418 -0
- package/templates/client-pwsh.ps1 +527 -0
- package/templates/command-baseline-ui.md +23 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
|
|
2
|
+
# ---- OpenCode Wyvern (auto-generato) ----
|
|
3
|
+
# Personalizza i valori qui sotto e rilancia: . $PROFILE
|
|
4
|
+
|
|
5
|
+
$env:OC_SERVER = '__OC_SERVER__' # alias SSH (vedi ~/.ssh/config)
|
|
6
|
+
$env:OC_HOST = '__OC_HOST__' # IP o hostname del server
|
|
7
|
+
$env:OC_USER = '__OC_USER__' # utente SSH
|
|
8
|
+
$env:OC_DIR = '__OC_DIR__' # cartella remota di default (es. ~/Server)
|
|
9
|
+
|
|
10
|
+
$global:OC_OPENCODE = $null # cache path, si azzera a ogni reload
|
|
11
|
+
|
|
12
|
+
function Get-OcPath {
|
|
13
|
+
if (-not $global:OC_OPENCODE) {
|
|
14
|
+
$remotePath = "(command -v opencode || ls -t ~/.nvm/versions/node/*/bin/opencode 2>/dev/null | head -n1)"
|
|
15
|
+
$found = ssh -o BatchMode=yes $env:OC_SERVER $remotePath 2>$null | Select-Object -First 1
|
|
16
|
+
if ($found) { $global:OC_OPENCODE = $found.Trim() }
|
|
17
|
+
else { $global:OC_OPENCODE = 'opencode' }
|
|
18
|
+
}
|
|
19
|
+
return $global:OC_OPENCODE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function oc-connect {
|
|
23
|
+
$key = "$env:USERPROFILE\.ssh\id_ed25519"
|
|
24
|
+
$pub = "$env:USERPROFILE\.ssh\id_ed25519.pub"
|
|
25
|
+
|
|
26
|
+
if (-not (Test-Path $key)) {
|
|
27
|
+
Write-Host "Genero SSH key..." -ForegroundColor Cyan
|
|
28
|
+
ssh-keygen -t ed25519 -C "$env:OC_USER@client" -f $key -N "" | Out-Null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Write-Host "Installo chiave sul server (inserisci password una volta)..." -ForegroundColor Cyan
|
|
32
|
+
Get-Content $pub | ssh $env:OC_SERVER "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && echo CHIAVE_INSTALLATA"
|
|
33
|
+
|
|
34
|
+
Write-Host "Verifico autenticazione a chiave..." -ForegroundColor Cyan
|
|
35
|
+
$res = ssh -o BatchMode=yes $env:OC_SERVER "echo OK" 2>&1
|
|
36
|
+
if ($res -eq 'OK') {
|
|
37
|
+
Write-Host "Autenticazione SSH a chiave attiva!" -ForegroundColor Green
|
|
38
|
+
} else {
|
|
39
|
+
Write-Host "ERRORE: la chiave non è stata accettata. Riepilogo:" -ForegroundColor Red
|
|
40
|
+
Write-Host " $res" -ForegroundColor Red
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function oc {
|
|
45
|
+
$oc = Get-OcPath
|
|
46
|
+
ssh -t $env:OC_SERVER "cd $env:OC_DIR && $oc"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function oc-ssh {
|
|
50
|
+
ssh $env:OC_SERVER
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Get-OcAllSessions {
|
|
54
|
+
$py = @'
|
|
55
|
+
import json,sys
|
|
56
|
+
raw=sys.stdin.read()
|
|
57
|
+
i=raw.find("[\n")
|
|
58
|
+
if i<0:
|
|
59
|
+
i=raw.find("[")
|
|
60
|
+
d=json.loads(raw[i:])
|
|
61
|
+
for s in d:
|
|
62
|
+
u=s.get("updated") or 0
|
|
63
|
+
title=(s.get("title") or "").replace("\t"," ").replace("\n"," ")
|
|
64
|
+
print(str(u)+"\t"+s.get("id","")+"\t"+title+"\t"+s.get("directory",""))
|
|
65
|
+
'@
|
|
66
|
+
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($py))
|
|
67
|
+
$oc = Get-OcPath
|
|
68
|
+
$cmd = "cd ~ && $oc --pure session list --format json 2>/dev/null | python3 -c `"import base64;exec(base64.b64decode('$b64').decode())`""
|
|
69
|
+
$lines = @(ssh -n -o BatchMode=yes $env:OC_SERVER $cmd 2>$null)
|
|
70
|
+
if ($LASTEXITCODE -eq 0 -and $lines.Count -gt 0) { Save-OcCache $lines }
|
|
71
|
+
$lines | ForEach-Object {
|
|
72
|
+
$parts = $_ -split "`t", 4
|
|
73
|
+
[pscustomobject]@{
|
|
74
|
+
Updated = [long]$parts[0]
|
|
75
|
+
Id = $parts[1]
|
|
76
|
+
Title = $parts[2]
|
|
77
|
+
Dir = if ($parts.Count -gt 3) { $parts[3] } else { "~" }
|
|
78
|
+
}
|
|
79
|
+
} | Sort-Object -Property Updated -Descending
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function Get-OcLastFile {
|
|
83
|
+
return Join-Path (Split-Path (Get-OcCacheFile)) "last.tsv"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Save-OcLastSession {
|
|
87
|
+
param([Parameter(Mandatory)][string]$Id, [string]$Dir = "~")
|
|
88
|
+
$file = Get-OcLastFile
|
|
89
|
+
New-Item -ItemType Directory -Path (Split-Path $file) -Force | Out-Null
|
|
90
|
+
Set-Content -Path $file -Value ("{0}`t{1}" -f $Id, $Dir) -Encoding UTF8
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Get-OcLastSession {
|
|
94
|
+
$file = Get-OcLastFile
|
|
95
|
+
if (-not (Test-Path $file)) { return $null }
|
|
96
|
+
$line = Get-Content $file | Select-Object -First 1
|
|
97
|
+
if (-not $line) { return $null }
|
|
98
|
+
$parts = $line -split "`t", 2
|
|
99
|
+
if ($parts.Count -lt 1 -or -not $parts[0]) { return $null }
|
|
100
|
+
return [pscustomobject]@{
|
|
101
|
+
Id = $parts[0]
|
|
102
|
+
Dir = if ($parts.Count -gt 1) { $parts[1] } else { "~" }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function Get-OcServerStatus {
|
|
107
|
+
$err = ssh -o BatchMode=yes -o ConnectTimeout=5 $env:OC_SERVER "echo PING_OK" 2>&1
|
|
108
|
+
if ($LASTEXITCODE -eq 0 -and ($err -contains 'PING_OK')) {
|
|
109
|
+
return [pscustomobject]@{ Up = $true; Message = 'OK' }
|
|
110
|
+
}
|
|
111
|
+
$msg = ($err | Where-Object { $_ } | Select-Object -Last 1)
|
|
112
|
+
if (-not $msg) { $msg = "Il server non risponde (timeout)." }
|
|
113
|
+
return [pscustomobject]@{ Up = $false; Message = "$msg" }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function Get-OcCacheFile {
|
|
117
|
+
return Join-Path $env:USERPROFILE ".cache\opencode-wyvern\sessions.tsv"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function Save-OcCache {
|
|
121
|
+
param([Parameter(Mandatory)][string[]]$Lines)
|
|
122
|
+
$file = Get-OcCacheFile
|
|
123
|
+
New-Item -ItemType Directory -Path (Split-Path $file) -Force | Out-Null
|
|
124
|
+
Set-Content -Path $file -Value $Lines -Encoding UTF8
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function Get-OcCachedSessions {
|
|
128
|
+
param([switch]$TodayOnly)
|
|
129
|
+
$file = Get-OcCacheFile
|
|
130
|
+
if (-not (Test-Path $file)) { return }
|
|
131
|
+
$rows = @(Get-Content $file | ForEach-Object {
|
|
132
|
+
$parts = $_ -split "`t", 4
|
|
133
|
+
if ($parts.Count -lt 3) { return }
|
|
134
|
+
$u = 0L
|
|
135
|
+
[long]::TryParse($parts[0], [ref]$u) | Out-Null
|
|
136
|
+
[pscustomobject]@{
|
|
137
|
+
Updated = $u
|
|
138
|
+
Id = $parts[1]
|
|
139
|
+
Title = $parts[2]
|
|
140
|
+
Dir = if ($parts.Count -gt 3) { $parts[3] } else { "~" }
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
$rows = @($rows | Where-Object { $_.Id } | Sort-Object Updated -Descending)
|
|
144
|
+
if ($TodayOnly) {
|
|
145
|
+
$cutoff = [DateTimeOffset]::UtcNow.AddHours(-24).ToUnixTimeMilliseconds()
|
|
146
|
+
$rows = @($rows | Where-Object { $_.Updated -ge $cutoff })
|
|
147
|
+
}
|
|
148
|
+
$rows
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function Get-OcRecentSessions {
|
|
152
|
+
$cutoff = [DateTimeOffset]::UtcNow.AddHours(-24).ToUnixTimeMilliseconds()
|
|
153
|
+
@(Get-OcAllSessions) | Where-Object { $_.Updated -ge $cutoff }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function oc-sessions {
|
|
157
|
+
$sessions = Get-OcRecentSessions
|
|
158
|
+
if (-not $sessions) {
|
|
159
|
+
Write-Host "Nessuna sessione nelle ultime 24 ore (o SSH a chiave non configurato - esegui oc-connect)." -ForegroundColor Yellow
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
Write-Host "Sessioni nelle ultime 24 ore:" -ForegroundColor Cyan
|
|
163
|
+
$i = 0
|
|
164
|
+
foreach ($s in $sessions) {
|
|
165
|
+
$i++
|
|
166
|
+
Write-Host (" {0}. [{1}] {2}" -f $i, $s.Id.Substring(0, [Math]::Min(12, $s.Id.Length)), $s.Title)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function oc-help {
|
|
171
|
+
Write-Host ""
|
|
172
|
+
Write-Host "OpenCode Wyvern - comandi" -ForegroundColor Cyan
|
|
173
|
+
Write-Host "============================" -ForegroundColor Cyan
|
|
174
|
+
Write-Host ""
|
|
175
|
+
Write-Host (" {0,-24} {1}" -f "oc-connect", "Setup SSH key + autorizzazione server")
|
|
176
|
+
Write-Host (" {0,-24} {1}" -f "oc", "Nuova sessione opencode in $env:OC_DIR")
|
|
177
|
+
Write-Host (" {0,-24} {1}" -f "oc-ssh", "Apri sessione SSH interattiva")
|
|
178
|
+
Write-Host (" {0,-24} {1}" -f "oc-sessions", "Lista sessioni opencode (ultime 24h)")
|
|
179
|
+
Write-Host (" {0,-24} {1}" -f "oc-find [testo]", "Cerca sessioni globali per titolo e riapri")
|
|
180
|
+
Write-Host (" {0,-24} {1}" -f "oc-delete [testo]", "Cerca ed elimina sessioni")
|
|
181
|
+
Write-Host (" {0,-24} {1}" -f "oc-resume", "Apre 1 tab per ogni sessione (ultime 24h)")
|
|
182
|
+
Write-Host (" {0,-24} {1}" -f "oc-go -Id <id> [-Dir <path>]", "Riprende una sessione specifica")
|
|
183
|
+
Write-Host (" {0,-24} {1}" -f "oc-recap", "Riepilogo sessioni (usa cache se il server è giù)")
|
|
184
|
+
Write-Host (" {0,-24} {1}" -f "oc-help", "Questo aiuto")
|
|
185
|
+
Write-Host ""
|
|
186
|
+
Write-Host "Nota: oc-ssh, oc-sessions, oc-resume richiedono SSH key configurata" -ForegroundColor DarkGray
|
|
187
|
+
Write-Host ""
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function oc-find {
|
|
191
|
+
param(
|
|
192
|
+
[string]$Search = '',
|
|
193
|
+
[int]$Limit = 30
|
|
194
|
+
)
|
|
195
|
+
$all = Get-OcAllSessions
|
|
196
|
+
if (-not $all) {
|
|
197
|
+
Write-Host "Nessuna sessione trovata (o SSH a chiave non configurato)." -ForegroundColor Yellow
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
if ($Search) {
|
|
201
|
+
$all = $all | Where-Object { $_.Title -match [regex]::Escape($Search) }
|
|
202
|
+
}
|
|
203
|
+
if ($Search -and $all.Count -gt 0) {
|
|
204
|
+
Write-Host ("Risultati per `"$Search`" (max {0}):" -f $Limit) -ForegroundColor Cyan
|
|
205
|
+
} elseif (-not $Search) {
|
|
206
|
+
Write-Host "Sessioni recenti (max $Limit):" -ForegroundColor Cyan
|
|
207
|
+
} else {
|
|
208
|
+
Write-Host "Nessuna sessione corrispondente." -ForegroundColor Yellow
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
$shown = @()
|
|
212
|
+
$i = 0
|
|
213
|
+
foreach ($s in $all | Select-Object -First $Limit) {
|
|
214
|
+
$i++
|
|
215
|
+
$date = [DateTimeOffset]::FromUnixTimeMilliseconds($s.Updated).DateTime.ToString('yyyy-MM-dd HH:mm')
|
|
216
|
+
$dir = $s.Dir -replace [regex]::Escape($env:USERPROFILE + '\'), '~/'
|
|
217
|
+
Write-Host (" {0,2}. [{1}] {2} ({3}) {4}" -f $i, $s.Id.Substring(0,12), $s.Title, $date, $dir)
|
|
218
|
+
$shown += $s
|
|
219
|
+
}
|
|
220
|
+
Write-Host ""
|
|
221
|
+
$choice = Read-Host "Numero da aprire (INVIO per annullare)"
|
|
222
|
+
if ($choice -match '^\d+$') {
|
|
223
|
+
$idx = [int]$choice - 1
|
|
224
|
+
if ($idx -ge 0 -and $idx -lt $shown.Count) {
|
|
225
|
+
$sel = $shown[$idx]
|
|
226
|
+
oc-go -Id $sel.Id -Dir $sel.Dir -NoRecap
|
|
227
|
+
} else {
|
|
228
|
+
Write-Host "Indice fuori range." -ForegroundColor Yellow
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function oc-delete {
|
|
234
|
+
param(
|
|
235
|
+
[string]$Search = ''
|
|
236
|
+
)
|
|
237
|
+
$all = Get-OcAllSessions
|
|
238
|
+
if (-not $all) {
|
|
239
|
+
Write-Host "Nessuna sessione trovata." -ForegroundColor Yellow
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
if ($Search) {
|
|
243
|
+
$all = $all | Where-Object { $_.Title -match [regex]::Escape($Search) -or $_.Id -match [regex]::Escape($Search) }
|
|
244
|
+
}
|
|
245
|
+
if (-not $all) {
|
|
246
|
+
Write-Host ("Nessuna sessione corrispondente a `"$Search`".") -ForegroundColor Yellow
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
Write-Host "Sessioni (seleziona da eliminare):" -ForegroundColor Cyan
|
|
250
|
+
$i = 0
|
|
251
|
+
foreach ($s in $all | Select-Object -First 30) {
|
|
252
|
+
$i++
|
|
253
|
+
$date = [DateTimeOffset]::FromUnixTimeMilliseconds($s.Updated).DateTime.ToString('yyyy-MM-dd HH:mm')
|
|
254
|
+
Write-Host (" {0,2}. [{1}] {2} ({3})" -f $i, $s.Id.Substring(0,12), $s.Title, $date)
|
|
255
|
+
}
|
|
256
|
+
Write-Host ""
|
|
257
|
+
$choice = Read-Host "Numero da eliminare (INVIO per annullare)"
|
|
258
|
+
if ($choice -notmatch '^\d+$') { return }
|
|
259
|
+
$idx = [int]$choice - 1
|
|
260
|
+
if ($idx -lt 0 -or $idx -ge [Math]::Min($all.Count, 30)) {
|
|
261
|
+
Write-Host "Indice fuori range." -ForegroundColor Yellow
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
$sel = ($all | Select-Object -First 30)[$idx]
|
|
265
|
+
$confirm = Read-Host "Eliminare definitivamente la sessione '$($sel.Title)'? (s/N)"
|
|
266
|
+
if ($confirm -notmatch '^(s|si|sì|y|yes)$') {
|
|
267
|
+
Write-Host "Annullato." -ForegroundColor Yellow
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
$oc = Get-OcPath
|
|
271
|
+
$remoteCmd = "cd ~ && $oc --pure session delete $($sel.Id) </dev/null"
|
|
272
|
+
ssh -o BatchMode=yes $env:OC_SERVER $remoteCmd 2>&1 | Select-Object -Last 2 | Out-Host
|
|
273
|
+
Write-Host "Sessione eliminata: $($sel.Title)" -ForegroundColor Green
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function oc-go {
|
|
277
|
+
param(
|
|
278
|
+
[Parameter(Mandatory)]
|
|
279
|
+
[string]$Id,
|
|
280
|
+
[string]$Dir = "~",
|
|
281
|
+
[switch]$NoRecap
|
|
282
|
+
)
|
|
283
|
+
$oc = Get-OcPath
|
|
284
|
+
if ($Dir -eq '~' -or [string]::IsNullOrEmpty($Dir)) {
|
|
285
|
+
$remoteCmd = "cd ~ && $oc -s $Id"
|
|
286
|
+
} else {
|
|
287
|
+
$remoteCmd = "mkdir -p '$Dir' && cd '$Dir' && $oc -s $Id"
|
|
288
|
+
}
|
|
289
|
+
Save-OcLastSession -Id $Id -Dir $Dir
|
|
290
|
+
ssh -t -o ConnectTimeout=8 $env:OC_SERVER $remoteCmd
|
|
291
|
+
$exitCode = $LASTEXITCODE
|
|
292
|
+
$errTime = Get-Date
|
|
293
|
+
if (-not $NoRecap) { Show-OcRecap -LastExit $exitCode -ErrTime $errTime }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function Show-OcRecap {
|
|
297
|
+
param(
|
|
298
|
+
[int]$LastExit = -1,
|
|
299
|
+
[datetime]$ErrTime = (Get-Date)
|
|
300
|
+
)
|
|
301
|
+
Write-Host ""
|
|
302
|
+
Write-Host ("{0}" -f ('=' * 56)) -ForegroundColor DarkCyan
|
|
303
|
+
Write-Host "Connessione terminata. Riepilogo per riprendere:" -ForegroundColor Cyan
|
|
304
|
+
Write-Host ("{0}" -f ('=' * 56)) -ForegroundColor DarkCyan
|
|
305
|
+
$retry = $true
|
|
306
|
+
while ($retry) {
|
|
307
|
+
$retry = $false
|
|
308
|
+
$sessions = @(Get-OcRecentSessions)
|
|
309
|
+
$fromCache = $false
|
|
310
|
+
if ($sessions.Count -eq 0) {
|
|
311
|
+
$status = Get-OcServerStatus
|
|
312
|
+
if ($status.Up) {
|
|
313
|
+
Write-Host " Server raggiungibile ma nessuna sessione nelle ultime 24 ore." -ForegroundColor Yellow
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
$cached = @(Get-OcCachedSessions -TodayOnly)
|
|
317
|
+
if ($cached.Count -eq 0) {
|
|
318
|
+
Write-Host (" [!] Connessione persa alle {0} (SSH exit {1})" -f $ErrTime.ToString('yyyy-MM-dd HH:mm:ss'), $LastExit) -ForegroundColor Red
|
|
319
|
+
Write-Host (" Errore: {0}" -f $status.Message) -ForegroundColor Yellow
|
|
320
|
+
Write-Host " Nessun riepilogo salvato: il client non si era mai collegato." -ForegroundColor Yellow
|
|
321
|
+
Write-Host " Quando il server torna su: oc-recap" -ForegroundColor DarkGray
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
$sessions = $cached
|
|
325
|
+
$fromCache = $true
|
|
326
|
+
}
|
|
327
|
+
if ($fromCache) {
|
|
328
|
+
$stamp = (Get-Item (Get-OcCacheFile)).LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss')
|
|
329
|
+
Write-Host (" [!] Connessione persa alle {0} (SSH exit {1})" -f $ErrTime.ToString('yyyy-MM-dd HH:mm:ss'), $LastExit) -ForegroundColor Red
|
|
330
|
+
Write-Host (" Errore: {0}" -f $status.Message) -ForegroundColor Yellow
|
|
331
|
+
Write-Host (" Riepilogo salvato localmente il {0}: quando la connessione torna, riprendi da qui." -f $stamp) -ForegroundColor DarkGray
|
|
332
|
+
Write-Host ""
|
|
333
|
+
}
|
|
334
|
+
$i = 0
|
|
335
|
+
foreach ($s in $sessions) {
|
|
336
|
+
$i++
|
|
337
|
+
$last = if ($s.Updated) { [DateTimeOffset]::FromUnixTimeMilliseconds($s.Updated).LocalDateTime.ToString('HH:mm') } else { '' }
|
|
338
|
+
Write-Host ""
|
|
339
|
+
Write-Host (" {0,2}) {1} [{2}...]" -f $i, $s.Title, $s.Id.Substring(0, [Math]::Min(12, $s.Id.Length))) -ForegroundColor White
|
|
340
|
+
Write-Host (" in: {0} (ultimo aggiornamento {1})" -f $s.Dir, $last) -ForegroundColor DarkGray
|
|
341
|
+
Write-Host (" run: opencode -s {0}" -f $s.Id) -ForegroundColor DarkGray
|
|
342
|
+
Write-Host (" oc-go: oc-go -Id '{0}' -Dir '{1}'" -f $s.Id, $s.Dir) -ForegroundColor DarkGray
|
|
343
|
+
}
|
|
344
|
+
Write-Host ""
|
|
345
|
+
$choice = Read-Host "Scelta (1..$i riprendi, r riprova, n nuova sessione, INVIO esci)"
|
|
346
|
+
if ($choice -match '^\d+$') {
|
|
347
|
+
$idx = [int]$choice - 1
|
|
348
|
+
if ($idx -ge 0 -and $idx -lt $i) {
|
|
349
|
+
$sel = $sessions[$idx]
|
|
350
|
+
Write-Host (" Riprendo: {0}" -f $sel.Title) -ForegroundColor Cyan
|
|
351
|
+
oc-go -Id $sel.Id -Dir $sel.Dir
|
|
352
|
+
} else {
|
|
353
|
+
Write-Host " Indice fuori range." -ForegroundColor Yellow
|
|
354
|
+
}
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
if ($choice -match '^r$') {
|
|
358
|
+
$retry = $true
|
|
359
|
+
$LastExit = -1
|
|
360
|
+
$ErrTime = Get-Date
|
|
361
|
+
$global:OC_OPENCODE = $null
|
|
362
|
+
Write-Host " Riprovo la connessione..." -ForegroundColor Cyan
|
|
363
|
+
continue
|
|
364
|
+
}
|
|
365
|
+
if ($choice -match '^n$') {
|
|
366
|
+
Write-Host " Nuova sessione..." -ForegroundColor Cyan
|
|
367
|
+
oc
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
Write-Host " Per riprendere in seguito: oc-recap oppure oc-resume" -ForegroundColor DarkGray
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
Set-Alias oc-recap Show-OcRecap
|
|
376
|
+
|
|
377
|
+
function Get-WarpConfigDir {
|
|
378
|
+
$candidates = @(
|
|
379
|
+
"$env:APPDATA\warp\Warp\data\tab_configs",
|
|
380
|
+
"$env:APPDATA\warp\WarpPreview\data\tab_configs",
|
|
381
|
+
"$env:LOCALAPPDATA\warp\Warp\data\tab_configs",
|
|
382
|
+
"$env:LOCALAPPDATA\warp\WarpPreview\data\tab_configs"
|
|
383
|
+
)
|
|
384
|
+
foreach ($d in $candidates) {
|
|
385
|
+
if (Test-Path $d) { return $d }
|
|
386
|
+
}
|
|
387
|
+
# crea la dir stabile se Warp è installato
|
|
388
|
+
if (Test-Path "$env:APPDATA\warp\Warp\data") {
|
|
389
|
+
New-Item -ItemType Directory -Path "$env:APPDATA\warp\Warp\data\tab_configs" -Force | Out-Null
|
|
390
|
+
return "$env:APPDATA\warp\Warp\data\tab_configs"
|
|
391
|
+
}
|
|
392
|
+
return $null
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function Get-OcTerminal {
|
|
396
|
+
if ($env:TERM_PROGRAM -match 'warp') { return 'warp' }
|
|
397
|
+
$cur = $PID
|
|
398
|
+
$depth = 0
|
|
399
|
+
while ($cur -gt 0 -and $depth -lt 15) {
|
|
400
|
+
$pr = Get-CimInstance Win32_Process -Filter "ProcessId=$cur" -ErrorAction SilentlyContinue
|
|
401
|
+
if (-not $pr) { break }
|
|
402
|
+
$name = $pr.Name
|
|
403
|
+
if ($name -match '^Warp') { return 'warp' }
|
|
404
|
+
if ($name -match 'WindowsTerminal') { return 'wt' }
|
|
405
|
+
$cur = [int]$pr.ParentProcessId
|
|
406
|
+
$depth++
|
|
407
|
+
}
|
|
408
|
+
if ($env:WT_SESSION -and (Get-Command wt -ErrorAction SilentlyContinue)) { return 'wt' }
|
|
409
|
+
return $null
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function Invoke-OcWarpResume {
|
|
413
|
+
param([Parameter(Mandatory)]$Sessions)
|
|
414
|
+
$dir = Get-WarpConfigDir
|
|
415
|
+
if (-not $dir) { return $false }
|
|
416
|
+
$oc = Get-OcPath
|
|
417
|
+
$created = @()
|
|
418
|
+
$i = 0
|
|
419
|
+
foreach ($s in $Sessions) {
|
|
420
|
+
$i++
|
|
421
|
+
$stem = "oc-resume-$i-$(Get-Random -Minimum 1000 -Maximum 9999)"
|
|
422
|
+
$file = Join-Path $dir "$stem.toml"
|
|
423
|
+
$title = ($s.Title -replace '[\\"]', ' ').Trim()
|
|
424
|
+
if ($s.Dir -eq '~' -or [string]::IsNullOrEmpty($s.Dir)) {
|
|
425
|
+
$remoteCmd = "cd ~ && $oc -s $($s.Id)"
|
|
426
|
+
} else {
|
|
427
|
+
$remoteCmd = "mkdir -p '$($s.Dir)' && cd '$($s.Dir)' && $oc -s $($s.Id)"
|
|
428
|
+
}
|
|
429
|
+
$cmd = ('ssh -t {0} "{1}"; oc-recap' -f $env:OC_SERVER, $remoteCmd).Replace('"', '\"')
|
|
430
|
+
$toml = @"
|
|
431
|
+
name = "[oc] $title"
|
|
432
|
+
title = "$title"
|
|
433
|
+
[[panes]]
|
|
434
|
+
id = "main"
|
|
435
|
+
type = "terminal"
|
|
436
|
+
shell = "pwsh"
|
|
437
|
+
commands = ["$cmd"]
|
|
438
|
+
"@
|
|
439
|
+
Set-Content -Path $file -Value $toml -Encoding UTF8
|
|
440
|
+
$created += @{ File = $file; Stem = $stem }
|
|
441
|
+
Write-Host " Apro tab Warp: $($s.Title)" -ForegroundColor Cyan
|
|
442
|
+
Start-Process "warp://tab_config/$stem"
|
|
443
|
+
Start-Sleep -Seconds 2
|
|
444
|
+
}
|
|
445
|
+
Write-Host ""
|
|
446
|
+
Write-Host "Se qualche tab non si e' aperta, riavvia lanciando: oc-resume -Warp" -ForegroundColor DarkGray
|
|
447
|
+
Write-Host "Per raggrupparle: Ctrl+click su tutte le tab, poi tasto destro e 'New group with tab'." -ForegroundColor DarkGray
|
|
448
|
+
Start-Job -ScriptBlock {
|
|
449
|
+
param($files)
|
|
450
|
+
Start-Sleep -Seconds 60
|
|
451
|
+
foreach ($f in $files) { Remove-Item -LiteralPath $f -Force -ErrorAction SilentlyContinue }
|
|
452
|
+
} -ArgumentList @($created.File) | Out-Null
|
|
453
|
+
return $true
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function oc-resume {
|
|
457
|
+
param(
|
|
458
|
+
[switch]$Copy,
|
|
459
|
+
[switch]$Tabs,
|
|
460
|
+
[switch]$Warp,
|
|
461
|
+
[switch]$AllTabs
|
|
462
|
+
)
|
|
463
|
+
$sessions = @(Get-OcRecentSessions)
|
|
464
|
+
|
|
465
|
+
if ($sessions.Count -eq 0) {
|
|
466
|
+
$status = Get-OcServerStatus
|
|
467
|
+
if (-not $status.Up) {
|
|
468
|
+
Write-Host ""
|
|
469
|
+
Write-Host (" [!] Errore di connessione al server ({0})" -f $status.Message) -ForegroundColor Red
|
|
470
|
+
$last = Get-OcLastSession
|
|
471
|
+
if ($last) {
|
|
472
|
+
Write-Host " Riprendo automaticamente l'ultima sessione terminata..." -ForegroundColor Cyan
|
|
473
|
+
Write-Host ""
|
|
474
|
+
oc-go -Id $last.Id -Dir $last.Dir
|
|
475
|
+
} else {
|
|
476
|
+
Write-Host " Nessuna sessione da riprendere automaticamente." -ForegroundColor Yellow
|
|
477
|
+
Show-OcRecap
|
|
478
|
+
}
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
Write-Host "Nessuna sessione nelle ultime 24 ore (o SSH a chiave non configurato - esegui oc-connect)." -ForegroundColor Yellow
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
$first = $sessions[0]
|
|
486
|
+
$rest = @($sessions | Select-Object -Skip 1)
|
|
487
|
+
|
|
488
|
+
$term = Get-OcTerminal
|
|
489
|
+
$useWarp = $Warp -or ($term -eq 'warp' -and -not $Copy -and -not $Tabs)
|
|
490
|
+
$useTabs = $Tabs -or ($term -eq 'wt' -and -not $Copy -and -not $Warp)
|
|
491
|
+
|
|
492
|
+
if ($useWarp) {
|
|
493
|
+
$toOpen = if ($AllTabs) { $sessions } else { $rest }
|
|
494
|
+
if ($toOpen.Count -gt 0) {
|
|
495
|
+
if (-not (Invoke-OcWarpResume $toOpen) -and -not $AllTabs) {
|
|
496
|
+
Write-Host "Apertura tab Warp fallita, proseguo solo con quella corrente." -ForegroundColor Yellow
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (-not $AllTabs) {
|
|
500
|
+
Write-Host (" Riprendo qui: {0}" -f $first.Title) -ForegroundColor Cyan
|
|
501
|
+
oc-go -Id $first.Id -Dir $first.Dir
|
|
502
|
+
}
|
|
503
|
+
return
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if ($useTabs) {
|
|
507
|
+
$toOpen = if ($AllTabs) { $sessions } else { $rest }
|
|
508
|
+
foreach ($s in $toOpen) {
|
|
509
|
+
Write-Host " Apro tab: $($s.Title)" -ForegroundColor Cyan
|
|
510
|
+
wt -w 0 new-tab --title "$($s.Title)" pwsh -Command "oc-go -Id '$($s.Id)' -Dir '$($s.Dir)'; if (\$LASTEXITCODE -ne 0 -or -not \$?) { Read-Host 'Premi INVIO per chiudere' }"
|
|
511
|
+
Start-Sleep -Milliseconds 800
|
|
512
|
+
}
|
|
513
|
+
if (-not $AllTabs) {
|
|
514
|
+
Write-Host (" Riprendo qui: {0}" -f $first.Title) -ForegroundColor Cyan
|
|
515
|
+
oc-go -Id $first.Id -Dir $first.Dir
|
|
516
|
+
}
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
Write-Host "Terminale corrente non Warp/Windows Terminal (o -Copy): copia e incolla i comandi" -ForegroundColor Yellow
|
|
521
|
+
Write-Host ""
|
|
522
|
+
foreach ($s in $sessions) {
|
|
523
|
+
Write-Host (" oc-go -Id '{0}' -Dir '{1}'" -f $s.Id, $s.Dir) -ForegroundColor Cyan
|
|
524
|
+
}
|
|
525
|
+
Write-Host ""
|
|
526
|
+
Write-Host "Suggerimenti: -Warp apre i tab in Warp, -Tabs in Windows Terminal, -AllTabs apre tutte le sessioni in tab nuove." -ForegroundColor DarkGray
|
|
527
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Baseline di interfaccia/struttura per una nuova feature o schermata
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Baseline UI
|
|
6
|
+
|
|
7
|
+
Prima di scrivere codice per un'interfaccia nuova o modificata, produci una baseline concisa che guidi l'implementazione. Non leggere file se non strettamente necessario: rispondi solo con la baseline.
|
|
8
|
+
|
|
9
|
+
## Struttura richiesta
|
|
10
|
+
|
|
11
|
+
1. **Obiettivo** - in una frase, cosa fa la UI.
|
|
12
|
+
2. **Componenti** - elenco dei componenti/schermate coinvolte (albero ASCII opzionale).
|
|
13
|
+
3. **Stati** - loading, empty, error, success; quali gestire e come (spinner, messaggi, retry).
|
|
14
|
+
4. **Accessibilità** - focus visibile, contrasto, alternative testuali, scorciatoie da tastiera.
|
|
15
|
+
5. **Dati/API** - chiamate necessarie, dove finiscono i dati, comportamento offline/fallback.
|
|
16
|
+
6. **Vincoli di sicurezza** - non incollare mai chiavi, token o segreti; usa variabili d'ambiente.
|
|
17
|
+
7. **Checklist di primo passo** - 3-6 voci concrete da seguire.
|
|
18
|
+
|
|
19
|
+
## Vincoli
|
|
20
|
+
|
|
21
|
+
- Non scrivere codice: solo la baseline in Markdown.
|
|
22
|
+
- Massimo ~80 righe; se serve più spazio, usa elenchi puntati, non testo piatto.
|
|
23
|
+
- Se mancano informazioni (framework, linguaggio, stile), dichiaralo e proponi un default ragionevole invece di assumere.
|