Browse Source

more tools

Dax Raad 9 months ago
parent
commit
80555f13e0

+ 92 - 6
js/bun.lock

@@ -4,16 +4,18 @@
     "": {
     "": {
       "name": "js",
       "name": "js",
       "dependencies": {
       "dependencies": {
-        "@ai-sdk/anthropic": "^2.0.0-alpha.2",
+        "@ai-sdk/anthropic": "^2.0.0-alpha.4",
         "@flystorage/file-storage": "^1.1.0",
         "@flystorage/file-storage": "^1.1.0",
         "@flystorage/local-fs": "^1.1.0",
         "@flystorage/local-fs": "^1.1.0",
         "@hono/zod-validator": "^0.5.0",
         "@hono/zod-validator": "^0.5.0",
-        "ai": "^5.0.0-alpha.2",
+        "ai": "^5.0.0-alpha.4",
         "cac": "^6.7.14",
         "cac": "^6.7.14",
         "clipanion": "^4.0.0-rc.4",
         "clipanion": "^4.0.0-rc.4",
         "hono": "^4.7.10",
         "hono": "^4.7.10",
         "hono-openapi": "^0.4.8",
         "hono-openapi": "^0.4.8",
+        "jsdom": "^26.1.0",
         "ts-lsp-client": "^1.0.3",
         "ts-lsp-client": "^1.0.3",
+        "turndown": "^7.2.0",
         "vscode-jsonrpc": "^8.2.1",
         "vscode-jsonrpc": "^8.2.1",
         "vscode-languageclient": "8",
         "vscode-languageclient": "8",
         "zod": "^3.25.3",
         "zod": "^3.25.3",
@@ -22,7 +24,9 @@
       "devDependencies": {
       "devDependencies": {
         "@tsconfig/bun": "^1.0.7",
         "@tsconfig/bun": "^1.0.7",
         "@types/bun": "latest",
         "@types/bun": "latest",
+        "@types/jsdom": "^21.1.7",
         "@types/react": "18",
         "@types/react": "18",
+        "@types/turndown": "^5.0.5",
         "ink": "^5.2.1",
         "ink": "^5.2.1",
         "ink-text-input": "^6.0.0",
         "ink-text-input": "^6.0.0",
         "react": "^18.0.0",
         "react": "^18.0.0",
@@ -33,16 +37,28 @@
     },
     },
   },
   },
   "packages": {
   "packages": {
-    "@ai-sdk/anthropic": ["@ai-sdk/[email protected].2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.2", "@ai-sdk/provider-utils": "3.0.0-alpha.2" }, "peerDependencies": { "zod": "^3.24.0" } }, "sha512-ynD9RHZBAoLKUh8wqmS0EROSZQGSYZkVoF5Y0ZgMQfyVkeDXTl3PCXfc0jqwBTjNk6OOHza6BD5hCZDSjh0Sqg=="],
+    "@ai-sdk/anthropic": ["@ai-sdk/[email protected].4", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.4", "@ai-sdk/provider-utils": "3.0.0-alpha.4" }, "peerDependencies": { "zod": "^3.24.0" } }, "sha512-uknR4CfBVjkxhlLqOz0ThCtsH5GzRT6IBVzV5oI/n+CgOSwWNQXfsyBmmO8RopPCEHq+HEb5xSbG/l/FmkxafA=="],
 
 
-    "@ai-sdk/provider": ["@ai-sdk/[email protected].2", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-jgRpHhpKmXnUEp41xUZyqJ8VPF9gS6W7SP2iYRaM9jaq66edcg6gTYOJLqM+nSU2tXYfkzfoBGGRvtl9ijH/VQ=="],
+    "@ai-sdk/provider": ["@ai-sdk/[email protected].4", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-g6IVjm0iGasiv4fAv6TtPIDTfeMTqFRQt3J6Jz8skm/Tb2r48qKEtcKNrDSpspDIFrQJ4fuC1Xw/aAPrVCs7tQ=="],
 
 
-    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected].2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.2", "@standard-schema/spec": "^1.0.0", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-oTlF6UlVitSdVPQv0e+kAkZmbuunJAUYdVEh7ZRvoti+kY/T4vOT6p22X0xTaWgl0+MI1igAT+c83j7tCMuo2w=="],
+    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected].4", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.4", "@standard-schema/spec": "^1.0.0", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-edEnOh8i676TLX8ibWD69gHTaU6btJ9uCN+7/8vXWlMHZJo2ST4mgjRP5f6xC4rH8XXVMYK0rVPwbnuyR/PGzA=="],
 
 
     "@alcalzone/ansi-tokenize": ["@alcalzone/[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw=="],
     "@alcalzone/ansi-tokenize": ["@alcalzone/[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw=="],
 
 
     "@apidevtools/json-schema-ref-parser": ["@apidevtools/[email protected]", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="],
     "@apidevtools/json-schema-ref-parser": ["@apidevtools/[email protected]", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="],
 
 
+    "@asamuzakjp/css-color": ["@asamuzakjp/[email protected]", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
+
+    "@csstools/color-helpers": ["@csstools/[email protected]", "", {}, "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA=="],
+
+    "@csstools/css-calc": ["@csstools/[email protected]", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw=="],
+
+    "@csstools/css-color-parser": ["@csstools/[email protected]", "", { "dependencies": { "@csstools/color-helpers": "^5.0.2", "@csstools/css-calc": "^2.1.3" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw=="],
+
+    "@csstools/css-parser-algorithms": ["@csstools/[email protected]", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A=="],
+
+    "@csstools/css-tokenizer": ["@csstools/[email protected]", "", {}, "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw=="],
+
     "@flystorage/dynamic-import": ["@flystorage/[email protected]", "", {}, "sha512-CIbIUrBdaPFyKnkVBaqzksvzNtsMSXITR/G/6zlil3MBnPFq2LX+X4Mv5p2XOmv/3OulFs/ff2SNb+5dc2Twtg=="],
     "@flystorage/dynamic-import": ["@flystorage/[email protected]", "", {}, "sha512-CIbIUrBdaPFyKnkVBaqzksvzNtsMSXITR/G/6zlil3MBnPFq2LX+X4Mv5p2XOmv/3OulFs/ff2SNb+5dc2Twtg=="],
 
 
     "@flystorage/file-storage": ["@flystorage/[email protected]", "", {}, "sha512-25Gd5EsXDmhHrK5orpRuVqebQms1Cm9m5ACMZ0sVDX+Sbl1V0G88CbcWt7mEoWRYLvQ1U072htqg6Sav76ZlVA=="],
     "@flystorage/file-storage": ["@flystorage/[email protected]", "", {}, "sha512-25Gd5EsXDmhHrK5orpRuVqebQms1Cm9m5ACMZ0sVDX+Sbl1V0G88CbcWt7mEoWRYLvQ1U072htqg6Sav76ZlVA=="],
@@ -55,6 +71,8 @@
 
 
     "@jsdevtools/ono": ["@jsdevtools/[email protected]", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
     "@jsdevtools/ono": ["@jsdevtools/[email protected]", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
 
 
+    "@mixmark-io/domino": ["@mixmark-io/[email protected]", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
+
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
 
 
     "@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
     "@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
@@ -67,6 +85,8 @@
 
 
     "@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
     "@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
 
 
+    "@types/jsdom": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA=="],
+
     "@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
     "@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
 
 
     "@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="],
     "@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="],
@@ -75,7 +95,13 @@
 
 
     "@types/react": ["@types/[email protected]", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-gXLBtmlcRJeT09/sI4PxVwyrku6SaNUj/6cMubjE6T6XdY1fDmBL7r0nX0jbSZPU/Xr0KuwLLZh6aOYY5d91Xw=="],
     "@types/react": ["@types/[email protected]", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-gXLBtmlcRJeT09/sI4PxVwyrku6SaNUj/6cMubjE6T6XdY1fDmBL7r0nX0jbSZPU/Xr0KuwLLZh6aOYY5d91Xw=="],
 
 
-    "ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.2", "@ai-sdk/provider-utils": "3.0.0-alpha.2", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-42asUyoFcqjV5AoZezJPawODCPT5Rb1y/UipVlcXn1tpqlypCchSEukjNw/l09YPVucqCbW19IVqojLttkTTVA=="],
+    "@types/tough-cookie": ["@types/[email protected]", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
+
+    "@types/turndown": ["@types/[email protected]", "", {}, "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="],
+
+    "agent-base": ["[email protected]", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
+
+    "ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-alpha.4", "@ai-sdk/provider-utils": "3.0.0-alpha.4", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-u2JGxWcmL19knVOhIMMNFp1k2/8Z4VpsT6BBg6YcpL5Vh1rl7y657sFTLQe3U+pao4b5oxwzd8rKR0ETvVRjnw=="],
 
 
     "ansi-escapes": ["[email protected]", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="],
     "ansi-escapes": ["[email protected]", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="],
 
 
@@ -121,18 +147,26 @@
 
 
     "convert-to-spaces": ["[email protected]", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
     "convert-to-spaces": ["[email protected]", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
 
 
+    "cssstyle": ["[email protected]", "", { "dependencies": { "@asamuzakjp/css-color": "^3.1.2", "rrweb-cssom": "^0.8.0" } }, "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q=="],
+
     "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
     "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
 
 
+    "data-urls": ["[email protected]", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="],
+
     "dateformat": ["[email protected]", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
     "dateformat": ["[email protected]", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
 
 
     "debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
     "debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
 
 
+    "decimal.js": ["[email protected]", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="],
+
     "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=="],
     "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=="],
     "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=="],
     "end-of-stream": ["[email protected]", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
 
 
+    "entities": ["[email protected]", "", {}, "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="],
+
     "environment": ["[email protected]", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
     "environment": ["[email protected]", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
 
 
     "es-toolkit": ["[email protected]", "", {}, "sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA=="],
     "es-toolkit": ["[email protected]", "", {}, "sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA=="],
@@ -155,6 +189,14 @@
 
 
     "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=="],
     "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=="],
 
 
+    "html-encoding-sniffer": ["[email protected]", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="],
+
+    "http-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
+
+    "https-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+    "iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
     "ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
     "ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
 
 
     "indent-string": ["[email protected]", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
     "indent-string": ["[email protected]", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
@@ -169,6 +211,8 @@
 
 
     "is-in-ci": ["[email protected]", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="],
     "is-in-ci": ["[email protected]", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="],
 
 
+    "is-potential-custom-element-name": ["[email protected]", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
+
     "jmespath": ["[email protected]", "", {}, "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w=="],
     "jmespath": ["[email protected]", "", {}, "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w=="],
 
 
     "joycon": ["[email protected]", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
     "joycon": ["[email protected]", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
@@ -177,6 +221,8 @@
 
 
     "js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
     "js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
 
 
+    "jsdom": ["[email protected]", "", { "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", "decimal.js": "^10.5.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.16", "parse5": "^7.2.1", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^5.1.1", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.1.1", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg=="],
+
     "json-rpc-2.0": ["[email protected]", "", {}, "sha512-asnLgC1qD5ytP+fvBP8uL0rvj+l8P6iYICbzZ8dVxCpESffVjzA7KkYkbKCIbavs7cllwH1ZUaNtJwphdeRqpg=="],
     "json-rpc-2.0": ["[email protected]", "", {}, "sha512-asnLgC1qD5ytP+fvBP8uL0rvj+l8P6iYICbzZ8dVxCpESffVjzA7KkYkbKCIbavs7cllwH1ZUaNtJwphdeRqpg=="],
 
 
     "json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
     "json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
@@ -187,6 +233,8 @@
 
 
     "loose-envify": ["[email protected]", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
     "loose-envify": ["[email protected]", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
 
 
+    "lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
     "mime-db": ["[email protected]", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
     "mime-db": ["[email protected]", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
 
 
     "mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
     "mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
@@ -199,6 +247,8 @@
 
 
     "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
     "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
 
 
+    "nwsapi": ["[email protected]", "", {}, "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA=="],
+
     "on-exit-leak-free": ["[email protected]", "", {}, "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg=="],
     "on-exit-leak-free": ["[email protected]", "", {}, "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg=="],
 
 
     "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
     "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
@@ -207,6 +257,8 @@
 
 
     "openapi-types": ["[email protected]", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
     "openapi-types": ["[email protected]", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
 
 
+    "parse5": ["[email protected]", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
+
     "patch-console": ["[email protected]", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
     "patch-console": ["[email protected]", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
 
 
     "peek-readable": ["[email protected]", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="],
     "peek-readable": ["[email protected]", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="],
@@ -223,6 +275,8 @@
 
 
     "pump": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="],
     "pump": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="],
 
 
+    "punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
     "quick-format-unescaped": ["[email protected]", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
     "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": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
@@ -237,10 +291,16 @@
 
 
     "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
     "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
 
 
+    "rrweb-cssom": ["[email protected]", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="],
+
     "safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
     "safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
 
 
     "safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
     "safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
 
 
+    "safer-buffer": ["[email protected]", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+
+    "saxes": ["[email protected]", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
+
     "scheduler": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
     "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=="],
     "semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
@@ -269,14 +329,26 @@
 
 
     "supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
     "supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
 
 
+    "symbol-tree": ["[email protected]", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
+
     "thread-stream": ["[email protected]", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
     "thread-stream": ["[email protected]", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
 
 
+    "tldts": ["[email protected]", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="],
+
+    "tldts-core": ["[email protected]", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
+
     "token-types": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
     "token-types": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
 
 
+    "tough-cookie": ["[email protected]", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
+
+    "tr46": ["[email protected]", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
+
     "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=="],
     "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=="],
     "tslib": ["[email protected]", "", {}, "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="],
 
 
+    "turndown": ["[email protected]", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
+
     "typanion": ["[email protected]", "", {}, "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug=="],
     "typanion": ["[email protected]", "", {}, "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug=="],
 
 
     "type-fest": ["[email protected]", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
     "type-fest": ["[email protected]", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@@ -297,6 +369,16 @@
 
 
     "vscode-languageserver-types": ["[email protected]", "", {}, "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="],
     "vscode-languageserver-types": ["[email protected]", "", {}, "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="],
 
 
+    "w3c-xmlserializer": ["[email protected]", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
+
+    "webidl-conversions": ["[email protected]", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
+
+    "whatwg-encoding": ["[email protected]", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
+
+    "whatwg-mimetype": ["[email protected]", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
+
+    "whatwg-url": ["[email protected]", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
+
     "widest-line": ["[email protected]", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
     "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=="],
     "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=="],
@@ -305,6 +387,10 @@
 
 
     "ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
     "ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
 
 
+    "xml-name-validator": ["[email protected]", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
+
+    "xmlchars": ["[email protected]", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
+
     "yoga-layout": ["[email protected]", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
     "yoga-layout": ["[email protected]", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
 
 
     "zod": ["[email protected]", "", {}, "sha512-VGZqnyYNrl8JpEJRZaFPqeVNIuqgXNu4cXZ5cOb6zEUO1OxKbRnWB4UdDIXMmiERWncs0yDQukssHov8JUxykQ=="],
     "zod": ["[email protected]", "", {}, "sha512-VGZqnyYNrl8JpEJRZaFPqeVNIuqgXNu4cXZ5cOb6zEUO1OxKbRnWB4UdDIXMmiERWncs0yDQukssHov8JUxykQ=="],

+ 6 - 2
js/package.json

@@ -9,7 +9,9 @@
   "devDependencies": {
   "devDependencies": {
     "@tsconfig/bun": "^1.0.7",
     "@tsconfig/bun": "^1.0.7",
     "@types/bun": "latest",
     "@types/bun": "latest",
+    "@types/jsdom": "^21.1.7",
     "@types/react": "18",
     "@types/react": "18",
+    "@types/turndown": "^5.0.5",
     "ink": "^5.2.1",
     "ink": "^5.2.1",
     "ink-text-input": "^6.0.0",
     "ink-text-input": "^6.0.0",
     "react": "^18.0.0"
     "react": "^18.0.0"
@@ -18,16 +20,18 @@
     "typescript": "5"
     "typescript": "5"
   },
   },
   "dependencies": {
   "dependencies": {
-    "@ai-sdk/anthropic": "^2.0.0-alpha.2",
+    "@ai-sdk/anthropic": "^2.0.0-alpha.4",
     "@flystorage/file-storage": "^1.1.0",
     "@flystorage/file-storage": "^1.1.0",
     "@flystorage/local-fs": "^1.1.0",
     "@flystorage/local-fs": "^1.1.0",
     "@hono/zod-validator": "^0.5.0",
     "@hono/zod-validator": "^0.5.0",
-    "ai": "^5.0.0-alpha.2",
+    "ai": "^5.0.0-alpha.4",
     "cac": "^6.7.14",
     "cac": "^6.7.14",
     "clipanion": "^4.0.0-rc.4",
     "clipanion": "^4.0.0-rc.4",
     "hono": "^4.7.10",
     "hono": "^4.7.10",
     "hono-openapi": "^0.4.8",
     "hono-openapi": "^0.4.8",
+    "jsdom": "^26.1.0",
     "ts-lsp-client": "^1.0.3",
     "ts-lsp-client": "^1.0.3",
+    "turndown": "^7.2.0",
     "vscode-jsonrpc": "^8.2.1",
     "vscode-jsonrpc": "^8.2.1",
     "vscode-languageclient": "8",
     "vscode-languageclient": "8",
     "zod": "^3.25.3",
     "zod": "^3.25.3",

+ 0 - 2
js/src/app/index.ts

@@ -3,7 +3,6 @@ import { AppPath } from "./path";
 import { Log } from "../util/log";
 import { Log } from "../util/log";
 import { Context } from "../util/context";
 import { Context } from "../util/context";
 import { Config } from "./config";
 import { Config } from "./config";
-import { Share } from "../share/share";
 
 
 export namespace App {
 export namespace App {
   const log = Log.create({ service: "app" });
   const log = Log.create({ service: "app" });
@@ -35,7 +34,6 @@ export namespace App {
       get root() {
       get root() {
         return input.directory;
         return input.directory;
       },
       },
-      service<T extends (app: any) => any>(service: any, init: T) {},
     };
     };
 
 
     return result;
     return result;

+ 2 - 1
js/src/session/session.ts

@@ -6,6 +6,7 @@ import { Storage } from "../storage/storage";
 import { Log } from "../util/log";
 import { Log } from "../util/log";
 import {
 import {
   convertToModelMessages,
   convertToModelMessages,
+  stepCountIs,
   streamText,
   streamText,
   type TextUIPart,
   type TextUIPart,
   type ToolInvocationUIPart,
   type ToolInvocationUIPart,
@@ -169,7 +170,7 @@ export namespace Session {
 
 
     const model = await LLM.findModel("claude-sonnet-4-20250514");
     const model = await LLM.findModel("claude-sonnet-4-20250514");
     const result = streamText({
     const result = streamText({
-      maxSteps: 1000,
+      stopWhen: stepCountIs(1000),
       messages: convertToModelMessages(msgs),
       messages: convertToModelMessages(msgs),
       temperature: 0,
       temperature: 0,
       tools,
       tools,

+ 137 - 0
js/src/tool/fetch.ts

@@ -0,0 +1,137 @@
+import { z } from "zod";
+import { Tool } from "./tool";
+import { JSDOM } from "jsdom";
+import TurndownService from "turndown";
+
+const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
+const DEFAULT_TIMEOUT = 30 * 1000; // 30 seconds
+const MAX_TIMEOUT = 120 * 1000; // 2 minutes
+
+const DESCRIPTION = `Fetches content from a URL and returns it in the specified format.
+
+WHEN TO USE THIS TOOL:
+- Use when you need to download content from a URL
+- Helpful for retrieving documentation, API responses, or web content
+- Useful for getting external information to assist with tasks
+
+HOW TO USE:
+- Provide the URL to fetch content from
+- Specify the desired output format (text, markdown, or html)
+- Optionally set a timeout for the request
+
+FEATURES:
+- Supports three output formats: text, markdown, and html
+- Automatically handles HTTP redirects
+- Sets reasonable timeouts to prevent hanging
+- Validates input parameters before making requests
+
+LIMITATIONS:
+- Maximum response size is 5MB
+- Only supports HTTP and HTTPS protocols
+- Cannot handle authentication or cookies
+- Some websites may block automated requests
+
+TIPS:
+- Use text format for plain text content or simple API responses
+- Use markdown format for content that should be rendered with formatting
+- Use html format when you need the raw HTML structure
+- Set appropriate timeouts for potentially slow websites`;
+
+export const Fetch = Tool.define({
+  name: "fetch",
+  description: DESCRIPTION,
+  parameters: z.object({
+    url: z.string().describe("The URL to fetch content from"),
+    format: z
+      .enum(["text", "markdown", "html"])
+      .describe(
+        "The format to return the content in (text, markdown, or html)",
+      ),
+    timeout: z
+      .number()
+      .min(0)
+      .max(MAX_TIMEOUT / 1000)
+      .describe("Optional timeout in seconds (max 120)")
+      .optional(),
+  }),
+  async execute(params, opts) {
+    // Validate URL
+    if (
+      !params.url.startsWith("http://") &&
+      !params.url.startsWith("https://")
+    ) {
+      throw new Error("URL must start with http:// or https://");
+    }
+
+    const timeout = Math.min(
+      (params.timeout ?? DEFAULT_TIMEOUT / 1000) * 1000,
+      MAX_TIMEOUT,
+    );
+
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), timeout);
+    if (opts?.abortSignal) {
+      opts.abortSignal.addEventListener("abort", () => controller.abort());
+    }
+
+    const response = await fetch(params.url, {
+      signal: controller.signal,
+      headers: {
+        "User-Agent": "opencode/1.0",
+      },
+    });
+
+    clearTimeout(timeoutId);
+
+    if (!response.ok) {
+      throw new Error(`Request failed with status code: ${response.status}`);
+    }
+
+    // Check content length
+    const contentLength = response.headers.get("content-length");
+    if (contentLength && parseInt(contentLength) > MAX_RESPONSE_SIZE) {
+      throw new Error("Response too large (exceeds 5MB limit)");
+    }
+
+    const arrayBuffer = await response.arrayBuffer();
+    if (arrayBuffer.byteLength > MAX_RESPONSE_SIZE) {
+      throw new Error("Response too large (exceeds 5MB limit)");
+    }
+
+    const content = new TextDecoder().decode(arrayBuffer);
+    const contentType = response.headers.get("content-type") || "";
+
+    switch (params.format) {
+      case "text":
+        if (contentType.includes("text/html")) {
+          const text = extractTextFromHTML(content);
+          return { output: text };
+        }
+        return { output: content };
+
+      case "markdown":
+        if (contentType.includes("text/html")) {
+          const markdown = convertHTMLToMarkdown(content);
+          return { output: markdown };
+        }
+        return { output: "```\n" + content + "\n```" };
+
+      case "html":
+        return { output: content };
+
+      default:
+        return { output: content };
+    }
+  },
+});
+
+function extractTextFromHTML(html: string): string {
+  const dom = new JSDOM(html);
+  const text = dom.window.document.body?.textContent || "";
+  return text.replace(/\s+/g, " ").trim();
+}
+
+function convertHTMLToMarkdown(html: string): string {
+  const turndownService = new TurndownService();
+  return turndownService.turndown(html);
+}

+ 345 - 0
js/src/tool/grep.ts

@@ -0,0 +1,345 @@
+import { z } from "zod";
+import { Tool } from "./tool";
+import { App } from "../app";
+import { spawn } from "child_process";
+import { promises as fs } from "fs";
+import path from "path";
+
+const DESCRIPTION = `Fast content search tool that finds files containing specific text or patterns, returning matching file paths sorted by modification time (newest first).
+
+WHEN TO USE THIS TOOL:
+- Use when you need to find files containing specific text or patterns
+- Great for searching code bases for function names, variable declarations, or error messages
+- Useful for finding all files that use a particular API or pattern
+
+HOW TO USE:
+- Provide a regex pattern to search for within file contents
+- Set literal_text=true if you want to search for the exact text with special characters (recommended for non-regex users)
+- Optionally specify a starting directory (defaults to current working directory)
+- Optionally provide an include pattern to filter which files to search
+- Results are sorted with most recently modified files first
+
+REGEX PATTERN SYNTAX (when literal_text=false):
+- Supports standard regular expression syntax
+- 'function' searches for the literal text "function"
+- 'log\\..*Error' finds text starting with "log." and ending with "Error"
+- 'import\\s+.*\\s+from' finds import statements in JavaScript/TypeScript
+
+COMMON INCLUDE PATTERN EXAMPLES:
+- '*.js' - Only search JavaScript files
+- '*.{ts,tsx}' - Only search TypeScript files
+- '*.go' - Only search Go files
+
+LIMITATIONS:
+- Results are limited to 100 files (newest first)
+- Performance depends on the number of files being searched
+- Very large binary files may be skipped
+- Hidden files (starting with '.') are skipped
+
+TIPS:
+- For faster, more targeted searches, first use Glob to find relevant files, then use Grep
+- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
+- Always check if results are truncated and refine your search pattern if needed
+- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.`;
+
+interface GrepMatch {
+  path: string;
+  modTime: number;
+  lineNum: number;
+  lineText: string;
+}
+
+function escapeRegexPattern(pattern: string): string {
+  const specialChars = [
+    "\\",
+    ".",
+    "+",
+    "*",
+    "?",
+    "(",
+    ")",
+    "[",
+    "]",
+    "{",
+    "}",
+    "^",
+    "$",
+    "|",
+  ];
+  let escaped = pattern;
+
+  for (const char of specialChars) {
+    escaped = escaped.replaceAll(char, "\\" + char);
+  }
+
+  return escaped;
+}
+
+function globToRegex(glob: string): string {
+  let regexPattern = glob.replaceAll(".", "\\.");
+  regexPattern = regexPattern.replaceAll("*", ".*");
+  regexPattern = regexPattern.replaceAll("?", ".");
+
+  // Handle {a,b,c} patterns
+  regexPattern = regexPattern.replace(/\{([^}]+)\}/g, (match, inner) => {
+    return "(" + inner.replace(/,/g, "|") + ")";
+  });
+
+  return regexPattern;
+}
+
+async function searchWithRipgrep(
+  pattern: string,
+  searchPath: string,
+  include?: string,
+): Promise<GrepMatch[]> {
+  return new Promise((resolve, reject) => {
+    const args = ["-n", pattern];
+    if (include) {
+      args.push("--glob", include);
+    }
+    args.push(searchPath);
+
+    const rg = spawn("rg", args);
+    let output = "";
+    let errorOutput = "";
+
+    rg.stdout.on("data", (data) => {
+      output += data.toString();
+    });
+
+    rg.stderr.on("data", (data) => {
+      errorOutput += data.toString();
+    });
+
+    rg.on("close", async (code) => {
+      if (code === 1) {
+        // No matches found
+        resolve([]);
+        return;
+      }
+
+      if (code !== 0) {
+        reject(new Error(`ripgrep failed: ${errorOutput}`));
+        return;
+      }
+
+      const lines = output.trim().split("\n");
+      const matches: GrepMatch[] = [];
+
+      for (const line of lines) {
+        if (!line) continue;
+
+        // Parse ripgrep output format: file:line:content
+        const parts = line.split(":", 3);
+        if (parts.length < 3) continue;
+
+        const filePath = parts[0];
+        const lineNum = parseInt(parts[1], 10);
+        const lineText = parts[2];
+
+        try {
+          const stats = await fs.stat(filePath);
+          matches.push({
+            path: filePath,
+            modTime: stats.mtime.getTime(),
+            lineNum,
+            lineText,
+          });
+        } catch {
+          // Skip files we can't access
+          continue;
+        }
+      }
+
+      resolve(matches);
+    });
+
+    rg.on("error", (err) => {
+      reject(err);
+    });
+  });
+}
+
+async function searchFilesWithRegex(
+  pattern: string,
+  rootPath: string,
+  include?: string,
+): Promise<GrepMatch[]> {
+  const matches: GrepMatch[] = [];
+  const regex = new RegExp(pattern);
+
+  let includePattern: RegExp | undefined;
+  if (include) {
+    const regexPattern = globToRegex(include);
+    includePattern = new RegExp(regexPattern);
+  }
+
+  async function walkDir(dir: string) {
+    if (matches.length >= 200) return;
+
+    try {
+      const entries = await fs.readdir(dir, { withFileTypes: true });
+
+      for (const entry of entries) {
+        if (matches.length >= 200) break;
+
+        const fullPath = path.join(dir, entry.name);
+
+        if (entry.isDirectory()) {
+          // Skip hidden directories
+          if (entry.name.startsWith(".")) continue;
+          await walkDir(fullPath);
+        } else if (entry.isFile()) {
+          // Skip hidden files
+          if (entry.name.startsWith(".")) continue;
+
+          if (includePattern && !includePattern.test(fullPath)) {
+            continue;
+          }
+
+          try {
+            const content = await fs.readFile(fullPath, "utf-8");
+            const lines = content.split("\n");
+
+            for (let i = 0; i < lines.length; i++) {
+              if (regex.test(lines[i])) {
+                const stats = await fs.stat(fullPath);
+                matches.push({
+                  path: fullPath,
+                  modTime: stats.mtime.getTime(),
+                  lineNum: i + 1,
+                  lineText: lines[i],
+                });
+                break; // Only first match per file
+              }
+            }
+          } catch {
+            // Skip files we can't read
+            continue;
+          }
+        }
+      }
+    } catch {
+      // Skip directories we can't read
+      return;
+    }
+  }
+
+  await walkDir(rootPath);
+  return matches;
+}
+
+async function searchFiles(
+  pattern: string,
+  rootPath: string,
+  include?: string,
+  limit: number = 100,
+): Promise<{ matches: GrepMatch[]; truncated: boolean }> {
+  let matches: GrepMatch[];
+
+  try {
+    matches = await searchWithRipgrep(pattern, rootPath, include);
+  } catch {
+    matches = await searchFilesWithRegex(pattern, rootPath, include);
+  }
+
+  // Sort by modification time (newest first)
+  matches.sort((a, b) => b.modTime - a.modTime);
+
+  const truncated = matches.length > limit;
+  if (truncated) {
+    matches = matches.slice(0, limit);
+  }
+
+  return { matches, truncated };
+}
+
+export const grep = Tool.define({
+  name: "grep",
+  description: DESCRIPTION,
+  parameters: z.object({
+    pattern: z
+      .string()
+      .describe("The regex pattern to search for in file contents"),
+    path: z
+      .string()
+      .describe(
+        "The directory to search in. Defaults to the current working directory.",
+      )
+      .optional(),
+    include: z
+      .string()
+      .describe(
+        'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")',
+      )
+      .optional(),
+    literal_text: z
+      .boolean()
+      .describe(
+        "If true, the pattern will be treated as literal text with special regex characters escaped. Default is false.",
+      )
+      .optional(),
+  }),
+  async execute(params) {
+    if (!params.pattern) {
+      throw new Error("pattern is required");
+    }
+
+    const app = await App.use();
+    const searchPath = params.path || app.root;
+
+    // If literal_text is true, escape the pattern
+    const searchPattern = params.literal_text
+      ? escapeRegexPattern(params.pattern)
+      : params.pattern;
+
+    const { matches, truncated } = await searchFiles(
+      searchPattern,
+      searchPath,
+      params.include,
+      100,
+    );
+
+    let output: string;
+    if (matches.length === 0) {
+      output = "No files found";
+    } else {
+      const lines = [`Found ${matches.length} matches`];
+
+      let currentFile = "";
+      for (const match of matches) {
+        if (currentFile !== match.path) {
+          if (currentFile !== "") {
+            lines.push("");
+          }
+          currentFile = match.path;
+          lines.push(`${match.path}:`);
+        }
+        if (match.lineNum > 0) {
+          lines.push(`  Line ${match.lineNum}: ${match.lineText}`);
+        } else {
+          lines.push(`  ${match.path}`);
+        }
+      }
+
+      if (truncated) {
+        lines.push("");
+        lines.push(
+          "(Results are truncated. Consider using a more specific path or pattern.)",
+        );
+      }
+
+      output = lines.join("\n");
+    }
+
+    return {
+      metadata: {
+        matches: matches.length,
+        truncated,
+      },
+      output,
+    };
+  },
+});
+

+ 3 - 2
js/src/tool/index.ts

@@ -1,6 +1,7 @@
-export * from "./tool";
 export * from "./bash";
 export * from "./bash";
 export * from "./edit";
 export * from "./edit";
+export * from "./fetch";
 export * from "./glob";
 export * from "./glob";
+export * from "./grep";
 export * from "./view";
 export * from "./view";
-export * from "./ls";
+export * from "./ls";

+ 33 - 41
js/src/tool/ls.ts

@@ -65,9 +65,8 @@ export const ls = Tool.define({
       searchPath = path.join(app.root, searchPath);
       searchPath = path.join(app.root, searchPath);
     }
     }
 
 
-    try {
-      await fs.promises.stat(searchPath);
-    } catch (err) {
+    const stat = await fs.promises.stat(searchPath).catch(() => null);
+    if (!stat) {
       return {
       return {
         metadata: {},
         metadata: {},
         output: `Path does not exist: ${searchPath}`,
         output: `Path does not exist: ${searchPath}`,
@@ -88,7 +87,7 @@ export const ls = Tool.define({
 
 
     return {
     return {
       metadata: {
       metadata: {
-        numberOfFiles: files.length,
+        count: files.length,
         truncated,
         truncated,
       },
       },
       output,
       output,
@@ -110,40 +109,38 @@ async function listDirectory(
       return;
       return;
     }
     }
 
 
-    try {
-      const entries = await fs.promises.readdir(dir, { withFileTypes: true });
+    const entries = await fs.promises
+      .readdir(dir, { withFileTypes: true })
+      .catch(() => []);
 
 
-      for (const entry of entries) {
-        const fullPath = path.join(dir, entry.name);
+    for (const entry of entries) {
+      const fullPath = path.join(dir, entry.name);
 
 
-        if (shouldSkip(fullPath, ignorePatterns)) {
-          continue;
+      if (shouldSkip(fullPath, ignorePatterns)) {
+        continue;
+      }
+
+      if (entry.isDirectory()) {
+        if (fullPath !== initialPath) {
+          results.push(fullPath + path.sep);
         }
         }
 
 
-        if (entry.isDirectory()) {
-          if (fullPath !== initialPath) {
-            results.push(fullPath + path.sep);
-          }
-
-          if (results.length < limit) {
-            await walk(fullPath);
-          } else {
-            truncated = true;
-            return;
-          }
-        } else if (entry.isFile()) {
-          if (fullPath !== initialPath) {
-            results.push(fullPath);
-          }
-
-          if (results.length >= limit) {
-            truncated = true;
-            return;
-          }
+        if (results.length < limit) {
+          await walk(fullPath);
+        } else {
+          truncated = true;
+          return;
+        }
+      } else if (entry.isFile()) {
+        if (fullPath !== initialPath) {
+          results.push(fullPath);
+        }
+
+        if (results.length >= limit) {
+          truncated = true;
+          return;
         }
         }
       }
       }
-    } catch (err) {
-      // Skip directories we don't have permission to access
     }
     }
   }
   }
 
 
@@ -200,13 +197,9 @@ function shouldSkip(filePath: string, ignorePatterns: string[]): boolean {
   }
   }
 
 
   for (const pattern of ignorePatterns) {
   for (const pattern of ignorePatterns) {
-    try {
-      const glob = new Bun.Glob(pattern);
-      if (glob.match(base)) {
-        return true;
-      }
-    } catch (err) {
-      // Skip invalid patterns
+    const glob = new Bun.Glob(pattern);
+    if (glob.match(base)) {
+      return true;
     }
     }
   }
   }
 
 
@@ -272,7 +265,7 @@ function printTree(tree: TreeNode[], rootPath: string): string {
   let result = `- ${rootPath}${path.sep}\n`;
   let result = `- ${rootPath}${path.sep}\n`;
 
 
   for (const node of tree) {
   for (const node of tree) {
-    printNode(node, 1, result);
+    result = printNode(node, 1, result);
   }
   }
 
 
   return result;
   return result;
@@ -296,4 +289,3 @@ function printNode(node: TreeNode, level: number, result: string): string {
 
 
   return result;
   return result;
 }
 }
-

+ 66 - 0
js/test/tool/__snapshots__/tool.test.ts.snap

@@ -0,0 +1,66 @@
+// Bun Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`tool.ls basic 1`] = `
+"- /home/thdxr/dev/projects/sst/opencode/js/
+  - home/
+    - thdxr/
+      - dev/
+        - projects/
+          - sst/
+            - opencode/
+              - js/
+                - package.json
+                - schema.json
+                - tsconfig.json
+                - test/
+                  - tool/
+                    - tool.test.ts
+                - example/
+                  - ink.tsx
+                  - cli.ts
+                - scrap.ts
+                - README.md
+                - bun.lock
+                - src/
+                  - lsp/
+                    - client.ts
+                    - index.ts
+                    - language.ts
+                  - server/
+                    - server.ts
+                  - storage/
+                    - storage.ts
+                  - app/
+                    - index.ts
+                    - path.ts
+                    - config.ts
+                  - share/
+                    - share.ts
+                  - tool/
+                    - bash.ts
+                    - glob.ts
+                    - tool.ts
+                    - ls.ts
+                    - util/
+                      - file-times.ts
+                    - view.ts
+                    - index.ts
+                    - edit.ts
+                  - llm/
+                    - llm.ts
+                  - util/
+                    - context.ts
+                    - scrap.ts
+                    - event.ts
+                    - log.ts
+                  - index.ts
+                  - id/
+                    - id.ts
+                  - bus/
+                    - index.ts
+                  - session/
+                    - session.ts
+                    - prompt/
+                      - anthropic.txt
+"
+`;

+ 18 - 0
js/test/tool/tool.test.ts

@@ -1,6 +1,7 @@
 import { describe, expect, test } from "bun:test";
 import { describe, expect, test } from "bun:test";
 import { App } from "../../src/app";
 import { App } from "../../src/app";
 import { glob } from "../../src/tool/glob";
 import { glob } from "../../src/tool/glob";
+import { ls } from "../../src/tool/ls";
 
 
 describe("tool.glob", () => {
 describe("tool.glob", () => {
   test("truncate", async () => {
   test("truncate", async () => {
@@ -35,3 +36,20 @@ describe("tool.glob", () => {
     });
     });
   });
   });
 });
 });
+
+describe("tool.ls", () => {
+  test("basic", async () => {
+    const result = await App.provide({ directory: process.cwd() }, async () => {
+      return await ls.execute(
+        {
+          path: ".",
+        },
+        {
+          toolCallId: "test",
+          messages: [],
+        },
+      );
+    });
+    expect(result.output).toMatchSnapshot();
+  });
+});