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:
Octoturge
2026-08-17 21:28:50 +02:00
commit a4f8941fca
25 changed files with 3702 additions and 0 deletions
+273
View File
@@ -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=="],
}
}
+285
View File
@@ -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);
+272
View File
@@ -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;
+261
View File
@@ -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;
+28
View File
@@ -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"
}
+154
View File
@@ -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 };
+118
View File
@@ -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 };
+162
View File
@@ -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 };
+155
View File
@@ -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 };
+68
View File
@@ -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 };