diff --git a/bun.lock b/bun.lock index 6f94f9c3..edacee9f 100644 --- a/bun.lock +++ b/bun.lock @@ -43,7 +43,7 @@ }, "packages/app": { "name": "@prover-coder-ai/docker-git", - "version": "1.3.8", + "version": "1.3.10", "bin": { "docker-git": "dist/src/docker-git/main.js", }, @@ -62,6 +62,7 @@ "@effect/workflow": "^0.18.2", "@gridland/bun": "0.4.3", "@gridland/web": "0.4.3", + "@prover-coder-ai/docker-git-openapi": "workspace:*", "@prover-coder-ai/docker-git-session-sync": "workspace:*", "effect": "^3.21.3", "react": "19.2.4", @@ -153,7 +154,7 @@ }, "packages/docker-git-session-sync": { "name": "@prover-coder-ai/docker-git-session-sync", - "version": "1.0.64", + "version": "1.0.66", "bin": { "docker-git-session-sync": "dist/docker-git-session-sync.js", }, @@ -231,6 +232,19 @@ "vitest": "^4.1.9", }, }, + "packages/openapi": { + "name": "@prover-coder-ai/docker-git-openapi", + "version": "0.1.0", + "dependencies": { + "@effect/schema": "^0.75.5", + "effect": "^3.21.3", + "openapi-fetch": "^0.17.0", + }, + "devDependencies": { + "openapi-typescript": "^7.13.0", + "typescript": "^6.0.3", + }, + }, "packages/terminal": { "name": "@prover-coder-ai/docker-git-terminal", "version": "0.1.1", @@ -654,12 +668,20 @@ "@prover-coder-ai/docker-git-container": ["@prover-coder-ai/docker-git-container@workspace:packages/container"], + "@prover-coder-ai/docker-git-openapi": ["@prover-coder-ai/docker-git-openapi@workspace:packages/openapi"], + "@prover-coder-ai/docker-git-session-sync": ["@prover-coder-ai/docker-git-session-sync@workspace:packages/docker-git-session-sync"], "@prover-coder-ai/docker-git-terminal": ["@prover-coder-ai/docker-git-terminal@workspace:packages/terminal"], "@prover-coder-ai/eslint-plugin-suggest-members": ["@prover-coder-ai/eslint-plugin-suggest-members@0.0.26", "", { "dependencies": { "@effect/platform": "^0.96.0", "@effect/platform-node": "^0.106.0", "@effect/schema": "^0.75.5", "@typescript-eslint/utils": "8.57.2", "effect": "^3.21.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <7.0.0" } }, "sha512-RWl1jYZTMK1p0L6GA7VXvTrtiNkbQyjkgk3mvz0Vv7ImTrctDOLFfNIRoJmhU+e5irj1u5uK2p9QoZtRzi4ILQ=="], + "@redocly/ajv": ["@redocly/ajv@8.11.2", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js-replace": "^1.0.1" } }, "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg=="], + + "@redocly/config": ["@redocly/config@0.22.0", "", {}, "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ=="], + + "@redocly/openapi-core": ["@redocly/openapi-core@1.34.15", "", { "dependencies": { "@redocly/ajv": "8.11.2", "@redocly/config": "0.22.0", "colorette": "1.4.0", "https-proxy-agent": "7.0.6", "js-levenshtein": "1.1.6", "js-yaml": "4.1.1", "minimatch": "5.1.9", "pluralize": "8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-HAwCnNyKcs5XGQqms+9t7OdAPM/5TDstmhF+0i7tdCFato2QKuYIlyWETwkXd8c5zbltr1oB+6y9NTeQLr2d6Q=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], @@ -846,6 +868,8 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "8.16.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "3.1.3", "fast-json-stable-stringify": "2.1.0", "json-schema-traverse": "0.4.1", "uri-js": "4.4.1" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], @@ -958,6 +982,8 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], + "colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="], "commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], @@ -1232,6 +1258,8 @@ "htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "2.3.0", "domhandler": "5.0.3", "domutils": "3.2.2", "entities": "6.0.1" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "human-id": ["human-id@4.1.3", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q=="], "human-signals": ["human-signals@1.1.1", "", {}, "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="], @@ -1246,6 +1274,8 @@ "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + "ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="], "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "1.3.0", "hasown": "2.0.2", "side-channel": "1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], @@ -1354,6 +1384,8 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], + "js-stringify": ["js-stringify@1.0.2", "", {}, "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g=="], "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], @@ -1524,6 +1556,12 @@ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "openapi-fetch": ["openapi-fetch@0.17.0", "", { "dependencies": { "openapi-typescript-helpers": "^0.1.0" } }, "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig=="], + + "openapi-typescript": ["openapi-typescript@7.13.0", "", { "dependencies": { "@redocly/openapi-core": "^1.34.6", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ=="], + + "openapi-typescript-helpers": ["openapi-typescript-helpers@0.1.0", "", {}, "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "0.1.4", "fast-levenshtein": "2.0.6", "levn": "0.4.1", "prelude-ls": "1.2.1", "type-check": "0.4.0", "word-wrap": "1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="], @@ -1548,7 +1586,7 @@ "parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "1.2.4", "character-entities-legacy": "1.1.4", "character-reference-invalid": "1.1.4", "is-alphanumerical": "1.0.4", "is-decimal": "1.0.4", "is-hexadecimal": "1.0.4" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "7.27.1", "error-ex": "1.3.4", "json-parse-even-better-errors": "2.3.1", "lines-and-columns": "1.2.4" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "6.0.1" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], @@ -1772,7 +1810,7 @@ "structured-field-values": ["structured-field-values@2.0.4", "", {}, "sha512-5zpJXYLPwW3WYUD/D58tQjIBs10l3Yx64jZfcKGs/RH79E2t9Xm/b9+ydwdMNVSksnsIY+HR/2IlQmgo0AcTAg=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -1842,6 +1880,8 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "2.3.1" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "uri-js-replace": ["uri-js-replace@1.0.1", "", {}, "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g=="], + "uri-template-router": ["uri-template-router@1.0.0", "", {}, "sha512-WKcL9ZSIEhHE3f5P4Z47Tf0nWbcgV1ISb/OBuF8YKEYi0SQOyTLCzM6B/gAKFWZhRhqA+C/Ks8UXe2qU5W0FVg=="], "url-template": ["url-template@3.1.1", "", {}, "sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA=="], @@ -1900,6 +1940,10 @@ "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], @@ -2006,6 +2050,10 @@ "@prover-coder-ai/eslint-plugin-suggest-members/effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="], + "@redocly/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "@redocly/openapi-core/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@ton-ai-core/vibecode-linter/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "3.1.3", "fast-uri": "3.1.0", "json-schema-traverse": "1.0.0", "require-from-string": "2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], @@ -2028,6 +2076,8 @@ "@vitest/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.61.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/types": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA=="], + "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "effect/fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": "2.1.2" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -2082,6 +2132,8 @@ "is-expression/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], + "istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "jest-util/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], "jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], @@ -2100,12 +2152,18 @@ "normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + "openapi-typescript/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "parse-json/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "read-pkg/parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "7.27.1", "error-ex": "1.3.4", "json-parse-even-better-errors": "2.3.1", "lines-and-columns": "1.2.4" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "read-pkg/type-fest": ["type-fest@0.6.0", "", {}, "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg=="], "read-pkg-up/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "5.0.0", "path-exists": "4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -2268,6 +2326,8 @@ "@prover-coder-ai/eslint-plugin-suggest-members/effect/fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + "@redocly/openapi-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "1.0.2" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@ton-ai-core/vibecode-linter/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@ton-ai-core/vibecode-linter/effect/fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], @@ -2458,6 +2518,8 @@ "@prover-coder-ai/eslint-plugin-suggest-members/effect/fast-check/pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "@redocly/openapi-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@ton-ai-core/vibecode-linter/effect/fast-check/pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], "@ton-ai-core/vibecode-linter/jscpd/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "2.0.1" }, "optionalDependencies": { "graceful-fs": "4.2.11" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], diff --git a/package.json b/package.json index b1a942d8..27e3c7cd 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "packages/container", "packages/docker-git-session-sync", "packages/lib", + "packages/openapi", "packages/terminal" ], "scripts": { @@ -20,7 +21,7 @@ "api:dev": "bun run --filter @effect-template/api dev", "api:test": "bun run --filter @effect-template/api test", "api:typecheck": "bun run --filter @effect-template/api typecheck", - "check": "bun run --filter @prover-coder-ai/docker-git-session-sync check && bun run --filter @prover-coder-ai/docker-git-terminal check && bun run --filter @prover-coder-ai/docker-git check && bun run --filter @effect-template/lib typecheck", + "check": "bun run --filter @prover-coder-ai/docker-git-session-sync check && bun run --filter @prover-coder-ai/docker-git-terminal check && bun run --filter @prover-coder-ai/docker-git-openapi check && bun run --filter @prover-coder-ai/docker-git check && bun run --filter @effect-template/lib typecheck", "check:dist-deps-prune": "bun node_modules/@prover-coder-ai/dist-deps-prune/dist/main.js scan --package ./packages/app/package.json --prune-dev true --silent", "changeset": "changeset", "changeset-publish": "bun -e \"if (!process.env.NPM_TOKEN) { console.log('Skipping publish: NPM_TOKEN is not set'); process.exit(0); }\" && changeset publish", @@ -42,13 +43,15 @@ "dev": "bun run --cwd packages/app dev", "web:dev": "bun run --cwd packages/app dev:web", "web:build": "bun run --cwd packages/app build:web", + "web:generate-api": "bun run --cwd packages/openapi generate", + "openapi:lint-contract": "bun run --cwd packages/api lint:openapi-contract", "web:preview": "bun run --cwd packages/app preview:web", "web:serve": "bun run --cwd packages/app serve:web", "lint": "bun run --filter @prover-coder-ai/docker-git-terminal lint && bun run --filter @prover-coder-ai/docker-git lint && bun run --filter @effect-template/lib lint", "lint:tests": "bun run --filter @prover-coder-ai/docker-git lint:tests", "lint:effect": "bun run --filter @prover-coder-ai/docker-git-session-sync lint:effect && bun run --filter @prover-coder-ai/docker-git-terminal lint:effect && bun run --filter @prover-coder-ai/docker-git lint:effect && bun run --filter @prover-coder-ai/docker-git-container lint:effect && bun run --filter @effect-template/lib lint:effect && bun run --filter @effect-template/api lint:effect", "test": "bun run --filter @prover-coder-ai/docker-git-session-sync test && bun run --filter @prover-coder-ai/docker-git-terminal test && bun run --filter @prover-coder-ai/docker-git test && bun run --filter @effect-template/lib test", - "typecheck": "bun run --filter @prover-coder-ai/docker-git-session-sync typecheck && bun run --filter @prover-coder-ai/docker-git-terminal typecheck && bun run --filter @prover-coder-ai/docker-git typecheck && bun run --filter @effect-template/lib typecheck", + "typecheck": "bun run --filter @prover-coder-ai/docker-git-session-sync typecheck && bun run --filter @prover-coder-ai/docker-git-terminal typecheck && bun run --filter @prover-coder-ai/docker-git-openapi typecheck && bun run --filter @prover-coder-ai/docker-git typecheck && bun run --filter @effect-template/lib typecheck", "start": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js" }, "devDependencies": { diff --git a/packages/api/Dockerfile b/packages/api/Dockerfile index 7a90dc03..593094f0 100644 --- a/packages/api/Dockerfile +++ b/packages/api/Dockerfile @@ -76,12 +76,13 @@ RUN set -eu; \ FROM controller-base AS workspace-deps COPY package.json bun.lock bunfig.toml tsconfig.base.json tsconfig.json ./ -RUN mkdir -p packages/api packages/app packages/container packages/docker-git-session-sync packages/lib packages/terminal +RUN mkdir -p packages/api packages/app packages/container packages/docker-git-session-sync packages/lib packages/openapi packages/terminal COPY packages/api/package.json ./packages/api/package.json COPY packages/app/package.json ./packages/app/package.json COPY packages/container/package.json ./packages/container/package.json COPY packages/docker-git-session-sync/package.json ./packages/docker-git-session-sync/package.json COPY packages/lib/package.json ./packages/lib/package.json +COPY packages/openapi/package.json ./packages/openapi/package.json COPY packages/terminal/package.json ./packages/terminal/package.json RUN set -eu; \ diff --git a/packages/api/package.json b/packages/api/package.json index b61ad57f..d2b9f357 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -16,6 +16,7 @@ "typecheck": "tsc --noEmit -p tsconfig.json", "lint": "eslint .", "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + "lint:openapi-contract": "vitest run tests/openapi.test.ts", "pretest": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build", "test": "vitest run" }, diff --git a/packages/api/src/api/openapi.ts b/packages/api/src/api/openapi.ts new file mode 100644 index 00000000..4c4da351 --- /dev/null +++ b/packages/api/src/api/openapi.ts @@ -0,0 +1,793 @@ +import * as HttpApi from "@effect/platform/HttpApi" +import * as HttpApiEndpoint from "@effect/platform/HttpApiEndpoint" +import * as HttpApiGroup from "@effect/platform/HttpApiGroup" +import * as HttpApiSchema from "@effect/platform/HttpApiSchema" +import * as OpenApi from "@effect/platform/OpenApi" +import * as Schema from "effect/Schema" + +import { + ActiveProjectTerminalSessionRequestSchema, + AgentSessionSchema, + ApplyAllRequestSchema, + ApplyProjectRequestSchema, + AuthMenuRequestSchema, + AuthTerminalSessionRequestSchema, + CodexAuthImportRequestSchema, + CodexAuthLogoutRequestSchema, + CreateProjectRequestSchema, + GitAuthLoginRequestSchema, + GitAuthLogoutRequestSchema, + GithubAuthLoginRequestSchema, + GithubAuthLogoutRequestSchema, + GitlabAuthLoginRequestSchema, + GitlabAuthLogoutRequestSchema, + GrokAuthLogoutRequestSchema, + ProjectAuthRequestSchema, + ProjectBrowserSessionSchema, + ProjectDatabaseForwardSchema, + ProjectDatabaseProfileRequestSchema, + ProjectDatabaseProfileSchema, + ProjectDatabaseSessionSchema, + ProjectPortForwardRequestSchema, + ProjectSkillUpdateRequestSchema, + StartPanelCloudflareTunnelRequestSchema, + StartProjectTerminalSessionRequestSchema, + UpProjectRequestSchema +} from "./schema.js" + +const NullableStringSchema = Schema.NullOr(Schema.String) +const OptionalOkSchema = Schema.optional(Schema.Boolean) + +const ProjectIdParam = HttpApiSchema.param("projectId", Schema.String) +const ProjectKeyParam = HttpApiSchema.param("projectKey", Schema.String) +const SessionIdParam = HttpApiSchema.param("sessionId", Schema.String) +const TargetPortParam = HttpApiSchema.param("targetPort", Schema.NumberFromString) +const ProfileIdParam = HttpApiSchema.param("profileId", Schema.String) +const PromptKindParam = HttpApiSchema.param("kind", Schema.String) +const ScopeIdParam = HttpApiSchema.param("scopeId", Schema.String) +const SkillNameParam = HttpApiSchema.param("name", Schema.String) +const PidParam = HttpApiSchema.param("pid", Schema.NumberFromString) + +export const OkResponseSchema = Schema.Struct({ + ok: Schema.Literal(true) +}) + +export const HealthResponseSchema = Schema.Struct({ + cwd: Schema.String, + ok: Schema.Boolean, + projectsRoot: Schema.String, + revision: NullableStringSchema +}) + +export const ProjectStatusSchema = Schema.Literal("running", "stopped", "unknown") + +const ProjectSummaryFields = { + clonedOnHostname: Schema.optional(Schema.String), + containerName: Schema.optional(Schema.String), + displayName: Schema.String, + id: Schema.String, + projectKey: Schema.String, + repoRef: Schema.String, + repoUrl: Schema.String, + sshSessions: Schema.Number, + startedAtEpochMs: Schema.NullOr(Schema.Number), + startedAtIso: NullableStringSchema, + status: ProjectStatusSchema, + statusLabel: Schema.String +} + +export const ProjectSummarySchema = Schema.Struct(ProjectSummaryFields) + +export const ProjectDetailsSchema = Schema.Struct({ + ...ProjectSummaryFields, + authorizedKeysExists: Schema.Boolean, + authorizedKeysPath: Schema.String, + codexAuthPath: Schema.String, + codexHome: Schema.String, + containerName: Schema.String, + envGlobalPath: Schema.String, + envProjectPath: Schema.String, + gpu: Schema.Literal("none", "all"), + projectDir: Schema.String, + serviceName: Schema.String, + sshCommand: Schema.String, + sshPort: Schema.Number, + sshUser: Schema.String, + targetDir: Schema.String +}) + +export const ProjectsResponseSchema = Schema.Struct({ + projects: Schema.Array(ProjectSummarySchema) +}) + +export const ProjectResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + project: ProjectDetailsSchema +}) + +export const CreateProjectAcceptedResponseSchema = Schema.Struct({ + accepted: Schema.Literal(true), + cursor: Schema.Number, + projectId: Schema.String +}) + +export const StartProjectTerminalSessionAcceptedResponseSchema = Schema.Struct({ + accepted: Schema.Literal(true), + cursor: Schema.Number, + projectId: Schema.String, + requestId: Schema.String +}) + +export const OutputResponseSchema = Schema.Struct({ + output: Schema.String +}) + +export const ProjectPortForwardStatusSchema = Schema.Literal("running", "stopped", "unknown") + +export const ProjectPortForwardSchema = Schema.Struct({ + bindHost: Schema.String, + containerName: Schema.String, + createdAt: NullableStringSchema, + hostPort: Schema.Number, + id: Schema.String, + projectId: Schema.String, + projectKey: Schema.String, + proxyPath: Schema.String, + publicHost: Schema.String, + status: ProjectPortForwardStatusSchema, + targetContainerName: Schema.String, + targetPort: Schema.Number, + url: Schema.String +}) + +export const ProjectPortForwardsResponseSchema = Schema.Struct({ + forwards: Schema.Array(ProjectPortForwardSchema) +}) + +export const ProjectPortForwardResponseSchema = Schema.Struct({ + forward: ProjectPortForwardSchema +}) + +export const ProjectBrowserResponseSchema = Schema.Struct({ + browser: ProjectBrowserSessionSchema +}) + +export const PanelCloudflareTunnelStatusSchema = Schema.Literal("starting", "running", "stopped", "failed") + +export const PanelCloudflareTunnelSessionSchema = Schema.Struct({ + error: NullableStringSchema, + id: Schema.String, + logTail: Schema.Array(Schema.String), + panelUrl: Schema.String, + publicUrl: NullableStringSchema, + startedAt: Schema.String, + status: PanelCloudflareTunnelStatusSchema, + stoppedAt: NullableStringSchema +}) + +export const PanelCloudflareTunnelResponseSchema = Schema.Struct({ + tunnel: Schema.NullOr(PanelCloudflareTunnelSessionSchema) +}) + +export const ProjectDatabaseProfilesResponseSchema = Schema.Struct({ + profiles: Schema.Array(ProjectDatabaseProfileSchema) +}) + +export const ProjectDatabaseProfileResponseSchema = Schema.Struct({ + profile: ProjectDatabaseProfileSchema +}) + +export const ProjectDatabaseSessionResponseSchema = Schema.Struct({ + session: ProjectDatabaseSessionSchema +}) + +export const ProjectDatabaseForwardsResponseSchema = Schema.Struct({ + forwards: Schema.Array(ProjectDatabaseForwardSchema) +}) + +export const ProjectDatabaseForwardResponseSchema = Schema.Struct({ + forward: ProjectDatabaseForwardSchema +}) + +export const GithubTokenStatusSchema = Schema.Struct({ + key: Schema.String, + label: Schema.String, + login: NullableStringSchema, + status: Schema.Literal("valid", "invalid", "unknown") +}) + +export const GithubAuthStatusSchema = Schema.Struct({ + summary: Schema.String, + tokens: Schema.Array(GithubTokenStatusSchema) +}) + +export const GithubStatusResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + status: GithubAuthStatusSchema +}) + +export const GitlabTokenStatusSchema = Schema.Struct({ + key: Schema.String, + label: Schema.String, + login: NullableStringSchema, + status: Schema.Literal("valid", "invalid", "unknown") +}) + +export const GitlabAuthStatusSchema = Schema.Struct({ + summary: Schema.String, + tokens: Schema.Array(GitlabTokenStatusSchema) +}) + +export const GitlabStatusResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + status: GitlabAuthStatusSchema +}) + +export const GitAuthConnectionStatusSchema = Schema.Struct({ + host: Schema.String, + user: Schema.String +}) + +export const GitAuthStatusSchema = Schema.Struct({ + connections: Schema.Array(GitAuthConnectionStatusSchema), + summary: Schema.String +}) + +export const GitStatusResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + status: GitAuthStatusSchema +}) + +export const CodexAuthStatusSchema = Schema.Struct({ + account: NullableStringSchema, + authPath: Schema.String, + label: Schema.String, + message: Schema.String, + present: Schema.Boolean +}) + +export const CodexStatusResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + status: CodexAuthStatusSchema +}) + +export const GrokAuthStatusSchema = Schema.Struct({ + authPath: Schema.String, + connected: Schema.Boolean, + label: Schema.String, + message: Schema.String, + method: Schema.Literal("none", "api-key", "oauth") +}) + +export const GrokStatusResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + status: GrokAuthStatusSchema +}) + +export const AuthSnapshotSchema = Schema.Struct({ + claudeAuthEntries: Schema.Number, + claudeAuthPath: Schema.String, + codexAuthEntries: Schema.optionalWith(Schema.Number, { default: () => 0 }), + codexAuthPath: Schema.optionalWith(Schema.String, { default: () => "" }), + geminiAuthEntries: Schema.Number, + geminiAuthPath: Schema.String, + gitTokenEntries: Schema.Number, + gitUserEntries: Schema.Number, + githubTokenEntries: Schema.Number, + globalEnvPath: Schema.String, + grokAuthEntries: Schema.optionalWith(Schema.Number, { default: () => 0 }), + grokAuthPath: Schema.optionalWith(Schema.String, { default: () => "" }), + totalEntries: Schema.Number +}) + +export const AuthSnapshotResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + snapshot: AuthSnapshotSchema +}) + +export const ProjectAuthSnapshotSchema = Schema.Struct({ + activeClaudeLabel: NullableStringSchema, + activeGeminiLabel: NullableStringSchema, + activeGitLabel: NullableStringSchema, + activeGithubLabel: NullableStringSchema, + activeGrokLabel: NullableStringSchema, + claudeAuthEntries: Schema.Number, + claudeAuthPath: Schema.String, + codexAuthEntries: Schema.optionalWith(Schema.Number, { default: () => 0 }), + codexAuthPath: Schema.optionalWith(Schema.String, { default: () => "" }), + envGlobalPath: Schema.String, + envProjectPath: Schema.String, + geminiAuthEntries: Schema.Number, + geminiAuthPath: Schema.String, + gitTokenEntries: Schema.Number, + githubTokenEntries: Schema.Number, + grokAuthEntries: Schema.optionalWith(Schema.Number, { default: () => 0 }), + grokAuthPath: Schema.optionalWith(Schema.String, { default: () => "" }), + projectDir: Schema.String, + projectName: Schema.String +}) + +export const ProjectAuthSnapshotResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + snapshot: ProjectAuthSnapshotSchema +}) + +export const TerminalSessionSchema = Schema.Struct({ + attachedClients: Schema.optional(Schema.Number), + closedAt: Schema.optional(Schema.String), + createdAt: Schema.String, + exitCode: Schema.optional(Schema.Number), + id: Schema.String, + projectId: Schema.String, + signal: Schema.optional(Schema.Number), + sshCommand: Schema.String, + startedAt: Schema.optional(Schema.String), + status: Schema.Literal("ready", "attached", "exited", "failed") +}) + +export const TerminalSessionResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + project: ProjectDetailsSchema, + session: TerminalSessionSchema +}) + +export const ProjectTerminalSessionsResponseSchema = Schema.Struct({ + activeSessionId: NullableStringSchema, + sessions: Schema.Array(TerminalSessionSchema) +}) + +export const ProjectTerminalSessionResponseSchema = Schema.Struct({ + session: TerminalSessionSchema +}) + +export const ActiveProjectTerminalSessionResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + session: TerminalSessionSchema +}) + +export const TerminalSessionLookupResponseSchema = Schema.Struct({ + projectDisplayName: Schema.String, + projectKey: Schema.String, + session: TerminalSessionSchema +}) + +export const AuthTerminalSessionResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + session: TerminalSessionSchema +}) + +export const ProjectPromptKindSchema = Schema.Literal("claude", "codex", "gemini", "grok") + +export const ProjectPromptFileSchema = Schema.Struct({ + absolutePath: Schema.String, + bytes: Schema.Number, + content: Schema.String, + exists: Schema.Boolean, + fileName: Schema.String, + kind: ProjectPromptKindSchema, + relativePath: Schema.String +}) + +export const ProjectPromptsSnapshotSchema = Schema.Struct({ + projectDir: Schema.String, + projectId: Schema.String, + projectKey: Schema.String, + prompts: Schema.Array(ProjectPromptFileSchema) +}) + +export const ProjectPromptsResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + snapshot: ProjectPromptsSnapshotSchema +}) + +export const ProjectPromptUpdateResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + prompt: ProjectPromptFileSchema, + snapshot: ProjectPromptsSnapshotSchema +}) + +export const ProjectSkillScopeSchema = Schema.Literal( + "skills", + "agents/skills", + "agents/.skills", + "claude/skills", + "codex/skills", + "gemini/skills", + "grok/skills" +) + +export const ProjectSkillFileSchema = Schema.Struct({ + absolutePath: Schema.String, + bytes: Schema.Number, + content: Schema.String, + id: Schema.String, + name: Schema.String, + relativePath: Schema.String, + scope: ProjectSkillScopeSchema, + updatedAtIso: NullableStringSchema +}) + +export const ProjectSkillScopeInfoSchema = Schema.Struct({ + absoluteRoot: Schema.String, + relativeRoot: Schema.String, + scope: ProjectSkillScopeSchema +}) + +export const ProjectSkillsSnapshotSchema = Schema.Struct({ + projectDir: Schema.String, + projectId: Schema.String, + projectKey: Schema.String, + scopes: Schema.Array(ProjectSkillScopeInfoSchema), + skills: Schema.Array(ProjectSkillFileSchema) +}) + +export const ProjectSkillsResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + snapshot: ProjectSkillsSnapshotSchema +}) + +export const ProjectSkillUpdateResponseSchema = Schema.Struct({ + ok: OptionalOkSchema, + skill: ProjectSkillFileSchema, + snapshot: ProjectSkillsSnapshotSchema +}) + +export const ContainerTaskKindSchema = Schema.Literal("ssh", "web-terminal", "agent", "background", "system") + +export const ContainerTaskSchema = Schema.Struct({ + command: Schema.String, + elapsed: Schema.optional(Schema.String), + etime: Schema.String, + etimes: Schema.Number, + kind: ContainerTaskKindSchema, + logAvailable: Schema.Boolean, + managedId: Schema.optional(Schema.String), + pid: Schema.Number, + ppid: Schema.Number, + tty: Schema.String, + user: Schema.String +}) + +export const ContainerTaskSnapshotSchema = Schema.Struct({ + agents: Schema.Array(AgentSessionSchema), + containerName: Schema.String, + generatedAt: Schema.String, + projectId: Schema.String, + sshConnections: Schema.Number, + tasks: Schema.Array(ContainerTaskSchema), + terminalSessions: Schema.Array(TerminalSessionSchema) +}) + +export const ContainerTaskSnapshotResponseSchema = Schema.Struct({ + snapshot: ContainerTaskSnapshotSchema +}) + +const QueryIncludeDefaultSchema = Schema.Struct({ + includeDefault: Schema.optional(Schema.String) +}) + +const QueryLabelSchema = Schema.Struct({ + label: Schema.optional(Schema.String) +}) + +const QueryLinesSchema = Schema.Struct({ + lines: Schema.optional(Schema.String) +}) + +const ApiErrorEnvelopeSchema = Schema.Struct({ + command: Schema.optional(Schema.String), + details: Schema.optional(Schema.Unknown), + message: Schema.String, + provider: Schema.optional(Schema.String), + type: Schema.String +}) + +const ApiErrorResponseSchema = Schema.Struct({ + error: ApiErrorEnvelopeSchema +}) + +const endpoint = { + del: HttpApiEndpoint.del, + get: HttpApiEndpoint.get, + post: HttpApiEndpoint.post, + put: HttpApiEndpoint.put +} + +const CoreGroup = HttpApiGroup.make("core").add( + endpoint.get("health", "/health").addSuccess(HealthResponseSchema) +) + +const ProjectsGroup = HttpApiGroup.make("projects") + .add(endpoint.get("listProjects", "/projects").addSuccess(ProjectsResponseSchema)) + .add( + endpoint.post("createProject", "/projects") + .setPayload(CreateProjectRequestSchema) + .addSuccess(ProjectResponseSchema, { status: 201 }) + .addSuccess(CreateProjectAcceptedResponseSchema, { status: 202 }) + ) + .add( + endpoint.post("applyAllProjects", "/projects/apply-all") + .setPayload(ApplyAllRequestSchema) + .addSuccess(OkResponseSchema) + ) + .add(endpoint.post("downAllProjects", "/projects/down-all").addSuccess(OkResponseSchema)) + .add(endpoint.get("getProject")`/projects/${ProjectIdParam}`.addSuccess(ProjectResponseSchema)) + .add(endpoint.del("deleteProject")`/projects/${ProjectIdParam}`.addSuccess(OkResponseSchema)) + .add(endpoint.post("downProject")`/projects/${ProjectIdParam}/down`.addSuccess(OkResponseSchema)) + .add( + endpoint.post("applyProject")`/projects/${ProjectIdParam}/apply` + .setPayload(ApplyProjectRequestSchema) + .addSuccess(ProjectResponseSchema) + ) + .add( + endpoint.post("upProject")`/projects/${ProjectIdParam}/up` + .setPayload(UpProjectRequestSchema) + .addSuccess(ProjectResponseSchema) + ) + .add(endpoint.post("resumeProject")`/projects/${ProjectIdParam}/resume`.addSuccess(ProjectResponseSchema)) + .add(endpoint.post("suspendProject")`/projects/${ProjectIdParam}/suspend`.addSuccess(ProjectResponseSchema)) + .add(endpoint.get("projectPs")`/projects/${ProjectIdParam}/ps`.addSuccess(OutputResponseSchema)) + .add(endpoint.get("projectLogs")`/projects/${ProjectIdParam}/logs`.addSuccess(OutputResponseSchema)) + +const ProjectPortsGroup = HttpApiGroup.make("projectPorts") + .add(endpoint.get("listProjectPorts")`/projects/${ProjectIdParam}/ports`.addSuccess(ProjectPortForwardsResponseSchema)) + .add( + endpoint.post("createProjectPort")`/projects/${ProjectIdParam}/ports` + .setPayload(ProjectPortForwardRequestSchema) + .addSuccess(ProjectPortForwardResponseSchema, { status: 201 }) + ) + .add( + endpoint.del("deleteProjectPort")`/projects/${ProjectIdParam}/ports/${TargetPortParam}` + .addSuccess(OkResponseSchema) + ) + +const ProjectBrowserGroup = HttpApiGroup.make("projectBrowser") + .add(endpoint.get("readProjectBrowser")`/projects/${ProjectIdParam}/browser`.addSuccess(ProjectBrowserResponseSchema)) + .add( + endpoint.post("startProjectBrowser")`/projects/${ProjectIdParam}/browser/start` + .addSuccess(ProjectBrowserResponseSchema) + ) + +const ProjectDatabasesGroup = HttpApiGroup.make("projectDatabases") + .add( + endpoint.get("listDatabaseProfiles")`/projects/${ProjectIdParam}/databases/profiles` + .addSuccess(ProjectDatabaseProfilesResponseSchema) + ) + .add( + endpoint.post("saveDatabaseProfile")`/projects/${ProjectIdParam}/databases/profiles` + .setPayload(ProjectDatabaseProfileRequestSchema) + .addSuccess(ProjectDatabaseProfileResponseSchema, { status: 201 }) + ) + .add( + endpoint.del("deleteDatabaseProfile")`/projects/${ProjectIdParam}/databases/profiles/${ProfileIdParam}` + .addSuccess(OkResponseSchema) + ) + .add( + endpoint.post("exposeDatabaseProfile")`/projects/${ProjectIdParam}/databases/profiles/${ProfileIdParam}/expose` + .addSuccess(ProjectDatabaseForwardResponseSchema, { status: 201 }) + ) + .add( + endpoint.del("deleteDatabaseForward")`/projects/${ProjectIdParam}/databases/profiles/${ProfileIdParam}/expose` + .addSuccess(OkResponseSchema) + ) + .add( + endpoint.get("listDatabaseForwards")`/projects/${ProjectIdParam}/databases/forwards` + .addSuccess(ProjectDatabaseForwardsResponseSchema) + ) + .add( + endpoint.get("readDatabaseSession")`/projects/${ProjectIdParam}/databases/session` + .addSuccess(ProjectDatabaseSessionResponseSchema) + ) + .add( + endpoint.post("openDatabaseEditor")`/projects/${ProjectIdParam}/databases/open` + .addSuccess(ProjectDatabaseSessionResponseSchema) + ) + .add( + endpoint.post("restartDatabaseEditor")`/projects/${ProjectIdParam}/databases/restart` + .addSuccess(ProjectDatabaseSessionResponseSchema) + ) + +const AuthGroup = HttpApiGroup.make("auth") + .add(endpoint.get("githubStatus", "/auth/github/status").addSuccess(GithubStatusResponseSchema)) + .add(endpoint.get("gitlabStatus", "/auth/gitlab/status").addSuccess(GitlabStatusResponseSchema)) + .add(endpoint.get("gitStatus", "/auth/git/status").addSuccess(GitStatusResponseSchema)) + .add( + endpoint.get("grokStatus", "/auth/grok/status") + .setUrlParams(QueryLabelSchema) + .addSuccess(GrokStatusResponseSchema) + ) + .add( + endpoint.get("codexStatus", "/auth/codex/status") + .setUrlParams(QueryLabelSchema) + .addSuccess(CodexStatusResponseSchema) + ) + .add( + endpoint.post("githubLogin", "/auth/github/login") + .setPayload(GithubAuthLoginRequestSchema) + .addSuccess(GithubStatusResponseSchema, { status: 201 }) + ) + .add( + endpoint.post("githubLogout", "/auth/github/logout") + .setPayload(GithubAuthLogoutRequestSchema) + .addSuccess(GithubStatusResponseSchema) + ) + .add( + endpoint.post("gitlabLogin", "/auth/gitlab/login") + .setPayload(GitlabAuthLoginRequestSchema) + .addSuccess(GitlabStatusResponseSchema, { status: 201 }) + ) + .add( + endpoint.post("gitlabLogout", "/auth/gitlab/logout") + .setPayload(GitlabAuthLogoutRequestSchema) + .addSuccess(GitlabStatusResponseSchema) + ) + .add( + endpoint.post("gitLogin", "/auth/git/login") + .setPayload(GitAuthLoginRequestSchema) + .addSuccess(GitStatusResponseSchema, { status: 201 }) + ) + .add( + endpoint.post("gitLogout", "/auth/git/logout") + .setPayload(GitAuthLogoutRequestSchema) + .addSuccess(GitStatusResponseSchema) + ) + .add(endpoint.get("authMenu", "/auth/menu").addSuccess(AuthSnapshotResponseSchema)) + .add( + endpoint.post("authMenuAction", "/auth/menu") + .setPayload(AuthMenuRequestSchema) + .addSuccess(AuthSnapshotResponseSchema) + ) + .add( + endpoint.post("authTerminalSession", "/auth/terminal-sessions") + .setPayload(AuthTerminalSessionRequestSchema) + .addSuccess(AuthTerminalSessionResponseSchema, { status: 201 }) + ) + .add( + endpoint.post("codexImport", "/auth/codex/import") + .setPayload(CodexAuthImportRequestSchema) + .addSuccess(CodexStatusResponseSchema, { status: 201 }) + ) + .add( + endpoint.post("codexLogout", "/auth/codex/logout") + .setPayload(CodexAuthLogoutRequestSchema) + .addSuccess(CodexStatusResponseSchema) + ) + .add( + endpoint.post("grokLogout", "/auth/grok/logout") + .setPayload(GrokAuthLogoutRequestSchema) + .addSuccess(GrokStatusResponseSchema) + ) + +const ProjectAuthGroup = HttpApiGroup.make("projectAuth") + .add(endpoint.get("projectAuth")`/projects/${ProjectIdParam}/auth/menu`.addSuccess(ProjectAuthSnapshotResponseSchema)) + .add( + endpoint.post("projectAuthAction")`/projects/${ProjectIdParam}/auth/menu` + .setPayload(ProjectAuthRequestSchema) + .addSuccess(ProjectAuthSnapshotResponseSchema) + ) + +const TerminalGroup = HttpApiGroup.make("terminal") + .add( + endpoint.post("createTerminalByKey")`/projects/by-key/${ProjectKeyParam}/terminal-sessions` + .addSuccess(TerminalSessionResponseSchema, { status: 201 }) + ) + .add( + endpoint.post("startTerminalByKey")`/projects/by-key/${ProjectKeyParam}/terminal-sessions/start` + .setPayload(StartProjectTerminalSessionRequestSchema) + .addSuccess(StartProjectTerminalSessionAcceptedResponseSchema, { status: 202 }) + ) + .add( + endpoint.get("listTerminalsByKey")`/projects/by-key/${ProjectKeyParam}/terminal-sessions` + .addSuccess(ProjectTerminalSessionsResponseSchema) + ) + .add( + endpoint.get("getTerminalByKey")`/projects/by-key/${ProjectKeyParam}/terminal-sessions/${SessionIdParam}` + .addSuccess(ProjectTerminalSessionResponseSchema) + ) + .add( + endpoint.del("deleteTerminalByKey")`/projects/by-key/${ProjectKeyParam}/terminal-sessions/${SessionIdParam}` + .addSuccess(OkResponseSchema) + ) + .add( + endpoint.put("setActiveTerminalByKey")`/projects/by-key/${ProjectKeyParam}/terminal-sessions/active` + .setPayload(ActiveProjectTerminalSessionRequestSchema) + .addSuccess(ActiveProjectTerminalSessionResponseSchema) + ) + .add(endpoint.get("lookupTerminal")`/terminal-sessions/${SessionIdParam}`.addSuccess(TerminalSessionLookupResponseSchema)) + .add( + endpoint.del("deleteAuthTerminal")`/auth/terminal-sessions/${SessionIdParam}`.addSuccess(OkResponseSchema) + ) + +const PromptsGroup = HttpApiGroup.make("prompts") + .add(endpoint.get("listPrompts")`/projects/${ProjectIdParam}/prompts`.addSuccess(ProjectPromptsResponseSchema)) + .add( + endpoint.put("writePrompt")`/projects/${ProjectIdParam}/prompts/${PromptKindParam}` + .setPayload(Schema.Struct({ content: Schema.String })) + .addSuccess(ProjectPromptUpdateResponseSchema) + ) + .add( + endpoint.del("deletePrompt")`/projects/${ProjectIdParam}/prompts/${PromptKindParam}` + .addSuccess(ProjectPromptsResponseSchema) + ) + +const SkillsGroup = HttpApiGroup.make("skills") + .add(endpoint.get("listSkills")`/projects/${ProjectIdParam}/skills`.addSuccess(ProjectSkillsResponseSchema)) + .add( + endpoint.post("writeSkill")`/projects/${ProjectIdParam}/skills` + .setPayload(ProjectSkillUpdateRequestSchema) + .addSuccess(ProjectSkillUpdateResponseSchema) + ) + .add( + endpoint.del("deleteSkill")`/projects/${ProjectIdParam}/skills/${ScopeIdParam}/${SkillNameParam}` + .addSuccess(ProjectSkillsResponseSchema) + ) + +const TasksGroup = HttpApiGroup.make("tasks") + .add( + endpoint.get("listTasks")`/projects/${ProjectIdParam}/tasks` + .setUrlParams(QueryIncludeDefaultSchema) + .addSuccess(ContainerTaskSnapshotResponseSchema) + ) + .add(endpoint.post("stopTask")`/projects/${ProjectIdParam}/tasks/${PidParam}/stop`.addSuccess(OkResponseSchema)) + .add( + endpoint.get("taskLogs")`/projects/${ProjectIdParam}/tasks/${PidParam}/logs` + .setUrlParams(QueryLinesSchema) + .addSuccess(OutputResponseSchema) + ) + +const SharingGroup = HttpApiGroup.make("sharing") + .add(endpoint.get("readPanelCloudflareTunnel", "/cloudflare-tunnels/panel").addSuccess(PanelCloudflareTunnelResponseSchema)) + .add( + endpoint.post("startPanelCloudflareTunnel", "/cloudflare-tunnels/panel") + .setPayload(StartPanelCloudflareTunnelRequestSchema) + .addSuccess(PanelCloudflareTunnelResponseSchema, { status: 202 }) + ) + .add(endpoint.del("stopPanelCloudflareTunnel", "/cloudflare-tunnels/panel").addSuccess(PanelCloudflareTunnelResponseSchema)) + +export const DockerGitApi = HttpApi.make("docker-git") + .annotate(OpenApi.Title, "docker-git API") + .annotate(OpenApi.Version, "1.0.0") + .annotate(OpenApi.Description, "Effect contract for docker-git JSON REST endpoints.") + .addError(ApiErrorResponseSchema, { status: 400 }) + .addError(ApiErrorResponseSchema, { status: 401 }) + .addError(ApiErrorResponseSchema, { status: 404 }) + .addError(ApiErrorResponseSchema, { status: 409 }) + .addError(ApiErrorResponseSchema, { status: 500 }) + .add(CoreGroup) + .add(ProjectsGroup) + .add(ProjectPortsGroup) + .add(ProjectBrowserGroup) + .add(ProjectDatabasesGroup) + .add(AuthGroup) + .add(ProjectAuthGroup) + .add(TerminalGroup) + .add(PromptsGroup) + .add(SkillsGroup) + .add(TasksGroup) + .add(SharingGroup) + +/** + * Builds the OpenAPI document from the Effect HttpApi contract. + * + * @returns OpenAPI 3.1 specification for JSON REST endpoints. + * + * @pure true - deterministic projection from the static Effect contract. + * @effect none + * @invariant every documented path is derived from DockerGitApi, not hand-written JSON. + * @precondition DockerGitApi is importable without starting the HTTP server. + * @postcondition the returned spec has openapi = "3.1.0". + * @complexity O(n) time / O(n) space where n is the number of endpoints and schemas. + * @throws Never. + */ +// CHANGE: derive Swagger/OpenAPI from the Effect HttpApi contract. +// WHY: frontend clients must be generated from one typed REST contract. +// QUOTE(ТЗ): "Надо сделать REST API нормальный на базе Effect и использовать Swagger." +// REF: user-message-2026-06-18-openapi-fetch +// SOURCE: https://openapi-ts.dev/openapi-fetch/ +// FORMAT THEOREM: forall endpoint e in DockerGitApi, e is represented in buildDockerGitOpenApi().paths. +// PURITY: CORE +// EFFECT: none +// INVARIANT: spec = OpenApi.fromApi(DockerGitApi). +// COMPLEXITY: O(n)/O(n) +export const buildDockerGitOpenApi = (): OpenApi.OpenAPISpec => + OpenApi.fromApi(DockerGitApi, { additionalPropertiesStrategy: "strict" }) diff --git a/packages/api/src/http.ts b/packages/api/src/http.ts index 0118efac..94c4752c 100644 --- a/packages/api/src/http.ts +++ b/packages/api/src/http.ts @@ -46,6 +46,7 @@ import { UpProjectRequestSchema } from "./api/schema.js" import type { UpProjectRequestInput } from "./api/schema.js" +import { buildDockerGitOpenApi } from "./api/openapi.js" import { defaultProjectsRoot } from "@effect-template/lib/usecases/menu-helpers" import { resolveWorkspaceRoot } from "@effect-template/lib/shell/workspace-root" import { @@ -300,6 +301,51 @@ const textResponse = (data: string, contentType: string, status = 200) => ) ) +/** + * Renders a Swagger UI document that loads the generated OpenAPI contract. + * + * @returns HTML document for interactive API documentation. + * + * @pure true - deterministic string construction. + * @effect none + * @invariant the document references the relative openapi.json path. + * @precondition Swagger UI CDN assets are reachable by the browser. + * @postcondition direct /docs and proxied /api/docs both resolve their adjacent OpenAPI document. + * @complexity O(1) time / O(1) space. + * @throws Never. + */ +// CHANGE: expose browser-readable Swagger UI for the Effect REST contract. +// WHY: generated clients and humans must inspect the same OpenAPI document. +// QUOTE(ТЗ): "использовать Swagger" +// REF: user-message-2026-06-18-openapi-fetch +// SOURCE: n/a +// FORMAT THEOREM: docsPath(d) = p -> openApiPath(d) = sibling(p, "openapi.json") +// PURITY: CORE +// EFFECT: none +// INVARIANT: Swagger UI reads the relative OpenAPI document. +// COMPLEXITY: O(1)/O(1) +const renderSwaggerDocsHtml = (): string => ` + +
+ + +