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.
- checksums.yaml +4 -4
- data/.claude/agents/README.md +16 -0
- data/.claude/agents/changelog-writer.md +181 -0
- data/.claude/hooks/README.md +43 -0
- data/.claude/hooks/node_modules/.bin/esbuild +1 -0
- data/.claude/hooks/node_modules/.bin/tsc +1 -0
- data/.claude/hooks/node_modules/.bin/tsserver +1 -0
- data/.claude/hooks/node_modules/.bin/tsx +1 -0
- data/.claude/hooks/node_modules/.package-lock.json +136 -0
- data/.claude/hooks/node_modules/@esbuild/linux-x64/README.md +3 -0
- data/.claude/hooks/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
- data/.claude/hooks/node_modules/@esbuild/linux-x64/package.json +20 -0
- data/.claude/hooks/node_modules/@types/node/LICENSE +21 -0
- data/.claude/hooks/node_modules/@types/node/README.md +15 -0
- data/.claude/hooks/node_modules/@types/node/assert/strict.d.ts +105 -0
- data/.claude/hooks/node_modules/@types/node/assert.d.ts +955 -0
- data/.claude/hooks/node_modules/@types/node/async_hooks.d.ts +623 -0
- data/.claude/hooks/node_modules/@types/node/buffer.buffer.d.ts +466 -0
- data/.claude/hooks/node_modules/@types/node/buffer.d.ts +1810 -0
- data/.claude/hooks/node_modules/@types/node/child_process.d.ts +1428 -0
- data/.claude/hooks/node_modules/@types/node/cluster.d.ts +486 -0
- data/.claude/hooks/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
- data/.claude/hooks/node_modules/@types/node/console.d.ts +151 -0
- data/.claude/hooks/node_modules/@types/node/constants.d.ts +20 -0
- data/.claude/hooks/node_modules/@types/node/crypto.d.ts +4065 -0
- data/.claude/hooks/node_modules/@types/node/dgram.d.ts +564 -0
- data/.claude/hooks/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
- data/.claude/hooks/node_modules/@types/node/dns/promises.d.ts +503 -0
- data/.claude/hooks/node_modules/@types/node/dns.d.ts +922 -0
- data/.claude/hooks/node_modules/@types/node/domain.d.ts +166 -0
- data/.claude/hooks/node_modules/@types/node/events.d.ts +1054 -0
- data/.claude/hooks/node_modules/@types/node/fs/promises.d.ts +1316 -0
- data/.claude/hooks/node_modules/@types/node/fs.d.ts +4676 -0
- data/.claude/hooks/node_modules/@types/node/globals.d.ts +150 -0
- data/.claude/hooks/node_modules/@types/node/globals.typedarray.d.ts +101 -0
- data/.claude/hooks/node_modules/@types/node/http.d.ts +2143 -0
- data/.claude/hooks/node_modules/@types/node/http2.d.ts +2480 -0
- data/.claude/hooks/node_modules/@types/node/https.d.ts +399 -0
- data/.claude/hooks/node_modules/@types/node/index.d.ts +115 -0
- data/.claude/hooks/node_modules/@types/node/inspector/promises.d.ts +41 -0
- data/.claude/hooks/node_modules/@types/node/inspector.d.ts +224 -0
- data/.claude/hooks/node_modules/@types/node/inspector.generated.d.ts +4226 -0
- data/.claude/hooks/node_modules/@types/node/module.d.ts +819 -0
- data/.claude/hooks/node_modules/@types/node/net.d.ts +933 -0
- data/.claude/hooks/node_modules/@types/node/os.d.ts +507 -0
- data/.claude/hooks/node_modules/@types/node/package.json +155 -0
- data/.claude/hooks/node_modules/@types/node/path/posix.d.ts +8 -0
- data/.claude/hooks/node_modules/@types/node/path/win32.d.ts +8 -0
- data/.claude/hooks/node_modules/@types/node/path.d.ts +187 -0
- data/.claude/hooks/node_modules/@types/node/perf_hooks.d.ts +621 -0
- data/.claude/hooks/node_modules/@types/node/process.d.ts +2097 -0
- data/.claude/hooks/node_modules/@types/node/punycode.d.ts +117 -0
- data/.claude/hooks/node_modules/@types/node/querystring.d.ts +152 -0
- data/.claude/hooks/node_modules/@types/node/quic.d.ts +910 -0
- data/.claude/hooks/node_modules/@types/node/readline/promises.d.ts +161 -0
- data/.claude/hooks/node_modules/@types/node/readline.d.ts +541 -0
- data/.claude/hooks/node_modules/@types/node/repl.d.ts +415 -0
- data/.claude/hooks/node_modules/@types/node/sea.d.ts +162 -0
- data/.claude/hooks/node_modules/@types/node/sqlite.d.ts +937 -0
- data/.claude/hooks/node_modules/@types/node/stream/consumers.d.ts +38 -0
- data/.claude/hooks/node_modules/@types/node/stream/promises.d.ts +211 -0
- data/.claude/hooks/node_modules/@types/node/stream/web.d.ts +296 -0
- data/.claude/hooks/node_modules/@types/node/stream.d.ts +1760 -0
- data/.claude/hooks/node_modules/@types/node/string_decoder.d.ts +67 -0
- data/.claude/hooks/node_modules/@types/node/test/reporters.d.ts +96 -0
- data/.claude/hooks/node_modules/@types/node/test.d.ts +2239 -0
- data/.claude/hooks/node_modules/@types/node/timers/promises.d.ts +108 -0
- data/.claude/hooks/node_modules/@types/node/timers.d.ts +159 -0
- data/.claude/hooks/node_modules/@types/node/tls.d.ts +1194 -0
- data/.claude/hooks/node_modules/@types/node/trace_events.d.ts +197 -0
- data/.claude/hooks/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
- data/.claude/hooks/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
- data/.claude/hooks/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
- data/.claude/hooks/node_modules/@types/node/ts5.6/index.d.ts +117 -0
- data/.claude/hooks/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
- data/.claude/hooks/node_modules/@types/node/ts5.7/index.d.ts +117 -0
- data/.claude/hooks/node_modules/@types/node/tty.d.ts +250 -0
- data/.claude/hooks/node_modules/@types/node/url.d.ts +519 -0
- data/.claude/hooks/node_modules/@types/node/util/types.d.ts +558 -0
- data/.claude/hooks/node_modules/@types/node/util.d.ts +1653 -0
- data/.claude/hooks/node_modules/@types/node/v8.d.ts +979 -0
- data/.claude/hooks/node_modules/@types/node/vm.d.ts +1180 -0
- data/.claude/hooks/node_modules/@types/node/wasi.d.ts +202 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/blob.d.ts +23 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/console.d.ts +9 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/events.d.ts +106 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/fetch.d.ts +54 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/performance.d.ts +45 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/storage.d.ts +24 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/streams.d.ts +115 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/timers.d.ts +44 -0
- data/.claude/hooks/node_modules/@types/node/web-globals/url.d.ts +24 -0
- data/.claude/hooks/node_modules/@types/node/worker_threads.d.ts +714 -0
- data/.claude/hooks/node_modules/@types/node/zlib.d.ts +618 -0
- data/.claude/hooks/node_modules/esbuild/LICENSE.md +21 -0
- data/.claude/hooks/node_modules/esbuild/README.md +3 -0
- data/.claude/hooks/node_modules/esbuild/bin/esbuild +0 -0
- data/.claude/hooks/node_modules/esbuild/install.js +289 -0
- data/.claude/hooks/node_modules/esbuild/lib/main.d.ts +716 -0
- data/.claude/hooks/node_modules/esbuild/lib/main.js +2242 -0
- data/.claude/hooks/node_modules/esbuild/package.json +49 -0
- data/.claude/hooks/node_modules/get-tsconfig/LICENSE +21 -0
- data/.claude/hooks/node_modules/get-tsconfig/README.md +235 -0
- data/.claude/hooks/node_modules/get-tsconfig/dist/index.cjs +7 -0
- data/.claude/hooks/node_modules/get-tsconfig/dist/index.d.cts +2088 -0
- data/.claude/hooks/node_modules/get-tsconfig/dist/index.d.mts +2088 -0
- data/.claude/hooks/node_modules/get-tsconfig/dist/index.mjs +7 -0
- data/.claude/hooks/node_modules/get-tsconfig/package.json +46 -0
- data/.claude/hooks/node_modules/resolve-pkg-maps/LICENSE +21 -0
- data/.claude/hooks/node_modules/resolve-pkg-maps/README.md +216 -0
- data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.cjs +1 -0
- data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.d.cts +11 -0
- data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.d.mts +11 -0
- data/.claude/hooks/node_modules/resolve-pkg-maps/dist/index.mjs +1 -0
- data/.claude/hooks/node_modules/resolve-pkg-maps/package.json +42 -0
- data/.claude/hooks/node_modules/tsx/LICENSE +21 -0
- data/.claude/hooks/node_modules/tsx/README.md +32 -0
- data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
- data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
- data/.claude/hooks/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/cjs/index.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/cjs/index.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/cli.cjs +54 -0
- data/.claude/hooks/node_modules/tsx/dist/cli.mjs +55 -0
- data/.claude/hooks/node_modules/tsx/dist/client-BQVF1NaW.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/client-D6NvIMSC.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/esm/api/index.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
- data/.claude/hooks/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
- data/.claude/hooks/node_modules/tsx/dist/esm/api/index.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/esm/index.cjs +2 -0
- data/.claude/hooks/node_modules/tsx/dist/esm/index.mjs +2 -0
- data/.claude/hooks/node_modules/tsx/dist/get-pipe-path-BHW2eJdv.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/get-pipe-path-BoR10qr8.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/index-7AaEi15b.mjs +14 -0
- data/.claude/hooks/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/index-gckBtVBf.cjs +14 -0
- data/.claude/hooks/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
- data/.claude/hooks/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
- data/.claude/hooks/node_modules/tsx/dist/loader.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/loader.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/node-features-_8ZFwP_x.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/node-features-roYmp9jK.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/package-CeBgXWuR.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/package-Dxt5kIHw.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/patch-repl.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/patch-repl.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/preflight.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/preflight.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/register-2sWVXuRQ.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/register-B7jrtLTO.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/register-CFH5oNdT.mjs +4 -0
- data/.claude/hooks/node_modules/tsx/dist/register-D46fvsV_.cjs +4 -0
- data/.claude/hooks/node_modules/tsx/dist/repl.cjs +3 -0
- data/.claude/hooks/node_modules/tsx/dist/repl.mjs +3 -0
- data/.claude/hooks/node_modules/tsx/dist/require-D4F1Lv60.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/require-DQxpCAr4.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/temporary-directory-CwHp0_NW.mjs +1 -0
- data/.claude/hooks/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
- data/.claude/hooks/node_modules/tsx/package.json +68 -0
- data/.claude/hooks/node_modules/typescript/LICENSE.txt +55 -0
- data/.claude/hooks/node_modules/typescript/README.md +50 -0
- data/.claude/hooks/node_modules/typescript/SECURITY.md +41 -0
- data/.claude/hooks/node_modules/typescript/ThirdPartyNoticeText.txt +193 -0
- data/.claude/hooks/node_modules/typescript/bin/tsc +2 -0
- data/.claude/hooks/node_modules/typescript/bin/tsserver +2 -0
- data/.claude/hooks/node_modules/typescript/lib/_tsc.js +133818 -0
- data/.claude/hooks/node_modules/typescript/lib/_tsserver.js +659 -0
- data/.claude/hooks/node_modules/typescript/lib/_typingsInstaller.js +222 -0
- data/.claude/hooks/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/de/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/es/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/it/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.d.ts +22 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.decorators.d.ts +384 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.decorators.legacy.d.ts +22 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +41 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.dom.d.ts +39429 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.dom.iterable.d.ts +571 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.collection.d.ts +147 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.core.d.ts +597 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.d.ts +28 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.generator.d.ts +77 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.iterable.d.ts +605 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.promise.d.ts +81 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.proxy.d.ts +128 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.reflect.d.ts +144 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.symbol.d.ts +46 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +326 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2016.array.include.d.ts +116 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2016.d.ts +21 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2016.full.d.ts +23 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2016.intl.d.ts +31 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +21 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.d.ts +26 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.date.d.ts +31 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.full.d.ts +23 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.intl.d.ts +44 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.object.d.ts +49 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +135 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.string.d.ts +45 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +53 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +77 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +53 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2018.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2018.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2018.intl.d.ts +83 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2018.promise.d.ts +30 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2018.regexp.d.ts +37 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2019.array.d.ts +79 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2019.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2019.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2019.intl.d.ts +23 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2019.object.d.ts +33 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2019.string.d.ts +37 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2019.symbol.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.bigint.d.ts +765 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.d.ts +27 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.date.d.ts +42 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.intl.d.ts +474 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.number.d.ts +28 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.promise.d.ts +47 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +99 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.string.d.ts +44 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +41 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2021.d.ts +23 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2021.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2021.intl.d.ts +166 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2021.promise.d.ts +48 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2021.string.d.ts +33 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2021.weakref.d.ts +78 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.array.d.ts +121 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.d.ts +25 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.error.d.ts +75 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.intl.d.ts +145 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.object.d.ts +26 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.regexp.d.ts +39 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2022.string.d.ts +25 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2023.array.d.ts +924 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2023.collection.d.ts +21 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2023.d.ts +22 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2023.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2023.intl.d.ts +56 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +65 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.collection.d.ts +29 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.d.ts +26 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.object.d.ts +29 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.promise.d.ts +35 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.regexp.d.ts +25 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +68 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es2024.string.d.ts +29 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es5.d.ts +4601 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.es6.d.ts +23 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.array.d.ts +35 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.collection.d.ts +96 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.d.ts +29 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.decorators.d.ts +28 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.disposable.d.ts +193 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.error.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.float16.d.ts +445 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.full.d.ts +24 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.intl.d.ts +21 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.iterator.d.ts +148 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.promise.d.ts +34 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +25 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.scripthost.d.ts +322 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +41 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.webworker.d.ts +13150 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +23 -0
- data/.claude/hooks/node_modules/typescript/lib/lib.webworker.iterable.d.ts +340 -0
- data/.claude/hooks/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/tsc.js +8 -0
- data/.claude/hooks/node_modules/typescript/lib/tsserver.js +8 -0
- data/.claude/hooks/node_modules/typescript/lib/tsserverlibrary.d.ts +17 -0
- data/.claude/hooks/node_modules/typescript/lib/tsserverlibrary.js +21 -0
- data/.claude/hooks/node_modules/typescript/lib/typesMap.json +497 -0
- data/.claude/hooks/node_modules/typescript/lib/typescript.d.ts +11437 -0
- data/.claude/hooks/node_modules/typescript/lib/typescript.js +200276 -0
- data/.claude/hooks/node_modules/typescript/lib/typingsInstaller.js +8 -0
- data/.claude/hooks/node_modules/typescript/lib/watchGuard.js +53 -0
- data/.claude/hooks/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +2122 -0
- data/.claude/hooks/node_modules/typescript/package.json +120 -0
- data/.claude/hooks/node_modules/undici-types/LICENSE +21 -0
- data/.claude/hooks/node_modules/undici-types/README.md +6 -0
- data/.claude/hooks/node_modules/undici-types/agent.d.ts +32 -0
- data/.claude/hooks/node_modules/undici-types/api.d.ts +43 -0
- data/.claude/hooks/node_modules/undici-types/balanced-pool.d.ts +29 -0
- data/.claude/hooks/node_modules/undici-types/cache-interceptor.d.ts +172 -0
- data/.claude/hooks/node_modules/undici-types/cache.d.ts +36 -0
- data/.claude/hooks/node_modules/undici-types/client-stats.d.ts +15 -0
- data/.claude/hooks/node_modules/undici-types/client.d.ts +108 -0
- data/.claude/hooks/node_modules/undici-types/connector.d.ts +34 -0
- data/.claude/hooks/node_modules/undici-types/content-type.d.ts +21 -0
- data/.claude/hooks/node_modules/undici-types/cookies.d.ts +30 -0
- data/.claude/hooks/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
- data/.claude/hooks/node_modules/undici-types/dispatcher.d.ts +276 -0
- data/.claude/hooks/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
- data/.claude/hooks/node_modules/undici-types/errors.d.ts +161 -0
- data/.claude/hooks/node_modules/undici-types/eventsource.d.ts +66 -0
- data/.claude/hooks/node_modules/undici-types/fetch.d.ts +211 -0
- data/.claude/hooks/node_modules/undici-types/formdata.d.ts +108 -0
- data/.claude/hooks/node_modules/undici-types/global-dispatcher.d.ts +9 -0
- data/.claude/hooks/node_modules/undici-types/global-origin.d.ts +7 -0
- data/.claude/hooks/node_modules/undici-types/h2c-client.d.ts +73 -0
- data/.claude/hooks/node_modules/undici-types/handlers.d.ts +15 -0
- data/.claude/hooks/node_modules/undici-types/header.d.ts +160 -0
- data/.claude/hooks/node_modules/undici-types/index.d.ts +80 -0
- data/.claude/hooks/node_modules/undici-types/interceptors.d.ts +39 -0
- data/.claude/hooks/node_modules/undici-types/mock-agent.d.ts +68 -0
- data/.claude/hooks/node_modules/undici-types/mock-call-history.d.ts +111 -0
- data/.claude/hooks/node_modules/undici-types/mock-client.d.ts +27 -0
- data/.claude/hooks/node_modules/undici-types/mock-errors.d.ts +12 -0
- data/.claude/hooks/node_modules/undici-types/mock-interceptor.d.ts +94 -0
- data/.claude/hooks/node_modules/undici-types/mock-pool.d.ts +27 -0
- data/.claude/hooks/node_modules/undici-types/package.json +55 -0
- data/.claude/hooks/node_modules/undici-types/patch.d.ts +29 -0
- data/.claude/hooks/node_modules/undici-types/pool-stats.d.ts +19 -0
- data/.claude/hooks/node_modules/undici-types/pool.d.ts +41 -0
- data/.claude/hooks/node_modules/undici-types/proxy-agent.d.ts +29 -0
- data/.claude/hooks/node_modules/undici-types/readable.d.ts +68 -0
- data/.claude/hooks/node_modules/undici-types/retry-agent.d.ts +8 -0
- data/.claude/hooks/node_modules/undici-types/retry-handler.d.ts +125 -0
- data/.claude/hooks/node_modules/undici-types/snapshot-agent.d.ts +109 -0
- data/.claude/hooks/node_modules/undici-types/util.d.ts +18 -0
- data/.claude/hooks/node_modules/undici-types/utility.d.ts +7 -0
- data/.claude/hooks/node_modules/undici-types/webidl.d.ts +341 -0
- data/.claude/hooks/node_modules/undici-types/websocket.d.ts +186 -0
- data/.claude/hooks/package-lock.json +562 -0
- data/.claude/hooks/package.json +19 -0
- data/.claude/hooks/skill-activation-prompt.sh +11 -0
- data/.claude/hooks/skill-activation-prompt.ts +185 -0
- data/.claude/hooks/tsconfig.json +12 -0
- data/.claude/settings.json +33 -0
- data/.claude/skills/README.md +29 -0
- data/.claude/skills/ruby-gem-development/SKILL.md +293 -0
- data/.claude/skills/skill-rules.json +117 -0
- data/.claude/skills/thor-cli-development/SKILL.md +431 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +3 -1
- data/completions/{_aictl → _langop} +37 -37
- data/completions/{aictl.bash → langop.bash} +16 -16
- data/completions/langop.fish +114 -0
- data/components/agent/Gemfile +1 -1
- data/components/tool/Gemfile +1 -1
- data/docs/README.md +10 -10
- data/docs/agent-internals.md +1 -1
- data/docs/cheat-sheet.md +76 -76
- data/docs/cli-reference.md +71 -71
- data/docs/how-agents-work.md +1 -1
- data/docs/installation.md +30 -30
- data/docs/quickstart.md +30 -30
- data/docs/using-tools.md +11 -11
- data/docs/webhooks.md +6 -6
- data/examples/ux_helpers_demo.rb +4 -4
- data/lib/language_operator/agent/task_executor.rb +0 -37
- data/lib/language_operator/agent/web_server.rb +311 -323
- data/lib/language_operator/cli/commands/agent/base.rb +16 -15
- data/lib/language_operator/cli/commands/agent/code_operations.rb +6 -6
- data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +1 -1
- data/lib/language_operator/cli/commands/agent/lifecycle.rb +2 -2
- data/lib/language_operator/cli/commands/agent/logs.rb +2 -2
- data/lib/language_operator/cli/commands/agent/workspace.rb +8 -8
- data/lib/language_operator/cli/commands/cluster.rb +172 -158
- data/lib/language_operator/cli/commands/install.rb +878 -81
- data/lib/language_operator/cli/commands/model/base.rb +7 -6
- data/lib/language_operator/cli/commands/organization.rb +61 -0
- data/lib/language_operator/cli/commands/persona.rb +10 -8
- data/lib/language_operator/cli/commands/status.rb +83 -13
- data/lib/language_operator/cli/commands/system/exec.rb +10 -10
- data/lib/language_operator/cli/commands/system/schema.rb +6 -6
- data/lib/language_operator/cli/commands/system/synthesis_template.rb +6 -6
- data/lib/language_operator/cli/commands/system/synthesize.rb +14 -14
- data/lib/language_operator/cli/commands/system/validate_template.rb +4 -4
- data/lib/language_operator/cli/commands/tool/base.rb +1 -1
- data/lib/language_operator/cli/commands/tool/install.rb +2 -2
- data/lib/language_operator/cli/commands/tool/search.rb +3 -3
- data/lib/language_operator/cli/commands/ui.rb +173 -0
- data/lib/language_operator/cli/commands/use.rb +80 -9
- data/lib/language_operator/cli/errors/suggestions.rb +21 -21
- data/lib/language_operator/cli/formatters/progress_formatter.rb +2 -1
- data/lib/language_operator/cli/formatters/table_formatter.rb +21 -1
- data/lib/language_operator/cli/helpers/cluster_validator.rb +2 -2
- data/lib/language_operator/cli/helpers/health_checker.rb +263 -0
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +3 -3
- data/lib/language_operator/cli/helpers/ux_helper.rb +4 -3
- data/lib/language_operator/cli/main.rb +30 -21
- data/lib/language_operator/cli/wizards/model_wizard.rb +7 -5
- data/lib/language_operator/config/cluster_config.rb +3 -3
- data/lib/language_operator/constants/kubernetes_labels.rb +1 -1
- data/lib/language_operator/kubernetes/client.rb +53 -109
- data/lib/language_operator/kubernetes/resource_builder.rb +46 -14
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
- data/lib/language_operator/utils/org_context.rb +100 -0
- data/lib/language_operator/version.rb +1 -1
- data/synth/001/Makefile +1 -1
- data/synth/002/Makefile +1 -1
- data/synth/003/Makefile +1 -1
- data/synth/004/Makefile +1 -1
- metadata +384 -11
- data/completions/aictl.fish +0 -114
- data/lib/language_operator/agent/event_config.rb +0 -172
- data/lib/language_operator/cli/commands/quickstart.rb +0 -22
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -561
- /data/bin/{aictl → langop} +0 -0
|
@@ -215,6 +215,317 @@ module LanguageOperator
|
|
|
215
215
|
puts 'Registered /api/v1/workspace/* endpoints for file management'
|
|
216
216
|
end
|
|
217
217
|
|
|
218
|
+
# Workspace file management handlers
|
|
219
|
+
|
|
220
|
+
# Handle GET /api/v1/workspace/files - list directory contents
|
|
221
|
+
def handle_workspace_list(context)
|
|
222
|
+
request = context[:request]
|
|
223
|
+
path = request.params['path'] || '/'
|
|
224
|
+
|
|
225
|
+
# Sanitize path to prevent directory traversal
|
|
226
|
+
safe_path = sanitize_workspace_path(path)
|
|
227
|
+
full_path = File.join(@workspace_path, safe_path)
|
|
228
|
+
|
|
229
|
+
return workspace_error_response(404, 'NotFound', "Path not found: #{path}") unless File.exist?(full_path)
|
|
230
|
+
|
|
231
|
+
if File.directory?(full_path)
|
|
232
|
+
list_directory_contents(full_path, path)
|
|
233
|
+
else
|
|
234
|
+
# If it's a file, return file info
|
|
235
|
+
get_file_info(full_path, path)
|
|
236
|
+
end
|
|
237
|
+
rescue StandardError => e
|
|
238
|
+
workspace_error_response(500, 'InternalError', e.message)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Handle GET /api/v1/workspace/files/view - view file contents
|
|
242
|
+
def handle_workspace_view(context)
|
|
243
|
+
request = context[:request]
|
|
244
|
+
path = request.params['path']
|
|
245
|
+
|
|
246
|
+
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path
|
|
247
|
+
|
|
248
|
+
# Sanitize path to prevent directory traversal
|
|
249
|
+
safe_path = sanitize_workspace_path(path)
|
|
250
|
+
full_path = File.join(@workspace_path, safe_path)
|
|
251
|
+
|
|
252
|
+
return workspace_error_response(404, 'NotFound', "File not found: #{path}") unless File.exist?(full_path)
|
|
253
|
+
|
|
254
|
+
return workspace_error_response(400, 'BadRequest', "Path is not a file: #{path}") unless File.file?(full_path)
|
|
255
|
+
|
|
256
|
+
# Check file size (limit to 10MB for API responses)
|
|
257
|
+
file_size = File.size(full_path)
|
|
258
|
+
return workspace_error_response(413, 'FileTooLarge', 'File too large for viewing (max 10MB)') if file_size > 10 * 1024 * 1024
|
|
259
|
+
|
|
260
|
+
# Read file contents
|
|
261
|
+
contents = File.read(full_path)
|
|
262
|
+
|
|
263
|
+
{
|
|
264
|
+
status: 200,
|
|
265
|
+
body: {
|
|
266
|
+
path: path,
|
|
267
|
+
size: file_size,
|
|
268
|
+
contents: contents
|
|
269
|
+
},
|
|
270
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
271
|
+
}
|
|
272
|
+
rescue StandardError => e
|
|
273
|
+
workspace_error_response(500, 'InternalError', e.message)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Handle POST /api/v1/workspace/files - upload file
|
|
277
|
+
def handle_workspace_upload(context)
|
|
278
|
+
request = context[:request]
|
|
279
|
+
|
|
280
|
+
# Parse multipart form data
|
|
281
|
+
if request.content_type&.start_with?('multipart/form-data')
|
|
282
|
+
boundary = request.content_type.split('boundary=')[1]
|
|
283
|
+
return workspace_error_response(400, 'BadRequest', 'Missing boundary in multipart data') unless boundary
|
|
284
|
+
|
|
285
|
+
# Parse the multipart data
|
|
286
|
+
body = request.body.read
|
|
287
|
+
parts = parse_multipart(body, boundary)
|
|
288
|
+
|
|
289
|
+
file_part = parts.find { |part| part[:name] == 'file' }
|
|
290
|
+
path_param = parts.find { |part| part[:name] == 'path' }&.dig(:content)
|
|
291
|
+
|
|
292
|
+
return workspace_error_response(400, 'BadRequest', 'file parameter required') unless file_part
|
|
293
|
+
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path_param
|
|
294
|
+
|
|
295
|
+
# Sanitize path to prevent directory traversal
|
|
296
|
+
safe_path = sanitize_workspace_path(path_param)
|
|
297
|
+
full_path = File.join(@workspace_path, safe_path)
|
|
298
|
+
|
|
299
|
+
# Check file size (limit to 100MB)
|
|
300
|
+
content = file_part[:content]
|
|
301
|
+
return workspace_error_response(413, 'FileTooLarge', 'File too large (max 100MB)') if content.bytesize > 100 * 1024 * 1024
|
|
302
|
+
|
|
303
|
+
# Create directory if it doesn't exist
|
|
304
|
+
FileUtils.mkdir_p(File.dirname(full_path))
|
|
305
|
+
|
|
306
|
+
# Write file
|
|
307
|
+
File.binwrite(full_path, content)
|
|
308
|
+
|
|
309
|
+
{
|
|
310
|
+
status: 201,
|
|
311
|
+
body: {
|
|
312
|
+
message: 'File uploaded successfully',
|
|
313
|
+
path: path_param,
|
|
314
|
+
size: content.bytesize
|
|
315
|
+
},
|
|
316
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
317
|
+
}
|
|
318
|
+
else
|
|
319
|
+
workspace_error_response(400, 'BadRequest', 'Expected multipart/form-data')
|
|
320
|
+
end
|
|
321
|
+
rescue StandardError => e
|
|
322
|
+
workspace_error_response(500, 'UploadError', "Upload failed: #{e.message}")
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Handle DELETE /api/v1/workspace/files - delete file
|
|
326
|
+
def handle_workspace_delete(context)
|
|
327
|
+
request = context[:request]
|
|
328
|
+
path = request.params['path']
|
|
329
|
+
|
|
330
|
+
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path
|
|
331
|
+
|
|
332
|
+
# Sanitize path to prevent directory traversal
|
|
333
|
+
safe_path = sanitize_workspace_path(path)
|
|
334
|
+
full_path = File.join(@workspace_path, safe_path)
|
|
335
|
+
|
|
336
|
+
return workspace_error_response(404, 'NotFound', "File not found: #{path}") unless File.exist?(full_path)
|
|
337
|
+
|
|
338
|
+
# Delete file or directory
|
|
339
|
+
if File.directory?(full_path)
|
|
340
|
+
FileUtils.rm_rf(full_path)
|
|
341
|
+
else
|
|
342
|
+
File.delete(full_path)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
{
|
|
346
|
+
status: 200,
|
|
347
|
+
body: {
|
|
348
|
+
message: 'File deleted successfully',
|
|
349
|
+
path: path
|
|
350
|
+
},
|
|
351
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
352
|
+
}
|
|
353
|
+
rescue StandardError => e
|
|
354
|
+
workspace_error_response(500, 'DeleteError', "Deletion failed: #{e.message}")
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Handle GET /api/v1/workspace/files/download - download file or directory as tar.gz
|
|
358
|
+
def handle_workspace_download(context)
|
|
359
|
+
request = context[:request]
|
|
360
|
+
path = request.params['path']
|
|
361
|
+
|
|
362
|
+
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path
|
|
363
|
+
|
|
364
|
+
# Sanitize path to prevent directory traversal
|
|
365
|
+
safe_path = sanitize_workspace_path(path)
|
|
366
|
+
full_path = File.join(@workspace_path, safe_path)
|
|
367
|
+
|
|
368
|
+
return workspace_error_response(404, 'NotFound', "Path not found: #{path}") unless File.exist?(full_path)
|
|
369
|
+
|
|
370
|
+
if File.file?(full_path)
|
|
371
|
+
# Single file download
|
|
372
|
+
content = File.binread(full_path)
|
|
373
|
+
filename = File.basename(full_path)
|
|
374
|
+
|
|
375
|
+
{
|
|
376
|
+
status: 200,
|
|
377
|
+
body: content,
|
|
378
|
+
headers: {
|
|
379
|
+
'Content-Type' => 'application/octet-stream',
|
|
380
|
+
'Content-Disposition' => "attachment; filename=\"#{filename}\""
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else
|
|
384
|
+
# Directory download as tar.gz
|
|
385
|
+
require 'tempfile'
|
|
386
|
+
require 'zlib'
|
|
387
|
+
require 'rubygems/package'
|
|
388
|
+
|
|
389
|
+
Tempfile.create(['workspace', '.tar.gz']) do |temp_file|
|
|
390
|
+
Zlib::GzipWriter.open(temp_file.path) do |gz|
|
|
391
|
+
Gem::Package::TarWriter.new(gz) do |tar|
|
|
392
|
+
add_directory_to_tar(tar, full_path, safe_path)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
content = File.binread(temp_file.path)
|
|
397
|
+
filename = "#{File.basename(path)}.tar.gz"
|
|
398
|
+
|
|
399
|
+
{
|
|
400
|
+
status: 200,
|
|
401
|
+
body: content,
|
|
402
|
+
headers: {
|
|
403
|
+
'Content-Type' => 'application/gzip',
|
|
404
|
+
'Content-Disposition' => "attachment; filename=\"#{filename}\""
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
rescue StandardError => e
|
|
410
|
+
workspace_error_response(500, 'DownloadError', "Download failed: #{e.message}")
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Workspace helper methods
|
|
414
|
+
|
|
415
|
+
# Sanitize workspace path to prevent directory traversal attacks
|
|
416
|
+
def sanitize_workspace_path(path)
|
|
417
|
+
# Remove leading slash and resolve relative paths
|
|
418
|
+
clean_path = path.sub(%r{^/+}, '')
|
|
419
|
+
|
|
420
|
+
# Resolve and normalize the path
|
|
421
|
+
normalized = File.expand_path(clean_path, '/')
|
|
422
|
+
|
|
423
|
+
# Ensure it doesn't escape the root
|
|
424
|
+
return '' if normalized == '/' || !normalized.start_with?('/')
|
|
425
|
+
|
|
426
|
+
# Remove leading slash for joining with workspace_path
|
|
427
|
+
normalized[1..]
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# List directory contents
|
|
431
|
+
def list_directory_contents(full_path, requested_path)
|
|
432
|
+
entries = Dir.entries(full_path).reject { |entry| entry.start_with?('.') }
|
|
433
|
+
|
|
434
|
+
files = entries.map do |entry|
|
|
435
|
+
entry_path = File.join(full_path, entry)
|
|
436
|
+
{
|
|
437
|
+
name: entry,
|
|
438
|
+
type: File.directory?(entry_path) ? 'directory' : 'file',
|
|
439
|
+
size: File.file?(entry_path) ? File.size(entry_path) : nil,
|
|
440
|
+
path: File.join(requested_path, entry).sub(%r{^/+}, '')
|
|
441
|
+
}
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
{
|
|
445
|
+
status: 200,
|
|
446
|
+
body: {
|
|
447
|
+
path: requested_path,
|
|
448
|
+
files: files
|
|
449
|
+
},
|
|
450
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
451
|
+
}
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Get file information
|
|
455
|
+
def get_file_info(full_path, requested_path)
|
|
456
|
+
{
|
|
457
|
+
status: 200,
|
|
458
|
+
body: {
|
|
459
|
+
path: requested_path,
|
|
460
|
+
size: File.size(full_path),
|
|
461
|
+
type: 'file'
|
|
462
|
+
},
|
|
463
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
464
|
+
}
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Parse multipart form data
|
|
468
|
+
def parse_multipart(body, boundary)
|
|
469
|
+
parts = []
|
|
470
|
+
boundary = "--#{boundary}"
|
|
471
|
+
sections = body.split(boundary)
|
|
472
|
+
|
|
473
|
+
sections[1..-2]&.each do |section|
|
|
474
|
+
next if section.strip.empty?
|
|
475
|
+
|
|
476
|
+
lines = section.split("\r\n")
|
|
477
|
+
content_disposition = lines.find { |line| line.include?('Content-Disposition') }
|
|
478
|
+
next unless content_disposition
|
|
479
|
+
|
|
480
|
+
name_match = content_disposition.match(/name="([^"]+)"/)
|
|
481
|
+
next unless name_match
|
|
482
|
+
|
|
483
|
+
name = name_match[1]
|
|
484
|
+
|
|
485
|
+
# Find empty line separating headers from content
|
|
486
|
+
content_start = lines.index('')
|
|
487
|
+
next unless content_start
|
|
488
|
+
|
|
489
|
+
content = lines[(content_start + 1)..-1].join("\r\n")
|
|
490
|
+
content = content[0..-3] if content.end_with?("\r\n") # Remove trailing CRLF
|
|
491
|
+
|
|
492
|
+
parts << { name: name, content: content }
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
parts
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Add directory to tar archive recursively
|
|
499
|
+
def add_directory_to_tar(tar, source_path, archive_path)
|
|
500
|
+
Dir.entries(source_path).each do |entry|
|
|
501
|
+
next if entry.start_with?('.')
|
|
502
|
+
|
|
503
|
+
full_path = File.join(source_path, entry)
|
|
504
|
+
tar_path = File.join(archive_path, entry)
|
|
505
|
+
|
|
506
|
+
if File.directory?(full_path)
|
|
507
|
+
tar.mkdir(tar_path, 0755)
|
|
508
|
+
add_directory_to_tar(tar, full_path, tar_path)
|
|
509
|
+
else
|
|
510
|
+
tar.add_file(tar_path, 0644) do |io|
|
|
511
|
+
io.write(File.binread(full_path))
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Generate workspace error response
|
|
518
|
+
def workspace_error_response(status, error_type, message)
|
|
519
|
+
{
|
|
520
|
+
status: status,
|
|
521
|
+
body: {
|
|
522
|
+
error: error_type,
|
|
523
|
+
message: message
|
|
524
|
+
},
|
|
525
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
526
|
+
}
|
|
527
|
+
end
|
|
528
|
+
|
|
218
529
|
# Handle incoming HTTP request
|
|
219
530
|
#
|
|
220
531
|
# @param env [Hash] Rack environment
|
|
@@ -966,330 +1277,7 @@ module LanguageOperator
|
|
|
966
1277
|
stream.close
|
|
967
1278
|
end
|
|
968
1279
|
|
|
969
|
-
# Workspace file management handlers
|
|
970
1280
|
|
|
971
|
-
# Handle GET /api/v1/workspace/files - list directory contents
|
|
972
|
-
def handle_workspace_list(context)
|
|
973
|
-
request = context[:request]
|
|
974
|
-
path = request.params['path'] || '/'
|
|
975
|
-
|
|
976
|
-
# Sanitize path to prevent directory traversal
|
|
977
|
-
safe_path = sanitize_workspace_path(path)
|
|
978
|
-
full_path = File.join(@workspace_path, safe_path)
|
|
979
|
-
|
|
980
|
-
return workspace_error_response(404, 'NotFound', "Path not found: #{path}") unless File.exist?(full_path)
|
|
981
|
-
|
|
982
|
-
if File.directory?(full_path)
|
|
983
|
-
list_directory_contents(full_path, path)
|
|
984
|
-
else
|
|
985
|
-
# If it's a file, return file info
|
|
986
|
-
get_file_info(full_path, path)
|
|
987
|
-
end
|
|
988
|
-
rescue StandardError => e
|
|
989
|
-
workspace_error_response(500, 'InternalError', e.message)
|
|
990
|
-
end
|
|
991
|
-
|
|
992
|
-
# Handle GET /api/v1/workspace/files/view - view file contents
|
|
993
|
-
def handle_workspace_view(context)
|
|
994
|
-
request = context[:request]
|
|
995
|
-
path = request.params['path']
|
|
996
|
-
|
|
997
|
-
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path
|
|
998
|
-
|
|
999
|
-
# Sanitize path to prevent directory traversal
|
|
1000
|
-
safe_path = sanitize_workspace_path(path)
|
|
1001
|
-
full_path = File.join(@workspace_path, safe_path)
|
|
1002
|
-
|
|
1003
|
-
return workspace_error_response(404, 'NotFound', "File not found: #{path}") unless File.exist?(full_path)
|
|
1004
|
-
|
|
1005
|
-
return workspace_error_response(400, 'BadRequest', "Path is not a file: #{path}") unless File.file?(full_path)
|
|
1006
|
-
|
|
1007
|
-
# Check file size (limit to 10MB for API responses)
|
|
1008
|
-
file_size = File.size(full_path)
|
|
1009
|
-
return workspace_error_response(413, 'FileTooLarge', 'File too large for viewing (max 10MB)') if file_size > 10 * 1024 * 1024
|
|
1010
|
-
|
|
1011
|
-
# Read file contents
|
|
1012
|
-
contents = File.read(full_path)
|
|
1013
|
-
|
|
1014
|
-
{
|
|
1015
|
-
status: 200,
|
|
1016
|
-
body: {
|
|
1017
|
-
path: path,
|
|
1018
|
-
size: file_size,
|
|
1019
|
-
modified: File.mtime(full_path).iso8601,
|
|
1020
|
-
content: contents,
|
|
1021
|
-
content_type: detect_content_type(full_path)
|
|
1022
|
-
}.to_json,
|
|
1023
|
-
headers: { 'Content-Type' => 'application/json' }
|
|
1024
|
-
}
|
|
1025
|
-
rescue StandardError => e
|
|
1026
|
-
workspace_error_response(500, 'InternalError', e.message)
|
|
1027
|
-
end
|
|
1028
|
-
|
|
1029
|
-
# Handle POST /api/v1/workspace/files - upload file
|
|
1030
|
-
def handle_workspace_upload(context)
|
|
1031
|
-
request = context[:request]
|
|
1032
|
-
|
|
1033
|
-
# Check for multipart form data
|
|
1034
|
-
content_type = request.env['CONTENT_TYPE']
|
|
1035
|
-
unless content_type&.start_with?('multipart/form-data')
|
|
1036
|
-
return workspace_error_response(400, 'BadRequest', 'Content-Type must be multipart/form-data')
|
|
1037
|
-
end
|
|
1038
|
-
|
|
1039
|
-
# Parse multipart data
|
|
1040
|
-
begin
|
|
1041
|
-
# Get file from params (Rack automatically parses multipart data)
|
|
1042
|
-
file_param = request.params['file']
|
|
1043
|
-
path_param = request.params['path']
|
|
1044
|
-
|
|
1045
|
-
return workspace_error_response(400, 'BadRequest', 'file parameter required') unless file_param
|
|
1046
|
-
|
|
1047
|
-
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path_param
|
|
1048
|
-
|
|
1049
|
-
# Handle file upload object
|
|
1050
|
-
return workspace_error_response(400, 'BadRequest', 'Invalid file upload format') unless file_param.is_a?(Hash) && file_param[:tempfile]
|
|
1051
|
-
|
|
1052
|
-
# Rack file upload format
|
|
1053
|
-
tempfile = file_param[:tempfile]
|
|
1054
|
-
original_filename = file_param[:filename]
|
|
1055
|
-
content_type = file_param[:type]
|
|
1056
|
-
|
|
1057
|
-
# Sanitize target path
|
|
1058
|
-
safe_path = sanitize_workspace_path(path_param)
|
|
1059
|
-
full_path = File.join(@workspace_path, safe_path)
|
|
1060
|
-
|
|
1061
|
-
# Ensure target directory exists
|
|
1062
|
-
target_dir = File.dirname(full_path)
|
|
1063
|
-
FileUtils.mkdir_p(target_dir) unless File.directory?(target_dir)
|
|
1064
|
-
|
|
1065
|
-
# Check file size (limit to 100MB)
|
|
1066
|
-
file_size = tempfile.size
|
|
1067
|
-
return workspace_error_response(413, 'FileTooLarge', 'File too large for upload (max 100MB)') if file_size > 100 * 1024 * 1024
|
|
1068
|
-
|
|
1069
|
-
# Copy uploaded file to workspace
|
|
1070
|
-
tempfile.rewind
|
|
1071
|
-
File.open(full_path, 'wb') do |dest_file|
|
|
1072
|
-
IO.copy_stream(tempfile, dest_file)
|
|
1073
|
-
end
|
|
1074
|
-
|
|
1075
|
-
# Set reasonable permissions
|
|
1076
|
-
File.chmod(0o644, full_path)
|
|
1077
|
-
|
|
1078
|
-
{
|
|
1079
|
-
status: 201,
|
|
1080
|
-
body: {
|
|
1081
|
-
path: path_param,
|
|
1082
|
-
size: file_size,
|
|
1083
|
-
original_filename: original_filename,
|
|
1084
|
-
content_type: content_type,
|
|
1085
|
-
message: 'File uploaded successfully'
|
|
1086
|
-
}.to_json,
|
|
1087
|
-
headers: { 'Content-Type' => 'application/json' }
|
|
1088
|
-
}
|
|
1089
|
-
rescue StandardError => e
|
|
1090
|
-
workspace_error_response(500, 'UploadError', "Upload failed: #{e.message}")
|
|
1091
|
-
ensure
|
|
1092
|
-
# Clean up temporary file
|
|
1093
|
-
tempfile&.close if tempfile.respond_to?(:close)
|
|
1094
|
-
end
|
|
1095
|
-
end
|
|
1096
|
-
|
|
1097
|
-
# Handle DELETE /api/v1/workspace/files - delete file
|
|
1098
|
-
def handle_workspace_delete(context)
|
|
1099
|
-
request = context[:request]
|
|
1100
|
-
path = request.params['path']
|
|
1101
|
-
|
|
1102
|
-
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path
|
|
1103
|
-
|
|
1104
|
-
# Sanitize path to prevent directory traversal
|
|
1105
|
-
safe_path = sanitize_workspace_path(path)
|
|
1106
|
-
full_path = File.join(@workspace_path, safe_path)
|
|
1107
|
-
|
|
1108
|
-
return workspace_error_response(404, 'NotFound', "Path not found: #{path}") unless File.exist?(full_path)
|
|
1109
|
-
|
|
1110
|
-
begin
|
|
1111
|
-
deleted_info = {
|
|
1112
|
-
path: path,
|
|
1113
|
-
type: File.directory?(full_path) ? 'directory' : 'file',
|
|
1114
|
-
size: File.directory?(full_path) ? nil : File.size(full_path)
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
if File.directory?(full_path)
|
|
1118
|
-
# Delete directory and all contents
|
|
1119
|
-
FileUtils.rm_rf(full_path)
|
|
1120
|
-
deleted_info[:message] = 'Directory deleted successfully'
|
|
1121
|
-
else
|
|
1122
|
-
# Delete single file
|
|
1123
|
-
File.delete(full_path)
|
|
1124
|
-
deleted_info[:message] = 'File deleted successfully'
|
|
1125
|
-
end
|
|
1126
|
-
|
|
1127
|
-
{
|
|
1128
|
-
status: 200,
|
|
1129
|
-
body: deleted_info.to_json,
|
|
1130
|
-
headers: { 'Content-Type' => 'application/json' }
|
|
1131
|
-
}
|
|
1132
|
-
rescue StandardError => e
|
|
1133
|
-
workspace_error_response(500, 'DeleteError', "Delete failed: #{e.message}")
|
|
1134
|
-
end
|
|
1135
|
-
end
|
|
1136
|
-
|
|
1137
|
-
# Handle GET /api/v1/workspace/files/download - download file/archive
|
|
1138
|
-
def handle_workspace_download(context)
|
|
1139
|
-
request = context[:request]
|
|
1140
|
-
path = request.params['path']
|
|
1141
|
-
|
|
1142
|
-
return workspace_error_response(400, 'BadRequest', 'path parameter required') unless path
|
|
1143
|
-
|
|
1144
|
-
# Sanitize path to prevent directory traversal
|
|
1145
|
-
safe_path = sanitize_workspace_path(path)
|
|
1146
|
-
full_path = File.join(@workspace_path, safe_path)
|
|
1147
|
-
|
|
1148
|
-
return workspace_error_response(404, 'NotFound', "Path not found: #{path}") unless File.exist?(full_path)
|
|
1149
|
-
|
|
1150
|
-
begin
|
|
1151
|
-
if File.directory?(full_path)
|
|
1152
|
-
# Create temporary tar.gz archive for directory
|
|
1153
|
-
archive_name = "#{File.basename(safe_path.empty? ? 'workspace' : safe_path)}.tar.gz"
|
|
1154
|
-
temp_archive = "/tmp/#{SecureRandom.hex(8)}_#{archive_name}"
|
|
1155
|
-
|
|
1156
|
-
# Create tar archive
|
|
1157
|
-
system('tar', '-czf', temp_archive, '-C', File.dirname(full_path), File.basename(full_path))
|
|
1158
|
-
|
|
1159
|
-
return workspace_error_response(500, 'ArchiveError', 'Failed to create archive') unless File.exist?(temp_archive)
|
|
1160
|
-
|
|
1161
|
-
# Read archive contents
|
|
1162
|
-
archive_data = File.binread(temp_archive)
|
|
1163
|
-
|
|
1164
|
-
# Clean up temporary file
|
|
1165
|
-
File.delete(temp_archive)
|
|
1166
|
-
|
|
1167
|
-
{
|
|
1168
|
-
status: 200,
|
|
1169
|
-
body: archive_data,
|
|
1170
|
-
headers: {
|
|
1171
|
-
'Content-Type' => 'application/gzip',
|
|
1172
|
-
'Content-Disposition' => "attachment; filename=\"#{archive_name}\"",
|
|
1173
|
-
'Content-Length' => archive_data.length.to_s
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
else
|
|
1177
|
-
# Direct file download
|
|
1178
|
-
file_data = File.binread(full_path)
|
|
1179
|
-
filename = File.basename(safe_path)
|
|
1180
|
-
content_type = detect_content_type(full_path)
|
|
1181
|
-
|
|
1182
|
-
# For binary files, use application/octet-stream
|
|
1183
|
-
content_disposition = if content_type == 'application/octet-stream'
|
|
1184
|
-
"attachment; filename=\"#{filename}\""
|
|
1185
|
-
else
|
|
1186
|
-
# For text files, allow inline viewing
|
|
1187
|
-
"inline; filename=\"#{filename}\""
|
|
1188
|
-
end
|
|
1189
|
-
|
|
1190
|
-
{
|
|
1191
|
-
status: 200,
|
|
1192
|
-
body: file_data,
|
|
1193
|
-
headers: {
|
|
1194
|
-
'Content-Type' => content_type,
|
|
1195
|
-
'Content-Disposition' => content_disposition,
|
|
1196
|
-
'Content-Length' => file_data.length.to_s
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
end
|
|
1200
|
-
rescue StandardError => e
|
|
1201
|
-
workspace_error_response(500, 'DownloadError', "Download failed: #{e.message}")
|
|
1202
|
-
end
|
|
1203
|
-
end
|
|
1204
|
-
|
|
1205
|
-
private
|
|
1206
|
-
|
|
1207
|
-
# Sanitize workspace path to prevent directory traversal attacks
|
|
1208
|
-
def sanitize_workspace_path(path)
|
|
1209
|
-
# Remove leading slash and resolve relative paths
|
|
1210
|
-
clean_path = path.sub(%r{^/+}, '')
|
|
1211
|
-
|
|
1212
|
-
# Resolve and normalize the path
|
|
1213
|
-
normalized = File.expand_path(clean_path, '/')
|
|
1214
|
-
|
|
1215
|
-
# Ensure it doesn't escape the root
|
|
1216
|
-
return '' if normalized == '/' || !normalized.start_with?('/')
|
|
1217
|
-
|
|
1218
|
-
# Remove leading slash for joining with workspace_path
|
|
1219
|
-
normalized[1..]
|
|
1220
|
-
end
|
|
1221
|
-
|
|
1222
|
-
# List directory contents
|
|
1223
|
-
def list_directory_contents(full_path, requested_path)
|
|
1224
|
-
entries = Dir.entries(full_path).reject { |entry| entry.start_with?('.') }
|
|
1225
|
-
|
|
1226
|
-
files = entries.map do |entry|
|
|
1227
|
-
entry_path = File.join(full_path, entry)
|
|
1228
|
-
relative_path = File.join(requested_path == '/' ? '' : requested_path, entry)
|
|
1229
|
-
|
|
1230
|
-
{
|
|
1231
|
-
name: entry,
|
|
1232
|
-
path: "/#{relative_path}".gsub(%r{/+}, '/'),
|
|
1233
|
-
type: File.directory?(entry_path) ? 'directory' : 'file',
|
|
1234
|
-
size: File.directory?(entry_path) ? nil : File.size(entry_path),
|
|
1235
|
-
modified: File.mtime(entry_path).iso8601
|
|
1236
|
-
}
|
|
1237
|
-
end
|
|
1238
|
-
|
|
1239
|
-
files.sort_by! { |f| [f[:type] == 'directory' ? 0 : 1, f[:name]] }
|
|
1240
|
-
|
|
1241
|
-
{
|
|
1242
|
-
status: 200,
|
|
1243
|
-
body: {
|
|
1244
|
-
path: requested_path,
|
|
1245
|
-
files: files,
|
|
1246
|
-
total: files.length
|
|
1247
|
-
}.to_json,
|
|
1248
|
-
headers: { 'Content-Type' => 'application/json' }
|
|
1249
|
-
}
|
|
1250
|
-
end
|
|
1251
|
-
|
|
1252
|
-
# Get file info for a single file
|
|
1253
|
-
def get_file_info(full_path, requested_path)
|
|
1254
|
-
{
|
|
1255
|
-
status: 200,
|
|
1256
|
-
body: {
|
|
1257
|
-
path: requested_path,
|
|
1258
|
-
type: 'file',
|
|
1259
|
-
size: File.size(full_path),
|
|
1260
|
-
modified: File.mtime(full_path).iso8601
|
|
1261
|
-
}.to_json,
|
|
1262
|
-
headers: { 'Content-Type' => 'application/json' }
|
|
1263
|
-
}
|
|
1264
|
-
end
|
|
1265
|
-
|
|
1266
|
-
# Detect content type based on file extension
|
|
1267
|
-
def detect_content_type(file_path)
|
|
1268
|
-
case File.extname(file_path).downcase
|
|
1269
|
-
when '.txt', '.log' then 'text/plain'
|
|
1270
|
-
when '.json' then 'application/json'
|
|
1271
|
-
when '.yml', '.yaml' then 'application/x-yaml'
|
|
1272
|
-
when '.md' then 'text/markdown'
|
|
1273
|
-
when '.html' then 'text/html'
|
|
1274
|
-
when '.css' then 'text/css'
|
|
1275
|
-
when '.js' then 'application/javascript'
|
|
1276
|
-
when '.py' then 'text/x-python'
|
|
1277
|
-
when '.rb' then 'text/x-ruby'
|
|
1278
|
-
else 'application/octet-stream'
|
|
1279
|
-
end
|
|
1280
|
-
end
|
|
1281
|
-
|
|
1282
|
-
# Generate workspace API error response
|
|
1283
|
-
def workspace_error_response(status, error_type, message)
|
|
1284
|
-
{
|
|
1285
|
-
status: status,
|
|
1286
|
-
body: {
|
|
1287
|
-
error: error_type,
|
|
1288
|
-
message: message
|
|
1289
|
-
}.to_json,
|
|
1290
|
-
headers: { 'Content-Type' => 'application/json' }
|
|
1291
|
-
}
|
|
1292
|
-
end
|
|
1293
1281
|
end
|
|
1294
1282
|
end
|
|
1295
1283
|
end
|