calabasas 0.1.12 → 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 +958 -513
- 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,29 +4201,38 @@ 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
4237
|
async function fetchSkillContent(env) {
|
|
4020
4238
|
const apiUrl = getApiUrlForEnv(env);
|
|
@@ -4046,145 +4264,6 @@ function detectExistingFiles(cwd) {
|
|
|
4046
4264
|
function hasCalabsasSection(content) {
|
|
4047
4265
|
return content.includes(SECTION_HEADER) || content.includes("## Calabasas Guidelines");
|
|
4048
4266
|
}
|
|
4049
|
-
async function prompt(question) {
|
|
4050
|
-
const rl = readline.createInterface({
|
|
4051
|
-
input: process.stdin,
|
|
4052
|
-
output: process.stdout
|
|
4053
|
-
});
|
|
4054
|
-
return new Promise((resolve5) => {
|
|
4055
|
-
rl.question(question, (answer) => {
|
|
4056
|
-
rl.close();
|
|
4057
|
-
resolve5(answer.trim());
|
|
4058
|
-
});
|
|
4059
|
-
});
|
|
4060
|
-
}
|
|
4061
|
-
async function selectFile(files) {
|
|
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
4267
|
async function skill(options) {
|
|
4189
4268
|
if (options.dev && options.prod) {
|
|
4190
4269
|
console.error("Error: Cannot use both --dev and --prod flags.");
|
|
@@ -4195,21 +4274,32 @@ async function skill(options) {
|
|
|
4195
4274
|
process.exit(1);
|
|
4196
4275
|
}
|
|
4197
4276
|
const env = options.dev ? "dev" : "prod";
|
|
4198
|
-
|
|
4277
|
+
p6.intro("calabasas skill");
|
|
4278
|
+
const s = p6.spinner();
|
|
4279
|
+
s.start("Fetching latest Calabasas guidelines...");
|
|
4199
4280
|
let skillContent;
|
|
4200
4281
|
try {
|
|
4201
4282
|
skillContent = await fetchSkillContent(env);
|
|
4202
4283
|
} catch (error) {
|
|
4203
|
-
|
|
4284
|
+
s.stop("Failed");
|
|
4285
|
+
p6.cancel(error instanceof Error ? error.message : "Failed to fetch skill content");
|
|
4204
4286
|
process.exit(1);
|
|
4205
4287
|
}
|
|
4288
|
+
s.stop("Guidelines fetched");
|
|
4206
4289
|
const cwd = process.cwd();
|
|
4207
4290
|
const detectedFiles = detectExistingFiles(cwd);
|
|
4208
4291
|
if (detectedFiles.length === 0) {
|
|
4209
|
-
const createPath = await
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
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;
|
|
4213
4303
|
}
|
|
4214
4304
|
const fullPath = path6.resolve(cwd, createPath);
|
|
4215
4305
|
const dir = path6.dirname(fullPath);
|
|
@@ -4221,17 +4311,34 @@ async function skill(options) {
|
|
|
4221
4311
|
|
|
4222
4312
|
${skillContent}`;
|
|
4223
4313
|
fs10.writeFileSync(fullPath, initialContent);
|
|
4224
|
-
|
|
4314
|
+
p6.outro(`Created ${createPath} with Calabasas guidelines`);
|
|
4225
4315
|
return;
|
|
4226
4316
|
}
|
|
4227
|
-
|
|
4228
|
-
|
|
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
|
+
}
|
|
4229
4334
|
const existingContent = fs10.readFileSync(selectedFile.fullPath, "utf8");
|
|
4230
4335
|
if (hasCalabsasSection(existingContent)) {
|
|
4231
|
-
const update = await
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
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;
|
|
4235
4342
|
}
|
|
4236
4343
|
const sectionRegex = /## Calabasas Guidelines[\s\S]*?(?=\n## |\n# |$)/;
|
|
4237
4344
|
const contentWithoutSection = existingContent.replace(sectionRegex, "").trimEnd();
|
|
@@ -4239,7 +4346,7 @@ ${skillContent}`;
|
|
|
4239
4346
|
|
|
4240
4347
|
${skillContent}`;
|
|
4241
4348
|
fs10.writeFileSync(selectedFile.fullPath, newContent);
|
|
4242
|
-
|
|
4349
|
+
p6.outro(`Updated Calabasas guidelines in ${selectedFile.path}`);
|
|
4243
4350
|
} else {
|
|
4244
4351
|
const separator = existingContent.endsWith(`
|
|
4245
4352
|
`) ? `
|
|
@@ -4248,13 +4355,14 @@ ${skillContent}`;
|
|
|
4248
4355
|
`;
|
|
4249
4356
|
const newContent = `${existingContent}${separator}${skillContent}`;
|
|
4250
4357
|
fs10.writeFileSync(selectedFile.fullPath, newContent);
|
|
4251
|
-
|
|
4358
|
+
p6.outro(`Added Calabasas guidelines to ${selectedFile.path}`);
|
|
4252
4359
|
}
|
|
4253
4360
|
}
|
|
4254
4361
|
|
|
4255
4362
|
// src/commands/add.ts
|
|
4256
4363
|
import * as fs11 from "fs";
|
|
4257
4364
|
import * as path7 from "path";
|
|
4365
|
+
import * as p7 from "@clack/prompts";
|
|
4258
4366
|
|
|
4259
4367
|
// src/lib/registry/components/channel-select.ts
|
|
4260
4368
|
var channelSelect = {
|
|
@@ -4876,73 +4984,6 @@ function parseEnabledSyncTypes(configPath) {
|
|
|
4876
4984
|
}
|
|
4877
4985
|
return enabled;
|
|
4878
4986
|
}
|
|
4879
|
-
async function selectComponents() {
|
|
4880
|
-
const selected = new Set;
|
|
4881
|
-
let cursor = 0;
|
|
4882
|
-
return new Promise((resolve6) => {
|
|
4883
|
-
const render = () => {
|
|
4884
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
4885
|
-
console.log(`Select components to add (arrow keys to navigate, space to toggle, enter to confirm)
|
|
4886
|
-
`);
|
|
4887
|
-
REGISTRY.forEach((comp, i) => {
|
|
4888
|
-
const isSelected = selected.has(i);
|
|
4889
|
-
const isCursor = cursor === i;
|
|
4890
|
-
const checkbox = isSelected ? "[✓]" : "[ ]";
|
|
4891
|
-
const pointer = isCursor ? "→" : " ";
|
|
4892
|
-
console.log(`${pointer} ${checkbox} ${comp.name}`);
|
|
4893
|
-
if (isCursor) {
|
|
4894
|
-
console.log(` ${comp.description}`);
|
|
4895
|
-
}
|
|
4896
|
-
});
|
|
4897
|
-
const count = selected.size;
|
|
4898
|
-
console.log(`
|
|
4899
|
-
${count} component${count === 1 ? "" : "s"} selected`);
|
|
4900
|
-
};
|
|
4901
|
-
render();
|
|
4902
|
-
if (process.stdin.isTTY) {
|
|
4903
|
-
process.stdin.setRawMode(true);
|
|
4904
|
-
}
|
|
4905
|
-
process.stdin.resume();
|
|
4906
|
-
process.stdin.setEncoding("utf8");
|
|
4907
|
-
const onKeypress = (key) => {
|
|
4908
|
-
if (key === "\x03") {
|
|
4909
|
-
if (process.stdin.isTTY)
|
|
4910
|
-
process.stdin.setRawMode(false);
|
|
4911
|
-
process.exit(0);
|
|
4912
|
-
}
|
|
4913
|
-
if (key === "\r" || key === `
|
|
4914
|
-
`) {
|
|
4915
|
-
process.stdin.removeListener("data", onKeypress);
|
|
4916
|
-
if (process.stdin.isTTY)
|
|
4917
|
-
process.stdin.setRawMode(false);
|
|
4918
|
-
console.log(`
|
|
4919
|
-
`);
|
|
4920
|
-
resolve6(Array.from(selected).map((i) => REGISTRY[i]));
|
|
4921
|
-
return;
|
|
4922
|
-
}
|
|
4923
|
-
if (key === " ") {
|
|
4924
|
-
if (selected.has(cursor)) {
|
|
4925
|
-
selected.delete(cursor);
|
|
4926
|
-
} else {
|
|
4927
|
-
selected.add(cursor);
|
|
4928
|
-
}
|
|
4929
|
-
render();
|
|
4930
|
-
return;
|
|
4931
|
-
}
|
|
4932
|
-
if (key === "\x1B[A" || key === "k") {
|
|
4933
|
-
cursor = Math.max(0, cursor - 1);
|
|
4934
|
-
render();
|
|
4935
|
-
return;
|
|
4936
|
-
}
|
|
4937
|
-
if (key === "\x1B[B" || key === "j") {
|
|
4938
|
-
cursor = Math.min(REGISTRY.length - 1, cursor + 1);
|
|
4939
|
-
render();
|
|
4940
|
-
return;
|
|
4941
|
-
}
|
|
4942
|
-
};
|
|
4943
|
-
process.stdin.on("data", onKeypress);
|
|
4944
|
-
});
|
|
4945
|
-
}
|
|
4946
4987
|
function existingQueryNames(filePath) {
|
|
4947
4988
|
if (!fs11.existsSync(filePath))
|
|
4948
4989
|
return new Set;
|
|
@@ -4984,7 +5025,20 @@ async function add(componentNames) {
|
|
|
4984
5025
|
}
|
|
4985
5026
|
let components;
|
|
4986
5027
|
if (componentNames.length === 0) {
|
|
4987
|
-
|
|
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);
|
|
4988
5042
|
if (components.length === 0) {
|
|
4989
5043
|
console.log("No components selected.");
|
|
4990
5044
|
return;
|
|
@@ -5007,13 +5061,11 @@ async function add(componentNames) {
|
|
|
5007
5061
|
const existing = existingQueryNames(queriesPath);
|
|
5008
5062
|
const allMissingShadcn = new Set;
|
|
5009
5063
|
for (const comp of components) {
|
|
5010
|
-
|
|
5011
|
-
console.log("");
|
|
5064
|
+
p7.log.step(`Adding ${comp.name}...`);
|
|
5012
5065
|
const missingSyncTypes = comp.requiredSyncTypes.filter((t) => !enabledSync.has(t));
|
|
5013
5066
|
if (missingSyncTypes.length > 0) {
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
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\`.`);
|
|
5017
5069
|
continue;
|
|
5018
5070
|
}
|
|
5019
5071
|
if (!fs11.existsSync(componentsDir)) {
|
|
@@ -5021,7 +5073,7 @@ async function add(componentNames) {
|
|
|
5021
5073
|
}
|
|
5022
5074
|
const componentPath = path7.join(componentsDir, `${comp.name}.tsx`);
|
|
5023
5075
|
fs11.writeFileSync(componentPath, comp.generateReactComponent());
|
|
5024
|
-
|
|
5076
|
+
p7.log.success(`Created components/calabasas/${comp.name}.tsx`);
|
|
5025
5077
|
const queryBlock = comp.generateConvexQueries();
|
|
5026
5078
|
const namesInBlock = queryNamesInBlock(queryBlock);
|
|
5027
5079
|
const newNames = namesInBlock.filter((n) => !existing.has(n));
|
|
@@ -5029,14 +5081,13 @@ async function add(componentNames) {
|
|
|
5029
5081
|
newQueryBlocks.push(queryBlock);
|
|
5030
5082
|
for (const n of newNames)
|
|
5031
5083
|
existing.add(n);
|
|
5032
|
-
|
|
5084
|
+
p7.log.success(`Updated convex/calabasas/queries.ts (added ${newNames.join(", ")})`);
|
|
5033
5085
|
} else {
|
|
5034
|
-
|
|
5086
|
+
p7.log.info(`convex/calabasas/queries.ts already has queries for ${comp.name}`);
|
|
5035
5087
|
}
|
|
5036
5088
|
for (const s of comp.requiredShadcnComponents) {
|
|
5037
5089
|
allMissingShadcn.add(s);
|
|
5038
5090
|
}
|
|
5039
|
-
console.log("");
|
|
5040
5091
|
}
|
|
5041
5092
|
if (newQueryBlocks.length > 0) {
|
|
5042
5093
|
if (fs11.existsSync(queriesPath)) {
|
|
@@ -5067,35 +5118,31 @@ ${newQueryBlocks.join(`
|
|
|
5067
5118
|
}
|
|
5068
5119
|
}
|
|
5069
5120
|
if (missingShadcn.length > 0) {
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5121
|
+
p7.note(`Missing: ${missingShadcn.join(", ")}
|
|
5122
|
+
|
|
5123
|
+
Install with: npx shadcn@latest add ${missingShadcn.join(" ")}`, "Required shadcn components");
|
|
5073
5124
|
}
|
|
5074
5125
|
const firstInstalled = components[0];
|
|
5075
5126
|
if (firstInstalled) {
|
|
5076
5127
|
const pascal = toPascalCase(firstInstalled.name);
|
|
5077
5128
|
const needsGuild = firstInstalled.requiredSyncTypes.some((t) => t === "channels" || t === "roles" || t === "members");
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
} else {
|
|
5090
|
-
console.log(` <${pascal}`);
|
|
5091
|
-
console.log(` onValueChange={(id) => console.log(id)}`);
|
|
5092
|
-
console.log(` />`);
|
|
5093
|
-
}
|
|
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");
|
|
5094
5140
|
}
|
|
5095
5141
|
}
|
|
5096
5142
|
|
|
5097
5143
|
// src/commands/bot.ts
|
|
5098
|
-
import * as
|
|
5144
|
+
import * as p8 from "@clack/prompts";
|
|
5145
|
+
import pc from "picocolors";
|
|
5099
5146
|
var INTENTS = [
|
|
5100
5147
|
{ name: "Guilds", value: 1 << 0, description: "Guild create/update/delete, roles, channels" },
|
|
5101
5148
|
{ name: "Guild Members", value: 1 << 1, description: "Member add/remove/update (Privileged)", privileged: true },
|
|
@@ -5117,9 +5164,9 @@ var INTENTS = [
|
|
|
5117
5164
|
{ name: "Auto Moderation Config", value: 1 << 20, description: "Auto mod rule changes" },
|
|
5118
5165
|
{ name: "Auto Moderation Execution", value: 1 << 21, description: "Auto mod action execution" }
|
|
5119
5166
|
];
|
|
5120
|
-
function encrypt(
|
|
5167
|
+
function encrypt(text2, key) {
|
|
5121
5168
|
const keyBytes = new TextEncoder().encode(key);
|
|
5122
|
-
const textBytes = new TextEncoder().encode(
|
|
5169
|
+
const textBytes = new TextEncoder().encode(text2);
|
|
5123
5170
|
const encrypted = new Uint8Array(textBytes.length);
|
|
5124
5171
|
for (let i = 0;i < textBytes.length; i++) {
|
|
5125
5172
|
encrypted[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
@@ -5134,91 +5181,6 @@ function generateSecret() {
|
|
|
5134
5181
|
}
|
|
5135
5182
|
return result;
|
|
5136
5183
|
}
|
|
5137
|
-
function prompt2(question) {
|
|
5138
|
-
const rl = readline2.createInterface({
|
|
5139
|
-
input: process.stdin,
|
|
5140
|
-
output: process.stdout
|
|
5141
|
-
});
|
|
5142
|
-
return new Promise((resolve6) => {
|
|
5143
|
-
rl.question(question, (answer) => {
|
|
5144
|
-
rl.close();
|
|
5145
|
-
resolve6(answer);
|
|
5146
|
-
});
|
|
5147
|
-
});
|
|
5148
|
-
}
|
|
5149
|
-
async function selectIntents() {
|
|
5150
|
-
const selected = new Set([0, 9]);
|
|
5151
|
-
let cursor = 0;
|
|
5152
|
-
return new Promise((resolve6) => {
|
|
5153
|
-
const render = () => {
|
|
5154
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
5155
|
-
console.log(`Select Gateway Intents (use arrow keys, space to toggle, enter to confirm)
|
|
5156
|
-
`);
|
|
5157
|
-
console.log(` ⚠️ Privileged intents require approval in Discord Developer Portal
|
|
5158
|
-
`);
|
|
5159
|
-
INTENTS.forEach((intent, i) => {
|
|
5160
|
-
const isSelected = selected.has(i);
|
|
5161
|
-
const isCursor = cursor === i;
|
|
5162
|
-
const checkbox = isSelected ? "[✓]" : "[ ]";
|
|
5163
|
-
const pointer = isCursor ? "→" : " ";
|
|
5164
|
-
const privilegedTag = intent.privileged ? " (Privileged)" : "";
|
|
5165
|
-
const color = intent.privileged ? "\x1B[33m" : "";
|
|
5166
|
-
const reset = "\x1B[0m";
|
|
5167
|
-
console.log(`${pointer} ${checkbox} ${color}${intent.name}${privilegedTag}${reset}`);
|
|
5168
|
-
if (isCursor) {
|
|
5169
|
-
console.log(` ${intent.description}`);
|
|
5170
|
-
}
|
|
5171
|
-
});
|
|
5172
|
-
const total = Array.from(selected).reduce((sum, i) => sum + INTENTS[i].value, 0);
|
|
5173
|
-
console.log(`
|
|
5174
|
-
Selected intents value: ${total}`);
|
|
5175
|
-
};
|
|
5176
|
-
render();
|
|
5177
|
-
if (process.stdin.isTTY) {
|
|
5178
|
-
process.stdin.setRawMode(true);
|
|
5179
|
-
}
|
|
5180
|
-
process.stdin.resume();
|
|
5181
|
-
process.stdin.setEncoding("utf8");
|
|
5182
|
-
const onKeypress = (key) => {
|
|
5183
|
-
if (key === "\x03") {
|
|
5184
|
-
process.stdin.setRawMode(false);
|
|
5185
|
-
process.exit(0);
|
|
5186
|
-
}
|
|
5187
|
-
if (key === "\r" || key === `
|
|
5188
|
-
`) {
|
|
5189
|
-
process.stdin.removeListener("data", onKeypress);
|
|
5190
|
-
if (process.stdin.isTTY) {
|
|
5191
|
-
process.stdin.setRawMode(false);
|
|
5192
|
-
}
|
|
5193
|
-
const total = Array.from(selected).reduce((sum, i) => sum + INTENTS[i].value, 0);
|
|
5194
|
-
console.log(`
|
|
5195
|
-
`);
|
|
5196
|
-
resolve6(total);
|
|
5197
|
-
return;
|
|
5198
|
-
}
|
|
5199
|
-
if (key === " ") {
|
|
5200
|
-
if (selected.has(cursor)) {
|
|
5201
|
-
selected.delete(cursor);
|
|
5202
|
-
} else {
|
|
5203
|
-
selected.add(cursor);
|
|
5204
|
-
}
|
|
5205
|
-
render();
|
|
5206
|
-
return;
|
|
5207
|
-
}
|
|
5208
|
-
if (key === "\x1B[A" || key === "k") {
|
|
5209
|
-
cursor = Math.max(0, cursor - 1);
|
|
5210
|
-
render();
|
|
5211
|
-
return;
|
|
5212
|
-
}
|
|
5213
|
-
if (key === "\x1B[B" || key === "j") {
|
|
5214
|
-
cursor = Math.min(INTENTS.length - 1, cursor + 1);
|
|
5215
|
-
render();
|
|
5216
|
-
return;
|
|
5217
|
-
}
|
|
5218
|
-
};
|
|
5219
|
-
process.stdin.on("data", onKeypress);
|
|
5220
|
-
});
|
|
5221
|
-
}
|
|
5222
5184
|
async function apiRequest(method, path8, body, env) {
|
|
5223
5185
|
const config = getConfig(env);
|
|
5224
5186
|
const url = `${getApiUrlForEnv(env)}${path8}`;
|
|
@@ -5247,30 +5209,59 @@ async function botAdd(options) {
|
|
|
5247
5209
|
console.log("Not logged in. Run `calabasas login` first.");
|
|
5248
5210
|
return;
|
|
5249
5211
|
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
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.");
|
|
5220
|
+
return;
|
|
5221
|
+
}
|
|
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.");
|
|
5255
5229
|
return;
|
|
5256
5230
|
}
|
|
5257
|
-
const
|
|
5258
|
-
|
|
5259
|
-
|
|
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.");
|
|
5260
5237
|
return;
|
|
5261
5238
|
}
|
|
5262
|
-
const
|
|
5263
|
-
|
|
5264
|
-
|
|
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.");
|
|
5265
5246
|
return;
|
|
5266
5247
|
}
|
|
5267
|
-
const
|
|
5268
|
-
|
|
5269
|
-
|
|
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.");
|
|
5270
5260
|
return;
|
|
5271
5261
|
}
|
|
5272
|
-
const intents =
|
|
5273
|
-
|
|
5262
|
+
const intents = selectedIntents.reduce((sum, i) => sum + INTENTS[i].value, 0);
|
|
5263
|
+
const s = p8.spinner();
|
|
5264
|
+
s.start("Creating bot...");
|
|
5274
5265
|
const encryptionKey = "calabasas-dev-key-change-in-production";
|
|
5275
5266
|
const encryptedToken = encrypt(token, encryptionKey);
|
|
5276
5267
|
const sharedSecret = generateSecret();
|
|
@@ -5283,20 +5274,16 @@ async function botAdd(options) {
|
|
|
5283
5274
|
sharedSecret
|
|
5284
5275
|
}, env);
|
|
5285
5276
|
if (!ok) {
|
|
5286
|
-
|
|
5287
|
-
|
|
5277
|
+
s.stop("Failed to create bot");
|
|
5278
|
+
p8.cancel(`${data.error || `HTTP ${status}`}`);
|
|
5288
5279
|
return;
|
|
5289
5280
|
}
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
console.log("Next steps:");
|
|
5297
|
-
console.log("1. Run `calabasas generate` to generate the event handler");
|
|
5298
|
-
console.log("2. Create your handler in convex/discord.ts");
|
|
5299
|
-
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!");
|
|
5300
5287
|
}
|
|
5301
5288
|
async function botList(options) {
|
|
5302
5289
|
if (options.dev && options.prod) {
|
|
@@ -5312,6 +5299,22 @@ async function botList(options) {
|
|
|
5312
5299
|
console.log("Not logged in. Run `calabasas login` first.");
|
|
5313
5300
|
return;
|
|
5314
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
|
+
}
|
|
5315
5318
|
const { ok, data, status } = await apiRequest("GET", "/api/cli/bots", undefined, env);
|
|
5316
5319
|
if (!ok) {
|
|
5317
5320
|
console.log(`Failed to list bots: ${data.error || `HTTP ${status}`}`);
|
|
@@ -5322,15 +5325,17 @@ async function botList(options) {
|
|
|
5322
5325
|
console.log("No bots found. Run `calabasas bot add` to create one.");
|
|
5323
5326
|
return;
|
|
5324
5327
|
}
|
|
5325
|
-
console.log(`
|
|
5326
|
-
|
|
5328
|
+
console.log(pc.bold(`
|
|
5329
|
+
Your Discord bots:
|
|
5330
|
+
`));
|
|
5327
5331
|
for (const bot of bots) {
|
|
5328
|
-
const
|
|
5329
|
-
|
|
5330
|
-
console.log(
|
|
5331
|
-
console.log(`
|
|
5332
|
-
console.log(`
|
|
5333
|
-
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}`);
|
|
5334
5339
|
console.log("");
|
|
5335
5340
|
}
|
|
5336
5341
|
}
|
|
@@ -5348,17 +5353,24 @@ async function botRemove(botId, options) {
|
|
|
5348
5353
|
console.log("Not logged in. Run `calabasas login` first.");
|
|
5349
5354
|
return;
|
|
5350
5355
|
}
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
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.");
|
|
5354
5362
|
return;
|
|
5355
5363
|
}
|
|
5364
|
+
const s = p8.spinner();
|
|
5365
|
+
s.start("Removing bot...");
|
|
5356
5366
|
const { ok, data, status } = await apiRequest("DELETE", `/api/cli/bots?id=${botId}`, undefined, env);
|
|
5357
5367
|
if (!ok) {
|
|
5358
|
-
|
|
5368
|
+
s.stop("Failed to remove bot");
|
|
5369
|
+
p8.cancel(`${data.error || `HTTP ${status}`}`);
|
|
5359
5370
|
return;
|
|
5360
5371
|
}
|
|
5361
|
-
|
|
5372
|
+
s.stop("Done");
|
|
5373
|
+
p8.outro("Bot removed successfully!");
|
|
5362
5374
|
}
|
|
5363
5375
|
async function botEdit(botId, options) {
|
|
5364
5376
|
if (options.dev && options.prod) {
|
|
@@ -5375,38 +5387,469 @@ async function botEdit(botId, options) {
|
|
|
5375
5387
|
return;
|
|
5376
5388
|
}
|
|
5377
5389
|
const updates = { botId };
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
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);
|
|
5390
5447
|
}
|
|
5391
5448
|
if (Object.keys(updates).length === 1) {
|
|
5392
|
-
console.log("No updates specified.
|
|
5393
|
-
console.log("");
|
|
5394
|
-
console.log("Examples:");
|
|
5395
|
-
console.log(' calabasas bot edit <id> --name "New Name"');
|
|
5396
|
-
console.log(" calabasas bot edit <id> --url https://new-project.convex.cloud");
|
|
5397
|
-
console.log(" calabasas bot edit <id> --token <new-token>");
|
|
5398
|
-
console.log(" calabasas bot edit <id> --intents 513");
|
|
5449
|
+
console.log("No updates specified.");
|
|
5399
5450
|
return;
|
|
5400
5451
|
}
|
|
5401
|
-
|
|
5452
|
+
const s = p8.spinner();
|
|
5453
|
+
s.start("Updating bot...");
|
|
5402
5454
|
const { ok, data, status } = await apiRequest("PATCH", "/api/cli/bots", updates, env);
|
|
5403
5455
|
if (!ok) {
|
|
5404
|
-
|
|
5456
|
+
s.stop("Failed to update bot");
|
|
5457
|
+
p8.cancel(`${data.error || `HTTP ${status}`}`);
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
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.");
|
|
5405
5812
|
return;
|
|
5406
5813
|
}
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
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();
|
|
5410
5853
|
}
|
|
5411
5854
|
|
|
5412
5855
|
// src/index.ts
|
|
@@ -5419,9 +5862,11 @@ program2.command("push").description("Push your calabasas.config.ts to Calabasas
|
|
|
5419
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);
|
|
5420
5863
|
program2.command("skill").description("Generate Calabasas documentation for AI assistants").option("--dev", "Use development environment").option("--prod", "Use production environment").action(skill);
|
|
5421
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);
|
|
5422
5867
|
var bot = program2.command("bot").description("Manage Discord bots");
|
|
5423
5868
|
bot.command("add").description("Add a new Discord bot").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botAdd);
|
|
5424
|
-
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);
|
|
5425
5870
|
bot.command("remove <botId>").alias("rm").description("Remove a Discord bot").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botRemove);
|
|
5426
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);
|
|
5427
5872
|
program2.parse();
|