calabasas 0.9.0 → 0.10.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 +49 -1199
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2447,1246 +2447,96 @@ ${intents.map((name) => ` - ${name}`).join(`
|
|
|
2447
2447
|
import * as fs4 from "fs";
|
|
2448
2448
|
import * as path4 from "path";
|
|
2449
2449
|
import * as p5 from "@clack/prompts";
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
const tables = [];
|
|
2454
|
-
if (sync.guilds) {
|
|
2455
|
-
tables.push(`export const calabasasGuilds = defineTable({
|
|
2456
|
-
discordId: v.string(),
|
|
2457
|
-
name: v.string(),
|
|
2458
|
-
icon: v.optional(v.string()),
|
|
2459
|
-
ownerId: v.string(),
|
|
2460
|
-
memberCount: v.optional(v.number()),
|
|
2461
|
-
features: v.array(v.string()),
|
|
2462
|
-
premiumTier: v.number(),
|
|
2463
|
-
syncedAt: v.number(),
|
|
2464
|
-
})
|
|
2465
|
-
.index("by_discord_id", ["discordId"]);`);
|
|
2466
|
-
tables.push(`export const calabasasGuildAdmins = defineTable({
|
|
2467
|
-
guildDiscordId: v.string(),
|
|
2468
|
-
discordUserId: v.string(),
|
|
2469
|
-
syncedAt: v.number(),
|
|
2470
|
-
})
|
|
2471
|
-
.index("by_user", ["discordUserId"])
|
|
2472
|
-
.index("by_guild", ["guildDiscordId"])
|
|
2473
|
-
.index("by_user_guild", ["discordUserId", "guildDiscordId"]);`);
|
|
2474
|
-
}
|
|
2475
|
-
if (sync.channels) {
|
|
2476
|
-
tables.push(`export const calabasasChannels = defineTable({
|
|
2477
|
-
discordId: v.string(),
|
|
2478
|
-
guildDiscordId: v.string(),
|
|
2479
|
-
name: v.string(),
|
|
2480
|
-
type: v.number(),
|
|
2481
|
-
position: v.number(),
|
|
2482
|
-
parentId: v.optional(v.string()),
|
|
2483
|
-
topic: v.optional(v.string()),
|
|
2484
|
-
nsfw: v.optional(v.boolean()),
|
|
2485
|
-
syncedAt: v.number(),
|
|
2486
|
-
})
|
|
2487
|
-
.index("by_discord_id", ["discordId"])
|
|
2488
|
-
.index("by_guild", ["guildDiscordId"]);`);
|
|
2489
|
-
}
|
|
2490
|
-
if (sync.roles) {
|
|
2491
|
-
tables.push(`export const calabasasRoles = defineTable({
|
|
2492
|
-
discordId: v.string(),
|
|
2493
|
-
guildDiscordId: v.string(),
|
|
2494
|
-
name: v.string(),
|
|
2495
|
-
color: v.number(),
|
|
2496
|
-
position: v.number(),
|
|
2497
|
-
permissions: v.string(),
|
|
2498
|
-
hoist: v.boolean(),
|
|
2499
|
-
mentionable: v.boolean(),
|
|
2500
|
-
syncedAt: v.number(),
|
|
2501
|
-
})
|
|
2502
|
-
.index("by_discord_id", ["discordId"])
|
|
2503
|
-
.index("by_guild", ["guildDiscordId"]);`);
|
|
2504
|
-
}
|
|
2505
|
-
if (sync.members) {
|
|
2506
|
-
tables.push(`export const calabasasMembers = defineTable({
|
|
2507
|
-
discordUserId: v.string(),
|
|
2508
|
-
guildDiscordId: v.string(),
|
|
2509
|
-
username: v.string(),
|
|
2510
|
-
displayName: v.optional(v.string()),
|
|
2511
|
-
avatar: v.optional(v.string()),
|
|
2512
|
-
nick: v.optional(v.string()),
|
|
2513
|
-
roles: v.array(v.string()),
|
|
2514
|
-
joinedAt: v.string(),
|
|
2515
|
-
bot: v.optional(v.boolean()),
|
|
2516
|
-
syncedAt: v.number(),
|
|
2517
|
-
})
|
|
2518
|
-
.index("by_user_guild", ["discordUserId", "guildDiscordId"])
|
|
2519
|
-
.index("by_guild", ["guildDiscordId"]);`);
|
|
2520
|
-
}
|
|
2521
|
-
if (sync.presence) {
|
|
2522
|
-
tables.push(`export const calabasasPresence = defineTable({
|
|
2523
|
-
discordUserId: v.string(),
|
|
2524
|
-
guildDiscordId: v.string(),
|
|
2525
|
-
status: v.string(),
|
|
2526
|
-
updatedAt: v.number(),
|
|
2527
|
-
})
|
|
2528
|
-
.index("by_guild", ["guildDiscordId"])
|
|
2529
|
-
.index("by_user_guild", ["discordUserId", "guildDiscordId"]);`);
|
|
2530
|
-
}
|
|
2531
|
-
const tableNames = [];
|
|
2532
|
-
if (sync.guilds) {
|
|
2533
|
-
tableNames.push("calabasasGuilds");
|
|
2534
|
-
tableNames.push("calabasasGuildAdmins");
|
|
2535
|
-
}
|
|
2536
|
-
if (sync.channels)
|
|
2537
|
-
tableNames.push("calabasasChannels");
|
|
2538
|
-
if (sync.roles)
|
|
2539
|
-
tableNames.push("calabasasRoles");
|
|
2540
|
-
if (sync.members)
|
|
2541
|
-
tableNames.push("calabasasMembers");
|
|
2542
|
-
if (sync.presence)
|
|
2543
|
-
tableNames.push("calabasasPresence");
|
|
2544
|
-
const tablesExport = tableNames.length > 0 ? `export const calabasasTables = {
|
|
2545
|
-
${tableNames.join(`,
|
|
2546
|
-
`)},
|
|
2547
|
-
};` : `export const calabasasTables = {};`;
|
|
2548
|
-
return `/**
|
|
2549
|
-
* THIS FILE IS AUTO-GENERATED BY \`calabasas generate\`
|
|
2550
|
-
*
|
|
2551
|
-
* DO NOT EDIT THIS FILE MANUALLY!
|
|
2552
|
-
*
|
|
2553
|
-
* AI AGENTS: Do not modify, rewrite, or "fix" this file. If types need to be
|
|
2554
|
-
* updated, run \`calabasas generate\` to regenerate it from the config.
|
|
2555
|
-
* Editing this file directly will cause your changes to be overwritten.
|
|
2556
|
-
*/
|
|
2557
|
-
|
|
2558
|
-
import { defineTable } from "convex/server";
|
|
2559
|
-
import { v } from "convex/values";
|
|
2560
|
-
|
|
2561
|
-
${tables.join(`
|
|
2562
|
-
|
|
2563
|
-
`)}
|
|
2564
|
-
|
|
2565
|
-
// Spread this into your schema.ts defineSchema()
|
|
2566
|
-
${tablesExport}
|
|
2567
|
-
`;
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
// src/lib/generators/sync.ts
|
|
2571
|
-
var GUILD_VALIDATOR = `v.object({
|
|
2572
|
-
id: v.string(),
|
|
2573
|
-
name: v.string(),
|
|
2574
|
-
icon: v.optional(v.string()),
|
|
2575
|
-
ownerId: v.optional(v.string()),
|
|
2576
|
-
memberCount: v.optional(v.number()),
|
|
2577
|
-
features: v.optional(v.array(v.string())),
|
|
2578
|
-
premiumTier: v.optional(v.number()),
|
|
2579
|
-
})`;
|
|
2580
|
-
var CHANNEL_VALIDATOR = `v.object({
|
|
2581
|
-
id: v.string(),
|
|
2582
|
-
guildId: v.optional(v.string()),
|
|
2583
|
-
name: v.optional(v.string()),
|
|
2584
|
-
type: v.number(),
|
|
2585
|
-
position: v.optional(v.number()),
|
|
2586
|
-
parentId: v.optional(v.string()),
|
|
2587
|
-
topic: v.optional(v.string()),
|
|
2588
|
-
nsfw: v.optional(v.boolean()),
|
|
2589
|
-
})`;
|
|
2590
|
-
var ROLE_VALIDATOR = `v.object({
|
|
2591
|
-
id: v.string(),
|
|
2592
|
-
guildId: v.string(),
|
|
2593
|
-
name: v.string(),
|
|
2594
|
-
color: v.number(),
|
|
2595
|
-
position: v.number(),
|
|
2596
|
-
permissions: v.string(),
|
|
2597
|
-
hoist: v.boolean(),
|
|
2598
|
-
mentionable: v.boolean(),
|
|
2599
|
-
})`;
|
|
2600
|
-
var MEMBER_VALIDATOR = `v.object({
|
|
2601
|
-
user: v.optional(v.object({
|
|
2602
|
-
id: v.string(),
|
|
2603
|
-
username: v.string(),
|
|
2604
|
-
globalName: v.optional(v.string()),
|
|
2605
|
-
avatar: v.optional(v.string()),
|
|
2606
|
-
bot: v.optional(v.boolean()),
|
|
2607
|
-
})),
|
|
2608
|
-
guildId: v.string(),
|
|
2609
|
-
nick: v.optional(v.string()),
|
|
2610
|
-
roles: v.optional(v.array(v.string())),
|
|
2611
|
-
joinedAt: v.optional(v.string()),
|
|
2612
|
-
})`;
|
|
2613
|
-
var GUILD_TYPE = `{
|
|
2614
|
-
id: string;
|
|
2615
|
-
name: string;
|
|
2616
|
-
icon?: string;
|
|
2617
|
-
ownerId?: string;
|
|
2618
|
-
memberCount?: number;
|
|
2619
|
-
features?: string[];
|
|
2620
|
-
premiumTier?: number;
|
|
2621
|
-
}`;
|
|
2622
|
-
var CHANNEL_TYPE = `{
|
|
2623
|
-
id: string;
|
|
2624
|
-
guildId?: string;
|
|
2625
|
-
name?: string;
|
|
2626
|
-
type: number;
|
|
2627
|
-
position?: number;
|
|
2628
|
-
parentId?: string;
|
|
2629
|
-
topic?: string;
|
|
2630
|
-
nsfw?: boolean;
|
|
2631
|
-
}`;
|
|
2632
|
-
var ROLE_TYPE = `{
|
|
2633
|
-
id: string;
|
|
2634
|
-
guildId: string;
|
|
2635
|
-
name: string;
|
|
2636
|
-
color: number;
|
|
2637
|
-
position: number;
|
|
2638
|
-
permissions: string;
|
|
2639
|
-
hoist: boolean;
|
|
2640
|
-
mentionable: boolean;
|
|
2641
|
-
}`;
|
|
2642
|
-
var MEMBER_TYPE = `{
|
|
2643
|
-
user?: {
|
|
2644
|
-
id: string;
|
|
2645
|
-
username: string;
|
|
2646
|
-
globalName?: string;
|
|
2647
|
-
avatar?: string;
|
|
2648
|
-
bot?: boolean;
|
|
2649
|
-
};
|
|
2650
|
-
guildId: string;
|
|
2651
|
-
nick?: string;
|
|
2652
|
-
roles?: string[];
|
|
2653
|
-
joinedAt?: string;
|
|
2654
|
-
}`;
|
|
2655
|
-
var PRESENCE_VALIDATOR = `v.object({
|
|
2656
|
-
userId: v.string(),
|
|
2657
|
-
guildId: v.string(),
|
|
2658
|
-
status: v.string(),
|
|
2659
|
-
})`;
|
|
2660
|
-
var PRESENCE_TYPE = `{
|
|
2661
|
-
userId: string;
|
|
2662
|
-
guildId: string;
|
|
2663
|
-
status: string;
|
|
2664
|
-
}`;
|
|
2665
|
-
var GUILD_ADMIN_VALIDATOR = `v.object({
|
|
2666
|
-
guildId: v.string(),
|
|
2667
|
-
adminUserIds: v.array(v.string()),
|
|
2668
|
-
})`;
|
|
2669
|
-
var GUILD_ADMIN_TYPE = `{
|
|
2670
|
-
guildId: string;
|
|
2671
|
-
adminUserIds: string[];
|
|
2672
|
-
}`;
|
|
2673
|
-
function generateGuildMutation() {
|
|
2674
|
-
return `export const syncGuild = internalMutation({
|
|
2675
|
-
args: {
|
|
2676
|
-
data: guildValidator,
|
|
2677
|
-
operation: v.union(v.literal("upsert"), v.literal("delete")),
|
|
2678
|
-
},
|
|
2679
|
-
returns: v.null(),
|
|
2680
|
-
handler: async (ctx, { data: guild, operation }) => {
|
|
2681
|
-
if (operation === "delete") {
|
|
2682
|
-
const existing = await ctx.db
|
|
2683
|
-
.query("calabasasGuilds")
|
|
2684
|
-
.withIndex("by_discord_id", (q) => q.eq("discordId", guild.id))
|
|
2685
|
-
.unique();
|
|
2686
|
-
if (existing) await ctx.db.delete(existing._id);
|
|
2687
|
-
return null;
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
|
-
const existing = await ctx.db
|
|
2691
|
-
.query("calabasasGuilds")
|
|
2692
|
-
.withIndex("by_discord_id", (q) => q.eq("discordId", guild.id))
|
|
2693
|
-
.unique();
|
|
2694
|
-
|
|
2695
|
-
const doc = {
|
|
2696
|
-
discordId: guild.id,
|
|
2697
|
-
name: guild.name,
|
|
2698
|
-
icon: guild.icon ?? undefined,
|
|
2699
|
-
ownerId: guild.ownerId ?? "",
|
|
2700
|
-
memberCount: guild.memberCount ?? undefined,
|
|
2701
|
-
features: guild.features ?? [],
|
|
2702
|
-
premiumTier: guild.premiumTier ?? 0,
|
|
2703
|
-
syncedAt: Date.now(),
|
|
2704
|
-
};
|
|
2705
|
-
|
|
2706
|
-
if (existing) {
|
|
2707
|
-
await ctx.db.patch(existing._id, doc);
|
|
2708
|
-
} else {
|
|
2709
|
-
await ctx.db.insert("calabasasGuilds", doc);
|
|
2710
|
-
}
|
|
2711
|
-
return null;
|
|
2712
|
-
},
|
|
2713
|
-
});`;
|
|
2714
|
-
}
|
|
2715
|
-
function generateChannelMutation() {
|
|
2716
|
-
return `export const syncChannel = internalMutation({
|
|
2717
|
-
args: {
|
|
2718
|
-
data: channelValidator,
|
|
2719
|
-
operation: v.union(v.literal("upsert"), v.literal("delete")),
|
|
2720
|
-
},
|
|
2721
|
-
returns: v.null(),
|
|
2722
|
-
handler: async (ctx, { data: channel, operation }) => {
|
|
2723
|
-
if (operation === "delete") {
|
|
2724
|
-
const existing = await ctx.db
|
|
2725
|
-
.query("calabasasChannels")
|
|
2726
|
-
.withIndex("by_discord_id", (q) => q.eq("discordId", channel.id))
|
|
2727
|
-
.unique();
|
|
2728
|
-
if (existing) await ctx.db.delete(existing._id);
|
|
2729
|
-
return null;
|
|
2730
|
-
}
|
|
2731
|
-
|
|
2732
|
-
const existing = await ctx.db
|
|
2733
|
-
.query("calabasasChannels")
|
|
2734
|
-
.withIndex("by_discord_id", (q) => q.eq("discordId", channel.id))
|
|
2735
|
-
.unique();
|
|
2736
|
-
|
|
2737
|
-
const doc = {
|
|
2738
|
-
discordId: channel.id,
|
|
2739
|
-
guildDiscordId: channel.guildId ?? "",
|
|
2740
|
-
name: channel.name ?? "",
|
|
2741
|
-
type: channel.type,
|
|
2742
|
-
position: channel.position ?? 0,
|
|
2743
|
-
parentId: channel.parentId ?? undefined,
|
|
2744
|
-
topic: channel.topic ?? undefined,
|
|
2745
|
-
nsfw: channel.nsfw ?? undefined,
|
|
2746
|
-
syncedAt: Date.now(),
|
|
2747
|
-
};
|
|
2748
|
-
|
|
2749
|
-
if (existing) {
|
|
2750
|
-
await ctx.db.patch(existing._id, doc);
|
|
2751
|
-
} else {
|
|
2752
|
-
await ctx.db.insert("calabasasChannels", doc);
|
|
2753
|
-
}
|
|
2754
|
-
return null;
|
|
2755
|
-
},
|
|
2756
|
-
});`;
|
|
2757
|
-
}
|
|
2758
|
-
function generateRoleMutation() {
|
|
2759
|
-
return `export const syncRole = internalMutation({
|
|
2760
|
-
args: {
|
|
2761
|
-
data: roleValidator,
|
|
2762
|
-
operation: v.union(v.literal("upsert"), v.literal("delete")),
|
|
2763
|
-
},
|
|
2764
|
-
returns: v.null(),
|
|
2765
|
-
handler: async (ctx, { data: role, operation }) => {
|
|
2766
|
-
if (operation === "delete") {
|
|
2767
|
-
const existing = await ctx.db
|
|
2768
|
-
.query("calabasasRoles")
|
|
2769
|
-
.withIndex("by_discord_id", (q) => q.eq("discordId", role.id))
|
|
2770
|
-
.unique();
|
|
2771
|
-
if (existing) await ctx.db.delete(existing._id);
|
|
2772
|
-
return null;
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
const existing = await ctx.db
|
|
2776
|
-
.query("calabasasRoles")
|
|
2777
|
-
.withIndex("by_discord_id", (q) => q.eq("discordId", role.id))
|
|
2778
|
-
.unique();
|
|
2779
|
-
|
|
2780
|
-
const doc = {
|
|
2781
|
-
discordId: role.id,
|
|
2782
|
-
guildDiscordId: role.guildId,
|
|
2783
|
-
name: role.name,
|
|
2784
|
-
color: role.color,
|
|
2785
|
-
position: role.position,
|
|
2786
|
-
permissions: role.permissions,
|
|
2787
|
-
hoist: role.hoist,
|
|
2788
|
-
mentionable: role.mentionable,
|
|
2789
|
-
syncedAt: Date.now(),
|
|
2790
|
-
};
|
|
2791
|
-
|
|
2792
|
-
if (existing) {
|
|
2793
|
-
await ctx.db.patch(existing._id, doc);
|
|
2794
|
-
} else {
|
|
2795
|
-
await ctx.db.insert("calabasasRoles", doc);
|
|
2796
|
-
}
|
|
2797
|
-
return null;
|
|
2798
|
-
},
|
|
2799
|
-
});`;
|
|
2800
|
-
}
|
|
2801
|
-
function generateMemberMutation() {
|
|
2802
|
-
return `export const syncMember = internalMutation({
|
|
2803
|
-
args: {
|
|
2804
|
-
data: memberValidator,
|
|
2805
|
-
operation: v.union(v.literal("upsert"), v.literal("delete")),
|
|
2806
|
-
},
|
|
2807
|
-
returns: v.null(),
|
|
2808
|
-
handler: async (ctx, { data: member, operation }) => {
|
|
2809
|
-
const userId = member.user?.id;
|
|
2810
|
-
if (!userId) return null;
|
|
2811
|
-
|
|
2812
|
-
if (operation === "delete") {
|
|
2813
|
-
const existing = await ctx.db
|
|
2814
|
-
.query("calabasasMembers")
|
|
2815
|
-
.withIndex("by_user_guild", (q) =>
|
|
2816
|
-
q.eq("discordUserId", userId).eq("guildDiscordId", member.guildId)
|
|
2817
|
-
)
|
|
2818
|
-
.unique();
|
|
2819
|
-
if (existing) await ctx.db.delete(existing._id);
|
|
2820
|
-
return null;
|
|
2821
|
-
}
|
|
2822
|
-
|
|
2823
|
-
const existing = await ctx.db
|
|
2824
|
-
.query("calabasasMembers")
|
|
2825
|
-
.withIndex("by_user_guild", (q) =>
|
|
2826
|
-
q.eq("discordUserId", userId).eq("guildDiscordId", member.guildId)
|
|
2827
|
-
)
|
|
2828
|
-
.unique();
|
|
2829
|
-
|
|
2830
|
-
const doc = {
|
|
2831
|
-
discordUserId: userId,
|
|
2832
|
-
guildDiscordId: member.guildId,
|
|
2833
|
-
username: member.user?.username ?? "",
|
|
2834
|
-
displayName: member.user?.globalName ?? undefined,
|
|
2835
|
-
avatar: member.user?.avatar ?? undefined,
|
|
2836
|
-
nick: member.nick ?? undefined,
|
|
2837
|
-
roles: member.roles ?? [],
|
|
2838
|
-
joinedAt: member.joinedAt ?? "",
|
|
2839
|
-
bot: member.user?.bot ?? undefined,
|
|
2840
|
-
syncedAt: Date.now(),
|
|
2841
|
-
};
|
|
2842
|
-
|
|
2843
|
-
if (existing) {
|
|
2844
|
-
await ctx.db.patch(existing._id, doc);
|
|
2845
|
-
} else {
|
|
2846
|
-
await ctx.db.insert("calabasasMembers", doc);
|
|
2847
|
-
}
|
|
2848
|
-
return null;
|
|
2849
|
-
},
|
|
2850
|
-
});`;
|
|
2851
|
-
}
|
|
2852
|
-
function generatePresenceMutation() {
|
|
2853
|
-
return `export const syncPresence = internalMutation({
|
|
2854
|
-
args: {
|
|
2855
|
-
data: presenceValidator,
|
|
2856
|
-
operation: v.union(v.literal("upsert"), v.literal("delete")),
|
|
2857
|
-
},
|
|
2858
|
-
returns: v.null(),
|
|
2859
|
-
handler: async (ctx, { data: presence, operation }) => {
|
|
2860
|
-
const existing = await ctx.db
|
|
2861
|
-
.query("calabasasPresence")
|
|
2862
|
-
.withIndex("by_user_guild", (q) =>
|
|
2863
|
-
q.eq("discordUserId", presence.userId).eq("guildDiscordId", presence.guildId)
|
|
2864
|
-
)
|
|
2865
|
-
.unique();
|
|
2866
|
-
|
|
2867
|
-
if (operation === "delete") {
|
|
2868
|
-
if (existing) await ctx.db.delete(existing._id);
|
|
2869
|
-
return null;
|
|
2870
|
-
}
|
|
2871
|
-
|
|
2872
|
-
const doc = {
|
|
2873
|
-
discordUserId: presence.userId,
|
|
2874
|
-
guildDiscordId: presence.guildId,
|
|
2875
|
-
status: presence.status,
|
|
2876
|
-
updatedAt: Date.now(),
|
|
2877
|
-
};
|
|
2878
|
-
|
|
2879
|
-
if (existing) {
|
|
2880
|
-
await ctx.db.patch(existing._id, doc);
|
|
2881
|
-
} else {
|
|
2882
|
-
await ctx.db.insert("calabasasPresence", doc);
|
|
2883
|
-
}
|
|
2884
|
-
return null;
|
|
2885
|
-
},
|
|
2886
|
-
});`;
|
|
2887
|
-
}
|
|
2888
|
-
function generateGuildAdminMutation() {
|
|
2889
|
-
return `export const _syncGuildAdmins = internalMutation({
|
|
2890
|
-
args: {
|
|
2891
|
-
data: guildAdminValidator,
|
|
2892
|
-
},
|
|
2893
|
-
returns: v.null(),
|
|
2894
|
-
handler: async (ctx, { data }) => {
|
|
2895
|
-
// Delete all existing admin entries for this guild
|
|
2896
|
-
const existing = await ctx.db
|
|
2897
|
-
.query("calabasasGuildAdmins")
|
|
2898
|
-
.withIndex("by_guild", (q) => q.eq("guildDiscordId", data.guildId))
|
|
2899
|
-
.collect();
|
|
2900
|
-
|
|
2901
|
-
await Promise.all(existing.map((entry) => ctx.db.delete(entry._id)));
|
|
2902
|
-
|
|
2903
|
-
// Insert new admin entries
|
|
2904
|
-
const now = Date.now();
|
|
2905
|
-
await Promise.all(
|
|
2906
|
-
data.adminUserIds.map((userId) =>
|
|
2907
|
-
ctx.db.insert("calabasasGuildAdmins", {
|
|
2908
|
-
guildDiscordId: data.guildId,
|
|
2909
|
-
discordUserId: userId,
|
|
2910
|
-
syncedAt: now,
|
|
2911
|
-
})
|
|
2912
|
-
)
|
|
2913
|
-
);
|
|
2914
|
-
|
|
2915
|
-
return null;
|
|
2916
|
-
},
|
|
2917
|
-
});`;
|
|
2918
|
-
}
|
|
2919
|
-
function generateGuildAdminPublicMutation() {
|
|
2920
|
-
return `
|
|
2921
|
-
/**
|
|
2922
|
-
* Public mutation called by Calabasas gateway.
|
|
2923
|
-
* Validates secret and delegates to internal mutation.
|
|
2924
|
-
*/
|
|
2925
|
-
export const syncGuildAdmins = mutation({
|
|
2926
|
-
args: {
|
|
2927
|
-
secret: v.string(),
|
|
2928
|
-
data: guildAdminValidator,
|
|
2929
|
-
},
|
|
2930
|
-
returns: v.null(),
|
|
2931
|
-
handler: async (ctx, { secret, data }) => {
|
|
2932
|
-
const expectedSecret = process.env.CALABASAS_SECRET;
|
|
2933
|
-
if (!expectedSecret || secret !== expectedSecret) {
|
|
2934
|
-
throw new Error("Invalid secret");
|
|
2935
|
-
}
|
|
2936
|
-
await ctx.runMutation(internal.calabasas._generated.sync._syncGuildAdmins, { data });
|
|
2937
|
-
return null;
|
|
2938
|
-
},
|
|
2939
|
-
});`;
|
|
2940
|
-
}
|
|
2941
|
-
function generatePublicSyncMutation(type) {
|
|
2942
|
-
const capitalType = type.charAt(0).toUpperCase() + type.slice(1);
|
|
2943
|
-
return `
|
|
2944
|
-
/**
|
|
2945
|
-
* Public mutation called by Calabasas gateway.
|
|
2946
|
-
* Validates secret and delegates to internal mutation.
|
|
2947
|
-
*/
|
|
2948
|
-
export const sync${capitalType} = mutation({
|
|
2949
|
-
args: {
|
|
2950
|
-
secret: v.string(),
|
|
2951
|
-
data: ${type}Validator,
|
|
2952
|
-
operation: v.union(v.literal("upsert"), v.literal("delete")),
|
|
2953
|
-
},
|
|
2954
|
-
returns: v.null(),
|
|
2955
|
-
handler: async (ctx, { secret, data, operation }) => {
|
|
2956
|
-
const expectedSecret = process.env.CALABASAS_SECRET;
|
|
2957
|
-
if (!expectedSecret || secret !== expectedSecret) {
|
|
2958
|
-
throw new Error("Invalid secret");
|
|
2959
|
-
}
|
|
2960
|
-
await ctx.runMutation(internal.calabasas._generated.sync._sync${capitalType}, { data, operation });
|
|
2961
|
-
return null;
|
|
2962
|
-
},
|
|
2963
|
-
});`;
|
|
2964
|
-
}
|
|
2965
|
-
function generateInternalSyncMutation(type, mutationCode) {
|
|
2966
|
-
return mutationCode.replace(`export const sync${type.charAt(0).toUpperCase() + type.slice(1)}`, `export const _sync${type.charAt(0).toUpperCase() + type.slice(1)}`);
|
|
2967
|
-
}
|
|
2968
|
-
function generateSyncFile(sync) {
|
|
2969
|
-
const validators = [];
|
|
2970
|
-
const internalMutations = [];
|
|
2971
|
-
const publicMutations = [];
|
|
2972
|
-
const types = [];
|
|
2973
|
-
const enabledTypes = [];
|
|
2974
|
-
if (sync.guilds) {
|
|
2975
|
-
validators.push(`const guildValidator = ${GUILD_VALIDATOR};`);
|
|
2976
|
-
validators.push(`const guildAdminValidator = ${GUILD_ADMIN_VALIDATOR};`);
|
|
2977
|
-
types.push(`export type GuildSync = ${GUILD_TYPE};`);
|
|
2978
|
-
types.push(`export type GuildAdminSync = ${GUILD_ADMIN_TYPE};`);
|
|
2979
|
-
internalMutations.push(generateInternalSyncMutation("guild", generateGuildMutation()));
|
|
2980
|
-
internalMutations.push(generateGuildAdminMutation());
|
|
2981
|
-
publicMutations.push(generatePublicSyncMutation("guild"));
|
|
2982
|
-
publicMutations.push(generateGuildAdminPublicMutation());
|
|
2983
|
-
enabledTypes.push("guild");
|
|
2984
|
-
}
|
|
2985
|
-
if (sync.channels) {
|
|
2986
|
-
validators.push(`const channelValidator = ${CHANNEL_VALIDATOR};`);
|
|
2987
|
-
types.push(`export type ChannelSync = ${CHANNEL_TYPE};`);
|
|
2988
|
-
internalMutations.push(generateInternalSyncMutation("channel", generateChannelMutation()));
|
|
2989
|
-
publicMutations.push(generatePublicSyncMutation("channel"));
|
|
2990
|
-
enabledTypes.push("channel");
|
|
2991
|
-
}
|
|
2992
|
-
if (sync.roles) {
|
|
2993
|
-
validators.push(`const roleValidator = ${ROLE_VALIDATOR};`);
|
|
2994
|
-
types.push(`export type RoleSync = ${ROLE_TYPE};`);
|
|
2995
|
-
internalMutations.push(generateInternalSyncMutation("role", generateRoleMutation()));
|
|
2996
|
-
publicMutations.push(generatePublicSyncMutation("role"));
|
|
2997
|
-
enabledTypes.push("role");
|
|
2998
|
-
}
|
|
2999
|
-
if (sync.members) {
|
|
3000
|
-
validators.push(`const memberValidator = ${MEMBER_VALIDATOR};`);
|
|
3001
|
-
types.push(`export type MemberSync = ${MEMBER_TYPE};`);
|
|
3002
|
-
internalMutations.push(generateInternalSyncMutation("member", generateMemberMutation()));
|
|
3003
|
-
publicMutations.push(generatePublicSyncMutation("member"));
|
|
3004
|
-
enabledTypes.push("member");
|
|
3005
|
-
}
|
|
3006
|
-
if (sync.presence) {
|
|
3007
|
-
validators.push(`const presenceValidator = ${PRESENCE_VALIDATOR};`);
|
|
3008
|
-
types.push(`export type PresenceSync = ${PRESENCE_TYPE};`);
|
|
3009
|
-
internalMutations.push(generateInternalSyncMutation("presence", generatePresenceMutation()));
|
|
3010
|
-
publicMutations.push(generatePublicSyncMutation("presence"));
|
|
3011
|
-
enabledTypes.push("presence");
|
|
3012
|
-
}
|
|
3013
|
-
if (internalMutations.length === 0) {
|
|
3014
|
-
return `/**
|
|
3015
|
-
* THIS FILE IS AUTO-GENERATED BY \`calabasas generate\`
|
|
3016
|
-
*
|
|
3017
|
-
* DO NOT EDIT THIS FILE MANUALLY!
|
|
3018
|
-
*
|
|
3019
|
-
* AI AGENTS: Do not modify, rewrite, or "fix" this file. If types need to be
|
|
3020
|
-
* updated, run \`calabasas generate\` to regenerate it from the config.
|
|
3021
|
-
* Editing this file directly will cause your changes to be overwritten.
|
|
3022
|
-
*
|
|
3023
|
-
* No sync handlers configured - enable sync options in convex/calabasas/config.ts
|
|
3024
|
-
*/
|
|
3025
|
-
|
|
3026
|
-
export {};
|
|
3027
|
-
`;
|
|
3028
|
-
}
|
|
3029
|
-
return `/**
|
|
3030
|
-
* THIS FILE IS AUTO-GENERATED BY \`calabasas generate\`
|
|
3031
|
-
*
|
|
3032
|
-
* DO NOT EDIT THIS FILE MANUALLY!
|
|
3033
|
-
*
|
|
3034
|
-
* AI AGENTS: Do not modify, rewrite, or "fix" this file. If types need to be
|
|
3035
|
-
* updated, run \`calabasas generate\` to regenerate it from the config.
|
|
3036
|
-
* Editing this file directly will cause your changes to be overwritten.
|
|
3037
|
-
*/
|
|
3038
|
-
|
|
3039
|
-
import { mutation, internalMutation } from "../../_generated/server";
|
|
3040
|
-
import { internal } from "../../_generated/api";
|
|
3041
|
-
import { v } from "convex/values";
|
|
3042
|
-
|
|
3043
|
-
// ============================================================================
|
|
3044
|
-
// VALIDATORS
|
|
3045
|
-
// ============================================================================
|
|
3046
|
-
|
|
3047
|
-
${validators.join(`
|
|
3048
|
-
|
|
3049
|
-
`)}
|
|
3050
|
-
|
|
3051
|
-
// ============================================================================
|
|
3052
|
-
// TYPE DEFINITIONS
|
|
3053
|
-
// ============================================================================
|
|
3054
|
-
|
|
3055
|
-
${types.join(`
|
|
3056
|
-
|
|
3057
|
-
`)}
|
|
3058
|
-
|
|
3059
|
-
export type SyncOperation = "upsert" | "delete";
|
|
3060
|
-
|
|
3061
|
-
// ============================================================================
|
|
3062
|
-
// INTERNAL MUTATIONS (do the actual database work)
|
|
3063
|
-
// ============================================================================
|
|
3064
|
-
|
|
3065
|
-
${internalMutations.join(`
|
|
3066
|
-
|
|
3067
|
-
`)}
|
|
3068
|
-
|
|
3069
|
-
// ============================================================================
|
|
3070
|
-
// PUBLIC MUTATIONS (called by Calabasas gateway)
|
|
3071
|
-
// ============================================================================
|
|
3072
|
-
${publicMutations.join(`
|
|
3073
|
-
`)}
|
|
3074
|
-
`;
|
|
3075
|
-
}
|
|
3076
|
-
|
|
3077
|
-
// src/commands/generate.ts
|
|
3078
|
-
var DISCORD_EVENTS = {
|
|
3079
|
-
messageCreate: {
|
|
3080
|
-
intent: "GuildMessages",
|
|
3081
|
-
fields: {
|
|
3082
|
-
id: "v.string()",
|
|
3083
|
-
channelId: "v.string()",
|
|
3084
|
-
guildId: "v.optional(v.string())",
|
|
3085
|
-
author: "v.object({ id: v.string(), username: v.string(), discriminator: v.string(), avatar: v.optional(v.string()), bot: v.optional(v.boolean()) })",
|
|
3086
|
-
content: "v.string()",
|
|
3087
|
-
timestamp: "v.string()",
|
|
3088
|
-
editedTimestamp: "v.optional(v.string())",
|
|
3089
|
-
tts: "v.boolean()",
|
|
3090
|
-
mentionEveryone: "v.boolean()",
|
|
3091
|
-
attachments: "v.array(v.object({ id: v.string(), filename: v.string(), size: v.number(), url: v.string() }))",
|
|
3092
|
-
embeds: "v.array(v.any())"
|
|
3093
|
-
}
|
|
3094
|
-
},
|
|
3095
|
-
messageUpdate: {
|
|
3096
|
-
intent: "GuildMessages",
|
|
3097
|
-
fields: {
|
|
3098
|
-
id: "v.string()",
|
|
3099
|
-
channelId: "v.string()",
|
|
3100
|
-
guildId: "v.optional(v.string())",
|
|
3101
|
-
content: "v.optional(v.string())",
|
|
3102
|
-
editedTimestamp: "v.optional(v.string())"
|
|
3103
|
-
}
|
|
3104
|
-
},
|
|
3105
|
-
messageDelete: {
|
|
3106
|
-
intent: "GuildMessages",
|
|
3107
|
-
fields: {
|
|
3108
|
-
id: "v.string()",
|
|
3109
|
-
channelId: "v.string()",
|
|
3110
|
-
guildId: "v.optional(v.string())"
|
|
3111
|
-
}
|
|
3112
|
-
},
|
|
3113
|
-
guildMemberAdd: {
|
|
3114
|
-
intent: "GuildMembers",
|
|
3115
|
-
fields: {
|
|
3116
|
-
guildId: "v.string()",
|
|
3117
|
-
user: "v.object({ id: v.string(), username: v.string(), discriminator: v.string(), avatar: v.optional(v.string()) })",
|
|
3118
|
-
nick: "v.optional(v.string())",
|
|
3119
|
-
roles: "v.array(v.string())",
|
|
3120
|
-
joinedAt: "v.string()"
|
|
3121
|
-
}
|
|
3122
|
-
},
|
|
3123
|
-
guildMemberRemove: {
|
|
3124
|
-
intent: "GuildMembers",
|
|
3125
|
-
fields: {
|
|
3126
|
-
guildId: "v.string()",
|
|
3127
|
-
user: "v.object({ id: v.string(), username: v.string(), discriminator: v.string(), avatar: v.optional(v.string()) })"
|
|
3128
|
-
}
|
|
3129
|
-
},
|
|
3130
|
-
guildMemberUpdate: {
|
|
3131
|
-
intent: "GuildMembers",
|
|
3132
|
-
fields: {
|
|
3133
|
-
guildId: "v.string()",
|
|
3134
|
-
user: "v.object({ id: v.string(), username: v.string(), discriminator: v.string(), avatar: v.optional(v.string()) })",
|
|
3135
|
-
nick: "v.optional(v.string())",
|
|
3136
|
-
roles: "v.array(v.string())"
|
|
3137
|
-
}
|
|
3138
|
-
},
|
|
3139
|
-
interactionCreate: {
|
|
3140
|
-
intent: null,
|
|
3141
|
-
fields: {
|
|
3142
|
-
id: "v.string()",
|
|
3143
|
-
applicationId: "v.string()",
|
|
3144
|
-
type: "v.number()",
|
|
3145
|
-
guildId: "v.optional(v.string())",
|
|
3146
|
-
channelId: "v.optional(v.string())",
|
|
3147
|
-
member: "v.optional(v.any())",
|
|
3148
|
-
user: "v.optional(v.object({ id: v.string(), username: v.string(), discriminator: v.string(), avatar: v.optional(v.string()) }))",
|
|
3149
|
-
token: "v.string()",
|
|
3150
|
-
data: "v.optional(v.any())"
|
|
3151
|
-
}
|
|
3152
|
-
},
|
|
3153
|
-
voiceStateUpdate: {
|
|
3154
|
-
intent: "GuildVoiceStates",
|
|
3155
|
-
fields: {
|
|
3156
|
-
guildId: "v.optional(v.string())",
|
|
3157
|
-
channelId: "v.optional(v.string())",
|
|
3158
|
-
userId: "v.string()",
|
|
3159
|
-
sessionId: "v.string()",
|
|
3160
|
-
deaf: "v.boolean()",
|
|
3161
|
-
mute: "v.boolean()",
|
|
3162
|
-
selfDeaf: "v.boolean()",
|
|
3163
|
-
selfMute: "v.boolean()"
|
|
3164
|
-
}
|
|
3165
|
-
},
|
|
3166
|
-
presenceUpdate: {
|
|
3167
|
-
intent: "GuildPresences",
|
|
3168
|
-
fields: {
|
|
3169
|
-
user: "v.object({ id: v.string() })",
|
|
3170
|
-
guildId: "v.string()",
|
|
3171
|
-
status: "v.string()",
|
|
3172
|
-
activities: "v.array(v.any())"
|
|
3173
|
-
}
|
|
3174
|
-
},
|
|
3175
|
-
typingStart: {
|
|
3176
|
-
intent: "GuildMessageTyping",
|
|
3177
|
-
fields: {
|
|
3178
|
-
channelId: "v.string()",
|
|
3179
|
-
guildId: "v.optional(v.string())",
|
|
3180
|
-
userId: "v.string()",
|
|
3181
|
-
timestamp: "v.number()"
|
|
3182
|
-
}
|
|
3183
|
-
},
|
|
3184
|
-
messageReactionAdd: {
|
|
3185
|
-
intent: "GuildMessageReactions",
|
|
3186
|
-
fields: {
|
|
3187
|
-
userId: "v.string()",
|
|
3188
|
-
channelId: "v.string()",
|
|
3189
|
-
messageId: "v.string()",
|
|
3190
|
-
guildId: "v.optional(v.string())",
|
|
3191
|
-
emoji: "v.object({ id: v.optional(v.string()), name: v.optional(v.string()) })"
|
|
3192
|
-
}
|
|
3193
|
-
},
|
|
3194
|
-
messageReactionRemove: {
|
|
3195
|
-
intent: "GuildMessageReactions",
|
|
3196
|
-
fields: {
|
|
3197
|
-
userId: "v.string()",
|
|
3198
|
-
channelId: "v.string()",
|
|
3199
|
-
messageId: "v.string()",
|
|
3200
|
-
guildId: "v.optional(v.string())",
|
|
3201
|
-
emoji: "v.object({ id: v.optional(v.string()), name: v.optional(v.string()) })"
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
};
|
|
3205
|
-
function toScreamingSnake(camelCase) {
|
|
3206
|
-
return camelCase.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
3207
|
-
}
|
|
3208
|
-
function generateValidatorForEvent(eventName) {
|
|
3209
|
-
const event = DISCORD_EVENTS[eventName];
|
|
3210
|
-
const fieldEntries = Object.entries(event.fields).map(([key, validator]) => ` ${key}: ${validator},`).join(`
|
|
3211
|
-
`);
|
|
3212
|
-
return `v.object({
|
|
3213
|
-
${fieldEntries}
|
|
3214
|
-
})`;
|
|
3215
|
-
}
|
|
3216
|
-
function generateTypeForEvent(eventName) {
|
|
3217
|
-
const event = DISCORD_EVENTS[eventName];
|
|
3218
|
-
const fieldEntries = Object.entries(event.fields).map(([key, validator]) => {
|
|
3219
|
-
const tsType = validatorToType(validator);
|
|
3220
|
-
return ` ${key}: ${tsType};`;
|
|
3221
|
-
}).join(`
|
|
3222
|
-
`);
|
|
3223
|
-
return `{
|
|
3224
|
-
${fieldEntries}
|
|
3225
|
-
}`;
|
|
3226
|
-
}
|
|
3227
|
-
function splitObjectFields(content) {
|
|
3228
|
-
const fields = [];
|
|
3229
|
-
let depth = 0;
|
|
3230
|
-
let current = "";
|
|
3231
|
-
for (const char of content) {
|
|
3232
|
-
if (char === "(" || char === "{")
|
|
3233
|
-
depth++;
|
|
3234
|
-
if (char === ")" || char === "}")
|
|
3235
|
-
depth--;
|
|
3236
|
-
if (char === "," && depth === 0) {
|
|
3237
|
-
if (current.trim())
|
|
3238
|
-
fields.push(current.trim());
|
|
3239
|
-
current = "";
|
|
3240
|
-
} else {
|
|
3241
|
-
current += char;
|
|
3242
|
-
}
|
|
3243
|
-
}
|
|
3244
|
-
if (current.trim())
|
|
3245
|
-
fields.push(current.trim());
|
|
3246
|
-
return fields;
|
|
3247
|
-
}
|
|
3248
|
-
function validatorToType(validator) {
|
|
3249
|
-
if (validator.startsWith("v.string()"))
|
|
3250
|
-
return "string";
|
|
3251
|
-
if (validator.startsWith("v.number()"))
|
|
3252
|
-
return "number";
|
|
3253
|
-
if (validator.startsWith("v.boolean()"))
|
|
3254
|
-
return "boolean";
|
|
3255
|
-
if (validator.startsWith("v.any()"))
|
|
3256
|
-
return "unknown";
|
|
3257
|
-
if (validator.startsWith("v.optional(")) {
|
|
3258
|
-
const inner = validator.slice(11, -1);
|
|
3259
|
-
return `${validatorToType(inner)} | undefined`;
|
|
3260
|
-
}
|
|
3261
|
-
if (validator.startsWith("v.array(")) {
|
|
3262
|
-
const inner = validator.slice(8, -1);
|
|
3263
|
-
return `Array<${validatorToType(inner)}>`;
|
|
3264
|
-
}
|
|
3265
|
-
if (validator.startsWith("v.object(")) {
|
|
3266
|
-
const objectLiteral = validator.slice(9, -1).trim();
|
|
3267
|
-
const content = objectLiteral.slice(1, -1).trim();
|
|
3268
|
-
if (!content)
|
|
3269
|
-
return "Record<string, never>";
|
|
3270
|
-
const fields = splitObjectFields(content);
|
|
3271
|
-
const fieldTypes = fields.filter((f) => f.includes(":")).map((field) => {
|
|
3272
|
-
const colonIndex = field.indexOf(":");
|
|
3273
|
-
const key = field.slice(0, colonIndex).trim();
|
|
3274
|
-
const fieldValidator = field.slice(colonIndex + 1).trim();
|
|
3275
|
-
const tsType = validatorToType(fieldValidator);
|
|
3276
|
-
return `${key}: ${tsType}`;
|
|
3277
|
-
});
|
|
3278
|
-
return `{ ${fieldTypes.join("; ")} }`;
|
|
3279
|
-
}
|
|
3280
|
-
return "unknown";
|
|
3281
|
-
}
|
|
3282
|
-
function generateInteractionTypes() {
|
|
3283
|
-
return `
|
|
3284
|
-
// Interaction type constants
|
|
3285
|
-
export const InteractionType = {
|
|
3286
|
-
/** Slash commands, user commands, and message commands */
|
|
3287
|
-
ApplicationCommand: 2,
|
|
3288
|
-
/** Buttons, select menus, and other message components */
|
|
3289
|
-
MessageComponent: 3,
|
|
3290
|
-
/** Autocomplete for command options */
|
|
3291
|
-
Autocomplete: 4,
|
|
3292
|
-
/** Modal form submissions */
|
|
3293
|
-
ModalSubmit: 5,
|
|
3294
|
-
} as const;
|
|
3295
|
-
|
|
3296
|
-
// Interaction data types — discriminated by interaction type
|
|
3297
|
-
export type ApplicationCommandOption = {
|
|
3298
|
-
name: string;
|
|
3299
|
-
type: number;
|
|
3300
|
-
value?: string | number | boolean;
|
|
3301
|
-
options?: ApplicationCommandOption[];
|
|
3302
|
-
focused?: boolean;
|
|
3303
|
-
};
|
|
3304
|
-
|
|
3305
|
-
/** Data for slash commands, user commands, and message commands (type 2) */
|
|
3306
|
-
export type ApplicationCommandData = {
|
|
3307
|
-
id: string;
|
|
3308
|
-
name: string;
|
|
3309
|
-
type: 1 | 2 | 3;
|
|
3310
|
-
options?: ApplicationCommandOption[];
|
|
3311
|
-
resolved?: Record<string, unknown>;
|
|
3312
|
-
guildId?: string;
|
|
3313
|
-
targetId?: string;
|
|
3314
|
-
};
|
|
3315
|
-
|
|
3316
|
-
/** Data for buttons, select menus, and other message components (type 3) */
|
|
3317
|
-
export type MessageComponentData = {
|
|
3318
|
-
custom_id: string;
|
|
3319
|
-
component_type: number;
|
|
3320
|
-
values?: string[];
|
|
3321
|
-
resolved?: Record<string, unknown>;
|
|
3322
|
-
};
|
|
3323
|
-
|
|
3324
|
-
/** Data for autocomplete interactions (type 4) */
|
|
3325
|
-
export type AutocompleteData = {
|
|
3326
|
-
id: string;
|
|
3327
|
-
name: string;
|
|
3328
|
-
type: 1 | 2 | 3;
|
|
3329
|
-
options?: ApplicationCommandOption[];
|
|
3330
|
-
};
|
|
3331
|
-
|
|
3332
|
-
/** Data for modal form submissions (type 5) */
|
|
3333
|
-
export type ModalSubmitData = {
|
|
3334
|
-
custom_id: string;
|
|
3335
|
-
components: Array<{
|
|
3336
|
-
type: number;
|
|
3337
|
-
components: Array<{
|
|
3338
|
-
type: number;
|
|
3339
|
-
custom_id: string;
|
|
3340
|
-
value: string;
|
|
3341
|
-
}>;
|
|
3342
|
-
}>;
|
|
3343
|
-
};
|
|
3344
|
-
|
|
3345
|
-
type InteractionBase = {
|
|
3346
|
-
id: string;
|
|
3347
|
-
applicationId: string;
|
|
3348
|
-
guildId?: string;
|
|
3349
|
-
channelId?: string;
|
|
3350
|
-
member?: Record<string, unknown>;
|
|
3351
|
-
user?: { id: string; username: string; discriminator: string; avatar?: string };
|
|
3352
|
-
token: string;
|
|
3353
|
-
};
|
|
3354
|
-
|
|
3355
|
-
export type InteractionCreateEvent =
|
|
3356
|
-
| (InteractionBase & { type: 2; data: ApplicationCommandData })
|
|
3357
|
-
| (InteractionBase & { type: 3; data: MessageComponentData })
|
|
3358
|
-
| (InteractionBase & { type: 4; data: AutocompleteData })
|
|
3359
|
-
| (InteractionBase & { type: 5; data: ModalSubmitData });
|
|
3360
|
-
|
|
3361
|
-
// Interaction response types
|
|
3362
|
-
export type InteractionResponse = {
|
|
3363
|
-
content?: string;
|
|
3364
|
-
embeds?: unknown[];
|
|
3365
|
-
components?: unknown[];
|
|
3366
|
-
ephemeral?: boolean;
|
|
3367
|
-
allowedMentions?: unknown;
|
|
3368
|
-
};
|
|
3369
|
-
|
|
3370
|
-
export type AutocompleteResponse = {
|
|
3371
|
-
choices: Array<{ name: string; value: string | number }>;
|
|
3372
|
-
};
|
|
3373
|
-
`;
|
|
3374
|
-
}
|
|
3375
|
-
function generateHandlersCode(eventNames) {
|
|
3376
|
-
const screamingEvents = eventNames.map(toScreamingSnake);
|
|
3377
|
-
const hasInteraction = eventNames.includes("interactionCreate");
|
|
3378
|
-
const interactionResponseTypes = hasInteraction ? generateInteractionTypes() : "";
|
|
3379
|
-
const handlerReturnType = (name) => {
|
|
3380
|
-
if (name === "interactionCreate") {
|
|
3381
|
-
return `Promise<InteractionResponse | AutocompleteResponse | void>`;
|
|
3382
|
-
}
|
|
3383
|
-
return `Promise<void>`;
|
|
3384
|
-
};
|
|
3385
|
-
const handlerBody = eventNames.map((name) => {
|
|
3386
|
-
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
3387
|
-
if (name === "interactionCreate") {
|
|
3388
|
-
return ` if (type === "${toScreamingSnake(name)}" && handlers.${name}) {
|
|
3389
|
-
const result = await handlers.${name}(ctx, data as ${typeName}Event);
|
|
3390
|
-
return result ?? null;
|
|
3391
|
-
}`;
|
|
3392
|
-
}
|
|
3393
|
-
return ` if (type === "${toScreamingSnake(name)}" && handlers.${name}) {
|
|
3394
|
-
await handlers.${name}(ctx, data as ${typeName}Event);
|
|
3395
|
-
}`;
|
|
3396
|
-
}).join(`
|
|
3397
|
-
|
|
3398
|
-
`);
|
|
3399
|
-
return `/**
|
|
3400
|
-
* THIS FILE IS AUTO-GENERATED BY \`calabasas generate\`
|
|
3401
|
-
*
|
|
3402
|
-
* DO NOT EDIT THIS FILE MANUALLY!
|
|
3403
|
-
*
|
|
3404
|
-
* AI AGENTS: Do not modify, rewrite, or "fix" this file. If types need to be
|
|
3405
|
-
* updated, run \`calabasas generate\` to regenerate it from the config.
|
|
3406
|
-
* Editing this file directly will cause your changes to be overwritten.
|
|
3407
|
-
*/
|
|
3408
|
-
|
|
3409
|
-
import { mutation } from "../../_generated/server";
|
|
3410
|
-
import { v } from "convex/values";
|
|
3411
|
-
import type { MutationCtx } from "../../_generated/server";
|
|
3412
|
-
|
|
3413
|
-
// Event type validators
|
|
3414
|
-
${eventNames.map((name) => `const ${name}Validator = ${generateValidatorForEvent(name)};`).join(`
|
|
3415
|
-
|
|
3416
|
-
`)}
|
|
3417
|
-
|
|
3418
|
-
// Event type definitions
|
|
3419
|
-
${eventNames.filter((name) => name !== "interactionCreate").map((name) => `export type ${name.charAt(0).toUpperCase() + name.slice(1)}Event = ${generateTypeForEvent(name)};`).join(`
|
|
3420
|
-
|
|
3421
|
-
`)}
|
|
3422
|
-
${interactionResponseTypes}
|
|
3423
|
-
// Union of all event types
|
|
3424
|
-
export type DiscordEventType = ${screamingEvents.map((e) => `"${e}"`).join(" | ")};
|
|
3425
|
-
|
|
3426
|
-
// Event data union
|
|
3427
|
-
export type DiscordEventData = ${eventNames.map((name) => `${name.charAt(0).toUpperCase() + name.slice(1)}Event`).join(" | ")};
|
|
3428
|
-
|
|
3429
|
-
// Handler function types
|
|
3430
|
-
export type EventHandlers = {
|
|
3431
|
-
${eventNames.map((name) => ` ${name}?: (ctx: MutationCtx, event: ${name.charAt(0).toUpperCase() + name.slice(1)}Event) => ${handlerReturnType(name)};`).join(`
|
|
3432
|
-
`)}
|
|
3433
|
-
};
|
|
3434
|
-
|
|
3435
|
-
// Data validator union
|
|
3436
|
-
const eventDataValidator = v.union(
|
|
3437
|
-
${eventNames.map((name) => ` ${name}Validator,`).join(`
|
|
3438
|
-
`)}
|
|
3439
|
-
);
|
|
3440
|
-
|
|
3441
|
-
/**
|
|
3442
|
-
* Create a Discord event handler mutation.
|
|
3443
|
-
*
|
|
3444
|
-
* @example
|
|
3445
|
-
* \`\`\`ts
|
|
3446
|
-
* export const receive = handleDiscordEvent({
|
|
3447
|
-
* messageCreate: async (ctx, event) => {
|
|
3448
|
-
* await ctx.db.insert("messages", {
|
|
3449
|
-
* discordId: event.id,
|
|
3450
|
-
* content: event.content,
|
|
3451
|
-
* });
|
|
3452
|
-
* },
|
|
3453
|
-
* });
|
|
3454
|
-
* \`\`\`
|
|
3455
|
-
*/
|
|
3456
|
-
export function handleDiscordEvent(handlers: EventHandlers) {
|
|
3457
|
-
return mutation({
|
|
3458
|
-
args: {
|
|
3459
|
-
secret: v.string(),
|
|
3460
|
-
type: v.union(${screamingEvents.map((e) => `v.literal("${e}")`).join(", ")}),
|
|
3461
|
-
data: eventDataValidator,
|
|
3462
|
-
},
|
|
3463
|
-
returns: v.any(),
|
|
3464
|
-
handler: async (ctx, { secret, type, data }) => {
|
|
3465
|
-
// Validate shared secret
|
|
3466
|
-
const expectedSecret = process.env.CALABASAS_SECRET;
|
|
3467
|
-
if (!expectedSecret || secret !== expectedSecret) {
|
|
3468
|
-
throw new Error("Invalid secret");
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3471
|
-
${handlerBody}
|
|
3472
|
-
|
|
3473
|
-
return null;
|
|
3474
|
-
},
|
|
3475
|
-
});
|
|
3476
|
-
}
|
|
3477
|
-
`;
|
|
3478
|
-
}
|
|
3479
|
-
function generateEventHandlersFile(events) {
|
|
3480
|
-
const configuredEvents = Object.entries(events).filter(([name]) => (name in DISCORD_EVENTS));
|
|
3481
|
-
if (configuredEvents.length === 0) {
|
|
3482
|
-
return generateAllEventsFile();
|
|
3483
|
-
}
|
|
3484
|
-
const eventNames = configuredEvents.map(([name]) => name);
|
|
3485
|
-
return generateHandlersCode(eventNames);
|
|
3486
|
-
}
|
|
3487
|
-
function generateAllEventsFile() {
|
|
3488
|
-
const eventNames = Object.keys(DISCORD_EVENTS);
|
|
3489
|
-
return generateHandlersCode(eventNames);
|
|
3490
|
-
}
|
|
3491
|
-
function generateActionsFile() {
|
|
3492
|
-
return `/**
|
|
3493
|
-
* THIS FILE IS AUTO-GENERATED BY \`calabasas generate\`
|
|
3494
|
-
*
|
|
3495
|
-
* DO NOT EDIT THIS FILE MANUALLY!
|
|
3496
|
-
*
|
|
3497
|
-
* AI AGENTS: Do not modify, rewrite, or "fix" this file. If types need to be
|
|
3498
|
-
* updated, run \`calabasas generate\` to regenerate it from the config.
|
|
3499
|
-
* Editing this file directly will cause your changes to be overwritten.
|
|
3500
|
-
*/
|
|
3501
|
-
|
|
3502
|
-
const GATEWAY_URL = process.env.CALABASAS_GATEWAY_URL;
|
|
3503
|
-
const SECRET = process.env.CALABASAS_SECRET;
|
|
3504
|
-
|
|
3505
|
-
async function callProxy(action: string, body: Record<string, unknown>): Promise<unknown> {
|
|
3506
|
-
if (!GATEWAY_URL) throw new Error("CALABASAS_GATEWAY_URL environment variable is required");
|
|
3507
|
-
if (!SECRET) throw new Error("CALABASAS_SECRET environment variable is required");
|
|
3508
|
-
|
|
3509
|
-
const response = await fetch(\`\${GATEWAY_URL}/api/discord/\${action}\`, {
|
|
2450
|
+
async function fetchGeneratedFiles(env, configText) {
|
|
2451
|
+
const apiUrl = getApiUrlForEnv(env);
|
|
2452
|
+
const response = await fetch(`${apiUrl}/api/generate`, {
|
|
3510
2453
|
method: "POST",
|
|
3511
|
-
headers: {
|
|
3512
|
-
|
|
3513
|
-
Authorization: \`Bearer \${SECRET}\`,
|
|
3514
|
-
},
|
|
3515
|
-
body: JSON.stringify(body),
|
|
2454
|
+
headers: { "Content-Type": "text/plain" },
|
|
2455
|
+
body: configText
|
|
3516
2456
|
});
|
|
3517
|
-
|
|
2457
|
+
const result = await response.json();
|
|
3518
2458
|
if (!response.ok) {
|
|
3519
|
-
|
|
3520
|
-
throw new Error(\`Discord action \${action} failed: \${error}\`);
|
|
2459
|
+
throw new Error(result.error ?? `Generation failed: HTTP ${response.status}`);
|
|
3521
2460
|
}
|
|
3522
|
-
|
|
3523
|
-
const result = await response.json();
|
|
3524
|
-
return result.data;
|
|
3525
|
-
}
|
|
3526
|
-
|
|
3527
|
-
export const discord = {
|
|
3528
|
-
/** Send a message to a channel */
|
|
3529
|
-
sendMessage(channelId: string, data: { content?: string; embeds?: unknown[]; components?: unknown[] }): Promise<unknown> {
|
|
3530
|
-
return callProxy("sendMessage", { channelId, ...data });
|
|
3531
|
-
},
|
|
3532
|
-
|
|
3533
|
-
/** Edit an existing message */
|
|
3534
|
-
editMessage(channelId: string, messageId: string, data: { content?: string; embeds?: unknown[]; components?: unknown[] }): Promise<unknown> {
|
|
3535
|
-
return callProxy("editMessage", { channelId, messageId, ...data });
|
|
3536
|
-
},
|
|
3537
|
-
|
|
3538
|
-
/** Delete a message */
|
|
3539
|
-
deleteMessage(channelId: string, messageId: string): Promise<unknown> {
|
|
3540
|
-
return callProxy("deleteMessage", { channelId, messageId });
|
|
3541
|
-
},
|
|
3542
|
-
|
|
3543
|
-
/** Get a message by ID */
|
|
3544
|
-
getMessage(channelId: string, messageId: string): Promise<unknown> {
|
|
3545
|
-
return callProxy("getMessage", { channelId, messageId });
|
|
3546
|
-
},
|
|
3547
|
-
|
|
3548
|
-
/** Add a reaction to a message */
|
|
3549
|
-
addReaction(channelId: string, messageId: string, emoji: string): Promise<unknown> {
|
|
3550
|
-
return callProxy("addReaction", { channelId, messageId, emoji });
|
|
3551
|
-
},
|
|
3552
|
-
|
|
3553
|
-
/** Remove your reaction from a message */
|
|
3554
|
-
removeReaction(channelId: string, messageId: string, emoji: string): Promise<unknown> {
|
|
3555
|
-
return callProxy("removeReaction", { channelId, messageId, emoji });
|
|
3556
|
-
},
|
|
3557
|
-
|
|
3558
|
-
/** Kick a member from a guild */
|
|
3559
|
-
kick(guildId: string, userId: string, reason?: string): Promise<unknown> {
|
|
3560
|
-
return callProxy("kick", { guildId, userId, reason });
|
|
3561
|
-
},
|
|
3562
|
-
|
|
3563
|
-
/** Ban a user from a guild */
|
|
3564
|
-
ban(guildId: string, userId: string, options?: { reason?: string; deleteMessageSeconds?: number }): Promise<unknown> {
|
|
3565
|
-
return callProxy("ban", { guildId, userId, ...options });
|
|
3566
|
-
},
|
|
3567
|
-
|
|
3568
|
-
/** Unban a user from a guild */
|
|
3569
|
-
unban(guildId: string, userId: string): Promise<unknown> {
|
|
3570
|
-
return callProxy("unban", { guildId, userId });
|
|
3571
|
-
},
|
|
3572
|
-
|
|
3573
|
-
/** Add a role to a guild member */
|
|
3574
|
-
addRole(guildId: string, userId: string, roleId: string): Promise<unknown> {
|
|
3575
|
-
return callProxy("addRole", { guildId, userId, roleId });
|
|
3576
|
-
},
|
|
3577
|
-
|
|
3578
|
-
/** Remove a role from a guild member */
|
|
3579
|
-
removeRole(guildId: string, userId: string, roleId: string): Promise<unknown> {
|
|
3580
|
-
return callProxy("removeRole", { guildId, userId, roleId });
|
|
3581
|
-
},
|
|
3582
|
-
|
|
3583
|
-
/** Edit a guild member's nickname */
|
|
3584
|
-
editNickname(guildId: string, userId: string, nick: string | null): Promise<unknown> {
|
|
3585
|
-
return callProxy("editNickname", { guildId, userId, nick });
|
|
3586
|
-
},
|
|
3587
|
-
|
|
3588
|
-
/** Create a channel in a guild */
|
|
3589
|
-
createChannel(guildId: string, data: { name: string; type?: number; parent_id?: string; topic?: string }): Promise<unknown> {
|
|
3590
|
-
return callProxy("createChannel", { guildId, ...data });
|
|
3591
|
-
},
|
|
3592
|
-
|
|
3593
|
-
/** Edit a channel */
|
|
3594
|
-
editChannel(channelId: string, data: { name?: string; topic?: string; position?: number }): Promise<unknown> {
|
|
3595
|
-
return callProxy("editChannel", { channelId, ...data });
|
|
3596
|
-
},
|
|
3597
|
-
|
|
3598
|
-
/** Delete a channel */
|
|
3599
|
-
deleteChannel(channelId: string): Promise<unknown> {
|
|
3600
|
-
return callProxy("deleteChannel", { channelId });
|
|
3601
|
-
},
|
|
3602
|
-
|
|
3603
|
-
/** Create a role in a guild */
|
|
3604
|
-
createRole(guildId: string, data: { name: string; color?: number; permissions?: string; hoist?: boolean; mentionable?: boolean }): Promise<unknown> {
|
|
3605
|
-
return callProxy("createRole", { guildId, ...data });
|
|
3606
|
-
},
|
|
3607
|
-
|
|
3608
|
-
/** Edit a role */
|
|
3609
|
-
editRole(guildId: string, roleId: string, data: { name?: string; color?: number; permissions?: string; hoist?: boolean; mentionable?: boolean }): Promise<unknown> {
|
|
3610
|
-
return callProxy("editRole", { guildId, roleId, ...data });
|
|
3611
|
-
},
|
|
3612
|
-
|
|
3613
|
-
/** Delete a role */
|
|
3614
|
-
deleteRole(guildId: string, roleId: string): Promise<unknown> {
|
|
3615
|
-
return callProxy("deleteRole", { guildId, roleId });
|
|
3616
|
-
},
|
|
3617
|
-
|
|
3618
|
-
/** Send a follow-up message to an interaction */
|
|
3619
|
-
sendFollowup(appId: string, token: string, data: { content?: string; embeds?: unknown[]; components?: unknown[]; flags?: number }): Promise<unknown> {
|
|
3620
|
-
return callProxy("sendFollowup", { appId, token, ...data });
|
|
3621
|
-
},
|
|
3622
|
-
|
|
3623
|
-
/** Edit a follow-up message */
|
|
3624
|
-
editFollowup(appId: string, token: string, messageId: string, data: { content?: string; embeds?: unknown[]; components?: unknown[] }): Promise<unknown> {
|
|
3625
|
-
return callProxy("editFollowup", { appId, token, messageId, ...data });
|
|
3626
|
-
},
|
|
3627
|
-
};
|
|
3628
|
-
`;
|
|
2461
|
+
return result.files;
|
|
3629
2462
|
}
|
|
3630
2463
|
async function generate(options) {
|
|
2464
|
+
const env = resolveEnv(options);
|
|
3631
2465
|
const convexDir = path4.resolve(process.cwd(), "convex");
|
|
3632
2466
|
const calabasasDir = path4.join(convexDir, "calabasas");
|
|
3633
2467
|
const configPath = path4.join(calabasasDir, "config.ts");
|
|
3634
2468
|
const legacyConfigPath = path4.join(convexDir, "calabasas.config.ts");
|
|
3635
2469
|
p5.intro("calabasas generate");
|
|
3636
|
-
let
|
|
2470
|
+
let configText;
|
|
3637
2471
|
if (fs4.existsSync(configPath)) {
|
|
3638
|
-
|
|
2472
|
+
configText = fs4.readFileSync(configPath, "utf-8");
|
|
3639
2473
|
} else if (fs4.existsSync(legacyConfigPath)) {
|
|
3640
2474
|
p5.log.warn("Found config at legacy path convex/calabasas.config.ts");
|
|
3641
2475
|
p5.log.info("Run `calabasas migrate dedicated-folder` to move files to the new layout.");
|
|
3642
|
-
|
|
2476
|
+
configText = fs4.readFileSync(legacyConfigPath, "utf-8");
|
|
3643
2477
|
} else {
|
|
3644
2478
|
p5.log.warn("No config found, generating with default settings...");
|
|
3645
2479
|
p5.log.info("Run `calabasas init` to create a config file.");
|
|
2480
|
+
configText = "export default defineCalabasas({})";
|
|
3646
2481
|
}
|
|
3647
2482
|
const s = p5.spinner();
|
|
3648
2483
|
s.start("Generating files...");
|
|
2484
|
+
let files;
|
|
2485
|
+
try {
|
|
2486
|
+
files = await fetchGeneratedFiles(env, configText);
|
|
2487
|
+
} catch (err) {
|
|
2488
|
+
s.stop("Generation failed");
|
|
2489
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2490
|
+
p5.log.error(message);
|
|
2491
|
+
process.exit(1);
|
|
2492
|
+
}
|
|
3649
2493
|
const generatedDir = path4.join(calabasasDir, "_generated");
|
|
3650
2494
|
if (!fs4.existsSync(generatedDir)) {
|
|
3651
2495
|
fs4.mkdirSync(generatedDir, { recursive: true });
|
|
3652
2496
|
}
|
|
3653
|
-
const
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
if (
|
|
2497
|
+
const generated = [];
|
|
2498
|
+
if (files["discord.ts"]) {
|
|
2499
|
+
const eventHandlersPath = path4.resolve(process.cwd(), options.output);
|
|
2500
|
+
fs4.writeFileSync(eventHandlersPath, files["discord.ts"]);
|
|
2501
|
+
generated.push(options.output);
|
|
2502
|
+
}
|
|
2503
|
+
if (files["schema.ts"]) {
|
|
3660
2504
|
const schemaPath = path4.join(generatedDir, "schema.ts");
|
|
3661
|
-
|
|
3662
|
-
fs4.writeFileSync(schemaPath, schemaCode);
|
|
2505
|
+
fs4.writeFileSync(schemaPath, files["schema.ts"]);
|
|
3663
2506
|
generated.push("convex/calabasas/_generated/schema.ts");
|
|
2507
|
+
}
|
|
2508
|
+
if (files["sync.ts"]) {
|
|
3664
2509
|
const syncPath = path4.join(generatedDir, "sync.ts");
|
|
3665
|
-
|
|
3666
|
-
fs4.writeFileSync(syncPath, syncCode);
|
|
2510
|
+
fs4.writeFileSync(syncPath, files["sync.ts"]);
|
|
3667
2511
|
generated.push("convex/calabasas/_generated/sync.ts");
|
|
3668
2512
|
}
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
2513
|
+
if (files["discord.actions.ts"]) {
|
|
2514
|
+
const actionsPath = path4.join(generatedDir, "discord.actions.ts");
|
|
2515
|
+
fs4.writeFileSync(actionsPath, files["discord.actions.ts"]);
|
|
2516
|
+
generated.push("convex/calabasas/_generated/discord.actions.ts");
|
|
2517
|
+
}
|
|
3673
2518
|
s.stop(`Generated ${generated.length} file${generated.length > 1 ? "s" : ""}`);
|
|
3674
2519
|
p5.note(generated.join(`
|
|
3675
2520
|
`), "Generated files");
|
|
2521
|
+
const hasSyncEnabled = !!files["schema.ts"];
|
|
3676
2522
|
if (hasSyncEnabled) {
|
|
3677
2523
|
const syncExports = [];
|
|
3678
|
-
|
|
2524
|
+
const text = configText.toLowerCase();
|
|
2525
|
+
if (text.includes("guilds") && text.includes("true")) {
|
|
3679
2526
|
syncExports.push("syncGuild");
|
|
3680
2527
|
syncExports.push("syncGuildAdmins");
|
|
3681
2528
|
}
|
|
3682
|
-
if (
|
|
2529
|
+
if (text.includes("channels") && text.includes("true"))
|
|
3683
2530
|
syncExports.push("syncChannel");
|
|
3684
|
-
if (
|
|
2531
|
+
if (text.includes("roles") && text.includes("true"))
|
|
3685
2532
|
syncExports.push("syncRole");
|
|
3686
|
-
if (
|
|
2533
|
+
if (text.includes("members") && text.includes("true"))
|
|
3687
2534
|
syncExports.push("syncMember");
|
|
3688
|
-
if (
|
|
2535
|
+
if (text.includes("presence") && text.includes("true"))
|
|
3689
2536
|
syncExports.push("syncPresence");
|
|
2537
|
+
if (syncExports.length === 0) {
|
|
2538
|
+
syncExports.push("syncGuild", "syncChannel", "syncRole", "syncMember");
|
|
2539
|
+
}
|
|
3690
2540
|
p5.note(`1. Add tables to your convex/schema.ts:
|
|
3691
2541
|
import { calabasasTables } from "./calabasas/_generated/schema";
|
|
3692
2542
|
|
|
@@ -6589,7 +5439,7 @@ program2.command("login").description("Authenticate with Calabasas using your AP
|
|
|
6589
5439
|
program2.command("logout").description("Clear stored credentials").option("--dev", "Use development environment").option("--prod", "Use production environment").action(logout);
|
|
6590
5440
|
program2.command("init").description("Initialize Calabasas config in your Convex project").action(init);
|
|
6591
5441
|
program2.command("push").description("Push your Calabasas config to the server").option("-c, --config <path>", "Path to config file", "convex/calabasas/config.ts").option("-b, --bot <botId>", "Bot ID to configure (prompts if not specified)").option("--dev", "Push to development environment").option("--prod", "Push to production environment").action(push);
|
|
6592
|
-
program2.command("generate").description("Generate type-safe Discord handlers and helpers").option("-o, --output <path>", "Output path", "convex/calabasas/_generated/discord.ts").action(generate);
|
|
5442
|
+
program2.command("generate").description("Generate type-safe Discord handlers and helpers").option("-o, --output <path>", "Output path", "convex/calabasas/_generated/discord.ts").option("--dev", "Use development environment").option("--prod", "Use production environment").action(generate);
|
|
6593
5443
|
program2.command("skill").description("Generate Calabasas documentation for AI assistants").option("--dev", "Use development environment").option("--prod", "Use production environment").option("-f, --file <path>", "Target file path (e.g. CLAUDE.md)").option("-y, --yes", "Auto-confirm replacing existing guidelines").action(skill);
|
|
6594
5444
|
program2.command("add [components...]").description("Add Discord UI components to your project").action(add);
|
|
6595
5445
|
program2.command("migrate [name]").description("Run a codemod migration (list available if no name given)").action(migrate);
|