calabasas 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +155 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2298,6 +2298,7 @@ async function push(options) {
|
|
|
2298
2298
|
syncChannels: calabasasConfig.sync?.channels ?? false,
|
|
2299
2299
|
syncRoles: calabasasConfig.sync?.roles ?? false,
|
|
2300
2300
|
syncMembers: calabasasConfig.sync?.members ?? false,
|
|
2301
|
+
syncPresence: calabasasConfig.sync?.presence ?? false,
|
|
2301
2302
|
eventConfigs
|
|
2302
2303
|
})
|
|
2303
2304
|
});
|
|
@@ -2333,7 +2334,8 @@ async function push(options) {
|
|
|
2333
2334
|
`Guilds: ${calabasasConfig.sync?.guilds ? "enabled" : "disabled"}`,
|
|
2334
2335
|
`Channels: ${calabasasConfig.sync?.channels ? "enabled" : "disabled"}`,
|
|
2335
2336
|
`Roles: ${calabasasConfig.sync?.roles ? "enabled" : "disabled"}`,
|
|
2336
|
-
`Members: ${calabasasConfig.sync?.members ? "enabled" : "disabled"}
|
|
2337
|
+
`Members: ${calabasasConfig.sync?.members ? "enabled" : "disabled"}`,
|
|
2338
|
+
`Presence: ${calabasasConfig.sync?.presence ? "enabled" : "disabled"}`
|
|
2337
2339
|
].join(`
|
|
2338
2340
|
`);
|
|
2339
2341
|
const eventSummary = eventConfigs.length > 0 ? `
|
|
@@ -2427,6 +2429,16 @@ function generateSchemaFile(sync) {
|
|
|
2427
2429
|
.index("by_user_guild", ["discordUserId", "guildDiscordId"])
|
|
2428
2430
|
.index("by_guild", ["guildDiscordId"]);`);
|
|
2429
2431
|
}
|
|
2432
|
+
if (sync.presence) {
|
|
2433
|
+
tables.push(`export const calabasasPresence = defineTable({
|
|
2434
|
+
discordUserId: v.string(),
|
|
2435
|
+
guildDiscordId: v.string(),
|
|
2436
|
+
status: v.string(),
|
|
2437
|
+
updatedAt: v.number(),
|
|
2438
|
+
})
|
|
2439
|
+
.index("by_guild", ["guildDiscordId"])
|
|
2440
|
+
.index("by_user_guild", ["discordUserId", "guildDiscordId"]);`);
|
|
2441
|
+
}
|
|
2430
2442
|
const tableNames = [];
|
|
2431
2443
|
if (sync.guilds) {
|
|
2432
2444
|
tableNames.push("calabasasGuilds");
|
|
@@ -2438,6 +2450,8 @@ function generateSchemaFile(sync) {
|
|
|
2438
2450
|
tableNames.push("calabasasRoles");
|
|
2439
2451
|
if (sync.members)
|
|
2440
2452
|
tableNames.push("calabasasMembers");
|
|
2453
|
+
if (sync.presence)
|
|
2454
|
+
tableNames.push("calabasasPresence");
|
|
2441
2455
|
const tablesExport = tableNames.length > 0 ? `export const calabasasTables = {
|
|
2442
2456
|
${tableNames.join(`,
|
|
2443
2457
|
`)},
|
|
@@ -2549,6 +2563,16 @@ var MEMBER_TYPE = `{
|
|
|
2549
2563
|
roles?: string[];
|
|
2550
2564
|
joinedAt?: string;
|
|
2551
2565
|
}`;
|
|
2566
|
+
var PRESENCE_VALIDATOR = `v.object({
|
|
2567
|
+
userId: v.string(),
|
|
2568
|
+
guildId: v.string(),
|
|
2569
|
+
status: v.string(),
|
|
2570
|
+
})`;
|
|
2571
|
+
var PRESENCE_TYPE = `{
|
|
2572
|
+
userId: string;
|
|
2573
|
+
guildId: string;
|
|
2574
|
+
status: string;
|
|
2575
|
+
}`;
|
|
2552
2576
|
var GUILD_ADMIN_VALIDATOR = `v.object({
|
|
2553
2577
|
guildId: v.string(),
|
|
2554
2578
|
adminUserIds: v.array(v.string()),
|
|
@@ -2736,6 +2760,42 @@ function generateMemberMutation() {
|
|
|
2736
2760
|
},
|
|
2737
2761
|
});`;
|
|
2738
2762
|
}
|
|
2763
|
+
function generatePresenceMutation() {
|
|
2764
|
+
return `export const syncPresence = internalMutation({
|
|
2765
|
+
args: {
|
|
2766
|
+
data: presenceValidator,
|
|
2767
|
+
operation: v.union(v.literal("upsert"), v.literal("delete")),
|
|
2768
|
+
},
|
|
2769
|
+
returns: v.null(),
|
|
2770
|
+
handler: async (ctx, { data: presence, operation }) => {
|
|
2771
|
+
const existing = await ctx.db
|
|
2772
|
+
.query("calabasasPresence")
|
|
2773
|
+
.withIndex("by_user_guild", (q) =>
|
|
2774
|
+
q.eq("discordUserId", presence.userId).eq("guildDiscordId", presence.guildId)
|
|
2775
|
+
)
|
|
2776
|
+
.unique();
|
|
2777
|
+
|
|
2778
|
+
if (operation === "delete") {
|
|
2779
|
+
if (existing) await ctx.db.delete(existing._id);
|
|
2780
|
+
return null;
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
const doc = {
|
|
2784
|
+
discordUserId: presence.userId,
|
|
2785
|
+
guildDiscordId: presence.guildId,
|
|
2786
|
+
status: presence.status,
|
|
2787
|
+
updatedAt: Date.now(),
|
|
2788
|
+
};
|
|
2789
|
+
|
|
2790
|
+
if (existing) {
|
|
2791
|
+
await ctx.db.patch(existing._id, doc);
|
|
2792
|
+
} else {
|
|
2793
|
+
await ctx.db.insert("calabasasPresence", doc);
|
|
2794
|
+
}
|
|
2795
|
+
return null;
|
|
2796
|
+
},
|
|
2797
|
+
});`;
|
|
2798
|
+
}
|
|
2739
2799
|
function generateGuildAdminMutation() {
|
|
2740
2800
|
return `export const _syncGuildAdmins = internalMutation({
|
|
2741
2801
|
args: {
|
|
@@ -2854,6 +2914,13 @@ function generateSyncFile(sync) {
|
|
|
2854
2914
|
publicMutations.push(generatePublicSyncMutation("member"));
|
|
2855
2915
|
enabledTypes.push("member");
|
|
2856
2916
|
}
|
|
2917
|
+
if (sync.presence) {
|
|
2918
|
+
validators.push(`const presenceValidator = ${PRESENCE_VALIDATOR};`);
|
|
2919
|
+
types.push(`export type PresenceSync = ${PRESENCE_TYPE};`);
|
|
2920
|
+
internalMutations.push(generateInternalSyncMutation("presence", generatePresenceMutation()));
|
|
2921
|
+
publicMutations.push(generatePublicSyncMutation("presence"));
|
|
2922
|
+
enabledTypes.push("presence");
|
|
2923
|
+
}
|
|
2857
2924
|
if (internalMutations.length === 0) {
|
|
2858
2925
|
return `/**
|
|
2859
2926
|
* THIS FILE IS AUTO-GENERATED BY \`calabasas generate\`
|
|
@@ -3499,7 +3566,7 @@ async function generate(options) {
|
|
|
3499
3566
|
fs4.writeFileSync(eventHandlersPath, eventHandlersCode);
|
|
3500
3567
|
const generated = [options.output];
|
|
3501
3568
|
const syncConfig = config.sync ?? {};
|
|
3502
|
-
const hasSyncEnabled = syncConfig.guilds || syncConfig.channels || syncConfig.roles || syncConfig.members;
|
|
3569
|
+
const hasSyncEnabled = syncConfig.guilds || syncConfig.channels || syncConfig.roles || syncConfig.members || syncConfig.presence;
|
|
3503
3570
|
if (hasSyncEnabled) {
|
|
3504
3571
|
const schemaPath = path4.join(generatedDir, "schema.ts");
|
|
3505
3572
|
const schemaCode = generateSchemaFile(syncConfig);
|
|
@@ -3529,6 +3596,8 @@ async function generate(options) {
|
|
|
3529
3596
|
syncExports.push("syncRole");
|
|
3530
3597
|
if (syncConfig.members)
|
|
3531
3598
|
syncExports.push("syncMember");
|
|
3599
|
+
if (syncConfig.presence)
|
|
3600
|
+
syncExports.push("syncPresence");
|
|
3532
3601
|
p5.note(`1. Add tables to your convex/schema.ts:
|
|
3533
3602
|
import { calabasasTables } from "./calabasas/_generated/schema";
|
|
3534
3603
|
|
|
@@ -3687,6 +3756,7 @@ import * as p7 from "@clack/prompts";
|
|
|
3687
3756
|
// src/lib/registry/components/channel-select.ts
|
|
3688
3757
|
var channelSelect = {
|
|
3689
3758
|
name: "channel-select",
|
|
3759
|
+
kind: "component",
|
|
3690
3760
|
description: "Searchable combobox to pick a Discord channel (with type icons)",
|
|
3691
3761
|
requiredSyncTypes: ["channels"],
|
|
3692
3762
|
requiredShadcnComponents: ["popover", "command", "button"],
|
|
@@ -3834,6 +3904,7 @@ export function ChannelSelect({
|
|
|
3834
3904
|
// src/lib/registry/components/role-select.ts
|
|
3835
3905
|
var roleSelect = {
|
|
3836
3906
|
name: "role-select",
|
|
3907
|
+
kind: "component",
|
|
3837
3908
|
description: "Searchable combobox to pick a Discord role (with color dots)",
|
|
3838
3909
|
requiredSyncTypes: ["roles"],
|
|
3839
3910
|
requiredShadcnComponents: ["popover", "command", "button"],
|
|
@@ -3972,6 +4043,7 @@ export function RoleSelect({
|
|
|
3972
4043
|
// src/lib/registry/components/member-select.ts
|
|
3973
4044
|
var memberSelect = {
|
|
3974
4045
|
name: "member-select",
|
|
4046
|
+
kind: "component",
|
|
3975
4047
|
description: "Searchable combobox to pick a Discord member (with avatar)",
|
|
3976
4048
|
requiredSyncTypes: ["members"],
|
|
3977
4049
|
requiredShadcnComponents: ["popover", "command", "button"],
|
|
@@ -4123,6 +4195,7 @@ export function MemberSelect({
|
|
|
4123
4195
|
// src/lib/registry/components/guild-select.ts
|
|
4124
4196
|
var guildSelect = {
|
|
4125
4197
|
name: "guild-select",
|
|
4198
|
+
kind: "component",
|
|
4126
4199
|
description: "Searchable combobox to pick a Discord server (with icon)",
|
|
4127
4200
|
requiredSyncTypes: ["guilds"],
|
|
4128
4201
|
requiredShadcnComponents: ["popover", "command", "button"],
|
|
@@ -4278,6 +4351,7 @@ export function GuildSelect({
|
|
|
4278
4351
|
// src/lib/registry/components/role-badge.ts
|
|
4279
4352
|
var roleBadge = {
|
|
4280
4353
|
name: "role-badge",
|
|
4354
|
+
kind: "component",
|
|
4281
4355
|
description: "Colored role pill badges that match Discord's role tags",
|
|
4282
4356
|
requiredSyncTypes: ["roles"],
|
|
4283
4357
|
requiredShadcnComponents: ["badge", "tooltip"],
|
|
@@ -4421,6 +4495,7 @@ export function RoleBadge({
|
|
|
4421
4495
|
// src/lib/registry/components/server-info.ts
|
|
4422
4496
|
var serverInfo = {
|
|
4423
4497
|
name: "server-info",
|
|
4498
|
+
kind: "component",
|
|
4424
4499
|
description: "Guild overview card with icon, name, boost tier, and feature tags",
|
|
4425
4500
|
requiredSyncTypes: ["guilds"],
|
|
4426
4501
|
requiredShadcnComponents: ["card", "avatar", "badge"],
|
|
@@ -4623,6 +4698,7 @@ export function ServerInfo({
|
|
|
4623
4698
|
// src/lib/registry/components/permission-viewer.ts
|
|
4624
4699
|
var permissionViewer = {
|
|
4625
4700
|
name: "permission-viewer",
|
|
4701
|
+
kind: "component",
|
|
4626
4702
|
description: "Read-only permission grid that parses Discord permission bitfields",
|
|
4627
4703
|
requiredSyncTypes: ["roles"],
|
|
4628
4704
|
requiredShadcnComponents: ["card", "badge", "tooltip", "separator"],
|
|
@@ -4855,6 +4931,7 @@ export function PermissionViewer({
|
|
|
4855
4931
|
// src/lib/registry/components/member-card.ts
|
|
4856
4932
|
var memberCard = {
|
|
4857
4933
|
name: "member-card",
|
|
4934
|
+
kind: "component",
|
|
4858
4935
|
description: "Profile card with avatar, name, role badges, and join date",
|
|
4859
4936
|
requiredSyncTypes: ["members", "roles"],
|
|
4860
4937
|
requiredShadcnComponents: ["card", "avatar", "badge", "separator"],
|
|
@@ -5091,6 +5168,7 @@ export function MemberCard({
|
|
|
5091
5168
|
// src/lib/registry/components/channel-tree.ts
|
|
5092
5169
|
var channelTree = {
|
|
5093
5170
|
name: "channel-tree",
|
|
5171
|
+
kind: "component",
|
|
5094
5172
|
description: "Hierarchical channel sidebar grouped by category with type icons",
|
|
5095
5173
|
requiredSyncTypes: ["channels"],
|
|
5096
5174
|
requiredShadcnComponents: ["collapsible", "scroll-area", "tooltip"],
|
|
@@ -5358,6 +5436,7 @@ function ChannelItem({
|
|
|
5358
5436
|
// src/lib/registry/components/member-roster.ts
|
|
5359
5437
|
var memberRoster = {
|
|
5360
5438
|
name: "member-roster",
|
|
5439
|
+
kind: "component",
|
|
5361
5440
|
description: "Member list grouped by hoisted roles, like Discord's right sidebar",
|
|
5362
5441
|
requiredSyncTypes: ["members", "roles"],
|
|
5363
5442
|
requiredShadcnComponents: ["scroll-area", "avatar", "badge", "separator"],
|
|
@@ -5614,6 +5693,44 @@ export function MemberRoster({
|
|
|
5614
5693
|
});`
|
|
5615
5694
|
};
|
|
5616
5695
|
|
|
5696
|
+
// src/lib/registry/components/use-online-count.ts
|
|
5697
|
+
var useOnlineCount = {
|
|
5698
|
+
name: "use-online-count",
|
|
5699
|
+
kind: "hook",
|
|
5700
|
+
description: "React hook that returns the number of online members in a guild",
|
|
5701
|
+
requiredSyncTypes: ["presence"],
|
|
5702
|
+
requiredShadcnComponents: [],
|
|
5703
|
+
generateReactComponent: () => `"use client";
|
|
5704
|
+
|
|
5705
|
+
import { useQuery } from "convex/react";
|
|
5706
|
+
import { api } from "@/convex/_generated/api";
|
|
5707
|
+
|
|
5708
|
+
/**
|
|
5709
|
+
* Returns the number of online members in a Discord guild.
|
|
5710
|
+
*
|
|
5711
|
+
* Requires \`sync.presence: true\` in your calabasas config.
|
|
5712
|
+
* The GuildPresences privileged intent must be enabled for your bot.
|
|
5713
|
+
*
|
|
5714
|
+
* @param guildDiscordId - The Discord guild ID
|
|
5715
|
+
* @returns The online member count, or \`undefined\` while loading
|
|
5716
|
+
*/
|
|
5717
|
+
export function useOnlineCount(guildDiscordId: string): number | undefined {
|
|
5718
|
+
return useQuery(api.calabasas.queries.onlineCount, { guildDiscordId });
|
|
5719
|
+
}
|
|
5720
|
+
`,
|
|
5721
|
+
generateConvexQueries: () => `export const onlineCount = query({
|
|
5722
|
+
args: { guildDiscordId: v.string() },
|
|
5723
|
+
returns: v.number(),
|
|
5724
|
+
handler: async (ctx, { guildDiscordId }) => {
|
|
5725
|
+
const presences = await ctx.db
|
|
5726
|
+
.query("calabasasPresence")
|
|
5727
|
+
.withIndex("by_guild", (q) => q.eq("guildDiscordId", guildDiscordId))
|
|
5728
|
+
.collect();
|
|
5729
|
+
return presences.length;
|
|
5730
|
+
},
|
|
5731
|
+
});`
|
|
5732
|
+
};
|
|
5733
|
+
|
|
5617
5734
|
// src/lib/registry/index.ts
|
|
5618
5735
|
var REGISTRY = [
|
|
5619
5736
|
channelSelect,
|
|
@@ -5625,7 +5742,8 @@ var REGISTRY = [
|
|
|
5625
5742
|
permissionViewer,
|
|
5626
5743
|
memberCard,
|
|
5627
5744
|
channelTree,
|
|
5628
|
-
memberRoster
|
|
5745
|
+
memberRoster,
|
|
5746
|
+
useOnlineCount
|
|
5629
5747
|
];
|
|
5630
5748
|
function getComponent(name) {
|
|
5631
5749
|
return REGISTRY.find((c) => c.name === name);
|
|
@@ -5646,6 +5764,8 @@ function parseEnabledSyncTypes(configPath) {
|
|
|
5646
5764
|
enabled.add("roles");
|
|
5647
5765
|
if (/members:\s*true/.test(syncContent))
|
|
5648
5766
|
enabled.add("members");
|
|
5767
|
+
if (/presence:\s*true/.test(syncContent))
|
|
5768
|
+
enabled.add("presence");
|
|
5649
5769
|
}
|
|
5650
5770
|
return enabled;
|
|
5651
5771
|
}
|
|
@@ -5737,9 +5857,10 @@ async function add(componentNames) {
|
|
|
5737
5857
|
if (!fs6.existsSync(componentsDir)) {
|
|
5738
5858
|
fs6.mkdirSync(componentsDir, { recursive: true });
|
|
5739
5859
|
}
|
|
5740
|
-
const
|
|
5860
|
+
const ext = comp.kind === "hook" ? ".ts" : ".tsx";
|
|
5861
|
+
const componentPath = path6.join(componentsDir, `${comp.name}${ext}`);
|
|
5741
5862
|
fs6.writeFileSync(componentPath, comp.generateReactComponent());
|
|
5742
|
-
p7.log.success(`Created components/calabasas/${comp.name}
|
|
5863
|
+
p7.log.success(`Created components/calabasas/${comp.name}${ext}`);
|
|
5743
5864
|
const queryBlock = comp.generateConvexQueries();
|
|
5744
5865
|
const namesInBlock = queryNamesInBlock(queryBlock);
|
|
5745
5866
|
const newNames = namesInBlock.filter((n) => !existing.has(n));
|
|
@@ -5790,45 +5911,52 @@ Install with: npx shadcn@latest add ${missingShadcn.join(" ")}`, "Required shadc
|
|
|
5790
5911
|
}
|
|
5791
5912
|
const firstInstalled = components[0];
|
|
5792
5913
|
if (firstInstalled) {
|
|
5793
|
-
const pascal = toPascalCase(firstInstalled.name);
|
|
5794
|
-
const needsGuild = firstInstalled.requiredSyncTypes.some((t) => t === "channels" || t === "roles" || t === "members");
|
|
5795
|
-
const displayComponents = new Set([
|
|
5796
|
-
"role-badge",
|
|
5797
|
-
"server-info",
|
|
5798
|
-
"permission-viewer",
|
|
5799
|
-
"member-card"
|
|
5800
|
-
]);
|
|
5801
|
-
const isDisplay = displayComponents.has(firstInstalled.name);
|
|
5802
5914
|
let usage;
|
|
5803
|
-
if (
|
|
5804
|
-
const
|
|
5805
|
-
|
|
5915
|
+
if (firstInstalled.kind === "hook") {
|
|
5916
|
+
const camel = firstInstalled.name.split("-").map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
5917
|
+
usage = `import { ${camel} } from "@/components/calabasas/${firstInstalled.name}";
|
|
5918
|
+
|
|
5919
|
+
const count = ${camel}("123456789");`;
|
|
5920
|
+
} else {
|
|
5921
|
+
const pascal = toPascalCase(firstInstalled.name);
|
|
5922
|
+
const needsGuild = firstInstalled.requiredSyncTypes.some((t) => t === "channels" || t === "roles" || t === "members");
|
|
5923
|
+
const displayComponents = new Set([
|
|
5924
|
+
"role-badge",
|
|
5925
|
+
"server-info",
|
|
5926
|
+
"permission-viewer",
|
|
5927
|
+
"member-card"
|
|
5928
|
+
]);
|
|
5929
|
+
const isDisplay = displayComponents.has(firstInstalled.name);
|
|
5930
|
+
if (isDisplay) {
|
|
5931
|
+
const propsMap = {
|
|
5932
|
+
"role-badge": ` guildDiscordId="123456789"
|
|
5806
5933
|
roleIds={["role_id_1", "role_id_2"]}`,
|
|
5807
|
-
|
|
5808
|
-
|
|
5934
|
+
"server-info": ` guildDiscordId="123456789"`,
|
|
5935
|
+
"permission-viewer": ` guildDiscordId="123456789"
|
|
5809
5936
|
roleDiscordId="role_id"`,
|
|
5810
|
-
|
|
5937
|
+
"member-card": ` guildDiscordId="123456789"
|
|
5811
5938
|
memberDiscordUserId="user_id"`
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5939
|
+
};
|
|
5940
|
+
const props = propsMap[firstInstalled.name] ?? ` guildDiscordId="123456789"`;
|
|
5941
|
+
usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
|
|
5815
5942
|
|
|
5816
5943
|
<${pascal}
|
|
5817
5944
|
${props}
|
|
5818
5945
|
/>`;
|
|
5819
|
-
|
|
5820
|
-
|
|
5946
|
+
} else if (needsGuild) {
|
|
5947
|
+
usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
|
|
5821
5948
|
|
|
5822
5949
|
<${pascal}
|
|
5823
5950
|
guildDiscordId="123456789"
|
|
5824
5951
|
onValueChange={(id) => console.log(id)}
|
|
5825
5952
|
/>`;
|
|
5826
|
-
|
|
5827
|
-
|
|
5953
|
+
} else {
|
|
5954
|
+
usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
|
|
5828
5955
|
|
|
5829
5956
|
<${pascal}
|
|
5830
5957
|
onValueChange={(id) => console.log(id)}
|
|
5831
5958
|
/>`;
|
|
5959
|
+
}
|
|
5832
5960
|
}
|
|
5833
5961
|
p7.note(usage, "Usage example");
|
|
5834
5962
|
}
|