@clawpify/skills 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/clawpify/SKILL.md +134 -0
  4. package/clawpify/references/blogs.md +385 -0
  5. package/clawpify/references/bulk-operations.md +386 -0
  6. package/clawpify/references/collections.md +71 -0
  7. package/clawpify/references/customers.md +141 -0
  8. package/clawpify/references/discounts.md +431 -0
  9. package/clawpify/references/draft-orders.md +495 -0
  10. package/clawpify/references/files.md +355 -0
  11. package/clawpify/references/fulfillments.md +437 -0
  12. package/clawpify/references/gift-cards.md +453 -0
  13. package/clawpify/references/inventory.md +107 -0
  14. package/clawpify/references/locations.md +349 -0
  15. package/clawpify/references/marketing.md +352 -0
  16. package/clawpify/references/markets.md +346 -0
  17. package/clawpify/references/menus.md +313 -0
  18. package/clawpify/references/metafields.md +461 -0
  19. package/clawpify/references/orders.md +164 -0
  20. package/clawpify/references/pages.md +308 -0
  21. package/clawpify/references/products.md +277 -0
  22. package/clawpify/references/refunds.md +401 -0
  23. package/clawpify/references/segments.md +319 -0
  24. package/clawpify/references/shipping.md +406 -0
  25. package/clawpify/references/shop.md +307 -0
  26. package/clawpify/references/subscriptions.md +429 -0
  27. package/clawpify/references/translations.md +270 -0
  28. package/clawpify/references/webhooks.md +400 -0
  29. package/dist/agent.d.ts +18 -0
  30. package/dist/agent.d.ts.map +1 -0
  31. package/dist/agent.js +100 -0
  32. package/dist/auth.d.ts +34 -0
  33. package/dist/auth.d.ts.map +1 -0
  34. package/dist/auth.js +58 -0
  35. package/dist/index.d.ts +41 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +22 -0
  38. package/dist/mcp-server.d.ts +3 -0
  39. package/dist/mcp-server.d.ts.map +1 -0
  40. package/dist/mcp-server.js +236 -0
  41. package/dist/shopify.d.ts +29 -0
  42. package/dist/shopify.d.ts.map +1 -0
  43. package/dist/shopify.js +41 -0
  44. package/dist/skills.d.ts +8 -0
  45. package/dist/skills.d.ts.map +1 -0
  46. package/dist/skills.js +36 -0
  47. package/package.json +100 -0
  48. package/src/agent.ts +133 -0
  49. package/src/auth.ts +109 -0
  50. package/src/index.ts +55 -0
  51. package/src/mcp-server.ts +190 -0
  52. package/src/shopify.ts +63 -0
  53. package/src/skills.ts +42 -0
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/mcp-server.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListResourcesRequestSchema,
9
+ ReadResourceRequestSchema,
10
+ ListToolsRequestSchema
11
+ } from "@modelcontextprotocol/sdk/types.js";
12
+
13
+ // src/auth.ts
14
+ async function getAccessToken(config) {
15
+ const storeUrl = config.storeUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
16
+ const response = await fetch(`https://${storeUrl}/admin/oauth/access_token`, {
17
+ method: "POST",
18
+ headers: {
19
+ "Content-Type": "application/x-www-form-urlencoded"
20
+ },
21
+ body: new URLSearchParams({
22
+ grant_type: "client_credentials",
23
+ client_id: config.clientId,
24
+ client_secret: config.clientSecret
25
+ })
26
+ });
27
+ if (!response.ok) {
28
+ const text = await response.text();
29
+ throw new Error(`OAuth error (${response.status}): ${text}`);
30
+ }
31
+ const data = await response.json();
32
+ return {
33
+ accessToken: data.access_token,
34
+ scope: data.scope,
35
+ expiresIn: data.expires_in
36
+ };
37
+ }
38
+ async function createAuthenticatedConfig() {
39
+ const storeUrl = process.env.SHOPIFY_STORE_URL;
40
+ const directToken = process.env.SHOPIFY_ACCESS_TOKEN;
41
+ const clientId = process.env.SHOPIFY_CLIENT_ID;
42
+ const clientSecret = process.env.SHOPIFY_CLIENT_SECRET;
43
+ if (!storeUrl) {
44
+ throw new Error("Missing required environment variable: SHOPIFY_STORE_URL");
45
+ }
46
+ if (directToken) {
47
+ return {
48
+ storeUrl,
49
+ accessToken: directToken
50
+ };
51
+ }
52
+ if (clientId && clientSecret) {
53
+ const token = await getAccessToken({ storeUrl, clientId, clientSecret });
54
+ return {
55
+ storeUrl,
56
+ accessToken: token.accessToken,
57
+ scope: token.scope,
58
+ expiresIn: token.expiresIn
59
+ };
60
+ }
61
+ throw new Error(`Missing authentication credentials. Provide either:
62
+ ` + ` - SHOPIFY_ACCESS_TOKEN (direct token), or
63
+ ` + " - SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET (OAuth client credentials)");
64
+ }
65
+
66
+ // src/shopify.ts
67
+ class ShopifyClient {
68
+ storeUrl;
69
+ accessToken;
70
+ apiVersion;
71
+ constructor(config) {
72
+ this.storeUrl = config.storeUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
73
+ this.accessToken = config.accessToken;
74
+ this.apiVersion = config.apiVersion ?? "2026-01";
75
+ }
76
+ async graphql(query, variables) {
77
+ const url = `https://${this.storeUrl}/admin/api/${this.apiVersion}/graphql.json`;
78
+ const response = await fetch(url, {
79
+ method: "POST",
80
+ headers: {
81
+ "Content-Type": "application/json",
82
+ "X-Shopify-Access-Token": this.accessToken
83
+ },
84
+ body: JSON.stringify({ query, variables })
85
+ });
86
+ if (!response.ok) {
87
+ const text = await response.text();
88
+ throw new Error(`Shopify API error (${response.status}): ${text}`);
89
+ }
90
+ return response.json();
91
+ }
92
+ }
93
+
94
+ // src/mcp-server.ts
95
+ import { readdir, readFile } from "node:fs/promises";
96
+ import { join, dirname } from "node:path";
97
+ import { fileURLToPath } from "node:url";
98
+ import { homedir } from "node:os";
99
+ import { existsSync } from "node:fs";
100
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
101
+ var skillsDir = join(__dirname2, "..", "clawpify");
102
+ var configDir = join(homedir(), ".clawpify");
103
+ var envPath = join(configDir, ".env");
104
+ try {
105
+ if (existsSync(envPath)) {
106
+ const envContent = await readFile(envPath, "utf-8");
107
+ for (const line of envContent.split(`
108
+ `)) {
109
+ const trimmed = line.trim();
110
+ if (trimmed && !trimmed.startsWith("#")) {
111
+ const [key, ...valueParts] = trimmed.split("=");
112
+ const value = valueParts.join("=");
113
+ if (key && value && !process.env[key]) {
114
+ process.env[key] = value;
115
+ }
116
+ }
117
+ }
118
+ console.error(`Loaded config from ${envPath}`);
119
+ }
120
+ } catch {}
121
+ var shopifyClient = null;
122
+ async function getShopifyClient() {
123
+ if (!shopifyClient) {
124
+ const config = await createAuthenticatedConfig();
125
+ shopifyClient = new ShopifyClient({
126
+ storeUrl: config.storeUrl,
127
+ accessToken: config.accessToken
128
+ });
129
+ }
130
+ return shopifyClient;
131
+ }
132
+ var server = new Server({
133
+ name: "clawpify",
134
+ version: "1.0.0"
135
+ }, {
136
+ capabilities: {
137
+ tools: {},
138
+ resources: {}
139
+ }
140
+ });
141
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
142
+ tools: [
143
+ {
144
+ name: "shopify_graphql",
145
+ description: "Execute GraphQL queries against Shopify Admin API. Use this to query products, orders, customers, inventory, and manage store data.",
146
+ inputSchema: {
147
+ type: "object",
148
+ properties: {
149
+ query: {
150
+ type: "string",
151
+ description: "GraphQL query or mutation"
152
+ },
153
+ variables: {
154
+ type: "object",
155
+ description: "GraphQL variables (optional)"
156
+ }
157
+ },
158
+ required: ["query"]
159
+ }
160
+ }
161
+ ]
162
+ }));
163
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
164
+ if (request.params.name === "shopify_graphql") {
165
+ const { query, variables } = request.params.arguments;
166
+ try {
167
+ const client = await getShopifyClient();
168
+ const result = await client.graphql(query, variables);
169
+ return {
170
+ content: [
171
+ {
172
+ type: "text",
173
+ text: JSON.stringify(result, null, 2)
174
+ }
175
+ ]
176
+ };
177
+ } catch (error) {
178
+ return {
179
+ content: [
180
+ {
181
+ type: "text",
182
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
183
+ }
184
+ ],
185
+ isError: true
186
+ };
187
+ }
188
+ }
189
+ throw new Error(`Unknown tool: ${request.params.name}`);
190
+ });
191
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
192
+ try {
193
+ const files = await readdir(skillsDir);
194
+ const resources = files.filter((f) => f.endsWith(".md")).map((f) => ({
195
+ uri: `shopify://skills/${f.replace(".md", "")}`,
196
+ name: f.replace(".md", ""),
197
+ mimeType: "text/markdown",
198
+ description: `Shopify ${f.replace(".md", "")} skill documentation`
199
+ }));
200
+ return { resources };
201
+ } catch {
202
+ return { resources: [] };
203
+ }
204
+ });
205
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
206
+ const uri = request.params.uri;
207
+ const match = uri.match(/^shopify:\/\/skills\/(.+)$/);
208
+ if (!match) {
209
+ throw new Error(`Invalid resource URI: ${uri}`);
210
+ }
211
+ const filename = `${match[1]}.md`;
212
+ const filepath = join(skillsDir, filename);
213
+ try {
214
+ const content = await readFile(filepath, "utf-8");
215
+ return {
216
+ contents: [
217
+ {
218
+ uri,
219
+ mimeType: "text/markdown",
220
+ text: content
221
+ }
222
+ ]
223
+ };
224
+ } catch {
225
+ throw new Error(`Failed to read resource: ${filename}`);
226
+ }
227
+ });
228
+ async function main() {
229
+ const transport = new StdioServerTransport;
230
+ await server.connect(transport);
231
+ console.error("Clawpify MCP server running on stdio");
232
+ }
233
+ main().catch((error) => {
234
+ console.error("Fatal error:", error);
235
+ process.exit(1);
236
+ });
@@ -0,0 +1,29 @@
1
+ export interface ShopifyClientConfig {
2
+ /** Shopify store URL (e.g. "my-store.myshopify.com") */
3
+ storeUrl: string;
4
+ /** Admin API access token */
5
+ accessToken: string;
6
+ /** API version (defaults to "2026-01") */
7
+ apiVersion?: string;
8
+ }
9
+ export interface GraphQLResponse<T = any> {
10
+ data?: T;
11
+ errors?: Array<{
12
+ message: string;
13
+ locations?: Array<{
14
+ line: number;
15
+ column: number;
16
+ }>;
17
+ }>;
18
+ extensions?: Record<string, any>;
19
+ }
20
+ export declare class ShopifyClient {
21
+ private storeUrl;
22
+ private accessToken;
23
+ private apiVersion;
24
+ constructor(config: ShopifyClientConfig);
25
+ /** Execute a GraphQL query or mutation against the Shopify Admin API */
26
+ graphql<T = any>(query: string, variables?: Record<string, any>): Promise<GraphQLResponse<T>>;
27
+ }
28
+ export declare function createShopifyClient(): ShopifyClient;
29
+ //# sourceMappingURL=shopify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shopify.d.ts","sourceRoot":"","sources":["../src/shopify.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,GAAG;IACtC,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,mBAAmB;IAMvC,wEAAwE;IAClE,OAAO,CAAC,CAAC,GAAG,GAAG,EACnB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;CAmB/B;AAED,wBAAgB,mBAAmB,kBAWlC"}
@@ -0,0 +1,41 @@
1
+ // src/shopify.ts
2
+ class ShopifyClient {
3
+ storeUrl;
4
+ accessToken;
5
+ apiVersion;
6
+ constructor(config) {
7
+ this.storeUrl = config.storeUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
8
+ this.accessToken = config.accessToken;
9
+ this.apiVersion = config.apiVersion ?? "2026-01";
10
+ }
11
+ async graphql(query, variables) {
12
+ const url = `https://${this.storeUrl}/admin/api/${this.apiVersion}/graphql.json`;
13
+ const response = await fetch(url, {
14
+ method: "POST",
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ "X-Shopify-Access-Token": this.accessToken
18
+ },
19
+ body: JSON.stringify({ query, variables })
20
+ });
21
+ if (!response.ok) {
22
+ const text = await response.text();
23
+ throw new Error(`Shopify API error (${response.status}): ${text}`);
24
+ }
25
+ return response.json();
26
+ }
27
+ }
28
+ function createShopifyClient() {
29
+ const storeUrl = process.env.SHOPIFY_STORE_URL;
30
+ const accessToken = process.env.SHOPIFY_ACCESS_TOKEN;
31
+ if (!storeUrl || !accessToken) {
32
+ throw new Error("Missing required environment variables: SHOPIFY_STORE_URL and SHOPIFY_ACCESS_TOKEN");
33
+ }
34
+ return new ShopifyClient({ storeUrl, accessToken });
35
+ }
36
+ export {
37
+ createShopifyClient,
38
+ ShopifyClient
39
+ };
40
+
41
+ export { ShopifyClient, createShopifyClient };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Load all skill files from the clawpify skills directory.
3
+ * Works both locally and when installed as an npm package.
4
+ *
5
+ * Optionally provide a custom directory path to load skills from.
6
+ */
7
+ export declare function loadSkills(customDir?: string): Promise<string>;
8
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASpE"}
package/dist/skills.js ADDED
@@ -0,0 +1,36 @@
1
+ // src/skills.ts
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ async function loadSkills(customDir) {
6
+ const baseDir = customDir ?? join(fileURLToPath(import.meta.url), "..", "..", "clawpify");
7
+ const skillParts = [];
8
+ await collectMarkdown(baseDir, skillParts);
9
+ return skillParts.join(`
10
+
11
+ ---
12
+
13
+ `);
14
+ }
15
+ async function collectMarkdown(dir, parts) {
16
+ let entries;
17
+ try {
18
+ entries = await readdir(dir, { withFileTypes: true });
19
+ } catch {
20
+ return;
21
+ }
22
+ for (const entry of entries) {
23
+ const fullPath = join(dir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ await collectMarkdown(fullPath, parts);
26
+ } else if (entry.name.endsWith(".md")) {
27
+ const content = await readFile(fullPath, "utf-8");
28
+ parts.push(content);
29
+ }
30
+ }
31
+ }
32
+ export {
33
+ loadSkills
34
+ };
35
+
36
+ export { loadSkills };
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@clawpify/skills",
3
+ "version": "1.0.1",
4
+ "description": "Shopify Agent SDK — query and manage Shopify stores via GraphQL Admin API with AI agents and MCP",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "bun": "./src/index.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./agent": {
16
+ "types": "./dist/agent.d.ts",
17
+ "bun": "./src/agent.ts",
18
+ "import": "./dist/agent.js",
19
+ "default": "./dist/agent.js"
20
+ },
21
+ "./client": {
22
+ "types": "./dist/shopify.d.ts",
23
+ "bun": "./src/shopify.ts",
24
+ "import": "./dist/shopify.js",
25
+ "default": "./dist/shopify.js"
26
+ },
27
+ "./auth": {
28
+ "types": "./dist/auth.d.ts",
29
+ "bun": "./src/auth.ts",
30
+ "import": "./dist/auth.js",
31
+ "default": "./dist/auth.js"
32
+ },
33
+ "./skills": {
34
+ "types": "./dist/skills.d.ts",
35
+ "bun": "./src/skills.ts",
36
+ "import": "./dist/skills.js",
37
+ "default": "./dist/skills.js"
38
+ }
39
+ },
40
+ "bin": {
41
+ "clawpify": "dist/mcp-server.js"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src",
46
+ "clawpify"
47
+ ],
48
+ "scripts": {
49
+ "build": "bun run build:clean && bun run build:js && bun run build:bin && bun run build:types",
50
+ "build:clean": "rm -rf dist",
51
+ "build:js": "bun build ./src/index.ts ./src/agent.ts ./src/shopify.ts ./src/auth.ts ./src/skills.ts --outdir ./dist --target node --format esm --splitting --external @anthropic-ai/sdk --external @modelcontextprotocol/sdk",
52
+ "build:bin": "bun build ./src/mcp-server.ts --outdir ./dist --target node --format esm --external @anthropic-ai/sdk --external @modelcontextprotocol/sdk",
53
+ "build:types": "tsc -p tsconfig.build.json",
54
+ "prepublishOnly": "bun run build",
55
+ "start": "bun run examples/server.ts"
56
+ },
57
+ "keywords": [
58
+ "shopify",
59
+ "ai",
60
+ "mcp",
61
+ "graphql",
62
+ "agent",
63
+ "sdk",
64
+ "claude",
65
+ "anthropic",
66
+ "model-context-protocol"
67
+ ],
68
+ "license": "MIT",
69
+ "publishConfig": {
70
+ "access": "public"
71
+ },
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "git+https://github.com/clawpify/skills.git"
75
+ },
76
+ "homepage": "https://github.com/clawpify/skills#readme",
77
+ "bugs": {
78
+ "url": "https://github.com/clawpify/skills/issues"
79
+ },
80
+ "engines": {
81
+ "node": ">=18.0.0"
82
+ },
83
+ "sideEffects": false,
84
+ "dependencies": {
85
+ "@modelcontextprotocol/sdk": "^1.26.0"
86
+ },
87
+ "peerDependencies": {
88
+ "@anthropic-ai/sdk": ">=0.39.0"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "@anthropic-ai/sdk": {
92
+ "optional": true
93
+ }
94
+ },
95
+ "devDependencies": {
96
+ "@anthropic-ai/sdk": "^0.39.0",
97
+ "bun-types": "latest",
98
+ "typescript": "^5.0.0"
99
+ }
100
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,133 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import { ShopifyClient } from "./shopify";
3
+
4
+ const SHOPIFY_GRAPHQL_TOOL: Anthropic.Tool = {
5
+ name: "shopify_graphql",
6
+ description:
7
+ "Execute a GraphQL query against the Shopify Admin API. Use this to interact with products, orders, customers, inventory, and other Shopify resources.",
8
+ input_schema: {
9
+ type: "object" as const,
10
+ properties: {
11
+ query: {
12
+ type: "string",
13
+ description: "The GraphQL query or mutation to execute",
14
+ },
15
+ variables: {
16
+ type: "object",
17
+ description: "Optional variables for the GraphQL query",
18
+ },
19
+ },
20
+ required: ["query"],
21
+ },
22
+ };
23
+
24
+ export class ShopifyAgent {
25
+ private anthropic: Anthropic;
26
+ private shopify: ShopifyClient;
27
+ private skillContent: string;
28
+ private model: string;
29
+
30
+ constructor(config: {
31
+ shopify: ShopifyClient;
32
+ skillContent: string;
33
+ model?: string;
34
+ }) {
35
+ this.anthropic = new Anthropic();
36
+ this.shopify = config.shopify;
37
+ this.skillContent = config.skillContent;
38
+ this.model = config.model ?? "claude-sonnet-4-5";
39
+ }
40
+
41
+ async chat(
42
+ userMessage: string,
43
+ conversationHistory: Anthropic.MessageParam[] = []
44
+ ): Promise<{ response: string; history: Anthropic.MessageParam[] }> {
45
+ const systemPrompt = `You are a helpful Shopify assistant that can query and manage a Shopify store using the GraphQL Admin API.
46
+
47
+ ${this.skillContent}
48
+
49
+ When the user asks about products, orders, customers, or any Shopify data, use the shopify_graphql tool to fetch or modify the data. Always explain what you're doing and present results clearly.`;
50
+
51
+ const messages: Anthropic.MessageParam[] = [
52
+ ...conversationHistory,
53
+ { role: "user", content: userMessage },
54
+ ];
55
+
56
+ let response = await this.anthropic.messages.create({
57
+ model: this.model,
58
+ max_tokens: 4096,
59
+ system: systemPrompt,
60
+ tools: [SHOPIFY_GRAPHQL_TOOL],
61
+ messages,
62
+ });
63
+
64
+ const assistantMessages: Anthropic.ContentBlockParam[] = [];
65
+
66
+ // Agentic loop: keep processing tool calls until we get a final response
67
+ while (response.stop_reason === "tool_use") {
68
+ const toolUseBlocks = response.content.filter(
69
+ (block: Anthropic.ContentBlock): block is Anthropic.ToolUseBlock =>
70
+ block.type === "tool_use"
71
+ );
72
+
73
+ assistantMessages.push(...(response.content as Anthropic.ContentBlockParam[]));
74
+
75
+ const toolResults: Anthropic.ToolResultBlockParam[] = [];
76
+
77
+ for (const toolUse of toolUseBlocks) {
78
+ if (toolUse.name === "shopify_graphql") {
79
+ const input = toolUse.input as {
80
+ query: string;
81
+ variables?: Record<string, any>;
82
+ };
83
+
84
+ try {
85
+ const result = await this.shopify.graphql(
86
+ input.query,
87
+ input.variables
88
+ );
89
+ toolResults.push({
90
+ type: "tool_result",
91
+ tool_use_id: toolUse.id,
92
+ content: JSON.stringify(result, null, 2),
93
+ });
94
+ } catch (error) {
95
+ toolResults.push({
96
+ type: "tool_result",
97
+ tool_use_id: toolUse.id,
98
+ content: `Error: ${error instanceof Error ? error.message : String(error)}`,
99
+ is_error: true,
100
+ });
101
+ }
102
+ }
103
+ }
104
+
105
+ messages.push({ role: "assistant", content: assistantMessages.slice() });
106
+ messages.push({ role: "user", content: toolResults });
107
+ assistantMessages.length = 0;
108
+
109
+ response = await this.anthropic.messages.create({
110
+ model: this.model,
111
+ max_tokens: 4096,
112
+ system: systemPrompt,
113
+ tools: [SHOPIFY_GRAPHQL_TOOL],
114
+ messages,
115
+ });
116
+ }
117
+
118
+ // Extract final text response
119
+ const textBlocks = response.content.filter(
120
+ (block: Anthropic.ContentBlock): block is Anthropic.TextBlock =>
121
+ block.type === "text"
122
+ );
123
+ const finalResponse = textBlocks.map((b: Anthropic.TextBlock) => b.text).join("\n");
124
+
125
+ // Build updated history
126
+ const updatedHistory: Anthropic.MessageParam[] = [
127
+ ...messages,
128
+ { role: "assistant", content: response.content },
129
+ ];
130
+
131
+ return { response: finalResponse, history: updatedHistory };
132
+ }
133
+ }