twindex-openclaw-plugin 0.8.2 → 0.8.3

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.
@@ -2,7 +2,7 @@
2
2
  "id": "twindex",
3
3
  "name": "New Lore",
4
4
  "description": "Music intelligence for AI agents. Get notified about tours, merch drops, releases, and presales for your favorite artists.",
5
- "version": "0.8.2",
5
+ "version": "0.8.3",
6
6
  "skills": ["./skills"],
7
7
  "configSchema": {
8
8
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twindex-openclaw-plugin",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Music intelligence for AI agents. Tours, merch drops, releases, presales.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@ Use `newlore_subscribe`. The existing delivery schedule picks them up automatica
39
39
  This is the killer feature. Users can see themselves wearing merch.
40
40
 
41
41
  **When a user sends a photo/selfie:**
42
- 1. Call `newlore_upload_photo` with NO parameters (it auto-finds the photo)
42
+ 1. Call `newlore_upload_photo` with NO parameters (it auto-finds the most recent photo from the current chat)
43
43
  2. Tell them they're all set for try-ons
44
44
 
45
45
  **When a user asks to try on a product:**
package/src/config.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+
5
+ type PluginConfig = Record<string, any>;
6
+
7
+ export function readDiskPluginConfig(): PluginConfig {
8
+ try {
9
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
10
+ if (!existsSync(configPath)) return {};
11
+ const raw = readFileSync(configPath, "utf-8");
12
+ const parsed = JSON.parse(raw);
13
+ return parsed?.plugins?.entries?.twindex?.config ?? {};
14
+ } catch {
15
+ return {};
16
+ }
17
+ }
18
+
19
+ export function mergedPluginConfig(api: any): PluginConfig {
20
+ const disk = readDiskPluginConfig();
21
+ const memory = api.config?.plugins?.entries?.twindex?.config ?? {};
22
+ return { ...disk, ...memory };
23
+ }
package/src/index.ts CHANGED
@@ -3,19 +3,24 @@ import { join } from "path";
3
3
  import { homedir } from "os";
4
4
  import * as twindex from "./client.js";
5
5
  import { createNotificationService } from "./poll-service.js";
6
+ import { mergedPluginConfig } from "./config.js";
6
7
 
7
8
  let bootstrapping = false;
8
9
 
9
10
  export default function register(api: any) {
10
- const cfg = () => api.config?.plugins?.entries?.twindex?.config ?? {};
11
+ const cfg = () => mergedPluginConfig(api);
11
12
 
12
13
  function persistConfig(updates: Record<string, any>) {
13
- if (!api.config?.plugins?.entries?.twindex) return;
14
- // Update in-memory config
15
- api.config.plugins.entries.twindex.config = {
14
+ const nextConfig = {
16
15
  ...cfg(),
17
16
  ...updates,
18
17
  };
18
+
19
+ // Update in-memory config when the plugin entry is available.
20
+ if (api.config?.plugins?.entries?.twindex) {
21
+ api.config.plugins.entries.twindex.config = nextConfig;
22
+ }
23
+
19
24
  // Write to disk — OpenClaw plugin API does not expose config.save()
20
25
  try {
21
26
  const configPath = join(homedir(), ".openclaw", "openclaw.json");
@@ -24,7 +29,7 @@ export default function register(api: any) {
24
29
  if (!disk.plugins) disk.plugins = {};
25
30
  if (!disk.plugins.entries) disk.plugins.entries = {};
26
31
  if (!disk.plugins.entries.twindex) disk.plugins.entries.twindex = {};
27
- disk.plugins.entries.twindex.config = api.config.plugins.entries.twindex.config;
32
+ disk.plugins.entries.twindex.config = nextConfig;
28
33
  writeFileSync(configPath, JSON.stringify(disk, null, 2) + "\n", "utf-8");
29
34
  api.logger?.info?.("NewLore: config persisted to disk");
30
35
  } catch (err: any) {
@@ -517,22 +522,31 @@ export default function register(api: any) {
517
522
  if (params.photo_url) {
518
523
  await twindex.uploadPhoto(apiKey, params.photo_url);
519
524
  } else {
520
- // Find the most recent inbound photo from OpenClaw media
521
- const inboundDir = join(homedir(), ".openclaw", "media", "inbound");
522
- let files: string[];
523
- try {
524
- files = readdirSync(inboundDir).filter((f) =>
525
- /\.(jpg|jpeg|png)$/i.test(f),
526
- );
527
- } catch {
528
- return {
529
- content: [
530
- {
531
- type: "text",
532
- text: "No photos found. Please send a selfie in chat first.",
533
- },
534
- ],
535
- };
525
+ // Find the most recent inbound photo scoped to the current chat.
526
+ // Try chat-scoped directory first, then fall back to global inbound.
527
+ const chatTarget = inferChatTarget();
528
+ const mediaBase = join(homedir(), ".openclaw", "media", "inbound");
529
+ const searchDirs: string[] = [];
530
+ if (chatTarget) {
531
+ searchDirs.push(join(mediaBase, chatTarget));
532
+ }
533
+ searchDirs.push(mediaBase);
534
+
535
+ let files: string[] = [];
536
+ let resolvedDir = mediaBase;
537
+ for (const dir of searchDirs) {
538
+ try {
539
+ const found = readdirSync(dir).filter((f) =>
540
+ /\.(jpg|jpeg|png)$/i.test(f),
541
+ );
542
+ if (found.length > 0) {
543
+ files = found;
544
+ resolvedDir = dir;
545
+ break;
546
+ }
547
+ } catch {
548
+ // Directory doesn't exist, try next
549
+ }
536
550
  }
537
551
 
538
552
  if (files.length === 0) {
@@ -548,12 +562,12 @@ export default function register(api: any) {
548
562
 
549
563
  // Sort by modification time, newest first
550
564
  files.sort((a, b) => {
551
- const aStat = statSync(join(inboundDir, a));
552
- const bStat = statSync(join(inboundDir, b));
565
+ const aStat = statSync(join(resolvedDir, a));
566
+ const bStat = statSync(join(resolvedDir, b));
553
567
  return bStat.mtimeMs - aStat.mtimeMs;
554
568
  });
555
569
 
556
- const latestFile = join(inboundDir, files[0]);
570
+ const latestFile = join(resolvedDir, files[0]);
557
571
  const fileData = readFileSync(latestFile);
558
572
  await twindex.uploadPhotoFile(
559
573
  apiKey,
@@ -687,4 +701,3 @@ export default function register(api: any) {
687
701
  },
688
702
  });
689
703
  }
690
-
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { execFile } from "child_process";
7
7
  import * as twindex from "./client.js";
8
+ import { mergedPluginConfig } from "./config.js";
8
9
 
9
10
  const FREQUENCY_MS: Record<string, number> = {
10
11
  realtime: 20 * 1000, // 20 sec (demo speed)
@@ -32,16 +33,20 @@ export function createNotificationService(api: any) {
32
33
  let running = false;
33
34
  let polling = false; // guard against overlapping polls
34
35
 
36
+ function cfg(): Record<string, any> {
37
+ return mergedPluginConfig(api);
38
+ }
39
+
35
40
  function getApiKey(): string | undefined {
36
- return api.config?.plugins?.entries?.twindex?.config?.apiKey;
41
+ return cfg().apiKey;
37
42
  }
38
43
 
39
44
  function getFrequency(): string {
40
- return api.config?.plugins?.entries?.twindex?.config?.frequency ?? "periodic";
45
+ return cfg().frequency ?? "periodic";
41
46
  }
42
47
 
43
48
  function getChatTarget(): string | undefined {
44
- const raw = api.config?.plugins?.entries?.twindex?.config?.chatTarget;
49
+ const raw = cfg().chatTarget;
45
50
  if (raw) {
46
51
  // Strip channel prefix if agent included it (e.g. "telegram:123" → "123")
47
52
  const colonIdx = raw.indexOf(":");
@@ -58,12 +63,12 @@ export function createNotificationService(api: any) {
58
63
  }
59
64
 
60
65
  function getChatChannel(): string {
61
- const raw = api.config?.plugins?.entries?.twindex?.config?.chatTarget ?? "";
66
+ const raw = cfg().chatTarget ?? "";
62
67
  // Infer channel from prefix if present (e.g. "telegram:123" → "telegram")
63
68
  const colonIdx = raw.indexOf(":");
64
69
  if (colonIdx > 0) return raw.slice(0, colonIdx);
65
70
 
66
- const explicit = api.config?.plugins?.entries?.twindex?.config?.chatChannel;
71
+ const explicit = cfg().chatChannel;
67
72
  if (explicit) return explicit;
68
73
 
69
74
  // Fallback: infer from which channel is configured