language-operator 0.1.81 → 0.1.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/agents/README.md +16 -0
  3. data/.claude/agents/changelog-writer.md +181 -0
  4. data/.claude/hooks/README.md +43 -0
  5. data/.claude/hooks/node_modules/.bin/esbuild +1 -0
  6. data/.claude/hooks/node_modules/.bin/tsc +1 -0
  7. data/.claude/hooks/node_modules/.bin/tsserver +1 -0
  8. data/.claude/hooks/node_modules/.bin/tsx +1 -0
  9. data/.claude/hooks/node_modules/.package-lock.json +136 -0
  10. data/.claude/hooks/node_modules/@esbuild/linux-x64/README.md +3 -0
  11. data/.claude/hooks/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
  12. data/.claude/hooks/node_modules/@esbuild/linux-x64/package.json +20 -0
  13. data/.claude/hooks/node_modules/@types/node/LICENSE +21 -0
  14. data/.claude/hooks/node_modules/@types/node/README.md +15 -0
  15. data/.claude/hooks/node_modules/@types/node/assert/strict.d.ts +105 -0
  16. data/.claude/hooks/node_modules/@types/node/assert.d.ts +955 -0
  17. data/.claude/hooks/node_modules/@types/node/async_hooks.d.ts +623 -0
  18. data/.claude/hooks/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  19. data/.claude/hooks/node_modules/@types/node/buffer.d.ts +1810 -0
  20. data/.claude/hooks/node_modules/@types/node/child_process.d.ts +1428 -0
  21. data/.claude/hooks/node_modules/@types/node/cluster.d.ts +486 -0
  22. data/.claude/hooks/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  23. data/.claude/hooks/node_modules/@types/node/console.d.ts +151 -0
  24. data/.claude/hooks/node_modules/@types/node/constants.d.ts +20 -0
  25. data/.claude/hooks/node_modules/@types/node/crypto.d.ts +4065 -0
  26. data/.claude/hooks/node_modules/@types/node/dgram.d.ts +564 -0
  27. data/.claude/hooks/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  28. data/.claude/hooks/node_modules/@types/node/dns/promises.d.ts +503 -0
  29. data/.claude/hooks/node_modules/@types/node/dns.d.ts +922 -0
  30. data/.claude/hooks/node_modules/@types/node/domain.d.ts +166 -0
  31. data/.claude/hooks/node_modules/@types/node/events.d.ts +1054 -0
  32. data/.claude/hooks/node_modules/@types/node/fs/promises.d.ts +1316 -0
  33. data/.claude/hooks/node_modules/@types/node/fs.d.ts +4676 -0
  34. data/.claude/hooks/node_modules/@types/node/globals.d.ts +150 -0
  35. data/.claude/hooks/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  36. data/.claude/hooks/node_modules/@types/node/http.d.ts +2143 -0
  37. data/.claude/hooks/node_modules/@types/node/http2.d.ts +2480 -0
  38. data/.claude/hooks/node_modules/@types/node/https.d.ts +399 -0
  39. data/.claude/hooks/node_modules/@types/node/index.d.ts +115 -0
  40. data/.claude/hooks/node_modules/@types/node/inspector/promises.d.ts +41 -0
  41. data/.claude/hooks/node_modules/@types/node/inspector.d.ts +224 -0
  42. data/.claude/hooks/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  43. data/.claude/hooks/node_modules/@types/node/module.d.ts +819 -0
  44. data/.claude/hooks/node_modules/@types/node/net.d.ts +933 -0
  45. data/.claude/hooks/node_modules/@types/node/os.d.ts +507 -0
  46. data/.claude/hooks/node_modules/@types/node/package.json +155 -0
  47. data/.claude/hooks/node_modules/@types/node/path/posix.d.ts +8 -0
  48. data/.claude/hooks/node_modules/@types/node/path/win32.d.ts +8 -0
  49. data/.claude/hooks/node_modules/@types/node/path.d.ts +187 -0
  50. data/.claude/hooks/node_modules/@types/node/perf_hooks.d.ts +621 -0
  51. data/.claude/hooks/node_modules/@types/node/process.d.ts +2097 -0
  52. data/.claude/hooks/node_modules/@types/node/punycode.d.ts +117 -0
  53. data/.claude/hooks/node_modules/@types/node/querystring.d.ts +152 -0
  54. data/.claude/hooks/node_modules/@types/node/quic.d.ts +910 -0
  55. data/.claude/hooks/node_modules/@types/node/readline/promises.d.ts +161 -0
  56. data/.claude/hooks/node_modules/@types/node/readline.d.ts +541 -0
  57. data/.claude/hooks/node_modules/@types/node/repl.d.ts +415 -0
  58. data/.claude/hooks/node_modules/@types/node/sea.d.ts +162 -0
  59. data/.claude/hooks/node_modules/@types/node/sqlite.d.ts +937 -0
  60. data/.claude/hooks/node_modules/@types/node/stream/consumers.d.ts +38 -0
  61. data/.claude/hooks/node_modules/@types/node/stream/promises.d.ts +211 -0
  62. data/.claude/hooks/node_modules/@types/node/stream/web.d.ts +296 -0
  63. data/.claude/hooks/node_modules/@types/node/stream.d.ts +1760 -0
  64. data/.claude/hooks/node_modules/@types/node/string_decoder.d.ts +67 -0
  65. data/.claude/hooks/node_modules/@types/node/test/reporters.d.ts +96 -0
  66. data/.claude/hooks/node_modules/@types/node/test.d.ts +2239 -0
  67. data/.claude/hooks/node_modules/@types/node/timers/promises.d.ts +108 -0
  68. data/.claude/hooks/node_modules/@types/node/timers.d.ts +159 -0
  69. data/.claude/hooks/node_modules/@types/node/tls.d.ts +1194 -0
  70. data/.claude/hooks/node_modules/@types/node/trace_events.d.ts +197 -0
  71. data/.claude/hooks/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  72. data/.claude/hooks/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  73. data/.claude/hooks/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  74. data/.claude/hooks/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  75. data/.claude/hooks/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  76. data/.claude/hooks/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  77. data/.claude/hooks/node_modules/@types/node/tty.d.ts +250 -0
  78. data/.claude/hooks/node_modules/@types/node/url.d.ts +519 -0
  79. data/.claude/hooks/node_modules/@types/node/util/types.d.ts +558 -0
  80. data/.claude/hooks/node_modules/@types/node/util.d.ts +1653 -0
  81. data/.claude/hooks/node_modules/@types/node/v8.d.ts +979 -0
  82. data/.claude/hooks/node_modules/@types/node/vm.d.ts +1180 -0
  83. data/.claude/hooks/node_modules/@types/node/wasi.d.ts +202 -0
  84. data/.claude/hooks/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  85. data/.claude/hooks/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  86. data/.claude/hooks/node_modules/@types/node/web-globals/console.d.ts +9 -0
  87. data/.claude/hooks/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  88. data/.claude/hooks/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  89. data/.claude/hooks/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  90. data/.claude/hooks/node_modules/@types/node/web-globals/events.d.ts +106 -0
  91. data/.claude/hooks/node_modules/@types/node/web-globals/fetch.d.ts +54 -0
  92. data/.claude/hooks/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  93. data/.claude/hooks/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  94. data/.claude/hooks/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  95. data/.claude/hooks/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  96. data/.claude/hooks/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  97. data/.claude/hooks/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  98. data/.claude/hooks/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  99. data/.claude/hooks/node_modules/@types/node/web-globals/url.d.ts +24 -0
  100. data/.claude/hooks/node_modules/@types/node/worker_threads.d.ts +714 -0
  101. data/.claude/hooks/node_modules/@types/node/zlib.d.ts +618 -0
  102. data/.claude/hooks/node_modules/esbuild/LICENSE.md +21 -0
  103. data/.claude/hooks/node_modules/esbuild/README.md +3 -0
  104. data/.claude/hooks/node_modules/esbuild/bin/esbuild +0 -0
  105. data/.claude/hooks/node_modules/esbuild/install.js +289 -0
  106. data/.claude/hooks/node_modules/esbuild/lib/main.d.ts +716 -0
  107. data/.claude/hooks/node_modules/esbuild/lib/main.js +2242 -0
  108. data/.claude/hooks/node_modules/esbuild/package.json +49 -0
  109. data/.claude/hooks/node_modules/get-tsconfig/LICENSE +21 -0
  110. data/.claude/hooks/node_modules/get-tsconfig/README.md +235 -0
  111. data/.claude/hooks/node_modules/get-tsconfig/dist/index.cjs +7 -0
  112. data/.claude/hooks/node_modules/get-tsconfig/dist/index.d.cts +2088 -0
  113. data/.claude/hooks/node_modules/get-tsconfig/dist/index.d.mts +2088 -0
  114. data/.claude/hooks/node_modules/get-tsconfig/dist/index.mjs +7 -0
  115. data/.claude/hooks/node_modules/get-tsconfig/package.json +46 -0
  116. data/.claude/hooks/node_modules/resolve-pkg-maps/LICENSE +21 -0
  117. data/.claude/hooks/node_modules/resolve-pkg-maps/README.md +216 -0
  118. data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.cjs +1 -0
  119. data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.d.cts +11 -0
  120. data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.d.mts +11 -0
  121. data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.mjs +1 -0
  122. data/.claude/hooks/node_modules/resolve-pkg-maps/package.json +42 -0
  123. data/.claude/hooks/node_modules/tsx/LICENSE +21 -0
  124. data/.claude/hooks/node_modules/tsx/README.md +32 -0
  125. data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
  126. data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
  127. data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
  128. data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
  129. data/.claude/hooks/node_modules/tsx/dist/cjs/index.cjs +1 -0
  130. data/.claude/hooks/node_modules/tsx/dist/cjs/index.mjs +1 -0
  131. data/.claude/hooks/node_modules/tsx/dist/cli.cjs +54 -0
  132. data/.claude/hooks/node_modules/tsx/dist/cli.mjs +55 -0
  133. data/.claude/hooks/node_modules/tsx/dist/client-BQVF1NaW.mjs +1 -0
  134. data/.claude/hooks/node_modules/tsx/dist/client-D6NvIMSC.cjs +1 -0
  135. data/.claude/hooks/node_modules/tsx/dist/esm/api/index.cjs +1 -0
  136. data/.claude/hooks/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
  137. data/.claude/hooks/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
  138. data/.claude/hooks/node_modules/tsx/dist/esm/api/index.mjs +1 -0
  139. data/.claude/hooks/node_modules/tsx/dist/esm/index.cjs +2 -0
  140. data/.claude/hooks/node_modules/tsx/dist/esm/index.mjs +2 -0
  141. data/.claude/hooks/node_modules/tsx/dist/get-pipe-path-BHW2eJdv.mjs +1 -0
  142. data/.claude/hooks/node_modules/tsx/dist/get-pipe-path-BoR10qr8.cjs +1 -0
  143. data/.claude/hooks/node_modules/tsx/dist/index-7AaEi15b.mjs +14 -0
  144. data/.claude/hooks/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
  145. data/.claude/hooks/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
  146. data/.claude/hooks/node_modules/tsx/dist/index-gckBtVBf.cjs +14 -0
  147. data/.claude/hooks/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
  148. data/.claude/hooks/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
  149. data/.claude/hooks/node_modules/tsx/dist/loader.cjs +1 -0
  150. data/.claude/hooks/node_modules/tsx/dist/loader.mjs +1 -0
  151. data/.claude/hooks/node_modules/tsx/dist/node-features-_8ZFwP_x.mjs +1 -0
  152. data/.claude/hooks/node_modules/tsx/dist/node-features-roYmp9jK.cjs +1 -0
  153. data/.claude/hooks/node_modules/tsx/dist/package-CeBgXWuR.mjs +1 -0
  154. data/.claude/hooks/node_modules/tsx/dist/package-Dxt5kIHw.cjs +1 -0
  155. data/.claude/hooks/node_modules/tsx/dist/patch-repl.cjs +1 -0
  156. data/.claude/hooks/node_modules/tsx/dist/patch-repl.mjs +1 -0
  157. data/.claude/hooks/node_modules/tsx/dist/preflight.cjs +1 -0
  158. data/.claude/hooks/node_modules/tsx/dist/preflight.mjs +1 -0
  159. data/.claude/hooks/node_modules/tsx/dist/register-2sWVXuRQ.cjs +1 -0
  160. data/.claude/hooks/node_modules/tsx/dist/register-B7jrtLTO.mjs +1 -0
  161. data/.claude/hooks/node_modules/tsx/dist/register-CFH5oNdT.mjs +4 -0
  162. data/.claude/hooks/node_modules/tsx/dist/register-D46fvsV_.cjs +4 -0
  163. data/.claude/hooks/node_modules/tsx/dist/repl.cjs +3 -0
  164. data/.claude/hooks/node_modules/tsx/dist/repl.mjs +3 -0
  165. data/.claude/hooks/node_modules/tsx/dist/require-D4F1Lv60.cjs +1 -0
  166. data/.claude/hooks/node_modules/tsx/dist/require-DQxpCAr4.mjs +1 -0
  167. data/.claude/hooks/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
  168. data/.claude/hooks/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
  169. data/.claude/hooks/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
  170. data/.claude/hooks/node_modules/tsx/dist/temporary-directory-CwHp0_NW.mjs +1 -0
  171. data/.claude/hooks/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
  172. data/.claude/hooks/node_modules/tsx/package.json +68 -0
  173. data/.claude/hooks/node_modules/typescript/LICENSE.txt +55 -0
  174. data/.claude/hooks/node_modules/typescript/README.md +50 -0
  175. data/.claude/hooks/node_modules/typescript/SECURITY.md +41 -0
  176. data/.claude/hooks/node_modules/typescript/ThirdPartyNoticeText.txt +193 -0
  177. data/.claude/hooks/node_modules/typescript/bin/tsc +2 -0
  178. data/.claude/hooks/node_modules/typescript/bin/tsserver +2 -0
  179. data/.claude/hooks/node_modules/typescript/lib/_tsc.js +133818 -0
  180. data/.claude/hooks/node_modules/typescript/lib/_tsserver.js +659 -0
  181. data/.claude/hooks/node_modules/typescript/lib/_typingsInstaller.js +222 -0
  182. data/.claude/hooks/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +2122 -0
  183. data/.claude/hooks/node_modules/typescript/lib/de/diagnosticMessages.generated.json +2122 -0
  184. data/.claude/hooks/node_modules/typescript/lib/es/diagnosticMessages.generated.json +2122 -0
  185. data/.claude/hooks/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +2122 -0
  186. data/.claude/hooks/node_modules/typescript/lib/it/diagnosticMessages.generated.json +2122 -0
  187. data/.claude/hooks/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +2122 -0
  188. data/.claude/hooks/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +2122 -0
  189. data/.claude/hooks/node_modules/typescript/lib/lib.d.ts +22 -0
  190. data/.claude/hooks/node_modules/typescript/lib/lib.decorators.d.ts +384 -0
  191. data/.claude/hooks/node_modules/typescript/lib/lib.decorators.legacy.d.ts +22 -0
  192. data/.claude/hooks/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +41 -0
  193. data/.claude/hooks/node_modules/typescript/lib/lib.dom.d.ts +39429 -0
  194. data/.claude/hooks/node_modules/typescript/lib/lib.dom.iterable.d.ts +571 -0
  195. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.collection.d.ts +147 -0
  196. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.core.d.ts +597 -0
  197. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.d.ts +28 -0
  198. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.generator.d.ts +77 -0
  199. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.iterable.d.ts +605 -0
  200. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.promise.d.ts +81 -0
  201. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.proxy.d.ts +128 -0
  202. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.reflect.d.ts +144 -0
  203. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.symbol.d.ts +46 -0
  204. data/.claude/hooks/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +326 -0
  205. data/.claude/hooks/node_modules/typescript/lib/lib.es2016.array.include.d.ts +116 -0
  206. data/.claude/hooks/node_modules/typescript/lib/lib.es2016.d.ts +21 -0
  207. data/.claude/hooks/node_modules/typescript/lib/lib.es2016.full.d.ts +23 -0
  208. data/.claude/hooks/node_modules/typescript/lib/lib.es2016.intl.d.ts +31 -0
  209. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +21 -0
  210. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.d.ts +26 -0
  211. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.date.d.ts +31 -0
  212. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.full.d.ts +23 -0
  213. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.intl.d.ts +44 -0
  214. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.object.d.ts +49 -0
  215. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +135 -0
  216. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.string.d.ts +45 -0
  217. data/.claude/hooks/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +53 -0
  218. data/.claude/hooks/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +77 -0
  219. data/.claude/hooks/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +53 -0
  220. data/.claude/hooks/node_modules/typescript/lib/lib.es2018.d.ts +24 -0
  221. data/.claude/hooks/node_modules/typescript/lib/lib.es2018.full.d.ts +24 -0
  222. data/.claude/hooks/node_modules/typescript/lib/lib.es2018.intl.d.ts +83 -0
  223. data/.claude/hooks/node_modules/typescript/lib/lib.es2018.promise.d.ts +30 -0
  224. data/.claude/hooks/node_modules/typescript/lib/lib.es2018.regexp.d.ts +37 -0
  225. data/.claude/hooks/node_modules/typescript/lib/lib.es2019.array.d.ts +79 -0
  226. data/.claude/hooks/node_modules/typescript/lib/lib.es2019.d.ts +24 -0
  227. data/.claude/hooks/node_modules/typescript/lib/lib.es2019.full.d.ts +24 -0
  228. data/.claude/hooks/node_modules/typescript/lib/lib.es2019.intl.d.ts +23 -0
  229. data/.claude/hooks/node_modules/typescript/lib/lib.es2019.object.d.ts +33 -0
  230. data/.claude/hooks/node_modules/typescript/lib/lib.es2019.string.d.ts +37 -0
  231. data/.claude/hooks/node_modules/typescript/lib/lib.es2019.symbol.d.ts +24 -0
  232. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.bigint.d.ts +765 -0
  233. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.d.ts +27 -0
  234. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.date.d.ts +42 -0
  235. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.full.d.ts +24 -0
  236. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.intl.d.ts +474 -0
  237. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.number.d.ts +28 -0
  238. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.promise.d.ts +47 -0
  239. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +99 -0
  240. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.string.d.ts +44 -0
  241. data/.claude/hooks/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +41 -0
  242. data/.claude/hooks/node_modules/typescript/lib/lib.es2021.d.ts +23 -0
  243. data/.claude/hooks/node_modules/typescript/lib/lib.es2021.full.d.ts +24 -0
  244. data/.claude/hooks/node_modules/typescript/lib/lib.es2021.intl.d.ts +166 -0
  245. data/.claude/hooks/node_modules/typescript/lib/lib.es2021.promise.d.ts +48 -0
  246. data/.claude/hooks/node_modules/typescript/lib/lib.es2021.string.d.ts +33 -0
  247. data/.claude/hooks/node_modules/typescript/lib/lib.es2021.weakref.d.ts +78 -0
  248. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.array.d.ts +121 -0
  249. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.d.ts +25 -0
  250. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.error.d.ts +75 -0
  251. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.full.d.ts +24 -0
  252. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.intl.d.ts +145 -0
  253. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.object.d.ts +26 -0
  254. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.regexp.d.ts +39 -0
  255. data/.claude/hooks/node_modules/typescript/lib/lib.es2022.string.d.ts +25 -0
  256. data/.claude/hooks/node_modules/typescript/lib/lib.es2023.array.d.ts +924 -0
  257. data/.claude/hooks/node_modules/typescript/lib/lib.es2023.collection.d.ts +21 -0
  258. data/.claude/hooks/node_modules/typescript/lib/lib.es2023.d.ts +22 -0
  259. data/.claude/hooks/node_modules/typescript/lib/lib.es2023.full.d.ts +24 -0
  260. data/.claude/hooks/node_modules/typescript/lib/lib.es2023.intl.d.ts +56 -0
  261. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +65 -0
  262. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.collection.d.ts +29 -0
  263. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.d.ts +26 -0
  264. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.full.d.ts +24 -0
  265. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.object.d.ts +29 -0
  266. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.promise.d.ts +35 -0
  267. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.regexp.d.ts +25 -0
  268. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +68 -0
  269. data/.claude/hooks/node_modules/typescript/lib/lib.es2024.string.d.ts +29 -0
  270. data/.claude/hooks/node_modules/typescript/lib/lib.es5.d.ts +4601 -0
  271. data/.claude/hooks/node_modules/typescript/lib/lib.es6.d.ts +23 -0
  272. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.array.d.ts +35 -0
  273. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.collection.d.ts +96 -0
  274. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.d.ts +29 -0
  275. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.decorators.d.ts +28 -0
  276. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.disposable.d.ts +193 -0
  277. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.error.d.ts +24 -0
  278. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.float16.d.ts +445 -0
  279. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.full.d.ts +24 -0
  280. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.intl.d.ts +21 -0
  281. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.iterator.d.ts +148 -0
  282. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.promise.d.ts +34 -0
  283. data/.claude/hooks/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +25 -0
  284. data/.claude/hooks/node_modules/typescript/lib/lib.scripthost.d.ts +322 -0
  285. data/.claude/hooks/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +41 -0
  286. data/.claude/hooks/node_modules/typescript/lib/lib.webworker.d.ts +13150 -0
  287. data/.claude/hooks/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +23 -0
  288. data/.claude/hooks/node_modules/typescript/lib/lib.webworker.iterable.d.ts +340 -0
  289. data/.claude/hooks/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +2122 -0
  290. data/.claude/hooks/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +2122 -0
  291. data/.claude/hooks/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +2122 -0
  292. data/.claude/hooks/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +2122 -0
  293. data/.claude/hooks/node_modules/typescript/lib/tsc.js +8 -0
  294. data/.claude/hooks/node_modules/typescript/lib/tsserver.js +8 -0
  295. data/.claude/hooks/node_modules/typescript/lib/tsserverlibrary.d.ts +17 -0
  296. data/.claude/hooks/node_modules/typescript/lib/tsserverlibrary.js +21 -0
  297. data/.claude/hooks/node_modules/typescript/lib/typesMap.json +497 -0
  298. data/.claude/hooks/node_modules/typescript/lib/typescript.d.ts +11437 -0
  299. data/.claude/hooks/node_modules/typescript/lib/typescript.js +200276 -0
  300. data/.claude/hooks/node_modules/typescript/lib/typingsInstaller.js +8 -0
  301. data/.claude/hooks/node_modules/typescript/lib/watchGuard.js +53 -0
  302. data/.claude/hooks/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +2122 -0
  303. data/.claude/hooks/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +2122 -0
  304. data/.claude/hooks/node_modules/typescript/package.json +120 -0
  305. data/.claude/hooks/node_modules/undici-types/LICENSE +21 -0
  306. data/.claude/hooks/node_modules/undici-types/README.md +6 -0
  307. data/.claude/hooks/node_modules/undici-types/agent.d.ts +32 -0
  308. data/.claude/hooks/node_modules/undici-types/api.d.ts +43 -0
  309. data/.claude/hooks/node_modules/undici-types/balanced-pool.d.ts +29 -0
  310. data/.claude/hooks/node_modules/undici-types/cache-interceptor.d.ts +172 -0
  311. data/.claude/hooks/node_modules/undici-types/cache.d.ts +36 -0
  312. data/.claude/hooks/node_modules/undici-types/client-stats.d.ts +15 -0
  313. data/.claude/hooks/node_modules/undici-types/client.d.ts +108 -0
  314. data/.claude/hooks/node_modules/undici-types/connector.d.ts +34 -0
  315. data/.claude/hooks/node_modules/undici-types/content-type.d.ts +21 -0
  316. data/.claude/hooks/node_modules/undici-types/cookies.d.ts +30 -0
  317. data/.claude/hooks/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  318. data/.claude/hooks/node_modules/undici-types/dispatcher.d.ts +276 -0
  319. data/.claude/hooks/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  320. data/.claude/hooks/node_modules/undici-types/errors.d.ts +161 -0
  321. data/.claude/hooks/node_modules/undici-types/eventsource.d.ts +66 -0
  322. data/.claude/hooks/node_modules/undici-types/fetch.d.ts +211 -0
  323. data/.claude/hooks/node_modules/undici-types/formdata.d.ts +108 -0
  324. data/.claude/hooks/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  325. data/.claude/hooks/node_modules/undici-types/global-origin.d.ts +7 -0
  326. data/.claude/hooks/node_modules/undici-types/h2c-client.d.ts +73 -0
  327. data/.claude/hooks/node_modules/undici-types/handlers.d.ts +15 -0
  328. data/.claude/hooks/node_modules/undici-types/header.d.ts +160 -0
  329. data/.claude/hooks/node_modules/undici-types/index.d.ts +80 -0
  330. data/.claude/hooks/node_modules/undici-types/interceptors.d.ts +39 -0
  331. data/.claude/hooks/node_modules/undici-types/mock-agent.d.ts +68 -0
  332. data/.claude/hooks/node_modules/undici-types/mock-call-history.d.ts +111 -0
  333. data/.claude/hooks/node_modules/undici-types/mock-client.d.ts +27 -0
  334. data/.claude/hooks/node_modules/undici-types/mock-errors.d.ts +12 -0
  335. data/.claude/hooks/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  336. data/.claude/hooks/node_modules/undici-types/mock-pool.d.ts +27 -0
  337. data/.claude/hooks/node_modules/undici-types/package.json +55 -0
  338. data/.claude/hooks/node_modules/undici-types/patch.d.ts +29 -0
  339. data/.claude/hooks/node_modules/undici-types/pool-stats.d.ts +19 -0
  340. data/.claude/hooks/node_modules/undici-types/pool.d.ts +41 -0
  341. data/.claude/hooks/node_modules/undici-types/proxy-agent.d.ts +29 -0
  342. data/.claude/hooks/node_modules/undici-types/readable.d.ts +68 -0
  343. data/.claude/hooks/node_modules/undici-types/retry-agent.d.ts +8 -0
  344. data/.claude/hooks/node_modules/undici-types/retry-handler.d.ts +125 -0
  345. data/.claude/hooks/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  346. data/.claude/hooks/node_modules/undici-types/util.d.ts +18 -0
  347. data/.claude/hooks/node_modules/undici-types/utility.d.ts +7 -0
  348. data/.claude/hooks/node_modules/undici-types/webidl.d.ts +341 -0
  349. data/.claude/hooks/node_modules/undici-types/websocket.d.ts +186 -0
  350. data/.claude/hooks/package-lock.json +562 -0
  351. data/.claude/hooks/package.json +19 -0
  352. data/.claude/hooks/skill-activation-prompt.sh +11 -0
  353. data/.claude/hooks/skill-activation-prompt.ts +185 -0
  354. data/.claude/hooks/tsconfig.json +12 -0
  355. data/.claude/settings.json +33 -0
  356. data/.claude/skills/README.md +29 -0
  357. data/.claude/skills/ruby-gem-development/SKILL.md +293 -0
  358. data/.claude/skills/skill-rules.json +117 -0
  359. data/.claude/skills/thor-cli-development/SKILL.md +431 -0
  360. data/CHANGELOG.md +7 -0
  361. data/Gemfile.lock +3 -1
  362. data/completions/{_aictl → _langop} +37 -37
  363. data/completions/{aictl.bash → langop.bash} +16 -16
  364. data/completions/langop.fish +114 -0
  365. data/components/agent/Gemfile +1 -1
  366. data/components/tool/Gemfile +1 -1
  367. data/docs/README.md +10 -10
  368. data/docs/agent-internals.md +1 -1
  369. data/docs/cheat-sheet.md +76 -76
  370. data/docs/cli-reference.md +71 -71
  371. data/docs/how-agents-work.md +1 -1
  372. data/docs/installation.md +30 -30
  373. data/docs/quickstart.md +30 -30
  374. data/docs/using-tools.md +11 -11
  375. data/docs/webhooks.md +6 -6
  376. data/examples/ux_helpers_demo.rb +4 -4
  377. data/lib/language_operator/agent/task_executor.rb +0 -37
  378. data/lib/language_operator/agent/web_server.rb +311 -323
  379. data/lib/language_operator/cli/commands/agent/base.rb +16 -15
  380. data/lib/language_operator/cli/commands/agent/code_operations.rb +6 -6
  381. data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +1 -1
  382. data/lib/language_operator/cli/commands/agent/lifecycle.rb +2 -2
  383. data/lib/language_operator/cli/commands/agent/logs.rb +2 -2
  384. data/lib/language_operator/cli/commands/agent/workspace.rb +8 -8
  385. data/lib/language_operator/cli/commands/cluster.rb +172 -158
  386. data/lib/language_operator/cli/commands/install.rb +878 -81
  387. data/lib/language_operator/cli/commands/model/base.rb +7 -6
  388. data/lib/language_operator/cli/commands/organization.rb +61 -0
  389. data/lib/language_operator/cli/commands/persona.rb +10 -8
  390. data/lib/language_operator/cli/commands/status.rb +83 -13
  391. data/lib/language_operator/cli/commands/system/exec.rb +10 -10
  392. data/lib/language_operator/cli/commands/system/schema.rb +6 -6
  393. data/lib/language_operator/cli/commands/system/synthesis_template.rb +6 -6
  394. data/lib/language_operator/cli/commands/system/synthesize.rb +14 -14
  395. data/lib/language_operator/cli/commands/system/validate_template.rb +4 -4
  396. data/lib/language_operator/cli/commands/tool/base.rb +1 -1
  397. data/lib/language_operator/cli/commands/tool/install.rb +2 -2
  398. data/lib/language_operator/cli/commands/tool/search.rb +3 -3
  399. data/lib/language_operator/cli/commands/ui.rb +173 -0
  400. data/lib/language_operator/cli/commands/use.rb +80 -9
  401. data/lib/language_operator/cli/errors/suggestions.rb +21 -21
  402. data/lib/language_operator/cli/formatters/progress_formatter.rb +2 -1
  403. data/lib/language_operator/cli/formatters/table_formatter.rb +21 -1
  404. data/lib/language_operator/cli/helpers/cluster_validator.rb +2 -2
  405. data/lib/language_operator/cli/helpers/health_checker.rb +263 -0
  406. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +3 -3
  407. data/lib/language_operator/cli/helpers/ux_helper.rb +4 -3
  408. data/lib/language_operator/cli/main.rb +30 -21
  409. data/lib/language_operator/cli/wizards/model_wizard.rb +7 -5
  410. data/lib/language_operator/config/cluster_config.rb +3 -3
  411. data/lib/language_operator/constants/kubernetes_labels.rb +1 -1
  412. data/lib/language_operator/kubernetes/client.rb +53 -109
  413. data/lib/language_operator/kubernetes/resource_builder.rb +46 -14
  414. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  415. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  416. data/lib/language_operator/utils/org_context.rb +100 -0
  417. data/lib/language_operator/version.rb +1 -1
  418. data/synth/001/Makefile +1 -1
  419. data/synth/002/Makefile +1 -1
  420. data/synth/003/Makefile +1 -1
  421. data/synth/004/Makefile +1 -1
  422. metadata +384 -11
  423. data/completions/aictl.fish +0 -114
  424. data/lib/language_operator/agent/event_config.rb +0 -172
  425. data/lib/language_operator/cli/commands/quickstart.rb +0 -22
  426. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -561
  427. /data/bin/{aictl → langop} +0 -0
@@ -15,7 +15,7 @@ module LanguageOperator
15
15
  HELM_REPO_URL = 'https://language-operator.github.io/charts'
16
16
  CHART_NAME = 'language-operator/language-operator'
17
17
  RELEASE_NAME = 'language-operator'
18
- DEFAULT_NAMESPACE = 'language-operator-system'
18
+ DEFAULT_NAMESPACE = 'language-operator'
19
19
 
20
20
  # Long descriptions for commands
21
21
  LONG_DESCRIPTIONS = {
@@ -30,16 +30,16 @@ module LanguageOperator
30
30
 
31
31
  Examples:
32
32
  # Install with defaults
33
- aictl install
33
+ langop install
34
34
 
35
35
  # Install with custom values
36
- aictl install --values my-values.yaml
36
+ langop install --values my-values.yaml
37
37
 
38
38
  # Install specific version
39
- aictl install --version 0.1.0
39
+ langop install --version 0.1.0
40
40
 
41
41
  # Dry run to see what would be installed
42
- aictl install --dry-run
42
+ langop install --dry-run
43
43
  DESC
44
44
  upgrade: <<-DESC,
45
45
  Upgrade the language-operator to a newer version using Helm.
@@ -52,29 +52,33 @@ module LanguageOperator
52
52
 
53
53
  Examples:
54
54
  # Upgrade to latest version
55
- aictl upgrade
55
+ langop upgrade
56
56
 
57
57
  # Upgrade with custom values
58
- aictl upgrade --values my-values.yaml
58
+ langop upgrade --values my-values.yaml
59
59
 
60
60
  # Upgrade to specific version
61
- aictl upgrade --version 0.2.0
61
+ langop upgrade --version 0.2.0
62
62
  DESC
63
63
  uninstall: <<-DESC
64
- Uninstall the language-operator from your Kubernetes cluster.
64
+ Completely uninstall the language-operator from your Kubernetes cluster.
65
65
 
66
- WARNING: This will remove the operator but NOT the CRDs or custom resources.
67
- Agents, tools, models, and personas will remain in the cluster.
66
+ This will:
67
+ 1. Delete all custom resources (agents, tools, models, personas, clusters)
68
+ 2. Uninstall the operator using Helm
69
+ 3. Remove all CRDs (CustomResourceDefinitions)
70
+
71
+ WARNING: This will completely remove all language-operator resources from the cluster.
68
72
 
69
73
  Examples:
70
- # Uninstall with confirmation
71
- aictl uninstall
74
+ # Complete uninstall with confirmation
75
+ langop uninstall
72
76
 
73
77
  # Force uninstall without confirmation
74
- aictl uninstall --force
78
+ langop uninstall --force
75
79
 
76
80
  # Uninstall from specific namespace
77
- aictl uninstall --namespace my-namespace
81
+ langop uninstall --namespace my-namespace
78
82
  DESC
79
83
  }.freeze
80
84
 
@@ -95,16 +99,16 @@ module LanguageOperator
95
99
 
96
100
  Examples:
97
101
  # Install with defaults
98
- aictl install
102
+ langop install
99
103
 
100
104
  # Install with custom values
101
- aictl install --values my-values.yaml
105
+ langop install --values my-values.yaml
102
106
 
103
107
  # Install specific version
104
- aictl install --version 0.1.0
108
+ langop install --version 0.1.0
105
109
 
106
110
  # Dry run to see what would be installed
107
- aictl install --dry-run
111
+ langop install --dry-run
108
112
  DESC
109
113
  option :values, type: :string, desc: 'Path to custom Helm values file'
110
114
  option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
@@ -112,6 +116,7 @@ module LanguageOperator
112
116
  option :dry_run, type: :boolean, default: false, desc: 'Preview installation without applying'
113
117
  option :wait, type: :boolean, default: true, desc: 'Wait for deployment to complete'
114
118
  option :create_namespace, type: :boolean, default: true, desc: 'Create namespace if it does not exist'
119
+ option :non_interactive, type: :boolean, default: false, desc: 'Skip interactive prompts and use defaults'
115
120
  def install
116
121
  handle_command_error('install') do
117
122
  # Check if helm is available
@@ -122,25 +127,33 @@ module LanguageOperator
122
127
  Formatters::ProgressFormatter.warn('Language operator is already installed')
123
128
  puts
124
129
  puts 'To upgrade, use:'
125
- puts ' aictl upgrade'
130
+ puts ' langop upgrade'
126
131
  return
127
132
  end
128
133
 
129
134
  namespace = options[:namespace]
130
135
 
131
- puts 'Installing language-operator...'
132
- puts " Namespace: #{namespace}"
133
- puts " Chart: #{CHART_NAME}"
134
- puts
136
+ # Interactive configuration unless non-interactive mode or custom values provided
137
+ unless options[:non_interactive] || options[:values]
138
+ logo(title: 'language operator installer')
139
+ puts 'This installer will help you configure the Language Operator for your cluster.'
140
+ puts
141
+
142
+ config = collect_interactive_configuration
143
+ values_file = generate_values_file(config)
144
+ # Create a mutable copy of options to avoid frozen hash error
145
+ @options = options.dup
146
+ @options[:values] = values_file
147
+ end
135
148
 
136
149
  # Add Helm repository
137
- add_helm_repo unless options[:dry_run]
150
+ add_helm_repo unless (@options || options)[:dry_run]
138
151
 
139
- # Build helm install command
152
+ # Build helm install command (use @options if we created a mutable copy)
140
153
  cmd = build_helm_command('install', namespace)
141
154
 
142
155
  # Execute helm install
143
- if options[:dry_run]
156
+ if (@options || options)[:dry_run]
144
157
  puts 'Dry run - would execute:'
145
158
  puts " #{cmd}"
146
159
  puts
@@ -152,12 +165,8 @@ module LanguageOperator
152
165
  raise "Helm install failed: #{output}" unless success
153
166
  end
154
167
 
155
- Formatters::ProgressFormatter.success('Language operator installed successfully!')
156
- puts
157
- puts 'Next steps:'
158
- puts ' 1. Create a cluster: aictl cluster create my-cluster'
159
- puts ' 2. Create a model: aictl model create gpt4 --provider openai --model gpt-4-turbo'
160
- puts ' 3. Create an agent: aictl agent create "your agent description"'
168
+ # Post-installation verification
169
+ verify_installation(config)
161
170
  end
162
171
  end
163
172
  end
@@ -174,13 +183,13 @@ module LanguageOperator
174
183
 
175
184
  Examples:
176
185
  # Upgrade to latest version
177
- aictl upgrade
186
+ langop upgrade
178
187
 
179
188
  # Upgrade with custom values
180
- aictl upgrade --values my-values.yaml
189
+ langop upgrade --values my-values.yaml
181
190
 
182
191
  # Upgrade to specific version
183
- aictl upgrade --version 0.2.0
192
+ langop upgrade --version 0.2.0
184
193
  DESC
185
194
  option :values, type: :string, desc: 'Path to custom Helm values file'
186
195
  option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
@@ -197,17 +206,12 @@ module LanguageOperator
197
206
  Formatters::ProgressFormatter.error('Language operator is not installed')
198
207
  puts
199
208
  puts 'To install, use:'
200
- puts ' aictl install'
209
+ puts ' langop install'
201
210
  exit 1
202
211
  end
203
212
 
204
213
  namespace = options[:namespace]
205
214
 
206
- puts 'Upgrading language-operator...'
207
- puts " Namespace: #{namespace}"
208
- puts " Chart: #{CHART_NAME}"
209
- puts
210
-
211
215
  # Update Helm repository
212
216
  update_helm_repo unless options[:dry_run]
213
217
 
@@ -227,27 +231,32 @@ module LanguageOperator
227
231
  raise "Helm upgrade failed: #{output}" unless success
228
232
  end
229
233
 
230
- Formatters::ProgressFormatter.success('Language operator upgraded successfully!')
234
+ # Restart the language-operator deployment to ensure new version is running
235
+ restart_language_operator_deployment(namespace)
231
236
  end
232
237
  end
233
238
  end
234
239
 
235
240
  desc 'uninstall', 'Uninstall the language-operator using Helm'
236
241
  long_desc <<-DESC
237
- Uninstall the language-operator from your Kubernetes cluster.
242
+ Completely uninstall the language-operator from your Kubernetes cluster.
238
243
 
239
- WARNING: This will remove the operator but NOT the CRDs or custom resources.
240
- Agents, tools, models, and personas will remain in the cluster.
244
+ This will:
245
+ 1. Delete all custom resources (agents, tools, models, personas, clusters)
246
+ 2. Uninstall the operator using Helm
247
+ 3. Remove all CRDs (CustomResourceDefinitions)
248
+
249
+ WARNING: This will completely remove all language-operator resources from the cluster.
241
250
 
242
251
  Examples:
243
- # Uninstall with confirmation
244
- aictl uninstall
252
+ # Complete uninstall with confirmation
253
+ langop uninstall
245
254
 
246
255
  # Force uninstall without confirmation
247
- aictl uninstall --force
256
+ langop uninstall --force
248
257
 
249
258
  # Uninstall from specific namespace
250
- aictl uninstall --namespace my-namespace
259
+ langop uninstall --namespace my-namespace
251
260
  DESC
252
261
  option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
253
262
  option :force, type: :boolean, default: false, desc: 'Skip confirmation prompt'
@@ -256,41 +265,53 @@ module LanguageOperator
256
265
  # Check if helm is available
257
266
  check_helm_installed!
258
267
 
259
- # Check if operator is installed
260
- unless operator_installed?
261
- Formatters::ProgressFormatter.warn('Language operator is not installed')
262
- return
263
- end
268
+ # Check if operator is installed but proceed with cleanup regardless
264
269
 
265
270
  namespace = options[:namespace]
266
271
 
267
272
  # Confirm deletion unless --force
268
273
  unless options[:force]
269
- puts "This will uninstall the language-operator from namespace '#{namespace}'"
274
+ puts 'This will completely uninstall the language-operator from your cluster:'
270
275
  puts
271
- puts 'WARNING: This will NOT delete:'
272
- puts ' - CRDs (CustomResourceDefinitions)'
273
- puts ' - LanguageAgent resources'
274
- puts ' - LanguageTool resources'
275
- puts ' - LanguageModel resources'
276
- puts ' - LanguagePersona resources'
276
+ puts ' - The language-operator Helm release'
277
+ puts ' - All existing clusters, agents, models, tools and personas'
278
+ puts ' - All organization namespaces and their resources'
279
+ puts ' - Persistent volumes (ClickHouse, PostgreSQL data)'
280
+ puts ' - Cluster-scoped resources (RBAC, webhooks, CRDs)'
281
+ puts " - The operator namespace (#{namespace})"
277
282
  puts
278
- return unless CLI::Helpers::UserPrompts.confirm('Continue with uninstall?')
283
+ puts "#{pastel.bold.red('WARNING')}: #{pastel.white.bold('This action cannot be undone!')}"
284
+ puts
285
+ return unless CLI::Helpers::UserPrompts.confirm('Continue with complete uninstall?')
279
286
  end
280
287
 
281
- # Build helm uninstall command
282
- cmd = "helm uninstall #{RELEASE_NAME} --namespace #{namespace}"
288
+ # Step 1: Delete all custom resources
289
+ delete_all_custom_resources
283
290
 
284
- # Execute helm uninstall
285
- Formatters::ProgressFormatter.with_spinner('Uninstalling language-operator') do
286
- success, output = run_helm_command(cmd)
287
- raise "Helm uninstall failed: #{output}" unless success
291
+ # Step 2: Delete organization namespaces
292
+ delete_organization_namespaces
293
+
294
+ # Step 3: Uninstall Helm release if it exists
295
+ if operator_installed?
296
+ cmd = "helm uninstall #{RELEASE_NAME} --namespace #{namespace}"
297
+
298
+ Formatters::ProgressFormatter.with_spinner('Uninstalling language-operator Helm release') do
299
+ success, output = run_helm_command(cmd)
300
+ raise "Helm uninstall failed: #{output}" unless success
301
+ end
288
302
  end
289
303
 
290
- Formatters::ProgressFormatter.success('Language operator uninstalled successfully!')
291
- puts
292
- puts 'Note: CRDs and custom resources remain in the cluster.'
293
- puts 'To completely remove all resources, you must manually delete them.'
304
+ # Step 4: Delete PVCs
305
+ delete_persistent_volumes
306
+
307
+ # Step 5: Clean up cluster-scoped resources
308
+ delete_cluster_scoped_resources
309
+
310
+ # Step 6: Delete CRDs
311
+ delete_language_operator_crds
312
+
313
+ # Step 7: Delete the operator namespace
314
+ delete_operator_namespace(namespace)
294
315
  end
295
316
  end
296
317
 
@@ -358,28 +379,35 @@ module LanguageOperator
358
379
  end
359
380
 
360
381
  def build_helm_command(action, namespace)
382
+ # Use @options if available (from interactive mode), otherwise use options
383
+ opts = @options || options
384
+
361
385
  cmd = ['helm', action, RELEASE_NAME]
362
386
 
363
- # Add chart name for install
364
- cmd << CHART_NAME if action == 'install'
387
+ # Add chart name for install and upgrade
388
+ cmd << CHART_NAME if %w[install upgrade].include?(action)
365
389
 
366
390
  # Add namespace
367
391
  cmd << '--namespace' << namespace
368
392
 
369
393
  # Add create-namespace for install
370
- cmd << '--create-namespace' if action == 'install' && options[:create_namespace]
394
+ cmd << '--create-namespace' if action == 'install' && opts[:create_namespace]
371
395
 
372
- # Add values file
373
- cmd << '--values' << options[:values] if options[:values]
396
+ # Add values file or reuse existing values for upgrade
397
+ if opts[:values]
398
+ cmd << '--values' << opts[:values]
399
+ elsif action == 'upgrade'
400
+ cmd << '--reuse-values'
401
+ end
374
402
 
375
403
  # Add version
376
- cmd << '--version' << options[:version] if options[:version]
404
+ cmd << '--version' << opts[:version] if opts[:version]
377
405
 
378
406
  # Add wait
379
- cmd << '--wait' if options[:wait]
407
+ cmd << '--wait' if opts[:wait]
380
408
 
381
409
  # Add dry-run
382
- cmd << '--dry-run' if options[:dry_run]
410
+ cmd << '--dry-run' if opts[:dry_run]
383
411
 
384
412
  cmd.join(' ')
385
413
  end
@@ -389,6 +417,775 @@ module LanguageOperator
389
417
  output = stdout + stderr
390
418
  [status.success?, output.strip]
391
419
  end
420
+
421
+ # Delete all language-operator custom resources from all namespaces
422
+ def delete_all_custom_resources
423
+ require_relative '../../kubernetes/client'
424
+ require_relative '../../constants'
425
+
426
+ k8s = Kubernetes::Client.new
427
+
428
+ resource_types = [
429
+ Constants::RESOURCE_AGENT,
430
+ Constants::RESOURCE_AGENT_VERSION,
431
+ Constants::RESOURCE_TOOL,
432
+ Constants::RESOURCE_MODEL,
433
+ Constants::RESOURCE_PERSONA,
434
+ 'LanguageCluster'
435
+ ]
436
+
437
+ resource_types.each do |resource_type|
438
+ begin
439
+ # Delete and wait for all resources of this type to be completely removed
440
+ Formatters::ProgressFormatter.with_spinner("Deleting all #{resource_type} resources") do
441
+ # First, get resources from all namespaces
442
+ resources = k8s.list_resources(resource_type, namespace: nil)
443
+
444
+ # Trigger deletion of each resource
445
+ resources.each do |resource|
446
+ name = resource.dig('metadata', 'name')
447
+ namespace = resource.dig('metadata', 'namespace')
448
+
449
+ begin
450
+ k8s.delete_resource(resource_type, name, namespace)
451
+ rescue StandardError => e
452
+ # Continue deleting other resources even if one fails
453
+ warn "Failed to delete #{resource_type} #{name}: #{e.message}" if ENV['DEBUG']
454
+ end
455
+ end
456
+
457
+ # Then wait for all resources of this type to be completely removed
458
+ wait_for_resources_deletion_inline(resource_type, k8s, timeout: 300)
459
+ end
460
+ rescue StandardError => e
461
+ # If API group doesn't exist (CRDs already deleted), show success anyway
462
+ if e.message.include?('404') || e.message.include?('Not Found')
463
+ puts " - #{resource_type} API not available (CRDs likely already deleted)" if ENV['DEBUG']
464
+ Formatters::ProgressFormatter.success("Deleting all #{resource_type} resources... done")
465
+ else
466
+ Formatters::ProgressFormatter.warn("Failed to delete #{resource_type} resources: #{e.message}")
467
+ end
468
+ end
469
+ end
470
+ end
471
+
472
+ # Delete language-operator CRDs
473
+ def delete_language_operator_crds
474
+ require_relative '../../kubernetes/client'
475
+ require_relative '../../constants'
476
+
477
+ k8s = Kubernetes::Client.new
478
+
479
+ Formatters::ProgressFormatter.with_spinner('Deleting language-operator CRDs') do
480
+ # Find all CRDs with langop.io domain (CRDs are in apiextensions.k8s.io/v1)
481
+ all_crds = k8s.list_resources('CustomResourceDefinition', namespace: nil, api_version: 'apiextensions.k8s.io/v1')
482
+ langop_crds = all_crds.select do |crd|
483
+ name = crd.dig('metadata', 'name')
484
+ name&.end_with?('.langop.io')
485
+ end
486
+
487
+ puts "Found #{langop_crds.length} langop.io CRDs to delete" if ENV['DEBUG']
488
+
489
+ langop_crds.each do |crd|
490
+ crd_name = crd.dig('metadata', 'name')
491
+ puts "Deleting CRD: #{crd_name}" if ENV['DEBUG']
492
+ begin
493
+ k8s.delete_resource('CustomResourceDefinition', crd_name, nil, 'apiextensions.k8s.io/v1')
494
+ puts "Successfully deleted CRD: #{crd_name}" if ENV['DEBUG']
495
+ rescue StandardError => e
496
+ # Continue deleting other CRDs even if one fails
497
+ puts "Failed to delete CRD #{crd_name}: #{e.message}"
498
+ end
499
+ end
500
+
501
+ puts 'No langop.io CRDs found to delete' if langop_crds.empty? && ENV.fetch('DEBUG', nil)
502
+ rescue StandardError => e
503
+ puts "Failed to list/delete langop.io CRDs: #{e.message}"
504
+ end
505
+ end
506
+
507
+ # Delete the operator namespace
508
+ def delete_operator_namespace(namespace)
509
+ require_relative '../../kubernetes/client'
510
+
511
+ k8s = Kubernetes::Client.new
512
+
513
+ Formatters::ProgressFormatter.with_spinner("Deleting operator namespace: #{namespace}") do
514
+ # Check if namespace exists before trying to delete
515
+ if k8s.namespace_exists?(namespace)
516
+ k8s.delete_resource('Namespace', namespace)
517
+ elsif ENV['DEBUG']
518
+ puts "Namespace #{namespace} doesn't exist, skipping"
519
+ end
520
+ rescue StandardError => e
521
+ warn "Failed to delete namespace #{namespace}: #{e.message}" if ENV['DEBUG']
522
+ end
523
+ end
524
+
525
+ # Restart the language-operator deployment to ensure new version is running
526
+ def restart_language_operator_deployment(namespace)
527
+ Formatters::ProgressFormatter.with_spinner('Restarting language-operator deployment') do
528
+ # Use kubectl rollout restart for the deployment
529
+ cmd = "kubectl rollout restart deployment/language-operator --namespace #{namespace}"
530
+
531
+ output = `#{cmd} 2>&1`
532
+ success = $?.success?
533
+
534
+ raise "Failed to restart deployment: #{output}" unless success
535
+ rescue StandardError => e
536
+ # Don't fail the entire upgrade if restart fails
537
+ warn "Warning: Could not restart language-operator deployment: #{e.message}"
538
+ end
539
+ end
540
+
541
+ # Collect interactive configuration from user
542
+ def collect_interactive_configuration
543
+ config = {}
544
+
545
+ # Generate a random password as default
546
+ require 'securerandom'
547
+ default_password = SecureRandom.alphanumeric(12)
548
+
549
+ puts pastel.white.bold('Create a Login')
550
+ config[:admin_name] = prompt.ask('Full Name:', default: 'Default')
551
+ config[:admin_email] = prompt.ask('Email:', default: 'admin@example.com')
552
+ config[:admin_password] = prompt.ask('Password:', default: default_password)
553
+
554
+ puts
555
+ puts pastel.white.bold('Configure a Gateway')
556
+
557
+ # Check if gateways are available first
558
+ gateways = get_available_gateways
559
+
560
+ if gateways.empty?
561
+ puts 'No gateways found in the cluster.'
562
+ puts 'You can configure gateway access later after creating a gateway resource.'
563
+ else
564
+ create_gateway = prompt.yes?('Do you want to configure a gateway for external access?')
565
+ config[:gateway] = collect_gateway_configuration if create_gateway
566
+ end
567
+
568
+ puts
569
+ # Check CNI support first to determine header color
570
+ cni_info = detect_cni_support
571
+ if cni_info[:supports_network_policies]
572
+ puts pastel.white.bold('Network Isolation')
573
+ elsif cni_info[:name] == 'Unknown'
574
+ puts pastel.yellow.bold('Warning: network isolation support not detected')
575
+ else
576
+ puts pastel.yellow.bold("Warning: #{cni_info[:name]} does not support network policies")
577
+ end
578
+ config[:network_isolation] = collect_network_isolation_configuration(cni_info)
579
+
580
+ puts
581
+ config
582
+ end
583
+
584
+ # Collect gateway configuration
585
+ def collect_gateway_configuration
586
+ gateway_config = {}
587
+
588
+ # Get available gateways (we know there are some since we checked before calling this)
589
+ gateways = get_available_gateways
590
+
591
+ # Create choices for the select menu
592
+ choices = gateways.map { |gw| { name: "#{gw[:name]} (#{gw[:namespace]})", value: gw } }
593
+
594
+ selected_gateway = prompt.select('Select a gateway:', choices)
595
+ gateway_config[:gateway_name] = selected_gateway[:name]
596
+ gateway_config[:gateway_namespace] = selected_gateway[:namespace]
597
+
598
+ gateway_config[:hostname] = prompt.ask('Hostname for the gateway:', default: 'langop.local')
599
+ gateway_config[:tls] = prompt.yes?('Enable TLS/HTTPS?')
600
+
601
+ gateway_config
602
+ end
603
+
604
+ # Get available gateways from the cluster
605
+ def get_available_gateways
606
+ require_relative '../../kubernetes/client'
607
+
608
+ begin
609
+ k8s = Kubernetes::Client.new
610
+ gateways = k8s.list_resources('Gateway', namespace: nil, api_version: 'gateway.networking.k8s.io/v1')
611
+ gateways.map do |gw|
612
+ {
613
+ name: gw.dig('metadata', 'name'),
614
+ namespace: gw.dig('metadata', 'namespace')
615
+ }
616
+ end.compact
617
+ rescue StandardError => e
618
+ warn "Failed to list gateways: #{e.message}" if ENV['DEBUG']
619
+ []
620
+ end
621
+ end
622
+
623
+ # Collect network isolation configuration
624
+ def collect_network_isolation_configuration(cni_info)
625
+ if cni_info[:supports_network_policies]
626
+ enable_isolation = prompt.yes?("Enforce network isolation policies with #{cni_info[:name]}?")
627
+ { enabled: enable_isolation }
628
+ else
629
+ proceed = prompt.yes?('Continue without network isolation?')
630
+ exit 1 unless proceed
631
+ { enabled: false }
632
+ end
633
+ end
634
+
635
+ # Detect CNI and NetworkPolicy support
636
+ def detect_cni_support
637
+ require_relative '../../kubernetes/client'
638
+
639
+ begin
640
+ k8s = Kubernetes::Client.new
641
+
642
+ # Try to detect CNI by checking nodes for CNI annotations or by looking for specific resources
643
+ nodes = k8s.list_resources('Node', namespace: nil)
644
+
645
+ # Check for common CNI indicators
646
+ cni_info = detect_cni_from_nodes(nodes)
647
+
648
+ # Only test API availability if we couldn't determine CNI-specific support
649
+ if cni_info[:name] == 'Unknown'
650
+ begin
651
+ k8s.list_resources('NetworkPolicy', namespace: 'kube-system', api_version: 'networking.k8s.io/v1')
652
+ cni_info[:supports_network_policies] = true
653
+ rescue StandardError
654
+ cni_info[:supports_network_policies] = false
655
+ end
656
+ end
657
+
658
+ cni_info
659
+ rescue StandardError => e
660
+ warn "Failed to detect CNI: #{e.message}" if ENV['DEBUG']
661
+ { name: 'Unknown', supports_network_policies: false }
662
+ end
663
+ end
664
+
665
+ # Detect CNI from CRDs (most reliable method)
666
+ def detect_cni_from_nodes(_nodes)
667
+ require_relative '../../kubernetes/client'
668
+ k8s = Kubernetes::Client.new
669
+
670
+ # Check for CNI-specific CRDs - this is the most reliable method
671
+ begin
672
+ crds = k8s.list_resources('CustomResourceDefinition', namespace: nil, api_version: 'apiextensions.k8s.io/v1')
673
+
674
+ crd_names = crds.map { |crd| crd.dig('metadata', 'name') }.compact.join(' ')
675
+
676
+ # Check for specific CNI CRDs
677
+ if crd_names.match?(/cilium/i)
678
+ return { name: 'Cilium', supports_network_policies: true }
679
+ elsif crd_names.match?(/(calico|projectcalico)/i)
680
+ return { name: 'Calico', supports_network_policies: true }
681
+ elsif crd_names.match?(/weave/i)
682
+ return { name: 'Weave Net', supports_network_policies: true }
683
+ elsif crd_names.match?(/antrea/i)
684
+ return { name: 'Antrea', supports_network_policies: true }
685
+ elsif crd_names.match?(/flannel/i)
686
+ return { name: 'Flannel', supports_network_policies: false }
687
+ end
688
+ rescue StandardError => e
689
+ warn "Failed to check CRDs for CNI detection: #{e.message}" if ENV['DEBUG']
690
+ end
691
+
692
+ # Fallback: Check for CNI-specific pods in kube-system
693
+ begin
694
+ pods = k8s.list_resources('Pod', namespace: 'kube-system')
695
+ pod_names = pods.map { |pod| pod.dig('metadata', 'name') }.compact.join(' ')
696
+
697
+ if pod_names.match?(/cilium/i)
698
+ return { name: 'Cilium', supports_network_policies: true }
699
+ elsif pod_names.match?(/calico/i)
700
+ return { name: 'Calico', supports_network_policies: true }
701
+ elsif pod_names.match?(/weave/i)
702
+ return { name: 'Weave Net', supports_network_policies: true }
703
+ elsif pod_names.match?(/antrea/i)
704
+ return { name: 'Antrea', supports_network_policies: true }
705
+ elsif pod_names.match?(/flannel/i)
706
+ return { name: 'Flannel', supports_network_policies: false }
707
+ elsif pod_names.match?(/aws-node/i)
708
+ return { name: 'AWS VPC CNI', supports_network_policies: false }
709
+ end
710
+ rescue StandardError => e
711
+ warn "Failed to check pods for CNI detection: #{e.message}" if ENV['DEBUG']
712
+ end
713
+
714
+ # Final fallback - we couldn't identify the CNI
715
+ { name: 'Unknown', supports_network_policies: false }
716
+ end
717
+
718
+ # Generate values.yaml file from configuration
719
+ def generate_values_file(config)
720
+ require 'tempfile'
721
+ require 'bcrypt'
722
+ require 'yaml'
723
+
724
+ # Hash the password and convert to string
725
+ password_hash = BCrypt::Password.create(config[:admin_password]).to_s
726
+
727
+ values = {
728
+ 'dashboard' => {
729
+ 'initialSetup' => {
730
+ 'enabled' => true,
731
+ 'adminUser' => {
732
+ 'name' => config[:admin_name],
733
+ 'email' => config[:admin_email],
734
+ 'passwordHash' => password_hash
735
+ }
736
+ },
737
+ 'features' => {
738
+ 'signupsDisabled' => true
739
+ }
740
+ },
741
+ 'networkIsolation' => {
742
+ 'enabled' => config.dig(:network_isolation, :enabled) || false
743
+ }
744
+ }
745
+
746
+ # Add gateway configuration if provided
747
+ if config[:gateway]
748
+ values['dashboard']['gateway'] = {
749
+ 'enabled' => true,
750
+ 'gatewayName' => config[:gateway][:gateway_name],
751
+ 'gatewayNamespace' => config[:gateway][:gateway_namespace],
752
+ 'hostname' => config[:gateway][:hostname],
753
+ 'tls' => {
754
+ 'enabled' => config[:gateway][:tls]
755
+ }
756
+ }
757
+ end
758
+
759
+ # Create temporary values file
760
+ values_file = Tempfile.new(['langop-values', '.yaml'])
761
+ values_file.write(YAML.dump(values))
762
+ values_file.close
763
+
764
+ values_file.path
765
+ end
766
+
767
+ # Delete all organization namespaces
768
+ def delete_organization_namespaces
769
+ require_relative '../../kubernetes/client'
770
+
771
+ k8s = Kubernetes::Client.new
772
+ namespace_names = []
773
+
774
+ # Delete and wait for all organization namespaces to be completely removed
775
+ Formatters::ProgressFormatter.with_spinner('Deleting organization namespaces') do
776
+ begin
777
+ # Find all namespaces with organization label
778
+ org_namespaces = k8s.list_namespaces(
779
+ label_selector: 'langop.io/type=organization'
780
+ )
781
+
782
+ # Trigger deletion of each namespace
783
+ org_namespaces.each do |namespace|
784
+ name = namespace.dig('metadata', 'name')
785
+ namespace_names << name
786
+ org_id = namespace.dig('metadata', 'labels', 'langop.io/organization-id')
787
+
788
+ begin
789
+ k8s.delete_resource('Namespace', name)
790
+ rescue StandardError => e
791
+ # Continue deleting other namespaces even if one fails
792
+ warn "Failed to delete organization namespace #{name} (#{org_id}): #{e.message}" if ENV['DEBUG']
793
+ end
794
+ end
795
+
796
+ # Then wait for all organization namespaces to be completely deleted
797
+ wait_for_namespaces_deletion_inline(namespace_names, k8s, timeout: 300) if namespace_names.any?
798
+ rescue StandardError => e
799
+ warn "Failed to list/delete organization namespaces: #{e.message}" if ENV['DEBUG']
800
+ end
801
+ end
802
+ end
803
+
804
+ # Delete all language-operator persistent volumes
805
+ def delete_persistent_volumes
806
+ require_relative '../../kubernetes/client'
807
+
808
+ k8s = Kubernetes::Client.new
809
+
810
+ Formatters::ProgressFormatter.with_spinner('Deleting persistent volumes') do
811
+ # Use label selector to find language-operator PVCs
812
+ label_selector = 'app.kubernetes.io/instance=language-operator'
813
+
814
+ begin
815
+ all_pvcs = k8s.list_resources('PersistentVolumeClaim', namespace: nil, label_selector: label_selector)
816
+
817
+ all_pvcs.each do |pvc|
818
+ name = pvc.dig('metadata', 'name')
819
+ namespace = pvc.dig('metadata', 'namespace')
820
+
821
+ begin
822
+ k8s.delete_resource('PersistentVolumeClaim', name, namespace)
823
+ rescue StandardError => e
824
+ warn "Failed to delete PVC #{name}: #{e.message}" if ENV['DEBUG']
825
+ end
826
+ end
827
+ rescue StandardError => e
828
+ warn "Failed to list/delete PVCs: #{e.message}" if ENV['DEBUG']
829
+ end
830
+ end
831
+ end
832
+
833
+ # Delete cluster-scoped resources that Helm doesn't automatically remove
834
+ def delete_cluster_scoped_resources
835
+ require_relative '../../kubernetes/client'
836
+
837
+ k8s = Kubernetes::Client.new
838
+
839
+ Formatters::ProgressFormatter.with_spinner('Cleaning up cluster-scoped resources') do
840
+ # 1. Delete ClusterRoles
841
+ delete_cluster_roles(k8s)
842
+
843
+ # 2. Delete ClusterRoleBindings
844
+ delete_cluster_role_bindings(k8s)
845
+
846
+ # 3. Delete webhook configurations
847
+ delete_webhook_configurations(k8s)
848
+
849
+ # 4. Remove finalizers from stuck resources
850
+ remove_stuck_finalizers(k8s)
851
+ end
852
+ end
853
+
854
+ # Delete language-operator ClusterRoles
855
+ def delete_cluster_roles(k8s)
856
+ # Find ClusterRoles with language-operator labels
857
+ cluster_roles = k8s.list_resources('ClusterRole',
858
+ namespace: nil,
859
+ api_version: 'rbac.authorization.k8s.io/v1',
860
+ label_selector: 'app.kubernetes.io/name=language-operator')
861
+
862
+ cluster_roles.each do |cr|
863
+ name = cr.dig('metadata', 'name')
864
+ begin
865
+ k8s.delete_resource('ClusterRole', name, nil, 'rbac.authorization.k8s.io/v1')
866
+ rescue StandardError => e
867
+ warn "Failed to delete ClusterRole #{name}: #{e.message}" if ENV['DEBUG']
868
+ end
869
+ end
870
+ rescue StandardError => e
871
+ warn "Failed to list/delete ClusterRoles: #{e.message}" if ENV['DEBUG']
872
+ end
873
+
874
+ # Delete language-operator ClusterRoleBindings
875
+ def delete_cluster_role_bindings(k8s)
876
+ # Find ClusterRoleBindings with language-operator labels
877
+ crbs = k8s.list_resources('ClusterRoleBinding',
878
+ namespace: nil,
879
+ api_version: 'rbac.authorization.k8s.io/v1',
880
+ label_selector: 'app.kubernetes.io/name=language-operator')
881
+
882
+ crbs.each do |crb|
883
+ name = crb.dig('metadata', 'name')
884
+ begin
885
+ k8s.delete_resource('ClusterRoleBinding', name, nil, 'rbac.authorization.k8s.io/v1')
886
+ rescue StandardError => e
887
+ warn "Failed to delete ClusterRoleBinding #{name}: #{e.message}" if ENV['DEBUG']
888
+ end
889
+ end
890
+
891
+ # Get all ClusterRoleBindings to find agent-specific ones and ServiceAccount references
892
+ all_crbs = k8s.list_resources('ClusterRoleBinding', namespace: nil, api_version: 'rbac.authorization.k8s.io/v1')
893
+ all_crbs.each do |crb|
894
+ name = crb.dig('metadata', 'name')
895
+ should_delete = false
896
+
897
+ # Check if this is an agent-specific ClusterRoleBinding (pattern: language-agent-*)
898
+ if name&.start_with?('language-agent-')
899
+ should_delete = true
900
+ puts "Found agent-specific ClusterRoleBinding: #{name}" if ENV['DEBUG']
901
+ end
902
+
903
+ # Also check if CRB references language-operator ServiceAccounts
904
+ unless should_delete
905
+ subjects = crb.dig('subjects') || []
906
+ has_langop_subject = subjects.any? do |subject|
907
+ subject['name']&.include?('language-operator')
908
+ end
909
+
910
+ if has_langop_subject
911
+ should_delete = true
912
+ puts "Found ClusterRoleBinding referencing language-operator ServiceAccount: #{name}" if ENV['DEBUG']
913
+ end
914
+ end
915
+
916
+ # Also check if CRB references non-existent ClusterRoles that we know about
917
+ unless should_delete
918
+ role_ref = crb.dig('roleRef')
919
+ if role_ref && role_ref['kind'] == 'ClusterRole' && role_ref['name'] == 'language-operator'
920
+ should_delete = true
921
+ puts "Found ClusterRoleBinding referencing deleted ClusterRole 'language-operator': #{name}" if ENV['DEBUG']
922
+ end
923
+ end
924
+
925
+ # Delete if we found a reason to
926
+ next unless should_delete
927
+
928
+ begin
929
+ k8s.delete_resource('ClusterRoleBinding', name, nil, 'rbac.authorization.k8s.io/v1')
930
+ puts "Successfully deleted ClusterRoleBinding: #{name}" if ENV['DEBUG']
931
+ rescue StandardError => e
932
+ warn "Failed to delete ClusterRoleBinding #{name}: #{e.message}" if ENV['DEBUG']
933
+ end
934
+ end
935
+ rescue StandardError => e
936
+ warn "Failed to list/delete ClusterRoleBindings: #{e.message}" if ENV['DEBUG']
937
+ end
938
+
939
+ # Delete webhook configurations
940
+ def delete_webhook_configurations(k8s)
941
+ # Delete ValidatingWebhookConfigurations
942
+ begin
943
+ vwcs = k8s.list_resources('ValidatingWebhookConfiguration',
944
+ namespace: nil,
945
+ label_selector: 'app.kubernetes.io/name=language-operator')
946
+
947
+ vwcs.each do |vwc|
948
+ name = vwc.dig('metadata', 'name')
949
+ begin
950
+ k8s.delete_resource('ValidatingWebhookConfiguration', name)
951
+ rescue StandardError => e
952
+ warn "Failed to delete ValidatingWebhookConfiguration #{name}: #{e.message}" if ENV['DEBUG']
953
+ end
954
+ end
955
+ rescue StandardError => e
956
+ warn "Failed to list/delete ValidatingWebhookConfigurations: #{e.message}" if ENV['DEBUG']
957
+ end
958
+
959
+ # Delete MutatingWebhookConfigurations
960
+ begin
961
+ mwcs = k8s.list_resources('MutatingWebhookConfiguration',
962
+ namespace: nil,
963
+ label_selector: 'app.kubernetes.io/name=language-operator')
964
+
965
+ mwcs.each do |mwc|
966
+ name = mwc.dig('metadata', 'name')
967
+ begin
968
+ k8s.delete_resource('MutatingWebhookConfiguration', name)
969
+ rescue StandardError => e
970
+ warn "Failed to delete MutatingWebhookConfiguration #{name}: #{e.message}" if ENV['DEBUG']
971
+ end
972
+ end
973
+ rescue StandardError => e
974
+ warn "Failed to list/delete MutatingWebhookConfigurations: #{e.message}" if ENV['DEBUG']
975
+ end
976
+ end
977
+
978
+ # Remove finalizers from stuck resources
979
+ def remove_stuck_finalizers(k8s)
980
+ # Remove finalizers from LanguageOperator CRDs if they're stuck
981
+ crd_names = [
982
+ 'languageagents.langop.io',
983
+ 'languagetools.langop.io',
984
+ 'languagemodels.langop.io',
985
+ 'languagepersonas.langop.io',
986
+ 'languageclusters.langop.io',
987
+ 'languageagentversions.langop.io'
988
+ ]
989
+
990
+ crd_names.each do |crd_name|
991
+ crd = k8s.get_resource('CustomResourceDefinition', crd_name, nil, 'apiextensions.k8s.io/v1')
992
+
993
+ # Check if CRD has finalizers
994
+ finalizers = crd.dig('metadata', 'finalizers')
995
+ if finalizers && !finalizers.empty?
996
+ # Remove finalizers to allow deletion
997
+ patch = {
998
+ 'metadata' => {
999
+ 'finalizers' => []
1000
+ }
1001
+ }
1002
+ k8s.patch_resource('CustomResourceDefinition', crd_name, patch, namespace: nil, api_version: 'apiextensions.k8s.io/v1')
1003
+ end
1004
+ rescue K8s::Error::NotFound
1005
+ # CRD doesn't exist, skip
1006
+ rescue StandardError => e
1007
+ warn "Failed to remove finalizers from CRD #{crd_name}: #{e.message}" if ENV['DEBUG']
1008
+ end
1009
+
1010
+ # Remove finalizers from stuck custom resources
1011
+ resource_types = %w[LanguageAgent LanguageTool LanguageModel LanguagePersona LanguageCluster LanguageAgentVersion]
1012
+ resource_types.each do |resource_type|
1013
+ resources = k8s.list_resources(resource_type, namespace: nil)
1014
+ resources.each do |resource|
1015
+ finalizers = resource.dig('metadata', 'finalizers')
1016
+ next unless finalizers && !finalizers.empty?
1017
+
1018
+ name = resource.dig('metadata', 'name')
1019
+ namespace = resource.dig('metadata', 'namespace')
1020
+
1021
+ patch = {
1022
+ 'metadata' => {
1023
+ 'finalizers' => []
1024
+ }
1025
+ }
1026
+ k8s.patch_resource(resource_type, name, patch, namespace: namespace)
1027
+ end
1028
+ rescue StandardError => e
1029
+ warn "Failed to remove finalizers from #{resource_type} resources: #{e.message}" if ENV['DEBUG']
1030
+ end
1031
+ rescue StandardError => e
1032
+ warn "Failed to remove stuck finalizers: #{e.message}" if ENV['DEBUG']
1033
+ end
1034
+
1035
+ # Verify installation and setup
1036
+ def verify_installation(config)
1037
+ # Wait a moment for pods to start
1038
+ sleep 3
1039
+
1040
+ # Verify account setup
1041
+ account_ready = verify_account_setup
1042
+
1043
+ # Verify gateway setup if configured
1044
+ gateway_ready = false
1045
+ gateway_url = nil
1046
+
1047
+ gateway_ready, gateway_url = verify_gateway_setup(config[:gateway]) if config&.dig(:gateway)
1048
+
1049
+ # Determine next steps based on verification results
1050
+ show_post_install_message(config, account_ready, gateway_ready, gateway_url)
1051
+ end
1052
+
1053
+ # Verify that the admin account was created successfully
1054
+ def verify_account_setup
1055
+ Formatters::ProgressFormatter.with_spinner('Verifying account setup') do
1056
+ # Check if dashboard pod is ready
1057
+ require_relative '../../kubernetes/client'
1058
+ k8s = Kubernetes::Client.new
1059
+
1060
+ begin
1061
+ # Check if dashboard deployment is ready
1062
+ deployment = k8s.get_resource('Deployment', 'language-operator-dashboard', 'language-operator')
1063
+ ready_replicas = deployment.dig('status', 'readyReplicas') || 0
1064
+ desired_replicas = deployment.dig('spec', 'replicas') || 1
1065
+
1066
+ ready_replicas >= desired_replicas
1067
+ rescue StandardError
1068
+ false
1069
+ end
1070
+ end
1071
+ end
1072
+
1073
+ # Verify gateway configuration and HTTPRoute creation
1074
+ def verify_gateway_setup(gateway_config)
1075
+ gateway_url = nil
1076
+
1077
+ success = Formatters::ProgressFormatter.with_spinner('Verifying gateway setup') do
1078
+ require_relative '../../kubernetes/client'
1079
+ k8s = Kubernetes::Client.new
1080
+
1081
+ begin
1082
+ # Check if HTTPRoute was created (it should be in the gateway namespace)
1083
+ httproute_namespace = gateway_config[:gateway_namespace]
1084
+ k8s.get_resource('HTTPRoute', 'language-operator-dashboard', httproute_namespace, 'gateway.networking.k8s.io/v1')
1085
+
1086
+ # Build the public URL
1087
+ protocol = gateway_config[:tls] ? 'https' : 'http'
1088
+ gateway_url = "#{protocol}://#{gateway_config[:hostname]}"
1089
+
1090
+ true
1091
+ rescue StandardError => e
1092
+ warn "Gateway verification failed: #{e.message}" if ENV['DEBUG']
1093
+ false
1094
+ end
1095
+ end
1096
+
1097
+ [success, gateway_url]
1098
+ end
1099
+
1100
+ # Show appropriate post-installation message and actions
1101
+ def show_post_install_message(config, _account_ready, gateway_ready, gateway_url)
1102
+ if gateway_ready && gateway_url
1103
+ # Gateway configured successfully - open public URL
1104
+ puts
1105
+ puts 'Your Language Operator dashboard is available at:'
1106
+ puts pastel.cyan.bold(gateway_url)
1107
+ puts
1108
+
1109
+ # Open browser to public URL
1110
+ open_browser(gateway_url) unless (@options || options)[:no_open]
1111
+
1112
+ elsif config&.dig(:gateway) && !gateway_ready
1113
+ # Gateway was configured but failed
1114
+ puts pastel.yellow('⚠') + ' Installation completed with warnings'
1115
+ puts
1116
+ puts pastel.yellow('Gateway configuration had issues. You can access the dashboard locally:')
1117
+ puts "Run: #{pastel.cyan('langop ui')}"
1118
+
1119
+ else
1120
+ # No gateway configured - show local access instructions
1121
+ puts
1122
+ puts "Run #{pastel.cyan.bold('langop ui')} to get started!"
1123
+ end
1124
+ end
1125
+
1126
+ # Open browser to URL (delegates to UI command implementation)
1127
+ def open_browser(url)
1128
+ require_relative 'ui'
1129
+ ui_command = Ui.new
1130
+ ui_command.send(:open_browser, url)
1131
+ end
1132
+
1133
+ # Wait for custom resources of a specific type to be completely deleted (inline with existing operation)
1134
+ def wait_for_resources_deletion_inline(resource_type, k8s, timeout: 300)
1135
+ start_time = Time.now
1136
+
1137
+ loop do
1138
+ resources = k8s.list_resources(resource_type, namespace: nil)
1139
+ return true if resources.empty?
1140
+
1141
+ # Check timeout
1142
+ if Time.now - start_time > timeout
1143
+ remaining_count = resources.length
1144
+ warn "Timeout waiting for #{resource_type} resources to be deleted (#{remaining_count} remaining)" if ENV['DEBUG']
1145
+ return false
1146
+ end
1147
+
1148
+ # Sleep before next check
1149
+ sleep 2
1150
+ rescue StandardError => e
1151
+ # If API group doesn't exist (CRDs already deleted), consider it success
1152
+ return true if e.message.include?('404') || e.message.include?('Not Found')
1153
+
1154
+ # Check timeout for other errors too
1155
+ if Time.now - start_time > timeout
1156
+ warn "Timeout waiting for #{resource_type} resources: #{e.message}" if ENV['DEBUG']
1157
+ return false
1158
+ end
1159
+
1160
+ sleep 2
1161
+ end
1162
+ end
1163
+
1164
+ # Wait for specific namespaces to be completely deleted (inline with existing operation)
1165
+ def wait_for_namespaces_deletion_inline(namespace_names, k8s, timeout: 300)
1166
+ start_time = Time.now
1167
+ remaining_namespaces = namespace_names.dup
1168
+
1169
+ loop do
1170
+ remaining_namespaces = remaining_namespaces.select do |name|
1171
+ k8s.namespace_exists?(name)
1172
+ rescue StandardError
1173
+ # If we can't check, assume it's gone
1174
+ false
1175
+ end
1176
+
1177
+ return true if remaining_namespaces.empty?
1178
+
1179
+ # Check timeout
1180
+ if Time.now - start_time > timeout
1181
+ warn "Timeout waiting for namespaces to be deleted: #{remaining_namespaces.join(', ')}" if ENV['DEBUG']
1182
+ return false
1183
+ end
1184
+
1185
+ # Sleep before next check
1186
+ sleep 2
1187
+ end
1188
+ end
392
1189
  end
393
1190
  end
394
1191
  end