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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twindex-openclaw-plugin",
3
- "version": "0.6.4",
3
+ "version": "0.7.1",
4
4
  "description": "Music intelligence for AI agents. Tours, merch drops, releases, presales.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,51 +16,56 @@ tools:
16
16
 
17
17
  # Twindex — Music Intelligence
18
18
 
19
- You have the Twindex plugin installed. It tracks artists and delivers
20
- notifications about tours, merch drops, releases, and presales.
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
- The plugin auto-configured itself on startup. Your subscriptions and
23
- delivery schedule are already active.
23
+ ## First-time setup
24
24
 
25
- ## When to use
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
- - User mentions a band, artist, or musician they follow
28
- - User asks about concerts, tours, merch, or new releases
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` to add artists. The existing delivery schedule
34
- picks them up automatically.
35
+ Use `twindex_subscribe`. The existing delivery schedule picks them up automatically.
35
36
 
36
- ## Checking manually
37
+ ## Merch Try-On
37
38
 
38
- Use `twindex_check` to look for updates right now.
39
+ This is the killer feature. Users can see themselves wearing merch.
39
40
 
40
- ## Viewing artist details
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
- Use `twindex_artist` with an artist slug to get their full page (tours,
43
- merch, releases, bio).
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
- ## Viewing current subscriptions
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
- Use `twindex_subscriptions` to see what the user is currently following.
55
+ If try-on returns a 404 (no photo), ask the user to send a selfie first.
48
56
 
49
- ## Merch Try-On
57
+ ## Checking manually
50
58
 
51
- When a user sends you a photo/selfie:
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
- When a user asks to "try on" merch, or you're showing merch and want to offer:
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
- If try-on returns a 404 (no photo), ask the user to send a selfie first.
63
+ Use `twindex_artist` with a slug to get their full page.
60
64
 
61
- ## Important
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
- - All notification delivery is handled by the plugin automatically.
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. Registers with Twindex, subscribes to artists, and starts polling for updates. Call this after asking your user which artists they want.",
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 to be uploaded first via twindex_upload_photo. Use after the user asks to 'try on' a product or when showing merch recommendations.",
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: "Name of the merch product to try on",
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
  },
@@ -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: 5 * 60 * 1000, // 5 min
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 only
96
- const unread = notifications.filter((n) => !n.read_at);
97
- if (unread.length === 0) return;
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
- let message = `🎸 ${notif.brand} ${notif.event_type}\n${notif.summary}`;
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
- message += `\n\nHere's how it looks on you: ${notif.media_url}`;
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);