yamchart 0.10.0 → 0.10.2
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/{advisor-O2BRAI4T.js → advisor-5F73GF54.js} +13 -13
- package/dist/{advisor-O2BRAI4T.js.map → advisor-5F73GF54.js.map} +1 -1
- package/dist/agent-TZZHNDPK.js +1 -0
- package/dist/{chunk-S2CH4HUZ.js → chunk-25ES7MGF.js} +5 -4
- package/dist/{chunk-QUIDZO5G.js → chunk-5CRXV5PQ.js} +23 -6
- package/dist/chunk-5CRXV5PQ.js.map +1 -0
- package/dist/chunk-7D4SUZUM.js +1 -0
- package/dist/chunk-7HWFVUGP.js +1239 -0
- package/dist/chunk-7HWFVUGP.js.map +1 -0
- package/dist/chunk-DZVT6PHW.js +1 -0
- package/dist/chunk-EAQXUGP6.js +1 -0
- package/dist/chunk-HJVVHYVN.js +1 -0
- package/dist/chunk-IVD4OP3K.js +1 -0
- package/dist/{chunk-5FHV22X2.js → chunk-PYVROL7Z.js} +5 -4
- package/dist/chunk-TZKNU5TD.js +1 -0
- package/dist/chunk-UOSOZT47.js +966 -0
- package/dist/chunk-UOSOZT47.js.map +1 -0
- package/dist/chunk-VJC24RKT.js +1 -0
- package/dist/{chunk-ZBCQNWVN.js → chunk-WVSDQNAN.js} +6 -5
- package/dist/chunk-WYS4ULBE.js +1 -0
- package/dist/{chunk-UFDQ3C7Q.js → chunk-X3UG6T3Q.js} +258 -257
- package/dist/chunk-X3UG6T3Q.js.map +1 -0
- package/dist/chunk-ZMJPRNOA.js +1 -0
- package/dist/compare-RQFCEZIK.js +1 -0
- package/dist/connection-utils-HNX3473B.js +20 -0
- package/dist/{describe-MEP72B56.js → describe-KDGXFPDQ.js} +8 -7
- package/dist/{dev-LZ4YHNDJ.js → dev-KOWFGDOP.js} +934 -128
- package/dist/dev-KOWFGDOP.js.map +1 -0
- package/dist/dev-supervisor-JXLVGEHP.js +43 -0
- package/dist/dev-supervisor-JXLVGEHP.js.map +1 -0
- package/dist/{dist-5IFWLWND.js → dist-LK3ZG74Q.js} +6 -6
- package/dist/dist-MIKFZKSD.js +1 -0
- package/dist/{dist-PPAD6KOM.js → dist-WMP7BFKP.js} +25 -2
- package/dist/{dist-XNCED7JW.js → dist-YB5RBWCC.js} +5 -4
- package/dist/generate-B7FAWVIQ.js +1 -0
- package/dist/index.js +66 -42
- package/dist/index.js.map +1 -1
- package/dist/{init-SECXKD5G.js → init-TJJVKMEN.js} +25 -5
- package/dist/init-TJJVKMEN.js.map +1 -0
- package/dist/lineage-HYO4RKZT.js +1 -0
- package/dist/public/assets/DataView-BfwnWnLH.js +9 -0
- package/dist/public/assets/{EventManagement-BnmeJDQl.js → EventManagement-BIkVUSCe.js} +2 -2
- package/dist/public/assets/ExplorePage-CeaAulqT.js +1 -0
- package/dist/public/assets/{LoginPage-CzaFkkjg.js → LoginPage-4WeNcdRi.js} +1 -1
- package/dist/public/assets/{PublicViewer-irjxqH6a.js → PublicViewer-CkW-G56D.js} +1 -1
- package/dist/public/assets/{SetupWizard-ConWIcmy.js → SetupWizard-CwTr3PCy.js} +1 -1
- package/dist/public/assets/{ShareManagement-CP4wdwLR.js → ShareManagement-DSon6CoC.js} +1 -1
- package/dist/public/assets/{SourceDetailView-DZS5518E.js → SourceDetailView-BA4XVKqV.js} +1 -1
- package/dist/public/assets/UserManagement-BWD2Ugkv.js +1 -0
- package/dist/public/assets/{index-C1X8RW4Z.css → index-Cb4omrVn.css} +1 -1
- package/dist/public/assets/{index-jlfTO7f5.js → index-iR9gLdl9.js} +41 -41
- package/dist/public/assets/{index.es-CgnvEWi5.js → index.es-BuEtjxhO.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-Cw5WefMt.js → jspdf.es.min-CScx2_oK.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-2H3YOPI2.js → query-7XHKSZ5H.js} +6 -5
- package/dist/reset-password-YVCZKZPC.js +1 -0
- package/dist/rewrite-database-BOA4QPUR.js +1 -0
- package/dist/sample-YNSJ3I3T.js +76 -0
- package/dist/sample-YNSJ3I3T.js.map +1 -0
- package/dist/{search-IPE4ISFB.js → search-NKRXXLRS.js} +8 -7
- package/dist/semantic-DD7P4634.js +30 -0
- package/dist/semantic-NBAX4WU4.js +76 -0
- package/dist/semantic-NBAX4WU4.js.map +1 -0
- package/dist/source-resolver-7NY3KEZ2.js +19 -0
- package/dist/source-resolver-7NY3KEZ2.js.map +1 -0
- package/dist/sync-dbt-72GVO75P.js +1 -0
- package/dist/{sync-warehouse-26L6JDSV.js → sync-warehouse-PK7HXW7U.js} +11 -6
- package/dist/sync-warehouse-PK7HXW7U.js.map +1 -0
- package/dist/{tables-K5NAN2WK.js → tables-WQTRZSXR.js} +8 -7
- package/dist/templates/default/docs/yamchart-reference.md +26 -0
- package/dist/{test-SRHVOXZB.js → test-MKNKUR3N.js} +20 -7
- package/dist/test-MKNKUR3N.js.map +1 -0
- package/dist/update-HWCJNQRP.js +1 -0
- package/package.json +9 -7
- package/dist/chunk-FN6R2LAC.js +0 -15442
- package/dist/chunk-FN6R2LAC.js.map +0 -1
- package/dist/chunk-QJ5CPQJK.js +0 -2053
- package/dist/chunk-QJ5CPQJK.js.map +0 -1
- package/dist/chunk-QUIDZO5G.js.map +0 -1
- package/dist/chunk-UFDQ3C7Q.js.map +0 -1
- package/dist/chunk-ZIY22VO7.js +0 -1189
- package/dist/chunk-ZIY22VO7.js.map +0 -1
- package/dist/connection-utils-FEUWER5I.js +0 -19
- package/dist/dev-LZ4YHNDJ.js.map +0 -1
- package/dist/fileFromPath-7TNUU6RI.js +0 -130
- package/dist/fileFromPath-7TNUU6RI.js.map +0 -1
- package/dist/init-SECXKD5G.js.map +0 -1
- package/dist/public/assets/DataView-DUCz_96y.js +0 -9
- package/dist/public/assets/ExplorePage-kk4z9ldZ.js +0 -1
- package/dist/public/assets/UserManagement-AubGd9hl.js +0 -1
- package/dist/sample-ODUGGSFA.js +0 -41
- package/dist/sample-ODUGGSFA.js.map +0 -1
- package/dist/semantic-K3MYXXJI.js +0 -40
- package/dist/semantic-K3MYXXJI.js.map +0 -1
- package/dist/source-resolver-HZQLOODU.js +0 -18
- package/dist/sync-warehouse-26L6JDSV.js.map +0 -1
- package/dist/test-SRHVOXZB.js.map +0 -1
- /package/dist/{chunk-S2CH4HUZ.js.map → chunk-25ES7MGF.js.map} +0 -0
- /package/dist/{chunk-5FHV22X2.js.map → chunk-PYVROL7Z.js.map} +0 -0
- /package/dist/{chunk-ZBCQNWVN.js.map → chunk-WVSDQNAN.js.map} +0 -0
- /package/dist/{connection-utils-FEUWER5I.js.map → connection-utils-HNX3473B.js.map} +0 -0
- /package/dist/{describe-MEP72B56.js.map → describe-KDGXFPDQ.js.map} +0 -0
- /package/dist/{dist-5IFWLWND.js.map → dist-LK3ZG74Q.js.map} +0 -0
- /package/dist/{dist-PPAD6KOM.js.map → dist-WMP7BFKP.js.map} +0 -0
- /package/dist/{dist-XNCED7JW.js.map → dist-YB5RBWCC.js.map} +0 -0
- /package/dist/{query-2H3YOPI2.js.map → query-7XHKSZ5H.js.map} +0 -0
- /package/dist/{search-IPE4ISFB.js.map → search-NKRXXLRS.js.map} +0 -0
- /package/dist/{source-resolver-HZQLOODU.js.map → semantic-DD7P4634.js.map} +0 -0
- /package/dist/{tables-K5NAN2WK.js.map → tables-WQTRZSXR.js.map} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { createRequire as __yamchartCreateRequire } from 'module'; if (!globalThis.require) globalThis.require = __yamchartCreateRequire(import.meta.url);
|
|
1
2
|
import {
|
|
2
3
|
loadEnvFile,
|
|
3
4
|
validateProject
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-25ES7MGF.js";
|
|
5
6
|
import {
|
|
6
7
|
box,
|
|
7
8
|
detail,
|
|
@@ -17,9 +18,10 @@ import {
|
|
|
17
18
|
filterCatalogEntries
|
|
18
19
|
} from "./chunk-ZMJPRNOA.js";
|
|
19
20
|
import {
|
|
21
|
+
buildProvider,
|
|
20
22
|
createEmbedder,
|
|
21
23
|
createProviderForTask
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-UOSOZT47.js";
|
|
23
25
|
import {
|
|
24
26
|
BigQueryConnector,
|
|
25
27
|
DuckDBConnector,
|
|
@@ -38,7 +40,7 @@ import {
|
|
|
38
40
|
resolveMySQLAuth,
|
|
39
41
|
resolvePostgresAuth,
|
|
40
42
|
resolveSnowflakeAuth
|
|
41
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-5CRXV5PQ.js";
|
|
42
44
|
import {
|
|
43
45
|
SemanticModelBuilder,
|
|
44
46
|
SemanticQueryCompiler,
|
|
@@ -55,8 +57,9 @@ import {
|
|
|
55
57
|
isRelativeDateRange,
|
|
56
58
|
loadSemanticDefinitions,
|
|
57
59
|
validateSemanticQuery
|
|
58
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-X3UG6T3Q.js";
|
|
59
61
|
import {
|
|
62
|
+
AiConfigSchema,
|
|
60
63
|
ChartSchema,
|
|
61
64
|
ConnectionSchema,
|
|
62
65
|
DashboardSchema,
|
|
@@ -64,12 +67,14 @@ import {
|
|
|
64
67
|
EventsFileSchema,
|
|
65
68
|
NewBlindspotSchema,
|
|
66
69
|
ProjectSchema,
|
|
70
|
+
ProviderConfigSchema,
|
|
71
|
+
RuntimeConfigSchema,
|
|
72
|
+
RuntimeConnectionSchema,
|
|
67
73
|
ScheduleSchema,
|
|
68
74
|
SemanticQuerySchema,
|
|
69
75
|
deepMerge,
|
|
70
76
|
resolveProjectConfig
|
|
71
|
-
} from "./chunk-
|
|
72
|
-
import "./chunk-QJ5CPQJK.js";
|
|
77
|
+
} from "./chunk-7HWFVUGP.js";
|
|
73
78
|
import {
|
|
74
79
|
AuthDatabase,
|
|
75
80
|
generateOpaqueToken,
|
|
@@ -7113,6 +7118,9 @@ var ConfigLoader = class {
|
|
|
7113
7118
|
projectDir;
|
|
7114
7119
|
project = null;
|
|
7115
7120
|
connections = /* @__PURE__ */ new Map();
|
|
7121
|
+
connectionSecrets = /* @__PURE__ */ new Map();
|
|
7122
|
+
runtimeKnowledgeUrl;
|
|
7123
|
+
runtimeAi;
|
|
7116
7124
|
charts = /* @__PURE__ */ new Map();
|
|
7117
7125
|
chartFolders = /* @__PURE__ */ new Map();
|
|
7118
7126
|
chartFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -7133,6 +7141,9 @@ var ConfigLoader = class {
|
|
|
7133
7141
|
}
|
|
7134
7142
|
async load() {
|
|
7135
7143
|
this.connections.clear();
|
|
7144
|
+
this.connectionSecrets.clear();
|
|
7145
|
+
this.runtimeKnowledgeUrl = void 0;
|
|
7146
|
+
this.runtimeAi = void 0;
|
|
7136
7147
|
this.charts.clear();
|
|
7137
7148
|
this.chartFolders.clear();
|
|
7138
7149
|
this.chartFilePaths.clear();
|
|
@@ -7447,6 +7458,31 @@ Fix: run "ulimit -n 10240" before starting yamchart dev, or add it to your shell
|
|
|
7447
7458
|
getConnectionByName(name) {
|
|
7448
7459
|
return this.connections.get(name);
|
|
7449
7460
|
}
|
|
7461
|
+
/** Merge a runtime config layer over the Git-loaded config. Call after load(). */
|
|
7462
|
+
applyRuntimeOverrides(rc) {
|
|
7463
|
+
for (const rconn of rc.connections ?? []) {
|
|
7464
|
+
const { secrets, ...rest } = rconn;
|
|
7465
|
+
this.connections.set(rconn.name, rest);
|
|
7466
|
+
if (secrets)
|
|
7467
|
+
this.connectionSecrets.set(rconn.name, secrets);
|
|
7468
|
+
}
|
|
7469
|
+
this.runtimeKnowledgeUrl = rc.knowledge?.database_url;
|
|
7470
|
+
this.runtimeAi = rc.ai;
|
|
7471
|
+
}
|
|
7472
|
+
getConnectionSecrets(name) {
|
|
7473
|
+
return this.connectionSecrets.get(name);
|
|
7474
|
+
}
|
|
7475
|
+
/** Knowledge DB URL: runtime override wins, else the named env var. */
|
|
7476
|
+
getKnowledgeDbUrl(envVarName, env) {
|
|
7477
|
+
return this.runtimeKnowledgeUrl ?? env[envVarName];
|
|
7478
|
+
}
|
|
7479
|
+
/** Effective AI config: Git project.ai with the runtime ai deep-merged over it. */
|
|
7480
|
+
getAiConfig() {
|
|
7481
|
+
const base2 = this.project?.ai;
|
|
7482
|
+
if (!this.runtimeAi)
|
|
7483
|
+
return base2;
|
|
7484
|
+
return deepMerge(base2 ?? {}, this.runtimeAi);
|
|
7485
|
+
}
|
|
7450
7486
|
getCharts() {
|
|
7451
7487
|
return Array.from(this.charts.values());
|
|
7452
7488
|
}
|
|
@@ -7508,6 +7544,152 @@ Fix: run "ulimit -n 10240" before starting yamchart dev, or add it to your shell
|
|
|
7508
7544
|
}
|
|
7509
7545
|
};
|
|
7510
7546
|
|
|
7547
|
+
// ../server/dist/services/runtime-config-store.js
|
|
7548
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
7549
|
+
import { dirname as dirname3, join as join3, isAbsolute } from "path";
|
|
7550
|
+
|
|
7551
|
+
// ../server/dist/services/secret-crypto.js
|
|
7552
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
7553
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
7554
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
7555
|
+
var ALGO = "aes-256-gcm";
|
|
7556
|
+
function deriveKeyFromEnv(env) {
|
|
7557
|
+
const b64 = env.YAMCHART_CONFIG_SECRET;
|
|
7558
|
+
if (!b64)
|
|
7559
|
+
return null;
|
|
7560
|
+
const key = Buffer.from(b64, "base64");
|
|
7561
|
+
if (key.length !== 32) {
|
|
7562
|
+
throw new Error("YAMCHART_CONFIG_SECRET must be a base64-encoded 32-byte key");
|
|
7563
|
+
}
|
|
7564
|
+
return key;
|
|
7565
|
+
}
|
|
7566
|
+
function getConfigKey(dataDir, env, log) {
|
|
7567
|
+
const fromEnv = deriveKeyFromEnv(env);
|
|
7568
|
+
if (fromEnv)
|
|
7569
|
+
return fromEnv;
|
|
7570
|
+
const keyPath = join2(dataDir, ".config-secret");
|
|
7571
|
+
if (existsSync(keyPath)) {
|
|
7572
|
+
const key2 = Buffer.from(readFileSync(keyPath, "utf8").trim(), "base64");
|
|
7573
|
+
if (key2.length !== 32) {
|
|
7574
|
+
throw new Error(`Key file at ${keyPath} does not contain a 32-byte key (got ${key2.length} bytes). Delete it to regenerate.`);
|
|
7575
|
+
}
|
|
7576
|
+
return key2;
|
|
7577
|
+
}
|
|
7578
|
+
mkdirSync(dirname2(keyPath), { recursive: true });
|
|
7579
|
+
const key = randomBytes(32);
|
|
7580
|
+
writeFileSync(keyPath, key.toString("base64"), { mode: 384 });
|
|
7581
|
+
log?.(`No YAMCHART_CONFIG_SECRET set \u2014 generated a machine-local key at ${keyPath}. Set YAMCHART_CONFIG_SECRET to keep secrets readable across deploys/volumes.`);
|
|
7582
|
+
return key;
|
|
7583
|
+
}
|
|
7584
|
+
function encryptSecret(plain, key) {
|
|
7585
|
+
const iv = randomBytes(12);
|
|
7586
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
7587
|
+
const ct = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
|
|
7588
|
+
const tag = cipher.getAuthTag();
|
|
7589
|
+
return `${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
7590
|
+
}
|
|
7591
|
+
function decryptSecret(enc, key) {
|
|
7592
|
+
const [ivB64, tagB64, ctB64] = enc.split(":");
|
|
7593
|
+
if (!ivB64 || !tagB64 || !ctB64)
|
|
7594
|
+
throw new Error("Malformed encrypted secret");
|
|
7595
|
+
const decipher = createDecipheriv(ALGO, key, Buffer.from(ivB64, "base64"));
|
|
7596
|
+
decipher.setAuthTag(Buffer.from(tagB64, "base64"));
|
|
7597
|
+
return Buffer.concat([decipher.update(Buffer.from(ctB64, "base64")), decipher.final()]).toString("utf8");
|
|
7598
|
+
}
|
|
7599
|
+
function maskSecret(plain) {
|
|
7600
|
+
if (plain.length <= 4)
|
|
7601
|
+
return "\u2022\u2022\u2022\u2022";
|
|
7602
|
+
return "\u2022\u2022\u2022\u2022" + plain.slice(-4);
|
|
7603
|
+
}
|
|
7604
|
+
|
|
7605
|
+
// ../server/dist/services/runtime-config-store.js
|
|
7606
|
+
var RuntimeConfigError = class extends Error {
|
|
7607
|
+
};
|
|
7608
|
+
var SECRET_LEAF_KEYS = /* @__PURE__ */ new Set(["password", "private_key", "credentials_json", "database_url", "api_key"]);
|
|
7609
|
+
function transformSecretLeaves(obj, fn) {
|
|
7610
|
+
if (Array.isArray(obj))
|
|
7611
|
+
return obj.map((v) => transformSecretLeaves(v, fn));
|
|
7612
|
+
if (obj && typeof obj === "object") {
|
|
7613
|
+
const out = {};
|
|
7614
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
7615
|
+
out[k] = SECRET_LEAF_KEYS.has(k) && typeof v === "string" ? fn(v) : transformSecretLeaves(v, fn);
|
|
7616
|
+
}
|
|
7617
|
+
return out;
|
|
7618
|
+
}
|
|
7619
|
+
return obj;
|
|
7620
|
+
}
|
|
7621
|
+
var RuntimeConfigStore = class {
|
|
7622
|
+
filePath;
|
|
7623
|
+
key;
|
|
7624
|
+
constructor(filePath, key) {
|
|
7625
|
+
this.filePath = filePath;
|
|
7626
|
+
this.key = key;
|
|
7627
|
+
}
|
|
7628
|
+
/** Directory the store (and key file) live in — useful for getConfigKey(). */
|
|
7629
|
+
get dataDir() {
|
|
7630
|
+
return dirname3(this.filePath);
|
|
7631
|
+
}
|
|
7632
|
+
static resolvePath(opts) {
|
|
7633
|
+
if (opts.env.YAMCHART_RUNTIME_CONFIG)
|
|
7634
|
+
return opts.env.YAMCHART_RUNTIME_CONFIG;
|
|
7635
|
+
if (opts.authDbPath && isAbsolute(opts.authDbPath))
|
|
7636
|
+
return join3(dirname3(opts.authDbPath), "runtime-config.json");
|
|
7637
|
+
return join3(opts.projectDir, ".yamchart", "runtime-config.json");
|
|
7638
|
+
}
|
|
7639
|
+
read() {
|
|
7640
|
+
if (!existsSync2(this.filePath))
|
|
7641
|
+
return { version: 1 };
|
|
7642
|
+
let parsed;
|
|
7643
|
+
try {
|
|
7644
|
+
parsed = JSON.parse(readFileSync2(this.filePath, "utf8"));
|
|
7645
|
+
} catch (err) {
|
|
7646
|
+
throw new RuntimeConfigError(`runtime-config.json is not valid JSON: ${err.message}`);
|
|
7647
|
+
}
|
|
7648
|
+
let decrypted;
|
|
7649
|
+
try {
|
|
7650
|
+
decrypted = transformSecretLeaves(parsed, (s) => decryptSecret(s, this.key));
|
|
7651
|
+
} catch (err) {
|
|
7652
|
+
throw new RuntimeConfigError(`Could not decrypt runtime-config.json (wrong YAMCHART_CONFIG_SECRET?): ${err.message}`);
|
|
7653
|
+
}
|
|
7654
|
+
const result = RuntimeConfigSchema.safeParse(decrypted);
|
|
7655
|
+
if (!result.success)
|
|
7656
|
+
throw new RuntimeConfigError(`Invalid runtime-config.json: ${result.error.message}`);
|
|
7657
|
+
return result.data;
|
|
7658
|
+
}
|
|
7659
|
+
write(config) {
|
|
7660
|
+
const validated = RuntimeConfigSchema.parse(config);
|
|
7661
|
+
const encrypted = transformSecretLeaves(validated, (s) => encryptSecret(s, this.key));
|
|
7662
|
+
mkdirSync2(dirname3(this.filePath), { recursive: true, mode: 448 });
|
|
7663
|
+
writeFileSync2(this.filePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
7664
|
+
}
|
|
7665
|
+
};
|
|
7666
|
+
|
|
7667
|
+
// ../server/dist/services/restart.js
|
|
7668
|
+
function requestRestart(env, exit = (c) => process.exit(c), schedule = (fn, ms) => {
|
|
7669
|
+
setTimeout(fn, ms).unref?.();
|
|
7670
|
+
}) {
|
|
7671
|
+
if (env.YAMCHART_DEV_CHILD === "1") {
|
|
7672
|
+
schedule(() => exit(75), 150);
|
|
7673
|
+
return { applying: true, restartRequired: false };
|
|
7674
|
+
}
|
|
7675
|
+
return { applying: false, restartRequired: true };
|
|
7676
|
+
}
|
|
7677
|
+
|
|
7678
|
+
// ../server/dist/services/connection-refs.js
|
|
7679
|
+
function findConnectionRefs(connectionName, input) {
|
|
7680
|
+
const refs = [];
|
|
7681
|
+
const resolves = (explicit) => (explicit ?? input.defaultConnection) === connectionName;
|
|
7682
|
+
for (const c of input.charts) {
|
|
7683
|
+
if (resolves(c.source?.connection))
|
|
7684
|
+
refs.push({ kind: "chart", name: c.name });
|
|
7685
|
+
}
|
|
7686
|
+
for (const m of input.models) {
|
|
7687
|
+
if (resolves(m.metadata.connection))
|
|
7688
|
+
refs.push({ kind: "model", name: m.metadata.name });
|
|
7689
|
+
}
|
|
7690
|
+
return refs;
|
|
7691
|
+
}
|
|
7692
|
+
|
|
7511
7693
|
// ../server/dist/services/cache.js
|
|
7512
7694
|
import { LRUCache } from "lru-cache";
|
|
7513
7695
|
var MemoryCache = class {
|
|
@@ -7804,18 +7986,18 @@ var QueryService = class {
|
|
|
7804
7986
|
};
|
|
7805
7987
|
|
|
7806
7988
|
// ../server/dist/services/connector-factory.js
|
|
7807
|
-
import { join as
|
|
7989
|
+
import { join as join4 } from "path";
|
|
7808
7990
|
function createConnectorFromConfig(connection, options) {
|
|
7809
7991
|
const { projectDir } = options;
|
|
7810
7992
|
switch (connection.type) {
|
|
7811
7993
|
case "duckdb": {
|
|
7812
7994
|
const duckdbConfig = connection;
|
|
7813
|
-
const dbPath = duckdbConfig.config.path.startsWith("/") ? duckdbConfig.config.path :
|
|
7995
|
+
const dbPath = duckdbConfig.config.path.startsWith("/") ? duckdbConfig.config.path : join4(projectDir, duckdbConfig.config.path);
|
|
7814
7996
|
return new DuckDBConnector({ path: dbPath });
|
|
7815
7997
|
}
|
|
7816
7998
|
case "postgres": {
|
|
7817
7999
|
const pgConnection = connection;
|
|
7818
|
-
const credentials = resolvePostgresAuth(pgConnection);
|
|
8000
|
+
const credentials = resolvePostgresAuth(pgConnection, options.secrets);
|
|
7819
8001
|
return new PostgresConnector({
|
|
7820
8002
|
host: pgConnection.config.host,
|
|
7821
8003
|
port: pgConnection.config.port,
|
|
@@ -7832,7 +8014,7 @@ function createConnectorFromConfig(connection, options) {
|
|
|
7832
8014
|
}
|
|
7833
8015
|
case "mysql": {
|
|
7834
8016
|
const mysqlConnection = connection;
|
|
7835
|
-
const credentials = resolveMySQLAuth(mysqlConnection);
|
|
8017
|
+
const credentials = resolveMySQLAuth(mysqlConnection, options.secrets);
|
|
7836
8018
|
return new MySQLConnector({
|
|
7837
8019
|
host: mysqlConnection.config.host,
|
|
7838
8020
|
port: mysqlConnection.config.port,
|
|
@@ -7848,12 +8030,12 @@ function createConnectorFromConfig(connection, options) {
|
|
|
7848
8030
|
}
|
|
7849
8031
|
case "sqlite": {
|
|
7850
8032
|
const sqliteConnection = connection;
|
|
7851
|
-
const dbPath = sqliteConnection.config.path.startsWith("/") ? sqliteConnection.config.path : sqliteConnection.config.path === ":memory:" ? ":memory:" :
|
|
8033
|
+
const dbPath = sqliteConnection.config.path.startsWith("/") ? sqliteConnection.config.path : sqliteConnection.config.path === ":memory:" ? ":memory:" : join4(projectDir, sqliteConnection.config.path);
|
|
7852
8034
|
return new SQLiteConnector({ path: dbPath });
|
|
7853
8035
|
}
|
|
7854
8036
|
case "snowflake": {
|
|
7855
8037
|
const sfConnection = connection;
|
|
7856
|
-
const credentials = resolveSnowflakeAuth(sfConnection);
|
|
8038
|
+
const credentials = resolveSnowflakeAuth(sfConnection, options.secrets);
|
|
7857
8039
|
return new SnowflakeConnector({
|
|
7858
8040
|
account: sfConnection.config.account,
|
|
7859
8041
|
username: credentials.username,
|
|
@@ -7870,8 +8052,8 @@ function createConnectorFromConfig(connection, options) {
|
|
|
7870
8052
|
}
|
|
7871
8053
|
case "bigquery": {
|
|
7872
8054
|
const bqConnection = connection;
|
|
7873
|
-
const creds = resolveBigQueryAuth(bqConnection);
|
|
7874
|
-
const keyFilename = creds.keyFilename ? creds.keyFilename.startsWith("/") ? creds.keyFilename :
|
|
8055
|
+
const creds = resolveBigQueryAuth(bqConnection, options.secrets);
|
|
8056
|
+
const keyFilename = creds.keyFilename ? creds.keyFilename.startsWith("/") ? creds.keyFilename : join4(projectDir, creds.keyFilename) : void 0;
|
|
7875
8057
|
return new BigQueryConnector({
|
|
7876
8058
|
projectId: bqConnection.config.project_id,
|
|
7877
8059
|
dataset: bqConnection.config.dataset,
|
|
@@ -8004,7 +8186,10 @@ async function configRoutes(fastify, options) {
|
|
|
8004
8186
|
});
|
|
8005
8187
|
fastify.get("/api/connections/status", async () => {
|
|
8006
8188
|
const connections = configLoader.getConnections();
|
|
8007
|
-
const results = await Promise.all(connections.map((conn) => testConnection(conn, {
|
|
8189
|
+
const results = await Promise.all(connections.map((conn) => testConnection(conn, {
|
|
8190
|
+
projectDir: options.projectDir,
|
|
8191
|
+
secrets: options.getConnectionSecrets?.(conn.name)
|
|
8192
|
+
})));
|
|
8008
8193
|
return { connections: results };
|
|
8009
8194
|
});
|
|
8010
8195
|
fastify.get("/api/connections/:name/status", async (request, reply) => {
|
|
@@ -8013,7 +8198,10 @@ async function configRoutes(fastify, options) {
|
|
|
8013
8198
|
if (!connection) {
|
|
8014
8199
|
return reply.status(404).send({ error: `Connection not found: ${name}` });
|
|
8015
8200
|
}
|
|
8016
|
-
const result = await testConnection(connection, {
|
|
8201
|
+
const result = await testConnection(connection, {
|
|
8202
|
+
projectDir: options.projectDir,
|
|
8203
|
+
secrets: options.getConnectionSecrets?.(connection.name)
|
|
8204
|
+
});
|
|
8017
8205
|
return result;
|
|
8018
8206
|
});
|
|
8019
8207
|
fastify.get("/api/cache/stats", async () => {
|
|
@@ -8492,10 +8680,10 @@ async function chartRoutes(fastify, options) {
|
|
|
8492
8680
|
|
|
8493
8681
|
// ../server/dist/routes/chart-events.js
|
|
8494
8682
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
8495
|
-
import { join as
|
|
8683
|
+
import { join as join5 } from "path";
|
|
8496
8684
|
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
8497
8685
|
async function readEventsFile(projectDir) {
|
|
8498
|
-
const eventsPath =
|
|
8686
|
+
const eventsPath = join5(projectDir, "events.yaml");
|
|
8499
8687
|
try {
|
|
8500
8688
|
const content = await readFile2(eventsPath, "utf-8");
|
|
8501
8689
|
const parsed = parseYaml2(content);
|
|
@@ -8506,7 +8694,7 @@ async function readEventsFile(projectDir) {
|
|
|
8506
8694
|
}
|
|
8507
8695
|
}
|
|
8508
8696
|
async function writeEventsFile(projectDir, events) {
|
|
8509
|
-
const eventsPath =
|
|
8697
|
+
const eventsPath = join5(projectDir, "events.yaml");
|
|
8510
8698
|
await writeFile(eventsPath, stringifyYaml({ events }));
|
|
8511
8699
|
}
|
|
8512
8700
|
async function chartEventsRoutes(fastify, options) {
|
|
@@ -8555,7 +8743,7 @@ async function chartEventsRoutes(fastify, options) {
|
|
|
8555
8743
|
|
|
8556
8744
|
// ../server/dist/routes/dashboards.js
|
|
8557
8745
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
8558
|
-
import { join as
|
|
8746
|
+
import { join as join6 } from "path";
|
|
8559
8747
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
8560
8748
|
function classifyError2(error2) {
|
|
8561
8749
|
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
@@ -8634,7 +8822,7 @@ async function dashboardRoutes(fastify, options) {
|
|
|
8634
8822
|
}
|
|
8635
8823
|
updated = { ...dashboard, layout };
|
|
8636
8824
|
}
|
|
8637
|
-
const filePath = configLoader.getDashboardFilePath(id) ??
|
|
8825
|
+
const filePath = configLoader.getDashboardFilePath(id) ?? join6(projectDir, "dashboards", `${id}.yaml`);
|
|
8638
8826
|
await writeFile2(filePath, stringifyYaml2(updated));
|
|
8639
8827
|
await configLoader.load();
|
|
8640
8828
|
const commitMessage = message || `Update ${dashboard.title} layout`;
|
|
@@ -8830,11 +9018,11 @@ async function semanticRoutes(fastify, options) {
|
|
|
8830
9018
|
errorType: "validation_error"
|
|
8831
9019
|
});
|
|
8832
9020
|
}
|
|
8833
|
-
const { writeFileSync, mkdirSync } = await import("fs");
|
|
8834
|
-
const { join:
|
|
8835
|
-
const fullPath =
|
|
8836
|
-
|
|
8837
|
-
|
|
9021
|
+
const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = await import("fs");
|
|
9022
|
+
const { join: join14, dirname: dirname5 } = await import("path");
|
|
9023
|
+
const fullPath = join14(projectDir, filePath);
|
|
9024
|
+
mkdirSync3(dirname5(fullPath), { recursive: true });
|
|
9025
|
+
writeFileSync3(fullPath, sql, "utf-8");
|
|
8838
9026
|
return { saved: true, path: filePath };
|
|
8839
9027
|
});
|
|
8840
9028
|
fastify.post("/api/semantic/rebuild", async () => {
|
|
@@ -8849,8 +9037,8 @@ async function semanticRoutes(fastify, options) {
|
|
|
8849
9037
|
}
|
|
8850
9038
|
|
|
8851
9039
|
// ../server/dist/services/semantic-service.js
|
|
8852
|
-
import { readFileSync, existsSync } from "fs";
|
|
8853
|
-
import { join as
|
|
9040
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
9041
|
+
import { join as join7 } from "path";
|
|
8854
9042
|
var SemanticService = class {
|
|
8855
9043
|
model = null;
|
|
8856
9044
|
builder;
|
|
@@ -8867,10 +9055,10 @@ var SemanticService = class {
|
|
|
8867
9055
|
this.model = this.builder.build(input);
|
|
8868
9056
|
}
|
|
8869
9057
|
buildFromDisk(models, chartHints) {
|
|
8870
|
-
const catalogPath =
|
|
9058
|
+
const catalogPath = join7(this.projectDir, ".yamchart", "catalog.json");
|
|
8871
9059
|
let catalog;
|
|
8872
|
-
if (
|
|
8873
|
-
const raw =
|
|
9060
|
+
if (existsSync3(catalogPath)) {
|
|
9061
|
+
const raw = readFileSync3(catalogPath, "utf-8");
|
|
8874
9062
|
catalog = JSON.parse(raw);
|
|
8875
9063
|
}
|
|
8876
9064
|
this.build({ catalog, models, chartHints });
|
|
@@ -9190,6 +9378,14 @@ var GitService = class {
|
|
|
9190
9378
|
}
|
|
9191
9379
|
};
|
|
9192
9380
|
|
|
9381
|
+
// ../server/dist/services/ai-error-redact.js
|
|
9382
|
+
function redactProviderError(msg, apiKey) {
|
|
9383
|
+
let out = msg.replace(/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, "[url redacted]");
|
|
9384
|
+
if (apiKey)
|
|
9385
|
+
out = out.split(apiKey).join("[redacted]");
|
|
9386
|
+
return out;
|
|
9387
|
+
}
|
|
9388
|
+
|
|
9193
9389
|
// ../../packages/knowledge/dist/memory-store.js
|
|
9194
9390
|
import { randomUUID } from "crypto";
|
|
9195
9391
|
|
|
@@ -9474,6 +9670,63 @@ var PostgresKnowledgeStore = class {
|
|
|
9474
9670
|
const r = rows[0];
|
|
9475
9671
|
return { id: r.id, connection: r.connection, source_kind: r.source_kind, source_id: r.source_id, text: r.text, vector: r.vector };
|
|
9476
9672
|
}
|
|
9673
|
+
async exportAll() {
|
|
9674
|
+
const profilesRes = await this.pool.query(`SELECT connection, profile FROM source_profiles`);
|
|
9675
|
+
return {
|
|
9676
|
+
profiles: profilesRes.rows.map((r) => ({ connection: r.connection, profile: r.profile })),
|
|
9677
|
+
hunches: await this.listHunches({}),
|
|
9678
|
+
blindspots: await this.listBlindspots({}),
|
|
9679
|
+
knowledgeDocs: await this.listKnowledgeDocs({}),
|
|
9680
|
+
embeddings: await this.listEmbeddings({})
|
|
9681
|
+
};
|
|
9682
|
+
}
|
|
9683
|
+
async importAll(snapshot) {
|
|
9684
|
+
for (const p of snapshot.profiles)
|
|
9685
|
+
await this.saveProfile(p.connection, p.profile);
|
|
9686
|
+
for (const h of snapshot.hunches) {
|
|
9687
|
+
await this.pool.query(`INSERT INTO hunches (id, connection, question, sql, generated_sql, status, tables_used, rationale, confidence, last_run_sample, created_at, updated_at)
|
|
9688
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT (id) DO NOTHING`, [
|
|
9689
|
+
h.id,
|
|
9690
|
+
h.connection,
|
|
9691
|
+
h.question,
|
|
9692
|
+
h.sql,
|
|
9693
|
+
h.generatedSql,
|
|
9694
|
+
h.status,
|
|
9695
|
+
JSON.stringify(h.tablesUsed ?? []),
|
|
9696
|
+
h.rationale ?? null,
|
|
9697
|
+
h.confidence ?? null,
|
|
9698
|
+
h.lastRunSample ? JSON.stringify(h.lastRunSample) : null,
|
|
9699
|
+
h.createdAt,
|
|
9700
|
+
h.updatedAt
|
|
9701
|
+
]);
|
|
9702
|
+
}
|
|
9703
|
+
for (const b of snapshot.blindspots) {
|
|
9704
|
+
await this.pool.query(`INSERT INTO blindspots (id, connection, kind, title, detail, subject, suggested_model, confidence, status, answer, created_at, updated_at)
|
|
9705
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT (id) DO NOTHING`, [
|
|
9706
|
+
b.id,
|
|
9707
|
+
b.connection,
|
|
9708
|
+
b.kind,
|
|
9709
|
+
b.title,
|
|
9710
|
+
b.detail,
|
|
9711
|
+
b.subject ?? null,
|
|
9712
|
+
JSON.stringify(b.suggestedModel ?? null),
|
|
9713
|
+
b.confidence ?? null,
|
|
9714
|
+
b.status,
|
|
9715
|
+
b.answer ?? null,
|
|
9716
|
+
b.createdAt,
|
|
9717
|
+
b.updatedAt
|
|
9718
|
+
]);
|
|
9719
|
+
}
|
|
9720
|
+
for (const d of snapshot.knowledgeDocs) {
|
|
9721
|
+
await this.pool.query(`INSERT INTO knowledge_docs (id, connection, title, content, source, created_at, updated_at)
|
|
9722
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (id) DO NOTHING`, [d.id, d.connection, d.title, d.content, d.source, d.createdAt, d.updatedAt]);
|
|
9723
|
+
}
|
|
9724
|
+
for (const e of snapshot.embeddings)
|
|
9725
|
+
await this.upsertEmbedding(e);
|
|
9726
|
+
}
|
|
9727
|
+
async close() {
|
|
9728
|
+
await this.pool.end();
|
|
9729
|
+
}
|
|
9477
9730
|
};
|
|
9478
9731
|
|
|
9479
9732
|
// ../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.es6.mjs
|
|
@@ -12165,8 +12418,8 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
12165
12418
|
_trigger(type, payload, ref) {
|
|
12166
12419
|
var _a, _b;
|
|
12167
12420
|
const typeLower = type.toLocaleLowerCase();
|
|
12168
|
-
const { close, error: error2, leave, join:
|
|
12169
|
-
const events = [close, error2, leave,
|
|
12421
|
+
const { close, error: error2, leave, join: join14 } = CHANNEL_EVENTS;
|
|
12422
|
+
const events = [close, error2, leave, join14];
|
|
12170
12423
|
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
|
12171
12424
|
return;
|
|
12172
12425
|
}
|
|
@@ -21598,14 +21851,14 @@ async function publicRoutes(fastify, options) {
|
|
|
21598
21851
|
}
|
|
21599
21852
|
|
|
21600
21853
|
// ../server/dist/routes/chat.js
|
|
21601
|
-
import { resolve, join as
|
|
21854
|
+
import { resolve, join as join8 } from "path";
|
|
21602
21855
|
import { readFile as readFile3 } from "fs/promises";
|
|
21603
21856
|
function isChatEnabled(configLoader) {
|
|
21604
21857
|
const project = configLoader.getProject();
|
|
21605
21858
|
return !!(project?.features?.chat && process.env.ANTHROPIC_API_KEY);
|
|
21606
21859
|
}
|
|
21607
21860
|
async function chatRoutes(fastify, options) {
|
|
21608
|
-
const { configLoader, queryService } = options;
|
|
21861
|
+
const { configLoader, queryService, aiConfig } = options;
|
|
21609
21862
|
fastify.post("/api/chat/generate-text", async (request, reply) => {
|
|
21610
21863
|
const { prompt, mode, dashboard: dashboardName, existingContent, filters } = request.body ?? {};
|
|
21611
21864
|
if (!prompt || !mode || !dashboardName) {
|
|
@@ -21616,10 +21869,9 @@ async function chatRoutes(fastify, options) {
|
|
|
21616
21869
|
if (mode !== "insert" && mode !== "replace") {
|
|
21617
21870
|
return reply.status(400).send({ error: 'mode must be "insert" or "replace"' });
|
|
21618
21871
|
}
|
|
21619
|
-
|
|
21620
|
-
if (!apiKey) {
|
|
21872
|
+
if (!aiConfig && !process.env.ANTHROPIC_API_KEY) {
|
|
21621
21873
|
return reply.status(400).send({
|
|
21622
|
-
error: "AI text generation requires an API key. Set ANTHROPIC_API_KEY in your environment."
|
|
21874
|
+
error: "AI text generation requires an API key. Set ANTHROPIC_API_KEY in your environment, or configure an ai: block in yamchart.yaml."
|
|
21623
21875
|
});
|
|
21624
21876
|
}
|
|
21625
21877
|
const dashboard = configLoader.getDashboardByName(dashboardName);
|
|
@@ -21726,8 +21978,7 @@ async function chatRoutes(fastify, options) {
|
|
|
21726
21978
|
systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
|
|
21727
21979
|
}
|
|
21728
21980
|
const systemPrompt = systemParts.join("\n");
|
|
21729
|
-
const
|
|
21730
|
-
const provider = new AnthropicProvider(apiKey);
|
|
21981
|
+
const provider = createProviderForTask(aiConfig, "text_generation", process.env);
|
|
21731
21982
|
const response = await provider.chat({
|
|
21732
21983
|
system: systemPrompt,
|
|
21733
21984
|
messages: [{ role: "user", content: prompt }],
|
|
@@ -21850,7 +22101,7 @@ IMPORTANT: When using query_model, pass the chart name (e.g. "${chartRefs[0] ??
|
|
|
21850
22101
|
The user is viewing the "${chart.title ?? chart.name}" chart (id: "${chart.name}", type: ${chart.chart?.type}).${model}${params}`;
|
|
21851
22102
|
}
|
|
21852
22103
|
} else if (chatContext.type === "explore") {
|
|
21853
|
-
const catalogPath =
|
|
22104
|
+
const catalogPath = join8(options.projectDir, ".yamchart", "catalog.json");
|
|
21854
22105
|
const tableLines = [];
|
|
21855
22106
|
try {
|
|
21856
22107
|
const catalogData = JSON.parse(await readFile3(catalogPath, "utf-8"));
|
|
@@ -28839,10 +29090,10 @@ async function runModel(deps, args) {
|
|
|
28839
29090
|
}
|
|
28840
29091
|
|
|
28841
29092
|
// ../../packages/mcp-core/dist/catalog.js
|
|
28842
|
-
import { join as
|
|
29093
|
+
import { join as join9 } from "path";
|
|
28843
29094
|
import { readFile as readFile4 } from "fs/promises";
|
|
28844
29095
|
async function readCatalogTables(projectDir, include) {
|
|
28845
|
-
const catalogPath =
|
|
29096
|
+
const catalogPath = join9(projectDir, ".yamchart", "catalog.json");
|
|
28846
29097
|
let parsed;
|
|
28847
29098
|
try {
|
|
28848
29099
|
parsed = JSON.parse(await readFile4(catalogPath, "utf-8"));
|
|
@@ -29040,7 +29291,7 @@ async function visualize(_deps, args) {
|
|
|
29040
29291
|
|
|
29041
29292
|
// ../../packages/mcp-core/dist/tools/save.js
|
|
29042
29293
|
import { mkdir, writeFile as writeFile3 } from "fs/promises";
|
|
29043
|
-
import { join as
|
|
29294
|
+
import { join as join10 } from "path";
|
|
29044
29295
|
import { stringify as stringifyYaml3 } from "yaml";
|
|
29045
29296
|
var SAFE_NAME = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
29046
29297
|
async function saveChart(deps, args) {
|
|
@@ -29058,9 +29309,9 @@ async function saveChart(deps, args) {
|
|
|
29058
29309
|
if (name.length > 200) {
|
|
29059
29310
|
return `Error: Chart name is too long (${name.length} chars). Maximum is 200.`;
|
|
29060
29311
|
}
|
|
29061
|
-
const chartsDir =
|
|
29312
|
+
const chartsDir = join10(deps.projectDir, "charts");
|
|
29062
29313
|
await mkdir(chartsDir, { recursive: true });
|
|
29063
|
-
await writeFile3(
|
|
29314
|
+
await writeFile3(join10(chartsDir, `${name}.yaml`), stringifyYaml3(parsed.data), "utf-8");
|
|
29064
29315
|
await deps.configLoader.load();
|
|
29065
29316
|
return `Chart "${name}" saved to charts/${name}.yaml`;
|
|
29066
29317
|
}
|
|
@@ -29367,15 +29618,15 @@ async function scanBlindspots(input) {
|
|
|
29367
29618
|
}
|
|
29368
29619
|
|
|
29369
29620
|
// ../../packages/learning/dist/context.js
|
|
29370
|
-
import { readFileSync as
|
|
29371
|
-
import { join as
|
|
29621
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4, readdirSync } from "fs";
|
|
29622
|
+
import { join as join11 } from "path";
|
|
29372
29623
|
var MAX_DOC = 6e3;
|
|
29373
29624
|
var MAX_TOTAL = 16e3;
|
|
29374
29625
|
function loadKnowledgeDocs(projectDir) {
|
|
29375
|
-
const dir =
|
|
29376
|
-
if (!
|
|
29626
|
+
const dir = join11(projectDir, "knowledge");
|
|
29627
|
+
if (!existsSync4(dir))
|
|
29377
29628
|
return [];
|
|
29378
|
-
return readdirSync(dir).filter((f) => /\.md$/i.test(f)).map((f) =>
|
|
29629
|
+
return readdirSync(dir).filter((f) => /\.md$/i.test(f)).map((f) => readFileSync4(join11(dir, f), "utf8"));
|
|
29379
29630
|
}
|
|
29380
29631
|
function buildContextBlock(input) {
|
|
29381
29632
|
const parts = [];
|
|
@@ -29494,6 +29745,34 @@ function limit(sql) {
|
|
|
29494
29745
|
return /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT 1000`;
|
|
29495
29746
|
}
|
|
29496
29747
|
|
|
29748
|
+
// ../../packages/learning/dist/reindex.js
|
|
29749
|
+
async function reindexEmbeddings(input) {
|
|
29750
|
+
const { store, embedder, connection } = input;
|
|
29751
|
+
const corpus = [];
|
|
29752
|
+
for (const h of await store.listHunches({ connection, status: "confirmed" })) {
|
|
29753
|
+
corpus.push({ kind: "hunch", id: h.id, connection: h.connection, text: h.question });
|
|
29754
|
+
}
|
|
29755
|
+
for (const b of await store.listBlindspots({ connection, status: "answered" })) {
|
|
29756
|
+
if (b.answer)
|
|
29757
|
+
corpus.push({ kind: "blindspot", id: b.id, connection: b.connection, text: `${b.title}: ${b.answer}` });
|
|
29758
|
+
}
|
|
29759
|
+
for (const d of await store.listKnowledgeDocs({ connection })) {
|
|
29760
|
+
corpus.push({ kind: "knowledge", id: d.id, connection: d.connection, text: `${d.title}
|
|
29761
|
+
${d.content}` });
|
|
29762
|
+
}
|
|
29763
|
+
if (corpus.length === 0)
|
|
29764
|
+
return 0;
|
|
29765
|
+
const vectors = await embedder.embed(corpus.map((c) => c.text));
|
|
29766
|
+
if (vectors.length !== corpus.length) {
|
|
29767
|
+
throw new Error(`Embedder returned ${vectors.length} vectors for ${corpus.length} texts`);
|
|
29768
|
+
}
|
|
29769
|
+
for (let i = 0; i < corpus.length; i++) {
|
|
29770
|
+
const item = corpus[i];
|
|
29771
|
+
await store.upsertEmbedding({ connection: item.connection, source_kind: item.kind, source_id: item.id, text: item.text, vector: vectors[i] });
|
|
29772
|
+
}
|
|
29773
|
+
return corpus.length;
|
|
29774
|
+
}
|
|
29775
|
+
|
|
29497
29776
|
// ../../packages/mcp-core/dist/tools/answer.js
|
|
29498
29777
|
async function answerQuestionTool(deps, args) {
|
|
29499
29778
|
const connection = args.connection ?? deps.defaultConnectionName ?? "default";
|
|
@@ -29599,31 +29878,129 @@ var text = (s) => ({
|
|
|
29599
29878
|
content: [{ type: "text", text: s }],
|
|
29600
29879
|
...s.startsWith("Error:") ? { isError: true } : {}
|
|
29601
29880
|
});
|
|
29881
|
+
var READ_ONLY = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
|
|
29882
|
+
var WRITE_SAFE = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
|
|
29883
|
+
function buildResourceMeta(baseUrl) {
|
|
29884
|
+
return {
|
|
29885
|
+
ui: {
|
|
29886
|
+
domain: baseUrl,
|
|
29887
|
+
csp: {
|
|
29888
|
+
connectDomains: [baseUrl],
|
|
29889
|
+
resourceDomains: ["https://persistent.oaistatic.com"]
|
|
29890
|
+
}
|
|
29891
|
+
}
|
|
29892
|
+
};
|
|
29893
|
+
}
|
|
29602
29894
|
function createMcpServer(deps) {
|
|
29603
29895
|
const server = new McpServer({ name: "yamchart", version: "0.1.0" });
|
|
29604
|
-
|
|
29896
|
+
const baseUrl = deps.baseUrl ?? process.env["BASE_URL"] ?? "https://yamchart.example.com";
|
|
29897
|
+
server.registerResource("chart-renderer", TEMPLATE_URI, {
|
|
29898
|
+
mimeType: "text/html;profile=mcp-app",
|
|
29899
|
+
description: "Interactive yamchart chart renderer",
|
|
29900
|
+
_meta: buildResourceMeta(baseUrl)
|
|
29901
|
+
}, async () => {
|
|
29605
29902
|
const tpl = await buildTemplateResource();
|
|
29606
29903
|
return { contents: [{ uri: TEMPLATE_URI, mimeType: tpl.resource.mimeType, text: tpl.resource.text ?? "" }] };
|
|
29607
29904
|
});
|
|
29608
|
-
server.registerTool("list_charts", {
|
|
29609
|
-
|
|
29905
|
+
server.registerTool("list_charts", {
|
|
29906
|
+
description: "List governed yamchart charts (name, title, type, model, parameters).",
|
|
29907
|
+
inputSchema: {},
|
|
29908
|
+
annotations: READ_ONLY
|
|
29909
|
+
}, async () => {
|
|
29910
|
+
const raw = await listCharts(deps);
|
|
29911
|
+
const charts = JSON.parse(raw);
|
|
29912
|
+
return {
|
|
29913
|
+
content: [{ type: "text", text: raw }],
|
|
29914
|
+
structuredContent: { charts }
|
|
29915
|
+
};
|
|
29916
|
+
});
|
|
29917
|
+
server.registerTool("list_models", {
|
|
29918
|
+
description: "List governed SQL models (name, description).",
|
|
29919
|
+
inputSchema: {},
|
|
29920
|
+
annotations: READ_ONLY
|
|
29921
|
+
}, async () => {
|
|
29922
|
+
const raw = await listModels(deps);
|
|
29923
|
+
const models = JSON.parse(raw);
|
|
29924
|
+
return {
|
|
29925
|
+
content: [{ type: "text", text: raw }],
|
|
29926
|
+
structuredContent: { models }
|
|
29927
|
+
};
|
|
29928
|
+
});
|
|
29610
29929
|
server.registerTool("run_model", {
|
|
29611
29930
|
description: "Run a governed chart/model by name with parameters and return its data.",
|
|
29612
|
-
inputSchema: { name: z2.string(), params: z2.record(z2.unknown()).optional() }
|
|
29613
|
-
|
|
29931
|
+
inputSchema: { name: z2.string(), params: z2.record(z2.unknown()).optional() },
|
|
29932
|
+
annotations: READ_ONLY
|
|
29933
|
+
}, async (args) => {
|
|
29934
|
+
const raw = await runModel(deps, args);
|
|
29935
|
+
if (raw.startsWith("Error:"))
|
|
29936
|
+
return { content: [{ type: "text", text: raw }], isError: true };
|
|
29937
|
+
const parsed = JSON.parse(raw);
|
|
29938
|
+
return {
|
|
29939
|
+
content: [{ type: "text", text: raw }],
|
|
29940
|
+
structuredContent: {
|
|
29941
|
+
rowCount: parsed.rows.length,
|
|
29942
|
+
columns: parsed.columns.map((c) => c.name),
|
|
29943
|
+
preview: parsed.rows.slice(0, 5)
|
|
29944
|
+
}
|
|
29945
|
+
};
|
|
29946
|
+
});
|
|
29614
29947
|
server.registerTool("run_sql", {
|
|
29615
29948
|
description: "Run a read-only ad-hoc SQL query (destructive statements blocked, row-capped).",
|
|
29616
|
-
inputSchema: { sql: z2.string(), limit: z2.number().optional() }
|
|
29617
|
-
|
|
29618
|
-
|
|
29949
|
+
inputSchema: { sql: z2.string(), limit: z2.number().optional() },
|
|
29950
|
+
annotations: READ_ONLY
|
|
29951
|
+
}, async (args) => {
|
|
29952
|
+
const raw = await runSql(deps, args);
|
|
29953
|
+
if (raw.startsWith("Error:"))
|
|
29954
|
+
return { content: [{ type: "text", text: raw }], isError: true };
|
|
29955
|
+
const parsed = JSON.parse(raw);
|
|
29956
|
+
return {
|
|
29957
|
+
content: [{ type: "text", text: raw }],
|
|
29958
|
+
structuredContent: {
|
|
29959
|
+
rowCount: parsed.rows.length,
|
|
29960
|
+
columns: parsed.columns.map((c) => c.name),
|
|
29961
|
+
preview: parsed.rows.slice(0, 5)
|
|
29962
|
+
}
|
|
29963
|
+
};
|
|
29964
|
+
});
|
|
29965
|
+
server.registerTool("list_tables", {
|
|
29966
|
+
description: "List warehouse tables from the cached catalog (name, schema, connection, column count).",
|
|
29967
|
+
inputSchema: {},
|
|
29968
|
+
annotations: READ_ONLY
|
|
29969
|
+
}, async () => {
|
|
29970
|
+
const raw = await listTables(deps);
|
|
29971
|
+
const tables = JSON.parse(raw);
|
|
29972
|
+
return {
|
|
29973
|
+
content: [{ type: "text", text: raw }],
|
|
29974
|
+
structuredContent: { tables }
|
|
29975
|
+
};
|
|
29976
|
+
});
|
|
29619
29977
|
server.registerTool("describe_table", {
|
|
29620
29978
|
description: 'Show columns and types for a catalog table. Accepts { table } (with optional dot-notation schema: "schema.table") or { table, schema } separately.',
|
|
29621
|
-
inputSchema: { table: z2.string(), schema: z2.string().optional() }
|
|
29979
|
+
inputSchema: { table: z2.string(), schema: z2.string().optional() },
|
|
29980
|
+
annotations: READ_ONLY
|
|
29622
29981
|
}, async (args) => text(await describeTable(deps, args)));
|
|
29623
|
-
server.registerTool("sample_table", {
|
|
29982
|
+
server.registerTool("sample_table", {
|
|
29983
|
+
description: "Return a few sample rows from a table.",
|
|
29984
|
+
inputSchema: { table: z2.string(), limit: z2.number().optional() },
|
|
29985
|
+
annotations: READ_ONLY
|
|
29986
|
+
}, async (args) => {
|
|
29987
|
+
const raw = await sampleTable(deps, args);
|
|
29988
|
+
if (raw.startsWith("Error:"))
|
|
29989
|
+
return { content: [{ type: "text", text: raw }], isError: true };
|
|
29990
|
+
const parsed = JSON.parse(raw);
|
|
29991
|
+
return {
|
|
29992
|
+
content: [{ type: "text", text: raw }],
|
|
29993
|
+
structuredContent: {
|
|
29994
|
+
rowCount: parsed.rows.length,
|
|
29995
|
+
columns: parsed.columns.map((c) => c.name),
|
|
29996
|
+
preview: parsed.rows.slice(0, 5)
|
|
29997
|
+
}
|
|
29998
|
+
};
|
|
29999
|
+
});
|
|
29624
30000
|
server.registerTool("visualize", {
|
|
29625
30001
|
description: "Render a yamchart chart spec + data rows to an interactive chart (with a static image fallback). Provide a full chart spec (name, title, source.model, chart) and the data array.",
|
|
29626
30002
|
inputSchema: { spec: z2.record(z2.unknown()), data: z2.array(z2.record(z2.unknown())) },
|
|
30003
|
+
annotations: READ_ONLY,
|
|
29627
30004
|
_meta: {
|
|
29628
30005
|
ui: { resourceUri: TEMPLATE_URI },
|
|
29629
30006
|
"openai/outputTemplate": TEMPLATE_URI
|
|
@@ -29645,17 +30022,27 @@ function createMcpServer(deps) {
|
|
|
29645
30022
|
});
|
|
29646
30023
|
server.registerTool("save_chart", {
|
|
29647
30024
|
description: "Save a yamchart chart spec to charts/<name>.yaml (requires editor/admin).",
|
|
29648
|
-
inputSchema: { spec: z2.record(z2.unknown()) }
|
|
30025
|
+
inputSchema: { spec: z2.record(z2.unknown()) },
|
|
30026
|
+
annotations: WRITE_SAFE
|
|
29649
30027
|
}, async (args) => text(await saveChart(deps, args)));
|
|
29650
30028
|
if (deps.semanticLayer) {
|
|
29651
30029
|
const semanticLayer = deps.semanticLayer;
|
|
29652
30030
|
server.registerTool("list_metrics", {
|
|
29653
30031
|
description: "List governed business metrics (the semantic glossary): name, synonyms, the entity they belong to, and which dimensions you can slice them by. Prefer querying these by name over writing raw SQL.",
|
|
29654
|
-
inputSchema: {}
|
|
29655
|
-
|
|
30032
|
+
inputSchema: {},
|
|
30033
|
+
annotations: READ_ONLY
|
|
30034
|
+
}, async () => {
|
|
30035
|
+
const raw = listMetrics({ semanticLayer });
|
|
30036
|
+
const metrics = JSON.parse(raw);
|
|
30037
|
+
return {
|
|
30038
|
+
content: [{ type: "text", text: raw }],
|
|
30039
|
+
structuredContent: { metrics }
|
|
30040
|
+
};
|
|
30041
|
+
});
|
|
29656
30042
|
server.registerTool("get_metric", {
|
|
29657
30043
|
description: "Get the full governed definition of a metric (by name or synonym): how it is computed (aggregation, field/sql, governed filter), its source table, format, and the dimensions/time dimension you can group it by.",
|
|
29658
|
-
inputSchema: { name: z2.string() }
|
|
30044
|
+
inputSchema: { name: z2.string() },
|
|
30045
|
+
annotations: READ_ONLY
|
|
29659
30046
|
}, async (args) => text(getMetric({ semanticLayer }, args)));
|
|
29660
30047
|
server.registerTool("query_metric", {
|
|
29661
30048
|
description: "Query governed metrics by name (or synonym), optionally sliced by dimensions and a time grain, and filtered by a time range. Returns the data rows, the generated SQL (for transparency), and a ready-to-use suggested_chart spec. Prefer this over run_sql for business questions about governed metrics \u2014 it guarantees correct, governed definitions.",
|
|
@@ -29670,10 +30057,25 @@ function createMcpServer(deps) {
|
|
|
29670
30057
|
filters: z2.array(z2.object({ dimension: z2.string(), operator: z2.string(), value: z2.any() })).optional(),
|
|
29671
30058
|
order_by: z2.object({ field: z2.string(), direction: z2.enum(["asc", "desc"]).optional() }).optional(),
|
|
29672
30059
|
limit: z2.number().int().positive().max(1e4).optional()
|
|
29673
|
-
}
|
|
30060
|
+
},
|
|
30061
|
+
annotations: READ_ONLY
|
|
29674
30062
|
}, async (args) => {
|
|
29675
30063
|
try {
|
|
29676
|
-
|
|
30064
|
+
const raw = await queryMetric({ semanticLayer, queryService: deps.queryService, connectionType: deps.connectionType }, args);
|
|
30065
|
+
if (raw.startsWith("Error:") || raw.startsWith("{") && JSON.parse(raw).error) {
|
|
30066
|
+
return text(raw);
|
|
30067
|
+
}
|
|
30068
|
+
const parsed = JSON.parse(raw);
|
|
30069
|
+
return {
|
|
30070
|
+
content: [{ type: "text", text: raw }],
|
|
30071
|
+
structuredContent: {
|
|
30072
|
+
rowCount: parsed.rows.length,
|
|
30073
|
+
columns: parsed.columns.map((c) => c.name),
|
|
30074
|
+
preview: parsed.rows.slice(0, 5),
|
|
30075
|
+
sql: parsed.sql,
|
|
30076
|
+
suggested_chart: parsed.suggested_chart
|
|
30077
|
+
}
|
|
30078
|
+
};
|
|
29677
30079
|
} catch (err) {
|
|
29678
30080
|
return text(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
29679
30081
|
}
|
|
@@ -29685,27 +30087,48 @@ function createMcpServer(deps) {
|
|
|
29685
30087
|
const answerProvider = deps.answerProvider;
|
|
29686
30088
|
server.registerTool("answer_question", {
|
|
29687
30089
|
description: 'Answer a business question using governed knowledge: retrieves confirmed Hunches + business context for the source and either reuses a matching SQL or generates SQL grounded in trusted examples. Prefer this for "how many / how much / which" questions.',
|
|
29688
|
-
inputSchema: { question: z2.string(), connection: z2.string().optional() }
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29692
|
-
|
|
29693
|
-
|
|
29694
|
-
|
|
29695
|
-
|
|
29696
|
-
|
|
30090
|
+
inputSchema: { question: z2.string(), connection: z2.string().optional() },
|
|
30091
|
+
annotations: READ_ONLY
|
|
30092
|
+
}, async (args) => {
|
|
30093
|
+
const raw = await answerQuestionTool({
|
|
30094
|
+
knowledgeStore,
|
|
30095
|
+
embedder,
|
|
30096
|
+
provider: answerProvider,
|
|
30097
|
+
queryService: deps.queryService,
|
|
30098
|
+
connectionType: deps.connectionType,
|
|
30099
|
+
defaultConnectionName: deps.aiConnectionName
|
|
30100
|
+
}, args);
|
|
30101
|
+
if (raw.startsWith("Error:"))
|
|
30102
|
+
return { content: [{ type: "text", text: raw }], isError: true };
|
|
30103
|
+
const parsed = JSON.parse(raw);
|
|
30104
|
+
const rows = parsed.rows ?? [];
|
|
30105
|
+
const columns = parsed.columns ?? [];
|
|
30106
|
+
return {
|
|
30107
|
+
content: [{ type: "text", text: raw }],
|
|
30108
|
+
structuredContent: {
|
|
30109
|
+
rowCount: rows.length,
|
|
30110
|
+
columns: columns.map((c) => c.name),
|
|
30111
|
+
preview: rows.slice(0, 5),
|
|
30112
|
+
sql: parsed.sql,
|
|
30113
|
+
answer: parsed.answer
|
|
30114
|
+
}
|
|
30115
|
+
};
|
|
30116
|
+
});
|
|
29697
30117
|
}
|
|
29698
30118
|
server.registerTool("get_chart_schema", {
|
|
29699
30119
|
description: "Get the exact yamchart chart spec shape and a working example for a chart type (bar, line, area, kpi, pie, scatter, table). Call this before building a chart. Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }.",
|
|
29700
|
-
inputSchema: { type: z2.string().optional() }
|
|
30120
|
+
inputSchema: { type: z2.string().optional() },
|
|
30121
|
+
annotations: READ_ONLY
|
|
29701
30122
|
}, async (args) => text(getChartSchema(args)));
|
|
29702
30123
|
server.registerTool("validate_chart", {
|
|
29703
30124
|
description: "Validate a yamchart chart spec without saving it. Returns { valid: true } or field-level errors with hints so you can self-correct before calling save_chart or visualize.",
|
|
29704
|
-
inputSchema: { spec: z2.record(z2.unknown()) }
|
|
30125
|
+
inputSchema: { spec: z2.record(z2.unknown()) },
|
|
30126
|
+
annotations: READ_ONLY
|
|
29705
30127
|
}, async (args) => text(validateChart(args)));
|
|
29706
30128
|
server.registerTool("get_chart_spec", {
|
|
29707
30129
|
description: "Get the full saved spec of an existing governed chart by name (so you can copy or adapt it). Call list_charts to discover names.",
|
|
29708
|
-
inputSchema: { name: z2.string() }
|
|
30130
|
+
inputSchema: { name: z2.string() },
|
|
30131
|
+
annotations: READ_ONLY
|
|
29709
30132
|
}, async (args) => text(getChartSpec({ configLoader: deps.configLoader }, args)));
|
|
29710
30133
|
return server;
|
|
29711
30134
|
}
|
|
@@ -29730,7 +30153,8 @@ async function mcpRoutes(fastify, options) {
|
|
|
29730
30153
|
knowledgeStore,
|
|
29731
30154
|
embedder,
|
|
29732
30155
|
answerProvider,
|
|
29733
|
-
aiConnectionName
|
|
30156
|
+
aiConnectionName,
|
|
30157
|
+
baseUrl: process.env["BASE_URL"]
|
|
29734
30158
|
});
|
|
29735
30159
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
29736
30160
|
reply.raw.on("close", () => {
|
|
@@ -29754,7 +30178,7 @@ async function mcpRoutes(fastify, options) {
|
|
|
29754
30178
|
}
|
|
29755
30179
|
|
|
29756
30180
|
// ../server/dist/routes/catalog.js
|
|
29757
|
-
import { join as
|
|
30181
|
+
import { join as join12 } from "path";
|
|
29758
30182
|
import { readFile as readFile5 } from "fs/promises";
|
|
29759
30183
|
async function catalogRoutes(fastify, options) {
|
|
29760
30184
|
const { configLoader, projectDir } = options;
|
|
@@ -29767,7 +30191,7 @@ async function catalogRoutes(fastify, options) {
|
|
|
29767
30191
|
name: m.metadata.name,
|
|
29768
30192
|
description: m.metadata.description
|
|
29769
30193
|
}));
|
|
29770
|
-
const catalogPath =
|
|
30194
|
+
const catalogPath = join12(projectDir, ".yamchart", "catalog.json");
|
|
29771
30195
|
let tables = [];
|
|
29772
30196
|
try {
|
|
29773
30197
|
const catalogData = JSON.parse(await readFile5(catalogPath, "utf-8"));
|
|
@@ -29826,10 +30250,14 @@ async function runHunch(deps, id) {
|
|
|
29826
30250
|
if (!isReadOnlySql(hunch.sql))
|
|
29827
30251
|
return { error: "Only read-only SELECT queries can be run." };
|
|
29828
30252
|
const limited = /\blimit\b/i.test(hunch.sql) ? hunch.sql : `${hunch.sql.replace(/;\s*$/, "")} LIMIT 50`;
|
|
29829
|
-
|
|
29830
|
-
|
|
29831
|
-
|
|
29832
|
-
|
|
30253
|
+
try {
|
|
30254
|
+
const res = await deps.queryService.executeRawSql(limited);
|
|
30255
|
+
const sample = { columns: res.columns, rows: res.rows, ranAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
30256
|
+
await deps.store.updateHunch(id, { lastRunSample: sample });
|
|
30257
|
+
return { sample };
|
|
30258
|
+
} catch (e) {
|
|
30259
|
+
return { error: e.message };
|
|
30260
|
+
}
|
|
29833
30261
|
}
|
|
29834
30262
|
async function answerBlindspot(deps, id, body) {
|
|
29835
30263
|
return deps.store.updateBlindspot(id, { status: "answered", answer: body.answer });
|
|
@@ -29895,7 +30323,17 @@ async function learningRoutes(fastify, opts) {
|
|
|
29895
30323
|
contextBlock
|
|
29896
30324
|
});
|
|
29897
30325
|
const created = await store.createHunches(candidates);
|
|
29898
|
-
|
|
30326
|
+
const threshold = opts.getConnectionThreshold?.(connection);
|
|
30327
|
+
let autoConfirmed = 0;
|
|
30328
|
+
if (threshold != null) {
|
|
30329
|
+
for (const h of created) {
|
|
30330
|
+
if ((h.confidence ?? 0) >= threshold) {
|
|
30331
|
+
await store.updateHunch(h.id, { status: "confirmed" });
|
|
30332
|
+
autoConfirmed++;
|
|
30333
|
+
}
|
|
30334
|
+
}
|
|
30335
|
+
}
|
|
30336
|
+
send("done", { hunches: created, autoConfirmed });
|
|
29899
30337
|
} catch (e) {
|
|
29900
30338
|
send("error", { message: e.message });
|
|
29901
30339
|
}
|
|
@@ -29940,7 +30378,12 @@ async function learningRoutes(fastify, opts) {
|
|
|
29940
30378
|
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29941
30379
|
return;
|
|
29942
30380
|
}
|
|
29943
|
-
|
|
30381
|
+
const result = await runHunch({ store, queryService }, request.params.id);
|
|
30382
|
+
if (result.error) {
|
|
30383
|
+
reply.code(422).send({ error: result.error });
|
|
30384
|
+
return;
|
|
30385
|
+
}
|
|
30386
|
+
return result;
|
|
29944
30387
|
});
|
|
29945
30388
|
fastify.post("/api/blindspots/scan", async (request, reply) => {
|
|
29946
30389
|
if (!requireStore(reply))
|
|
@@ -30017,6 +30460,36 @@ async function learningRoutes(fastify, opts) {
|
|
|
30017
30460
|
}
|
|
30018
30461
|
return dismissBlindspot({ store }, request.params.id);
|
|
30019
30462
|
});
|
|
30463
|
+
fastify.get("/api/coverage", async (request, reply) => {
|
|
30464
|
+
if (!requireStore(reply))
|
|
30465
|
+
return;
|
|
30466
|
+
const q = request.query;
|
|
30467
|
+
const connection = q.connection ?? opts.defaultConnectionName;
|
|
30468
|
+
const [hunches, blindspots] = await Promise.all([
|
|
30469
|
+
store.listHunches({ connection }),
|
|
30470
|
+
store.listBlindspots({ connection })
|
|
30471
|
+
]);
|
|
30472
|
+
const profile = await store.getProfile(connection);
|
|
30473
|
+
const profileTables = profile?.tables?.map((t) => t.table) ?? [];
|
|
30474
|
+
const tableCounts = {};
|
|
30475
|
+
const ensure = (t) => {
|
|
30476
|
+
tableCounts[t] ??= { hunches: 0, blindspots: 0 };
|
|
30477
|
+
};
|
|
30478
|
+
profileTables.forEach(ensure);
|
|
30479
|
+
for (const h of hunches) {
|
|
30480
|
+
for (const t of h.tablesUsed ?? []) {
|
|
30481
|
+
ensure(t);
|
|
30482
|
+
tableCounts[t].hunches++;
|
|
30483
|
+
}
|
|
30484
|
+
}
|
|
30485
|
+
for (const b of blindspots) {
|
|
30486
|
+
if (b.subject) {
|
|
30487
|
+
ensure(b.subject);
|
|
30488
|
+
tableCounts[b.subject].blindspots++;
|
|
30489
|
+
}
|
|
30490
|
+
}
|
|
30491
|
+
return Object.entries(tableCounts).map(([name, counts]) => ({ name, ...counts })).sort((a, b) => b.hunches + b.blindspots - (a.hunches + a.blindspots));
|
|
30492
|
+
});
|
|
30020
30493
|
}
|
|
30021
30494
|
|
|
30022
30495
|
// ../server/dist/routes/data.js
|
|
@@ -30105,6 +30578,257 @@ async function dataRoutes(fastify, opts) {
|
|
|
30105
30578
|
});
|
|
30106
30579
|
}
|
|
30107
30580
|
|
|
30581
|
+
// ../server/dist/routes/admin-config.js
|
|
30582
|
+
function redactUrls(msg) {
|
|
30583
|
+
return msg.replace(/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, "[url redacted]");
|
|
30584
|
+
}
|
|
30585
|
+
function maskSecrets(secrets) {
|
|
30586
|
+
if (!secrets)
|
|
30587
|
+
return void 0;
|
|
30588
|
+
const out = {};
|
|
30589
|
+
for (const [k, v] of Object.entries(secrets))
|
|
30590
|
+
if (typeof v === "string")
|
|
30591
|
+
out[k] = maskSecret(v);
|
|
30592
|
+
return out;
|
|
30593
|
+
}
|
|
30594
|
+
function maskProviderKey(p) {
|
|
30595
|
+
if (p && typeof p.api_key === "string")
|
|
30596
|
+
return { ...p, api_key: maskSecret(p.api_key) };
|
|
30597
|
+
return p;
|
|
30598
|
+
}
|
|
30599
|
+
function maskAiConfig(ai) {
|
|
30600
|
+
if (!ai)
|
|
30601
|
+
return null;
|
|
30602
|
+
const providers = ai.providers ? Object.fromEntries(Object.entries(ai.providers).map(([n, p]) => [n, maskProviderKey(p)])) : void 0;
|
|
30603
|
+
return { ...ai, providers, embedding: maskProviderKey(ai.embedding) };
|
|
30604
|
+
}
|
|
30605
|
+
function mergeAiSecrets(incoming, stored) {
|
|
30606
|
+
const keep = (inc, st) => {
|
|
30607
|
+
if (!inc)
|
|
30608
|
+
return inc;
|
|
30609
|
+
if (inc.api_key === void 0 || inc.api_key === "" || inc.api_key.startsWith("\u2022\u2022\u2022\u2022")) {
|
|
30610
|
+
return { ...inc, api_key: st?.api_key };
|
|
30611
|
+
}
|
|
30612
|
+
return inc;
|
|
30613
|
+
};
|
|
30614
|
+
const providers = incoming.providers ? Object.fromEntries(Object.entries(incoming.providers).map(([n, p]) => [n, keep(p, stored?.providers?.[n])])) : void 0;
|
|
30615
|
+
return { ...incoming, providers, embedding: keep(incoming.embedding, stored?.embedding) };
|
|
30616
|
+
}
|
|
30617
|
+
async function adminConfigRoutes(fastify, opts) {
|
|
30618
|
+
const guard = (req, reply) => {
|
|
30619
|
+
if (!opts.canAdmin(req)) {
|
|
30620
|
+
reply.code(403).send({ error: "Requires admin role." });
|
|
30621
|
+
return false;
|
|
30622
|
+
}
|
|
30623
|
+
return true;
|
|
30624
|
+
};
|
|
30625
|
+
fastify.get("/api/admin/config", async (req, reply) => {
|
|
30626
|
+
if (!guard(req, reply))
|
|
30627
|
+
return;
|
|
30628
|
+
const runtime = opts.runtimeStore.read();
|
|
30629
|
+
const runtimeByName = new Map((runtime.connections ?? []).map((c) => [c.name, c]));
|
|
30630
|
+
const git = opts.gitConnections();
|
|
30631
|
+
const names = /* @__PURE__ */ new Set([...git.map((g) => g.name), ...runtimeByName.keys()]);
|
|
30632
|
+
const connections = [...names].map((name) => {
|
|
30633
|
+
const rt = runtimeByName.get(name);
|
|
30634
|
+
if (rt) {
|
|
30635
|
+
return { name, type: rt.type, source: "runtime", config: rt.config, secrets: maskSecrets(rt.secrets) };
|
|
30636
|
+
}
|
|
30637
|
+
const g = git.find((x) => x.name === name);
|
|
30638
|
+
return { name, type: g.type, source: "git" };
|
|
30639
|
+
});
|
|
30640
|
+
return {
|
|
30641
|
+
connections,
|
|
30642
|
+
knowledge: { configured: opts.knowledgeDbConfigured, urlSet: !!runtime.knowledge?.database_url },
|
|
30643
|
+
ai: maskAiConfig(opts.aiConfig)
|
|
30644
|
+
};
|
|
30645
|
+
});
|
|
30646
|
+
fastify.post("/api/admin/restart", async (req, reply) => {
|
|
30647
|
+
if (!guard(req, reply))
|
|
30648
|
+
return;
|
|
30649
|
+
return opts.requestRestart();
|
|
30650
|
+
});
|
|
30651
|
+
const mergeStoredSecrets = (conn, cfg) => {
|
|
30652
|
+
const existing = cfg.connections?.find((c) => c.name === conn.name);
|
|
30653
|
+
const incoming = Object.fromEntries(
|
|
30654
|
+
// Drop blank AND masked-sentinel ("••••…") values so neither wipes nor overwrites the
|
|
30655
|
+
// stored secret (the existing value is kept via the spread below).
|
|
30656
|
+
Object.entries(conn.secrets ?? {}).filter(([, v]) => typeof v === "string" && v !== "" && !v.startsWith("\u2022\u2022\u2022\u2022"))
|
|
30657
|
+
);
|
|
30658
|
+
const merged = { ...existing?.secrets ?? {}, ...incoming };
|
|
30659
|
+
const secrets = Object.keys(merged).length > 0 ? merged : void 0;
|
|
30660
|
+
return { ...conn, secrets };
|
|
30661
|
+
};
|
|
30662
|
+
const upsertConnection = (conn) => {
|
|
30663
|
+
const cfg = opts.runtimeStore.read();
|
|
30664
|
+
const merged = mergeStoredSecrets(conn, cfg);
|
|
30665
|
+
const list = (cfg.connections ?? []).filter((c) => c.name !== merged.name);
|
|
30666
|
+
list.push(merged);
|
|
30667
|
+
opts.runtimeStore.write({ ...cfg, connections: list });
|
|
30668
|
+
};
|
|
30669
|
+
const createOrUpdate = async (req, reply) => {
|
|
30670
|
+
if (!guard(req, reply))
|
|
30671
|
+
return;
|
|
30672
|
+
const parsed = RuntimeConnectionSchema.safeParse(req.body);
|
|
30673
|
+
if (!parsed.success) {
|
|
30674
|
+
reply.code(400).send({ error: "Invalid connection", details: parsed.error.flatten() });
|
|
30675
|
+
return;
|
|
30676
|
+
}
|
|
30677
|
+
upsertConnection(parsed.data);
|
|
30678
|
+
return opts.requestRestart();
|
|
30679
|
+
};
|
|
30680
|
+
fastify.post("/api/admin/connections", createOrUpdate);
|
|
30681
|
+
fastify.put("/api/admin/connections/:name", async (req, reply) => {
|
|
30682
|
+
if (!guard(req, reply))
|
|
30683
|
+
return;
|
|
30684
|
+
const parsed = RuntimeConnectionSchema.safeParse(req.body);
|
|
30685
|
+
if (!parsed.success) {
|
|
30686
|
+
reply.code(400).send({ error: "Invalid connection", details: parsed.error.flatten() });
|
|
30687
|
+
return;
|
|
30688
|
+
}
|
|
30689
|
+
const { name } = req.params;
|
|
30690
|
+
if (parsed.data.name !== name) {
|
|
30691
|
+
reply.code(400).send({ error: `Connection name mismatch: URL "${name}" vs body "${parsed.data.name}". Renaming is not supported.` });
|
|
30692
|
+
return;
|
|
30693
|
+
}
|
|
30694
|
+
upsertConnection(parsed.data);
|
|
30695
|
+
return opts.requestRestart();
|
|
30696
|
+
});
|
|
30697
|
+
fastify.delete("/api/admin/connections/:name", async (req, reply) => {
|
|
30698
|
+
if (!guard(req, reply))
|
|
30699
|
+
return;
|
|
30700
|
+
const { name } = req.params;
|
|
30701
|
+
const refs = opts.findConnectionRefs(name);
|
|
30702
|
+
if (refs.length > 0) {
|
|
30703
|
+
reply.code(409).send({ error: "Connection is in use", references: refs });
|
|
30704
|
+
return;
|
|
30705
|
+
}
|
|
30706
|
+
const cfg = opts.runtimeStore.read();
|
|
30707
|
+
opts.runtimeStore.write({ ...cfg, connections: (cfg.connections ?? []).filter((c) => c.name !== name) });
|
|
30708
|
+
return opts.requestRestart();
|
|
30709
|
+
});
|
|
30710
|
+
fastify.post("/api/admin/connections/test", async (req, reply) => {
|
|
30711
|
+
if (!guard(req, reply))
|
|
30712
|
+
return;
|
|
30713
|
+
const parsed = RuntimeConnectionSchema.safeParse(req.body);
|
|
30714
|
+
if (!parsed.success) {
|
|
30715
|
+
reply.code(400).send({ error: "Invalid connection", details: parsed.error.flatten() });
|
|
30716
|
+
return;
|
|
30717
|
+
}
|
|
30718
|
+
const cfg = opts.runtimeStore.read();
|
|
30719
|
+
const merged = mergeStoredSecrets(parsed.data, cfg);
|
|
30720
|
+
const { secrets, ...connLike } = merged;
|
|
30721
|
+
return testConnection(connLike, { projectDir: opts.projectDir, secrets });
|
|
30722
|
+
});
|
|
30723
|
+
fastify.post("/api/admin/knowledge-db/test", async (req, reply) => {
|
|
30724
|
+
if (!guard(req, reply))
|
|
30725
|
+
return;
|
|
30726
|
+
const url = req.body?.url;
|
|
30727
|
+
if (!url || url.trim() === "") {
|
|
30728
|
+
reply.code(400).send({ error: "A database URL is required." });
|
|
30729
|
+
return;
|
|
30730
|
+
}
|
|
30731
|
+
const target = opts.createKnowledgeStore(url);
|
|
30732
|
+
try {
|
|
30733
|
+
await target.init();
|
|
30734
|
+
return { ok: true };
|
|
30735
|
+
} catch (err) {
|
|
30736
|
+
return { ok: false, error: redactUrls(err instanceof Error ? err.message : "Connection failed") };
|
|
30737
|
+
} finally {
|
|
30738
|
+
try {
|
|
30739
|
+
await target.close();
|
|
30740
|
+
} catch {
|
|
30741
|
+
}
|
|
30742
|
+
}
|
|
30743
|
+
});
|
|
30744
|
+
fastify.put("/api/admin/knowledge-db", async (req, reply) => {
|
|
30745
|
+
if (!guard(req, reply))
|
|
30746
|
+
return;
|
|
30747
|
+
const url = req.body?.url;
|
|
30748
|
+
if (!url || url.trim() === "") {
|
|
30749
|
+
reply.code(400).send({ error: "A non-empty database URL is required." });
|
|
30750
|
+
return;
|
|
30751
|
+
}
|
|
30752
|
+
const cfg = opts.runtimeStore.read();
|
|
30753
|
+
opts.runtimeStore.write({ ...cfg, knowledge: { database_url: url } });
|
|
30754
|
+
return opts.requestRestart();
|
|
30755
|
+
});
|
|
30756
|
+
fastify.post("/api/admin/knowledge-db/migrate", async (req, reply) => {
|
|
30757
|
+
if (!guard(req, reply))
|
|
30758
|
+
return;
|
|
30759
|
+
const url = req.body?.url;
|
|
30760
|
+
if (!url) {
|
|
30761
|
+
reply.code(400).send({ error: "A target database URL is required." });
|
|
30762
|
+
return;
|
|
30763
|
+
}
|
|
30764
|
+
if (!opts.knowledgeStore) {
|
|
30765
|
+
reply.code(503).send({ error: "No current knowledge database to copy from." });
|
|
30766
|
+
return;
|
|
30767
|
+
}
|
|
30768
|
+
const target = opts.createKnowledgeStore(url);
|
|
30769
|
+
try {
|
|
30770
|
+
await target.init();
|
|
30771
|
+
const snapshot = await opts.knowledgeStore.exportAll();
|
|
30772
|
+
await target.importAll(snapshot);
|
|
30773
|
+
return {
|
|
30774
|
+
migrated: {
|
|
30775
|
+
hunches: snapshot.hunches.length,
|
|
30776
|
+
blindspots: snapshot.blindspots.length,
|
|
30777
|
+
knowledgeDocs: snapshot.knowledgeDocs.length,
|
|
30778
|
+
embeddings: snapshot.embeddings.length,
|
|
30779
|
+
profiles: snapshot.profiles.length
|
|
30780
|
+
}
|
|
30781
|
+
};
|
|
30782
|
+
} catch (err) {
|
|
30783
|
+
reply.code(500).send({ error: redactUrls(err instanceof Error ? err.message : "Migration failed") });
|
|
30784
|
+
} finally {
|
|
30785
|
+
try {
|
|
30786
|
+
await target.close();
|
|
30787
|
+
} catch {
|
|
30788
|
+
}
|
|
30789
|
+
}
|
|
30790
|
+
});
|
|
30791
|
+
fastify.put("/api/admin/ai", async (req, reply) => {
|
|
30792
|
+
if (!guard(req, reply))
|
|
30793
|
+
return;
|
|
30794
|
+
const parsed = AiConfigSchema.safeParse(req.body);
|
|
30795
|
+
if (!parsed.success) {
|
|
30796
|
+
reply.code(400).send({ error: "Invalid AI config", details: parsed.error.flatten() });
|
|
30797
|
+
return;
|
|
30798
|
+
}
|
|
30799
|
+
const cfg = opts.runtimeStore.read();
|
|
30800
|
+
const merged = mergeAiSecrets(parsed.data, cfg.ai);
|
|
30801
|
+
opts.runtimeStore.write({ ...cfg, ai: merged });
|
|
30802
|
+
return opts.requestRestart();
|
|
30803
|
+
});
|
|
30804
|
+
fastify.post("/api/admin/ai/test", async (req, reply) => {
|
|
30805
|
+
if (!guard(req, reply))
|
|
30806
|
+
return;
|
|
30807
|
+
const body = req.body;
|
|
30808
|
+
const parsed = ProviderConfigSchema.safeParse(body?.provider);
|
|
30809
|
+
if (!parsed.success) {
|
|
30810
|
+
reply.code(400).send({ error: "Invalid provider config", details: parsed.error.flatten() });
|
|
30811
|
+
return;
|
|
30812
|
+
}
|
|
30813
|
+
const kind = body?.kind === "embedding" ? "embedding" : "llm";
|
|
30814
|
+
return opts.testAiProvider(parsed.data, kind);
|
|
30815
|
+
});
|
|
30816
|
+
fastify.post("/api/admin/knowledge-db/reembed", async (req, reply) => {
|
|
30817
|
+
if (!guard(req, reply))
|
|
30818
|
+
return;
|
|
30819
|
+
if (!opts.knowledgeStore || !opts.embedder) {
|
|
30820
|
+
reply.code(503).send({ error: "Reindex requires a knowledge database and a configured embedder." });
|
|
30821
|
+
return;
|
|
30822
|
+
}
|
|
30823
|
+
try {
|
|
30824
|
+
const embedded = await reindexEmbeddings({ store: opts.knowledgeStore, embedder: opts.embedder });
|
|
30825
|
+
return { embedded };
|
|
30826
|
+
} catch (err) {
|
|
30827
|
+
reply.code(500).send({ error: redactUrls(err instanceof Error ? err.message : "Reindex failed") });
|
|
30828
|
+
}
|
|
30829
|
+
});
|
|
30830
|
+
}
|
|
30831
|
+
|
|
30108
30832
|
// ../server/dist/oauth/service.js
|
|
30109
30833
|
import { randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
30110
30834
|
|
|
@@ -30631,17 +31355,17 @@ data: ${data}
|
|
|
30631
31355
|
}
|
|
30632
31356
|
|
|
30633
31357
|
// ../server/dist/server.js
|
|
30634
|
-
import { join as
|
|
31358
|
+
import { join as join13, dirname as dirname4 } from "path";
|
|
30635
31359
|
import { fileURLToPath } from "url";
|
|
30636
31360
|
import { access as access2, readFile as readFile6 } from "fs/promises";
|
|
30637
31361
|
import { watch as fsWatch } from "fs";
|
|
30638
|
-
var __dirname =
|
|
30639
|
-
var packageJsonPath =
|
|
31362
|
+
var __dirname = dirname4(fileURLToPath(import.meta.url));
|
|
31363
|
+
var packageJsonPath = join13(__dirname, "..", "package.json");
|
|
30640
31364
|
var packageJson = JSON.parse(await readFile6(packageJsonPath, "utf-8"));
|
|
30641
31365
|
var VERSION = packageJson.version;
|
|
30642
31366
|
async function enrichRefsFromCatalog(projectDir, refs) {
|
|
30643
31367
|
try {
|
|
30644
|
-
const catalogPath =
|
|
31368
|
+
const catalogPath = join13(projectDir, ".yamchart", "catalog.json");
|
|
30645
31369
|
const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
|
|
30646
31370
|
for (const model of catalogData.models ?? []) {
|
|
30647
31371
|
if (model.name && model.table) {
|
|
@@ -30652,7 +31376,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
|
|
|
30652
31376
|
}
|
|
30653
31377
|
}
|
|
30654
31378
|
async function createServer(options) {
|
|
30655
|
-
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir =
|
|
31379
|
+
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join13(__dirname, "public"), auth } = options;
|
|
30656
31380
|
if (auth) {
|
|
30657
31381
|
initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
|
|
30658
31382
|
}
|
|
@@ -30682,12 +31406,35 @@ async function createServer(options) {
|
|
|
30682
31406
|
}
|
|
30683
31407
|
const configLoader = new ConfigLoader(projectDir, options.env);
|
|
30684
31408
|
await configLoader.load();
|
|
31409
|
+
const runtimeAuthDbPath = options.localAuth?.dbPath;
|
|
31410
|
+
const runtimeConfigPath = RuntimeConfigStore.resolvePath({
|
|
31411
|
+
env: process.env,
|
|
31412
|
+
projectDir,
|
|
31413
|
+
authDbPath: runtimeAuthDbPath
|
|
31414
|
+
});
|
|
31415
|
+
const configKey = getConfigKey(dirname4(runtimeConfigPath), process.env, (m) => fastify.log.warn(m));
|
|
31416
|
+
const runtimeStore = new RuntimeConfigStore(runtimeConfigPath, configKey);
|
|
31417
|
+
const applyRuntime = () => {
|
|
31418
|
+
try {
|
|
31419
|
+
configLoader.applyRuntimeOverrides(runtimeStore.read());
|
|
31420
|
+
} catch (err) {
|
|
31421
|
+
if (err instanceof RuntimeConfigError) {
|
|
31422
|
+
fastify.log.error(`Runtime config ignored (Git-only fallback): ${err.message}`);
|
|
31423
|
+
} else
|
|
31424
|
+
throw err;
|
|
31425
|
+
}
|
|
31426
|
+
};
|
|
31427
|
+
applyRuntime();
|
|
31428
|
+
const effectiveAi = configLoader.getAiConfig();
|
|
30685
31429
|
const gitService = new GitService(projectDir);
|
|
30686
31430
|
const defaultConnection = configLoader.getDefaultConnection();
|
|
30687
31431
|
if (!defaultConnection) {
|
|
30688
31432
|
throw new Error("No connection configured");
|
|
30689
31433
|
}
|
|
30690
|
-
const rawConnector = createConnectorFromConfig(defaultConnection, {
|
|
31434
|
+
const rawConnector = createConnectorFromConfig(defaultConnection, {
|
|
31435
|
+
projectDir,
|
|
31436
|
+
secrets: configLoader.getConnectionSecrets(defaultConnection.name)
|
|
31437
|
+
});
|
|
30691
31438
|
const connector = new ReconnectingConnector(rawConnector);
|
|
30692
31439
|
await connector.connect();
|
|
30693
31440
|
const project = configLoader.getProject();
|
|
@@ -30697,7 +31444,7 @@ async function createServer(options) {
|
|
|
30697
31444
|
defaultTtlMs: cacheTtl
|
|
30698
31445
|
});
|
|
30699
31446
|
const knowledgeUrlVar = project.knowledge?.database_url_var ?? "KNOWLEDGE_DATABASE_URL";
|
|
30700
|
-
const knowledgeDbUrl = process.env
|
|
31447
|
+
const knowledgeDbUrl = configLoader.getKnowledgeDbUrl(knowledgeUrlVar, process.env);
|
|
30701
31448
|
let knowledgeStore;
|
|
30702
31449
|
if (knowledgeDbUrl) {
|
|
30703
31450
|
const store = new PostgresKnowledgeStore(knowledgeDbUrl);
|
|
@@ -30751,20 +31498,6 @@ async function createServer(options) {
|
|
|
30751
31498
|
authType: defaultConnection.type === "snowflake" && defaultConnection.auth ? defaultConnection.auth.type : void 0
|
|
30752
31499
|
};
|
|
30753
31500
|
});
|
|
30754
|
-
fastify.post("/api/connections/reconnect", async (_request2, reply) => {
|
|
30755
|
-
try {
|
|
30756
|
-
try {
|
|
30757
|
-
await connector.disconnect();
|
|
30758
|
-
} catch {
|
|
30759
|
-
}
|
|
30760
|
-
await connector.connect();
|
|
30761
|
-
return { success: true };
|
|
30762
|
-
} catch (err) {
|
|
30763
|
-
return reply.status(500).send({
|
|
30764
|
-
error: err instanceof Error ? err.message : "Reconnection failed"
|
|
30765
|
-
});
|
|
30766
|
-
}
|
|
30767
|
-
});
|
|
30768
31501
|
await fastify.register(semanticRoutes, { semanticService, queryService, projectDir });
|
|
30769
31502
|
if (authDb) {
|
|
30770
31503
|
const secureCookies = process.env.NODE_ENV === "production";
|
|
@@ -30827,31 +31560,31 @@ async function createServer(options) {
|
|
|
30827
31560
|
await fastify.register(async (protectedRoutes) => {
|
|
30828
31561
|
protectedRoutes.addHook("preHandler", authMiddleware);
|
|
30829
31562
|
protectedRoutes.addHook("preHandler", orgMiddleware);
|
|
30830
|
-
await protectedRoutes.register(configRoutes, { configLoader, projectDir, queryService });
|
|
31563
|
+
await protectedRoutes.register(configRoutes, { configLoader, projectDir, queryService, getConnectionSecrets: (name) => configLoader.getConnectionSecrets(name) });
|
|
30831
31564
|
await protectedRoutes.register(chartRoutes, { configLoader, queryService });
|
|
30832
31565
|
await protectedRoutes.register(chartEventsRoutes, { configLoader, projectDir });
|
|
30833
31566
|
await protectedRoutes.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
30834
|
-
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
31567
|
+
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir, aiConfig: effectiveAi });
|
|
30835
31568
|
await protectedRoutes.register(catalogRoutes, { configLoader, projectDir });
|
|
30836
31569
|
});
|
|
30837
31570
|
} else if (authDb) {
|
|
30838
31571
|
const localMiddleware = createLocalAuthMiddleware(authDb);
|
|
30839
31572
|
await fastify.register(async (protectedRoutes) => {
|
|
30840
31573
|
protectedRoutes.addHook("preHandler", localMiddleware);
|
|
30841
|
-
await protectedRoutes.register(configRoutes, { configLoader, projectDir, queryService });
|
|
31574
|
+
await protectedRoutes.register(configRoutes, { configLoader, projectDir, queryService, getConnectionSecrets: (name) => configLoader.getConnectionSecrets(name) });
|
|
30842
31575
|
await protectedRoutes.register(chartRoutes, { configLoader, queryService });
|
|
30843
31576
|
await protectedRoutes.register(chartEventsRoutes, { configLoader, projectDir });
|
|
30844
31577
|
await protectedRoutes.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
30845
|
-
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
31578
|
+
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir, aiConfig: effectiveAi });
|
|
30846
31579
|
await protectedRoutes.register(catalogRoutes, { configLoader, projectDir });
|
|
30847
31580
|
await protectedRoutes.register(sharesRoutes, { authDb });
|
|
30848
31581
|
});
|
|
30849
31582
|
} else {
|
|
30850
|
-
await fastify.register(configRoutes, { configLoader, projectDir, queryService });
|
|
31583
|
+
await fastify.register(configRoutes, { configLoader, projectDir, queryService, getConnectionSecrets: (name) => configLoader.getConnectionSecrets(name) });
|
|
30851
31584
|
await fastify.register(chartRoutes, { configLoader, queryService });
|
|
30852
31585
|
await fastify.register(chartEventsRoutes, { configLoader, projectDir });
|
|
30853
31586
|
await fastify.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
30854
|
-
await fastify.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
31587
|
+
await fastify.register(chatRoutes, { configLoader, queryService, projectDir, aiConfig: effectiveAi });
|
|
30855
31588
|
await fastify.register(catalogRoutes, { configLoader, projectDir });
|
|
30856
31589
|
}
|
|
30857
31590
|
const mcpBaseUrl = process.env.BASE_URL || project.defaults?.base_url || `http://localhost:${port}`;
|
|
@@ -30868,12 +31601,12 @@ async function createServer(options) {
|
|
|
30868
31601
|
let embedder;
|
|
30869
31602
|
let answerProvider;
|
|
30870
31603
|
try {
|
|
30871
|
-
embedder = createEmbedder(
|
|
31604
|
+
embedder = createEmbedder(effectiveAi?.embedding, process.env);
|
|
30872
31605
|
} catch (err) {
|
|
30873
31606
|
fastify.log.warn({ err }, "Failed to build embedder; answer_question tool disabled");
|
|
30874
31607
|
}
|
|
30875
31608
|
try {
|
|
30876
|
-
answerProvider = createProviderForTask(
|
|
31609
|
+
answerProvider = createProviderForTask(effectiveAi, "answer", process.env);
|
|
30877
31610
|
} catch (err) {
|
|
30878
31611
|
fastify.log.warn({ err }, "Failed to build answer provider; answer_question tool disabled");
|
|
30879
31612
|
}
|
|
@@ -30906,7 +31639,7 @@ async function createServer(options) {
|
|
|
30906
31639
|
dialect: defaultConnection?.type ?? "duckdb",
|
|
30907
31640
|
metricNames: semanticLayer?.listMetrics().map((m) => m.name) ?? [],
|
|
30908
31641
|
getConnectionTables: async (connection) => {
|
|
30909
|
-
const catalogPath =
|
|
31642
|
+
const catalogPath = join13(projectDir, ".yamchart", "catalog.json");
|
|
30910
31643
|
try {
|
|
30911
31644
|
const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
|
|
30912
31645
|
return (catalogData.models ?? []).filter((m) => !m.connection || m.connection === connection).map((m) => ({
|
|
@@ -30925,7 +31658,11 @@ async function createServer(options) {
|
|
|
30925
31658
|
return true;
|
|
30926
31659
|
return user.role !== "viewer";
|
|
30927
31660
|
},
|
|
30928
|
-
aiConfig:
|
|
31661
|
+
aiConfig: effectiveAi,
|
|
31662
|
+
getConnectionThreshold: (name) => {
|
|
31663
|
+
const conn = configLoader.getConnections().find((c) => c.name === name);
|
|
31664
|
+
return conn?.hunch_confidence_threshold;
|
|
31665
|
+
}
|
|
30929
31666
|
};
|
|
30930
31667
|
const dataOptions = {
|
|
30931
31668
|
store: knowledgeStore,
|
|
@@ -30933,12 +31670,76 @@ async function createServer(options) {
|
|
|
30933
31670
|
getConnectionTables: learningOptions.getConnectionTables,
|
|
30934
31671
|
canWrite: learningOptions.canWrite
|
|
30935
31672
|
};
|
|
31673
|
+
const adminConfigOptions = {
|
|
31674
|
+
runtimeStore,
|
|
31675
|
+
gitConnections: () => configLoader.getConnections().map((c) => ({ name: c.name, type: c.type })),
|
|
31676
|
+
knowledgeDbConfigured: !!knowledgeStore,
|
|
31677
|
+
aiConfig: effectiveAi,
|
|
31678
|
+
projectDir,
|
|
31679
|
+
findConnectionRefs: (name) => findConnectionRefs(name, {
|
|
31680
|
+
charts: configLoader.getCharts().map((c) => ({
|
|
31681
|
+
name: c.name,
|
|
31682
|
+
source: { connection: c.source.connection }
|
|
31683
|
+
})),
|
|
31684
|
+
models: configLoader.getModels().map((m) => ({
|
|
31685
|
+
metadata: {
|
|
31686
|
+
name: m.metadata.name,
|
|
31687
|
+
connection: m.metadata.connection
|
|
31688
|
+
}
|
|
31689
|
+
})),
|
|
31690
|
+
defaultConnection: configLoader.getDefaultConnection()?.name
|
|
31691
|
+
}),
|
|
31692
|
+
requestRestart: () => requestRestart(process.env),
|
|
31693
|
+
// Admin-only: when auth disabled (no req.user) allow (dev); else require admin role.
|
|
31694
|
+
canAdmin: (req) => {
|
|
31695
|
+
const user = req.user;
|
|
31696
|
+
if (!user)
|
|
31697
|
+
return true;
|
|
31698
|
+
return user.role === "admin";
|
|
31699
|
+
},
|
|
31700
|
+
knowledgeStore,
|
|
31701
|
+
embedder,
|
|
31702
|
+
createKnowledgeStore: (url) => new PostgresKnowledgeStore(url),
|
|
31703
|
+
testAiProvider: async (cfg, kind) => {
|
|
31704
|
+
try {
|
|
31705
|
+
if (kind === "embedding") {
|
|
31706
|
+
const e = createEmbedder(cfg, process.env);
|
|
31707
|
+
if (!e)
|
|
31708
|
+
return { ok: false, error: "Could not build embedder for this config." };
|
|
31709
|
+
await e.embed(["ping"]);
|
|
31710
|
+
} else {
|
|
31711
|
+
const provider = buildProvider(cfg, process.env);
|
|
31712
|
+
await provider.chat({ system: "", messages: [{ role: "user", content: "ping" }], tools: [], maxTokens: 1 });
|
|
31713
|
+
}
|
|
31714
|
+
return { ok: true };
|
|
31715
|
+
} catch (err) {
|
|
31716
|
+
const msg = err instanceof Error ? err.message : "Test failed";
|
|
31717
|
+
return { ok: false, error: redactProviderError(msg, cfg.api_key) };
|
|
31718
|
+
}
|
|
31719
|
+
}
|
|
31720
|
+
};
|
|
31721
|
+
const registerReconnect = async (scope) => {
|
|
31722
|
+
scope.post("/api/connections/reconnect", async (_request2, reply) => {
|
|
31723
|
+
try {
|
|
31724
|
+
try {
|
|
31725
|
+
await connector.disconnect();
|
|
31726
|
+
} catch {
|
|
31727
|
+
}
|
|
31728
|
+
await connector.connect();
|
|
31729
|
+
return { success: true };
|
|
31730
|
+
} catch (err) {
|
|
31731
|
+
return reply.status(500).send({ error: err instanceof Error ? err.message : "Reconnection failed" });
|
|
31732
|
+
}
|
|
31733
|
+
});
|
|
31734
|
+
};
|
|
30936
31735
|
if (auth) {
|
|
30937
31736
|
await fastify.register(async (protectedRoutes) => {
|
|
30938
31737
|
protectedRoutes.addHook("preHandler", authMiddleware);
|
|
30939
31738
|
protectedRoutes.addHook("preHandler", orgMiddleware);
|
|
30940
31739
|
await protectedRoutes.register(learningRoutes, learningOptions);
|
|
30941
31740
|
await protectedRoutes.register(dataRoutes, dataOptions);
|
|
31741
|
+
await protectedRoutes.register(adminConfigRoutes, adminConfigOptions);
|
|
31742
|
+
await registerReconnect(protectedRoutes);
|
|
30942
31743
|
});
|
|
30943
31744
|
} else if (authDb) {
|
|
30944
31745
|
const localMiddleware = createLocalAuthMiddleware(authDb);
|
|
@@ -30946,12 +31747,16 @@ async function createServer(options) {
|
|
|
30946
31747
|
protectedRoutes.addHook("preHandler", localMiddleware);
|
|
30947
31748
|
await protectedRoutes.register(learningRoutes, learningOptions);
|
|
30948
31749
|
await protectedRoutes.register(dataRoutes, dataOptions);
|
|
31750
|
+
await protectedRoutes.register(adminConfigRoutes, adminConfigOptions);
|
|
31751
|
+
await registerReconnect(protectedRoutes);
|
|
30949
31752
|
});
|
|
30950
31753
|
} else {
|
|
30951
31754
|
await fastify.register(learningRoutes, learningOptions);
|
|
30952
31755
|
await fastify.register(dataRoutes, dataOptions);
|
|
31756
|
+
await fastify.register(adminConfigRoutes, adminConfigOptions);
|
|
31757
|
+
await registerReconnect(fastify);
|
|
30953
31758
|
}
|
|
30954
|
-
const assetsDir =
|
|
31759
|
+
const assetsDir = join13(projectDir, "assets");
|
|
30955
31760
|
try {
|
|
30956
31761
|
await access2(assetsDir);
|
|
30957
31762
|
await fastify.register(fastifyStatic, {
|
|
@@ -30962,7 +31767,7 @@ async function createServer(options) {
|
|
|
30962
31767
|
} catch {
|
|
30963
31768
|
}
|
|
30964
31769
|
if (project.theme?.customCss) {
|
|
30965
|
-
const cssPath =
|
|
31770
|
+
const cssPath = join13(projectDir, project.theme.customCss);
|
|
30966
31771
|
fastify.get("/api/theme/custom.css", async (_request2, reply) => {
|
|
30967
31772
|
try {
|
|
30968
31773
|
const content = await readFile6(cssPath, "utf-8");
|
|
@@ -31009,6 +31814,7 @@ async function createServer(options) {
|
|
|
31009
31814
|
configLoader.startWatching();
|
|
31010
31815
|
configLoader.onChange(async () => {
|
|
31011
31816
|
fastify.log.info("Config reloaded");
|
|
31817
|
+
applyRuntime();
|
|
31012
31818
|
const { models: newModels, refs: newRefs } = await buildModelsAndRefs();
|
|
31013
31819
|
queryService.updateCompiler({ models: newModels, refs: newRefs });
|
|
31014
31820
|
queryService.invalidateAll();
|
|
@@ -31021,7 +31827,7 @@ async function createServer(options) {
|
|
|
31021
31827
|
fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
31022
31828
|
broadcastConfigChange();
|
|
31023
31829
|
});
|
|
31024
|
-
const catalogPath =
|
|
31830
|
+
const catalogPath = join13(projectDir, ".yamchart", "catalog.json");
|
|
31025
31831
|
try {
|
|
31026
31832
|
catalogWatcher = fsWatch(catalogPath, () => {
|
|
31027
31833
|
semanticService.buildFromDisk();
|
|
@@ -31085,15 +31891,15 @@ async function runDevServer(projectDir, options) {
|
|
|
31085
31891
|
let usesBrowserAuth = false;
|
|
31086
31892
|
let localAuth;
|
|
31087
31893
|
try {
|
|
31088
|
-
const { readFileSync:
|
|
31894
|
+
const { readFileSync: readFileSync5 } = await import("fs");
|
|
31089
31895
|
const { parse: parse2 } = await import("yaml");
|
|
31090
|
-
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-
|
|
31091
|
-
const raw =
|
|
31896
|
+
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-WMP7BFKP.js");
|
|
31897
|
+
const raw = readFileSync5(resolve2(projectDir, "yamchart.yaml"), "utf-8");
|
|
31092
31898
|
const rawConfig = parse2(raw);
|
|
31093
31899
|
const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
|
|
31094
31900
|
let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
|
|
31095
31901
|
try {
|
|
31096
|
-
const localRaw =
|
|
31902
|
+
const localRaw = readFileSync5(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
|
|
31097
31903
|
const localOverrides = parse2(localRaw);
|
|
31098
31904
|
if (localOverrides && typeof localOverrides === "object") {
|
|
31099
31905
|
projectConfig = deepMerge3(projectConfig, localOverrides);
|
|
@@ -31111,7 +31917,7 @@ async function runDevServer(projectDir, options) {
|
|
|
31111
31917
|
const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
|
|
31112
31918
|
if (targetConn) {
|
|
31113
31919
|
try {
|
|
31114
|
-
const connRaw =
|
|
31920
|
+
const connRaw = readFileSync5(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
|
|
31115
31921
|
const connConfig = parse2(connRaw);
|
|
31116
31922
|
if (connConfig?.auth?.type === "externalbrowser") {
|
|
31117
31923
|
usesBrowserAuth = true;
|
|
@@ -31191,4 +31997,4 @@ async function runDevServer(projectDir, options) {
|
|
|
31191
31997
|
export {
|
|
31192
31998
|
runDevServer
|
|
31193
31999
|
};
|
|
31194
|
-
//# sourceMappingURL=dev-
|
|
32000
|
+
//# sourceMappingURL=dev-KOWFGDOP.js.map
|