diff --git a/bun.lock b/bun.lock index ba0590a17..7d210019b 100644 --- a/bun.lock +++ b/bun.lock @@ -14,6 +14,8 @@ "ink": "^7.1.0", "ink-scroll-view": "^0.3.7", "lodash": "^4.18.1", + "pino": "^10.3.1", + "pino-roll": "^4.0.0", "react": "^19.2.7", "react-devtools-core": "^7.0.1", "react-router": "^8.1.0", @@ -35,55 +37,57 @@ "packages": { "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.3.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA=="], - "@aws-sdk/client-bedrock-agentcore": ["@aws-sdk/client-bedrock-agentcore@3.1079.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/credential-provider-node": "^3.972.62", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-oxzhUn85kZuYoCZL9gKevXUh6OBugFfYbrYgFFbQovEZIfhOO/9q2D8LFsEjbXhaOhC48WohC1+y7p150wSzFA=="], + "@aws-sdk/client-bedrock-agentcore": ["@aws-sdk/client-bedrock-agentcore@3.1083.0", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/credential-provider-node": "^3.972.66", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/fetch-http-handler": "^5.6.4", "@smithy/node-http-handler": "^4.9.4", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-9FRpUnpOVtV9KuVoqM9XXwlCbi63p5Wu29aj4nsP+6DkRiyOm2vDvrp+dGshUp3EZdkhbpq38OAjl7s2LjWMuA=="], - "@aws-sdk/client-bedrock-agentcore-control": ["@aws-sdk/client-bedrock-agentcore-control@3.1079.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/credential-provider-node": "^3.972.62", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-fvW20m7mKLjSVQw1Cq8oXUg/gKTcGNqx1Gm2FSg+zovbqUzbDozJtCpkFVsk5V6ob3Z8Yb7g92mhTvb69DQn6w=="], + "@aws-sdk/client-bedrock-agentcore-control": ["@aws-sdk/client-bedrock-agentcore-control@3.1083.0", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/credential-provider-node": "^3.972.66", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/fetch-http-handler": "^5.6.4", "@smithy/node-http-handler": "^4.9.4", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-tzdKrPo2UQkfeszs8CnRU/ChsNirSa7Vk4B7VIop7Td0qJxrfcsIk75qEvb3nNTZ+bwpIFXrbj0HmyyzG74edw=="], - "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-node": "^3.972.63", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-9XmVmMTBfxJdFz+aM/jtNKer967q6wHVyBbUAsy1obgJnG28IXafHwhIjkZG5ZS3N3frb5Y7T/xpqJLtUJFksA=="], + "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1083.0", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/credential-provider-node": "^3.972.66", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/fetch-http-handler": "^5.6.4", "@smithy/node-http-handler": "^4.9.4", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-1BfmzDtaHTt4zQqbeV134bly5xN0j6TaPBisb9K1iB7Arq2LdjD8sgEWxmM0qZeO/9B0enpngaWXHaF74gnPTQ=="], - "@aws-sdk/core": ["@aws-sdk/core@3.974.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.15", "@aws-sdk/xml-builder": "^3.972.33", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.0", "@smithy/signature-v4": "^5.6.1", "@smithy/types": "^4.15.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-WRWEgIq6vx+NU6ot3VrRu4Jovj9MIObitSi6of/GV5THDDPccBhivCRNkWJutMM+m3GvdeI3l/UbGNcoOobxOA=="], + "@aws-sdk/core": ["@aws-sdk/core@3.975.1", "", { "dependencies": { "@aws-sdk/types": "^3.974.0", "@aws-sdk/xml-builder": "^3.972.34", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.2", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.53", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-+KDA3uc/HZ1vIneGu5QMQb0gAXDYrm2vOE60+BJ7lS0YinMQ5i2oV4PR1A16XkF6K1IbSwjEHd1hQIIgMsK48w=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.57", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.55", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-1gBfkWY3RWeBlCoB9lIJjXMx45/54wxcgfzv6BY9otTmMrZPcNPi1v+MwZxxaCUg441NV3jsr1efnFNCXiW70g=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/fetch-http-handler": "^5.6.4", "@smithy/node-http-handler": "^4.9.4", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.60", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/credential-provider-env": "^3.972.53", "@aws-sdk/credential-provider-http": "^3.972.55", "@aws-sdk/credential-provider-login": "^3.972.59", "@aws-sdk/credential-provider-process": "^3.972.53", "@aws-sdk/credential-provider-sso": "^3.972.59", "@aws-sdk/credential-provider-web-identity": "^3.972.59", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-CV2md+PXvABwRjApWGhQ0wACy9WSFIhnUGrovLcjnjBCd/46TbuivLADtkF8IWNjtCQmQ+2IagSaxqBYqXBNAQ=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.1", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/credential-provider-env": "^3.972.57", "@aws-sdk/credential-provider-http": "^3.972.59", "@aws-sdk/credential-provider-login": "^3.972.63", "@aws-sdk/credential-provider-process": "^3.972.57", "@aws-sdk/credential-provider-sso": "^3.973.1", "@aws-sdk/credential-provider-web-identity": "^3.972.63", "@aws-sdk/nested-clients": "^3.997.31", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/credential-provider-imds": "^4.4.7", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-JG4S9yyA1GFzJdJXqLKrUzZbyK+VDp2QIsJD7YOicJHAhqymfHpDJIok2dLnhOdVB0I37RjdC53uOwCMVS00gw=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.63", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/nested-clients": "^3.997.31", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.62", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.53", "@aws-sdk/credential-provider-http": "^3.972.55", "@aws-sdk/credential-provider-ini": "^3.972.60", "@aws-sdk/credential-provider-process": "^3.972.53", "@aws-sdk/credential-provider-sso": "^3.972.59", "@aws-sdk/credential-provider-web-identity": "^3.972.59", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-S6Slq3Tx7bvFk5yc34XNADyZYTX2HUXvaFAnowGRQnhjBO8J/mP62Fn7lxvJwjaDyYm/7gh9h6HEHaltRyMFXw=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.66", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.57", "@aws-sdk/credential-provider-http": "^3.972.59", "@aws-sdk/credential-provider-ini": "^3.973.1", "@aws-sdk/credential-provider-process": "^3.972.57", "@aws-sdk/credential-provider-sso": "^3.973.1", "@aws-sdk/credential-provider-web-identity": "^3.972.63", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/credential-provider-imds": "^4.4.7", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.53", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-EhfH+MQlqOMCkXIVa8MMObPzAQqwTTtxA7KhEJiyPeuNVA8PLOOUpgK7nBrgaDaGiIDLN/9LpGdaHuDjomeRTw=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.57", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/token-providers": "3.1079.0", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-h8793pOjcImx0SB+VcLONcaQQ57VAvKVuqyewQMRKqqH+CSXsG2dwOeLMUJPMxLdNvL7dXOM0ueTukyNUnu5mA=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.1", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/nested-clients": "^3.997.31", "@aws-sdk/token-providers": "3.1083.0", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.59", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-VoyO9+vl3XVmpZwn4obskrWIkrA/Jf3lSe1E3ZERlaN9u0D4YZ6+HywC3+L98QOXqZesEfedk67gRER8tK8+8w=="], + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.63", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/nested-clients": "^3.997.31", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/signature-v4-multi-region": "^3.996.38", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-A8PIePF9NIIOJ/4Lg1rl9xm/+QaKkHGetq+Z9wb5B+3Da31YYXRo8n7IDMh5C+HQI5eyEmjrwkGWVdYtnLtbXQ=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.31", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/signature-v4-multi-region": "^3.996.39", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/fetch-http-handler": "^5.6.4", "@smithy/node-http-handler": "^4.9.4", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw=="], - "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.38", "", { "dependencies": { "@aws-sdk/types": "^3.973.15", "@smithy/signature-v4": "^5.6.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-C379Sk+MiFZCfWZphKlMyLHKxV22OjoGM5KJjj5IJNJcOCWL4IGIpnEGzv1FQiRwhYXfq55SJMfxlqPE08JJ9g=="], + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.39", "", { "dependencies": { "@aws-sdk/types": "^3.974.0", "@smithy/signature-v4": "^5.6.3", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1079.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.27", "@aws-sdk/nested-clients": "^3.997.27", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-cbietrLlHPhhmbnMPTuDS4Zj/KNGhY+3vVhn6dwjO6Dqzrwothzg2srtcY34T9mlICsTXn34avDoWLHSntP54A=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1083.0", "", { "dependencies": { "@aws-sdk/core": "^3.975.1", "@aws-sdk/nested-clients": "^3.997.31", "@aws-sdk/types": "^3.974.0", "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.15", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-IULn8uBV/SMtmOIANsm4WHXIOtVPBWfOWs3WGL0j/sI+KhaYehvOw0ET+9urnn8MBpiijuU/0JOpuwKOE451PQ=="], + "@aws-sdk/types": ["@aws-sdk/types@3.974.0", "", { "dependencies": { "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.33", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-ezbwz9WpuLctm6o7P2t2naDhVVPI5jFGrVefVybhcKGjU57VIyT46pQVO0RI2RYkUdhdj2Z9uSIlAzGZE9NW9A=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.34", "", { "dependencies": { "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA=="], "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], "@inkui-cli/data-table": ["@inkui-cli/data-table@0.2.0", "", { "peerDependencies": { "ink": "^6.0.0", "react": "^19.0.0" } }, "sha512-kekgwqWnsluB/khs/SO5CGVuMUPJdaPJzGEkJNk3bFnBqFOJNq5S7DpmAtXz/Lta2PqGoiXaSPWewm6PNItznQ=="], - "@smithy/core": ["@smithy/core@3.29.1", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-qoiY4nrk5OCu1+eIR1VB8l5DmON/oKiqrd5zZFAhXJXjJlLWQusKEW/SkBDAtGDcPaz86m9kfcE1lngU0GlM6A=="], + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.6", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-B2WQ/PV/H6Jeg3lrIq6bKUfa6Hy01mtK7CGs6lhjzHA6k4aagldH6T6eEjnzKl4HI0cJnAsxfJ19pgb5PV+CVQ=="], + "@smithy/core": ["@smithy/core@3.29.2", "", { "dependencies": { "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-DXUk6yU0C1Q1tYvJh1VCtl8QOBcSoZpKwjTPkxT6A4MUQYHvgeKGByL8mrEdxnvhdf9nq5GyzmRb5n/vPgu3Lw=="], - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.3", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-CwCc/7SMTj45y97MUnDTbTaxvtAsiNNRm81z3abROIuMbMsC2Iy5EKfkkVdsKrz8WExQAAMx1EJapq+9j4fFTQ=="], + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.7", "", { "dependencies": { "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-UEMLOoA0Fl4uYBxh6l0uN0H6EJe/A89OGeDNTteQeXpJ20BcpfIr4wlCY9pel1jEAUHAxaYwuqrYlrKdXE1GKQ=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.3", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-qZTa4gQFUo8RM02rk6q5UVTDLNrQ1oS20LsepBzqq1QBVc/EHJ03OOUADcqMZiXHArW+Y7+OGY0BpdTwZRq/Yg=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.4", "", { "dependencies": { "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-psnst7NZWdAEvJvyW8YZEE7xNVMyLrQFfHtyrVFrxNyy+dKWkQ+rqC6oI5ZhxThpUy9RSfEshgm34zqbOxzsRw=="], - "@smithy/signature-v4": ["@smithy/signature-v4@5.6.2", "", { "dependencies": { "@smithy/core": "^3.29.1", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-QgHflghMoPxCJ9axiCVh8KZfbC9fuP6vkXXyK//E3cq7nLaSSyyLj0GAoqVWezYeDQmXIZhmlRvLE16jsqDK6g=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.4", "", { "dependencies": { "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA=="], - "@smithy/types": ["@smithy/types@4.15.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q=="], + "@smithy/signature-v4": ["@smithy/signature-v4@5.6.3", "", { "dependencies": { "@smithy/core": "^3.29.2", "@smithy/types": "^4.16.0", "tslib": "^2.6.2" } }, "sha512-8qVKKzqh7naF27ePmx0SkUfnGP/wBI9dyaeAmhHvopnbIlItUAmB/e6PkPCU3rRb2v9BY8D4EZXSoydSibatvw=="], + + "@smithy/types": ["@smithy/types@4.16.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-aVUabzlBBmY0PfvVgLKQSOGFIL5/7R54JE3uD9a5Ay/jSED61SkuAcCYENNXJzYUvJ1NPrWO0P+rAXHCkbBUKw=="], "@tanstack/query-core": ["@tanstack/query-core@5.101.2", "", {}, "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw=="], @@ -91,7 +95,7 @@ "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], - "@types/node": ["@types/node@26.0.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA=="], + "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], @@ -101,6 +105,8 @@ "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], @@ -125,6 +131,8 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="], + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], @@ -153,7 +161,7 @@ "lint-staged": ["lint-staged@17.0.8", "", { "dependencies": { "listr2": "^10.2.1", "picomatch": "^4.0.4", "string-argv": "^0.3.2", "tinyexec": "^1.2.4" }, "optionalDependencies": { "yaml": "^2.9.0" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA=="], - "listr2": ["listr2@10.2.1", "", { "dependencies": { "cli-truncate": "^5.2.0", "eventemitter3": "^5.0.4", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^10.0.0" } }, "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q=="], + "listr2": ["listr2@10.2.2", "", { "dependencies": { "cli-truncate": "^5.2.0", "eventemitter3": "^5.0.4", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^10.0.0" } }, "sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw=="], "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], @@ -163,13 +171,27 @@ "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], + + "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + + "pino-roll": ["pino-roll@4.0.0", "", { "dependencies": { "date-fns": "^4.1.0", "sonic-boom": "^4.0.1" } }, "sha512-axI1aQaIxXdw1F4OFFli1EDxIrdYNGLowkw/ZoZogX8oCSLHUghzwVVXUS8U+xD/Savwa5IXpiXmsSGKFX/7Sg=="], - "prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="], + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + + "prettier": ["prettier@3.9.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg=="], + + "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], @@ -177,12 +199,16 @@ "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - "react-router": ["react-router@8.1.0", "", { "dependencies": { "cookie-es": "^3.1.1" }, "peerDependencies": { "react": ">=19.2.7", "react-dom": ">=19.2.7" }, "optionalPeers": ["react-dom"] }, "sha512-Mdfi61uObuvWNN9OhChOC0HV6YWOIfKRzEWOvCHRSuQg8IM+Nv10edaM/2HE8ZixBpUTdQbruyWqC3sDkkh9vw=="], + "react-router": ["react-router@8.2.0", "", { "dependencies": { "cookie-es": "^3.1.1" }, "peerDependencies": { "react": ">=19.2.7", "react-dom": ">=19.2.7" }, "optionalPeers": ["react-dom"] }, "sha512-uP1LgGNHyilL1u+ZkecQk74DJOKOb6q4NLNYLRJcD/fjoogftNb5/Rj/07o/QBFsY/5MbAKPm1peTCQuVPv9cQ=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "shell-quote": ["shell-quote@1.9.0", "", {}, "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA=="], @@ -191,11 +217,15 @@ "slice-ansi": ["slice-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA=="], + "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], - "string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], + "string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="], "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], @@ -203,11 +233,13 @@ "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="], + "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], + "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="], + "type-fest": ["type-fest@5.8.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -225,10 +257,6 @@ "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@aws-sdk/client-iam/@aws-sdk/core": ["@aws-sdk/core@3.974.28", "", { "dependencies": { "@aws-sdk/types": "^3.973.15", "@aws-sdk/xml-builder": "^3.972.33", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.0", "@smithy/signature-v4": "^5.6.1", "@smithy/types": "^4.15.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-4/1DtLwgLqzIg2uFzkFaFjMQHhhhwHIZN4PfziIVqXYX7koO78omuchQlLHyzQBw80l255dtmTA/J4W5yhb7zw=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.63", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.54", "@aws-sdk/credential-provider-http": "^3.972.56", "@aws-sdk/credential-provider-ini": "^3.972.61", "@aws-sdk/credential-provider-process": "^3.972.54", "@aws-sdk/credential-provider-sso": "^3.972.60", "@aws-sdk/credential-provider-web-identity": "^3.972.60", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-YmgWtTPZDStyT74ApSHpApD3r7W9znsc+WEZjW0vceom+NAxRx9/F3TyukOKix8kJkPaa49aIREQJdpGeLDiEw=="], - "listr2/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], "log-update/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], @@ -239,17 +267,7 @@ "react-devtools-core/ws": ["ws@7.5.11", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA=="], - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.54", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-F4WQCG8GULIt+XrMHsqUM9dZc0eTwZM3HUWByOjIKOwBqJSzUxX8CFAtbgMvWfCua52FS1oi5FXarH3+khFq8A=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.56", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-PiwHgEK2srdfi/lFveyQ+3w/qikyh6MKvuZAdlMC+dwQi4K5iVBr32oh7cugnYw+jA8iGtnQMhCGvLm/mqplmw=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.61", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-env": "^3.972.54", "@aws-sdk/credential-provider-http": "^3.972.56", "@aws-sdk/credential-provider-login": "^3.972.60", "@aws-sdk/credential-provider-process": "^3.972.54", "@aws-sdk/credential-provider-sso": "^3.972.60", "@aws-sdk/credential-provider-web-identity": "^3.972.60", "@aws-sdk/nested-clients": "^3.997.28", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-9CZWMjhBlgfKlqJk40R7kvMOLEIoOr3HJ1J/ELjAH9H5LzR4bCxLujPVM/1PKAEjzSZKZCxWOalm7JUGZbhQqw=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.54", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-UNmUjtTnp3wH3YTZcctN7yK17S2AdaJpiNIdIf0l70hHOdGuDfdMxk62P5rt64GwGm6qkFOYG0js/cU6cdZDdg=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.60", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/nested-clients": "^3.997.28", "@aws-sdk/token-providers": "3.1080.0", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-zH8SvJkTRw1Kb7GARjGPjzkQPfL3jDi/OxK32I5SQq7bgwgFHJZaoDEwkEvFlfNfpByM6n4Rs20Y1mLyOdb6ag=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.60", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/nested-clients": "^3.997.28", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-q2rJSQ/AMjemUS18OtQqczqSo2R6VjOCLmLVmLVcdrP5/AKoj632lGCrMUWQ6EgnaLMEHbq0UO4mWh/u6gjPhA=="], + "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], "listr2/cli-truncate/slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], @@ -257,16 +275,6 @@ "log-update/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.60", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/nested-clients": "^3.997.28", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-Ak6OOrCbXvACyxLFIP1mcS+JTLS9ZpW1ZqyBtqu6axvdpsbG1gVNhUlAfQq8TK/gar/h2w35LrzlQU0PcUzJpw=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.28", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/signature-v4-multi-region": "^3.996.38", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-1oG/mM3jmE2M3kad2zWJS6IKIY8hjRV4l5kAgg+xTQdLMTtehhcSucL/y4WqQpcHmQwi6+gRK8E46GYl1NBW9Q=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.28", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/signature-v4-multi-region": "^3.996.38", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-1oG/mM3jmE2M3kad2zWJS6IKIY8hjRV4l5kAgg+xTQdLMTtehhcSucL/y4WqQpcHmQwi6+gRK8E46GYl1NBW9Q=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/nested-clients": "^3.997.28", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-8PufAQvncWXvdZUvODbuyXa8l3aszefEzwSMBUcgheNbZOmJMcNn388Ebt/piVrUHyxKN1MlxnR2OonzTyZaGw=="], - - "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.28", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/signature-v4-multi-region": "^3.996.38", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-1oG/mM3jmE2M3kad2zWJS6IKIY8hjRV4l5kAgg+xTQdLMTtehhcSucL/y4WqQpcHmQwi6+gRK8E46GYl1NBW9Q=="], - "log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "log-update/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], diff --git a/package.json b/package.json index fac0f947e..b9c3cc3aa 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,8 @@ "react": "^19.2.7", "react-devtools-core": "^7.0.1", "react-router": "^8.1.0", + "pino": "^10.3.1", + "pino-roll": "^4.0.0", "zod": "^4.4.3" } } diff --git a/src/handlers/config/config.test.tsx b/src/handlers/config/config.test.tsx index 6be9585bb..bdb79908c 100644 --- a/src/handlers/config/config.test.tsx +++ b/src/handlers/config/config.test.tsx @@ -1,6 +1,6 @@ import { test, expect, describe } from "bun:test"; import { createRootHandler } from "../index"; -import { TestCoreClient, testIO } from "../../testing"; +import { createSilentLogger, TestCoreClient, testIO } from "../../testing"; // End-to-end tests for the `config` command, driven through the real root // handler and top-level route(). A TestCoreClient stands in for Core (config @@ -9,7 +9,10 @@ import { TestCoreClient, testIO } from "../../testing"; async function run(args: string[]): Promise { const io = testIO(); - const root = createRootHandler(new TestCoreClient(), io.io); + const root = createRootHandler(new TestCoreClient(), { + io: io.io, + logger: createSilentLogger(), + }); await root.route(["node", "agentcore", "config", ...args]); return io.stdout(); } diff --git a/src/handlers/harness/harness.test.tsx b/src/handlers/harness/harness.test.tsx index ae2c5caa5..59a8a11a0 100644 --- a/src/handlers/harness/harness.test.tsx +++ b/src/handlers/harness/harness.test.tsx @@ -2,7 +2,13 @@ import { test, expect, describe } from "bun:test"; import { join } from "node:path"; import { CoreClient } from "../../core"; import { createRootHandler } from "../index"; -import { fixtureFactories, isRecording, matchGolden, testIO } from "../../testing"; +import { + createSilentLogger, + fixtureFactories, + isRecording, + matchGolden, + testIO, +} from "../../testing"; // End-to-end command-flow tests for the `harness` subtree. // @@ -26,7 +32,7 @@ async function run(args: string[]): Promise { const { createControlClient, createDataClient, createIamClient } = fixtureFactories(FIXTURES); const core = new CoreClient(createControlClient, createDataClient, createIamClient); const io = testIO(); - const root = createRootHandler(core, io.io); + const root = createRootHandler(core, { io: io.io, logger: createSilentLogger() }); await root.route(["node", "agentcore", ...args, "--region", REGION]); return io.stdout(); } diff --git a/src/handlers/harness/invoke/invoke.test.tsx b/src/handlers/harness/invoke/invoke.test.tsx index 346595421..25015719a 100644 --- a/src/handlers/harness/invoke/invoke.test.tsx +++ b/src/handlers/harness/invoke/invoke.test.tsx @@ -5,7 +5,7 @@ import type { } from "@aws-sdk/client-bedrock-agentcore"; import type { GetHarnessResponse } from "@aws-sdk/client-bedrock-agentcore-control"; import { createRootHandler } from "../../index"; -import { TestCoreClient, testIO } from "../../../testing"; +import { createSilentLogger, TestCoreClient, testIO } from "../../../testing"; // Command-flow tests for `harness invoke`, driven through the real root handler // exactly as the CLI runs it. Unlike the get/list suites these use a @@ -41,7 +41,7 @@ async function run(args: string[], configure?: (core: TestCoreClient) => void) { core.harness.setInvokeEvents(...TURN_EVENTS); configure?.(core); const io = testIO(); - const root = createRootHandler(core, io.io); + const root = createRootHandler(core, { io: io.io, logger: createSilentLogger() }); await root.route(["node", "agentcore", ...args, "--region", "us-west-2"]); return { core, stdout: io.stdout() }; } diff --git a/src/handlers/help.screen.test.tsx b/src/handlers/help.screen.test.tsx index 37c896158..064c68adf 100644 --- a/src/handlers/help.screen.test.tsx +++ b/src/handlers/help.screen.test.tsx @@ -4,7 +4,7 @@ import { render, cleanup } from "ink-testing-library"; import { ValueContext, compile, CommandKey } from "../router"; import { createRootHandler } from "./index"; import { HelpScreen } from "./screen"; -import { TestCoreClient, testIO } from "../testing"; +import { createSilentLogger, TestCoreClient, testIO } from "../testing"; afterEach(cleanup); @@ -16,7 +16,7 @@ afterEach(cleanup); describe("HelpScreen", () => { test("renders the command's help text", () => { const command = compile( - createRootHandler(new TestCoreClient(), testIO().io), + createRootHandler(new TestCoreClient(), { io: testIO().io, logger: createSilentLogger() }), ValueContext.EmptyContext(), ); const ctx = ValueContext.EmptyContext().withValue(CommandKey, command); diff --git a/src/handlers/index.tsx b/src/handlers/index.tsx index b6f891efc..1fd52e1b3 100644 --- a/src/handlers/index.tsx +++ b/src/handlers/index.tsx @@ -3,10 +3,17 @@ import { createHarnessHandler } from "./harness/index.tsx"; import { DebugKey, EndpointKey, JsonKey, RegionKey } from "./keys.tsx"; import { createConfigHandler } from "./config/"; import { renderTui } from "../tui"; -import { withRegion, withJsonRenderer } from "../middleware"; +import { withRegion, withJsonRenderer, withLogging } from "../middleware"; import type { AppIO, Core } from "./types.tsx"; +import type { Logger } from "../logging"; -export function createRootHandler(core: Core, io: AppIO): Router { +export interface RootHandlerConfig { + io: AppIO; + logger: Logger; +} + +export function createRootHandler(core: Core, config: RootHandlerConfig): Router { + const { io, logger } = config; const root = new Router("agentcore", "the platform for production AI agents"); // Add global flags @@ -20,6 +27,9 @@ export function createRootHandler(core: Core, io: AppIO): Router { // machine-readable output without touching the process streams directly. root.use(withJsonRenderer(io)); + // Inject a logger into each handler. + root.use(withLogging({ logger })); + // Install sub handlers root.handler(createHarnessHandler(core, io)); root.handler(createConfigHandler(io)); diff --git a/src/handlers/root.test.tsx b/src/handlers/root.test.tsx index 3e9a033da..16b185a12 100644 --- a/src/handlers/root.test.tsx +++ b/src/handlers/root.test.tsx @@ -1,10 +1,13 @@ import { test, expect, describe } from "bun:test"; import { createRootHandler } from "./index"; -import { TestCoreClient, testIO } from "../testing"; +import { createSilentLogger, TestCoreClient, testIO } from "../testing"; describe("createRootHandler", () => { test("builds the agentcore command tree with its subcommands", () => { - const root = createRootHandler(new TestCoreClient(), testIO().io); + const root = createRootHandler(new TestCoreClient(), { + io: testIO().io, + logger: createSilentLogger(), + }); expect(root.name()).toBe("agentcore"); expect(root.children().map((c) => c.name())).toEqual(["harness", "config"]); }); diff --git a/src/index.ts b/src/index.ts index 48c962849..bb41c6042 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,28 +4,59 @@ // published `bin` directly executable by Node. It's ignored during development // when the file is run via `bun run src/index.ts`. +import { homedir } from "os"; +import { join } from "path"; + import { CoreClient } from "./core"; import { createControlClient, createDataClient, createIamClient } from "./core/factories"; import { createRootHandler } from "./handlers"; +import { createFileLogger, LOG_LEVEL } from "./logging"; import { runWithExitCode } from "./runnable"; process.exit( await runWithExitCode(async (argv: string[]) => { - // Wrap the SDK clients in the CoreClient the handlers consume. Passing - // factories (rather than instances) lets CoreClient build one client per - // region on demand. - const coreClient = new CoreClient(createControlClient, createDataClient, createIamClient); - - // Pass it to the root handler, along with the process's standard streams as - // the app's io. CoreClient exposes feature sub-clients (e.g. `.harness`), so - // it satisfies the Core contract directly. - const rootHandler = createRootHandler(coreClient, { + // generate a unique identifier corresponding to this process of this CLI. (ex. one command invoke, one TUI session) + // TODO: wire this id into telemetry as well + const cliSessionId = crypto.randomUUID(); + + const rootLogger = createFileLogger({ + filePath: join(homedir(), ".agentcore", "logs", "output"), + // TODO: allow overriding via global settings + logLevel: LOG_LEVEL.DEBUG, + bindings: { cliSessionId }, + }); + + const io = { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr, - }); + }; + + try { + // Wrap the SDK clients in the CoreClient the handlers consume. Passing + // factories (rather than instances) lets CoreClient build one client per + // region on demand. + const coreClient = new CoreClient(createControlClient, createDataClient, createIamClient); + + // Pass it to the root handler, along with the process's standard streams as + // the app's io. CoreClient exposes feature sub-clients (e.g. `.harness`), so + // it satisfies the Core contract directly. + const rootHandler = createRootHandler(coreClient, { + io, + logger: rootLogger, + }); - // Handle the request - await rootHandler.route(argv); + // Handle the request + await rootHandler.route(argv); + } catch (e) { + const error = e instanceof Error ? e : new Error(String(e)); + io.stderr.write(`${error.name}: ${error.message}\n`); + rootLogger + .child({ errorName: error.name, errorMessage: error.message, stack: error.stack ?? "" }) + .error(); + throw e; + } finally { + await rootLogger.flush(); + } }), ); diff --git a/src/logging/fileLogger.tsx b/src/logging/fileLogger.tsx new file mode 100644 index 000000000..b08f3c64c --- /dev/null +++ b/src/logging/fileLogger.tsx @@ -0,0 +1,62 @@ +import pino from "pino"; +import { type AsyncLogger, type LoggerBindings, type LogLevel } from "./types"; + +export interface FileLoggerConfig { + filePath: string; + maxSizeInMB?: number; + maxFileCount?: number; + bindings?: LoggerBindings; + logLevel: LogLevel; +} + +function wrapPinoLogger(pinoLogger: pino.Logger): AsyncLogger { + const log = + (level: pino.Level) => + (...args: string[]) => + pinoLogger[level](args.join(" ")); + return { + debug: log("debug"), + info: log("info"), + warn: log("warn"), + error: log("error"), + child: (bindings) => wrapPinoLogger(pinoLogger.child(bindings)), + // we convert pino's flush method that accepts a callback into a promise to make it easier to work with. + // Note: we also treat flush as best-effort and swallow errors + flush: () => new Promise((resolve) => pinoLogger.flush(() => resolve())), + }; +} + +/** + * Creates a logger that writes structured JSON to a rotating file. + * + * @param config - Logger configuration (file path, rotation limits, level). + * @returns A {@link AsyncLogger} that writes to a rotating file via pino. + */ +export function createFileLogger(config: FileLoggerConfig): AsyncLogger { + const maxSizeInMB = config.maxSizeInMB ?? 10; + const maxFileCount = config.maxFileCount ?? 5; + const bindings = config.bindings ?? {}; + return wrapPinoLogger( + pino({ + level: config.logLevel, + base: undefined, // omit pid and hostname + formatters: { + level(label) { + return { level: label }; + }, + }, + transport: { + target: "pino-roll", + options: { + extension: ".log", + dateFormat: "yyyy-MM-dd'T'HH-mm-ss", + // Rotate when file reaches {maxSizeInMB} MB, and start deleting once we have {maxFileCount} files + size: `${maxSizeInMB}m`, + limit: { count: maxFileCount }, + file: config.filePath, + mkdir: true, + }, + }, + }), + ).child(bindings); +} diff --git a/src/logging/index.tsx b/src/logging/index.tsx new file mode 100644 index 000000000..e9321f4fa --- /dev/null +++ b/src/logging/index.tsx @@ -0,0 +1,2 @@ +export { type Logger, type LoggerBindings, LOG_LEVEL } from "./types"; +export { createFileLogger } from "./fileLogger"; diff --git a/src/logging/types.tsx b/src/logging/types.tsx new file mode 100644 index 000000000..1f6169f18 --- /dev/null +++ b/src/logging/types.tsx @@ -0,0 +1,31 @@ +/** + * Available log levels ordered by severity. `SILENT` disables all output. + */ +export const LOG_LEVEL = { + DEBUG: "debug", + INFO: "info", + WARN: "warn", + ERROR: "error", + SILENT: "silent", +} as const; + +export type LogLevel = (typeof LOG_LEVEL)[keyof typeof LOG_LEVEL]; + +export type LoggerBindings = Record; + +type LogFn = (...messages: string[]) => void; + +/** App-wide structured logging contract with child-logger support */ +export interface Logger { + debug: LogFn; + info: LogFn; + warn: LogFn; + error: LogFn; + child: (bindings: LoggerBindings) => Logger; +} + +/** An extension of {@link Logger} that writes logs asynchronously and requires output to be flushed */ +export interface AsyncLogger extends Logger { + child: (bindings: LoggerBindings) => AsyncLogger; + flush: () => Promise; +} diff --git a/src/middleware/index.tsx b/src/middleware/index.tsx index 115bb4e56..e4f9a09f3 100644 --- a/src/middleware/index.tsx +++ b/src/middleware/index.tsx @@ -1,3 +1,4 @@ export { withRegion } from "./withRegion"; export { withTuiOnEmptyFlagsAndArgs } from "./withTuiOnEmptyFlagsAndArgs"; export { withJsonRenderer } from "./withJsonRenderer"; +export { withLogging } from "./withLogging"; diff --git a/src/middleware/withLogging.test.ts b/src/middleware/withLogging.test.ts new file mode 100644 index 000000000..862c35891 --- /dev/null +++ b/src/middleware/withLogging.test.ts @@ -0,0 +1,69 @@ +import { test, describe, beforeEach, afterEach } from "bun:test"; +import { Router, createHandler } from "../router"; +import { withLogging } from "./withLogging"; +import { createFileLogger } from "../logging/fileLogger"; +import { LOG_LEVEL, type AsyncLogger } from "../logging/types"; +import { assertLogsMatch } from "../testing"; +import { join } from "node:path"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; + +describe("withLogging", () => { + let tempDir: string; + let logger: AsyncLogger; + + beforeEach(async () => { + tempDir = await mkdtemp(join(tmpdir(), "logging-test-")); + logger = createFileLogger({ + filePath: join(tempDir, "output"), + logLevel: LOG_LEVEL.DEBUG, + }); + }); + + afterEach(async () => { + await logger.flush(); + await rm(tempDir, { recursive: true, force: true }); + }); + + test("logs success and error with correct command path bindings", async () => { + const app = new Router("myapp", "test app"); + app.use(withLogging({ logger })); + app.handler( + createHandler({ + name: "happy", + description: "succeeds", + handle: async () => {}, + }), + ); + app.handler( + createHandler({ + name: "boom", + description: "throws", + handle: async () => { + throw new Error("connection timeout"); + }, + }), + ); + + await app.route(["node", "myapp", "happy"]); + await app.route(["node", "myapp", "boom"]).catch(() => {}); + await app.route(["node", "myapp", "happy"]); + + await assertLogsMatch(tempDir, [ + { + filter: (l: any) => + l.msg === "command executed successfully" && l.commandPath === "/myapp/happy", + expectedCount: 2, + }, + { + filter: (l: any) => + l.level === "error" && + l.msg === "command failed" && + l.errorName === "Error" && + l.errorMessage === "connection timeout" && + l.commandPath === "/myapp/boom", + expectedCount: 1, + }, + ]); + }); +}); diff --git a/src/middleware/withLogging.tsx b/src/middleware/withLogging.tsx new file mode 100644 index 000000000..ef9b9df59 --- /dev/null +++ b/src/middleware/withLogging.tsx @@ -0,0 +1,39 @@ +import type { Logger } from "../logging"; +import type { Middleware } from "../router"; +import { LoggerKey, PathKey } from "../router"; + +interface WithLoggingConfig { + logger: Logger; +} + +/** + * Middleware that creates a child logger bound to the current command path + * and logs execution start, success, and failure. + * + * @param config - Contains the root {@link Logger} to derive children from. + */ +export function withLogging(config: WithLoggingConfig): Middleware { + return (h) => ({ + name: () => h.name(), + description: () => h.description(), + flags: () => h.flags(), + arguments: () => h.arguments(), + children: () => h.children(), + handle: async (ctx, flags, args) => { + const commandPath = ctx.require(PathKey); + const logger = config.logger.child({ commandPath }); + try { + // TODO: ensure sensitive fields are redacted from flags/args. + logger.child({ flags, args }).debug("executing command"); + await h.handle(ctx.withValue(LoggerKey, logger), flags, args); + logger.debug("command executed successfully"); + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)); + logger + .child({ errorName: error.name, errorMessage: error.message, stack: error.stack ?? "" }) + .error("command failed"); + throw err; + } + }, + }); +} diff --git a/src/middleware/withRegion.test.tsx b/src/middleware/withRegion.test.tsx index 70113d5ca..93a15d8ac 100644 --- a/src/middleware/withRegion.test.tsx +++ b/src/middleware/withRegion.test.tsx @@ -4,6 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { createRootHandler } from "../handlers"; import { TestCoreClient, testIO } from "../testing"; +import { createSilentLogger } from "../testing/"; // writeConfigFile writes an AWS shared-config file with the given contents to a // fresh temp dir and returns its path, for exercising the config-file region tier. @@ -23,7 +24,7 @@ function writeConfigFile(contents: string): string { // opening the TUI) and returns the region the handler passed to Core. async function resolvedRegion(args: string[]): Promise { const core = new TestCoreClient(); - const root = createRootHandler(core, testIO().io); + const root = createRootHandler(core, { io: testIO().io, logger: createSilentLogger() }); await root.route(["node", "agentcore", "harness", "list", "--json", ...args]); const call = core.harness.calls.at(-1); const options = call?.args[2] as { region: string }; diff --git a/src/router/index.tsx b/src/router/index.tsx index d81544d2d..98f581fa8 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -3,6 +3,7 @@ export { compile, CommandKey, PathKey, + LoggerKey, type DefaultHandle, type DefaultHandlerProvider, isDefaultHandlerProvider, diff --git a/src/router/router.tsx b/src/router/router.tsx index 13972146d..8d520dbf9 100644 --- a/src/router/router.tsx +++ b/src/router/router.tsx @@ -5,12 +5,15 @@ import { applyGlobalFlags, formatParameterDetails, parseFlags, toOption } from " import { parseArguments, toCommanderArgument } from "./args"; import { Command } from "commander"; +import type { Logger } from "../logging"; // CommandKey exposes the Commander Command for the executing leaf via context. export const CommandKey: ContextKey = contextKey("commander.command"); // PathKey exposes the path to the executing leaf via context. export const PathKey: ContextKey = contextKey("path"); +export const LoggerKey = contextKey("logger"); + // DefaultHandle runs when a group is selected without a subcommand (e.g. // `agentcore` or `agentcore harness`). It reads group-level/global flags from the // context; own flags/arguments are not supported, so it receives empty objects. diff --git a/src/runnable/index.tsx b/src/runnable/index.tsx index 626a21c2a..b4a7f84ac 100644 --- a/src/runnable/index.tsx +++ b/src/runnable/index.tsx @@ -27,9 +27,7 @@ export async function runWithExitCode( try { await fn(argv); return ExitCode.SUCCESS; - } catch (error: unknown) { - const message = error instanceof Error ? error.message : String(error); - console.error(`Error: ${message}`); + } catch { return ExitCode.FAILURE; } } diff --git a/src/testing/index.tsx b/src/testing/index.tsx index 23c118c7e..30ee08187 100644 --- a/src/testing/index.tsx +++ b/src/testing/index.tsx @@ -1,15 +1,15 @@ export { parse, stringify } from "./serialization"; export { fixtureFactories, isRecording, matchGolden } from "./fixtures"; export { testIO, type TestIO } from "./testIO"; +export { tick, waitFor } from "./timing"; export { TestCoreClient, TestHarnessClient, type RecordedCall } from "./TestCoreClient"; export { StreamController } from "./StreamController"; export { renderScreen, cleanupScreens, keys, - tick, - waitFor, waitForText, type RenderScreenOptions, type RenderScreenResult, } from "./renderScreen"; +export { createSilentLogger, assertLogsMatch, type LogQuery } from "./logging"; diff --git a/src/testing/logging.tsx b/src/testing/logging.tsx new file mode 100644 index 000000000..ba4e56521 --- /dev/null +++ b/src/testing/logging.tsx @@ -0,0 +1,90 @@ +import { type Logger } from "../logging"; +import { readFile, readdir } from "node:fs/promises"; +import { join } from "node:path"; +import { waitFor } from "./timing"; + +function parseJSONLines(content: string) { + return content + .trim() + .split("\n") + .filter((l) => l.length > 0) + .map((l) => JSON.parse(l)); +} + +/** + * Find and read the first `.log` file in a directory. + * Returns the file content, or an empty string if no log file exists yet. + */ +export async function readLogFile(dir: string): Promise { + const files = await readdir(dir).catch(() => []); + const logFile = files.find((f) => f.endsWith(".log")); + if (!logFile) return ""; + return readFile(join(dir, logFile), "utf-8"); +} + +const noop = () => {}; + +/** + * Create a silent logger that discards all output. + */ +export function createSilentLogger(): Logger { + const silent: Logger = { + debug: noop, + info: noop, + warn: noop, + error: noop, + child: () => silent, + }; + return silent; +} + +/** A predicate over parsed log lines with an optional exact match count. */ +export interface LogQuery { + filter: (log: object) => boolean; + expectedCount?: number; +} + +/** + * Asserts that log lines in {@link dir} satisfy all provided queries. + * + * @param dir - Directory containing `.log` files (as created by `createFileLogger`). + * @param queries - One or more queries to assert against the log lines. + * @param options.timeoutMs - Max time to wait in ms (default: 2000). + */ +export async function assertLogsMatch( + dir: string, + queries: LogQuery[], + options?: { timeoutMs?: number }, +): Promise { + let lastResults: ReturnType = []; + + // pino-roll writes via async worker threads, so logs may not be flushed to + // disk immediately. Poll until all query conditions are satisfied. + try { + await waitFor(async () => { + const content = await readLogFile(dir); + if (!content.trim()) return false; + const lines = parseJSONLines(content); + lastResults = evaluateQueries(lines, queries); + return lastResults.every((r) => r.passed); + }, options?.timeoutMs ?? 2000); + } catch { + const failures = lastResults + .filter((r) => !r.passed) + .map((r) => { + const expected = + r.query.expectedCount != null ? `exactly ${r.query.expectedCount}` : "at least 1"; + return ` query ${r.index}: expected ${expected}, found ${r.actual}`; + }); + + throw new Error(`assertLogsMatch timed out. Failed queries:\n${failures.join("\n")}`); + } +} + +function evaluateQueries(lines: object[], queries: LogQuery[]) { + return queries.map((query, index) => { + const actual = lines.filter(query.filter).length; + const passed = query.expectedCount != null ? actual === query.expectedCount : actual > 0; + return { query, index, actual, passed }; + }); +} diff --git a/src/testing/renderScreen.tsx b/src/testing/renderScreen.tsx index 9d3f6baa9..18973cce1 100644 --- a/src/testing/renderScreen.tsx +++ b/src/testing/renderScreen.tsx @@ -8,6 +8,8 @@ import { createRootHandler } from "../handlers"; import { Root } from "../components/Root"; import { TestCoreClient } from "./TestCoreClient"; import { testIO } from "./testIO"; +import { tick, waitFor } from "./timing"; +import { createSilentLogger } from "./logging"; // TUI test harness. // @@ -26,7 +28,10 @@ import { testIO } from "./testIO"; // (region/json/debug), and a no-op JsonRenderer. Compiling the real handler tree // keeps the command menus faithful to the production command structure. function baseContext(core: TestCoreClient): Context { - const rootCommand = compile(createRootHandler(core, testIO().io), ValueContext.EmptyContext()); + const rootCommand = compile( + createRootHandler(core, { io: testIO().io, logger: createSilentLogger() }), + ValueContext.EmptyContext(), + ); return ValueContext.EmptyContext() .withValue(CommandKey, rootCommand) @@ -128,25 +133,6 @@ export function renderScreen(path: string, options: RenderScreenOptions = {}): R }; } -// tick waits a macrotask so react-query state transitions (pending -> settled) -// and Ink re-renders flush before assertions. -export function tick(ms = 0): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -// waitFor polls `predicate` until it returns true (or the timeout elapses), -// ticking between attempts so pending renders/queries flush. Use it instead of a -// fixed sleep so tests are robust to timing: assert on the outcome, not a delay. -export async function waitFor(predicate: () => boolean, timeoutMs = 1000): Promise { - const step = 5; - let waited = 0; - while (!predicate()) { - if (waited >= timeoutMs) throw new Error("waitFor: condition not met before timeout"); - await tick(step); - waited += step; - } -} - // waitForText resolves once `text` appears in the latest frame. export function waitForText( lastFrame: () => string | undefined, diff --git a/src/testing/timing.tsx b/src/testing/timing.tsx new file mode 100644 index 000000000..2d7b62494 --- /dev/null +++ b/src/testing/timing.tsx @@ -0,0 +1,27 @@ +export function tick(ms = 0): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Polls {@link predicate} until it returns true or the timeout elapses, + * ticking between attempts so pending renders/queries flush. + * + * Use instead of a fixed sleep so tests are robust to timing: assert on the + * outcome, not a delay. + * + * @param predicate - Condition to wait for. + * @param timeoutMs - Maximum time to wait in milliseconds (default 1000). + * @throws If the predicate is not satisfied before the timeout. + */ +export async function waitFor( + predicate: () => boolean | Promise, + timeoutMs = 1000, +): Promise { + const step = 5; + let waited = 0; + while (!(await predicate())) { + if (waited >= timeoutMs) throw new Error("waitFor: condition not met before timeout"); + await tick(step); + waited += step; + } +} diff --git a/src/tui/index.tsx b/src/tui/index.tsx index 03025066c..9c6f91ec2 100644 --- a/src/tui/index.tsx +++ b/src/tui/index.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { render } from "ink"; import { Root } from "../components/Root"; import { diff --git a/src/tui/tui.test.tsx b/src/tui/tui.test.tsx index bb93a1875..c018fb748 100644 --- a/src/tui/tui.test.tsx +++ b/src/tui/tui.test.tsx @@ -1,7 +1,7 @@ import { test, expect, describe } from "bun:test"; import { createRootHandler } from "../handlers"; import { renderJson } from "./index"; -import { TestCoreClient, testIO } from "../testing"; +import { createSilentLogger, TestCoreClient, testIO } from "../testing"; describe("renderJson", () => { test("pretty-prints a value as indented JSON to the given writer", () => { @@ -18,7 +18,10 @@ describe("--json short-circuits the TUI", () => { // mount Ink against a non-TTY stdin. async function runRoot(args: string[]): Promise { const io = testIO(); - const root = createRootHandler(new TestCoreClient(), io.io); + const root = createRootHandler(new TestCoreClient(), { + io: io.io, + logger: createSilentLogger(), + }); await root.route(["node", "agentcore", ...args, "--json"]); return io.stdout(); }