James Read hace 6 meses
padre
commit
59afe01e61

+ 21 - 0
frontend/js/OutputTerminal.js

@@ -74,4 +74,25 @@ export class OutputTerminal {
   resize (cols, rows) {
     this.terminal.resize(cols, rows)
   }
+
+  /**
+   * Get the terminal buffer content as a string.
+   * This method is intended for use in integration tests to verify output.
+   * @returns {string} The terminal buffer content as a string
+   */
+  getBufferAsString () {
+    if (!this.terminal) {
+      return ''
+    }
+
+    const buffer = this.terminal.buffer.active
+    let text = ''
+    for (let i = 0; i < buffer.length; i++) {
+      const line = buffer.getLine(i)
+      if (line) {
+        text += line.translateToString(true) + '\n'
+      }
+    }
+    return text
+  }
 }

+ 240 - 237
frontend/package-lock.json

@@ -11,18 +11,18 @@
 			"dependencies": {
 				"@connectrpc/connect": "^2.1.1",
 				"@connectrpc/connect-web": "^2.1.1",
-				"@hugeicons/core-free-icons": "^2.0.0",
-				"@hugeicons/vue": "^1.0.3",
-				"@vitejs/plugin-vue": "^6.0.2",
-				"@xterm/addon-fit": "^0.10.0",
-				"@xterm/xterm": "^5.5.0",
+				"@hugeicons/core-free-icons": "^3.1.0",
+				"@hugeicons/vue": "^1.0.4",
+				"@vitejs/plugin-vue": "^6.0.3",
+				"@xterm/addon-fit": "^0.11.0",
+				"@xterm/xterm": "^6.0.0",
 				"iconify-icon": "^3.0.2",
-				"picocrank": "^1.9.0",
+				"picocrank": "^1.10.0",
 				"standard": "^17.1.2",
 				"unplugin-vue-components": "^30.0.0",
-				"vite": "^7.2.6",
-				"vue-i18n": "^11.2.2",
-				"vue-router": "^4.6.3"
+				"vite": "^7.3.0",
+				"vue-i18n": "^11.2.7",
+				"vue-router": "^4.6.4"
 			},
 			"devDependencies": {
 				"process": "^0.11.10",
@@ -380,9 +380,9 @@
 			}
 		},
 		"node_modules/@esbuild/aix-ppc64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
-			"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+			"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
 			"cpu": [
 				"ppc64"
 			],
@@ -396,9 +396,9 @@
 			}
 		},
 		"node_modules/@esbuild/android-arm": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
-			"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+			"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
 			"cpu": [
 				"arm"
 			],
@@ -412,9 +412,9 @@
 			}
 		},
 		"node_modules/@esbuild/android-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
-			"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+			"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
 			"cpu": [
 				"arm64"
 			],
@@ -428,9 +428,9 @@
 			}
 		},
 		"node_modules/@esbuild/android-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
-			"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+			"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
 			"cpu": [
 				"x64"
 			],
@@ -444,9 +444,9 @@
 			}
 		},
 		"node_modules/@esbuild/darwin-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
-			"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+			"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
 			"cpu": [
 				"arm64"
 			],
@@ -460,9 +460,9 @@
 			}
 		},
 		"node_modules/@esbuild/darwin-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
-			"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+			"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
 			"cpu": [
 				"x64"
 			],
@@ -476,9 +476,9 @@
 			}
 		},
 		"node_modules/@esbuild/freebsd-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
-			"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+			"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
 			"cpu": [
 				"arm64"
 			],
@@ -492,9 +492,9 @@
 			}
 		},
 		"node_modules/@esbuild/freebsd-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
-			"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+			"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
 			"cpu": [
 				"x64"
 			],
@@ -508,9 +508,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-arm": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
-			"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+			"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
 			"cpu": [
 				"arm"
 			],
@@ -524,9 +524,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
-			"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+			"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
 			"cpu": [
 				"arm64"
 			],
@@ -540,9 +540,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-ia32": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
-			"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+			"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
 			"cpu": [
 				"ia32"
 			],
@@ -556,9 +556,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-loong64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
-			"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+			"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
 			"cpu": [
 				"loong64"
 			],
@@ -572,9 +572,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-mips64el": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
-			"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+			"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
 			"cpu": [
 				"mips64el"
 			],
@@ -588,9 +588,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-ppc64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
-			"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+			"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
 			"cpu": [
 				"ppc64"
 			],
@@ -604,9 +604,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-riscv64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
-			"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+			"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
 			"cpu": [
 				"riscv64"
 			],
@@ -620,9 +620,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-s390x": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
-			"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+			"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
 			"cpu": [
 				"s390x"
 			],
@@ -636,9 +636,9 @@
 			}
 		},
 		"node_modules/@esbuild/linux-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
-			"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+			"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
 			"cpu": [
 				"x64"
 			],
@@ -652,9 +652,9 @@
 			}
 		},
 		"node_modules/@esbuild/netbsd-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
-			"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+			"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
 			"cpu": [
 				"arm64"
 			],
@@ -668,9 +668,9 @@
 			}
 		},
 		"node_modules/@esbuild/netbsd-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
-			"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+			"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
 			"cpu": [
 				"x64"
 			],
@@ -684,9 +684,9 @@
 			}
 		},
 		"node_modules/@esbuild/openbsd-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
-			"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+			"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
 			"cpu": [
 				"arm64"
 			],
@@ -700,9 +700,9 @@
 			}
 		},
 		"node_modules/@esbuild/openbsd-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
-			"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+			"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
 			"cpu": [
 				"x64"
 			],
@@ -716,9 +716,9 @@
 			}
 		},
 		"node_modules/@esbuild/openharmony-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
-			"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+			"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
 			"cpu": [
 				"arm64"
 			],
@@ -732,9 +732,9 @@
 			}
 		},
 		"node_modules/@esbuild/sunos-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
-			"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+			"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
 			"cpu": [
 				"x64"
 			],
@@ -748,9 +748,9 @@
 			}
 		},
 		"node_modules/@esbuild/win32-arm64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
-			"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+			"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
 			"cpu": [
 				"arm64"
 			],
@@ -764,9 +764,9 @@
 			}
 		},
 		"node_modules/@esbuild/win32-ia32": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
-			"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+			"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
 			"cpu": [
 				"ia32"
 			],
@@ -780,9 +780,9 @@
 			}
 		},
 		"node_modules/@esbuild/win32-x64": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
-			"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+			"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
 			"cpu": [
 				"x64"
 			],
@@ -855,15 +855,16 @@
 			}
 		},
 		"node_modules/@hugeicons/core-free-icons": {
-			"version": "2.0.0",
-			"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-2.0.0.tgz",
-			"integrity": "sha512-OSfv5k0iB0yG61dcfK7jcf00AIK8EXyQOgtcNJzSBFvm88n9VOelkxihZHJnNwDUFpO/jZI3vZSVp6i1dmRvJQ==",
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.1.0.tgz",
+			"integrity": "sha512-DVIdHcPxJ8MyaXaGe3appbaB5z4DWit5RLn8vTy3hsKTrpFEm4QftixfbcHXRqLpOMAoMa2+UXk35qNJ4ZWIsg==",
 			"license": "MIT"
 		},
 		"node_modules/@hugeicons/vue": {
-			"version": "1.0.3",
-			"resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.3.tgz",
-			"integrity": "sha512-DF9A277Ej4Eahu11Hkd3v6V0eZ1NHWZWs9OOByJaxGekgG8q7DAbkhltIo3bqsoxVprxaKSX3Mmn5a2dyzLsHA==",
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.4.tgz",
+			"integrity": "sha512-OtFEXbyW5jYUig98C/n/HygktLvfF5Ga6nN6gK8R0E0jCrVw3EfgoZZVXqo+xGxyIjH5R1wdbg6nJrtf6mzLKQ==",
+			"license": "MIT",
 			"peerDependencies": {
 				"vue": "^2.6.0 || ^3.0.0"
 			}
@@ -910,13 +911,13 @@
 			"license": "MIT"
 		},
 		"node_modules/@intlify/core-base": {
-			"version": "11.2.2",
-			"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.2.2.tgz",
-			"integrity": "sha512-0mCTBOLKIqFUP3BzwuFW23hYEl9g/wby6uY//AC5hTgQfTsM2srCYF2/hYGp+a5DZ/HIFIgKkLJMzXTt30r0JQ==",
+			"version": "11.2.7",
+			"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.2.7.tgz",
+			"integrity": "sha512-+Ra9I/LAzXDnmv/IrTO03WMCiLya7pHRmGJvNl9fKwx/W4REJ0xaMk2PxCRqnxcBsX443amEMdebQ3R1geiuIw==",
 			"license": "MIT",
 			"dependencies": {
-				"@intlify/message-compiler": "11.2.2",
-				"@intlify/shared": "11.2.2"
+				"@intlify/message-compiler": "11.2.7",
+				"@intlify/shared": "11.2.7"
 			},
 			"engines": {
 				"node": ">= 16"
@@ -926,12 +927,12 @@
 			}
 		},
 		"node_modules/@intlify/message-compiler": {
-			"version": "11.2.2",
-			"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.2.2.tgz",
-			"integrity": "sha512-XS2p8Ff5JxWsKhgfld4/MRQzZRQ85drMMPhb7Co6Be4ZOgqJX1DzcZt0IFgGTycgqL8rkYNwgnD443Q+TapOoA==",
+			"version": "11.2.7",
+			"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.2.7.tgz",
+			"integrity": "sha512-TFamC+GzJAotAFwUNvbtRVBgvuSn2nCwKNresmPUHv3IIVMmXJt7QQJj/DORI1h8hs46ZF6L0Fs2xBohSOE4iQ==",
 			"license": "MIT",
 			"dependencies": {
-				"@intlify/shared": "11.2.2",
+				"@intlify/shared": "11.2.7",
 				"source-map-js": "^1.0.2"
 			},
 			"engines": {
@@ -942,9 +943,9 @@
 			}
 		},
 		"node_modules/@intlify/shared": {
-			"version": "11.2.2",
-			"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.2.2.tgz",
-			"integrity": "sha512-OtCmyFpSXxNu/oET/aN6HtPCbZ01btXVd0f3w00YsHOb13Kverk1jzA2k47pAekM55qbUw421fvPF1yxZ+gicw==",
+			"version": "11.2.7",
+			"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.2.7.tgz",
+			"integrity": "sha512-uvlkvc/0uQ4FDlHQZccpUnmcOwNcaI3i+69ck2YJ+GqM35AoVbuS63b+YfirV4G0SZh64Ij2UMcFRMmB4nr95w==",
 			"license": "MIT",
 			"engines": {
 				"node": ">= 16"
@@ -1038,9 +1039,9 @@
 			}
 		},
 		"node_modules/@rolldown/pluginutils": {
-			"version": "1.0.0-beta.50",
-			"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz",
-			"integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==",
+			"version": "1.0.0-beta.53",
+			"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
+			"integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
 			"license": "MIT"
 		},
 		"node_modules/@rollup/rollup-android-arm-eabi": {
@@ -1328,55 +1329,55 @@
 			"license": "ISC"
 		},
 		"node_modules/@vitejs/plugin-vue": {
-			"version": "6.0.2",
-			"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.2.tgz",
-			"integrity": "sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==",
+			"version": "6.0.3",
+			"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz",
+			"integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==",
 			"license": "MIT",
 			"dependencies": {
-				"@rolldown/pluginutils": "1.0.0-beta.50"
+				"@rolldown/pluginutils": "1.0.0-beta.53"
 			},
 			"engines": {
 				"node": "^20.19.0 || >=22.12.0"
 			},
 			"peerDependencies": {
-				"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
+				"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0",
 				"vue": "^3.2.25"
 			}
 		},
 		"node_modules/@vue/compiler-core": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
-			"integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz",
+			"integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==",
 			"license": "MIT",
 			"dependencies": {
 				"@babel/parser": "^7.28.5",
-				"@vue/shared": "3.5.24",
-				"entities": "^4.5.0",
+				"@vue/shared": "3.5.26",
+				"entities": "^7.0.0",
 				"estree-walker": "^2.0.2",
 				"source-map-js": "^1.2.1"
 			}
 		},
 		"node_modules/@vue/compiler-dom": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
-			"integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz",
+			"integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==",
 			"license": "MIT",
 			"dependencies": {
-				"@vue/compiler-core": "3.5.24",
-				"@vue/shared": "3.5.24"
+				"@vue/compiler-core": "3.5.26",
+				"@vue/shared": "3.5.26"
 			}
 		},
 		"node_modules/@vue/compiler-sfc": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
-			"integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz",
+			"integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==",
 			"license": "MIT",
 			"dependencies": {
 				"@babel/parser": "^7.28.5",
-				"@vue/compiler-core": "3.5.24",
-				"@vue/compiler-dom": "3.5.24",
-				"@vue/compiler-ssr": "3.5.24",
-				"@vue/shared": "3.5.24",
+				"@vue/compiler-core": "3.5.26",
+				"@vue/compiler-dom": "3.5.26",
+				"@vue/compiler-ssr": "3.5.26",
+				"@vue/shared": "3.5.26",
 				"estree-walker": "^2.0.2",
 				"magic-string": "^0.30.21",
 				"postcss": "^8.5.6",
@@ -1384,13 +1385,13 @@
 			}
 		},
 		"node_modules/@vue/compiler-ssr": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
-			"integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz",
+			"integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==",
 			"license": "MIT",
 			"dependencies": {
-				"@vue/compiler-dom": "3.5.24",
-				"@vue/shared": "3.5.24"
+				"@vue/compiler-dom": "3.5.26",
+				"@vue/shared": "3.5.26"
 			}
 		},
 		"node_modules/@vue/devtools-api": {
@@ -1400,67 +1401,69 @@
 			"license": "MIT"
 		},
 		"node_modules/@vue/reactivity": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
-			"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz",
+			"integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==",
 			"license": "MIT",
 			"dependencies": {
-				"@vue/shared": "3.5.24"
+				"@vue/shared": "3.5.26"
 			}
 		},
 		"node_modules/@vue/runtime-core": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
-			"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz",
+			"integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==",
 			"license": "MIT",
 			"dependencies": {
-				"@vue/reactivity": "3.5.24",
-				"@vue/shared": "3.5.24"
+				"@vue/reactivity": "3.5.26",
+				"@vue/shared": "3.5.26"
 			}
 		},
 		"node_modules/@vue/runtime-dom": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
-			"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz",
+			"integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==",
 			"license": "MIT",
 			"dependencies": {
-				"@vue/reactivity": "3.5.24",
-				"@vue/runtime-core": "3.5.24",
-				"@vue/shared": "3.5.24",
-				"csstype": "^3.1.3"
+				"@vue/reactivity": "3.5.26",
+				"@vue/runtime-core": "3.5.26",
+				"@vue/shared": "3.5.26",
+				"csstype": "^3.2.3"
 			}
 		},
 		"node_modules/@vue/server-renderer": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
-			"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz",
+			"integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==",
 			"license": "MIT",
 			"dependencies": {
-				"@vue/compiler-ssr": "3.5.24",
-				"@vue/shared": "3.5.24"
+				"@vue/compiler-ssr": "3.5.26",
+				"@vue/shared": "3.5.26"
 			},
 			"peerDependencies": {
-				"vue": "3.5.24"
+				"vue": "3.5.26"
 			}
 		},
 		"node_modules/@vue/shared": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz",
-			"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz",
+			"integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==",
 			"license": "MIT"
 		},
 		"node_modules/@xterm/addon-fit": {
-			"version": "0.10.0",
-			"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
-			"integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==",
-			"peerDependencies": {
-				"@xterm/xterm": "^5.0.0"
-			}
+			"version": "0.11.0",
+			"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz",
+			"integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==",
+			"license": "MIT"
 		},
 		"node_modules/@xterm/xterm": {
-			"version": "5.5.0",
-			"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
-			"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
+			"version": "6.0.0",
+			"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz",
+			"integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==",
+			"license": "MIT",
+			"workspaces": [
+				"addons/*"
+			]
 		},
 		"node_modules/acorn": {
 			"version": "8.15.0",
@@ -1993,9 +1996,9 @@
 			}
 		},
 		"node_modules/csstype": {
-			"version": "3.1.3",
-			"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
-			"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+			"version": "3.2.3",
+			"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+			"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
 			"license": "MIT"
 		},
 		"node_modules/data-view-buffer": {
@@ -2151,9 +2154,9 @@
 			"dev": true
 		},
 		"node_modules/entities": {
-			"version": "4.5.0",
-			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz",
+			"integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==",
 			"license": "BSD-2-Clause",
 			"engines": {
 				"node": ">=0.12"
@@ -2351,9 +2354,9 @@
 			}
 		},
 		"node_modules/esbuild": {
-			"version": "0.25.8",
-			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
-			"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
+			"version": "0.27.2",
+			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+			"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
 			"hasInstallScript": true,
 			"license": "MIT",
 			"bin": {
@@ -2363,32 +2366,32 @@
 				"node": ">=18"
 			},
 			"optionalDependencies": {
-				"@esbuild/aix-ppc64": "0.25.8",
-				"@esbuild/android-arm": "0.25.8",
-				"@esbuild/android-arm64": "0.25.8",
-				"@esbuild/android-x64": "0.25.8",
-				"@esbuild/darwin-arm64": "0.25.8",
-				"@esbuild/darwin-x64": "0.25.8",
-				"@esbuild/freebsd-arm64": "0.25.8",
-				"@esbuild/freebsd-x64": "0.25.8",
-				"@esbuild/linux-arm": "0.25.8",
-				"@esbuild/linux-arm64": "0.25.8",
-				"@esbuild/linux-ia32": "0.25.8",
-				"@esbuild/linux-loong64": "0.25.8",
-				"@esbuild/linux-mips64el": "0.25.8",
-				"@esbuild/linux-ppc64": "0.25.8",
-				"@esbuild/linux-riscv64": "0.25.8",
-				"@esbuild/linux-s390x": "0.25.8",
-				"@esbuild/linux-x64": "0.25.8",
-				"@esbuild/netbsd-arm64": "0.25.8",
-				"@esbuild/netbsd-x64": "0.25.8",
-				"@esbuild/openbsd-arm64": "0.25.8",
-				"@esbuild/openbsd-x64": "0.25.8",
-				"@esbuild/openharmony-arm64": "0.25.8",
-				"@esbuild/sunos-x64": "0.25.8",
-				"@esbuild/win32-arm64": "0.25.8",
-				"@esbuild/win32-ia32": "0.25.8",
-				"@esbuild/win32-x64": "0.25.8"
+				"@esbuild/aix-ppc64": "0.27.2",
+				"@esbuild/android-arm": "0.27.2",
+				"@esbuild/android-arm64": "0.27.2",
+				"@esbuild/android-x64": "0.27.2",
+				"@esbuild/darwin-arm64": "0.27.2",
+				"@esbuild/darwin-x64": "0.27.2",
+				"@esbuild/freebsd-arm64": "0.27.2",
+				"@esbuild/freebsd-x64": "0.27.2",
+				"@esbuild/linux-arm": "0.27.2",
+				"@esbuild/linux-arm64": "0.27.2",
+				"@esbuild/linux-ia32": "0.27.2",
+				"@esbuild/linux-loong64": "0.27.2",
+				"@esbuild/linux-mips64el": "0.27.2",
+				"@esbuild/linux-ppc64": "0.27.2",
+				"@esbuild/linux-riscv64": "0.27.2",
+				"@esbuild/linux-s390x": "0.27.2",
+				"@esbuild/linux-x64": "0.27.2",
+				"@esbuild/netbsd-arm64": "0.27.2",
+				"@esbuild/netbsd-x64": "0.27.2",
+				"@esbuild/openbsd-arm64": "0.27.2",
+				"@esbuild/openbsd-x64": "0.27.2",
+				"@esbuild/openharmony-arm64": "0.27.2",
+				"@esbuild/sunos-x64": "0.27.2",
+				"@esbuild/win32-arm64": "0.27.2",
+				"@esbuild/win32-ia32": "0.27.2",
+				"@esbuild/win32-x64": "0.27.2"
 			}
 		},
 		"node_modules/escape-string-regexp": {
@@ -2958,9 +2961,9 @@
 			}
 		},
 		"node_modules/femtocrank": {
-			"version": "2.4.11",
-			"resolved": "https://registry.npmjs.org/femtocrank/-/femtocrank-2.4.11.tgz",
-			"integrity": "sha512-IDEcr8+w5/ux58F2N1heaxse7BvjJ0mJfJcCGx15QZCn8wi+hiQaIBud0ctlzRR3+SPmy7UCY5Ym+ZeyE4Bslg==",
+			"version": "2.4.12",
+			"resolved": "https://registry.npmjs.org/femtocrank/-/femtocrank-2.4.12.tgz",
+			"integrity": "sha512-X2a4WVG1ADGjQcULUyH2FJ4njJNZobfP+iPO1MpAEtWRyVEDxps6dmktbOb3igoyCxNObF0T0uKwUC7zV21C0A==",
 			"license": "AGPL-3.0"
 		},
 		"node_modules/file-entry-cache": {
@@ -4604,19 +4607,19 @@
 			"license": "ISC"
 		},
 		"node_modules/picocrank": {
-			"version": "1.9.0",
-			"resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.9.0.tgz",
-			"integrity": "sha512-51Ej4F0tgUp3JxywmiMW/IR6orTjs0aXsWMAbMHqYQNmFxmsR3SgQY7J9dhTCq27BMP43KGtGdx9dcItz23JkQ==",
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.10.0.tgz",
+			"integrity": "sha512-rpuFopcko5jKuQpnqpvN0Umg9M2qmDI6Z/tVzbnT9PSplwJmtw9wTjgDHyQXujoBBULyurRqtIeQKDPNFLlHOQ==",
 			"license": "ISC",
 			"dependencies": {
-				"@hugeicons/core-free-icons": "^2.0.0",
-				"@hugeicons/vue": "^1.0.3",
-				"@vitejs/plugin-vue": "^6.0.2",
-				"femtocrank": "^2.4.11",
+				"@hugeicons/core-free-icons": "^3.1.0",
+				"@hugeicons/vue": "^1.0.4",
+				"@vitejs/plugin-vue": "^6.0.3",
+				"femtocrank": "^2.4.12",
 				"unplugin-vue-components": "^30.0.0",
-				"vite": "^7.2.4",
-				"vue": "^3.5.24",
-				"vue-router": "^4.6.3"
+				"vite": "^7.3.0",
+				"vue": "^3.5.26",
+				"vue-router": "^4.6.4"
 			}
 		},
 		"node_modules/picomatch": {
@@ -6119,12 +6122,12 @@
 			}
 		},
 		"node_modules/vite": {
-			"version": "7.2.6",
-			"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz",
-			"integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
+			"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
 			"license": "MIT",
 			"dependencies": {
-				"esbuild": "^0.25.0",
+				"esbuild": "^0.27.0",
 				"fdir": "^6.5.0",
 				"picomatch": "^4.0.3",
 				"postcss": "^8.5.6",
@@ -6222,16 +6225,16 @@
 			}
 		},
 		"node_modules/vue": {
-			"version": "3.5.24",
-			"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
-			"integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
+			"version": "3.5.26",
+			"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz",
+			"integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==",
 			"license": "MIT",
 			"dependencies": {
-				"@vue/compiler-dom": "3.5.24",
-				"@vue/compiler-sfc": "3.5.24",
-				"@vue/runtime-dom": "3.5.24",
-				"@vue/server-renderer": "3.5.24",
-				"@vue/shared": "3.5.24"
+				"@vue/compiler-dom": "3.5.26",
+				"@vue/compiler-sfc": "3.5.26",
+				"@vue/runtime-dom": "3.5.26",
+				"@vue/server-renderer": "3.5.26",
+				"@vue/shared": "3.5.26"
 			},
 			"peerDependencies": {
 				"typescript": "*"
@@ -6243,13 +6246,13 @@
 			}
 		},
 		"node_modules/vue-i18n": {
-			"version": "11.2.2",
-			"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.2.2.tgz",
-			"integrity": "sha512-ULIKZyRluUPRCZmihVgUvpq8hJTtOqnbGZuv4Lz+byEKZq4mU0g92og414l6f/4ju+L5mORsiUuEPYrAuX2NJg==",
+			"version": "11.2.7",
+			"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.2.7.tgz",
+			"integrity": "sha512-LPv8bAY5OA0UvFEXl4vBQOBqJzRrlExy92tWgRuwW7tbykHf7CH71G2Y4TM2OwGcIS4+hyqKHS2EVBqaYwPY9Q==",
 			"license": "MIT",
 			"dependencies": {
-				"@intlify/core-base": "11.2.2",
-				"@intlify/shared": "11.2.2",
+				"@intlify/core-base": "11.2.7",
+				"@intlify/shared": "11.2.7",
 				"@vue/devtools-api": "^6.5.0"
 			},
 			"engines": {
@@ -6263,9 +6266,9 @@
 			}
 		},
 		"node_modules/vue-router": {
-			"version": "4.6.3",
-			"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz",
-			"integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==",
+			"version": "4.6.4",
+			"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
+			"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
 			"license": "MIT",
 			"dependencies": {
 				"@vue/devtools-api": "^6.6.4"

+ 9 - 9
frontend/package.json

@@ -24,17 +24,17 @@
 	"dependencies": {
 		"@connectrpc/connect": "^2.1.1",
 		"@connectrpc/connect-web": "^2.1.1",
-		"@hugeicons/core-free-icons": "^2.0.0",
-		"@hugeicons/vue": "^1.0.3",
-		"@vitejs/plugin-vue": "^6.0.2",
-		"@xterm/addon-fit": "^0.10.0",
-		"@xterm/xterm": "^5.5.0",
+		"@hugeicons/core-free-icons": "^3.1.0",
+		"@hugeicons/vue": "^1.0.4",
+		"@vitejs/plugin-vue": "^6.0.3",
+		"@xterm/addon-fit": "^0.11.0",
+		"@xterm/xterm": "^6.0.0",
 		"iconify-icon": "^3.0.2",
-		"picocrank": "^1.9.0",
+		"picocrank": "^1.10.0",
 		"standard": "^17.1.2",
 		"unplugin-vue-components": "^30.0.0",
-		"vite": "^7.2.6",
-		"vue-i18n": "^11.2.2",
-		"vue-router": "^4.6.3"
+		"vite": "^7.3.0",
+		"vue-i18n": "^11.2.7",
+		"vue-router": "^4.6.4"
 	}
 }

+ 11 - 1
frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts

@@ -1,4 +1,4 @@
-// @generated by protoc-gen-es v2.10.1
+// @generated by protoc-gen-es v2.10.2
 // @generated from file olivetin/api/v1/olivetin.proto (package olivetin.api.v1, syntax proto3)
 /* eslint-disable */
 
@@ -686,6 +686,16 @@ export declare type ValidateArgumentTypeRequest = Message<"olivetin.api.v1.Valid
    * @generated from field: string type = 2;
    */
   type: string;
+
+  /**
+   * @generated from field: string binding_id = 3;
+   */
+  bindingId: string;
+
+  /**
+   * @generated from field: string argument_name = 4;
+   */
+  argumentName: string;
 };
 
 /**

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js


+ 19 - 15
frontend/resources/vue/App.vue

@@ -19,7 +19,9 @@
     </Header>
 
     <div id="layout">
-        <Sidebar ref="sidebar" id = "mainnav" v-if="showNavigation" />
+        <Navigation ref="navigation" v-if="showNavigation">
+            <Sidebar ref="sidebar" id = "mainnav" v-if="showNavigation" />
+        </Navigation>
 
 		<div id="content" initial-martial-complete="{{ hasLoaded }}">
             <main title="Main content">
@@ -77,6 +79,7 @@
 import { ref, onMounted, computed } from 'vue';
 import { useRouter } from 'vue-router';
 import Sidebar from 'picocrank/vue/components/Sidebar.vue';
+import Navigation from 'picocrank/vue/components/Navigation.vue';
 import Header from 'picocrank/vue/components/Header.vue';
 import { HugeiconsIcon } from '@hugeicons/vue'
 import { Menu01Icon } from '@hugeicons/core-free-icons'
@@ -91,6 +94,7 @@ const { t, locale } = useI18n();
 const router = useRouter();
 
 const sidebar = ref(null);
+const navigation = ref(null);
 const username = ref('notset');
 const isLoggedIn = ref(false);
 const serverConnection = ref(true);
@@ -181,7 +185,7 @@ function updateHeaderFromInit() {
         showLoginLink.value = false
     }
 
-    renderSidebar()
+    renderNavigation()
 
     if (window.initResponse.loginRequired) {
         router.push('/login')
@@ -189,19 +193,19 @@ function updateHeaderFromInit() {
     }
 }
 
-function renderSidebar() {
-    if (!sidebar.value) {
+function renderNavigation() {
+    if (!navigation.value) {
         return
     }
 
     const rootDashboards = window.initResponse?.rootDashboards || []
 
-    if (typeof sidebar.value.clear === 'function') {
-        sidebar.value.clear()
+    if (typeof navigation.value.clear === 'function') {
+        navigation.value.clear()
     }
 
     for (const rootDashboard of rootDashboards) {
-        sidebar.value.addNavigationLink({
+        navigation.value.addNavigationLink({
             id: rootDashboard,
             name: rootDashboard,
             title: rootDashboard,
@@ -210,15 +214,15 @@ function renderSidebar() {
         })
     }
 
-    sidebar.value.addSeparator()
-    sidebar.value.addRouterLink('Entities', t('nav.entities'))
+    navigation.value.addSeparator()
+    navigation.value.addRouterLink('Entities', t('nav.entities'))
 
     if (showLogs.value) {
-        sidebar.value.addRouterLink('Logs', t('nav.logs'))
+        navigation.value.addRouterLink('Logs', t('nav.logs'))
     }
 
     if (showDiagnostics.value) {
-        sidebar.value.addRouterLink('Diagnostics', t('nav.diagnostics'))
+        navigation.value.addRouterLink('Diagnostics', t('nav.diagnostics'))
     }
 }
 
@@ -257,9 +261,9 @@ function changeLanguage() {
         languagePreference.value = selectedLanguage.value
     }
 
-    // Update sidebar with new translations
-    if (sidebar.value) {
-        renderSidebar()
+    // Update navigation with new translations
+    if (navigation.value) {
+        renderNavigation()
     }
 
     closeLanguageDialog()
@@ -347,4 +351,4 @@ onMounted(() => {
     color: var(--fg2, #555);
     margin-bottom: 1rem;
 }
-</style>
+</style>

+ 38 - 1
frontend/resources/vue/components/DashboardComponentDirectory.vue

@@ -1,11 +1,13 @@
 <template>
     <button @click="navigateToDirectory">
-        {{ component.title }}
+        <span class="icon" v-html="unicodeIcon"></span>
+        <span class="title">{{ component.title }}</span>
     </button>
 </template>
 
 <script setup>
 import { useRouter } from 'vue-router'
+import { computed } from 'vue'
 
 const router = useRouter()
 
@@ -16,6 +18,18 @@ const props = defineProps({
     }
 })
 
+function getUnicodeIcon(icon) {
+    if (icon === '' || !icon) {
+        return '&#x1f4c1;' // Default folder icon
+    } else {
+        return unescape(icon)
+    }
+}
+
+const unicodeIcon = computed(() => {
+    return getUnicodeIcon(props.component.icon)
+})
+
 function navigateToDirectory() {
     const params = { title: props.component.title }
     
@@ -34,9 +48,18 @@ function navigateToDirectory() {
 }
 
 button {
+    display: flex;
+    flex-direction: column;
+    flex-grow: 1;
+    justify-content: center;
+    padding: 0.5em;
     box-shadow: 0 0 .6em #aaa;
     background-color: #fff;
     border-radius: .7em;
+    border: 1px solid #ccc;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    font-size: .85em;
 }
 
 button:hover {
@@ -44,16 +67,30 @@ button:hover {
     border-color: #999;
 }
 
+button .icon {
+    font-size: 3em;
+    flex-grow: 1;
+    align-content: center;
+}
+
+button .title {
+    font-weight: 500;
+    padding: 0.2em;
+}
+
 @media (prefers-color-scheme: dark) {
     button {
         box-shadow: 0 0 .6em #000;
         background-color: #111;
         border-color: #000;
+        color: #fff;
     }
 
     button:hover {
         background-color: #222;
         border-color: #000;
+        box-shadow: 0 0 6px #444;
+        color: #fff;
     }
 }
 

+ 30 - 5
frontend/resources/vue/views/ArgumentForm.vue

@@ -25,7 +25,9 @@
                 </option>
               </select>
               
-              <component v-else :is="getInputComponent(arg)" :id="arg.name" :name="arg.name" :value="getArgumentValue(arg)"
+              <component v-else :is="getInputComponent(arg)" :id="arg.name" :name="arg.name" 
+                :value="(arg.type === 'checkbox' || arg.type === 'confirmation') ? undefined : getArgumentValue(arg)"
+                :checked="(arg.type === 'checkbox' || arg.type === 'confirmation') ? getArgumentValue(arg) : undefined"
                 :list="arg.suggestions ? `${arg.name}-choices` : undefined" 
                 :type="getInputComponent(arg) !== 'select' ? getInputType(arg) : undefined"
                 :rows="arg.type === 'raw_string_multiline' ? 5 : undefined"
@@ -109,15 +111,26 @@ async function setup() {
       confirmationChecked.value = checkedValue
     } else {
       const paramValue = getQueryParamValue(arg.name)
-      argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
+      if (arg.type === 'checkbox') {
+        // For checkboxes, handle boolean default values properly
+        if (paramValue !== null) {
+          argValues.value[arg.name] = paramValue === '1' || paramValue === 'true' || paramValue === true
+        } else if (arg.defaultValue !== undefined && arg.defaultValue !== '') {
+          argValues.value[arg.name] = arg.defaultValue === '1' || arg.defaultValue === 'true' || arg.defaultValue === true
+        } else {
+          argValues.value[arg.name] = false
+        }
+      } else {
+        argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
+      }
     }
   })
 
   // Run initial validation on all fields after DOM is updated
   await nextTick()
   for (const arg of actionArguments.value) {
-    if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '' && arg.type !== 'confirmation') {
-      await validateArgument(arg, argValues.value[arg.name])
+    if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '' && arg.type !== 'confirmation' && arg.type !== 'checkbox') {
+      await validateArgument(arg, argValues.value[arg.name] || '')
     }
   }
 }
@@ -212,10 +225,22 @@ async function validateArgument(arg, value) {
     return
   }
 
+  // Skip validation for checkbox and confirmation - they're always valid
+  if (arg.type === 'checkbox' || arg.type === 'confirmation') {
+    const inputElement = document.getElementById(arg.name)
+    if (inputElement) {
+      inputElement.setCustomValidity('')
+    }
+    delete formErrors.value[arg.name]
+    return
+  }
+
   try {
     const validateArgumentTypeArgs = {
       value: value,
-      type: arg.type
+      type: arg.type,
+      bindingId: props.bindingId,
+      argumentName: arg.name
     }
 
     const validation = await window.client.validateArgumentType(validateArgumentTypeArgs)

+ 15 - 0
integration-tests/lib/elements.js

@@ -137,3 +137,18 @@ export async function getActionButton (webdriver, title) {
 
   return buttons[0]
 }
+
+export async function getTerminalBuffer() {
+  try {
+    const output = await webdriver.executeScript(`
+      if (window.terminal && window.terminal.getBufferAsString) {
+        return window.terminal.getBufferAsString();
+      }
+      return null;
+    `)
+    return output
+  } catch (e) {
+    console.log('[getTerminalBuffer] Error:', e.message)
+    return null
+  }
+}

+ 27 - 26
integration-tests/package-lock.json

@@ -12,10 +12,10 @@
         "wait-on": "^9.0.3"
       },
       "devDependencies": {
-        "chai": "^6.2.1",
-        "eslint": "^9.39.1",
+        "chai": "^6.2.2",
+        "eslint": "^9.39.2",
         "mocha": "^11.7.5",
-        "selenium-webdriver": "^4.38.0"
+        "selenium-webdriver": "^4.39.0"
       }
     },
     "node_modules/@aashutoshrathi/word-wrap": {
@@ -28,9 +28,9 @@
       }
     },
     "node_modules/@bazel/runfiles": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.3.1.tgz",
-      "integrity": "sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==",
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.5.0.tgz",
+      "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==",
       "dev": true,
       "license": "Apache-2.0"
     },
@@ -142,9 +142,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "9.39.1",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
-      "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+      "version": "9.39.2",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+      "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -490,9 +490,9 @@
       }
     },
     "node_modules/chai": {
-      "version": "6.2.1",
-      "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz",
-      "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==",
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+      "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -818,9 +818,9 @@
       }
     },
     "node_modules/eslint": {
-      "version": "9.39.1",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
-      "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+      "version": "9.39.2",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+      "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -830,7 +830,7 @@
         "@eslint/config-helpers": "^0.4.2",
         "@eslint/core": "^0.17.0",
         "@eslint/eslintrc": "^3.3.1",
-        "@eslint/js": "9.39.1",
+        "@eslint/js": "9.39.2",
         "@eslint/plugin-kit": "^0.4.1",
         "@humanfs/node": "^0.16.6",
         "@humanwhocodes/module-importer": "^1.0.1",
@@ -1191,9 +1191,9 @@
       }
     },
     "node_modules/glob/node_modules/brace-expansion": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+      "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1455,10 +1455,11 @@
       }
     },
     "node_modules/js-yaml": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+      "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "argparse": "^2.0.1"
       },
@@ -1952,9 +1953,9 @@
       "dev": true
     },
     "node_modules/selenium-webdriver": {
-      "version": "4.38.0",
-      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.38.0.tgz",
-      "integrity": "sha512-5/UXXFSQmn7FGQkbcpAqvfhzflUdMWtT7QqpEgkFD6Q6rDucxB5EUfzgjmr6JbUj30QodcW3mDXehzoeS/Vy5w==",
+      "version": "4.39.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.39.0.tgz",
+      "integrity": "sha512-NAs9jCU+UeZ/ZmRb8R6zOp7N8eMklefdBYASnaRmCNXdgFE8w3OCxxZmLixkwqnGDHY5VF7hCulfw1Mls43N/A==",
       "dev": true,
       "funding": [
         {
@@ -1968,7 +1969,7 @@
       ],
       "license": "Apache-2.0",
       "dependencies": {
-        "@bazel/runfiles": "^6.3.1",
+        "@bazel/runfiles": "^6.5.0",
         "jszip": "^3.10.1",
         "tmp": "^0.2.5",
         "ws": "^8.18.3"

+ 3 - 3
integration-tests/package.json

@@ -11,10 +11,10 @@
   "author": "",
   "license": "AGPL-3.0-only",
   "devDependencies": {
-    "chai": "^6.2.1",
-    "eslint": "^9.39.1",
+    "chai": "^6.2.2",
+    "eslint": "^9.39.2",
     "mocha": "^11.7.5",
-    "selenium-webdriver": "^4.38.0"
+    "selenium-webdriver": "^4.39.0"
   },
   "dependencies": {
     "wait-on": "^9.0.3"

+ 150 - 0
integration-tests/tests/checkbox/checkbox.mjs

@@ -0,0 +1,150 @@
+import { describe, it, before, after } from 'mocha'
+import { expect } from 'chai'
+import { By, Condition } from 'selenium-webdriver'
+import {
+  getRootAndWait,
+  getActionButton,
+  takeScreenshotOnFailure,
+  getTerminalBuffer,
+} from '../../lib/elements.js'
+
+async function openCheckboxArgumentForm() {
+  await getRootAndWait()
+  const btn = await getActionButton(webdriver, 'Test checkbox argument')
+  await btn.click()
+
+  await webdriver.wait(
+    new Condition('wait for argument form page', async () => {
+      const url = await webdriver.getCurrentUrl()
+      return url.includes('/actionBinding/') && url.includes('/argumentForm')
+    }),
+    5000
+  )
+}
+
+async function getCheckboxInput() {
+  return await webdriver.findElement(By.id('confirm'))
+}
+
+async function submitCheckboxForm() {
+  const submitButton = await webdriver.findElement(By.css('button[name="start"]'))
+  await submitButton.click()
+}
+
+async function waitForLogsPage() {
+  await webdriver.wait(
+    new Condition('wait for logs page', async () => {
+      const url = await webdriver.getCurrentUrl()
+      return url.includes('/logs/') && !url.endsWith('/logs')
+    }),
+    5000
+  )
+}
+
+async function waitForExecutionComplete() {
+  await webdriver.wait(
+    new Condition('wait for execution status', async () => {
+      const statusElements = await webdriver.findElements(By.id('execution-dialog-status'))
+      return statusElements.length > 0
+    }),
+    5000
+  )
+
+  await webdriver.wait(
+    new Condition('wait for execution to finish', async () => {
+      try {
+        const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
+        const statusText = await statusElement.getText()
+        return !statusText.includes('Executing')
+      } catch (e) {
+        return false
+      }
+    }),
+    5000
+  )
+
+  // Small delay to allow terminal to write output
+  await webdriver.sleep(500)
+}
+
+async function waitForTerminalOutput(expectedValue) {
+  await webdriver.wait(
+    new Condition(`wait for checkbox value ${expectedValue} in output`, async () => {
+      try {
+        const terminalReady = await webdriver.executeScript(`
+          return !!(window.terminal && window.terminal.getBufferAsString);
+        `)
+        if (!terminalReady) {
+          return false
+        }
+        
+        const output = await getTerminalBuffer()
+        if (!output) {
+          return false
+        }
+        
+        return output.trim().includes(`Checkbox value: ${expectedValue}`)
+      } catch (e) {
+        return false
+      }
+    }),
+    5000
+  )
+}
+
+describe('config: checkbox', function () {
+  before(async function () {
+    await runner.start('checkbox')
+  })
+
+  after(async () => {
+    await runner.stop()
+  })
+
+  afterEach(function () {
+    takeScreenshotOnFailure(this.currentTest, webdriver)
+  })
+
+  it('Checkbox argument is rendered as a checkbox input', async function () {
+    await openCheckboxArgumentForm()
+
+    const checkboxInput = await getCheckboxInput()
+
+    expect(await checkboxInput.getTagName()).to.equal('input')
+    expect(await checkboxInput.getAttribute('type')).to.equal('checkbox')
+
+    const label = await webdriver.findElement(By.css('label[for="confirm"]'))
+    expect(await label.getText()).to.contain('Confirm option')
+  })
+
+  it('Checkbox argument submits 0 by default when unchecked', async function () {
+    this.timeout(15000)
+    await openCheckboxArgumentForm()
+
+    const checkboxInput = await getCheckboxInput()
+    expect(await checkboxInput.isSelected()).to.be.false
+
+    await submitCheckboxForm()
+    await waitForLogsPage()
+    await waitForExecutionComplete()
+    await waitForTerminalOutput('0')
+  })
+
+  it('Checkbox argument can be toggled and submitted', async function () {
+    this.timeout(15000)
+    await openCheckboxArgumentForm()
+
+    const checkboxInput = await getCheckboxInput()
+    await checkboxInput.click()
+    await webdriver.sleep(100)
+
+    expect(await checkboxInput.isSelected()).to.be.true
+
+    await submitCheckboxForm()
+    await waitForLogsPage()
+    await waitForExecutionComplete()
+    await waitForTerminalOutput('1')
+  })
+})
+
+

+ 18 - 0
integration-tests/tests/checkbox/config.yaml

@@ -0,0 +1,18 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:1337
+
+logLevel: "DEBUG"
+checkForUpdates: false
+
+actions:
+  - title: Test checkbox argument
+    shell: "echo 'Checkbox value: {{ confirm }}'"
+    icon: ping
+    arguments:
+      - name: confirm
+        title: Confirm option
+        type: checkbox
+        description: "When checked: 1, when unchecked: 0"
+        default: false
+
+

+ 17 - 0
integration-tests/tests/customTheme/config.yaml

@@ -0,0 +1,17 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:1337
+
+logLevel: "DEBUG"
+checkForUpdates: false
+
+themeName: "test-theme"
+
+actions:
+  - title: Test action
+    shell: "echo 'Hello from themed OliveTin'"
+    icon: ping
+
+
+
+
+

+ 14 - 0
integration-tests/tests/customTheme/custom-webui/themes/test-theme/theme.css

@@ -0,0 +1,14 @@
+/* Custom theme for integration testing */
+body {
+    background-color: #ff6b6b;
+}
+
+/* Add a distinctive style that we can test for */
+#content {
+    border: 5px solid #4ecdc4;
+}
+
+
+
+
+

+ 82 - 0
integration-tests/tests/customTheme/customTheme.mjs

@@ -0,0 +1,82 @@
+import { describe, it, before, after } from 'mocha'
+import { expect } from 'chai'
+import { By } from 'selenium-webdriver'
+import {
+  getRootAndWait,
+  takeScreenshotOnFailure,
+} from '../../lib/elements.js'
+
+describe('config: customTheme', function () {
+  before(async function () {
+    await runner.start('customTheme')
+  })
+
+  after(async () => {
+    await runner.stop()
+  })
+
+  afterEach(function () {
+    takeScreenshotOnFailure(this.currentTest, webdriver)
+  })
+
+  it('Custom theme CSS is loaded and accessible', async function () {
+    await getRootAndWait()
+
+    // Fetch the theme CSS directly
+    const themeCssUrl = runner.baseUrl() + 'theme.css'
+    await webdriver.get(themeCssUrl)
+
+    // Wait for the page to load
+    await webdriver.sleep(500)
+
+    // Get the page source (should be CSS content)
+    const pageSource = await webdriver.getPageSource()
+
+    // Verify the theme CSS contains our custom styles
+    expect(pageSource).to.include('background-color: #ff6b6b')
+    expect(pageSource).to.include('border: 5px solid #4ecdc4')
+    expect(pageSource).to.include('Custom theme for integration testing')
+  })
+
+  it('Custom theme styles are applied to the page', async function () {
+    await getRootAndWait()
+
+    // Wait a bit for CSS to be fully loaded
+    await webdriver.sleep(500)
+
+    // Get computed background color of body
+    const backgroundColor = await webdriver.executeScript(
+      'return window.getComputedStyle(document.body).backgroundColor'
+    )
+
+    // The background color should be rgb(255, 107, 107) which is #ff6b6b
+    // Different browsers might return different formats, so we check for the RGB values
+    expect(backgroundColor).to.include('255, 107, 107')
+
+    // Get computed border color of content element
+    const borderColor = await webdriver.executeScript(
+      'const el = document.getElementById("content"); return el ? window.getComputedStyle(el).borderColor : null'
+    )
+
+    // The border color should be rgb(78, 205, 196) which is #4ecdc4
+    // borderColor might return "rgb(78, 205, 196)" or similar format
+    expect(borderColor).to.include('78, 205, 196')
+  })
+
+  it('Theme CSS link is present in the HTML', async function () {
+    await getRootAndWait()
+
+    // Find the theme.css link in the head
+    const themeLink = await webdriver.findElement(By.css('link[href="/theme.css"]'))
+
+    expect(themeLink).to.not.be.null
+
+    // Verify it's a stylesheet
+    const rel = await themeLink.getAttribute('rel')
+    expect(rel).to.equal('stylesheet')
+
+    const type = await themeLink.getAttribute('type')
+    expect(type).to.equal('text/css')
+  })
+})
+

+ 2 - 0
proto/olivetin/api/v1/olivetin.proto

@@ -164,6 +164,8 @@ message GetActionLogsResponse {
 message ValidateArgumentTypeRequest {
 	string value = 1;
 	string type = 2;
+	string binding_id = 3;
+	string argument_name = 4;
 }
 
 message ValidateArgumentTypeResponse {

+ 22 - 3
service/gen/olivetin/api/v1/olivetin.pb.go

@@ -1,6 +1,6 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.36.10
+// 	protoc-gen-go v1.36.11
 // 	protoc        (unknown)
 // source: olivetin/api/v1/olivetin.proto
 
@@ -1509,6 +1509,8 @@ type ValidateArgumentTypeRequest struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
 	Value         string                 `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
 	Type          string                 `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
+	BindingId     string                 `protobuf:"bytes,3,opt,name=binding_id,json=bindingId,proto3" json:"binding_id,omitempty"`
+	ArgumentName  string                 `protobuf:"bytes,4,opt,name=argument_name,json=argumentName,proto3" json:"argument_name,omitempty"`
 	unknownFields protoimpl.UnknownFields
 	sizeCache     protoimpl.SizeCache
 }
@@ -1557,6 +1559,20 @@ func (x *ValidateArgumentTypeRequest) GetType() string {
 	return ""
 }
 
+func (x *ValidateArgumentTypeRequest) GetBindingId() string {
+	if x != nil {
+		return x.BindingId
+	}
+	return ""
+}
+
+func (x *ValidateArgumentTypeRequest) GetArgumentName() string {
+	if x != nil {
+		return x.ArgumentName
+	}
+	return ""
+}
+
 type ValidateArgumentTypeResponse struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
 	Valid         bool                   `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
@@ -3946,10 +3962,13 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" +
 	"\tpage_size\x18\x03 \x01(\x03R\bpageSize\x12\x1f\n" +
 	"\vtotal_count\x18\x04 \x01(\x03R\n" +
 	"totalCount\x12!\n" +
-	"\fstart_offset\x18\x05 \x01(\x03R\vstartOffset\"G\n" +
+	"\fstart_offset\x18\x05 \x01(\x03R\vstartOffset\"\x8b\x01\n" +
 	"\x1bValidateArgumentTypeRequest\x12\x14\n" +
 	"\x05value\x18\x01 \x01(\tR\x05value\x12\x12\n" +
-	"\x04type\x18\x02 \x01(\tR\x04type\"V\n" +
+	"\x04type\x18\x02 \x01(\tR\x04type\x12\x1d\n" +
+	"\n" +
+	"binding_id\x18\x03 \x01(\tR\tbindingId\x12#\n" +
+	"\rargument_name\x18\x04 \x01(\tR\fargumentName\"V\n" +
 	"\x1cValidateArgumentTypeResponse\x12\x14\n" +
 	"\x05valid\x18\x01 \x01(\bR\x05valid\x12 \n" +
 	"\vdescription\x18\x02 \x01(\tR\vdescription\"K\n" +

+ 34 - 34
service/go.mod

@@ -20,7 +20,7 @@ require (
 	github.com/jamesread/golure v0.0.0-20250919212919-976d085a100c
 	github.com/knadh/koanf/parsers/yaml v1.1.0
 	github.com/knadh/koanf/providers/env v1.1.0
-	github.com/knadh/koanf/providers/file v1.2.0
+	github.com/knadh/koanf/providers/file v1.2.1
 	github.com/knadh/koanf/providers/rawbytes v1.0.0
 	github.com/knadh/koanf/v2 v2.3.0
 	github.com/prometheus/client_golang v1.23.2
@@ -28,25 +28,25 @@ require (
 	github.com/sirupsen/logrus v1.9.3
 	github.com/stretchr/testify v1.11.1
 	go.akshayshah.org/connectproto v0.6.0
-	golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6
-	golang.org/x/oauth2 v0.33.0
-	golang.org/x/sys v0.38.0
-	google.golang.org/protobuf v1.36.10
+	golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
+	golang.org/x/oauth2 v0.34.0
+	golang.org/x/sys v0.39.0
+	google.golang.org/protobuf v1.36.11
 	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
-	buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.10-20250718181942-e35f9b667443.1 // indirect
-	buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.10-20250109164928-1da0de137947.1 // indirect
-	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect
-	buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251027152159-f1066ce064ca.2 // indirect
-	buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.10-20251027152159-f1066ce064ca.1 // indirect
-	buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.10-20241007202033-cf42259fcbfc.1 // indirect
+	buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1 // indirect
+	buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1 // indirect
+	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect
+	buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251202164234-62b14f0b533c.2 // indirect
+	buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20251202164234-62b14f0b533c.1 // indirect
+	buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 // indirect
 	buf.build/go/app v0.2.0 // indirect
 	buf.build/go/bufplugin v0.9.0 // indirect
 	buf.build/go/bufprivateusage v0.1.0 // indirect
 	buf.build/go/interrupt v1.1.0 // indirect
-	buf.build/go/protovalidate v1.0.1 // indirect
+	buf.build/go/protovalidate v1.1.0 // indirect
 	buf.build/go/protoyaml v0.6.0 // indirect
 	buf.build/go/spdx v0.2.0 // indirect
 	buf.build/go/standard v0.1.0 // indirect
@@ -57,7 +57,7 @@ require (
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/bufbuild/protocompile v0.14.2-0.20251120233202-3f9009bcd6c8 // indirect
+	github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e // indirect
 	github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/cli/browser v1.3.0 // indirect
@@ -68,7 +68,7 @@ require (
 	github.com/cristalhq/acmd v0.12.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/distribution/reference v0.6.0 // indirect
-	github.com/docker/cli v29.0.2+incompatible // indirect
+	github.com/docker/cli v29.1.3+incompatible // indirect
 	github.com/docker/distribution v2.8.3+incompatible // indirect
 	github.com/docker/docker v28.5.2+incompatible // indirect
 	github.com/docker/docker-credential-helpers v0.9.4 // indirect
@@ -90,11 +90,11 @@ require (
 	github.com/gofrs/flock v0.13.0 // indirect
 	github.com/google/cel-go v0.26.1 // indirect
 	github.com/google/go-cmp v0.7.0 // indirect
-	github.com/google/go-containerregistry v0.20.6 // indirect
+	github.com/google/go-containerregistry v0.20.7 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jdx/go-netrc v1.0.0 // indirect
-	github.com/klauspost/compress v1.18.1 // indirect
+	github.com/klauspost/compress v1.18.2 // indirect
 	github.com/klauspost/pgzip v1.2.6 // indirect
 	github.com/knadh/koanf/maps v0.1.2 // indirect
 	github.com/mattn/go-colorable v0.1.14 // indirect
@@ -104,7 +104,7 @@ require (
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/moby/docker-image-spec v1.3.1 // indirect
 	github.com/moby/term v0.5.2 // indirect
-	github.com/morikuni/aec v1.0.0 // indirect
+	github.com/morikuni/aec v1.1.0 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.1.1 // indirect
@@ -119,16 +119,16 @@ require (
 	github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
 	github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
 	github.com/quic-go/qpack v0.6.0 // indirect
-	github.com/quic-go/quic-go v0.57.0 // indirect
+	github.com/quic-go/quic-go v0.58.0 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/rs/cors v1.11.1 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/segmentio/asm v1.2.1 // indirect
 	github.com/segmentio/encoding v0.5.3 // indirect
-	github.com/spf13/cobra v1.10.1 // indirect
+	github.com/spf13/cobra v1.10.2 // indirect
 	github.com/spf13/pflag v1.0.10 // indirect
 	github.com/stoewer/go-strcase v1.3.1 // indirect
-	github.com/tetratelabs/wazero v1.10.1 // indirect
+	github.com/tetratelabs/wazero v1.11.0 // indirect
 	github.com/tidwall/btree v1.8.1 // indirect
 	github.com/vbatts/tar-split v0.12.2 // indirect
 	go.lsp.dev/jsonrpc2 v0.10.0 // indirect
@@ -136,26 +136,26 @@ require (
 	go.lsp.dev/protocol v0.12.0 // indirect
 	go.lsp.dev/uri v0.3.0 // indirect
 	go.opentelemetry.io/auto/sdk v1.2.1 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
-	go.opentelemetry.io/otel v1.38.0 // indirect
-	go.opentelemetry.io/otel/metric v1.38.0 // indirect
-	go.opentelemetry.io/otel/trace v1.38.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
+	go.opentelemetry.io/otel v1.39.0 // indirect
+	go.opentelemetry.io/otel/metric v1.39.0 // indirect
+	go.opentelemetry.io/otel/trace v1.39.0 // indirect
 	go.uber.org/mock v0.6.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.27.1 // indirect
 	go.yaml.in/yaml/v2 v2.4.3 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
-	golang.org/x/crypto v0.45.0 // indirect
-	golang.org/x/exp/typeparams v0.0.0-20251113190631-e25ba8c21ef6 // indirect
-	golang.org/x/mod v0.30.0 // indirect
-	golang.org/x/net v0.47.0 // indirect
-	golang.org/x/sync v0.18.0 // indirect
-	golang.org/x/term v0.37.0 // indirect
-	golang.org/x/text v0.31.0 // indirect
+	golang.org/x/crypto v0.46.0 // indirect
+	golang.org/x/exp/typeparams v0.0.0-20251219203646-944ab1f22d93 // indirect
+	golang.org/x/mod v0.31.0 // indirect
+	golang.org/x/net v0.48.0 // indirect
+	golang.org/x/sync v0.19.0 // indirect
+	golang.org/x/term v0.38.0 // indirect
+	golang.org/x/text v0.32.0 // indirect
 	golang.org/x/time v0.14.0 // indirect
-	golang.org/x/tools v0.39.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
+	golang.org/x/tools v0.40.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
 	google.golang.org/grpc v1.75.1 // indirect
 	pluginrpc.com/pluginrpc v0.5.0 // indirect
 )

+ 73 - 73
service/go.sum

@@ -1,15 +1,15 @@
-buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.10-20250718181942-e35f9b667443.1 h1:FzJGrb8r7vir+P3zJ5Ebey8p54LYTYtQsrM/U35YO9Q=
-buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.10-20250718181942-e35f9b667443.1/go.mod h1:E6HwqUm4Ag7bXtg/tX7jHWO7CgpknbmeACgDax0icV0=
-buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.10-20250109164928-1da0de137947.1 h1:9hkMnVoImDlY7rTlAWIWXdkGUKOjf3YlyZeSbYT29uA=
-buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.10-20250109164928-1da0de137947.1/go.mod h1:/AouMCAeQ+kB7+RRFpdUlZe3503p18VoUNcU2AFqZXM=
-buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc=
-buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4=
-buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251027152159-f1066ce064ca.2 h1:Dbh4Edwy5qHlz1/boPAQ7T5Q7ZDMgEuQlEbXa94+JEo=
-buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251027152159-f1066ce064ca.2/go.mod h1:SqqTA3aiYVDkpDINxgbxDT6QBjkVjdqUXtbiz6DiWIg=
-buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.10-20251027152159-f1066ce064ca.1 h1:5tUFlRgcC+N2JJtjwlwyb2J4bBk/bJYLXk50zlewtzk=
-buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.10-20251027152159-f1066ce064ca.1/go.mod h1:AaYXXeRvnOc151wEuupAmn58Mh9bccKce2kk3QKMIrQ=
-buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.10-20241007202033-cf42259fcbfc.1 h1:CzM0kZcoaIr8+R4i8QVorUNRM/CqMr87i3j+w2pdpCc=
-buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.10-20241007202033-cf42259fcbfc.1/go.mod h1:bG+Fa7tcA+4pW0JdOh4h7iKjleyZIKhfVzVS10qfrnk=
+buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1 h1:zQ9C3e6FtwSZUFuKAQfpIKGFk5ZuRoGt5g35Bix55sI=
+buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1/go.mod h1:1Znr6gmYBhbxWUPRrrVnSLXQsz8bvFVw1HHJq2bI3VQ=
+buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1 h1:HwzzCRS4ZrEm1++rzSDxHnO0DOjiT1b8I/24e8a4exY=
+buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1/go.mod h1:8PRKXhgNes29Tjrnv8KdZzg3I1QceOkzibW1QK7EXv0=
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 h1:j9yeqTWEFrtimt8Nng2MIeRrpoCvQzM9/g25XTvqUGg=
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
+buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251202164234-62b14f0b533c.2 h1:eQ6XRVUaYYZFOZvBsyrOYLWbw6464s5dVnHscxa0b8w=
+buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251202164234-62b14f0b533c.2/go.mod h1:omxVRch3jEPMINnUipLsuRWoEhND6LPXELKBG7xzyDw=
+buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20251202164234-62b14f0b533c.1 h1:PdfIJUbUVKdajMVYuMdvr2Wvo+wmzGnlPEYA4bhFaWI=
+buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20251202164234-62b14f0b533c.1/go.mod h1:1JJi9jvOqRxSMa+JxiZSm57doB+db/1WYCIa2lHfc40=
+buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 h1:iGPvEJltOXUMANWf0zajcRcbiOXLD90ZwPUFvbcuv6Q=
+buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1/go.mod h1:nWVKKRA29zdt4uvkjka3i/y4mkrswyWwiu0TbdX0zts=
 buf.build/go/app v0.2.0 h1:NYaH13A+RzPb7M5vO8uZYZ2maBZI5+MS9A9tQm66fy8=
 buf.build/go/app v0.2.0/go.mod h1:0XVOYemubVbxNXVY0DnsVgWeGkcbbAvjDa1fmhBC+Wo=
 buf.build/go/bufplugin v0.9.0 h1:ktZJNP3If7ldcWVqh46XKeiYJVPxHQxCfjzVQDzZ/lo=
@@ -18,8 +18,8 @@ buf.build/go/bufprivateusage v0.1.0 h1:SzCoCcmzS3zyXHEXHeSQhGI7OTkgtljoknLzsUz9G
 buf.build/go/bufprivateusage v0.1.0/go.mod h1:GlCCJ3VVF7EqqU0CoRmo1FzAwwaKymEWSr+ty69xU5w=
 buf.build/go/interrupt v1.1.0 h1:olBuhgv9Sav4/9pkSLoxgiOsZDgM5VhRhvRpn3DL0lE=
 buf.build/go/interrupt v1.1.0/go.mod h1:ql56nXPG1oHlvZa6efNC7SKAQ/tUjS6z0mhJl0gyeRM=
-buf.build/go/protovalidate v1.0.1 h1:Fwmf08OOUuKVeMvEnDmcKxQam4PJc/zFgvVX64BhTms=
-buf.build/go/protovalidate v1.0.1/go.mod h1:SoZmvk/3ZzOVg9YSkTdm4grMAByjf8zgZq4ZNaLZXoQ=
+buf.build/go/protovalidate v1.1.0 h1:pQqEQRpOo4SqS60qkvmhLTTQU9JwzEvdyiqAtXa5SeY=
+buf.build/go/protovalidate v1.1.0/go.mod h1:bGZcPiAQDC3ErCHK3t74jSoJDFOs2JH3d7LWuTEIdss=
 buf.build/go/protoyaml v0.6.0 h1:Nzz1lvcXF8YgNZXk+voPPwdU8FjDPTUV4ndNTXN0n2w=
 buf.build/go/protoyaml v0.6.0/go.mod h1:RgUOsBu/GYKLDSIRgQXniXbNgFlGEZnQpRAUdLAFV2Q=
 buf.build/go/spdx v0.2.0 h1:IItqM0/cMxvFJJumcBuP8NrsIzMs/UYjp/6WSpq8LTw=
@@ -54,8 +54,8 @@ github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo
 github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
 github.com/bufbuild/buf v1.61.0 h1:JPaK/RM2eoheyzznW+1LxaFgN6xjBCi8s25q2kUbH9A=
 github.com/bufbuild/buf v1.61.0/go.mod h1:Xs3leBmxjL5tTnSVYfNwNXHXD1k5et3fR/tJyIyQl4s=
-github.com/bufbuild/protocompile v0.14.2-0.20251120233202-3f9009bcd6c8 h1:l4PKzJ7Usff8j5/e+YaWZPaM+rJHIghgDxRn8vDNxNo=
-github.com/bufbuild/protocompile v0.14.2-0.20251120233202-3f9009bcd6c8/go.mod h1:HKN246DRQwavs64sr2xYmSL+RFOFxmLti+WGCZ2jh9U=
+github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e h1:LQA+1MyiPkolGHJGC2GMDC5Xu+0RDVH6jGMKech7Exs=
+github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e/go.mod h1:5UUj46Eu+U+C59C5N6YilaMI7WWfP2bW9xGcOkme2DI=
 github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 h1:V1xulAoqLqVg44rY97xOR+mQpD2N+GzhMHVwJ030WEU=
 github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@@ -84,8 +84,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
 github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/cli v29.0.2+incompatible h1:iLuKy2GWOSLXGp8feLYBJQVDv7m/8xoofz6lPq41x6A=
-github.com/docker/cli v29.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v29.1.3+incompatible h1:+kz9uDWgs+mAaIZojWfFt4d53/jv0ZUOOoSh5ZnH36c=
+github.com/docker/cli v29.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
@@ -142,8 +142,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
-github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
+github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I=
+github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
@@ -156,8 +156,8 @@ github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ=
 github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8=
 github.com/jhump/protoreflect/v2 v2.0.0-beta.2 h1:qZU+rEZUOYTz1Bnhi3xbwn+VxdXkLVeEpAeZzVXLY88=
 github.com/jhump/protoreflect/v2 v2.0.0-beta.2/go.mod h1:4tnOYkB/mq7QTyS3YKtVtNrJv4Psqout8HA1U+hZtgM=
-github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
-github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
+github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
+github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
 github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
 github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
@@ -166,8 +166,8 @@ github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1y
 github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
 github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc=
 github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
-github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
-github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
+github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM=
+github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
 github.com/knadh/koanf/providers/rawbytes v1.0.0 h1:MrKDh/HksJlKJmaZjgs4r8aVBb/zsJyc/8qaSnzcdNI=
 github.com/knadh/koanf/providers/rawbytes v1.0.0/go.mod h1:KxwYJf1uezTKy6PBtfE+m725NGp4GPVA7XoNTJ/PtLo=
 github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
@@ -196,8 +196,8 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z
 github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
 github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
 github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
-github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
-github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
+github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -230,8 +230,8 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l
 github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
 github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
 github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
-github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
-github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
+github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug=
+github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@@ -250,8 +250,8 @@ github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQcc
 github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
-github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
+github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
+github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
 github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -266,14 +266,14 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
-github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
+github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
+github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
 github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
 github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
 github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
 github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.akshayshah.org/attest v1.0.0 h1:f66BDlh/xo2KjIfmtqOFlj5cpn6mvGrP1LXY3Tex4L0=
+go.akshayshah.org/attest v1.0.0 h1:RVGitcLbAO5i4PIJJDztZ/E9qQ8VSp1PS5PnR4Btg0c=
 go.akshayshah.org/attest v1.0.0/go.mod h1:PnWzcW5j9dkyGwTlBmUsYpPnHG0AUPrs1RQ+HrldWO0=
 go.akshayshah.org/connectproto v0.6.0 h1:tqmysQF2AfvUeYS03mRAAZTFpiQeXqhGIDnH1GO2D2U=
 go.akshayshah.org/connectproto v0.6.0/go.mod h1:uA9TR/6MhBlLn0fh8VXRyL26EKTJlimWao4jbz7JHbA=
@@ -287,22 +287,22 @@ go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
 go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
 go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
 go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
-go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
-go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
+go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
+go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
-go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
-go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
-go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
-go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
-go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
-go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
-go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
-go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
+go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
+go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
+go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
+go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
+go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
+go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
+go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
+go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
 go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
 go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -320,32 +320,32 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
-golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
-golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
+golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
+golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
+golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
+golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
 golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
-golang.org/x/exp/typeparams v0.0.0-20251113190631-e25ba8c21ef6 h1:8dPTIY8FDvi6k5oSD/GuDbs0QyC+A53U8psHrD7K3jw=
-golang.org/x/exp/typeparams v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
+golang.org/x/exp/typeparams v0.0.0-20251219203646-944ab1f22d93 h1:PbC785RGO6yPO051ItgbG/adwoKRWC0VS7kXXeD/iqk=
+golang.org/x/exp/typeparams v0.0.0-20251219203646-944ab1f22d93/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
-golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
+golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
+golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
-golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
-golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
+golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
+golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
+golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -357,41 +357,41 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
+golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
+golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
+golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
 golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
 golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
-golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
+golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
+golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
-google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
+google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
+google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
 google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
 google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
-google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
-google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

+ 36 - 2
service/internal/api/api.go

@@ -587,11 +587,13 @@ func paginate(total int64, size int64, start int64) pageInfo {
 This function is ONLY a helper for the UI - the arguments are validated properly
 on the StartAction -> Executor chain. This is here basically to provide helpful
 error messages more quickly before starting the action.
+
+It uses the same validation logic as the executor, including mangling argument
+values (e.g., datetime formatting, checkbox title-to-value conversion).
 */
 func (api *oliveTinAPI) ValidateArgumentType(ctx ctx.Context, req *connect.Request[apiv1.ValidateArgumentTypeRequest]) (*connect.Response[apiv1.ValidateArgumentTypeResponse], error) {
-	err := executor.TypeSafetyCheck("", req.Msg.Value, req.Msg.Type)
+	err := api.validateArgumentTypeInternal(req.Msg)
 	desc := ""
-
 	if err != nil {
 		desc = err.Error()
 	}
@@ -602,6 +604,38 @@ func (api *oliveTinAPI) ValidateArgumentType(ctx ctx.Context, req *connect.Reque
 	}), nil
 }
 
+func (api *oliveTinAPI) validateArgumentTypeInternal(msg *apiv1.ValidateArgumentTypeRequest) error {
+	if msg.BindingId == "" || msg.ArgumentName == "" {
+		return executor.TypeSafetyCheck("", msg.Value, msg.Type)
+	}
+
+	arg, action := api.findArgumentForValidation(msg.BindingId, msg.ArgumentName)
+	if arg == nil {
+		return fmt.Errorf("argument not found")
+	}
+
+	return executor.ValidateArgument(arg, msg.Value, action)
+}
+
+func (api *oliveTinAPI) findArgumentForValidation(bindingId string, argumentName string) (*config.ActionArgument, *config.Action) {
+	binding := api.executor.FindBindingByID(bindingId)
+	if binding == nil || binding.Action == nil {
+		return nil, nil
+	}
+
+	arg := api.findArgumentByName(binding.Action, argumentName)
+	return arg, binding.Action
+}
+
+func (api *oliveTinAPI) findArgumentByName(action *config.Action, name string) *config.ActionArgument {
+	for i := range action.Arguments {
+		if action.Arguments[i].Name == name {
+			return &action.Arguments[i]
+		}
+	}
+	return nil
+}
+
 func (api *oliveTinAPI) WhoAmI(ctx ctx.Context, req *connect.Request[apiv1.WhoAmIRequest]) (*connect.Response[apiv1.WhoAmIResponse], error) {
 	user := auth.UserFromApiCall(ctx, req, api.cfg)
 

+ 37 - 21
service/internal/auth/otoauth2/restapi_auth_oauth2.go

@@ -219,11 +219,43 @@ func getOAuthCertBundle(providerConfig *config.OAuth2Provider) *x509.CertPool {
 	return caCertPool
 }
 
+func (h *OAuth2Handler) exchangeOAuthCode(ctx context.Context, providerConfig *oauth2.Config, code string, clientSettings *HttpClientSettings) (*oauth2.Token, error) {
+	exchangeClient := &http.Client{
+		Transport: clientSettings.Transport,
+		Timeout:   clientSettings.Timeout,
+	}
+
+	ctx = context.WithValue(ctx, oauth2.HTTPClient, exchangeClient)
+
+	return providerConfig.Exchange(ctx, code)
+}
+
+func (h *OAuth2Handler) createUserInfoClient(ctx context.Context, providerConfig *oauth2.Config, tok *oauth2.Token, clientSettings *HttpClientSettings) *http.Client {
+	return &http.Client{
+		Transport: &oauth2.Transport{
+			Source: providerConfig.TokenSource(ctx, tok),
+			Base:   clientSettings.Transport,
+		},
+		Timeout: clientSettings.Timeout,
+	}
+}
+
+func (h *OAuth2Handler) computeUsergroup(userinfo *UserInfo, providerConfig *config.OAuth2Provider) string {
+	usergroup := userinfo.Usergroup
+	if providerConfig != nil && providerConfig.AddToUsergroup != "" {
+		if usergroup != "" {
+			usergroup = usergroup + " " + providerConfig.AddToUsergroup
+		} else {
+			usergroup = providerConfig.AddToUsergroup
+		}
+	}
+	return usergroup
+}
+
 func (h *OAuth2Handler) HandleOAuthCallback(w http.ResponseWriter, r *http.Request) {
 	log.Infof("OAuth2 Callback received")
 
 	registeredState, state, ok := h.checkOAuthCallbackCookie(w, r)
-
 	if !ok {
 		return
 	}
@@ -236,37 +268,21 @@ func (h *OAuth2Handler) HandleOAuthCallback(w http.ResponseWriter, r *http.Reque
 	}).Debug("OAuth2 Token Code")
 
 	providerConfig := h.cfg.AuthOAuth2Providers[registeredState.providerName]
-
 	clientSettings := getOAuth2HttpClient(providerConfig)
 
-	exchangeClient := &http.Client{
-		Transport: clientSettings.Transport,
-		Timeout:   clientSettings.Timeout,
-	}
-
 	ctx := context.Background()
-	ctx = context.WithValue(ctx, oauth2.HTTPClient, exchangeClient)
-
-	tok, err := registeredState.providerConfig.Exchange(ctx, code)
-
+	tok, err := h.exchangeOAuthCode(ctx, registeredState.providerConfig, code, clientSettings)
 	if err != nil {
 		log.Errorf("Failed to exchange code: %v", err)
 		http.Error(w, "Failed to exchange code", http.StatusBadRequest)
 		return
 	}
 
-	userInfoClient := &http.Client{
-		Transport: &oauth2.Transport{
-			Source: registeredState.providerConfig.TokenSource(ctx, tok),
-			Base:   clientSettings.Transport,
-		},
-		Timeout: clientSettings.Timeout,
-	}
-
-	userinfo := getUserInfo(h.cfg, userInfoClient, h.cfg.AuthOAuth2Providers[registeredState.providerName])
+	userInfoClient := h.createUserInfoClient(ctx, registeredState.providerConfig, tok, clientSettings)
+	userinfo := getUserInfo(h.cfg, userInfoClient, providerConfig)
 
 	h.registeredStates[state].Username = userinfo.Username
-	h.registeredStates[state].Usergroup = userinfo.Usergroup
+	h.registeredStates[state].Usergroup = h.computeUsergroup(userinfo, providerConfig)
 
 	http.Redirect(w, r, "/", http.StatusFound)
 }

+ 1 - 0
service/internal/config/config.go

@@ -184,6 +184,7 @@ type OAuth2Provider struct {
 	InsecureSkipVerify bool     `koanf:"insecureSkipVerify"`
 	CallbackTimeout    int      `koanf:"callbackTimeout"`
 	CertBundlePath     string   `koanf:"certBundlePath"`
+	AddToUsergroup     string   `koanf:"addToUsergroup"`
 }
 
 type NavigationLink struct {

+ 87 - 0
service/internal/executor/arguments.go

@@ -175,6 +175,25 @@ func typecheckActionArgument(arg *config.ActionArgument, value string, action *c
 	return typecheckActionArgumentFound(value, action, arg)
 }
 
+// ValidateArgument validates a single argument value using the same logic as the executor.
+// It applies mangling transformations and performs full validation including null checks,
+// choice validation, and type safety checks.
+func ValidateArgument(arg *config.ActionArgument, value string, action *config.Action) error {
+	if arg == nil {
+		return fmt.Errorf("ValidateArgument: arg is nil")
+	}
+
+	if action == nil {
+		return fmt.Errorf("ValidateArgument: action is nil")
+	}
+
+	// Apply mangling transformations
+	mangledValue := MangleArgumentValue(arg, value, action.Title)
+
+	// Use the same validation path as the executor
+	return typecheckActionArgument(arg, mangledValue, action)
+}
+
 func typecheckActionArgumentFound(value string, action *config.Action, arg *config.ActionArgument) error {
 	if value == "" {
 		return typecheckNull(arg)
@@ -198,6 +217,8 @@ func TypeSafetyCheck(name string, value string, argumentType string) error {
 		return nil
 	case "raw_string_multiline":
 		return nil
+	case "checkbox":
+		return nil
 	case "email":
 		return typeSafetyCheckEmail(value)
 	case "url":
@@ -369,3 +390,69 @@ func mangleInvalidDatetimeValues(req *ExecutionRequest, arg *config.ActionArgume
 		req.Arguments[arg.Name] = timestamp.Format("2006-01-02T15:04:05")
 	}
 }
+
+// MangleArgumentValue applies mangling transformations to a single argument value.
+// This is used by the validation API to ensure the value matches what would be
+// used during actual execution.
+func MangleArgumentValue(arg *config.ActionArgument, value string, actionTitle string) string {
+	if arg == nil {
+		log.Debugf("MangleArgumentValue called with nil arg, returning value unchanged")
+		return value
+	}
+
+	if arg.Type == "datetime" {
+		return mangleDatetimeValue(arg, value, actionTitle)
+	}
+
+	if arg.Type == "checkbox" {
+		return mangleCheckboxValue(arg, value, actionTitle)
+	}
+
+	return value
+}
+
+func mangleDatetimeValue(arg *config.ActionArgument, value string, actionTitle string) string {
+	if arg == nil {
+		log.Debugf("mangleDatetimeValue called with nil arg, returning value unchanged")
+		return value
+	}
+
+	if value == "" {
+		return value
+	}
+
+	timestamp, err := time.Parse("2006-01-02T15:04", value)
+	if err != nil {
+		return value
+	}
+
+	log.WithFields(log.Fields{
+		"arg":         arg.Name,
+		"value":       value,
+		"actionTitle": actionTitle,
+	}).Warnf("Mangled invalid datetime value without seconds to :00 seconds, this issue is commonly caused by Android browsers.")
+
+	return timestamp.Format("2006-01-02T15:04:05")
+}
+
+func mangleCheckboxValue(arg *config.ActionArgument, value string, actionTitle string) string {
+	if arg == nil {
+		log.Debugf("mangleCheckboxValue called with nil arg, returning value unchanged")
+		return value
+	}
+
+	for _, choice := range arg.Choices {
+		if value == choice.Title {
+			log.WithFields(log.Fields{
+				"arg":         arg.Name,
+				"oldValue":    value,
+				"newValue":    choice.Value,
+				"actionTitle": actionTitle,
+			}).Infof("Mangled checkbox value")
+
+			return choice.Value
+		}
+	}
+
+	return value
+}

+ 92 - 0
service/internal/executor/arguments_test.go

@@ -6,6 +6,7 @@ import (
 
 	config "github.com/OliveTin/OliveTin/internal/config"
 	"github.com/OliveTin/OliveTin/internal/entities"
+	log "github.com/sirupsen/logrus"
 
 	"testing"
 
@@ -22,6 +23,97 @@ func TestSanitizeUnimplemented(t *testing.T) {
 	assert.NotNil(t, err, "Test an argument type that does not exist")
 }
 
+func TestValidateArgumentCheckboxDefaultValues(t *testing.T) {
+	arg := config.ActionArgument{
+		Name: "confirm",
+		Type: "checkbox",
+	}
+	action := config.Action{
+		Title: "Test checkbox default values",
+	}
+
+	// Default checkbox values without choices should accept "1" and "0"
+	err := ValidateArgument(&arg, "1", &action)
+	assert.Nil(t, err, "Expected checkbox value \"1\" to be accepted without choices")
+
+	err = ValidateArgument(&arg, "0", &action)
+	assert.Nil(t, err, "Expected checkbox value \"0\" to be accepted without choices")
+}
+
+func TestMangleCheckboxValueWithChoices(t *testing.T) {
+	log.SetLevel(log.PanicLevel)
+
+	arg := config.ActionArgument{
+		Name: "confirm",
+		Type: "checkbox",
+		Choices: []config.ActionArgumentChoice{
+			{Title: "Enabled", Value: "on"},
+			{Title: "Disabled", Value: "off"},
+		},
+	}
+
+	// When the incoming value matches a choice title, it should be mapped to the choice value
+	out := mangleCheckboxValue(&arg, "Enabled", "Test action")
+	assert.Equal(t, "on", out, "Expected checkbox title to be mangled to its value")
+
+	out = mangleCheckboxValue(&arg, "Disabled", "Test action")
+	assert.Equal(t, "off", out, "Expected checkbox title to be mangled to its value")
+
+	// When there is no matching title, the value should be returned unchanged
+	out = mangleCheckboxValue(&arg, "something-else", "Test action")
+	assert.Equal(t, "something-else", out, "Expected non-matching value to be returned unchanged")
+}
+
+func TestMangleArgumentValueCheckbox(t *testing.T) {
+	log.SetLevel(log.PanicLevel)
+
+	arg := config.ActionArgument{
+		Name: "confirm",
+		Type: "checkbox",
+		Choices: []config.ActionArgumentChoice{
+			{Title: "Yes", Value: "true-value"},
+			{Title: "No", Value: "false-value"},
+		},
+	}
+
+	out := MangleArgumentValue(&arg, "Yes", "Test action")
+	assert.Equal(t, "true-value", out, "Expected MangleArgumentValue to delegate to mangleCheckboxValue for checkbox types")
+
+	out = MangleArgumentValue(&arg, "No", "Test action")
+	assert.Equal(t, "false-value", out)
+
+	// For non-matching values, it should return the original value
+	out = MangleArgumentValue(&arg, "maybe", "Test action")
+	assert.Equal(t, "maybe", out)
+}
+
+func TestValidateArgumentCheckboxWithChoices(t *testing.T) {
+	log.SetLevel(log.PanicLevel)
+
+	arg := config.ActionArgument{
+		Name: "confirm",
+		Type: "checkbox",
+		Choices: []config.ActionArgumentChoice{
+			{Title: "Enabled", Value: "on"},
+			{Title: "Disabled", Value: "off"},
+		},
+	}
+	action := config.Action{
+		Title: "Test checkbox with choices",
+	}
+
+	// Titles should be accepted once mangled to their values
+	err := ValidateArgument(&arg, "Enabled", &action)
+	assert.Nil(t, err, "Expected checkbox title \"Enabled\" to be accepted after mangling to choice value")
+
+	err = ValidateArgument(&arg, "Disabled", &action)
+	assert.Nil(t, err, "Expected checkbox title \"Disabled\" to be accepted after mangling to choice value")
+
+	// Unknown titles should be rejected because they do not match any choice value
+	err = ValidateArgument(&arg, "Maybe", &action)
+	assert.NotNil(t, err, "Expected unknown checkbox title to be rejected against choices")
+}
+
 func TestArgumentValueNullable(t *testing.T) {
 	a1 := config.Action{
 		Title: "Release the hounds",

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio