diff --git a/package-lock.json b/package-lock.json index 88d3390c..869c4f3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "plotlink", "version": "0.1.0", + "workspaces": [ + "packages/*" + ], "dependencies": { "@aws-sdk/client-s3": "^3.1009.0", "@supabase/supabase-js": "^2.99.1", @@ -2570,6 +2573,10 @@ "node": ">=12.4.0" } }, + "node_modules/@plotlink/sdk": { + "resolved": "packages/sdk", + "link": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -4963,6 +4970,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5286,6 +5300,22 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -5420,6 +5450,22 @@ "node": ">= 16" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -5446,6 +5492,15 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5453,6 +5508,23 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -6524,6 +6596,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -7414,6 +7498,16 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7802,6 +7896,36 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7942,6 +8066,19 @@ } } }, + "node_modules/mlly": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz", + "integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7949,6 +8086,18 @@ "dev": true, "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -8409,6 +8558,32 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/plotlink-cli": { + "resolved": "packages/cli", + "link": true + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -8448,6 +8623,49 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8529,6 +8747,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -8978,6 +9210,16 @@ "dev": true, "license": "ISC" }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -9213,6 +9455,39 @@ } } }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -9260,6 +9535,29 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -9365,6 +9663,16 @@ "node": ">=8.0" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -9378,6 +9686,13 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -9539,6 +9854,13 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -10211,6 +10533,166 @@ "optional": true } } + }, + "packages/cli": { + "name": "plotlink-cli", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@plotlink/sdk": "*", + "@supabase/supabase-js": "^2.49.4", + "commander": "^13.1.0", + "viem": "^2.47.2" + }, + "bin": { + "plotlink": "dist/index.js" + }, + "devDependencies": { + "tsup": "^8.4.0", + "typescript": "^5" + } + }, + "packages/cli/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/cli/node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "packages/sdk": { + "name": "@plotlink/sdk", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-s3": "^3.1009.0" + }, + "devDependencies": { + "eslint": "^9", + "tsup": "^8.4.0", + "typescript": "^5" + }, + "peerDependencies": { + "viem": "^2.0.0" + } + }, + "packages/sdk/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/sdk/node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index d7ec715d..ebffd042 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "plotlink", "version": "0.1.0", "private": true, + "workspaces": [ + "packages/*" + ], "scripts": { "dev": "next dev", "build": "next build", diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 00000000..dd661f85 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,27 @@ +{ + "name": "plotlink-cli", + "version": "0.1.0", + "description": "CLI for the PlotLink protocol — create storylines, chain plots, register agents, and claim royalties.", + "type": "module", + "bin": { + "plotlink": "./dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@plotlink/sdk": "*", + "@supabase/supabase-js": "^2.49.4", + "commander": "^13.1.0", + "viem": "^2.47.2" + }, + "devDependencies": { + "tsup": "^8.4.0", + "typescript": "^5" + }, + "license": "MIT" +} diff --git a/packages/cli/src/commands/agent-register.ts b/packages/cli/src/commands/agent-register.ts new file mode 100644 index 00000000..1efd30bb --- /dev/null +++ b/packages/cli/src/commands/agent-register.ts @@ -0,0 +1,72 @@ +import type { Command } from "commander"; +import type { Address } from "viem"; +import { buildClient } from "../sdk.js"; + +export function registerAgentRegister(program: Command): void { + const agent = program + .command("agent") + .description("Agent identity commands"); + + agent + .command("register") + .description("Register an AI agent on the ERC-8004 registry and set its wallet") + .requiredOption("-n, --name ", "Agent display name") + .requiredOption("-d, --description ", "Short description of the agent") + .requiredOption("-g, --genre ", "Primary genre the agent writes in") + .requiredOption("-m, --model ", "LLM model identifier (e.g. \"Claude Opus 4\")") + .option("-w, --wallet
", "Agent wallet address (defaults to the configured wallet)") + .option("--skip-wallet", "Skip wallet binding step", false) + .action( + async (opts: { + name: string; + description: string; + genre: string; + model: string; + wallet?: string; + skipWallet?: boolean; + }) => { + try { + // Resolve wallet key from env BEFORE spending gas on registration + let walletKey: string | undefined; + if (opts.wallet && !opts.skipWallet) { + walletKey = process.env.PLOTLINK_AGENT_WALLET_KEY; + if (!walletKey) { + console.error( + "Error: PLOTLINK_AGENT_WALLET_KEY env var is required when --wallet is set.\n" + + "Set it before running this command, or pass --skip-wallet to register without binding.", + ); + process.exit(1); + } + } + + const client = buildClient({ ipfs: false }); + + console.log(`Registering agent "${opts.name}"...`); + const result = await client.registerAgent( + opts.name, + opts.description, + opts.genre, + opts.model, + ); + + console.log("Agent registered!"); + console.log(` Agent ID: ${result.agentId}`); + console.log(` TX: ${result.txHash}`); + + if (opts.wallet && !opts.skipWallet && walletKey) { + const walletAddress = opts.wallet as Address; + console.log(`Setting agent wallet to ${walletAddress}...`); + const walletResult = await client.setAgentWallet( + result.agentId, + walletAddress, + walletKey, + ); + console.log(`Agent wallet set! TX: ${walletResult.txHash}`); + } + } catch (err) { + console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + }, + ); +} diff --git a/packages/cli/src/commands/chain.ts b/packages/cli/src/commands/chain.ts new file mode 100644 index 00000000..b90d7a70 --- /dev/null +++ b/packages/cli/src/commands/chain.ts @@ -0,0 +1,28 @@ +import { readFileSync } from "node:fs"; +import type { Command } from "commander"; +import { buildClient } from "../sdk.js"; + +export function registerChain(program: Command): void { + program + .command("chain") + .description("Chain a new plot onto an existing storyline") + .requiredOption("-s, --storyline ", "Storyline ID") + .requiredOption("-f, --file ", "Path to content file (plain text)") + .action(async (opts: { storyline: string; file: string }) => { + try { + const content = readFileSync(opts.file, "utf-8"); + const storylineId = BigInt(opts.storyline); + const client = buildClient({ ipfs: true }); + + console.log(`Chaining plot onto storyline ${storylineId}...`); + const result = await client.chainPlot(storylineId, content); + + console.log("Plot chained!"); + console.log(` TX: ${result.txHash}`); + console.log(` CID: ${result.contentCid}`); + } catch (err) { + console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + }); +} diff --git a/packages/cli/src/commands/claim.ts b/packages/cli/src/commands/claim.ts new file mode 100644 index 00000000..e6150bb8 --- /dev/null +++ b/packages/cli/src/commands/claim.ts @@ -0,0 +1,33 @@ +import type { Command } from "commander"; +import type { Address } from "viem"; +import { buildClient } from "../sdk.js"; + +export function registerClaim(program: Command): void { + program + .command("claim") + .description("Claim accumulated royalties for a storyline token") + .requiredOption("-a, --address ", "Storyline ERC-20 token address") + .action(async (opts: { address: string }) => { + try { + const tokenAddress = opts.address as Address; + const client = buildClient({ ipfs: false }); + + console.log("Checking royalties..."); + const info = await client.getRoyaltyInfo(tokenAddress); + console.log(` Unclaimed: ${info.unclaimed}`); + console.log(` Beneficiary: ${info.beneficiary}`); + + if (info.unclaimed === 0n) { + console.log("No royalties to claim."); + return; + } + + console.log("Claiming royalties..."); + const txHash = await client.claimRoyalties(tokenAddress); + console.log(`Royalties claimed! TX: ${txHash}`); + } catch (err) { + console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + }); +} diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts new file mode 100644 index 00000000..59146969 --- /dev/null +++ b/packages/cli/src/commands/create.ts @@ -0,0 +1,35 @@ +import { readFileSync } from "node:fs"; +import type { Command } from "commander"; +import { buildClient } from "../sdk.js"; + +export function registerCreate(program: Command): void { + program + .command("create") + .description("Create a new storyline from a content file") + .requiredOption("-t, --title ", "Storyline title") + .requiredOption("-f, --file <path>", "Path to content file (plain text)") + .requiredOption("-g, --genre <genre>", "Genre label") + .option("-d, --deadline", "Enable sunset deadline", false) + .action(async (opts: { title: string; file: string; genre: string; deadline: boolean }) => { + try { + const content = readFileSync(opts.file, "utf-8"); + const client = buildClient({ ipfs: true }); + + console.log(`Creating storyline "${opts.title}"...`); + const result = await client.createStoryline( + opts.title, + content, + opts.genre, + opts.deadline, + ); + + console.log("Storyline created!"); + console.log(` ID: ${result.storylineId}`); + console.log(` TX: ${result.txHash}`); + console.log(` CID: ${result.contentCid}`); + } catch (err) { + console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + }); +} diff --git a/packages/cli/src/commands/status.ts b/packages/cli/src/commands/status.ts new file mode 100644 index 00000000..c3e821b4 --- /dev/null +++ b/packages/cli/src/commands/status.ts @@ -0,0 +1,133 @@ +import type { Command } from "commander"; +import { createClient } from "@supabase/supabase-js"; +import { type Address, formatUnits } from "viem"; +import { buildClient } from "../sdk.js"; +import { loadConfig } from "../config.js"; + +export function registerStatus(program: Command): void { + program + .command("status") + .description("Query storyline data (plots, deadline, token price) from Supabase and on-chain") + .requiredOption("-s, --storyline <id>", "Storyline ID") + .action(async (opts: { storyline: string }) => { + try { + const storylineId = BigInt(opts.storyline); + const cfg = loadConfig(); + const client = buildClient({ ipfs: false }); + + console.log(`Fetching storyline ${storylineId}...`); + + // ----------------------------------------------------------------- + // 1. On-chain event data (always available) + // ----------------------------------------------------------------- + const info = await client.getStoryline(storylineId); + if (!info) { + console.error(`Storyline ${storylineId} not found on-chain.`); + process.exit(1); + } + + // ----------------------------------------------------------------- + // 2. Supabase metadata (optional — richer data when configured) + // ----------------------------------------------------------------- + let dbRow: { + plot_count: number; + last_plot_time: string | null; + has_deadline: boolean; + sunset: boolean; + writer_type: number | null; + block_timestamp: string | null; + } | null = null; + + if (cfg.supabaseUrl && cfg.supabaseAnonKey) { + const supabase = createClient(cfg.supabaseUrl, cfg.supabaseAnonKey); + const { data } = await supabase + .from("storylines") + .select("plot_count, last_plot_time, has_deadline, sunset, writer_type, block_timestamp") + .eq("storyline_id", Number(storylineId)) + .single(); + dbRow = data; + } + + // ----------------------------------------------------------------- + // 3. On-chain token price (MCV2_Bond) + // ----------------------------------------------------------------- + const tokenPrice = await client.getTokenPrice(info.tokenAddress); + + // ----------------------------------------------------------------- + // 4. On-chain royalty info + // ----------------------------------------------------------------- + let unclaimedRoyalty: bigint | null = null; + try { + const royalty = await client.getRoyaltyInfo(info.tokenAddress); + unclaimedRoyalty = royalty.unclaimed; + } catch { + // Token may not have a bond yet + } + + // ----------------------------------------------------------------- + // 5. Fall back to event-derived plot count if no Supabase + // ----------------------------------------------------------------- + let plotCount: number; + if (dbRow) { + plotCount = dbRow.plot_count; + } else { + const plots = await client.getPlots(storylineId); + plotCount = plots.length; + } + + // ----------------------------------------------------------------- + // Display + // ----------------------------------------------------------------- + console.log(); + console.log(`Title: ${info.title}`); + console.log(`Creator: ${info.creator}`); + console.log(`Token: ${info.tokenAddress}`); + console.log(`Has deadline: ${info.hasDeadline ? "yes" : "no"}`); + console.log(`Opening CID: ${info.openingCID}`); + console.log(`Plot count: ${plotCount}`); + + if (dbRow) { + console.log(`Sunset: ${dbRow.sunset ? "yes" : "no"}`); + console.log(`Writer type: ${dbRow.writer_type === 1 ? "agent" : dbRow.writer_type === 0 ? "human" : "unknown"}`); + if (dbRow.block_timestamp) { + console.log(`Created: ${new Date(dbRow.block_timestamp).toISOString()}`); + } + if (dbRow.last_plot_time) { + console.log(`Last plot: ${new Date(dbRow.last_plot_time).toISOString()}`); + } + + // Deadline remaining (72h from last plot) + if (dbRow.has_deadline && dbRow.last_plot_time && !dbRow.sunset) { + const DEADLINE_HOURS = 72; + const deadlineMs = + new Date(dbRow.last_plot_time).getTime() + DEADLINE_HOURS * 60 * 60 * 1000; + const remainingMs = deadlineMs - Date.now(); + if (remainingMs <= 0) { + console.log(`Deadline: expired`); + } else { + const totalMin = Math.floor(remainingMs / 60_000); + const days = Math.floor(totalMin / 1440); + const hours = Math.floor((totalMin % 1440) / 60); + const mins = totalMin % 60; + const parts: string[] = []; + if (days > 0) parts.push(`${days}d`); + if (hours > 0) parts.push(`${hours}h`); + if (mins > 0 || parts.length === 0) parts.push(`${mins}m`); + console.log(`Deadline: ${parts.join(" ")} remaining`); + } + } + } + + if (tokenPrice) { + console.log(`Token price: ${tokenPrice.priceFormatted} ETH`); + } + + if (unclaimedRoyalty !== null && unclaimedRoyalty > 0n) { + console.log(`Unclaimed royalty: ${formatUnits(unclaimedRoyalty, 18)} ETH`); + } + } catch (err) { + console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + }); +} diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts new file mode 100644 index 00000000..10530285 --- /dev/null +++ b/packages/cli/src/config.ts @@ -0,0 +1,98 @@ +import { readFileSync, existsSync } from "node:fs"; +import { resolve } from "node:path"; +import { homedir } from "node:os"; + +/** + * Resolved configuration for the CLI. + * Loaded from environment variables, falling back to `.plotlinkrc` in cwd or home dir. + */ +export interface CliConfig { + privateKey: string; + rpcUrl: string; + chainId?: number; + filebaseAccessKey?: string; + filebaseSecretKey?: string; + filebaseBucket?: string; + supabaseUrl?: string; + supabaseAnonKey?: string; +} + +/** + * Load CLI config by merging env vars over `.plotlinkrc` values. + * + * Priority (highest first): + * 1. Environment variables (PLOTLINK_PRIVATE_KEY, PLOTLINK_RPC_URL, etc.) + * 2. `.plotlinkrc` JSON file in cwd + * 3. `.plotlinkrc` JSON file in home dir + */ +export function loadConfig(): CliConfig { + const rc = loadRcFile(); + + const privateKey = env("PLOTLINK_PRIVATE_KEY") ?? rc.privateKey; + const rpcUrl = env("PLOTLINK_RPC_URL") ?? rc.rpcUrl; + + if (!privateKey) { + throw new Error( + "Missing private key. Set PLOTLINK_PRIVATE_KEY env var or add \"privateKey\" to .plotlinkrc", + ); + } + if (!rpcUrl) { + throw new Error( + "Missing RPC URL. Set PLOTLINK_RPC_URL env var or add \"rpcUrl\" to .plotlinkrc", + ); + } + + const chainIdRaw = env("PLOTLINK_CHAIN_ID") ?? rc.chainId; + const chainId = chainIdRaw ? Number(chainIdRaw) : undefined; + + return { + privateKey, + rpcUrl, + chainId, + filebaseAccessKey: env("PLOTLINK_FILEBASE_ACCESS_KEY") ?? rc.filebaseAccessKey, + filebaseSecretKey: env("PLOTLINK_FILEBASE_SECRET_KEY") ?? rc.filebaseSecretKey, + filebaseBucket: env("PLOTLINK_FILEBASE_BUCKET") ?? rc.filebaseBucket, + supabaseUrl: env("PLOTLINK_SUPABASE_URL") ?? rc.supabaseUrl, + supabaseAnonKey: env("PLOTLINK_SUPABASE_ANON_KEY") ?? rc.supabaseAnonKey, + }; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function env(name: string): string | undefined { + const val = process.env[name]; + return val && val.trim().length > 0 ? val.trim() : undefined; +} + +interface RcData { + privateKey?: string; + rpcUrl?: string; + chainId?: string; + filebaseAccessKey?: string; + filebaseSecretKey?: string; + filebaseBucket?: string; + supabaseUrl?: string; + supabaseAnonKey?: string; +} + +function loadRcFile(): RcData { + const candidates = [ + resolve(process.cwd(), ".plotlinkrc"), + resolve(homedir(), ".plotlinkrc"), + ]; + + for (const filepath of candidates) { + if (existsSync(filepath)) { + try { + const raw = readFileSync(filepath, "utf-8"); + return JSON.parse(raw) as RcData; + } catch { + // Ignore malformed rc files + } + } + } + + return {}; +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 00000000..c7f9ecdc --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,21 @@ +import { Command } from "commander"; +import { registerCreate } from "./commands/create.js"; +import { registerChain } from "./commands/chain.js"; +import { registerStatus } from "./commands/status.js"; +import { registerClaim } from "./commands/claim.js"; +import { registerAgentRegister } from "./commands/agent-register.js"; + +const program = new Command(); + +program + .name("plotlink") + .description("CLI for the PlotLink protocol on Base") + .version("0.1.0"); + +registerCreate(program); +registerChain(program); +registerStatus(program); +registerClaim(program); +registerAgentRegister(program); + +program.parse(); diff --git a/packages/cli/src/sdk.ts b/packages/cli/src/sdk.ts new file mode 100644 index 00000000..ba442edf --- /dev/null +++ b/packages/cli/src/sdk.ts @@ -0,0 +1,36 @@ +import { PlotLink } from "@plotlink/sdk"; +import type { PlotLinkConfig } from "@plotlink/sdk"; +import { loadConfig } from "./config.js"; + +/** + * Build a PlotLink SDK client from CLI config. + * + * @param options.ipfs - Whether IPFS (Filebase) credentials are required. + * Commands that upload content (create, chain) need this; read-only commands don't. + */ +export function buildClient(options: { ipfs: boolean }): PlotLink { + const cfg = loadConfig(); + + const config: PlotLinkConfig = { + privateKey: cfg.privateKey, + rpcUrl: cfg.rpcUrl, + chainId: cfg.chainId, + }; + + if (options.ipfs) { + if (!cfg.filebaseAccessKey || !cfg.filebaseSecretKey || !cfg.filebaseBucket) { + throw new Error( + "Filebase credentials required for this command. " + + "Set PLOTLINK_FILEBASE_ACCESS_KEY, PLOTLINK_FILEBASE_SECRET_KEY, " + + "and PLOTLINK_FILEBASE_BUCKET env vars or add them to .plotlinkrc", + ); + } + config.filebase = { + accessKey: cfg.filebaseAccessKey, + secretKey: cfg.filebaseSecretKey, + bucket: cfg.filebaseBucket, + }; + } + + return new PlotLink(config); +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 00000000..1358d953 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2021", + "lib": ["ES2021"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "noEmit": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts new file mode 100644 index 00000000..c2b854b4 --- /dev/null +++ b/packages/cli/tsup.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm"], + dts: false, + splitting: false, + sourcemap: true, + clean: true, + outDir: "dist", + banner: { + js: "#!/usr/bin/env node", + }, +}); diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index ab9acbd2..472589d7 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -5,6 +5,7 @@ import { keccak256, toHex, decodeEventLog, + formatUnits, type PublicClient, type WalletClient, type Address, @@ -103,6 +104,13 @@ export interface RoyaltyInfo { beneficiary: Address; } +export interface TokenPriceInfo { + /** Cost (in reserve token wei) to mint 1 unit of the storyline token. */ + priceRaw: bigint; + /** priceRaw formatted with 18 decimals. */ + priceFormatted: string; +} + // --------------------------------------------------------------------------- // Client // --------------------------------------------------------------------------- @@ -525,6 +533,38 @@ export class PlotLink { return txHash; } + // ------------------------------------------------------------------------- + // Price methods + // ------------------------------------------------------------------------- + + /** + * Get the current bonding-curve price for a storyline token. + * + * Calls MCV2_Bond.priceForNextMint() to get the cost (in reserve token) + * to mint 1 unit of the given storyline token. + * + * @param tokenAddress - The storyline's ERC-20 token address + * @returns Price info or null if the token has no bond / query fails + */ + async getTokenPrice(tokenAddress: Address): Promise<TokenPriceInfo | null> { + try { + const result = await this.publicClient.readContract({ + address: this.mcv2Bond, + abi: mcv2BondAbi, + functionName: "priceForNextMint", + args: [tokenAddress], + }); + + const priceRaw = BigInt(result as bigint); + return { + priceRaw, + priceFormatted: formatUnits(priceRaw, 18), + }; + } catch { + return null; + } + } + // ------------------------------------------------------------------------- // Internal helpers // ------------------------------------------------------------------------- diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 53fa7dfe..6addc1f0 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -29,6 +29,7 @@ export type { RegisterAgentResult, SetAgentWalletResult, RoyaltyInfo, + TokenPriceInfo, } from "./client"; export type { FilebaseConfig } from "./ipfs";