calabasas 0.6.1 → 0.8.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 +208 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2298,6 +2298,7 @@ async function push(options) {
2298
2298
  syncChannels: calabasasConfig.sync?.channels ?? false,
2299
2299
  syncRoles: calabasasConfig.sync?.roles ?? false,
2300
2300
  syncMembers: calabasasConfig.sync?.members ?? false,
2301
+ syncPresence: calabasasConfig.sync?.presence ?? false,
2301
2302
  eventConfigs
2302
2303
  })
2303
2304
  });
@@ -2333,7 +2334,8 @@ async function push(options) {
2333
2334
  `Guilds: ${calabasasConfig.sync?.guilds ? "enabled" : "disabled"}`,
2334
2335
  `Channels: ${calabasasConfig.sync?.channels ? "enabled" : "disabled"}`,
2335
2336
  `Roles: ${calabasasConfig.sync?.roles ? "enabled" : "disabled"}`,
2336
- `Members: ${calabasasConfig.sync?.members ? "enabled" : "disabled"}`
2337
+ `Members: ${calabasasConfig.sync?.members ? "enabled" : "disabled"}`,
2338
+ `Presence: ${calabasasConfig.sync?.presence ? "enabled" : "disabled"}`
2337
2339
  ].join(`
2338
2340
  `);
2339
2341
  const eventSummary = eventConfigs.length > 0 ? `
@@ -2427,6 +2429,16 @@ function generateSchemaFile(sync) {
2427
2429
  .index("by_user_guild", ["discordUserId", "guildDiscordId"])
2428
2430
  .index("by_guild", ["guildDiscordId"]);`);
2429
2431
  }
2432
+ if (sync.presence) {
2433
+ tables.push(`export const calabasasPresence = defineTable({
2434
+ discordUserId: v.string(),
2435
+ guildDiscordId: v.string(),
2436
+ status: v.string(),
2437
+ updatedAt: v.number(),
2438
+ })
2439
+ .index("by_guild", ["guildDiscordId"])
2440
+ .index("by_user_guild", ["discordUserId", "guildDiscordId"]);`);
2441
+ }
2430
2442
  const tableNames = [];
2431
2443
  if (sync.guilds) {
2432
2444
  tableNames.push("calabasasGuilds");
@@ -2438,6 +2450,8 @@ function generateSchemaFile(sync) {
2438
2450
  tableNames.push("calabasasRoles");
2439
2451
  if (sync.members)
2440
2452
  tableNames.push("calabasasMembers");
2453
+ if (sync.presence)
2454
+ tableNames.push("calabasasPresence");
2441
2455
  const tablesExport = tableNames.length > 0 ? `export const calabasasTables = {
2442
2456
  ${tableNames.join(`,
2443
2457
  `)},
@@ -2549,6 +2563,16 @@ var MEMBER_TYPE = `{
2549
2563
  roles?: string[];
2550
2564
  joinedAt?: string;
2551
2565
  }`;
2566
+ var PRESENCE_VALIDATOR = `v.object({
2567
+ userId: v.string(),
2568
+ guildId: v.string(),
2569
+ status: v.string(),
2570
+ })`;
2571
+ var PRESENCE_TYPE = `{
2572
+ userId: string;
2573
+ guildId: string;
2574
+ status: string;
2575
+ }`;
2552
2576
  var GUILD_ADMIN_VALIDATOR = `v.object({
2553
2577
  guildId: v.string(),
2554
2578
  adminUserIds: v.array(v.string()),
@@ -2736,6 +2760,42 @@ function generateMemberMutation() {
2736
2760
  },
2737
2761
  });`;
2738
2762
  }
2763
+ function generatePresenceMutation() {
2764
+ return `export const syncPresence = internalMutation({
2765
+ args: {
2766
+ data: presenceValidator,
2767
+ operation: v.union(v.literal("upsert"), v.literal("delete")),
2768
+ },
2769
+ returns: v.null(),
2770
+ handler: async (ctx, { data: presence, operation }) => {
2771
+ const existing = await ctx.db
2772
+ .query("calabasasPresence")
2773
+ .withIndex("by_user_guild", (q) =>
2774
+ q.eq("discordUserId", presence.userId).eq("guildDiscordId", presence.guildId)
2775
+ )
2776
+ .unique();
2777
+
2778
+ if (operation === "delete") {
2779
+ if (existing) await ctx.db.delete(existing._id);
2780
+ return null;
2781
+ }
2782
+
2783
+ const doc = {
2784
+ discordUserId: presence.userId,
2785
+ guildDiscordId: presence.guildId,
2786
+ status: presence.status,
2787
+ updatedAt: Date.now(),
2788
+ };
2789
+
2790
+ if (existing) {
2791
+ await ctx.db.patch(existing._id, doc);
2792
+ } else {
2793
+ await ctx.db.insert("calabasasPresence", doc);
2794
+ }
2795
+ return null;
2796
+ },
2797
+ });`;
2798
+ }
2739
2799
  function generateGuildAdminMutation() {
2740
2800
  return `export const _syncGuildAdmins = internalMutation({
2741
2801
  args: {
@@ -2854,6 +2914,13 @@ function generateSyncFile(sync) {
2854
2914
  publicMutations.push(generatePublicSyncMutation("member"));
2855
2915
  enabledTypes.push("member");
2856
2916
  }
2917
+ if (sync.presence) {
2918
+ validators.push(`const presenceValidator = ${PRESENCE_VALIDATOR};`);
2919
+ types.push(`export type PresenceSync = ${PRESENCE_TYPE};`);
2920
+ internalMutations.push(generateInternalSyncMutation("presence", generatePresenceMutation()));
2921
+ publicMutations.push(generatePublicSyncMutation("presence"));
2922
+ enabledTypes.push("presence");
2923
+ }
2857
2924
  if (internalMutations.length === 0) {
2858
2925
  return `/**
2859
2926
  * THIS FILE IS AUTO-GENERATED BY \`calabasas generate\`
@@ -3499,7 +3566,7 @@ async function generate(options) {
3499
3566
  fs4.writeFileSync(eventHandlersPath, eventHandlersCode);
3500
3567
  const generated = [options.output];
3501
3568
  const syncConfig = config.sync ?? {};
3502
- const hasSyncEnabled = syncConfig.guilds || syncConfig.channels || syncConfig.roles || syncConfig.members;
3569
+ const hasSyncEnabled = syncConfig.guilds || syncConfig.channels || syncConfig.roles || syncConfig.members || syncConfig.presence;
3503
3570
  if (hasSyncEnabled) {
3504
3571
  const schemaPath = path4.join(generatedDir, "schema.ts");
3505
3572
  const schemaCode = generateSchemaFile(syncConfig);
@@ -3529,6 +3596,8 @@ async function generate(options) {
3529
3596
  syncExports.push("syncRole");
3530
3597
  if (syncConfig.members)
3531
3598
  syncExports.push("syncMember");
3599
+ if (syncConfig.presence)
3600
+ syncExports.push("syncPresence");
3532
3601
  p5.note(`1. Add tables to your convex/schema.ts:
3533
3602
  import { calabasasTables } from "./calabasas/_generated/schema";
3534
3603
 
@@ -3609,17 +3678,23 @@ async function skill(options) {
3609
3678
  const cwd = process.cwd();
3610
3679
  const detectedFiles = detectExistingFiles(cwd);
3611
3680
  if (detectedFiles.length === 0) {
3612
- const createPath = await p6.select({
3613
- message: "No CLAUDE.md or AGENTS.md found. Where should we create one?",
3614
- options: [
3615
- { value: "CLAUDE.md", label: "CLAUDE.md (project root)" },
3616
- { value: "AGENTS.md", label: "AGENTS.md (project root)" },
3617
- { value: ".claude/CLAUDE.md", label: ".claude/CLAUDE.md" }
3618
- ]
3619
- });
3620
- if (p6.isCancel(createPath)) {
3621
- p6.cancel("Cancelled.");
3622
- return;
3681
+ let createPath;
3682
+ if (options.file) {
3683
+ createPath = options.file;
3684
+ } else {
3685
+ const selected = await p6.select({
3686
+ message: "No CLAUDE.md or AGENTS.md found. Where should we create one?",
3687
+ options: [
3688
+ { value: "CLAUDE.md", label: "CLAUDE.md (project root)" },
3689
+ { value: "AGENTS.md", label: "AGENTS.md (project root)" },
3690
+ { value: ".claude/CLAUDE.md", label: ".claude/CLAUDE.md" }
3691
+ ]
3692
+ });
3693
+ if (p6.isCancel(selected)) {
3694
+ p6.cancel("Cancelled.");
3695
+ return;
3696
+ }
3697
+ createPath = selected;
3623
3698
  }
3624
3699
  const fullPath = path5.resolve(cwd, createPath);
3625
3700
  const dir = path5.dirname(fullPath);
@@ -3635,7 +3710,33 @@ ${skillContent}`;
3635
3710
  return;
3636
3711
  }
3637
3712
  let selectedFile;
3638
- if (detectedFiles.length === 1) {
3713
+ if (options.file) {
3714
+ const match = detectedFiles.find((f) => f.path === options.file);
3715
+ if (match) {
3716
+ selectedFile = match;
3717
+ } else {
3718
+ const fullPath = path5.resolve(cwd, options.file);
3719
+ const dir = path5.dirname(fullPath);
3720
+ if (!fs5.existsSync(dir)) {
3721
+ fs5.mkdirSync(dir, { recursive: true });
3722
+ }
3723
+ if (fs5.existsSync(fullPath)) {
3724
+ selectedFile = {
3725
+ path: options.file,
3726
+ fullPath,
3727
+ type: options.file.includes("CLAUDE") ? "CLAUDE.md" : "AGENTS.md"
3728
+ };
3729
+ } else {
3730
+ const projectName = path5.basename(cwd);
3731
+ const initialContent = `# ${projectName}
3732
+
3733
+ ${skillContent}`;
3734
+ fs5.writeFileSync(fullPath, initialContent);
3735
+ p6.outro(`Created ${options.file} with Calabasas guidelines`);
3736
+ return;
3737
+ }
3738
+ }
3739
+ } else if (detectedFiles.length === 1) {
3639
3740
  selectedFile = detectedFiles[0];
3640
3741
  } else {
3641
3742
  const selected = await p6.select({
@@ -3653,12 +3754,14 @@ ${skillContent}`;
3653
3754
  }
3654
3755
  const existingContent = fs5.readFileSync(selectedFile.fullPath, "utf8");
3655
3756
  if (hasCalabsasSection(existingContent)) {
3656
- const update = await p6.confirm({
3657
- message: `Calabasas guidelines already exist in ${selectedFile.path}. Replace?`
3658
- });
3659
- if (p6.isCancel(update) || !update) {
3660
- p6.cancel("Cancelled.");
3661
- return;
3757
+ if (!options.yes) {
3758
+ const update = await p6.confirm({
3759
+ message: `Calabasas guidelines already exist in ${selectedFile.path}. Replace?`
3760
+ });
3761
+ if (p6.isCancel(update) || !update) {
3762
+ p6.cancel("Cancelled.");
3763
+ return;
3764
+ }
3662
3765
  }
3663
3766
  const sectionRegex = /## Calabasas Guidelines[\s\S]*?(?=\n## |\n# |$)/;
3664
3767
  const contentWithoutSection = existingContent.replace(sectionRegex, "").trimEnd();
@@ -3687,6 +3790,7 @@ import * as p7 from "@clack/prompts";
3687
3790
  // src/lib/registry/components/channel-select.ts
3688
3791
  var channelSelect = {
3689
3792
  name: "channel-select",
3793
+ kind: "component",
3690
3794
  description: "Searchable combobox to pick a Discord channel (with type icons)",
3691
3795
  requiredSyncTypes: ["channels"],
3692
3796
  requiredShadcnComponents: ["popover", "command", "button"],
@@ -3834,6 +3938,7 @@ export function ChannelSelect({
3834
3938
  // src/lib/registry/components/role-select.ts
3835
3939
  var roleSelect = {
3836
3940
  name: "role-select",
3941
+ kind: "component",
3837
3942
  description: "Searchable combobox to pick a Discord role (with color dots)",
3838
3943
  requiredSyncTypes: ["roles"],
3839
3944
  requiredShadcnComponents: ["popover", "command", "button"],
@@ -3972,6 +4077,7 @@ export function RoleSelect({
3972
4077
  // src/lib/registry/components/member-select.ts
3973
4078
  var memberSelect = {
3974
4079
  name: "member-select",
4080
+ kind: "component",
3975
4081
  description: "Searchable combobox to pick a Discord member (with avatar)",
3976
4082
  requiredSyncTypes: ["members"],
3977
4083
  requiredShadcnComponents: ["popover", "command", "button"],
@@ -4123,6 +4229,7 @@ export function MemberSelect({
4123
4229
  // src/lib/registry/components/guild-select.ts
4124
4230
  var guildSelect = {
4125
4231
  name: "guild-select",
4232
+ kind: "component",
4126
4233
  description: "Searchable combobox to pick a Discord server (with icon)",
4127
4234
  requiredSyncTypes: ["guilds"],
4128
4235
  requiredShadcnComponents: ["popover", "command", "button"],
@@ -4278,6 +4385,7 @@ export function GuildSelect({
4278
4385
  // src/lib/registry/components/role-badge.ts
4279
4386
  var roleBadge = {
4280
4387
  name: "role-badge",
4388
+ kind: "component",
4281
4389
  description: "Colored role pill badges that match Discord's role tags",
4282
4390
  requiredSyncTypes: ["roles"],
4283
4391
  requiredShadcnComponents: ["badge", "tooltip"],
@@ -4421,6 +4529,7 @@ export function RoleBadge({
4421
4529
  // src/lib/registry/components/server-info.ts
4422
4530
  var serverInfo = {
4423
4531
  name: "server-info",
4532
+ kind: "component",
4424
4533
  description: "Guild overview card with icon, name, boost tier, and feature tags",
4425
4534
  requiredSyncTypes: ["guilds"],
4426
4535
  requiredShadcnComponents: ["card", "avatar", "badge"],
@@ -4623,6 +4732,7 @@ export function ServerInfo({
4623
4732
  // src/lib/registry/components/permission-viewer.ts
4624
4733
  var permissionViewer = {
4625
4734
  name: "permission-viewer",
4735
+ kind: "component",
4626
4736
  description: "Read-only permission grid that parses Discord permission bitfields",
4627
4737
  requiredSyncTypes: ["roles"],
4628
4738
  requiredShadcnComponents: ["card", "badge", "tooltip", "separator"],
@@ -4855,6 +4965,7 @@ export function PermissionViewer({
4855
4965
  // src/lib/registry/components/member-card.ts
4856
4966
  var memberCard = {
4857
4967
  name: "member-card",
4968
+ kind: "component",
4858
4969
  description: "Profile card with avatar, name, role badges, and join date",
4859
4970
  requiredSyncTypes: ["members", "roles"],
4860
4971
  requiredShadcnComponents: ["card", "avatar", "badge", "separator"],
@@ -5091,6 +5202,7 @@ export function MemberCard({
5091
5202
  // src/lib/registry/components/channel-tree.ts
5092
5203
  var channelTree = {
5093
5204
  name: "channel-tree",
5205
+ kind: "component",
5094
5206
  description: "Hierarchical channel sidebar grouped by category with type icons",
5095
5207
  requiredSyncTypes: ["channels"],
5096
5208
  requiredShadcnComponents: ["collapsible", "scroll-area", "tooltip"],
@@ -5358,6 +5470,7 @@ function ChannelItem({
5358
5470
  // src/lib/registry/components/member-roster.ts
5359
5471
  var memberRoster = {
5360
5472
  name: "member-roster",
5473
+ kind: "component",
5361
5474
  description: "Member list grouped by hoisted roles, like Discord's right sidebar",
5362
5475
  requiredSyncTypes: ["members", "roles"],
5363
5476
  requiredShadcnComponents: ["scroll-area", "avatar", "badge", "separator"],
@@ -5614,6 +5727,44 @@ export function MemberRoster({
5614
5727
  });`
5615
5728
  };
5616
5729
 
5730
+ // src/lib/registry/components/use-online-count.ts
5731
+ var useOnlineCount = {
5732
+ name: "use-online-count",
5733
+ kind: "hook",
5734
+ description: "React hook that returns the number of online members in a guild",
5735
+ requiredSyncTypes: ["presence"],
5736
+ requiredShadcnComponents: [],
5737
+ generateReactComponent: () => `"use client";
5738
+
5739
+ import { useQuery } from "convex/react";
5740
+ import { api } from "@/convex/_generated/api";
5741
+
5742
+ /**
5743
+ * Returns the number of online members in a Discord guild.
5744
+ *
5745
+ * Requires \`sync.presence: true\` in your calabasas config.
5746
+ * The GuildPresences privileged intent must be enabled for your bot.
5747
+ *
5748
+ * @param guildDiscordId - The Discord guild ID
5749
+ * @returns The online member count, or \`undefined\` while loading
5750
+ */
5751
+ export function useOnlineCount(guildDiscordId: string): number | undefined {
5752
+ return useQuery(api.calabasas.queries.onlineCount, { guildDiscordId });
5753
+ }
5754
+ `,
5755
+ generateConvexQueries: () => `export const onlineCount = query({
5756
+ args: { guildDiscordId: v.string() },
5757
+ returns: v.number(),
5758
+ handler: async (ctx, { guildDiscordId }) => {
5759
+ const presences = await ctx.db
5760
+ .query("calabasasPresence")
5761
+ .withIndex("by_guild", (q) => q.eq("guildDiscordId", guildDiscordId))
5762
+ .collect();
5763
+ return presences.length;
5764
+ },
5765
+ });`
5766
+ };
5767
+
5617
5768
  // src/lib/registry/index.ts
5618
5769
  var REGISTRY = [
5619
5770
  channelSelect,
@@ -5625,7 +5776,8 @@ var REGISTRY = [
5625
5776
  permissionViewer,
5626
5777
  memberCard,
5627
5778
  channelTree,
5628
- memberRoster
5779
+ memberRoster,
5780
+ useOnlineCount
5629
5781
  ];
5630
5782
  function getComponent(name) {
5631
5783
  return REGISTRY.find((c) => c.name === name);
@@ -5646,6 +5798,8 @@ function parseEnabledSyncTypes(configPath) {
5646
5798
  enabled.add("roles");
5647
5799
  if (/members:\s*true/.test(syncContent))
5648
5800
  enabled.add("members");
5801
+ if (/presence:\s*true/.test(syncContent))
5802
+ enabled.add("presence");
5649
5803
  }
5650
5804
  return enabled;
5651
5805
  }
@@ -5737,9 +5891,10 @@ async function add(componentNames) {
5737
5891
  if (!fs6.existsSync(componentsDir)) {
5738
5892
  fs6.mkdirSync(componentsDir, { recursive: true });
5739
5893
  }
5740
- const componentPath = path6.join(componentsDir, `${comp.name}.tsx`);
5894
+ const ext = comp.kind === "hook" ? ".ts" : ".tsx";
5895
+ const componentPath = path6.join(componentsDir, `${comp.name}${ext}`);
5741
5896
  fs6.writeFileSync(componentPath, comp.generateReactComponent());
5742
- p7.log.success(`Created components/calabasas/${comp.name}.tsx`);
5897
+ p7.log.success(`Created components/calabasas/${comp.name}${ext}`);
5743
5898
  const queryBlock = comp.generateConvexQueries();
5744
5899
  const namesInBlock = queryNamesInBlock(queryBlock);
5745
5900
  const newNames = namesInBlock.filter((n) => !existing.has(n));
@@ -5790,45 +5945,52 @@ Install with: npx shadcn@latest add ${missingShadcn.join(" ")}`, "Required shadc
5790
5945
  }
5791
5946
  const firstInstalled = components[0];
5792
5947
  if (firstInstalled) {
5793
- const pascal = toPascalCase(firstInstalled.name);
5794
- const needsGuild = firstInstalled.requiredSyncTypes.some((t) => t === "channels" || t === "roles" || t === "members");
5795
- const displayComponents = new Set([
5796
- "role-badge",
5797
- "server-info",
5798
- "permission-viewer",
5799
- "member-card"
5800
- ]);
5801
- const isDisplay = displayComponents.has(firstInstalled.name);
5802
5948
  let usage;
5803
- if (isDisplay) {
5804
- const propsMap = {
5805
- "role-badge": ` guildDiscordId="123456789"
5949
+ if (firstInstalled.kind === "hook") {
5950
+ const camel = firstInstalled.name.split("-").map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)).join("");
5951
+ usage = `import { ${camel} } from "@/components/calabasas/${firstInstalled.name}";
5952
+
5953
+ const count = ${camel}("123456789");`;
5954
+ } else {
5955
+ const pascal = toPascalCase(firstInstalled.name);
5956
+ const needsGuild = firstInstalled.requiredSyncTypes.some((t) => t === "channels" || t === "roles" || t === "members");
5957
+ const displayComponents = new Set([
5958
+ "role-badge",
5959
+ "server-info",
5960
+ "permission-viewer",
5961
+ "member-card"
5962
+ ]);
5963
+ const isDisplay = displayComponents.has(firstInstalled.name);
5964
+ if (isDisplay) {
5965
+ const propsMap = {
5966
+ "role-badge": ` guildDiscordId="123456789"
5806
5967
  roleIds={["role_id_1", "role_id_2"]}`,
5807
- "server-info": ` guildDiscordId="123456789"`,
5808
- "permission-viewer": ` guildDiscordId="123456789"
5968
+ "server-info": ` guildDiscordId="123456789"`,
5969
+ "permission-viewer": ` guildDiscordId="123456789"
5809
5970
  roleDiscordId="role_id"`,
5810
- "member-card": ` guildDiscordId="123456789"
5971
+ "member-card": ` guildDiscordId="123456789"
5811
5972
  memberDiscordUserId="user_id"`
5812
- };
5813
- const props = propsMap[firstInstalled.name] ?? ` guildDiscordId="123456789"`;
5814
- usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
5973
+ };
5974
+ const props = propsMap[firstInstalled.name] ?? ` guildDiscordId="123456789"`;
5975
+ usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
5815
5976
 
5816
5977
  <${pascal}
5817
5978
  ${props}
5818
5979
  />`;
5819
- } else if (needsGuild) {
5820
- usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
5980
+ } else if (needsGuild) {
5981
+ usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
5821
5982
 
5822
5983
  <${pascal}
5823
5984
  guildDiscordId="123456789"
5824
5985
  onValueChange={(id) => console.log(id)}
5825
5986
  />`;
5826
- } else {
5827
- usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
5987
+ } else {
5988
+ usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
5828
5989
 
5829
5990
  <${pascal}
5830
5991
  onValueChange={(id) => console.log(id)}
5831
5992
  />`;
5993
+ }
5832
5994
  }
5833
5995
  p7.note(usage, "Usage example");
5834
5996
  }
@@ -6354,7 +6516,7 @@ program2.command("logout").description("Clear stored credentials").option("--dev
6354
6516
  program2.command("init").description("Initialize Calabasas config in your Convex project").action(init);
6355
6517
  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);
6356
6518
  program2.command("generate").description("Generate type-safe Discord handlers and helpers").option("-o, --output <path>", "Output path", "convex/calabasas/_generated/discord.ts").action(generate);
6357
- program2.command("skill").description("Generate Calabasas documentation for AI assistants").option("--dev", "Use development environment").option("--prod", "Use production environment").action(skill);
6519
+ 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);
6358
6520
  program2.command("add [components...]").description("Add Discord UI components to your project").action(add);
6359
6521
  program2.command("migrate [name]").description("Run a codemod migration (list available if no name given)").action(migrate);
6360
6522
  program2.command("status").alias("dashboard").description("Real-time dashboard showing bot status, events, and stats").option("--dev", "Use development environment").option("--prod", "Use production environment").action(dashboard);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calabasas",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "CLI for Calabasas - Discord Gateway as a Service for Convex",
5
5
  "type": "module",
6
6
  "bin": {