diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffd0ee7b..dd0a73d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: cache: "pnpm" - name: Install dependencies - run: pnpm install + run: pnpm install --no-frozen-lockfile - name: Check formatting run: pnpm run format:check diff --git a/package.json b/package.json index 5616cf0b..fb779b92 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "cmdk": "^1.1.1", "cookies-next": "^6.0.0", "date-fns": "^4.1.0", - "etsy-ts": "^4.2.0", + "etsy-ts": "4.2.0", "discord-api-types": "^0.38.17", "exa-js": "^1.8.8", "fast-deep-equal": "^3.1.3", @@ -140,6 +140,9 @@ "typescript": "^5.8.2", "typescript-eslint": "^8.27.0" }, + "patchedDependencies": { + "etsy-ts@4.2.0": "patches/etsy-ts@4.2.0.patch" + }, "ct3aMetadata": { "initVersion": "7.39.3" }, diff --git a/patches/etsy-ts@4.2.0.patch b/patches/etsy-ts@4.2.0.patch new file mode 100644 index 00000000..fdd4a1d2 --- /dev/null +++ b/patches/etsy-ts@4.2.0.patch @@ -0,0 +1,13 @@ +diff --git a/dist/api/data-contracts.d.ts b/dist/api/data-contracts.d.ts +index 5e8b06b081a84e93153e2e325ed76d53669fb033..675b39b3240104358379d1ef7ddc1d46bc1a3384 100644 +--- a/dist/api/data-contracts.d.ts ++++ b/dist/api/data-contracts.d.ts +@@ -2082,7 +2082,7 @@ export interface ICreateDraftListingPayload { + /** An enumerated string indicating who made the product. Helps buyers locate the listing under the Handmade heading. Requires 'is_supply' and 'when_made'. */ + who_made: "i_did" | "someone_else" | "collective"; + /** An enumerated string for the era in which the maker made the product in this listing. Helps buyers locate the listing under the Vintage heading. Requires 'is_supply' and 'who_made'. */ +- when_made: "made_to_order" | "2020_2023" | "2010_2019" | "2004_2009" | "before_2004" | "2000_2003" | "1990s" | "1980s" | "1970s" | "1960s" | "1950s" | "1940s" | "1930s" | "1920s" | "1910s" | "1900s" | "1800s" | "1700s" | "before_1700"; ++ when_made: "made_to_order" | "2020_2025" | "2010_2019" | "2006_2009" | "before_2006" | "2000_2005" | "1990s" | "1980s" | "1970s" | "1960s" | "1950s" | "1940s" | "1930s" | "1920s" | "1910s" | "1900s" | "1800s" | "1700s" | "before_1700"; + /** + * The numerical taxonomy ID of the listing. See [SellerTaxonomy](/documentation/reference#tag/SellerTaxonomy) and [BuyerTaxonomy](/documentation/reference#tag/BuyerTaxonomy) for more information. + * @min 1 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cafcd36..4a403f4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + etsy-ts@4.2.0: + hash: 1b38e33f32486e10e0a008fd3505d112c6ba6a828a3227ca85c9f2be552d2c1e + path: patches/etsy-ts@4.2.0.patch + importers: .: @@ -179,12 +184,12 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 - etsy-ts: - specifier: ^4.2.0 - version: 4.2.0 discord-api-types: specifier: ^0.38.17 version: 0.38.18 + etsy-ts: + specifier: 4.2.0 + version: 4.2.0(patch_hash=1b38e33f32486e10e0a008fd3505d112c6ba6a828a3227ca85c9f2be552d2c1e) exa-js: specifier: ^1.8.8 version: 1.8.8(encoding@0.1.13)(ws@8.18.3)(zod@3.25.56) @@ -2091,14 +2096,14 @@ packages: '@types/node@18.19.111': resolution: {integrity: sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==} - '@types/node@18.19.121': - resolution: {integrity: sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==} + '@types/node@18.19.124': + resolution: {integrity: sha512-hY4YWZFLs3ku6D2Gqo3RchTd9VRCcrjqp/I0mmohYeUVA5Y8eCXKJEasHxLAJVZRJuQogfd1GiJ9lgogBgKeuQ==} '@types/node@20.19.0': resolution: {integrity: sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==} - '@types/node@20.19.9': - resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} + '@types/node@20.19.13': + resolution: {integrity: sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==} '@types/pg@8.11.0': resolution: {integrity: sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==} @@ -2503,6 +2508,9 @@ packages: peerDependencies: axios: '>= 0.18 < 0.19.0 || >= 0.19.1' + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + axios@1.7.7: resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} @@ -3301,6 +3309,15 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -3649,8 +3666,8 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -3847,8 +3864,8 @@ packages: jose@6.0.11: resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==} - js-tiktoken@1.0.20: - resolution: {integrity: sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==} + js-tiktoken@1.0.21: + resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3857,9 +3874,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -3901,8 +3915,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - langsmith@0.3.50: - resolution: {integrity: sha512-yosW6sR0EFnMnYKKyBmcqTNknDVOs+dUfcswWk80JoRxox6WEyel7hmSkSzabP/GmTs0hXbrtc+lZwpJWSnI0w==} + langsmith@0.3.67: + resolution: {integrity: sha512-l4y3RmJ9yWF5a29fLg3eWZQxn6Q6dxTOgLGgQHzPGZHF3NUynn+A+airYIe/Yt4rwjGbuVrABAPsXBkVu/Hi7g==} peerDependencies: '@opentelemetry/api': '*' '@opentelemetry/exporter-trace-otlp-proto': '*' @@ -4413,8 +4427,8 @@ packages: sass: optional: true - node-abi@3.75.0: - resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} + node-abi@3.77.0: + resolution: {integrity: sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==} engines: {node: '>=10'} node-addon-api@7.1.1: @@ -5183,8 +5197,8 @@ packages: resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} engines: {node: '>= 10'} - socks@2.8.6: - resolution: {integrity: sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==} + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} sonner@2.0.5: @@ -5204,9 +5218,6 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - sqlite3@5.1.7: resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} @@ -5803,7 +5814,7 @@ snapshots: '@anthropic-ai/sdk@0.40.1(encoding@0.1.13)': dependencies: - '@types/node': 18.19.121 + '@types/node': 18.19.124 '@types/node-fetch': 2.6.13 abort-controller: 3.0.0 agentkeepalive: 4.6.0 @@ -6176,7 +6187,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.9 + '@types/node': 20.19.13 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -6203,8 +6214,8 @@ snapshots: ansi-styles: 5.2.0 camelcase: 6.3.0 decamelize: 1.2.0 - js-tiktoken: 1.0.20 - langsmith: 0.3.50(@opentelemetry/api@1.9.0)(openai@5.3.0(ws@8.18.3)(zod@3.25.56)) + js-tiktoken: 1.0.21 + langsmith: 0.3.67(@opentelemetry/api@1.9.0)(openai@5.3.0(ws@8.18.3)(zod@3.25.56)) mustache: 4.2.0 p-queue: 6.6.2 p-retry: 4.6.2 @@ -7382,14 +7393,14 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 20.19.9 + '@types/node': 20.19.13 form-data: 4.0.4 '@types/node@18.19.111': dependencies: undici-types: 5.26.5 - '@types/node@18.19.121': + '@types/node@18.19.124': dependencies: undici-types: 5.26.5 @@ -7397,13 +7408,13 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@20.19.9': + '@types/node@20.19.13': dependencies: undici-types: 6.21.0 '@types/pg@8.11.0': dependencies: - '@types/node': 20.19.9 + '@types/node': 20.19.13 pg-protocol: 1.10.3 pg-types: 4.1.0 @@ -7421,7 +7432,7 @@ snapshots: '@types/sqlite3@3.1.11': dependencies: - '@types/node': 20.19.9 + '@types/node': 20.19.13 '@types/stack-utils@2.0.3': {} @@ -7433,7 +7444,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.9 + '@types/node': 20.19.13 '@types/yargs-parser@21.0.3': {} @@ -7805,9 +7816,17 @@ snapshots: axe-core@4.10.3: {} - axios-auth-refresh@3.3.6(axios@1.7.7): + axios-auth-refresh@3.3.6(axios@1.11.0): dependencies: - axios: 1.7.7 + axios: 1.11.0 + + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug axios@1.7.7: dependencies: @@ -7979,7 +7998,7 @@ snapshots: cloudflare@4.3.0(encoding@0.1.13): dependencies: - '@types/node': 18.19.121 + '@types/node': 18.19.124 '@types/node-fetch': 2.6.13 abort-controller: 3.0.0 agentkeepalive: 4.6.0 @@ -8670,11 +8689,11 @@ snapshots: etag@1.8.1: {} - etsy-ts@4.2.0: + etsy-ts@4.2.0(patch_hash=1b38e33f32486e10e0a008fd3505d112c6ba6a828a3227ca85c9f2be552d2c1e): dependencies: - axios: 1.7.7 - axios-auth-refresh: 3.3.6(axios@1.7.7) - form-data: 4.0.3 + axios: 1.11.0 + axios-auth-refresh: 3.3.6(axios@1.11.0) + form-data: 4.0.4 tslib: 2.8.1 transitivePeerDependencies: - debug @@ -8819,6 +8838,8 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.11: {} + follow-redirects@1.15.9: {} for-each@0.3.5: @@ -9050,7 +9071,7 @@ snapshots: groq-sdk@0.3.0(encoding@0.1.13): dependencies: - '@types/node': 18.19.121 + '@types/node': 18.19.124 '@types/node-fetch': 2.6.13 abort-controller: 3.0.0 agentkeepalive: 4.6.0 @@ -9304,10 +9325,7 @@ snapshots: internmap@2.0.3: {} - ip-address@9.0.5: - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 + ip-address@10.0.1: optional: true ipaddr.js@1.9.1: {} @@ -9501,7 +9519,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.9 + '@types/node': 20.19.13 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -9513,7 +9531,7 @@ snapshots: jose@6.0.11: {} - js-tiktoken@1.0.20: + js-tiktoken@1.0.21: dependencies: base64-js: 1.5.1 @@ -9523,9 +9541,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsbn@1.1.0: - optional: true - json-bigint@1.0.0: dependencies: bignumber.js: 9.3.0 @@ -9574,7 +9589,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - langsmith@0.3.50(@opentelemetry/api@1.9.0)(openai@5.3.0(ws@8.18.3)(zod@3.25.56)): + langsmith@0.3.67(@opentelemetry/api@1.9.0)(openai@5.3.0(ws@8.18.3)(zod@3.25.56)): dependencies: '@types/uuid': 10.0.0 chalk: 4.1.2 @@ -10290,7 +10305,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - node-abi@3.75.0: + node-abi@3.77.0: dependencies: semver: 7.7.2 @@ -10643,7 +10658,7 @@ snapshots: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 2.0.0 - node-abi: 3.75.0 + node-abi: 3.77.0 pump: 3.0.3 rc: 1.2.8 simple-get: 4.0.1 @@ -11172,14 +11187,14 @@ snapshots: dependencies: agent-base: 6.0.2 debug: 4.4.1 - socks: 2.8.6 + socks: 2.8.7 transitivePeerDependencies: - supports-color optional: true - socks@2.8.6: + socks@2.8.7: dependencies: - ip-address: 9.0.5 + ip-address: 10.0.1 smart-buffer: 4.2.0 optional: true @@ -11194,9 +11209,6 @@ snapshots: split2@4.2.0: {} - sprintf-js@1.1.3: - optional: true - sqlite3@5.1.7: dependencies: bindings: 1.5.0 diff --git a/src/server/auth/custom-providers/etsy.ts b/src/server/auth/custom-providers/etsy.ts index 2358607b..20e3e3e2 100644 --- a/src/server/auth/custom-providers/etsy.ts +++ b/src/server/auth/custom-providers/etsy.ts @@ -10,7 +10,8 @@ export interface EtsyProfile { image_url_75x75?: string | null; } -export const etsyScopes = "email_r shops_r listings_r"; +export const etsyScopes = + "address_r address_w billing_r cart_r cart_w email_r favorites_r favorites_w feedback_r listings_d listings_r listings_w profile_r profile_w recommend_r recommend_w shops_r shops_w transactions_r transactions_w"; export default function EtsyProvider

( options: OAuthUserConfig

, diff --git a/src/toolkits/toolkits/etsy/base.ts b/src/toolkits/toolkits/etsy/base.ts index d585bf7d..31ee13ac 100644 --- a/src/toolkits/toolkits/etsy/base.ts +++ b/src/toolkits/toolkits/etsy/base.ts @@ -4,6 +4,8 @@ import { EtsyTools } from "./tools/tools"; import { getListings } from "@/toolkits/toolkits/etsy/tools/get-listings/base"; +import { createDraftListing } from "@/toolkits/toolkits/etsy/tools/create-draft-listing/base"; + import type { ToolkitConfig } from "@/toolkits/types"; export const etsyParameters = z.object({}); @@ -14,6 +16,7 @@ export const baseEtsyToolkitConfig: ToolkitConfig< > = { tools: { [EtsyTools.getListings]: getListings, + [EtsyTools.createDraftListing]: createDraftListing, }, parameters: etsyParameters, }; diff --git a/src/toolkits/toolkits/etsy/client.tsx b/src/toolkits/toolkits/etsy/client.tsx index dc79b09b..1274e57f 100644 --- a/src/toolkits/toolkits/etsy/client.tsx +++ b/src/toolkits/toolkits/etsy/client.tsx @@ -10,6 +10,8 @@ import { createClientToolkit } from "@/toolkits/create-toolkit"; import { getListingsClientConfig } from "@/toolkits/toolkits/etsy/tools/get-listings/client"; +import { createDraftListingClientConfig } from "@/toolkits/toolkits/etsy/tools/create-draft-listing/client"; + import { ToolkitGroups } from "@/toolkits/types"; import { EtsyTools } from "./tools/tools"; @@ -17,7 +19,8 @@ export const etsyClientToolkit = createClientToolkit( baseEtsyToolkitConfig, { name: "Etsy Toolkit", - description: "Etsy toolkit for fetching listing details.", + description: + "Etsy toolkit for Listing management, Payment management, Receipt management, Shipping management, Shop management and more!", icon: SiEtsy, form: null, type: ToolkitGroups.DataSource, @@ -37,5 +40,6 @@ export const etsyClientToolkit = createClientToolkit( }, { [EtsyTools.getListings]: getListingsClientConfig, + [EtsyTools.createDraftListing]: createDraftListingClientConfig, }, ); diff --git a/src/toolkits/toolkits/etsy/server.ts b/src/toolkits/toolkits/etsy/server.ts index a2cc94f5..9985fc3e 100644 --- a/src/toolkits/toolkits/etsy/server.ts +++ b/src/toolkits/toolkits/etsy/server.ts @@ -10,11 +10,13 @@ import { EtsyTools } from "./tools/tools"; import { EtsySecurityDataStorage } from "./security-data-storage"; import { getListingsServerConfig } from "@/toolkits/toolkits/etsy/tools/get-listings/server"; +import { createDraftListingServerConfig } from "@/toolkits/toolkits/etsy/tools/create-draft-listing/server"; export const etsyToolkitServer = createServerToolkit( baseEtsyToolkitConfig, "You have access to the Etsy toolkit for general account management. Currently, this toolkit provides:\n" + - "- **Get Listings**: Retrieves all listings and their image URLs associated with the shop associated with the signed-in user.\n\n", + "- **Get Listings By Shop**: Retrieves listings associated with the shop owned by authenticated user. Has the ability to fetch associations relating to each listing as well.\n" + + "- **Create Draft Listing**: Creates a new draft listing in the shop owned by authenticated user. Accepts a variety of inputs to assign to listing.\n", async () => { const account = await api.accounts.getAccountByProvider("etsy"); @@ -32,7 +34,14 @@ export const etsyToolkitServer = createServerToolkit( }); return { - [EtsyTools.getListings]: getListingsServerConfig(etsy), + [EtsyTools.getListings]: getListingsServerConfig( + etsy, + account.providerAccountId, + ), + [EtsyTools.createDraftListing]: createDraftListingServerConfig( + etsy, + account.providerAccountId, + ), }; }, ); diff --git a/src/toolkits/toolkits/etsy/tools/create-draft-listing/base.ts b/src/toolkits/toolkits/etsy/tools/create-draft-listing/base.ts new file mode 100644 index 00000000..603e2866 --- /dev/null +++ b/src/toolkits/toolkits/etsy/tools/create-draft-listing/base.ts @@ -0,0 +1,299 @@ +import { z } from "zod"; +import { createBaseTool } from "@/toolkits/create-tool"; +import type { IShopListing } from "etsy-ts"; + +export const createDraftListing = createBaseTool({ + description: + "Creates a new draft listing in the Etsy shop associated with the authenticated user." + + "Requires input: quantity, title, description, price, who_made, when_made, is_supply, and taxonomy_id." + + "Optional inputs include: (if not specified, the default value is null" + + "shipping_profile_id — ID of the shipping profile to associate with the listing. REQUIRED if listing type is physical." + + "return_policy_id - the numeric ID of the Return Policy." + + "materials - A list of material strings for materials used in the product. Valid materials strings contain only letters, numbers, and whitespace characters. (regex: /[^\\p{L}\\p{Nd}\\p{Zs}]/u)." + + "shop_section_id - The numeric ID of the shop section for this listing." + + "processing_min - The minimum number of days it takes to produce the item." + + "processing_max - The maximum number of days it takes to produce the item." + + "readiness_state_id - The numeric ID of the processing profile associated with the listing. Required when type is physical." + + "tags - A comma-separated list of tag strings for the listing. When creating or updating a listing, valid tag strings contain only letters, numbers, whitespace characters, -, ', ™, ©, and ®. (regex: /[^\\p{L}\\p{Nd}\\p{Zs}-'™©®]/u)." + + 'styles - An array of style strings for this listing, each of which is free-form text string such as "Formal", or "Steampunk". When creating or updating a listing, the listing may have up to two styles. Valid style strings contain only letters, numbers, and whitespace characters. (regex: /[^\\p{L}\\p{Nd}\\p{Zs}]/u).' + + "item_weight - The numeric weight of the product measured in units set in 'item_weight_unit'. If set, the values must be greater than 0." + + "item_length - The numeric length of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0." + + "item_width - The numeric width of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0." + + "item_height - The numeric height of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0." + + "item_weight_unit - The unit of measurement for the weight of the product. Valid values are 'g' (grams), 'kg' (kilograms), 'oz' (ounces), and 'lb' (pounds). Required if 'item_weight' is provided." + + "item_dimensions_unit - The unit of measurement for the dimensions of the product. Valid values are 'mm' (millimeters), 'cm' (centimeters), 'm' (meters), 'in' (inches), and 'ft' (feet). Required if any of 'item_length', 'item_width', or 'item_height' is provided." + + "is_personalizable - When ture, indicates that the listing is personalizable." + + "personalization_is_required - When true, indicates that personalization is required for the listing. Will only change if _is_personalizable is true" + + "personalization_char_count_max - The maximum number of characters allowed for personalization. Will only change if _is_personalizable is true." + + "personalization_instructions - Instructions for personalization. Will only change if _is_personalizable is true." + + "production_partner_ids - An array of unique IDs of production partner ids." + + "image_ids - An array of numeric image IDs of the images in a listing, which can include up to 10 images." + + "is_customizable - When true, a buyer may contact the seller for a customized order. The default value is true when a shop accepts custom orders. Does not apply to shops that do not accept custom orders." + + "should_auto_renew - When true, renews a listing for four months upon expiration." + + "is_taxable - When true, applicable shop tax rates apply to this listing at checkout" + + "type - An enumerated type string that indicates whether the listing is physical or a digital download or both.", + inputSchema: z.object({ + title: z + .string() + .min(1) + .max(140) + .describe( + "The title of the listing. Can only contain letters, numbers, punctuation marks, mathematical symbols, whitespace characters", + ), + description: z + .string() + .min(1) + .max(1000) + .describe("The description of the listing."), + price: z + .number() + .positive() + .describe( + "The price of the item. Must be a positive value with up to two decimal places.", + ), + quantity: z + .number() + .min(1) + .describe( + "The quantity of items available in this listing. Must be at least 1.", + ), + who_made: z + .enum(["i_did", "collective", "someone_else"]) + .describe("Who made the item."), + when_made: z + .enum([ + "made_to_order", + "2020_2025", + "2010_2019", + "2006_2009", + "before_2006", + "2000_2005", + "1990s", + "1980s", + "1970s", + "1960s", + "1950s", + "1940s", + "1930s", + "1920s", + "1910s", + "1900s", + "1800s", + "1700s", + "before_1700", + ]) + .describe( + "An enumerated string for the era in which the maker made the product in this listing. Helps buyers locate the listing under the Vintage heading.", + ), + taxonomy_id: z + .number() + .int() + .positive() + .describe("The taxonomy ID for the category of the listing."), + is_supply: z + .boolean() + .describe( + "When true, tags the listing as a supply product, else indicates that it's a finished product.", + ), + shipping_profile_id: z + .number() + .int() + .positive() + .optional() + .describe( + "ID of the shipping profile to associate with the listing. REQUIRED if listing type is physical.", + ), + return_policy_id: z + .number() + .int() + .positive() + .optional() + .describe("The numeric ID of the Return Policy."), + materials: z + .array( + z + .string() + .regex(/[^\p{L}\p{Nd}\p{Zs}]/u, { + message: + "Valid materials strings contain only letters, numbers, and whitespace characters.", + }) + .min(1) + .max(25) + .describe( + "A material string for materials used in the product. Valid materials strings contain only letters, numbers, and whitespace characters.", + ), + ) + .max(25), + shop_section_id: z + .number() + .int() + .positive() + .optional() + .describe("The numeric ID of the shop section for this listing."), + processing_min: z + .number() + .int() + .min(0) + .optional() + .describe("The minimum number of days it takes to produce the item."), + processing_max: z + .number() + .int() + .min(0) + .optional() + .describe("The maximum number of days it takes to produce the item."), + readiness_state_id: z + .number() + .int() + .positive() + .optional() + .describe( + "The numeric ID of the processing profile associated with the listing. Required when type is physical.", + ), + tags: z + .array( + z + .string() + .regex(/^[\p{L}\p{Nd}\p{Zs}'™©®-]+$/u, { + message: + "Valid tag strings contain only letters, numbers, whitespace characters, -, ', ™, ©, and ®.", + }) + .min(1) + .max(25) + .describe( + "A tag string for the listing. When creating or updating a listing, valid tag strings contain only letters, numbers, whitespace characters, -, ', ™, ©, and ®.", + ), + ) + .max(13) + .describe("A list of tag strings for the listing."), + styles: z + .array( + z + .string() + .regex(/[^\p{L}\p{Nd}\p{Zs}]/u, { + message: + "Valid style strings contain only letters, numbers, and whitespace characters.", + }) + .min(1) + .max(25) + .describe( + 'A style string for this listing, such as "Formal", or "Steampunk". When creating or updating a listing, the listing may have up to two styles. Valid style strings contain only letters, numbers, and whitespace characters.', + ), + ) + .max(2) + .describe("An array of style strings for this listing."), + item_weight: z + .number() + .positive() + .optional() + .describe( + "The numeric weight of the product measured in units set in 'item_weight_unit'. If set, the values must be greater than 0.", + ), + item_length: z + .number() + .positive() + .optional() + .describe( + "The numeric length of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0.", + ), + item_width: z + .number() + .positive() + .optional() + .describe( + "The numeric width of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0.", + ), + item_height: z + .number() + .positive() + .optional() + .describe( + "The numeric height of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0.", + ), + item_weight_unit: z + .enum(["g", "kg", "oz", "lb"]) + .optional() + .describe( + "The unit of measurement for the weight of the product. Valid values are 'g' (grams), 'kg' (kilograms), 'oz' (ounces), and 'lb' (pounds). Required if 'item_weight' is provided.", + ), + item_dimensions_unit: z + .enum(["mm", "cm", "m", "in", "ft"]) + .optional() + .describe( + "The unit of measurement for the dimensions of the product. Valid values are 'mm' (millimeters), 'cm' (centimeters), 'm' (meters), 'in' (inches), and 'ft' (feet). Required if any of 'item_length', 'item_width', or 'item_height' is provided.", + ), + is_personalizable: z + .boolean() + .optional() + .describe("When true, indicates that the listing is personalizable."), + personalization_is_required: z + .boolean() + .optional() + .describe( + "When true, indicates that personalization is required for the listing. Will only change if _is_personalizable is true", + ), + personalization_char_count_max: z + .number() + .int() + .min(1) + .max(250) + .optional() + .describe( + "The maximum number of characters allowed for personalization. Will only change if _is_personalizable is true.", + ), + personalization_instructions: z + .string() + .max(500) + .optional() + .describe( + "Instructions for personalization. Will only change if _is_personalizable is true.", + ), + production_partner_ids: z + .array( + z + .number() + .int() + .positive() + .describe("A unique ID of a production partner."), + ) + .optional() + .describe("An array of unique IDs of production partner ids."), + image_ids: z + .array( + z + .number() + .int() + .positive() + .describe("A numeric image ID of an image in a listing."), + ) + .max(10) + .optional() + .describe( + "An array of numeric image IDs of the images in a listing, which can include up to 10 images.", + ), + is_customizable: z + .boolean() + .optional() + .describe( + "When true, a buyer may contact the seller for a customized order. The default value is true when a shop accepts custom orders. Does not apply to shops that do not accept custom orders.", + ), + should_auto_renew: z + .boolean() + .optional() + .describe("When true, renews a listing for four months upon expiration."), + is_taxable: z + .boolean() + .optional() + .describe( + "When true, applicable shop tax rates apply to this listing at checkout", + ), + type: z + .enum(["physical", "download", "both"]) + .describe( + "An enumerated type string that indicates whether the listing is physical or a digital download or both.", + ), + }), + outputSchema: z.object({ + result: z.custom(), + }), +}); diff --git a/src/toolkits/toolkits/etsy/tools/create-draft-listing/client.tsx b/src/toolkits/toolkits/etsy/tools/create-draft-listing/client.tsx new file mode 100644 index 00000000..92fe60d4 --- /dev/null +++ b/src/toolkits/toolkits/etsy/tools/create-draft-listing/client.tsx @@ -0,0 +1,21 @@ +import { Search } from "lucide-react"; +import type { ClientToolConfig } from "@/toolkits/types"; +import type { createDraftListing } from "./base"; + +export const createDraftListingClientConfig: ClientToolConfig< + typeof createDraftListing.inputSchema.shape, + typeof createDraftListing.outputSchema.shape +> = { + CallComponent: ({ isPartial }) => ( +

+ + {isPartial && ...} +
+ ), + ResultComponent: ({ result: { result } }) => ( +
+

Listing

+
{result.title}
) +
+ ), +}; diff --git a/src/toolkits/toolkits/etsy/tools/create-draft-listing/server.ts b/src/toolkits/toolkits/etsy/tools/create-draft-listing/server.ts new file mode 100644 index 00000000..f351962d --- /dev/null +++ b/src/toolkits/toolkits/etsy/tools/create-draft-listing/server.ts @@ -0,0 +1,122 @@ +import type { Etsy, ICreateDraftListingPayload } from "etsy-ts"; + +import type { ServerToolConfig } from "@/toolkits/types"; +import type { createDraftListing } from "./base"; + +export const createDraftListingServerConfig = ( + etsy: Etsy, + userId: string, +): ServerToolConfig< + typeof createDraftListing.inputSchema.shape, + typeof createDraftListing.outputSchema.shape +> => { + return { + callback: async ({ + title, + description, + price, + quantity, + who_made, + is_supply, + when_made, + taxonomy_id, + shipping_profile_id, + shop_section_id, + materials, + return_policy_id, + processing_min, + processing_max, + readiness_state_id, + tags, + styles, + item_weight, + item_weight_unit, + item_length, + item_width, + item_height, + item_dimensions_unit, + is_personalizable, + personalization_is_required, + personalization_char_count_max, + personalization_instructions, + production_partner_ids, + image_ids, + is_customizable, + should_auto_renew, + is_taxable, + type, + }) => { + try { + const shop = await etsy.Shop.getShopByOwnerUserId(Number(userId)); + + const shopId = shop.data.shop_id; + + if (!shopId) throw new Error("Missing Etsy shop ID"); + + const params: ICreateDraftListingPayload = { + title, + description, + price, + quantity, + who_made, + is_supply, + when_made, + taxonomy_id, + materials, + type, + styles, + ...(shipping_profile_id !== undefined ? { shipping_profile_id } : {}), + ...(shop_section_id !== undefined ? { shop_section_id } : {}), + ...(return_policy_id !== undefined ? { return_policy_id } : {}), + ...(processing_min !== undefined ? { processing_min } : {}), + ...(processing_max !== undefined ? { processing_max } : {}), + ...(readiness_state_id !== undefined ? { readiness_state_id } : {}), + ...(item_weight !== undefined ? { item_weight } : {}), + ...(item_weight_unit !== undefined ? { item_weight_unit } : {}), + ...(item_length !== undefined ? { item_length } : {}), + ...(item_width !== undefined ? { item_width } : {}), + ...(item_height !== undefined ? { item_height } : {}), + ...(item_dimensions_unit !== undefined + ? { item_dimensions_unit } + : {}), + ...(is_personalizable !== undefined ? { is_personalizable } : {}), + ...(personalization_is_required !== undefined + ? { personalization_is_required } + : {}), + ...(personalization_char_count_max !== undefined + ? { personalization_char_count_max } + : {}), + ...(personalization_instructions !== undefined + ? { personalization_instructions } + : {}), + ...(production_partner_ids !== undefined + ? { production_partner_ids } + : {}), + ...(image_ids !== undefined ? { image_ids } : {}), + ...(is_customizable !== undefined ? { is_customizable } : {}), + ...(should_auto_renew !== undefined ? { should_auto_renew } : {}), + ...(is_taxable !== undefined ? { is_taxable } : {}), + ...(type !== undefined ? { type } : {}), + }; + + const listing = await etsy.ShopListing.createDraftListing( + shopId, + params, + undefined, + ); + + if (!listing.data) throw new Error("Missing Etsy listing"); + + return { + result: listing.data, + }; + } catch (error) { + console.error("Etsy API error:", error); + throw new Error("Failed to create draft listing on Etsy"); + } + }, + message: + "Successfully created the Etsy draft listing. The user is shown the responses in the UI. Do not reiterate them. " + + "If you called this tool because the user asked a question, answer the question.", + }; +}; diff --git a/src/toolkits/toolkits/etsy/tools/get-listings/base.ts b/src/toolkits/toolkits/etsy/tools/get-listings/base.ts index 74dc6515..b67c9734 100644 --- a/src/toolkits/toolkits/etsy/tools/get-listings/base.ts +++ b/src/toolkits/toolkits/etsy/tools/get-listings/base.ts @@ -4,8 +4,55 @@ import type { IShopListing } from "etsy-ts"; export const getListings = createBaseTool({ description: - "Fetches all listings from the Etsy shop associated with the authenticated user.", - inputSchema: z.object({}), + "Fetches all listings from the Etsy shop associated with the authenticated user." + + "No additional input is required. But there are optional ones:" + + "limit — page size (default 25; max 100)" + + "offset — number of results to skip (use for pagination)" + + "sort_on — field to sort by (e.g., created, updated, price, score). Note: some sorts only work when combined with a search option; score is always descending regardless of sort_order." + + "sort_order — up (ascending) or down (descending), when supported by the chosen sort_on." + + "includes - An enumerated string that attaches a valid association. Acceptable inputs are 'Shipping', 'Shop', 'Images', 'User', 'Translations' and 'Inventory'.", + inputSchema: z.object({ + limit: z + .number() + .min(1) + .max(100) + .optional() + .describe( + "Maximum number of items to return. Default: 25. Min: 1. Max: 100", + ), + sort_on: z + .enum(["created", "updated", "price", "score"]) + .optional() + .describe( + "Field to sort by (e.g., created, updated, price, score). Note: some sorts only work when combined with a search option; score is always descending regardless of sort_order. Default: created", + ), + sort_order: z + .enum(["asc", "desc", "up", "down"]) + .optional() + .describe( + "Sort order: up (ascending) or down (descending). Default: desc", + ), + offset: z + .number() + .min(0) + .optional() + .describe("Number of results to skip (use for pagination). Default: 0"), + includes: z + .enum([ + "Shipping", + "Images", + "Shop", + "User", + "Translations", + "Inventory", + "Videos", + ]) + .array() + .optional() + .describe( + "An enumerated string that attaches a valid association. Acceptable inputs are 'Shipping', 'Shop', 'Images', 'User', 'Translations' and 'Inventory'.", + ), + }), outputSchema: z.object({ results: z.array(z.custom()), }), diff --git a/src/toolkits/toolkits/etsy/tools/get-listings/server.ts b/src/toolkits/toolkits/etsy/tools/get-listings/server.ts index f6e11259..77c66250 100644 --- a/src/toolkits/toolkits/etsy/tools/get-listings/server.ts +++ b/src/toolkits/toolkits/etsy/tools/get-listings/server.ts @@ -1,32 +1,34 @@ -import type { Etsy } from "etsy-ts"; +import type { Etsy, IGetListingsByShopParams } from "etsy-ts"; import type { ServerToolConfig } from "@/toolkits/types"; import type { getListings } from "./base"; export const getListingsServerConfig = ( etsy: Etsy, + userId: string, ): ServerToolConfig< typeof getListings.inputSchema.shape, typeof getListings.outputSchema.shape > => { return { - callback: async () => { + callback: async ({ limit, offset, sort_on, sort_order, includes }) => { try { - const user = await etsy.User.getMe(); - - const userId = user.data.user_id; - - if (!userId) throw new Error("Missing Etsy user ID"); - - const shop = await etsy.Shop.getShopByOwnerUserId(userId); + const shop = await etsy.Shop.getShopByOwnerUserId(Number(userId)); const shopId = shop.data.shop_id; if (!shopId) throw new Error("Missing Etsy shop ID"); - const listings = await etsy.ShopListing.getFeaturedListingsByShop({ + const params: IGetListingsByShopParams = { shopId, - }); + ...(limit !== undefined ? { limit } : {}), + ...(offset !== undefined ? { offset } : {}), + ...(sort_on !== undefined ? { sort_on } : {}), + ...(sort_order !== undefined ? { sort_order } : {}), + ...(includes !== undefined ? { includes } : {}), + }; + + const listings = await etsy.ShopListing.getListingsByShop(params); if (!listings.data.results) throw new Error("Missing Etsy listings"); diff --git a/src/toolkits/toolkits/etsy/tools/tools.ts b/src/toolkits/toolkits/etsy/tools/tools.ts index 8af37ff8..f8724452 100644 --- a/src/toolkits/toolkits/etsy/tools/tools.ts +++ b/src/toolkits/toolkits/etsy/tools/tools.ts @@ -1,3 +1,4 @@ export enum EtsyTools { getListings = "get-listings", + createDraftListing = "create-draft-listing", }