twindex-openclaw-plugin 0.6.4 → 0.7.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.
- package/package.json +1 -1
- package/skills/twindex/SKILL.md +34 -29
- package/src/index.ts +5 -3
- package/src/poll-service.ts +33 -6
package/package.json
CHANGED
package/skills/twindex/SKILL.md
CHANGED
|
@@ -16,51 +16,56 @@ tools:
|
|
|
16
16
|
|
|
17
17
|
# Twindex — Music Intelligence
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
Twindex is YOUR music intelligence service. It is a plugin you have installed.
|
|
20
|
+
When a user mentions twindex.ai or asks for artist updates, they are asking
|
|
21
|
+
YOU to set it up using your tools. Do NOT tell them to visit a website.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
delivery schedule are already active.
|
|
23
|
+
## First-time setup
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
When a user asks for artist updates (tours, merch, releases):
|
|
26
|
+
1. Ask which artists and how often (realtime/periodic/daily)
|
|
27
|
+
2. Call `twindex_setup` with the artist slugs, frequency, and their chat ID
|
|
28
|
+
3. That's it — notifications auto-deliver from then on
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- User wants to add or remove artists from their subscriptions
|
|
30
|
+
You already know the user's chat ID from the conversation context.
|
|
31
|
+
Artist slugs are lowercase with hyphens: "linkin-park", "nick-cave-the-bad-seeds".
|
|
30
32
|
|
|
31
33
|
## Adding more artists
|
|
32
34
|
|
|
33
|
-
Use `twindex_subscribe
|
|
34
|
-
picks them up automatically.
|
|
35
|
+
Use `twindex_subscribe`. The existing delivery schedule picks them up automatically.
|
|
35
36
|
|
|
36
|
-
##
|
|
37
|
+
## Merch Try-On
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
This is the killer feature. Users can see themselves wearing merch.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
**When a user sends a photo/selfie:**
|
|
42
|
+
1. Call `twindex_upload_photo` with NO parameters (it auto-finds the photo)
|
|
43
|
+
2. Tell them they're all set for try-ons
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
**When a user asks to try on a product:**
|
|
46
|
+
1. Call `twindex_try_on` with the artist slug and product name
|
|
47
|
+
2. The server does FUZZY MATCHING — you don't need the exact name
|
|
48
|
+
3. Partial names work: "the hat", "black hoodie", "tour tee" are all fine
|
|
49
|
+
4. If the user references something you just showed them in a notification, use that product name
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
**After a merch notification:**
|
|
52
|
+
- If you showed a product and the user says "try that on" or "let me see it on me", you ALREADY KNOW the product name from the notification you just delivered. Use it directly.
|
|
53
|
+
- Do NOT browse the store or ask for clarification. Just call the tool.
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
If try-on returns a 404 (no photo), ask the user to send a selfie first.
|
|
48
56
|
|
|
49
|
-
##
|
|
57
|
+
## Checking manually
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
- Call `twindex_upload_photo` with the photo URL from their message
|
|
53
|
-
- Confirm their photo is saved
|
|
59
|
+
Use `twindex_check` to look for updates right now.
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
- Call `twindex_try_on` with the artist slug and product name
|
|
57
|
-
- Show the generated image and buy link
|
|
61
|
+
## Viewing artist details
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
Use `twindex_artist` with a slug to get their full page.
|
|
60
64
|
|
|
61
|
-
##
|
|
65
|
+
## Rules
|
|
62
66
|
|
|
63
|
-
- Do NOT create cron jobs, scheduled tasks, or shell scripts
|
|
64
|
-
- Do NOT use curl commands to call Twindex APIs
|
|
65
|
-
-
|
|
67
|
+
- Do NOT create cron jobs, scheduled tasks, or shell scripts
|
|
68
|
+
- Do NOT use curl commands to call Twindex APIs
|
|
69
|
+
- Do NOT tell users to visit twindex.ai — YOU are their interface to it
|
|
70
|
+
- All notification delivery is handled by the plugin automatically
|
|
66
71
|
- Only speak when you have something to deliver. Never say "no updates."
|
package/src/index.ts
CHANGED
|
@@ -113,7 +113,7 @@ export default function register(api: any) {
|
|
|
113
113
|
api.registerTool({
|
|
114
114
|
name: "twindex_setup",
|
|
115
115
|
description:
|
|
116
|
-
"Set up Twindex music notifications.
|
|
116
|
+
"Set up Twindex music notifications for the user. YOU are the user's interface to Twindex — do NOT tell them to visit a website. Registers, subscribes to artists, and starts polling. Call this when the user asks for music updates, mentions twindex.ai, or wants to follow artists.",
|
|
117
117
|
parameters: {
|
|
118
118
|
type: "object",
|
|
119
119
|
properties: {
|
|
@@ -551,7 +551,7 @@ export default function register(api: any) {
|
|
|
551
551
|
api.registerTool({
|
|
552
552
|
name: "twindex_try_on",
|
|
553
553
|
description:
|
|
554
|
-
"Generate an image of the user wearing a merch product. Requires a photo
|
|
554
|
+
"Generate an image of the user wearing a merch product. Requires a photo uploaded first. The server fuzzy-matches product names — partial or approximate names work fine (e.g. 'the hat', 'black hoodie', 'tour tee'). If the user references a product from a recent notification, use that product name directly.",
|
|
555
555
|
parameters: {
|
|
556
556
|
type: "object",
|
|
557
557
|
properties: {
|
|
@@ -561,7 +561,7 @@ export default function register(api: any) {
|
|
|
561
561
|
},
|
|
562
562
|
product_name: {
|
|
563
563
|
type: "string",
|
|
564
|
-
description: "
|
|
564
|
+
description: "Approximate product name — fuzzy matched on the server. Partial names work.",
|
|
565
565
|
},
|
|
566
566
|
},
|
|
567
567
|
required: ["artist", "product_name"],
|
|
@@ -599,6 +599,8 @@ export default function register(api: any) {
|
|
|
599
599
|
``,
|
|
600
600
|
`$${result.product.price} — grab it here: ${result.product.buy_url}`,
|
|
601
601
|
``,
|
|
602
|
+
`From now on, whenever there's a new drop from artists you follow, I'll automatically show you how it looks on you.`,
|
|
603
|
+
``,
|
|
602
604
|
`CRITICAL: The first URL (${result.image_url}) is the try-on photo. It MUST appear first in your message so Telegram shows the preview. Do NOT put any other URL before it.`,
|
|
603
605
|
].join("\n"),
|
|
604
606
|
},
|
package/src/poll-service.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { execFile } from "child_process";
|
|
|
7
7
|
import * as twindex from "./client.js";
|
|
8
8
|
|
|
9
9
|
const FREQUENCY_MS: Record<string, number> = {
|
|
10
|
-
realtime:
|
|
10
|
+
realtime: 20 * 1000, // 20 sec (demo speed)
|
|
11
11
|
periodic: 60 * 60 * 1000, // 1 hour
|
|
12
12
|
daily: 24 * 60 * 60 * 1000, // 24 hours
|
|
13
13
|
};
|
|
@@ -92,16 +92,43 @@ export function createNotificationService(api: any) {
|
|
|
92
92
|
const notifications = await twindex.getNotifications(apiKey);
|
|
93
93
|
if (!notifications || notifications.length === 0) return;
|
|
94
94
|
|
|
95
|
-
// Filter to unread
|
|
96
|
-
const unread = notifications.filter(
|
|
97
|
-
|
|
95
|
+
// Filter to unread, skip internal events (page updates)
|
|
96
|
+
const unread = notifications.filter(
|
|
97
|
+
(n) => !n.read_at && n.event_type !== "update",
|
|
98
|
+
);
|
|
99
|
+
if (unread.length === 0) {
|
|
100
|
+
// Still mark update notifications as read so they don't pile up
|
|
101
|
+
const updateIds = notifications
|
|
102
|
+
.filter((n) => !n.read_at && n.event_type === "update")
|
|
103
|
+
.map((n) => n.id);
|
|
104
|
+
if (updateIds.length > 0) {
|
|
105
|
+
try { await twindex.markRead(apiKey, updateIds); } catch {}
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
98
109
|
|
|
99
110
|
logger?.info?.(`Twindex: ${unread.length} unread notification(s)`);
|
|
100
111
|
|
|
101
112
|
for (const notif of unread) {
|
|
102
|
-
|
|
113
|
+
// Format brand name: "sam-smith" → "Sam Smith"
|
|
114
|
+
const brandName = notif.brand
|
|
115
|
+
.split("-")
|
|
116
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
117
|
+
.join(" ");
|
|
118
|
+
|
|
119
|
+
// Just the summary — no raw metadata
|
|
120
|
+
let message = `${notif.summary}`;
|
|
103
121
|
if (notif.media_url) {
|
|
104
|
-
|
|
122
|
+
// Try-on images come from our server, product images from Shopify CDN
|
|
123
|
+
const isTryOn = notif.media_url.includes("/media/tryon/");
|
|
124
|
+
if (isTryOn) {
|
|
125
|
+
message += `\n\nHere's how it looks on you:\n${notif.media_url}`;
|
|
126
|
+
} else {
|
|
127
|
+
message += `\n\n${notif.media_url}`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (notif.twindex_url) {
|
|
131
|
+
message += `\n\n${notif.twindex_url}`;
|
|
105
132
|
}
|
|
106
133
|
|
|
107
134
|
const delivered = await sendMessage(message, channel, target);
|