🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@shadowob/shared

Package Overview
Dependencies
Maintainers
1
Versions
77
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@shadowob/shared - npm Package Compare versions

Comparing version
1.1.1
to
1.1.3
+58
dist/chunk-DMUZB4WV.js
// src/constants/events.ts
var CLIENT_EVENTS = {
CHANNEL_JOIN: "channel:join",
CHANNEL_LEAVE: "channel:leave",
MESSAGE_SEND: "message:send",
MESSAGE_TYPING: "message:typing",
PRESENCE_UPDATE: "presence:update"
};
var SERVER_EVENTS = {
MESSAGE_NEW: "message:new",
MESSAGE_UPDATE: "message:update",
MESSAGE_DELETE: "message:delete",
MEMBER_TYPING: "member:typing",
MEMBER_JOIN: "member:join",
MEMBER_LEAVE: "member:leave",
PRESENCE_CHANGE: "presence:change",
REACTION_ADD: "reaction:add",
REACTION_REMOVE: "reaction:remove",
NOTIFICATION_NEW: "notification:new"
};
// src/constants/limits.ts
var LIMITS = {
/** Max message content length (16KB — enough for detailed agent responses) */
MESSAGE_CONTENT_MAX: 16e3,
/** Max username length */
USERNAME_MAX: 32,
/** Min username length */
USERNAME_MIN: 3,
/** Max display name length */
DISPLAY_NAME_MAX: 64,
/** Max server name length */
SERVER_NAME_MAX: 100,
/** Max channel name length */
CHANNEL_NAME_MAX: 100,
/** Max thread name length */
THREAD_NAME_MAX: 100,
/** Max file upload size (10MB) */
FILE_UPLOAD_MAX_SIZE: 10 * 1024 * 1024,
/** Messages per page (cursor pagination) */
MESSAGES_PER_PAGE: 50,
/** Max servers per user */
SERVERS_PER_USER_MAX: 100,
/** Max channels per server */
CHANNELS_PER_SERVER_MAX: 200,
/** Invite code length */
INVITE_CODE_LENGTH: 8,
/** Password min length */
PASSWORD_MIN: 8,
/** Max reactions per message per user */
REACTIONS_PER_MESSAGE_MAX: 20
};
export {
CLIENT_EVENTS,
SERVER_EVENTS,
LIMITS
};
// src/utils/index.ts
import { customAlphabet } from "nanoid";
// src/utils/avatar-generator.ts
var COLORS = {
bg: [
"transparent",
"#1e1f22",
"#313338",
"#5865F2",
"#23a559",
"#da373c",
"#f472b6",
"#3b82f6",
"#fbbf24",
"#a855f7",
"#1abc9c",
"#f39c12",
"#e74c3c"
],
body: [
"#2d2d30",
"#e8842c",
"#e8e8e8",
"#7a7a80",
"#d4a574",
"#6b8094",
"#f472b6",
"#c8d6e5",
"#3e2723",
"#bdc3c7",
"#ffb8b8"
],
eyes: [
"#f8e71c",
"#00f3ff",
"#4ade80",
"#60a5fa",
"#a855f7",
"#fbbf24",
"#f87171",
"#ffc0cb",
"#1dd1a1",
"#e056fd"
],
pattern: ["#1a1a1c", "#ffffff", "#5a4a46", "#3d3d40", "#9a9aa0", "#d1ccc0", "#2d3436"]
};
function getRandomElement(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function generateRandomCatConfig() {
return {
bg: getRandomElement(COLORS.bg),
bgPattern: getRandomElement(["none", "dots", "stripes", "grid", "stars"]),
body: getRandomElement(COLORS.body),
pattern: getRandomElement([
"none",
"tabby",
"tuxedo",
"siamese",
"calico",
"bicolor"
]),
patternColor: getRandomElement(COLORS.pattern),
eyeColor: getRandomElement(COLORS.eyes),
expression: getRandomElement([
"smile",
"open",
"flat",
"sad",
"surprised",
"kawaii",
"winking",
"smirk"
]),
decoration: getRandomElement([
"none",
"glasses",
"blush",
"scar",
"flower",
"fish",
"headband"
])
};
}
function renderCatSvg(config) {
const { bg, bgPattern, body, pattern, patternColor, eyeColor, expression, decoration } = config;
const stroke = "#1a1a1c";
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">`;
if (bg && bg !== "transparent") {
svg += `<rect width="100" height="100" fill="${bg}" rx="20" />`;
const pColor = `rgba(255,255,255,0.15)`;
const cleanBg = bg.replace("#", "");
if (bgPattern === "dots") {
svg += `<pattern id="p-${cleanBg}-dots" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="2" fill="${pColor}"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-dots)" rx="20" />`;
} else if (bgPattern === "stripes") {
svg += `<pattern id="p-${cleanBg}-str" x="0" y="0" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="12" stroke="${pColor}" stroke-width="4"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-str)" rx="20" />`;
} else if (bgPattern === "grid") {
svg += `<pattern id="p-${cleanBg}-grid" width="16" height="16" patternUnits="userSpaceOnUse"><path d="M 16 0 L 0 0 0 16" fill="none" stroke="${pColor}" stroke-width="1"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-grid)" rx="20" />`;
} else if (bgPattern === "stars") {
svg += `<pattern id="p-${cleanBg}-star" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M10,2 L12,8 L18,8 L13,12 L15,18 L10,14 L5,18 L7,12 L2,8 L8,8 Z" fill="${pColor}" transform="scale(0.5) translate(5,5)"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-star)" rx="20" />`;
}
}
svg += `<ellipse cx="50" cy="85" rx="30" ry="6" fill="rgba(0,0,0,0.2)"/>`;
svg += `<path d="M22,45 C15,22 28,18 34,22 C38,25 40,38 40,38" fill="${body}" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>`;
svg += `<path d="M78,45 C85,22 72,18 66,22 C62,25 60,38 60,38" fill="${body}" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>`;
svg += `<path d="M26,38 C22,26 29,24 33,26 C35,28 36,34 36,34" fill="#ffb8b8" opacity="0.8"/>`;
svg += `<path d="M74,38 C78,26 71,24 67,26 C65,28 64,34 64,34" fill="#ffb8b8" opacity="0.8"/>`;
svg += `<ellipse cx="50" cy="58" rx="38" ry="30" fill="${body}" stroke="${stroke}" stroke-width="2.5"/>`;
if (pattern === "tabby") {
svg += `<path d="M50,30 L50,40 M42,32 L46,39 M58,32 L54,39" stroke="${patternColor}" stroke-width="3" stroke-linecap="round" opacity="0.7"/>`;
svg += `<path d="M18,55 L26,57 M20,62 L27,62" stroke="${patternColor}" stroke-width="2.5" stroke-linecap="round" opacity="0.7"/>`;
svg += `<path d="M82,55 L74,57 M80,62 L73,62" stroke="${patternColor}" stroke-width="2.5" stroke-linecap="round" opacity="0.7"/>`;
} else if (pattern === "tuxedo") {
svg += `<path d="M50,56 C30,68 28,88 50,88 C72,88 70,68 50,56" fill="${patternColor}" opacity="0.95"/>`;
svg += `<ellipse cx="50" cy="65" rx="16" ry="12" fill="${patternColor}" opacity="0.95"/>`;
} else if (pattern === "siamese") {
svg += `<ellipse cx="50" cy="62" rx="20" ry="16" fill="${patternColor}" opacity="0.6"/>`;
} else if (pattern === "calico") {
svg += `<path d="M25,40 Q35,30 45,45 Q35,55 25,40" fill="${patternColor}" opacity="0.8"/>`;
svg += `<path d="M75,45 Q65,60 55,50 Q65,35 75,45" fill="#e8842c" opacity="0.8"/>`;
} else if (pattern === "bicolor") {
svg += `<path d="M12,58 Q30,30 50,40 Q60,70 50,88 Q12,88 12,58 Z" fill="${patternColor}" opacity="0.8"/>`;
}
if (decoration === "blush") {
svg += `<ellipse cx="28" cy="62" rx="5" ry="3" fill="#ff7675" opacity="0.7"/>`;
svg += `<ellipse cx="72" cy="62" rx="5" ry="3" fill="#ff7675" opacity="0.7"/>`;
}
if (decoration === "scar") {
svg += `<path d="M28,42 L40,52 M30,48 L35,43 M34,51 L39,46 M32,53 L37,48" stroke="#d63031" stroke-width="1.5" stroke-linecap="round"/>`;
}
const drawEye = (cx, cy, lookDir = 0) => {
if (expression === "kawaii") {
return `<path d="M${cx - 8},${cy} Q${cx},${cy - 8} ${cx + 8},${cy} Q${cx},${cy - 3} ${cx - 8},${cy}" fill="${eyeColor}" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx + lookDir}" cy="${cy - 2}" r="3" fill="white"/><circle cx="${cx - 3 + lookDir}" cy="${cy}" r="1" fill="white"/>`;
} else if (expression === "winking" && cx > 50) {
return `<path d="M${cx - 7},${cy + 2} Q${cx},${cy - 4} ${cx + 7},${cy + 2}" fill="none" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round"/>`;
} else if (expression === "surprised") {
return `<circle cx="${cx}" cy="${cy}" r="8" fill="white" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx}" cy="${cy}" r="3" fill="${eyeColor}"/>`;
} else {
return `<circle cx="${cx}" cy="${cy}" r="7.5" fill="${eyeColor}" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx - 2 + lookDir}" cy="${cy - 2}" r="2.5" fill="white"/><circle cx="${cx + 2 + lookDir}" cy="${cy + 1}" r="1" fill="white"/>`;
}
};
if (expression === "smirk") {
svg += `<path d="M26,50 L42,50" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round"/>`;
svg += drawEye(66, 52, 2);
} else {
svg += drawEye(34, 52, 0);
svg += drawEye(66, 52, 0);
}
if (decoration === "glasses") {
svg += `<circle cx="34" cy="52" r="11" fill="rgba(255,255,255,0.2)" stroke="#2d3436" stroke-width="2.5"/>`;
svg += `<circle cx="66" cy="52" r="11" fill="rgba(255,255,255,0.2)" stroke="#2d3436" stroke-width="2.5"/>`;
svg += `<path d="M45,50 Q50,48 55,50" fill="none" stroke="#2d3436" stroke-width="2.5" stroke-linecap="round"/>`;
}
svg += `<path d="M47,62 L53,62 L50,65 Z" fill="#ff9ff3" stroke="${stroke}" stroke-width="1" stroke-linejoin="round"/>`;
if (expression === "smile" || expression === "kawaii" || expression === "winking") {
svg += `<path d="M42,67 Q46,72 50,67 M50,67 Q54,72 58,67" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
} else if (expression === "open") {
svg += `<path d="M46,67 Q50,75 54,67 Z" fill="#ff7675" stroke="${stroke}" stroke-width="1.5" stroke-linejoin="round"/>`;
} else if (expression === "flat") {
svg += `<path d="M47,68 L53,68" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
} else if (expression === "sad") {
svg += `<path d="M43,70 Q50,64 57,70" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
} else if (expression === "surprised") {
svg += `<circle cx="50" cy="70" r="3" fill="#1a1a1c"/>`;
} else if (expression === "smirk") {
svg += `<path d="M46,67 Q52,69 56,64" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
}
svg += `<path d="M28,62 L15,60 M28,65 L14,66" stroke="${stroke}" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>`;
svg += `<path d="M72,62 L85,60 M72,65 L86,66" stroke="${stroke}" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>`;
if (decoration === "headband") {
svg += `<path d="M16,35 Q50,20 84,35" fill="none" stroke="#e17055" stroke-width="6" stroke-linecap="round"/>`;
svg += `<path d="M50,15 L60,25 L50,28 L40,25 Z" fill="#fab1a0" stroke="#e17055" stroke-width="2"/>`;
} else if (decoration === "flower") {
svg += `<path d="M75,30 Q80,20 85,30 Q95,25 85,35 Q90,45 80,40 Q70,45 75,35 Q65,25 75,30 Z" fill="#ffeaa7" stroke="#fdcb6e" stroke-width="1.5"/>`;
svg += `<circle cx="80" cy="33" r="3" fill="#d63031"/>`;
} else if (decoration === "fish") {
svg += `<path d="M40,82 Q50,75 60,82 L65,78 L65,86 L60,82 Q50,89 40,82 Z" fill="#81ecec" stroke="${stroke}" stroke-width="1.5"/>`;
svg += `<circle cx="45" cy="81" r="1" fill="${stroke}"/>`;
}
svg += `</svg>`;
return `data:image/svg+xml,${encodeURIComponent(svg.replace(/\n\s*/g, ""))}`;
}
// src/utils/message-mentions.ts
function uniqueValues(values) {
return Array.from(new Set(values.filter((value) => !!value)));
}
function canonicalMentionToken(mention) {
if (mention.kind === "channel") return `<#${mention.channelId ?? mention.targetId}>`;
if (mention.kind === "server") return `<@server:${mention.serverId ?? mention.targetId}>`;
if (mention.kind === "here" || mention.kind === "everyone") {
const scope = mention.serverId ?? mention.targetId;
return scope ? `<!${mention.kind}:${scope}>` : `<!${mention.kind}>`;
}
return `<@${mention.userId ?? mention.targetId}>`;
}
function parseCanonicalMentionToken(token) {
const user = token.match(/^<@([^>:]+)>$/u);
if (user?.[1]) return { kind: "user", targetId: user[1] };
const server = token.match(/^<@server:([^>]+)>$/u);
if (server?.[1]) return { kind: "server", targetId: server[1] };
const channel = token.match(/^<#([^>]+)>$/u);
if (channel?.[1]) return { kind: "channel", targetId: channel[1] };
const broadcast = token.match(/^<!(here|everyone)(?::([^>]+))?>$/u);
if (broadcast?.[1] === "here" || broadcast?.[1] === "everyone") {
return {
kind: broadcast[1],
...broadcast[2] ? { targetId: broadcast[2] } : {}
};
}
return null;
}
function isCanonicalMentionToken(token) {
return parseCanonicalMentionToken(token) !== null;
}
function matchTextsForMention(mention) {
return uniqueValues([mention.token, mention.sourceToken, canonicalMentionToken(mention)]);
}
function isValidRange(content, mention, range) {
if (!range) return false;
if (!Number.isInteger(range.start) || !Number.isInteger(range.end)) return false;
if (range.start < 0 || range.end <= range.start || range.end > content.length) return false;
const sourceText = content.slice(range.start, range.end);
return matchTextsForMention(mention).includes(sourceText);
}
function overlaps(a, b) {
return a.start < b.end && b.start < a.end;
}
function canUseRange(range, used) {
return !used.some((candidate) => overlaps(candidate, range));
}
function findOccurrences(content, token, used) {
const occurrences = [];
let index = content.indexOf(token);
while (index >= 0) {
const range = { start: index, end: index + token.length };
if (canUseRange(range, used)) occurrences.push(range);
index = content.indexOf(token, index + token.length);
}
return occurrences;
}
function addCandidate(candidates, used, mention, range, order, sourceText) {
if (!canUseRange(range, used)) return;
const matchedText = sourceText ?? mention.sourceToken ?? mention.token;
used.push(range);
candidates.push({
start: range.start,
end: range.end,
order,
sourceText: matchedText,
mention: { ...mention, range }
});
}
function segmentTextByMentions(content, mentions) {
if (!content) return [];
const usableMentions = (mentions ?? []).filter((mention) => mention.token);
if (usableMentions.length === 0) return [{ type: "text", text: content }];
const candidates = [];
const usedRanges = [];
const pendingByText = /* @__PURE__ */ new Map();
usableMentions.forEach((mention, order) => {
if (isValidRange(content, mention, mention.range)) {
addCandidate(
candidates,
usedRanges,
mention,
mention.range,
order,
content.slice(mention.range.start, mention.range.end)
);
return;
}
for (const text of matchTextsForMention(mention)) {
const pending = pendingByText.get(text) ?? [];
pending.push({ mention, order });
pendingByText.set(text, pending);
}
});
const pendingGroups = Array.from(pendingByText.entries()).sort((a, b) => {
const lengthDelta = b[0].length - a[0].length;
if (lengthDelta !== 0) return lengthDelta;
return a[1][0].order - b[1][0].order;
});
for (const [token, pending] of pendingGroups) {
const occurrences = findOccurrences(content, token, usedRanges);
if (occurrences.length === 0) continue;
if (pending.length === 1) {
const { mention, order } = pending[0];
for (const range of occurrences) {
addCandidate(candidates, usedRanges, mention, range, order, token);
}
continue;
}
pending.forEach(({ mention, order }, index) => {
const range = occurrences[index];
if (range) addCandidate(candidates, usedRanges, mention, range, order, token);
});
}
candidates.sort((a, b) => a.start - b.start || b.end - a.end || a.order - b.order);
const segments = [];
let cursor = 0;
for (const candidate of candidates) {
if (candidate.start < cursor) continue;
if (candidate.start > cursor) {
segments.push({ type: "text", text: content.slice(cursor, candidate.start) });
}
const text = content.slice(candidate.start, candidate.end);
segments.push({
type: "mention",
text,
range: { start: candidate.start, end: candidate.end },
mention: { ...candidate.mention, sourceToken: candidate.sourceText }
});
cursor = candidate.end;
}
if (cursor < content.length) {
segments.push({ type: "text", text: content.slice(cursor) });
}
return segments.length > 0 ? segments : [{ type: "text", text: content }];
}
function assignMentionRanges(content, mentions) {
return segmentTextByMentions(content, mentions).filter((segment) => {
return segment.type === "mention";
}).map((segment) => ({
...segment.mention,
token: segment.mention.token || segment.text,
range: segment.range
}));
}
function canonicalizeMentionContent(content, mentions) {
const canonicalMentions = [];
let nextContent = "";
for (const segment of segmentTextByMentions(content, mentions)) {
if (segment.type === "text") {
nextContent += segment.text;
continue;
}
const token = canonicalMentionToken(segment.mention);
const start = nextContent.length;
nextContent += token;
canonicalMentions.push({
...segment.mention,
token,
sourceToken: segment.text === token ? segment.mention.sourceToken : segment.text,
range: { start, end: start + token.length }
});
}
return { content: nextContent, mentions: canonicalMentions };
}
function mentionDisplayText(mention) {
return mention.label || mention.token;
}
function escapeMarkdownLinkLabel(label) {
return label.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
}
function buildMentionMarkdownLinks(content, mentions, hrefForMention) {
const linkedMentions = [];
const markdown = segmentTextByMentions(content, mentions).map((segment) => {
if (segment.type === "text") return segment.text;
const href = hrefForMention(segment.mention, linkedMentions.length);
if (!href) return segment.text;
linkedMentions.push(segment.mention);
return `[${escapeMarkdownLinkLabel(mentionDisplayText(segment.mention))}](${href})`;
}).join("");
return { markdown, mentions: linkedMentions };
}
// src/utils/pixel-cats.ts
var variants = [
// 0: Shadow — Black cat (logo style)
{
body: "#2d2d30",
stroke: "#1a1a1c",
earInner: "#3d3d40",
eyeL: "#f8e71c",
eyeR: "#00f3ff",
nose: "#3a2a26"
},
// 1: Mikan — Orange tabby
{
body: "#e8842c",
stroke: "#1a1a1c",
earInner: "#f5a623",
eyeL: "#4ade80",
eyeR: "#4ade80",
nose: "#d46b1a"
},
// 2: Yuki — White cat
{
body: "#e8e8e8",
stroke: "#a0a0a0",
earInner: "#ffc0cb",
eyeL: "#60a5fa",
eyeR: "#60a5fa",
nose: "#f5a0b0"
},
// 3: Haiiro — Gray cat
{
body: "#7a7a80",
stroke: "#4a4a50",
earInner: "#9a9aa0",
eyeL: "#fbbf24",
eyeR: "#fbbf24",
nose: "#5a4a46"
},
// 4: Tuxedo — Black & white accents
{
body: "#2d2d30",
stroke: "#1a1a1c",
earInner: "#e0e0e0",
eyeL: "#22c55e",
eyeR: "#22c55e",
nose: "#3a2a26"
},
// 5: Mocha — Cream/beige
{
body: "#d4a574",
stroke: "#8b6914",
earInner: "#e8c9a0",
eyeL: "#d97706",
eyeR: "#d97706",
nose: "#a0705a"
},
// 6: Blue — Russian blue
{
body: "#6b8094",
stroke: "#3d5060",
earInner: "#8ba0b4",
eyeL: "#22c55e",
eyeR: "#22c55e",
nose: "#5a6a76"
},
// 7: Sakura — Fantasy pink
{
body: "#f472b6",
stroke: "#be185d",
earInner: "#f9a8d4",
eyeL: "#a855f7",
eyeR: "#c084fc",
nose: "#d44a8c"
}
];
function makeCatSvg(c) {
return [
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">',
`<path d="M22,45 Q15,22 28,22 Q34,22 40,38" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5" stroke-linecap="round"/>`,
`<path d="M78,45 Q85,22 72,22 Q66,22 60,38" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5" stroke-linecap="round"/>`,
`<path d="M26,38 Q22,26 29,26 Q33,26 36,34" fill="${c.earInner}" opacity="0.5"/>`,
`<path d="M74,38 Q78,26 71,26 Q67,26 64,34" fill="${c.earInner}" opacity="0.5"/>`,
`<ellipse cx="50" cy="58" rx="36" ry="28" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5"/>`,
`<circle cx="35" cy="52" r="7" fill="${c.eyeL}" stroke="${c.stroke}" stroke-width="1.5"/>`,
`<circle cx="33" cy="49" r="2.5" fill="white"/>`,
`<circle cx="65" cy="52" r="7" fill="${c.eyeR}" stroke="${c.stroke}" stroke-width="1.5"/>`,
`<circle cx="63" cy="49" r="2.5" fill="white"/>`,
`<ellipse cx="50" cy="62" rx="3.5" ry="2.2" fill="${c.nose}"/>`,
`<path d="M42,67 Q46,72 50,67" fill="none" stroke="${c.stroke}" stroke-width="2" stroke-linecap="round"/>`,
`<path d="M50,67 Q54,72 58,67" fill="none" stroke="${c.stroke}" stroke-width="2" stroke-linecap="round"/>`,
"</svg>"
].join("");
}
var catDataUris = variants.map((v) => {
const svg = makeCatSvg(v);
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
});
function getCatAvatar(index) {
return catDataUris[index % catDataUris.length];
}
function getCatAvatarByUserId(userId) {
let hash = 0;
for (let i = 0; i < userId.length; i++) {
hash = (hash << 5) - hash + userId.charCodeAt(i) | 0;
}
return getCatAvatar(Math.abs(hash) % catDataUris.length);
}
function getAllCatAvatars() {
const names = ["\u5F71\u5B50", "\u871C\u67D1", "\u5C0F\u96EA", "\u7070\u7070", "\u71D5\u5C3E\u670D", "\u6469\u5361", "\u84DD\u84DD", "\u5C0F\u6A31"];
return catDataUris.map((uri, i) => ({ index: i, name: names[i], dataUri: uri }));
}
function getCatSvgString(index) {
return makeCatSvg(variants[index % variants.length]);
}
var CAT_AVATAR_COUNT = variants.length;
// src/utils/index.ts
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var generateInviteCode = customAlphabet(alphabet, 8);
function formatDate(date) {
const d = typeof date === "string" ? new Date(date) : date;
return d.toISOString();
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function slugify(text) {
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
}
export {
generateRandomCatConfig,
renderCatSvg,
canonicalMentionToken,
parseCanonicalMentionToken,
isCanonicalMentionToken,
segmentTextByMentions,
assignMentionRanges,
canonicalizeMentionContent,
mentionDisplayText,
escapeMarkdownLinkLabel,
buildMentionMarkdownLinks,
getCatAvatar,
getCatAvatarByUserId,
getAllCatAvatars,
getCatSvgString,
CAT_AVATAR_COUNT,
generateInviteCode,
formatDate,
isValidEmail,
slugify
};
// src/play-catalog/index.ts
var playCover = (id) => `/home-assets/plays/${id}.jpg`;
var playTemplate = (id) => ({
template: {
kind: "cloud",
slug: id,
path: `apps/cloud/templates/${id}.template.json`
},
materials: { cover: playCover(id) }
});
var communityAction = (channelName, greeting) => ({
kind: "public_channel",
channelName,
buddyTemplateSlug: channelName,
...greeting ? { greeting } : {}
});
var roomAction = (namePrefix, greeting) => ({
kind: "private_room",
namePrefix,
buddyTemplateSlug: namePrefix,
...greeting ? { greeting } : {}
});
var cloudAction = (templateSlug, resourceTier = "lightweight", defaultChannelName) => ({
kind: "cloud_deploy",
templateSlug,
buddyTemplateSlug: templateSlug,
resourceTier,
...defaultChannelName ? { defaultChannelName } : {}
});
function getPlayBuddyUsername(templateSlug) {
const normalized = templateSlug.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 27) || "play";
return `play_${normalized}`.slice(0, 32);
}
function getPlayBuddyEmail(templateSlug) {
return `${getPlayBuddyUsername(templateSlug)}@shadowob.bot`;
}
var SHADOW_PLAY_SERVER_TEMPLATE = {
slug: "shadow-plays",
name: "Shadow Plays",
description: "Default public community space for launchable homepage plays.",
channels: [
{
name: "general",
topic: "General discussion for new players and Shadow community members."
},
{
name: "world-pulse",
topic: "A public room for real-time global events and daily signal."
},
{
name: "financial-freedom",
topic: "A public room for lightweight financial freedom simulations and planning prompts."
},
{
name: "ai-werewolf",
topic: "A public room for AI-hosted social deduction sessions."
},
{
name: "code-arena",
topic: "A public room for coding challenges and real-time battles."
},
{
name: "brain-fix",
topic: "A calm public room for one-minute focus resets and reflection."
},
{
name: "gitstory",
topic: "A public room for turning software history into stories and retrospectives."
},
{
name: "gstack",
topic: "A public room for founder strategy, product stress tests, and launch planning."
}
]
};
function cloudPlay(id, input) {
const { defaultChannelName, ...content } = input;
return {
id,
image: playCover(id),
status: "gated",
action: cloudAction(id, "lightweight", defaultChannelName),
gates: { auth: "required", membership: "required", profile: "optional" },
...playTemplate(id),
...content
};
}
var DEFAULT_HOMEPLAY_CATALOG = [
{
id: "retire-buddy",
image: playCover("retire-buddy"),
title: "\u9000\u4F11\u52A9\u624B",
titleEn: "RetireBuddy",
desc: "\u5E2E\u4F60\u89C4\u5212\u9000\u4F11\u751F\u6D3B\u3001\u8D22\u52A1\u81EA\u7531\u8DEF\u5F84\uFF0C24\u5C0F\u65F6\u6E29\u6696\u966A\u4F34\uFF0C\u8BA9\u544A\u522B\u804C\u573A\u53D8\u6210\u4EBA\u751F\u65B0\u7AE0\u8282\u3002",
descEn: "Plan your retirement and path to financial freedom with a warm 24/7 companion.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "24.5k",
accentColor: "var(--shadow-accent)",
hot: true,
status: "available",
action: roomAction("retire-buddy"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("retire-buddy")
},
{
id: "financial-freedom",
image: playCover("financial-freedom"),
title: "\u6211\u8D22\u5BCC\u81EA\u7531\u4E86\u5417\uFF1F",
titleEn: "Am I Free?",
desc: "\u8F93\u5165\u4F60\u7684\u8D44\u4EA7\u4E0E\u652F\u51FA\uFF0CAI \u4E3A\u4F60\u8BA1\u7B97\u8D22\u52A1\u81EA\u7531\u8DDD\u79BB\uFF0C\u7ED9\u51FA\u6E05\u6670\u7684\u8FBE\u6210\u8DEF\u7EBF\u56FE\u3002",
descEn: "Input your assets and expenses \u2014 get your financial freedom score and roadmap.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "18.2k",
accentColor: "#f8e71c",
status: "available",
action: communityAction("financial-freedom"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("financial-freedom")
},
{
id: "brain-fix",
image: playCover("brain-fix"),
title: "\u4E00\u5206\u949F\u4FEE\u590D\u4F60\u7684\u5927\u8111\uFF01",
titleEn: "1-Min Brain Fix",
desc: "\u79D1\u5B66\u51A5\u60F3 + \u5FAE\u547C\u5438\u7EC3\u4E60\uFF0C60\u79D2\u5185\u4ECE\u7126\u8651\u6A21\u5F0F\u5207\u6362\u5230\u4E13\u6CE8\u72B6\u6001\uFF0C\u5C61\u8BD5\u4E0D\u723D\u3002",
descEn: "Science-backed micro-meditation. Switch from anxious to focused in 60 seconds.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "15.9k",
accentColor: "#a78bfa",
status: "available",
action: communityAction("brain-fix"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("brain-fix")
},
{
id: "world-pulse",
image: playCover("world-pulse"),
title: "\u5730\u7403\u8109\u640F",
titleEn: "World Pulse",
desc: "\u5B9E\u65F6\u6293\u53D6\u5168\u7403\u91CD\u5927\u4E8B\u4EF6\uFF0C\u7528\u4E09\u53E5\u8BDD\u544A\u8BC9\u4F60\u4ECA\u5929\u771F\u6B63\u53D1\u751F\u4E86\u4EC0\u4E48\uFF0C\u65E0\u5E9F\u8BDD\u3002",
descEn: "Real-time global events in 3 sentences. No filler, just signal.",
category: "\u4E16\u754C\u8D44\u8BAF",
categoryEn: "World News",
starts: "14.1k",
accentColor: "#38bdf8",
status: "available",
action: communityAction("world-pulse"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("world-pulse")
},
{
id: "daily-brief",
image: playCover("daily-brief"),
title: "\u6668\u95F4\u7B80\u62A5",
titleEn: "Morning Brief",
desc: "\u6BCF\u5929 7:00 \u63A8\u9001\u4E00\u4EFD\u5B9A\u5236\u65E9\u62A5\uFF1A\u56FD\u9645\u3001\u79D1\u6280\u3001\u5E02\u573A\u4E09\u5927\u677F\u5757\uFF0C\u8BFB\u5B8C\u53EA\u9700 3 \u5206\u949F\u3002",
descEn: "Custom morning digest at 7am \u2014 global news, tech, markets. 3-minute read.",
category: "\u4E16\u754C\u8D44\u8BAF",
categoryEn: "World News",
starts: "11.3k",
accentColor: "#fb923c",
status: "available",
action: roomAction("daily-brief"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("daily-brief")
},
{
id: "ai-werewolf",
image: playCover("ai-werewolf"),
title: "AI \u72FC\u4EBA\u6740",
titleEn: "AI Werewolf",
desc: "AI \u62C5\u4EFB\u4E3B\u6301\uFF0C\u968F\u673A\u5206\u914D\u8EAB\u4EFD\uFF0C\u5728\u804A\u5929\u4E2D\u5C55\u5F00\u63A8\u7406\u4E0E\u535A\u5F08\uFF0C3 \u4EBA\u5373\u53EF\u5F00\u5C40\u3002",
descEn: "AI-hosted werewolf \u2014 roles assigned randomly, deduce, bluff, and vote. 3+ players.",
category: "\u4E92\u52A8\u6E38\u620F",
categoryEn: "Games",
starts: "20.8k",
accentColor: "#f87171",
hot: true,
status: "available",
action: communityAction("ai-werewolf"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("ai-werewolf")
},
{
id: "code-arena",
image: playCover("code-arena"),
title: "\u4EE3\u7801\u64C2\u53F0",
titleEn: "Code Arena",
desc: "\u5B9E\u65F6\u7F16\u7A0B\u5BF9\u6218\uFF0CAI \u51FA\u9898\u3001\u8BA1\u65F6\u3001\u81EA\u52A8\u8BC4\u6D4B\uFF0C\u6311\u6218\u597D\u53CB\u6216\u5339\u914D\u964C\u751F\u5BF9\u624B\u3002",
descEn: "Real-time coding battles \u2014 AI generates problems, auto-judges, ranks you live.",
category: "\u4E92\u52A8\u6E38\u620F",
categoryEn: "Games",
starts: "8.6k",
accentColor: "#fbbf24",
status: "available",
action: communityAction("code-arena"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("code-arena")
},
{
id: "gitstory",
image: playCover("gitstory"),
title: "GitStory",
titleEn: "GitStory",
desc: "\u628A\u4F60\u7684 GitHub \u63D0\u4EA4\u5386\u53F2\u53D8\u6210\u4E00\u672C\u81EA\u4F20\u5C0F\u8BF4\u2014\u2014AI \u5E2E\u4F60\u56DE\u987E\u6BCF\u4E00\u6BB5\u4EE3\u7801\u80CC\u540E\u7684\u6545\u4E8B\u3002",
descEn: "Turn your GitHub commits into an autobiography. Every line of code has a story.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "12.1k",
accentColor: "#34d399",
hot: true,
status: "available",
action: communityAction("gitstory"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("gitstory")
},
{
id: "gstack",
image: playCover("gstack"),
title: "gstack",
titleEn: "gstack",
desc: "\u521B\u4E1A\u8005\u7684 AI \u53C2\u8C0B\uFF0C\u5E2E\u4F60\u5FEB\u901F\u9A8C\u8BC1\u5546\u4E1A\u60F3\u6CD5\u3001\u5206\u6790\u7ADE\u4E89\u683C\u5C40\u3001\u751F\u6210\u878D\u8D44\u6587\u4EF6\u3002",
descEn: "AI co-founder for founders. Validate ideas, map competitors, generate pitch decks.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "9.3k",
accentColor: "#f97316",
status: "available",
action: communityAction("gstack"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("gstack")
},
{
id: "little-match-girl",
image: "/home-assets/topics/night-radio.jpg",
title: "\u5356\u706B\u67F4\u7684\u5C0F\u5973\u5B69",
titleEn: "Little Match Girl",
desc: "\u90E8\u7F72\u4E00\u4E2A\u4F1A\u63A8\u9500\u706B\u67F4\u7684\u7AE5\u8BDD Buddy\uFF0C\u8D2D\u4E70\u540E\u5728\u804A\u5929\u53F3\u4FA7\u6253\u5F00\u706B\u67F4\u52A8\u753B\u4ED8\u8D39\u6587\u4EF6\u3002",
descEn: "Deploy a fairy-tale Buddy who sells glowing matches and unlocks a paid HTML flame animation.",
category: "MVP \u5B9E\u9A8C",
categoryEn: "MVP Labs",
starts: "1.2k",
accentColor: "#f59e0b",
hot: true,
status: "gated",
action: cloudAction("little-match-girl", "lightweight", "match-street"),
gates: { auth: "required", membership: "required", profile: "optional" },
...playTemplate("little-match-girl"),
materials: { cover: "/home-assets/topics/night-radio.jpg" }
},
cloudPlay("agent-marketplace-buddy", {
title: "Agent Marketplace Buddy",
titleEn: "Agent Marketplace Buddy",
desc: "\u53EF\u7EC4\u5408\u4E13\u5BB6 agent \u5E02\u573A\uFF0C\u8986\u76D6\u5F00\u53D1\u3001\u5B89\u5168\u3001\u57FA\u7840\u8BBE\u65BD\u3001\u6570\u636E\u3001\u6587\u6863\u3001SEO \u548C workflow \u7F16\u6392\u3002",
descEn: "A composable specialist-agent marketplace for development, security, infra, data, docs, SEO, and workflow orchestration.",
category: "Buddy \u56E2\u961F",
categoryEn: "Buddy Teams",
starts: "16.4k",
accentColor: "#22d3ee",
hot: true,
defaultChannelName: "choose"
}),
cloudPlay("bmad-method-buddy", {
title: "BMAD \u65B9\u6CD5 Buddy",
titleEn: "BMAD Method Buddy",
desc: "\u5B8C\u6574 BMAD \u65B9\u6CD5\u8BBA\u56E2\u961F\uFF1A\u5206\u6790\u5E08\u3001PM\u3001\u67B6\u6784\u5E08\u3001Scrum Master\u3001\u5F00\u53D1\u3001QA\uFF0C\u8D2F\u7A7F\u89C4\u5212\u5230\u4EA4\u4ED8\u3002",
descEn: "A full BMAD method team: analyst, PM, architect, scrum master, dev, and QA from planning to delivery.",
category: "Buddy \u56E2\u961F",
categoryEn: "Buddy Teams",
starts: "13.7k",
accentColor: "#60a5fa",
defaultChannelName: "delivery"
}),
cloudPlay("claude-ads-buddy", {
title: "Claude Ads Buddy",
titleEn: "Claude Ads Buddy",
desc: "\u4ED8\u8D39\u6295\u653E\u8BCA\u65AD\u3001\u9884\u7B97\u5EFA\u6A21\u3001\u521B\u610F\u5BA1\u67E5\u3001\u8FFD\u8E2A\u95EE\u9898\u548C\u843D\u5730\u9875\u74F6\u9888\u5206\u6790\u3002",
descEn: "Paid ads audits, budget models, creative review, tracking issues, and landing-page bottlenecks.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "10.8k",
accentColor: "#fb7185"
}),
cloudPlay("claude-seo-buddy", {
title: "Claude SEO Buddy",
titleEn: "Claude SEO Buddy",
desc: "SEO \u5185\u5BB9\u548C\u6280\u672F\u5BA1\u67E5\u56E2\u961F\uFF0C\u8986\u76D6\u5173\u952E\u8BCD\u3001\u5185\u94FE\u3001\u7ED3\u6784\u5316\u6570\u636E\u3001\u9875\u9762\u8D28\u91CF\u548C\u589E\u957F\u8BA1\u5212\u3002",
descEn: "SEO content and technical review for keywords, links, schema, quality, and growth plans.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "12.6k",
accentColor: "#84cc16"
}),
cloudPlay("everything-claude-code-buddy", {
title: "Everything Claude Code Buddy",
titleEn: "Everything Claude Code Buddy",
desc: "Claude Code \u5DE5\u4F5C\u6D41\u3001\u547D\u4EE4\u548C\u5DE5\u7A0B\u5B9E\u8DF5\u5408\u96C6\uFF0C\u9002\u5408\u7814\u53D1\u56E2\u961F\u6C89\u6DC0\u81EA\u52A8\u5316\u80FD\u529B\u3002",
descEn: "Claude Code workflows, commands, and engineering practices for automation-heavy teams.",
category: "\u5F00\u53D1\u6280\u80FD",
categoryEn: "Developer Skills",
starts: "19.2k",
accentColor: "#c084fc",
hot: true
}),
cloudPlay("google-workspace-buddy", {
title: "Google Workspace Buddy",
titleEn: "Google Workspace Buddy",
desc: "\u628A Docs\u3001Sheets\u3001Drive\u3001\u65E5\u5386\u548C\u90AE\u4EF6\u534F\u4F5C\u7F16\u6392\u5230 Buddy \u5DE5\u4F5C\u6D41\u91CC\u3002",
descEn: "Coordinate Docs, Sheets, Drive, Calendar, and email collaboration through Buddy workflows.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "9.9k",
accentColor: "#34d399"
}),
cloudPlay("gsd-buddy", {
title: "GSD Buddy",
titleEn: "GSD Buddy",
desc: "\u6267\u884C\u529B\u56E2\u961F\uFF1A\u62C6\u89E3\u4EFB\u52A1\u3001\u6392\u4F18\u5148\u7EA7\u3001\u63A8\u52A8\u51B3\u7B56\u3001\u8FFD\u8E2A\u963B\u585E\uFF0C\u5E2E\u56E2\u961F\u6301\u7EED get stuff done\u3002",
descEn: "Execution team for task breakdown, priority, decisions, blockers, and getting stuff done.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "17.5k",
accentColor: "#facc15",
hot: true
}),
cloudPlay("gstack-buddy", {
title: "gstack \u6218\u7565 Buddy",
titleEn: "gstack Strategy Buddy",
desc: "YC \u98CE\u683C\u4EA7\u54C1\u538B\u529B\u6D4B\u8BD5\u3001CEO \u89C6\u89D2\u8303\u56F4\u8BC4\u5BA1\u3001\u8C03\u67E5\u7EAA\u5F8B\u3001\u5468\u590D\u76D8\u548C gstack \u811A\u672C\u5DE5\u5177\u3002",
descEn: "YC-style product pressure testing, CEO scope review, investigation discipline, retros, and gstack scripts.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "15.1k",
accentColor: "#fb923c",
hot: true,
defaultChannelName: "office-hours"
}),
cloudPlay("marketingskills-buddy", {
title: "\u8425\u9500\u6280\u80FD Buddy",
titleEn: "MarketingSkills Buddy",
desc: "\u589E\u957F\u56E2\u961F\u7684\u8425\u9500\u534F\u4F5C\u667A\u80FD\u4F53\uFF0C\u8986\u76D6 CRO\u3001\u6587\u6848\u3001SEO\u3001\u4ED8\u8D39\u3001\u90AE\u4EF6\u548C\u589E\u957F\u51B3\u7B56\u3002",
descEn: "Marketing collaboration agents for CRO, copy, SEO, paid, email, and growth decisions.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "11.7k",
accentColor: "#f472b6"
}),
cloudPlay("scientific-skills-buddy", {
title: "\u79D1\u7814\u6280\u80FD Buddy",
titleEn: "Scientific Skills Buddy",
desc: "\u7814\u7A76\u9605\u8BFB\u3001\u5B9E\u9A8C\u8BBE\u8BA1\u3001\u8BBA\u6587\u7ED3\u6784\u3001\u6570\u636E\u5206\u6790\u548C\u5B66\u672F\u5199\u4F5C\u534F\u4F5C\u56E2\u961F\u3002",
descEn: "Research reading, experiment design, paper structure, data analysis, and academic writing workflows.",
category: "\u79D1\u7814\u6280\u80FD",
categoryEn: "Research Skills",
starts: "7.8k",
accentColor: "#38bdf8"
}),
cloudPlay("seomachine-buddy", {
title: "SEO Machine Buddy",
titleEn: "SEO Machine Buddy",
desc: "\u6301\u7EED\u8FD0\u884C\u7684 SEO \u673A\u5668\uFF1A\u9009\u9898\u3001brief\u3001\u5185\u5BB9\u5BA1\u67E5\u3001\u6280\u672F\u68C0\u67E5\u548C\u6392\u540D\u590D\u76D8\u3002",
descEn: "An always-on SEO machine for topics, briefs, review, technical checks, and ranking retros.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "10.2k",
accentColor: "#a3e635"
}),
cloudPlay("slavingia-skills-buddy", {
title: "Slavingia Skills Buddy",
titleEn: "Slavingia Skills Buddy",
desc: "\u521B\u4F5C\u8005\u548C\u72EC\u7ACB\u5F00\u53D1\u8005\u7684\u6280\u80FD\u5E93\uFF0C\u8986\u76D6\u5199\u4F5C\u3001\u4EA7\u54C1\u3001\u589E\u957F\u3001\u793E\u533A\u548C\u53D1\u5E03\u8282\u594F\u3002",
descEn: "A creator and indie-builder skill stack for writing, product, growth, community, and shipping rhythm.",
category: "\u521B\u4F5C\u8005\u6280\u80FD",
categoryEn: "Creator Skills",
starts: "8.4k",
accentColor: "#f97316"
}),
cloudPlay("superclaude-buddy", {
title: "SuperClaude Buddy",
titleEn: "SuperClaude Buddy",
desc: "SuperClaude \u6307\u4EE4\u3001\u89D2\u8272\u548C\u5DE5\u4F5C\u6D41\u80FD\u529B\uFF0C\u5E2E\u52A9\u56E2\u961F\u628A Claude \u7528\u6210\u7ED3\u6784\u5316\u5DE5\u7A0B\u4F19\u4F34\u3002",
descEn: "SuperClaude commands, personas, and workflows for structured engineering collaboration.",
category: "\u5F00\u53D1\u6280\u80FD",
categoryEn: "Developer Skills",
starts: "18.9k",
accentColor: "#818cf8",
hot: true
}),
cloudPlay("superpowers-buddy", {
title: "Superpowers Buddy",
titleEn: "Superpowers Buddy",
desc: "\u4E2A\u4EBA\u751F\u4EA7\u529B\u8D85\u80FD\u529B\u7EC4\u5408\uFF1A\u9605\u8BFB\u3001\u5199\u4F5C\u3001\u4EFB\u52A1\u3001\u7814\u7A76\u3001\u81EA\u52A8\u5316\u548C\u590D\u76D8\u3002",
descEn: "Personal productivity superpowers for reading, writing, tasks, research, automation, and retros.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "12.4k",
accentColor: "#2dd4bf"
}),
{
id: "e-wife",
image: playCover("e-wife"),
title: "\u7535\u5B50\u8001\u5A46",
titleEn: "E-Wife",
desc: "\u4E00\u4E2A\u5E26\u6709\u966A\u4F34\u611F\u7684\u865A\u62DF\u751F\u6D3B\u4F19\u4F34\u73A9\u6CD5\uFF0C\u540E\u7EED\u4F1A\u63A5\u5165\u4E2A\u6027\u5316\u8BB0\u5FC6\u548C\u79C1\u6709\u623F\u95F4\u3002",
descEn: "A companion-style virtual life partner play, later connected to memory and private rooms.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "22.0k",
accentColor: "#f0abfc",
status: "available",
action: roomAction("e-wife"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("e-wife")
}
];
function getDefaultHomePlay(playId) {
return DEFAULT_HOMEPLAY_CATALOG.find((play) => play.id === playId) ?? null;
}
export {
getPlayBuddyUsername,
getPlayBuddyEmail,
SHADOW_PLAY_SERVER_TEMPLATE,
DEFAULT_HOMEPLAY_CATALOG,
getDefaultHomePlay
};
interface Message {
id: string;
content: string;
channelId: string;
authorId: string;
threadId: string | null;
replyToId: string | null;
isEdited: boolean;
isPinned: boolean;
createdAt: string;
updatedAt: string;
author?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
isBot: boolean;
};
attachments?: Attachment[];
reactions?: ReactionGroup[];
metadata?: MessageMetadata | null;
}
type MessageMentionKind = 'user' | 'buddy' | 'channel' | 'server' | 'here' | 'everyone';
interface MessageMentionRange {
start: number;
end: number;
}
interface MessageMention {
kind: MessageMentionKind;
/** Canonical target id. For users this is userId, for channels channelId, for servers serverId. */
targetId: string;
/** Canonical text persisted in message content, e.g. <@userId>, <#channelId>. */
token: string;
/** Optional display text selected or typed by the sender before canonicalization. */
sourceToken?: string;
/** Human-readable label used by renderers. */
label: string;
/** Optional source range in content. Clients may omit it; servers may recompute later. */
range?: MessageMentionRange;
serverId?: string;
serverSlug?: string | null;
serverName?: string | null;
channelId?: string;
channelName?: string | null;
userId?: string;
username?: string | null;
displayName?: string | null;
avatarUrl?: string | null;
isBot?: boolean;
isPrivate?: boolean;
}
interface MessageMetadata {
mentions?: MessageMention[];
agentChain?: Record<string, unknown>;
interactive?: Record<string, unknown>;
interactiveResponse?: Record<string, unknown>;
interactiveState?: Record<string, unknown>;
commerceCards?: CommerceMessageCard[];
paidFileCards?: PaidFileCard[];
[key: string]: unknown;
}
interface CommerceOfferCardInput {
id?: string;
kind: 'offer';
offerId: string;
}
type CommerceMessageCard = CommerceProductCard | CommerceOfferCardInput;
interface CommerceProductCard {
id: string;
kind: 'offer' | 'product';
offerId?: string;
shopId: string;
shopScope: {
kind: 'server' | 'user';
id: string;
};
productId: string;
skuId?: string;
snapshot: {
name: string;
summary?: string | null;
imageUrl?: string | null;
price: number;
currency: string;
productType: 'physical' | 'entitlement';
billingMode?: 'one_time' | 'fixed_duration' | 'subscription';
durationSeconds?: number | null;
resourceType?: string;
resourceId?: string;
capability?: string;
};
purchase: {
mode: 'direct' | 'select_sku' | 'open_detail';
};
}
interface PaidFileCard {
id: string;
kind: 'paid_file';
fileId: string;
entitlementId?: string | null;
deliverableId?: string;
snapshot: {
name: string;
summary?: string | null;
mime?: string | null;
sizeBytes?: number | null;
previewUrl?: string | null;
};
action: {
mode: 'open_paid_file';
};
}
type MentionSuggestionTrigger = '@' | '#';
interface MentionSuggestion {
id: string;
kind: MessageMentionKind;
targetId: string;
token: string;
label: string;
description?: string | null;
serverId?: string;
serverSlug?: string | null;
serverName?: string | null;
channelId?: string;
channelName?: string | null;
userId?: string;
username?: string | null;
displayName?: string | null;
avatarUrl?: string | null;
isBot?: boolean;
isPrivate?: boolean;
}
interface Attachment {
id: string;
messageId: string;
filename: string;
url: string;
contentType: string;
size: number;
width: number | null;
height: number | null;
createdAt: string;
}
interface ReactionGroup {
emoji: string;
count: number;
userIds: string[];
}
interface Thread {
id: string;
name: string;
channelId: string;
parentMessageId: string;
creatorId: string;
isArchived: boolean;
createdAt: string;
updatedAt: string;
}
interface SendMessageRequest {
content: string;
threadId?: string;
replyToId?: string;
mentions?: MessageMention[];
metadata?: MessageMetadata;
}
interface UpdateMessageRequest {
content: string;
}
type NotificationType = 'mention' | 'reply' | 'dm' | 'system';
interface Notification {
id: string;
userId: string;
type: NotificationType;
title: string;
body: string | null;
referenceId: string | null;
referenceType: string | null;
isRead: boolean;
createdAt: string;
}
export type { Attachment as A, CommerceMessageCard as C, MentionSuggestion as M, Notification as N, PaidFileCard as P, ReactionGroup as R, SendMessageRequest as S, Thread as T, UpdateMessageRequest as U, CommerceOfferCardInput as a, CommerceProductCard as b, MentionSuggestionTrigger as c, Message as d, MessageMention as e, MessageMentionKind as f, MessageMentionRange as g, MessageMetadata as h, NotificationType as i };
interface Message {
id: string;
content: string;
channelId: string;
authorId: string;
threadId: string | null;
replyToId: string | null;
isEdited: boolean;
isPinned: boolean;
createdAt: string;
updatedAt: string;
author?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
isBot: boolean;
};
attachments?: Attachment[];
reactions?: ReactionGroup[];
metadata?: MessageMetadata | null;
}
type MessageMentionKind = 'user' | 'buddy' | 'channel' | 'server' | 'here' | 'everyone';
interface MessageMentionRange {
start: number;
end: number;
}
interface MessageMention {
kind: MessageMentionKind;
/** Canonical target id. For users this is userId, for channels channelId, for servers serverId. */
targetId: string;
/** Canonical text persisted in message content, e.g. <@userId>, <#channelId>. */
token: string;
/** Optional display text selected or typed by the sender before canonicalization. */
sourceToken?: string;
/** Human-readable label used by renderers. */
label: string;
/** Optional source range in content. Clients may omit it; servers may recompute later. */
range?: MessageMentionRange;
serverId?: string;
serverSlug?: string | null;
serverName?: string | null;
channelId?: string;
channelName?: string | null;
userId?: string;
username?: string | null;
displayName?: string | null;
avatarUrl?: string | null;
isBot?: boolean;
isPrivate?: boolean;
}
interface MessageMetadata {
mentions?: MessageMention[];
agentChain?: Record<string, unknown>;
interactive?: Record<string, unknown>;
interactiveResponse?: Record<string, unknown>;
interactiveState?: Record<string, unknown>;
commerceCards?: CommerceMessageCard[];
paidFileCards?: PaidFileCard[];
[key: string]: unknown;
}
interface CommerceOfferCardInput {
id?: string;
kind: 'offer';
offerId: string;
}
type CommerceMessageCard = CommerceProductCard | CommerceOfferCardInput;
interface CommerceProductCard {
id: string;
kind: 'offer' | 'product';
offerId?: string;
shopId: string;
shopScope: {
kind: 'server' | 'user';
id: string;
};
productId: string;
skuId?: string;
snapshot: {
name: string;
summary?: string | null;
imageUrl?: string | null;
price: number;
currency: string;
productType: 'physical' | 'entitlement';
billingMode?: 'one_time' | 'fixed_duration' | 'subscription';
durationSeconds?: number | null;
resourceType?: string;
resourceId?: string;
capability?: string;
};
purchase: {
mode: 'direct' | 'select_sku' | 'open_detail';
};
}
interface PaidFileCard {
id: string;
kind: 'paid_file';
fileId: string;
entitlementId?: string | null;
deliverableId?: string;
snapshot: {
name: string;
summary?: string | null;
mime?: string | null;
sizeBytes?: number | null;
previewUrl?: string | null;
};
action: {
mode: 'open_paid_file';
};
}
type MentionSuggestionTrigger = '@' | '#';
interface MentionSuggestion {
id: string;
kind: MessageMentionKind;
targetId: string;
token: string;
label: string;
description?: string | null;
serverId?: string;
serverSlug?: string | null;
serverName?: string | null;
channelId?: string;
channelName?: string | null;
userId?: string;
username?: string | null;
displayName?: string | null;
avatarUrl?: string | null;
isBot?: boolean;
isPrivate?: boolean;
}
interface Attachment {
id: string;
messageId: string;
filename: string;
url: string;
contentType: string;
size: number;
width: number | null;
height: number | null;
createdAt: string;
}
interface ReactionGroup {
emoji: string;
count: number;
userIds: string[];
}
interface Thread {
id: string;
name: string;
channelId: string;
parentMessageId: string;
creatorId: string;
isArchived: boolean;
createdAt: string;
updatedAt: string;
}
interface SendMessageRequest {
content: string;
threadId?: string;
replyToId?: string;
mentions?: MessageMention[];
metadata?: MessageMetadata;
}
interface UpdateMessageRequest {
content: string;
}
type NotificationType = 'mention' | 'reply' | 'dm' | 'system';
interface Notification {
id: string;
userId: string;
type: NotificationType;
title: string;
body: string | null;
referenceId: string | null;
referenceType: string | null;
isRead: boolean;
createdAt: string;
}
export type { Attachment as A, CommerceMessageCard as C, MentionSuggestion as M, Notification as N, PaidFileCard as P, ReactionGroup as R, SendMessageRequest as S, Thread as T, UpdateMessageRequest as U, CommerceOfferCardInput as a, CommerceProductCard as b, MentionSuggestionTrigger as c, Message as d, MessageMention as e, MessageMentionKind as f, MessageMentionRange as g, MessageMetadata as h, NotificationType as i };
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/play-catalog/index.ts
var play_catalog_exports = {};
__export(play_catalog_exports, {
DEFAULT_HOMEPLAY_CATALOG: () => DEFAULT_HOMEPLAY_CATALOG,
SHADOW_PLAY_SERVER_TEMPLATE: () => SHADOW_PLAY_SERVER_TEMPLATE,
getDefaultHomePlay: () => getDefaultHomePlay,
getPlayBuddyEmail: () => getPlayBuddyEmail,
getPlayBuddyUsername: () => getPlayBuddyUsername
});
module.exports = __toCommonJS(play_catalog_exports);
var playCover = (id) => `/home-assets/plays/${id}.jpg`;
var playTemplate = (id) => ({
template: {
kind: "cloud",
slug: id,
path: `apps/cloud/templates/${id}.template.json`
},
materials: { cover: playCover(id) }
});
var communityAction = (channelName, greeting) => ({
kind: "public_channel",
channelName,
buddyTemplateSlug: channelName,
...greeting ? { greeting } : {}
});
var roomAction = (namePrefix, greeting) => ({
kind: "private_room",
namePrefix,
buddyTemplateSlug: namePrefix,
...greeting ? { greeting } : {}
});
var cloudAction = (templateSlug, resourceTier = "lightweight", defaultChannelName) => ({
kind: "cloud_deploy",
templateSlug,
buddyTemplateSlug: templateSlug,
resourceTier,
...defaultChannelName ? { defaultChannelName } : {}
});
function getPlayBuddyUsername(templateSlug) {
const normalized = templateSlug.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 27) || "play";
return `play_${normalized}`.slice(0, 32);
}
function getPlayBuddyEmail(templateSlug) {
return `${getPlayBuddyUsername(templateSlug)}@shadowob.bot`;
}
var SHADOW_PLAY_SERVER_TEMPLATE = {
slug: "shadow-plays",
name: "Shadow Plays",
description: "Default public community space for launchable homepage plays.",
channels: [
{
name: "general",
topic: "General discussion for new players and Shadow community members."
},
{
name: "world-pulse",
topic: "A public room for real-time global events and daily signal."
},
{
name: "financial-freedom",
topic: "A public room for lightweight financial freedom simulations and planning prompts."
},
{
name: "ai-werewolf",
topic: "A public room for AI-hosted social deduction sessions."
},
{
name: "code-arena",
topic: "A public room for coding challenges and real-time battles."
},
{
name: "brain-fix",
topic: "A calm public room for one-minute focus resets and reflection."
},
{
name: "gitstory",
topic: "A public room for turning software history into stories and retrospectives."
},
{
name: "gstack",
topic: "A public room for founder strategy, product stress tests, and launch planning."
}
]
};
function cloudPlay(id, input) {
const { defaultChannelName, ...content } = input;
return {
id,
image: playCover(id),
status: "gated",
action: cloudAction(id, "lightweight", defaultChannelName),
gates: { auth: "required", membership: "required", profile: "optional" },
...playTemplate(id),
...content
};
}
var DEFAULT_HOMEPLAY_CATALOG = [
{
id: "retire-buddy",
image: playCover("retire-buddy"),
title: "\u9000\u4F11\u52A9\u624B",
titleEn: "RetireBuddy",
desc: "\u5E2E\u4F60\u89C4\u5212\u9000\u4F11\u751F\u6D3B\u3001\u8D22\u52A1\u81EA\u7531\u8DEF\u5F84\uFF0C24\u5C0F\u65F6\u6E29\u6696\u966A\u4F34\uFF0C\u8BA9\u544A\u522B\u804C\u573A\u53D8\u6210\u4EBA\u751F\u65B0\u7AE0\u8282\u3002",
descEn: "Plan your retirement and path to financial freedom with a warm 24/7 companion.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "24.5k",
accentColor: "var(--shadow-accent)",
hot: true,
status: "available",
action: roomAction("retire-buddy"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("retire-buddy")
},
{
id: "financial-freedom",
image: playCover("financial-freedom"),
title: "\u6211\u8D22\u5BCC\u81EA\u7531\u4E86\u5417\uFF1F",
titleEn: "Am I Free?",
desc: "\u8F93\u5165\u4F60\u7684\u8D44\u4EA7\u4E0E\u652F\u51FA\uFF0CAI \u4E3A\u4F60\u8BA1\u7B97\u8D22\u52A1\u81EA\u7531\u8DDD\u79BB\uFF0C\u7ED9\u51FA\u6E05\u6670\u7684\u8FBE\u6210\u8DEF\u7EBF\u56FE\u3002",
descEn: "Input your assets and expenses \u2014 get your financial freedom score and roadmap.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "18.2k",
accentColor: "#f8e71c",
status: "available",
action: communityAction("financial-freedom"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("financial-freedom")
},
{
id: "brain-fix",
image: playCover("brain-fix"),
title: "\u4E00\u5206\u949F\u4FEE\u590D\u4F60\u7684\u5927\u8111\uFF01",
titleEn: "1-Min Brain Fix",
desc: "\u79D1\u5B66\u51A5\u60F3 + \u5FAE\u547C\u5438\u7EC3\u4E60\uFF0C60\u79D2\u5185\u4ECE\u7126\u8651\u6A21\u5F0F\u5207\u6362\u5230\u4E13\u6CE8\u72B6\u6001\uFF0C\u5C61\u8BD5\u4E0D\u723D\u3002",
descEn: "Science-backed micro-meditation. Switch from anxious to focused in 60 seconds.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "15.9k",
accentColor: "#a78bfa",
status: "available",
action: communityAction("brain-fix"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("brain-fix")
},
{
id: "world-pulse",
image: playCover("world-pulse"),
title: "\u5730\u7403\u8109\u640F",
titleEn: "World Pulse",
desc: "\u5B9E\u65F6\u6293\u53D6\u5168\u7403\u91CD\u5927\u4E8B\u4EF6\uFF0C\u7528\u4E09\u53E5\u8BDD\u544A\u8BC9\u4F60\u4ECA\u5929\u771F\u6B63\u53D1\u751F\u4E86\u4EC0\u4E48\uFF0C\u65E0\u5E9F\u8BDD\u3002",
descEn: "Real-time global events in 3 sentences. No filler, just signal.",
category: "\u4E16\u754C\u8D44\u8BAF",
categoryEn: "World News",
starts: "14.1k",
accentColor: "#38bdf8",
status: "available",
action: communityAction("world-pulse"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("world-pulse")
},
{
id: "daily-brief",
image: playCover("daily-brief"),
title: "\u6668\u95F4\u7B80\u62A5",
titleEn: "Morning Brief",
desc: "\u6BCF\u5929 7:00 \u63A8\u9001\u4E00\u4EFD\u5B9A\u5236\u65E9\u62A5\uFF1A\u56FD\u9645\u3001\u79D1\u6280\u3001\u5E02\u573A\u4E09\u5927\u677F\u5757\uFF0C\u8BFB\u5B8C\u53EA\u9700 3 \u5206\u949F\u3002",
descEn: "Custom morning digest at 7am \u2014 global news, tech, markets. 3-minute read.",
category: "\u4E16\u754C\u8D44\u8BAF",
categoryEn: "World News",
starts: "11.3k",
accentColor: "#fb923c",
status: "available",
action: roomAction("daily-brief"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("daily-brief")
},
{
id: "ai-werewolf",
image: playCover("ai-werewolf"),
title: "AI \u72FC\u4EBA\u6740",
titleEn: "AI Werewolf",
desc: "AI \u62C5\u4EFB\u4E3B\u6301\uFF0C\u968F\u673A\u5206\u914D\u8EAB\u4EFD\uFF0C\u5728\u804A\u5929\u4E2D\u5C55\u5F00\u63A8\u7406\u4E0E\u535A\u5F08\uFF0C3 \u4EBA\u5373\u53EF\u5F00\u5C40\u3002",
descEn: "AI-hosted werewolf \u2014 roles assigned randomly, deduce, bluff, and vote. 3+ players.",
category: "\u4E92\u52A8\u6E38\u620F",
categoryEn: "Games",
starts: "20.8k",
accentColor: "#f87171",
hot: true,
status: "available",
action: communityAction("ai-werewolf"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("ai-werewolf")
},
{
id: "code-arena",
image: playCover("code-arena"),
title: "\u4EE3\u7801\u64C2\u53F0",
titleEn: "Code Arena",
desc: "\u5B9E\u65F6\u7F16\u7A0B\u5BF9\u6218\uFF0CAI \u51FA\u9898\u3001\u8BA1\u65F6\u3001\u81EA\u52A8\u8BC4\u6D4B\uFF0C\u6311\u6218\u597D\u53CB\u6216\u5339\u914D\u964C\u751F\u5BF9\u624B\u3002",
descEn: "Real-time coding battles \u2014 AI generates problems, auto-judges, ranks you live.",
category: "\u4E92\u52A8\u6E38\u620F",
categoryEn: "Games",
starts: "8.6k",
accentColor: "#fbbf24",
status: "available",
action: communityAction("code-arena"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("code-arena")
},
{
id: "gitstory",
image: playCover("gitstory"),
title: "GitStory",
titleEn: "GitStory",
desc: "\u628A\u4F60\u7684 GitHub \u63D0\u4EA4\u5386\u53F2\u53D8\u6210\u4E00\u672C\u81EA\u4F20\u5C0F\u8BF4\u2014\u2014AI \u5E2E\u4F60\u56DE\u987E\u6BCF\u4E00\u6BB5\u4EE3\u7801\u80CC\u540E\u7684\u6545\u4E8B\u3002",
descEn: "Turn your GitHub commits into an autobiography. Every line of code has a story.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "12.1k",
accentColor: "#34d399",
hot: true,
status: "available",
action: communityAction("gitstory"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("gitstory")
},
{
id: "gstack",
image: playCover("gstack"),
title: "gstack",
titleEn: "gstack",
desc: "\u521B\u4E1A\u8005\u7684 AI \u53C2\u8C0B\uFF0C\u5E2E\u4F60\u5FEB\u901F\u9A8C\u8BC1\u5546\u4E1A\u60F3\u6CD5\u3001\u5206\u6790\u7ADE\u4E89\u683C\u5C40\u3001\u751F\u6210\u878D\u8D44\u6587\u4EF6\u3002",
descEn: "AI co-founder for founders. Validate ideas, map competitors, generate pitch decks.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "9.3k",
accentColor: "#f97316",
status: "available",
action: communityAction("gstack"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("gstack")
},
{
id: "little-match-girl",
image: "/home-assets/topics/night-radio.jpg",
title: "\u5356\u706B\u67F4\u7684\u5C0F\u5973\u5B69",
titleEn: "Little Match Girl",
desc: "\u90E8\u7F72\u4E00\u4E2A\u4F1A\u63A8\u9500\u706B\u67F4\u7684\u7AE5\u8BDD Buddy\uFF0C\u8D2D\u4E70\u540E\u5728\u804A\u5929\u53F3\u4FA7\u6253\u5F00\u706B\u67F4\u52A8\u753B\u4ED8\u8D39\u6587\u4EF6\u3002",
descEn: "Deploy a fairy-tale Buddy who sells glowing matches and unlocks a paid HTML flame animation.",
category: "MVP \u5B9E\u9A8C",
categoryEn: "MVP Labs",
starts: "1.2k",
accentColor: "#f59e0b",
hot: true,
status: "gated",
action: cloudAction("little-match-girl", "lightweight", "match-street"),
gates: { auth: "required", membership: "required", profile: "optional" },
...playTemplate("little-match-girl"),
materials: { cover: "/home-assets/topics/night-radio.jpg" }
},
cloudPlay("agent-marketplace-buddy", {
title: "Agent Marketplace Buddy",
titleEn: "Agent Marketplace Buddy",
desc: "\u53EF\u7EC4\u5408\u4E13\u5BB6 agent \u5E02\u573A\uFF0C\u8986\u76D6\u5F00\u53D1\u3001\u5B89\u5168\u3001\u57FA\u7840\u8BBE\u65BD\u3001\u6570\u636E\u3001\u6587\u6863\u3001SEO \u548C workflow \u7F16\u6392\u3002",
descEn: "A composable specialist-agent marketplace for development, security, infra, data, docs, SEO, and workflow orchestration.",
category: "Buddy \u56E2\u961F",
categoryEn: "Buddy Teams",
starts: "16.4k",
accentColor: "#22d3ee",
hot: true,
defaultChannelName: "choose"
}),
cloudPlay("bmad-method-buddy", {
title: "BMAD \u65B9\u6CD5 Buddy",
titleEn: "BMAD Method Buddy",
desc: "\u5B8C\u6574 BMAD \u65B9\u6CD5\u8BBA\u56E2\u961F\uFF1A\u5206\u6790\u5E08\u3001PM\u3001\u67B6\u6784\u5E08\u3001Scrum Master\u3001\u5F00\u53D1\u3001QA\uFF0C\u8D2F\u7A7F\u89C4\u5212\u5230\u4EA4\u4ED8\u3002",
descEn: "A full BMAD method team: analyst, PM, architect, scrum master, dev, and QA from planning to delivery.",
category: "Buddy \u56E2\u961F",
categoryEn: "Buddy Teams",
starts: "13.7k",
accentColor: "#60a5fa",
defaultChannelName: "delivery"
}),
cloudPlay("claude-ads-buddy", {
title: "Claude Ads Buddy",
titleEn: "Claude Ads Buddy",
desc: "\u4ED8\u8D39\u6295\u653E\u8BCA\u65AD\u3001\u9884\u7B97\u5EFA\u6A21\u3001\u521B\u610F\u5BA1\u67E5\u3001\u8FFD\u8E2A\u95EE\u9898\u548C\u843D\u5730\u9875\u74F6\u9888\u5206\u6790\u3002",
descEn: "Paid ads audits, budget models, creative review, tracking issues, and landing-page bottlenecks.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "10.8k",
accentColor: "#fb7185"
}),
cloudPlay("claude-seo-buddy", {
title: "Claude SEO Buddy",
titleEn: "Claude SEO Buddy",
desc: "SEO \u5185\u5BB9\u548C\u6280\u672F\u5BA1\u67E5\u56E2\u961F\uFF0C\u8986\u76D6\u5173\u952E\u8BCD\u3001\u5185\u94FE\u3001\u7ED3\u6784\u5316\u6570\u636E\u3001\u9875\u9762\u8D28\u91CF\u548C\u589E\u957F\u8BA1\u5212\u3002",
descEn: "SEO content and technical review for keywords, links, schema, quality, and growth plans.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "12.6k",
accentColor: "#84cc16"
}),
cloudPlay("everything-claude-code-buddy", {
title: "Everything Claude Code Buddy",
titleEn: "Everything Claude Code Buddy",
desc: "Claude Code \u5DE5\u4F5C\u6D41\u3001\u547D\u4EE4\u548C\u5DE5\u7A0B\u5B9E\u8DF5\u5408\u96C6\uFF0C\u9002\u5408\u7814\u53D1\u56E2\u961F\u6C89\u6DC0\u81EA\u52A8\u5316\u80FD\u529B\u3002",
descEn: "Claude Code workflows, commands, and engineering practices for automation-heavy teams.",
category: "\u5F00\u53D1\u6280\u80FD",
categoryEn: "Developer Skills",
starts: "19.2k",
accentColor: "#c084fc",
hot: true
}),
cloudPlay("google-workspace-buddy", {
title: "Google Workspace Buddy",
titleEn: "Google Workspace Buddy",
desc: "\u628A Docs\u3001Sheets\u3001Drive\u3001\u65E5\u5386\u548C\u90AE\u4EF6\u534F\u4F5C\u7F16\u6392\u5230 Buddy \u5DE5\u4F5C\u6D41\u91CC\u3002",
descEn: "Coordinate Docs, Sheets, Drive, Calendar, and email collaboration through Buddy workflows.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "9.9k",
accentColor: "#34d399"
}),
cloudPlay("gsd-buddy", {
title: "GSD Buddy",
titleEn: "GSD Buddy",
desc: "\u6267\u884C\u529B\u56E2\u961F\uFF1A\u62C6\u89E3\u4EFB\u52A1\u3001\u6392\u4F18\u5148\u7EA7\u3001\u63A8\u52A8\u51B3\u7B56\u3001\u8FFD\u8E2A\u963B\u585E\uFF0C\u5E2E\u56E2\u961F\u6301\u7EED get stuff done\u3002",
descEn: "Execution team for task breakdown, priority, decisions, blockers, and getting stuff done.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "17.5k",
accentColor: "#facc15",
hot: true
}),
cloudPlay("gstack-buddy", {
title: "gstack \u6218\u7565 Buddy",
titleEn: "gstack Strategy Buddy",
desc: "YC \u98CE\u683C\u4EA7\u54C1\u538B\u529B\u6D4B\u8BD5\u3001CEO \u89C6\u89D2\u8303\u56F4\u8BC4\u5BA1\u3001\u8C03\u67E5\u7EAA\u5F8B\u3001\u5468\u590D\u76D8\u548C gstack \u811A\u672C\u5DE5\u5177\u3002",
descEn: "YC-style product pressure testing, CEO scope review, investigation discipline, retros, and gstack scripts.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "15.1k",
accentColor: "#fb923c",
hot: true,
defaultChannelName: "office-hours"
}),
cloudPlay("marketingskills-buddy", {
title: "\u8425\u9500\u6280\u80FD Buddy",
titleEn: "MarketingSkills Buddy",
desc: "\u589E\u957F\u56E2\u961F\u7684\u8425\u9500\u534F\u4F5C\u667A\u80FD\u4F53\uFF0C\u8986\u76D6 CRO\u3001\u6587\u6848\u3001SEO\u3001\u4ED8\u8D39\u3001\u90AE\u4EF6\u548C\u589E\u957F\u51B3\u7B56\u3002",
descEn: "Marketing collaboration agents for CRO, copy, SEO, paid, email, and growth decisions.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "11.7k",
accentColor: "#f472b6"
}),
cloudPlay("scientific-skills-buddy", {
title: "\u79D1\u7814\u6280\u80FD Buddy",
titleEn: "Scientific Skills Buddy",
desc: "\u7814\u7A76\u9605\u8BFB\u3001\u5B9E\u9A8C\u8BBE\u8BA1\u3001\u8BBA\u6587\u7ED3\u6784\u3001\u6570\u636E\u5206\u6790\u548C\u5B66\u672F\u5199\u4F5C\u534F\u4F5C\u56E2\u961F\u3002",
descEn: "Research reading, experiment design, paper structure, data analysis, and academic writing workflows.",
category: "\u79D1\u7814\u6280\u80FD",
categoryEn: "Research Skills",
starts: "7.8k",
accentColor: "#38bdf8"
}),
cloudPlay("seomachine-buddy", {
title: "SEO Machine Buddy",
titleEn: "SEO Machine Buddy",
desc: "\u6301\u7EED\u8FD0\u884C\u7684 SEO \u673A\u5668\uFF1A\u9009\u9898\u3001brief\u3001\u5185\u5BB9\u5BA1\u67E5\u3001\u6280\u672F\u68C0\u67E5\u548C\u6392\u540D\u590D\u76D8\u3002",
descEn: "An always-on SEO machine for topics, briefs, review, technical checks, and ranking retros.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "10.2k",
accentColor: "#a3e635"
}),
cloudPlay("slavingia-skills-buddy", {
title: "Slavingia Skills Buddy",
titleEn: "Slavingia Skills Buddy",
desc: "\u521B\u4F5C\u8005\u548C\u72EC\u7ACB\u5F00\u53D1\u8005\u7684\u6280\u80FD\u5E93\uFF0C\u8986\u76D6\u5199\u4F5C\u3001\u4EA7\u54C1\u3001\u589E\u957F\u3001\u793E\u533A\u548C\u53D1\u5E03\u8282\u594F\u3002",
descEn: "A creator and indie-builder skill stack for writing, product, growth, community, and shipping rhythm.",
category: "\u521B\u4F5C\u8005\u6280\u80FD",
categoryEn: "Creator Skills",
starts: "8.4k",
accentColor: "#f97316"
}),
cloudPlay("superclaude-buddy", {
title: "SuperClaude Buddy",
titleEn: "SuperClaude Buddy",
desc: "SuperClaude \u6307\u4EE4\u3001\u89D2\u8272\u548C\u5DE5\u4F5C\u6D41\u80FD\u529B\uFF0C\u5E2E\u52A9\u56E2\u961F\u628A Claude \u7528\u6210\u7ED3\u6784\u5316\u5DE5\u7A0B\u4F19\u4F34\u3002",
descEn: "SuperClaude commands, personas, and workflows for structured engineering collaboration.",
category: "\u5F00\u53D1\u6280\u80FD",
categoryEn: "Developer Skills",
starts: "18.9k",
accentColor: "#818cf8",
hot: true
}),
cloudPlay("superpowers-buddy", {
title: "Superpowers Buddy",
titleEn: "Superpowers Buddy",
desc: "\u4E2A\u4EBA\u751F\u4EA7\u529B\u8D85\u80FD\u529B\u7EC4\u5408\uFF1A\u9605\u8BFB\u3001\u5199\u4F5C\u3001\u4EFB\u52A1\u3001\u7814\u7A76\u3001\u81EA\u52A8\u5316\u548C\u590D\u76D8\u3002",
descEn: "Personal productivity superpowers for reading, writing, tasks, research, automation, and retros.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "12.4k",
accentColor: "#2dd4bf"
}),
{
id: "e-wife",
image: playCover("e-wife"),
title: "\u7535\u5B50\u8001\u5A46",
titleEn: "E-Wife",
desc: "\u4E00\u4E2A\u5E26\u6709\u966A\u4F34\u611F\u7684\u865A\u62DF\u751F\u6D3B\u4F19\u4F34\u73A9\u6CD5\uFF0C\u540E\u7EED\u4F1A\u63A5\u5165\u4E2A\u6027\u5316\u8BB0\u5FC6\u548C\u79C1\u6709\u623F\u95F4\u3002",
descEn: "A companion-style virtual life partner play, later connected to memory and private rooms.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "22.0k",
accentColor: "#f0abfc",
status: "available",
action: roomAction("e-wife"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("e-wife")
}
];
function getDefaultHomePlay(playId) {
return DEFAULT_HOMEPLAY_CATALOG.find((play) => play.id === playId) ?? null;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
DEFAULT_HOMEPLAY_CATALOG,
SHADOW_PLAY_SERVER_TEMPLATE,
getDefaultHomePlay,
getPlayBuddyEmail,
getPlayBuddyUsername
});
type ShadowPlayAvailability = 'available' | 'gated' | 'coming_soon' | 'misconfigured';
type PlayActionBase = {
buddyUserIds?: string[];
buddyTemplateSlug?: string;
greeting?: string;
};
type ShadowPlayAction = (PlayActionBase & {
kind: 'public_channel';
serverId?: string;
serverSlug?: string;
channelId?: string;
channelName?: string;
inviteCode?: string;
}) | (PlayActionBase & {
kind: 'private_room';
serverId?: string;
serverSlug?: string;
namePrefix?: string;
}) | (PlayActionBase & {
kind: 'cloud_deploy';
templateSlug: string;
resourceTier?: 'lightweight' | 'standard' | 'pro';
defaultChannelName?: string;
}) | {
kind: 'external_oauth_app';
clientId: string;
redirectUri: string;
scopes?: string[];
state?: string;
} | {
kind: 'landing_page';
url: string;
};
interface ShadowHomePlayCatalogItem {
id: string;
image: string;
title: string;
titleEn: string;
desc: string;
descEn: string;
category: string;
categoryEn: string;
starts: string;
accentColor: string;
hot?: boolean;
status: ShadowPlayAvailability;
action?: ShadowPlayAction;
gates?: {
auth?: 'optional' | 'required';
membership?: 'none' | 'required';
profile?: 'optional' | 'required';
};
template?: {
kind: 'cloud';
slug: string;
path: string;
};
materials?: {
cover: string;
};
}
interface ShadowPlayServerTemplate {
slug: string;
name: string;
description: string;
channels: Array<{
name: string;
topic: string;
}>;
}
declare function getPlayBuddyUsername(templateSlug: string): string;
declare function getPlayBuddyEmail(templateSlug: string): string;
declare const SHADOW_PLAY_SERVER_TEMPLATE: ShadowPlayServerTemplate;
declare const DEFAULT_HOMEPLAY_CATALOG: ShadowHomePlayCatalogItem[];
declare function getDefaultHomePlay(playId: string): ShadowHomePlayCatalogItem | null;
export { DEFAULT_HOMEPLAY_CATALOG, SHADOW_PLAY_SERVER_TEMPLATE, type ShadowHomePlayCatalogItem, type ShadowPlayAction, type ShadowPlayAvailability, type ShadowPlayServerTemplate, getDefaultHomePlay, getPlayBuddyEmail, getPlayBuddyUsername };
type ShadowPlayAvailability = 'available' | 'gated' | 'coming_soon' | 'misconfigured';
type PlayActionBase = {
buddyUserIds?: string[];
buddyTemplateSlug?: string;
greeting?: string;
};
type ShadowPlayAction = (PlayActionBase & {
kind: 'public_channel';
serverId?: string;
serverSlug?: string;
channelId?: string;
channelName?: string;
inviteCode?: string;
}) | (PlayActionBase & {
kind: 'private_room';
serverId?: string;
serverSlug?: string;
namePrefix?: string;
}) | (PlayActionBase & {
kind: 'cloud_deploy';
templateSlug: string;
resourceTier?: 'lightweight' | 'standard' | 'pro';
defaultChannelName?: string;
}) | {
kind: 'external_oauth_app';
clientId: string;
redirectUri: string;
scopes?: string[];
state?: string;
} | {
kind: 'landing_page';
url: string;
};
interface ShadowHomePlayCatalogItem {
id: string;
image: string;
title: string;
titleEn: string;
desc: string;
descEn: string;
category: string;
categoryEn: string;
starts: string;
accentColor: string;
hot?: boolean;
status: ShadowPlayAvailability;
action?: ShadowPlayAction;
gates?: {
auth?: 'optional' | 'required';
membership?: 'none' | 'required';
profile?: 'optional' | 'required';
};
template?: {
kind: 'cloud';
slug: string;
path: string;
};
materials?: {
cover: string;
};
}
interface ShadowPlayServerTemplate {
slug: string;
name: string;
description: string;
channels: Array<{
name: string;
topic: string;
}>;
}
declare function getPlayBuddyUsername(templateSlug: string): string;
declare function getPlayBuddyEmail(templateSlug: string): string;
declare const SHADOW_PLAY_SERVER_TEMPLATE: ShadowPlayServerTemplate;
declare const DEFAULT_HOMEPLAY_CATALOG: ShadowHomePlayCatalogItem[];
declare function getDefaultHomePlay(playId: string): ShadowHomePlayCatalogItem | null;
export { DEFAULT_HOMEPLAY_CATALOG, SHADOW_PLAY_SERVER_TEMPLATE, type ShadowHomePlayCatalogItem, type ShadowPlayAction, type ShadowPlayAvailability, type ShadowPlayServerTemplate, getDefaultHomePlay, getPlayBuddyEmail, getPlayBuddyUsername };
import {
DEFAULT_HOMEPLAY_CATALOG,
SHADOW_PLAY_SERVER_TEMPLATE,
getDefaultHomePlay,
getPlayBuddyEmail,
getPlayBuddyUsername
} from "../chunk-EXZEQO5X.js";
export {
DEFAULT_HOMEPLAY_CATALOG,
SHADOW_PLAY_SERVER_TEMPLATE,
getDefaultHomePlay,
getPlayBuddyEmail,
getPlayBuddyUsername
};
+3
-4

@@ -47,4 +47,3 @@ "use strict";

REACTION_REMOVE: "reaction:remove",
NOTIFICATION_NEW: "notification:new",
DM_MESSAGE_NEW: "dm:message:new"
NOTIFICATION_NEW: "notification:new"
};

@@ -54,4 +53,4 @@

var LIMITS = {
/** Max message content length */
MESSAGE_CONTENT_MAX: 4e3,
/** Max message content length (16KB — enough for detailed agent responses) */
MESSAGE_CONTENT_MAX: 16e3,
/** Max username length */

@@ -58,0 +57,0 @@ USERNAME_MAX: 32,

@@ -19,3 +19,2 @@ declare const CLIENT_EVENTS: {

readonly NOTIFICATION_NEW: "notification:new";
readonly DM_MESSAGE_NEW: "dm:message:new";
};

@@ -26,4 +25,4 @@ type ClientEvent = (typeof CLIENT_EVENTS)[keyof typeof CLIENT_EVENTS];

declare const LIMITS: {
/** Max message content length */
readonly MESSAGE_CONTENT_MAX: 4000;
/** Max message content length (16KB — enough for detailed agent responses) */
readonly MESSAGE_CONTENT_MAX: 16000;
/** Max username length */

@@ -30,0 +29,0 @@ readonly USERNAME_MAX: 32;

@@ -19,3 +19,2 @@ declare const CLIENT_EVENTS: {

readonly NOTIFICATION_NEW: "notification:new";
readonly DM_MESSAGE_NEW: "dm:message:new";
};

@@ -26,4 +25,4 @@ type ClientEvent = (typeof CLIENT_EVENTS)[keyof typeof CLIENT_EVENTS];

declare const LIMITS: {
/** Max message content length */
readonly MESSAGE_CONTENT_MAX: 4000;
/** Max message content length (16KB — enough for detailed agent responses) */
readonly MESSAGE_CONTENT_MAX: 16000;
/** Max username length */

@@ -30,0 +29,0 @@ readonly USERNAME_MAX: 32;

@@ -5,3 +5,3 @@ import {

SERVER_EVENTS
} from "../chunk-EMLX23LF.js";
} from "../chunk-DMUZB4WV.js";
export {

@@ -8,0 +8,0 @@ CLIENT_EVENTS,

@@ -25,4 +25,11 @@ "use strict";

CLIENT_EVENTS: () => CLIENT_EVENTS,
DEFAULT_HOMEPLAY_CATALOG: () => DEFAULT_HOMEPLAY_CATALOG,
LIMITS: () => LIMITS,
SERVER_EVENTS: () => SERVER_EVENTS,
SHADOW_PLAY_SERVER_TEMPLATE: () => SHADOW_PLAY_SERVER_TEMPLATE,
assignMentionRanges: () => assignMentionRanges,
buildMentionMarkdownLinks: () => buildMentionMarkdownLinks,
canonicalMentionToken: () => canonicalMentionToken,
canonicalizeMentionContent: () => canonicalizeMentionContent,
escapeMarkdownLinkLabel: () => escapeMarkdownLinkLabel,
formatDate: () => formatDate,

@@ -35,4 +42,11 @@ generateInviteCode: () => generateInviteCode,

getCatSvgString: () => getCatSvgString,
getDefaultHomePlay: () => getDefaultHomePlay,
getPlayBuddyEmail: () => getPlayBuddyEmail,
getPlayBuddyUsername: () => getPlayBuddyUsername,
isCanonicalMentionToken: () => isCanonicalMentionToken,
isValidEmail: () => isValidEmail,
mentionDisplayText: () => mentionDisplayText,
parseCanonicalMentionToken: () => parseCanonicalMentionToken,
renderCatSvg: () => renderCatSvg,
segmentTextByMentions: () => segmentTextByMentions,
slugify: () => slugify

@@ -60,4 +74,3 @@ });

REACTION_REMOVE: "reaction:remove",
NOTIFICATION_NEW: "notification:new",
DM_MESSAGE_NEW: "dm:message:new"
NOTIFICATION_NEW: "notification:new"
};

@@ -67,4 +80,4 @@

var LIMITS = {
/** Max message content length */
MESSAGE_CONTENT_MAX: 4e3,
/** Max message content length (16KB — enough for detailed agent responses) */
MESSAGE_CONTENT_MAX: 16e3,
/** Max username length */

@@ -98,2 +111,424 @@ USERNAME_MAX: 32,

// src/play-catalog/index.ts
var playCover = (id) => `/home-assets/plays/${id}.jpg`;
var playTemplate = (id) => ({
template: {
kind: "cloud",
slug: id,
path: `apps/cloud/templates/${id}.template.json`
},
materials: { cover: playCover(id) }
});
var communityAction = (channelName, greeting) => ({
kind: "public_channel",
channelName,
buddyTemplateSlug: channelName,
...greeting ? { greeting } : {}
});
var roomAction = (namePrefix, greeting) => ({
kind: "private_room",
namePrefix,
buddyTemplateSlug: namePrefix,
...greeting ? { greeting } : {}
});
var cloudAction = (templateSlug, resourceTier = "lightweight", defaultChannelName) => ({
kind: "cloud_deploy",
templateSlug,
buddyTemplateSlug: templateSlug,
resourceTier,
...defaultChannelName ? { defaultChannelName } : {}
});
function getPlayBuddyUsername(templateSlug) {
const normalized = templateSlug.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 27) || "play";
return `play_${normalized}`.slice(0, 32);
}
function getPlayBuddyEmail(templateSlug) {
return `${getPlayBuddyUsername(templateSlug)}@shadowob.bot`;
}
var SHADOW_PLAY_SERVER_TEMPLATE = {
slug: "shadow-plays",
name: "Shadow Plays",
description: "Default public community space for launchable homepage plays.",
channels: [
{
name: "general",
topic: "General discussion for new players and Shadow community members."
},
{
name: "world-pulse",
topic: "A public room for real-time global events and daily signal."
},
{
name: "financial-freedom",
topic: "A public room for lightweight financial freedom simulations and planning prompts."
},
{
name: "ai-werewolf",
topic: "A public room for AI-hosted social deduction sessions."
},
{
name: "code-arena",
topic: "A public room for coding challenges and real-time battles."
},
{
name: "brain-fix",
topic: "A calm public room for one-minute focus resets and reflection."
},
{
name: "gitstory",
topic: "A public room for turning software history into stories and retrospectives."
},
{
name: "gstack",
topic: "A public room for founder strategy, product stress tests, and launch planning."
}
]
};
function cloudPlay(id, input) {
const { defaultChannelName, ...content } = input;
return {
id,
image: playCover(id),
status: "gated",
action: cloudAction(id, "lightweight", defaultChannelName),
gates: { auth: "required", membership: "required", profile: "optional" },
...playTemplate(id),
...content
};
}
var DEFAULT_HOMEPLAY_CATALOG = [
{
id: "retire-buddy",
image: playCover("retire-buddy"),
title: "\u9000\u4F11\u52A9\u624B",
titleEn: "RetireBuddy",
desc: "\u5E2E\u4F60\u89C4\u5212\u9000\u4F11\u751F\u6D3B\u3001\u8D22\u52A1\u81EA\u7531\u8DEF\u5F84\uFF0C24\u5C0F\u65F6\u6E29\u6696\u966A\u4F34\uFF0C\u8BA9\u544A\u522B\u804C\u573A\u53D8\u6210\u4EBA\u751F\u65B0\u7AE0\u8282\u3002",
descEn: "Plan your retirement and path to financial freedom with a warm 24/7 companion.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "24.5k",
accentColor: "var(--shadow-accent)",
hot: true,
status: "available",
action: roomAction("retire-buddy"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("retire-buddy")
},
{
id: "financial-freedom",
image: playCover("financial-freedom"),
title: "\u6211\u8D22\u5BCC\u81EA\u7531\u4E86\u5417\uFF1F",
titleEn: "Am I Free?",
desc: "\u8F93\u5165\u4F60\u7684\u8D44\u4EA7\u4E0E\u652F\u51FA\uFF0CAI \u4E3A\u4F60\u8BA1\u7B97\u8D22\u52A1\u81EA\u7531\u8DDD\u79BB\uFF0C\u7ED9\u51FA\u6E05\u6670\u7684\u8FBE\u6210\u8DEF\u7EBF\u56FE\u3002",
descEn: "Input your assets and expenses \u2014 get your financial freedom score and roadmap.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "18.2k",
accentColor: "#f8e71c",
status: "available",
action: communityAction("financial-freedom"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("financial-freedom")
},
{
id: "brain-fix",
image: playCover("brain-fix"),
title: "\u4E00\u5206\u949F\u4FEE\u590D\u4F60\u7684\u5927\u8111\uFF01",
titleEn: "1-Min Brain Fix",
desc: "\u79D1\u5B66\u51A5\u60F3 + \u5FAE\u547C\u5438\u7EC3\u4E60\uFF0C60\u79D2\u5185\u4ECE\u7126\u8651\u6A21\u5F0F\u5207\u6362\u5230\u4E13\u6CE8\u72B6\u6001\uFF0C\u5C61\u8BD5\u4E0D\u723D\u3002",
descEn: "Science-backed micro-meditation. Switch from anxious to focused in 60 seconds.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "15.9k",
accentColor: "#a78bfa",
status: "available",
action: communityAction("brain-fix"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("brain-fix")
},
{
id: "world-pulse",
image: playCover("world-pulse"),
title: "\u5730\u7403\u8109\u640F",
titleEn: "World Pulse",
desc: "\u5B9E\u65F6\u6293\u53D6\u5168\u7403\u91CD\u5927\u4E8B\u4EF6\uFF0C\u7528\u4E09\u53E5\u8BDD\u544A\u8BC9\u4F60\u4ECA\u5929\u771F\u6B63\u53D1\u751F\u4E86\u4EC0\u4E48\uFF0C\u65E0\u5E9F\u8BDD\u3002",
descEn: "Real-time global events in 3 sentences. No filler, just signal.",
category: "\u4E16\u754C\u8D44\u8BAF",
categoryEn: "World News",
starts: "14.1k",
accentColor: "#38bdf8",
status: "available",
action: communityAction("world-pulse"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("world-pulse")
},
{
id: "daily-brief",
image: playCover("daily-brief"),
title: "\u6668\u95F4\u7B80\u62A5",
titleEn: "Morning Brief",
desc: "\u6BCF\u5929 7:00 \u63A8\u9001\u4E00\u4EFD\u5B9A\u5236\u65E9\u62A5\uFF1A\u56FD\u9645\u3001\u79D1\u6280\u3001\u5E02\u573A\u4E09\u5927\u677F\u5757\uFF0C\u8BFB\u5B8C\u53EA\u9700 3 \u5206\u949F\u3002",
descEn: "Custom morning digest at 7am \u2014 global news, tech, markets. 3-minute read.",
category: "\u4E16\u754C\u8D44\u8BAF",
categoryEn: "World News",
starts: "11.3k",
accentColor: "#fb923c",
status: "available",
action: roomAction("daily-brief"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("daily-brief")
},
{
id: "ai-werewolf",
image: playCover("ai-werewolf"),
title: "AI \u72FC\u4EBA\u6740",
titleEn: "AI Werewolf",
desc: "AI \u62C5\u4EFB\u4E3B\u6301\uFF0C\u968F\u673A\u5206\u914D\u8EAB\u4EFD\uFF0C\u5728\u804A\u5929\u4E2D\u5C55\u5F00\u63A8\u7406\u4E0E\u535A\u5F08\uFF0C3 \u4EBA\u5373\u53EF\u5F00\u5C40\u3002",
descEn: "AI-hosted werewolf \u2014 roles assigned randomly, deduce, bluff, and vote. 3+ players.",
category: "\u4E92\u52A8\u6E38\u620F",
categoryEn: "Games",
starts: "20.8k",
accentColor: "#f87171",
hot: true,
status: "available",
action: communityAction("ai-werewolf"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("ai-werewolf")
},
{
id: "code-arena",
image: playCover("code-arena"),
title: "\u4EE3\u7801\u64C2\u53F0",
titleEn: "Code Arena",
desc: "\u5B9E\u65F6\u7F16\u7A0B\u5BF9\u6218\uFF0CAI \u51FA\u9898\u3001\u8BA1\u65F6\u3001\u81EA\u52A8\u8BC4\u6D4B\uFF0C\u6311\u6218\u597D\u53CB\u6216\u5339\u914D\u964C\u751F\u5BF9\u624B\u3002",
descEn: "Real-time coding battles \u2014 AI generates problems, auto-judges, ranks you live.",
category: "\u4E92\u52A8\u6E38\u620F",
categoryEn: "Games",
starts: "8.6k",
accentColor: "#fbbf24",
status: "available",
action: communityAction("code-arena"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("code-arena")
},
{
id: "gitstory",
image: playCover("gitstory"),
title: "GitStory",
titleEn: "GitStory",
desc: "\u628A\u4F60\u7684 GitHub \u63D0\u4EA4\u5386\u53F2\u53D8\u6210\u4E00\u672C\u81EA\u4F20\u5C0F\u8BF4\u2014\u2014AI \u5E2E\u4F60\u56DE\u987E\u6BCF\u4E00\u6BB5\u4EE3\u7801\u80CC\u540E\u7684\u6545\u4E8B\u3002",
descEn: "Turn your GitHub commits into an autobiography. Every line of code has a story.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "12.1k",
accentColor: "#34d399",
hot: true,
status: "available",
action: communityAction("gitstory"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("gitstory")
},
{
id: "gstack",
image: playCover("gstack"),
title: "gstack",
titleEn: "gstack",
desc: "\u521B\u4E1A\u8005\u7684 AI \u53C2\u8C0B\uFF0C\u5E2E\u4F60\u5FEB\u901F\u9A8C\u8BC1\u5546\u4E1A\u60F3\u6CD5\u3001\u5206\u6790\u7ADE\u4E89\u683C\u5C40\u3001\u751F\u6210\u878D\u8D44\u6587\u4EF6\u3002",
descEn: "AI co-founder for founders. Validate ideas, map competitors, generate pitch decks.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "9.3k",
accentColor: "#f97316",
status: "available",
action: communityAction("gstack"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("gstack")
},
{
id: "little-match-girl",
image: "/home-assets/topics/night-radio.jpg",
title: "\u5356\u706B\u67F4\u7684\u5C0F\u5973\u5B69",
titleEn: "Little Match Girl",
desc: "\u90E8\u7F72\u4E00\u4E2A\u4F1A\u63A8\u9500\u706B\u67F4\u7684\u7AE5\u8BDD Buddy\uFF0C\u8D2D\u4E70\u540E\u5728\u804A\u5929\u53F3\u4FA7\u6253\u5F00\u706B\u67F4\u52A8\u753B\u4ED8\u8D39\u6587\u4EF6\u3002",
descEn: "Deploy a fairy-tale Buddy who sells glowing matches and unlocks a paid HTML flame animation.",
category: "MVP \u5B9E\u9A8C",
categoryEn: "MVP Labs",
starts: "1.2k",
accentColor: "#f59e0b",
hot: true,
status: "gated",
action: cloudAction("little-match-girl", "lightweight", "match-street"),
gates: { auth: "required", membership: "required", profile: "optional" },
...playTemplate("little-match-girl"),
materials: { cover: "/home-assets/topics/night-radio.jpg" }
},
cloudPlay("agent-marketplace-buddy", {
title: "Agent Marketplace Buddy",
titleEn: "Agent Marketplace Buddy",
desc: "\u53EF\u7EC4\u5408\u4E13\u5BB6 agent \u5E02\u573A\uFF0C\u8986\u76D6\u5F00\u53D1\u3001\u5B89\u5168\u3001\u57FA\u7840\u8BBE\u65BD\u3001\u6570\u636E\u3001\u6587\u6863\u3001SEO \u548C workflow \u7F16\u6392\u3002",
descEn: "A composable specialist-agent marketplace for development, security, infra, data, docs, SEO, and workflow orchestration.",
category: "Buddy \u56E2\u961F",
categoryEn: "Buddy Teams",
starts: "16.4k",
accentColor: "#22d3ee",
hot: true,
defaultChannelName: "choose"
}),
cloudPlay("bmad-method-buddy", {
title: "BMAD \u65B9\u6CD5 Buddy",
titleEn: "BMAD Method Buddy",
desc: "\u5B8C\u6574 BMAD \u65B9\u6CD5\u8BBA\u56E2\u961F\uFF1A\u5206\u6790\u5E08\u3001PM\u3001\u67B6\u6784\u5E08\u3001Scrum Master\u3001\u5F00\u53D1\u3001QA\uFF0C\u8D2F\u7A7F\u89C4\u5212\u5230\u4EA4\u4ED8\u3002",
descEn: "A full BMAD method team: analyst, PM, architect, scrum master, dev, and QA from planning to delivery.",
category: "Buddy \u56E2\u961F",
categoryEn: "Buddy Teams",
starts: "13.7k",
accentColor: "#60a5fa",
defaultChannelName: "delivery"
}),
cloudPlay("claude-ads-buddy", {
title: "Claude Ads Buddy",
titleEn: "Claude Ads Buddy",
desc: "\u4ED8\u8D39\u6295\u653E\u8BCA\u65AD\u3001\u9884\u7B97\u5EFA\u6A21\u3001\u521B\u610F\u5BA1\u67E5\u3001\u8FFD\u8E2A\u95EE\u9898\u548C\u843D\u5730\u9875\u74F6\u9888\u5206\u6790\u3002",
descEn: "Paid ads audits, budget models, creative review, tracking issues, and landing-page bottlenecks.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "10.8k",
accentColor: "#fb7185"
}),
cloudPlay("claude-seo-buddy", {
title: "Claude SEO Buddy",
titleEn: "Claude SEO Buddy",
desc: "SEO \u5185\u5BB9\u548C\u6280\u672F\u5BA1\u67E5\u56E2\u961F\uFF0C\u8986\u76D6\u5173\u952E\u8BCD\u3001\u5185\u94FE\u3001\u7ED3\u6784\u5316\u6570\u636E\u3001\u9875\u9762\u8D28\u91CF\u548C\u589E\u957F\u8BA1\u5212\u3002",
descEn: "SEO content and technical review for keywords, links, schema, quality, and growth plans.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "12.6k",
accentColor: "#84cc16"
}),
cloudPlay("everything-claude-code-buddy", {
title: "Everything Claude Code Buddy",
titleEn: "Everything Claude Code Buddy",
desc: "Claude Code \u5DE5\u4F5C\u6D41\u3001\u547D\u4EE4\u548C\u5DE5\u7A0B\u5B9E\u8DF5\u5408\u96C6\uFF0C\u9002\u5408\u7814\u53D1\u56E2\u961F\u6C89\u6DC0\u81EA\u52A8\u5316\u80FD\u529B\u3002",
descEn: "Claude Code workflows, commands, and engineering practices for automation-heavy teams.",
category: "\u5F00\u53D1\u6280\u80FD",
categoryEn: "Developer Skills",
starts: "19.2k",
accentColor: "#c084fc",
hot: true
}),
cloudPlay("google-workspace-buddy", {
title: "Google Workspace Buddy",
titleEn: "Google Workspace Buddy",
desc: "\u628A Docs\u3001Sheets\u3001Drive\u3001\u65E5\u5386\u548C\u90AE\u4EF6\u534F\u4F5C\u7F16\u6392\u5230 Buddy \u5DE5\u4F5C\u6D41\u91CC\u3002",
descEn: "Coordinate Docs, Sheets, Drive, Calendar, and email collaboration through Buddy workflows.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "9.9k",
accentColor: "#34d399"
}),
cloudPlay("gsd-buddy", {
title: "GSD Buddy",
titleEn: "GSD Buddy",
desc: "\u6267\u884C\u529B\u56E2\u961F\uFF1A\u62C6\u89E3\u4EFB\u52A1\u3001\u6392\u4F18\u5148\u7EA7\u3001\u63A8\u52A8\u51B3\u7B56\u3001\u8FFD\u8E2A\u963B\u585E\uFF0C\u5E2E\u56E2\u961F\u6301\u7EED get stuff done\u3002",
descEn: "Execution team for task breakdown, priority, decisions, blockers, and getting stuff done.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "17.5k",
accentColor: "#facc15",
hot: true
}),
cloudPlay("gstack-buddy", {
title: "gstack \u6218\u7565 Buddy",
titleEn: "gstack Strategy Buddy",
desc: "YC \u98CE\u683C\u4EA7\u54C1\u538B\u529B\u6D4B\u8BD5\u3001CEO \u89C6\u89D2\u8303\u56F4\u8BC4\u5BA1\u3001\u8C03\u67E5\u7EAA\u5F8B\u3001\u5468\u590D\u76D8\u548C gstack \u811A\u672C\u5DE5\u5177\u3002",
descEn: "YC-style product pressure testing, CEO scope review, investigation discipline, retros, and gstack scripts.",
category: "\u9ED1\u5BA2\u4E0E\u753B\u5BB6",
categoryEn: "Hacker & Painter",
starts: "15.1k",
accentColor: "#fb923c",
hot: true,
defaultChannelName: "office-hours"
}),
cloudPlay("marketingskills-buddy", {
title: "\u8425\u9500\u6280\u80FD Buddy",
titleEn: "MarketingSkills Buddy",
desc: "\u589E\u957F\u56E2\u961F\u7684\u8425\u9500\u534F\u4F5C\u667A\u80FD\u4F53\uFF0C\u8986\u76D6 CRO\u3001\u6587\u6848\u3001SEO\u3001\u4ED8\u8D39\u3001\u90AE\u4EF6\u548C\u589E\u957F\u51B3\u7B56\u3002",
descEn: "Marketing collaboration agents for CRO, copy, SEO, paid, email, and growth decisions.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "11.7k",
accentColor: "#f472b6"
}),
cloudPlay("scientific-skills-buddy", {
title: "\u79D1\u7814\u6280\u80FD Buddy",
titleEn: "Scientific Skills Buddy",
desc: "\u7814\u7A76\u9605\u8BFB\u3001\u5B9E\u9A8C\u8BBE\u8BA1\u3001\u8BBA\u6587\u7ED3\u6784\u3001\u6570\u636E\u5206\u6790\u548C\u5B66\u672F\u5199\u4F5C\u534F\u4F5C\u56E2\u961F\u3002",
descEn: "Research reading, experiment design, paper structure, data analysis, and academic writing workflows.",
category: "\u79D1\u7814\u6280\u80FD",
categoryEn: "Research Skills",
starts: "7.8k",
accentColor: "#38bdf8"
}),
cloudPlay("seomachine-buddy", {
title: "SEO Machine Buddy",
titleEn: "SEO Machine Buddy",
desc: "\u6301\u7EED\u8FD0\u884C\u7684 SEO \u673A\u5668\uFF1A\u9009\u9898\u3001brief\u3001\u5185\u5BB9\u5BA1\u67E5\u3001\u6280\u672F\u68C0\u67E5\u548C\u6392\u540D\u590D\u76D8\u3002",
descEn: "An always-on SEO machine for topics, briefs, review, technical checks, and ranking retros.",
category: "\u8425\u9500\u6280\u80FD",
categoryEn: "Marketing Skills",
starts: "10.2k",
accentColor: "#a3e635"
}),
cloudPlay("slavingia-skills-buddy", {
title: "Slavingia Skills Buddy",
titleEn: "Slavingia Skills Buddy",
desc: "\u521B\u4F5C\u8005\u548C\u72EC\u7ACB\u5F00\u53D1\u8005\u7684\u6280\u80FD\u5E93\uFF0C\u8986\u76D6\u5199\u4F5C\u3001\u4EA7\u54C1\u3001\u589E\u957F\u3001\u793E\u533A\u548C\u53D1\u5E03\u8282\u594F\u3002",
descEn: "A creator and indie-builder skill stack for writing, product, growth, community, and shipping rhythm.",
category: "\u521B\u4F5C\u8005\u6280\u80FD",
categoryEn: "Creator Skills",
starts: "8.4k",
accentColor: "#f97316"
}),
cloudPlay("superclaude-buddy", {
title: "SuperClaude Buddy",
titleEn: "SuperClaude Buddy",
desc: "SuperClaude \u6307\u4EE4\u3001\u89D2\u8272\u548C\u5DE5\u4F5C\u6D41\u80FD\u529B\uFF0C\u5E2E\u52A9\u56E2\u961F\u628A Claude \u7528\u6210\u7ED3\u6784\u5316\u5DE5\u7A0B\u4F19\u4F34\u3002",
descEn: "SuperClaude commands, personas, and workflows for structured engineering collaboration.",
category: "\u5F00\u53D1\u6280\u80FD",
categoryEn: "Developer Skills",
starts: "18.9k",
accentColor: "#818cf8",
hot: true
}),
cloudPlay("superpowers-buddy", {
title: "Superpowers Buddy",
titleEn: "Superpowers Buddy",
desc: "\u4E2A\u4EBA\u751F\u4EA7\u529B\u8D85\u80FD\u529B\u7EC4\u5408\uFF1A\u9605\u8BFB\u3001\u5199\u4F5C\u3001\u4EFB\u52A1\u3001\u7814\u7A76\u3001\u81EA\u52A8\u5316\u548C\u590D\u76D8\u3002",
descEn: "Personal productivity superpowers for reading, writing, tasks, research, automation, and retros.",
category: "\u6548\u7387\u5DE5\u5177",
categoryEn: "Productivity",
starts: "12.4k",
accentColor: "#2dd4bf"
}),
{
id: "e-wife",
image: playCover("e-wife"),
title: "\u7535\u5B50\u8001\u5A46",
titleEn: "E-Wife",
desc: "\u4E00\u4E2A\u5E26\u6709\u966A\u4F34\u611F\u7684\u865A\u62DF\u751F\u6D3B\u4F19\u4F34\u73A9\u6CD5\uFF0C\u540E\u7EED\u4F1A\u63A5\u5165\u4E2A\u6027\u5316\u8BB0\u5FC6\u548C\u79C1\u6709\u623F\u95F4\u3002",
descEn: "A companion-style virtual life partner play, later connected to memory and private rooms.",
category: "\u5FC3\u7406\u7597\u6108",
categoryEn: "Healing",
starts: "22.0k",
accentColor: "#f0abfc",
status: "available",
action: roomAction("e-wife"),
gates: { auth: "required", membership: "none", profile: "optional" },
...playTemplate("e-wife")
}
];
function getDefaultHomePlay(playId) {
return DEFAULT_HOMEPLAY_CATALOG.find((play) => play.id === playId) ?? null;
}
// src/utils/index.ts

@@ -284,2 +719,186 @@ var import_nanoid = require("nanoid");

// src/utils/message-mentions.ts
function uniqueValues(values) {
return Array.from(new Set(values.filter((value) => !!value)));
}
function canonicalMentionToken(mention) {
if (mention.kind === "channel") return `<#${mention.channelId ?? mention.targetId}>`;
if (mention.kind === "server") return `<@server:${mention.serverId ?? mention.targetId}>`;
if (mention.kind === "here" || mention.kind === "everyone") {
const scope = mention.serverId ?? mention.targetId;
return scope ? `<!${mention.kind}:${scope}>` : `<!${mention.kind}>`;
}
return `<@${mention.userId ?? mention.targetId}>`;
}
function parseCanonicalMentionToken(token) {
const user = token.match(/^<@([^>:]+)>$/u);
if (user?.[1]) return { kind: "user", targetId: user[1] };
const server = token.match(/^<@server:([^>]+)>$/u);
if (server?.[1]) return { kind: "server", targetId: server[1] };
const channel = token.match(/^<#([^>]+)>$/u);
if (channel?.[1]) return { kind: "channel", targetId: channel[1] };
const broadcast = token.match(/^<!(here|everyone)(?::([^>]+))?>$/u);
if (broadcast?.[1] === "here" || broadcast?.[1] === "everyone") {
return {
kind: broadcast[1],
...broadcast[2] ? { targetId: broadcast[2] } : {}
};
}
return null;
}
function isCanonicalMentionToken(token) {
return parseCanonicalMentionToken(token) !== null;
}
function matchTextsForMention(mention) {
return uniqueValues([mention.token, mention.sourceToken, canonicalMentionToken(mention)]);
}
function isValidRange(content, mention, range) {
if (!range) return false;
if (!Number.isInteger(range.start) || !Number.isInteger(range.end)) return false;
if (range.start < 0 || range.end <= range.start || range.end > content.length) return false;
const sourceText = content.slice(range.start, range.end);
return matchTextsForMention(mention).includes(sourceText);
}
function overlaps(a, b) {
return a.start < b.end && b.start < a.end;
}
function canUseRange(range, used) {
return !used.some((candidate) => overlaps(candidate, range));
}
function findOccurrences(content, token, used) {
const occurrences = [];
let index = content.indexOf(token);
while (index >= 0) {
const range = { start: index, end: index + token.length };
if (canUseRange(range, used)) occurrences.push(range);
index = content.indexOf(token, index + token.length);
}
return occurrences;
}
function addCandidate(candidates, used, mention, range, order, sourceText) {
if (!canUseRange(range, used)) return;
const matchedText = sourceText ?? mention.sourceToken ?? mention.token;
used.push(range);
candidates.push({
start: range.start,
end: range.end,
order,
sourceText: matchedText,
mention: { ...mention, range }
});
}
function segmentTextByMentions(content, mentions) {
if (!content) return [];
const usableMentions = (mentions ?? []).filter((mention) => mention.token);
if (usableMentions.length === 0) return [{ type: "text", text: content }];
const candidates = [];
const usedRanges = [];
const pendingByText = /* @__PURE__ */ new Map();
usableMentions.forEach((mention, order) => {
if (isValidRange(content, mention, mention.range)) {
addCandidate(
candidates,
usedRanges,
mention,
mention.range,
order,
content.slice(mention.range.start, mention.range.end)
);
return;
}
for (const text of matchTextsForMention(mention)) {
const pending = pendingByText.get(text) ?? [];
pending.push({ mention, order });
pendingByText.set(text, pending);
}
});
const pendingGroups = Array.from(pendingByText.entries()).sort((a, b) => {
const lengthDelta = b[0].length - a[0].length;
if (lengthDelta !== 0) return lengthDelta;
return a[1][0].order - b[1][0].order;
});
for (const [token, pending] of pendingGroups) {
const occurrences = findOccurrences(content, token, usedRanges);
if (occurrences.length === 0) continue;
if (pending.length === 1) {
const { mention, order } = pending[0];
for (const range of occurrences) {
addCandidate(candidates, usedRanges, mention, range, order, token);
}
continue;
}
pending.forEach(({ mention, order }, index) => {
const range = occurrences[index];
if (range) addCandidate(candidates, usedRanges, mention, range, order, token);
});
}
candidates.sort((a, b) => a.start - b.start || b.end - a.end || a.order - b.order);
const segments = [];
let cursor = 0;
for (const candidate of candidates) {
if (candidate.start < cursor) continue;
if (candidate.start > cursor) {
segments.push({ type: "text", text: content.slice(cursor, candidate.start) });
}
const text = content.slice(candidate.start, candidate.end);
segments.push({
type: "mention",
text,
range: { start: candidate.start, end: candidate.end },
mention: { ...candidate.mention, sourceToken: candidate.sourceText }
});
cursor = candidate.end;
}
if (cursor < content.length) {
segments.push({ type: "text", text: content.slice(cursor) });
}
return segments.length > 0 ? segments : [{ type: "text", text: content }];
}
function assignMentionRanges(content, mentions) {
return segmentTextByMentions(content, mentions).filter((segment) => {
return segment.type === "mention";
}).map((segment) => ({
...segment.mention,
token: segment.mention.token || segment.text,
range: segment.range
}));
}
function canonicalizeMentionContent(content, mentions) {
const canonicalMentions = [];
let nextContent = "";
for (const segment of segmentTextByMentions(content, mentions)) {
if (segment.type === "text") {
nextContent += segment.text;
continue;
}
const token = canonicalMentionToken(segment.mention);
const start = nextContent.length;
nextContent += token;
canonicalMentions.push({
...segment.mention,
token,
sourceToken: segment.text === token ? segment.mention.sourceToken : segment.text,
range: { start, end: start + token.length }
});
}
return { content: nextContent, mentions: canonicalMentions };
}
function mentionDisplayText(mention) {
return mention.label || mention.token;
}
function escapeMarkdownLinkLabel(label) {
return label.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
}
function buildMentionMarkdownLinks(content, mentions, hrefForMention) {
const linkedMentions = [];
const markdown = segmentTextByMentions(content, mentions).map((segment) => {
if (segment.type === "text") return segment.text;
const href = hrefForMention(segment.mention, linkedMentions.length);
if (!href) return segment.text;
linkedMentions.push(segment.mention);
return `[${escapeMarkdownLinkLabel(mentionDisplayText(segment.mention))}](${href})`;
}).join("");
return { markdown, mentions: linkedMentions };
}
// src/utils/pixel-cats.ts

@@ -418,4 +1037,11 @@ var variants = [

CLIENT_EVENTS,
DEFAULT_HOMEPLAY_CATALOG,
LIMITS,
SERVER_EVENTS,
SHADOW_PLAY_SERVER_TEMPLATE,
assignMentionRanges,
buildMentionMarkdownLinks,
canonicalMentionToken,
canonicalizeMentionContent,
escapeMarkdownLinkLabel,
formatDate,

@@ -428,5 +1054,12 @@ generateInviteCode,

getCatSvgString,
getDefaultHomePlay,
getPlayBuddyEmail,
getPlayBuddyUsername,
isCanonicalMentionToken,
isValidEmail,
mentionDisplayText,
parseCanonicalMentionToken,
renderCatSvg,
segmentTextByMentions,
slugify
});
export { CLIENT_EVENTS, ClientEvent, LIMITS, SERVER_EVENTS, ServerEvent } from './constants/index.cjs';
export { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, Attachment, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, DmAttachment, DmChannel, DmMessage, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, Message, Notification, NotificationType, ReactionGroup, RegisterRequest, SendMessageRequest, Server, Thread, UpdateChannelRequest, UpdateDmMessageRequest, UpdateMessageRequest, UpdateServerRequest, User, UserProfile, UserStatus } from './types/index.cjs';
export { BgPattern, CAT_AVATAR_COUNT, CatConfig, CatDecoration, CatExpression, CatPattern, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isValidEmail, renderCatSvg, slugify } from './utils/index.cjs';
export { DEFAULT_HOMEPLAY_CATALOG, SHADOW_PLAY_SERVER_TEMPLATE, ShadowHomePlayCatalogItem, ShadowPlayAction, ShadowPlayAvailability, ShadowPlayServerTemplate, getDefaultHomePlay, getPlayBuddyEmail, getPlayBuddyUsername } from './play-catalog/index.cjs';
export { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, RegisterRequest, Server, UpdateChannelRequest, UpdateServerRequest, User, UserMembership, UserMembershipTier, UserProfile, UserStatus } from './types/index.cjs';
export { A as Attachment, C as CommerceMessageCard, a as CommerceOfferCardInput, b as CommerceProductCard, M as MentionSuggestion, c as MentionSuggestionTrigger, d as Message, e as MessageMention, f as MessageMentionKind, g as MessageMentionRange, h as MessageMetadata, N as Notification, i as NotificationType, P as PaidFileCard, R as ReactionGroup, S as SendMessageRequest, T as Thread, U as UpdateMessageRequest } from './message.types-lG4qBSus.cjs';
export { BgPattern, CAT_AVATAR_COUNT, CatConfig, CatDecoration, CatExpression, CatPattern, MessageMentionTextSegment, assignMentionRanges, buildMentionMarkdownLinks, canonicalMentionToken, canonicalizeMentionContent, escapeMarkdownLinkLabel, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isCanonicalMentionToken, isValidEmail, mentionDisplayText, parseCanonicalMentionToken, renderCatSvg, segmentTextByMentions, slugify } from './utils/index.cjs';
export { CLIENT_EVENTS, ClientEvent, LIMITS, SERVER_EVENTS, ServerEvent } from './constants/index.js';
export { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, Attachment, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, DmAttachment, DmChannel, DmMessage, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, Message, Notification, NotificationType, ReactionGroup, RegisterRequest, SendMessageRequest, Server, Thread, UpdateChannelRequest, UpdateDmMessageRequest, UpdateMessageRequest, UpdateServerRequest, User, UserProfile, UserStatus } from './types/index.js';
export { BgPattern, CAT_AVATAR_COUNT, CatConfig, CatDecoration, CatExpression, CatPattern, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isValidEmail, renderCatSvg, slugify } from './utils/index.js';
export { DEFAULT_HOMEPLAY_CATALOG, SHADOW_PLAY_SERVER_TEMPLATE, ShadowHomePlayCatalogItem, ShadowPlayAction, ShadowPlayAvailability, ShadowPlayServerTemplate, getDefaultHomePlay, getPlayBuddyEmail, getPlayBuddyUsername } from './play-catalog/index.js';
export { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, RegisterRequest, Server, UpdateChannelRequest, UpdateServerRequest, User, UserMembership, UserMembershipTier, UserProfile, UserStatus } from './types/index.js';
export { A as Attachment, C as CommerceMessageCard, a as CommerceOfferCardInput, b as CommerceProductCard, M as MentionSuggestion, c as MentionSuggestionTrigger, d as Message, e as MessageMention, f as MessageMentionKind, g as MessageMentionRange, h as MessageMetadata, N as Notification, i as NotificationType, P as PaidFileCard, R as ReactionGroup, S as SendMessageRequest, T as Thread, U as UpdateMessageRequest } from './message.types-lG4qBSus.js';
export { BgPattern, CAT_AVATAR_COUNT, CatConfig, CatDecoration, CatExpression, CatPattern, MessageMentionTextSegment, assignMentionRanges, buildMentionMarkdownLinks, canonicalMentionToken, canonicalizeMentionContent, escapeMarkdownLinkLabel, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isCanonicalMentionToken, isValidEmail, mentionDisplayText, parseCanonicalMentionToken, renderCatSvg, segmentTextByMentions, slugify } from './utils/index.js';

@@ -5,6 +5,18 @@ import {

SERVER_EVENTS
} from "./chunk-EMLX23LF.js";
} from "./chunk-DMUZB4WV.js";
import {
DEFAULT_HOMEPLAY_CATALOG,
SHADOW_PLAY_SERVER_TEMPLATE,
getDefaultHomePlay,
getPlayBuddyEmail,
getPlayBuddyUsername
} from "./chunk-EXZEQO5X.js";
import "./chunk-6H4LIJZC.js";
import {
CAT_AVATAR_COUNT,
assignMentionRanges,
buildMentionMarkdownLinks,
canonicalMentionToken,
canonicalizeMentionContent,
escapeMarkdownLinkLabel,
formatDate,

@@ -17,11 +29,22 @@ generateInviteCode,

getCatSvgString,
isCanonicalMentionToken,
isValidEmail,
mentionDisplayText,
parseCanonicalMentionToken,
renderCatSvg,
segmentTextByMentions,
slugify
} from "./chunk-PXKHJSTK.js";
} from "./chunk-E3UBH4AI.js";
export {
CAT_AVATAR_COUNT,
CLIENT_EVENTS,
DEFAULT_HOMEPLAY_CATALOG,
LIMITS,
SERVER_EVENTS,
SHADOW_PLAY_SERVER_TEMPLATE,
assignMentionRanges,
buildMentionMarkdownLinks,
canonicalMentionToken,
canonicalizeMentionContent,
escapeMarkdownLinkLabel,
formatDate,

@@ -34,5 +57,12 @@ generateInviteCode,

getCatSvgString,
getDefaultHomePlay,
getPlayBuddyEmail,
getPlayBuddyUsername,
isCanonicalMentionToken,
isValidEmail,
mentionDisplayText,
parseCanonicalMentionToken,
renderCatSvg,
segmentTextByMentions,
slugify
};

@@ -0,1 +1,3 @@

export { A as Attachment, C as CommerceMessageCard, a as CommerceOfferCardInput, b as CommerceProductCard, M as MentionSuggestion, c as MentionSuggestionTrigger, d as Message, e as MessageMention, f as MessageMentionKind, g as MessageMentionRange, h as MessageMetadata, N as Notification, i as NotificationType, P as PaidFileCard, R as ReactionGroup, S as SendMessageRequest, T as Thread, U as UpdateMessageRequest } from '../message.types-lG4qBSus.cjs';
type AgentStatus = 'running' | 'stopped' | 'error';

@@ -75,3 +77,3 @@ type AgentKernelType = 'claude-code' | 'cursor' | 'mcp-server' | 'custom';

}
type FriendSource = 'friend' | 'owned_claw' | 'rented_claw';
type FriendSource = 'friend' | 'owned_agent' | 'rented_agent';
interface FriendEntry {

@@ -92,118 +94,2 @@ friendshipId: string;

interface Message {
id: string;
content: string;
channelId: string;
authorId: string;
threadId: string | null;
replyToId: string | null;
isEdited: boolean;
isPinned: boolean;
createdAt: string;
updatedAt: string;
author?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
isBot: boolean;
};
attachments?: Attachment[];
reactions?: ReactionGroup[];
}
interface Attachment {
id: string;
messageId: string;
filename: string;
url: string;
contentType: string;
size: number;
width: number | null;
height: number | null;
createdAt: string;
}
interface ReactionGroup {
emoji: string;
count: number;
userIds: string[];
}
interface Thread {
id: string;
name: string;
channelId: string;
parentMessageId: string;
creatorId: string;
isArchived: boolean;
createdAt: string;
updatedAt: string;
}
interface SendMessageRequest {
content: string;
threadId?: string;
replyToId?: string;
}
interface UpdateMessageRequest {
content: string;
}
type NotificationType = 'mention' | 'reply' | 'dm' | 'system';
interface Notification {
id: string;
userId: string;
type: NotificationType;
title: string;
body: string | null;
referenceId: string | null;
referenceType: string | null;
isRead: boolean;
createdAt: string;
}
interface DmChannel {
id: string;
userAId: string;
userBId: string;
lastMessageAt: string | null;
createdAt: string;
otherUser?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
status: string;
};
}
/** DM message — mirrors channel Message but scoped to DM channels */
interface DmMessage {
id: string;
content: string;
dmChannelId: string;
authorId: string;
replyToId: string | null;
isEdited: boolean;
createdAt: string;
updatedAt: string;
author?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
isBot: boolean;
};
attachments?: DmAttachment[];
reactions?: ReactionGroup[];
}
interface DmAttachment {
id: string;
dmMessageId: string;
filename: string;
url: string;
contentType: string;
size: number;
width: number | null;
height: number | null;
createdAt: string;
}
interface UpdateDmMessageRequest {
content: string;
}
interface Server {

@@ -253,2 +139,3 @@ id: string;

isBot: boolean;
membership?: UserMembership;
createdAt: string;

@@ -271,5 +158,6 @@ updatedAt: string;

email: string;
username: string;
displayName: string;
username?: string;
displayName?: string;
password: string;
inviteCode?: string;
}

@@ -281,3 +169,18 @@ interface AuthResponse {

}
interface UserMembershipTier {
id: string;
level: number;
label: string;
capabilities: string[];
}
interface UserMembership {
status: string;
tier: UserMembershipTier;
level: number;
isMember: boolean;
memberSince?: string | null;
inviteCodeId?: string | null;
capabilities: string[];
}
export type { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, Attachment, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, DmAttachment, DmChannel, DmMessage, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, Message, Notification, NotificationType, ReactionGroup, RegisterRequest, SendMessageRequest, Server, Thread, UpdateChannelRequest, UpdateDmMessageRequest, UpdateMessageRequest, UpdateServerRequest, User, UserProfile, UserStatus };
export type { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, RegisterRequest, Server, UpdateChannelRequest, UpdateServerRequest, User, UserMembership, UserMembershipTier, UserProfile, UserStatus };

@@ -0,1 +1,3 @@

export { A as Attachment, C as CommerceMessageCard, a as CommerceOfferCardInput, b as CommerceProductCard, M as MentionSuggestion, c as MentionSuggestionTrigger, d as Message, e as MessageMention, f as MessageMentionKind, g as MessageMentionRange, h as MessageMetadata, N as Notification, i as NotificationType, P as PaidFileCard, R as ReactionGroup, S as SendMessageRequest, T as Thread, U as UpdateMessageRequest } from '../message.types-lG4qBSus.js';
type AgentStatus = 'running' | 'stopped' | 'error';

@@ -75,3 +77,3 @@ type AgentKernelType = 'claude-code' | 'cursor' | 'mcp-server' | 'custom';

}
type FriendSource = 'friend' | 'owned_claw' | 'rented_claw';
type FriendSource = 'friend' | 'owned_agent' | 'rented_agent';
interface FriendEntry {

@@ -92,118 +94,2 @@ friendshipId: string;

interface Message {
id: string;
content: string;
channelId: string;
authorId: string;
threadId: string | null;
replyToId: string | null;
isEdited: boolean;
isPinned: boolean;
createdAt: string;
updatedAt: string;
author?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
isBot: boolean;
};
attachments?: Attachment[];
reactions?: ReactionGroup[];
}
interface Attachment {
id: string;
messageId: string;
filename: string;
url: string;
contentType: string;
size: number;
width: number | null;
height: number | null;
createdAt: string;
}
interface ReactionGroup {
emoji: string;
count: number;
userIds: string[];
}
interface Thread {
id: string;
name: string;
channelId: string;
parentMessageId: string;
creatorId: string;
isArchived: boolean;
createdAt: string;
updatedAt: string;
}
interface SendMessageRequest {
content: string;
threadId?: string;
replyToId?: string;
}
interface UpdateMessageRequest {
content: string;
}
type NotificationType = 'mention' | 'reply' | 'dm' | 'system';
interface Notification {
id: string;
userId: string;
type: NotificationType;
title: string;
body: string | null;
referenceId: string | null;
referenceType: string | null;
isRead: boolean;
createdAt: string;
}
interface DmChannel {
id: string;
userAId: string;
userBId: string;
lastMessageAt: string | null;
createdAt: string;
otherUser?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
status: string;
};
}
/** DM message — mirrors channel Message but scoped to DM channels */
interface DmMessage {
id: string;
content: string;
dmChannelId: string;
authorId: string;
replyToId: string | null;
isEdited: boolean;
createdAt: string;
updatedAt: string;
author?: {
id: string;
username: string;
displayName: string;
avatarUrl: string | null;
isBot: boolean;
};
attachments?: DmAttachment[];
reactions?: ReactionGroup[];
}
interface DmAttachment {
id: string;
dmMessageId: string;
filename: string;
url: string;
contentType: string;
size: number;
width: number | null;
height: number | null;
createdAt: string;
}
interface UpdateDmMessageRequest {
content: string;
}
interface Server {

@@ -253,2 +139,3 @@ id: string;

isBot: boolean;
membership?: UserMembership;
createdAt: string;

@@ -271,5 +158,6 @@ updatedAt: string;

email: string;
username: string;
displayName: string;
username?: string;
displayName?: string;
password: string;
inviteCode?: string;
}

@@ -281,3 +169,18 @@ interface AuthResponse {

}
interface UserMembershipTier {
id: string;
level: number;
label: string;
capabilities: string[];
}
interface UserMembership {
status: string;
tier: UserMembershipTier;
level: number;
isMember: boolean;
memberSince?: string | null;
inviteCodeId?: string | null;
capabilities: string[];
}
export type { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, Attachment, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, DmAttachment, DmChannel, DmMessage, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, Message, Notification, NotificationType, ReactionGroup, RegisterRequest, SendMessageRequest, Server, Thread, UpdateChannelRequest, UpdateDmMessageRequest, UpdateMessageRequest, UpdateServerRequest, User, UserProfile, UserStatus };
export type { Agent, AgentCapability, AgentInfo, AgentKernelType, AgentStatus, AuthResponse, Channel, ChannelSortBy, ChannelSortDirection, ChannelSortOptions, ChannelType, CreateAgentRequest, CreateChannelRequest, CreateServerRequest, FriendEntry, FriendSource, Friendship, FriendshipStatus, LoginRequest, Member, MemberRole, RegisterRequest, Server, UpdateChannelRequest, UpdateServerRequest, User, UserMembership, UserMembershipTier, UserProfile, UserStatus };

@@ -24,2 +24,7 @@ "use strict";

CAT_AVATAR_COUNT: () => CAT_AVATAR_COUNT,
assignMentionRanges: () => assignMentionRanges,
buildMentionMarkdownLinks: () => buildMentionMarkdownLinks,
canonicalMentionToken: () => canonicalMentionToken,
canonicalizeMentionContent: () => canonicalizeMentionContent,
escapeMarkdownLinkLabel: () => escapeMarkdownLinkLabel,
formatDate: () => formatDate,

@@ -32,4 +37,8 @@ generateInviteCode: () => generateInviteCode,

getCatSvgString: () => getCatSvgString,
isCanonicalMentionToken: () => isCanonicalMentionToken,
isValidEmail: () => isValidEmail,
mentionDisplayText: () => mentionDisplayText,
parseCanonicalMentionToken: () => parseCanonicalMentionToken,
renderCatSvg: () => renderCatSvg,
segmentTextByMentions: () => segmentTextByMentions,
slugify: () => slugify

@@ -222,2 +231,186 @@ });

// src/utils/message-mentions.ts
function uniqueValues(values) {
return Array.from(new Set(values.filter((value) => !!value)));
}
function canonicalMentionToken(mention) {
if (mention.kind === "channel") return `<#${mention.channelId ?? mention.targetId}>`;
if (mention.kind === "server") return `<@server:${mention.serverId ?? mention.targetId}>`;
if (mention.kind === "here" || mention.kind === "everyone") {
const scope = mention.serverId ?? mention.targetId;
return scope ? `<!${mention.kind}:${scope}>` : `<!${mention.kind}>`;
}
return `<@${mention.userId ?? mention.targetId}>`;
}
function parseCanonicalMentionToken(token) {
const user = token.match(/^<@([^>:]+)>$/u);
if (user?.[1]) return { kind: "user", targetId: user[1] };
const server = token.match(/^<@server:([^>]+)>$/u);
if (server?.[1]) return { kind: "server", targetId: server[1] };
const channel = token.match(/^<#([^>]+)>$/u);
if (channel?.[1]) return { kind: "channel", targetId: channel[1] };
const broadcast = token.match(/^<!(here|everyone)(?::([^>]+))?>$/u);
if (broadcast?.[1] === "here" || broadcast?.[1] === "everyone") {
return {
kind: broadcast[1],
...broadcast[2] ? { targetId: broadcast[2] } : {}
};
}
return null;
}
function isCanonicalMentionToken(token) {
return parseCanonicalMentionToken(token) !== null;
}
function matchTextsForMention(mention) {
return uniqueValues([mention.token, mention.sourceToken, canonicalMentionToken(mention)]);
}
function isValidRange(content, mention, range) {
if (!range) return false;
if (!Number.isInteger(range.start) || !Number.isInteger(range.end)) return false;
if (range.start < 0 || range.end <= range.start || range.end > content.length) return false;
const sourceText = content.slice(range.start, range.end);
return matchTextsForMention(mention).includes(sourceText);
}
function overlaps(a, b) {
return a.start < b.end && b.start < a.end;
}
function canUseRange(range, used) {
return !used.some((candidate) => overlaps(candidate, range));
}
function findOccurrences(content, token, used) {
const occurrences = [];
let index = content.indexOf(token);
while (index >= 0) {
const range = { start: index, end: index + token.length };
if (canUseRange(range, used)) occurrences.push(range);
index = content.indexOf(token, index + token.length);
}
return occurrences;
}
function addCandidate(candidates, used, mention, range, order, sourceText) {
if (!canUseRange(range, used)) return;
const matchedText = sourceText ?? mention.sourceToken ?? mention.token;
used.push(range);
candidates.push({
start: range.start,
end: range.end,
order,
sourceText: matchedText,
mention: { ...mention, range }
});
}
function segmentTextByMentions(content, mentions) {
if (!content) return [];
const usableMentions = (mentions ?? []).filter((mention) => mention.token);
if (usableMentions.length === 0) return [{ type: "text", text: content }];
const candidates = [];
const usedRanges = [];
const pendingByText = /* @__PURE__ */ new Map();
usableMentions.forEach((mention, order) => {
if (isValidRange(content, mention, mention.range)) {
addCandidate(
candidates,
usedRanges,
mention,
mention.range,
order,
content.slice(mention.range.start, mention.range.end)
);
return;
}
for (const text of matchTextsForMention(mention)) {
const pending = pendingByText.get(text) ?? [];
pending.push({ mention, order });
pendingByText.set(text, pending);
}
});
const pendingGroups = Array.from(pendingByText.entries()).sort((a, b) => {
const lengthDelta = b[0].length - a[0].length;
if (lengthDelta !== 0) return lengthDelta;
return a[1][0].order - b[1][0].order;
});
for (const [token, pending] of pendingGroups) {
const occurrences = findOccurrences(content, token, usedRanges);
if (occurrences.length === 0) continue;
if (pending.length === 1) {
const { mention, order } = pending[0];
for (const range of occurrences) {
addCandidate(candidates, usedRanges, mention, range, order, token);
}
continue;
}
pending.forEach(({ mention, order }, index) => {
const range = occurrences[index];
if (range) addCandidate(candidates, usedRanges, mention, range, order, token);
});
}
candidates.sort((a, b) => a.start - b.start || b.end - a.end || a.order - b.order);
const segments = [];
let cursor = 0;
for (const candidate of candidates) {
if (candidate.start < cursor) continue;
if (candidate.start > cursor) {
segments.push({ type: "text", text: content.slice(cursor, candidate.start) });
}
const text = content.slice(candidate.start, candidate.end);
segments.push({
type: "mention",
text,
range: { start: candidate.start, end: candidate.end },
mention: { ...candidate.mention, sourceToken: candidate.sourceText }
});
cursor = candidate.end;
}
if (cursor < content.length) {
segments.push({ type: "text", text: content.slice(cursor) });
}
return segments.length > 0 ? segments : [{ type: "text", text: content }];
}
function assignMentionRanges(content, mentions) {
return segmentTextByMentions(content, mentions).filter((segment) => {
return segment.type === "mention";
}).map((segment) => ({
...segment.mention,
token: segment.mention.token || segment.text,
range: segment.range
}));
}
function canonicalizeMentionContent(content, mentions) {
const canonicalMentions = [];
let nextContent = "";
for (const segment of segmentTextByMentions(content, mentions)) {
if (segment.type === "text") {
nextContent += segment.text;
continue;
}
const token = canonicalMentionToken(segment.mention);
const start = nextContent.length;
nextContent += token;
canonicalMentions.push({
...segment.mention,
token,
sourceToken: segment.text === token ? segment.mention.sourceToken : segment.text,
range: { start, end: start + token.length }
});
}
return { content: nextContent, mentions: canonicalMentions };
}
function mentionDisplayText(mention) {
return mention.label || mention.token;
}
function escapeMarkdownLinkLabel(label) {
return label.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
}
function buildMentionMarkdownLinks(content, mentions, hrefForMention) {
const linkedMentions = [];
const markdown = segmentTextByMentions(content, mentions).map((segment) => {
if (segment.type === "text") return segment.text;
const href = hrefForMention(segment.mention, linkedMentions.length);
if (!href) return segment.text;
linkedMentions.push(segment.mention);
return `[${escapeMarkdownLinkLabel(mentionDisplayText(segment.mention))}](${href})`;
}).join("");
return { markdown, mentions: linkedMentions };
}
// src/utils/pixel-cats.ts

@@ -355,2 +548,7 @@ var variants = [

CAT_AVATAR_COUNT,
assignMentionRanges,
buildMentionMarkdownLinks,
canonicalMentionToken,
canonicalizeMentionContent,
escapeMarkdownLinkLabel,
formatDate,

@@ -363,5 +561,9 @@ generateInviteCode,

getCatSvgString,
isCanonicalMentionToken,
isValidEmail,
mentionDisplayText,
parseCanonicalMentionToken,
renderCatSvg,
segmentTextByMentions,
slugify
});

@@ -0,1 +1,3 @@

import { g as MessageMentionRange, e as MessageMention } from '../message.types-lG4qBSus.cjs';
type CatPattern = 'none' | 'tabby' | 'tuxedo' | 'siamese' | 'calico' | 'bicolor';

@@ -18,2 +20,39 @@ type CatExpression = 'smile' | 'open' | 'flat' | 'sad' | 'surprised' | 'kawaii' | 'winking' | 'smirk';

type MessageMentionTextSegment = {
type: 'text';
text: string;
} | {
type: 'mention';
text: string;
range: MessageMentionRange;
mention: MessageMention;
};
declare function canonicalMentionToken(mention: Pick<MessageMention, 'kind' | 'targetId'> & Partial<MessageMention>): string;
declare function parseCanonicalMentionToken(token: string): {
kind: 'user';
targetId: string;
} | {
kind: 'channel';
targetId: string;
} | {
kind: 'server';
targetId: string;
} | {
kind: 'here' | 'everyone';
targetId?: string;
} | null;
declare function isCanonicalMentionToken(token: string): boolean;
declare function segmentTextByMentions(content: string, mentions: readonly MessageMention[] | null | undefined): MessageMentionTextSegment[];
declare function assignMentionRanges(content: string, mentions: readonly MessageMention[] | null | undefined): MessageMention[];
declare function canonicalizeMentionContent(content: string, mentions: readonly MessageMention[] | null | undefined): {
content: string;
mentions: MessageMention[];
};
declare function mentionDisplayText(mention: Pick<MessageMention, 'label' | 'token'>): string;
declare function escapeMarkdownLinkLabel(label: string): string;
declare function buildMentionMarkdownLinks(content: string, mentions: readonly MessageMention[] | null | undefined, hrefForMention: (mention: MessageMention, index: number) => string | null | undefined): {
markdown: string;
mentions: MessageMention[];
};
/**

@@ -42,2 +81,2 @@ * Pixel Art Cat Avatar System

export { type BgPattern, CAT_AVATAR_COUNT, type CatConfig, type CatDecoration, type CatExpression, type CatPattern, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isValidEmail, renderCatSvg, slugify };
export { type BgPattern, CAT_AVATAR_COUNT, type CatConfig, type CatDecoration, type CatExpression, type CatPattern, type MessageMentionTextSegment, assignMentionRanges, buildMentionMarkdownLinks, canonicalMentionToken, canonicalizeMentionContent, escapeMarkdownLinkLabel, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isCanonicalMentionToken, isValidEmail, mentionDisplayText, parseCanonicalMentionToken, renderCatSvg, segmentTextByMentions, slugify };

@@ -0,1 +1,3 @@

import { g as MessageMentionRange, e as MessageMention } from '../message.types-lG4qBSus.js';
type CatPattern = 'none' | 'tabby' | 'tuxedo' | 'siamese' | 'calico' | 'bicolor';

@@ -18,2 +20,39 @@ type CatExpression = 'smile' | 'open' | 'flat' | 'sad' | 'surprised' | 'kawaii' | 'winking' | 'smirk';

type MessageMentionTextSegment = {
type: 'text';
text: string;
} | {
type: 'mention';
text: string;
range: MessageMentionRange;
mention: MessageMention;
};
declare function canonicalMentionToken(mention: Pick<MessageMention, 'kind' | 'targetId'> & Partial<MessageMention>): string;
declare function parseCanonicalMentionToken(token: string): {
kind: 'user';
targetId: string;
} | {
kind: 'channel';
targetId: string;
} | {
kind: 'server';
targetId: string;
} | {
kind: 'here' | 'everyone';
targetId?: string;
} | null;
declare function isCanonicalMentionToken(token: string): boolean;
declare function segmentTextByMentions(content: string, mentions: readonly MessageMention[] | null | undefined): MessageMentionTextSegment[];
declare function assignMentionRanges(content: string, mentions: readonly MessageMention[] | null | undefined): MessageMention[];
declare function canonicalizeMentionContent(content: string, mentions: readonly MessageMention[] | null | undefined): {
content: string;
mentions: MessageMention[];
};
declare function mentionDisplayText(mention: Pick<MessageMention, 'label' | 'token'>): string;
declare function escapeMarkdownLinkLabel(label: string): string;
declare function buildMentionMarkdownLinks(content: string, mentions: readonly MessageMention[] | null | undefined, hrefForMention: (mention: MessageMention, index: number) => string | null | undefined): {
markdown: string;
mentions: MessageMention[];
};
/**

@@ -42,2 +81,2 @@ * Pixel Art Cat Avatar System

export { type BgPattern, CAT_AVATAR_COUNT, type CatConfig, type CatDecoration, type CatExpression, type CatPattern, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isValidEmail, renderCatSvg, slugify };
export { type BgPattern, CAT_AVATAR_COUNT, type CatConfig, type CatDecoration, type CatExpression, type CatPattern, type MessageMentionTextSegment, assignMentionRanges, buildMentionMarkdownLinks, canonicalMentionToken, canonicalizeMentionContent, escapeMarkdownLinkLabel, formatDate, generateInviteCode, generateRandomCatConfig, getAllCatAvatars, getCatAvatar, getCatAvatarByUserId, getCatSvgString, isCanonicalMentionToken, isValidEmail, mentionDisplayText, parseCanonicalMentionToken, renderCatSvg, segmentTextByMentions, slugify };
import {
CAT_AVATAR_COUNT,
assignMentionRanges,
buildMentionMarkdownLinks,
canonicalMentionToken,
canonicalizeMentionContent,
escapeMarkdownLinkLabel,
formatDate,

@@ -10,8 +15,17 @@ generateInviteCode,

getCatSvgString,
isCanonicalMentionToken,
isValidEmail,
mentionDisplayText,
parseCanonicalMentionToken,
renderCatSvg,
segmentTextByMentions,
slugify
} from "../chunk-PXKHJSTK.js";
} from "../chunk-E3UBH4AI.js";
export {
CAT_AVATAR_COUNT,
assignMentionRanges,
buildMentionMarkdownLinks,
canonicalMentionToken,
canonicalizeMentionContent,
escapeMarkdownLinkLabel,
formatDate,

@@ -24,5 +38,9 @@ generateInviteCode,

getCatSvgString,
isCanonicalMentionToken,
isValidEmail,
mentionDisplayText,
parseCanonicalMentionToken,
renderCatSvg,
segmentTextByMentions,
slugify
};
{
"name": "@shadowob/shared",
"version": "1.1.1",
"version": "1.1.3",
"type": "module",

@@ -13,2 +13,3 @@ "main": "./dist/index.js",

"development": "./src/index.ts",
"react-native": "./src/index.ts",
"import": "./dist/index.js",

@@ -21,2 +22,3 @@ "require": "./dist/index.cjs",

"development": "./src/types/index.ts",
"react-native": "./src/types/index.ts",
"import": "./dist/types/index.js",

@@ -29,2 +31,3 @@ "require": "./dist/types/index.cjs",

"development": "./src/constants/index.ts",
"react-native": "./src/constants/index.ts",
"import": "./dist/constants/index.js",

@@ -34,5 +37,14 @@ "require": "./dist/constants/index.cjs",

},
"./play-catalog": {
"types": "./dist/play-catalog/index.d.ts",
"development": "./src/play-catalog/index.ts",
"react-native": "./src/play-catalog/index.ts",
"import": "./dist/play-catalog/index.js",
"require": "./dist/play-catalog/index.cjs",
"default": "./dist/play-catalog/index.js"
},
"./utils": {
"types": "./dist/utils/index.d.ts",
"development": "./src/utils/index.ts",
"react-native": "./src/utils/index.ts",
"import": "./dist/utils/index.js",

@@ -47,3 +59,3 @@ "require": "./dist/utils/index.cjs",

"dependencies": {
"nanoid": "^5.1.7"
"nanoid": "^5.1.11"
},

@@ -60,2 +72,3 @@ "devDependencies": {

"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"test": "vitest run",

@@ -62,0 +75,0 @@ "test:watch": "vitest"

// src/constants/events.ts
var CLIENT_EVENTS = {
CHANNEL_JOIN: "channel:join",
CHANNEL_LEAVE: "channel:leave",
MESSAGE_SEND: "message:send",
MESSAGE_TYPING: "message:typing",
PRESENCE_UPDATE: "presence:update"
};
var SERVER_EVENTS = {
MESSAGE_NEW: "message:new",
MESSAGE_UPDATE: "message:update",
MESSAGE_DELETE: "message:delete",
MEMBER_TYPING: "member:typing",
MEMBER_JOIN: "member:join",
MEMBER_LEAVE: "member:leave",
PRESENCE_CHANGE: "presence:change",
REACTION_ADD: "reaction:add",
REACTION_REMOVE: "reaction:remove",
NOTIFICATION_NEW: "notification:new",
DM_MESSAGE_NEW: "dm:message:new"
};
// src/constants/limits.ts
var LIMITS = {
/** Max message content length */
MESSAGE_CONTENT_MAX: 4e3,
/** Max username length */
USERNAME_MAX: 32,
/** Min username length */
USERNAME_MIN: 3,
/** Max display name length */
DISPLAY_NAME_MAX: 64,
/** Max server name length */
SERVER_NAME_MAX: 100,
/** Max channel name length */
CHANNEL_NAME_MAX: 100,
/** Max thread name length */
THREAD_NAME_MAX: 100,
/** Max file upload size (10MB) */
FILE_UPLOAD_MAX_SIZE: 10 * 1024 * 1024,
/** Messages per page (cursor pagination) */
MESSAGES_PER_PAGE: 50,
/** Max servers per user */
SERVERS_PER_USER_MAX: 100,
/** Max channels per server */
CHANNELS_PER_SERVER_MAX: 200,
/** Invite code length */
INVITE_CODE_LENGTH: 8,
/** Password min length */
PASSWORD_MIN: 8,
/** Max reactions per message per user */
REACTIONS_PER_MESSAGE_MAX: 20
};
export {
CLIENT_EVENTS,
SERVER_EVENTS,
LIMITS
};
// src/utils/index.ts
import { customAlphabet } from "nanoid";
// src/utils/avatar-generator.ts
var COLORS = {
bg: [
"transparent",
"#1e1f22",
"#313338",
"#5865F2",
"#23a559",
"#da373c",
"#f472b6",
"#3b82f6",
"#fbbf24",
"#a855f7",
"#1abc9c",
"#f39c12",
"#e74c3c"
],
body: [
"#2d2d30",
"#e8842c",
"#e8e8e8",
"#7a7a80",
"#d4a574",
"#6b8094",
"#f472b6",
"#c8d6e5",
"#3e2723",
"#bdc3c7",
"#ffb8b8"
],
eyes: [
"#f8e71c",
"#00f3ff",
"#4ade80",
"#60a5fa",
"#a855f7",
"#fbbf24",
"#f87171",
"#ffc0cb",
"#1dd1a1",
"#e056fd"
],
pattern: ["#1a1a1c", "#ffffff", "#5a4a46", "#3d3d40", "#9a9aa0", "#d1ccc0", "#2d3436"]
};
function getRandomElement(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function generateRandomCatConfig() {
return {
bg: getRandomElement(COLORS.bg),
bgPattern: getRandomElement(["none", "dots", "stripes", "grid", "stars"]),
body: getRandomElement(COLORS.body),
pattern: getRandomElement([
"none",
"tabby",
"tuxedo",
"siamese",
"calico",
"bicolor"
]),
patternColor: getRandomElement(COLORS.pattern),
eyeColor: getRandomElement(COLORS.eyes),
expression: getRandomElement([
"smile",
"open",
"flat",
"sad",
"surprised",
"kawaii",
"winking",
"smirk"
]),
decoration: getRandomElement([
"none",
"glasses",
"blush",
"scar",
"flower",
"fish",
"headband"
])
};
}
function renderCatSvg(config) {
const { bg, bgPattern, body, pattern, patternColor, eyeColor, expression, decoration } = config;
const stroke = "#1a1a1c";
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">`;
if (bg && bg !== "transparent") {
svg += `<rect width="100" height="100" fill="${bg}" rx="20" />`;
const pColor = `rgba(255,255,255,0.15)`;
const cleanBg = bg.replace("#", "");
if (bgPattern === "dots") {
svg += `<pattern id="p-${cleanBg}-dots" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="2" fill="${pColor}"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-dots)" rx="20" />`;
} else if (bgPattern === "stripes") {
svg += `<pattern id="p-${cleanBg}-str" x="0" y="0" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="12" stroke="${pColor}" stroke-width="4"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-str)" rx="20" />`;
} else if (bgPattern === "grid") {
svg += `<pattern id="p-${cleanBg}-grid" width="16" height="16" patternUnits="userSpaceOnUse"><path d="M 16 0 L 0 0 0 16" fill="none" stroke="${pColor}" stroke-width="1"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-grid)" rx="20" />`;
} else if (bgPattern === "stars") {
svg += `<pattern id="p-${cleanBg}-star" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M10,2 L12,8 L18,8 L13,12 L15,18 L10,14 L5,18 L7,12 L2,8 L8,8 Z" fill="${pColor}" transform="scale(0.5) translate(5,5)"/></pattern><rect width="100" height="100" fill="url(#p-${cleanBg}-star)" rx="20" />`;
}
}
svg += `<ellipse cx="50" cy="85" rx="30" ry="6" fill="rgba(0,0,0,0.2)"/>`;
svg += `<path d="M22,45 C15,22 28,18 34,22 C38,25 40,38 40,38" fill="${body}" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>`;
svg += `<path d="M78,45 C85,22 72,18 66,22 C62,25 60,38 60,38" fill="${body}" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>`;
svg += `<path d="M26,38 C22,26 29,24 33,26 C35,28 36,34 36,34" fill="#ffb8b8" opacity="0.8"/>`;
svg += `<path d="M74,38 C78,26 71,24 67,26 C65,28 64,34 64,34" fill="#ffb8b8" opacity="0.8"/>`;
svg += `<ellipse cx="50" cy="58" rx="38" ry="30" fill="${body}" stroke="${stroke}" stroke-width="2.5"/>`;
if (pattern === "tabby") {
svg += `<path d="M50,30 L50,40 M42,32 L46,39 M58,32 L54,39" stroke="${patternColor}" stroke-width="3" stroke-linecap="round" opacity="0.7"/>`;
svg += `<path d="M18,55 L26,57 M20,62 L27,62" stroke="${patternColor}" stroke-width="2.5" stroke-linecap="round" opacity="0.7"/>`;
svg += `<path d="M82,55 L74,57 M80,62 L73,62" stroke="${patternColor}" stroke-width="2.5" stroke-linecap="round" opacity="0.7"/>`;
} else if (pattern === "tuxedo") {
svg += `<path d="M50,56 C30,68 28,88 50,88 C72,88 70,68 50,56" fill="${patternColor}" opacity="0.95"/>`;
svg += `<ellipse cx="50" cy="65" rx="16" ry="12" fill="${patternColor}" opacity="0.95"/>`;
} else if (pattern === "siamese") {
svg += `<ellipse cx="50" cy="62" rx="20" ry="16" fill="${patternColor}" opacity="0.6"/>`;
} else if (pattern === "calico") {
svg += `<path d="M25,40 Q35,30 45,45 Q35,55 25,40" fill="${patternColor}" opacity="0.8"/>`;
svg += `<path d="M75,45 Q65,60 55,50 Q65,35 75,45" fill="#e8842c" opacity="0.8"/>`;
} else if (pattern === "bicolor") {
svg += `<path d="M12,58 Q30,30 50,40 Q60,70 50,88 Q12,88 12,58 Z" fill="${patternColor}" opacity="0.8"/>`;
}
if (decoration === "blush") {
svg += `<ellipse cx="28" cy="62" rx="5" ry="3" fill="#ff7675" opacity="0.7"/>`;
svg += `<ellipse cx="72" cy="62" rx="5" ry="3" fill="#ff7675" opacity="0.7"/>`;
}
if (decoration === "scar") {
svg += `<path d="M28,42 L40,52 M30,48 L35,43 M34,51 L39,46 M32,53 L37,48" stroke="#d63031" stroke-width="1.5" stroke-linecap="round"/>`;
}
const drawEye = (cx, cy, lookDir = 0) => {
if (expression === "kawaii") {
return `<path d="M${cx - 8},${cy} Q${cx},${cy - 8} ${cx + 8},${cy} Q${cx},${cy - 3} ${cx - 8},${cy}" fill="${eyeColor}" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx + lookDir}" cy="${cy - 2}" r="3" fill="white"/><circle cx="${cx - 3 + lookDir}" cy="${cy}" r="1" fill="white"/>`;
} else if (expression === "winking" && cx > 50) {
return `<path d="M${cx - 7},${cy + 2} Q${cx},${cy - 4} ${cx + 7},${cy + 2}" fill="none" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round"/>`;
} else if (expression === "surprised") {
return `<circle cx="${cx}" cy="${cy}" r="8" fill="white" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx}" cy="${cy}" r="3" fill="${eyeColor}"/>`;
} else {
return `<circle cx="${cx}" cy="${cy}" r="7.5" fill="${eyeColor}" stroke="${stroke}" stroke-width="1.5"/><circle cx="${cx - 2 + lookDir}" cy="${cy - 2}" r="2.5" fill="white"/><circle cx="${cx + 2 + lookDir}" cy="${cy + 1}" r="1" fill="white"/>`;
}
};
if (expression === "smirk") {
svg += `<path d="M26,50 L42,50" stroke="${stroke}" stroke-width="2.5" stroke-linecap="round"/>`;
svg += drawEye(66, 52, 2);
} else {
svg += drawEye(34, 52, 0);
svg += drawEye(66, 52, 0);
}
if (decoration === "glasses") {
svg += `<circle cx="34" cy="52" r="11" fill="rgba(255,255,255,0.2)" stroke="#2d3436" stroke-width="2.5"/>`;
svg += `<circle cx="66" cy="52" r="11" fill="rgba(255,255,255,0.2)" stroke="#2d3436" stroke-width="2.5"/>`;
svg += `<path d="M45,50 Q50,48 55,50" fill="none" stroke="#2d3436" stroke-width="2.5" stroke-linecap="round"/>`;
}
svg += `<path d="M47,62 L53,62 L50,65 Z" fill="#ff9ff3" stroke="${stroke}" stroke-width="1" stroke-linejoin="round"/>`;
if (expression === "smile" || expression === "kawaii" || expression === "winking") {
svg += `<path d="M42,67 Q46,72 50,67 M50,67 Q54,72 58,67" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
} else if (expression === "open") {
svg += `<path d="M46,67 Q50,75 54,67 Z" fill="#ff7675" stroke="${stroke}" stroke-width="1.5" stroke-linejoin="round"/>`;
} else if (expression === "flat") {
svg += `<path d="M47,68 L53,68" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
} else if (expression === "sad") {
svg += `<path d="M43,70 Q50,64 57,70" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
} else if (expression === "surprised") {
svg += `<circle cx="50" cy="70" r="3" fill="#1a1a1c"/>`;
} else if (expression === "smirk") {
svg += `<path d="M46,67 Q52,69 56,64" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round"/>`;
}
svg += `<path d="M28,62 L15,60 M28,65 L14,66" stroke="${stroke}" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>`;
svg += `<path d="M72,62 L85,60 M72,65 L86,66" stroke="${stroke}" stroke-width="1.5" stroke-linecap="round" opacity="0.6"/>`;
if (decoration === "headband") {
svg += `<path d="M16,35 Q50,20 84,35" fill="none" stroke="#e17055" stroke-width="6" stroke-linecap="round"/>`;
svg += `<path d="M50,15 L60,25 L50,28 L40,25 Z" fill="#fab1a0" stroke="#e17055" stroke-width="2"/>`;
} else if (decoration === "flower") {
svg += `<path d="M75,30 Q80,20 85,30 Q95,25 85,35 Q90,45 80,40 Q70,45 75,35 Q65,25 75,30 Z" fill="#ffeaa7" stroke="#fdcb6e" stroke-width="1.5"/>`;
svg += `<circle cx="80" cy="33" r="3" fill="#d63031"/>`;
} else if (decoration === "fish") {
svg += `<path d="M40,82 Q50,75 60,82 L65,78 L65,86 L60,82 Q50,89 40,82 Z" fill="#81ecec" stroke="${stroke}" stroke-width="1.5"/>`;
svg += `<circle cx="45" cy="81" r="1" fill="${stroke}"/>`;
}
svg += `</svg>`;
return `data:image/svg+xml,${encodeURIComponent(svg.replace(/\n\s*/g, ""))}`;
}
// src/utils/pixel-cats.ts
var variants = [
// 0: Shadow — Black cat (logo style)
{
body: "#2d2d30",
stroke: "#1a1a1c",
earInner: "#3d3d40",
eyeL: "#f8e71c",
eyeR: "#00f3ff",
nose: "#3a2a26"
},
// 1: Mikan — Orange tabby
{
body: "#e8842c",
stroke: "#1a1a1c",
earInner: "#f5a623",
eyeL: "#4ade80",
eyeR: "#4ade80",
nose: "#d46b1a"
},
// 2: Yuki — White cat
{
body: "#e8e8e8",
stroke: "#a0a0a0",
earInner: "#ffc0cb",
eyeL: "#60a5fa",
eyeR: "#60a5fa",
nose: "#f5a0b0"
},
// 3: Haiiro — Gray cat
{
body: "#7a7a80",
stroke: "#4a4a50",
earInner: "#9a9aa0",
eyeL: "#fbbf24",
eyeR: "#fbbf24",
nose: "#5a4a46"
},
// 4: Tuxedo — Black & white accents
{
body: "#2d2d30",
stroke: "#1a1a1c",
earInner: "#e0e0e0",
eyeL: "#22c55e",
eyeR: "#22c55e",
nose: "#3a2a26"
},
// 5: Mocha — Cream/beige
{
body: "#d4a574",
stroke: "#8b6914",
earInner: "#e8c9a0",
eyeL: "#d97706",
eyeR: "#d97706",
nose: "#a0705a"
},
// 6: Blue — Russian blue
{
body: "#6b8094",
stroke: "#3d5060",
earInner: "#8ba0b4",
eyeL: "#22c55e",
eyeR: "#22c55e",
nose: "#5a6a76"
},
// 7: Sakura — Fantasy pink
{
body: "#f472b6",
stroke: "#be185d",
earInner: "#f9a8d4",
eyeL: "#a855f7",
eyeR: "#c084fc",
nose: "#d44a8c"
}
];
function makeCatSvg(c) {
return [
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">',
`<path d="M22,45 Q15,22 28,22 Q34,22 40,38" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5" stroke-linecap="round"/>`,
`<path d="M78,45 Q85,22 72,22 Q66,22 60,38" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5" stroke-linecap="round"/>`,
`<path d="M26,38 Q22,26 29,26 Q33,26 36,34" fill="${c.earInner}" opacity="0.5"/>`,
`<path d="M74,38 Q78,26 71,26 Q67,26 64,34" fill="${c.earInner}" opacity="0.5"/>`,
`<ellipse cx="50" cy="58" rx="36" ry="28" fill="${c.body}" stroke="${c.stroke}" stroke-width="2.5"/>`,
`<circle cx="35" cy="52" r="7" fill="${c.eyeL}" stroke="${c.stroke}" stroke-width="1.5"/>`,
`<circle cx="33" cy="49" r="2.5" fill="white"/>`,
`<circle cx="65" cy="52" r="7" fill="${c.eyeR}" stroke="${c.stroke}" stroke-width="1.5"/>`,
`<circle cx="63" cy="49" r="2.5" fill="white"/>`,
`<ellipse cx="50" cy="62" rx="3.5" ry="2.2" fill="${c.nose}"/>`,
`<path d="M42,67 Q46,72 50,67" fill="none" stroke="${c.stroke}" stroke-width="2" stroke-linecap="round"/>`,
`<path d="M50,67 Q54,72 58,67" fill="none" stroke="${c.stroke}" stroke-width="2" stroke-linecap="round"/>`,
"</svg>"
].join("");
}
var catDataUris = variants.map((v) => {
const svg = makeCatSvg(v);
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
});
function getCatAvatar(index) {
return catDataUris[index % catDataUris.length];
}
function getCatAvatarByUserId(userId) {
let hash = 0;
for (let i = 0; i < userId.length; i++) {
hash = (hash << 5) - hash + userId.charCodeAt(i) | 0;
}
return getCatAvatar(Math.abs(hash) % catDataUris.length);
}
function getAllCatAvatars() {
const names = ["\u5F71\u5B50", "\u871C\u67D1", "\u5C0F\u96EA", "\u7070\u7070", "\u71D5\u5C3E\u670D", "\u6469\u5361", "\u84DD\u84DD", "\u5C0F\u6A31"];
return catDataUris.map((uri, i) => ({ index: i, name: names[i], dataUri: uri }));
}
function getCatSvgString(index) {
return makeCatSvg(variants[index % variants.length]);
}
var CAT_AVATAR_COUNT = variants.length;
// src/utils/index.ts
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var generateInviteCode = customAlphabet(alphabet, 8);
function formatDate(date) {
const d = typeof date === "string" ? new Date(date) : date;
return d.toISOString();
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function slugify(text) {
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
}
export {
generateRandomCatConfig,
renderCatSvg,
getCatAvatar,
getCatAvatarByUserId,
getAllCatAvatars,
getCatSvgString,
CAT_AVATAR_COUNT,
generateInviteCode,
formatDate,
isValidEmail,
slugify
};