diff --git a/apps/cli/bin/app.js b/apps/cli/bin/app.js index b865bc95..2d55efd4 100755 --- a/apps/cli/bin/app.js +++ b/apps/cli/bin/app.js @@ -1,7 +1,8 @@ #!/usr/bin/env node import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { app, modelConfig, updateState, importExample, listExamples, exportWorkflow } from '../dist/app.js'; +import { app, modelConfig, importExample, listExamples, exportWorkflow } from '../dist/app.js'; +import { runTui } from '../dist/tui/index.js'; yargs(hideBin(process.argv)) @@ -36,6 +37,20 @@ yargs(hideBin(process.argv)) }); } ) + .command( + "ui", + "Launch the interactive Rowboat dashboard", + (y) => y + .option("server-url", { + type: "string", + description: "Rowboat server base URL", + }), + (argv) => { + runTui({ + serverUrl: argv.serverUrl, + }); + } + ) .command( "import", "Import an example workflow (--example) or custom workflow from file (--file)", diff --git a/apps/cli/package-lock.json b/apps/cli/package-lock.json index 90c2818a..1660cd7a 100644 --- a/apps/cli/package-lock.json +++ b/apps/cli/package-lock.json @@ -14,17 +14,28 @@ "@ai-sdk/openai": "^2.0.53", "@ai-sdk/openai-compatible": "^1.0.27", "@ai-sdk/provider": "^2.0.0", + "@google-cloud/local-auth": "^3.0.1", "@hono/node-server": "^1.19.6", "@hono/standard-validator": "^0.1.5", "@modelcontextprotocol/sdk": "^1.20.2", "@openrouter/ai-sdk-provider": "^1.2.6", "ai": "^5.0.102", "awilix": "^12.0.5", + "eventsource-parser": "^1.1.2", + "google-auth-library": "^10.5.0", + "googleapis": "^169.0.0", "hono": "^4.10.7", "hono-openapi": "^1.1.1", + "ink": "^5.1.0", + "ink-select-input": "^6.2.0", + "ink-spinner": "^5.0.0", + "ink-text-input": "^6.0.0", "json-schema-to-zod": "^2.6.1", "nanoid": "^5.1.6", + "node-html-markdown": "^2.0.0", "ollama-ai-provider-v2": "^1.5.4", + "react": "^18.3.1", + "yaml": "^2.8.2", "yargs": "^18.0.0", "zod": "^4.1.12" }, @@ -33,6 +44,7 @@ }, "devDependencies": { "@types/node": "^24.9.1", + "@types/react": "^18.3.12", "typescript": "^5.9.3" } }, @@ -146,6 +158,132 @@ "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ai-sdk/provider-utils/node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", + "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/@google-cloud/local-auth": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/local-auth/-/local-auth-3.0.1.tgz", + "integrity": "sha512-YJ3GFbksfHyEarbVHPSCzhKpjbnlAhdzg2SEf79l6ODukrSM1qUOqfopY232Xkw26huKSndyzmJz+A6b2WYn7Q==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.1", + "google-auth-library": "^9.0.0", + "open": "^7.0.3", + "server-destroy": "^1.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/local-auth/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/local-auth/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/local-auth/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/local-auth/node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/local-auth/node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/local-auth/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@hono/node-server": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz", @@ -168,6 +306,63 @@ "hono": ">=3.9.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.1.tgz", @@ -205,6 +400,15 @@ } } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -274,6 +478,16 @@ "node": ">=8.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@standard-community/standard-json": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@standard-community/standard-json/-/standard-json-0.3.5.tgz", @@ -385,6 +599,24 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, "node_modules/@vercel/oidc": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz", @@ -407,6 +639,15 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ai": { "version": "5.0.106", "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.106.tgz", @@ -458,6 +699,21 @@ } } }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -482,6 +738,27 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/awilix": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/awilix/-/awilix-12.0.5.tgz", @@ -495,6 +772,41 @@ "node": ">=16.3.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/body-parser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", @@ -519,6 +831,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -531,6 +858,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -579,6 +912,89 @@ "tslib": "^2.0.3" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", @@ -593,6 +1009,36 @@ "node": ">=20" } }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -615,6 +1061,15 @@ "node": ">= 0.6" } }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -660,6 +1115,50 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -686,6 +1185,61 @@ "node": ">= 0.8" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -700,6 +1254,21 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -721,6 +1290,30 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -751,6 +1344,16 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", + "integrity": "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -766,6 +1369,15 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -788,6 +1400,15 @@ } }, "node_modules/eventsource-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz", + "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/eventsource/node_modules/eventsource-parser": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", @@ -854,6 +1475,12 @@ "express": ">= 4.11" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -901,6 +1528,44 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -934,6 +1599,46 @@ "url": "https://opencollective.com/express" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -961,6 +1666,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1019,6 +1753,26 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1031,6 +1785,62 @@ "node": ">= 6" } }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "169.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-169.0.0.tgz", + "integrity": "sha512-IOGMG8tljCZSLvYgdojRu6mB10KEsK0J7X62sXXlQz9koe5BUAW+rqkY3qhQM9wXM6hVL3/Hase7XbxoMyeYiQ==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.2.0", + "googleapis-common": "^8.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.1.tgz", + "integrity": "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^7.0.0-rc.4", + "google-auth-library": "^10.1.0", + "qs": "^6.7.0", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1043,6 +1853,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1067,6 +1890,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/hono": { "version": "4.10.7", "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.7.tgz", @@ -1118,6 +1950,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", @@ -1134,12 +1979,122 @@ "url": "https://opencollective.com/express" } }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ink": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", + "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "react": ">=18.0.0", + "react-devtools-core": "^4.19.1" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-select-input": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ink-select-input/-/ink-select-input-6.2.0.tgz", + "integrity": "sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ==", + "license": "MIT", + "dependencies": { + "figures": "^6.1.0", + "to-rotated": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ink-spinner": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ink-spinner/-/ink-spinner-5.0.0.tgz", + "integrity": "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA==", + "license": "MIT", + "dependencies": { + "cli-spinners": "^2.7.0" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "ink": ">=4.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ink-text-input": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz", + "integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5", + "react": ">=18" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1149,6 +2104,21 @@ "node": ">= 0.10" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1158,6 +2128,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1170,6 +2152,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ci": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", + "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1185,12 +2182,63 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -1200,6 +2248,21 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -1221,6 +2284,39 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -1230,6 +2326,12 @@ "tslib": "^2.0.3" } }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1307,6 +2409,39 @@ "url": "https://opencollective.com/express" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1350,6 +2485,78 @@ "tslib": "^2.0.3" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-html-markdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-html-markdown/-/node-html-markdown-2.0.0.tgz", + "integrity": "sha512-DqUC3GGP7pwSYxS93SwHoP+qCw78xcMP6C6H2DuC8rPD2AweJRjBzQb5SdXpKtDlqAQ7hVotJcfhgU7hU5Gthw==", + "license": "MIT", + "dependencies": { + "node-html-parser": "^6.1.13" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/node-html-parser": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1408,6 +2615,37 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openapi-types": { "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", @@ -1415,6 +2653,12 @@ "license": "MIT", "peer": true }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1434,6 +2678,15 @@ "tslib": "^2.0.3" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -1443,6 +2696,22 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -1563,6 +2832,34 @@ "node": ">= 0.10" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", + "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1572,6 +2869,22 @@ "node": ">=0.10.0" } }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -1582,6 +2895,21 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1621,12 +2949,41 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/send": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", @@ -1664,6 +3021,12 @@ "node": ">= 18" } }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "license": "ISC" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1763,6 +3126,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -1789,6 +3201,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -1804,6 +3267,28 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1816,6 +3301,18 @@ "node": ">=8.0" } }, + "node_modules/to-rotated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-rotated/-/to-rotated-1.0.0.tgz", + "integrity": "sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1825,12 +3322,30 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -1875,6 +3390,25 @@ "node": ">= 0.8" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1884,6 +3418,31 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1899,6 +3458,21 @@ "node": ">= 8" } }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -1916,12 +3490,116 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -1931,6 +3609,21 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", @@ -1957,6 +3650,12 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, "node_modules/zod": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", diff --git a/apps/cli/package.json b/apps/cli/package.json index 7b42ffb9..d254a82d 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -21,6 +21,7 @@ "description": "", "devDependencies": { "@types/node": "^24.9.1", + "@types/react": "^18.3.12", "typescript": "^5.9.3" }, "dependencies": { @@ -29,17 +30,28 @@ "@ai-sdk/openai": "^2.0.53", "@ai-sdk/openai-compatible": "^1.0.27", "@ai-sdk/provider": "^2.0.0", + "@google-cloud/local-auth": "^3.0.1", "@hono/node-server": "^1.19.6", "@hono/standard-validator": "^0.1.5", "@modelcontextprotocol/sdk": "^1.20.2", "@openrouter/ai-sdk-provider": "^1.2.6", "ai": "^5.0.102", "awilix": "^12.0.5", + "eventsource-parser": "^1.1.2", + "google-auth-library": "^10.5.0", + "googleapis": "^169.0.0", "hono": "^4.10.7", "hono-openapi": "^1.1.1", + "ink": "^5.1.0", + "ink-select-input": "^6.2.0", + "ink-spinner": "^5.0.0", + "ink-text-input": "^6.0.0", "json-schema-to-zod": "^2.6.1", "nanoid": "^5.1.6", + "node-html-markdown": "^2.0.0", "ollama-ai-provider-v2": "^1.5.4", + "react": "^18.3.1", + "yaml": "^2.8.2", "yargs": "^18.0.0", "zod": "^4.1.12" } diff --git a/apps/cli/src/agents/agents.ts b/apps/cli/src/agents/agents.ts index 3c74f109..2ebfaef8 100644 --- a/apps/cli/src/agents/agents.ts +++ b/apps/cli/src/agents/agents.ts @@ -29,7 +29,7 @@ export const Agent = z.object({ name: z.string(), provider: z.string().optional(), model: z.string().optional(), - description: z.string(), + description: z.string().optional(), instructions: z.string(), tools: z.record(z.string(), ToolAttachment).optional(), }); diff --git a/apps/cli/src/agents/repo.ts b/apps/cli/src/agents/repo.ts index 615a8afc..317beb0e 100644 --- a/apps/cli/src/agents/repo.ts +++ b/apps/cli/src/agents/repo.ts @@ -1,8 +1,12 @@ import { WorkDir } from "../config/config.js"; import fs from "fs/promises"; +import { glob } from "node:fs/promises"; import path from "path"; import z from "zod"; import { Agent } from "./agents.js"; +import { parse, stringify } from "yaml"; + +const UpdateAgentSchema = Agent.omit({ name: true }); export interface IAgentsRepo { list(): Promise[]>; @@ -13,33 +17,76 @@ export interface IAgentsRepo { } export class FSAgentsRepo implements IAgentsRepo { + private readonly agentsDir = path.join(WorkDir, "agents"); + async list(): Promise[]> { const result: z.infer[] = []; - // list all json files in workdir/agents/ - const agentsDir = path.join(WorkDir, "agents"); - const files = await fs.readdir(agentsDir); - for (const file of files) { - const contents = await fs.readFile(path.join(agentsDir, file), "utf8"); - result.push(Agent.parse(JSON.parse(contents))); + // list all md files in workdir/agents/ + const matches = await Array.fromAsync(glob("**/*.md", { cwd: this.agentsDir })); + for (const file of matches) { + try { + const agent = await this.parseAgentMd(path.join(this.agentsDir, file)); + result.push(agent); + } catch (error) { + console.error(`Error parsing agent ${file}: ${error instanceof Error ? error.message : String(error)}`); + continue; + } } return result; } + private async parseAgentMd(filePath: string): Promise> { + const raw = await fs.readFile(filePath, "utf8"); + + // strip the path prefix from the file name + // and the .md extension + const agentName = filePath + .replace(this.agentsDir + "/", "") + .replace(/\.md$/, ""); + let agent: z.infer = { + name: agentName, + instructions: raw, + }; + let content = raw; + + // check for frontmatter markers at start + if (raw.startsWith("---")) { + const end = raw.indexOf("\n---", 3); + + if (end !== -1) { + const fm = raw.slice(3, end).trim(); // YAML text + content = raw.slice(end + 4).trim(); // body after frontmatter + const yaml = parse(fm); + const parsed = Agent + .omit({ name: true, instructions: true }) + .parse(yaml); + agent = { + ...agent, + ...parsed, + instructions: content, + }; + } + } + + return agent; + } + async fetch(id: string): Promise> { - const contents = await fs.readFile(path.join(WorkDir, "agents", `${id}.json`), "utf8"); - return Agent.parse(JSON.parse(contents)); + return this.parseAgentMd(path.join(this.agentsDir, `${id}.md`)); } async create(agent: z.infer): Promise { - await fs.writeFile(path.join(WorkDir, "agents", `${agent.name}.json`), JSON.stringify(agent, null, 2)); + await fs.writeFile(path.join(this.agentsDir, `${agent.name}.md`), agent.instructions); } - - async update(id: string, agent: z.infer): Promise { - await fs.writeFile(path.join(WorkDir, "agents", `${id}.json`), JSON.stringify(agent, null, 2)); + + async update(id: string, agent: z.infer): Promise { + const { instructions, ...rest } = agent; + const contents = `---\n${stringify(rest)}\n---\n${instructions}`; + await fs.writeFile(path.join(this.agentsDir, `${id}.md`), contents); } async delete(id: string): Promise { - await fs.unlink(path.join(WorkDir, "agents", `${id}.json`)); + await fs.unlink(path.join(this.agentsDir, `${id}.md`)); } } \ No newline at end of file diff --git a/apps/cli/src/agents/runtime.ts b/apps/cli/src/agents/runtime.ts index b665730b..92c3fea7 100644 --- a/apps/cli/src/agents/runtime.ts +++ b/apps/cli/src/agents/runtime.ts @@ -21,6 +21,7 @@ import { IBus } from "../application/lib/bus.js"; import { IMessageQueue } from "../application/lib/message-queue.js"; import { IRunsRepo } from "../runs/repo.js"; import { IRunsLock } from "../runs/lock.js"; +import { PrefixLogger } from "../shared/prefix-logger.js"; export interface IAgentRuntime { trigger(runId: string): Promise; @@ -63,6 +64,11 @@ export class AgentRuntime implements IAgentRuntime { return; } try { + await this.bus.publish({ + runId, + type: "run-processing-start", + subflow: [], + }); while (true) { let eventCount = 0; const run = await this.runsRepo.fetch(runId); @@ -94,6 +100,11 @@ export class AgentRuntime implements IAgentRuntime { } } finally { await this.runsLock.release(runId); + await this.bus.publish({ + runId, + type: "run-processing-end", + subflow: [], + }); } } } @@ -499,6 +510,8 @@ export async function* streamAgent({ messageQueue: IMessageQueue; modelConfigRepo: IModelConfigRepo; }): AsyncGenerator, void, unknown> { + const logger = new PrefixLogger(`run-${runId}-${state.agentName}`); + async function* processEvent(event: z.infer): AsyncGenerator, void, unknown> { state.ingest(event); yield event; @@ -518,61 +531,29 @@ export async function* streamAgent({ // set up provider + model const provider = await getProvider(agent.provider); const model = provider.languageModel(agent.model || modelConfig.defaults.model); + let loopCounter = 0; - - console.log('here'); - - async function pendingMsgs() { - const pendingMsgs = []; - - } - while (true) { - // console.error(`loop counter: ${loopCounter++}`) - // if last response is from assistant and text, get any pending msgs - const lastMessage = state.messages[state.messages.length - 1]; - if (lastMessage - && lastMessage.role === "assistant" - && (typeof lastMessage.content === "string" - || !lastMessage.content.some(part => part.type === "tool-call") - ) - ) { - let pending = 0; - while(true) { - const msg = await messageQueue.dequeue(runId); - if (!msg) { - break; - } - pending++; - yield *processEvent({ - runId, - type: "message", - messageId: msg.messageId, - message: { - role: "user", - content: msg.message, - }, - subflow: [], - }); - } - // if no msgs found, return - if (!pending) { - return; - } - } + loopCounter++; + let loopLogger = logger.child(`iter-${loopCounter}`); + loopLogger.log('starting loop iteration'); // execute any pending tool calls for (const toolCallId of Object.keys(state.pendingToolCalls)) { const toolCall = state.toolCallIdMap[toolCallId]; + let _logger = loopLogger.child(`tc-${toolCallId}-${toolCall.toolName}`); + _logger.log('processing'); // if ask-human, skip if (toolCall.toolName === "ask-human") { + _logger.log('skipping, reason: ask-human'); continue; } // if tool has been denied, deny if (state.deniedToolCallIds[toolCallId]) { - yield *processEvent({ + _logger.log('returning denied tool message, reason: tool has been denied'); + yield* processEvent({ runId, messageId: await idGenerator.next(), type: "message", @@ -587,13 +568,15 @@ export async function* streamAgent({ continue; } - // if permission is pending on this tool call, allow execution + // if permission is pending on this tool call, skip execution if (state.pendingToolPermissionRequests[toolCallId]) { + _logger.log('skipping, reason: permission is pending'); continue; } // execute approved tool - yield *processEvent({ + _logger.log('executing tool'); + yield* processEvent({ runId, type: "tool-invocation", toolCallId, @@ -611,7 +594,7 @@ export async function* streamAgent({ messageQueue, modelConfigRepo, })) { - yield *processEvent({ + yield* processEvent({ ...event, subflow: [toolCallId, ...event.subflow], }); @@ -637,7 +620,7 @@ export async function* streamAgent({ result: result, subflow: [], }); - yield *processEvent({ + yield* processEvent({ runId, messageId: await idGenerator.next(), type: "message", @@ -647,26 +630,20 @@ export async function* streamAgent({ } } - // if pending state, exit + // if waiting on user permission or ask-human, exit if (state.getPendingAskHumans().length || state.getPendingPermissions().length) { - // console.error("pending asks or permissions, exiting (b.)") + loopLogger.log('exiting loop, reason: pending asks or permissions'); return; } - // if current message state isn't runnable, exit - /* - if (state.messages.length === 0 || state.messages[state.messages.length - 1].role === "assistant") { - // console.error("current message state isn't runnable, exiting (c.)") - return; - } - */ - - while(true) { + // get any queued user messages + while (true) { const msg = await messageQueue.dequeue(runId); if (!msg) { break; } - yield *processEvent({ + loopLogger.log('dequeued user message', msg.messageId); + yield* processEvent({ runId, type: "message", messageId: msg.messageId, @@ -678,7 +655,20 @@ export async function* streamAgent({ }); } + // if last response is from assistant and text, exit + const lastMessage = state.messages[state.messages.length - 1]; + if (lastMessage + && lastMessage.role === "assistant" + && (typeof lastMessage.content === "string" + || !lastMessage.content.some(part => part.type === "tool-call") + ) + ) { + loopLogger.log('exiting loop, reason: last message is from assistant and text'); + return; + } + // run one LLM turn. + loopLogger.log('running llm turn'); // stream agent response and build message const messageBuilder = new StreamStepMessageBuilder(); for await (const event of streamLlm( @@ -687,8 +677,9 @@ export async function* streamAgent({ agent.instructions, tools, )) { + loopLogger.log('got llm-stream-event:', event.type) messageBuilder.ingest(event); - yield *processEvent({ + yield* processEvent({ runId, type: "llm-stream-event", event: event, @@ -698,7 +689,7 @@ export async function* streamAgent({ // build and emit final message from agent response const message = messageBuilder.get(); - yield *processEvent({ + yield* processEvent({ runId, messageId: await idGenerator.next(), type: "message", @@ -712,7 +703,8 @@ export async function* streamAgent({ if (part.type === "tool-call") { const underlyingTool = agent.tools![part.toolName]; if (underlyingTool.type === "builtin" && underlyingTool.name === "ask-human") { - yield *processEvent({ + loopLogger.log('emitting ask-human-request, toolCallId:', part.toolCallId); + yield* processEvent({ runId, type: "ask-human-request", toolCallId: part.toolCallId, @@ -723,7 +715,8 @@ export async function* streamAgent({ if (underlyingTool.type === "builtin" && underlyingTool.name === "executeCommand") { // if command is blocked, then seek permission if (isBlocked(part.arguments.command)) { - yield *processEvent({ + loopLogger.log('emitting tool-permission-request, toolCallId:', part.toolCallId); + yield* processEvent({ runId, type: "tool-permission-request", toolCall: part, @@ -732,14 +725,15 @@ export async function* streamAgent({ } } if (underlyingTool.type === "agent" && underlyingTool.name) { - yield *processEvent({ + loopLogger.log('emitting spawn-subflow, toolCallId:', part.toolCallId); + yield* processEvent({ runId, type: "spawn-subflow", agentName: underlyingTool.name, toolCallId: part.toolCallId, subflow: [], }); - yield *processEvent({ + yield* processEvent({ runId, messageId: await idGenerator.next(), type: "message", diff --git a/apps/cli/src/app.ts b/apps/cli/src/app.ts index 205ee899..a2391f0e 100644 --- a/apps/cli/src/app.ts +++ b/apps/cli/src/app.ts @@ -9,8 +9,7 @@ import { RunEvent } from "./entities/run-events.js"; import { createInterface, Interface } from "node:readline/promises"; import { ToolCallPart } from "./entities/message.js"; import { Agent } from "./agents/agents.js"; -import { McpServerConfig } from "./mcp/mcp.js"; -import { McpServerDefinition } from "./mcp/mcp.js"; +import { McpServerConfig, McpServerDefinition } from "./mcp/schema.js"; import { Example } from "./entities/example.js"; import { z } from "zod"; import { Flavor } from "./models/models.js"; diff --git a/apps/cli/src/application/lib/builtin-tools.ts b/apps/cli/src/application/lib/builtin-tools.ts index b7839079..0bf11cdc 100644 --- a/apps/cli/src/application/lib/builtin-tools.ts +++ b/apps/cli/src/application/lib/builtin-tools.ts @@ -7,7 +7,7 @@ import { resolveSkill, availableSkills } from "../assistant/skills/index.js"; import { executeTool, listServers, listTools } from "../../mcp/mcp.js"; import container from "../../di/container.js"; import { IMcpConfigRepo } from "../..//mcp/repo.js"; -import { McpServerDefinition } from "../../mcp/mcp.js"; +import { McpServerDefinition } from "../../mcp/schema.js"; const BuiltinToolsSchema = z.record(z.string(), z.object({ description: z.string(), diff --git a/apps/cli/src/application/lib/bus.ts b/apps/cli/src/application/lib/bus.ts index d49c5d36..0987978e 100644 --- a/apps/cli/src/application/lib/bus.ts +++ b/apps/cli/src/application/lib/bus.ts @@ -13,7 +13,6 @@ export class InMemoryBus implements IBus { private subscribers: Map) => Promise)[]> = new Map(); async publish(event: z.infer): Promise { - console.log(this.subscribers); const pending: Promise[] = []; for (const subscriber of this.subscribers.get(event.runId) || []) { pending.push(subscriber(event)); @@ -21,7 +20,6 @@ export class InMemoryBus implements IBus { for (const subscriber of this.subscribers.get('*') || []) { pending.push(subscriber(event)); } - console.log(pending.length); await Promise.all(pending); } @@ -30,7 +28,6 @@ export class InMemoryBus implements IBus { this.subscribers.set(runId, []); } this.subscribers.get(runId)!.push(handler); - console.log(this.subscribers); return () => { this.subscribers.get(runId)!.splice(this.subscribers.get(runId)!.indexOf(handler), 1); }; diff --git a/apps/cli/src/entities/example.ts b/apps/cli/src/entities/example.ts index 92e2f3f7..779ffe75 100644 --- a/apps/cli/src/entities/example.ts +++ b/apps/cli/src/entities/example.ts @@ -1,6 +1,6 @@ import z from "zod" import { Agent } from "../agents/agents.js" -import { McpServerDefinition } from "../mcp/mcp.js"; +import { McpServerDefinition } from "../mcp/schema.js"; export const Example = z.object({ id: z.string(), @@ -9,4 +9,4 @@ export const Example = z.object({ entryAgent: z.string().optional(), agents: z.array(Agent).optional(), mcpServers: z.record(z.string(), McpServerDefinition).optional(), -}); \ No newline at end of file +}); diff --git a/apps/cli/src/entities/run-events.ts b/apps/cli/src/entities/run-events.ts index cfbd0e45..63180354 100644 --- a/apps/cli/src/entities/run-events.ts +++ b/apps/cli/src/entities/run-events.ts @@ -8,6 +8,14 @@ const BaseRunEvent = z.object({ subflow: z.array(z.string()), }); +export const RunProcessingStartEvent = BaseRunEvent.extend({ + type: z.literal("run-processing-start"), +}); + +export const RunProcessingEndEvent = BaseRunEvent.extend({ + type: z.literal("run-processing-end"), +}); + export const StartEvent = BaseRunEvent.extend({ type: z.literal("start"), agentName: z.string(), @@ -73,6 +81,8 @@ export const RunErrorEvent = BaseRunEvent.extend({ }); export const RunEvent = z.union([ + RunProcessingStartEvent, + RunProcessingEndEvent, StartEvent, SpawnSubFlowEvent, LlmStreamEvent, diff --git a/apps/cli/src/knowledge/sync_calendar.ts b/apps/cli/src/knowledge/sync_calendar.ts new file mode 100644 index 00000000..4168e2e4 --- /dev/null +++ b/apps/cli/src/knowledge/sync_calendar.ts @@ -0,0 +1,286 @@ +import fs from 'fs'; +import path from 'path'; +import { google } from 'googleapis'; +import { authenticate } from '@google-cloud/local-auth'; +import { OAuth2Client } from 'google-auth-library'; +import { NodeHtmlMarkdown } from 'node-html-markdown' + +// Configuration +const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json'); +const TOKEN_PATH = path.join(process.cwd(), 'token_calendar_notes.json'); // Changed to force re-auth with new scopes +const SYNC_INTERVAL_MS = 60 * 1000; +const SCOPES = [ + 'https://www.googleapis.com/auth/calendar.readonly', + 'https://www.googleapis.com/auth/drive.readonly' +]; + +const nhm = new NodeHtmlMarkdown(); + +// --- Auth Functions --- + +async function loadSavedCredentialsIfExist(): Promise { + try { + if (!fs.existsSync(TOKEN_PATH)) return null; + const tokenContent = fs.readFileSync(TOKEN_PATH, 'utf-8'); + const tokenData = JSON.parse(tokenContent); + + const credsContent = fs.readFileSync(CREDENTIALS_PATH, 'utf-8'); + const keys = JSON.parse(credsContent); + const key = keys.installed || keys.web; + + const client = new google.auth.OAuth2( + key.client_id, + key.client_secret, + key.redirect_uris ? key.redirect_uris[0] : 'http://localhost' + ); + + client.setCredentials({ + refresh_token: tokenData.refresh_token || tokenData.refreshToken, + access_token: tokenData.token || tokenData.access_token, + expiry_date: tokenData.expiry || tokenData.expiry_date, + scope: tokenData.scope + }); + + return client; + } catch (err) { + console.error("Error loading saved credentials:", err); + return null; + } +} + +async function saveCredentials(client: OAuth2Client) { + const content = fs.readFileSync(CREDENTIALS_PATH, 'utf-8'); + const keys = JSON.parse(content); + const key = keys.installed || keys.web; + const payload = JSON.stringify({ + type: 'authorized_user', + client_id: key.client_id, + client_secret: key.client_secret, + refresh_token: client.credentials.refresh_token, + access_token: client.credentials.access_token, + expiry_date: client.credentials.expiry_date, + }, null, 2); + fs.writeFileSync(TOKEN_PATH, payload); +} + +async function authorize(): Promise { + let client = await loadSavedCredentialsIfExist(); + if (client && client.credentials && client.credentials.expiry_date && client.credentials.expiry_date > Date.now()) { + console.log("Using existing valid token."); + return client; + } + + if (client && client.credentials && (!client.credentials.expiry_date || client.credentials.expiry_date <= Date.now()) && client.credentials.refresh_token) { + console.log("Refreshing expired token..."); + try { + await client.refreshAccessToken(); + await saveCredentials(client); + return client; + } catch (e) { + console.error("Failed to refresh token:", e); + if (fs.existsSync(TOKEN_PATH)) fs.unlinkSync(TOKEN_PATH); + } + } + + console.log("Performing new OAuth authentication..."); + client = await authenticate({ + scopes: SCOPES, + keyfilePath: CREDENTIALS_PATH, + }) as any; + if (client && client.credentials) { + await saveCredentials(client); + } + return client!; +} + +// --- Helper Functions --- + +function cleanFilename(name: string): string { + return name.replace(/[\\/*?:\"<>|]/g, "").replace(/\s+/g, "_").substring(0, 100).trim(); +} + +// --- Sync Logic --- + +function cleanUpOldFiles(currentEventIds: Set, syncDir: string) { + if (!fs.existsSync(syncDir)) return; + + const files = fs.readdirSync(syncDir); + for (const filename of files) { + if (filename === 'sync_state.json') continue; + + // We expect files like: + // {eventId}.json + // {eventId}_doc_{docId}.md + + let eventId: string | null = null; + + if (filename.endsWith('.json')) { + eventId = filename.replace('.json', ''); + } else if (filename.endsWith('.md')) { + // Try to extract eventId from prefix + // Assuming eventId doesn't contain underscores usually, but if it does, this split might be fragile. + // Google Calendar IDs are usually alphanumeric. + // Let's rely on the delimiter we use: "_doc_" + const parts = filename.split('_doc_'); + if (parts.length > 1) { + eventId = parts[0]; + } + } + + if (eventId && !currentEventIds.has(eventId)) { + try { + fs.unlinkSync(path.join(syncDir, filename)); + console.log(`Removed old/out-of-window file: ${filename}`); + } catch (e) { + console.error(`Error deleting file ${filename}:`, e); + } + } + } +} + +async function saveEvent(event: any, syncDir: string): Promise { + const eventId = event.id; + if (!eventId) return false; + + const filePath = path.join(syncDir, `${eventId}.json`); + + try { + fs.writeFileSync(filePath, JSON.stringify(event, null, 2)); + return true; + } catch (e) { + console.error(`Error saving event ${eventId}:`, e); + return false; + } +} + +async function processAttachments(drive: any, event: any, syncDir: string) { + if (!event.attachments || event.attachments.length === 0) return; + + const eventId = event.id; + const eventTitle = event.summary || 'Untitled'; + const eventDate = event.start?.dateTime || event.start?.date || 'Unknown'; + const organizer = event.organizer?.email || 'Unknown'; + + for (const att of event.attachments) { + // We only care about Google Docs + if (att.mimeType === 'application/vnd.google-apps.document') { + const fileId = att.fileId; + const safeTitle = cleanFilename(att.title); + // Unique filename linked to event + const filename = `${eventId}_doc_${safeTitle}.md`; + const filePath = path.join(syncDir, filename); + + // Simple cache check: if file exists, skip. + // Ideally we check modifiedTime, but that requires an extra API call per file. + // Given the loop interval, we can just check existence to save quota. + // If user updates notes, they might want them re-synced. + // For now, let's just check existence. To be smarter, we'd need a state file or check API. + if (fs.existsSync(filePath)) continue; + + try { + const res = await drive.files.export({ + fileId: fileId, + mimeType: 'text/html' + }); + + const html = res.data; + const md = nhm.translate(html); + + const frontmatter = [ + `# ${att.title}`, + `**Event:** ${eventTitle}`, + `**Date:** ${eventDate}`, + `**Organizer:** ${organizer}`, + `**Link:** ${att.fileUrl}`, + `---`, + `` + ].join('\n'); + + fs.writeFileSync(filePath, frontmatter + md); + console.log(`Synced Note: ${att.title} for event ${eventTitle}`); + } catch (e) { + console.error(`Failed to download note ${att.title}:`, e); + } + } + } +} + +async function syncCalendarWindow(auth: OAuth2Client, syncDir: string, lookbackDays: number) { + // Calculate window + const now = new Date(); + const lookbackMs = lookbackDays * 24 * 60 * 60 * 1000; + const twoWeeksForwardMs = 14 * 24 * 60 * 60 * 1000; + + const timeMin = new Date(now.getTime() - lookbackMs).toISOString(); + const timeMax = new Date(now.getTime() + twoWeeksForwardMs).toISOString(); + + console.log(`Syncing calendar from ${timeMin} to ${timeMax} (lookback: ${lookbackDays} days)...`); + + const calendar = google.calendar({ version: 'v3', auth }); + const drive = google.drive({ version: 'v3', auth }); + + try { + const res = await calendar.events.list({ + calendarId: 'primary', + timeMin: timeMin, + timeMax: timeMax, + singleEvents: true, + orderBy: 'startTime' + }); + + const events = res.data.items || []; + const currentEventIds = new Set(); + + if (events.length === 0) { + console.log("No events found in this window."); + } else { + console.log(`Found ${events.length} events.`); + for (const event of events) { + if (event.id) { + await saveEvent(event, syncDir); + await processAttachments(drive, event, syncDir); + currentEventIds.add(event.id); + } + } + } + + cleanUpOldFiles(currentEventIds, syncDir); + + } catch (error) { + console.error("An error occurred during calendar sync:", error); + } +} + +async function main() { + console.log("Starting Google Calendar & Notes Sync (TS)..."); + + const syncDirArg = process.argv[2]; + const lookbackDaysArg = process.argv[3]; + + const SYNC_DIR = syncDirArg || 'synced_calendar_events'; + const LOOKBACK_DAYS = lookbackDaysArg ? parseInt(lookbackDaysArg, 10) : 14; + + if (isNaN(LOOKBACK_DAYS) || LOOKBACK_DAYS <= 0) { + console.error("Error: Lookback days must be a positive number."); + process.exit(1); + } + + if (!fs.existsSync(SYNC_DIR)) { + fs.mkdirSync(SYNC_DIR, { recursive: true }); + } + + try { + const auth = await authorize(); + console.log("Authorization successful."); + + while (true) { + await syncCalendarWindow(auth, SYNC_DIR, LOOKBACK_DAYS); + console.log(`Sleeping for ${SYNC_INTERVAL_MS / 1000} seconds...`); + await new Promise(resolve => setTimeout(resolve, SYNC_INTERVAL_MS)); + } + } catch (error) { + console.error("Fatal error in main loop:", error); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/apps/cli/src/knowledge/sync_gmail.ts b/apps/cli/src/knowledge/sync_gmail.ts new file mode 100644 index 00000000..b795e619 --- /dev/null +++ b/apps/cli/src/knowledge/sync_gmail.ts @@ -0,0 +1,368 @@ +import fs from 'fs'; +import path from 'path'; +import { google } from 'googleapis'; +import { authenticate } from '@google-cloud/local-auth'; +import { NodeHtmlMarkdown } from 'node-html-markdown' +import { OAuth2Client } from 'google-auth-library'; + +// Configuration +const DEFAULT_SYNC_DIR = 'synced_emails_ts'; +const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json'); +const TOKEN_PATH = path.join(process.cwd(), 'token_api.json'); // Reuse Python's token +const SYNC_INTERVAL_MS = 60 * 1000; +const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']; + +const nhm = new NodeHtmlMarkdown(); + +// --- Auth Functions --- + +async function loadSavedCredentialsIfExist(): Promise { + try { + const tokenContent = fs.readFileSync(TOKEN_PATH, 'utf-8'); + const tokenData = JSON.parse(tokenContent); + + const credsContent = fs.readFileSync(CREDENTIALS_PATH, 'utf-8'); + const keys = JSON.parse(credsContent); + const key = keys.installed || keys.web; + + // Manually construct credentials for google.auth.fromJSON + const credentials = { + type: 'authorized_user', + client_id: key.client_id, + client_secret: key.client_secret, + refresh_token: tokenData.refresh_token || tokenData.refreshToken, // Handle both cases + access_token: tokenData.token || tokenData.access_token, // Handle both cases + expiry_date: tokenData.expiry || tokenData.expiry_date + }; + return google.auth.fromJSON(credentials) as OAuth2Client; + } catch (err) { + console.error("Error loading saved credentials:", err); + return null; + } +} + +async function saveCredentials(client: OAuth2Client) { + const content = fs.readFileSync(CREDENTIALS_PATH, 'utf-8'); + const keys = JSON.parse(content); + const key = keys.installed || keys.web; + const payload = JSON.stringify({ + type: 'authorized_user', + client_id: key.client_id, + client_secret: key.client_secret, + refresh_token: client.credentials.refresh_token, + access_token: client.credentials.access_token, + expiry_date: client.credentials.expiry_date, + }, null, 2); + fs.writeFileSync(TOKEN_PATH, payload); +} + +async function authorize(): Promise { + let client = await loadSavedCredentialsIfExist(); + if (client && client.credentials && client.credentials.expiry_date && client.credentials.expiry_date > Date.now()) { + console.log("Using existing valid token."); + return client; + } + + if (client && client.credentials && (!client.credentials.expiry_date || client.credentials.expiry_date <= Date.now()) && client.credentials.refresh_token) { + console.log("Refreshing expired token..."); + try { + await client.refreshAccessToken(); + await saveCredentials(client); // Save refreshed token + return client; + } catch (e) { + console.error("Failed to refresh token:", e); + // Fall through to full re-auth if refresh fails + fs.existsSync(TOKEN_PATH) && fs.unlinkSync(TOKEN_PATH); + } + } + + console.log("Performing new OAuth authentication..."); + client = await authenticate({ + scopes: SCOPES, + keyfilePath: CREDENTIALS_PATH, + }) as any; + if (client && client.credentials) { + await saveCredentials(client); + } + return client!; +} + +// --- Helper Functions --- + +function cleanFilename(name: string): string { + return name.replace(/[\\/*?:":<>|]/g, "").substring(0, 100).trim(); +} + +function decodeBase64(data: string): string { + return Buffer.from(data, 'base64').toString('utf-8'); +} + +function getBody(payload: any): string { + let body = ""; + if (payload.parts) { + for (const part of payload.parts) { + if (part.mimeType === 'text/plain' && part.body && part.body.data) { + const text = decodeBase64(part.body.data); + // Strip quoted lines + const cleanLines = text.split('\n').filter((line: string) => !line.trim().startsWith('>')); + body += cleanLines.join('\n'); + } else if (part.mimeType === 'text/html' && part.body && part.body.data) { + const html = decodeBase64(part.body.data); + let md = nhm.translate(html); + // Simple quote stripping for MD + const cleanLines = md.split('\n').filter((line: string) => !line.trim().startsWith('>')); + body += cleanLines.join('\n'); + } else if (part.parts) { + body += getBody(part); + } + } + } else if (payload.body && payload.body.data) { + const data = decodeBase64(payload.body.data); + if (payload.mimeType === 'text/html') { + let md = nhm.translate(data); + body += md.split('\n').filter((line: string) => !line.trim().startsWith('>')).join('\n'); + } else { + body += data.split('\n').filter((line: string) => !line.trim().startsWith('>')).join('\n'); + } + } + return body; +} + +async function saveAttachment(gmail: any, userId: string, msgId: string, part: any, attachmentsDir: string): Promise { + const filename = part.filename; + const attId = part.body?.attachmentId; + if (!filename || !attId) return null; + + const safeName = `${msgId}_${cleanFilename(filename)}`; + const filePath = path.join(attachmentsDir, safeName); + + if (fs.existsSync(filePath)) return safeName; + + try { + const res = await gmail.users.messages.attachments.get({ + userId, + messageId: msgId, + id: attId + }); + + const data = res.data.data; + if (data) { + fs.writeFileSync(filePath, Buffer.from(data, 'base64')); + console.log(`Saved attachment: ${safeName}`); + return safeName; + } + } catch (e) { + console.error(`Error saving attachment ${filename}:`, e); + } + return null; +} + +// --- Sync Logic --- + +async function processThread(auth: OAuth2Client, threadId: string, syncDir: string, attachmentsDir: string) { + const gmail = google.gmail({ version: 'v1', auth }); + try { + const res = await gmail.users.threads.get({ userId: 'me', id: threadId }); + const thread = res.data; + const messages = thread.messages; + + if (!messages || messages.length === 0) return; + + // Subject from first message + const firstHeader = messages[0].payload?.headers; + const subject = firstHeader?.find(h => h.name === 'Subject')?.value || '(No Subject)'; + + let mdContent = `# ${subject}\n\n`; + mdContent += `**Thread ID:** ${threadId}\n`; + mdContent += `**Message Count:** ${messages.length}\n\n---\n\n`; + + for (const msg of messages) { + const msgId = msg.id!; + const headers = msg.payload?.headers || []; + const from = headers.find(h => h.name === 'From')?.value || 'Unknown'; + const date = headers.find(h => h.name === 'Date')?.value || 'Unknown'; + + mdContent += `### From: ${from}\n`; + mdContent += `**Date:** ${date}\n\n`; + + const body = getBody(msg.payload); + mdContent += `${body}\n\n`; + + // Attachments + const parts: any[] = []; + const traverseParts = (pList: any[]) => { + for (const p of pList) { + parts.push(p); + if (p.parts) traverseParts(p.parts); + } + }; + if (msg.payload?.parts) traverseParts(msg.payload.parts); + + let attachmentsFound = false; + for (const part of parts) { + if (part.filename && part.body?.attachmentId) { + const savedName = await saveAttachment(gmail, 'me', msgId, part, attachmentsDir); + if (savedName) { + if (!attachmentsFound) { + mdContent += "**Attachments:**\n"; + attachmentsFound = true; + } + mdContent += `- [${part.filename}](attachments/${savedName})\n`; + } + } + } + mdContent += "\n---\n\n"; + } + + fs.writeFileSync(path.join(syncDir, `${threadId}.md`), mdContent); + console.log(`Synced Thread: ${subject} (${threadId})`); + + } catch (error) { + console.error(`Error processing thread ${threadId}:`, error); + } +} + +function loadState(stateFile: string): { historyId?: string } { + if (fs.existsSync(stateFile)) { + return JSON.parse(fs.readFileSync(stateFile, 'utf-8')); + } + return {}; +} + +function saveState(historyId: string, stateFile: string) { + fs.writeFileSync(stateFile, JSON.stringify({ + historyId, + last_sync: new Date().toISOString() + }, null, 2)); +} + +async function fullSync(auth: OAuth2Client, syncDir: string, attachmentsDir: string, stateFile: string, lookbackDays: number) { + console.log(`Performing full sync of last ${lookbackDays} days...`); + const gmail = google.gmail({ version: 'v1', auth }); + + const pastDate = new Date(); + pastDate.setDate(pastDate.getDate() - lookbackDays); + const dateQuery = pastDate.toISOString().split('T')[0].replace(/-/g, '/'); + + // Get History ID + const profile = await gmail.users.getProfile({ userId: 'me' }); + const currentHistoryId = profile.data.historyId!; + + let pageToken: string | undefined; + do { + const res: any = await gmail.users.threads.list({ + userId: 'me', + q: `after:${dateQuery}`, + pageToken + }); + + const threads = res.data.threads; + if (threads) { + for (const thread of threads) { + await processThread(auth, thread.id!, syncDir, attachmentsDir); + } + } + pageToken = res.data.nextPageToken; + } while (pageToken); + + saveState(currentHistoryId, stateFile); + console.log("Full sync complete."); +} + +async function partialSync(auth: OAuth2Client, startHistoryId: string, syncDir: string, attachmentsDir: string, stateFile: string, lookbackDays: number) { + console.log(`Checking updates since historyId ${startHistoryId}...`); + const gmail = google.gmail({ version: 'v1', auth }); + + try { + const res = await gmail.users.history.list({ + userId: 'me', + startHistoryId, + historyTypes: ['messageAdded'] + }); + + const changes = res.data.history; + if (!changes || changes.length === 0) { + console.log("No new changes."); + const profile = await gmail.users.getProfile({ userId: 'me' }); + saveState(profile.data.historyId!, stateFile); + return; + } + + console.log(`Found ${changes.length} history records.`); + const threadIds = new Set(); + + for (const record of changes) { + if (record.messagesAdded) { + for (const item of record.messagesAdded) { + if (item.message?.threadId) { + threadIds.add(item.message.threadId); + } + } + } + } + + for (const tid of threadIds) { + await processThread(auth, tid, syncDir, attachmentsDir); + } + + const profile = await gmail.users.getProfile({ userId: 'me' }); + saveState(profile.data.historyId!, stateFile); + + } catch (error: any) { + if (error.response?.status === 404) { + console.log("History ID expired. Falling back to full sync."); + await fullSync(auth, syncDir, attachmentsDir, stateFile, lookbackDays); + } else { + console.error("Error during partial sync:", error); + // If 401, remove token to force re-auth next run + if (error.response?.status === 401 && fs.existsSync(TOKEN_PATH)) { + console.log("401 Unauthorized. Deleting token to force re-authentication."); + fs.unlinkSync(TOKEN_PATH); + } + } + } +} + +async function main() { + console.log("Starting Gmail Sync (TS)..."); + const syncDirArg = process.argv[2]; + const lookbackDaysArg = process.argv[3]; + + const SYNC_DIR = syncDirArg || DEFAULT_SYNC_DIR; + const LOOKBACK_DAYS = lookbackDaysArg ? parseInt(lookbackDaysArg, 10) : 7; // Default to 7 days + + if (isNaN(LOOKBACK_DAYS) || LOOKBACK_DAYS <= 0) { + console.error("Error: Lookback days must be a positive number."); + process.exit(1); + } + + const ATTACHMENTS_DIR = path.join(SYNC_DIR, 'attachments'); + const STATE_FILE = path.join(SYNC_DIR, 'sync_state.json'); + + // Ensure directories exist + if (!fs.existsSync(SYNC_DIR)) fs.mkdirSync(SYNC_DIR, { recursive: true }); + if (!fs.existsSync(ATTACHMENTS_DIR)) fs.mkdirSync(ATTACHMENTS_DIR, { recursive: true }); + + try { + const auth = await authorize(); + console.log("Authorization successful."); + + while (true) { + const state = loadState(STATE_FILE); + if (!state.historyId) { + console.log("No history ID found, starting full sync..."); + await fullSync(auth, SYNC_DIR, ATTACHMENTS_DIR, STATE_FILE, LOOKBACK_DAYS); + } else { + console.log("History ID found, starting partial sync..."); + await partialSync(auth, state.historyId, SYNC_DIR, ATTACHMENTS_DIR, STATE_FILE, LOOKBACK_DAYS); + } + + console.log(`Sleeping for ${SYNC_INTERVAL_MS / 1000} seconds...`); + await new Promise(resolve => setTimeout(resolve, SYNC_INTERVAL_MS)); + } + } catch (error) { + console.error("Fatal error in main loop:", error); + } +} + +main().catch(console.error); diff --git a/apps/cli/src/mcp/mcp.ts b/apps/cli/src/mcp/mcp.ts index 6e38bd98..7131de12 100644 --- a/apps/cli/src/mcp/mcp.ts +++ b/apps/cli/src/mcp/mcp.ts @@ -6,63 +6,12 @@ import z from "zod"; import { IMcpConfigRepo } from "./repo.js"; import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; - -export const StdioMcpServerConfig = z.object({ - type: z.literal("stdio").optional(), - command: z.string(), - args: z.array(z.string()).optional(), - env: z.record(z.string(), z.string()).optional(), -}); - -export const HttpMcpServerConfig = z.object({ - type: z.literal("http").optional(), - url: z.string(), - headers: z.record(z.string(), z.string()).optional(), -}); - -export const McpServerDefinition = z.union([StdioMcpServerConfig, HttpMcpServerConfig]); - -export const McpServerConfig = z.object({ - mcpServers: z.record(z.string(), McpServerDefinition), -}); - -const connectionState = z.enum(["disconnected", "connected", "error"]); - -export const McpServerList = z.object({ - mcpServers: z.record(z.string(), z.object({ - config: McpServerDefinition, - state: connectionState, - error: z.string().nullable(), - })), -}); - -/* - inputSchema: { - [x: string]: unknown; - type: "object"; - properties?: Record | undefined; - required?: string[] | undefined; - }; -*/ -export const Tool = z.object({ - name: z.string(), - description: z.string().optional(), - inputSchema: z.object({ - type: z.literal("object"), - properties: z.record(z.string(), z.any()).optional(), - required: z.array(z.string()).optional(), - }), - outputSchema: z.object({ - type: z.literal("object"), - properties: z.record(z.string(), z.any()).optional(), - required: z.array(z.string()).optional(), - }).optional(), -}) - -export const ListToolsResponse = z.object({ - tools: z.array(Tool), - nextCursor: z.string().optional(), -}); +import { + connectionState, + ListToolsResponse, + McpServerDefinition, + McpServerList, +} from "./schema.js"; type mcpState = { state: z.infer, @@ -171,4 +120,4 @@ export async function executeTool(serverName: string, toolName: string, input: a arguments: input, }); return result; -} \ No newline at end of file +} diff --git a/apps/cli/src/mcp/repo.ts b/apps/cli/src/mcp/repo.ts index d43569af..fbb4106e 100644 --- a/apps/cli/src/mcp/repo.ts +++ b/apps/cli/src/mcp/repo.ts @@ -1,6 +1,5 @@ import { WorkDir } from "../config/config.js"; -import { McpServerConfig } from "./mcp.js"; -import { McpServerDefinition } from "./mcp.js"; +import { McpServerConfig, McpServerDefinition } from "./schema.js"; import fs from "fs/promises"; import path from "path"; import z from "zod"; @@ -42,4 +41,4 @@ export class FSMcpConfigRepo implements IMcpConfigRepo { delete conf.mcpServers[serverName]; await fs.writeFile(this.configPath, JSON.stringify(conf, null, 2)); } -} \ No newline at end of file +} diff --git a/apps/cli/src/mcp/schema.ts b/apps/cli/src/mcp/schema.ts new file mode 100644 index 00000000..2637397f --- /dev/null +++ b/apps/cli/src/mcp/schema.ts @@ -0,0 +1,50 @@ +import z from "zod"; + +export const StdioMcpServerConfig = z.object({ + type: z.literal("stdio").optional(), + command: z.string(), + args: z.array(z.string()).optional(), + env: z.record(z.string(), z.string()).optional(), +}); + +export const HttpMcpServerConfig = z.object({ + type: z.literal("http").optional(), + url: z.string(), + headers: z.record(z.string(), z.string()).optional(), +}); + +export const McpServerDefinition = z.union([StdioMcpServerConfig, HttpMcpServerConfig]); + +export const McpServerConfig = z.object({ + mcpServers: z.record(z.string(), McpServerDefinition), +}); + +export const connectionState = z.enum(["disconnected", "connected", "error"]); + +export const McpServerList = z.object({ + mcpServers: z.record(z.string(), z.object({ + config: McpServerDefinition, + state: connectionState, + error: z.string().nullable(), + })), +}); + +export const Tool = z.object({ + name: z.string(), + description: z.string().optional(), + inputSchema: z.object({ + type: z.literal("object"), + properties: z.record(z.string(), z.any()).optional(), + required: z.array(z.string()).optional(), + }), + outputSchema: z.object({ + type: z.literal("object"), + properties: z.record(z.string(), z.any()).optional(), + required: z.array(z.string()).optional(), + }).optional(), +}); + +export const ListToolsResponse = z.object({ + tools: z.array(Tool), + nextCursor: z.string().optional(), +}); diff --git a/apps/cli/src/server.ts b/apps/cli/src/server.ts index fde66419..be281d27 100644 --- a/apps/cli/src/server.ts +++ b/apps/cli/src/server.ts @@ -4,8 +4,8 @@ import { streamSSE } from 'hono/streaming' import { describeRoute, validator, resolver, openAPIRouteHandler } from "hono-openapi" import z from 'zod'; import container from './di/container.js'; -import { executeTool, listServers, listTools, ListToolsResponse, McpServerList } from "./mcp/mcp.js"; -import { McpServerDefinition } from "./mcp/mcp.js"; +import { executeTool, listServers, listTools } from "./mcp/mcp.js"; +import { ListToolsResponse, McpServerDefinition, McpServerList } from "./mcp/schema.js"; import { IMcpConfigRepo } from './mcp/repo.js'; import { IModelConfigRepo } from './models/repo.js'; import { ModelConfig, Provider } from "./models/models.js"; @@ -14,6 +14,7 @@ import { Agent } from "./agents/agents.js"; import { AskHumanResponsePayload, authorizePermission, createMessage, createRun, replyToHumanInputRequest, Run, stop, ToolPermissionAuthorizePayload } from './runs/runs.js'; import { IRunsRepo, CreateRunOptions, ListRunsResponse } from './runs/repo.js'; import { IBus } from './application/lib/bus.js'; +import { cors } from 'hono/cors'; let id = 0; @@ -620,7 +621,6 @@ const routes = new Hono() unsub = await bus.subscribe('*', async (event) => { if (aborted) return; - console.log('got ev', event); await stream.writeSSE({ data: JSON.stringify(event), event: "message", @@ -638,6 +638,7 @@ const routes = new Hono() ; const app = new Hono() + .use("/*", cors()) .route("/", routes) .get( "/openapi.json", @@ -665,4 +666,4 @@ serve({ // PUT /skills/ // DELETE /skills/ -// GET /sse \ No newline at end of file +// GET /sse diff --git a/apps/cli/src/shared/prefix-logger.ts b/apps/cli/src/shared/prefix-logger.ts new file mode 100644 index 00000000..8d199b56 --- /dev/null +++ b/apps/cli/src/shared/prefix-logger.ts @@ -0,0 +1,26 @@ +// create a PrefixLogger class that wraps console.log with a prefix +// and allows chaining with a parent logger +export class PrefixLogger { + private prefix: string; + private parent: PrefixLogger | null; + + constructor(prefix: string, parent: PrefixLogger | null = null) { + this.prefix = prefix; + this.parent = parent; + } + + log(...args: any[]) { + const timestamp = new Date().toISOString(); + const prefix = '[' + this.prefix + ']'; + + if (this.parent) { + this.parent.log(prefix, ...args); + } else { + console.log(timestamp, prefix, ...args); + } + } + + child(childPrefix: string): PrefixLogger { + return new PrefixLogger(childPrefix, this); + } +} \ No newline at end of file diff --git a/apps/cli/src/tui/api.ts b/apps/cli/src/tui/api.ts new file mode 100644 index 00000000..b54534ac --- /dev/null +++ b/apps/cli/src/tui/api.ts @@ -0,0 +1,190 @@ +import { createParser } from "eventsource-parser"; +import { Agent } from "../agents/agents.js"; +import { AskHumanResponsePayload, Run, ToolPermissionAuthorizePayload } from "../runs/runs.js"; +import { ListRunsResponse } from "../runs/repo.js"; +import { ModelConfig } from "../models/models.js"; +import { RunEvent } from "../entities/run-events.js"; +import z from "zod"; + +const HealthSchema = z.object({ + status: z.literal("ok"), +}); + +const MessageResponse = z.object({ + messageId: z.string(), +}); + +const SuccessSchema = z.object({ + success: z.literal(true), +}); + +type RunEventType = z.infer; + +export interface RowboatApiOptions { + baseUrl?: string; +} + +export class RowboatApi { + readonly baseUrl: string; + constructor({ baseUrl }: RowboatApiOptions = {}) { + this.baseUrl = baseUrl ?? process.env.ROWBOATX_SERVER_URL ?? "http://127.0.0.1:3000"; + } + + private buildUrl(pathname: string): string { + return new URL(pathname, this.baseUrl).toString(); + } + + private async request(pathname: string, init?: RequestInit): Promise { + const headers: Record = { + Accept: "application/json", + }; + if (init?.headers instanceof Headers) { + init.headers.forEach((value, key) => { + headers[key] = value; + }); + } else if (Array.isArray(init?.headers)) { + for (const [key, value] of init.headers) { + headers[key] = value; + } + } else if (init?.headers) { + Object.assign(headers, init.headers as Record); + } + if (init?.body && !headers["Content-Type"]) { + headers["Content-Type"] = "application/json"; + } + const response = await fetch(this.buildUrl(pathname), { + method: "GET", + ...init, + headers, + }); + if (!response.ok) { + const text = await response.text().catch(() => ""); + throw new Error(`Request to ${pathname} failed (${response.status}): ${text || response.statusText}`); + } + if (response.status === 204) { + return undefined as T; + } + const text = await response.text(); + if (!text) { + return undefined as T; + } + return JSON.parse(text) as T; + } + + async getHealth(): Promise> { + const payload = await this.request("/health"); + return HealthSchema.parse(payload); + } + + async getModelConfig(): Promise> { + const payload = await this.request("/models"); + return ModelConfig.parse(payload); + } + + async listAgents(): Promise[]> { + const payload = await this.request("/agents"); + return Agent.array().parse(payload); + } + + async listRuns(cursor?: string): Promise> { + const searchParams = new URLSearchParams(); + if (cursor) { + searchParams.set("cursor", cursor); + } + const payload = await this.request(`/runs${searchParams.size ? `?${searchParams.toString()}` : ""}`); + return ListRunsResponse.parse(payload); + } + + async getRun(runId: string): Promise> { + const payload = await this.request(`/runs/${encodeURIComponent(runId)}`); + return Run.parse(payload); + } + + async createRun(agentId: string): Promise> { + const payload = await this.request("/runs/new", { + method: "POST", + body: JSON.stringify({ agentId }), + }); + return Run.parse(payload); + } + + async sendMessage(runId: string, message: string): Promise> { + const payload = await this.request(`/runs/${encodeURIComponent(runId)}/messages/new`, { + method: "POST", + body: JSON.stringify({ message }), + }); + return MessageResponse.parse(payload); + } + + async authorizeTool(runId: string, payload: z.infer): Promise { + const response = await this.request(`/runs/${encodeURIComponent(runId)}/permissions/authorize`, { + method: "POST", + body: JSON.stringify(payload), + }); + SuccessSchema.parse(response); + } + + async replyToHuman(runId: string, requestId: string, payload: z.infer): Promise { + const response = await this.request(`/runs/${encodeURIComponent(runId)}/human-input-requests/${encodeURIComponent(requestId)}/reply`, { + method: "POST", + body: JSON.stringify(payload), + }); + SuccessSchema.parse(response); + } + + async stopRun(runId: string): Promise { + const response = await this.request(`/runs/${encodeURIComponent(runId)}/stop`, { + method: "POST", + }); + SuccessSchema.parse(response); + } + + async subscribeToEvents(onEvent: (event: RunEventType) => void, onError?: (error: Error) => void): Promise<() => void> { + const controller = new AbortController(); + const response = await fetch(this.buildUrl("/stream"), { + method: "GET", + headers: { + Accept: "text/event-stream", + }, + signal: controller.signal, + }); + if (!response.ok || !response.body) { + throw new Error(`Failed to subscribe to event stream (${response.status})`); + } + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + const parser = createParser((event) => { + if (event.type !== "event" || !event.data) { + return; + } + try { + const parsed = RunEvent.parse(JSON.parse(event.data)); + onEvent(parsed); + } catch (error) { + onError?.(error instanceof Error ? error : new Error(String(error))); + } + }); + + (async () => { + try { + while (true) { + const { value, done } = await reader.read(); + if (done) { + break; + } + parser.feed(decoder.decode(value, { stream: true })); + } + } catch (error) { + if (controller.signal.aborted) { + return; + } + onError?.(error instanceof Error ? error : new Error(String(error))); + } + })(); + + return () => { + controller.abort(); + reader.cancel().catch(() => undefined); + }; + } +} diff --git a/apps/cli/src/tui/index.tsx b/apps/cli/src/tui/index.tsx new file mode 100644 index 00000000..7e3dd3c0 --- /dev/null +++ b/apps/cli/src/tui/index.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { render } from "ink"; +import { RowboatTui } from "./ui.js"; + +export function runTui({ serverUrl }: { serverUrl?: string }) { + const baseUrl = serverUrl ?? process.env.ROWBOATX_SERVER_URL ?? "http://127.0.0.1:3000"; + render(); +} diff --git a/apps/cli/src/tui/ui.tsx b/apps/cli/src/tui/ui.tsx new file mode 100644 index 00000000..b11bad5e --- /dev/null +++ b/apps/cli/src/tui/ui.tsx @@ -0,0 +1,1174 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { Box, Text, useApp, useInput, useStdout } from "ink"; +import Spinner from "ink-spinner"; +import SelectInput from "ink-select-input"; +import TextInput from "ink-text-input"; +import z from "zod"; +import { RowboatApi } from "./api.js"; +import { ModelConfig } from "../models/models.js"; +import { Agent } from "../agents/agents.js"; +import { ListRunsResponse } from "../runs/repo.js"; +import { Run } from "../runs/runs.js"; +import { RunEvent } from "../entities/run-events.js"; + +type AgentType = z.infer; +type ModelConfigType = z.infer; +type RunSummary = z.infer["runs"][number]; +type RunType = z.infer; +type RunEventType = z.infer; + +type Toast = { + type: "info" | "error" | "success"; + text: string; +}; + +type ChatLine = { + text: string; + color?: string; + variant?: "user" | "assistant" | "streaming" | "thinking" | "system" | "tool" | "other"; +}; + +type ModalState = + | { type: "agent-picker" } + | { + type: "human-response"; + runId: string; + requestId: string; + subflow: string[]; + prompt: string; + value: string; + submitting: boolean; + }; + +type ConnectionState = "connecting" | "ready" | "error"; +type FocusTarget = "chat" | "sidebar"; + +type PendingPermission = { + toolCallId: string; + toolName: string; + args: unknown; + subflow: string[]; +}; + +type PendingHuman = { + toolCallId: string; + query: string; + subflow: string[]; +}; + +type SidebarItem = + | { kind: "action"; action: "new-copilot" | "new-agent"; label: string; hint?: string } + | { kind: "run"; run: RunSummary; status: { label: string; color: string } }; + +export function RowboatTui({ serverUrl }: { serverUrl: string }) { + const api = useMemo(() => new RowboatApi({ baseUrl: serverUrl }), [serverUrl]); + const { exit } = useApp(); + const { stdout } = useStdout(); + + const [connectionState, setConnectionState] = useState("connecting"); + const [connectionError, setConnectionError] = useState(null); + const [modelConfig, setModelConfig] = useState(null); + const [agents, setAgents] = useState([]); + const [runs, setRuns] = useState([]); + const [runsCursor, setRunsCursor] = useState(); + const [runsLoading, setRunsLoading] = useState(false); + const [runDetails, setRunDetails] = useState>({}); + const [activeRunId, setActiveRunId] = useState(null); + const [draftAgent, setDraftAgent] = useState("copilot"); + const [composerValue, setComposerValue] = useState(""); + const [composerBusy, setComposerBusy] = useState(false); + const [focusTarget, setFocusTarget] = useState("chat"); + const [sidebarIndex, setSidebarIndex] = useState(0); + const [toast, setToast] = useState(null); + const [modal, setModal] = useState(null); + const [streamError, setStreamError] = useState(null); + const [eventStreamActive, setEventStreamActive] = useState(false); + const [chatScrollOffset, setChatScrollOffset] = useState(0); + + const selectedRun = activeRunId ? runDetails[activeRunId] : undefined; + const pendingPermissions = useMemo(() => derivePendingPermissions(selectedRun), [selectedRun]); + const pendingHuman = useMemo(() => derivePendingHuman(selectedRun), [selectedRun]); + + const defaultCopilot = useMemo(() => { + return "copilot"; + }, [agents]); + + useEffect(() => { + if (!agents.length) { + return; + } + setDraftAgent((prev) => prev || defaultCopilot); + }, [agents, defaultCopilot]); + + const runStatusMap = useMemo(() => { + const map: Record = {}; + for (const summary of runs) { + map[summary.id] = getRunStatus(runDetails[summary.id]); + } + return map; + }, [runs, runDetails]); + + const sidebarItems: SidebarItem[] = useMemo(() => { + const items: SidebarItem[] = [ + { + kind: "action", + action: "new-copilot", + label: `+ New chat (${defaultCopilot})`, + hint: "Ctrl+N", + }, + { + kind: "action", + action: "new-agent", + label: "+ New chat (choose agent)", + hint: "Ctrl+G", + }, + ]; + for (const run of runs) { + items.push({ + kind: "run", + run, + status: runStatusMap[run.id] ?? { label: "loading…", color: "gray" }, + }); + } + return items; + }, [defaultCopilot, runStatusMap, runs]); + + useEffect(() => { + setSidebarIndex((idx) => { + if (sidebarItems.length === 0) { + return 0; + } + return Math.min(idx, sidebarItems.length - 1); + }); + }, [sidebarItems.length]); + + const showToast = useCallback((next: Toast) => { + setToast(next); + }, []); + + useEffect(() => { + if (!toast) { + return; + } + const timer = setTimeout(() => { + setToast(null); + }, 4000); + return () => clearTimeout(timer); + }, [toast]); + + const loadInitial = useCallback(async () => { + setConnectionState("connecting"); + setConnectionError(null); + try { + const [health, config, agentList, runsResponse] = await Promise.all([ + api.getHealth(), + api.getModelConfig(), + api.listAgents(), + api.listRuns(), + ]); + if (health.status !== "ok") { + throw new Error("Server is not healthy"); + } + setModelConfig(config); + setAgents(agentList); + setRuns(runsResponse.runs); + setRunsCursor(runsResponse.nextCursor); + setConnectionState("ready"); + } catch (error) { + setConnectionState("error"); + setConnectionError(error instanceof Error ? error.message : String(error)); + } + }, [api]); + + useEffect(() => { + loadInitial(); + }, [loadInitial]); + + useEffect(() => { + if (!activeRunId) { + return; + } + if (runDetails[activeRunId]) { + return; + } + let cancelled = false; + (async () => { + try { + const run = await api.getRun(activeRunId); + if (!cancelled) { + setRunDetails((prev) => ({ + ...prev, + [run.id]: run, + })); + } + } catch (error) { + if (!cancelled) { + showToast({ + type: "error", + text: `Failed to load run: ${error instanceof Error ? error.message : String(error)}`, + }); + } + } + })(); + return () => { + cancelled = true; + }; + }, [activeRunId, api, runDetails, showToast]); + + const refreshRuns = useCallback(async () => { + setRunsLoading(true); + try { + const response = await api.listRuns(); + setRuns(response.runs); + setRunsCursor(response.nextCursor); + } catch (error) { + showToast({ + type: "error", + text: `Failed to refresh runs: ${error instanceof Error ? error.message : String(error)}`, + }); + } finally { + setRunsLoading(false); + } + }, [api, showToast]); + + useEffect(() => { + if (connectionState !== "ready") { + return; + } + let unsub: (() => void) | null = null; + let cancelled = false; + setStreamError(null); + setEventStreamActive(false); + (async () => { + try { + unsub = await api.subscribeToEvents((event) => { + if (cancelled) { + return; + } + setEventStreamActive(true); + if (event.type === "start") { + setRuns((prev) => { + const next = [...prev]; + const idx = next.findIndex((r) => r.id === event.runId); + const summary: RunSummary = { + id: event.runId, + agentId: event.agentName, + createdAt: event.ts ?? new Date().toISOString(), + }; + if (idx >= 0) { + next[idx] = summary; + return next; + } + return [summary, ...next]; + }); + } + setRunDetails((prev) => { + const existing = prev[event.runId]; + if (!existing) { + return prev; + } + return { + ...prev, + [event.runId]: { + ...existing, + log: [...existing.log, event], + }, + }; + }); + }, (error) => { + setStreamError(error.message); + }); + } catch (error) { + if (!cancelled) { + setStreamError(error instanceof Error ? error.message : String(error)); + } + } + })(); + return () => { + cancelled = true; + unsub?.(); + }; + }, [api, connectionState]); + + const startDraftChat = useCallback((agentName: string) => { + setActiveRunId(null); + setDraftAgent(agentName); + setComposerValue(""); + setFocusTarget("chat"); + setSidebarIndex(0); + }, []); + + const composeMessage = useCallback(async (value: string) => { + const trimmed = value.trim(); + if (!trimmed) { + return; + } + setComposerBusy(true); + try { + let runId = activeRunId; + if (!runId) { + const agentName = draftAgent || defaultCopilot; + const run = await api.createRun(agentName); + runId = run.id; + setRuns((prev) => { + const without = prev.filter((r) => r.id !== run.id); + return [ + { + id: run.id, + createdAt: run.createdAt, + agentId: run.agentId, + }, + ...without, + ]; + }); + setRunDetails((prev) => ({ + ...prev, + [run.id]: run, + })); + setActiveRunId(run.id); + } + await api.sendMessage(runId, trimmed); + setComposerValue(""); + showToast({ + type: "success", + text: "Message queued", + }); + } catch (error) { + showToast({ + type: "error", + text: `Failed to send message: ${error instanceof Error ? error.message : String(error)}`, + }); + } finally { + setComposerBusy(false); + } + }, [activeRunId, api, defaultCopilot, draftAgent, showToast]); + + const handleApprovePermission = useCallback(async () => { + const run = selectedRun; + const pending = pendingPermissions[0]; + if (!run || !pending) { + showToast({ type: "info", text: "No pending tool permissions" }); + return; + } + try { + await api.authorizeTool(run.id, { + toolCallId: pending.toolCallId, + response: "approve", + subflow: pending.subflow, + }); + showToast({ type: "success", text: `Approved ${pending.toolName}` }); + } catch (error) { + showToast({ + type: "error", + text: `Failed to approve: ${error instanceof Error ? error.message : String(error)}`, + }); + } + }, [api, pendingPermissions, selectedRun, showToast]); + + const handleDenyPermission = useCallback(async () => { + const run = selectedRun; + const pending = pendingPermissions[0]; + if (!run || !pending) { + showToast({ type: "info", text: "No pending tool permissions" }); + return; + } + try { + await api.authorizeTool(run.id, { + toolCallId: pending.toolCallId, + response: "deny", + subflow: pending.subflow, + }); + showToast({ type: "success", text: `Denied ${pending.toolName}` }); + } catch (error) { + showToast({ + type: "error", + text: `Failed to deny: ${error instanceof Error ? error.message : String(error)}`, + }); + } + }, [api, pendingPermissions, selectedRun, showToast]); + + const handleStopRun = useCallback(async () => { + if (!selectedRun) { + showToast({ type: "info", text: "No run selected" }); + return; + } + try { + await api.stopRun(selectedRun.id); + showToast({ type: "success", text: `Stop requested for ${selectedRun.id}` }); + } catch (error) { + showToast({ + type: "error", + text: `Failed to stop: ${error instanceof Error ? error.message : String(error)}`, + }); + } + }, [api, selectedRun, showToast]); + + const handleReplyHuman = useCallback(async (value: string, context: PendingHuman | undefined) => { + if (!selectedRun || !context) { + showToast({ type: "info", text: "No pending human requests" }); + return; + } + try { + await api.replyToHuman(selectedRun.id, context.toolCallId, { + toolCallId: context.toolCallId, + response: value, + subflow: context.subflow, + }); + showToast({ type: "success", text: "Reply sent" }); + } catch (error) { + showToast({ + type: "error", + text: `Failed to send reply: ${error instanceof Error ? error.message : String(error)}`, + }); + throw error; + } + }, [api, selectedRun, showToast]); + + const currentHumanRequest = pendingHuman[0]; + const maxVisibleEvents = Math.max(8, (stdout?.rows ?? 40) - 14); + + const chatTimeline = useMemo(() => { + if (!selectedRun) { + return { + visibleEvents: [] as ChatLine[], + maxOffset: 0, + total: 0, + }; + } + const lines: ChatLine[] = []; + let streamingText = ""; + let streamingActive = false; + let reasoningText = ""; + let reasoningActive = false; + for (const event of selectedRun.log) { + if (event.type === "llm-stream-event") { + const step = event.event; + switch (step.type) { + case "text-start": + streamingActive = true; + streamingText = ""; + break; + case "text-delta": + streamingActive = true; + streamingText += step.delta; + break; + case "text-end": + case "finish-step": + streamingActive = false; + break; + case "reasoning-start": + reasoningActive = true; + reasoningText = ""; + break; + case "reasoning-delta": + reasoningActive = true; + reasoningText += step.delta; + break; + case "reasoning-end": + reasoningActive = false; + break; + default: + break; + } + continue; + } + const formatted = formatEvent(event); + if (formatted) { + lines.push(formatted); + } + } + if (reasoningActive && reasoningText) { + lines.push({ + text: `assistant (thinking): ${reasoningText}`, + color: "black", + variant: "thinking", + }); + } + if (streamingActive && streamingText) { + lines.push({ + text: `assistant (streaming): ${streamingText}`, + color: "black", + variant: "streaming", + }); + } + const total = lines.length; + const maxOffset = Math.max(0, total - maxVisibleEvents); + const clampedOffset = Math.min(chatScrollOffset, maxOffset); + const end = total - clampedOffset; + const start = Math.max(0, end - maxVisibleEvents); + return { + visibleEvents: lines.slice(start, end), + maxOffset, + total, + }; + }, [chatScrollOffset, maxVisibleEvents, selectedRun]); + + useEffect(() => { + setChatScrollOffset(0); + }, [selectedRun?.id]); + + useEffect(() => { + setChatScrollOffset((offset) => Math.min(offset, chatTimeline.maxOffset)); + }, [chatTimeline.maxOffset]); + + useInput((input, key) => { + if (modal) { + if (key.escape) { + setModal(null); + } + return; + } + if (key.tab) { + setFocusTarget((prev) => (prev === "chat" ? "sidebar" : "chat")); + return; + } + if (key.ctrl && input === "q") { + exit(); + return; + } + if (key.ctrl && input === "n") { + startDraftChat(defaultCopilot); + return; + } + if (key.ctrl && input === "g") { + if (agents.length === 0) { + showToast({ type: "error", text: "No agents available" }); + return; + } + setModal({ type: "agent-picker" }); + return; + } + if (key.ctrl && input === "l") { + refreshRuns(); + return; + } + if (key.ctrl && input === "a") { + handleApprovePermission(); + return; + } + if (key.ctrl && input === "d") { + handleDenyPermission(); + return; + } + if (key.ctrl && input === "s") { + handleStopRun(); + return; + } + if (key.ctrl && input === "h") { + if (!currentHumanRequest) { + showToast({ type: "info", text: "No pending human input requests" }); + return; + } + if (!selectedRun) { + showToast({ type: "info", text: "Select a run to respond" }); + return; + } + setModal({ + type: "human-response", + runId: selectedRun.id, + requestId: currentHumanRequest.toolCallId, + subflow: currentHumanRequest.subflow, + prompt: currentHumanRequest.query, + value: "", + submitting: false, + }); + return; + } + if (focusTarget === "sidebar") { + if (key.upArrow) { + setSidebarIndex((idx) => Math.max(0, idx - 1)); + return; + } + if (key.downArrow) { + setSidebarIndex((idx) => Math.min(sidebarItems.length - 1, idx + 1)); + return; + } + if (key.return) { + const item = sidebarItems[sidebarIndex]; + if (!item) { + return; + } + if (item.kind === "action") { + if (item.action === "new-copilot") { + startDraftChat(defaultCopilot); + } else { + if (agents.length === 0) { + showToast({ type: "error", text: "No agents available" }); + } else { + setModal({ type: "agent-picker" }); + } + } + } else { + setActiveRunId(item.run.id); + setFocusTarget("chat"); + } + } + } + if (focusTarget === "chat") { + const scrollStep = Math.max(3, Math.floor(maxVisibleEvents / 2)); + if (key.pageUp) { + setChatScrollOffset((offset) => Math.min(chatTimeline.maxOffset, offset + scrollStep)); + return; + } + if (key.pageDown) { + setChatScrollOffset((offset) => Math.max(0, offset - scrollStep)); + return; + } + } + }); + + return ( + +
+ + + + 0} + scrollHint={chatTimeline.maxOffset > 0} + /> + + + + + Tab toggles focus · Ctrl+N new Copilot chat · Ctrl+G choose agent · Ctrl+L refresh chats · Ctrl+Q quit + + + + {toast && ( + + + {toast.text} + + + )} + + {modal && ( + + {modal.type === "agent-picker" && ( + { + setModal(null); + startDraftChat(agent); + }} + onCancel={() => setModal(null)} + /> + )} + {modal.type === "human-response" && ( + setModal({ ...modal, value })} + onSubmit={async (value) => { + const ctx: PendingHuman = { + toolCallId: modal.requestId, + query: modal.prompt, + subflow: modal.subflow, + }; + setModal({ ...modal, submitting: true }); + try { + await handleReplyHuman(value.trim(), ctx); + setModal(null); + } catch { + setModal({ ...modal, submitting: false }); + } + }} + onCancel={() => setModal(null)} + /> + )} + + )} + + ); +} + +function Header({ + serverUrl, + state, + error, + modelConfig, + agentsCount, + runsCount, + runsCursor, + streamError, + listening, +}: { + serverUrl: string; + state: ConnectionState; + error: string | null; + modelConfig: ModelConfigType | null; + agentsCount: number; + runsCount: number; + runsCursor: string | undefined; + streamError: string | null; + listening: boolean; +}) { + return ( + + + RowboatX chat · Server {serverUrl} + + + {state === "connecting" && ( + <> + + + {" "} + Connecting… + + )} + {state === "ready" && ( + + Connected · default {modelConfig?.defaults?.provider ?? "n/a"}/{modelConfig?.defaults?.model ?? "n/a"} + + )} + {state === "error" && ( + + Offline: {error ?? "Unknown error"} · Ctrl+L to retry + + )} + + + Agents: {agentsCount} · Chats loaded: {runsCount} + {runsCursor ? " (+ more)" : ""} + + {streamError && ( + Event stream issue: {streamError} + )} + {state === "ready" && listening === false && ( + Listening for run events… + )} + + ); +} + +function Sidebar({ + items, + focus, + index, + activeRunId, + runsLoading, +}: { + items: SidebarItem[]; + focus: boolean; + index: number; + activeRunId: string | null; + runsLoading: boolean; +}) { + return ( + + Chats + {focus ? "↑/↓ move · Enter select · Esc to leave" : "Tab to focus sidebar"} + + {runsLoading && ( + + refreshing… + + )} + {items.length === 0 && No chats yet.} + {items.map((item, idx) => { + let divider: React.ReactNode = null; + const isCursor = focus && idx === index; + if (item.kind === "action") { + return ( + + {isCursor ? "❯" : " "} {item.label} {item.hint ? `(${item.hint})` : ""} + + ); + } + const previousRuns = items.slice(0, idx).some((entry) => entry.kind === "run"); + if (!previousRuns) { + divider = ( + + ── recent chats ── + + ); + } + const isActiveRun = item.run.id === activeRunId; + return ( + + {divider} + + + {isCursor ? "❯" : isActiveRun ? "●" : " "} + {" "} + {item.run.agentId}{" "} + {item.run.id}{" "} + {item.status.label}{" "} + {timeAgo(item.run.createdAt)} + + + ); + })} + + + ); +} + +function ChatPanel({ + focus, + draftAgent, + run, + events, + composerValue, + composerBusy, + onChangeComposer, + onSubmitComposer, + pendingPermissions, + pendingHuman, + showHumanHint, + showPermissionHint, + scrollHint, +}: { + focus: boolean; + draftAgent: string; + run: RunType | undefined; + events: ChatLine[]; + composerValue: string; + composerBusy: boolean; + onChangeComposer: (value: string) => void; + onSubmitComposer: (value: string) => void; + pendingPermissions: PendingPermission[]; + pendingHuman: PendingHuman[]; + showHumanHint: boolean; + showPermissionHint: boolean; + scrollHint: boolean; +}) { + return ( + + + + {run ? run.agentId : draftAgent} + {" "} + {run ? ( + <> + · Run {run.id} · started {formatTimestamp(run.createdAt)} ({timeAgo(run.createdAt)}) + + ) : ( + · new chat + )} + + {!run && ( + Type a prompt and press enter to spin up a new {draftAgent} chat. + )} + {showPermissionHint && ( + Tool approval pending · Ctrl+A approve · Ctrl+D deny + )} + {showHumanHint && ( + Agent asked for help · Ctrl+H to reply + )} + + {run && events.length === 0 && ( + Loading chat log… + )} + {!run && ( + No messages yet. + )} + {events.map((event, idx) => ( + + ))} + + + + {focus + ? `Enter to send · Ctrl+N new chat${scrollHint ? " · PgUp/PgDn scroll" : ""}` + : "Tab to focus composer"} + + onSubmitComposer(value)} + focus={focus && !composerBusy} + placeholder="Send a message…" + /> + {composerBusy && ( + + Sending… + + )} + + + ); +} + +function ModalSurface({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} + +function AgentPickerModal({ + agents, + onSelect, + onCancel, +}: { + agents: AgentType[]; + onSelect: (agentName: string) => void; + onCancel: () => void; +}) { + const items = agents.map((agent) => ({ + label: `${agent.name}${agent.description ? ` – ${truncate(agent.description, 40)}` : ""}`, + value: agent.name, + })); + return ( + + Select an agent (esc to cancel) + {items.length === 0 ? ( + No agents configured. + ) : ( + + items={items} + onSelect={(item) => onSelect(item.value)} + /> + )} + {items.length} agents available. + + ); +} + +function MessageModal({ + typeLabel, + prompt, + value, + submitting, + onChange, + onSubmit, + onCancel, +}: { + typeLabel: string; + prompt?: string; + value: string; + submitting: boolean; + onChange: (value: string) => void; + onSubmit: (value: string) => Promise; + onCancel: () => void; +}) { + return ( + + {typeLabel} (esc to cancel) + {prompt && ( + {truncate(prompt, 120)} + )} + { + if (!text.trim()) { + return; + } + onSubmit(text); + }} + focus={!submitting} + placeholder="Type your response…" + /> + {submitting ? ( + + Sending… + + ) : ( + Enter to submit · esc to cancel + )} + + ); +} + +function derivePendingPermissions(run: RunType | undefined): PendingPermission[] { + if (!run) { + return []; + } + const responded = new Set( + run.log + .filter((event) => event.type === "tool-permission-response") + .map((event) => event.toolCallId), + ); + const pending: PendingPermission[] = []; + for (const event of run.log) { + if (event.type === "tool-permission-request") { + const id = event.toolCall.toolCallId; + if (!responded.has(id)) { + pending.push({ + toolCallId: id, + toolName: event.toolCall.toolName, + args: event.toolCall.arguments, + subflow: event.subflow, + }); + } + } + } + return pending; +} + +function derivePendingHuman(run: RunType | undefined): PendingHuman[] { + if (!run) { + return []; + } + const responded = new Set( + run.log + .filter((event) => event.type === "ask-human-response") + .map((event) => event.toolCallId), + ); + const pending: PendingHuman[] = []; + for (const event of run.log) { + if (event.type === "ask-human-request" && !responded.has(event.toolCallId)) { + pending.push({ + toolCallId: event.toolCallId, + query: event.query, + subflow: event.subflow, + }); + } + } + return pending; +} + +function getRunStatus(run: RunType | undefined): { label: string; color: string } { + if (!run) { + return { label: "loading…", color: "gray" }; + } + const last = run.log[run.log.length - 1]; + if (last?.type === "error") { + return { label: "error", color: "red" }; + } + if (derivePendingHuman(run).length > 0) { + return { label: "awaiting human", color: "magenta" }; + } + if (derivePendingPermissions(run).length > 0) { + return { label: "needs approval", color: "yellow" }; + } + return { label: "running", color: "green" }; +} + +function MessageBubble({ event }: { event: ChatLine }) { + const isUser = event.variant === "user"; + const isAssistant = event.variant === "assistant" || event.variant === "streaming"; + const align = isUser ? "flex-end" : "flex-start"; + const bubbleColor = isUser ? "blue" : undefined; + const textColor = isUser ? "white" : event.color; + return ( + + + + {event.text} + + + + ); +} + +function formatEvent(event: RunEventType): ChatLine | null { + switch (event.type) { + case "start": + return { text: `▶ Start · ${event.agentName}`, color: "green", variant: "system" }; + case "message": { + const content = typeof event.message.content === "string" + ? event.message.content + : event.message.content + .map((part) => { + if (part.type === "text" || part.type === "reasoning") { + return part.text; + } + if (part.type === "tool-call") { + return `[tool:${part.toolName}] ${JSON.stringify(part.arguments)}`; + } + return ""; + }) + .join("\n"); + return { + text: `${event.message.role}: ${content}`, + color: event.message.role === "user" ? "black" : event.message.role === "assistant" ? "black" : "white", + variant: event.message.role === "user" + ? "user" + : event.message.role === "assistant" + ? "assistant" + : "system", + }; + } + case "tool-invocation": + return { text: `🔧 Invoking ${event.toolName} ${JSON.stringify(event.input)}`, color: "yellow", variant: "tool" }; + case "tool-result": + return { text: `✅ ${event.toolName} → ${truncate(JSON.stringify(event.result), 120)}`, color: "green", variant: "tool" }; + case "tool-permission-request": + return { text: `⚠️ Permission needed for ${event.toolCall.toolName}`, color: "yellow", variant: "system" }; + case "tool-permission-response": + return { text: `Permission ${event.response} for ${event.toolCallId}`, color: event.response === "approve" ? "green" : "red", variant: "system" }; + case "ask-human-request": + return { text: `🧑 Agent asks: ${truncate(event.query, 120)}`, color: "magenta", variant: "system" }; + case "ask-human-response": + return { text: `🙋 Human replied`, color: "magenta", variant: "system" }; + case "llm-stream-event": + return { text: `… ${event.event.type}`, color: "gray" }; + case "error": + return { text: `✖ ${event.error}`, color: "red", variant: "system" }; + case "spawn-subflow": + return { text: `↳ Spawned ${event.agentName}`, color: "cyan", variant: "system" }; + default: + return { text: "unknown event", color: "white", variant: "other" }; + } +} + +function truncate(input: string, len = 60): string { + if (input.length <= len) { + return input; + } + return `${input.slice(0, len - 1)}…`; +} + +function formatTimestamp(iso: string): string { + const date = new Date(iso); + if (Number.isNaN(date.getTime())) { + return iso; + } + return date.toLocaleString(); +} + +function timeAgo(iso: string): string { + const date = new Date(iso); + if (Number.isNaN(date.getTime())) { + return iso; + } + const diff = Date.now() - date.getTime(); + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 1fad84d4..a0cffd24 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -11,6 +11,7 @@ "esModuleInterop": true, "skipLibCheck": true, "sourceMap": true, + "jsx": "react-jsx", "paths": { "@/*": [ "./src/*"