@orchyn/mcp 1.12.2 → 1.13.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.
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  MCP (Model Context Protocol) server for [orchyn](https://orchyn.com) — fetch,
4
4
  discover and understand social posts (TikTok, Instagram, YouTube, X/Twitter,
5
- Douyin, Xiaohongshu, Bilibili) with your orchyn account and orchyn credits.
5
+ Douyin, Xiaohongshu, Bilibili, LinkedIn) with your orchyn account and orchyn
6
+ credits.
6
7
 
7
8
  ## Install (one link)
8
9
 
@@ -38,12 +39,12 @@ npx @orchyn/mcp login # one-time sign-in (Google)
38
39
 
39
40
  | Tool | Credits | Description |
40
41
  |------|---------|-------------|
41
- | `analyze_post` | first free* | **Preferred.** Analyze any post (video, image, carousel/slideshow) from a TikTok/Instagram/YouTube/X-Twitter/Douyin/Xiaohongshu/Bilibili URL — imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). Returns a `jobId` to poll via `GET /ai/analyze-post`. *First analysis free per workspace via the dashboard free grant.* |
42
+ | `analyze_post` | first free* | **Preferred.** Analyze any post (video, image, carousel/slideshow, text) from a TikTok/Instagram/YouTube/X-Twitter/Douyin/Xiaohongshu/Bilibili/LinkedIn URL — imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). Returns a `jobId` to poll via `GET /ai/analyze-post`. *First analysis free per workspace via the dashboard free grant.* |
42
43
  | `get_social_media` | 1 | Fetch a post's media from a URL: `contentType` (video/image/carousel/slideshow), title, caption, author, stats, direct media URLs **+ inline thumbnail image in chat**. |
43
44
  | `discover_social_posts` | 2 | **Preferred.** Find recent posts (video/image/carousel/slideshow) for a niche on YouTube, TikTok, Instagram, Douyin, Xiaohongshu, X/Twitter or Bilibili. Each post includes title/caption, views/likes/comments, author, `externalUrl` + **inline thumbnails (4 at a time)** — see *Images in chat* below. Supports `limit`/`offset` pagination (“next”). |
44
- | `get_user_posts` | 2 | List recent posts by a creator handle (e.g. `@zoundsapp`) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or Bilibili — title/caption, `thumbnailUrl`, `externalUrl`, views/likes/comments, **inline thumbnails (up to 4)**. Use to scan a whole profile or spot patterns across an account. |
45
- | `analyze_creator_profile` | 15 | Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or Bilibili: fetches recent posts, runs multimodal AI on up to 3, then synthesizes a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, engagement patterns, audience insights, variation ideas, collaboration fit. |
46
- | `get_post_comments` | 2 | Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter or Bilibili, plus keyword clusters from TikTok Analytics when available — audience sentiment/audience-signal analysis. |
45
+ | `get_user_posts` | 2 | List recent posts by a creator handle (e.g. `@zoundsapp`) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn (LinkedIn uses the profile `public_id`, e.g. `billgates`) — title/caption, `thumbnailUrl`, `externalUrl`, views/likes/comments, **inline thumbnails (up to 4)**. Use to scan a whole profile or spot patterns across an account. |
46
+ | `analyze_creator_profile` | 15 | Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn: fetches recent posts, runs multimodal AI on up to 3, then synthesizes a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, engagement patterns, audience insights, variation ideas, collaboration fit. |
47
+ | `get_post_comments` | 2 | Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter, Bilibili or LinkedIn, plus keyword clusters from TikTok Analytics when available — audience sentiment/audience-signal analysis. |
47
48
  | `search_creators` | 2 | Search creators by niche/keyword on TikTok, Instagram, Xiaohongshu, YouTube or Douyin — username, nickname, follower count, signature, verified status. Use to find influencers to vet or analyze. |
48
49
  | `get_similar_creators` | 2 | Find lookalike creators for a given handle (TikTok similar-user recommendations or Instagram similar users) — “if this creator works, here are more like them”. |
49
50
  | `discover_sounds` | 2 | Discover trending sounds/music for a keyword on TikTok or Instagram — sound choice is a huge ranking signal for TikTok virality. Returns title, artist, duration, play/cover URLs. |
@@ -57,9 +58,23 @@ All tools require a connected orchyn account and are billed against your orchyn
57
58
 
58
59
  `*` `analyze_post` also bills against **workspace** credits (first free grant); the other tools bill against **per-user MCP** credits (never tied to an app/workspace).
59
60
 
60
- ## Images in Claude / ChatGPT chat
61
+ ## Interactive cards in Claude / ChatGPT chat
61
62
 
62
- Every tool that returns posts also renders **inline thumbnails** directly in the chat:
63
+ Every tool that returns posts also renders **inline interactive cards** directly
64
+ in the chat (MCP Apps `ui://orchyn/view` resource rendered in a sandboxed
65
+ iframe):
66
+
67
+ - **Video posts** (TikTok/IG/YouTube/Douyin/LinkedIn) — an inline `<video>`
68
+ player with the thumbnail as poster, playing the re-hosted permanent MP4
69
+ (no expiring CDN tokens).
70
+ - **Carousels / slideshows** — a horizontally scrollable strip of every slide
71
+ with an image count chip.
72
+ - **Single images** — inline thumbnail.
73
+ - **Text-only posts** (LinkedIn / X) — a styled quote block of the post text.
74
+ - **Official brand marks** — each card shows the platform's real logo
75
+ (simple-icons) in its brand color instead of an emoji.
76
+
77
+ Also **inline thumbnails** render for the model / plain-text clients:
63
78
 
64
79
  - `get_social_media` / `understand_social_post` / `analyze_post` — up to 4 frames inline (poster + carousel slides). The full `mediaItems[].preview_url` + `thumbnailUrl` stay in `structuredContent` for the model to reason over.
65
80
  - `discover_social_posts` / `get_user_posts` / `analyze_creator_profile` — each returned post shows its thumbnail inline (up to 4 at once) together with its title/caption + views/likes/comments. Say **"next"** or **"show more"** — Claude will re-call with `offset`/`limit` pagination. Say **"analyze the 2nd one"** — Claude calls `analyze_post` or `understand_social_post` on that URL.
@@ -256,8 +271,10 @@ per the MCP 2025-03-26 spec):
256
271
  - Douyin: `douyin.com/*`
257
272
  - Xiaohongshu: `xiaohongshu.com/*`, `xhslink.com/*`
258
273
  - Bilibili: `bilibili.com/*`, `b23.tv/*`
274
+ - LinkedIn: `linkedin.com/*` (posts, profile URLs)
259
275
 
260
- All tools accept these hosts and handle **video, image, carousel and slideshow** posts.
276
+ All tools accept these hosts and handle **video, image, carousel, slideshow
277
+ and text** posts.
261
278
 
262
279
  ## Troubleshooting
263
280
 
@@ -302,7 +319,7 @@ package for **Claude Desktop**, **Cursor**, and **OpenAI Agents SDK**.
302
319
  ```bash
303
320
  npm install
304
321
  npm run build # tsc
305
- npm test # vitest (54 unit tests, mocked fetch — no network)
322
+ npm test # vitest (66 unit tests, mocked fetch — no network)
306
323
  ```
307
324
 
308
325
  ## License
@@ -11,13 +11,31 @@ import { OrchynError } from "./orchyn.js";
11
11
  import { formatPaywallError, runVideoAnalysis, validatePostUrl } from "./video.js";
12
12
  import { ORCHYN_UI_TEMPLATE } from "./ui-template.js";
13
13
  /** Current MCP server version — bumped on every deploy for traceability. */
14
- export const MCP_SERVER_VERSION = "1.12.1";
14
+ export const MCP_SERVER_VERSION = "1.13.0";
15
15
  /** MCP Apps extension identifier */
16
16
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
17
17
  /** MIME type for MCP Apps HTML resources */
18
18
  const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
19
19
  /** Single UI resource URI shared by all tools */
20
20
  const UI_RESOURCE_URI = "ui://orchyn/view";
21
+ /** Convert an arbitrary http(s) URL to a base64 MCP `image` data block. */
22
+ async function fetchAsBase64Image(url, mimeType) {
23
+ try {
24
+ const res = await fetch(url, { signal: AbortSignal.timeout(12_000) });
25
+ if (!res.ok)
26
+ return { type: "image", data: "", mimeType };
27
+ const buf = await res.arrayBuffer();
28
+ const bytes = new Uint8Array(buf);
29
+ let bin = "";
30
+ for (let i = 0; i < bytes.length; i += 0x8000) {
31
+ bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
32
+ }
33
+ return { type: "image", data: btoa(bin), mimeType };
34
+ }
35
+ catch {
36
+ return { type: "image", data: "", mimeType };
37
+ }
38
+ }
21
39
  /**
22
40
  * Rewrite external image URLs to go through the orchyn proxy so they
23
41
  * work inside ChatGPT's sandboxed iframe (CORS + CSP restrictions).
@@ -52,7 +70,7 @@ function proxyUrls(obj) {
52
70
  if (obj && typeof obj === "object") {
53
71
  const out = {};
54
72
  for (const [k, v] of Object.entries(obj)) {
55
- if (["thumbnailUrl", "preview_url", "coverUrl", "avatarUrl", "avatar_thumb", "image_url", "url"].includes(k) && typeof v === "string") {
73
+ if (["thumbnailUrl", "preview_url", "coverUrl", "avatarUrl", "avatar_thumb", "image_url", "videoUrl", "video_url", "url"].includes(k) && typeof v === "string") {
56
74
  out[k] = proxyImageUrl(v);
57
75
  }
58
76
  else {
@@ -63,14 +81,15 @@ function proxyUrls(obj) {
63
81
  }
64
82
  return obj;
65
83
  }
66
- function toToolResult(proxy) {
67
- const images = proxy.contentBlocks
68
- .filter((c) => c.type === "image")
69
- .map((c) => ({
70
- type: "image",
71
- data: String(c.data ?? ""),
72
- mimeType: String(c.mimeType ?? "image/jpeg"),
73
- }));
84
+ async function toToolResult(proxy) {
85
+ // The MCP SDK's ImageContent only accepts `data` (base64). The orchyn
86
+ // backend now emits `url` image blocks pointing at permanent, re-hosted
87
+ // JPEG thumbnails (HEIC already transcoded), so convert those URLs to
88
+ // base64 data here — guaranteeing clients render a valid image.
89
+ const imgBlocks = proxy.contentBlocks.filter((c) => c.type === "image");
90
+ const images = await Promise.all(imgBlocks.map((c) => c.url
91
+ ? fetchAsBase64Image(String(c.url), String(c.mimeType ?? "image/jpeg"))
92
+ : Promise.resolve({ type: "image", data: String(c.data ?? ""), mimeType: String(c.mimeType ?? "image/jpeg") })));
74
93
  // The Rust backend embeds HTML cards directly in the text block
75
94
  // (type:"text", text:"<div>...</div>\n\n{json}"). We extract the HTML
76
95
  // prefix from the first text contentBlock and prepend it.
@@ -91,9 +110,9 @@ function toToolResult(proxy) {
91
110
  structuredContent: proxied ?? {},
92
111
  };
93
112
  }
94
- /** Replace external image src/href URLs in HTML with proxied versions. */
113
+ /** Replace external image/video src, poster and href URLs in HTML with proxied versions. */
95
114
  function proxyImageUrlsInHtml(html) {
96
- return html.replace(/(src|href)="(https?:\/\/[^"']+?)"/g, (_match, attr, url) => {
115
+ return html.replace(/(src|href|poster)="(https?:\/\/[^"']+?)"/g, (_match, attr, url) => {
97
116
  return `${attr}="${proxyImageUrl(url)}"`;
98
117
  });
99
118
  }
@@ -101,19 +120,36 @@ function toolError(prefix, err) {
101
120
  const msg = err instanceof Error ? err.message : String(err);
102
121
  return { content: [{ type: "text", text: `${prefix}: ${msg}` }], isError: true };
103
122
  }
104
- /** Build an HTML card for analyze_post results. */
105
- function buildAnalysisHtmlCard(result, thumbnailUrl, platform, creatorHandle, title) {
106
- const platformEmoji = {
107
- tiktok: "🎵", instagram: "📸", youtube: "▶️", twitter: "🐦",
108
- x: "🐦", douyin: "🎵", xiaohongshu: "📕", bilibili: "📺",
123
+ /** Official brand color per platform (matches ui-template.ts pColor). */
124
+ function platformBrandColor(p) {
125
+ const map = {
126
+ tiktok: "#000000", douyin: "#000000", instagram: "#E4405F",
127
+ youtube: "#FF0000", xiaohongshu: "#FF2442", x: "#000000",
128
+ twitter: "#1DA1F2", bilibili: "#00A1D6", linkedin: "#0A66C2",
109
129
  };
110
- const platformColor = {
111
- tiktok: "#000000", instagram: "#E4405F", youtube: "#FF0000",
112
- twitter: "#1DA1F2", x: "#000000", douyin: "#000000",
113
- xiaohongshu: "#FF2442", bilibili: "#00A1D6",
130
+ return map[p] || "#6b7280";
131
+ }
132
+ /** Official brand mark (simple-icons path) as an inline SVG inheriting currentColor. */
133
+ function platformSvgMark(p, size = 14) {
134
+ const paths = {
135
+ tiktok: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
136
+ douyin: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
137
+ instagram: "M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077",
138
+ youtube: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
139
+ x: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
140
+ twitter: "M21.543 7.104c.015.211.015.423.015.636 0 6.507-4.954 14.01-14.01 14.01v-.003A13.94 13.94 0 0 1 0 19.539a9.88 9.88 0 0 0 7.287-2.041 4.93 4.93 0 0 1-4.6-3.42 4.916 4.916 0 0 0 2.223-.084A4.926 4.926 0 0 1 .96 9.167v-.062a4.887 4.887 0 0 0 2.235.616A4.928 4.928 0 0 1 1.67 3.148 13.98 13.98 0 0 0 11.82 8.292a4.929 4.929 0 0 1 8.39-4.49 9.868 9.868 0 0 0 3.128-1.196 4.941 4.941 0 0 1-2.165 2.724A9.828 9.828 0 0 0 24 4.555a10.019 10.019 0 0 1-2.457 2.549z",
141
+ xiaohongshu: "M22.405 9.879c.002.016.01.02.07.019h.725a.797.797 0 0 0 .78-.972.794.794 0 0 0-.884-.618.795.795 0 0 0-.692.794c0 .101-.002.666.001.777zm-11.509 4.808c-.203.001-1.353.004-1.685.003a2.528 2.528 0 0 1-.766-.126.025.025 0 0 0-.03.014L7.7 16.127a.025.025 0 0 0 .01.032c.111.06.336.124.495.124.66.01 1.32.002 1.981 0 .01 0 .02-.006.023-.015l.712-1.545a.025.025 0 0 0-.024-.036zM.477 9.91c-.071 0-.076.002-.076.01a.834.834 0 0 0-.01.08c-.027.397-.038.495-.234 3.06-.012.24-.034.389-.135.607-.026.057-.033.042.003.112.046.092.681 1.523.787 1.74.008.015.011.02.017.02.008 0 .033-.026.047-.044.147-.187.268-.391.371-.606.306-.635.44-1.325.486-1.706.014-.11.021-.22.03-.33l.204-2.616.022-.293c.003-.029 0-.033-.03-.034zm7.203 3.757a1.427 1.427 0 0 1-.135-.607c-.004-.084-.031-.39-.235-3.06a.443.443 0 0 0-.01-.082c-.004-.011-.052-.008-.076-.008h-1.48c-.03.001-.034.005-.03.034l.021.293c.076.982.153 1.964.233 2.946.05.4.186 1.085.487 1.706.103.215.223.419.37.606.015.018.037.051.048.049.02-.003.742-1.642.804-1.765.036-.07.03-.055.003-.112zm3.861-.913h-.872a.126.126 0 0 1-.116-.178l1.178-2.625a.025.025 0 0 0-.023-.035l-1.318-.003a.148.148 0 0 1-.135-.21l.876-1.954a.025.025 0 0 0-.023-.035h-1.56c-.01 0-.02.006-.024.015l-.926 2.068c-.085.169-.314.634-.399.938a.534.534 0 0 0-.02.191.46.46 0 0 0 .23.378.981.981 0 0 0 .46.119h.59c.041 0-.688 1.482-.834 1.972a.53.53 0 0 0-.023.172.465.465 0 0 0 .23.398c.15.092.342.12.475.12l1.66-.001c.01 0 .02-.006.023-.015l.575-1.28a.025.025 0 0 0-.024-.035zm-6.93-4.937H3.1a.032.032 0 0 0-.034.033c0 1.048-.01 2.795-.01 6.829 0 .288-.269.262-.28.262h-.74c-.04.001-.044.004-.04.047.001.037.465 1.064.555 1.263.01.02.03.033.051.033.157.003.767.009.938-.014.153-.02.3-.06.438-.132.3-.156.49-.419.595-.765.052-.172.075-.353.075-.533.002-2.33 0-4.66-.007-6.991a.032.032 0 0 0-.032-.032zm11.784 6.896c0-.014-.01-.021-.024-.022h-1.465c-.048-.001-.049-.002-.05-.049v-4.66c0-.072-.005-.07.07-.07h.863c.08 0 .075.004.075-.074V8.393c0-.082.006-.076-.08-.076h-3.5c-.064 0-.075-.006-.075.073v1.445c0 .083-.006.077.08.077h.854c.075 0 .07-.004.07.07v4.624c0 .095.008.084-.085.084-.37 0-1.11-.002-1.304 0-.048.001-.06.03-.06.03l-.697 1.519s-.014.025-.008.036c.006.01.013.008.058.008 1.748.003 3.495.002 5.243.002.03-.001.034-.006.035-.033v-1.539zm4.177-3.43c0 .013-.007.023-.02.024-.346.006-.692.004-1.037.004-.014-.002-.022-.01-.022-.024-.005-.434-.007-.869-.01-1.303 0-.072-.006-.071.07-.07l.733-.003c.041 0 .081.002.12.015.093.025.16.107.165.204.006.431.002 1.153.001 1.153zm2.67.244a1.953 1.953 0 0 0-.883-.222h-.18c-.04-.001-.04-.003-.042-.04V10.21c0-.132-.007-.263-.025-.394a1.823 1.823 0 0 0-.153-.53 1.533 1.533 0 0 0-.677-.71 2.167 2.167 0 0 0-1-.258c-.153-.003-.567 0-.72 0-.07 0-.068.004-.068-.065V7.76c0-.031-.01-.041-.046-.039H17.93s-.016 0-.023.007c-.006.006-.008.012-.008.023v.546c-.008.036-.057.015-.082.022h-.95c-.022.002-.028.008-.03.032v1.481c0 .09-.004.082.082.082h.913c.082 0 .072.128.072.128V11.19s.003.117-.06.117h-1.482c-.068 0-.06.082-.06.082v1.445s-.01.068.064.068h1.457c.082 0 .076-.006.076.079v3.225c0 .088-.007.081.082.081h1.43c.09 0 .082.007.082-.08v-3.27c0-.029.006-.035.033-.035l2.323-.003c.098 0 .191.02.28.061a.46.46 0 0 1 .274.407c.008.395.003.79.003 1.185 0 .259-.107.367-.33.367h-1.218c-.023.002-.029.008-.028.033.184.437.374.871.57 1.303a.045.045 0 0 0 .04.026c.17.005.34.002.51.003.15-.002.517.004.666-.01a2.03 2.03 0 0 0 .408-.075c.59-.18.975-.698.976-1.313v-1.981c0-.128-.01-.254-.034-.38 0 .078-.029-.641-.724-.998z",
142
+ bilibili: "M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373Z",
143
+ linkedin: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
144
+ unknown: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",
114
145
  };
115
- const emoji = platformEmoji[platform] || "📱";
116
- const color = platformColor[platform] || "#6b7280";
146
+ const d = paths[p] || paths.unknown;
147
+ return `<svg viewBox="0 0 24 24" width="${size}" height="${size}" fill="currentColor" style="vertical-align:-2px;flex-shrink:0;display:inline-block" aria-hidden="true"><path d="${d}"/></svg>`;
148
+ }
149
+ /** Build an HTML card for analyze_post results. */
150
+ function buildAnalysisHtmlCard(result, thumbnailUrl, platform, creatorHandle, title) {
151
+ const brandSvg = platformSvgMark(platform, 14);
152
+ const color = platformBrandColor(platform);
117
153
  const post = (result.post ?? {});
118
154
  const analysis = (result.analysis ?? {});
119
155
  const views = typeof post.views === "number" ? post.views.toLocaleString() : "—";
@@ -126,12 +162,19 @@ function buildAnalysisHtmlCard(result, thumbnailUrl, platform, creatorHandle, ti
126
162
  const viralTriggers = Array.isArray(analysis.viralTriggers) ? analysis.viralTriggers : [];
127
163
  const variationIdeas = Array.isArray(analysis.variationIdeas) ? analysis.variationIdeas.slice(0, 3) : [];
128
164
  const suggestedHook = typeof analysis.suggestedHook === "string" ? analysis.suggestedHook : "";
165
+ const videoUrl = typeof post.videoUrl === "string"
166
+ ? post.videoUrl
167
+ : Array.isArray(post.mediaItems)
168
+ ? post.mediaItems.find((m) => m.kind === "video" && typeof m.preview_url === "string")?.preview_url
169
+ : undefined;
129
170
  let html = `<div style="font-family:system-ui,-apple-system,sans-serif;max-width:600px;background:#0d1117;color:#e6edf3;border-radius:12px;overflow:hidden;margin:0 auto">`;
130
- // Thumbnail
131
- if (thumbnailUrl) {
132
- html += `<div style="position:relative"><img src="${thumbnailUrl}" style="width:100%;max-height:400px;object-fit:cover;display:block" onerror="this.style.display='none'" /><div style="position:absolute;bottom:12px;left:12px;display:flex;gap:8px;flex-wrap:wrap">`;
133
- html += `<span style="background:${color};color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600">${emoji} ${platform}</span>`;
134
- html += `</div></div>`;
171
+ // Thumbnail / video
172
+ const badgeHtml = `<span style="background:${color};color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;display:inline-flex;align-items:center;gap:4px">${brandSvg} ${platform}</span>`;
173
+ if (videoUrl) {
174
+ html += `<div style="position:relative"><video src="${videoUrl}" controls preload="metadata" playsinline poster="${thumbnailUrl}" style="width:100%;max-height:400px;object-fit:contain;display:block;background:#000" onerror="this.outerHTML='<img src=&quot;${thumbnailUrl}&quot; style=&quot;width:100%;max-height:400px;object-fit:cover;display:block&quot;/>'">Your browser doesn't support video playback.</video><div style="position:absolute;bottom:12px;left:12px;display:flex;gap:8px;flex-wrap:wrap">${badgeHtml}</div></div>`;
175
+ }
176
+ else if (thumbnailUrl) {
177
+ html += `<div style="position:relative"><img src="${thumbnailUrl}" style="width:100%;max-height:400px;object-fit:cover;display:block" onerror="this.style.display='none'" /><div style="position:absolute;bottom:12px;left:12px;display:flex;gap:8px;flex-wrap:wrap">${badgeHtml}</div></div>`;
135
178
  }
136
179
  // Header
137
180
  html += `<div style="padding:16px 20px">`;
@@ -201,6 +244,8 @@ export function createMcpServer(makeClient) {
201
244
  "https://*.cdninstagram.com",
202
245
  "https://*.ytimg.com",
203
246
  "https://*.googlevideo.com",
247
+ "https://*.licdn.com",
248
+ "https://*.linkedin.com",
204
249
  ];
205
250
  const apiUrl = process.env.ORCHYN_API_URL || process.env.ORCHYN_BASE_URL;
206
251
  if (apiUrl && apiUrl.trim()) {
@@ -257,11 +302,14 @@ export function createMcpServer(makeClient) {
257
302
  const creatorHandle = String(post.creatorHandle ?? "");
258
303
  const title = String(post.title ?? post.caption ?? "");
259
304
  const htmlCard = buildAnalysisHtmlCard(proxied, thumbnailUrl, platform, creatorHandle, title);
260
- const images = (result.inlineImages ?? []).map((img) => ({
261
- type: "image",
262
- data: String(img.data),
263
- mimeType: String(img.mimeType ?? "image/jpeg"),
264
- }));
305
+ // The backend attaches `_inlineImages` as permanent orchyn public URLs
306
+ // (re-hosted into storage, HEIC already transcoded to JPEG). The SDK's
307
+ // ImageContent only accepts base64 `data`, so fetch each URL and encode it.
308
+ const images = await Promise.all((result.inlineImages ?? [])
309
+ .filter((img) => typeof img === "object" && img !== null)
310
+ .map((img) => "url" in img && img.url
311
+ ? fetchAsBase64Image(String(img.url), String(img.mimeType ?? "image/jpeg"))
312
+ : Promise.resolve({ type: "image", data: String(img.data ?? ""), mimeType: String(img.mimeType ?? "image/jpeg") })));
265
313
  const textJson = JSON.stringify(proxied, null, 2);
266
314
  return {
267
315
  content: [...images, { type: "text", text: `${htmlCard}\n\n${textJson}` }],
@@ -293,7 +341,7 @@ export function createMcpServer(makeClient) {
293
341
  }, async (args, extra) => {
294
342
  const client = await makeClient(extra);
295
343
  try {
296
- return toToolResult(await client.callTool("get_social_media", { url: args.url }));
344
+ return await toToolResult(await client.callTool("get_social_media", { url: args.url }));
297
345
  }
298
346
  catch (err) {
299
347
  return toolError("get_social_media failed", err);
@@ -320,7 +368,7 @@ export function createMcpServer(makeClient) {
320
368
  }, async (args, extra) => {
321
369
  const client = await makeClient(extra);
322
370
  try {
323
- return toToolResult(await client.callTool("discover_social_posts", { ...args }));
371
+ return await toToolResult(await client.callTool("discover_social_posts", { ...args }));
324
372
  }
325
373
  catch (err) {
326
374
  return toolError("discover_social_posts failed", err);
@@ -328,7 +376,7 @@ export function createMcpServer(makeClient) {
328
376
  });
329
377
  server.registerTool("get_user_posts", {
330
378
  title: "Get User Posts",
331
- description: "List recent posts by a creator handle (e.g. @zoundsapp) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or Bilibili. " +
379
+ description: "List recent posts by a creator handle (e.g. @zoundsapp) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn (LinkedIn uses the profile public_id, e.g. 'billgates'). " +
332
380
  "Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
333
381
  "Use this when Claude needs to pull more posts from the same account to spot a pattern, or to scan a whole profile. Consumes 2 orchyn credits. First use free per user.",
334
382
  _meta: { ui: { resourceUri: UI_RESOURCE_URI } },
@@ -336,7 +384,7 @@ export function createMcpServer(makeClient) {
336
384
  .object({
337
385
  username: z.string().describe("Creator handle, e.g. 'zoundsapp' or '@zoundsapp'."),
338
386
  platform: z
339
- .enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili"])
387
+ .enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "linkedin"])
340
388
  .optional()
341
389
  .describe("Which platform (default tiktok)."),
342
390
  limit: z.number().int().optional().describe("Max posts (default 6)."),
@@ -345,7 +393,7 @@ export function createMcpServer(makeClient) {
345
393
  }, async (args, extra) => {
346
394
  const client = await makeClient(extra);
347
395
  try {
348
- return toToolResult(await client.callTool("get_user_posts", { ...args }));
396
+ return await toToolResult(await client.callTool("get_user_posts", { ...args }));
349
397
  }
350
398
  catch (err) {
351
399
  return toolError("get_user_posts failed", err);
@@ -353,7 +401,7 @@ export function createMcpServer(makeClient) {
353
401
  });
354
402
  server.registerTool("analyze_creator_profile", {
355
403
  title: "Analyze Creator Profile",
356
- description: "Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or Bilibili: fetch recent posts, run multimodal AI on up to 3, " +
404
+ description: "Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn: fetch recent posts, run multimodal AI on up to 3, " +
357
405
  "then synthesize a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, " +
358
406
  "engagement patterns, audience insights, variation ideas, collaboration fit. Consumes 15 orchyn credits. First use free per user.",
359
407
  _meta: { ui: { resourceUri: UI_RESOURCE_URI } },
@@ -361,7 +409,7 @@ export function createMcpServer(makeClient) {
361
409
  .object({
362
410
  username: z.string().describe("Creator handle, e.g. 'zoundsapp'."),
363
411
  platform: z
364
- .enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili"])
412
+ .enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "linkedin"])
365
413
  .optional()
366
414
  .describe("Which platform (default tiktok)."),
367
415
  limit: z.number().int().optional().describe("Posts to fetch (default 6; first 3 analyzed)."),
@@ -371,7 +419,7 @@ export function createMcpServer(makeClient) {
371
419
  }, async (args, extra) => {
372
420
  const client = await makeClient(extra);
373
421
  try {
374
- return toToolResult(await client.callTool("analyze_creator_profile", { ...args }));
422
+ return await toToolResult(await client.callTool("analyze_creator_profile", { ...args }));
375
423
  }
376
424
  catch (err) {
377
425
  return toolError("analyze_creator_profile failed", err);
@@ -379,19 +427,19 @@ export function createMcpServer(makeClient) {
379
427
  });
380
428
  server.registerTool("get_post_comments", {
381
429
  title: "Get Post Comments",
382
- description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter or Bilibili, plus keyword clusters from TikTok Analytics " +
430
+ description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter, Bilibili or LinkedIn, plus keyword clusters from TikTok Analytics " +
383
431
  "when available — audience sentiment/audience-signal analysis. Consumes 2 orchyn credits. First use free per user.",
384
432
  _meta: { ui: { resourceUri: UI_RESOURCE_URI } },
385
433
  inputSchema: z
386
434
  .object({
387
- url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili)."),
435
+ url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili/LinkedIn)."),
388
436
  limit: z.number().int().optional().describe("Max comments (default 20)."),
389
437
  })
390
438
  .strict(),
391
439
  }, async (args, extra) => {
392
440
  const client = await makeClient(extra);
393
441
  try {
394
- return toToolResult(await client.callTool("get_post_comments", { ...args }));
442
+ return await toToolResult(await client.callTool("get_post_comments", { ...args }));
395
443
  }
396
444
  catch (err) {
397
445
  return toolError("get_post_comments failed", err);
@@ -415,7 +463,7 @@ export function createMcpServer(makeClient) {
415
463
  }, async (args, extra) => {
416
464
  const client = await makeClient(extra);
417
465
  try {
418
- return toToolResult(await client.callTool("search_creators", { ...args }));
466
+ return await toToolResult(await client.callTool("search_creators", { ...args }));
419
467
  }
420
468
  catch (err) {
421
469
  return toolError("search_creators failed", err);
@@ -435,7 +483,7 @@ export function createMcpServer(makeClient) {
435
483
  }, async (args, extra) => {
436
484
  const client = await makeClient(extra);
437
485
  try {
438
- return toToolResult(await client.callTool("get_similar_creators", { ...args }));
486
+ return await toToolResult(await client.callTool("get_similar_creators", { ...args }));
439
487
  }
440
488
  catch (err) {
441
489
  return toolError("get_similar_creators failed", err);
@@ -456,7 +504,7 @@ export function createMcpServer(makeClient) {
456
504
  }, async (args, extra) => {
457
505
  const client = await makeClient(extra);
458
506
  try {
459
- return toToolResult(await client.callTool("discover_sounds", { ...args }));
507
+ return await toToolResult(await client.callTool("discover_sounds", { ...args }));
460
508
  }
461
509
  catch (err) {
462
510
  return toolError("discover_sounds failed", err);
@@ -470,7 +518,7 @@ export function createMcpServer(makeClient) {
470
518
  }, async (_args, extra) => {
471
519
  const client = await makeClient(extra);
472
520
  try {
473
- return toToolResult(await client.callTool("check_orchyn_credits", {}));
521
+ return await toToolResult(await client.callTool("check_orchyn_credits", {}));
474
522
  }
475
523
  catch (err) {
476
524
  return toolError("check_orchyn_credits failed", err);
@@ -484,7 +532,7 @@ export function createMcpServer(makeClient) {
484
532
  }, async (_args, extra) => {
485
533
  const client = await makeClient(extra);
486
534
  try {
487
- return toToolResult(await client.callTool("buy_orchyn_credits", {}));
535
+ return await toToolResult(await client.callTool("buy_orchyn_credits", {}));
488
536
  }
489
537
  catch (err) {
490
538
  return toolError("buy_orchyn_credits failed", err);
@@ -508,7 +556,7 @@ export function createMcpServer(makeClient) {
508
556
  }, async (args, extra) => {
509
557
  const client = await makeClient(extra);
510
558
  try {
511
- return toToolResult(await client.callTool("understand_social_post", { ...args }));
559
+ return await toToolResult(await client.callTool("understand_social_post", { ...args }));
512
560
  }
513
561
  catch (err) {
514
562
  return toolError("understand_social_post failed", err);
@@ -36,10 +36,13 @@ export const ORCHYN_UI_TEMPLATE = `<!DOCTYPE html>
36
36
  body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--bg);color:var(--fg);padding:16px;line-height:1.5;}
37
37
 
38
38
  /* ─── Loading / Skeleton ─── */
39
- .loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;gap:16px;}
40
- .loading-spinner{width:32px;height:32px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;}
39
+ .loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;gap:12px;}
40
+ .orchyn-logo{animation:logoSpin 3s ease-in-out infinite;}
41
+ @keyframes logoSpin{0%,100%{transform:rotate(0deg) scale(1)}50%{transform:rotate(180deg) scale(1.1)}}
42
+ .loading-brand{font-size:18px;font-weight:700;letter-spacing:-0.02em;color:var(--fg);}
43
+ .loading-spinner{width:28px;height:28px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;}
41
44
  @keyframes spin{to{transform:rotate(360deg)}}
42
- .loading-text{font-size:14px;color:var(--muted);animation:pulse 1.5s ease-in-out infinite;}
45
+ .loading-text{font-size:13px;color:var(--muted);animation:pulse 1.5s ease-in-out infinite;}
43
46
  @keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
44
47
 
45
48
  .skeleton{background:var(--tag);border-radius:var(--radius-sm);overflow:hidden;position:relative;}
@@ -72,7 +75,7 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
72
75
  .gallery{display:flex;flex-wrap:wrap;gap:10px;}
73
76
  .card{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;width:300px;background:var(--card);display:inline-block;vertical-align:top;box-shadow:var(--shadow-sm);transition:var(--transition);cursor:default;}
74
77
  .card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px);border-color:var(--muted);}
75
- .card-wide{max-width:520px;width:100%;}
78
+ .card-wide{width:100%;}
76
79
  .card img{width:100%;height:200px;object-fit:cover;display:block;transition:transform .3s ease;}
77
80
  .card:hover img{transform:scale(1.02);}
78
81
  .card-wide img{max-height:340px;}
@@ -165,14 +168,30 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
165
168
  <body>
166
169
  <div id="app">
167
170
  <div class="loading-container">
168
- <div class="loading-spinner"></div>
169
- <div class="loading-text">Loading results…</div>
171
+ <div class="orchyn-logo">
172
+ <svg width="40" height="40" viewBox="0 0 48 48" fill="none">
173
+ <g fill="var(--accent)" transform="translate(24 24)">
174
+ <circle r="4.1"/>
175
+ <g id="r"><path d="M-2.85 -5.2 L0 -20.6 L2.85 -5.2 L1.15 1.1 L-1.15 1.1 Z"/></g>
176
+ <use href="#r" transform="rotate(45)"/>
177
+ <use href="#r" transform="rotate(90)"/>
178
+ <use href="#r" transform="rotate(135)"/>
179
+ <use href="#r" transform="rotate(180)"/>
180
+ <use href="#r" transform="rotate(225)"/>
181
+ <use href="#r" transform="rotate(270)"/>
182
+ <use href="#r" transform="rotate(315)"/>
183
+ </g>
184
+ </svg>
185
+ </div>
186
+ <div class="loading-brand">Orchyn</div>
187
+ <div class="loading-text">Analyzing…</div>
170
188
  </div>
171
189
  </div>
172
190
  <script>
173
191
  (function(){
174
192
  var nextId=1;var pending=new Map();
175
193
  var loadingTimer=null;
194
+ var currentTool="";
176
195
 
177
196
  function send(method,params){
178
197
  var id=nextId++;
@@ -197,14 +216,27 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
197
216
  render(d.params);
198
217
  }
199
218
  if(d.method==="ui/notifications/tool-input-partial"){
219
+ currentTool=d.params&&d.params.name?d.params.name:"";
200
220
  updateProgress(d.params);
201
221
  }
202
222
  });
203
223
 
204
- // Show skeleton after 300ms of waiting (feels instant but shows loading for slow tools)
224
+ // Show loading state after 300ms branded spinner until tool name arrives,
225
+ // then switch to the correct skeleton type.
205
226
  loadingTimer=setTimeout(function(){
206
227
  var app=document.getElementById("app");
207
- if(app)app.innerHTML=renderSkeleton();
228
+ if(!app)return;
229
+ if(currentTool){
230
+ // Tool name already known → show correct skeleton
231
+ app.innerHTML=renderSkeleton(currentTool);
232
+ }else{
233
+ // Tool name not yet known → show branded spinner (neutral)
234
+ app.innerHTML='<div class="loading-container fade-in">'
235
+ +'<div class="orchyn-logo"><svg width="40" height="40" viewBox="0 0 48 48" fill="none"><g fill="var(--accent)" transform="translate(24 24)"><circle r="4.1"/><g id="rl"><path d="M-2.85 -5.2 L0 -20.6 L2.85 -5.2 L1.15 1.1 L-1.15 1.1 Z"/></g><use href="#rl" transform="rotate(45)"/><use href="#rl" transform="rotate(90)"/><use href="#rl" transform="rotate(135)"/><use href="#rl" transform="rotate(180)"/><use href="#rl" transform="rotate(225)"/><use href="#rl" transform="rotate(270)"/><use href="#rl" transform="rotate(315)"/></g></svg></div>'
236
+ +'<div class="loading-brand">Orchyn</div>'
237
+ +'<div class="loading-text">Loading…</div>'
238
+ +'</div>';
239
+ }
208
240
  },300);
209
241
 
210
242
  // Fallback: if no tool-result arrives within 3s, check if data is embedded
@@ -228,11 +260,12 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
228
260
  if(decoded&&typeof decoded==="object"){render(decoded);return;}
229
261
  }
230
262
  }catch(e){}
231
- // Last resort: show a minimal card indicating results are available
263
+ // Last resort: show a branded card indicating results are available
232
264
  var app=document.getElementById("app");
233
- if(app)app.innerHTML='<div class="card card-wide fade-in"><div class="card-body">'
234
- +'<div style="font-size:14px;font-weight:600;margin-bottom:8px">✅ Results ready</div>'
235
- +'<div style="font-size:13px;color:var(--muted)">Tool execution complete. See the full response in the chat.</div>'
265
+ if(app)app.innerHTML='<div class="card card-wide fade-in"><div class="card-body" style="text-align:center;padding:24px">'
266
+ +'<svg width="28" height="28" viewBox="0 0 48 48" fill="none" style="margin-bottom:8px"><g fill="var(--accent)" transform="translate(24 24)"><circle r="4.1"/><g id="rf"><path d="M-2.85 -5.2 L0 -20.6 L2.85 -5.2 L1.15 1.1 L-1.15 1.1 Z"/></g><use href="#rf" transform="rotate(45)"/><use href="#rf" transform="rotate(90)"/><use href="#rf" transform="rotate(135)"/><use href="#rf" transform="rotate(180)"/><use href="#rf" transform="rotate(225)"/><use href="#rf" transform="rotate(270)"/><use href="#rf" transform="rotate(315)"/></g></svg>'
267
+ +'<div style="font-size:14px;font-weight:600;margin-bottom:4px">✅ Results ready</div>'
268
+ +'<div style="font-size:12px;color:var(--muted)">Orchyn analysis complete — see the full response in the chat.</div>'
236
269
  +'</div></div>';
237
270
  },3000);
238
271
 
@@ -248,17 +281,85 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
248
281
  // ─── Helpers ───
249
282
  function esc(s){return String(s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");}
250
283
  function fmtNum(n){n=Number(n)||0;return n>=1e6?(n/1e6).toFixed(1)+"M":n>=1e3?(n/1e3).toFixed(1)+"K":String(n);}
251
- function pColor(p){return{tiktok:"#000",instagram:"#E1306C",youtube:"#FF0000",douyin:"#000",xiaohongshu:"#FF2442",twitter:"#1DA1F2",x:"#1DA1F2",bilibili:"#00A1D6"}[p]||"#6B7280";}
252
- function pEmoji(p){return{tiktok:"🎵",instagram:"📸",youtube:"▶️",douyin:"🎶",xiaohongshu:"📕",twitter:"🐦",x:"🐦",bilibili:"📺"}[p]||"🔗";}
284
+ function pColor(p){return{tiktok:"#000",douyin:"#000",instagram:"#E4405F",youtube:"#FF0000",xiaohongshu:"#FF2442",x:"#000",twitter:"#1DA1F2",bilibili:"#00A1D6",linkedin:"#0A66C2"}[p]||"#6B7280";}
285
+ // Official brand mark (simple-icons path) as an inline SVG that inherits
286
+ // the current text color via fill="currentColor" — matches badge accents.
287
+ function pSvg(p,size){
288
+ var s=size||14,paths={
289
+ tiktok:"M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
290
+ instagram:"M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077",
291
+ youtube:"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
292
+ x:"M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
293
+ twitter:"M21.543 7.104c.015.211.015.423.015.636 0 6.507-4.954 14.01-14.01 14.01v-.003A13.94 13.94 0 0 1 0 19.539a9.88 9.88 0 0 0 7.287-2.041 4.93 4.93 0 0 1-4.6-3.42 4.916 4.916 0 0 0 2.223-.084A4.926 4.926 0 0 1 .96 9.167v-.062a4.887 4.887 0 0 0 2.235.616A4.928 4.928 0 0 1 1.67 3.148 13.98 13.98 0 0 0 11.82 8.292a4.929 4.929 0 0 1 8.39-4.49 9.868 9.868 0 0 0 3.128-1.196 4.941 4.941 0 0 1-2.165 2.724A9.828 9.828 0 0 0 24 4.555a10.019 10.019 0 0 1-2.457 2.549z",
294
+ douyin:"M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
295
+ xiaohongshu:"M22.405 9.879c.002.016.01.02.07.019h.725a.797.797 0 0 0 .78-.972.794.794 0 0 0-.884-.618.795.795 0 0 0-.692.794c0 .101-.002.666.001.777zm-11.509 4.808c-.203.001-1.353.004-1.685.003a2.528 2.528 0 0 1-.766-.126.025.025 0 0 0-.03.014L7.7 16.127a.025.025 0 0 0 .01.032c.111.06.336.124.495.124.66.01 1.32.002 1.981 0 .01 0 .02-.006.023-.015l.712-1.545a.025.025 0 0 0-.024-.036zM.477 9.91c-.071 0-.076.002-.076.01a.834.834 0 0 0-.01.08c-.027.397-.038.495-.234 3.06-.012.24-.034.389-.135.607-.026.057-.033.042.003.112.046.092.681 1.523.787 1.74.008.015.011.02.017.02.008 0 .033-.026.047-.044.147-.187.268-.391.371-.606.306-.635.44-1.325.486-1.706.014-.11.021-.22.03-.33l.204-2.616.022-.293c.003-.029 0-.033-.03-.034zm7.203 3.757a1.427 1.427 0 0 1-.135-.607c-.004-.084-.031-.39-.235-3.06a.443.443 0 0 0-.01-.082c-.004-.011-.052-.008-.076-.008h-1.48c-.03.001-.034.005-.03.034l.021.293c.076.982.153 1.964.233 2.946.05.4.186 1.085.487 1.706.103.215.223.419.37.606.015.018.037.051.048.049.02-.003.742-1.642.804-1.765.036-.07.03-.055.003-.112zm3.861-.913h-.872a.126.126 0 0 1-.116-.178l1.178-2.625a.025.025 0 0 0-.023-.035l-1.318-.003a.148.148 0 0 1-.135-.21l.876-1.954a.025.025 0 0 0-.023-.035h-1.56c-.01 0-.02.006-.024.015l-.926 2.068c-.085.169-.314.634-.399.938a.534.534 0 0 0-.02.191.46.46 0 0 0 .23.378.981.981 0 0 0 .46.119h.59c.041 0-.688 1.482-.834 1.972a.53.53 0 0 0-.023.172.465.465 0 0 0 .23.398c.15.092.342.12.475.12l1.66-.001c.01 0 .02-.006.023-.015l.575-1.28a.025.025 0 0 0-.024-.035zm-6.93-4.937H3.1a.032.032 0 0 0-.034.033c0 1.048-.01 2.795-.01 6.829 0 .288-.269.262-.28.262h-.74c-.04.001-.044.004-.04.047.001.037.465 1.064.555 1.263.01.02.03.033.051.033.157.003.767.009.938-.014.153-.02.3-.06.438-.132.3-.156.49-.419.595-.765.052-.172.075-.353.075-.533.002-2.33 0-4.66-.007-6.991a.032.032 0 0 0-.032-.032zm11.784 6.896c0-.014-.01-.021-.024-.022h-1.465c-.048-.001-.049-.002-.05-.049v-4.66c0-.072-.005-.07.07-.07h.863c.08 0 .075.004.075-.074V8.393c0-.082.006-.076-.08-.076h-3.5c-.064 0-.075-.006-.075.073v1.445c0 .083-.006.077.08.077h.854c.075 0 .07-.004.07.07v4.624c0 .095.008.084-.085.084-.37 0-1.11-.002-1.304 0-.048.001-.06.03-.06.03l-.697 1.519s-.014.025-.008.036c.006.01.013.008.058.008 1.748.003 3.495.002 5.243.002.03-.001.034-.006.035-.033v-1.539zm4.177-3.43c0 .013-.007.023-.02.024-.346.006-.692.004-1.037.004-.014-.002-.022-.01-.022-.024-.005-.434-.007-.869-.01-1.303 0-.072-.006-.071.07-.07l.733-.003c.041 0 .081.002.12.015.093.025.16.107.165.204.006.431.002 1.153.001 1.153zm2.67.244a1.953 1.953 0 0 0-.883-.222h-.18c-.04-.001-.04-.003-.042-.04V10.21c0-.132-.007-.263-.025-.394a1.823 1.823 0 0 0-.153-.53 1.533 1.533 0 0 0-.677-.71 2.167 2.167 0 0 0-1-.258c-.153-.003-.567 0-.72 0-.07 0-.068.004-.068-.065V7.76c0-.031-.01-.041-.046-.039H17.93s-.016 0-.023.007c-.006.006-.008.012-.008.023v.546c-.008.036-.057.015-.082.022h-.95c-.022.002-.028.008-.03.032v1.481c0 .09-.004.082.082.082h.913c.082 0 .072.128.072.128V11.19s.003.117-.06.117h-1.482c-.068 0-.06.082-.06.082v1.445s-.01.068.064.068h1.457c.082 0 .076-.006.076.079v3.225c0 .088-.007.081.082.081h1.43c.09 0 .082.007.082-.08v-3.27c0-.029.006-.035.033-.035l2.323-.003c.098 0 .191.02.28.061a.46.46 0 0 1 .274.407c.008.395.003.79.003 1.185 0 .259-.107.367-.33.367h-1.218c-.023.002-.029.008-.028.033.184.437.374.871.57 1.303a.045.045 0 0 0 .04.026c.17.005.34.002.51.003.15-.002.517.004.666-.01a2.03 2.03 0 0 0 .408-.075c.59-.18.975-.698.976-1.313v-1.981c0-.128-.01-.254-.034-.38 0 .078-.029-.641-.724-.998z",
296
+ bilibili:"M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373Z",
297
+ linkedin:"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
298
+ unknown:"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
299
+ };
300
+ var d=paths[p]||paths.unknown;
301
+ return '<svg viewBox="0 0 24 24" width="'+s+'" height="'+s+'" fill="currentColor" style="vertical-align:-2px;flex-shrink:0;display:inline-block" aria-hidden="true"><path d="'+d+'"/></svg>';
302
+ }
253
303
 
254
304
  function copyText(text){
255
305
  navigator.clipboard.writeText(text).catch(function(){});
256
306
  }
257
307
 
258
308
  // ─── Skeleton ───
259
- function renderSkeleton(){
260
- return '<div class="skeleton-gallery stagger">'
261
- +Array(3).fill(0).map(function(){
309
+ // Tools that analyze/fetch a single URL: show 1 wide card skeleton
310
+ var SINGLE_URL_TOOLS={analyze_post:1,get_social_media:1,understand_social_post:1,get_post_comments:1,analyze_creator_profile:1};
311
+ // Tools that return galleries: show multiple small card skeletons
312
+ var GALLERY_TOOLS={discover_social_posts:1,get_user_posts:1,search_creators:1,get_similar_creators:1,discover_sounds:1};
313
+ // Tools that return credits/checkout: show 1 credit card skeleton
314
+ var CREDIT_TOOLS={check_orchyn_credits:1,buy_orchyn_credits:1};
315
+
316
+ function renderSkeleton(toolName){
317
+ // Branded header above skeletons
318
+ var brandHeader='<div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;justify-content:center">'
319
+ +'<svg width="20" height="20" viewBox="0 0 48 48" fill="none"><g fill="var(--accent)" transform="translate(24 24)"><circle r="4.1"/><g id="sb"><path d="M-2.85 -5.2 L0 -20.6 L2.85 -5.2 L1.15 1.1 L-1.15 1.1 Z"/></g><use href="#sb" transform="rotate(45)"/><use href="#sb" transform="rotate(90)"/><use href="#sb" transform="rotate(135)"/><use href="#sb" transform="rotate(180)"/><use href="#sb" transform="rotate(225)"/><use href="#sb" transform="rotate(270)"/><use href="#sb" transform="rotate(315)"/></g></svg>'
320
+ +'<span style="font-size:13px;font-weight:600;color:var(--muted)">Orchyn</span></div>';
321
+ // Single URL tools → 1 wide card skeleton
322
+ if(SINGLE_URL_TOOLS[toolName]){
323
+ return brandHeader+'<div class="fade-in">'
324
+ +'<div class="skeleton-card" style="width:100%">'
325
+ +'<div class="skeleton skeleton-thumb" style="height:220px"></div>'
326
+ +'<div style="padding:14px 16px">'
327
+ +'<div class="skeleton skeleton-badge"></div>'
328
+ +'<div class="skeleton skeleton-line" style="width:90%"></div>'
329
+ +'<div class="skeleton skeleton-line medium"></div>'
330
+ +'<div style="display:flex;gap:6px;margin-top:10px">'
331
+ +'<div class="skeleton" style="width:70px;height:28px;border-radius:999px"></div>'
332
+ +'<div class="skeleton" style="width:60px;height:28px;border-radius:999px"></div>'
333
+ +'<div class="skeleton" style="width:65px;height:28px;border-radius:999px"></div>'
334
+ +'</div></div></div></div>';
335
+ }
336
+ // Credit tools → credit card skeleton
337
+ if(CREDIT_TOOLS[toolName]){
338
+ return brandHeader+'<div class="fade-in" style="max-width:380px">'
339
+ +'<div class="credits-card" style="margin:0">'
340
+ +'<div class="skeleton" style="width:120px;height:20px;margin-bottom:14px"></div>'
341
+ +'<div class="skeleton" style="width:80px;height:40px;margin-bottom:8px"></div>'
342
+ +'<div class="skeleton" style="width:140px;height:14px"></div>'
343
+ +'</div></div>';
344
+ }
345
+ // Gallery tools → multiple small card skeletons
346
+ // Unknown tool → default to 1 wide card (most common case: single URL tools)
347
+ if(!toolName||SINGLE_URL_TOOLS[toolName]||CREDIT_TOOLS[toolName]){
348
+ // Already handled above, but catch unknown tools with single card
349
+ if(!toolName){
350
+ return brandHeader+'<div class="fade-in" style="max-width:520px">'
351
+ +'<div class="skeleton-card" style="width:100%">'
352
+ +'<div class="skeleton skeleton-thumb" style="height:220px"></div>'
353
+ +'<div style="padding:14px 16px">'
354
+ +'<div class="skeleton skeleton-badge"></div>'
355
+ +'<div class="skeleton skeleton-line" style="width:90%"></div>'
356
+ +'<div class="skeleton skeleton-line medium"></div>'
357
+ +'</div></div></div>';
358
+ }
359
+ }
360
+ var count=toolName==='discover_sounds'?4:toolName==='search_creators'||toolName==='get_similar_creators'?3:6;
361
+ return brandHeader+'<div class="skeleton-gallery stagger">'
362
+ +Array(count).fill(0).map(function(){
262
363
  return '<div class="skeleton-card">'
263
364
  +'<div class="skeleton skeleton-thumb"></div>'
264
365
  +'<div class="skeleton skeleton-badge"></div>'
@@ -270,7 +371,7 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
270
371
 
271
372
  // ─── Post Card ───
272
373
  function postCard(p,wide){
273
- var platform=p.platform||"unknown",color=pColor(platform),emoji=pEmoji(platform);
374
+ var platform=p.platform||"unknown",color=pColor(platform),brandSvg=pSvg(platform);
274
375
  var title=p.title||p.caption||"",handle=p.creatorHandle||"",url=p.externalUrl||"";
275
376
  var thumb=p.thumbnailUrl||"",ct=p.contentType||"post";
276
377
  var views=p.views||0,likes=p.likes||0,comments=p.comments||0,shares=p.shares||0;
@@ -280,11 +381,42 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
280
381
  .map(function(s){return'<span class="stat-pill">'+s[1]+" "+fmtNum(s[0])+"</span>";}).join("");
281
382
  var handleHtml=handle?'<span class="handle">@'+esc(handle)+"</span>":"";
282
383
  var linkHtml=url?'<a href="'+esc(url)+'" target="_blank" class="btn" style="background:'+color+'">View on '+esc(platform)+' →</a>':"";
283
- var thumbHtml=thumb?'<img src="'+esc(thumb)+'" alt="thumbnail" loading="lazy"/>':"";
384
+ // Prefer video playback over a static thumbnail when the post has one
385
+ // (videoUrl, or a video mediaItem preview_url). Videos are proxied /
386
+ // re-hosted to a permanent orchyn URL so they play inside the CSP.
387
+ var video="",images=[];
388
+ if(typeof p.videoUrl==="string"&&p.videoUrl)video=p.videoUrl;
389
+ if(p.mediaItems&&p.mediaItems.length){
390
+ for(var i=0;i<p.mediaItems.length;i++){
391
+ var m=p.mediaItems[i];
392
+ if(m.kind==="video"&&m.preview_url){if(!video)video=m.preview_url;}
393
+ else if(m.kind==="image"&&m.preview_url&&images.length<8)images.push(m.preview_url);
394
+ }
395
+ }
396
+ var mediaHtml="";
397
+ var vh=wide?"340px":"200px";
398
+ var bodyText=p.caption||p.text||"";
399
+ if(video){
400
+ mediaHtml='<video controls preload="metadata" playsinline poster="'+esc(thumb)+'" style="width:100%;height:'+vh+';object-fit:cover;display:block;background:#000">'
401
+ +'Your browser doesn\'t support video playback.</video>';
402
+ }else if(images.length>1){
403
+ // Carousel / multi-image gallery — horizontally scrollable strip.
404
+ var strip=images.map(function(u,i){
405
+ return '<img src="'+esc(u)+'" alt="slide '+(i+1)+'" loading="lazy" style="height:'+vh+';max-width:85%;object-fit:cover;border-radius:6px;flex-shrink:0;display:block"/>';
406
+ }).join("");
407
+ mediaHtml='<div style="display:flex;gap:6px;overflow-x:auto;padding:8px;background:var(--border);border-bottom:1px solid var(--border)">'+strip+'</div>'
408
+ +'<div style="display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:11px;color:var(--muted);font-weight:600;border-bottom:1px solid var(--border)">🖼 '+images.length+' images</div>';
409
+ }else if(thumb){
410
+ mediaHtml='<img src="'+esc(thumb)+'" alt="thumbnail" loading="lazy"/>';
411
+ }else if(bodyText){
412
+ // Text-only post (LinkedIn / X) — styled quote block.
413
+ mediaHtml='<div style="padding:16px 18px;border-bottom:1px solid var(--border);background:var(--card)">'
414
+ +'<div style="font-size:13px;line-height:1.55;color:var(--fg);white-space:pre-line">'+esc(bodyText.length>400?bodyText.slice(0,400)+"…":bodyText)+'</div></div>';
415
+ }
284
416
  var ctTag=wide?'<span style="font-size:12px;color:var(--muted);text-transform:capitalize">'+esc(ct)+"</span>":"";
285
- return '<div class="'+cls+'">'+thumbHtml+'<div class="card-body">'
417
+ return '<div class="'+cls+'">'+mediaHtml+'<div class="card-body">'
286
418
  +'<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">'
287
- +'<span class="badge" style="background:'+color+'15;color:'+color+'">'+emoji+" "+esc(platform)+"</span>"
419
+ +'<span class="badge" style="background:'+color+'15;color:'+color+';display:inline-flex;align-items:center;gap:4px">'+brandSvg+" "+esc(platform)+"</span>"
288
420
  +ctTag+handleHtml+"</div>"
289
421
  +'<div class="title">'+esc(title.length>160?title.slice(0,160)+"…":title)+"</div>"
290
422
  +'<div class="stats">'+statsHtml+"</div>"
@@ -293,13 +425,13 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
293
425
 
294
426
  // ─── Creator Card ───
295
427
  function creatorCard(c){
296
- var platform=c.platform||"tiktok",color=pColor(platform),emoji=pEmoji(platform);
428
+ var platform=c.platform||"tiktok",color=pColor(platform),brandSvg=pSvg(platform,22);
297
429
  var username=c.username||c.uniqueId||"",nickname=c.nickname||c.displayName||username;
298
430
  var followers=c.followers||c.followerCount||0,sig=(c.signature||c.bio||"").slice(0,100);
299
431
  var verified=c.verified||false,avatar=c.avatarUrl||c.avatar_thumb||"";
300
432
  var avatarHtml=avatar
301
433
  ?'<img class="avatar" src="'+esc(avatar)+'" alt="avatar" loading="lazy"/>'
302
- :'<div class="avatar-placeholder" style="background:'+color+'20">'+emoji+"</div>";
434
+ :'<div class="avatar-placeholder" style="background:'+color+'20">'+brandSvg+"</div>";
303
435
  var vBadge=verified?'<span class="verified" style="background:'+color+'">✓</span>':"";
304
436
  return '<div class="creator-card">'
305
437
  +'<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">'
@@ -436,12 +568,19 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
436
568
  function updateProgress(params){
437
569
  var app=document.getElementById("app");
438
570
  if(!app)return;
439
- var text=params&&params.arguments?JSON.stringify(params.arguments):"Processing…";
440
- app.innerHTML='<div class="loading-container fade-in">'
441
- +'<div class="loading-spinner"></div>'
442
- +'<div class="loading-text">'+esc(text.length>80?text.slice(0,80)+"…":text)+'</div>'
443
- +'<div class="progress-bar" style="width:200px"><div class="progress-fill" style="width:60%"></div></div>'
444
- +'</div>';
571
+ // Learn the tool name
572
+ var toolName=params&&params.name?params.name:currentTool;
573
+ if(toolName&&toolName!==currentTool){
574
+ currentTool=toolName;
575
+ // Switch from spinner to correct skeleton if still loading
576
+ if(app.querySelector(".loading-spinner")||app.querySelector(".loading-container")||app.querySelector(".skeleton")){
577
+ app.innerHTML=renderSkeleton(currentTool);
578
+ }
579
+ }
580
+ // Add a progress bar below the existing content if not already present
581
+ if(!app.querySelector(".progress-bar")){
582
+ app.innerHTML=app.innerHTML+'<div style="text-align:center;margin-top:12px"><div class="progress-bar" style="width:200px;margin:0 auto"><div class="progress-fill" style="width:60%"></div></div></div>';
583
+ }
445
584
  }
446
585
 
447
586
  // Global helpers
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orchyn/mcp",
3
- "version": "1.12.2",
4
- "description": "MCP server for orchyn - fetch, discover and understand TikTok/Instagram/YouTube/X posts with AI (media metadata, niche discovery, hook & viral analysis)",
3
+ "version": "1.13.0",
4
+ "description": "MCP server for orchyn - fetch, discover and understand TikTok/Instagram/YouTube/X/LinkedIn posts with AI (media metadata, niche discovery, hook & viral analysis)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "files": [