chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
import type {
ShopifyAdjustInventoryParams,
ShopifyInventoryAdjustmentResponse,
} from '@/tools/shopify/types'
import { INVENTORY_ADJUSTMENT_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyAdjustInventoryTool: ToolConfig<
ShopifyAdjustInventoryParams,
ShopifyInventoryAdjustmentResponse
> = {
id: 'shopify_adjust_inventory',
name: 'Shopify Adjust Inventory',
description: 'Adjust inventory quantity for a product variant at a specific location',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
inventoryItemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Inventory item ID (gid://shopify/InventoryItem/123456789)',
},
locationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Location ID (gid://shopify/Location/123456789)',
},
delta: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Amount to adjust (positive to increase, negative to decrease)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.inventoryItemId) {
throw new Error('Inventory item ID is required')
}
if (!params.locationId) {
throw new Error('Location ID is required')
}
if (params.delta === undefined || params.delta === null) {
throw new Error('Delta is required')
}
return {
query: `
mutation inventoryAdjustQuantities($input: InventoryAdjustQuantitiesInput!) {
inventoryAdjustQuantities(input: $input) {
inventoryAdjustmentGroup {
createdAt
reason
changes {
name
delta
quantityAfterChange
item {
id
sku
}
location {
id
name
}
}
}
userErrors {
field
message
}
}
}
`,
variables: {
input: {
reason: 'correction',
name: 'available',
changes: [
{
inventoryItemId: params.inventoryItemId.trim(),
locationId: params.locationId.trim(),
delta: params.delta,
},
],
},
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to adjust inventory',
output: {},
}
}
const result = data.data?.inventoryAdjustQuantities
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
const adjustmentGroup = result?.inventoryAdjustmentGroup
if (!adjustmentGroup) {
return {
success: false,
error: 'Inventory adjustment was not successful',
output: {},
}
}
return {
success: true,
output: {
inventoryLevel: {
adjustmentGroup,
changes: adjustmentGroup.changes,
},
},
}
},
outputs: {
inventoryLevel: {
type: 'object',
description: 'The inventory adjustment result',
properties: INVENTORY_ADJUSTMENT_OUTPUT_PROPERTIES,
},
},
}
+156
View File
@@ -0,0 +1,156 @@
import type { ShopifyCancelOrderParams, ShopifyCancelOrderResponse } from '@/tools/shopify/types'
import { CANCEL_ORDER_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyCancelOrderTool: ToolConfig<
ShopifyCancelOrderParams,
ShopifyCancelOrderResponse
> = {
id: 'shopify_cancel_order',
name: 'Shopify Cancel Order',
description: 'Cancel an order in your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
orderId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Order ID to cancel (gid://shopify/Order/123456789)',
},
reason: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cancellation reason (CUSTOMER, DECLINED, FRAUD, INVENTORY, STAFF, OTHER)',
},
notifyCustomer: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to notify the customer about the cancellation',
},
restock: {
type: 'boolean',
required: true,
visibility: 'user-or-llm',
description: 'Whether to restock the inventory committed to the order',
},
refundMethod: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
'Optional refund method object, for example {"originalPaymentMethodsRefund": true}',
},
staffNote: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'A note about the cancellation for staff reference',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.orderId) {
throw new Error('Order ID is required to cancel an order')
}
if (!params.reason) {
throw new Error('Cancellation reason is required')
}
if (typeof params.restock !== 'boolean') {
throw new Error('Restock is required')
}
return {
query: `
mutation orderCancel($orderId: ID!, $reason: OrderCancelReason!, $notifyCustomer: Boolean, $refundMethod: OrderCancelRefundMethodInput, $restock: Boolean!, $staffNote: String) {
orderCancel(orderId: $orderId, reason: $reason, notifyCustomer: $notifyCustomer, refundMethod: $refundMethod, restock: $restock, staffNote: $staffNote) {
job {
id
done
}
orderCancelUserErrors {
field
message
code
}
}
}
`,
variables: {
orderId: params.orderId.trim(),
reason: params.reason,
notifyCustomer: params.notifyCustomer ?? false,
refundMethod: params.refundMethod ?? null,
restock: params.restock,
staffNote: params.staffNote?.trim() || null,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to cancel order',
output: {},
}
}
const result = data.data?.orderCancel
if (result?.orderCancelUserErrors?.length > 0) {
return {
success: false,
error: result.orderCancelUserErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
return {
success: true,
output: {
order: {
id: result?.job?.id,
cancelled: result?.job?.done ?? true,
message: 'Order cancellation initiated',
},
},
}
},
outputs: {
order: {
type: 'object',
description: 'The cancellation result',
properties: CANCEL_ORDER_OUTPUT_PROPERTIES,
},
},
}
+211
View File
@@ -0,0 +1,211 @@
import type { ShopifyCreateCustomerParams, ShopifyCustomerResponse } from '@/tools/shopify/types'
import { CUSTOMER_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyCreateCustomerTool: ToolConfig<
ShopifyCreateCustomerParams,
ShopifyCustomerResponse
> = {
id: 'shopify_create_customer',
name: 'Shopify Create Customer',
description: 'Create a new customer in your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Customer email address',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Customer first name',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Customer last name',
},
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Customer phone number',
},
note: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Note about the customer',
},
tags: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Customer tags',
},
addresses: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Customer addresses',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
// Shopify requires at least one of: email, phone, firstName, or lastName
const hasEmail = params.email?.trim()
const hasPhone = params.phone?.trim()
const hasFirstName = params.firstName?.trim()
const hasLastName = params.lastName?.trim()
if (!hasEmail && !hasPhone && !hasFirstName && !hasLastName) {
throw new Error('Customer must have at least one of: email, phone, firstName, or lastName')
}
const input: Record<string, unknown> = {}
if (hasEmail) {
input.email = params.email
}
if (hasFirstName) {
input.firstName = params.firstName
}
if (hasLastName) {
input.lastName = params.lastName
}
if (hasPhone) {
input.phone = params.phone
}
if (params.note) {
input.note = params.note
}
if (params.tags && Array.isArray(params.tags)) {
input.tags = params.tags
}
if (params.addresses && Array.isArray(params.addresses)) {
input.addresses = params.addresses
}
return {
query: `
mutation customerCreate($input: CustomerInput!) {
customerCreate(input: $input) {
customer {
id
email
firstName
lastName
phone
createdAt
updatedAt
note
tags
amountSpent {
amount
currencyCode
}
addresses {
address1
address2
city
province
country
zip
phone
}
defaultAddress {
address1
city
province
country
zip
}
}
userErrors {
field
message
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create customer',
output: {},
}
}
const result = data.data?.customerCreate
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
const customer = result?.customer
if (!customer) {
return {
success: false,
error: 'Customer creation was not successful',
output: {},
}
}
return {
success: true,
output: {
customer,
},
}
},
outputs: {
customer: {
type: 'object',
description: 'The created customer',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,214 @@
import type {
ShopifyCreateFulfillmentParams,
ShopifyFulfillmentResponse,
} from '@/tools/shopify/types'
import { FULFILLMENT_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyCreateFulfillmentTool: ToolConfig<
ShopifyCreateFulfillmentParams,
ShopifyFulfillmentResponse
> = {
id: 'shopify_create_fulfillment',
name: 'Shopify Create Fulfillment',
description:
'Create a fulfillment to mark order items as shipped. Requires a fulfillment order ID (get this from the order details).',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
fulfillmentOrderId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The fulfillment order ID (e.g., gid://shopify/FulfillmentOrder/123456789)',
},
trackingNumber: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tracking number for the shipment',
},
trackingCompany: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Shipping carrier name (e.g., UPS, FedEx, USPS, DHL)',
},
trackingUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'URL to track the shipment',
},
notifyCustomer: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to send a shipping confirmation email to the customer (default: true)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
// Build tracking info if any tracking details provided
const trackingInfo: {
number?: string
company?: string
url?: string
} = {}
if (params.trackingNumber) {
trackingInfo.number = params.trackingNumber
}
if (params.trackingCompany) {
trackingInfo.company = params.trackingCompany
}
if (params.trackingUrl) {
trackingInfo.url = params.trackingUrl
}
const fulfillmentInput: {
lineItemsByFulfillmentOrder: Array<{ fulfillmentOrderId: string }>
notifyCustomer?: boolean
trackingInfo?: typeof trackingInfo
} = {
lineItemsByFulfillmentOrder: [
{
fulfillmentOrderId: params.fulfillmentOrderId.trim(),
},
],
notifyCustomer: params.notifyCustomer !== false, // Default to true
}
// Only include trackingInfo if we have at least one tracking field
if (Object.keys(trackingInfo).length > 0) {
fulfillmentInput.trackingInfo = trackingInfo
}
return {
query: `
mutation fulfillmentCreate($fulfillment: FulfillmentInput!) {
fulfillmentCreate(fulfillment: $fulfillment) {
fulfillment {
id
status
createdAt
updatedAt
trackingInfo {
company
number
url
}
fulfillmentLineItems(first: 50) {
edges {
node {
id
quantity
lineItem {
title
}
}
}
}
}
userErrors {
field
message
}
}
}
`,
variables: {
fulfillment: fulfillmentInput,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create fulfillment',
output: {},
}
}
const result = data.data?.fulfillmentCreate
if (!result) {
return {
success: false,
error: 'Failed to create fulfillment',
output: {},
}
}
if (result.userErrors && result.userErrors.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
const fulfillment = result.fulfillment
if (!fulfillment) {
return {
success: false,
error: 'No fulfillment returned',
output: {},
}
}
// Transform fulfillment line items from edges format
const fulfillmentLineItems =
fulfillment.fulfillmentLineItems?.edges?.map((edge: { node: unknown }) => edge.node) || []
return {
success: true,
output: {
fulfillment: {
id: fulfillment.id,
status: fulfillment.status,
createdAt: fulfillment.createdAt,
updatedAt: fulfillment.updatedAt,
trackingInfo: fulfillment.trackingInfo || [],
fulfillmentLineItems,
},
},
}
},
outputs: {
fulfillment: {
type: 'object',
description: 'The created fulfillment with tracking info and fulfilled items',
properties: FULFILLMENT_OUTPUT_PROPERTIES,
},
},
}
+210
View File
@@ -0,0 +1,210 @@
import type { ShopifyCreateProductParams, ShopifyProductResponse } from '@/tools/shopify/types'
import { PRODUCT_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyCreateProductTool: ToolConfig<
ShopifyCreateProductParams,
ShopifyProductResponse
> = {
id: 'shopify_create_product',
name: 'Shopify Create Product',
description: 'Create a new product in your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Product title',
},
descriptionHtml: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Product description (HTML)',
},
vendor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Product vendor/brand',
},
productType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Product type/category',
},
tags: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Product tags',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Product status (ACTIVE, DRAFT, ARCHIVED)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.title || !params.title.trim()) {
throw new Error('Title is required to create a Shopify product')
}
const input: Record<string, unknown> = {
title: params.title,
}
if (params.descriptionHtml) {
input.descriptionHtml = params.descriptionHtml
}
if (params.vendor) {
input.vendor = params.vendor
}
if (params.productType) {
input.productType = params.productType
}
if (params.tags && Array.isArray(params.tags)) {
input.tags = params.tags
}
if (params.status) {
input.status = params.status
}
return {
query: `
mutation productCreate($product: ProductCreateInput!) {
productCreate(product: $product) {
product {
id
title
handle
descriptionHtml
vendor
productType
tags
status
createdAt
updatedAt
variants(first: 10) {
edges {
node {
id
title
price
compareAtPrice
sku
inventoryQuantity
}
}
}
images(first: 10) {
edges {
node {
id
url
altText
}
}
}
}
userErrors {
field
message
}
}
}
`,
variables: {
product: input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create product',
output: {},
}
}
const result = data.data?.productCreate
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
const product = result?.product
if (!product) {
return {
success: false,
error: 'Product creation was not successful',
output: {},
}
}
return {
success: true,
output: {
product: {
id: product.id,
title: product.title,
handle: product.handle,
descriptionHtml: product.descriptionHtml,
vendor: product.vendor,
productType: product.productType,
tags: product.tags,
status: product.status,
createdAt: product.createdAt,
updatedAt: product.updatedAt,
variants: product.variants,
images: product.images,
},
},
}
},
outputs: {
product: {
type: 'object',
description: 'The created product',
properties: PRODUCT_OUTPUT_PROPERTIES,
},
},
}
+114
View File
@@ -0,0 +1,114 @@
import type { ShopifyDeleteCustomerParams, ShopifyDeleteResponse } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyDeleteCustomerTool: ToolConfig<
ShopifyDeleteCustomerParams,
ShopifyDeleteResponse
> = {
id: 'shopify_delete_customer',
name: 'Shopify Delete Customer',
description: 'Delete a customer from your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
customerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer ID to delete (gid://shopify/Customer/123456789)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.customerId) {
throw new Error('Customer ID is required to delete a customer')
}
return {
query: `
mutation customerDelete($input: CustomerDeleteInput!) {
customerDelete(input: $input) {
deletedCustomerId
userErrors {
field
message
}
}
}
`,
variables: {
input: {
id: params.customerId,
},
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete customer',
output: {},
}
}
const result = data.data?.customerDelete
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
if (!result?.deletedCustomerId) {
return {
success: false,
error: 'Customer deletion was not successful',
output: {},
}
}
return {
success: true,
output: {
deletedId: result.deletedCustomerId,
},
}
},
outputs: {
deletedId: {
type: 'string',
description: 'The ID of the deleted customer',
},
},
}
+114
View File
@@ -0,0 +1,114 @@
import type { ShopifyDeleteProductParams, ShopifyDeleteResponse } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyDeleteProductTool: ToolConfig<
ShopifyDeleteProductParams,
ShopifyDeleteResponse
> = {
id: 'shopify_delete_product',
name: 'Shopify Delete Product',
description: 'Delete a product from your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
productId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Product ID to delete (gid://shopify/Product/123456789)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.productId) {
throw new Error('Product ID is required to delete a product')
}
return {
query: `
mutation productDelete($input: ProductDeleteInput!) {
productDelete(input: $input) {
deletedProductId
userErrors {
field
message
}
}
}
`,
variables: {
input: {
id: params.productId,
},
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete product',
output: {},
}
}
const result = data.data?.productDelete
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
if (!result?.deletedProductId) {
return {
success: false,
error: 'Product deletion was not successful',
output: {},
}
}
return {
success: true,
output: {
deletedId: result.deletedProductId,
},
}
},
outputs: {
deletedId: {
type: 'string',
description: 'The ID of the deleted product',
},
},
}
+190
View File
@@ -0,0 +1,190 @@
import type { ShopifyCollectionResponse, ShopifyGetCollectionParams } from '@/tools/shopify/types'
import { COLLECTION_WITH_PRODUCTS_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyGetCollectionTool: ToolConfig<
ShopifyGetCollectionParams,
ShopifyCollectionResponse
> = {
id: 'shopify_get_collection',
name: 'Shopify Get Collection',
description:
'Get a specific collection by ID, including its products. Use this to retrieve products within a collection.',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
collectionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The collection ID (e.g., gid://shopify/Collection/123456789)',
},
productsFirst: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of products to return from this collection (default: 50, max: 250)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
const productsFirst = Math.min(params.productsFirst || 50, 250)
return {
query: `
query getCollection($id: ID!, $productsFirst: Int!) {
collection(id: $id) {
id
title
handle
description
descriptionHtml
productsCount {
count
}
sortOrder
updatedAt
image {
id
url
altText
}
products(first: $productsFirst) {
edges {
node {
id
title
handle
status
vendor
productType
totalInventory
featuredMedia {
preview {
image {
url
altText
}
}
}
}
}
}
}
}
`,
variables: {
id: params.collectionId.trim(),
productsFirst,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to get collection',
output: {},
}
}
const collection = data.data?.collection
if (!collection) {
return {
success: false,
error: 'Collection not found',
output: {},
}
}
// Transform products from edges format and map featuredMedia to featuredImage
const products =
collection.products?.edges?.map(
(edge: {
node: {
id: string
title: string
handle: string
status: string
vendor: string
productType: string
totalInventory: number
featuredMedia?: {
preview?: {
image?: {
url: string
altText: string | null
}
}
}
}
}) => {
const product = edge.node
return {
id: product.id,
title: product.title,
handle: product.handle,
status: product.status,
vendor: product.vendor,
productType: product.productType,
totalInventory: product.totalInventory,
featuredImage: product.featuredMedia?.preview?.image || null,
}
}
) || []
return {
success: true,
output: {
collection: {
id: collection.id,
title: collection.title,
handle: collection.handle,
description: collection.description,
descriptionHtml: collection.descriptionHtml,
productsCount: collection.productsCount?.count ?? 0,
sortOrder: collection.sortOrder,
updatedAt: collection.updatedAt,
image: collection.image,
products,
},
},
}
},
outputs: {
collection: {
type: 'object',
description: 'The collection details including its products',
properties: COLLECTION_WITH_PRODUCTS_OUTPUT_PROPERTIES,
},
},
}
+135
View File
@@ -0,0 +1,135 @@
import type { ShopifyCustomerResponse, ShopifyGetCustomerParams } from '@/tools/shopify/types'
import { CUSTOMER_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyGetCustomerTool: ToolConfig<ShopifyGetCustomerParams, ShopifyCustomerResponse> =
{
id: 'shopify_get_customer',
name: 'Shopify Get Customer',
description: 'Get a single customer by ID from your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
customerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer ID (gid://shopify/Customer/123456789)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.customerId) {
throw new Error('Customer ID is required')
}
return {
query: `
query getCustomer($id: ID!) {
customer(id: $id) {
id
email
firstName
lastName
phone
createdAt
updatedAt
note
tags
amountSpent {
amount
currencyCode
}
addresses {
firstName
lastName
address1
address2
city
province
provinceCode
country
countryCode
zip
phone
}
defaultAddress {
firstName
lastName
address1
address2
city
province
country
zip
}
}
}
`,
variables: {
id: params.customerId,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to get customer',
output: {},
}
}
const customer = data.data?.customer
if (!customer) {
return {
success: false,
error: 'Customer not found',
output: {},
}
}
return {
success: true,
output: {
customer,
},
}
},
outputs: {
customer: {
type: 'object',
description: 'The customer details',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,173 @@
import type {
ShopifyGetInventoryLevelParams,
ShopifyInventoryResponse,
} from '@/tools/shopify/types'
import { INVENTORY_LEVEL_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyGetInventoryLevelTool: ToolConfig<
ShopifyGetInventoryLevelParams,
ShopifyInventoryResponse
> = {
id: 'shopify_get_inventory_level',
name: 'Shopify Get Inventory Level',
description: 'Get inventory level for a product variant at a specific location',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
inventoryItemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Inventory item ID (gid://shopify/InventoryItem/123456789)',
},
locationId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Location ID to filter by (optional)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.inventoryItemId) {
throw new Error('Inventory item ID is required')
}
return {
query: `
query getInventoryItem($id: ID!) {
inventoryItem(id: $id) {
id
sku
tracked
inventoryLevels(first: 50) {
edges {
node {
id
quantities(names: ["available", "on_hand", "committed", "incoming", "reserved"]) {
name
quantity
}
location {
id
name
}
}
}
}
}
}
`,
variables: {
id: params.inventoryItemId.trim(),
},
}
},
},
transformResponse: async (response, params) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to get inventory level',
output: {},
}
}
const inventoryItem = data.data?.inventoryItem
if (!inventoryItem) {
return {
success: false,
error: 'Inventory item not found',
output: {},
}
}
const requestedLocationId = params?.locationId?.trim()
const inventoryLevels = inventoryItem.inventoryLevels.edges
.map(
(edge: {
node: {
id: string
quantities: Array<{ name: string; quantity: number }>
location: { id: string; name: string }
}
}) => {
const node = edge.node
// Extract quantities into a more usable format
const quantitiesMap: Record<string, number> = {}
node.quantities.forEach((q) => {
quantitiesMap[q.name] = q.quantity
})
return {
id: node.id,
available: quantitiesMap.available ?? 0,
onHand: quantitiesMap.on_hand ?? 0,
committed: quantitiesMap.committed ?? 0,
incoming: quantitiesMap.incoming ?? 0,
reserved: quantitiesMap.reserved ?? 0,
location: node.location,
}
}
)
.filter(
(level: { location: { id: string } }) =>
!requestedLocationId || level.location.id === requestedLocationId
)
if (requestedLocationId && inventoryLevels.length === 0) {
return {
success: false,
error: 'No inventory level found for the provided location',
output: {},
}
}
return {
success: true,
output: {
inventoryLevel: {
id: inventoryItem.id,
sku: inventoryItem.sku,
tracked: inventoryItem.tracked,
levels: inventoryLevels,
},
},
}
},
outputs: {
inventoryLevel: {
type: 'object',
description: 'The inventory level details',
properties: INVENTORY_LEVEL_OUTPUT_PROPERTIES,
},
},
}
+205
View File
@@ -0,0 +1,205 @@
import type { ShopifyGetOrderParams, ShopifyOrderResponse } from '@/tools/shopify/types'
import { ORDER_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyGetOrderTool: ToolConfig<ShopifyGetOrderParams, ShopifyOrderResponse> = {
id: 'shopify_get_order',
name: 'Shopify Get Order',
description: 'Get a single order by ID from your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
orderId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Order ID (gid://shopify/Order/123456789)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.orderId) {
throw new Error('Order ID is required')
}
return {
query: `
query getOrder($id: ID!) {
order(id: $id) {
id
name
email
phone
createdAt
updatedAt
cancelledAt
closedAt
displayFinancialStatus
displayFulfillmentStatus
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
subtotalPriceSet {
shopMoney {
amount
currencyCode
}
}
totalTaxSet {
shopMoney {
amount
currencyCode
}
}
totalShippingPriceSet {
shopMoney {
amount
currencyCode
}
}
note
tags
customer {
id
email
firstName
lastName
phone
}
lineItems(first: 50) {
edges {
node {
id
title
quantity
variant {
id
title
price
sku
}
originalTotalSet {
shopMoney {
amount
currencyCode
}
}
discountedTotalSet {
shopMoney {
amount
currencyCode
}
}
}
}
}
shippingAddress {
firstName
lastName
address1
address2
city
province
provinceCode
country
countryCode
zip
phone
}
billingAddress {
firstName
lastName
address1
address2
city
province
provinceCode
country
countryCode
zip
phone
}
fulfillments {
id
status
createdAt
updatedAt
trackingInfo {
company
number
url
}
}
}
}
`,
variables: {
id: params.orderId,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to get order',
output: {},
}
}
const order = data.data?.order
if (!order) {
return {
success: false,
error: 'Order not found',
output: {},
}
}
return {
success: true,
output: {
order,
},
}
},
outputs: {
order: {
type: 'object',
description: 'The order details',
properties: ORDER_OUTPUT_PROPERTIES,
},
},
}
+129
View File
@@ -0,0 +1,129 @@
import type { ShopifyGetProductParams, ShopifyProductResponse } from '@/tools/shopify/types'
import { PRODUCT_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyGetProductTool: ToolConfig<ShopifyGetProductParams, ShopifyProductResponse> = {
id: 'shopify_get_product',
name: 'Shopify Get Product',
description: 'Get a single product by ID from your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
productId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Product ID (gid://shopify/Product/123456789)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.productId) {
throw new Error('Product ID is required')
}
return {
query: `
query getProduct($id: ID!) {
product(id: $id) {
id
title
handle
descriptionHtml
vendor
productType
tags
status
createdAt
updatedAt
variants(first: 50) {
edges {
node {
id
title
price
compareAtPrice
sku
inventoryQuantity
}
}
}
images(first: 20) {
edges {
node {
id
url
altText
}
}
}
}
}
`,
variables: {
id: params.productId,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to get product',
output: {},
}
}
const product = data.data?.product
if (!product) {
return {
success: false,
error: 'Product not found',
output: {},
}
}
return {
success: true,
output: {
product,
},
}
},
outputs: {
product: {
type: 'object',
description: 'The product details',
properties: PRODUCT_OUTPUT_PROPERTIES,
},
},
}
+30
View File
@@ -0,0 +1,30 @@
// Product Tools
export { shopifyAdjustInventoryTool } from './adjust_inventory'
export { shopifyCancelOrderTool } from './cancel_order'
// Customer Tools
export { shopifyCreateCustomerTool } from './create_customer'
// Fulfillment Tools
export { shopifyCreateFulfillmentTool } from './create_fulfillment'
export { shopifyCreateProductTool } from './create_product'
export { shopifyDeleteCustomerTool } from './delete_customer'
export { shopifyDeleteProductTool } from './delete_product'
export { shopifyGetCollectionTool } from './get_collection'
export { shopifyGetCustomerTool } from './get_customer'
export { shopifyGetInventoryLevelTool } from './get_inventory_level'
// Order Tools
export { shopifyGetOrderTool } from './get_order'
export { shopifyGetProductTool } from './get_product'
// Collection Tools
export { shopifyListCollectionsTool } from './list_collections'
export { shopifyListCustomersTool } from './list_customers'
// Inventory Tools
export { shopifyListInventoryItemsTool } from './list_inventory_items'
// Location Tools
export { shopifyListLocationsTool } from './list_locations'
export { shopifyListOrdersTool } from './list_orders'
export { shopifyListProductsTool } from './list_products'
export * from './types'
export { shopifyUpdateCustomerTool } from './update_customer'
export { shopifyUpdateOrderTool } from './update_order'
export { shopifyUpdateProductTool } from './update_product'
+169
View File
@@ -0,0 +1,169 @@
import type {
ShopifyCollectionsResponse,
ShopifyListCollectionsParams,
} from '@/tools/shopify/types'
import { COLLECTION_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyListCollectionsTool: ToolConfig<
ShopifyListCollectionsParams,
ShopifyCollectionsResponse
> = {
id: 'shopify_list_collections',
name: 'Shopify List Collections',
description:
'List product collections from your Shopify store. Filter by title, type (custom/smart), or handle.',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of collections to return (default: 50, max: 250)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Search query to filter collections (e.g., "title:Summer" or "collection_type:smart")',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
const first = Math.min(params.first || 50, 250)
return {
query: `
query listCollections($first: Int!, $query: String) {
collections(first: $first, query: $query) {
edges {
node {
id
title
handle
description
descriptionHtml
productsCount {
count
}
sortOrder
updatedAt
image {
id
url
altText
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`,
variables: {
first,
query: params.query || null,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list collections',
output: {},
}
}
const collectionsData = data.data?.collections
if (!collectionsData) {
return {
success: false,
error: 'Failed to retrieve collections',
output: {},
}
}
const collections = collectionsData.edges.map(
(edge: {
node: {
id: string
title: string
handle: string
description: string | null
descriptionHtml: string | null
productsCount: { count: number }
sortOrder: string
updatedAt: string
image: { id: string; url: string; altText: string | null } | null
}
}) => ({
id: edge.node.id,
title: edge.node.title,
handle: edge.node.handle,
description: edge.node.description,
descriptionHtml: edge.node.descriptionHtml,
productsCount: edge.node.productsCount?.count ?? 0,
sortOrder: edge.node.sortOrder,
updatedAt: edge.node.updatedAt,
image: edge.node.image,
})
)
return {
success: true,
output: {
collections,
pageInfo: collectionsData.pageInfo,
},
}
},
outputs: {
collections: {
type: 'array',
description: 'List of collections with their IDs, titles, and product counts',
items: {
type: 'object',
properties: COLLECTION_OUTPUT_PROPERTIES,
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: PAGE_INFO_OUTPUT_PROPERTIES,
},
},
}
+146
View File
@@ -0,0 +1,146 @@
import type { ShopifyCustomersResponse, ShopifyListCustomersParams } from '@/tools/shopify/types'
import { CUSTOMER_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyListCustomersTool: ToolConfig<
ShopifyListCustomersParams,
ShopifyCustomersResponse
> = {
id: 'shopify_list_customers',
name: 'Shopify List Customers',
description: 'List customers from your Shopify store with optional filtering',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of customers to return (default: 50, max: 250)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Search query to filter customers (e.g., "first_name:John" or "last_name:Smith" or "email:*@gmail.com" or "tag:vip")',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
const first = Math.min(params.first || 50, 250)
return {
query: `
query listCustomers($first: Int!, $query: String) {
customers(first: $first, query: $query) {
edges {
node {
id
email
firstName
lastName
phone
createdAt
updatedAt
note
tags
amountSpent {
amount
currencyCode
}
defaultAddress {
address1
city
province
country
zip
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`,
variables: {
first,
query: params.query || null,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list customers',
output: {},
}
}
const customersData = data.data?.customers
if (!customersData) {
return {
success: false,
error: 'Failed to retrieve customers',
output: {},
}
}
const customers = customersData.edges.map((edge: { node: unknown }) => edge.node)
return {
success: true,
output: {
customers,
pageInfo: customersData.pageInfo,
},
}
},
outputs: {
customers: {
type: 'array',
description: 'List of customers',
items: {
type: 'object',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: PAGE_INFO_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,208 @@
import {
INVENTORY_ITEM_OUTPUT_PROPERTIES,
PAGE_INFO_OUTPUT_PROPERTIES,
type ShopifyInventoryItemsResponse,
type ShopifyListInventoryItemsParams,
} from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyListInventoryItemsTool: ToolConfig<
ShopifyListInventoryItemsParams,
ShopifyInventoryItemsResponse
> = {
id: 'shopify_list_inventory_items',
name: 'Shopify List Inventory Items',
description:
'List inventory items from your Shopify store. Use this to find inventory item IDs by SKU.',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of inventory items to return (default: 50, max: 250)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search query to filter inventory items (e.g., "sku:ABC123")',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
const first = Math.min(params.first || 50, 250)
return {
query: `
query listInventoryItems($first: Int!, $query: String) {
inventoryItems(first: $first, query: $query) {
edges {
node {
id
sku
tracked
createdAt
updatedAt
variant {
id
title
product {
id
title
}
}
inventoryLevels(first: 10) {
edges {
node {
id
quantities(names: ["available", "on_hand"]) {
name
quantity
}
location {
id
name
}
}
}
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`,
variables: {
first,
query: params.query || null,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list inventory items',
output: {},
}
}
const inventoryItemsData = data.data?.inventoryItems
if (!inventoryItemsData) {
return {
success: false,
error: 'Failed to retrieve inventory items',
output: {},
}
}
const inventoryItems = inventoryItemsData.edges.map(
(edge: {
node: {
id: string
sku: string | null
tracked: boolean
createdAt: string
updatedAt: string
variant?: {
id: string
title: string
product?: {
id: string
title: string
}
}
inventoryLevels: {
edges: Array<{
node: {
id: string
quantities: Array<{ name: string; quantity: number }>
location: { id: string; name: string }
}
}>
}
}
}) => {
const node = edge.node
// Transform inventory levels to include available quantity
const inventoryLevels = node.inventoryLevels.edges.map((levelEdge) => {
const levelNode = levelEdge.node
const availableQty =
levelNode.quantities.find((q) => q.name === 'available')?.quantity ?? 0
return {
id: levelNode.id,
available: availableQty,
location: levelNode.location,
}
})
return {
id: node.id,
sku: node.sku,
tracked: node.tracked,
createdAt: node.createdAt,
updatedAt: node.updatedAt,
variant: node.variant,
inventoryLevels,
}
}
)
return {
success: true,
output: {
inventoryItems,
pageInfo: inventoryItemsData.pageInfo,
},
}
},
outputs: {
inventoryItems: {
type: 'array',
description: 'List of inventory items with their IDs, SKUs, and stock levels',
items: {
type: 'object',
properties: INVENTORY_ITEM_OUTPUT_PROPERTIES,
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: PAGE_INFO_OUTPUT_PROPERTIES,
},
},
}
+141
View File
@@ -0,0 +1,141 @@
import type { ShopifyListLocationsParams, ShopifyLocationsResponse } from '@/tools/shopify/types'
import { LOCATION_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyListLocationsTool: ToolConfig<
ShopifyListLocationsParams,
ShopifyLocationsResponse
> = {
id: 'shopify_list_locations',
name: 'Shopify List Locations',
description:
'List inventory locations from your Shopify store. Use this to find location IDs needed for inventory operations.',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of locations to return (default: 50, max: 250)',
},
includeInactive: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include deactivated locations (default: false)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
const first = Math.min(params.first || 50, 250)
return {
query: `
query listLocations($first: Int!, $includeInactive: Boolean) {
locations(first: $first, includeInactive: $includeInactive) {
edges {
node {
id
name
isActive
fulfillsOnlineOrders
address {
address1
address2
city
province
provinceCode
country
countryCode
zip
phone
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`,
variables: {
first,
includeInactive: params.includeInactive || false,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list locations',
output: {},
}
}
const locationsData = data.data?.locations
if (!locationsData) {
return {
success: false,
error: 'Failed to retrieve locations',
output: {},
}
}
const locations = locationsData.edges.map((edge: { node: unknown }) => edge.node)
return {
success: true,
output: {
locations,
pageInfo: locationsData.pageInfo,
},
}
},
outputs: {
locations: {
type: 'array',
description: 'List of locations with their IDs, names, and addresses',
items: {
type: 'object',
properties: LOCATION_OUTPUT_PROPERTIES,
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: PAGE_INFO_OUTPUT_PROPERTIES,
},
},
}
+192
View File
@@ -0,0 +1,192 @@
import type { ShopifyListOrdersParams, ShopifyOrdersResponse } from '@/tools/shopify/types'
import { ORDER_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyListOrdersTool: ToolConfig<ShopifyListOrdersParams, ShopifyOrdersResponse> = {
id: 'shopify_list_orders',
name: 'Shopify List Orders',
description: 'List orders from your Shopify store with optional filtering',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of orders to return (default: 50, max: 250)',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by order status (open, closed, cancelled, any)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Search query to filter orders (e.g., "financial_status:paid" or "fulfillment_status:unfulfilled" or "email:customer@example.com")',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
const first = Math.min(params.first || 50, 250)
// Build query string with status filter if provided
const queryParts: string[] = []
if (params.status && params.status !== 'any') {
queryParts.push(`status:${params.status}`)
}
if (params.query) {
queryParts.push(params.query)
}
const queryString = queryParts.length > 0 ? queryParts.join(' ') : null
return {
query: `
query listOrders($first: Int!, $query: String) {
orders(first: $first, query: $query) {
edges {
node {
id
name
email
phone
createdAt
updatedAt
cancelledAt
closedAt
displayFinancialStatus
displayFulfillmentStatus
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
subtotalPriceSet {
shopMoney {
amount
currencyCode
}
}
note
tags
customer {
id
email
firstName
lastName
}
lineItems(first: 10) {
edges {
node {
id
title
quantity
variant {
id
title
price
sku
}
}
}
}
shippingAddress {
firstName
lastName
city
province
country
zip
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`,
variables: {
first,
query: queryString,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list orders',
output: {},
}
}
const ordersData = data.data?.orders
if (!ordersData) {
return {
success: false,
error: 'Failed to retrieve orders',
output: {},
}
}
const orders = ordersData.edges.map((edge: { node: unknown }) => edge.node)
return {
success: true,
output: {
orders,
pageInfo: ordersData.pageInfo,
},
}
},
outputs: {
orders: {
type: 'array',
description: 'List of orders',
items: {
type: 'object',
properties: ORDER_OUTPUT_PROPERTIES,
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: PAGE_INFO_OUTPUT_PROPERTIES,
},
},
}
+157
View File
@@ -0,0 +1,157 @@
import type { ShopifyListProductsParams, ShopifyProductsResponse } from '@/tools/shopify/types'
import { PAGE_INFO_OUTPUT_PROPERTIES, PRODUCT_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyListProductsTool: ToolConfig<
ShopifyListProductsParams,
ShopifyProductsResponse
> = {
id: 'shopify_list_products',
name: 'Shopify List Products',
description: 'List products from your Shopify store with optional filtering',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of products to return (default: 50, max: 250)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Search query to filter products (e.g., "title:shirt" or "vendor:Nike" or "status:active")',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
const first = Math.min(params.first || 50, 250)
return {
query: `
query listProducts($first: Int!, $query: String) {
products(first: $first, query: $query) {
edges {
node {
id
title
handle
descriptionHtml
vendor
productType
tags
status
createdAt
updatedAt
variants(first: 10) {
edges {
node {
id
title
price
compareAtPrice
sku
inventoryQuantity
}
}
}
images(first: 5) {
edges {
node {
id
url
altText
}
}
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`,
variables: {
first,
query: params.query || null,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list products',
output: {},
}
}
const productsData = data.data?.products
if (!productsData) {
return {
success: false,
error: 'Failed to retrieve products',
output: {},
}
}
const products = productsData.edges.map((edge: { node: unknown }) => edge.node)
return {
success: true,
output: {
products,
pageInfo: productsData.pageInfo,
},
}
},
outputs: {
products: {
type: 'array',
description: 'List of products',
items: {
type: 'object',
properties: PRODUCT_OUTPUT_PROPERTIES,
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: PAGE_INFO_OUTPUT_PROPERTIES,
},
},
}
File diff suppressed because it is too large Load Diff
+202
View File
@@ -0,0 +1,202 @@
import type { ShopifyCustomerResponse, ShopifyUpdateCustomerParams } from '@/tools/shopify/types'
import { CUSTOMER_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyUpdateCustomerTool: ToolConfig<
ShopifyUpdateCustomerParams,
ShopifyCustomerResponse
> = {
id: 'shopify_update_customer',
name: 'Shopify Update Customer',
description: 'Update an existing customer in your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
customerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer ID to update (gid://shopify/Customer/123456789)',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New customer email address',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New customer first name',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New customer last name',
},
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New customer phone number',
},
note: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New note about the customer',
},
tags: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'New customer tags',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.customerId) {
throw new Error('Customer ID is required to update a customer')
}
const input: Record<string, unknown> = {
id: params.customerId,
}
if (params.email !== undefined) {
input.email = params.email
}
if (params.firstName !== undefined) {
input.firstName = params.firstName
}
if (params.lastName !== undefined) {
input.lastName = params.lastName
}
if (params.phone !== undefined) {
input.phone = params.phone
}
if (params.note !== undefined) {
input.note = params.note
}
if (params.tags !== undefined) {
input.tags = params.tags
}
return {
query: `
mutation customerUpdate($input: CustomerInput!) {
customerUpdate(input: $input) {
customer {
id
email
firstName
lastName
phone
createdAt
updatedAt
note
tags
amountSpent {
amount
currencyCode
}
addresses {
address1
city
province
country
zip
}
defaultAddress {
address1
city
province
country
zip
}
}
userErrors {
field
message
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update customer',
output: {},
}
}
const result = data.data?.customerUpdate
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
const customer = result?.customer
if (!customer) {
return {
success: false,
error: 'Customer update was not successful',
output: {},
}
}
return {
success: true,
output: {
customer,
},
}
},
outputs: {
customer: {
type: 'object',
description: 'The updated customer',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
}
+167
View File
@@ -0,0 +1,167 @@
import type { ShopifyOrderResponse, ShopifyUpdateOrderParams } from '@/tools/shopify/types'
import { ORDER_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyUpdateOrderTool: ToolConfig<ShopifyUpdateOrderParams, ShopifyOrderResponse> = {
id: 'shopify_update_order',
name: 'Shopify Update Order',
description: 'Update an existing order in your Shopify store (note, tags, email)',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
orderId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Order ID to update (gid://shopify/Order/123456789)',
},
note: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New order note',
},
tags: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'New order tags',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New customer email for the order',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.orderId) {
throw new Error('Order ID is required to update an order')
}
const input: Record<string, unknown> = {
id: params.orderId,
}
if (params.note !== undefined) {
input.note = params.note
}
if (params.tags !== undefined) {
input.tags = params.tags
}
if (params.email !== undefined) {
input.email = params.email
}
return {
query: `
mutation orderUpdate($input: OrderInput!) {
orderUpdate(input: $input) {
order {
id
name
email
phone
createdAt
updatedAt
note
tags
displayFinancialStatus
displayFulfillmentStatus
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
customer {
id
email
firstName
lastName
}
}
userErrors {
field
message
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update order',
output: {},
}
}
const result = data.data?.orderUpdate
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
const order = result?.order
if (!order) {
return {
success: false,
error: 'Order update was not successful',
output: {},
}
}
return {
success: true,
output: {
order,
},
}
},
outputs: {
order: {
type: 'object',
description: 'The updated order',
properties: ORDER_OUTPUT_PROPERTIES,
},
},
}
+206
View File
@@ -0,0 +1,206 @@
import type { ShopifyProductResponse, ShopifyUpdateProductParams } from '@/tools/shopify/types'
import { PRODUCT_OUTPUT_PROPERTIES } from '@/tools/shopify/types'
import type { ToolConfig } from '@/tools/types'
export const shopifyUpdateProductTool: ToolConfig<
ShopifyUpdateProductParams,
ShopifyProductResponse
> = {
id: 'shopify_update_product',
name: 'Shopify Update Product',
description: 'Update an existing product in your Shopify store',
version: '1.0.0',
oauth: {
required: true,
provider: 'shopify',
},
params: {
shopDomain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Shopify store domain (e.g., mystore.myshopify.com)',
},
productId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Product ID to update (gid://shopify/Product/123456789)',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New product title',
},
descriptionHtml: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New product description (HTML)',
},
vendor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New product vendor/brand',
},
productType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New product type/category',
},
tags: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'New product tags',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New product status (ACTIVE, DRAFT, ARCHIVED)',
},
},
request: {
url: (params) =>
`https://${params.shopDomain || params.idToken}/admin/api/2024-10/graphql.json`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Shopify API request')
}
return {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': params.accessToken,
}
},
body: (params) => {
if (!params.productId) {
throw new Error('Product ID is required to update a product')
}
const input: Record<string, unknown> = {
id: params.productId,
}
if (params.title !== undefined) {
input.title = params.title
}
if (params.descriptionHtml !== undefined) {
input.descriptionHtml = params.descriptionHtml
}
if (params.vendor !== undefined) {
input.vendor = params.vendor
}
if (params.productType !== undefined) {
input.productType = params.productType
}
if (params.tags !== undefined) {
input.tags = params.tags
}
if (params.status !== undefined) {
input.status = params.status
}
return {
query: `
mutation productUpdate($product: ProductUpdateInput!) {
productUpdate(product: $product) {
product {
id
title
handle
descriptionHtml
vendor
productType
tags
status
createdAt
updatedAt
variants(first: 10) {
edges {
node {
id
title
price
compareAtPrice
sku
inventoryQuantity
}
}
}
images(first: 10) {
edges {
node {
id
url
altText
}
}
}
}
userErrors {
field
message
}
}
}
`,
variables: {
product: input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update product',
output: {},
}
}
const result = data.data?.productUpdate
if (result?.userErrors?.length > 0) {
return {
success: false,
error: result.userErrors.map((e: { message: string }) => e.message).join(', '),
output: {},
}
}
const product = result?.product
if (!product) {
return {
success: false,
error: 'Product update was not successful',
output: {},
}
}
return {
success: true,
output: {
product,
},
}
},
outputs: {
product: {
type: 'object',
description: 'The updated product',
properties: PRODUCT_OUTPUT_PROPERTIES,
},
},
}