@bonnard/cli 0.1.1 → 0.1.3

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/bin/bon.mjs CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  import { program } from "commander";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import pc from "picocolors";
7
+ import http from "node:http";
8
+ import crypto from "node:crypto";
9
+ import os from "node:os";
10
+ import { encode } from "@toon-format/toon";
6
11
 
7
12
  //#region src/commands/init.ts
8
13
  const BON_YAML_TEMPLATE = (projectName) => `project:
@@ -31,10 +36,566 @@ async function initCommand() {
31
36
  console.log(` ${pc.dim(".gitignore")} git ignore rules`);
32
37
  }
33
38
 
39
+ //#endregion
40
+ //#region src/lib/credentials.ts
41
+ const CREDENTIALS_DIR = path.join(os.homedir(), ".config", "bon");
42
+ const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
43
+ function saveCredentials(credentials) {
44
+ fs.mkdirSync(CREDENTIALS_DIR, {
45
+ recursive: true,
46
+ mode: 448
47
+ });
48
+ fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 384 });
49
+ }
50
+ function loadCredentials() {
51
+ try {
52
+ const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
53
+ const parsed = JSON.parse(raw);
54
+ if (parsed.token && parsed.email) return parsed;
55
+ return null;
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ function clearCredentials() {
61
+ try {
62
+ fs.unlinkSync(CREDENTIALS_FILE);
63
+ } catch {}
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/commands/login.ts
68
+ const APP_URL$1 = process.env.BON_APP_URL || "http://localhost:3000";
69
+ const TIMEOUT_MS = 120 * 1e3;
70
+ async function loginCommand() {
71
+ const state = crypto.randomUUID();
72
+ const { port, close } = await startCallbackServer(state);
73
+ const url = `${APP_URL$1}/auth/device?state=${state}&port=${port}`;
74
+ console.log(pc.dim(`Opening browser to ${url}`));
75
+ const open = (await import("open")).default;
76
+ await open(url);
77
+ console.log("Waiting for authentication...");
78
+ const timeout = setTimeout(() => {
79
+ close();
80
+ console.log(pc.red("Login timed out. Please try again."));
81
+ process.exit(1);
82
+ }, TIMEOUT_MS);
83
+ const result = await waitForCallback;
84
+ clearTimeout(timeout);
85
+ close();
86
+ saveCredentials({
87
+ token: result.token,
88
+ email: result.email
89
+ });
90
+ console.log(pc.green(`Logged in as ${result.email}`));
91
+ }
92
+ let resolveCallback;
93
+ const waitForCallback = new Promise((resolve, reject) => {
94
+ resolveCallback = resolve;
95
+ });
96
+ function startCallbackServer(expectedState) {
97
+ return new Promise((resolve, reject) => {
98
+ const server = http.createServer((req, res) => {
99
+ const url = new URL(req.url, `http://localhost`);
100
+ if (url.pathname !== "/callback") {
101
+ res.writeHead(404);
102
+ res.end("Not found");
103
+ return;
104
+ }
105
+ const token = url.searchParams.get("token");
106
+ const email = url.searchParams.get("email");
107
+ if (url.searchParams.get("state") !== expectedState) {
108
+ res.writeHead(400);
109
+ res.end("Invalid state parameter");
110
+ return;
111
+ }
112
+ if (!token || !email) {
113
+ res.writeHead(400);
114
+ res.end("Missing token or email");
115
+ return;
116
+ }
117
+ res.writeHead(200, { "Content-Type": "text/html" });
118
+ res.end(`<!DOCTYPE html>
119
+ <html lang="en">
120
+ <head>
121
+ <meta charset="utf-8" />
122
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
123
+ <title>Bonnard CLI</title>
124
+ <style>
125
+ * { margin: 0; padding: 0; box-sizing: border-box; }
126
+ body {
127
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
128
+ background: #0a0a0a;
129
+ color: #fafafa;
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ min-height: 100vh;
134
+ }
135
+ .card {
136
+ text-align: center;
137
+ padding: 3rem;
138
+ max-width: 400px;
139
+ }
140
+ .check {
141
+ width: 48px;
142
+ height: 48px;
143
+ border-radius: 50%;
144
+ background: #22c55e;
145
+ display: inline-flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ margin-bottom: 1.5rem;
149
+ }
150
+ .check svg { width: 24px; height: 24px; }
151
+ h1 {
152
+ font-size: 1.25rem;
153
+ font-weight: 600;
154
+ margin-bottom: 0.5rem;
155
+ }
156
+ p {
157
+ color: #a1a1aa;
158
+ font-size: 0.875rem;
159
+ line-height: 1.5;
160
+ }
161
+ </style>
162
+ </head>
163
+ <body>
164
+ <div class="card">
165
+ <div class="check">
166
+ <svg fill="none" stroke="white" stroke-width="3" viewBox="0 0 24 24">
167
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
168
+ </svg>
169
+ </div>
170
+ <h1>Authentication successful</h1>
171
+ <p>You can close this tab and return to your terminal.</p>
172
+ </div>
173
+ </body>
174
+ </html>`);
175
+ resolveCallback({
176
+ token,
177
+ email
178
+ });
179
+ });
180
+ server.on("error", reject);
181
+ server.listen(0, "127.0.0.1", () => {
182
+ const addr = server.address();
183
+ if (!addr || typeof addr === "string") {
184
+ reject(/* @__PURE__ */ new Error("Failed to start callback server"));
185
+ return;
186
+ }
187
+ resolve({
188
+ port: addr.port,
189
+ close: () => server.close()
190
+ });
191
+ });
192
+ });
193
+ }
194
+
195
+ //#endregion
196
+ //#region src/commands/logout.ts
197
+ async function logoutCommand() {
198
+ clearCredentials();
199
+ console.log(pc.green("Logged out"));
200
+ }
201
+
202
+ //#endregion
203
+ //#region src/lib/api.ts
204
+ const APP_URL = process.env.BON_APP_URL || "http://localhost:3000";
205
+ function getToken() {
206
+ const creds = loadCredentials();
207
+ if (!creds) {
208
+ console.error(pc.red("Not logged in. Run `bon login` first."));
209
+ process.exit(1);
210
+ }
211
+ return creds.token;
212
+ }
213
+ async function request(method, path, body) {
214
+ const token = getToken();
215
+ const url = `${APP_URL}${path}`;
216
+ const res = await fetch(url, {
217
+ method,
218
+ headers: {
219
+ Authorization: `Bearer ${token}`,
220
+ "Content-Type": "application/json"
221
+ },
222
+ body: body ? JSON.stringify(body) : void 0
223
+ });
224
+ const data = await res.json();
225
+ if (!res.ok) {
226
+ const message = data.error || res.statusText;
227
+ throw new Error(message);
228
+ }
229
+ return data;
230
+ }
231
+ function get(path) {
232
+ return request("GET", path);
233
+ }
234
+ function post(path, body) {
235
+ return request("POST", path, body);
236
+ }
237
+ function del(path) {
238
+ return request("DELETE", path);
239
+ }
240
+
241
+ //#endregion
242
+ //#region src/commands/datasource/add.ts
243
+ async function prompts() {
244
+ return import("@inquirer/prompts");
245
+ }
246
+ const WAREHOUSE_TYPES = [
247
+ {
248
+ value: "snowflake",
249
+ label: "Snowflake",
250
+ configFields: [
251
+ {
252
+ name: "account",
253
+ message: "Account identifier (e.g. xy12345.us-east-1)",
254
+ required: true
255
+ },
256
+ {
257
+ name: "database",
258
+ message: "Database name",
259
+ required: true
260
+ },
261
+ {
262
+ name: "schema",
263
+ message: "Schema name",
264
+ required: true
265
+ },
266
+ {
267
+ name: "warehouse",
268
+ message: "Warehouse name",
269
+ required: true
270
+ },
271
+ {
272
+ name: "role",
273
+ message: "Role (optional)"
274
+ }
275
+ ],
276
+ credentialFields: [{
277
+ name: "username",
278
+ message: "Username"
279
+ }, {
280
+ name: "password",
281
+ message: "Password",
282
+ secret: true
283
+ }]
284
+ },
285
+ {
286
+ value: "postgres",
287
+ label: "Postgres",
288
+ configFields: [
289
+ {
290
+ name: "host",
291
+ message: "Host",
292
+ required: true
293
+ },
294
+ {
295
+ name: "port",
296
+ message: "Port (default: 5432)"
297
+ },
298
+ {
299
+ name: "database",
300
+ message: "Database name",
301
+ required: true
302
+ },
303
+ {
304
+ name: "schema",
305
+ message: "Schema (default: public)"
306
+ }
307
+ ],
308
+ credentialFields: [{
309
+ name: "username",
310
+ message: "Username"
311
+ }, {
312
+ name: "password",
313
+ message: "Password",
314
+ secret: true
315
+ }]
316
+ },
317
+ {
318
+ value: "bigquery",
319
+ label: "BigQuery",
320
+ configFields: [
321
+ {
322
+ name: "project_id",
323
+ message: "GCP Project ID",
324
+ required: true
325
+ },
326
+ {
327
+ name: "dataset",
328
+ message: "Dataset name",
329
+ required: true
330
+ },
331
+ {
332
+ name: "location",
333
+ message: "Location (e.g. US, EU)"
334
+ }
335
+ ],
336
+ credentialFields: [{
337
+ name: "service_account_json",
338
+ message: "Service account JSON (paste or path)"
339
+ }]
340
+ },
341
+ {
342
+ value: "databricks",
343
+ label: "Databricks",
344
+ configFields: [
345
+ {
346
+ name: "hostname",
347
+ message: "Server hostname",
348
+ required: true
349
+ },
350
+ {
351
+ name: "http_path",
352
+ message: "HTTP path",
353
+ required: true
354
+ },
355
+ {
356
+ name: "catalog",
357
+ message: "Catalog name"
358
+ }
359
+ ],
360
+ credentialFields: [{
361
+ name: "token",
362
+ message: "Personal access token",
363
+ secret: true
364
+ }]
365
+ }
366
+ ];
367
+ async function datasourceAddCommand() {
368
+ const { input, select, password } = await prompts();
369
+ let name;
370
+ while (true) {
371
+ name = await input({ message: "Name for this data source:" });
372
+ const { dataSources } = await get("/api/datasources");
373
+ if (dataSources.some((ds) => ds.name === name)) {
374
+ console.log(pc.red(`A data source named "${name}" already exists. Choose a different name.`));
375
+ continue;
376
+ }
377
+ break;
378
+ }
379
+ const warehouseType = await select({
380
+ message: "Warehouse type:",
381
+ choices: WAREHOUSE_TYPES.map((w) => ({
382
+ name: w.label,
383
+ value: w.value
384
+ }))
385
+ });
386
+ const wt = WAREHOUSE_TYPES.find((w) => w.value === warehouseType);
387
+ const config = {};
388
+ for (const field of wt.configFields) {
389
+ const value = await input({
390
+ message: field.message,
391
+ required: field.required
392
+ });
393
+ if (value) config[field.name] = value;
394
+ }
395
+ const credentials = {};
396
+ for (const field of wt.credentialFields) {
397
+ const value = field.secret ? await password({ message: field.message }) : await input({ message: field.message });
398
+ if (value) credentials[field.name] = value;
399
+ }
400
+ try {
401
+ const result = await post("/api/datasources", {
402
+ name,
403
+ warehouse_type: warehouseType,
404
+ config,
405
+ credentials
406
+ });
407
+ console.log(pc.green(`Data source "${result.dataSource.name}" created (${result.dataSource.id})`));
408
+ } catch (err) {
409
+ console.error(pc.red(`Failed to create data source: ${err.message}`));
410
+ process.exit(1);
411
+ }
412
+ }
413
+
414
+ //#endregion
415
+ //#region src/commands/datasource/list.ts
416
+ async function datasourceListCommand() {
417
+ try {
418
+ const result = await get("/api/datasources");
419
+ if (result.dataSources.length === 0) {
420
+ console.log(pc.dim("No data sources found. Run `bon datasource add` to create one."));
421
+ return;
422
+ }
423
+ console.log(pc.bold("Data Sources\n"));
424
+ for (const ds of result.dataSources) {
425
+ const statusColor = ds.status === "active" ? pc.green : ds.status === "error" ? pc.red : pc.yellow;
426
+ console.log(` ${pc.bold(ds.name)}`);
427
+ console.log(` ID: ${pc.dim(ds.id)}`);
428
+ console.log(` Type: ${ds.warehouse_type}`);
429
+ console.log(` Status: ${statusColor(ds.status)}`);
430
+ console.log(` Created: ${new Date(ds.created_at).toLocaleDateString()}`);
431
+ console.log();
432
+ }
433
+ } catch (err) {
434
+ console.error(pc.red(`Failed to list data sources: ${err.message}`));
435
+ process.exit(1);
436
+ }
437
+ }
438
+
439
+ //#endregion
440
+ //#region src/commands/datasource/test.ts
441
+ async function datasourceTestCommand(name) {
442
+ try {
443
+ const result = await post("/api/datasources/test", { name });
444
+ if (result.success) {
445
+ console.log(pc.green(result.message));
446
+ if (result.details) {
447
+ if (result.details.warehouse) console.log(pc.dim(` Warehouse: ${result.details.warehouse}`));
448
+ if (result.details.account) console.log(pc.dim(` Account: ${result.details.account}`));
449
+ if (result.details.latencyMs != null) console.log(pc.dim(` Latency: ${result.details.latencyMs}ms`));
450
+ }
451
+ } else console.log(pc.red(result.message));
452
+ } catch (err) {
453
+ console.error(pc.red(`Failed to test data source: ${err.message}`));
454
+ process.exit(1);
455
+ }
456
+ }
457
+
458
+ //#endregion
459
+ //#region src/commands/datasource/remove.ts
460
+ async function datasourceRemoveCommand(name) {
461
+ try {
462
+ await del(`/api/datasources/${encodeURIComponent(name)}`);
463
+ console.log(pc.green(`Data source "${name}" removed.`));
464
+ } catch (err) {
465
+ console.error(pc.red(`Failed to remove data source: ${err.message}`));
466
+ process.exit(1);
467
+ }
468
+ }
469
+
470
+ //#endregion
471
+ //#region src/commands/query.ts
472
+ async function queryCommand(datasourceName, sql, options) {
473
+ const limit = options.limit ? parseInt(options.limit, 10) : 1e3;
474
+ const format = options.format ?? "toon";
475
+ try {
476
+ const result = await post("/api/datasources/query", {
477
+ name: datasourceName,
478
+ sql,
479
+ options: {
480
+ schema: options.schema,
481
+ database: options.database,
482
+ limit
483
+ }
484
+ });
485
+ if (result.error) {
486
+ console.error(pc.red(result.error));
487
+ process.exit(1);
488
+ }
489
+ if (result.rowCount === 0) {
490
+ console.log("No rows returned.");
491
+ return;
492
+ }
493
+ if (format === "json") console.log(JSON.stringify(result, null, 2));
494
+ else {
495
+ const toon = encode({ results: result.rows });
496
+ console.log(toon);
497
+ }
498
+ if (result.truncated) console.log(pc.dim(`(truncated to ${result.rowCount} rows)`));
499
+ } catch (err) {
500
+ console.error(pc.red(`Query failed: ${err.message}`));
501
+ process.exit(1);
502
+ }
503
+ }
504
+
505
+ //#endregion
506
+ //#region src/commands/validate.ts
507
+ async function validateCommand() {
508
+ const cwd = process.cwd();
509
+ if (!fs.existsSync(path.join(cwd, "bon.yaml"))) {
510
+ console.log(pc.red("No bon.yaml found. Are you in a Bonnard project?"));
511
+ process.exit(1);
512
+ }
513
+ const { validate } = await import("./validate-Bd1D39Bj.mjs");
514
+ const result = await validate(cwd);
515
+ if (result.cubes.length === 0 && result.views.length === 0 && result.valid) {
516
+ console.log(pc.yellow("No model or view files found in models/ or views/."));
517
+ return;
518
+ }
519
+ if (!result.valid) {
520
+ console.log(pc.red("Validation failed:\n"));
521
+ for (const err of result.errors) console.log(pc.red(` • ${err}`));
522
+ process.exit(1);
523
+ }
524
+ console.log(pc.green("Validation passed."));
525
+ console.log();
526
+ if (result.cubes.length > 0) console.log(` ${pc.dim("Cubes")} (${result.cubes.length}): ${result.cubes.join(", ")}`);
527
+ if (result.views.length > 0) console.log(` ${pc.dim("Views")} (${result.views.length}): ${result.views.join(", ")}`);
528
+ }
529
+
530
+ //#endregion
531
+ //#region src/commands/deploy.ts
532
+ function collectFiles(dir, rootDir) {
533
+ const files = {};
534
+ if (!fs.existsSync(dir)) return files;
535
+ function walk(current) {
536
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
537
+ const fullPath = path.join(current, entry.name);
538
+ if (entry.isDirectory()) walk(fullPath);
539
+ else if (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) {
540
+ const relativePath = path.relative(rootDir, fullPath);
541
+ files[relativePath] = fs.readFileSync(fullPath, "utf-8");
542
+ }
543
+ }
544
+ }
545
+ walk(dir);
546
+ return files;
547
+ }
548
+ async function deployCommand() {
549
+ const cwd = process.cwd();
550
+ if (!fs.existsSync(path.join(cwd, "bon.yaml"))) {
551
+ console.log(pc.red("No bon.yaml found. Are you in a Bonnard project?"));
552
+ process.exit(1);
553
+ }
554
+ console.log(pc.dim("Validating models..."));
555
+ const { validate } = await import("./validate-Bd1D39Bj.mjs");
556
+ const result = await validate(cwd);
557
+ if (!result.valid) {
558
+ console.log(pc.red("Validation failed:\n"));
559
+ for (const err of result.errors) console.log(pc.red(` • ${err}`));
560
+ process.exit(1);
561
+ }
562
+ if (result.cubes.length === 0 && result.views.length === 0) {
563
+ console.log(pc.yellow("No model or view files found in models/ or views/. Nothing to deploy."));
564
+ process.exit(1);
565
+ }
566
+ console.log(pc.dim(` Found ${result.cubes.length} cube(s) and ${result.views.length} view(s)`));
567
+ const files = {
568
+ ...collectFiles(path.join(cwd, "models"), cwd),
569
+ ...collectFiles(path.join(cwd, "views"), cwd)
570
+ };
571
+ const fileCount = Object.keys(files).length;
572
+ console.log(pc.dim(` Deploying ${fileCount} file(s)...\n`));
573
+ try {
574
+ const response = await post("/api/deploy", { files });
575
+ console.log(pc.green("Deploy successful!"));
576
+ console.log(`Deployment ID: ${pc.cyan(response.deployment.id)}`);
577
+ console.log(`Cube API: ${pc.cyan(`${response.deployment.cubeApiUrl}/cubejs-api/v1`)}`);
578
+ } catch (err) {
579
+ console.log(pc.red(`Deploy failed: ${err instanceof Error ? err.message : err}`));
580
+ process.exit(1);
581
+ }
582
+ }
583
+
34
584
  //#endregion
35
585
  //#region src/bin/bon.ts
36
- program.name("bon").description("Bonnard semantic layer CLI").version("0.1.0");
586
+ const { version } = createRequire(import.meta.url)("../../package.json");
587
+ program.name("bon").description("Bonnard semantic layer CLI").version(version);
37
588
  program.command("init").description("Create a new Bonnard project in the current directory").action(initCommand);
589
+ program.command("login").description("Authenticate with Bonnard via your browser").action(loginCommand);
590
+ program.command("logout").description("Remove stored credentials").action(logoutCommand);
591
+ const datasource = program.command("datasource").description("Manage warehouse data source connections");
592
+ datasource.command("add").description("Add a new data source connection").action(datasourceAddCommand);
593
+ datasource.command("list").description("List configured data sources").action(datasourceListCommand);
594
+ datasource.command("test").description("Test data source connectivity").argument("<name>", "Data source name").action(datasourceTestCommand);
595
+ datasource.command("remove").description("Remove a data source").argument("<name>", "Data source name").action(datasourceRemoveCommand);
596
+ program.command("query").description("Run a SQL query against a warehouse").argument("<datasource-name>", "Data source name").argument("<sql>", "SQL query to execute").option("--schema <schema>", "Override schema").option("--database <database>", "Override database").option("--limit <limit>", "Max rows to return", "1000").option("--format <format>", "Output format: toon or json", "toon").action(queryCommand);
597
+ program.command("validate").description("Validate Cube model and view YAML files").action(validateCommand);
598
+ program.command("deploy").description("Deploy models to your Bonnard Cube instance").action(deployCommand);
38
599
  program.parse();
39
600
 
40
601
  //#endregion
@@ -0,0 +1,65 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { compile } from "@cubejs-backend/schema-compiler";
4
+
5
+ //#region src/lib/validate.ts
6
+ function collectYamlFiles(dir, rootDir) {
7
+ if (!fs.existsSync(dir)) return [];
8
+ const results = [];
9
+ function walk(current) {
10
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
11
+ const fullPath = path.join(current, entry.name);
12
+ if (entry.isDirectory()) walk(fullPath);
13
+ else if (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) results.push({
14
+ fileName: path.relative(rootDir, fullPath),
15
+ content: fs.readFileSync(fullPath, "utf-8")
16
+ });
17
+ }
18
+ }
19
+ walk(dir);
20
+ return results;
21
+ }
22
+ function createModelRepository(projectPath) {
23
+ const modelsDir = path.join(projectPath, "models");
24
+ const viewsDir = path.join(projectPath, "views");
25
+ return {
26
+ localPath: () => projectPath,
27
+ dataSchemaFiles: () => {
28
+ const files = [...collectYamlFiles(modelsDir, projectPath), ...collectYamlFiles(viewsDir, projectPath)];
29
+ return Promise.resolve(files);
30
+ }
31
+ };
32
+ }
33
+ async function validate(projectPath) {
34
+ const repo = createModelRepository(projectPath);
35
+ if ((await repo.dataSchemaFiles()).length === 0) return {
36
+ valid: true,
37
+ errors: [],
38
+ cubes: [],
39
+ views: []
40
+ };
41
+ try {
42
+ const { cubeEvaluator } = await compile(repo, {});
43
+ const cubes = [];
44
+ const views = [];
45
+ for (const cube of cubeEvaluator.cubeNames()) if (cubeEvaluator.cubeFromPath(cube).isView) views.push(cube);
46
+ else cubes.push(cube);
47
+ return {
48
+ valid: true,
49
+ errors: [],
50
+ cubes,
51
+ views
52
+ };
53
+ } catch (err) {
54
+ const raw = err.messages ?? err.message ?? String(err);
55
+ return {
56
+ valid: false,
57
+ errors: Array.isArray(raw) ? raw : [raw],
58
+ cubes: [],
59
+ views: []
60
+ };
61
+ }
62
+ }
63
+
64
+ //#endregion
65
+ export { validate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonnard/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "bon": "./dist/bin/bon.mjs"
@@ -8,21 +8,25 @@
8
8
  "files": [
9
9
  "dist"
10
10
  ],
11
- "scripts": {
12
- "build": "tsdown src/bin/bon.ts --format esm --out-dir dist/bin",
13
- "dev": "tsdown src/bin/bon.ts --format esm --out-dir dist/bin --watch",
14
- "test": "vitest run"
15
- },
16
11
  "dependencies": {
12
+ "@inquirer/prompts": "^7.0.0",
13
+ "@toon-format/toon": "^2.1.0",
17
14
  "commander": "^12.0.0",
15
+ "open": "^11.0.0",
16
+ "@cubejs-backend/schema-compiler": "^1.6.7",
17
+ "@cubejs-backend/shared": "^1.6.7",
18
18
  "picocolors": "^1.0.0"
19
19
  },
20
20
  "devDependencies": {
21
- "@bonnard/core": "workspace:*",
22
21
  "tsdown": "^0.20.1",
23
22
  "vitest": "^2.0.0"
24
23
  },
25
24
  "engines": {
26
25
  "node": ">=20.0.0"
26
+ },
27
+ "scripts": {
28
+ "build": "tsdown src/bin/bon.ts --format esm --out-dir dist/bin",
29
+ "dev": "tsdown src/bin/bon.ts --format esm --out-dir dist/bin --watch",
30
+ "test": "vitest run"
27
31
  }
28
- }
32
+ }