Compare commits

...

5 Commits
v1.0.0 ... main

Author SHA1 Message Date
eb9c1c4a46
chore: add sonar workflow
All checks were successful
Node.js CI / Lint and Test (push) Successful in 57s
Code Analysis / SonarQube (push) Successful in 1m10s
2025-02-26 13:28:33 -08:00
bb1c327160 feat: error handling (#3)
All checks were successful
Node.js CI / Lint and Test (push) Successful in 38s
### Explanation

_No response_

### Issue

_No response_

### Attestations

- [x] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [x] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [x] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [ ] I have pinned the dependencies to a specific patch version.

### Style

- [x] I have run the linter and resolved any errors.
- [x] My pull request uses an appropriate title, matching the conventional commit standards.
- [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: #3
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
2025-02-10 21:47:13 -08:00
c63003bb75
release: v1.1.0
All checks were successful
Node.js CI / Lint and Test (push) Successful in 37s
2025-02-10 21:12:40 -08:00
95976b8b01 feat: start logging token usage and subscriptions (#2)
All checks were successful
Node.js CI / Lint and Test (push) Successful in 38s
### Explanation

_No response_

### Issue

_No response_

### Attestations

- [x] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [x] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [x] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [x] I have pinned the dependencies to a specific patch version.

### Style

- [x] I have run the linter and resolved any errors.
- [x] My pull request uses an appropriate title, matching the conventional commit standards.
- [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [x] All new and existing tests pass locally with my changes.
- [x] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

Minor - My pull request introduces a new non-breaking feature.

Reviewed-on: #2
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
2025-02-10 21:11:15 -08:00
4218596559
fix: update html
All checks were successful
Node.js CI / Lint and Test (push) Successful in 38s
2025-02-10 18:11:56 -08:00
17 changed files with 448 additions and 438 deletions

View File

@ -0,0 +1,34 @@
name: Code Analysis
on:
push:
branches:
- main
jobs:
sonar:
name: SonarQube
steps:
- name: Checkout Source Files
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: SonarCube Scan
uses: SonarSource/sonarqube-scan-action@v4
timeout-minutes: 10
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: "https://quality.nhcarrigan.com"
with:
args: >
-Dsonar.sources=.
-Dsonar.projectKey=cordelia-taryne
- name: SonarQube Quality Gate check
uses: sonarsource/sonarqube-quality-gate-action@v1
with:
pollingTimeoutSec: 600
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: "https://quality.nhcarrigan.com"

View File

@ -1,6 +1,6 @@
{ {
"name": "cordelia-taryne", "name": "cordelia-taryne",
"version": "1.0.0", "version": "1.1.0",
"description": "An AI-powered multi-purpose assistant for Discord.", "description": "An AI-powered multi-purpose assistant for Discord.",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@ -22,8 +22,8 @@
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "0.36.3", "@anthropic-ai/sdk": "0.36.3",
"@nhcarrigan/logger": "1.0.0",
"discord.js": "14.18.0", "discord.js": "14.18.0",
"fastify": "5.2.1", "fastify": "5.2.1"
"winston": "3.17.0"
} }
} }

206
pnpm-lock.yaml generated
View File

@ -11,15 +11,15 @@ importers:
'@anthropic-ai/sdk': '@anthropic-ai/sdk':
specifier: 0.36.3 specifier: 0.36.3
version: 0.36.3 version: 0.36.3
'@nhcarrigan/logger':
specifier: 1.0.0
version: 1.0.0
discord.js: discord.js:
specifier: 14.18.0 specifier: 14.18.0
version: 14.18.0 version: 14.18.0
fastify: fastify:
specifier: 5.2.1 specifier: 5.2.1
version: 5.2.1 version: 5.2.1
winston:
specifier: 3.17.0
version: 3.17.0
devDependencies: devDependencies:
'@nhcarrigan/eslint-config': '@nhcarrigan/eslint-config':
specifier: 5.1.0 specifier: 5.1.0
@ -50,13 +50,6 @@ packages:
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@colors/colors@1.6.0':
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
engines: {node: '>=0.1.90'}
'@dabh/diagnostics@2.0.3':
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
'@discordjs/builders@1.10.1': '@discordjs/builders@1.10.1':
resolution: {integrity: sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==} resolution: {integrity: sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==}
engines: {node: '>=16.11.0'} engines: {node: '>=16.11.0'}
@ -347,6 +340,9 @@ packages:
typescript: '>=5' typescript: '>=5'
vitest: '>=2' vitest: '>=2'
'@nhcarrigan/logger@1.0.0':
resolution: {integrity: sha512-2e19Bie+ZKb6yKPKjhawqsENkhHatYkvBAmFZx9eToOXdOca+CYi51tldRMtejg6e0+4hOOf2bo5zdBQKmH0dw==}
'@nhcarrigan/typescript-config@4.0.0': '@nhcarrigan/typescript-config@4.0.0':
resolution: {integrity: sha512-969HVha7A/Sg77fuMwOm6p14a+7C5iE6g55OD71srqwKIgksQl+Ex/hAI/pyzTQFDQ/FBJbpnHlR4Ov25QV/rw==} resolution: {integrity: sha512-969HVha7A/Sg77fuMwOm6p14a+7C5iE6g55OD71srqwKIgksQl+Ex/hAI/pyzTQFDQ/FBJbpnHlR4Ov25QV/rw==}
engines: {node: '20', pnpm: '9'} engines: {node: '20', pnpm: '9'}
@ -506,9 +502,6 @@ packages:
'@types/normalize-package-data@2.4.4': '@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
'@types/ws@8.5.14': '@types/ws@8.5.14':
resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==}
@ -752,9 +745,6 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
asynckit@0.4.0: asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@ -834,28 +824,13 @@ packages:
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
engines: {node: '>=4'} engines: {node: '>=4'}
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
color-convert@2.0.1: color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
color-name@1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
color@3.2.1:
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
colorspace@1.1.4:
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
combined-stream@1.0.8: combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -952,9 +927,6 @@ packages:
electron-to-chromium@1.5.97: electron-to-chromium@1.5.97:
resolution: {integrity: sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==} resolution: {integrity: sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==}
enabled@2.0.0:
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
error-ex@1.3.2: error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@ -1173,9 +1145,6 @@ packages:
fastq@1.19.0: fastq@1.19.0:
resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
fecha@4.2.3:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@ -1203,9 +1172,6 @@ packages:
flatted@3.3.2: flatted@3.3.2:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
for-each@0.3.4: for-each@0.3.4:
resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==} resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1337,9 +1303,6 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'} engines: {node: '>=8'}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
internal-slot@1.1.0: internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1355,9 +1318,6 @@ packages:
is-arrayish@0.2.1: is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
is-async-function@2.1.1: is-async-function@2.1.1:
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1430,10 +1390,6 @@ packages:
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
is-string@1.1.1: is-string@1.1.1:
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1517,9 +1473,6 @@ packages:
keyv@4.5.4: keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
kuler@2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
levn@0.4.1: levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -1547,10 +1500,6 @@ packages:
lodash@4.17.21: lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
logform@2.7.0:
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
engines: {node: '>= 12.0.0'}
loose-envify@1.4.0: loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true hasBin: true
@ -1664,9 +1613,6 @@ packages:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
one-time@1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -1807,10 +1753,6 @@ packages:
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
engines: {node: '>=8'} engines: {node: '>=8'}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
real-require@0.2.0: real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'} engines: {node: '>= 12.13.0'}
@ -1875,9 +1817,6 @@ packages:
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1951,9 +1890,6 @@ packages:
siginfo@2.0.0: siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
slash@3.0.0: slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1987,9 +1923,6 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'} engines: {node: '>= 10.x'}
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
stackback@0.0.2: stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@ -2015,9 +1948,6 @@ packages:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-bom@3.0.0: strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -2042,9 +1972,6 @@ packages:
resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
text-hex@1.0.0:
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
thread-stream@3.1.0: thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
@ -2077,10 +2004,6 @@ packages:
tr46@0.0.3: tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
triple-beam@1.4.1:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'}
ts-api-utils@1.4.3: ts-api-utils@1.4.3:
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -2162,9 +2085,6 @@ packages:
uri-js@4.4.1: uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
validate-npm-package-license@3.0.4: validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@ -2277,14 +2197,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
hasBin: true hasBin: true
winston-transport@4.9.0:
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
engines: {node: '>= 12.0.0'}
winston@3.17.0:
resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==}
engines: {node: '>= 12.0.0'}
word-wrap@1.2.5: word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2327,14 +2239,6 @@ snapshots:
'@babel/helper-validator-identifier@7.25.9': {} '@babel/helper-validator-identifier@7.25.9': {}
'@colors/colors@1.6.0': {}
'@dabh/diagnostics@2.0.3':
dependencies:
colorspace: 1.1.4
enabled: 2.0.0
kuler: 2.0.0
'@discordjs/builders@1.10.1': '@discordjs/builders@1.10.1':
dependencies: dependencies:
'@discordjs/formatters': 0.6.0 '@discordjs/formatters': 0.6.0
@ -2588,6 +2492,8 @@ snapshots:
- eslint-import-resolver-webpack - eslint-import-resolver-webpack
- supports-color - supports-color
'@nhcarrigan/logger@1.0.0': {}
'@nhcarrigan/typescript-config@4.0.0(typescript@5.7.3)': '@nhcarrigan/typescript-config@4.0.0(typescript@5.7.3)':
dependencies: dependencies:
typescript: 5.7.3 typescript: 5.7.3
@ -2707,8 +2613,6 @@ snapshots:
'@types/normalize-package-data@2.4.4': {} '@types/normalize-package-data@2.4.4': {}
'@types/triple-beam@1.3.5': {}
'@types/ws@8.5.14': '@types/ws@8.5.14':
dependencies: dependencies:
'@types/node': 22.13.1 '@types/node': 22.13.1
@ -3033,8 +2937,6 @@ snapshots:
async-function@1.0.0: {} async-function@1.0.0: {}
async@3.2.6: {}
asynckit@0.4.0: {} asynckit@0.4.0: {}
atomic-sleep@1.0.0: {} atomic-sleep@1.0.0: {}
@ -3116,33 +3018,12 @@ snapshots:
dependencies: dependencies:
escape-string-regexp: 1.0.5 escape-string-regexp: 1.0.5
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
color-convert@2.0.1: color-convert@2.0.1:
dependencies: dependencies:
color-name: 1.1.4 color-name: 1.1.4
color-name@1.1.3: {}
color-name@1.1.4: {} color-name@1.1.4: {}
color-string@1.9.1:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
color@3.2.1:
dependencies:
color-convert: 1.9.3
color-string: 1.9.1
colorspace@1.1.4:
dependencies:
color: 3.2.1
text-hex: 1.0.0
combined-stream@1.0.8: combined-stream@1.0.8:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
@ -3245,8 +3126,6 @@ snapshots:
electron-to-chromium@1.5.97: {} electron-to-chromium@1.5.97: {}
enabled@2.0.0: {}
error-ex@1.3.2: error-ex@1.3.2:
dependencies: dependencies:
is-arrayish: 0.2.1 is-arrayish: 0.2.1
@ -3650,8 +3529,6 @@ snapshots:
dependencies: dependencies:
reusify: 1.0.4 reusify: 1.0.4
fecha@4.2.3: {}
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
dependencies: dependencies:
flat-cache: 4.0.1 flat-cache: 4.0.1
@ -3683,8 +3560,6 @@ snapshots:
flatted@3.3.2: {} flatted@3.3.2: {}
fn.name@1.1.0: {}
for-each@0.3.4: for-each@0.3.4:
dependencies: dependencies:
is-callable: 1.2.7 is-callable: 1.2.7
@ -3818,8 +3693,6 @@ snapshots:
indent-string@4.0.0: {} indent-string@4.0.0: {}
inherits@2.0.4: {}
internal-slot@1.1.0: internal-slot@1.1.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@ -3836,8 +3709,6 @@ snapshots:
is-arrayish@0.2.1: {} is-arrayish@0.2.1: {}
is-arrayish@0.3.2: {}
is-async-function@2.1.1: is-async-function@2.1.1:
dependencies: dependencies:
async-function: 1.0.0 async-function: 1.0.0
@ -3915,8 +3786,6 @@ snapshots:
dependencies: dependencies:
call-bound: 1.0.3 call-bound: 1.0.3
is-stream@2.0.1: {}
is-string@1.1.1: is-string@1.1.1:
dependencies: dependencies:
call-bound: 1.0.3 call-bound: 1.0.3
@ -3997,8 +3866,6 @@ snapshots:
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
kuler@2.0.0: {}
levn@0.4.1: levn@0.4.1:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
@ -4026,15 +3893,6 @@ snapshots:
lodash@4.17.21: {} lodash@4.17.21: {}
logform@2.7.0:
dependencies:
'@colors/colors': 1.6.0
'@types/triple-beam': 1.3.5
fecha: 4.2.3
ms: 2.1.3
safe-stable-stringify: 2.5.0
triple-beam: 1.4.1
loose-envify@1.4.0: loose-envify@1.4.0:
dependencies: dependencies:
js-tokens: 4.0.0 js-tokens: 4.0.0
@ -4138,10 +3996,6 @@ snapshots:
on-exit-leak-free@2.1.2: {} on-exit-leak-free@2.1.2: {}
one-time@1.0.0:
dependencies:
fn.name: 1.1.0
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@ -4280,12 +4134,6 @@ snapshots:
parse-json: 5.2.0 parse-json: 5.2.0
type-fest: 0.6.0 type-fest: 0.6.0
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
real-require@0.2.0: {} real-require@0.2.0: {}
reflect.getprototypeof@1.0.10: reflect.getprototypeof@1.0.10:
@ -4375,8 +4223,6 @@ snapshots:
has-symbols: 1.1.0 has-symbols: 1.1.0
isarray: 2.0.5 isarray: 2.0.5
safe-buffer@5.2.1: {}
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@ -4462,10 +4308,6 @@ snapshots:
siginfo@2.0.0: {} siginfo@2.0.0: {}
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
slash@3.0.0: {} slash@3.0.0: {}
slashes@3.0.12: {} slashes@3.0.12: {}
@ -4497,8 +4339,6 @@ snapshots:
split2@4.2.0: {} split2@4.2.0: {}
stack-trace@0.0.10: {}
stackback@0.0.2: {} stackback@0.0.2: {}
std-env@3.8.0: {} std-env@3.8.0: {}
@ -4547,10 +4387,6 @@ snapshots:
define-properties: 1.2.1 define-properties: 1.2.1
es-object-atoms: 1.1.1 es-object-atoms: 1.1.1
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-bom@3.0.0: {} strip-bom@3.0.0: {}
strip-indent@3.0.0: strip-indent@3.0.0:
@ -4570,8 +4406,6 @@ snapshots:
'@pkgr/core': 0.1.1 '@pkgr/core': 0.1.1
tslib: 2.8.1 tslib: 2.8.1
text-hex@1.0.0: {}
thread-stream@3.1.0: thread-stream@3.1.0:
dependencies: dependencies:
real-require: 0.2.0 real-require: 0.2.0
@ -4594,8 +4428,6 @@ snapshots:
tr46@0.0.3: {} tr46@0.0.3: {}
triple-beam@1.4.1: {}
ts-api-utils@1.4.3(typescript@5.7.3): ts-api-utils@1.4.3(typescript@5.7.3):
dependencies: dependencies:
typescript: 5.7.3 typescript: 5.7.3
@ -4683,8 +4515,6 @@ snapshots:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
util-deprecate@1.0.2: {}
validate-npm-package-license@3.0.4: validate-npm-package-license@3.0.4:
dependencies: dependencies:
spdx-correct: 3.2.0 spdx-correct: 3.2.0
@ -4816,26 +4646,6 @@ snapshots:
siginfo: 2.0.0 siginfo: 2.0.0
stackback: 0.0.2 stackback: 0.0.2
winston-transport@4.9.0:
dependencies:
logform: 2.7.0
readable-stream: 3.6.2
triple-beam: 1.4.1
winston@3.17.0:
dependencies:
'@colors/colors': 1.6.0
'@dabh/diagnostics': 2.0.3
async: 3.2.6
is-stream: 2.0.1
logform: 2.7.0
one-time: 1.0.0
readable-stream: 3.6.2
safe-stable-stringify: 2.5.0
stack-trace: 0.0.10
triple-beam: 1.4.1
winston-transport: 4.9.0
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
ws@8.18.0: {} ws@8.18.0: {}

View File

@ -1,2 +1,3 @@
DISCORD_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/discord_token" DISCORD_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/discord_token"
AI_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/ai_token" AI_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/ai_token"
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"

View File

@ -12,7 +12,7 @@ import { proofread } from "./modules/proofread.js";
import { query } from "./modules/query.js"; import { query } from "./modules/query.js";
import { summarise } from "./modules/summarise.js"; import { summarise } from "./modules/summarise.js";
import { instantiateServer } from "./server/serve.js"; import { instantiateServer } from "./server/serve.js";
import { logHandler } from "./utils/logHandler.js"; import { logger } from "./utils/logger.js";
const commands: Record< const commands: Record<
string, string,
@ -28,6 +28,22 @@ const commands: Record<
"summarise": summarise, "summarise": summarise,
}; };
process.on("unhandledRejection", (error) => {
if (error instanceof Error) {
void logger.error("Unhandled Rejection", error);
return;
}
void logger.error("unhandled rejection", new Error(String(error)));
});
process.on("uncaughtException", (error) => {
if (error instanceof Error) {
void logger.error("Uncaught Exception", error);
return;
}
void logger.error("uncaught exception", new Error(String(error)));
});
const client = new Client({ const client = new Client({
intents: [], intents: [],
}); });
@ -41,8 +57,16 @@ client.on(Events.InteractionCreate, (interaction) => {
} }
}); });
client.on(Events.EntitlementCreate, (entitlement) => {
void logger.log("info", `User ${entitlement.userId} has subscribed!`);
});
client.on(Events.EntitlementDelete, (entitlement) => {
void logger.log("info", `User ${entitlement.userId} has unsubscribed... :c`);
});
client.on(Events.ClientReady, () => { client.on(Events.ClientReady, () => {
logHandler.info("Bot is ready."); void logger.log("debug", "Bot is ready.");
}); });
instantiateServer(); instantiateServer();

View File

@ -13,56 +13,66 @@ import {
MessageFlags, MessageFlags,
type ChatInputCommandInteraction, type ChatInputCommandInteraction,
} from "discord.js"; } from "discord.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Responds with information about the bot. * Responds with information about the bot.
* @param interaction -- The interaction payload from Discord. * @param interaction -- The interaction payload from Discord.
*/ */
// eslint-disable-next-line max-lines-per-function -- Refactor at a later time.
export const about = async( export const about = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); try {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
const version = process.env.npm_package_version ?? "Unknown"; const version = process.env.npm_package_version ?? "Unknown";
const commit = execSync("git rev-parse --short HEAD").toString(). const commit = execSync("git rev-parse --short HEAD").toString().
trim(); trim();
const embed = new EmbedBuilder(); const embed = new EmbedBuilder();
embed.setTitle("About Cordelia Taryne"); embed.setTitle("About Cordelia Taryne");
embed.setDescription( embed.setDescription(
// eslint-disable-next-line stylistic/max-len -- It's a long string. // eslint-disable-next-line stylistic/max-len -- It's a long string.
"Cordelia Taryne is a Discord bot that uses Anthropic to provide assistive features. She is developed by NHCarrigan. To use the bot, type `/` and select one of her commands!", "Cordelia Taryne is a Discord bot that uses Anthropic to provide assistive features. She is developed by NHCarrigan. To use the bot, type `/` and select one of her commands!",
); );
embed.addFields( embed.addFields(
{ {
name: "Running Version", name: "Running Version",
value: version, value: version,
}, },
{ {
name: "Current Commit", name: "Current Commit",
value: commit, value: commit,
}, },
); );
const supportButton = new ButtonBuilder(). const supportButton = new ButtonBuilder().
setLabel("Need help?"). setLabel("Need help?").
setStyle(ButtonStyle.Link). setStyle(ButtonStyle.Link).
setURL("https://chat.nhcarrigan.com"); setURL("https://chat.nhcarrigan.com");
const sourceButton = new ButtonBuilder(). const sourceButton = new ButtonBuilder().
setLabel("Source Code"). setLabel("Source Code").
setStyle(ButtonStyle.Link). setStyle(ButtonStyle.Link).
setURL("https://git.nhcarrigan.com/nhcarrigan/aria-iuvo"); setURL("https://git.nhcarrigan.com/nhcarrigan/aria-iuvo");
const subscribeButton = new ButtonBuilder(). const subscribeButton = new ButtonBuilder().
setStyle(ButtonStyle.Premium). setStyle(ButtonStyle.Premium).
setSKUId("1338672773261951026"); setSKUId("1338672773261951026");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents( const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
supportButton, supportButton,
sourceButton, sourceButton,
subscribeButton, subscribeButton,
); );
await interaction.editReply({ await interaction.editReply({
components: [ row ], components: [ row ],
embeds: [ embed ], embeds: [ embed ],
}); });
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("about command", error);
}
}
}; };

View File

@ -6,7 +6,10 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
import type { ImageBlockParam } from "@anthropic-ai/sdk/resources/index.js"; import type { ImageBlockParam } from "@anthropic-ai/sdk/resources/index.js";
const isValidContentType = ( const isValidContentType = (
@ -30,83 +33,93 @@ const isValidContentType = (
export const alt = async( export const alt = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); try {
const sub = await isSubscribed(interaction); await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
if (!sub) { const sub = await isSubscribed(interaction);
return; if (!sub) {
} return;
}
const image = interaction.options.getAttachment("image", true); const image = interaction.options.getAttachment("image", true);
const { contentType, height, width, size, url } = image; const { contentType, height, width, size, url } = image;
// Claude supports JPG, PNG, GIF, WEBP // Claude supports JPG, PNG, GIF, WEBP
if ( if (
contentType === null contentType === null
|| !isValidContentType(contentType) || !isValidContentType(contentType)
|| height === null || height === null
|| width === null || width === null
) { ) {
await interaction.editReply({ await interaction.editReply({
content: "That does not appear to be a valid image.", content: "That does not appear to be a valid image.",
}); });
return; return;
} }
// Max file size is 5MB // Max file size is 5MB
if (size > 5 * 1024 * 1024) { if (size > 5 * 1024 * 1024) {
await interaction.editReply({ await interaction.editReply({
content: content:
// eslint-disable-next-line stylistic/max-len -- It's a long string. // eslint-disable-next-line stylistic/max-len -- It's a long string.
"That image is too large. Please provide an image that is less than 5MB.", "That image is too large. Please provide an image that is less than 5MB.",
}); });
return; return;
} }
// Max dimensions are 8000px // Max dimensions are 8000px
if (height > 8000 || width > 8000) { if (height > 8000 || width > 8000) {
await interaction.editReply({ await interaction.editReply({
content: content:
// eslint-disable-next-line stylistic/max-len -- It's a long string. // eslint-disable-next-line stylistic/max-len -- It's a long string.
"That image is too large. Please provide an image that is less than 8000 pixels high or wide.", "That image is too large. Please provide an image that is less than 8000 pixels high or wide.",
}); });
return; return;
} }
const downloadRequest = await fetch(url); const downloadRequest = await fetch(url);
const blob = await downloadRequest.arrayBuffer(); const blob = await downloadRequest.arrayBuffer();
const base64 = Buffer.from(blob).toString("base64"); const base64 = Buffer.from(blob).toString("base64");
const messages = await ai.messages.create({ const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK. // eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000, max_tokens: 2000,
messages: [ messages: [
{ {
content: [ content: [
{ {
source: { source: {
data: base64, data: base64,
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required property syntax for SDK. // eslint-disable-next-line @typescript-eslint/naming-convention -- Required property syntax for SDK.
media_type: contentType, media_type: contentType,
type: "base64", type: "base64",
},
type: "image",
}, },
type: "image", ],
}, role: "user",
], },
role: "user", ],
}, model: "claude-3-5-sonnet-latest",
], system: `${personality} Your role in this conversation is to generate descriptive and accessible alt-text for the user's image. Be as descriptive as possible. Do not include ANYTHING in your response EXCEPT the actual alt-text. Wrap the text in a multi-line code block for easy copying.`,
model: "claude-3-5-sonnet-latest", temperature: 1,
system: `${personality} Your role in this conversation is to generate descriptive and accessible alt-text for the user's image. Be as descriptive as possible. Do not include ANYTHING in your response EXCEPT the actual alt-text. Wrap the text in a multi-line code block for easy copying.`, });
temperature: 1,
});
const response = messages.content.find((message) => { const response = messages.content.find((message) => {
return message.type === "text"; return message.type === "text";
}); });
await interaction.editReply( await interaction.editReply(
response?.text response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.", ?? "I'm sorry, I don't have an answer for that. Please try again later.",
); );
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "alt-text");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("alt-text command", error);
}
}
}; };

View File

@ -6,7 +6,10 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts an arbitrary code snippet from the user, then sends * Accepts an arbitrary code snippet from the user, then sends
@ -16,28 +19,38 @@ import { isSubscribed } from "../utils/isSubscribed.js";
export const evaluate = async( export const evaluate = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); try {
const sub = await isSubscribed(interaction); await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
if (!sub) { const sub = await isSubscribed(interaction);
return; if (!sub) {
} return;
}
const code = interaction.options.getString("code", true); const code = interaction.options.getString("code", true);
const messages = await ai.messages.create({ const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK. // eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000, max_tokens: 2000,
messages: [ { content: code, role: "user" } ], messages: [ { content: code, role: "user" } ],
model: "claude-3-5-sonnet-latest", model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to evaluate the user's code and provide the result. Wrap ONLY THE CODE RESULT in a multi-line code block for easy copying.`, system: `${personality} Your role in this conversation is to evaluate the user's code and provide the result. Wrap ONLY THE CODE RESULT in a multi-line code block for easy copying.`,
temperature: 1, temperature: 1,
}); });
const response = messages.content.find((message) => { const response = messages.content.find((message) => {
return message.type === "text"; return message.type === "text";
}); });
await interaction.editReply( await interaction.editReply(
response?.text response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.", ?? "I'm sorry, I don't have an answer for that. Please try again later.",
); );
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "evaluate");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("evaluate command", error);
}
}
}; };

View File

@ -6,7 +6,10 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts a text snippet from the user. Submits it to Anthropic * Accepts a text snippet from the user. Submits it to Anthropic
@ -16,29 +19,39 @@ import { isSubscribed } from "../utils/isSubscribed.js";
export const mood = async( export const mood = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); try {
const sub = await isSubscribed(interaction); await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
if (!sub) { const sub = await isSubscribed(interaction);
return; if (!sub) {
} return;
}
const prompt = interaction.options.getString("text", true); const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({ const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK. // eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000, max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ], messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest", model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to analyse the text the user provides for the overall sentiment and mood of the author.`, system: `${personality} Your role in this conversation is to analyse the text the user provides for the overall sentiment and mood of the author.`,
temperature: 1, temperature: 1,
}); });
const response = messages.content.find((message) => { const response = messages.content.find((message) => {
return message.type === "text"; return message.type === "text";
}); });
await interaction.editReply( await interaction.editReply(
response?.text response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.", ?? "I'm sorry, I don't have an answer for that. Please try again later.",
); );
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "mood");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("mood command", error);
}
}
}; };

View File

@ -6,7 +6,10 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts a text snippet from the user. Submits it to Anthropic * Accepts a text snippet from the user. Submits it to Anthropic
@ -16,29 +19,39 @@ import { isSubscribed } from "../utils/isSubscribed.js";
export const proofread = async( export const proofread = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); try {
const sub = await isSubscribed(interaction); await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
if (!sub) { const sub = await isSubscribed(interaction);
return; if (!sub) {
} return;
}
const prompt = interaction.options.getString("text", true); const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({ const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK. // eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000, max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ], messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest", model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to proofread the text the user has provided. You should identify spelling and grammatical errors using British English.`, system: `${personality} Your role in this conversation is to proofread the text the user has provided. You should identify spelling and grammatical errors using British English.`,
temperature: 1, temperature: 1,
}); });
const response = messages.content.find((message) => { const response = messages.content.find((message) => {
return message.type === "text"; return message.type === "text";
}); });
await interaction.editReply( await interaction.editReply(
response?.text response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.", ?? "I'm sorry, I don't have an answer for that. Please try again later.",
); );
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "proofread");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("proofread command", error);
}
}
}; };

View File

@ -6,7 +6,10 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts an arbitrary question from the user, then sends it to Anthropic * Accepts an arbitrary question from the user, then sends it to Anthropic
@ -16,29 +19,39 @@ import { isSubscribed } from "../utils/isSubscribed.js";
export const query = async( export const query = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); try {
const sub = await isSubscribed(interaction); await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
if (!sub) { const sub = await isSubscribed(interaction);
return; if (!sub) {
} return;
}
const prompt = interaction.options.getString("prompt", true); const prompt = interaction.options.getString("prompt", true);
const messages = await ai.messages.create({ const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK. // eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000, max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ], messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest", model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to answer the user's question to the best of your abilities. When possible, include links to relevant sources.`, system: `${personality} Your role in this conversation is to answer the user's question to the best of your abilities. When possible, include links to relevant sources.`,
temperature: 1, temperature: 1,
}); });
const response = messages.content.find((message) => { const response = messages.content.find((message) => {
return message.type === "text"; return message.type === "text";
}); });
await interaction.editReply( await interaction.editReply(
response?.text response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.", ?? "I'm sorry, I don't have an answer for that. Please try again later.",
); );
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "query");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("query command", error);
}
}
}; };

View File

@ -6,7 +6,10 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts a text snippet from the user. Submits it to Anthropic * Accepts a text snippet from the user. Submits it to Anthropic
@ -16,29 +19,39 @@ import { isSubscribed } from "../utils/isSubscribed.js";
export const summarise = async( export const summarise = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); try {
const sub = await isSubscribed(interaction); await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
if (!sub) { const sub = await isSubscribed(interaction);
return; if (!sub) {
} return;
}
const prompt = interaction.options.getString("text", true); const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({ const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK. // eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000, max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ], messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest", model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to summarise the text the user has provided. Your goal is to reach 250 words or less. Wrap ONLY THE SUMMARY in multi-line code block so it is easy to copy.`, system: `${personality} Your role in this conversation is to summarise the text the user has provided. Your goal is to reach 250 words or less. Wrap ONLY THE SUMMARY in multi-line code block so it is easy to copy.`,
temperature: 1, temperature: 1,
}); });
const response = messages.content.find((message) => { const response = messages.content.find((message) => {
return message.type === "text"; return message.type === "text";
}); });
await interaction.editReply( await interaction.editReply(
response?.text response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.", ?? "I'm sorry, I don't have an answer for that. Please try again later.",
); );
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "summarise");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("summarise command", error);
}
}
}; };

View File

@ -5,27 +5,27 @@
*/ */
import fastify from "fastify"; import fastify from "fastify";
import { logHandler } from "../utils/logHandler.js"; import { logger } from "../utils/logger.js";
const html = `<!DOCTYPE html> const html = `<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Aria Iuvo</title> <title>Cordelia Taryne</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Bot to translate your messages on Discord!" /> <meta name="description" content="AI-powered multi-purpose assistant for Discord!" />
<script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script> <script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script>
</head> </head>
<body> <body>
<main> <main>
<h1>Aria Iuvo</h1> <h1>Cordelia Taryne</h1>
<section> <section>
<p>Bot to translate your messages on Discord!</p> <p>AI-powered multi-purpose assistant for Discord!</p>
</section> </section>
<section> <section>
<h2>Links</h2> <h2>Links</h2>
<p> <p>
<a href="https://git.nhcarrigan.com/nhcarrigan/aria-iuvo"> <a href="https://git.nhcarrigan.com/nhcarrigan/cordelia-taryne">
<i class="fa-solid fa-code"></i> Source Code <i class="fa-solid fa-code"></i> Source Code
</a> </a>
</p> </p>
@ -60,12 +60,16 @@ export const instantiateServer = (): void => {
server.listen({ port: 5002 }, (error) => { server.listen({ port: 5002 }, (error) => {
if (error) { if (error) {
logHandler.error(error); void logger.error("instantiate server", error);
return; return;
} }
logHandler.info("Server listening on port 5002."); void logger.log("debug", "Server listening on port 5002.");
}); });
} catch (error) { } catch (error) {
logHandler.error(error); if (error instanceof Error) {
void logger.error("instantiate server", error);
return;
}
void logger.error("instantiate server", new Error("Unknown error"));
} }
}; };

View File

@ -0,0 +1,30 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { logger } from "./logger.js";
import type { Usage } from "@anthropic-ai/sdk/resources/index.js";
/**
* Calculates the cost of a command run by a user, and sends to
* our logging service.
* @param usage -- The usage payload from Anthropic.
* @param uuid -- The Discord ID of the user who ran the command.
* @param command -- The command that was run.
*/
export const calculateCost = async(
usage: Usage,
uuid: string,
command: string,
): Promise<void> => {
const inputCost = usage.input_tokens * (3 / 1_000_000);
const outputCost = usage.output_tokens * (15 / 1_000_000);
const totalCost = inputCost + outputCost;
await logger.log(
"info",
`User ${uuid} ran \`${command}\` which accepted ${usage.input_tokens.toString()} and generated ${usage.output_tokens.toString()}.
Total cost: ${totalCost.toLocaleString("en-GB", { currency: "USD", style: "currency" })}`,
);
};

View File

@ -1,31 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { createLogger, format, transports, config } from "winston";
const { combine, timestamp, colorize, printf } = format;
/**
* Standard log handler, using winston to wrap and format
* messages. Call with `logHandler.log(level, message)`.
* @param {string} level - The log level to use.
* @param {string} message - The message to log.
*/
export const logHandler = createLogger({
exitOnError: false,
format: combine(
timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
colorize(),
printf((info) => {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- Winston properties...
return `${info.level}: ${info.timestamp}: ${info.message}`;
}),
),
level: "silly",
levels: config.npm.levels,
transports: [ new transports.Console() ],
});

12
src/utils/logger.ts Normal file
View File

@ -0,0 +1,12 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Logger } from "@nhcarrigan/logger";
export const logger = new Logger(
"Cordelia Taryne",
process.env.LOG_TOKEN ?? "",
);

38
src/utils/replyToError.ts Normal file
View File

@ -0,0 +1,38 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
type ChatInputCommandInteraction,
type MessageContextMenuCommandInteraction,
} from "discord.js";
/**
* Responds to an interaction with a generic error message.
* @param interaction -- The interaction payload from Discord.
*/
export const replyToError = async(
interaction:
| ChatInputCommandInteraction
| MessageContextMenuCommandInteraction,
): Promise<void> => {
const button = new ButtonBuilder().setLabel("Need help?").
setStyle(ButtonStyle.Link).
setURL("https://chat.nhcarrigan.com");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(button);
if (interaction.deferred || interaction.replied) {
await interaction.editReply({
components: [ row ],
content: "An error occurred while running this command.",
});
return;
}
await interaction.reply({
components: [ row ],
content: "An error occurred while running this command.",
});
};