chore: initialize homebrew-magic-rpg monorepo
Consolidates the HbM: RPG project (formerly the loose hbm-books working folder) into a single repo. ObsidianNotes, foundry-lore, and foundry-system are wired in as submodules pointing at their existing Gitea repos. Loose tooling scripts are organized under tools/, obsolete/one-off scripts (fix_vault.py, generate_mocs.py, debug-*.ts, write_file_helper.py) and _books/ (superseded by ObsidianNotes) are excluded via .gitignore rather than deleted. foundryvtt-admin is excluded pending its own separate repo.
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/server-gdrive": "^2025.1.14",
|
||||
"googleapis": "^170.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@google-cloud/local-auth": ["@google-cloud/local-auth@3.0.1", "", { "dependencies": { "arrify": "^2.0.1", "google-auth-library": "^9.0.0", "open": "^7.0.3", "server-destroy": "^1.0.1" } }, "sha512-YJ3GFbksfHyEarbVHPSCzhKpjbnlAhdzg2SEf79l6ODukrSM1qUOqfopY232Xkw26huKSndyzmJz+A6b2WYn7Q=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.0.1", "", { "dependencies": { "content-type": "^1.0.5", "raw-body": "^3.0.0", "zod": "^3.23.8" } }, "sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive": ["@modelcontextprotocol/server-gdrive@2025.1.14", "", { "dependencies": { "@google-cloud/local-auth": "^3.0.1", "@modelcontextprotocol/sdk": "1.0.1", "googleapis": "^144.0.0" }, "bin": { "mcp-server-gdrive": "dist/index.js" } }, "sha512-MWrvgaHARzAsFEYpzOrG/ewYoWrDSvF1ybmW56cMIrnVQekTP0iGWKC/aEXVcc+V5hAmMGm7iABSJFNrBX9a0A=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
|
||||
|
||||
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
|
||||
|
||||
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
||||
|
||||
"googleapis": ["googleapis@170.0.0", "", { "dependencies": { "google-auth-library": "^10.2.0", "googleapis-common": "^8.0.0" } }, "sha512-UJz71WZ3ubMr4NhkEU+CFTS0CMrrXq+ltrFnAQo8Llf9M3cy0AIfKLyFQdUJyhqIpJ4jPW4SRCcBBntrLQ72/A=="],
|
||||
|
||||
"googleapis-common": ["googleapis-common@8.0.1", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" } }, "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
||||
|
||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
|
||||
|
||||
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||
|
||||
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"server-destroy": ["server-destroy@1.0.1", "", {}, "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="],
|
||||
|
||||
"uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@google-cloud/local-auth/google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis": ["googleapis@144.0.0", "", { "dependencies": { "google-auth-library": "^9.0.0", "googleapis-common": "^7.0.0" } }, "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw=="],
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@google-cloud/local-auth/google-auth-library/gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
|
||||
|
||||
"@google-cloud/local-auth/google-auth-library/gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
|
||||
|
||||
"@google-cloud/local-auth/google-auth-library/gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/googleapis-common": ["googleapis-common@7.2.0", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^6.0.3", "google-auth-library": "^9.7.0", "qs": "^6.7.0", "url-template": "^2.0.8", "uuid": "^9.0.0" } }, "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA=="],
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@google-cloud/local-auth/google-auth-library/gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"@google-cloud/local-auth/google-auth-library/gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/google-auth-library/gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/google-auth-library/gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/google-auth-library/gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/googleapis-common/gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/google-auth-library/gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/google-auth-library/gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
|
||||
|
||||
"@modelcontextprotocol/server-gdrive/googleapis/googleapis-common/gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* HbM: RPG v3 - Main CLI Tool
|
||||
*
|
||||
* Unified interface for content creation and management
|
||||
*/
|
||||
|
||||
import { GDriveClient } from './gdrive-client.js';
|
||||
import { ContentManager } from './content-manager.js';
|
||||
import { formatSpellForBook, spellTemplate } from './templates/spell.js';
|
||||
import { formatCreatureForBook, creatureTemplate } from './templates/creature.js';
|
||||
import { formatItemForBook, itemTemplate } from './templates/item.js';
|
||||
import { formatNPCForBook, npcTemplate } from './templates/npc.js';
|
||||
import { formatLocationForBook, locationTemplate } from './templates/location.js';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import readline from 'readline';
|
||||
|
||||
const BOOKS = {
|
||||
'podrecznik': 'HbM: RPG v3 - Podręcznik Gry',
|
||||
'magia': 'HbM: RPG v3 - Księga Magii',
|
||||
'przewodnik': 'HbM: RPG v3 - Przewodnik Ludzkości po Magicznym Świecie',
|
||||
'bestiariusz': 'HbM: RPG v3 - Bestiariusz',
|
||||
'arcanum': 'HbM: RPG v3 - Arcanum Sanguinis',
|
||||
'kult': 'HbM: RPG v3 - Chwała Szkarłatnemu Kultowi',
|
||||
'klatwa': 'HbM: RPG v3 - Klątwa Otchłani',
|
||||
};
|
||||
|
||||
async function prompt(question) {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
rl.question(question, answer => {
|
||||
rl.close();
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function printHelp() {
|
||||
console.log(`
|
||||
╔═══════════════════════════════════════════════════════════════════╗
|
||||
║ 🎲 HbM: RPG v3 - Content Creation Environment 🎲 ║
|
||||
╚═══════════════════════════════════════════════════════════════════╝
|
||||
|
||||
GOOGLE DRIVE COMMANDS:
|
||||
gdrive:auth Authenticate with Google Drive
|
||||
gdrive:list List all HbM books on Drive
|
||||
gdrive:sync Download all books to local cache
|
||||
gdrive:get <book> Get specific book content
|
||||
|
||||
CONTENT COMMANDS:
|
||||
new:spell Create a new spell
|
||||
new:creature Create a new creature/monster
|
||||
new:item Create a new magic item
|
||||
new:npc Create a new NPC
|
||||
new:location Create a new location
|
||||
|
||||
draft <book> <section> Create a new draft for a book section
|
||||
revision <book> Create a revision (copy-paste ready)
|
||||
|
||||
SEARCH & BROWSE:
|
||||
search <query> Search across all downloaded books
|
||||
list <type> List content (spells/creatures/items/npcs/locations)
|
||||
show <type> <name> Show specific content item
|
||||
|
||||
TEMPLATES:
|
||||
template:spell Show spell template structure
|
||||
template:creature Show creature template structure
|
||||
template:item Show item template structure
|
||||
template:npc Show NPC template structure
|
||||
template:location Show location template structure
|
||||
|
||||
BOOK SHORTCUTS:
|
||||
podrecznik → Podręcznik Gry (Game Manual)
|
||||
magia → Księga Magii (Magic Book)
|
||||
przewodnik → Przewodnik Ludzkości (Humanity's Guide)
|
||||
bestiariusz → Bestiariusz (Bestiary)
|
||||
arcanum → Arcanum Sanguinis
|
||||
kult → Chwała Szkarłatnemu Kultowi
|
||||
klatwa → Klątwa Otchłani
|
||||
|
||||
Examples:
|
||||
bun run hbm gdrive:sync
|
||||
bun run hbm search "ognista kula"
|
||||
bun run hbm new:spell
|
||||
bun run hbm draft magia "Nowy Czar"
|
||||
`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const command = args[0];
|
||||
|
||||
if (!command || command === 'help' || command === '--help') {
|
||||
await printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
const manager = new ContentManager();
|
||||
await manager.init();
|
||||
|
||||
// Google Drive commands
|
||||
if (command.startsWith('gdrive:')) {
|
||||
const gdrive = new GDriveClient();
|
||||
|
||||
switch (command) {
|
||||
case 'gdrive:auth':
|
||||
const code = args[1];
|
||||
await gdrive.initOAuth();
|
||||
if (code) {
|
||||
await gdrive.saveToken(code);
|
||||
} else {
|
||||
await gdrive.authenticate();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'gdrive:list':
|
||||
await gdrive.init();
|
||||
const books = await gdrive.searchBooks();
|
||||
console.log('\n📚 HbM: RPG v3 Books on Google Drive:\n');
|
||||
books.forEach(b => {
|
||||
const modified = new Date(b.modifiedTime).toLocaleDateString('pl-PL');
|
||||
console.log(` 📖 ${b.name}`);
|
||||
console.log(` Last modified: ${modified}\n`);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'gdrive:sync':
|
||||
await gdrive.init();
|
||||
console.log('\n🔄 Syncing books from Google Drive...\n');
|
||||
const results = await gdrive.downloadAllBooks();
|
||||
console.log(`\n✅ Downloaded ${results.length} books to _books/`);
|
||||
results.forEach(r => console.log(` 📖 ${r.name}`));
|
||||
break;
|
||||
|
||||
case 'gdrive:get':
|
||||
const pattern = args[1] || '';
|
||||
const bookName = BOOKS[pattern.toLowerCase()] || pattern;
|
||||
await gdrive.init();
|
||||
const book = await gdrive.downloadBook(bookName);
|
||||
console.log(book.text);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// New content commands
|
||||
if (command.startsWith('new:')) {
|
||||
const type = command.split(':')[1];
|
||||
const templates = {
|
||||
spell: { template: spellTemplate, formatter: formatSpellForBook },
|
||||
creature: { template: creatureTemplate, formatter: formatCreatureForBook },
|
||||
item: { template: itemTemplate, formatter: formatItemForBook },
|
||||
npc: { template: npcTemplate, formatter: formatNPCForBook },
|
||||
location: { template: locationTemplate, formatter: formatLocationForBook },
|
||||
};
|
||||
|
||||
if (!templates[type]) {
|
||||
console.log(`Unknown type: ${type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const name = await prompt(`Enter ${type} name: `);
|
||||
if (!name) {
|
||||
console.log('Name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = { ...templates[type].template, name };
|
||||
const filePath = await manager.saveContent(`${type}s`, name, data);
|
||||
|
||||
console.log(`\n✅ Created ${type}: ${filePath}`);
|
||||
console.log(`\nEdit the JSON file to add details, then run:`);
|
||||
console.log(` npm run hbm show ${type}s "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Template commands
|
||||
if (command.startsWith('template:')) {
|
||||
const type = command.split(':')[1];
|
||||
const templates = {
|
||||
spell: spellTemplate,
|
||||
creature: creatureTemplate,
|
||||
item: itemTemplate,
|
||||
npc: npcTemplate,
|
||||
location: locationTemplate,
|
||||
};
|
||||
|
||||
if (templates[type]) {
|
||||
console.log(`\n${type.toUpperCase()} TEMPLATE:\n`);
|
||||
console.log(JSON.stringify(templates[type], null, 2));
|
||||
} else {
|
||||
console.log(`Unknown template: ${type}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Other commands
|
||||
switch (command) {
|
||||
case 'search':
|
||||
const query = args.slice(1).join(' ');
|
||||
if (!query) {
|
||||
console.log('Usage: npm run hbm search <query>');
|
||||
return;
|
||||
}
|
||||
const searchResults = await manager.searchBooks(query);
|
||||
console.log(`\n🔍 Found ${searchResults.length} results for "${query}":\n`);
|
||||
searchResults.slice(0, 15).forEach(r => {
|
||||
console.log(`📄 ${r.file}:${r.line}`);
|
||||
console.log(` ${r.text}`);
|
||||
console.log('');
|
||||
});
|
||||
if (searchResults.length > 15) {
|
||||
console.log(`... and ${searchResults.length - 15} more results`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
const listType = args[1] || 'spells';
|
||||
const items = await manager.listContent(listType);
|
||||
console.log(`\n📋 ${listType} (${items.length} items):\n`);
|
||||
items.forEach(i => console.log(` • ${i.name}`));
|
||||
break;
|
||||
|
||||
case 'show':
|
||||
const showType = args[1];
|
||||
const showName = args.slice(2).join(' ');
|
||||
if (!showType || !showName) {
|
||||
console.log('Usage: npm run hbm show <type> <name>');
|
||||
return;
|
||||
}
|
||||
const contentItems = await manager.listContent(showType);
|
||||
const item = contentItems.find(i =>
|
||||
i.name.toLowerCase().includes(showName.toLowerCase())
|
||||
);
|
||||
if (!item) {
|
||||
console.log(`Not found: ${showName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const formatters = {
|
||||
spells: formatSpellForBook,
|
||||
creatures: formatCreatureForBook,
|
||||
items: formatItemForBook,
|
||||
npcs: formatNPCForBook,
|
||||
locations: formatLocationForBook,
|
||||
};
|
||||
|
||||
if (formatters[showType]) {
|
||||
console.log('\n' + formatters[showType](item) + '\n');
|
||||
} else {
|
||||
console.log(JSON.stringify(item, null, 2));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'draft':
|
||||
const draftBook = BOOKS[args[1]?.toLowerCase()] || args[1] || 'Unknown';
|
||||
const draftSection = args.slice(2).join(' ') || 'New Section';
|
||||
const draftPath = await manager.createDraft(draftBook, draftSection);
|
||||
console.log(`\n✅ Draft created: ${draftPath}`);
|
||||
break;
|
||||
|
||||
case 'revision':
|
||||
const revBook = BOOKS[args[1]?.toLowerCase()] || args[1] || 'Unknown';
|
||||
const revSection = await prompt('Section title: ');
|
||||
const revOriginal = await prompt('Paste original text (end with empty line):\n');
|
||||
const revNew = await prompt('Paste revised text (end with empty line):\n');
|
||||
const revNotes = await prompt('Notes (optional): ');
|
||||
|
||||
const revPath = await manager.createRevision(revBook, revSection, revOriginal, revNew, revNotes);
|
||||
console.log(`\n✅ Revision created: ${revPath}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`Unknown command: ${command}`);
|
||||
console.log('Run "npm run hbm help" for available commands');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* HbM: RPG v3 - Content Manager
|
||||
*
|
||||
* Tools for creating, editing, and organizing TT-RPG content
|
||||
*/
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
// Content lives directly at the repo root (Obsidian-vault-friendly layout).
|
||||
// Scripts live in .src/, so go up one level to reach the vault root.
|
||||
const CONTENT_DIR = path.resolve(import.meta.dirname, '..');
|
||||
const DRAFTS_DIR = path.join(CONTENT_DIR, '_drafts');
|
||||
const REVISIONS_DIR = path.join(CONTENT_DIR, 'revisions');
|
||||
const TEMPLATES_DIR = path.join(CONTENT_DIR, '_templates');
|
||||
|
||||
export class ContentManager {
|
||||
constructor() {
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
async init() {
|
||||
// Create directory structure (folders live at repo root)
|
||||
await fs.mkdir(DRAFTS_DIR, { recursive: true });
|
||||
await fs.mkdir(REVISIONS_DIR, { recursive: true });
|
||||
await fs.mkdir(path.join(CONTENT_DIR, 'spells'), { recursive: true });
|
||||
await fs.mkdir(path.join(CONTENT_DIR, 'creatures'), { recursive: true });
|
||||
await fs.mkdir(path.join(CONTENT_DIR, 'items'), { recursive: true });
|
||||
await fs.mkdir(path.join(CONTENT_DIR, 'locations'), { recursive: true });
|
||||
await fs.mkdir(path.join(CONTENT_DIR, 'npcs'), { recursive: true });
|
||||
await fs.mkdir(path.join(CONTENT_DIR, 'rules'), { recursive: true });
|
||||
await fs.mkdir(path.join(CONTENT_DIR, 'adventures'), { recursive: true });
|
||||
|
||||
this.initialized = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new draft for a specific book
|
||||
*/
|
||||
async createDraft(bookName, sectionTitle, content = '') {
|
||||
const timestamp = new Date().toISOString().split('T')[0];
|
||||
const safeName = sectionTitle.replace(/[^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\-\s]/g, '').trim();
|
||||
const fileName = `${timestamp}_${safeName}.md`;
|
||||
|
||||
const draft = `---
|
||||
book: "${bookName}"
|
||||
section: "${sectionTitle}"
|
||||
created: "${new Date().toISOString()}"
|
||||
status: draft
|
||||
---
|
||||
|
||||
# ${sectionTitle}
|
||||
|
||||
${content}
|
||||
|
||||
---
|
||||
<!-- REVISION NOTES -->
|
||||
<!-- Add notes about changes here -->
|
||||
`;
|
||||
|
||||
const filePath = path.join(DRAFTS_DIR, fileName);
|
||||
await fs.writeFile(filePath, draft);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a revision (ready to copy-paste version)
|
||||
*/
|
||||
async createRevision(bookName, sectionTitle, originalText, revisedText, notes = '') {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const safeName = sectionTitle.replace(/[^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\-\s]/g, '').trim();
|
||||
const fileName = `${safeName}_rev_${timestamp}.md`;
|
||||
|
||||
const revision = `---
|
||||
book: "${bookName}"
|
||||
section: "${sectionTitle}"
|
||||
created: "${new Date().toISOString()}"
|
||||
type: revision
|
||||
---
|
||||
|
||||
# Revision: ${sectionTitle}
|
||||
|
||||
**Book:** ${bookName}
|
||||
**Created:** ${new Date().toLocaleString('pl-PL')}
|
||||
|
||||
## Notes
|
||||
${notes || '_No notes provided_'}
|
||||
|
||||
---
|
||||
|
||||
## 📋 COPY-PASTE READY TEXT
|
||||
|
||||
\`\`\`
|
||||
${revisedText}
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
## Original Text (for reference)
|
||||
|
||||
<details>
|
||||
<summary>Click to expand original</summary>
|
||||
|
||||
${originalText}
|
||||
|
||||
</details>
|
||||
`;
|
||||
|
||||
const filePath = path.join(REVISIONS_DIR, fileName);
|
||||
await fs.writeFile(filePath, revision);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save content by type (spell, creature, etc.)
|
||||
*/
|
||||
async saveContent(type, name, data) {
|
||||
const typeDir = path.join(CONTENT_DIR, type);
|
||||
const safeName = name.replace(/[^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\-\s]/g, '').trim();
|
||||
const filePath = path.join(typeDir, `${safeName}.json`);
|
||||
|
||||
const content = {
|
||||
name,
|
||||
type,
|
||||
created: new Date().toISOString(),
|
||||
modified: new Date().toISOString(),
|
||||
...data
|
||||
};
|
||||
|
||||
await fs.writeFile(filePath, JSON.stringify(content, null, 2));
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all content of a type
|
||||
*/
|
||||
async listContent(type) {
|
||||
const typeDir = path.join(CONTENT_DIR, type);
|
||||
try {
|
||||
const files = await fs.readdir(typeDir);
|
||||
const contents = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.json')) {
|
||||
const data = JSON.parse(await fs.readFile(path.join(typeDir, file), 'utf-8'));
|
||||
contents.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search across all downloaded books
|
||||
*/
|
||||
async searchBooks(query, booksDir = '_books') {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(booksDir);
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.md')) continue;
|
||||
|
||||
const content = await fs.readFile(path.join(booksDir, file), 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
if (line.toLowerCase().includes(query.toLowerCase())) {
|
||||
results.push({
|
||||
file,
|
||||
line: index + 1,
|
||||
text: line.trim(),
|
||||
context: lines.slice(Math.max(0, index - 2), index + 3).join('\n')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Search error:', err.message);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a section from a book
|
||||
*/
|
||||
async extractSection(bookPath, startPattern, endPattern = null) {
|
||||
const content = await fs.readFile(bookPath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
let capturing = false;
|
||||
let section = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (!capturing && line.includes(startPattern)) {
|
||||
capturing = true;
|
||||
}
|
||||
|
||||
if (capturing) {
|
||||
if (endPattern && line.includes(endPattern)) {
|
||||
break;
|
||||
}
|
||||
section.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
return section.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// CLI handler
|
||||
if (process.argv[1].endsWith('content-manager.js')) {
|
||||
const manager = new ContentManager();
|
||||
await manager.init();
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case 'init':
|
||||
console.log('✅ Content directories initialized!');
|
||||
break;
|
||||
|
||||
case 'draft':
|
||||
const book = process.argv[3] || 'Unknown';
|
||||
const section = process.argv[4] || 'New Section';
|
||||
const draftPath = await manager.createDraft(book, section);
|
||||
console.log(`✅ Draft created: ${draftPath}`);
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
const query = process.argv[3];
|
||||
if (!query) {
|
||||
console.log('Usage: node src/content-manager.js search <query>');
|
||||
process.exit(1);
|
||||
}
|
||||
const results = await manager.searchBooks(query);
|
||||
console.log(`\n🔍 Found ${results.length} results for "${query}":\n`);
|
||||
results.slice(0, 10).forEach(r => {
|
||||
console.log(`📄 ${r.file}:${r.line}`);
|
||||
console.log(` ${r.text}\n`);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
const type = process.argv[3] || 'spells';
|
||||
const items = await manager.listContent(type);
|
||||
console.log(`\n📋 ${type} (${items.length} items):\n`);
|
||||
items.forEach(i => console.log(` - ${i.name}`));
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`
|
||||
HbM: RPG v3 - Content Manager
|
||||
|
||||
Commands:
|
||||
init - Initialize content directories
|
||||
draft <book> <section> - Create a new draft
|
||||
search <query> - Search across downloaded books
|
||||
list <type> - List content by type (spells, creatures, etc.)
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
export default ContentManager;
|
||||
Submodule
+1
Submodule Foundry-Data/foundry-lore added at 447b90dbb7
Submodule
+1
Submodule Foundry-Data/foundry-system added at 0fe9dd02d6
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* HbM: RPG v3 - Google Drive Client
|
||||
*
|
||||
* Downloads and processes books from Google Drive for content creation
|
||||
*/
|
||||
|
||||
import { google } from 'googleapis';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
const SCOPES = [
|
||||
'https://www.googleapis.com/auth/drive.readonly',
|
||||
'https://www.googleapis.com/auth/documents.readonly'
|
||||
];
|
||||
|
||||
// Root of the vault (one level above .src/)
|
||||
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
||||
|
||||
const TOKEN_PATH = path.join(ROOT_DIR, 'gdrive-token.json');
|
||||
const CREDENTIALS_PATH = path.join(import.meta.dirname, 'client_secret_837588858675-hp2m36p432m755aeuudtigo1225388i7.apps.googleusercontent.com.json');
|
||||
|
||||
// Book IDs will be stored here after first search
|
||||
const BOOKS_CACHE_PATH = path.join(import.meta.dirname, 'data', 'books-cache.json');
|
||||
|
||||
export class GDriveClient {
|
||||
constructor() {
|
||||
this.auth = null;
|
||||
this.drive = null;
|
||||
this.docs = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize OAuth client only (without requiring token)
|
||||
*/
|
||||
async initOAuth() {
|
||||
const credentials = JSON.parse(await fs.readFile(CREDENTIALS_PATH, 'utf-8'));
|
||||
const { client_id, client_secret, redirect_uris } = credentials.installed || credentials.web;
|
||||
|
||||
this.auth = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.initOAuth();
|
||||
|
||||
// Try to load existing token
|
||||
try {
|
||||
const token = JSON.parse(await fs.readFile(TOKEN_PATH, 'utf-8'));
|
||||
this.auth.setCredentials(token);
|
||||
} catch (err) {
|
||||
await this.authenticate();
|
||||
}
|
||||
|
||||
this.drive = google.drive({ version: 'v3', auth: this.auth });
|
||||
this.docs = google.docs({ version: 'v1', auth: this.auth });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
const authUrl = this.auth.generateAuthUrl({
|
||||
access_type: 'offline',
|
||||
scope: SCOPES,
|
||||
});
|
||||
|
||||
console.log('\n🔐 Authorization required!');
|
||||
console.log('Please visit this URL to authorize:\n');
|
||||
console.log(authUrl);
|
||||
console.log('\nThen run: node src/gdrive-client.js auth <CODE>\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async saveToken(code) {
|
||||
const { tokens } = await this.auth.getToken(code);
|
||||
this.auth.setCredentials(tokens);
|
||||
await fs.writeFile(TOKEN_PATH, JSON.stringify(tokens, null, 2));
|
||||
console.log('✅ Token saved successfully!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all HbM: RPG v3 books
|
||||
*/
|
||||
async searchBooks() {
|
||||
const query = "name contains 'HbM: RPG v3' and mimeType = 'application/vnd.google-apps.document' and trashed = false";
|
||||
|
||||
const res = await this.drive.files.list({
|
||||
q: query,
|
||||
fields: 'files(id, name, modifiedTime, webViewLink, ownedByMe)',
|
||||
orderBy: 'name',
|
||||
includeItemsFromAllDrives: false,
|
||||
supportsAllDrives: false,
|
||||
});
|
||||
|
||||
return res.data.files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document content as plain text
|
||||
*/
|
||||
async getDocumentAsText(fileId) {
|
||||
const res = await this.drive.files.export({
|
||||
fileId: fileId,
|
||||
mimeType: 'text/plain',
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document content as HTML (preserves some formatting)
|
||||
*/
|
||||
async getDocumentAsHtml(fileId) {
|
||||
const res = await this.drive.files.export({
|
||||
fileId: fileId,
|
||||
mimeType: 'text/html',
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document structure using Docs API (best for structured editing)
|
||||
*/
|
||||
async getDocumentStructure(documentId) {
|
||||
const res = await this.docs.documents.get({
|
||||
documentId: documentId,
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a specific book by name pattern
|
||||
*/
|
||||
async downloadBook(namePattern) {
|
||||
const books = await this.searchBooks();
|
||||
const book = books.find(b => b.name.toLowerCase().includes(namePattern.toLowerCase()));
|
||||
|
||||
if (!book) {
|
||||
throw new Error(`Book not found: ${namePattern}`);
|
||||
}
|
||||
|
||||
console.log(`📖 Downloading: ${book.name}`);
|
||||
|
||||
const text = await this.getDocumentAsText(book.id);
|
||||
const html = await this.getDocumentAsHtml(book.id);
|
||||
|
||||
return {
|
||||
id: book.id,
|
||||
name: book.name,
|
||||
modifiedTime: book.modifiedTime,
|
||||
text,
|
||||
html,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Download all books and save to local cache
|
||||
*/
|
||||
async downloadAllBooks(outputDir = '_books') {
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
const books = await this.searchBooks();
|
||||
|
||||
// Deduplicate by name (keep the most recently modified)
|
||||
const uniqueBooks = new Map();
|
||||
for (const book of books) {
|
||||
const existing = uniqueBooks.get(book.name);
|
||||
if (!existing || new Date(book.modifiedTime) > new Date(existing.modifiedTime)) {
|
||||
uniqueBooks.set(book.name, book);
|
||||
}
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const book of uniqueBooks.values()) {
|
||||
// Skip character sheets
|
||||
if (book.name.includes('KP v')) continue;
|
||||
|
||||
console.log(`📖 Downloading: ${book.name}`);
|
||||
|
||||
try {
|
||||
const text = await this.getDocumentAsText(book.id);
|
||||
const safeName = book.name.replace(/[^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\-\s]/g, '').trim();
|
||||
|
||||
// Save as markdown
|
||||
const mdPath = path.join(outputDir, `${safeName}.md`);
|
||||
await fs.writeFile(mdPath, `# ${book.name}\n\n_Last synced: ${new Date().toISOString()}_\n\n---\n\n${text}`);
|
||||
|
||||
results.push({
|
||||
id: book.id,
|
||||
name: book.name,
|
||||
localPath: mdPath,
|
||||
modifiedTime: book.modifiedTime,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`❌ Failed to download ${book.name}:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Save cache
|
||||
await fs.mkdir('data', { recursive: true });
|
||||
await fs.writeFile(BOOKS_CACHE_PATH, JSON.stringify(results, null, 2));
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
// CLI handler
|
||||
if (process.argv[1].endsWith('gdrive-client.js')) {
|
||||
const client = new GDriveClient();
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case 'auth':
|
||||
const code = process.argv[3];
|
||||
await client.initOAuth();
|
||||
if (!code) {
|
||||
// No code provided - show auth URL
|
||||
await client.authenticate();
|
||||
} else {
|
||||
// Code provided - save token
|
||||
await client.saveToken(code);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
await client.init();
|
||||
const books = await client.searchBooks();
|
||||
console.log('\n📚 HbM: RPG v3 Books:\n');
|
||||
books.forEach(b => console.log(` - ${b.name}`));
|
||||
break;
|
||||
|
||||
case 'download':
|
||||
await client.init();
|
||||
const results = await client.downloadAllBooks();
|
||||
console.log(`\n✅ Downloaded ${results.length} books to _books/`);
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
const pattern = process.argv[3];
|
||||
if (!pattern) {
|
||||
console.log('Usage: node src/gdrive-client.js get <name-pattern>');
|
||||
process.exit(1);
|
||||
}
|
||||
await client.init();
|
||||
const book = await client.downloadBook(pattern);
|
||||
console.log(book.text);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`
|
||||
HbM: RPG v3 - Google Drive Client
|
||||
|
||||
Commands:
|
||||
auth <code> - Authenticate with Google (follow OAuth flow)
|
||||
list - List all HbM books on Google Drive
|
||||
download - Download all books to _books/
|
||||
get <pattern> - Get a specific book by name pattern
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
export default GDriveClient;
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "hbm-rpg-content-tools",
|
||||
"version": "1.0.0",
|
||||
"description": "Content creation tools for Homebrew Magic: Role Playing Game v3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"hbm": "bun cli.js",
|
||||
"gdrive:auth": "bun gdrive-client.js auth",
|
||||
"gdrive:list": "bun gdrive-client.js list",
|
||||
"gdrive:sync": "bun gdrive-client.js download",
|
||||
"content:init": "bun content-manager.js init",
|
||||
"search": "bun content-manager.js search"
|
||||
},
|
||||
"bin": {
|
||||
"hbm": "./cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/server-gdrive": "^2025.1.14",
|
||||
"googleapis": "^170.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"ttrpg",
|
||||
"homebrew",
|
||||
"rpg",
|
||||
"content-creation"
|
||||
],
|
||||
"license": "PRIVATE"
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* HbM: RPG v3 - Creature/Monster Template
|
||||
*/
|
||||
|
||||
export const creatureTemplate = {
|
||||
name: '',
|
||||
nameEN: '',
|
||||
type: '', // Typ (np. Humanoid, Bestia, Nieumarły)
|
||||
size: '', // Rozmiar
|
||||
alignment: '', // Charakter
|
||||
|
||||
// Stats
|
||||
stats: {
|
||||
hp: 0,
|
||||
hpFormula: '',
|
||||
ac: 0,
|
||||
acType: '',
|
||||
speed: '',
|
||||
|
||||
// Attributes
|
||||
str: 10,
|
||||
dex: 10,
|
||||
con: 10,
|
||||
int: 10,
|
||||
wis: 10,
|
||||
cha: 10,
|
||||
},
|
||||
|
||||
// Combat
|
||||
combat: {
|
||||
attacks: [],
|
||||
specialAbilities: [],
|
||||
reactions: [],
|
||||
legendaryActions: [],
|
||||
},
|
||||
|
||||
// Defenses
|
||||
defenses: {
|
||||
savingThrows: [],
|
||||
skills: [],
|
||||
damageResistances: [],
|
||||
damageImmunities: [],
|
||||
conditionImmunities: [],
|
||||
senses: '',
|
||||
languages: '',
|
||||
},
|
||||
|
||||
// Challenge
|
||||
challenge: {
|
||||
cr: 0,
|
||||
xp: 0,
|
||||
},
|
||||
|
||||
// Lore
|
||||
lore: {
|
||||
description: '',
|
||||
habitat: '',
|
||||
behavior: '',
|
||||
history: '',
|
||||
},
|
||||
|
||||
notes: '',
|
||||
};
|
||||
|
||||
function getModifier(score) {
|
||||
return Math.floor((score - 10) / 2);
|
||||
}
|
||||
|
||||
function formatModifier(mod) {
|
||||
return mod >= 0 ? `+${mod}` : `${mod}`;
|
||||
}
|
||||
|
||||
export function formatCreatureForBook(creature) {
|
||||
const stats = creature.stats;
|
||||
const mods = {
|
||||
str: formatModifier(getModifier(stats.str)),
|
||||
dex: formatModifier(getModifier(stats.dex)),
|
||||
con: formatModifier(getModifier(stats.con)),
|
||||
int: formatModifier(getModifier(stats.int)),
|
||||
wis: formatModifier(getModifier(stats.wis)),
|
||||
cha: formatModifier(getModifier(stats.cha)),
|
||||
};
|
||||
|
||||
let output = `## ${creature.name}
|
||||
*${creature.size} ${creature.type}, ${creature.alignment}*
|
||||
|
||||
---
|
||||
|
||||
**Klasa Pancerza:** ${stats.ac}${stats.acType ? ` (${stats.acType})` : ''}
|
||||
**Punkty Wytrzymałości:** ${stats.hp}${stats.hpFormula ? ` (${stats.hpFormula})` : ''}
|
||||
**Szybkość:** ${stats.speed}
|
||||
|
||||
---
|
||||
|
||||
| SIŁ | ZRE | KON | INT | MDR | CHA |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| ${stats.str} (${mods.str}) | ${stats.dex} (${mods.dex}) | ${stats.con} (${mods.con}) | ${stats.int} (${mods.int}) | ${stats.wis} (${mods.wis}) | ${stats.cha} (${mods.cha}) |
|
||||
|
||||
---
|
||||
`;
|
||||
|
||||
const def = creature.defenses;
|
||||
if (def.savingThrows.length) output += `**Rzuty Obronne:** ${def.savingThrows.join(', ')}\n`;
|
||||
if (def.skills.length) output += `**Umiejętności:** ${def.skills.join(', ')}\n`;
|
||||
if (def.damageResistances.length) output += `**Odporności na Obrażenia:** ${def.damageResistances.join(', ')}\n`;
|
||||
if (def.damageImmunities.length) output += `**Niewrażliwości na Obrażenia:** ${def.damageImmunities.join(', ')}\n`;
|
||||
if (def.conditionImmunities.length) output += `**Niewrażliwości na Stany:** ${def.conditionImmunities.join(', ')}\n`;
|
||||
if (def.senses) output += `**Zmysły:** ${def.senses}\n`;
|
||||
if (def.languages) output += `**Języki:** ${def.languages}\n`;
|
||||
output += `**Poziom Wyzwania:** ${creature.challenge.cr} (${creature.challenge.xp} PD)\n`;
|
||||
|
||||
output += '\n---\n\n';
|
||||
|
||||
// Special Abilities
|
||||
if (creature.combat.specialAbilities.length) {
|
||||
creature.combat.specialAbilities.forEach(ability => {
|
||||
output += `***${ability.name}.*** ${ability.description}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Actions
|
||||
if (creature.combat.attacks.length) {
|
||||
output += '### Akcje\n\n';
|
||||
creature.combat.attacks.forEach(attack => {
|
||||
output += `***${attack.name}.*** ${attack.description}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Reactions
|
||||
if (creature.combat.reactions.length) {
|
||||
output += '### Reakcje\n\n';
|
||||
creature.combat.reactions.forEach(reaction => {
|
||||
output += `***${reaction.name}.*** ${reaction.description}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Legendary Actions
|
||||
if (creature.combat.legendaryActions.length) {
|
||||
output += '### Akcje Legendarne\n\n';
|
||||
creature.combat.legendaryActions.forEach(action => {
|
||||
output += `***${action.name}.*** ${action.description}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Lore
|
||||
if (creature.lore.description) {
|
||||
output += '---\n\n';
|
||||
output += creature.lore.description + '\n';
|
||||
}
|
||||
|
||||
return output.trim();
|
||||
}
|
||||
|
||||
export default { creatureTemplate, formatCreatureForBook };
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* HbM: RPG v3 - Item Template
|
||||
*/
|
||||
|
||||
export const itemTemplate = {
|
||||
name: '',
|
||||
nameEN: '',
|
||||
type: '', // Weapon, Armor, Potion, Wondrous, etc.
|
||||
rarity: '', // Common, Uncommon, Rare, Very Rare, Legendary, Artifact
|
||||
attunement: false,
|
||||
attunementReq: '', // e.g., "by a spellcaster"
|
||||
|
||||
// For weapons
|
||||
weapon: {
|
||||
damage: '',
|
||||
damageType: '',
|
||||
properties: [], // e.g., ["Finesse", "Light", "Thrown (20/60)"]
|
||||
},
|
||||
|
||||
// For armor
|
||||
armor: {
|
||||
ac: 0,
|
||||
acBonus: 0,
|
||||
type: '', // Light, Medium, Heavy, Shield
|
||||
stealthDisadv: false,
|
||||
strRequirement: 0,
|
||||
},
|
||||
|
||||
description: '',
|
||||
properties: [], // Special properties/abilities
|
||||
history: '', // Item lore/backstory
|
||||
|
||||
// Pricing
|
||||
value: '',
|
||||
weight: '',
|
||||
|
||||
notes: '',
|
||||
};
|
||||
|
||||
const rarityPL = {
|
||||
'Common': 'Pospolity',
|
||||
'Uncommon': 'Niepospolity',
|
||||
'Rare': 'Rzadki',
|
||||
'Very Rare': 'Bardzo Rzadki',
|
||||
'Legendary': 'Legendarny',
|
||||
'Artifact': 'Artefakt',
|
||||
};
|
||||
|
||||
export function formatItemForBook(item) {
|
||||
const rarity = rarityPL[item.rarity] || item.rarity;
|
||||
let typeStr = item.type;
|
||||
|
||||
if (item.weapon.damage) {
|
||||
typeStr = `Broń (${item.weapon.properties.join(', ')})`;
|
||||
} else if (item.armor.type) {
|
||||
typeStr = `Zbroja (${item.armor.type})`;
|
||||
}
|
||||
|
||||
let attunement = '';
|
||||
if (item.attunement) {
|
||||
attunement = item.attunementReq
|
||||
? ` (wymaga dostrojenia ${item.attunementReq})`
|
||||
: ' (wymaga dostrojenia)';
|
||||
}
|
||||
|
||||
let output = `### ${item.name}
|
||||
*${typeStr}, ${rarity}${attunement}*
|
||||
|
||||
`;
|
||||
|
||||
// Weapon stats
|
||||
if (item.weapon.damage) {
|
||||
output += `**Obrażenia:** ${item.weapon.damage} ${item.weapon.damageType}\n`;
|
||||
if (item.weapon.properties.length) {
|
||||
output += `**Właściwości:** ${item.weapon.properties.join(', ')}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Armor stats
|
||||
if (item.armor.type) {
|
||||
if (item.armor.ac) {
|
||||
output += `**KP:** ${item.armor.ac}`;
|
||||
} else if (item.armor.acBonus) {
|
||||
output += `**Bonus KP:** +${item.armor.acBonus}`;
|
||||
}
|
||||
if (item.armor.stealthDisadv) output += ` (utrudnia skradanie)`;
|
||||
if (item.armor.strRequirement) output += ` (wymaga SIŁ ${item.armor.strRequirement})`;
|
||||
output += '\n\n';
|
||||
}
|
||||
|
||||
output += item.description + '\n';
|
||||
|
||||
// Special properties
|
||||
if (item.properties.length) {
|
||||
output += '\n';
|
||||
item.properties.forEach(prop => {
|
||||
output += `• **${prop.name}:** ${prop.description}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Value and weight
|
||||
if (item.value || item.weight) {
|
||||
output += '\n---\n';
|
||||
if (item.value) output += `*Wartość: ${item.value}* `;
|
||||
if (item.weight) output += `*Waga: ${item.weight}*`;
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Lore/History
|
||||
if (item.history) {
|
||||
output += `\n*${item.history}*\n`;
|
||||
}
|
||||
|
||||
return output.trim();
|
||||
}
|
||||
|
||||
export default { itemTemplate, formatItemForBook };
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* HbM: RPG v3 - Location Template
|
||||
*/
|
||||
|
||||
export const locationTemplate = {
|
||||
name: '',
|
||||
nameEN: '',
|
||||
type: '', // City, Village, Dungeon, Forest, etc.
|
||||
region: '',
|
||||
|
||||
// Basic info
|
||||
population: '',
|
||||
government: '',
|
||||
economy: '',
|
||||
|
||||
// Description
|
||||
description: {
|
||||
overview: '',
|
||||
atmosphere: '', // Mood, feeling, sounds, smells
|
||||
history: '',
|
||||
secrets: '', // Hidden info for GM
|
||||
},
|
||||
|
||||
// Points of Interest
|
||||
landmarks: [], // { name, description }
|
||||
|
||||
// Inhabitants
|
||||
notableNPCs: [], // References or brief descriptions
|
||||
factions: [], // { name, description, goals }
|
||||
|
||||
// For adventures
|
||||
encounters: [], // Possible random encounters
|
||||
hooks: [], // Adventure hooks
|
||||
rumors: [], // Things NPCs might say
|
||||
|
||||
// For dungeons/adventure sites
|
||||
rooms: [], // { number, name, description, contents }
|
||||
|
||||
// Maps
|
||||
mapDescription: '', // Text description if no map available
|
||||
|
||||
notes: '',
|
||||
};
|
||||
|
||||
export function formatLocationForBook(location) {
|
||||
let output = `## ${location.name}\n`;
|
||||
if (location.type || location.region) {
|
||||
output += `*${[location.type, location.region].filter(Boolean).join(', ')}*\n`;
|
||||
}
|
||||
output += '\n';
|
||||
|
||||
// Overview
|
||||
if (location.description.overview) {
|
||||
output += location.description.overview + '\n\n';
|
||||
}
|
||||
|
||||
// Basic info
|
||||
const info = [];
|
||||
if (location.population) info.push(`**Populacja:** ${location.population}`);
|
||||
if (location.government) info.push(`**Władza:** ${location.government}`);
|
||||
if (location.economy) info.push(`**Gospodarka:** ${location.economy}`);
|
||||
if (info.length) {
|
||||
output += info.join(' | ') + '\n\n';
|
||||
}
|
||||
|
||||
// Atmosphere
|
||||
if (location.description.atmosphere) {
|
||||
output += '### Atmosfera\n\n';
|
||||
output += location.description.atmosphere + '\n\n';
|
||||
}
|
||||
|
||||
// Landmarks
|
||||
if (location.landmarks.length) {
|
||||
output += '### Ważne Miejsca\n\n';
|
||||
location.landmarks.forEach(landmark => {
|
||||
output += `#### ${landmark.name}\n`;
|
||||
output += landmark.description + '\n\n';
|
||||
});
|
||||
}
|
||||
|
||||
// Notable NPCs
|
||||
if (location.notableNPCs.length) {
|
||||
output += '### Ważne Postacie\n\n';
|
||||
location.notableNPCs.forEach(npc => {
|
||||
if (typeof npc === 'string') {
|
||||
output += `• ${npc}\n`;
|
||||
} else {
|
||||
output += `• **${npc.name}** - ${npc.description}\n`;
|
||||
}
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Factions
|
||||
if (location.factions.length) {
|
||||
output += '### Frakcje\n\n';
|
||||
location.factions.forEach(faction => {
|
||||
output += `#### ${faction.name}\n`;
|
||||
output += faction.description + '\n';
|
||||
if (faction.goals) output += `*Cele: ${faction.goals}*\n`;
|
||||
output += '\n';
|
||||
});
|
||||
}
|
||||
|
||||
// History
|
||||
if (location.description.history) {
|
||||
output += '### Historia\n\n';
|
||||
output += location.description.history + '\n\n';
|
||||
}
|
||||
|
||||
// Rumors
|
||||
if (location.rumors.length) {
|
||||
output += '### Plotki\n\n';
|
||||
location.rumors.forEach((rumor, i) => {
|
||||
output += `${i + 1}. ${rumor}\n`;
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Adventure Hooks
|
||||
if (location.hooks.length) {
|
||||
output += '### Zaczepki Przygodowe\n\n';
|
||||
location.hooks.forEach(hook => {
|
||||
output += `• ${hook}\n`;
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Encounters
|
||||
if (location.encounters.length) {
|
||||
output += '### Możliwe Spotkania\n\n';
|
||||
output += '| k20 | Spotkanie |\n';
|
||||
output += '|:---:|:----------|\n';
|
||||
location.encounters.forEach((enc, i) => {
|
||||
output += `| ${i + 1} | ${enc} |\n`;
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Dungeon Rooms
|
||||
if (location.rooms.length) {
|
||||
output += '---\n\n## Pomieszczenia\n\n';
|
||||
location.rooms.forEach(room => {
|
||||
output += `### ${room.number}. ${room.name}\n\n`;
|
||||
output += room.description + '\n';
|
||||
if (room.contents) {
|
||||
output += `\n**Zawartość:** ${room.contents}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
});
|
||||
}
|
||||
|
||||
// GM Secrets
|
||||
if (location.description.secrets) {
|
||||
output += '---\n\n';
|
||||
output += '> **[DLA MG]** ' + location.description.secrets + '\n';
|
||||
}
|
||||
|
||||
return output.trim();
|
||||
}
|
||||
|
||||
export default { locationTemplate, formatLocationForBook };
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* HbM: RPG v3 - NPC Template
|
||||
*/
|
||||
|
||||
export const npcTemplate = {
|
||||
name: '',
|
||||
title: '', // e.g., "The Blacksmith", "High Priest"
|
||||
race: '',
|
||||
gender: '',
|
||||
age: '',
|
||||
occupation: '',
|
||||
location: '',
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
height: '',
|
||||
build: '',
|
||||
hair: '',
|
||||
eyes: '',
|
||||
distinguishing: '', // Scars, tattoos, etc.
|
||||
clothing: '',
|
||||
},
|
||||
|
||||
// Personality
|
||||
personality: {
|
||||
traits: [],
|
||||
ideals: [],
|
||||
bonds: [],
|
||||
flaws: [],
|
||||
mannerisms: '',
|
||||
voice: '', // How they speak
|
||||
},
|
||||
|
||||
// Background
|
||||
background: {
|
||||
history: '',
|
||||
secrets: '',
|
||||
goals: '',
|
||||
fears: '',
|
||||
},
|
||||
|
||||
// Relationships
|
||||
relationships: [], // { name, relation, notes }
|
||||
|
||||
// For combat NPCs
|
||||
statBlock: null, // Reference to creature template if needed
|
||||
|
||||
// Roleplay hooks
|
||||
hooks: [], // Quest hooks, rumors, etc.
|
||||
|
||||
notes: '',
|
||||
};
|
||||
|
||||
export function formatNPCForBook(npc) {
|
||||
let output = `## ${npc.name}`;
|
||||
if (npc.title) output += `, ${npc.title}`;
|
||||
output += '\n\n';
|
||||
|
||||
// Basic info
|
||||
const basicInfo = [npc.race, npc.gender, npc.age, npc.occupation].filter(Boolean);
|
||||
if (basicInfo.length) {
|
||||
output += `*${basicInfo.join(', ')}*\n`;
|
||||
}
|
||||
if (npc.location) {
|
||||
output += `**Lokacja:** ${npc.location}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
|
||||
// Appearance
|
||||
if (npc.appearance.distinguishing || npc.appearance.clothing) {
|
||||
output += '### Wygląd\n\n';
|
||||
const appearanceDetails = [];
|
||||
if (npc.appearance.height) appearanceDetails.push(npc.appearance.height);
|
||||
if (npc.appearance.build) appearanceDetails.push(npc.appearance.build);
|
||||
if (npc.appearance.hair) appearanceDetails.push(`włosy: ${npc.appearance.hair}`);
|
||||
if (npc.appearance.eyes) appearanceDetails.push(`oczy: ${npc.appearance.eyes}`);
|
||||
|
||||
if (appearanceDetails.length) {
|
||||
output += appearanceDetails.join(', ') + '.\n\n';
|
||||
}
|
||||
if (npc.appearance.distinguishing) {
|
||||
output += `**Znaki szczególne:** ${npc.appearance.distinguishing}\n`;
|
||||
}
|
||||
if (npc.appearance.clothing) {
|
||||
output += `**Ubiór:** ${npc.appearance.clothing}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Personality
|
||||
output += '### Osobowość\n\n';
|
||||
if (npc.personality.traits.length) {
|
||||
output += `**Cechy:** ${npc.personality.traits.join(', ')}\n`;
|
||||
}
|
||||
if (npc.personality.ideals.length) {
|
||||
output += `**Ideały:** ${npc.personality.ideals.join(', ')}\n`;
|
||||
}
|
||||
if (npc.personality.bonds.length) {
|
||||
output += `**Więzi:** ${npc.personality.bonds.join(', ')}\n`;
|
||||
}
|
||||
if (npc.personality.flaws.length) {
|
||||
output += `**Wady:** ${npc.personality.flaws.join(', ')}\n`;
|
||||
}
|
||||
if (npc.personality.mannerisms) {
|
||||
output += `**Maniery:** ${npc.personality.mannerisms}\n`;
|
||||
}
|
||||
if (npc.personality.voice) {
|
||||
output += `**Głos:** ${npc.personality.voice}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
|
||||
// Background
|
||||
if (npc.background.history || npc.background.goals) {
|
||||
output += '### Historia\n\n';
|
||||
if (npc.background.history) {
|
||||
output += npc.background.history + '\n\n';
|
||||
}
|
||||
if (npc.background.goals) {
|
||||
output += `**Cele:** ${npc.background.goals}\n`;
|
||||
}
|
||||
if (npc.background.fears) {
|
||||
output += `**Lęki:** ${npc.background.fears}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Relationships
|
||||
if (npc.relationships.length) {
|
||||
output += '### Relacje\n\n';
|
||||
npc.relationships.forEach(rel => {
|
||||
output += `• **${rel.name}** (${rel.relation})`;
|
||||
if (rel.notes) output += ` - ${rel.notes}`;
|
||||
output += '\n';
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Hooks
|
||||
if (npc.hooks.length) {
|
||||
output += '### Zaczepki Fabularne\n\n';
|
||||
npc.hooks.forEach(hook => {
|
||||
output += `• ${hook}\n`;
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Secrets (for GM)
|
||||
if (npc.background.secrets) {
|
||||
output += '> **[DLA MG]** ' + npc.background.secrets + '\n';
|
||||
}
|
||||
|
||||
return output.trim();
|
||||
}
|
||||
|
||||
export default { npcTemplate, formatNPCForBook };
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* HbM: RPG v3 - Spell Template
|
||||
*/
|
||||
|
||||
export const spellTemplate = {
|
||||
name: '',
|
||||
nameEN: '', // English translation
|
||||
school: '', // Szkoła magii
|
||||
circle: 1, // Krąg (1-9)
|
||||
castingTime: '',
|
||||
range: '',
|
||||
duration: '',
|
||||
components: {
|
||||
verbal: false,
|
||||
somatic: false,
|
||||
material: '',
|
||||
},
|
||||
description: '',
|
||||
higherCircles: '', // Opis dla wyższych kręgów
|
||||
notes: '',
|
||||
};
|
||||
|
||||
export function formatSpellForBook(spell) {
|
||||
const components = [];
|
||||
if (spell.components.verbal) components.push('W');
|
||||
if (spell.components.somatic) components.push('S');
|
||||
if (spell.components.material) components.push(`M (${spell.components.material})`);
|
||||
|
||||
return `### ${spell.name}
|
||||
*${spell.school}, ${spell.circle}. krąg*
|
||||
|
||||
**Czas rzucania:** ${spell.castingTime}
|
||||
**Zasięg:** ${spell.range}
|
||||
**Komponenty:** ${components.join(', ')}
|
||||
**Czas trwania:** ${spell.duration}
|
||||
|
||||
${spell.description}
|
||||
|
||||
${spell.higherCircles ? `**Na wyższych kręgach:** ${spell.higherCircles}` : ''}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
export function parseSpellFromText(text) {
|
||||
// Basic parser for spell text - customize based on your format
|
||||
const spell = { ...spellTemplate };
|
||||
|
||||
const nameMatch = text.match(/^###?\s*(.+)/m);
|
||||
if (nameMatch) spell.name = nameMatch[1].trim();
|
||||
|
||||
const schoolMatch = text.match(/\*([^,]+),\s*(\d+)\.\s*krąg\*/);
|
||||
if (schoolMatch) {
|
||||
spell.school = schoolMatch[1].trim();
|
||||
spell.circle = parseInt(schoolMatch[2]);
|
||||
}
|
||||
|
||||
const castingMatch = text.match(/Czas rzucania:\*?\*?\s*(.+)/i);
|
||||
if (castingMatch) spell.castingTime = castingMatch[1].trim();
|
||||
|
||||
const rangeMatch = text.match(/Zasięg:\*?\*?\s*(.+)/i);
|
||||
if (rangeMatch) spell.range = rangeMatch[1].trim();
|
||||
|
||||
const durationMatch = text.match(/Czas trwania:\*?\*?\s*(.+)/i);
|
||||
if (durationMatch) spell.duration = durationMatch[1].trim();
|
||||
|
||||
return spell;
|
||||
}
|
||||
|
||||
export default { spellTemplate, formatSpellForBook, parseSpellFromText };
|
||||
Reference in New Issue
Block a user