calabasas 0.1.11 → 0.2.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 +972 -518
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -2599,6 +2599,206 @@ var init_open = __esm(() => {
|
|
|
2599
2599
|
open_default = open;
|
|
2600
2600
|
});
|
|
2601
2601
|
|
|
2602
|
+
// src/lib/convex.tsx
|
|
2603
|
+
var exports_convex = {};
|
|
2604
|
+
__export(exports_convex, {
|
|
2605
|
+
createConvexClient: () => createConvexClient,
|
|
2606
|
+
cliApi: () => cliApi,
|
|
2607
|
+
ConvexProvider: () => ConvexProvider
|
|
2608
|
+
});
|
|
2609
|
+
import { ConvexProvider as BaseConvexProvider, ConvexReactClient } from "convex/react";
|
|
2610
|
+
import { makeFunctionReference } from "convex/server";
|
|
2611
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
2612
|
+
function createConvexClient(url) {
|
|
2613
|
+
return new ConvexReactClient(url);
|
|
2614
|
+
}
|
|
2615
|
+
function ConvexProvider({
|
|
2616
|
+
client,
|
|
2617
|
+
children
|
|
2618
|
+
}) {
|
|
2619
|
+
const Provider = BaseConvexProvider;
|
|
2620
|
+
return /* @__PURE__ */ jsxDEV(Provider, {
|
|
2621
|
+
client,
|
|
2622
|
+
children
|
|
2623
|
+
}, undefined, false, undefined, this);
|
|
2624
|
+
}
|
|
2625
|
+
var cliApi;
|
|
2626
|
+
var init_convex = __esm(() => {
|
|
2627
|
+
cliApi = {
|
|
2628
|
+
listBots: makeFunctionReference("cli:listBots"),
|
|
2629
|
+
recentLogs: makeFunctionReference("cli:recentLogs"),
|
|
2630
|
+
botStats: makeFunctionReference("cli:botStats"),
|
|
2631
|
+
botDetail: makeFunctionReference("cli:botDetail")
|
|
2632
|
+
};
|
|
2633
|
+
});
|
|
2634
|
+
|
|
2635
|
+
// src/components/StatusBadge.tsx
|
|
2636
|
+
import { Text } from "ink";
|
|
2637
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
2638
|
+
function StatusBadge({ status }) {
|
|
2639
|
+
const config = STATUS_CONFIG[status];
|
|
2640
|
+
return /* @__PURE__ */ jsxDEV2(Text, {
|
|
2641
|
+
children: [
|
|
2642
|
+
/* @__PURE__ */ jsxDEV2(Text, {
|
|
2643
|
+
color: config.color,
|
|
2644
|
+
children: config.dot
|
|
2645
|
+
}, undefined, false, undefined, this),
|
|
2646
|
+
/* @__PURE__ */ jsxDEV2(Text, {
|
|
2647
|
+
children: [
|
|
2648
|
+
" ",
|
|
2649
|
+
status
|
|
2650
|
+
]
|
|
2651
|
+
}, undefined, true, undefined, this)
|
|
2652
|
+
]
|
|
2653
|
+
}, undefined, true, undefined, this);
|
|
2654
|
+
}
|
|
2655
|
+
var STATUS_CONFIG;
|
|
2656
|
+
var init_StatusBadge = __esm(() => {
|
|
2657
|
+
STATUS_CONFIG = {
|
|
2658
|
+
connected: { color: "green", dot: "●" },
|
|
2659
|
+
connecting: { color: "yellow", dot: "●" },
|
|
2660
|
+
error: { color: "red", dot: "●" },
|
|
2661
|
+
disconnected: { color: "gray", dot: "●" }
|
|
2662
|
+
};
|
|
2663
|
+
});
|
|
2664
|
+
|
|
2665
|
+
// src/lib/format.ts
|
|
2666
|
+
function relativeTime(timestamp) {
|
|
2667
|
+
if (!timestamp)
|
|
2668
|
+
return "never";
|
|
2669
|
+
const diff = Date.now() - timestamp;
|
|
2670
|
+
const seconds = Math.floor(diff / 1000);
|
|
2671
|
+
if (seconds < 60)
|
|
2672
|
+
return `${seconds}s ago`;
|
|
2673
|
+
const minutes = Math.floor(seconds / 60);
|
|
2674
|
+
if (minutes < 60)
|
|
2675
|
+
return `${minutes}m ago`;
|
|
2676
|
+
const hours = Math.floor(minutes / 60);
|
|
2677
|
+
if (hours < 24)
|
|
2678
|
+
return `${hours}h ago`;
|
|
2679
|
+
const days = Math.floor(hours / 24);
|
|
2680
|
+
return `${days}d ago`;
|
|
2681
|
+
}
|
|
2682
|
+
function formatLatency(ms) {
|
|
2683
|
+
if (ms < 1000)
|
|
2684
|
+
return `${Math.round(ms)}ms`;
|
|
2685
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
2686
|
+
}
|
|
2687
|
+
function formatNumber(n) {
|
|
2688
|
+
return n.toLocaleString("en-US");
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// src/components/BotList.tsx
|
|
2692
|
+
var exports_BotList = {};
|
|
2693
|
+
__export(exports_BotList, {
|
|
2694
|
+
BotList: () => BotList
|
|
2695
|
+
});
|
|
2696
|
+
import { Text as Text2, Box } from "ink";
|
|
2697
|
+
import Spinner from "ink-spinner";
|
|
2698
|
+
import { useQuery } from "convex/react";
|
|
2699
|
+
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
2700
|
+
function BotList({
|
|
2701
|
+
apiKey,
|
|
2702
|
+
selectedIndex,
|
|
2703
|
+
onSelect
|
|
2704
|
+
}) {
|
|
2705
|
+
const bots = useQuery(cliApi.listBots, { apiKey });
|
|
2706
|
+
if (bots === undefined) {
|
|
2707
|
+
return /* @__PURE__ */ jsxDEV3(Box, {
|
|
2708
|
+
children: [
|
|
2709
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2710
|
+
color: "cyan",
|
|
2711
|
+
children: /* @__PURE__ */ jsxDEV3(Spinner, {
|
|
2712
|
+
type: "dots"
|
|
2713
|
+
}, undefined, false, undefined, this)
|
|
2714
|
+
}, undefined, false, undefined, this),
|
|
2715
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2716
|
+
children: " Loading bots..."
|
|
2717
|
+
}, undefined, false, undefined, this)
|
|
2718
|
+
]
|
|
2719
|
+
}, undefined, true, undefined, this);
|
|
2720
|
+
}
|
|
2721
|
+
if (bots.length === 0) {
|
|
2722
|
+
return /* @__PURE__ */ jsxDEV3(Box, {
|
|
2723
|
+
children: /* @__PURE__ */ jsxDEV3(Text2, {
|
|
2724
|
+
dimColor: true,
|
|
2725
|
+
children: "No bots found. Run `calabasas bot add` to create one."
|
|
2726
|
+
}, undefined, false, undefined, this)
|
|
2727
|
+
}, undefined, false, undefined, this);
|
|
2728
|
+
}
|
|
2729
|
+
return /* @__PURE__ */ jsxDEV3(Box, {
|
|
2730
|
+
flexDirection: "column",
|
|
2731
|
+
children: [
|
|
2732
|
+
/* @__PURE__ */ jsxDEV3(Box, {
|
|
2733
|
+
marginBottom: 1,
|
|
2734
|
+
children: /* @__PURE__ */ jsxDEV3(Text2, {
|
|
2735
|
+
bold: true,
|
|
2736
|
+
children: [
|
|
2737
|
+
" Name".padEnd(22),
|
|
2738
|
+
"Status".padEnd(16),
|
|
2739
|
+
"Discord App ID".padEnd(22),
|
|
2740
|
+
"Convex URL".padEnd(36),
|
|
2741
|
+
"Last Connected"
|
|
2742
|
+
]
|
|
2743
|
+
}, undefined, true, undefined, this)
|
|
2744
|
+
}, undefined, false, undefined, this),
|
|
2745
|
+
bots.map((bot, i) => {
|
|
2746
|
+
const isSelected = selectedIndex === i;
|
|
2747
|
+
return /* @__PURE__ */ jsxDEV3(Box, {
|
|
2748
|
+
flexDirection: "column",
|
|
2749
|
+
children: [
|
|
2750
|
+
/* @__PURE__ */ jsxDEV3(Box, {
|
|
2751
|
+
children: [
|
|
2752
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2753
|
+
color: isSelected ? "cyan" : undefined,
|
|
2754
|
+
children: isSelected ? "→ " : " "
|
|
2755
|
+
}, undefined, false, undefined, this),
|
|
2756
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2757
|
+
bold: isSelected,
|
|
2758
|
+
children: bot.name.padEnd(20)
|
|
2759
|
+
}, undefined, false, undefined, this),
|
|
2760
|
+
/* @__PURE__ */ jsxDEV3(StatusBadge, {
|
|
2761
|
+
status: bot.status
|
|
2762
|
+
}, undefined, false, undefined, this),
|
|
2763
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2764
|
+
children: "".padEnd(Math.max(0, 16 - bot.status.length - 2))
|
|
2765
|
+
}, undefined, false, undefined, this),
|
|
2766
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2767
|
+
dimColor: true,
|
|
2768
|
+
children: bot.discordAppId.padEnd(22)
|
|
2769
|
+
}, undefined, false, undefined, this),
|
|
2770
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2771
|
+
dimColor: true,
|
|
2772
|
+
children: bot.convexUrl.replace("https://", "").padEnd(36)
|
|
2773
|
+
}, undefined, false, undefined, this),
|
|
2774
|
+
/* @__PURE__ */ jsxDEV3(Text2, {
|
|
2775
|
+
dimColor: true,
|
|
2776
|
+
children: relativeTime(bot.lastConnectedAt)
|
|
2777
|
+
}, undefined, false, undefined, this)
|
|
2778
|
+
]
|
|
2779
|
+
}, undefined, true, undefined, this),
|
|
2780
|
+
bot.errorMessage && /* @__PURE__ */ jsxDEV3(Box, {
|
|
2781
|
+
marginLeft: 4,
|
|
2782
|
+
children: /* @__PURE__ */ jsxDEV3(Text2, {
|
|
2783
|
+
color: "red",
|
|
2784
|
+
dimColor: true,
|
|
2785
|
+
children: [
|
|
2786
|
+
"└ ",
|
|
2787
|
+
bot.errorMessage
|
|
2788
|
+
]
|
|
2789
|
+
}, undefined, true, undefined, this)
|
|
2790
|
+
}, undefined, false, undefined, this)
|
|
2791
|
+
]
|
|
2792
|
+
}, bot._id, true, undefined, this);
|
|
2793
|
+
})
|
|
2794
|
+
]
|
|
2795
|
+
}, undefined, true, undefined, this);
|
|
2796
|
+
}
|
|
2797
|
+
var init_BotList = __esm(() => {
|
|
2798
|
+
init_convex();
|
|
2799
|
+
init_StatusBadge();
|
|
2800
|
+
});
|
|
2801
|
+
|
|
2602
2802
|
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
|
|
2603
2803
|
var import__ = __toESM(require_commander(), 1);
|
|
2604
2804
|
var {
|
|
@@ -2617,6 +2817,7 @@ var {
|
|
|
2617
2817
|
|
|
2618
2818
|
// src/commands/login.ts
|
|
2619
2819
|
import * as http from "http";
|
|
2820
|
+
import * as p from "@clack/prompts";
|
|
2620
2821
|
|
|
2621
2822
|
// src/lib/config.ts
|
|
2622
2823
|
import * as fs from "fs";
|
|
@@ -2630,6 +2831,8 @@ function getConfigFile(env) {
|
|
|
2630
2831
|
}
|
|
2631
2832
|
var PROD_API_URL = "https://savory-llama-364.convex.site";
|
|
2632
2833
|
var DEV_API_URL = "https://notable-monitor-41.convex.site";
|
|
2834
|
+
var PROD_CONVEX_URL = "https://savory-llama-364.convex.cloud";
|
|
2835
|
+
var DEV_CONVEX_URL = "https://notable-monitor-41.convex.cloud";
|
|
2633
2836
|
var PROD_WEB_URL = "https://calabasas-web.vercel.app";
|
|
2634
2837
|
var DEV_WEB_URL = "http://localhost:3000";
|
|
2635
2838
|
function getConfig(env) {
|
|
@@ -2678,6 +2881,11 @@ function getWebUrlForEnv(env) {
|
|
|
2678
2881
|
return DEV_WEB_URL;
|
|
2679
2882
|
return PROD_WEB_URL;
|
|
2680
2883
|
}
|
|
2884
|
+
function getConvexUrl(env) {
|
|
2885
|
+
if (env === "dev")
|
|
2886
|
+
return DEV_CONVEX_URL;
|
|
2887
|
+
return PROD_CONVEX_URL;
|
|
2888
|
+
}
|
|
2681
2889
|
|
|
2682
2890
|
// src/commands/login.ts
|
|
2683
2891
|
var CALLBACK_PORT = 9876;
|
|
@@ -2692,15 +2900,16 @@ async function login(options) {
|
|
|
2692
2900
|
}
|
|
2693
2901
|
const env = options.dev ? "dev" : "prod";
|
|
2694
2902
|
const existing = getConfig(env);
|
|
2903
|
+
p.intro("calabasas login");
|
|
2695
2904
|
if (existing.apiKey) {
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2905
|
+
p.note(`API Key: ${existing.apiKey.slice(0, 8)}...
|
|
2906
|
+
|
|
2907
|
+
Run \`calabasas logout\` to clear credentials.`, "Already logged in");
|
|
2908
|
+
p.outro("Done");
|
|
2700
2909
|
return;
|
|
2701
2910
|
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2911
|
+
const s = p.spinner();
|
|
2912
|
+
s.start("Opening browser for authentication...");
|
|
2704
2913
|
const server = http.createServer((req, res) => {
|
|
2705
2914
|
const url = new URL(req.url || "", `http://localhost:${CALLBACK_PORT}`);
|
|
2706
2915
|
if (url.pathname === "/callback") {
|
|
@@ -2722,18 +2931,17 @@ async function login(options) {
|
|
|
2722
2931
|
</head>
|
|
2723
2932
|
<body>
|
|
2724
2933
|
<div class="container">
|
|
2725
|
-
<h1
|
|
2934
|
+
<h1>Authenticated!</h1>
|
|
2726
2935
|
<p>You can close this window and return to the terminal.</p>
|
|
2727
2936
|
</div>
|
|
2728
2937
|
</body>
|
|
2729
2938
|
</html>
|
|
2730
2939
|
`);
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
console.log(" calabasas generate - Generate event handlers");
|
|
2940
|
+
s.stop("Authenticated!");
|
|
2941
|
+
p.note(`calabasas bot add - Add a Discord bot
|
|
2942
|
+
calabasas bot list - List your bots
|
|
2943
|
+
calabasas generate - Generate event handlers`, "Available commands");
|
|
2944
|
+
p.outro("Logged in successfully!");
|
|
2737
2945
|
setTimeout(() => {
|
|
2738
2946
|
server.close();
|
|
2739
2947
|
process.exit(0);
|
|
@@ -2758,9 +2966,11 @@ async function login(options) {
|
|
|
2758
2966
|
});
|
|
2759
2967
|
server.listen(CALLBACK_PORT, async () => {
|
|
2760
2968
|
const authUrl = `${getWebUrlForEnv(env)}/auth/cli?callback=http://localhost:${CALLBACK_PORT}/callback`;
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2969
|
+
s.stop("Browser opened");
|
|
2970
|
+
p.note(`If the browser doesn't open automatically, visit:
|
|
2971
|
+
${authUrl}`, "Authentication URL");
|
|
2972
|
+
const waitSpinner = p.spinner();
|
|
2973
|
+
waitSpinner.start("Waiting for authentication...");
|
|
2764
2974
|
const open2 = await Promise.resolve().then(() => (init_open(), exports_open)).catch(() => null);
|
|
2765
2975
|
if (open2) {
|
|
2766
2976
|
open2.default(authUrl);
|
|
@@ -2771,14 +2981,14 @@ async function login(options) {
|
|
|
2771
2981
|
}
|
|
2772
2982
|
});
|
|
2773
2983
|
setTimeout(() => {
|
|
2774
|
-
|
|
2775
|
-
Authentication timed out. Please try again.`);
|
|
2984
|
+
p.cancel("Authentication timed out. Please try again.");
|
|
2776
2985
|
server.close();
|
|
2777
2986
|
process.exit(1);
|
|
2778
2987
|
}, 5 * 60 * 1000);
|
|
2779
2988
|
}
|
|
2780
2989
|
|
|
2781
2990
|
// src/commands/logout.ts
|
|
2991
|
+
import * as p2 from "@clack/prompts";
|
|
2782
2992
|
async function logout(options) {
|
|
2783
2993
|
if (options.dev && options.prod) {
|
|
2784
2994
|
console.error("Error: Cannot use both --dev and --prod flags.");
|
|
@@ -2789,14 +2999,26 @@ async function logout(options) {
|
|
|
2789
2999
|
process.exit(1);
|
|
2790
3000
|
}
|
|
2791
3001
|
const env = options.dev ? "dev" : "prod";
|
|
3002
|
+
p2.intro("calabasas logout");
|
|
3003
|
+
if (!isAuthenticated(env)) {
|
|
3004
|
+
p2.outro("Not logged in. Nothing to clear.");
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
const confirmed = await p2.confirm({
|
|
3008
|
+
message: `Clear ${env} credentials?`
|
|
3009
|
+
});
|
|
3010
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
3011
|
+
p2.cancel("Cancelled.");
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
2792
3014
|
clearConfig(env);
|
|
2793
|
-
|
|
2794
|
-
console.log(`✅ Logged out of ${label} successfully. Credentials cleared.`);
|
|
3015
|
+
p2.outro(`Logged out of ${env} successfully. Credentials cleared.`);
|
|
2795
3016
|
}
|
|
2796
3017
|
|
|
2797
3018
|
// src/commands/init.ts
|
|
2798
3019
|
import * as fs7 from "fs";
|
|
2799
3020
|
import * as path3 from "path";
|
|
3021
|
+
import * as p3 from "@clack/prompts";
|
|
2800
3022
|
var CONFIG_TEMPLATE = `// Calabasas configuration
|
|
2801
3023
|
// Documentation: https://calabasas.dev/docs/config
|
|
2802
3024
|
|
|
@@ -2822,32 +3044,30 @@ export default defineCalabasas({
|
|
|
2822
3044
|
async function init() {
|
|
2823
3045
|
const convexDir = path3.resolve(process.cwd(), "convex");
|
|
2824
3046
|
const configPath = path3.join(convexDir, "calabasas.config.ts");
|
|
3047
|
+
p3.intro("calabasas init");
|
|
2825
3048
|
if (!fs7.existsSync(convexDir)) {
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3049
|
+
p3.cancel(`convex/ directory not found.
|
|
3050
|
+
|
|
3051
|
+
Make sure you're in a Convex project directory.
|
|
3052
|
+
Run \`npx convex dev\` to initialize a new Convex project.`);
|
|
2830
3053
|
process.exit(1);
|
|
2831
3054
|
}
|
|
2832
3055
|
if (fs7.existsSync(configPath)) {
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
3056
|
+
p3.cancel(`convex/calabasas.config.ts already exists.
|
|
3057
|
+
|
|
3058
|
+
Delete the existing file if you want to start fresh:
|
|
3059
|
+
rm convex/calabasas.config.ts`);
|
|
2837
3060
|
process.exit(1);
|
|
2838
3061
|
}
|
|
2839
3062
|
fs7.writeFileSync(configPath, CONFIG_TEMPLATE);
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
console.log("Next steps:");
|
|
2843
|
-
console.log("1. Edit convex/calabasas.config.ts to configure sync and events");
|
|
2844
|
-
console.log("2. Run `calabasas generate` to generate type-safe handlers");
|
|
2845
|
-
console.log("3. Run `calabasas push` to sync config with Calabasas");
|
|
3063
|
+
p3.note("1. Edit convex/calabasas.config.ts to configure sync and events\n2. Run `calabasas generate` to generate type-safe handlers\n3. Run `calabasas push` to sync config with Calabasas", "Next steps");
|
|
3064
|
+
p3.outro("Created convex/calabasas.config.ts");
|
|
2846
3065
|
}
|
|
2847
3066
|
|
|
2848
3067
|
// src/commands/push.ts
|
|
2849
3068
|
import * as fs8 from "fs";
|
|
2850
3069
|
import * as path4 from "path";
|
|
3070
|
+
import * as p4 from "@clack/prompts";
|
|
2851
3071
|
function parseConfigFile(configPath) {
|
|
2852
3072
|
const configContent = fs8.readFileSync(configPath, "utf-8");
|
|
2853
3073
|
const syncMatch = configContent.match(/sync:\s*\{([^}]+)\}/);
|
|
@@ -2885,38 +3105,29 @@ async function selectBot(apiKey, apiUrl) {
|
|
|
2885
3105
|
}
|
|
2886
3106
|
});
|
|
2887
3107
|
if (!response.ok) {
|
|
2888
|
-
|
|
3108
|
+
p4.cancel("Error fetching bots: " + await response.text());
|
|
2889
3109
|
return null;
|
|
2890
3110
|
}
|
|
2891
3111
|
const { bots } = await response.json();
|
|
2892
3112
|
if (!bots || bots.length === 0) {
|
|
2893
|
-
|
|
3113
|
+
p4.cancel("No bots found. Add a bot first with `calabasas bot add`");
|
|
2894
3114
|
return null;
|
|
2895
3115
|
}
|
|
2896
3116
|
if (bots.length === 1) {
|
|
2897
3117
|
return bots[0]._id;
|
|
2898
3118
|
}
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
input: process.stdin,
|
|
2906
|
-
output: process.stdout
|
|
2907
|
-
});
|
|
2908
|
-
return new Promise((resolve3) => {
|
|
2909
|
-
rl.question("Enter number: ", (answer) => {
|
|
2910
|
-
rl.close();
|
|
2911
|
-
const index = parseInt(answer, 10) - 1;
|
|
2912
|
-
if (index >= 0 && index < bots.length) {
|
|
2913
|
-
resolve3(bots[index]._id);
|
|
2914
|
-
} else {
|
|
2915
|
-
console.error("Invalid selection");
|
|
2916
|
-
resolve3(null);
|
|
2917
|
-
}
|
|
2918
|
-
});
|
|
3119
|
+
const selected = await p4.select({
|
|
3120
|
+
message: "Select a bot to push config to",
|
|
3121
|
+
options: bots.map((bot) => ({
|
|
3122
|
+
value: bot._id,
|
|
3123
|
+
label: bot.name
|
|
3124
|
+
}))
|
|
2919
3125
|
});
|
|
3126
|
+
if (p4.isCancel(selected)) {
|
|
3127
|
+
p4.cancel("Cancelled.");
|
|
3128
|
+
return null;
|
|
3129
|
+
}
|
|
3130
|
+
return selected;
|
|
2920
3131
|
}
|
|
2921
3132
|
async function push(options) {
|
|
2922
3133
|
if (options.dev && options.prod) {
|
|
@@ -2932,8 +3143,7 @@ async function push(options) {
|
|
|
2932
3143
|
const configPath = path4.resolve(process.cwd(), options.config);
|
|
2933
3144
|
if (!fs8.existsSync(configPath)) {
|
|
2934
3145
|
console.error(`Error: Config file not found: ${configPath}`);
|
|
2935
|
-
console.error("");
|
|
2936
|
-
console.error("Run `calabasas init` to create a calabasas.config.ts file.");
|
|
3146
|
+
console.error("\nRun `calabasas init` to create a calabasas.config.ts file.");
|
|
2937
3147
|
process.exit(1);
|
|
2938
3148
|
}
|
|
2939
3149
|
if (!isAuthenticated(env)) {
|
|
@@ -2942,6 +3152,7 @@ async function push(options) {
|
|
|
2942
3152
|
}
|
|
2943
3153
|
const config = getConfig(env);
|
|
2944
3154
|
const apiKey = config.apiKey;
|
|
3155
|
+
p4.intro("calabasas push");
|
|
2945
3156
|
let botId = options.bot;
|
|
2946
3157
|
if (!botId) {
|
|
2947
3158
|
botId = await selectBot(apiKey, apiUrl) ?? undefined;
|
|
@@ -2949,10 +3160,10 @@ async function push(options) {
|
|
|
2949
3160
|
process.exit(1);
|
|
2950
3161
|
}
|
|
2951
3162
|
}
|
|
2952
|
-
const envLabel = env ?? "prod";
|
|
2953
|
-
console.log(`Pushing config from ${options.config} to ${envLabel}...`);
|
|
2954
3163
|
const calabasasConfig = parseConfigFile(configPath);
|
|
2955
3164
|
const eventConfigs = Object.keys(calabasasConfig.events ?? {}).map((eventType) => ({ eventType }));
|
|
3165
|
+
const s = p4.spinner();
|
|
3166
|
+
s.start(`Pushing config from ${options.config} to ${env}...`);
|
|
2956
3167
|
const response = await fetch(`${apiUrl}/api/cli/sync-config`, {
|
|
2957
3168
|
method: "POST",
|
|
2958
3169
|
headers: {
|
|
@@ -2970,28 +3181,33 @@ async function push(options) {
|
|
|
2970
3181
|
});
|
|
2971
3182
|
if (!response.ok) {
|
|
2972
3183
|
const error = await response.text();
|
|
2973
|
-
|
|
3184
|
+
s.stop("Push failed");
|
|
3185
|
+
p4.cancel(`Error: ${error}`);
|
|
2974
3186
|
process.exit(1);
|
|
2975
3187
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3188
|
+
s.stop("Config pushed!");
|
|
3189
|
+
const syncSummary = [
|
|
3190
|
+
`Guilds: ${calabasasConfig.sync?.guilds ? "enabled" : "disabled"}`,
|
|
3191
|
+
`Channels: ${calabasasConfig.sync?.channels ? "enabled" : "disabled"}`,
|
|
3192
|
+
`Roles: ${calabasasConfig.sync?.roles ? "enabled" : "disabled"}`,
|
|
3193
|
+
`Members: ${calabasasConfig.sync?.members ? "enabled" : "disabled"}`
|
|
3194
|
+
].join(`
|
|
3195
|
+
`);
|
|
3196
|
+
const eventSummary = eventConfigs.length > 0 ? `
|
|
3197
|
+
|
|
3198
|
+
Event handlers: ${eventConfigs.length} configured
|
|
3199
|
+
${eventConfigs.map((ec) => ` - ${ec.eventType}`).join(`
|
|
3200
|
+
`)}` : `
|
|
3201
|
+
|
|
3202
|
+
No event handlers configured.`;
|
|
3203
|
+
p4.note(syncSummary + eventSummary, "Sync settings");
|
|
3204
|
+
p4.outro("Config pushed successfully!");
|
|
2990
3205
|
}
|
|
2991
3206
|
|
|
2992
3207
|
// src/commands/generate.ts
|
|
2993
3208
|
import * as fs9 from "fs";
|
|
2994
3209
|
import * as path5 from "path";
|
|
3210
|
+
import * as p5 from "@clack/prompts";
|
|
2995
3211
|
|
|
2996
3212
|
// src/lib/generators/schema.ts
|
|
2997
3213
|
function generateSchemaFile(sync) {
|
|
@@ -3940,19 +4156,20 @@ ${eventNames.map((name) => ` if (type === "${toScreamingSnake(name)}" && ha
|
|
|
3940
4156
|
async function generate(options) {
|
|
3941
4157
|
const convexDir = path5.resolve(process.cwd(), "convex");
|
|
3942
4158
|
const configPath = path5.join(convexDir, "calabasas.config.ts");
|
|
4159
|
+
p5.intro("calabasas generate");
|
|
3943
4160
|
let config = { sync: {}, events: {} };
|
|
3944
4161
|
if (fs9.existsSync(configPath)) {
|
|
3945
|
-
console.log("Reading convex/calabasas.config.ts...");
|
|
3946
4162
|
config = parseConfigFile2(configPath);
|
|
3947
4163
|
} else {
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
console.log("");
|
|
4164
|
+
p5.log.warn("No calabasas.config.ts found, generating with default settings...");
|
|
4165
|
+
p5.log.info("Run `calabasas init` to create a config file.");
|
|
3951
4166
|
}
|
|
4167
|
+
const s = p5.spinner();
|
|
4168
|
+
s.start("Generating files...");
|
|
3952
4169
|
const eventHandlersPath = path5.resolve(process.cwd(), options.output);
|
|
3953
4170
|
const eventHandlersCode = generateEventHandlersFile(config.events ?? {});
|
|
3954
4171
|
fs9.writeFileSync(eventHandlersPath, eventHandlersCode);
|
|
3955
|
-
|
|
4172
|
+
const generated = [options.output];
|
|
3956
4173
|
const syncConfig = config.sync ?? {};
|
|
3957
4174
|
const hasSyncEnabled = syncConfig.guilds || syncConfig.channels || syncConfig.roles || syncConfig.members;
|
|
3958
4175
|
if (hasSyncEnabled) {
|
|
@@ -3963,24 +4180,16 @@ async function generate(options) {
|
|
|
3963
4180
|
const schemaPath = path5.join(calabasasDir, "schema.ts");
|
|
3964
4181
|
const schemaCode = generateSchemaFile(syncConfig);
|
|
3965
4182
|
fs9.writeFileSync(schemaPath, schemaCode);
|
|
3966
|
-
|
|
4183
|
+
generated.push("convex/calabasas/schema.ts");
|
|
3967
4184
|
const syncPath = path5.join(calabasasDir, "sync.ts");
|
|
3968
4185
|
const syncCode = generateSyncFile(syncConfig);
|
|
3969
4186
|
fs9.writeFileSync(syncPath, syncCode);
|
|
3970
|
-
|
|
4187
|
+
generated.push("convex/calabasas/sync.ts");
|
|
3971
4188
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
4189
|
+
s.stop(`Generated ${generated.length} file${generated.length > 1 ? "s" : ""}`);
|
|
4190
|
+
p5.note(generated.join(`
|
|
4191
|
+
`), "Generated files");
|
|
3974
4192
|
if (hasSyncEnabled) {
|
|
3975
|
-
console.log("1. Add tables to your convex/schema.ts:");
|
|
3976
|
-
console.log(' import { calabasasTables } from "./calabasas/schema";');
|
|
3977
|
-
console.log("");
|
|
3978
|
-
console.log(" export default defineSchema({");
|
|
3979
|
-
console.log(" ...calabasasTables,");
|
|
3980
|
-
console.log(" // your other tables...");
|
|
3981
|
-
console.log(" });");
|
|
3982
|
-
console.log("");
|
|
3983
|
-
console.log("2. Re-export sync mutations in convex/discord.ts:");
|
|
3984
4193
|
const syncExports = [];
|
|
3985
4194
|
if (syncConfig.guilds) {
|
|
3986
4195
|
syncExports.push("syncGuild");
|
|
@@ -3992,32 +4201,41 @@ async function generate(options) {
|
|
|
3992
4201
|
syncExports.push("syncRole");
|
|
3993
4202
|
if (syncConfig.members)
|
|
3994
4203
|
syncExports.push("syncMember");
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4204
|
+
p5.note(`1. Add tables to your convex/schema.ts:
|
|
4205
|
+
import { calabasasTables } from "./calabasas/schema";
|
|
4206
|
+
|
|
4207
|
+
export default defineSchema({
|
|
4208
|
+
...calabasasTables,
|
|
4209
|
+
});
|
|
4210
|
+
|
|
4211
|
+
2. Re-export sync mutations in convex/discord.ts:
|
|
4212
|
+
export { ${syncExports.join(", ")} } from "./calabasas/sync";
|
|
4213
|
+
|
|
4214
|
+
3. Add CALABASAS_SECRET to your Convex environment variables
|
|
4215
|
+
4. Create your event handler in convex/discord.ts
|
|
4216
|
+
5. Run \`calabasas push\` to sync config with Calabasas`, "Next steps");
|
|
4000
4217
|
} else {
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4218
|
+
p5.note(`1. Add CALABASAS_SECRET to your Convex environment variables
|
|
4219
|
+
2. Create your handler in convex/discord.ts:
|
|
4220
|
+
|
|
4221
|
+
import { handleDiscordEvent } from "./discord.generated";
|
|
4222
|
+
|
|
4223
|
+
export const receive = handleDiscordEvent({
|
|
4224
|
+
messageCreate: async (ctx, event) => {
|
|
4225
|
+
console.log('New message:', event.content);
|
|
4226
|
+
},
|
|
4227
|
+
});`, "Next steps");
|
|
4011
4228
|
}
|
|
4229
|
+
p5.outro("Generation complete!");
|
|
4012
4230
|
}
|
|
4013
4231
|
|
|
4014
4232
|
// src/commands/skill.ts
|
|
4015
4233
|
import * as fs10 from "fs";
|
|
4016
4234
|
import * as path6 from "path";
|
|
4017
|
-
import * as
|
|
4235
|
+
import * as p6 from "@clack/prompts";
|
|
4018
4236
|
var SECTION_HEADER = "## Calabasas Guidelines";
|
|
4019
|
-
async function fetchSkillContent() {
|
|
4020
|
-
const apiUrl = getApiUrlForEnv(
|
|
4237
|
+
async function fetchSkillContent(env) {
|
|
4238
|
+
const apiUrl = getApiUrlForEnv(env);
|
|
4021
4239
|
const response = await fetch(`${apiUrl}/api/skill`);
|
|
4022
4240
|
if (!response.ok) {
|
|
4023
4241
|
throw new Error(`Failed to fetch skill content: HTTP ${response.status}`);
|
|
@@ -4046,161 +4264,42 @@ function detectExistingFiles(cwd) {
|
|
|
4046
4264
|
function hasCalabsasSection(content) {
|
|
4047
4265
|
return content.includes(SECTION_HEADER) || content.includes("## Calabasas Guidelines");
|
|
4048
4266
|
}
|
|
4049
|
-
async function
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
}
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
if (files.length === 1) {
|
|
4063
|
-
return 0;
|
|
4064
|
-
}
|
|
4065
|
-
let cursor = 0;
|
|
4066
|
-
return new Promise((resolve5) => {
|
|
4067
|
-
const render = () => {
|
|
4068
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
4069
|
-
console.log(`Multiple config files found
|
|
4070
|
-
`);
|
|
4071
|
-
console.log(`Which file should receive the Calabasas guidelines?
|
|
4072
|
-
`);
|
|
4073
|
-
files.forEach((file, i) => {
|
|
4074
|
-
const isCursor = cursor === i;
|
|
4075
|
-
const pointer = isCursor ? "→" : " ";
|
|
4076
|
-
const highlight = isCursor ? "\x1B[36m" : "";
|
|
4077
|
-
const reset = "\x1B[0m";
|
|
4078
|
-
console.log(`${pointer} ${highlight}${file.path}${reset}`);
|
|
4079
|
-
});
|
|
4080
|
-
console.log(`
|
|
4081
|
-
↑/↓ to navigate, Enter to select`);
|
|
4082
|
-
};
|
|
4083
|
-
render();
|
|
4084
|
-
if (process.stdin.isTTY) {
|
|
4085
|
-
process.stdin.setRawMode(true);
|
|
4086
|
-
}
|
|
4087
|
-
process.stdin.resume();
|
|
4088
|
-
process.stdin.setEncoding("utf8");
|
|
4089
|
-
const onKeypress = (key) => {
|
|
4090
|
-
if (key === "\x03") {
|
|
4091
|
-
if (process.stdin.isTTY) {
|
|
4092
|
-
process.stdin.setRawMode(false);
|
|
4093
|
-
}
|
|
4094
|
-
console.log(`
|
|
4095
|
-
`);
|
|
4096
|
-
process.exit(0);
|
|
4097
|
-
}
|
|
4098
|
-
if (key === "\r" || key === `
|
|
4099
|
-
`) {
|
|
4100
|
-
process.stdin.removeListener("data", onKeypress);
|
|
4101
|
-
if (process.stdin.isTTY) {
|
|
4102
|
-
process.stdin.setRawMode(false);
|
|
4103
|
-
}
|
|
4104
|
-
console.log(`
|
|
4105
|
-
`);
|
|
4106
|
-
resolve5(cursor);
|
|
4107
|
-
return;
|
|
4108
|
-
}
|
|
4109
|
-
if (key === "\x1B[A" || key === "k") {
|
|
4110
|
-
cursor = Math.max(0, cursor - 1);
|
|
4111
|
-
render();
|
|
4112
|
-
return;
|
|
4113
|
-
}
|
|
4114
|
-
if (key === "\x1B[B" || key === "j") {
|
|
4115
|
-
cursor = Math.min(files.length - 1, cursor + 1);
|
|
4116
|
-
render();
|
|
4117
|
-
return;
|
|
4118
|
-
}
|
|
4119
|
-
};
|
|
4120
|
-
process.stdin.on("data", onKeypress);
|
|
4121
|
-
});
|
|
4122
|
-
}
|
|
4123
|
-
async function selectCreateLocation() {
|
|
4124
|
-
const options = [
|
|
4125
|
-
{ label: "CLAUDE.md (project root)", path: "CLAUDE.md" },
|
|
4126
|
-
{ label: "AGENTS.md (project root)", path: "AGENTS.md" },
|
|
4127
|
-
{ label: ".claude/CLAUDE.md", path: ".claude/CLAUDE.md" },
|
|
4128
|
-
{ label: "Cancel", path: null }
|
|
4129
|
-
];
|
|
4130
|
-
let cursor = 0;
|
|
4131
|
-
return new Promise((resolve5) => {
|
|
4132
|
-
const render = () => {
|
|
4133
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
4134
|
-
console.log(`No CLAUDE.md or AGENTS.md found
|
|
4135
|
-
`);
|
|
4136
|
-
console.log(`Would you like to create one?
|
|
4137
|
-
`);
|
|
4138
|
-
options.forEach((opt, i) => {
|
|
4139
|
-
const isCursor = cursor === i;
|
|
4140
|
-
const pointer = isCursor ? "→" : " ";
|
|
4141
|
-
const highlight = isCursor ? "\x1B[36m" : "";
|
|
4142
|
-
const reset = "\x1B[0m";
|
|
4143
|
-
console.log(`${pointer} ${highlight}${opt.label}${reset}`);
|
|
4144
|
-
});
|
|
4145
|
-
console.log(`
|
|
4146
|
-
↑/↓ to navigate, Enter to select`);
|
|
4147
|
-
};
|
|
4148
|
-
render();
|
|
4149
|
-
if (process.stdin.isTTY) {
|
|
4150
|
-
process.stdin.setRawMode(true);
|
|
4151
|
-
}
|
|
4152
|
-
process.stdin.resume();
|
|
4153
|
-
process.stdin.setEncoding("utf8");
|
|
4154
|
-
const onKeypress = (key) => {
|
|
4155
|
-
if (key === "\x03") {
|
|
4156
|
-
if (process.stdin.isTTY) {
|
|
4157
|
-
process.stdin.setRawMode(false);
|
|
4158
|
-
}
|
|
4159
|
-
console.log(`
|
|
4160
|
-
`);
|
|
4161
|
-
process.exit(0);
|
|
4162
|
-
}
|
|
4163
|
-
if (key === "\r" || key === `
|
|
4164
|
-
`) {
|
|
4165
|
-
process.stdin.removeListener("data", onKeypress);
|
|
4166
|
-
if (process.stdin.isTTY) {
|
|
4167
|
-
process.stdin.setRawMode(false);
|
|
4168
|
-
}
|
|
4169
|
-
console.log(`
|
|
4170
|
-
`);
|
|
4171
|
-
resolve5(options[cursor].path);
|
|
4172
|
-
return;
|
|
4173
|
-
}
|
|
4174
|
-
if (key === "\x1B[A" || key === "k") {
|
|
4175
|
-
cursor = Math.max(0, cursor - 1);
|
|
4176
|
-
render();
|
|
4177
|
-
return;
|
|
4178
|
-
}
|
|
4179
|
-
if (key === "\x1B[B" || key === "j") {
|
|
4180
|
-
cursor = Math.min(options.length - 1, cursor + 1);
|
|
4181
|
-
render();
|
|
4182
|
-
return;
|
|
4183
|
-
}
|
|
4184
|
-
};
|
|
4185
|
-
process.stdin.on("data", onKeypress);
|
|
4186
|
-
});
|
|
4187
|
-
}
|
|
4188
|
-
async function skill() {
|
|
4189
|
-
console.log("Fetching latest Calabasas guidelines...");
|
|
4267
|
+
async function skill(options) {
|
|
4268
|
+
if (options.dev && options.prod) {
|
|
4269
|
+
console.error("Error: Cannot use both --dev and --prod flags.");
|
|
4270
|
+
process.exit(1);
|
|
4271
|
+
}
|
|
4272
|
+
if (!options.dev && !options.prod) {
|
|
4273
|
+
console.error("Error: You must specify either --dev or --prod.");
|
|
4274
|
+
process.exit(1);
|
|
4275
|
+
}
|
|
4276
|
+
const env = options.dev ? "dev" : "prod";
|
|
4277
|
+
p6.intro("calabasas skill");
|
|
4278
|
+
const s = p6.spinner();
|
|
4279
|
+
s.start("Fetching latest Calabasas guidelines...");
|
|
4190
4280
|
let skillContent;
|
|
4191
4281
|
try {
|
|
4192
|
-
skillContent = await fetchSkillContent();
|
|
4282
|
+
skillContent = await fetchSkillContent(env);
|
|
4193
4283
|
} catch (error) {
|
|
4194
|
-
|
|
4284
|
+
s.stop("Failed");
|
|
4285
|
+
p6.cancel(error instanceof Error ? error.message : "Failed to fetch skill content");
|
|
4195
4286
|
process.exit(1);
|
|
4196
4287
|
}
|
|
4288
|
+
s.stop("Guidelines fetched");
|
|
4197
4289
|
const cwd = process.cwd();
|
|
4198
4290
|
const detectedFiles = detectExistingFiles(cwd);
|
|
4199
4291
|
if (detectedFiles.length === 0) {
|
|
4200
|
-
const createPath = await
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4292
|
+
const createPath = await p6.select({
|
|
4293
|
+
message: "No CLAUDE.md or AGENTS.md found. Where should we create one?",
|
|
4294
|
+
options: [
|
|
4295
|
+
{ value: "CLAUDE.md", label: "CLAUDE.md (project root)" },
|
|
4296
|
+
{ value: "AGENTS.md", label: "AGENTS.md (project root)" },
|
|
4297
|
+
{ value: ".claude/CLAUDE.md", label: ".claude/CLAUDE.md" }
|
|
4298
|
+
]
|
|
4299
|
+
});
|
|
4300
|
+
if (p6.isCancel(createPath)) {
|
|
4301
|
+
p6.cancel("Cancelled.");
|
|
4302
|
+
return;
|
|
4204
4303
|
}
|
|
4205
4304
|
const fullPath = path6.resolve(cwd, createPath);
|
|
4206
4305
|
const dir = path6.dirname(fullPath);
|
|
@@ -4212,17 +4311,34 @@ async function skill() {
|
|
|
4212
4311
|
|
|
4213
4312
|
${skillContent}`;
|
|
4214
4313
|
fs10.writeFileSync(fullPath, initialContent);
|
|
4215
|
-
|
|
4314
|
+
p6.outro(`Created ${createPath} with Calabasas guidelines`);
|
|
4216
4315
|
return;
|
|
4217
4316
|
}
|
|
4218
|
-
|
|
4219
|
-
|
|
4317
|
+
let selectedFile;
|
|
4318
|
+
if (detectedFiles.length === 1) {
|
|
4319
|
+
selectedFile = detectedFiles[0];
|
|
4320
|
+
} else {
|
|
4321
|
+
const selected = await p6.select({
|
|
4322
|
+
message: "Which file should receive the Calabasas guidelines?",
|
|
4323
|
+
options: detectedFiles.map((file) => ({
|
|
4324
|
+
value: file.path,
|
|
4325
|
+
label: file.path
|
|
4326
|
+
}))
|
|
4327
|
+
});
|
|
4328
|
+
if (p6.isCancel(selected)) {
|
|
4329
|
+
p6.cancel("Cancelled.");
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
selectedFile = detectedFiles.find((f) => f.path === selected);
|
|
4333
|
+
}
|
|
4220
4334
|
const existingContent = fs10.readFileSync(selectedFile.fullPath, "utf8");
|
|
4221
4335
|
if (hasCalabsasSection(existingContent)) {
|
|
4222
|
-
const update = await
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4336
|
+
const update = await p6.confirm({
|
|
4337
|
+
message: `Calabasas guidelines already exist in ${selectedFile.path}. Replace?`
|
|
4338
|
+
});
|
|
4339
|
+
if (p6.isCancel(update) || !update) {
|
|
4340
|
+
p6.cancel("Cancelled.");
|
|
4341
|
+
return;
|
|
4226
4342
|
}
|
|
4227
4343
|
const sectionRegex = /## Calabasas Guidelines[\s\S]*?(?=\n## |\n# |$)/;
|
|
4228
4344
|
const contentWithoutSection = existingContent.replace(sectionRegex, "").trimEnd();
|
|
@@ -4230,7 +4346,7 @@ ${skillContent}`;
|
|
|
4230
4346
|
|
|
4231
4347
|
${skillContent}`;
|
|
4232
4348
|
fs10.writeFileSync(selectedFile.fullPath, newContent);
|
|
4233
|
-
|
|
4349
|
+
p6.outro(`Updated Calabasas guidelines in ${selectedFile.path}`);
|
|
4234
4350
|
} else {
|
|
4235
4351
|
const separator = existingContent.endsWith(`
|
|
4236
4352
|
`) ? `
|
|
@@ -4239,13 +4355,14 @@ ${skillContent}`;
|
|
|
4239
4355
|
`;
|
|
4240
4356
|
const newContent = `${existingContent}${separator}${skillContent}`;
|
|
4241
4357
|
fs10.writeFileSync(selectedFile.fullPath, newContent);
|
|
4242
|
-
|
|
4358
|
+
p6.outro(`Added Calabasas guidelines to ${selectedFile.path}`);
|
|
4243
4359
|
}
|
|
4244
4360
|
}
|
|
4245
4361
|
|
|
4246
4362
|
// src/commands/add.ts
|
|
4247
4363
|
import * as fs11 from "fs";
|
|
4248
4364
|
import * as path7 from "path";
|
|
4365
|
+
import * as p7 from "@clack/prompts";
|
|
4249
4366
|
|
|
4250
4367
|
// src/lib/registry/components/channel-select.ts
|
|
4251
4368
|
var channelSelect = {
|
|
@@ -4867,73 +4984,6 @@ function parseEnabledSyncTypes(configPath) {
|
|
|
4867
4984
|
}
|
|
4868
4985
|
return enabled;
|
|
4869
4986
|
}
|
|
4870
|
-
async function selectComponents() {
|
|
4871
|
-
const selected = new Set;
|
|
4872
|
-
let cursor = 0;
|
|
4873
|
-
return new Promise((resolve6) => {
|
|
4874
|
-
const render = () => {
|
|
4875
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
4876
|
-
console.log(`Select components to add (arrow keys to navigate, space to toggle, enter to confirm)
|
|
4877
|
-
`);
|
|
4878
|
-
REGISTRY.forEach((comp, i) => {
|
|
4879
|
-
const isSelected = selected.has(i);
|
|
4880
|
-
const isCursor = cursor === i;
|
|
4881
|
-
const checkbox = isSelected ? "[✓]" : "[ ]";
|
|
4882
|
-
const pointer = isCursor ? "→" : " ";
|
|
4883
|
-
console.log(`${pointer} ${checkbox} ${comp.name}`);
|
|
4884
|
-
if (isCursor) {
|
|
4885
|
-
console.log(` ${comp.description}`);
|
|
4886
|
-
}
|
|
4887
|
-
});
|
|
4888
|
-
const count = selected.size;
|
|
4889
|
-
console.log(`
|
|
4890
|
-
${count} component${count === 1 ? "" : "s"} selected`);
|
|
4891
|
-
};
|
|
4892
|
-
render();
|
|
4893
|
-
if (process.stdin.isTTY) {
|
|
4894
|
-
process.stdin.setRawMode(true);
|
|
4895
|
-
}
|
|
4896
|
-
process.stdin.resume();
|
|
4897
|
-
process.stdin.setEncoding("utf8");
|
|
4898
|
-
const onKeypress = (key) => {
|
|
4899
|
-
if (key === "\x03") {
|
|
4900
|
-
if (process.stdin.isTTY)
|
|
4901
|
-
process.stdin.setRawMode(false);
|
|
4902
|
-
process.exit(0);
|
|
4903
|
-
}
|
|
4904
|
-
if (key === "\r" || key === `
|
|
4905
|
-
`) {
|
|
4906
|
-
process.stdin.removeListener("data", onKeypress);
|
|
4907
|
-
if (process.stdin.isTTY)
|
|
4908
|
-
process.stdin.setRawMode(false);
|
|
4909
|
-
console.log(`
|
|
4910
|
-
`);
|
|
4911
|
-
resolve6(Array.from(selected).map((i) => REGISTRY[i]));
|
|
4912
|
-
return;
|
|
4913
|
-
}
|
|
4914
|
-
if (key === " ") {
|
|
4915
|
-
if (selected.has(cursor)) {
|
|
4916
|
-
selected.delete(cursor);
|
|
4917
|
-
} else {
|
|
4918
|
-
selected.add(cursor);
|
|
4919
|
-
}
|
|
4920
|
-
render();
|
|
4921
|
-
return;
|
|
4922
|
-
}
|
|
4923
|
-
if (key === "\x1B[A" || key === "k") {
|
|
4924
|
-
cursor = Math.max(0, cursor - 1);
|
|
4925
|
-
render();
|
|
4926
|
-
return;
|
|
4927
|
-
}
|
|
4928
|
-
if (key === "\x1B[B" || key === "j") {
|
|
4929
|
-
cursor = Math.min(REGISTRY.length - 1, cursor + 1);
|
|
4930
|
-
render();
|
|
4931
|
-
return;
|
|
4932
|
-
}
|
|
4933
|
-
};
|
|
4934
|
-
process.stdin.on("data", onKeypress);
|
|
4935
|
-
});
|
|
4936
|
-
}
|
|
4937
4987
|
function existingQueryNames(filePath) {
|
|
4938
4988
|
if (!fs11.existsSync(filePath))
|
|
4939
4989
|
return new Set;
|
|
@@ -4975,7 +5025,20 @@ async function add(componentNames) {
|
|
|
4975
5025
|
}
|
|
4976
5026
|
let components;
|
|
4977
5027
|
if (componentNames.length === 0) {
|
|
4978
|
-
|
|
5028
|
+
const selected = await p7.multiselect({
|
|
5029
|
+
message: "Select components to add",
|
|
5030
|
+
options: REGISTRY.map((comp) => ({
|
|
5031
|
+
value: comp.name,
|
|
5032
|
+
label: comp.name,
|
|
5033
|
+
hint: comp.description
|
|
5034
|
+
})),
|
|
5035
|
+
required: true
|
|
5036
|
+
});
|
|
5037
|
+
if (p7.isCancel(selected)) {
|
|
5038
|
+
p7.cancel("Cancelled.");
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
components = selected.map((name) => getComponent(name)).filter((c) => c !== undefined);
|
|
4979
5042
|
if (components.length === 0) {
|
|
4980
5043
|
console.log("No components selected.");
|
|
4981
5044
|
return;
|
|
@@ -4998,13 +5061,11 @@ async function add(componentNames) {
|
|
|
4998
5061
|
const existing = existingQueryNames(queriesPath);
|
|
4999
5062
|
const allMissingShadcn = new Set;
|
|
5000
5063
|
for (const comp of components) {
|
|
5001
|
-
|
|
5002
|
-
console.log("");
|
|
5064
|
+
p7.log.step(`Adding ${comp.name}...`);
|
|
5003
5065
|
const missingSyncTypes = comp.requiredSyncTypes.filter((t) => !enabledSync.has(t));
|
|
5004
5066
|
if (missingSyncTypes.length > 0) {
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
console.log("");
|
|
5067
|
+
p7.log.warn(`Skipped ${comp.name} — requires sync types not enabled: ${missingSyncTypes.join(", ")}`);
|
|
5068
|
+
p7.log.info(`Enable them in calabasas.config.ts and re-run \`calabasas generate\`.`);
|
|
5008
5069
|
continue;
|
|
5009
5070
|
}
|
|
5010
5071
|
if (!fs11.existsSync(componentsDir)) {
|
|
@@ -5012,7 +5073,7 @@ async function add(componentNames) {
|
|
|
5012
5073
|
}
|
|
5013
5074
|
const componentPath = path7.join(componentsDir, `${comp.name}.tsx`);
|
|
5014
5075
|
fs11.writeFileSync(componentPath, comp.generateReactComponent());
|
|
5015
|
-
|
|
5076
|
+
p7.log.success(`Created components/calabasas/${comp.name}.tsx`);
|
|
5016
5077
|
const queryBlock = comp.generateConvexQueries();
|
|
5017
5078
|
const namesInBlock = queryNamesInBlock(queryBlock);
|
|
5018
5079
|
const newNames = namesInBlock.filter((n) => !existing.has(n));
|
|
@@ -5020,14 +5081,13 @@ async function add(componentNames) {
|
|
|
5020
5081
|
newQueryBlocks.push(queryBlock);
|
|
5021
5082
|
for (const n of newNames)
|
|
5022
5083
|
existing.add(n);
|
|
5023
|
-
|
|
5084
|
+
p7.log.success(`Updated convex/calabasas/queries.ts (added ${newNames.join(", ")})`);
|
|
5024
5085
|
} else {
|
|
5025
|
-
|
|
5086
|
+
p7.log.info(`convex/calabasas/queries.ts already has queries for ${comp.name}`);
|
|
5026
5087
|
}
|
|
5027
5088
|
for (const s of comp.requiredShadcnComponents) {
|
|
5028
5089
|
allMissingShadcn.add(s);
|
|
5029
5090
|
}
|
|
5030
|
-
console.log("");
|
|
5031
5091
|
}
|
|
5032
5092
|
if (newQueryBlocks.length > 0) {
|
|
5033
5093
|
if (fs11.existsSync(queriesPath)) {
|
|
@@ -5058,35 +5118,31 @@ ${newQueryBlocks.join(`
|
|
|
5058
5118
|
}
|
|
5059
5119
|
}
|
|
5060
5120
|
if (missingShadcn.length > 0) {
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5121
|
+
p7.note(`Missing: ${missingShadcn.join(", ")}
|
|
5122
|
+
|
|
5123
|
+
Install with: npx shadcn@latest add ${missingShadcn.join(" ")}`, "Required shadcn components");
|
|
5064
5124
|
}
|
|
5065
5125
|
const firstInstalled = components[0];
|
|
5066
5126
|
if (firstInstalled) {
|
|
5067
5127
|
const pascal = toPascalCase(firstInstalled.name);
|
|
5068
5128
|
const needsGuild = firstInstalled.requiredSyncTypes.some((t) => t === "channels" || t === "roles" || t === "members");
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
} else {
|
|
5081
|
-
console.log(` <${pascal}`);
|
|
5082
|
-
console.log(` onValueChange={(id) => console.log(id)}`);
|
|
5083
|
-
console.log(` />`);
|
|
5084
|
-
}
|
|
5129
|
+
const usage = needsGuild ? `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
|
|
5130
|
+
|
|
5131
|
+
<${pascal}
|
|
5132
|
+
guildDiscordId="123456789"
|
|
5133
|
+
onValueChange={(id) => console.log(id)}
|
|
5134
|
+
/>` : `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
|
|
5135
|
+
|
|
5136
|
+
<${pascal}
|
|
5137
|
+
onValueChange={(id) => console.log(id)}
|
|
5138
|
+
/>`;
|
|
5139
|
+
p7.note(usage, "Usage example");
|
|
5085
5140
|
}
|
|
5086
5141
|
}
|
|
5087
5142
|
|
|
5088
5143
|
// src/commands/bot.ts
|
|
5089
|
-
import * as
|
|
5144
|
+
import * as p8 from "@clack/prompts";
|
|
5145
|
+
import pc from "picocolors";
|
|
5090
5146
|
var INTENTS = [
|
|
5091
5147
|
{ name: "Guilds", value: 1 << 0, description: "Guild create/update/delete, roles, channels" },
|
|
5092
5148
|
{ name: "Guild Members", value: 1 << 1, description: "Member add/remove/update (Privileged)", privileged: true },
|
|
@@ -5108,9 +5164,9 @@ var INTENTS = [
|
|
|
5108
5164
|
{ name: "Auto Moderation Config", value: 1 << 20, description: "Auto mod rule changes" },
|
|
5109
5165
|
{ name: "Auto Moderation Execution", value: 1 << 21, description: "Auto mod action execution" }
|
|
5110
5166
|
];
|
|
5111
|
-
function encrypt(
|
|
5167
|
+
function encrypt(text2, key) {
|
|
5112
5168
|
const keyBytes = new TextEncoder().encode(key);
|
|
5113
|
-
const textBytes = new TextEncoder().encode(
|
|
5169
|
+
const textBytes = new TextEncoder().encode(text2);
|
|
5114
5170
|
const encrypted = new Uint8Array(textBytes.length);
|
|
5115
5171
|
for (let i = 0;i < textBytes.length; i++) {
|
|
5116
5172
|
encrypted[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
@@ -5125,91 +5181,6 @@ function generateSecret() {
|
|
|
5125
5181
|
}
|
|
5126
5182
|
return result;
|
|
5127
5183
|
}
|
|
5128
|
-
function prompt2(question) {
|
|
5129
|
-
const rl = readline2.createInterface({
|
|
5130
|
-
input: process.stdin,
|
|
5131
|
-
output: process.stdout
|
|
5132
|
-
});
|
|
5133
|
-
return new Promise((resolve6) => {
|
|
5134
|
-
rl.question(question, (answer) => {
|
|
5135
|
-
rl.close();
|
|
5136
|
-
resolve6(answer);
|
|
5137
|
-
});
|
|
5138
|
-
});
|
|
5139
|
-
}
|
|
5140
|
-
async function selectIntents() {
|
|
5141
|
-
const selected = new Set([0, 9]);
|
|
5142
|
-
let cursor = 0;
|
|
5143
|
-
return new Promise((resolve6) => {
|
|
5144
|
-
const render = () => {
|
|
5145
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
5146
|
-
console.log(`Select Gateway Intents (use arrow keys, space to toggle, enter to confirm)
|
|
5147
|
-
`);
|
|
5148
|
-
console.log(` ⚠️ Privileged intents require approval in Discord Developer Portal
|
|
5149
|
-
`);
|
|
5150
|
-
INTENTS.forEach((intent, i) => {
|
|
5151
|
-
const isSelected = selected.has(i);
|
|
5152
|
-
const isCursor = cursor === i;
|
|
5153
|
-
const checkbox = isSelected ? "[✓]" : "[ ]";
|
|
5154
|
-
const pointer = isCursor ? "→" : " ";
|
|
5155
|
-
const privilegedTag = intent.privileged ? " (Privileged)" : "";
|
|
5156
|
-
const color = intent.privileged ? "\x1B[33m" : "";
|
|
5157
|
-
const reset = "\x1B[0m";
|
|
5158
|
-
console.log(`${pointer} ${checkbox} ${color}${intent.name}${privilegedTag}${reset}`);
|
|
5159
|
-
if (isCursor) {
|
|
5160
|
-
console.log(` ${intent.description}`);
|
|
5161
|
-
}
|
|
5162
|
-
});
|
|
5163
|
-
const total = Array.from(selected).reduce((sum, i) => sum + INTENTS[i].value, 0);
|
|
5164
|
-
console.log(`
|
|
5165
|
-
Selected intents value: ${total}`);
|
|
5166
|
-
};
|
|
5167
|
-
render();
|
|
5168
|
-
if (process.stdin.isTTY) {
|
|
5169
|
-
process.stdin.setRawMode(true);
|
|
5170
|
-
}
|
|
5171
|
-
process.stdin.resume();
|
|
5172
|
-
process.stdin.setEncoding("utf8");
|
|
5173
|
-
const onKeypress = (key) => {
|
|
5174
|
-
if (key === "\x03") {
|
|
5175
|
-
process.stdin.setRawMode(false);
|
|
5176
|
-
process.exit(0);
|
|
5177
|
-
}
|
|
5178
|
-
if (key === "\r" || key === `
|
|
5179
|
-
`) {
|
|
5180
|
-
process.stdin.removeListener("data", onKeypress);
|
|
5181
|
-
if (process.stdin.isTTY) {
|
|
5182
|
-
process.stdin.setRawMode(false);
|
|
5183
|
-
}
|
|
5184
|
-
const total = Array.from(selected).reduce((sum, i) => sum + INTENTS[i].value, 0);
|
|
5185
|
-
console.log(`
|
|
5186
|
-
`);
|
|
5187
|
-
resolve6(total);
|
|
5188
|
-
return;
|
|
5189
|
-
}
|
|
5190
|
-
if (key === " ") {
|
|
5191
|
-
if (selected.has(cursor)) {
|
|
5192
|
-
selected.delete(cursor);
|
|
5193
|
-
} else {
|
|
5194
|
-
selected.add(cursor);
|
|
5195
|
-
}
|
|
5196
|
-
render();
|
|
5197
|
-
return;
|
|
5198
|
-
}
|
|
5199
|
-
if (key === "\x1B[A" || key === "k") {
|
|
5200
|
-
cursor = Math.max(0, cursor - 1);
|
|
5201
|
-
render();
|
|
5202
|
-
return;
|
|
5203
|
-
}
|
|
5204
|
-
if (key === "\x1B[B" || key === "j") {
|
|
5205
|
-
cursor = Math.min(INTENTS.length - 1, cursor + 1);
|
|
5206
|
-
render();
|
|
5207
|
-
return;
|
|
5208
|
-
}
|
|
5209
|
-
};
|
|
5210
|
-
process.stdin.on("data", onKeypress);
|
|
5211
|
-
});
|
|
5212
|
-
}
|
|
5213
5184
|
async function apiRequest(method, path8, body, env) {
|
|
5214
5185
|
const config = getConfig(env);
|
|
5215
5186
|
const url = `${getApiUrlForEnv(env)}${path8}`;
|
|
@@ -5238,30 +5209,59 @@ async function botAdd(options) {
|
|
|
5238
5209
|
console.log("Not logged in. Run `calabasas login` first.");
|
|
5239
5210
|
return;
|
|
5240
5211
|
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5212
|
+
p8.intro("calabasas bot add");
|
|
5213
|
+
const name = await p8.text({
|
|
5214
|
+
message: "Bot name",
|
|
5215
|
+
placeholder: "My Discord Bot",
|
|
5216
|
+
validate: (v) => v.trim() ? undefined : "Bot name is required"
|
|
5217
|
+
});
|
|
5218
|
+
if (p8.isCancel(name)) {
|
|
5219
|
+
p8.cancel("Cancelled.");
|
|
5246
5220
|
return;
|
|
5247
5221
|
}
|
|
5248
|
-
const discordAppId = await
|
|
5249
|
-
|
|
5250
|
-
|
|
5222
|
+
const discordAppId = await p8.text({
|
|
5223
|
+
message: "Discord Application ID",
|
|
5224
|
+
placeholder: "123456789012345678",
|
|
5225
|
+
validate: (v) => v.trim() ? undefined : "Discord Application ID is required"
|
|
5226
|
+
});
|
|
5227
|
+
if (p8.isCancel(discordAppId)) {
|
|
5228
|
+
p8.cancel("Cancelled.");
|
|
5251
5229
|
return;
|
|
5252
5230
|
}
|
|
5253
|
-
const token = await
|
|
5254
|
-
|
|
5255
|
-
|
|
5231
|
+
const token = await p8.password({
|
|
5232
|
+
message: "Bot token",
|
|
5233
|
+
validate: (v) => v.trim() ? undefined : "Bot token is required"
|
|
5234
|
+
});
|
|
5235
|
+
if (p8.isCancel(token)) {
|
|
5236
|
+
p8.cancel("Cancelled.");
|
|
5256
5237
|
return;
|
|
5257
5238
|
}
|
|
5258
|
-
const convexUrl = await
|
|
5259
|
-
|
|
5260
|
-
|
|
5239
|
+
const convexUrl = await p8.text({
|
|
5240
|
+
message: "Your Convex URL",
|
|
5241
|
+
placeholder: "https://your-project.convex.cloud",
|
|
5242
|
+
validate: (v) => v.trim() ? undefined : "Convex URL is required"
|
|
5243
|
+
});
|
|
5244
|
+
if (p8.isCancel(convexUrl)) {
|
|
5245
|
+
p8.cancel("Cancelled.");
|
|
5246
|
+
return;
|
|
5247
|
+
}
|
|
5248
|
+
const selectedIntents = await p8.multiselect({
|
|
5249
|
+
message: "Select Gateway Intents",
|
|
5250
|
+
options: INTENTS.map((intent, i) => ({
|
|
5251
|
+
value: i,
|
|
5252
|
+
label: intent.privileged ? pc.yellow(intent.name + " (Privileged)") : intent.name,
|
|
5253
|
+
hint: intent.description
|
|
5254
|
+
})),
|
|
5255
|
+
initialValues: [0, 9],
|
|
5256
|
+
required: true
|
|
5257
|
+
});
|
|
5258
|
+
if (p8.isCancel(selectedIntents)) {
|
|
5259
|
+
p8.cancel("Cancelled.");
|
|
5261
5260
|
return;
|
|
5262
5261
|
}
|
|
5263
|
-
const intents =
|
|
5264
|
-
|
|
5262
|
+
const intents = selectedIntents.reduce((sum, i) => sum + INTENTS[i].value, 0);
|
|
5263
|
+
const s = p8.spinner();
|
|
5264
|
+
s.start("Creating bot...");
|
|
5265
5265
|
const encryptionKey = "calabasas-dev-key-change-in-production";
|
|
5266
5266
|
const encryptedToken = encrypt(token, encryptionKey);
|
|
5267
5267
|
const sharedSecret = generateSecret();
|
|
@@ -5274,20 +5274,16 @@ async function botAdd(options) {
|
|
|
5274
5274
|
sharedSecret
|
|
5275
5275
|
}, env);
|
|
5276
5276
|
if (!ok) {
|
|
5277
|
-
|
|
5278
|
-
|
|
5277
|
+
s.stop("Failed to create bot");
|
|
5278
|
+
p8.cancel(`${data.error || `HTTP ${status}`}`);
|
|
5279
5279
|
return;
|
|
5280
5280
|
}
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
console.log("Next steps:");
|
|
5288
|
-
console.log("1. Run `calabasas generate` to generate the event handler");
|
|
5289
|
-
console.log("2. Create your handler in convex/discord.ts");
|
|
5290
|
-
console.log("3. Add CALABASAS_SECRET to your Convex environment variables");
|
|
5281
|
+
s.stop("Bot created!");
|
|
5282
|
+
p8.note(`${sharedSecret}
|
|
5283
|
+
|
|
5284
|
+
Add this to your Convex environment as CALABASAS_SECRET`, "Shared Secret");
|
|
5285
|
+
p8.note("1. Run `calabasas generate` to generate the event handler\n2. Create your handler in convex/discord.ts\n3. Add CALABASAS_SECRET to your Convex environment variables", "Next steps");
|
|
5286
|
+
p8.outro("Bot created successfully!");
|
|
5291
5287
|
}
|
|
5292
5288
|
async function botList(options) {
|
|
5293
5289
|
if (options.dev && options.prod) {
|
|
@@ -5303,6 +5299,22 @@ async function botList(options) {
|
|
|
5303
5299
|
console.log("Not logged in. Run `calabasas login` first.");
|
|
5304
5300
|
return;
|
|
5305
5301
|
}
|
|
5302
|
+
if (options.watch) {
|
|
5303
|
+
const React = await import("react");
|
|
5304
|
+
const { render } = await import("ink");
|
|
5305
|
+
const { createConvexClient: createConvexClient2, ConvexProvider: ConvexProvider2 } = await Promise.resolve().then(() => (init_convex(), exports_convex));
|
|
5306
|
+
const { BotList: BotList2 } = await Promise.resolve().then(() => (init_BotList(), exports_BotList));
|
|
5307
|
+
const config = getConfig(env);
|
|
5308
|
+
const convexUrl = getConvexUrl(env);
|
|
5309
|
+
const client = createConvexClient2(convexUrl);
|
|
5310
|
+
const { waitUntilExit } = render(React.createElement(ConvexProvider2, {
|
|
5311
|
+
client,
|
|
5312
|
+
children: React.createElement(BotList2, { apiKey: config.apiKey })
|
|
5313
|
+
}));
|
|
5314
|
+
await waitUntilExit();
|
|
5315
|
+
await client.close();
|
|
5316
|
+
return;
|
|
5317
|
+
}
|
|
5306
5318
|
const { ok, data, status } = await apiRequest("GET", "/api/cli/bots", undefined, env);
|
|
5307
5319
|
if (!ok) {
|
|
5308
5320
|
console.log(`Failed to list bots: ${data.error || `HTTP ${status}`}`);
|
|
@@ -5313,15 +5325,17 @@ async function botList(options) {
|
|
|
5313
5325
|
console.log("No bots found. Run `calabasas bot add` to create one.");
|
|
5314
5326
|
return;
|
|
5315
5327
|
}
|
|
5316
|
-
console.log(`
|
|
5317
|
-
|
|
5328
|
+
console.log(pc.bold(`
|
|
5329
|
+
Your Discord bots:
|
|
5330
|
+
`));
|
|
5318
5331
|
for (const bot of bots) {
|
|
5319
|
-
const
|
|
5320
|
-
|
|
5321
|
-
console.log(
|
|
5322
|
-
console.log(`
|
|
5323
|
-
console.log(`
|
|
5324
|
-
console.log(`
|
|
5332
|
+
const statusColor = bot.status === "connected" ? pc.green : bot.status === "connecting" ? pc.yellow : bot.status === "error" ? pc.red : pc.dim;
|
|
5333
|
+
const statusDot = bot.status === "connected" ? pc.green("●") : bot.status === "connecting" ? pc.yellow("●") : bot.status === "error" ? pc.red("●") : pc.dim("●");
|
|
5334
|
+
console.log(`${statusDot} ${pc.bold(bot.name)}`);
|
|
5335
|
+
console.log(` ${pc.dim("ID:")} ${bot._id}`);
|
|
5336
|
+
console.log(` ${pc.dim("Discord App:")} ${bot.discordAppId}`);
|
|
5337
|
+
console.log(` ${pc.dim("Status:")} ${statusColor(bot.status)}`);
|
|
5338
|
+
console.log(` ${pc.dim("Convex:")} ${bot.convexUrl}`);
|
|
5325
5339
|
console.log("");
|
|
5326
5340
|
}
|
|
5327
5341
|
}
|
|
@@ -5339,17 +5353,24 @@ async function botRemove(botId, options) {
|
|
|
5339
5353
|
console.log("Not logged in. Run `calabasas login` first.");
|
|
5340
5354
|
return;
|
|
5341
5355
|
}
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5356
|
+
p8.intro("calabasas bot remove");
|
|
5357
|
+
const confirmed = await p8.confirm({
|
|
5358
|
+
message: `Are you sure you want to remove bot ${botId}?`
|
|
5359
|
+
});
|
|
5360
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
5361
|
+
p8.cancel("Cancelled.");
|
|
5345
5362
|
return;
|
|
5346
5363
|
}
|
|
5364
|
+
const s = p8.spinner();
|
|
5365
|
+
s.start("Removing bot...");
|
|
5347
5366
|
const { ok, data, status } = await apiRequest("DELETE", `/api/cli/bots?id=${botId}`, undefined, env);
|
|
5348
5367
|
if (!ok) {
|
|
5349
|
-
|
|
5368
|
+
s.stop("Failed to remove bot");
|
|
5369
|
+
p8.cancel(`${data.error || `HTTP ${status}`}`);
|
|
5350
5370
|
return;
|
|
5351
5371
|
}
|
|
5352
|
-
|
|
5372
|
+
s.stop("Done");
|
|
5373
|
+
p8.outro("Bot removed successfully!");
|
|
5353
5374
|
}
|
|
5354
5375
|
async function botEdit(botId, options) {
|
|
5355
5376
|
if (options.dev && options.prod) {
|
|
@@ -5366,38 +5387,469 @@ async function botEdit(botId, options) {
|
|
|
5366
5387
|
return;
|
|
5367
5388
|
}
|
|
5368
5389
|
const updates = { botId };
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5390
|
+
const hasFlags = options.name || options.url || options.token || options.intents;
|
|
5391
|
+
if (!hasFlags) {
|
|
5392
|
+
p8.intro("calabasas bot edit");
|
|
5393
|
+
const field = await p8.select({
|
|
5394
|
+
message: "Which field do you want to edit?",
|
|
5395
|
+
options: [
|
|
5396
|
+
{ value: "name", label: "Bot name" },
|
|
5397
|
+
{ value: "url", label: "Convex URL" },
|
|
5398
|
+
{ value: "token", label: "Bot token" },
|
|
5399
|
+
{ value: "intents", label: "Intents value" }
|
|
5400
|
+
]
|
|
5401
|
+
});
|
|
5402
|
+
if (p8.isCancel(field)) {
|
|
5403
|
+
p8.cancel("Cancelled.");
|
|
5404
|
+
return;
|
|
5405
|
+
}
|
|
5406
|
+
if (field === "name") {
|
|
5407
|
+
const value = await p8.text({ message: "New bot name" });
|
|
5408
|
+
if (p8.isCancel(value)) {
|
|
5409
|
+
p8.cancel("Cancelled.");
|
|
5410
|
+
return;
|
|
5411
|
+
}
|
|
5412
|
+
updates.name = value;
|
|
5413
|
+
} else if (field === "url") {
|
|
5414
|
+
const value = await p8.text({ message: "New Convex URL" });
|
|
5415
|
+
if (p8.isCancel(value)) {
|
|
5416
|
+
p8.cancel("Cancelled.");
|
|
5417
|
+
return;
|
|
5418
|
+
}
|
|
5419
|
+
updates.convexUrl = value;
|
|
5420
|
+
} else if (field === "token") {
|
|
5421
|
+
const value = await p8.password({ message: "New bot token" });
|
|
5422
|
+
if (p8.isCancel(value)) {
|
|
5423
|
+
p8.cancel("Cancelled.");
|
|
5424
|
+
return;
|
|
5425
|
+
}
|
|
5426
|
+
const encryptionKey = "calabasas-dev-key-change-in-production";
|
|
5427
|
+
updates.encryptedToken = encrypt(value, encryptionKey);
|
|
5428
|
+
} else if (field === "intents") {
|
|
5429
|
+
const value = await p8.text({ message: "New intents value (number)" });
|
|
5430
|
+
if (p8.isCancel(value)) {
|
|
5431
|
+
p8.cancel("Cancelled.");
|
|
5432
|
+
return;
|
|
5433
|
+
}
|
|
5434
|
+
updates.intents = parseInt(value, 10);
|
|
5435
|
+
}
|
|
5436
|
+
} else {
|
|
5437
|
+
if (options.name)
|
|
5438
|
+
updates.name = options.name;
|
|
5439
|
+
if (options.url)
|
|
5440
|
+
updates.convexUrl = options.url;
|
|
5441
|
+
if (options.token) {
|
|
5442
|
+
const encryptionKey = "calabasas-dev-key-change-in-production";
|
|
5443
|
+
updates.encryptedToken = encrypt(options.token, encryptionKey);
|
|
5444
|
+
}
|
|
5445
|
+
if (options.intents)
|
|
5446
|
+
updates.intents = parseInt(options.intents, 10);
|
|
5381
5447
|
}
|
|
5382
5448
|
if (Object.keys(updates).length === 1) {
|
|
5383
|
-
console.log("No updates specified.
|
|
5384
|
-
console.log("");
|
|
5385
|
-
console.log("Examples:");
|
|
5386
|
-
console.log(' calabasas bot edit <id> --name "New Name"');
|
|
5387
|
-
console.log(" calabasas bot edit <id> --url https://new-project.convex.cloud");
|
|
5388
|
-
console.log(" calabasas bot edit <id> --token <new-token>");
|
|
5389
|
-
console.log(" calabasas bot edit <id> --intents 513");
|
|
5449
|
+
console.log("No updates specified.");
|
|
5390
5450
|
return;
|
|
5391
5451
|
}
|
|
5392
|
-
|
|
5452
|
+
const s = p8.spinner();
|
|
5453
|
+
s.start("Updating bot...");
|
|
5393
5454
|
const { ok, data, status } = await apiRequest("PATCH", "/api/cli/bots", updates, env);
|
|
5394
5455
|
if (!ok) {
|
|
5395
|
-
|
|
5456
|
+
s.stop("Failed to update bot");
|
|
5457
|
+
p8.cancel(`${data.error || `HTTP ${status}`}`);
|
|
5396
5458
|
return;
|
|
5397
5459
|
}
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5460
|
+
s.stop("Done");
|
|
5461
|
+
p8.note("The Gateway will automatically reconnect with the new settings.", "Note");
|
|
5462
|
+
p8.outro("Bot updated successfully!");
|
|
5463
|
+
}
|
|
5464
|
+
|
|
5465
|
+
// src/commands/dashboard.tsx
|
|
5466
|
+
import { render } from "ink";
|
|
5467
|
+
init_convex();
|
|
5468
|
+
|
|
5469
|
+
// src/components/Dashboard.tsx
|
|
5470
|
+
import { useState, useCallback } from "react";
|
|
5471
|
+
import { Box as Box5, useInput as useInput2, useApp } from "ink";
|
|
5472
|
+
import { useQuery as useQuery4 } from "convex/react";
|
|
5473
|
+
|
|
5474
|
+
// src/components/Header.tsx
|
|
5475
|
+
import { Text as Text3, Box as Box2 } from "ink";
|
|
5476
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
5477
|
+
function Header({
|
|
5478
|
+
botCount,
|
|
5479
|
+
env
|
|
5480
|
+
}) {
|
|
5481
|
+
return /* @__PURE__ */ jsxDEV4(Box2, {
|
|
5482
|
+
marginBottom: 1,
|
|
5483
|
+
children: [
|
|
5484
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5485
|
+
bold: true,
|
|
5486
|
+
color: "magenta",
|
|
5487
|
+
children: "calabasas"
|
|
5488
|
+
}, undefined, false, undefined, this),
|
|
5489
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5490
|
+
dimColor: true,
|
|
5491
|
+
children: " v0.1.12"
|
|
5492
|
+
}, undefined, false, undefined, this),
|
|
5493
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5494
|
+
dimColor: true,
|
|
5495
|
+
children: " · "
|
|
5496
|
+
}, undefined, false, undefined, this),
|
|
5497
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5498
|
+
children: [
|
|
5499
|
+
botCount,
|
|
5500
|
+
" bot",
|
|
5501
|
+
botCount !== 1 ? "s" : ""
|
|
5502
|
+
]
|
|
5503
|
+
}, undefined, true, undefined, this),
|
|
5504
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5505
|
+
dimColor: true,
|
|
5506
|
+
children: " · "
|
|
5507
|
+
}, undefined, false, undefined, this),
|
|
5508
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5509
|
+
color: env === "dev" ? "yellow" : "green",
|
|
5510
|
+
children: env
|
|
5511
|
+
}, undefined, false, undefined, this),
|
|
5512
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5513
|
+
dimColor: true,
|
|
5514
|
+
children: " · Press "
|
|
5515
|
+
}, undefined, false, undefined, this),
|
|
5516
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5517
|
+
bold: true,
|
|
5518
|
+
children: "q"
|
|
5519
|
+
}, undefined, false, undefined, this),
|
|
5520
|
+
/* @__PURE__ */ jsxDEV4(Text3, {
|
|
5521
|
+
dimColor: true,
|
|
5522
|
+
children: " to quit"
|
|
5523
|
+
}, undefined, false, undefined, this)
|
|
5524
|
+
]
|
|
5525
|
+
}, undefined, true, undefined, this);
|
|
5526
|
+
}
|
|
5527
|
+
|
|
5528
|
+
// src/components/Dashboard.tsx
|
|
5529
|
+
init_BotList();
|
|
5530
|
+
|
|
5531
|
+
// src/components/StatsPanel.tsx
|
|
5532
|
+
init_convex();
|
|
5533
|
+
import { Text as Text4, Box as Box3 } from "ink";
|
|
5534
|
+
import Spinner2 from "ink-spinner";
|
|
5535
|
+
import { useQuery as useQuery2 } from "convex/react";
|
|
5536
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
5537
|
+
var ONE_HOUR = 60 * 60 * 1000;
|
|
5538
|
+
function StatsPanel({
|
|
5539
|
+
apiKey,
|
|
5540
|
+
botId
|
|
5541
|
+
}) {
|
|
5542
|
+
const stats = useQuery2(cliApi.botStats, {
|
|
5543
|
+
apiKey,
|
|
5544
|
+
botId,
|
|
5545
|
+
since: Date.now() - ONE_HOUR
|
|
5546
|
+
});
|
|
5547
|
+
if (stats === undefined) {
|
|
5548
|
+
return /* @__PURE__ */ jsxDEV5(Box3, {
|
|
5549
|
+
children: [
|
|
5550
|
+
/* @__PURE__ */ jsxDEV5(Text4, {
|
|
5551
|
+
color: "cyan",
|
|
5552
|
+
children: /* @__PURE__ */ jsxDEV5(Spinner2, {
|
|
5553
|
+
type: "dots"
|
|
5554
|
+
}, undefined, false, undefined, this)
|
|
5555
|
+
}, undefined, false, undefined, this),
|
|
5556
|
+
/* @__PURE__ */ jsxDEV5(Text4, {
|
|
5557
|
+
children: " Loading stats..."
|
|
5558
|
+
}, undefined, false, undefined, this)
|
|
5559
|
+
]
|
|
5560
|
+
}, undefined, true, undefined, this);
|
|
5561
|
+
}
|
|
5562
|
+
return /* @__PURE__ */ jsxDEV5(Box3, {
|
|
5563
|
+
gap: 2,
|
|
5564
|
+
marginBottom: 1,
|
|
5565
|
+
children: [
|
|
5566
|
+
/* @__PURE__ */ jsxDEV5(Text4, {
|
|
5567
|
+
dimColor: true,
|
|
5568
|
+
children: "Events (1h):"
|
|
5569
|
+
}, undefined, false, undefined, this),
|
|
5570
|
+
/* @__PURE__ */ jsxDEV5(Text4, {
|
|
5571
|
+
bold: true,
|
|
5572
|
+
children: formatNumber(stats.total)
|
|
5573
|
+
}, undefined, false, undefined, this),
|
|
5574
|
+
/* @__PURE__ */ jsxDEV5(Text4, {
|
|
5575
|
+
color: "green",
|
|
5576
|
+
children: [
|
|
5577
|
+
formatNumber(stats.success),
|
|
5578
|
+
" ok"
|
|
5579
|
+
]
|
|
5580
|
+
}, undefined, true, undefined, this),
|
|
5581
|
+
/* @__PURE__ */ jsxDEV5(Text4, {
|
|
5582
|
+
color: "red",
|
|
5583
|
+
children: [
|
|
5584
|
+
formatNumber(stats.failed),
|
|
5585
|
+
" failed"
|
|
5586
|
+
]
|
|
5587
|
+
}, undefined, true, undefined, this),
|
|
5588
|
+
/* @__PURE__ */ jsxDEV5(Text4, {
|
|
5589
|
+
dimColor: true,
|
|
5590
|
+
children: [
|
|
5591
|
+
"avg ",
|
|
5592
|
+
formatLatency(stats.avgLatencyMs)
|
|
5593
|
+
]
|
|
5594
|
+
}, undefined, true, undefined, this)
|
|
5595
|
+
]
|
|
5596
|
+
}, undefined, true, undefined, this);
|
|
5597
|
+
}
|
|
5598
|
+
|
|
5599
|
+
// src/components/LogViewer.tsx
|
|
5600
|
+
init_convex();
|
|
5601
|
+
import { Text as Text5, Box as Box4, useInput } from "ink";
|
|
5602
|
+
import Spinner3 from "ink-spinner";
|
|
5603
|
+
import { useQuery as useQuery3 } from "convex/react";
|
|
5604
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
5605
|
+
function formatTimestamp(ts) {
|
|
5606
|
+
const d = new Date(ts);
|
|
5607
|
+
return d.toLocaleTimeString("en-US", { hour12: false });
|
|
5608
|
+
}
|
|
5609
|
+
function LogViewer({
|
|
5610
|
+
apiKey,
|
|
5611
|
+
botId,
|
|
5612
|
+
limit = 50,
|
|
5613
|
+
onQuit
|
|
5614
|
+
}) {
|
|
5615
|
+
const logs = useQuery3(cliApi.recentLogs, {
|
|
5616
|
+
apiKey,
|
|
5617
|
+
botId,
|
|
5618
|
+
limit
|
|
5619
|
+
});
|
|
5620
|
+
useInput((input) => {
|
|
5621
|
+
if (input === "q" && onQuit) {
|
|
5622
|
+
onQuit();
|
|
5623
|
+
}
|
|
5624
|
+
});
|
|
5625
|
+
if (logs === undefined) {
|
|
5626
|
+
return /* @__PURE__ */ jsxDEV6(Box4, {
|
|
5627
|
+
children: [
|
|
5628
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5629
|
+
color: "cyan",
|
|
5630
|
+
children: /* @__PURE__ */ jsxDEV6(Spinner3, {
|
|
5631
|
+
type: "dots"
|
|
5632
|
+
}, undefined, false, undefined, this)
|
|
5633
|
+
}, undefined, false, undefined, this),
|
|
5634
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5635
|
+
children: " Loading logs..."
|
|
5636
|
+
}, undefined, false, undefined, this)
|
|
5637
|
+
]
|
|
5638
|
+
}, undefined, true, undefined, this);
|
|
5639
|
+
}
|
|
5640
|
+
if (logs.length === 0) {
|
|
5641
|
+
return /* @__PURE__ */ jsxDEV6(Box4, {
|
|
5642
|
+
children: /* @__PURE__ */ jsxDEV6(Text5, {
|
|
5643
|
+
dimColor: true,
|
|
5644
|
+
children: "No events yet."
|
|
5645
|
+
}, undefined, false, undefined, this)
|
|
5646
|
+
}, undefined, false, undefined, this);
|
|
5647
|
+
}
|
|
5648
|
+
return /* @__PURE__ */ jsxDEV6(Box4, {
|
|
5649
|
+
flexDirection: "column",
|
|
5650
|
+
children: [
|
|
5651
|
+
/* @__PURE__ */ jsxDEV6(Box4, {
|
|
5652
|
+
marginBottom: 1,
|
|
5653
|
+
children: [
|
|
5654
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5655
|
+
bold: true,
|
|
5656
|
+
children: "Event Log"
|
|
5657
|
+
}, undefined, false, undefined, this),
|
|
5658
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5659
|
+
dimColor: true,
|
|
5660
|
+
children: [
|
|
5661
|
+
" (live · ",
|
|
5662
|
+
logs.length,
|
|
5663
|
+
" events)"
|
|
5664
|
+
]
|
|
5665
|
+
}, undefined, true, undefined, this)
|
|
5666
|
+
]
|
|
5667
|
+
}, undefined, true, undefined, this),
|
|
5668
|
+
logs.map((log3) => /* @__PURE__ */ jsxDEV6(Box4, {
|
|
5669
|
+
gap: 1,
|
|
5670
|
+
children: [
|
|
5671
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5672
|
+
dimColor: true,
|
|
5673
|
+
children: formatTimestamp(log3.timestamp)
|
|
5674
|
+
}, undefined, false, undefined, this),
|
|
5675
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5676
|
+
color: log3.success ? "green" : "red",
|
|
5677
|
+
children: log3.success ? "✓" : "✗"
|
|
5678
|
+
}, undefined, false, undefined, this),
|
|
5679
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5680
|
+
bold: true,
|
|
5681
|
+
children: log3.eventType
|
|
5682
|
+
}, undefined, false, undefined, this),
|
|
5683
|
+
/* @__PURE__ */ jsxDEV6(Text5, {
|
|
5684
|
+
dimColor: true,
|
|
5685
|
+
children: formatLatency(log3.latencyMs)
|
|
5686
|
+
}, undefined, false, undefined, this),
|
|
5687
|
+
log3.error && /* @__PURE__ */ jsxDEV6(Text5, {
|
|
5688
|
+
color: "red",
|
|
5689
|
+
children: log3.error
|
|
5690
|
+
}, undefined, false, undefined, this)
|
|
5691
|
+
]
|
|
5692
|
+
}, log3._id, true, undefined, this))
|
|
5693
|
+
]
|
|
5694
|
+
}, undefined, true, undefined, this);
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
// src/components/Dashboard.tsx
|
|
5698
|
+
init_convex();
|
|
5699
|
+
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
5700
|
+
function Dashboard({
|
|
5701
|
+
apiKey,
|
|
5702
|
+
env
|
|
5703
|
+
}) {
|
|
5704
|
+
const { exit } = useApp();
|
|
5705
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
5706
|
+
const bots = useQuery4(cliApi.listBots, { apiKey });
|
|
5707
|
+
const botCount = bots?.length ?? 0;
|
|
5708
|
+
const selectedBot = bots?.[selectedIndex];
|
|
5709
|
+
useInput2(useCallback((input, key) => {
|
|
5710
|
+
if (input === "q") {
|
|
5711
|
+
exit();
|
|
5712
|
+
return;
|
|
5713
|
+
}
|
|
5714
|
+
if ((input === "j" || key.downArrow) && botCount > 0) {
|
|
5715
|
+
setSelectedIndex((i) => Math.min(i, botCount - 1) === botCount - 1 ? 0 : i + 1);
|
|
5716
|
+
}
|
|
5717
|
+
if ((input === "k" || key.upArrow) && botCount > 0) {
|
|
5718
|
+
setSelectedIndex((i) => i === 0 ? botCount - 1 : i - 1);
|
|
5719
|
+
}
|
|
5720
|
+
}, [botCount, exit]));
|
|
5721
|
+
return /* @__PURE__ */ jsxDEV7(Box5, {
|
|
5722
|
+
flexDirection: "column",
|
|
5723
|
+
children: [
|
|
5724
|
+
/* @__PURE__ */ jsxDEV7(Header, {
|
|
5725
|
+
botCount,
|
|
5726
|
+
env
|
|
5727
|
+
}, undefined, false, undefined, this),
|
|
5728
|
+
/* @__PURE__ */ jsxDEV7(BotList, {
|
|
5729
|
+
apiKey,
|
|
5730
|
+
selectedIndex
|
|
5731
|
+
}, undefined, false, undefined, this),
|
|
5732
|
+
selectedBot && /* @__PURE__ */ jsxDEV7(Box5, {
|
|
5733
|
+
flexDirection: "column",
|
|
5734
|
+
marginTop: 1,
|
|
5735
|
+
children: [
|
|
5736
|
+
/* @__PURE__ */ jsxDEV7(StatsPanel, {
|
|
5737
|
+
apiKey,
|
|
5738
|
+
botId: selectedBot._id
|
|
5739
|
+
}, undefined, false, undefined, this),
|
|
5740
|
+
/* @__PURE__ */ jsxDEV7(LogViewer, {
|
|
5741
|
+
apiKey,
|
|
5742
|
+
botId: selectedBot._id
|
|
5743
|
+
}, undefined, false, undefined, this)
|
|
5744
|
+
]
|
|
5745
|
+
}, undefined, true, undefined, this)
|
|
5746
|
+
]
|
|
5747
|
+
}, undefined, true, undefined, this);
|
|
5748
|
+
}
|
|
5749
|
+
|
|
5750
|
+
// src/commands/dashboard.tsx
|
|
5751
|
+
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
5752
|
+
async function dashboard(options) {
|
|
5753
|
+
if (options.dev && options.prod) {
|
|
5754
|
+
console.error("Error: Cannot use both --dev and --prod flags.");
|
|
5755
|
+
process.exit(1);
|
|
5756
|
+
}
|
|
5757
|
+
if (!options.dev && !options.prod) {
|
|
5758
|
+
console.error("Error: You must specify either --dev or --prod.");
|
|
5759
|
+
process.exit(1);
|
|
5760
|
+
}
|
|
5761
|
+
const env = options.dev ? "dev" : "prod";
|
|
5762
|
+
if (!isAuthenticated(env)) {
|
|
5763
|
+
console.log("Not logged in. Run `calabasas login` first.");
|
|
5764
|
+
return;
|
|
5765
|
+
}
|
|
5766
|
+
const config = getConfig(env);
|
|
5767
|
+
const apiKey = config.apiKey;
|
|
5768
|
+
const convexUrl = getConvexUrl(env);
|
|
5769
|
+
const client = createConvexClient(convexUrl);
|
|
5770
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsxDEV8(ConvexProvider, {
|
|
5771
|
+
client,
|
|
5772
|
+
children: /* @__PURE__ */ jsxDEV8(Dashboard, {
|
|
5773
|
+
apiKey,
|
|
5774
|
+
env
|
|
5775
|
+
}, undefined, false, undefined, this)
|
|
5776
|
+
}, undefined, false, undefined, this));
|
|
5777
|
+
await waitUntilExit();
|
|
5778
|
+
await client.close();
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5781
|
+
// src/commands/logs.tsx
|
|
5782
|
+
import { render as render2 } from "ink";
|
|
5783
|
+
import * as p9 from "@clack/prompts";
|
|
5784
|
+
init_convex();
|
|
5785
|
+
import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
|
|
5786
|
+
async function fetchBotList(apiKey, env) {
|
|
5787
|
+
const apiUrl = getApiUrlForEnv(env);
|
|
5788
|
+
const response = await fetch(`${apiUrl}/api/cli/bots`, {
|
|
5789
|
+
method: "GET",
|
|
5790
|
+
headers: {
|
|
5791
|
+
"Content-Type": "application/json",
|
|
5792
|
+
Authorization: `Bearer ${apiKey}`
|
|
5793
|
+
}
|
|
5794
|
+
});
|
|
5795
|
+
if (!response.ok)
|
|
5796
|
+
return [];
|
|
5797
|
+
const { bots } = await response.json();
|
|
5798
|
+
return bots;
|
|
5799
|
+
}
|
|
5800
|
+
async function logs(botId, options) {
|
|
5801
|
+
if (options.dev && options.prod) {
|
|
5802
|
+
console.error("Error: Cannot use both --dev and --prod flags.");
|
|
5803
|
+
process.exit(1);
|
|
5804
|
+
}
|
|
5805
|
+
if (!options.dev && !options.prod) {
|
|
5806
|
+
console.error("Error: You must specify either --dev or --prod.");
|
|
5807
|
+
process.exit(1);
|
|
5808
|
+
}
|
|
5809
|
+
const env = options.dev ? "dev" : "prod";
|
|
5810
|
+
if (!isAuthenticated(env)) {
|
|
5811
|
+
console.log("Not logged in. Run `calabasas login` first.");
|
|
5812
|
+
return;
|
|
5813
|
+
}
|
|
5814
|
+
const config = getConfig(env);
|
|
5815
|
+
const apiKey = config.apiKey;
|
|
5816
|
+
if (!botId) {
|
|
5817
|
+
const bots = await fetchBotList(apiKey, env);
|
|
5818
|
+
if (bots.length === 0) {
|
|
5819
|
+
console.log("No bots found. Run `calabasas bot add` to create one.");
|
|
5820
|
+
return;
|
|
5821
|
+
}
|
|
5822
|
+
if (bots.length === 1) {
|
|
5823
|
+
botId = bots[0]._id;
|
|
5824
|
+
} else {
|
|
5825
|
+
const selected = await p9.select({
|
|
5826
|
+
message: "Select a bot to view logs",
|
|
5827
|
+
options: bots.map((bot) => ({
|
|
5828
|
+
value: bot._id,
|
|
5829
|
+
label: bot.name
|
|
5830
|
+
}))
|
|
5831
|
+
});
|
|
5832
|
+
if (p9.isCancel(selected)) {
|
|
5833
|
+
p9.cancel("Cancelled.");
|
|
5834
|
+
return;
|
|
5835
|
+
}
|
|
5836
|
+
botId = selected;
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
const limit = options.limit ? parseInt(options.limit, 10) : 50;
|
|
5840
|
+
const convexUrl = getConvexUrl(env);
|
|
5841
|
+
const client = createConvexClient(convexUrl);
|
|
5842
|
+
const { waitUntilExit } = render2(/* @__PURE__ */ jsxDEV9(ConvexProvider, {
|
|
5843
|
+
client,
|
|
5844
|
+
children: /* @__PURE__ */ jsxDEV9(LogViewer, {
|
|
5845
|
+
apiKey,
|
|
5846
|
+
botId,
|
|
5847
|
+
limit,
|
|
5848
|
+
onQuit: () => process.exit(0)
|
|
5849
|
+
}, undefined, false, undefined, this)
|
|
5850
|
+
}, undefined, false, undefined, this));
|
|
5851
|
+
await waitUntilExit();
|
|
5852
|
+
await client.close();
|
|
5401
5853
|
}
|
|
5402
5854
|
|
|
5403
5855
|
// src/index.ts
|
|
@@ -5408,11 +5860,13 @@ program2.command("logout").description("Clear stored credentials").option("--dev
|
|
|
5408
5860
|
program2.command("init").description("Initialize Calabasas config in your Convex project").action(init);
|
|
5409
5861
|
program2.command("push").description("Push your calabasas.config.ts to Calabasas").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);
|
|
5410
5862
|
program2.command("generate").description("Generate discord.generated.ts with type-safe handlers").option("-o, --output <path>", "Output path", "convex/discord.generated.ts").action(generate);
|
|
5411
|
-
program2.command("skill").description("Generate Calabasas documentation for AI assistants").action(skill);
|
|
5863
|
+
program2.command("skill").description("Generate Calabasas documentation for AI assistants").option("--dev", "Use development environment").option("--prod", "Use production environment").action(skill);
|
|
5412
5864
|
program2.command("add [components...]").description("Add Discord UI components to your project").action(add);
|
|
5865
|
+
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);
|
|
5866
|
+
program2.command("logs [botId]").description("Live event log viewer for a bot").option("-n, --limit <number>", "Number of log entries to show", "50").option("--dev", "Use development environment").option("--prod", "Use production environment").action(logs);
|
|
5413
5867
|
var bot = program2.command("bot").description("Manage Discord bots");
|
|
5414
5868
|
bot.command("add").description("Add a new Discord bot").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botAdd);
|
|
5415
|
-
bot.command("list").alias("ls").description("List your Discord bots").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botList);
|
|
5869
|
+
bot.command("list").alias("ls").description("List your Discord bots").option("-w, --watch", "Watch mode with live updates").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botList);
|
|
5416
5870
|
bot.command("remove <botId>").alias("rm").description("Remove a Discord bot").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botRemove);
|
|
5417
5871
|
bot.command("edit <botId>").description("Edit a Discord bot").option("-n, --name <name>", "New bot name").option("-u, --url <url>", "New Convex URL").option("-t, --token <token>", "New bot token").option("-i, --intents <intents>", "New intents value").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botEdit);
|
|
5418
5872
|
program2.parse();
|