@bonnard/cli 0.2.16 → 0.3.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.
- package/README.md +7 -0
- package/dist/bin/bon.mjs +254 -48
- package/dist/docs/topics/dashboards.components.md +246 -0
- package/dist/docs/topics/dashboards.examples.md +343 -0
- package/dist/docs/topics/dashboards.inputs.md +173 -0
- package/dist/docs/topics/dashboards.md +112 -0
- package/dist/docs/topics/dashboards.queries.md +112 -0
- package/dist/docs/topics/sdk.browser.md +1 -1
- package/dist/templates/claude/skills/bonnard-build-dashboard/SKILL.md +77 -39
- package/dist/templates/cursor/rules/bonnard-build-dashboard.mdc +76 -38
- package/dist/templates/shared/bonnard.md +3 -2
- package/dist/viewer.html +261 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -174,6 +174,13 @@ Handles automatic datasource synchronisation and skips interactive prompts. Fits
|
|
|
174
174
|
| `bon query` | Run queries from the terminal (JSON or SQL) |
|
|
175
175
|
| `bon mcp` | Show MCP server setup instructions |
|
|
176
176
|
| `bon mcp test` | Test MCP connection |
|
|
177
|
+
| `bon dashboard dev` | Preview a markdown dashboard locally with live reload |
|
|
178
|
+
| `bon dashboard deploy` | Deploy a markdown or HTML dashboard |
|
|
179
|
+
| `bon dashboard list` | List deployed dashboards |
|
|
180
|
+
| `bon dashboard remove` | Remove a deployed dashboard |
|
|
181
|
+
| `bon dashboard open` | Open a dashboard in the browser |
|
|
182
|
+
| `bon pull` | Download deployed models to local project |
|
|
183
|
+
| `bon keys list` / `create` / `revoke` | Manage API keys |
|
|
177
184
|
| `bon docs` | Browse or search documentation from the CLI |
|
|
178
185
|
| `bon login` / `bon logout` | Manage authentication |
|
|
179
186
|
| `bon whoami` | Check current session |
|
package/dist/bin/bon.mjs
CHANGED
|
@@ -557,36 +557,120 @@ function loadJsonTemplate(relativePath) {
|
|
|
557
557
|
return JSON.parse(content);
|
|
558
558
|
}
|
|
559
559
|
/**
|
|
560
|
-
* Write a template file
|
|
560
|
+
* Write a template file.
|
|
561
|
+
* - init mode: skip if file contains `# Bonnard`, append if exists without it, create if missing
|
|
562
|
+
* - update mode: overwrite if changed, create if missing
|
|
561
563
|
*/
|
|
562
|
-
function writeTemplateFile(content, targetPath,
|
|
564
|
+
function writeTemplateFile(content, targetPath, results, mode = "init") {
|
|
565
|
+
const relativePath = path.relative(process.cwd(), targetPath);
|
|
563
566
|
if (fs.existsSync(targetPath)) {
|
|
564
|
-
|
|
567
|
+
const existingContent = fs.readFileSync(targetPath, "utf-8");
|
|
568
|
+
if (mode === "update") if (existingContent === content) results.push({
|
|
569
|
+
action: "unchanged",
|
|
570
|
+
path: relativePath
|
|
571
|
+
});
|
|
572
|
+
else {
|
|
573
|
+
fs.writeFileSync(targetPath, content);
|
|
574
|
+
results.push({
|
|
575
|
+
action: "updated",
|
|
576
|
+
path: relativePath
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
else if (!existingContent.includes("# Bonnard")) {
|
|
565
580
|
fs.appendFileSync(targetPath, `\n\n${content}`);
|
|
566
|
-
|
|
581
|
+
results.push({
|
|
582
|
+
action: "appended",
|
|
583
|
+
path: relativePath
|
|
584
|
+
});
|
|
567
585
|
}
|
|
568
586
|
} else {
|
|
569
587
|
fs.writeFileSync(targetPath, content);
|
|
570
|
-
|
|
588
|
+
results.push({
|
|
589
|
+
action: mode === "update" ? "added" : "created",
|
|
590
|
+
path: relativePath
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Write a file that may contain user content alongside the Bonnard section.
|
|
596
|
+
* In update mode, replaces only content from `# Bonnard Semantic Layer` onwards,
|
|
597
|
+
* preserving any user content before it. Falls back to overwrite if marker not found.
|
|
598
|
+
*/
|
|
599
|
+
function writeBonnardSection(content, targetPath, results, mode = "init") {
|
|
600
|
+
const relativePath = path.relative(process.cwd(), targetPath);
|
|
601
|
+
const SECTION_MARKER = "# Bonnard Semantic Layer";
|
|
602
|
+
if (fs.existsSync(targetPath)) {
|
|
603
|
+
const existingContent = fs.readFileSync(targetPath, "utf-8");
|
|
604
|
+
if (mode === "update") {
|
|
605
|
+
const sectionStart = existingContent.indexOf(SECTION_MARKER);
|
|
606
|
+
let newContent;
|
|
607
|
+
if (sectionStart > 0) newContent = existingContent.slice(0, sectionStart).trimEnd() + "\n\n" + content;
|
|
608
|
+
else newContent = content;
|
|
609
|
+
if (existingContent === newContent) results.push({
|
|
610
|
+
action: "unchanged",
|
|
611
|
+
path: relativePath
|
|
612
|
+
});
|
|
613
|
+
else {
|
|
614
|
+
fs.writeFileSync(targetPath, newContent);
|
|
615
|
+
results.push({
|
|
616
|
+
action: "updated",
|
|
617
|
+
path: relativePath
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
} else if (!existingContent.includes("# Bonnard")) {
|
|
621
|
+
fs.appendFileSync(targetPath, `\n\n${content}`);
|
|
622
|
+
results.push({
|
|
623
|
+
action: "appended",
|
|
624
|
+
path: relativePath
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
fs.writeFileSync(targetPath, content);
|
|
629
|
+
results.push({
|
|
630
|
+
action: mode === "update" ? "added" : "created",
|
|
631
|
+
path: relativePath
|
|
632
|
+
});
|
|
571
633
|
}
|
|
572
634
|
}
|
|
573
635
|
/**
|
|
574
636
|
* Merge settings.json, preserving existing settings
|
|
575
637
|
*/
|
|
576
|
-
function mergeSettingsJson(templateSettings, targetPath,
|
|
638
|
+
function mergeSettingsJson(templateSettings, targetPath, results, mode = "init") {
|
|
639
|
+
const relativePath = path.relative(process.cwd(), targetPath);
|
|
577
640
|
if (fs.existsSync(targetPath)) {
|
|
578
|
-
const
|
|
641
|
+
const existingRaw = fs.readFileSync(targetPath, "utf-8");
|
|
642
|
+
const existingContent = JSON.parse(existingRaw);
|
|
579
643
|
const templatePerms = templateSettings.permissions;
|
|
580
644
|
if (templatePerms?.allow) {
|
|
581
645
|
existingContent.permissions = existingContent.permissions || {};
|
|
582
646
|
existingContent.permissions.allow = existingContent.permissions.allow || [];
|
|
583
647
|
for (const permission of templatePerms.allow) if (!existingContent.permissions.allow.includes(permission)) existingContent.permissions.allow.push(permission);
|
|
584
648
|
}
|
|
585
|
-
|
|
586
|
-
|
|
649
|
+
const newRaw = JSON.stringify(existingContent, null, 2) + "\n";
|
|
650
|
+
if (mode === "update") if (existingRaw === newRaw) results.push({
|
|
651
|
+
action: "unchanged",
|
|
652
|
+
path: relativePath
|
|
653
|
+
});
|
|
654
|
+
else {
|
|
655
|
+
fs.writeFileSync(targetPath, newRaw);
|
|
656
|
+
results.push({
|
|
657
|
+
action: "updated",
|
|
658
|
+
path: relativePath
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
fs.writeFileSync(targetPath, newRaw);
|
|
663
|
+
results.push({
|
|
664
|
+
action: "merged",
|
|
665
|
+
path: relativePath
|
|
666
|
+
});
|
|
667
|
+
}
|
|
587
668
|
} else {
|
|
588
669
|
fs.writeFileSync(targetPath, JSON.stringify(templateSettings, null, 2) + "\n");
|
|
589
|
-
|
|
670
|
+
results.push({
|
|
671
|
+
action: mode === "update" ? "added" : "created",
|
|
672
|
+
path: relativePath
|
|
673
|
+
});
|
|
590
674
|
}
|
|
591
675
|
}
|
|
592
676
|
/**
|
|
@@ -603,8 +687,8 @@ alwaysApply: ${alwaysApply}
|
|
|
603
687
|
/**
|
|
604
688
|
* Create agent templates (Claude Code, Cursor, and Codex)
|
|
605
689
|
*/
|
|
606
|
-
function createAgentTemplates(cwd, env) {
|
|
607
|
-
const
|
|
690
|
+
function createAgentTemplates(cwd, env, mode = "init") {
|
|
691
|
+
const results = [];
|
|
608
692
|
let sharedBonnard = loadTemplate("shared/bonnard.md");
|
|
609
693
|
if (env) sharedBonnard += "\n\n" + generateProjectContext(env);
|
|
610
694
|
const claudeRulesDir = path.join(cwd, ".claude", "rules");
|
|
@@ -614,35 +698,64 @@ function createAgentTemplates(cwd, env) {
|
|
|
614
698
|
fs.mkdirSync(path.join(claudeSkillsDir, "bonnard-metabase-migrate"), { recursive: true });
|
|
615
699
|
fs.mkdirSync(path.join(claudeSkillsDir, "bonnard-design-guide"), { recursive: true });
|
|
616
700
|
fs.mkdirSync(path.join(claudeSkillsDir, "bonnard-build-dashboard"), { recursive: true });
|
|
617
|
-
|
|
618
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-get-started/SKILL.md"), path.join(claudeSkillsDir, "bonnard-get-started", "SKILL.md"),
|
|
619
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-metabase-migrate/SKILL.md"), path.join(claudeSkillsDir, "bonnard-metabase-migrate", "SKILL.md"),
|
|
620
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-design-guide/SKILL.md"), path.join(claudeSkillsDir, "bonnard-design-guide", "SKILL.md"),
|
|
621
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-build-dashboard/SKILL.md"), path.join(claudeSkillsDir, "bonnard-build-dashboard", "SKILL.md"),
|
|
622
|
-
mergeSettingsJson(loadJsonTemplate("claude/settings.json"), path.join(cwd, ".claude", "settings.json"),
|
|
701
|
+
writeBonnardSection(sharedBonnard, path.join(claudeRulesDir, "bonnard.md"), results, mode);
|
|
702
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-get-started/SKILL.md"), path.join(claudeSkillsDir, "bonnard-get-started", "SKILL.md"), results, mode);
|
|
703
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-metabase-migrate/SKILL.md"), path.join(claudeSkillsDir, "bonnard-metabase-migrate", "SKILL.md"), results, mode);
|
|
704
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-design-guide/SKILL.md"), path.join(claudeSkillsDir, "bonnard-design-guide", "SKILL.md"), results, mode);
|
|
705
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-build-dashboard/SKILL.md"), path.join(claudeSkillsDir, "bonnard-build-dashboard", "SKILL.md"), results, mode);
|
|
706
|
+
mergeSettingsJson(loadJsonTemplate("claude/settings.json"), path.join(cwd, ".claude", "settings.json"), results, mode);
|
|
623
707
|
const cursorRulesDir = path.join(cwd, ".cursor", "rules");
|
|
624
708
|
fs.mkdirSync(cursorRulesDir, { recursive: true });
|
|
625
|
-
writeTemplateFile(withCursorFrontmatter(sharedBonnard, "Bonnard semantic layer project context", true), path.join(cursorRulesDir, "bonnard.mdc"),
|
|
626
|
-
writeTemplateFile(loadTemplate("cursor/rules/bonnard-get-started.mdc"), path.join(cursorRulesDir, "bonnard-get-started.mdc"),
|
|
627
|
-
writeTemplateFile(loadTemplate("cursor/rules/bonnard-metabase-migrate.mdc"), path.join(cursorRulesDir, "bonnard-metabase-migrate.mdc"),
|
|
628
|
-
writeTemplateFile(loadTemplate("cursor/rules/bonnard-design-guide.mdc"), path.join(cursorRulesDir, "bonnard-design-guide.mdc"),
|
|
629
|
-
writeTemplateFile(loadTemplate("cursor/rules/bonnard-build-dashboard.mdc"), path.join(cursorRulesDir, "bonnard-build-dashboard.mdc"),
|
|
709
|
+
writeTemplateFile(withCursorFrontmatter(sharedBonnard, "Bonnard semantic layer project context", true), path.join(cursorRulesDir, "bonnard.mdc"), results, mode);
|
|
710
|
+
writeTemplateFile(loadTemplate("cursor/rules/bonnard-get-started.mdc"), path.join(cursorRulesDir, "bonnard-get-started.mdc"), results, mode);
|
|
711
|
+
writeTemplateFile(loadTemplate("cursor/rules/bonnard-metabase-migrate.mdc"), path.join(cursorRulesDir, "bonnard-metabase-migrate.mdc"), results, mode);
|
|
712
|
+
writeTemplateFile(loadTemplate("cursor/rules/bonnard-design-guide.mdc"), path.join(cursorRulesDir, "bonnard-design-guide.mdc"), results, mode);
|
|
713
|
+
writeTemplateFile(loadTemplate("cursor/rules/bonnard-build-dashboard.mdc"), path.join(cursorRulesDir, "bonnard-build-dashboard.mdc"), results, mode);
|
|
630
714
|
const codexSkillsDir = path.join(cwd, ".agents", "skills");
|
|
631
715
|
fs.mkdirSync(path.join(codexSkillsDir, "bonnard-get-started"), { recursive: true });
|
|
632
716
|
fs.mkdirSync(path.join(codexSkillsDir, "bonnard-metabase-migrate"), { recursive: true });
|
|
633
717
|
fs.mkdirSync(path.join(codexSkillsDir, "bonnard-design-guide"), { recursive: true });
|
|
634
718
|
fs.mkdirSync(path.join(codexSkillsDir, "bonnard-build-dashboard"), { recursive: true });
|
|
635
|
-
|
|
636
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-get-started/SKILL.md"), path.join(codexSkillsDir, "bonnard-get-started", "SKILL.md"),
|
|
637
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-metabase-migrate/SKILL.md"), path.join(codexSkillsDir, "bonnard-metabase-migrate", "SKILL.md"),
|
|
638
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-design-guide/SKILL.md"), path.join(codexSkillsDir, "bonnard-design-guide", "SKILL.md"),
|
|
639
|
-
writeTemplateFile(loadTemplate("claude/skills/bonnard-build-dashboard/SKILL.md"), path.join(codexSkillsDir, "bonnard-build-dashboard", "SKILL.md"),
|
|
640
|
-
return
|
|
641
|
-
}
|
|
642
|
-
|
|
719
|
+
writeBonnardSection(sharedBonnard, path.join(cwd, "AGENTS.md"), results, mode);
|
|
720
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-get-started/SKILL.md"), path.join(codexSkillsDir, "bonnard-get-started", "SKILL.md"), results, mode);
|
|
721
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-metabase-migrate/SKILL.md"), path.join(codexSkillsDir, "bonnard-metabase-migrate", "SKILL.md"), results, mode);
|
|
722
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-design-guide/SKILL.md"), path.join(codexSkillsDir, "bonnard-design-guide", "SKILL.md"), results, mode);
|
|
723
|
+
writeTemplateFile(loadTemplate("claude/skills/bonnard-build-dashboard/SKILL.md"), path.join(codexSkillsDir, "bonnard-build-dashboard", "SKILL.md"), results, mode);
|
|
724
|
+
return results;
|
|
725
|
+
}
|
|
726
|
+
function formatFileResult(result) {
|
|
727
|
+
switch (result.action) {
|
|
728
|
+
case "appended": return `${result.path} (appended)`;
|
|
729
|
+
case "merged": return `${result.path} (merged)`;
|
|
730
|
+
default: return result.path;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async function initCommand(options = {}) {
|
|
643
734
|
const cwd = process.cwd();
|
|
644
|
-
const projectName = path.basename(cwd);
|
|
645
735
|
const paths = getProjectPaths(cwd);
|
|
736
|
+
if (options.update) {
|
|
737
|
+
if (!fs.existsSync(paths.config)) {
|
|
738
|
+
console.log(pc.red("No bon.yaml found. Run `bon init` first."));
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
const env = detectProjectEnvironment(cwd);
|
|
742
|
+
const results = createAgentTemplates(cwd, env.tools.length > 0 || env.warehouse ? env : void 0, "update");
|
|
743
|
+
const updated = results.filter((r) => r.action === "updated");
|
|
744
|
+
const added = results.filter((r) => r.action === "added");
|
|
745
|
+
if (updated.length === 0 && added.length === 0) console.log(pc.green("All agent templates are up to date."));
|
|
746
|
+
else {
|
|
747
|
+
const parts = [];
|
|
748
|
+
if (updated.length > 0) parts.push(`updated ${updated.length} file${updated.length !== 1 ? "s" : ""}`);
|
|
749
|
+
if (added.length > 0) parts.push(`added ${added.length} new file${added.length !== 1 ? "s" : ""}`);
|
|
750
|
+
console.log(pc.green(`Agent templates: ${parts.join(", ")}.`));
|
|
751
|
+
for (const r of [...updated, ...added]) {
|
|
752
|
+
const label = r.action === "added" ? pc.cyan("new") : pc.yellow("updated");
|
|
753
|
+
console.log(` ${label} ${pc.dim(r.path)}`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const projectName = path.basename(cwd);
|
|
646
759
|
if (fs.existsSync(paths.config)) {
|
|
647
760
|
console.log(pc.red("A bon.yaml already exists in this directory."));
|
|
648
761
|
process.exit(1);
|
|
@@ -665,7 +778,7 @@ async function initCommand() {
|
|
|
665
778
|
if (agentFiles.length > 0) {
|
|
666
779
|
console.log();
|
|
667
780
|
console.log(pc.bold("Agent support:"));
|
|
668
|
-
for (const file of agentFiles) console.log(` ${pc.dim(file)}`);
|
|
781
|
+
for (const file of agentFiles) console.log(` ${pc.dim(formatFileResult(file))}`);
|
|
669
782
|
}
|
|
670
783
|
if (env.tools.length > 0 || env.warehouse) {
|
|
671
784
|
console.log();
|
|
@@ -677,12 +790,12 @@ async function initCommand() {
|
|
|
677
790
|
|
|
678
791
|
//#endregion
|
|
679
792
|
//#region src/commands/login.ts
|
|
680
|
-
const APP_URL$
|
|
793
|
+
const APP_URL$4 = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
681
794
|
const TIMEOUT_MS = 120 * 1e3;
|
|
682
795
|
async function loginCommand() {
|
|
683
796
|
const state = crypto.randomUUID();
|
|
684
797
|
const { port, close } = await startCallbackServer(state);
|
|
685
|
-
const url = `${APP_URL$
|
|
798
|
+
const url = `${APP_URL$4}/auth/device?state=${state}&port=${port}`;
|
|
686
799
|
console.log(pc.dim(`Opening browser to ${url}`));
|
|
687
800
|
const open = (await import("open")).default;
|
|
688
801
|
await open(url);
|
|
@@ -3879,15 +3992,24 @@ async function keysRevokeCommand(nameOrPrefix) {
|
|
|
3879
3992
|
|
|
3880
3993
|
//#endregion
|
|
3881
3994
|
//#region src/commands/dashboard/deploy.ts
|
|
3882
|
-
const APP_URL$
|
|
3995
|
+
const APP_URL$3 = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
3883
3996
|
/**
|
|
3884
3997
|
* Extract <title> content from HTML string
|
|
3885
3998
|
*/
|
|
3886
|
-
function
|
|
3999
|
+
function extractHtmlTitle(html) {
|
|
3887
4000
|
const match = html.match(/<title[^>]*>(.*?)<\/title>/is);
|
|
3888
4001
|
return match ? match[1].trim() : null;
|
|
3889
4002
|
}
|
|
3890
4003
|
/**
|
|
4004
|
+
* Extract title from YAML frontmatter in markdown
|
|
4005
|
+
*/
|
|
4006
|
+
function extractFrontmatterTitle(content) {
|
|
4007
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4008
|
+
if (!match) return null;
|
|
4009
|
+
const titleMatch = match[1].match(/^title:\s*["']?(.+?)["']?\s*$/m);
|
|
4010
|
+
return titleMatch ? titleMatch[1] : null;
|
|
4011
|
+
}
|
|
4012
|
+
/**
|
|
3891
4013
|
* Derive slug from filename: strip extension, lowercase, replace non-alphanumeric with hyphens
|
|
3892
4014
|
*/
|
|
3893
4015
|
function slugFromFilename(filename) {
|
|
@@ -3904,20 +4026,23 @@ async function dashboardDeployCommand(file, options) {
|
|
|
3904
4026
|
console.error(pc.red("File exceeds 2MB limit."));
|
|
3905
4027
|
process.exit(1);
|
|
3906
4028
|
}
|
|
4029
|
+
const format = path.extname(filePath).toLowerCase() === ".md" ? "markdown" : "html";
|
|
3907
4030
|
const slug = options.slug || slugFromFilename(file);
|
|
3908
|
-
const title = options.title ||
|
|
3909
|
-
console.log(pc.dim(`Deploying dashboard "${slug}"...`));
|
|
4031
|
+
const title = options.title || (format === "markdown" ? extractFrontmatterTitle(content) || slug : extractHtmlTitle(content) || slug);
|
|
4032
|
+
console.log(pc.dim(`Deploying ${format} dashboard "${slug}"...`));
|
|
3910
4033
|
try {
|
|
3911
4034
|
const dashboard = (await post("/api/dashboards", {
|
|
3912
4035
|
slug,
|
|
3913
4036
|
title,
|
|
3914
|
-
content
|
|
4037
|
+
content,
|
|
4038
|
+
format
|
|
3915
4039
|
})).dashboard;
|
|
3916
|
-
const url = `${APP_URL$
|
|
4040
|
+
const url = `${APP_URL$3}/d/${dashboard.org_slug}/${dashboard.slug}`;
|
|
3917
4041
|
console.log(pc.green(`Dashboard deployed successfully`));
|
|
3918
4042
|
console.log();
|
|
3919
4043
|
console.log(` ${pc.bold("Slug")} ${dashboard.slug}`);
|
|
3920
4044
|
console.log(` ${pc.bold("Title")} ${dashboard.title}`);
|
|
4045
|
+
console.log(` ${pc.bold("Format")} ${format}`);
|
|
3921
4046
|
console.log(` ${pc.bold("Version")} ${dashboard.version}`);
|
|
3922
4047
|
console.log(` ${pc.bold("URL")} ${url}`);
|
|
3923
4048
|
} catch (err) {
|
|
@@ -3928,7 +4053,7 @@ async function dashboardDeployCommand(file, options) {
|
|
|
3928
4053
|
|
|
3929
4054
|
//#endregion
|
|
3930
4055
|
//#region src/commands/dashboard/list.ts
|
|
3931
|
-
const APP_URL$
|
|
4056
|
+
const APP_URL$2 = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
3932
4057
|
async function dashboardListCommand() {
|
|
3933
4058
|
try {
|
|
3934
4059
|
const dashboards = (await get("/api/dashboards")).dashboards;
|
|
@@ -3949,7 +4074,7 @@ async function dashboardListCommand() {
|
|
|
3949
4074
|
].join(" ");
|
|
3950
4075
|
console.log(pc.dim(header));
|
|
3951
4076
|
for (const d of dashboards) {
|
|
3952
|
-
const url = `${APP_URL$
|
|
4077
|
+
const url = `${APP_URL$2}/d/${d.org_slug}/${d.slug}`;
|
|
3953
4078
|
const date = new Date(d.updated_at).toLocaleDateString();
|
|
3954
4079
|
const row = [
|
|
3955
4080
|
d.slug.padEnd(slugWidth),
|
|
@@ -3998,11 +4123,11 @@ async function dashboardRemoveCommand(slug, options) {
|
|
|
3998
4123
|
|
|
3999
4124
|
//#endregion
|
|
4000
4125
|
//#region src/commands/dashboard/open.ts
|
|
4001
|
-
const APP_URL = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
4126
|
+
const APP_URL$1 = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
4002
4127
|
async function dashboardOpenCommand(slug) {
|
|
4003
4128
|
try {
|
|
4004
4129
|
const result = await get(`/api/dashboards/${encodeURIComponent(slug)}`);
|
|
4005
|
-
const url = `${APP_URL}/d/${result.dashboard.org_slug}/${result.dashboard.slug}`;
|
|
4130
|
+
const url = `${APP_URL$1}/d/${result.dashboard.org_slug}/${result.dashboard.slug}`;
|
|
4006
4131
|
console.log(pc.dim(`Opening ${url}`));
|
|
4007
4132
|
await open(url);
|
|
4008
4133
|
} catch (err) {
|
|
@@ -4011,11 +4136,91 @@ async function dashboardOpenCommand(slug) {
|
|
|
4011
4136
|
}
|
|
4012
4137
|
}
|
|
4013
4138
|
|
|
4139
|
+
//#endregion
|
|
4140
|
+
//#region src/commands/dashboard/dev.ts
|
|
4141
|
+
const APP_URL = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
4142
|
+
function loadViewer() {
|
|
4143
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
4144
|
+
const viewerPath = path.resolve(dir, "..", "viewer.html");
|
|
4145
|
+
if (!fs.existsSync(viewerPath)) {
|
|
4146
|
+
console.error(pc.red("Viewer not found. Rebuild the CLI with `pnpm build`."));
|
|
4147
|
+
process.exit(1);
|
|
4148
|
+
}
|
|
4149
|
+
return fs.readFileSync(viewerPath, "utf-8");
|
|
4150
|
+
}
|
|
4151
|
+
async function dashboardDevCommand(file, options) {
|
|
4152
|
+
const filePath = path.resolve(file);
|
|
4153
|
+
if (!fs.existsSync(filePath)) {
|
|
4154
|
+
console.error(pc.red(`File not found: ${file}`));
|
|
4155
|
+
process.exit(1);
|
|
4156
|
+
}
|
|
4157
|
+
const creds = loadCredentials();
|
|
4158
|
+
if (!creds) {
|
|
4159
|
+
console.error(pc.red("Not logged in. Run `bon login` first."));
|
|
4160
|
+
process.exit(1);
|
|
4161
|
+
}
|
|
4162
|
+
const viewerHtml = loadViewer();
|
|
4163
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
4164
|
+
let debounce = null;
|
|
4165
|
+
fs.watch(filePath, () => {
|
|
4166
|
+
if (debounce) clearTimeout(debounce);
|
|
4167
|
+
debounce = setTimeout(() => {
|
|
4168
|
+
for (const client of sseClients) client.write("data: reload\n\n");
|
|
4169
|
+
}, 50);
|
|
4170
|
+
});
|
|
4171
|
+
const server = http.createServer((req, res) => {
|
|
4172
|
+
const url = req.url || "/";
|
|
4173
|
+
if (url === "/") {
|
|
4174
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4175
|
+
res.end(viewerHtml);
|
|
4176
|
+
return;
|
|
4177
|
+
}
|
|
4178
|
+
if (url === "/__bon/content") {
|
|
4179
|
+
res.writeHead(200, {
|
|
4180
|
+
"Content-Type": "text/plain",
|
|
4181
|
+
"Cache-Control": "no-cache"
|
|
4182
|
+
});
|
|
4183
|
+
res.end(fs.readFileSync(filePath, "utf-8"));
|
|
4184
|
+
return;
|
|
4185
|
+
}
|
|
4186
|
+
if (url === "/__bon/config") {
|
|
4187
|
+
res.writeHead(200, {
|
|
4188
|
+
"Content-Type": "application/json",
|
|
4189
|
+
"Cache-Control": "no-cache"
|
|
4190
|
+
});
|
|
4191
|
+
res.end(JSON.stringify({
|
|
4192
|
+
token: creds.token,
|
|
4193
|
+
baseUrl: APP_URL
|
|
4194
|
+
}));
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
if (url === "/__bon/events") {
|
|
4198
|
+
res.writeHead(200, {
|
|
4199
|
+
"Content-Type": "text/event-stream",
|
|
4200
|
+
"Cache-Control": "no-cache",
|
|
4201
|
+
Connection: "keep-alive"
|
|
4202
|
+
});
|
|
4203
|
+
sseClients.add(res);
|
|
4204
|
+
req.on("close", () => sseClients.delete(res));
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
res.writeHead(404);
|
|
4208
|
+
res.end();
|
|
4209
|
+
});
|
|
4210
|
+
const port = options.port ? parseInt(options.port, 10) : 0;
|
|
4211
|
+
server.listen(port, () => {
|
|
4212
|
+
const url = `http://localhost:${server.address().port}`;
|
|
4213
|
+
console.log(pc.green(`Dashboard preview running at ${pc.bold(url)}`));
|
|
4214
|
+
console.log(pc.dim(`Watching ${path.basename(filePath)} for changes...\n`));
|
|
4215
|
+
open(url);
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4014
4219
|
//#endregion
|
|
4015
4220
|
//#region src/bin/bon.ts
|
|
4016
4221
|
const { version } = createRequire(import.meta.url)("../../package.json");
|
|
4017
4222
|
program.name("bon").description("Bonnard semantic layer CLI").version(version);
|
|
4018
|
-
program.command("init").description("Create bon.yaml, bonnard/cubes/, bonnard/views/, .bon/, and agent templates (.claude/, .cursor/)").action(initCommand);
|
|
4223
|
+
program.command("init").description("Create bon.yaml, bonnard/cubes/, bonnard/views/, .bon/, and agent templates (.claude/, .cursor/)").option("--update", "Update agent templates to match installed CLI version").action(initCommand);
|
|
4019
4224
|
program.command("login").description("Authenticate with Bonnard via your browser").action(loginCommand);
|
|
4020
4225
|
program.command("logout").description("Remove stored credentials").action(logoutCommand);
|
|
4021
4226
|
program.command("whoami").description("Show current login status").option("--verify", "Verify session is still valid with the server").action(whoamiCommand);
|
|
@@ -4036,8 +4241,9 @@ const keys = program.command("keys").description("Manage API keys for the Bonnar
|
|
|
4036
4241
|
keys.command("list").description("List all API keys for your organization").action(keysListCommand);
|
|
4037
4242
|
keys.command("create").description("Create a new API key").requiredOption("--name <name>", "Key name (e.g. 'Production SDK')").requiredOption("--type <type>", "Key type: publishable or secret").action(keysCreateCommand);
|
|
4038
4243
|
keys.command("revoke").description("Revoke an API key by name or prefix").argument("<name-or-prefix>", "Key name or key prefix to revoke").action(keysRevokeCommand);
|
|
4039
|
-
const dashboard = program.command("dashboard").description("Manage hosted
|
|
4040
|
-
dashboard.command("
|
|
4244
|
+
const dashboard = program.command("dashboard").description("Manage hosted dashboards");
|
|
4245
|
+
dashboard.command("dev").description("Preview a markdown dashboard locally with live reload").argument("<file>", "Path to dashboard .md file").option("--port <port>", "Server port (default: random available port)").action(dashboardDevCommand);
|
|
4246
|
+
dashboard.command("deploy").description("Deploy an HTML or markdown file as a hosted dashboard").argument("<file>", "Path to HTML or markdown file").option("--slug <slug>", "Dashboard slug (defaults to filename)").option("--title <title>", "Dashboard title (defaults to <title> tag or slug)").action(dashboardDeployCommand);
|
|
4041
4247
|
dashboard.command("list").description("List deployed dashboards").action(dashboardListCommand);
|
|
4042
4248
|
dashboard.command("remove").description("Remove a deployed dashboard").argument("<slug>", "Dashboard slug to remove").option("--force", "Skip confirmation prompt").action(dashboardRemoveCommand);
|
|
4043
4249
|
dashboard.command("open").description("Open a deployed dashboard in the browser").argument("<slug>", "Dashboard slug to open").action(dashboardOpenCommand);
|