calabasas 0.5.1 → 0.6.1

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.
@@ -0,0 +1,236 @@
1
+ import {
2
+ LogViewer
3
+ } from "./index-f8td7thj.js";
4
+ import {
5
+ getConfig,
6
+ getConvexUrl,
7
+ isAuthenticated,
8
+ resolveEnv
9
+ } from "./index-85h6h388.js";
10
+ import {
11
+ build_default,
12
+ formatLatency,
13
+ formatNumber
14
+ } from "./index-8gymgyxd.js";
15
+ import"./index-tre7d3f1.js";
16
+ import {
17
+ __toESM
18
+ } from "./index-sdksp5px.js";
19
+ import {
20
+ BotList
21
+ } from "./BotList-jqxxq32e.js";
22
+ import {
23
+ Box_default,
24
+ Text,
25
+ render_default,
26
+ use_app_default,
27
+ use_input_default
28
+ } from "./index-4rn9k8et.js";
29
+ import {
30
+ ConvexProvider,
31
+ cliApi,
32
+ createConvexClient,
33
+ require_jsx_dev_runtime,
34
+ useQuery
35
+ } from "./convex-1z1jsz1n.js";
36
+ import {
37
+ require_react
38
+ } from "./index-vmy4gfe1.js";
39
+
40
+ // src/components/Dashboard.tsx
41
+ var import_react2 = __toESM(require_react(), 1);
42
+
43
+ // src/components/Header.tsx
44
+ var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
45
+ function Header({
46
+ botCount,
47
+ env
48
+ }) {
49
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
50
+ marginBottom: 1,
51
+ children: [
52
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
53
+ bold: true,
54
+ color: "magenta",
55
+ children: "calabasas"
56
+ }, undefined, false, undefined, this),
57
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
58
+ dimColor: true,
59
+ children: " v0.1.12"
60
+ }, undefined, false, undefined, this),
61
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
62
+ dimColor: true,
63
+ children: " · "
64
+ }, undefined, false, undefined, this),
65
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
66
+ children: [
67
+ botCount,
68
+ " bot",
69
+ botCount !== 1 ? "s" : ""
70
+ ]
71
+ }, undefined, true, undefined, this),
72
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
73
+ dimColor: true,
74
+ children: " · "
75
+ }, undefined, false, undefined, this),
76
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
77
+ color: env === "dev" ? "yellow" : "green",
78
+ children: env
79
+ }, undefined, false, undefined, this),
80
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
81
+ dimColor: true,
82
+ children: " · Press "
83
+ }, undefined, false, undefined, this),
84
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
85
+ bold: true,
86
+ children: "q"
87
+ }, undefined, false, undefined, this),
88
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
89
+ dimColor: true,
90
+ children: " to quit"
91
+ }, undefined, false, undefined, this)
92
+ ]
93
+ }, undefined, true, undefined, this);
94
+ }
95
+
96
+ // src/components/StatsPanel.tsx
97
+ var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
98
+ var ONE_HOUR = 60 * 60 * 1000;
99
+ function StatsPanel({
100
+ apiKey,
101
+ botId
102
+ }) {
103
+ const stats = useQuery(cliApi.botStats, {
104
+ apiKey,
105
+ botId,
106
+ since: Date.now() - ONE_HOUR
107
+ });
108
+ if (stats === undefined) {
109
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
110
+ children: [
111
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
112
+ color: "cyan",
113
+ children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(build_default, {
114
+ type: "dots"
115
+ }, undefined, false, undefined, this)
116
+ }, undefined, false, undefined, this),
117
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
118
+ children: " Loading stats..."
119
+ }, undefined, false, undefined, this)
120
+ ]
121
+ }, undefined, true, undefined, this);
122
+ }
123
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
124
+ gap: 2,
125
+ marginBottom: 1,
126
+ children: [
127
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
128
+ dimColor: true,
129
+ children: "Events (1h):"
130
+ }, undefined, false, undefined, this),
131
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
132
+ bold: true,
133
+ children: formatNumber(stats.total)
134
+ }, undefined, false, undefined, this),
135
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
136
+ color: "green",
137
+ children: [
138
+ formatNumber(stats.success),
139
+ " ok"
140
+ ]
141
+ }, undefined, true, undefined, this),
142
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
143
+ color: "red",
144
+ children: [
145
+ formatNumber(stats.failed),
146
+ " failed"
147
+ ]
148
+ }, undefined, true, undefined, this),
149
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
150
+ dimColor: true,
151
+ children: [
152
+ "avg ",
153
+ formatLatency(stats.avgLatencyMs)
154
+ ]
155
+ }, undefined, true, undefined, this)
156
+ ]
157
+ }, undefined, true, undefined, this);
158
+ }
159
+
160
+ // src/components/Dashboard.tsx
161
+ var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
162
+ function Dashboard({
163
+ apiKey,
164
+ env
165
+ }) {
166
+ const { exit } = use_app_default();
167
+ const [selectedIndex, setSelectedIndex] = import_react2.useState(0);
168
+ const bots = useQuery(cliApi.listBots, { apiKey });
169
+ const botCount = bots?.length ?? 0;
170
+ const selectedBot = bots?.[selectedIndex];
171
+ use_input_default(import_react2.useCallback((input, key) => {
172
+ if (input === "q") {
173
+ exit();
174
+ return;
175
+ }
176
+ if ((input === "j" || key.downArrow) && botCount > 0) {
177
+ setSelectedIndex((i) => Math.min(i, botCount - 1) === botCount - 1 ? 0 : i + 1);
178
+ }
179
+ if ((input === "k" || key.upArrow) && botCount > 0) {
180
+ setSelectedIndex((i) => i === 0 ? botCount - 1 : i - 1);
181
+ }
182
+ }, [botCount, exit]));
183
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
184
+ flexDirection: "column",
185
+ children: [
186
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Header, {
187
+ botCount,
188
+ env
189
+ }, undefined, false, undefined, this),
190
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(BotList, {
191
+ apiKey,
192
+ selectedIndex
193
+ }, undefined, false, undefined, this),
194
+ selectedBot && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
195
+ flexDirection: "column",
196
+ marginTop: 1,
197
+ children: [
198
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(StatsPanel, {
199
+ apiKey,
200
+ botId: selectedBot._id
201
+ }, undefined, false, undefined, this),
202
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(LogViewer, {
203
+ apiKey,
204
+ botId: selectedBot._id
205
+ }, undefined, false, undefined, this)
206
+ ]
207
+ }, undefined, true, undefined, this)
208
+ ]
209
+ }, undefined, true, undefined, this);
210
+ }
211
+
212
+ // src/commands/dashboard.tsx
213
+ var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
214
+ async function dashboard(options) {
215
+ const env = resolveEnv(options);
216
+ if (!isAuthenticated(env)) {
217
+ console.log("Not logged in. Run `calabasas login` first.");
218
+ return;
219
+ }
220
+ const config = getConfig(env);
221
+ const apiKey = config.apiKey;
222
+ const convexUrl = getConvexUrl(env);
223
+ const client = createConvexClient(convexUrl);
224
+ const { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(ConvexProvider, {
225
+ client,
226
+ children: /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Dashboard, {
227
+ apiKey,
228
+ env
229
+ }, undefined, false, undefined, this)
230
+ }, undefined, false, undefined, this));
231
+ await waitUntilExit();
232
+ await client.close();
233
+ }
234
+ export {
235
+ dashboard
236
+ };
@@ -0,0 +1,185 @@
1
+ import {
2
+ __require,
3
+ __toESM
4
+ } from "./index-sdksp5px.js";
5
+
6
+ // src/lib/config.ts
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+ var CONFIG_DIR = path.join(os.homedir(), ".calabasas");
11
+ function getConfigFile(env) {
12
+ if (env === "dev")
13
+ return path.join(CONFIG_DIR, "config.dev.json");
14
+ return path.join(CONFIG_DIR, "config.json");
15
+ }
16
+ var PROD_API_URL = "https://savory-llama-364.convex.site";
17
+ var DEV_API_URL = "https://notable-monitor-41.convex.site";
18
+ var PROD_CONVEX_URL = "https://savory-llama-364.convex.cloud";
19
+ var DEV_CONVEX_URL = "https://notable-monitor-41.convex.cloud";
20
+ var PROD_WEB_URL = "https://calabasas-web.vercel.app";
21
+ var DEV_WEB_URL = "http://localhost:3000";
22
+ function getConfig(env) {
23
+ try {
24
+ const configFile = getConfigFile(env);
25
+ if (!fs.existsSync(configFile)) {
26
+ return {};
27
+ }
28
+ const content = fs.readFileSync(configFile, "utf-8");
29
+ return JSON.parse(content);
30
+ } catch {
31
+ return {};
32
+ }
33
+ }
34
+ function saveConfig(config, env) {
35
+ const existing = getConfig(env);
36
+ const merged = { ...existing, ...config };
37
+ if (!fs.existsSync(CONFIG_DIR)) {
38
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
39
+ }
40
+ fs.writeFileSync(getConfigFile(env), JSON.stringify(merged, null, 2));
41
+ }
42
+ function clearConfig(env) {
43
+ const configFile = getConfigFile(env);
44
+ if (fs.existsSync(configFile)) {
45
+ fs.unlinkSync(configFile);
46
+ }
47
+ }
48
+ function isAuthenticated(env) {
49
+ const config = getConfig(env);
50
+ return !!config.apiKey;
51
+ }
52
+ function getApiUrl(env) {
53
+ const config = getConfig(env);
54
+ return config.apiUrl || PROD_API_URL;
55
+ }
56
+ function getApiUrlForEnv(env) {
57
+ if (env === "dev")
58
+ return DEV_API_URL;
59
+ if (env === "prod")
60
+ return PROD_API_URL;
61
+ return getApiUrl();
62
+ }
63
+ function getWebUrlForEnv(env) {
64
+ if (env === "dev")
65
+ return DEV_WEB_URL;
66
+ return PROD_WEB_URL;
67
+ }
68
+ function getConvexUrl(env) {
69
+ if (env === "dev")
70
+ return DEV_CONVEX_URL;
71
+ return PROD_CONVEX_URL;
72
+ }
73
+ function parseEnvLocal() {
74
+ const envPath = path.join(process.cwd(), ".env.local");
75
+ if (!fs.existsSync(envPath))
76
+ return {};
77
+ const content = fs.readFileSync(envPath, "utf-8");
78
+ const result = {};
79
+ for (const line of content.split(`
80
+ `)) {
81
+ const trimmed = line.trim();
82
+ if (!trimmed || trimmed.startsWith("#"))
83
+ continue;
84
+ const eqIndex = trimmed.indexOf("=");
85
+ if (eqIndex === -1)
86
+ continue;
87
+ const key = trimmed.slice(0, eqIndex).trim();
88
+ let value = trimmed.slice(eqIndex + 1).trim();
89
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
90
+ value = value.slice(1, -1);
91
+ }
92
+ result[key] = value;
93
+ }
94
+ return result;
95
+ }
96
+ function resolveEnv(options) {
97
+ if (options.dev && options.prod) {
98
+ console.error("Error: Cannot use both --dev and --prod flags.");
99
+ process.exit(1);
100
+ }
101
+ if (options.dev)
102
+ return "dev";
103
+ if (options.prod)
104
+ return "prod";
105
+ const envLocal = parseEnvLocal();
106
+ const calabasasEnv = envLocal["CALABASAS_ENV"];
107
+ if (calabasasEnv === "dev" || calabasasEnv === "prod") {
108
+ return calabasasEnv;
109
+ }
110
+ if (calabasasEnv) {
111
+ console.error(`Error: CALABASAS_ENV in .env.local must be "dev" or "prod", got "${calabasasEnv}".`);
112
+ process.exit(1);
113
+ }
114
+ console.error("Error: Specify --dev or --prod, or set CALABASAS_ENV in .env.local");
115
+ process.exit(1);
116
+ }
117
+ async function resolveBot(options, apiKey, apiUrl) {
118
+ if (options.bot)
119
+ return options.bot;
120
+ const envLocal = parseEnvLocal();
121
+ const envBotId = envLocal["CALABASAS_BOT_ID"];
122
+ if (envBotId)
123
+ return envBotId;
124
+ const response = await fetch(`${apiUrl}/api/cli/bots`, {
125
+ method: "GET",
126
+ headers: {
127
+ "Content-Type": "application/json",
128
+ Authorization: `Bearer ${apiKey}`
129
+ }
130
+ });
131
+ if (!response.ok) {
132
+ console.error("Error fetching bots: " + await response.text());
133
+ process.exit(1);
134
+ }
135
+ const { bots } = await response.json();
136
+ if (!bots || bots.length === 0) {
137
+ console.error("No bots found. Add a bot first with `calabasas bot add`.");
138
+ process.exit(1);
139
+ }
140
+ if (bots.length === 1) {
141
+ return bots[0]._id;
142
+ }
143
+ const p = await import("@clack/prompts");
144
+ const selected = await p.select({
145
+ message: "Select a bot",
146
+ options: bots.map((bot) => ({
147
+ value: bot._id,
148
+ label: bot.name
149
+ }))
150
+ });
151
+ if (p.isCancel(selected)) {
152
+ p.cancel("Cancelled.");
153
+ process.exit(1);
154
+ }
155
+ return selected;
156
+ }
157
+ function appendToEnvLocal(entries) {
158
+ const envPath = path.join(process.cwd(), ".env.local");
159
+ let lines = [];
160
+ if (fs.existsSync(envPath)) {
161
+ lines = fs.readFileSync(envPath, "utf-8").split(`
162
+ `);
163
+ }
164
+ for (const [key, value] of Object.entries(entries)) {
165
+ const lineIndex = lines.findIndex((l) => {
166
+ const trimmed = l.trim();
167
+ return trimmed.startsWith(`${key}=`) || trimmed.startsWith(`${key} =`);
168
+ });
169
+ if (lineIndex !== -1) {
170
+ lines[lineIndex] = `${key}=${value}`;
171
+ } else {
172
+ if (!lines.some((l) => l.trim() === "# Calabasas")) {
173
+ if (lines.length > 0 && lines[lines.length - 1].trim() !== "") {
174
+ lines.push("");
175
+ }
176
+ lines.push("# Calabasas");
177
+ }
178
+ lines.push(`${key}=${value}`);
179
+ }
180
+ }
181
+ fs.writeFileSync(envPath, lines.join(`
182
+ `));
183
+ }
184
+
185
+ export { getConfig, saveConfig, clearConfig, isAuthenticated, getApiUrlForEnv, getWebUrlForEnv, getConvexUrl, resolveEnv, resolveBot, appendToEnvLocal };
package/dist/index.js CHANGED
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ appendToEnvLocal,
3
4
  clearConfig,
4
5
  getApiUrlForEnv,
5
6
  getConfig,
6
7
  getConvexUrl,
7
8
  getWebUrlForEnv,
8
9
  isAuthenticated,
10
+ resolveBot,
11
+ resolveEnv,
9
12
  saveConfig
10
- } from "./index-enq39f86.js";
13
+ } from "./index-85h6h388.js";
11
14
  import {
12
15
  __commonJS,
13
16
  __require,
@@ -2069,15 +2072,7 @@ import * as http from "http";
2069
2072
  import * as p from "@clack/prompts";
2070
2073
  var CALLBACK_PORT = 9876;
2071
2074
  async function login(options) {
2072
- if (options.dev && options.prod) {
2073
- console.error("Error: Cannot use both --dev and --prod flags.");
2074
- process.exit(1);
2075
- }
2076
- if (!options.dev && !options.prod) {
2077
- console.error("Error: You must specify either --dev or --prod.");
2078
- process.exit(1);
2079
- }
2080
- const env = options.dev ? "dev" : "prod";
2075
+ const env = resolveEnv(options);
2081
2076
  const existing = getConfig(env);
2082
2077
  p.intro("calabasas login");
2083
2078
  if (existing.apiKey) {
@@ -2169,15 +2164,7 @@ ${authUrl}`, "Authentication URL");
2169
2164
  // src/commands/logout.ts
2170
2165
  import * as p2 from "@clack/prompts";
2171
2166
  async function logout(options) {
2172
- if (options.dev && options.prod) {
2173
- console.error("Error: Cannot use both --dev and --prod flags.");
2174
- process.exit(1);
2175
- }
2176
- if (!options.dev && !options.prod) {
2177
- console.error("Error: You must specify either --dev or --prod.");
2178
- process.exit(1);
2179
- }
2180
- const env = options.dev ? "dev" : "prod";
2167
+ const env = resolveEnv(options);
2181
2168
  p2.intro("calabasas logout");
2182
2169
  if (!isAuthenticated(env)) {
2183
2170
  p2.outro("Not logged in. Nothing to clear.");
@@ -2278,49 +2265,8 @@ async function parseConfigFile(configPath) {
2278
2265
  }
2279
2266
 
2280
2267
  // src/commands/push.ts
2281
- async function selectBot(apiKey, apiUrl) {
2282
- const response = await fetch(`${apiUrl}/api/cli/bots`, {
2283
- method: "GET",
2284
- headers: {
2285
- "Content-Type": "application/json",
2286
- Authorization: `Bearer ${apiKey}`
2287
- }
2288
- });
2289
- if (!response.ok) {
2290
- p4.cancel("Error fetching bots: " + await response.text());
2291
- return null;
2292
- }
2293
- const { bots } = await response.json();
2294
- if (!bots || bots.length === 0) {
2295
- p4.cancel("No bots found. Add a bot first with `calabasas bot add`");
2296
- return null;
2297
- }
2298
- if (bots.length === 1) {
2299
- return bots[0]._id;
2300
- }
2301
- const selected = await p4.select({
2302
- message: "Select a bot to push config to",
2303
- options: bots.map((bot) => ({
2304
- value: bot._id,
2305
- label: bot.name
2306
- }))
2307
- });
2308
- if (p4.isCancel(selected)) {
2309
- p4.cancel("Cancelled.");
2310
- return null;
2311
- }
2312
- return selected;
2313
- }
2314
2268
  async function push(options) {
2315
- if (options.dev && options.prod) {
2316
- console.error("Error: Cannot use both --dev and --prod flags.");
2317
- process.exit(1);
2318
- }
2319
- if (!options.dev && !options.prod) {
2320
- console.error("Error: You must specify either --dev or --prod.");
2321
- process.exit(1);
2322
- }
2323
- const env = options.dev ? "dev" : "prod";
2269
+ const env = resolveEnv(options);
2324
2270
  const apiUrl = getApiUrlForEnv(env);
2325
2271
  const configPath = path3.resolve(process.cwd(), options.config);
2326
2272
  if (!fs3.existsSync(configPath)) {
@@ -2335,13 +2281,7 @@ async function push(options) {
2335
2281
  const config = getConfig(env);
2336
2282
  const apiKey = config.apiKey;
2337
2283
  p4.intro("calabasas push");
2338
- let botId = options.bot;
2339
- if (!botId) {
2340
- botId = await selectBot(apiKey, apiUrl) ?? undefined;
2341
- if (!botId) {
2342
- process.exit(1);
2343
- }
2344
- }
2284
+ const botId = await resolveBot(options, apiKey, apiUrl);
2345
2285
  const calabasasConfig = await parseConfigFile(configPath);
2346
2286
  const eventConfigs = Object.keys(calabasasConfig.events ?? {}).map((eventType) => ({ eventType }));
2347
2287
  const s = p4.spinner();
@@ -3128,6 +3068,27 @@ function generateTypeForEvent(eventName) {
3128
3068
  ${fieldEntries}
3129
3069
  }`;
3130
3070
  }
3071
+ function splitObjectFields(content) {
3072
+ const fields = [];
3073
+ let depth = 0;
3074
+ let current = "";
3075
+ for (const char of content) {
3076
+ if (char === "(" || char === "{")
3077
+ depth++;
3078
+ if (char === ")" || char === "}")
3079
+ depth--;
3080
+ if (char === "," && depth === 0) {
3081
+ if (current.trim())
3082
+ fields.push(current.trim());
3083
+ current = "";
3084
+ } else {
3085
+ current += char;
3086
+ }
3087
+ }
3088
+ if (current.trim())
3089
+ fields.push(current.trim());
3090
+ return fields;
3091
+ }
3131
3092
  function validatorToType(validator) {
3132
3093
  if (validator.startsWith("v.string()"))
3133
3094
  return "string";
@@ -3146,7 +3107,19 @@ function validatorToType(validator) {
3146
3107
  return `Array<${validatorToType(inner)}>`;
3147
3108
  }
3148
3109
  if (validator.startsWith("v.object(")) {
3149
- return "Record<string, unknown>";
3110
+ const objectLiteral = validator.slice(9, -1).trim();
3111
+ const content = objectLiteral.slice(1, -1).trim();
3112
+ if (!content)
3113
+ return "Record<string, never>";
3114
+ const fields = splitObjectFields(content);
3115
+ const fieldTypes = fields.filter((f) => f.includes(":")).map((field) => {
3116
+ const colonIndex = field.indexOf(":");
3117
+ const key = field.slice(0, colonIndex).trim();
3118
+ const fieldValidator = field.slice(colonIndex + 1).trim();
3119
+ const tsType = validatorToType(fieldValidator);
3120
+ return `${key}: ${tsType}`;
3121
+ });
3122
+ return `{ ${fieldTypes.join("; ")} }`;
3150
3123
  }
3151
3124
  return "unknown";
3152
3125
  }
@@ -3620,15 +3593,7 @@ function hasCalabsasSection(content) {
3620
3593
  return content.includes(SECTION_HEADER) || content.includes("## Calabasas Guidelines");
3621
3594
  }
3622
3595
  async function skill(options) {
3623
- if (options.dev && options.prod) {
3624
- console.error("Error: Cannot use both --dev and --prod flags.");
3625
- process.exit(1);
3626
- }
3627
- if (!options.dev && !options.prod) {
3628
- console.error("Error: You must specify either --dev or --prod.");
3629
- process.exit(1);
3630
- }
3631
- const env = options.dev ? "dev" : "prod";
3596
+ const env = resolveEnv(options);
3632
3597
  p6.intro("calabasas skill");
3633
3598
  const s = p6.spinner();
3634
3599
  s.start("Fetching latest Calabasas guidelines...");
@@ -6099,70 +6064,90 @@ async function apiRequest(method, path8, body, env) {
6099
6064
  return { ok: response.ok, data, status: response.status };
6100
6065
  }
6101
6066
  async function botAdd(options) {
6102
- if (options.dev && options.prod) {
6103
- console.error("Error: Cannot use both --dev and --prod flags.");
6104
- process.exit(1);
6105
- }
6106
- if (!options.dev && !options.prod) {
6107
- console.error("Error: You must specify either --dev or --prod.");
6108
- process.exit(1);
6109
- }
6110
- const env = options.dev ? "dev" : "prod";
6067
+ const env = resolveEnv(options);
6111
6068
  if (!isAuthenticated(env)) {
6112
6069
  console.log("Not logged in. Run `calabasas login` first.");
6113
6070
  return;
6114
6071
  }
6072
+ const nonInteractive = !!(options.name && options.appId && options.token && options.url);
6115
6073
  p10.intro("calabasas bot add");
6116
- const name = await p10.text({
6117
- message: "Bot name",
6118
- placeholder: "My Discord Bot",
6119
- validate: (v) => v.trim() ? undefined : "Bot name is required"
6120
- });
6121
- if (p10.isCancel(name)) {
6122
- p10.cancel("Cancelled.");
6123
- return;
6074
+ let name;
6075
+ if (options.name) {
6076
+ name = options.name;
6077
+ } else {
6078
+ name = await p10.text({
6079
+ message: "Bot name",
6080
+ placeholder: "My Discord Bot",
6081
+ validate: (v) => v.trim() ? undefined : "Bot name is required"
6082
+ });
6083
+ if (p10.isCancel(name)) {
6084
+ p10.cancel("Cancelled.");
6085
+ return;
6086
+ }
6124
6087
  }
6125
- const discordAppId = await p10.text({
6126
- message: "Discord Application ID",
6127
- placeholder: "123456789012345678",
6128
- validate: (v) => v.trim() ? undefined : "Discord Application ID is required"
6129
- });
6130
- if (p10.isCancel(discordAppId)) {
6131
- p10.cancel("Cancelled.");
6132
- return;
6088
+ let discordAppId;
6089
+ if (options.appId) {
6090
+ discordAppId = options.appId;
6091
+ } else {
6092
+ discordAppId = await p10.text({
6093
+ message: "Discord Application ID",
6094
+ placeholder: "123456789012345678",
6095
+ validate: (v) => v.trim() ? undefined : "Discord Application ID is required"
6096
+ });
6097
+ if (p10.isCancel(discordAppId)) {
6098
+ p10.cancel("Cancelled.");
6099
+ return;
6100
+ }
6133
6101
  }
6134
- const token = await p10.password({
6135
- message: "Bot token",
6136
- validate: (v) => v.trim() ? undefined : "Bot token is required"
6137
- });
6138
- if (p10.isCancel(token)) {
6139
- p10.cancel("Cancelled.");
6140
- return;
6102
+ let token;
6103
+ if (options.token) {
6104
+ token = options.token;
6105
+ } else {
6106
+ token = await p10.password({
6107
+ message: "Bot token",
6108
+ validate: (v) => v.trim() ? undefined : "Bot token is required"
6109
+ });
6110
+ if (p10.isCancel(token)) {
6111
+ p10.cancel("Cancelled.");
6112
+ return;
6113
+ }
6141
6114
  }
6142
- const convexUrl = await p10.text({
6143
- message: "Your Convex URL",
6144
- placeholder: "https://your-project.convex.cloud",
6145
- validate: (v) => v.trim() ? undefined : "Convex URL is required"
6146
- });
6147
- if (p10.isCancel(convexUrl)) {
6148
- p10.cancel("Cancelled.");
6149
- return;
6115
+ let convexUrl;
6116
+ if (options.url) {
6117
+ convexUrl = options.url;
6118
+ } else {
6119
+ convexUrl = await p10.text({
6120
+ message: "Your Convex URL",
6121
+ placeholder: "https://your-project.convex.cloud",
6122
+ validate: (v) => v.trim() ? undefined : "Convex URL is required"
6123
+ });
6124
+ if (p10.isCancel(convexUrl)) {
6125
+ p10.cancel("Cancelled.");
6126
+ return;
6127
+ }
6150
6128
  }
6151
- const selectedIntents = await p10.multiselect({
6152
- message: "Select Gateway Intents",
6153
- options: INTENTS.map((intent, i) => ({
6154
- value: i,
6155
- label: intent.privileged ? pc.yellow(intent.name + " (Privileged)") : intent.name,
6156
- hint: intent.description
6157
- })),
6158
- initialValues: [0, 9],
6159
- required: true
6160
- });
6161
- if (p10.isCancel(selectedIntents)) {
6162
- p10.cancel("Cancelled.");
6163
- return;
6129
+ let intents;
6130
+ if (options.intents) {
6131
+ intents = parseInt(options.intents, 10);
6132
+ } else if (nonInteractive) {
6133
+ intents = 513;
6134
+ } else {
6135
+ const selectedIntents = await p10.multiselect({
6136
+ message: "Select Gateway Intents",
6137
+ options: INTENTS.map((intent, i) => ({
6138
+ value: i,
6139
+ label: intent.privileged ? pc.yellow(intent.name + " (Privileged)") : intent.name,
6140
+ hint: intent.description
6141
+ })),
6142
+ initialValues: [0, 9],
6143
+ required: true
6144
+ });
6145
+ if (p10.isCancel(selectedIntents)) {
6146
+ p10.cancel("Cancelled.");
6147
+ return;
6148
+ }
6149
+ intents = selectedIntents.reduce((sum, i) => sum + INTENTS[i].value, 0);
6164
6150
  }
6165
- const intents = selectedIntents.reduce((sum, i) => sum + INTENTS[i].value, 0);
6166
6151
  const s = p10.spinner();
6167
6152
  s.start("Creating bot...");
6168
6153
  const { ok, data, status } = await apiRequest("POST", "/api/cli/bots", {
@@ -6178,23 +6163,29 @@ async function botAdd(options) {
6178
6163
  return;
6179
6164
  }
6180
6165
  s.stop("Bot created!");
6181
- const sharedSecret = data.sharedSecret;
6166
+ const { sharedSecret, botId } = data;
6182
6167
  p10.note(`${sharedSecret}
6183
6168
 
6184
6169
  Add this to your Convex environment as CALABASAS_SECRET`, "Shared Secret");
6170
+ if (nonInteractive) {
6171
+ appendToEnvLocal({ CALABASAS_ENV: env, CALABASAS_BOT_ID: botId });
6172
+ p10.note(`CALABASAS_ENV=${env}
6173
+ CALABASAS_BOT_ID=${botId}`, "Saved to .env.local");
6174
+ } else {
6175
+ const saveEnv = await p10.confirm({
6176
+ message: "Save defaults to .env.local? (CALABASAS_ENV + CALABASAS_BOT_ID)"
6177
+ });
6178
+ if (!p10.isCancel(saveEnv) && saveEnv) {
6179
+ appendToEnvLocal({ CALABASAS_ENV: env, CALABASAS_BOT_ID: botId });
6180
+ p10.note(`CALABASAS_ENV=${env}
6181
+ CALABASAS_BOT_ID=${botId}`, "Saved to .env.local");
6182
+ }
6183
+ }
6185
6184
  p10.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");
6186
6185
  p10.outro("Bot created successfully!");
6187
6186
  }
6188
6187
  async function botList(options) {
6189
- if (options.dev && options.prod) {
6190
- console.error("Error: Cannot use both --dev and --prod flags.");
6191
- process.exit(1);
6192
- }
6193
- if (!options.dev && !options.prod) {
6194
- console.error("Error: You must specify either --dev or --prod.");
6195
- process.exit(1);
6196
- }
6197
- const env = options.dev ? "dev" : "prod";
6188
+ const env = resolveEnv(options);
6198
6189
  if (!isAuthenticated(env)) {
6199
6190
  console.log("Not logged in. Run `calabasas login` first.");
6200
6191
  return;
@@ -6241,26 +6232,20 @@ Your Discord bots:
6241
6232
  }
6242
6233
  }
6243
6234
  async function botRemove(botId, options) {
6244
- if (options.dev && options.prod) {
6245
- console.error("Error: Cannot use both --dev and --prod flags.");
6246
- process.exit(1);
6247
- }
6248
- if (!options.dev && !options.prod) {
6249
- console.error("Error: You must specify either --dev or --prod.");
6250
- process.exit(1);
6251
- }
6252
- const env = options.dev ? "dev" : "prod";
6235
+ const env = resolveEnv(options);
6253
6236
  if (!isAuthenticated(env)) {
6254
6237
  console.log("Not logged in. Run `calabasas login` first.");
6255
6238
  return;
6256
6239
  }
6257
6240
  p10.intro("calabasas bot remove");
6258
- const confirmed = await p10.confirm({
6259
- message: `Are you sure you want to remove bot ${botId}?`
6260
- });
6261
- if (p10.isCancel(confirmed) || !confirmed) {
6262
- p10.cancel("Cancelled.");
6263
- return;
6241
+ if (!options.yes) {
6242
+ const confirmed = await p10.confirm({
6243
+ message: `Are you sure you want to remove bot ${botId}?`
6244
+ });
6245
+ if (p10.isCancel(confirmed) || !confirmed) {
6246
+ p10.cancel("Cancelled.");
6247
+ return;
6248
+ }
6264
6249
  }
6265
6250
  const s = p10.spinner();
6266
6251
  s.start("Removing bot...");
@@ -6274,15 +6259,7 @@ async function botRemove(botId, options) {
6274
6259
  p10.outro("Bot removed successfully!");
6275
6260
  }
6276
6261
  async function botEdit(botId, options) {
6277
- if (options.dev && options.prod) {
6278
- console.error("Error: Cannot use both --dev and --prod flags.");
6279
- process.exit(1);
6280
- }
6281
- if (!options.dev && !options.prod) {
6282
- console.error("Error: You must specify either --dev or --prod.");
6283
- process.exit(1);
6284
- }
6285
- const env = options.dev ? "dev" : "prod";
6262
+ const env = resolveEnv(options);
6286
6263
  if (!isAuthenticated(env)) {
6287
6264
  console.log("Not logged in. Run `calabasas login` first.");
6288
6265
  return;
@@ -6363,11 +6340,11 @@ async function botEdit(botId, options) {
6363
6340
 
6364
6341
  // src/index.ts
6365
6342
  var dashboard = async (...args) => {
6366
- const mod = await import("./dashboard-sp8am093.js");
6343
+ const mod = await import("./dashboard-ndj73vnp.js");
6367
6344
  return mod.dashboard(args[0]);
6368
6345
  };
6369
6346
  var logs = async (...args) => {
6370
- const mod = await import("./logs-9y7zq71r.js");
6347
+ const mod = await import("./logs-1c8x4217.js");
6371
6348
  return mod.logs(args[0], args[1]);
6372
6349
  };
6373
6350
  var program2 = new Command;
@@ -6383,8 +6360,8 @@ program2.command("migrate [name]").description("Run a codemod migration (list av
6383
6360
  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);
6384
6361
  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);
6385
6362
  var bot = program2.command("bot").description("Manage Discord bots");
6386
- bot.command("add").description("Add a new Discord bot").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botAdd);
6363
+ bot.command("add").description("Add a new Discord bot").option("-n, --name <name>", "Bot name").option("-a, --app-id <id>", "Discord Application ID").option("-t, --token <token>", "Bot token").option("-u, --url <url>", "Convex URL").option("-i, --intents <intents>", "Intents value (number)").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botAdd);
6387
6364
  bot.command("list").alias("ls").description("List your Discord bots (real-time by default)").option("--once", "One-shot output instead of real-time view").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botList);
6388
- bot.command("remove <botId>").alias("rm").description("Remove a Discord bot").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botRemove);
6365
+ bot.command("remove <botId>").alias("rm").description("Remove a Discord bot").option("-y, --yes", "Skip confirmation prompt").option("--dev", "Use development environment").option("--prod", "Use production environment").action(botRemove);
6389
6366
  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);
6390
6367
  program2.parse();
@@ -0,0 +1,70 @@
1
+ import {
2
+ LogViewer
3
+ } from "./index-f8td7thj.js";
4
+ import {
5
+ getApiUrlForEnv,
6
+ getConfig,
7
+ getConvexUrl,
8
+ isAuthenticated,
9
+ resolveBot,
10
+ resolveEnv
11
+ } from "./index-85h6h388.js";
12
+ import"./index-8gymgyxd.js";
13
+ import"./index-tre7d3f1.js";
14
+ import {
15
+ __toESM
16
+ } from "./index-sdksp5px.js";
17
+ import {
18
+ render_default,
19
+ use_app_default,
20
+ use_input_default
21
+ } from "./index-4rn9k8et.js";
22
+ import {
23
+ ConvexProvider,
24
+ createConvexClient,
25
+ require_jsx_dev_runtime
26
+ } from "./convex-1z1jsz1n.js";
27
+ import"./index-vmy4gfe1.js";
28
+
29
+ // src/commands/logs.tsx
30
+ var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
31
+ function LogsApp({ apiKey, botId, limit }) {
32
+ const { exit } = use_app_default();
33
+ use_input_default((input) => {
34
+ if (input === "q") {
35
+ exit();
36
+ }
37
+ });
38
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(LogViewer, {
39
+ apiKey,
40
+ botId,
41
+ limit
42
+ }, undefined, false, undefined, this);
43
+ }
44
+ async function logs(botId, options) {
45
+ const env = resolveEnv(options);
46
+ if (!isAuthenticated(env)) {
47
+ console.log("Not logged in. Run `calabasas login` first.");
48
+ return;
49
+ }
50
+ const config = getConfig(env);
51
+ const apiKey = config.apiKey;
52
+ const apiUrl = getApiUrlForEnv(env);
53
+ botId = await resolveBot({ bot: botId }, apiKey, apiUrl);
54
+ const limit = options.limit ? parseInt(options.limit, 10) : 50;
55
+ const convexUrl = getConvexUrl(env);
56
+ const client = createConvexClient(convexUrl);
57
+ const { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime.jsxDEV(ConvexProvider, {
58
+ client,
59
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(LogsApp, {
60
+ apiKey,
61
+ botId,
62
+ limit
63
+ }, undefined, false, undefined, this)
64
+ }, undefined, false, undefined, this));
65
+ await waitUntilExit();
66
+ await client.close();
67
+ }
68
+ export {
69
+ logs
70
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calabasas",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "CLI for Calabasas - Discord Gateway as a Service for Convex",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "build": "bun run build:cli && bun run build:lib",
20
20
  "build:cli": "bun build src/index.ts --outdir dist --target node --splitting --external @clack/prompts --external picocolors && node scripts/fix-duplicate-exports.js",
21
21
  "build:lib": "bun build src/config.ts --outfile dist/config.js --target node --format esm && cp src/config.d.ts dist/config.d.ts",
22
+ "test": "bun test",
22
23
  "typecheck": "tsc --noEmit",
23
24
  "prepublishOnly": "bun run build"
24
25
  },