Dax Raad před 9 měsíci
rodič
revize
2860a2bb1a

+ 127 - 0
js/bun.lock

@@ -12,6 +12,9 @@
         "clipanion": "^4.0.0-rc.4",
         "hono": "^4.7.10",
         "hono-openapi": "^0.4.8",
+        "ts-lsp-client": "^1.0.3",
+        "vscode-jsonrpc": "^8.2.1",
+        "vscode-languageclient": "8",
         "zod": "^3.25.3",
         "zod-openapi": "^4.2.4",
       },
@@ -45,6 +48,8 @@
 
     "@flystorage/local-fs": ["@flystorage/[email protected]", "", { "dependencies": { "@flystorage/dynamic-import": "^1.0.0", "@flystorage/file-storage": "^1.1.0", "file-type": "^20.5.0", "mime-types": "^3.0.1" } }, "sha512-dbErRhqmCv2UF0zPdeH7iVWuVeTWAJHuJD/mXDe2V370/SL7XIvdE3ditBHWC+1SzBKXJ0lkykOenwlum+oqIA=="],
 
+    "@hapi/bourne": ["@hapi/[email protected]", "", {}, "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q=="],
+
     "@hono/zod-validator": ["@hono/[email protected]", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-ds5bW6DCgAnNHP33E3ieSbaZFd5dkV52ZjyaXtGoR06APFrCtzAsKZxTHwOrJNBdXsi0e5wNwo5L4nVEVnJUdg=="],
 
     "@jsdevtools/ono": ["@jsdevtools/[email protected]", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
@@ -79,10 +84,20 @@
 
     "argparse": ["[email protected]", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
 
+    "args": ["[email protected]", "", { "dependencies": { "camelcase": "5.0.0", "chalk": "2.4.2", "leven": "2.1.0", "mri": "1.1.4" } }, "sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA=="],
+
+    "atomic-sleep": ["[email protected]", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
+
     "auto-bind": ["[email protected]", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
 
+    "balanced-match": ["[email protected]", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+    "brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+
     "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
 
+    "camelcase": ["[email protected]", "", {}, "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA=="],
+
     "chalk": ["[email protected]", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
 
     "cli-boxes": ["[email protected]", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
@@ -97,26 +112,42 @@
 
     "code-excerpt": ["[email protected]", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
 
+    "color-convert": ["[email protected]", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+    "color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
     "convert-to-spaces": ["[email protected]", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
 
     "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
 
+    "dateformat": ["[email protected]", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
+
     "debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
 
+    "duplexify": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="],
+
     "emoji-regex": ["[email protected]", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
 
+    "end-of-stream": ["[email protected]", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
+
     "environment": ["[email protected]", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
 
     "es-toolkit": ["[email protected]", "", {}, "sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA=="],
 
     "escape-string-regexp": ["[email protected]", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
 
+    "fast-redact": ["[email protected]", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
+
+    "fast-safe-stringify": ["[email protected]", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
+
     "fflate": ["[email protected]", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
 
     "file-type": ["[email protected]", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="],
 
     "get-east-asian-width": ["[email protected]", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
 
+    "has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
     "hono": ["[email protected]", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
 
     "hono-openapi": ["[email protected]", "", { "dependencies": { "json-schema-walker": "^2.0.0" }, "peerDependencies": { "@hono/arktype-validator": "^2.0.0", "@hono/effect-validator": "^1.2.0", "@hono/typebox-validator": "^0.2.0 || ^0.3.0", "@hono/valibot-validator": "^0.5.1", "@hono/zod-validator": "^0.4.1", "@sinclair/typebox": "^0.34.9", "@valibot/to-json-schema": "^1.0.0-beta.3", "arktype": "^2.0.0", "effect": "^3.11.3", "hono": "^4.6.13", "openapi-types": "^12.1.3", "valibot": "^1.0.0-beta.9", "zod": "^3.23.8", "zod-openapi": "^4.0.0" }, "optionalPeers": ["@hono/arktype-validator", "@hono/effect-validator", "@hono/typebox-validator", "@hono/valibot-validator", "@hono/zod-validator", "@sinclair/typebox", "@valibot/to-json-schema", "arktype", "effect", "hono", "valibot", "zod", "zod-openapi"] }, "sha512-LYr5xdtD49M7hEAduV1PftOMzuT8ZNvkyWfh1DThkLsIr4RkvDb12UxgIiFbwrJB6FLtFXLoOZL9x4IeDk2+VA=="],
@@ -125,6 +156,8 @@
 
     "indent-string": ["[email protected]", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
 
+    "inherits": ["[email protected]", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
     "ink": ["[email protected]", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg=="],
 
     "ink-text-input": ["[email protected]", "", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="],
@@ -133,14 +166,22 @@
 
     "is-in-ci": ["[email protected]", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="],
 
+    "jmespath": ["[email protected]", "", {}, "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w=="],
+
+    "joycon": ["[email protected]", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
+
     "js-tokens": ["[email protected]", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
 
     "js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
 
+    "json-rpc-2.0": ["[email protected]", "", {}, "sha512-asnLgC1qD5ytP+fvBP8uL0rvj+l8P6iYICbzZ8dVxCpESffVjzA7KkYkbKCIbavs7cllwH1ZUaNtJwphdeRqpg=="],
+
     "json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
 
     "json-schema-walker": ["[email protected]", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", "clone": "^2.1.2" } }, "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw=="],
 
+    "leven": ["[email protected]", "", {}, "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA=="],
+
     "loose-envify": ["[email protected]", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
 
     "mime-db": ["[email protected]", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
@@ -149,8 +190,16 @@
 
     "mimic-fn": ["[email protected]", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
 
+    "minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+
+    "mri": ["[email protected]", "", {}, "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w=="],
+
     "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
 
+    "on-exit-leak-free": ["[email protected]", "", {}, "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg=="],
+
+    "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
     "onetime": ["[email protected]", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
 
     "openapi-types": ["[email protected]", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
@@ -159,28 +208,72 @@
 
     "peek-readable": ["[email protected]", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="],
 
+    "pino": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.0.0", "on-exit-leak-free": "^0.2.0", "pino-abstract-transport": "v0.5.0", "pino-std-serializers": "^4.0.0", "process-warning": "^1.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.1.0", "safe-stable-stringify": "^2.1.0", "sonic-boom": "^2.2.1", "thread-stream": "^0.15.1" }, "bin": { "pino": "bin.js" } }, "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg=="],
+
+    "pino-abstract-transport": ["[email protected]", "", { "dependencies": { "duplexify": "^4.1.2", "split2": "^4.0.0" } }, "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ=="],
+
+    "pino-pretty": ["[email protected]", "", { "dependencies": { "@hapi/bourne": "^2.0.0", "args": "^5.0.1", "chalk": "^4.0.0", "dateformat": "^4.5.1", "fast-safe-stringify": "^2.0.7", "jmespath": "^0.15.0", "joycon": "^3.0.0", "pump": "^3.0.0", "readable-stream": "^3.6.0", "rfdc": "^1.3.0", "split2": "^3.1.1", "strip-json-comments": "^3.1.1" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-Zj+0TVdYKkAAIx9EUCL5e4TttwgsaFvJh2ceIMQeFCY8ak9tseEZQGSgpvyjEj1/iIVGIh5tdhkGEQWSMILKHA=="],
+
+    "pino-std-serializers": ["[email protected]", "", {}, "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q=="],
+
+    "process-warning": ["[email protected]", "", {}, "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="],
+
+    "pump": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="],
+
+    "quick-format-unescaped": ["[email protected]", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
+
     "react": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
 
     "react-reconciler": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="],
 
+    "readable-stream": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+    "real-require": ["[email protected]", "", {}, "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg=="],
+
     "restore-cursor": ["[email protected]", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
 
+    "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
+
+    "safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+    "safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
+
     "scheduler": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
 
+    "semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
     "signal-exit": ["[email protected]", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
 
     "slice-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg=="],
 
+    "sonic-boom": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg=="],
+
+    "split2": ["[email protected]", "", { "dependencies": { "readable-stream": "^3.0.0" } }, "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="],
+
     "stack-utils": ["[email protected]", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
 
+    "stream-shift": ["[email protected]", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="],
+
     "string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
 
+    "string_decoder": ["[email protected]", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
     "strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
 
+    "strip-json-comments": ["[email protected]", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
+
     "strtok3": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="],
 
+    "supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+    "thread-stream": ["[email protected]", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
+
     "token-types": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
 
+    "ts-lsp-client": ["[email protected]", "", { "dependencies": { "json-rpc-2.0": "^1.7.0", "pino": "^7.0.5", "pino-pretty": "^5.1.3", "tslib": "~2.6.2" } }, "sha512-0ItrsqvNUM9KNFGbeT1N8jSi9gvasGOvxJUXjGf4P2TX0w250AUWLeRStaSrQbYcFDshDtE5d4BshUmYwodDgw=="],
+
+    "tslib": ["[email protected]", "", {}, "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="],
+
     "typanion": ["[email protected]", "", {}, "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug=="],
 
     "type-fest": ["[email protected]", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@@ -191,10 +284,22 @@
 
     "undici-types": ["[email protected]", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
 
+    "util-deprecate": ["[email protected]", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+    "vscode-jsonrpc": ["[email protected]", "", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="],
+
+    "vscode-languageclient": ["[email protected]", "", { "dependencies": { "minimatch": "^5.1.0", "semver": "^7.3.7", "vscode-languageserver-protocol": "3.17.3" } }, "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing=="],
+
+    "vscode-languageserver-protocol": ["[email protected]", "", { "dependencies": { "vscode-jsonrpc": "8.1.0", "vscode-languageserver-types": "3.17.3" } }, "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA=="],
+
+    "vscode-languageserver-types": ["[email protected]", "", {}, "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="],
+
     "widest-line": ["[email protected]", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
 
     "wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="],
 
+    "wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
     "ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
 
     "yoga-layout": ["[email protected]", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
@@ -205,8 +310,30 @@
 
     "zod-to-json-schema": ["[email protected]", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
 
+    "args/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
     "cli-truncate/slice-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
 
+    "pino-abstract-transport/split2": ["[email protected]", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
+
+    "pino-pretty/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
     "slice-ansi/is-fullwidth-code-point": ["[email protected]", "", { "dependencies": { "get-east-asian-width": "^1.0.0" } }, "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA=="],
+
+    "vscode-languageserver-protocol/vscode-jsonrpc": ["[email protected]", "", {}, "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw=="],
+
+    "args/chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+    "args/chalk/escape-string-regexp": ["[email protected]", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+    "args/chalk/supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+    "pino-pretty/chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+    "args/chalk/ansi-styles/color-convert": ["[email protected]", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+    "args/chalk/supports-color/has-flag": ["[email protected]", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+    "args/chalk/ansi-styles/color-convert/color-name": ["[email protected]", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
   }
 }

+ 3 - 0
js/package.json

@@ -26,6 +26,9 @@
     "clipanion": "^4.0.0-rc.4",
     "hono": "^4.7.10",
     "hono-openapi": "^0.4.8",
+    "ts-lsp-client": "^1.0.3",
+    "vscode-jsonrpc": "^8.2.1",
+    "vscode-languageclient": "8",
     "zod": "^3.25.3",
     "zod-openapi": "^4.2.4"
   }

+ 6 - 1
js/src/index.ts

@@ -1,10 +1,11 @@
 import { App } from "./app";
 import { Server } from "./server/server";
-import { Cli, Command, Option, runExit } from "clipanion";
+import { Cli, Command, Option } from "clipanion";
 import fs from "fs/promises";
 import path from "path";
 import { Bus } from "./bus";
 import { Session } from "./session/session";
+import { LSP } from "./lsp";
 
 const cli = new Cli({
   binaryLabel: `opencode`,
@@ -25,6 +26,7 @@ cli.register(
     }
   },
 );
+
 cli.register(
   class extends Command {
     static paths = [["generate"]];
@@ -71,6 +73,9 @@ cli.register(
               "tool:",
               part.toolInvocation.toolName,
               part.toolInvocation.args,
+              part.toolInvocation.state === "result"
+                ? part.toolInvocation.result
+                : "",
             );
           }
         }

+ 178 - 0
js/src/lsp/client.ts

@@ -0,0 +1,178 @@
+import { spawn } from "child_process";
+import path from "path";
+import {
+  createMessageConnection,
+  Disposable,
+  StreamMessageReader,
+  StreamMessageWriter,
+} from "vscode-jsonrpc/node";
+import { App } from "../app";
+import { Log } from "../util/log";
+import { LANGUAGE_EXTENSIONS } from "./language";
+
+export namespace LSPClient {
+  const log = Log.create({ service: "lsp.client" });
+
+  export type Info = Awaited<ReturnType<typeof create>>;
+
+  export async function create(input: { cmd: string[] }) {
+    log.info("starting client", input);
+    let version = 0;
+
+    const app = await App.use();
+    const [command, ...args] = input.cmd;
+    const server = spawn(command, args, {
+      stdio: ["pipe", "pipe", "pipe"],
+      cwd: app.root,
+    });
+
+    const connection = createMessageConnection(
+      new StreamMessageReader(server.stdout),
+      new StreamMessageWriter(server.stdin),
+    );
+
+    const diagnostics = new Map<string, any>();
+    connection.onNotification("textDocument/publishDiagnostics", (params) => {
+      log.info("textDocument/publishDiagnostics", {
+        path: new URL(params.uri).pathname,
+      });
+      diagnostics.set(new URL(params.uri).pathname, params.diagnostics);
+    });
+    connection.listen();
+
+    await connection.sendRequest("initialize", {
+      processId: server.pid,
+      initializationOptions: {
+        workspaceFolders: [
+          {
+            name: "workspace",
+            uri: "file://" + app.root,
+          },
+        ],
+        tsserver: {
+          path: require.resolve("typescript/lib/tsserver.js"),
+        },
+      },
+      capabilities: {
+        workspace: {
+          configuration: true,
+          didChangeConfiguration: {
+            dynamicRegistration: true,
+          },
+          didChangeWatchedFiles: {
+            dynamicRegistration: true,
+            relativePatternSupport: true,
+          },
+        },
+        textDocument: {
+          synchronization: {
+            dynamicRegistration: true,
+            didSave: true,
+          },
+          completion: {
+            completionItem: {},
+          },
+          codeLens: {
+            dynamicRegistration: true,
+          },
+          documentSymbol: {},
+          codeAction: {
+            codeActionLiteralSupport: {
+              codeActionKind: {
+                valueSet: [],
+              },
+            },
+          },
+          publishDiagnostics: {
+            versionSupport: true,
+          },
+          semanticTokens: {
+            requests: {
+              range: {},
+              full: {},
+            },
+            tokenTypes: [],
+            tokenModifiers: [],
+            formats: [],
+          },
+        },
+        window: {},
+      },
+    });
+    await connection.sendNotification("initialized", {});
+    log.info("initialized");
+
+    const result = {
+      get connection() {
+        return connection;
+      },
+      notify: {
+        async open(input: { path: string }) {
+          log.info("textDocument/didOpen", input);
+          diagnostics.delete(input.path);
+          const text = await Bun.file(input.path).text();
+          const languageId = LANGUAGE_EXTENSIONS[path.extname(input.path)];
+          await connection.sendNotification("textDocument/didOpen", {
+            textDocument: {
+              uri: `file://` + input.path,
+              languageId,
+              version: 1,
+              text: text,
+            },
+          });
+        },
+        async change(input: { path: string }) {
+          log.info("textDocument/didChange", input);
+          diagnostics.delete(input.path);
+          const text = await Bun.file(input.path).text();
+          version++;
+          await connection.sendNotification("textDocument/didChange", {
+            textDocument: {
+              uri: `file://` + input.path,
+              version: Date.now(),
+            },
+            contentChanges: [
+              {
+                text,
+              },
+            ],
+          });
+        },
+      },
+      get diagnostics() {
+        return diagnostics;
+      },
+      async refreshDiagnostics(input: { path: string }) {
+        log.info("refreshing diagnostics", input);
+        let notif: Disposable | undefined;
+        return await Promise.race([
+          new Promise<void>(async (resolve) => {
+            notif = connection.onNotification(
+              "textDocument/publishDiagnostics",
+              (params) => {
+                log.info("refreshed diagnostics", input);
+                if (new URL(params.uri).pathname === input.path) {
+                  diagnostics.set(
+                    new URL(params.uri).pathname,
+                    params.diagnostics,
+                  );
+                  resolve();
+                  notif?.dispose();
+                }
+              },
+            );
+            await result.notify.change(input);
+          }),
+          new Promise<void>((resolve) =>
+            setTimeout(() => {
+              notif?.dispose();
+              resolve();
+            }, 5000),
+          ),
+        ]);
+      },
+    };
+
+    return result;
+  }
+}

+ 31 - 0
js/src/lsp/index.ts

@@ -0,0 +1,31 @@
+import { App } from "../app";
+import { Log } from "../util/log";
+import { LSPClient } from "./client";
+
+export namespace LSP {
+  const log = Log.create({ service: "lsp" });
+
+  const state = App.state("lsp", async () => {
+    const clients = new Map<string, LSPClient.Info>();
+
+    clients.set(
+      "typescript",
+      await LSPClient.create({
+        cmd: ["bun", "x", "typescript-language-server", "--stdio"],
+      }),
+    );
+
+    return {
+      clients,
+      diagnostics: new Map<string, any>(),
+    };
+  });
+
+  export async function run<T>(
+    input: (client: LSPClient.Info) => Promise<T>,
+  ): Promise<T[]> {
+    const clients = await state().then((x) => [...x.clients.values()]);
+    const tasks = clients.map((x) => input(x));
+    return Promise.all(tasks);
+  }
+}

+ 83 - 0
js/src/lsp/language.ts

@@ -0,0 +1,83 @@
+export const LANGUAGE_EXTENSIONS: Record<string, string> = {
+  ".abap": "abap",
+  ".bat": "bat",
+  ".bib": "bibtex",
+  ".bibtex": "bibtex",
+  ".clj": "clojure",
+  ".coffee": "coffeescript",
+  ".c": "c",
+  ".cpp": "cpp",
+  ".cxx": "cpp",
+  ".cc": "cpp",
+  ".c++": "cpp",
+  ".cs": "csharp",
+  ".css": "css",
+  ".d": "d",
+  ".pas": "pascal",
+  ".pascal": "pascal",
+  ".diff": "diff",
+  ".patch": "diff",
+  ".dart": "dart",
+  ".dockerfile": "dockerfile",
+  ".ex": "elixir",
+  ".exs": "elixir",
+  ".erl": "erlang",
+  ".hrl": "erlang",
+  ".fs": "fsharp",
+  ".fsi": "fsharp",
+  ".fsx": "fsharp",
+  ".fsscript": "fsharp",
+  ".gitcommit": "git-commit",
+  ".gitrebase": "git-rebase",
+  ".go": "go",
+  ".groovy": "groovy",
+  ".hbs": "handlebars",
+  ".handlebars": "handlebars",
+  ".hs": "haskell",
+  ".html": "html",
+  ".htm": "html",
+  ".ini": "ini",
+  ".java": "java",
+  ".js": "javascript",
+  ".jsx": "javascriptreact",
+  ".json": "json",
+  ".tex": "latex",
+  ".latex": "latex",
+  ".less": "less",
+  ".lua": "lua",
+  ".makefile": "makefile",
+  makefile: "makefile",
+  ".md": "markdown",
+  ".markdown": "markdown",
+  ".m": "objective-c",
+  ".mm": "objective-cpp",
+  ".pl": "perl",
+  ".pm": "perl6",
+  ".php": "php",
+  ".ps1": "powershell",
+  ".psm1": "powershell",
+  ".pug": "jade",
+  ".jade": "jade",
+  ".py": "python",
+  ".r": "r",
+  ".cshtml": "razor",
+  ".razor": "razor",
+  ".rb": "ruby",
+  ".rs": "rust",
+  ".scss": "scss",
+  ".sass": "sass",
+  ".scala": "scala",
+  ".shader": "shaderlab",
+  ".sh": "shellscript",
+  ".bash": "shellscript",
+  ".zsh": "shellscript",
+  ".ksh": "shellscript",
+  ".sql": "sql",
+  ".swift": "swift",
+  ".ts": "typescript",
+  ".tsx": "typescriptreact",
+  ".xml": "xml",
+  ".xsl": "xsl",
+  ".yaml": "yaml",
+  ".yml": "yaml",
+};

+ 62 - 170
js/src/tool/edit.ts

@@ -4,6 +4,7 @@ import * as path from "path";
 import { Log } from "../util/log";
 import { Tool } from "./tool";
 import { FileTimes } from "./util/file-times";
+import { LSP } from "../lsp";
 
 const log = Log.create({ service: "tool.edit" });
 
@@ -78,7 +79,7 @@ Before using this tool:
    - Use the LS tool to verify the parent directory exists and is the correct location
 
 To make a file edit, provide the following:
-1. file_path: The absolute path to the file to modify (must be absolute, not relative)
+1. file_path: The relative path to the file to modify (must be relative, not absolute)
 2. old_string: The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)
 3. new_string: The edited text to replace the old_string
 
@@ -112,7 +113,7 @@ WARNING: If you do not follow these requirements:
 When making edits:
    - Ensure the edit results in idiomatic, correct code
    - Do not leave the code in a broken state
-   - Always use absolute file paths (starting with /)
+   - Always use relative file paths 
 
 Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`;
 
@@ -134,21 +135,68 @@ export const EditTool = Tool.define({
       filePath = path.join(process.cwd(), filePath);
     }
 
-    // Handle different operations based on parameters
-    if (params.old_string === "") {
-      return {
-        output: createNewFile(filePath, params.new_string),
-      };
-    }
+    await (async () => {
+      if (params.old_string === "") {
+        await createNewFile(filePath, params.new_string);
+        return;
+      }
 
-    if (params.new_string === "") {
-      return {
-        output: deleteContent(filePath, params.old_string),
-      };
+      const read = FileTimes.get(filePath);
+      if (!read)
+        throw new Error(
+          `You must read the file ${filePath} before editing it. Use the View tool first`,
+        );
+      const file = Bun.file(filePath);
+      if (!(await file.exists())) throw new Error(`File ${filePath} not found`);
+      const stats = await file.stat();
+      if (stats.isDirectory())
+        throw new Error(`Path is a directory, not a file: ${filePath}`);
+      if (stats.mtime.getTime() > read.getTime())
+        throw new Error(
+          `File ${filePath} has been modified since it was last read.\nLast modification: ${read.toISOString()}\nLast read: ${stats.mtime.toISOString()}\n\nPlease read the file again before modifying it.`,
+        );
+
+      const content = await file.text();
+      const index = content.indexOf(params.old_string);
+      if (index === -1)
+        throw new Error(
+          `old_string not found in file. Make sure it matches exactly, including whitespace and line breaks`,
+        );
+      const lastIndex = content.lastIndexOf(params.old_string);
+      if (index !== lastIndex)
+        throw new Error(
+          `old_string appears multiple times in the file. Please provide more context to ensure a unique match`,
+        );
+
+      const newContent =
+        content.substring(0, index) +
+        params.new_string +
+        content.substring(index + params.old_string.length);
+
+      console.log(newContent);
+      await file.write(newContent);
+    })();
+
+    FileTimes.write(filePath);
+    FileTimes.read(filePath);
+
+    let output = "";
+    await LSP.run((client) => client.refreshDiagnostics({ path: filePath }));
+    const diagnostics = await LSP.run(async (client) => client.diagnostics);
+    for (const diagnostic of diagnostics) {
+      for (const [file, params] of diagnostic.entries()) {
+        if (params.length === 0) continue;
+        if (file === filePath) {
+          output += `\nThis file has errors, please fix\n<file_diagnostics>\n${JSON.stringify(params)}\n</file_diagnostics>\n`;
+          continue;
+        }
+        output += `\n<project_diagnostics>\n${JSON.stringify(params)}\n</project_diagnostics>\n`;
+      }
     }
+    console.log(output);
 
     return {
-      output: replaceContent(filePath, params.old_string, params.new_string),
+      output,
     };
   },
 });
@@ -186,160 +234,4 @@ async function createNewFile(
   }
 }
 
-async function deleteContent(
-  filePath: string,
-  oldString: string,
-): Promise<string> {
-  try {
-    // Check if file exists
-    let fileStats;
-    try {
-      fileStats = fs.statSync(filePath);
-      if (fileStats.isDirectory()) {
-        throw new Error(`Path is a directory, not a file: ${filePath}`);
-      }
-    } catch (err: any) {
-      if (err.code === "ENOENT") {
-        throw new Error(`File not found: ${filePath}`);
-      }
-      throw err;
-    }
-
-    const lastReadTime = FileTimes.get(filePath);
-    if (!lastReadTime) {
-      throw new Error(
-        "You must read the file before editing it. Use the View tool first",
-      );
-    }
-
-    const modTime = fileStats.mtime;
-    if (modTime > lastReadTime) {
-      throw new Error(
-        `File ${filePath} has been modified since it was last read (mod time: ${modTime.toISOString()}, last read: ${lastReadTime.toISOString()})`,
-      );
-    }
-
-    const oldContent = fs.readFileSync(filePath, "utf8");
-    const index = oldContent.indexOf(oldString);
-    if (index === -1) {
-      throw new Error(
-        "old_string not found in file. Make sure it matches exactly, including whitespace and line breaks",
-      );
-    }
-
-    const lastIndex = oldContent.lastIndexOf(oldString);
-    if (index !== lastIndex) {
-      throw new Error(
-        "old_string appears multiple times in the file. Please provide more context to ensure a unique match",
-      );
-    }
-
-    const newContent =
-      oldContent.substring(0, index) +
-      oldContent.substring(index + oldString.length);
-
-    const { diff, additions, removals } = generateDiff(
-      oldContent,
-      newContent,
-      filePath,
-    );
-
-    // Write the file
-    fs.writeFileSync(filePath, newContent);
-
-    FileTimes.write(filePath);
-    FileTimes.read(filePath);
-
-    return `Content deleted from file: ${filePath}`;
-  } catch (err: any) {
-    throw new Error(`Failed to delete content: ${err.message}`);
-  }
-}
-
-async function replaceContent(
-  filePath: string,
-  oldString: string,
-  newString: string,
-): Promise<string> {
-  try {
-    // Check if file exists
-    let fileStats;
-    try {
-      fileStats = fs.statSync(filePath);
-      if (fileStats.isDirectory()) {
-        throw new Error(`Path is a directory, not a file: ${filePath}`);
-      }
-    } catch (err: any) {
-      if (err.code === "ENOENT") {
-        throw new Error(`File not found: ${filePath}`);
-      }
-      throw err;
-    }
-
-    // Check if file has been read before
-    const lastReadTime = getLastReadTime(filePath);
-    if (!lastReadTime) {
-      throw new Error(
-        "You must read the file before editing it. Use the View tool first",
-      );
-    }
-
-    // Check if file has been modified since last read
-    const modTime = fileStats.mtime;
-    if (modTime > lastReadTime) {
-      throw new Error(
-        `File ${filePath} has been modified since it was last read (mod time: ${modTime.toISOString()}, last read: ${lastReadTime.toISOString()})`,
-      );
-    }
-
-    // Read the file content
-    const oldContent = fs.readFileSync(filePath, "utf8");
-
-    // Find the string to replace
-    const index = oldContent.indexOf(oldString);
-    if (index === -1) {
-      throw new Error(
-        "old_string not found in file. Make sure it matches exactly, including whitespace and line breaks",
-      );
-    }
-
-    // Check if the string appears multiple times
-    const lastIndex = oldContent.lastIndexOf(oldString);
-    if (index !== lastIndex) {
-      throw new Error(
-        "old_string appears multiple times in the file. Please provide more context to ensure a unique match",
-      );
-    }
-
-    // Create the new content
-    const newContent =
-      oldContent.substring(0, index) +
-      newString +
-      oldContent.substring(index + oldString.length);
-
-    // Check if content actually changed
-    if (oldContent === newContent) {
-      throw new Error(
-        "new content is the same as old content. No changes made.",
-      );
-    }
-
-    // Generate diff
-    const { diff, additions, removals } = generateDiff(
-      oldContent,
-      newContent,
-      filePath,
-    );
-
-    // Write the file
-    fs.writeFileSync(filePath, newContent);
-
-    FileTimes.write(filePath);
-    FileTimes.read(filePath);
-
-    return `Content replaced in file: ${filePath}`;
-  } catch (err: any) {
-    throw new Error(`Failed to replace content: ${err.message}`);
-  }
-}
-
+function getFile(filePath: string) {}

+ 3 - 0
js/src/tool/tool.ts

@@ -40,6 +40,9 @@ export namespace Tool {
               output: result.output,
             };
           } catch (e: any) {
+            log.error("error", {
+              msg: e.toString(),
+            });
             return "An error occurred: " + e.toString();
           }
         },

+ 6 - 2
js/src/tool/view.ts

@@ -2,6 +2,8 @@ import { z } from "zod";
 import * as fs from "fs";
 import * as path from "path";
 import { Tool } from "./tool";
+import { LSP } from "../lsp";
+import { FileTimes } from "./util/file-times";
 
 const MAX_READ_SIZE = 250 * 1024;
 const DEFAULT_READ_LIMIT = 2000;
@@ -117,8 +119,11 @@ export const ViewTool = Tool.define({
     }
     output += "\n</file>";
 
+    await LSP.run((client) => client.notify.open({ path: filePath }));
+    FileTimes.read(filePath);
+
     return {
-      output: output,
+      output,
     };
   },
 });
@@ -143,4 +148,3 @@ function isImageFile(filePath: string): string | false {
       return false;
   }
 }
-

+ 1 - 0
js/src/util/log.ts

@@ -12,6 +12,7 @@ export namespace Log {
   };
 
   export function file(directory: string) {
+    return;
     const out = Bun.file(
       path.join(AppPath.data(directory), "opencode.out.log"),
     ).writer();

+ 5 - 0
js/src/util/scrap.ts

@@ -0,0 +1,5 @@
+export const foo: string = "42";
+
+export function dummyFunction(): void {
+  console.log("This is a dummy function");
+}