twindex-openclaw-plugin 0.8.1 → 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.1",
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.1",
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) {
@@ -117,6 +122,16 @@ export default function register(api: any) {
117
122
  } finally {
118
123
  bootstrapping = false;
119
124
  }
125
+
126
+ // Sync city on every reload (city often arrives after bootstrap via config.patch)
127
+ const postConfig = cfg();
128
+ if (postConfig.apiKey && postConfig.city) {
129
+ twindex.setCity(postConfig.apiKey, postConfig.city).then(() => {
130
+ api.logger?.info?.(`NewLore: city synced to ${postConfig.city}`);
131
+ }).catch((err: any) => {
132
+ api.logger?.warn?.(`NewLore: failed to sync city: ${err.message}`);
133
+ });
134
+ }
120
135
  })();
121
136
 
122
137
  // ── Tools ──────────────────────────────────────────────────────────
@@ -507,22 +522,31 @@ export default function register(api: any) {
507
522
  if (params.photo_url) {
508
523
  await twindex.uploadPhoto(apiKey, params.photo_url);
509
524
  } else {
510
- // Find the most recent inbound photo from OpenClaw media
511
- const inboundDir = join(homedir(), ".openclaw", "media", "inbound");
512
- let files: string[];
513
- try {
514
- files = readdirSync(inboundDir).filter((f) =>
515
- /\.(jpg|jpeg|png)$/i.test(f),
516
- );
517
- } catch {
518
- return {
519
- content: [
520
- {
521
- type: "text",
522
- text: "No photos found. Please send a selfie in chat first.",
523
- },
524
- ],
525
- };
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
+ }
526
550
  }
527
551
 
528
552
  if (files.length === 0) {
@@ -538,12 +562,12 @@ export default function register(api: any) {
538
562
 
539
563
  // Sort by modification time, newest first
540
564
  files.sort((a, b) => {
541
- const aStat = statSync(join(inboundDir, a));
542
- const bStat = statSync(join(inboundDir, b));
565
+ const aStat = statSync(join(resolvedDir, a));
566
+ const bStat = statSync(join(resolvedDir, b));
543
567
  return bStat.mtimeMs - aStat.mtimeMs;
544
568
  });
545
569
 
546
- const latestFile = join(inboundDir, files[0]);
570
+ const latestFile = join(resolvedDir, files[0]);
547
571
  const fileData = readFileSync(latestFile);
548
572
  await twindex.uploadPhotoFile(
549
573
  apiKey,
@@ -677,4 +701,3 @@ export default function register(api: any) {
677
701
  },
678
702
  });
679
703
  }
680
-
@@ -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