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.
Files changed (2) hide show
  1. package/dist/index.js +49 -1199
  2. 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
- // src/lib/generators/schema.ts
2452
- function generateSchemaFile(sync) {
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
- "Content-Type": "application/json",
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
- const error = await response.text();
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 config = { sync: {}, events: {} };
2470
+ let configText;
3637
2471
  if (fs4.existsSync(configPath)) {
3638
- config = await parseConfigFile(configPath);
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
- config = await parseConfigFile(legacyConfigPath);
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 eventHandlersPath = path4.resolve(process.cwd(), options.output);
3654
- const eventHandlersCode = generateEventHandlersFile(config.events ?? {});
3655
- fs4.writeFileSync(eventHandlersPath, eventHandlersCode);
3656
- const generated = [options.output];
3657
- const syncConfig = config.sync ?? {};
3658
- const hasSyncEnabled = syncConfig.guilds || syncConfig.channels || syncConfig.roles || syncConfig.members || syncConfig.presence;
3659
- if (hasSyncEnabled) {
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
- const schemaCode = generateSchemaFile(syncConfig);
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
- const syncCode = generateSyncFile(syncConfig);
3666
- fs4.writeFileSync(syncPath, syncCode);
2510
+ fs4.writeFileSync(syncPath, files["sync.ts"]);
3667
2511
  generated.push("convex/calabasas/_generated/sync.ts");
3668
2512
  }
3669
- const actionsPath = path4.join(generatedDir, "discord.actions.ts");
3670
- const actionsCode = generateActionsFile();
3671
- fs4.writeFileSync(actionsPath, actionsCode);
3672
- generated.push("convex/calabasas/_generated/discord.actions.ts");
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
- if (syncConfig.guilds) {
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 (syncConfig.channels)
2529
+ if (text.includes("channels") && text.includes("true"))
3683
2530
  syncExports.push("syncChannel");
3684
- if (syncConfig.roles)
2531
+ if (text.includes("roles") && text.includes("true"))
3685
2532
  syncExports.push("syncRole");
3686
- if (syncConfig.members)
2533
+ if (text.includes("members") && text.includes("true"))
3687
2534
  syncExports.push("syncMember");
3688
- if (syncConfig.presence)
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calabasas",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "CLI for Calabasas - Discord Gateway as a Service for Convex",
5
5
  "type": "module",
6
6
  "bin": {