@runcontext/ui 0.5.11 → 0.6.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.cjs +381 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +381 -116
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/static/app.js +16 -0
- package/static/setup.css +1069 -47
- package/static/uxd.css +286 -1
- package/static/setup.js +0 -1
package/dist/index.mjs
CHANGED
|
@@ -357,13 +357,12 @@ function uploadRoutes(contextDir) {
|
|
|
357
357
|
|
|
358
358
|
// src/routes/api/pipeline.ts
|
|
359
359
|
import { Hono as Hono4 } from "hono";
|
|
360
|
-
import { execFile as execFileCb } from "child_process";
|
|
360
|
+
import { execFile as execFileCb, spawn } from "child_process";
|
|
361
361
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
362
|
-
import { existsSync as existsSync3
|
|
363
|
-
import { join as join4, dirname } from "path";
|
|
362
|
+
import { existsSync as existsSync3 } from "fs";
|
|
363
|
+
import { join as join4, dirname, resolve } from "path";
|
|
364
364
|
import { promisify } from "util";
|
|
365
365
|
import { fileURLToPath } from "url";
|
|
366
|
-
import { parse as parseYaml } from "yaml";
|
|
367
366
|
|
|
368
367
|
// src/events.ts
|
|
369
368
|
import { EventEmitter } from "events";
|
|
@@ -399,6 +398,10 @@ function resolveCliBin() {
|
|
|
399
398
|
}
|
|
400
399
|
} catch {
|
|
401
400
|
}
|
|
401
|
+
const cwdCli = join4(process.cwd(), "packages", "cli", "dist", "index.js");
|
|
402
|
+
if (existsSync3(cwdCli)) {
|
|
403
|
+
return { cmd: process.execPath, prefix: [cwdCli] };
|
|
404
|
+
}
|
|
402
405
|
if (process.argv[1] && existsSync3(process.argv[1])) {
|
|
403
406
|
return { cmd: process.execPath, prefix: [process.argv[1]] };
|
|
404
407
|
}
|
|
@@ -469,35 +472,47 @@ function pipelineRoutes(rootDir, contextDir) {
|
|
|
469
472
|
if (!run) return c.json({ error: "Not found" }, 404);
|
|
470
473
|
return c.json(run);
|
|
471
474
|
});
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
475
|
+
let mcpProcess = null;
|
|
476
|
+
app.post("/api/mcp/start", (c) => {
|
|
477
|
+
if (mcpProcess && !mcpProcess.killed) {
|
|
478
|
+
return c.json({ ok: true, status: "already_running" });
|
|
479
|
+
}
|
|
480
|
+
const cli = resolveCliBin();
|
|
481
|
+
mcpProcess = spawn(cli.cmd, [...cli.prefix, "serve"], {
|
|
482
|
+
cwd: rootDir,
|
|
483
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
484
|
+
detached: false,
|
|
485
|
+
env: { ...process.env, NODE_OPTIONS: "--no-deprecation" }
|
|
486
|
+
});
|
|
487
|
+
mcpProcess.on("exit", () => {
|
|
488
|
+
mcpProcess = null;
|
|
489
|
+
});
|
|
490
|
+
mcpProcess.on("error", () => {
|
|
491
|
+
mcpProcess = null;
|
|
492
|
+
});
|
|
493
|
+
return c.json({ ok: true, status: "started" });
|
|
494
|
+
});
|
|
495
|
+
app.post("/api/mcp/stop", (c) => {
|
|
496
|
+
if (mcpProcess && !mcpProcess.killed) {
|
|
497
|
+
mcpProcess.kill();
|
|
498
|
+
mcpProcess = null;
|
|
486
499
|
}
|
|
500
|
+
return c.json({ ok: true, status: "stopped" });
|
|
501
|
+
});
|
|
502
|
+
app.get("/api/mcp/status", (c) => {
|
|
503
|
+
const running = mcpProcess !== null && !mcpProcess.killed;
|
|
504
|
+
return c.json({ running });
|
|
505
|
+
});
|
|
506
|
+
app.get("/api/mcp-config", (c) => {
|
|
487
507
|
const cli = resolveCliBin();
|
|
508
|
+
const absRoot = resolve(rootDir);
|
|
488
509
|
const mcpServers = {
|
|
489
510
|
runcontext: {
|
|
490
511
|
command: cli.cmd,
|
|
491
512
|
args: [...cli.prefix, "serve"],
|
|
492
|
-
cwd:
|
|
513
|
+
cwd: absRoot
|
|
493
514
|
}
|
|
494
515
|
};
|
|
495
|
-
if (connection) {
|
|
496
|
-
mcpServers["runcontext-db"] = {
|
|
497
|
-
command: "npx",
|
|
498
|
-
args: ["--yes", "@runcontext/db", "--url", connection]
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
516
|
return c.json({ mcpServers });
|
|
502
517
|
});
|
|
503
518
|
return app;
|
|
@@ -521,10 +536,16 @@ function buildCliArgs(stage, dataSource) {
|
|
|
521
536
|
if (dataSource) args.push("--source", dataSource);
|
|
522
537
|
return args;
|
|
523
538
|
}
|
|
524
|
-
case "verify":
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return
|
|
539
|
+
case "verify": {
|
|
540
|
+
const args = ["verify"];
|
|
541
|
+
if (dataSource) args.push("--source", dataSource);
|
|
542
|
+
return args;
|
|
543
|
+
}
|
|
544
|
+
case "autofix": {
|
|
545
|
+
const args = ["fix"];
|
|
546
|
+
if (dataSource) args.push("--source", dataSource);
|
|
547
|
+
return args;
|
|
548
|
+
}
|
|
528
549
|
case "agent-instructions":
|
|
529
550
|
return ["build"];
|
|
530
551
|
}
|
|
@@ -550,7 +571,7 @@ async function executePipeline(run, rootDir, contextDir, dataSource, sessionId)
|
|
|
550
571
|
const cli = resolveCliBin();
|
|
551
572
|
const { stdout } = await execFile(cli.cmd, [...cli.prefix, ...cliArgs], {
|
|
552
573
|
cwd: rootDir,
|
|
553
|
-
timeout:
|
|
574
|
+
timeout: 3e5,
|
|
554
575
|
env: {
|
|
555
576
|
...process.env,
|
|
556
577
|
NODE_OPTIONS: "--max-old-space-size=4096 --no-deprecation"
|
|
@@ -582,8 +603,10 @@ async function executePipeline(run, rootDir, contextDir, dataSource, sessionId)
|
|
|
582
603
|
continue;
|
|
583
604
|
}
|
|
584
605
|
stage.status = "error";
|
|
585
|
-
|
|
606
|
+
const errDetail = execErr.stderr || (err instanceof Error ? err.message : String(err));
|
|
607
|
+
stage.error = errDetail;
|
|
586
608
|
stage.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
609
|
+
console.error(`[pipeline] Stage ${stage.stage} failed:`, errDetail);
|
|
587
610
|
if (sessionId) {
|
|
588
611
|
setupBus.emitEvent({
|
|
589
612
|
type: "pipeline:stage",
|
|
@@ -602,36 +625,236 @@ async function executePipeline(run, rootDir, contextDir, dataSource, sessionId)
|
|
|
602
625
|
import { Hono as Hono5 } from "hono";
|
|
603
626
|
import * as fs4 from "fs";
|
|
604
627
|
import * as path4 from "path";
|
|
628
|
+
import { execFile as execFileCb2 } from "child_process";
|
|
629
|
+
import { promisify as promisify2 } from "util";
|
|
605
630
|
import { parse as parse3 } from "yaml";
|
|
631
|
+
var execFile2 = promisify2(execFileCb2);
|
|
632
|
+
function detectTier(contextDir, sourceName) {
|
|
633
|
+
const rulesPath = path4.join(contextDir, "rules", `${sourceName}.rules.yaml`);
|
|
634
|
+
const modelPath = path4.join(contextDir, "models", `${sourceName}.osi.yaml`);
|
|
635
|
+
if (fs4.existsSync(rulesPath)) {
|
|
636
|
+
try {
|
|
637
|
+
const rules = parse3(fs4.readFileSync(rulesPath, "utf-8"));
|
|
638
|
+
const realQueries = (rules?.golden_queries || []).filter(
|
|
639
|
+
(q) => q.sql && !q.sql.includes("TODO") && !q.sql.includes("table_name") && q.question && !q.question.includes("TODO")
|
|
640
|
+
);
|
|
641
|
+
const realGuardrails = (rules?.guardrail_filters || rules?.guardrails || []).filter(
|
|
642
|
+
(g) => g.name && !g.name.includes("TODO")
|
|
643
|
+
);
|
|
644
|
+
if (realQueries.length >= 3 && realGuardrails.length >= 1) return "gold";
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (fs4.existsSync(modelPath)) {
|
|
649
|
+
try {
|
|
650
|
+
const model = parse3(fs4.readFileSync(modelPath, "utf-8"));
|
|
651
|
+
let datasets = model?.tables || model?.models || [];
|
|
652
|
+
if (datasets.length === 0 && Array.isArray(model?.semantic_model)) {
|
|
653
|
+
for (const sm of model.semantic_model) {
|
|
654
|
+
if (Array.isArray(sm.datasets)) datasets.push(...sm.datasets);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
let fieldsWithSamples = 0;
|
|
658
|
+
for (const d of datasets) {
|
|
659
|
+
for (const f of d.columns || d.fields || []) {
|
|
660
|
+
if (f.sample_values?.length > 0) fieldsWithSamples++;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (fieldsWithSamples >= 2) return "silver";
|
|
664
|
+
} catch {
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return "bronze";
|
|
668
|
+
}
|
|
669
|
+
function countTablesAndColumns(contextDir, sourceName) {
|
|
670
|
+
const modelPath = path4.join(contextDir, "models", `${sourceName}.osi.yaml`);
|
|
671
|
+
if (!fs4.existsSync(modelPath)) return { tables: 0, columns: 0 };
|
|
672
|
+
try {
|
|
673
|
+
const model = parse3(fs4.readFileSync(modelPath, "utf-8"));
|
|
674
|
+
let datasets = model?.tables || model?.models || [];
|
|
675
|
+
if (datasets.length === 0 && Array.isArray(model?.semantic_model)) {
|
|
676
|
+
for (const sm of model.semantic_model) {
|
|
677
|
+
if (Array.isArray(sm.datasets)) datasets.push(...sm.datasets);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const columns = datasets.reduce((sum, t) => sum + (t.columns?.length || t.fields?.length || 0), 0);
|
|
681
|
+
return { tables: datasets.length, columns };
|
|
682
|
+
} catch {
|
|
683
|
+
return { tables: 0, columns: 0 };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
606
686
|
function productsRoutes(contextDir) {
|
|
607
687
|
const app = new Hono5();
|
|
608
688
|
app.get("/api/products", (c) => {
|
|
609
|
-
|
|
610
|
-
if (!fs4.existsSync(productsDir)) {
|
|
689
|
+
if (!fs4.existsSync(contextDir)) {
|
|
611
690
|
return c.json([]);
|
|
612
691
|
}
|
|
613
692
|
const products = [];
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
693
|
+
const briefFiles = fs4.readdirSync(contextDir).filter((f) => f.endsWith(".context-brief.yaml"));
|
|
694
|
+
for (const briefFile of briefFiles) {
|
|
695
|
+
const briefPath = path4.join(contextDir, briefFile);
|
|
696
|
+
try {
|
|
697
|
+
const brief = parse3(fs4.readFileSync(briefPath, "utf-8"));
|
|
698
|
+
const name = brief?.product_name || briefFile.replace(".context-brief.yaml", "");
|
|
699
|
+
const modelsDir = path4.join(contextDir, "models");
|
|
700
|
+
let sourceName = "";
|
|
701
|
+
const briefSource = brief?.data_sources?.[0]?.name || brief?.data_source || "";
|
|
702
|
+
if (briefSource && fs4.existsSync(path4.join(modelsDir, `${briefSource}.osi.yaml`))) {
|
|
703
|
+
sourceName = briefSource;
|
|
704
|
+
} else if (fs4.existsSync(modelsDir)) {
|
|
705
|
+
const modelFiles = fs4.readdirSync(modelsDir).filter((f) => f.endsWith(".osi.yaml"));
|
|
706
|
+
if (modelFiles.length > 0) {
|
|
707
|
+
sourceName = modelFiles[0].replace(".osi.yaml", "");
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const { tables, columns } = sourceName ? countTablesAndColumns(contextDir, sourceName) : { tables: 0, columns: 0 };
|
|
711
|
+
const tier = sourceName ? detectTier(contextDir, sourceName) : "bronze";
|
|
712
|
+
products.push({
|
|
713
|
+
name,
|
|
714
|
+
description: brief?.description,
|
|
715
|
+
sensitivity: brief?.sensitivity,
|
|
716
|
+
tier,
|
|
717
|
+
tables,
|
|
718
|
+
columns,
|
|
719
|
+
hasBrief: true
|
|
720
|
+
});
|
|
721
|
+
} catch {
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const seen = /* @__PURE__ */ new Set();
|
|
725
|
+
const unique = products.filter((p) => {
|
|
726
|
+
if (seen.has(p.name)) return false;
|
|
727
|
+
seen.add(p.name);
|
|
728
|
+
return true;
|
|
617
729
|
});
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
730
|
+
return c.json(unique);
|
|
731
|
+
});
|
|
732
|
+
app.get("/api/products/:name/detail", (c) => {
|
|
733
|
+
const modelsDir = path4.join(contextDir, "models");
|
|
734
|
+
const rulesDir = path4.join(contextDir, "rules");
|
|
735
|
+
const govDir = path4.join(contextDir, "governance");
|
|
736
|
+
const glossaryDir = path4.join(contextDir, "glossary");
|
|
737
|
+
const ownersDir = path4.join(contextDir, "owners");
|
|
738
|
+
const modelFiles = fs4.existsSync(modelsDir) ? fs4.readdirSync(modelsDir).filter((f) => f.endsWith(".osi.yaml")) : [];
|
|
739
|
+
const sourceName = modelFiles.length > 0 ? modelFiles[0].replace(".osi.yaml", "") : "";
|
|
740
|
+
let tables = [];
|
|
741
|
+
let modelYaml = "";
|
|
742
|
+
if (sourceName) {
|
|
743
|
+
const modelPath = path4.join(modelsDir, modelFiles[0]);
|
|
744
|
+
modelYaml = fs4.readFileSync(modelPath, "utf-8");
|
|
745
|
+
try {
|
|
746
|
+
const model = parse3(modelYaml);
|
|
747
|
+
let datasets = model?.tables || model?.models || [];
|
|
748
|
+
if (datasets.length === 0 && Array.isArray(model?.semantic_model)) {
|
|
749
|
+
for (const sm of model.semantic_model) {
|
|
750
|
+
if (Array.isArray(sm.datasets)) datasets.push(...sm.datasets);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
tables = datasets.map((d) => {
|
|
754
|
+
const fields = d.columns || d.fields || [];
|
|
755
|
+
return {
|
|
756
|
+
name: d.name,
|
|
757
|
+
description: d.description || "",
|
|
758
|
+
fields: fields.map((f) => ({
|
|
759
|
+
name: f.name,
|
|
760
|
+
type: f.type || f.data_type || "",
|
|
761
|
+
description: f.description || "",
|
|
762
|
+
sampleValues: f.sample_values || [],
|
|
763
|
+
semanticRole: f.semantic_role || ""
|
|
764
|
+
}))
|
|
765
|
+
};
|
|
766
|
+
});
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
let rules = {};
|
|
771
|
+
let rulesYaml = "";
|
|
772
|
+
if (sourceName) {
|
|
773
|
+
const rulesPath = path4.join(rulesDir, `${sourceName}.rules.yaml`);
|
|
774
|
+
if (fs4.existsSync(rulesPath)) {
|
|
775
|
+
rulesYaml = fs4.readFileSync(rulesPath, "utf-8");
|
|
776
|
+
try {
|
|
777
|
+
rules = parse3(rulesYaml) || {};
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
let governance = {};
|
|
783
|
+
let govYaml = "";
|
|
784
|
+
if (sourceName) {
|
|
785
|
+
const govPath = path4.join(govDir, `${sourceName}.governance.yaml`);
|
|
786
|
+
if (fs4.existsSync(govPath)) {
|
|
787
|
+
govYaml = fs4.readFileSync(govPath, "utf-8");
|
|
788
|
+
try {
|
|
789
|
+
governance = parse3(govYaml) || {};
|
|
790
|
+
} catch {
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const glossary = [];
|
|
795
|
+
if (fs4.existsSync(glossaryDir)) {
|
|
796
|
+
for (const f of fs4.readdirSync(glossaryDir).filter((f2) => f2.endsWith(".term.yaml"))) {
|
|
625
797
|
try {
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
sensitivity = brief?.sensitivity;
|
|
798
|
+
const term = parse3(fs4.readFileSync(path4.join(glossaryDir, f), "utf-8"));
|
|
799
|
+
if (term) glossary.push(term);
|
|
629
800
|
} catch {
|
|
630
801
|
}
|
|
631
802
|
}
|
|
632
|
-
products.push({ name, description, sensitivity, hasBrief });
|
|
633
803
|
}
|
|
634
|
-
|
|
804
|
+
const owners = [];
|
|
805
|
+
if (fs4.existsSync(ownersDir)) {
|
|
806
|
+
for (const f of fs4.readdirSync(ownersDir).filter((f2) => f2.endsWith(".owner.yaml"))) {
|
|
807
|
+
try {
|
|
808
|
+
const owner = parse3(fs4.readFileSync(path4.join(ownersDir, f), "utf-8"));
|
|
809
|
+
if (owner) owners.push(owner);
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return c.json({
|
|
815
|
+
tables,
|
|
816
|
+
rules: {
|
|
817
|
+
joinRules: rules.join_rules || [],
|
|
818
|
+
goldenQueries: rules.golden_queries || [],
|
|
819
|
+
guardrails: rules.guardrail_filters || rules.guardrails || [],
|
|
820
|
+
grainStatements: rules.grain_statements || []
|
|
821
|
+
},
|
|
822
|
+
governance,
|
|
823
|
+
glossary,
|
|
824
|
+
owners,
|
|
825
|
+
yaml: {
|
|
826
|
+
model: modelYaml.slice(0, 5e4),
|
|
827
|
+
rules: rulesYaml.slice(0, 2e4),
|
|
828
|
+
governance: govYaml.slice(0, 1e4)
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
app.get("/api/tier", async (c) => {
|
|
833
|
+
const cwdCli = path4.join(process.cwd(), "packages", "cli", "dist", "index.js");
|
|
834
|
+
const cliPath = fs4.existsSync(cwdCli) ? cwdCli : null;
|
|
835
|
+
if (!cliPath) return c.json({ tier: "unknown", output: "CLI not found" });
|
|
836
|
+
try {
|
|
837
|
+
const { stdout } = await execFile2(process.execPath, [cliPath, "tier"], {
|
|
838
|
+
cwd: process.cwd(),
|
|
839
|
+
timeout: 15e3,
|
|
840
|
+
env: { ...process.env, NODE_OPTIONS: "--no-deprecation" }
|
|
841
|
+
});
|
|
842
|
+
const tierMatch = stdout.match(/(BRONZE|SILVER|GOLD)/i);
|
|
843
|
+
return c.json({ tier: tierMatch ? tierMatch[1].toLowerCase() : "unknown", output: stdout });
|
|
844
|
+
} catch (err) {
|
|
845
|
+
const stdout = err?.stdout || "";
|
|
846
|
+
const tierMatch = stdout.match(/(BRONZE|SILVER|GOLD)/i);
|
|
847
|
+
return c.json({ tier: tierMatch ? tierMatch[1].toLowerCase() : "unknown", output: stdout || err.message });
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
app.get("/api/agent-instructions", (c) => {
|
|
851
|
+
const distInstructions = path4.join(process.cwd(), "dist", "AGENT_INSTRUCTIONS.md");
|
|
852
|
+
const cliInstructions = path4.join(process.cwd(), "packages", "cli", "assets", "AGENT_INSTRUCTIONS.md");
|
|
853
|
+
const instrPath = fs4.existsSync(distInstructions) ? distInstructions : fs4.existsSync(cliInstructions) ? cliInstructions : null;
|
|
854
|
+
if (!instrPath) {
|
|
855
|
+
return c.json({ instructions: null, error: "Agent instructions not found" });
|
|
856
|
+
}
|
|
857
|
+
return c.json({ instructions: fs4.readFileSync(instrPath, "utf-8") });
|
|
635
858
|
});
|
|
636
859
|
return app;
|
|
637
860
|
}
|
|
@@ -644,7 +867,7 @@ import {
|
|
|
644
867
|
} from "@runcontext/core";
|
|
645
868
|
import * as fs5 from "fs";
|
|
646
869
|
import * as path5 from "path";
|
|
647
|
-
import { parse as
|
|
870
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
648
871
|
function authRoutes(rootDir) {
|
|
649
872
|
const app = new Hono6();
|
|
650
873
|
const registry = createDefaultRegistry();
|
|
@@ -734,11 +957,17 @@ function authRoutes(rootDir) {
|
|
|
734
957
|
let config = {};
|
|
735
958
|
if (fs5.existsSync(configPath)) {
|
|
736
959
|
try {
|
|
737
|
-
config =
|
|
960
|
+
config = parseYaml(fs5.readFileSync(configPath, "utf-8")) ?? {};
|
|
738
961
|
} catch {
|
|
739
962
|
}
|
|
740
963
|
}
|
|
741
|
-
|
|
964
|
+
const existingSources = config.data_sources ?? {};
|
|
965
|
+
for (const [key, val] of Object.entries(existingSources)) {
|
|
966
|
+
if (val && typeof val === "object" && !val.connection && !val.path) {
|
|
967
|
+
delete existingSources[key];
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
config.data_sources = existingSources;
|
|
742
971
|
const sourceName = (database.name || database.database || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
743
972
|
config.data_sources[sourceName] = { adapter: database.adapter, connection: connStr };
|
|
744
973
|
fs5.writeFileSync(configPath, stringifyYaml(config), "utf-8");
|
|
@@ -753,9 +982,9 @@ function authRoutes(rootDir) {
|
|
|
753
982
|
|
|
754
983
|
// src/routes/api/suggest-brief.ts
|
|
755
984
|
import { Hono as Hono7 } from "hono";
|
|
756
|
-
import { execFile as
|
|
757
|
-
import { promisify as
|
|
758
|
-
var
|
|
985
|
+
import { execFile as execFileCb3 } from "child_process";
|
|
986
|
+
import { promisify as promisify3 } from "util";
|
|
987
|
+
var execFile3 = promisify3(execFileCb3);
|
|
759
988
|
function suggestBriefRoutes(rootDir) {
|
|
760
989
|
const app = new Hono7();
|
|
761
990
|
app.post("/api/suggest-brief", async (c) => {
|
|
@@ -778,7 +1007,7 @@ function suggestBriefRoutes(rootDir) {
|
|
|
778
1007
|
let ownerEmail = "";
|
|
779
1008
|
let ownerTeam = "";
|
|
780
1009
|
try {
|
|
781
|
-
const { stdout: name } = await
|
|
1010
|
+
const { stdout: name } = await execFile3("git", ["config", "user.name"], {
|
|
782
1011
|
cwd: rootDir,
|
|
783
1012
|
timeout: 3e3
|
|
784
1013
|
});
|
|
@@ -786,7 +1015,7 @@ function suggestBriefRoutes(rootDir) {
|
|
|
786
1015
|
} catch {
|
|
787
1016
|
}
|
|
788
1017
|
try {
|
|
789
|
-
const { stdout: email } = await
|
|
1018
|
+
const { stdout: email } = await execFile3("git", ["config", "user.email"], {
|
|
790
1019
|
cwd: rootDir,
|
|
791
1020
|
timeout: 3e3
|
|
792
1021
|
});
|
|
@@ -890,26 +1119,38 @@ function createApp(opts) {
|
|
|
890
1119
|
app.get("/setup", (c) => {
|
|
891
1120
|
return c.html(setupPageHTML());
|
|
892
1121
|
});
|
|
1122
|
+
app.get("/planes", (c) => {
|
|
1123
|
+
return c.html(pageHTML({
|
|
1124
|
+
title: "Semantic Planes",
|
|
1125
|
+
activePage: "planes",
|
|
1126
|
+
contentId: "page-content"
|
|
1127
|
+
}));
|
|
1128
|
+
});
|
|
1129
|
+
app.get("/analytics", (c) => {
|
|
1130
|
+
return c.html(pageHTML({
|
|
1131
|
+
title: "Analytics",
|
|
1132
|
+
activePage: "analytics",
|
|
1133
|
+
contentId: "page-content"
|
|
1134
|
+
}));
|
|
1135
|
+
});
|
|
1136
|
+
app.get("/settings", (c) => {
|
|
1137
|
+
return c.html(pageHTML({
|
|
1138
|
+
title: "Settings",
|
|
1139
|
+
activePage: "settings",
|
|
1140
|
+
contentId: "page-content"
|
|
1141
|
+
}));
|
|
1142
|
+
});
|
|
893
1143
|
app.get("/", (c) => c.redirect("/setup"));
|
|
894
1144
|
return app;
|
|
895
1145
|
}
|
|
896
|
-
function
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
905
|
-
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
906
|
-
<link rel="stylesheet" href="/static/uxd.css" />
|
|
907
|
-
<link rel="stylesheet" href="/static/setup.css" />
|
|
908
|
-
</head>
|
|
909
|
-
<body>
|
|
910
|
-
<div class="app-shell">
|
|
911
|
-
<!-- Sidebar -->
|
|
912
|
-
<aside class="sidebar">
|
|
1146
|
+
function sidebarHTML(activePage) {
|
|
1147
|
+
const nav = (page, href, label) => {
|
|
1148
|
+
const isActive = activePage === page;
|
|
1149
|
+
return `<a class="nav-item${isActive ? " active" : ""}" href="${href}">
|
|
1150
|
+
<span>${label}</span>
|
|
1151
|
+
</a>`;
|
|
1152
|
+
};
|
|
1153
|
+
return `<aside class="sidebar">
|
|
913
1154
|
<div class="sidebar-brand">
|
|
914
1155
|
<svg class="brand-chevron" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
915
1156
|
<path d="M4 4l8 8-8 8" stroke="#c9a55a" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
@@ -921,42 +1162,22 @@ function setupPageHTML() {
|
|
|
921
1162
|
<span class="brand-badge">Local</span>
|
|
922
1163
|
</div>
|
|
923
1164
|
<nav class="sidebar-nav">
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
<
|
|
928
|
-
<span>Semantic Planes</span>
|
|
929
|
-
<svg class="lock-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
930
|
-
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
|
931
|
-
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
|
932
|
-
</svg>
|
|
933
|
-
</a>
|
|
934
|
-
<a class="nav-item locked" data-nav="analytics">
|
|
935
|
-
<span>Analytics</span>
|
|
936
|
-
<svg class="lock-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
937
|
-
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
|
938
|
-
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
|
939
|
-
</svg>
|
|
940
|
-
</a>
|
|
941
|
-
<a class="nav-item" data-nav="mcp">
|
|
1165
|
+
${nav("setup", "/setup", "Setup")}
|
|
1166
|
+
${nav("planes", "/planes", "Semantic Planes")}
|
|
1167
|
+
${nav("analytics", "/analytics", "Analytics")}
|
|
1168
|
+
<div class="nav-item mcp-toggle" id="mcp-nav-toggle" title="Click to start/stop MCP server" style="cursor:pointer">
|
|
942
1169
|
<span class="status-dot" id="mcp-status-dot"></span>
|
|
943
1170
|
<span>MCP Server</span>
|
|
944
1171
|
<span class="nav-detail" id="mcp-status-text">checking...</span>
|
|
945
|
-
</
|
|
946
|
-
|
|
947
|
-
<span>Settings</span>
|
|
948
|
-
<svg class="lock-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
949
|
-
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
|
950
|
-
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
|
951
|
-
</svg>
|
|
952
|
-
</a>
|
|
1172
|
+
</div>
|
|
1173
|
+
${nav("settings", "/settings", "Settings")}
|
|
953
1174
|
</nav>
|
|
954
1175
|
<div class="sidebar-status">
|
|
955
1176
|
<div class="status-row">
|
|
956
1177
|
<span class="status-dot" id="db-status-dot"></span>
|
|
957
1178
|
<span id="db-status-text">No database</span>
|
|
958
1179
|
</div>
|
|
959
|
-
<div class="status-row">
|
|
1180
|
+
<div class="status-row mcp-toggle" id="mcp-toggle-row" title="Click to start/stop MCP server">
|
|
960
1181
|
<span class="status-dot" id="mcp-server-dot"></span>
|
|
961
1182
|
<span id="mcp-server-text">MCP stopped</span>
|
|
962
1183
|
</div>
|
|
@@ -964,36 +1185,80 @@ function setupPageHTML() {
|
|
|
964
1185
|
<span class="tier-badge" id="tier-badge">Free</span>
|
|
965
1186
|
</div>
|
|
966
1187
|
</div>
|
|
967
|
-
|
|
1188
|
+
<div class="sidebar-security">
|
|
1189
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--rc-color-status-success)" stroke-width="2">
|
|
1190
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
1191
|
+
</svg>
|
|
1192
|
+
<span>Local-only processing</span>
|
|
1193
|
+
</div>
|
|
1194
|
+
</aside>`;
|
|
1195
|
+
}
|
|
1196
|
+
function footerHTML() {
|
|
1197
|
+
return `<footer class="app-footer">
|
|
1198
|
+
<span>Powered by <a href="https://runcontext.dev" target="_blank" rel="noopener">RunContext</a></span>
|
|
1199
|
+
<span class="footer-links">
|
|
1200
|
+
<a href="https://docs.runcontext.dev" target="_blank" rel="noopener">Docs</a>
|
|
1201
|
+
<span class="footer-sep">·</span>
|
|
1202
|
+
<a href="https://runcontext.dev/pricing" target="_blank" rel="noopener">Cloud</a>
|
|
1203
|
+
<span class="footer-sep">·</span>
|
|
1204
|
+
<a href="https://github.com/Quiet-Victory-Labs/runcontext" target="_blank" rel="noopener">GitHub</a>
|
|
1205
|
+
</span>
|
|
1206
|
+
</footer>`;
|
|
1207
|
+
}
|
|
1208
|
+
function pageHTML(opts) {
|
|
1209
|
+
const isSetup = opts.activePage === "setup";
|
|
1210
|
+
const headerContent = isSetup ? `<div class="header-stepper" id="stepper"></div>` : `<h1 class="header-title">${opts.title}</h1>`;
|
|
1211
|
+
const lockedTooltip = isSetup ? `
|
|
1212
|
+
<!-- Locked tooltip (hidden by default) -->
|
|
1213
|
+
<div class="locked-tooltip" id="locked-tooltip" style="display:none">
|
|
1214
|
+
<p><strong>Cloud Feature</strong></p>
|
|
1215
|
+
<p>This feature is available on RunContext Cloud with team collaboration, hosted endpoints, and analytics.</p>
|
|
1216
|
+
<a href="https://runcontext.dev/pricing" target="_blank" rel="noopener">View plans \u2192</a>
|
|
1217
|
+
</div>` : "";
|
|
1218
|
+
return `<!DOCTYPE html>
|
|
1219
|
+
<html lang="en">
|
|
1220
|
+
<head>
|
|
1221
|
+
<meta charset="utf-8" />
|
|
1222
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1223
|
+
<title>RunContext \u2014 ${opts.title}</title>
|
|
1224
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
1225
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
1226
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
1227
|
+
<link rel="stylesheet" href="/static/uxd.css" />
|
|
1228
|
+
<link rel="stylesheet" href="/static/setup.css" />
|
|
1229
|
+
</head>
|
|
1230
|
+
<body data-page="${opts.activePage}">
|
|
1231
|
+
<div class="app-shell">
|
|
1232
|
+
<!-- Sidebar -->
|
|
1233
|
+
${sidebarHTML(opts.activePage)}
|
|
968
1234
|
|
|
969
1235
|
<!-- Header -->
|
|
970
1236
|
<header class="app-header">
|
|
971
|
-
|
|
1237
|
+
${headerContent}
|
|
972
1238
|
</header>
|
|
973
1239
|
|
|
974
1240
|
<!-- Main Content -->
|
|
975
1241
|
<main class="main-content">
|
|
976
|
-
<div class="content-wrapper" id="
|
|
1242
|
+
<div class="content-wrapper" id="${opts.contentId}"></div>
|
|
977
1243
|
</main>
|
|
978
1244
|
|
|
979
1245
|
<!-- Footer -->
|
|
980
|
-
|
|
981
|
-
<span>Powered by RunContext · Open Semantic Interchange</span>
|
|
982
|
-
</footer>
|
|
1246
|
+
${footerHTML()}
|
|
983
1247
|
</div>
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
<div class="locked-tooltip" id="locked-tooltip" style="display:none">
|
|
987
|
-
<p>Available on RunContext Cloud</p>
|
|
988
|
-
<a href="https://runcontext.dev/pricing" target="_blank" rel="noopener">Learn more</a>
|
|
989
|
-
</div>
|
|
990
|
-
|
|
991
|
-
<script src="/static/setup.js"></script>
|
|
1248
|
+
${lockedTooltip}
|
|
1249
|
+
<script src="/static/app.js"></script>
|
|
992
1250
|
</body>
|
|
993
1251
|
</html>`;
|
|
994
1252
|
}
|
|
1253
|
+
function setupPageHTML() {
|
|
1254
|
+
return pageHTML({
|
|
1255
|
+
title: "Build Your Context Layer",
|
|
1256
|
+
activePage: "setup",
|
|
1257
|
+
contentId: "wizard-content"
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
995
1260
|
function startUIServer(opts) {
|
|
996
|
-
return new Promise((
|
|
1261
|
+
return new Promise((resolve3, reject) => {
|
|
997
1262
|
const app = createApp(opts);
|
|
998
1263
|
const server = serve({
|
|
999
1264
|
fetch: app.fetch,
|
|
@@ -1001,7 +1266,7 @@ function startUIServer(opts) {
|
|
|
1001
1266
|
hostname: opts.host
|
|
1002
1267
|
}, (info) => {
|
|
1003
1268
|
console.log(`RunContext UI running at http://${opts.host === "0.0.0.0" ? "localhost" : opts.host}:${info.port}/setup`);
|
|
1004
|
-
|
|
1269
|
+
resolve3();
|
|
1005
1270
|
});
|
|
1006
1271
|
attachWebSocket(server);
|
|
1007
1272
|
server.on("error", reject);
|