@open-neko/plugin-shopify 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Thin HTTP wrapper for Shopify's Admin REST API. All network calls
3
+ * go through `fetchImpl` for testability — the production runtime
4
+ * uses globalThis.fetch; tests inject a stub.
5
+ *
6
+ * Shopify's API is per-store; the storeDomain in the config is the
7
+ * canonical *.myshopify.com host (NOT the custom storefront domain
8
+ * the operator may have configured for shoppers).
9
+ *
10
+ * Pinned API version: Shopify rotates quarterly. We default to
11
+ * "2026-01" — operators can override via SHOPIFY_API_VERSION when
12
+ * Shopify deprecates that version.
13
+ */
14
+ export declare const DEFAULT_API_VERSION = "2026-01";
15
+ export interface ShopifyClientConfig {
16
+ /** Canonical *.myshopify.com domain — no scheme. */
17
+ storeDomain: string;
18
+ accessToken: string;
19
+ apiVersion?: string;
20
+ fetchImpl?: typeof fetch;
21
+ }
22
+ export interface ShopifyOrderSummary {
23
+ id: number;
24
+ name: string;
25
+ order_number: number;
26
+ email: string | null;
27
+ financial_status: string | null;
28
+ fulfillment_status: string | null;
29
+ total_price: string;
30
+ currency: string;
31
+ created_at: string;
32
+ updated_at: string;
33
+ tags: string;
34
+ note: string | null;
35
+ }
36
+ export interface ShopifyOrderDetail extends ShopifyOrderSummary {
37
+ customer: {
38
+ id: number;
39
+ email: string | null;
40
+ first_name: string | null;
41
+ last_name: string | null;
42
+ } | null;
43
+ line_items: Array<{
44
+ id: number;
45
+ title: string;
46
+ quantity: number;
47
+ sku: string | null;
48
+ price: string;
49
+ }>;
50
+ shipping_address: Record<string, unknown> | null;
51
+ billing_address: Record<string, unknown> | null;
52
+ refunds: Array<Record<string, unknown>>;
53
+ }
54
+ export interface ListOrdersParams {
55
+ /** ISO timestamp lower bound on created_at. */
56
+ createdAtMin?: string;
57
+ /** ISO timestamp upper bound on created_at. */
58
+ createdAtMax?: string;
59
+ /** "any" | "authorized" | "pending" | "paid" | "partially_paid" | "refunded" | "voided" | "partially_refunded" | "unpaid" */
60
+ financialStatus?: string;
61
+ /** "shipped" | "partial" | "unshipped" | "any" | "unfulfilled" */
62
+ fulfillmentStatus?: string;
63
+ /** 1-250 (Shopify max). Default 50. */
64
+ limit?: number;
65
+ /** "open" | "closed" | "cancelled" | "any". Default "open". */
66
+ status?: string;
67
+ }
68
+ export interface UpdateOrderNoteParams {
69
+ orderId: number;
70
+ note: string;
71
+ /** When true, append " — <note>" to the existing note instead of replacing. */
72
+ append?: boolean;
73
+ /** Optional tags to add (comma-joined). */
74
+ addTags?: string[];
75
+ }
76
+ export declare class ShopifyClient {
77
+ private readonly cfg;
78
+ private readonly fetchImpl;
79
+ private readonly apiVersion;
80
+ constructor(cfg: ShopifyClientConfig);
81
+ private url;
82
+ private headers;
83
+ listOrders(params?: ListOrdersParams): Promise<{
84
+ orders: ShopifyOrderSummary[];
85
+ }>;
86
+ getOrder(orderId: number): Promise<{
87
+ order: ShopifyOrderDetail;
88
+ }>;
89
+ /**
90
+ * Append-or-replace the note + optionally add tags. Shopify's PUT
91
+ * order endpoint is field-by-field PATCH semantics: only the keys
92
+ * you pass change.
93
+ */
94
+ updateOrderNote(params: UpdateOrderNoteParams): Promise<{
95
+ order: ShopifyOrderDetail;
96
+ }>;
97
+ }
98
+ //# sourceMappingURL=shopify-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shopify-client.d.ts","sourceRoot":"","sources":["../src/shopify-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Thin HTTP wrapper for Shopify's Admin REST API. All network calls
3
+ * go through `fetchImpl` for testability — the production runtime
4
+ * uses globalThis.fetch; tests inject a stub.
5
+ *
6
+ * Shopify's API is per-store; the storeDomain in the config is the
7
+ * canonical *.myshopify.com host (NOT the custom storefront domain
8
+ * the operator may have configured for shoppers).
9
+ *
10
+ * Pinned API version: Shopify rotates quarterly. We default to
11
+ * "2026-01" — operators can override via SHOPIFY_API_VERSION when
12
+ * Shopify deprecates that version.
13
+ */
14
+ export const DEFAULT_API_VERSION = "2026-01";
15
+ export class ShopifyClient {
16
+ cfg;
17
+ fetchImpl;
18
+ apiVersion;
19
+ constructor(cfg) {
20
+ this.cfg = cfg;
21
+ this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch;
22
+ this.apiVersion = cfg.apiVersion ?? DEFAULT_API_VERSION;
23
+ }
24
+ url(path) {
25
+ return `https://${this.cfg.storeDomain}/admin/api/${this.apiVersion}/${path}`;
26
+ }
27
+ headers(extra = {}) {
28
+ return {
29
+ "X-Shopify-Access-Token": this.cfg.accessToken,
30
+ Accept: "application/json",
31
+ ...extra,
32
+ };
33
+ }
34
+ async listOrders(params = {}) {
35
+ const u = new URL(this.url("orders.json"));
36
+ u.searchParams.set("limit", String(clampInt(params.limit ?? 50, 1, 250)));
37
+ u.searchParams.set("status", params.status ?? "open");
38
+ if (params.financialStatus)
39
+ u.searchParams.set("financial_status", params.financialStatus);
40
+ if (params.fulfillmentStatus)
41
+ u.searchParams.set("fulfillment_status", params.fulfillmentStatus);
42
+ if (params.createdAtMin)
43
+ u.searchParams.set("created_at_min", params.createdAtMin);
44
+ if (params.createdAtMax)
45
+ u.searchParams.set("created_at_max", params.createdAtMax);
46
+ const res = await this.fetchImpl(u.toString(), { headers: this.headers() });
47
+ if (!res.ok)
48
+ throw await shopifyError(res, "list_shopify_orders");
49
+ return (await res.json());
50
+ }
51
+ async getOrder(orderId) {
52
+ const res = await this.fetchImpl(this.url(`orders/${orderId}.json`), {
53
+ headers: this.headers(),
54
+ });
55
+ if (!res.ok)
56
+ throw await shopifyError(res, "get_shopify_order");
57
+ return (await res.json());
58
+ }
59
+ /**
60
+ * Append-or-replace the note + optionally add tags. Shopify's PUT
61
+ * order endpoint is field-by-field PATCH semantics: only the keys
62
+ * you pass change.
63
+ */
64
+ async updateOrderNote(params) {
65
+ let nextNote = params.note;
66
+ let nextTags;
67
+ if (params.append || params.addTags?.length) {
68
+ // Need the current state to append safely. Fetch once.
69
+ const current = await this.getOrder(params.orderId);
70
+ if (params.append) {
71
+ const existing = current.order.note ?? "";
72
+ nextNote = existing ? `${existing} — ${params.note}` : params.note;
73
+ }
74
+ if (params.addTags?.length) {
75
+ const existingTags = (current.order.tags ?? "")
76
+ .split(",")
77
+ .map((s) => s.trim())
78
+ .filter(Boolean);
79
+ const merged = new Set(existingTags);
80
+ for (const t of params.addTags)
81
+ merged.add(t);
82
+ nextTags = [...merged].join(", ");
83
+ }
84
+ }
85
+ const body = { id: params.orderId, note: nextNote };
86
+ if (nextTags !== undefined)
87
+ body.tags = nextTags;
88
+ const res = await this.fetchImpl(this.url(`orders/${params.orderId}.json`), {
89
+ method: "PUT",
90
+ headers: this.headers({ "Content-Type": "application/json" }),
91
+ body: JSON.stringify({ order: body }),
92
+ });
93
+ if (!res.ok)
94
+ throw await shopifyError(res, "update_shopify_order_note");
95
+ return (await res.json());
96
+ }
97
+ }
98
+ function clampInt(value, min, max) {
99
+ if (!Number.isFinite(value))
100
+ return min;
101
+ return Math.min(Math.max(Math.trunc(value), min), max);
102
+ }
103
+ async function shopifyError(res, action) {
104
+ let body = "";
105
+ try {
106
+ body = await res.text();
107
+ }
108
+ catch {
109
+ /* ignore */
110
+ }
111
+ return new Error(`${action} failed (HTTP ${res.status}): ${body || res.statusText}`);
112
+ }
113
+ //# sourceMappingURL=shopify-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shopify-client.js","sourceRoot":"","sources":["../src/shopify-client.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@open-neko/plugin-shopify",
3
+ "version": "0.1.0",
4
+ "description": "Shopify Admin API connector for OpenNeko — list orders, fetch order detail, and update internal order notes against an operator's store. Three sandboxed actions: list_shopify_orders, get_shopify_order, update_shopify_order_note. Network egress limited to the operator's *.myshopify.com host.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/open-neko/plugins.git",
9
+ "directory": "packages/shopify"
10
+ },
11
+ "type": "module",
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "skill"
16
+ ],
17
+ "openneko": {
18
+ "runner": "./dist/run.js",
19
+ "skill": "./skill",
20
+ "permissions": {
21
+ "network": [
22
+ "*.myshopify.com"
23
+ ],
24
+ "env": [
25
+ {
26
+ "key": "SHOPIFY_STORE_DOMAIN",
27
+ "required": true,
28
+ "secret": false,
29
+ "description": "Your store's permanent myshopify.com subdomain — e.g. acme.myshopify.com. Use the .myshopify.com domain even if you've configured a custom storefront domain; the Admin API only honors the canonical one."
30
+ },
31
+ {
32
+ "key": "SHOPIFY_ACCESS_TOKEN",
33
+ "required": true,
34
+ "secret": true,
35
+ "description": "Shopify Admin API access token (starts with shpat_). Create at admin → Settings → Apps and sales channels → Develop apps → Create an app → Configuration → Admin API access scopes. Needs read_orders + write_orders. Token is shown ONCE on install — copy it immediately."
36
+ },
37
+ {
38
+ "key": "SHOPIFY_API_VERSION",
39
+ "required": false,
40
+ "secret": false,
41
+ "description": "Shopify Admin API version to pin (defaults to 2026-01). Shopify rotates quarterly; pin a stable version to avoid surprise breakage."
42
+ }
43
+ ]
44
+ },
45
+ "capabilities": {
46
+ "action": {
47
+ "kinds": [
48
+ {
49
+ "kind": "list_shopify_orders",
50
+ "description": "List recent orders, optionally filtered by financialStatus, fulfillmentStatus, or a date range (createdAtMin/createdAtMax).",
51
+ "default_mode": "auto",
52
+ "example": {
53
+ "financialStatus": "paid",
54
+ "createdAtMin": "2026-05-01T00:00:00Z",
55
+ "limit": 20
56
+ }
57
+ },
58
+ {
59
+ "kind": "get_shopify_order",
60
+ "description": "Fetch full detail for one order by id — line items, customer, addresses, refunds, tags.",
61
+ "default_mode": "auto",
62
+ "example": {
63
+ "orderId": 1234567890
64
+ }
65
+ },
66
+ {
67
+ "kind": "update_shopify_order_note",
68
+ "description": "Set or append the internal note on an order (user-visible in Shopify admin only; not customer-facing).",
69
+ "default_mode": "ask",
70
+ "example": {
71
+ "orderId": 1234567890,
72
+ "note": "Flagged for review by ops.",
73
+ "append": true
74
+ }
75
+ }
76
+ ]
77
+ }
78
+ }
79
+ },
80
+ "dependencies": {
81
+ "@open-neko/plugin-types": "0.1.0"
82
+ },
83
+ "devDependencies": {
84
+ "@types/node": "^20.19.0",
85
+ "esbuild": "^0.25.0",
86
+ "typescript": "^5.6.3",
87
+ "vitest": "^2.1.8"
88
+ },
89
+ "scripts": {
90
+ "build": "tsc -p tsconfig.json && node scripts/bundle.mjs",
91
+ "typecheck": "tsc -p tsconfig.json --noEmit",
92
+ "test": "vitest run",
93
+ "test:watch": "vitest"
94
+ }
95
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: shopify
3
+ description: Patterns for the @open-neko/plugin-shopify actions — when to use list_shopify_orders vs get_shopify_order, how to filter for a specific operations question, and how to log internal context via update_shopify_order_note. Use whenever the operator's question is about orders flowing through their Shopify store (revenue checks, fulfillment status, customer service follow-ups, ops tagging). Assumes SHOPIFY_STORE_DOMAIN and SHOPIFY_ACCESS_TOKEN are configured.
4
+ license: Apache-2.0
5
+ metadata:
6
+ authoredBy: open-neko
7
+ pairsWith: "@open-neko/plugin-shopify"
8
+ ---
9
+
10
+ # Shopify patterns
11
+
12
+ Shopify is OpenNeko's reference ecommerce connector. Most operator
13
+ questions fall into one of three shapes:
14
+
15
+ | Operator says | Right action | Why |
16
+ |---|---|---|
17
+ | "What's happened in the last hour?" | `list_shopify_orders` with `createdAtMin` set to T-1h | Bounded read, server-side filter |
18
+ | "Pull up order #1234" | `get_shopify_order` with the parsed number | Single-record fetch, full detail |
19
+ | "Mark this order as 'priority shipping' and tell ops we noticed" | `update_shopify_order_note` with `append: true` and `addTags: ["priority"]` | One write, preserves prior state |
20
+
21
+ ## Identifying an order from operator input
22
+
23
+ Operators usually paste either:
24
+
25
+ - `#1234` — Shopify's display name. The numeric part after `#` is
26
+ `order_number`, NOT `id`. The plugin's actions need `id` (the
27
+ internal numeric identifier).
28
+ - `https://acme.myshopify.com/admin/orders/4567890123456` — the
29
+ number in the URL IS `id`. Use it directly.
30
+ - A bare integer the operator says is "order 4567890123456" — same.
31
+
32
+ If the operator gives you `#1234`, run a `list_shopify_orders` with
33
+ no filter and `limit: 50`, then match against `order_number`. Don't
34
+ guess.
35
+
36
+ ## `list_shopify_orders` patterns
37
+
38
+ ### Filter server-side, always
39
+
40
+ Default `status: "open"` keeps you off completed/archived orders.
41
+ Override only when the operator's question explicitly includes
42
+ them ("how many cancellations did we get yesterday?" → `status:
43
+ "cancelled"`).
44
+
45
+ Three commonly useful filter combos:
46
+
47
+ - **"What's stuck?"** → `fulfillmentStatus: "unfulfilled"`, `financialStatus: "paid"`
48
+ - **"What needs payment chasing?"** → `financialStatus: "pending"`
49
+ - **"What landed today?"** → `createdAtMin` set to today's midnight in the store's timezone
50
+
51
+ ### Page size
52
+
53
+ Default 50; cap at 250 (Shopify's max per call). Bigger pages are
54
+ slower and ALL of the payload comes through the agent's context. For
55
+ "show me the last 10" requests, set `limit: 10`.
56
+
57
+ ### Time zones matter
58
+
59
+ Shopify returns ISO timestamps in UTC. The operator usually thinks
60
+ in their own timezone. If the question is "what happened today?",
61
+ the agent should convert the operator's local "today" to a UTC
62
+ ISO range before passing to `createdAtMin`/`createdAtMax`. Don't
63
+ ask Shopify to filter on a calendar day in the wrong timezone.
64
+
65
+ ## `update_shopify_order_note` patterns
66
+
67
+ This is the only write action in this plugin, and it's deliberately
68
+ low-blast-radius: the `note` field is operator-visible inside the
69
+ Shopify admin only, never customer-facing.
70
+
71
+ ### Always prefer `append: true` for ops logging
72
+
73
+ ```
74
+ note: "OpenNeko revenue-drop watcher pinged @amit at 14:03"
75
+ append: true
76
+ ```
77
+
78
+ Operators rely on the note as a running log. Replacing it wipes
79
+ their history. The only time to omit `append` is when the operator
80
+ explicitly says "replace the note with…".
81
+
82
+ ### Pair with tags for filterable ops state
83
+
84
+ ```
85
+ addTags: ["needs-review", "noticed-by-openneko"]
86
+ ```
87
+
88
+ Tags ARE searchable in Shopify admin's order list, notes aren't.
89
+ Adding a tag like `"noticed-by-openneko"` lets the operator pull
90
+ "show me everything OpenNeko flagged this week" later.
91
+
92
+ ## Failure modes the operator should see
93
+
94
+ - **`401 Unauthorized`** — `SHOPIFY_ACCESS_TOKEN` expired or scope
95
+ insufficient. Prompt the operator to regenerate the token with
96
+ read_orders + write_orders.
97
+ - **`404 Not Found`** on an order id — usually the operator gave you
98
+ the `order_number` (#1234) instead of the `id`. Re-run `list` to
99
+ resolve.
100
+ - **`429 Too Many Requests`** — Shopify rate limits per app per
101
+ store (2 req/sec leaky bucket on REST). Back off; if the operator
102
+ is waiting, surface the Retry-After header so they know how long.
103
+ - **`Invalid SHOPIFY_STORE_DOMAIN`** — the plugin rejects domains
104
+ that aren't `*.myshopify.com`. Custom storefront domains don't
105
+ work on the Admin API.
106
+
107
+ ## What this skill is NOT for
108
+
109
+ - Storefront product browsing — that's the public Storefront API,
110
+ a different scope set, and this connector doesn't enable it.
111
+ - Customer-facing email — Shopify can send transactional emails;
112
+ this connector doesn't trigger them. Use the operator's
113
+ email/Gmail connector for that.
114
+ - Bulk migrations — Shopify's REST API caps you at ~2 req/sec.
115
+ For >1000 changes, point the operator at Shopify's bulk operations
116
+ GraphQL endpoint, not this plugin.