yamchart 0.9.4 → 0.10.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/{advisor-SC64RTZO.js → advisor-O2BRAI4T.js} +24 -14
- package/dist/advisor-O2BRAI4T.js.map +1 -0
- package/dist/{chunk-NXQ6ZO3V.js → chunk-5FHV22X2.js} +6 -6
- package/dist/chunk-5FHV22X2.js.map +1 -0
- package/dist/{chunk-7CD6UQAV.js → chunk-DZVT6PHW.js} +9 -1
- package/dist/chunk-DZVT6PHW.js.map +1 -0
- package/dist/chunk-FN6R2LAC.js +15442 -0
- package/dist/chunk-FN6R2LAC.js.map +1 -0
- package/dist/chunk-QJ5CPQJK.js +2053 -0
- package/dist/chunk-QJ5CPQJK.js.map +1 -0
- package/dist/{chunk-S7YQXEKM.js → chunk-QUIDZO5G.js} +103 -275
- package/dist/chunk-QUIDZO5G.js.map +1 -0
- package/dist/{chunk-RMIDEBHD.js → chunk-S2CH4HUZ.js} +6 -6
- package/dist/{chunk-UND73EOB.js → chunk-UFDQ3C7Q.js} +473 -4
- package/dist/chunk-UFDQ3C7Q.js.map +1 -0
- package/dist/{chunk-H4L3FNLS.js → chunk-ZBCQNWVN.js} +2 -2
- package/dist/{chunk-RM6MNDVF.js → chunk-ZIY22VO7.js} +192 -11
- package/dist/chunk-ZIY22VO7.js.map +1 -0
- package/dist/{connection-utils-C4FQGBW6.js → connection-utils-FEUWER5I.js} +5 -5
- package/dist/{describe-X75C2VDU.js → describe-MEP72B56.js} +6 -6
- package/dist/{dev-7YLRQ6SA.js → dev-LZ4YHNDJ.js} +1334 -77
- package/dist/dev-LZ4YHNDJ.js.map +1 -0
- package/dist/dist-5IFWLWND.js +48 -0
- package/dist/{dist-NGQG7Z4G.js → dist-MIKFZKSD.js} +2 -2
- package/dist/{dist-MX5K2ABB.js → dist-PPAD6KOM.js} +44 -2
- package/dist/{dist-JMLAZUL7.js → dist-XNCED7JW.js} +29 -12
- package/dist/fileFromPath-7TNUU6RI.js +130 -0
- package/dist/fileFromPath-7TNUU6RI.js.map +1 -0
- package/dist/index.js +25 -25
- package/dist/public/assets/DataView-DUCz_96y.js +9 -0
- package/dist/public/assets/{EventManagement-B7leMxfo.js → EventManagement-BnmeJDQl.js} +1 -1
- package/dist/public/assets/{ExplorePage-zI1OiYlH.js → ExplorePage-kk4z9ldZ.js} +1 -1
- package/dist/public/assets/{LoginPage-Dyk2ILmo.js → LoginPage-CzaFkkjg.js} +1 -1
- package/dist/public/assets/{PublicViewer-C2tNYBR2.js → PublicViewer-irjxqH6a.js} +1 -1
- package/dist/public/assets/{SetupWizard-BzZSJlbt.js → SetupWizard-ConWIcmy.js} +1 -1
- package/dist/public/assets/{ShareManagement-C7RIZRWe.js → ShareManagement-CP4wdwLR.js} +1 -1
- package/dist/public/assets/SourceDetailView-DZS5518E.js +1 -0
- package/dist/public/assets/{UserManagement-BD-lLbVH.js → UserManagement-AubGd9hl.js} +1 -1
- package/dist/public/assets/data-3vtzSuAZ.js +1 -0
- package/dist/public/assets/{index-CfyF2Wf-.css → index-C1X8RW4Z.css} +1 -1
- package/dist/public/assets/{index-lklRbe2I.js → index-jlfTO7f5.js} +44 -44
- package/dist/public/assets/{index.es-CLyC5-GY.js → index.es-CgnvEWi5.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-CTZVk96E.js → jspdf.es.min-Cw5WefMt.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-QNRDS74I.js → query-2H3YOPI2.js} +5 -5
- package/dist/{reset-password-HDCLH7PZ.js → reset-password-YVCZKZPC.js} +2 -2
- package/dist/{sample-SKLHBZBU.js → sample-ODUGGSFA.js} +5 -5
- package/dist/{search-4KMETZVX.js → search-IPE4ISFB.js} +6 -6
- package/dist/{semantic-6WKELH5V.js → semantic-K3MYXXJI.js} +3 -2
- package/dist/{semantic-6WKELH5V.js.map → semantic-K3MYXXJI.js.map} +1 -1
- package/dist/{source-resolver-R7WBIL7M.js → source-resolver-HZQLOODU.js} +6 -6
- package/dist/source-resolver-HZQLOODU.js.map +1 -0
- package/dist/{sync-warehouse-UWRNUXE7.js → sync-warehouse-26L6JDSV.js} +10 -10
- package/dist/{tables-V65QUGHK.js → tables-K5NAN2WK.js} +6 -6
- package/dist/templates/default/docs/yamchart-reference.md +46 -0
- package/dist/{test-UE5OWG3E.js → test-SRHVOXZB.js} +8 -7
- package/dist/{test-UE5OWG3E.js.map → test-SRHVOXZB.js.map} +1 -1
- package/package.json +2 -2
- package/dist/advisor-SC64RTZO.js.map +0 -1
- package/dist/chunk-7CD6UQAV.js.map +0 -1
- package/dist/chunk-NXQ6ZO3V.js.map +0 -1
- package/dist/chunk-RM6MNDVF.js.map +0 -1
- package/dist/chunk-S7YQXEKM.js.map +0 -1
- package/dist/chunk-UND73EOB.js.map +0 -1
- package/dist/dev-7YLRQ6SA.js.map +0 -1
- package/dist/dist-MNXSMGV6.js +0 -790
- package/dist/dist-MNXSMGV6.js.map +0 -1
- /package/dist/{chunk-RMIDEBHD.js.map → chunk-S2CH4HUZ.js.map} +0 -0
- /package/dist/{chunk-H4L3FNLS.js.map → chunk-ZBCQNWVN.js.map} +0 -0
- /package/dist/{connection-utils-C4FQGBW6.js.map → connection-utils-FEUWER5I.js.map} +0 -0
- /package/dist/{describe-X75C2VDU.js.map → describe-MEP72B56.js.map} +0 -0
- /package/dist/{dist-JMLAZUL7.js.map → dist-5IFWLWND.js.map} +0 -0
- /package/dist/{dist-MX5K2ABB.js.map → dist-MIKFZKSD.js.map} +0 -0
- /package/dist/{dist-NGQG7Z4G.js.map → dist-PPAD6KOM.js.map} +0 -0
- /package/dist/{source-resolver-R7WBIL7M.js.map → dist-XNCED7JW.js.map} +0 -0
- /package/dist/{query-QNRDS74I.js.map → query-2H3YOPI2.js.map} +0 -0
- /package/dist/{reset-password-HDCLH7PZ.js.map → reset-password-YVCZKZPC.js.map} +0 -0
- /package/dist/{sample-SKLHBZBU.js.map → sample-ODUGGSFA.js.map} +0 -0
- /package/dist/{search-4KMETZVX.js.map → search-IPE4ISFB.js.map} +0 -0
- /package/dist/{sync-warehouse-UWRNUXE7.js.map → sync-warehouse-26L6JDSV.js.map} +0 -0
- /package/dist/{tables-V65QUGHK.js.map → tables-K5NAN2WK.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadEnvFile,
|
|
3
3
|
validateProject
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-S2CH4HUZ.js";
|
|
5
5
|
import {
|
|
6
6
|
box,
|
|
7
7
|
detail,
|
|
@@ -12,31 +12,14 @@ import {
|
|
|
12
12
|
spinner,
|
|
13
13
|
success
|
|
14
14
|
} from "./chunk-HJVVHYVN.js";
|
|
15
|
-
import {
|
|
16
|
-
ChartSchema,
|
|
17
|
-
ConnectionSchema,
|
|
18
|
-
DashboardSchema,
|
|
19
|
-
EventSchema,
|
|
20
|
-
EventsFileSchema,
|
|
21
|
-
ProjectSchema,
|
|
22
|
-
ScheduleSchema,
|
|
23
|
-
SemanticQuerySchema,
|
|
24
|
-
deepMerge,
|
|
25
|
-
resolveProjectConfig
|
|
26
|
-
} from "./chunk-RM6MNDVF.js";
|
|
27
15
|
import {
|
|
28
16
|
StreamingChatAgent,
|
|
29
17
|
filterCatalogEntries
|
|
30
18
|
} from "./chunk-ZMJPRNOA.js";
|
|
31
19
|
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
hashPassword,
|
|
36
|
-
hashToken,
|
|
37
|
-
parseTtl,
|
|
38
|
-
verifyPassword
|
|
39
|
-
} from "./chunk-7CD6UQAV.js";
|
|
20
|
+
createEmbedder,
|
|
21
|
+
createProviderForTask
|
|
22
|
+
} from "./chunk-FN6R2LAC.js";
|
|
40
23
|
import {
|
|
41
24
|
BigQueryConnector,
|
|
42
25
|
DuckDBConnector,
|
|
@@ -46,28 +29,56 @@ import {
|
|
|
46
29
|
ReconnectingConnector,
|
|
47
30
|
SQLiteConnector,
|
|
48
31
|
SnowflakeConnector,
|
|
49
|
-
|
|
32
|
+
buildSourceProfile,
|
|
50
33
|
createTemplateContext,
|
|
51
|
-
|
|
52
|
-
expandDatePreset,
|
|
53
|
-
expandRelativeDateRange,
|
|
54
|
-
formatPeriodLabel,
|
|
55
|
-
isCustomDateRange,
|
|
56
|
-
isDatePreset,
|
|
57
|
-
isRelativeDateRange,
|
|
34
|
+
isReadOnlySql,
|
|
58
35
|
parseModelMetadata,
|
|
59
36
|
renderTemplate,
|
|
60
37
|
resolveBigQueryAuth,
|
|
61
38
|
resolveMySQLAuth,
|
|
62
39
|
resolvePostgresAuth,
|
|
63
40
|
resolveSnowflakeAuth
|
|
64
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-QUIDZO5G.js";
|
|
65
42
|
import {
|
|
66
43
|
SemanticModelBuilder,
|
|
67
44
|
SemanticQueryCompiler,
|
|
68
45
|
SemanticValidationError,
|
|
46
|
+
buildSemanticLayer,
|
|
47
|
+
compileMetricQuery,
|
|
48
|
+
computePreviousPeriod,
|
|
49
|
+
expandCustomDateRange,
|
|
50
|
+
expandDatePreset,
|
|
51
|
+
expandRelativeDateRange,
|
|
52
|
+
formatPeriodLabel,
|
|
53
|
+
isCustomDateRange,
|
|
54
|
+
isDatePreset,
|
|
55
|
+
isRelativeDateRange,
|
|
56
|
+
loadSemanticDefinitions,
|
|
69
57
|
validateSemanticQuery
|
|
70
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-UFDQ3C7Q.js";
|
|
59
|
+
import {
|
|
60
|
+
ChartSchema,
|
|
61
|
+
ConnectionSchema,
|
|
62
|
+
DashboardSchema,
|
|
63
|
+
EventSchema,
|
|
64
|
+
EventsFileSchema,
|
|
65
|
+
NewBlindspotSchema,
|
|
66
|
+
ProjectSchema,
|
|
67
|
+
ScheduleSchema,
|
|
68
|
+
SemanticQuerySchema,
|
|
69
|
+
deepMerge,
|
|
70
|
+
resolveProjectConfig
|
|
71
|
+
} from "./chunk-ZIY22VO7.js";
|
|
72
|
+
import "./chunk-QJ5CPQJK.js";
|
|
73
|
+
import {
|
|
74
|
+
AuthDatabase,
|
|
75
|
+
generateOpaqueToken,
|
|
76
|
+
generateSessionToken,
|
|
77
|
+
hashPassword,
|
|
78
|
+
hashToken,
|
|
79
|
+
parseTtl,
|
|
80
|
+
verifyPassword
|
|
81
|
+
} from "./chunk-DZVT6PHW.js";
|
|
71
82
|
import {
|
|
72
83
|
__commonJS,
|
|
73
84
|
__toESM
|
|
@@ -2230,14 +2241,14 @@ var require_resolve = __commonJS({
|
|
|
2230
2241
|
"enum",
|
|
2231
2242
|
"const"
|
|
2232
2243
|
]);
|
|
2233
|
-
function inlineRef(schema,
|
|
2244
|
+
function inlineRef(schema, limit2 = true) {
|
|
2234
2245
|
if (typeof schema == "boolean")
|
|
2235
2246
|
return true;
|
|
2236
|
-
if (
|
|
2247
|
+
if (limit2 === true)
|
|
2237
2248
|
return !hasRef(schema);
|
|
2238
|
-
if (!
|
|
2249
|
+
if (!limit2)
|
|
2239
2250
|
return false;
|
|
2240
|
-
return countKeys(schema) <=
|
|
2251
|
+
return countKeys(schema) <= limit2;
|
|
2241
2252
|
}
|
|
2242
2253
|
exports.inlineRef = inlineRef;
|
|
2243
2254
|
var REF_KEYWORDS = /* @__PURE__ */ new Set([
|
|
@@ -7762,7 +7773,7 @@ var QueryService = class {
|
|
|
7762
7773
|
async executeRawSql(sql) {
|
|
7763
7774
|
return this.connector.execute(sql);
|
|
7764
7775
|
}
|
|
7765
|
-
async executeModel(modelName, params, userContext,
|
|
7776
|
+
async executeModel(modelName, params, userContext, limit2 = 1e3, filters) {
|
|
7766
7777
|
const model = this.models.get(modelName);
|
|
7767
7778
|
if (!model) {
|
|
7768
7779
|
throw new Error(`Unknown model: ${modelName}`);
|
|
@@ -7771,9 +7782,9 @@ var QueryService = class {
|
|
|
7771
7782
|
let sql = renderTemplate(model.sql, context);
|
|
7772
7783
|
if (filters && Object.keys(filters).length > 0) {
|
|
7773
7784
|
const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
|
|
7774
|
-
sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${
|
|
7785
|
+
sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit2}`;
|
|
7775
7786
|
} else {
|
|
7776
|
-
sql = `SELECT * FROM (${sql}) _detail LIMIT ${
|
|
7787
|
+
sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit2}`;
|
|
7777
7788
|
}
|
|
7778
7789
|
return this.connector.execute(sql);
|
|
7779
7790
|
}
|
|
@@ -8820,8 +8831,8 @@ async function semanticRoutes(fastify, options) {
|
|
|
8820
8831
|
});
|
|
8821
8832
|
}
|
|
8822
8833
|
const { writeFileSync, mkdirSync } = await import("fs");
|
|
8823
|
-
const { join:
|
|
8824
|
-
const fullPath =
|
|
8834
|
+
const { join: join12, dirname: dirname3 } = await import("path");
|
|
8835
|
+
const fullPath = join12(projectDir, filePath);
|
|
8825
8836
|
mkdirSync(dirname3(fullPath), { recursive: true });
|
|
8826
8837
|
writeFileSync(fullPath, sql, "utf-8");
|
|
8827
8838
|
return { saved: true, path: filePath };
|
|
@@ -9179,6 +9190,292 @@ var GitService = class {
|
|
|
9179
9190
|
}
|
|
9180
9191
|
};
|
|
9181
9192
|
|
|
9193
|
+
// ../../packages/knowledge/dist/memory-store.js
|
|
9194
|
+
import { randomUUID } from "crypto";
|
|
9195
|
+
|
|
9196
|
+
// ../../packages/knowledge/dist/postgres-store.js
|
|
9197
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
9198
|
+
import pg from "pg";
|
|
9199
|
+
|
|
9200
|
+
// ../../packages/knowledge/dist/migrations.js
|
|
9201
|
+
var MIGRATIONS = [
|
|
9202
|
+
`CREATE TABLE IF NOT EXISTS source_profiles (
|
|
9203
|
+
connection TEXT PRIMARY KEY,
|
|
9204
|
+
profiled_at TIMESTAMPTZ NOT NULL,
|
|
9205
|
+
profile JSONB NOT NULL
|
|
9206
|
+
);`,
|
|
9207
|
+
`CREATE TABLE IF NOT EXISTS hunches (
|
|
9208
|
+
id TEXT PRIMARY KEY,
|
|
9209
|
+
connection TEXT NOT NULL,
|
|
9210
|
+
question TEXT NOT NULL,
|
|
9211
|
+
sql TEXT NOT NULL,
|
|
9212
|
+
generated_sql TEXT NOT NULL,
|
|
9213
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
9214
|
+
tables_used JSONB NOT NULL DEFAULT '[]',
|
|
9215
|
+
rationale TEXT,
|
|
9216
|
+
confidence DOUBLE PRECISION,
|
|
9217
|
+
last_run_sample JSONB,
|
|
9218
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
9219
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
9220
|
+
);`,
|
|
9221
|
+
`CREATE INDEX IF NOT EXISTS hunches_conn_status ON hunches (connection, status);`,
|
|
9222
|
+
`CREATE TABLE IF NOT EXISTS blindspots (
|
|
9223
|
+
id TEXT PRIMARY KEY, connection TEXT NOT NULL, kind TEXT NOT NULL,
|
|
9224
|
+
title TEXT NOT NULL, detail TEXT NOT NULL, subject TEXT,
|
|
9225
|
+
suggested_model JSONB, confidence DOUBLE PRECISION,
|
|
9226
|
+
status TEXT NOT NULL DEFAULT 'open', answer TEXT,
|
|
9227
|
+
created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL
|
|
9228
|
+
);`,
|
|
9229
|
+
`CREATE INDEX IF NOT EXISTS blindspots_conn_status ON blindspots (connection, status);`,
|
|
9230
|
+
`CREATE TABLE IF NOT EXISTS knowledge_docs (
|
|
9231
|
+
id TEXT PRIMARY KEY, connection TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL,
|
|
9232
|
+
source TEXT NOT NULL DEFAULT 'ui', created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL
|
|
9233
|
+
);`,
|
|
9234
|
+
`CREATE INDEX IF NOT EXISTS knowledge_docs_conn ON knowledge_docs (connection);`,
|
|
9235
|
+
`CREATE TABLE IF NOT EXISTS knowledge_embeddings (
|
|
9236
|
+
id TEXT PRIMARY KEY, connection TEXT NOT NULL, source_kind TEXT NOT NULL, source_id TEXT NOT NULL,
|
|
9237
|
+
text TEXT NOT NULL, vector JSONB NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
9238
|
+
UNIQUE (source_kind, source_id)
|
|
9239
|
+
);`,
|
|
9240
|
+
`CREATE INDEX IF NOT EXISTS knowledge_embeddings_conn ON knowledge_embeddings (connection);`
|
|
9241
|
+
];
|
|
9242
|
+
|
|
9243
|
+
// ../../packages/knowledge/dist/postgres-store.js
|
|
9244
|
+
function rowToBlindspot(r) {
|
|
9245
|
+
return {
|
|
9246
|
+
id: r.id,
|
|
9247
|
+
connection: r.connection,
|
|
9248
|
+
kind: r.kind,
|
|
9249
|
+
title: r.title,
|
|
9250
|
+
detail: r.detail,
|
|
9251
|
+
subject: r.subject ?? void 0,
|
|
9252
|
+
suggestedModel: r.suggested_model ?? void 0,
|
|
9253
|
+
confidence: r.confidence ?? void 0,
|
|
9254
|
+
status: r.status,
|
|
9255
|
+
answer: r.answer ?? void 0,
|
|
9256
|
+
createdAt: new Date(r.created_at).toISOString(),
|
|
9257
|
+
updatedAt: new Date(r.updated_at).toISOString()
|
|
9258
|
+
};
|
|
9259
|
+
}
|
|
9260
|
+
function rowToKnowledgeDoc(r) {
|
|
9261
|
+
return {
|
|
9262
|
+
id: r.id,
|
|
9263
|
+
connection: r.connection,
|
|
9264
|
+
title: r.title,
|
|
9265
|
+
content: r.content,
|
|
9266
|
+
source: r.source,
|
|
9267
|
+
createdAt: new Date(r.created_at).toISOString(),
|
|
9268
|
+
updatedAt: new Date(r.updated_at).toISOString()
|
|
9269
|
+
};
|
|
9270
|
+
}
|
|
9271
|
+
function rowToHunch(r) {
|
|
9272
|
+
return {
|
|
9273
|
+
id: r.id,
|
|
9274
|
+
connection: r.connection,
|
|
9275
|
+
question: r.question,
|
|
9276
|
+
sql: r.sql,
|
|
9277
|
+
generatedSql: r.generated_sql,
|
|
9278
|
+
status: r.status,
|
|
9279
|
+
tablesUsed: r.tables_used ?? [],
|
|
9280
|
+
rationale: r.rationale ?? void 0,
|
|
9281
|
+
confidence: r.confidence ?? void 0,
|
|
9282
|
+
lastRunSample: r.last_run_sample ?? void 0,
|
|
9283
|
+
createdAt: new Date(r.created_at).toISOString(),
|
|
9284
|
+
updatedAt: new Date(r.updated_at).toISOString()
|
|
9285
|
+
};
|
|
9286
|
+
}
|
|
9287
|
+
var PostgresKnowledgeStore = class {
|
|
9288
|
+
pool;
|
|
9289
|
+
constructor(connectionString) {
|
|
9290
|
+
this.pool = new pg.Pool({ connectionString });
|
|
9291
|
+
}
|
|
9292
|
+
async init() {
|
|
9293
|
+
for (const m of MIGRATIONS)
|
|
9294
|
+
await this.pool.query(m);
|
|
9295
|
+
}
|
|
9296
|
+
async saveProfile(connection, profile) {
|
|
9297
|
+
await this.pool.query(`INSERT INTO source_profiles (connection, profiled_at, profile) VALUES ($1,$2,$3)
|
|
9298
|
+
ON CONFLICT (connection) DO UPDATE SET profiled_at = EXCLUDED.profiled_at, profile = EXCLUDED.profile`, [connection, profile.profiledAt, JSON.stringify(profile)]);
|
|
9299
|
+
}
|
|
9300
|
+
async getProfile(connection) {
|
|
9301
|
+
const { rows } = await this.pool.query(`SELECT profile FROM source_profiles WHERE connection = $1`, [connection]);
|
|
9302
|
+
return rows[0]?.profile;
|
|
9303
|
+
}
|
|
9304
|
+
async createHunches(items) {
|
|
9305
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9306
|
+
const out = [];
|
|
9307
|
+
for (const it of items) {
|
|
9308
|
+
const id = randomUUID2();
|
|
9309
|
+
const { rows } = await this.pool.query(`INSERT INTO hunches (id, connection, question, sql, generated_sql, status, tables_used, rationale, confidence, created_at, updated_at)
|
|
9310
|
+
VALUES ($1,$2,$3,$4,$5,'pending',$6,$7,$8,$9,$9) RETURNING *`, [id, it.connection, it.question, it.sql, it.generatedSql, JSON.stringify(it.tablesUsed ?? []), it.rationale ?? null, it.confidence ?? null, now]);
|
|
9311
|
+
out.push(rowToHunch(rows[0]));
|
|
9312
|
+
}
|
|
9313
|
+
return out;
|
|
9314
|
+
}
|
|
9315
|
+
async listHunches(filter) {
|
|
9316
|
+
const clauses = [];
|
|
9317
|
+
const params = [];
|
|
9318
|
+
if (filter.connection) {
|
|
9319
|
+
params.push(filter.connection);
|
|
9320
|
+
clauses.push(`connection = $${params.length}`);
|
|
9321
|
+
}
|
|
9322
|
+
if (filter.status) {
|
|
9323
|
+
params.push(filter.status);
|
|
9324
|
+
clauses.push(`status = $${params.length}`);
|
|
9325
|
+
}
|
|
9326
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9327
|
+
const { rows } = await this.pool.query(`SELECT * FROM hunches ${where} ORDER BY created_at DESC`, params);
|
|
9328
|
+
return rows.map(rowToHunch);
|
|
9329
|
+
}
|
|
9330
|
+
async getHunch(id) {
|
|
9331
|
+
const { rows } = await this.pool.query(`SELECT * FROM hunches WHERE id = $1`, [id]);
|
|
9332
|
+
return rows[0] ? rowToHunch(rows[0]) : void 0;
|
|
9333
|
+
}
|
|
9334
|
+
async updateHunch(id, patch) {
|
|
9335
|
+
const sets = [];
|
|
9336
|
+
const params = [];
|
|
9337
|
+
if (patch.sql !== void 0) {
|
|
9338
|
+
params.push(patch.sql);
|
|
9339
|
+
sets.push(`sql = $${params.length}`);
|
|
9340
|
+
}
|
|
9341
|
+
if (patch.status !== void 0) {
|
|
9342
|
+
params.push(patch.status);
|
|
9343
|
+
sets.push(`status = $${params.length}`);
|
|
9344
|
+
}
|
|
9345
|
+
if (patch.lastRunSample !== void 0) {
|
|
9346
|
+
params.push(JSON.stringify(patch.lastRunSample));
|
|
9347
|
+
sets.push(`last_run_sample = $${params.length}`);
|
|
9348
|
+
}
|
|
9349
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
9350
|
+
sets.push(`updated_at = $${params.length}`);
|
|
9351
|
+
params.push(id);
|
|
9352
|
+
const { rows } = await this.pool.query(`UPDATE hunches SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
|
|
9353
|
+
if (!rows[0])
|
|
9354
|
+
throw new Error(`Hunch "${id}" not found.`);
|
|
9355
|
+
return rowToHunch(rows[0]);
|
|
9356
|
+
}
|
|
9357
|
+
async createBlindspots(items) {
|
|
9358
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9359
|
+
const out = [];
|
|
9360
|
+
for (const it of items) {
|
|
9361
|
+
const id = randomUUID2();
|
|
9362
|
+
const { rows } = await this.pool.query(`INSERT INTO blindspots (id, connection, kind, title, detail, subject, suggested_model, confidence, status, created_at, updated_at)
|
|
9363
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'open',$9,$9) RETURNING *`, [id, it.connection, it.kind, it.title, it.detail, it.subject ?? null, JSON.stringify(it.suggestedModel ?? null), it.confidence ?? null, now]);
|
|
9364
|
+
out.push(rowToBlindspot(rows[0]));
|
|
9365
|
+
}
|
|
9366
|
+
return out;
|
|
9367
|
+
}
|
|
9368
|
+
async listBlindspots(filter) {
|
|
9369
|
+
const clauses = [];
|
|
9370
|
+
const params = [];
|
|
9371
|
+
if (filter.connection) {
|
|
9372
|
+
params.push(filter.connection);
|
|
9373
|
+
clauses.push(`connection = $${params.length}`);
|
|
9374
|
+
}
|
|
9375
|
+
if (filter.status) {
|
|
9376
|
+
params.push(filter.status);
|
|
9377
|
+
clauses.push(`status = $${params.length}`);
|
|
9378
|
+
}
|
|
9379
|
+
if (filter.kind) {
|
|
9380
|
+
params.push(filter.kind);
|
|
9381
|
+
clauses.push(`kind = $${params.length}`);
|
|
9382
|
+
}
|
|
9383
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9384
|
+
const { rows } = await this.pool.query(`SELECT * FROM blindspots ${where} ORDER BY created_at DESC`, params);
|
|
9385
|
+
return rows.map(rowToBlindspot);
|
|
9386
|
+
}
|
|
9387
|
+
async getBlindspot(id) {
|
|
9388
|
+
const { rows } = await this.pool.query(`SELECT * FROM blindspots WHERE id = $1`, [id]);
|
|
9389
|
+
return rows[0] ? rowToBlindspot(rows[0]) : void 0;
|
|
9390
|
+
}
|
|
9391
|
+
async updateBlindspot(id, patch) {
|
|
9392
|
+
const sets = [];
|
|
9393
|
+
const params = [];
|
|
9394
|
+
if (patch.status !== void 0) {
|
|
9395
|
+
params.push(patch.status);
|
|
9396
|
+
sets.push(`status = $${params.length}`);
|
|
9397
|
+
}
|
|
9398
|
+
if (patch.answer !== void 0) {
|
|
9399
|
+
params.push(patch.answer);
|
|
9400
|
+
sets.push(`answer = $${params.length}`);
|
|
9401
|
+
}
|
|
9402
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
9403
|
+
sets.push(`updated_at = $${params.length}`);
|
|
9404
|
+
params.push(id);
|
|
9405
|
+
const { rows } = await this.pool.query(`UPDATE blindspots SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
|
|
9406
|
+
if (!rows[0])
|
|
9407
|
+
throw new Error(`Blindspot "${id}" not found.`);
|
|
9408
|
+
return rowToBlindspot(rows[0]);
|
|
9409
|
+
}
|
|
9410
|
+
async deleteOpenBlindspots(connection) {
|
|
9411
|
+
await this.pool.query(`DELETE FROM blindspots WHERE connection = $1 AND status = 'open'`, [connection]);
|
|
9412
|
+
}
|
|
9413
|
+
async createKnowledgeDoc(doc) {
|
|
9414
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9415
|
+
const id = randomUUID2();
|
|
9416
|
+
const { rows } = await this.pool.query(`INSERT INTO knowledge_docs (id, connection, title, content, source, created_at, updated_at)
|
|
9417
|
+
VALUES ($1,$2,$3,$4,$5,$6,$6) RETURNING *`, [id, doc.connection, doc.title, doc.content, doc.source, now]);
|
|
9418
|
+
return [rowToKnowledgeDoc(rows[0])];
|
|
9419
|
+
}
|
|
9420
|
+
async listKnowledgeDocs(filter) {
|
|
9421
|
+
const clauses = [];
|
|
9422
|
+
const params = [];
|
|
9423
|
+
if (filter.connection) {
|
|
9424
|
+
params.push(filter.connection);
|
|
9425
|
+
clauses.push(`connection = $${params.length}`);
|
|
9426
|
+
}
|
|
9427
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9428
|
+
const { rows } = await this.pool.query(`SELECT * FROM knowledge_docs ${where} ORDER BY created_at DESC`, params);
|
|
9429
|
+
return rows.map(rowToKnowledgeDoc);
|
|
9430
|
+
}
|
|
9431
|
+
async updateKnowledgeDoc(id, patch) {
|
|
9432
|
+
const sets = [];
|
|
9433
|
+
const params = [];
|
|
9434
|
+
if (patch.title !== void 0) {
|
|
9435
|
+
params.push(patch.title);
|
|
9436
|
+
sets.push(`title = $${params.length}`);
|
|
9437
|
+
}
|
|
9438
|
+
if (patch.content !== void 0) {
|
|
9439
|
+
params.push(patch.content);
|
|
9440
|
+
sets.push(`content = $${params.length}`);
|
|
9441
|
+
}
|
|
9442
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
9443
|
+
sets.push(`updated_at = $${params.length}`);
|
|
9444
|
+
params.push(id);
|
|
9445
|
+
const { rows } = await this.pool.query(`UPDATE knowledge_docs SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
|
|
9446
|
+
if (!rows[0])
|
|
9447
|
+
throw new Error(`KnowledgeDoc "${id}" not found.`);
|
|
9448
|
+
return rowToKnowledgeDoc(rows[0]);
|
|
9449
|
+
}
|
|
9450
|
+
async deleteKnowledgeDoc(id) {
|
|
9451
|
+
await this.pool.query(`DELETE FROM knowledge_docs WHERE id = $1`, [id]);
|
|
9452
|
+
}
|
|
9453
|
+
async upsertEmbedding(e) {
|
|
9454
|
+
const id = `${e.source_kind}:${e.source_id}`;
|
|
9455
|
+
await this.pool.query(`INSERT INTO knowledge_embeddings (id, connection, source_kind, source_id, text, vector, updated_at)
|
|
9456
|
+
VALUES ($1,$2,$3,$4,$5,$6,now())
|
|
9457
|
+
ON CONFLICT (source_kind, source_id) DO UPDATE SET text=EXCLUDED.text, vector=EXCLUDED.vector, updated_at=now()`, [id, e.connection, e.source_kind, e.source_id, e.text, JSON.stringify(e.vector)]);
|
|
9458
|
+
}
|
|
9459
|
+
async listEmbeddings(filter) {
|
|
9460
|
+
const clauses = [];
|
|
9461
|
+
const params = [];
|
|
9462
|
+
if (filter.connection) {
|
|
9463
|
+
params.push(filter.connection);
|
|
9464
|
+
clauses.push(`connection = $${params.length}`);
|
|
9465
|
+
}
|
|
9466
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9467
|
+
const { rows } = await this.pool.query(`SELECT * FROM knowledge_embeddings ${where}`, params);
|
|
9468
|
+
return rows.map((r) => ({ id: r.id, connection: r.connection, source_kind: r.source_kind, source_id: r.source_id, text: r.text, vector: r.vector }));
|
|
9469
|
+
}
|
|
9470
|
+
async getEmbedding(source_kind, source_id) {
|
|
9471
|
+
const { rows } = await this.pool.query(`SELECT * FROM knowledge_embeddings WHERE source_kind = $1 AND source_id = $2`, [source_kind, source_id]);
|
|
9472
|
+
if (!rows[0])
|
|
9473
|
+
return void 0;
|
|
9474
|
+
const r = rows[0];
|
|
9475
|
+
return { id: r.id, connection: r.connection, source_kind: r.source_kind, source_id: r.source_id, text: r.text, vector: r.vector };
|
|
9476
|
+
}
|
|
9477
|
+
};
|
|
9478
|
+
|
|
9182
9479
|
// ../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.es6.mjs
|
|
9183
9480
|
function __rest(s, e) {
|
|
9184
9481
|
var t = {};
|
|
@@ -11868,8 +12165,8 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
11868
12165
|
_trigger(type, payload, ref) {
|
|
11869
12166
|
var _a, _b;
|
|
11870
12167
|
const typeLower = type.toLocaleLowerCase();
|
|
11871
|
-
const { close, error: error2, leave, join:
|
|
11872
|
-
const events = [close, error2, leave,
|
|
12168
|
+
const { close, error: error2, leave, join: join12 } = CHANNEL_EVENTS;
|
|
12169
|
+
const events = [close, error2, leave, join12];
|
|
11873
12170
|
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
|
11874
12171
|
return;
|
|
11875
12172
|
}
|
|
@@ -20842,20 +21139,20 @@ async function authAdminRoutes(fastify, options) {
|
|
|
20842
21139
|
});
|
|
20843
21140
|
fastify.post("/api/auth/users", async (request, reply) => {
|
|
20844
21141
|
const { email, name, password, role } = request.body;
|
|
20845
|
-
if (!email || !name || !
|
|
20846
|
-
return reply.status(400).send({ error: "Email, name,
|
|
21142
|
+
if (!email || !name || !role) {
|
|
21143
|
+
return reply.status(400).send({ error: "Email, name, and role are required" });
|
|
20847
21144
|
}
|
|
20848
21145
|
if (!["admin", "editor", "viewer"].includes(role)) {
|
|
20849
21146
|
return reply.status(400).send({ error: "Role must be admin, editor, or viewer" });
|
|
20850
21147
|
}
|
|
20851
|
-
if (password.length < 8) {
|
|
21148
|
+
if (password && password.length < 8) {
|
|
20852
21149
|
return reply.status(400).send({ error: "Password must be at least 8 characters" });
|
|
20853
21150
|
}
|
|
20854
21151
|
const existing = authDb.getUserByEmail(email);
|
|
20855
21152
|
if (existing) {
|
|
20856
21153
|
return reply.status(409).send({ error: "A user with this email already exists" });
|
|
20857
21154
|
}
|
|
20858
|
-
const passwordHash = await hashPassword(password);
|
|
21155
|
+
const passwordHash = password ? await hashPassword(password) : null;
|
|
20859
21156
|
const user = authDb.createUser({ email, name, password_hash: passwordHash, role });
|
|
20860
21157
|
return reply.status(201).send({ user });
|
|
20861
21158
|
});
|
|
@@ -21016,6 +21313,21 @@ async function authSSORoutes(fastify, options) {
|
|
|
21016
21313
|
}
|
|
21017
21314
|
const existingByEmail = authDb.getUserByEmail(email);
|
|
21018
21315
|
if (existingByEmail) {
|
|
21316
|
+
const isInvite = existingByEmail.password_hash === null && (existingByEmail.provider === "local" || existingByEmail.provider === provider) && !existingByEmail.external_id;
|
|
21317
|
+
if (isInvite) {
|
|
21318
|
+
authDb.updateUser(existingByEmail.id, {
|
|
21319
|
+
provider,
|
|
21320
|
+
external_id: externalId,
|
|
21321
|
+
name
|
|
21322
|
+
});
|
|
21323
|
+
const token2 = generateSessionToken();
|
|
21324
|
+
authDb.createSession(token2, existingByEmail.id, sessionTtlMs);
|
|
21325
|
+
reply.setCookie("yamchart_session", token2, cookieOptions);
|
|
21326
|
+
const ret2 = sanitizeReturnTo(request.cookies?.yamchart_sso_return);
|
|
21327
|
+
if (ret2)
|
|
21328
|
+
reply.clearCookie("yamchart_sso_return", { path: "/" });
|
|
21329
|
+
return reply.redirect(ret2 ?? "/#/");
|
|
21330
|
+
}
|
|
21019
21331
|
return reply.redirect("/#/login?error=account_conflict");
|
|
21020
21332
|
}
|
|
21021
21333
|
const { allowed, role } = resolveSsoProvisioning(email, config);
|
|
@@ -21414,7 +21726,7 @@ async function chatRoutes(fastify, options) {
|
|
|
21414
21726
|
systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
|
|
21415
21727
|
}
|
|
21416
21728
|
const systemPrompt = systemParts.join("\n");
|
|
21417
|
-
const { AnthropicProvider } = await import("./dist-
|
|
21729
|
+
const { AnthropicProvider } = await import("./dist-5IFWLWND.js");
|
|
21418
21730
|
const provider = new AnthropicProvider(apiKey);
|
|
21419
21731
|
const response = await provider.chat({
|
|
21420
21732
|
system: systemPrompt,
|
|
@@ -28512,8 +28824,8 @@ async function runSql(deps, args) {
|
|
|
28512
28824
|
if (DESTRUCTIVE.test(sql) || /^\s*WITH\b/i.test(sql) && WRITABLE_CTE.test(sql)) {
|
|
28513
28825
|
return "Error: Destructive SQL statements are not allowed through the MCP.";
|
|
28514
28826
|
}
|
|
28515
|
-
const
|
|
28516
|
-
const limited = /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT ${
|
|
28827
|
+
const limit2 = args.limit ?? DEFAULT_LIMIT;
|
|
28828
|
+
const limited = /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT ${limit2}`;
|
|
28517
28829
|
const result = await deps.queryService.executeRawSql(limited);
|
|
28518
28830
|
return JSON.stringify({ columns: result.columns, rows: result.rows });
|
|
28519
28831
|
}
|
|
@@ -28569,8 +28881,9 @@ async function listTables(deps) {
|
|
|
28569
28881
|
})));
|
|
28570
28882
|
}
|
|
28571
28883
|
async function describeTable(deps, args) {
|
|
28572
|
-
const
|
|
28573
|
-
const
|
|
28884
|
+
const normalizedTable = args.schema != null && !args.table.includes(".") ? `${args.schema}.${args.table}` : args.table;
|
|
28885
|
+
const parts = normalizedTable.split(".");
|
|
28886
|
+
const tableName = parts[parts.length - 1] ?? normalizedTable;
|
|
28574
28887
|
const schema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
|
|
28575
28888
|
const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
|
|
28576
28889
|
const match = tables.find((t) => t.name === tableName && (schema === void 0 || t.schema === schema));
|
|
@@ -28588,14 +28901,14 @@ async function sampleTable(deps, args) {
|
|
|
28588
28901
|
if (!TABLE_IDENT.test(args.table)) {
|
|
28589
28902
|
return `Error: Invalid table identifier "${args.table}".`;
|
|
28590
28903
|
}
|
|
28591
|
-
const
|
|
28904
|
+
const limit2 = Math.min(Math.max(args.limit ?? 5, 1), 100);
|
|
28592
28905
|
const parts = args.table.split(".");
|
|
28593
28906
|
const tableName = parts[parts.length - 1] ?? args.table;
|
|
28594
28907
|
const providedSchema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
|
|
28595
28908
|
const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
|
|
28596
28909
|
const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || t.schema === providedSchema));
|
|
28597
28910
|
const from = providedSchema === void 0 && match?.schema ? `${match.schema}.${args.table}` : args.table;
|
|
28598
|
-
const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${
|
|
28911
|
+
const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit2}`);
|
|
28599
28912
|
return JSON.stringify({ columns: result.columns, rows: result.rows });
|
|
28600
28913
|
}
|
|
28601
28914
|
|
|
@@ -28676,11 +28989,40 @@ function renderChartToPng(option, opts = {}) {
|
|
|
28676
28989
|
return Buffer.from(resvg.render().asPng());
|
|
28677
28990
|
}
|
|
28678
28991
|
|
|
28992
|
+
// ../../packages/mcp-core/dist/chart-errors.js
|
|
28993
|
+
function flattenIssues(issues) {
|
|
28994
|
+
const flat = [];
|
|
28995
|
+
for (const issue of issues) {
|
|
28996
|
+
if (issue.code === "invalid_union" && issue.unionErrors?.length) {
|
|
28997
|
+
const typeMatchMember = issue.unionErrors.find((e) => !e.issues.some((i) => i.path.length >= 2 && i.path[i.path.length - 1] === "type" && i.code === "invalid_literal"));
|
|
28998
|
+
const best = typeMatchMember ?? issue.unionErrors.reduce((a, b) => a.issues.length <= b.issues.length ? a : b);
|
|
28999
|
+
flat.push(...flattenIssues(best.issues));
|
|
29000
|
+
} else {
|
|
29001
|
+
const path = issue.path.length ? issue.path.join(".") : "(root)";
|
|
29002
|
+
let detail2 = issue.message;
|
|
29003
|
+
if (/expected object, received string/i.test(issue.message)) {
|
|
29004
|
+
detail2 = `expected an object like { field: "<column>", type: "temporal|quantitative|ordinal|nominal" }, received a string`;
|
|
29005
|
+
}
|
|
29006
|
+
flat.push({ path, message: detail2 });
|
|
29007
|
+
}
|
|
29008
|
+
}
|
|
29009
|
+
return flat;
|
|
29010
|
+
}
|
|
29011
|
+
function formatChartErrors(error2) {
|
|
29012
|
+
const issues = flattenIssues(error2.issues);
|
|
29013
|
+
const lines = issues.map((i) => ` \u2022 ${i.path}: ${i.message}`);
|
|
29014
|
+
return [
|
|
29015
|
+
"Invalid chart spec:",
|
|
29016
|
+
...lines,
|
|
29017
|
+
"Tip: call get_chart_schema (optionally with { type }) for the exact shape and a working example."
|
|
29018
|
+
].join("\n");
|
|
29019
|
+
}
|
|
29020
|
+
|
|
28679
29021
|
// ../../packages/mcp-core/dist/tools/visualize.js
|
|
28680
29022
|
async function visualize(_deps, args) {
|
|
28681
29023
|
const parsed = ChartSchema.safeParse(args.spec);
|
|
28682
29024
|
if (!parsed.success) {
|
|
28683
|
-
return { error:
|
|
29025
|
+
return { error: formatChartErrors(parsed.error) };
|
|
28684
29026
|
}
|
|
28685
29027
|
try {
|
|
28686
29028
|
const option = buildEChartsOption(parsed.data, args.data ?? []);
|
|
@@ -28707,7 +29049,7 @@ async function saveChart(deps, args) {
|
|
|
28707
29049
|
}
|
|
28708
29050
|
const parsed = ChartSchema.safeParse(args.spec);
|
|
28709
29051
|
if (!parsed.success) {
|
|
28710
|
-
return `Error:
|
|
29052
|
+
return `Error: ${formatChartErrors(parsed.error)}`;
|
|
28711
29053
|
}
|
|
28712
29054
|
const name = parsed.data.name;
|
|
28713
29055
|
if (!SAFE_NAME.test(name)) {
|
|
@@ -28723,6 +29065,450 @@ async function saveChart(deps, args) {
|
|
|
28723
29065
|
return `Chart "${name}" saved to charts/${name}.yaml`;
|
|
28724
29066
|
}
|
|
28725
29067
|
|
|
29068
|
+
// ../../packages/mcp-core/dist/suggested-chart.js
|
|
29069
|
+
function suggestChart(input) {
|
|
29070
|
+
const { name, title, metrics, timeDimension, dimensions } = input;
|
|
29071
|
+
const primary = metrics[0] ?? { name: "value", format: void 0 };
|
|
29072
|
+
if (dimensions.length === 0) {
|
|
29073
|
+
return {
|
|
29074
|
+
name,
|
|
29075
|
+
title,
|
|
29076
|
+
chart: {
|
|
29077
|
+
type: "kpi",
|
|
29078
|
+
value: { field: primary.name },
|
|
29079
|
+
// KpiFormatSchema requires `type` — always include it.
|
|
29080
|
+
format: primary.format?.includes("$") ? { type: "currency", currency: "USD", decimals: 0 } : { type: "number" }
|
|
29081
|
+
}
|
|
29082
|
+
};
|
|
29083
|
+
}
|
|
29084
|
+
const groupField = dimensions[0] ?? "dimension";
|
|
29085
|
+
const isTime = timeDimension && dimensions.includes(timeDimension);
|
|
29086
|
+
return {
|
|
29087
|
+
name,
|
|
29088
|
+
title,
|
|
29089
|
+
chart: {
|
|
29090
|
+
type: "bar",
|
|
29091
|
+
x: { field: groupField, type: isTime ? "temporal" : "nominal" },
|
|
29092
|
+
y: {
|
|
29093
|
+
field: primary.name,
|
|
29094
|
+
type: "quantitative",
|
|
29095
|
+
label: primary.name,
|
|
29096
|
+
...primary.format ? { format: primary.format } : {}
|
|
29097
|
+
}
|
|
29098
|
+
}
|
|
29099
|
+
};
|
|
29100
|
+
}
|
|
29101
|
+
|
|
29102
|
+
// ../../packages/mcp-core/dist/tools/semantic.js
|
|
29103
|
+
function listMetrics(deps) {
|
|
29104
|
+
return JSON.stringify(deps.semanticLayer.listMetrics(), null, 2);
|
|
29105
|
+
}
|
|
29106
|
+
function getMetric(deps, args) {
|
|
29107
|
+
const resolved = deps.semanticLayer.resolveMetric(args.name);
|
|
29108
|
+
if (!resolved) {
|
|
29109
|
+
return JSON.stringify({ error: `Unknown metric "${args.name}". Call list_metrics to see governed metrics.` });
|
|
29110
|
+
}
|
|
29111
|
+
const { metric, entity } = resolved;
|
|
29112
|
+
return JSON.stringify({
|
|
29113
|
+
name: metric.name,
|
|
29114
|
+
label: metric.label,
|
|
29115
|
+
description: metric.description,
|
|
29116
|
+
entity: entity.name,
|
|
29117
|
+
source: entity.sourceRef,
|
|
29118
|
+
agg: metric.agg,
|
|
29119
|
+
field: metric.field,
|
|
29120
|
+
sql: metric.sql,
|
|
29121
|
+
filter: metric.filter,
|
|
29122
|
+
filter_sql: metric.filter ? entity.filters[metric.filter]?.sql : void 0,
|
|
29123
|
+
synonyms: metric.synonyms ?? [],
|
|
29124
|
+
format: metric.format,
|
|
29125
|
+
dimensions: entity.dimensions,
|
|
29126
|
+
time_dimension: entity.timeDimension
|
|
29127
|
+
}, null, 2);
|
|
29128
|
+
}
|
|
29129
|
+
async function queryMetric(deps, args) {
|
|
29130
|
+
const resolved = args.metrics.map((m) => {
|
|
29131
|
+
const r = deps.semanticLayer.resolveMetric(m);
|
|
29132
|
+
if (!r)
|
|
29133
|
+
throw new Error(`Unknown metric "${m}". Call list_metrics.`);
|
|
29134
|
+
return r;
|
|
29135
|
+
});
|
|
29136
|
+
if (resolved.length === 0) {
|
|
29137
|
+
return JSON.stringify({ error: "At least one metric is required." });
|
|
29138
|
+
}
|
|
29139
|
+
const entity = resolved[0].entity;
|
|
29140
|
+
if (resolved.some((r) => r.entity.name !== entity.name)) {
|
|
29141
|
+
return JSON.stringify({
|
|
29142
|
+
error: `Metrics span multiple entities (${[...new Set(resolved.map((r) => r.entity.name))].join(", ")}). Query one entity at a time in Phase 1.`
|
|
29143
|
+
});
|
|
29144
|
+
}
|
|
29145
|
+
const canonicalMetrics = resolved.map((r) => r.metric.name);
|
|
29146
|
+
const { sql, metrics } = compileMetricQuery(entity, { ...args, metrics: canonicalMetrics }, deps.connectionType);
|
|
29147
|
+
const result = await deps.queryService.executeRawSql(sql);
|
|
29148
|
+
const explicitDimensions = args.dimensions ?? [];
|
|
29149
|
+
const effectiveDimensions = args.grain && entity.timeDimension && !explicitDimensions.includes(entity.timeDimension) ? [entity.timeDimension, ...explicitDimensions] : explicitDimensions;
|
|
29150
|
+
const suggested_chart = suggestChart({
|
|
29151
|
+
name: `metric_${canonicalMetrics.join("_")}`.slice(0, 60),
|
|
29152
|
+
title: metrics.map((m) => m.label ?? m.name).join(", "),
|
|
29153
|
+
metrics: metrics.map((m) => ({ name: m.name, format: m.format })),
|
|
29154
|
+
timeDimension: entity.timeDimension,
|
|
29155
|
+
dimensions: effectiveDimensions
|
|
29156
|
+
});
|
|
29157
|
+
return JSON.stringify({ columns: result.columns, rows: result.rows, sql, suggested_chart }, null, 2);
|
|
29158
|
+
}
|
|
29159
|
+
|
|
29160
|
+
// ../../packages/mcp-core/dist/tools/chart-dx.js
|
|
29161
|
+
var EXAMPLES = {
|
|
29162
|
+
bar: {
|
|
29163
|
+
name: "revenue_by_month",
|
|
29164
|
+
title: "Revenue by Month",
|
|
29165
|
+
source: { model: "monthly_revenue" },
|
|
29166
|
+
chart: {
|
|
29167
|
+
type: "bar",
|
|
29168
|
+
x: { field: "month", type: "temporal" },
|
|
29169
|
+
y: { field: "invoiced_revenue", type: "quantitative", format: "$,.0f" }
|
|
29170
|
+
}
|
|
29171
|
+
},
|
|
29172
|
+
line: {
|
|
29173
|
+
name: "revenue_trend",
|
|
29174
|
+
title: "Revenue Trend",
|
|
29175
|
+
source: { model: "monthly_revenue" },
|
|
29176
|
+
chart: {
|
|
29177
|
+
type: "line",
|
|
29178
|
+
x: { field: "month", type: "temporal" },
|
|
29179
|
+
y: { field: "invoiced_revenue", type: "quantitative" }
|
|
29180
|
+
}
|
|
29181
|
+
},
|
|
29182
|
+
area: {
|
|
29183
|
+
name: "revenue_area",
|
|
29184
|
+
title: "Revenue Area",
|
|
29185
|
+
source: { model: "monthly_revenue" },
|
|
29186
|
+
chart: {
|
|
29187
|
+
type: "area",
|
|
29188
|
+
x: { field: "month", type: "temporal" },
|
|
29189
|
+
y: { field: "invoiced_revenue", type: "quantitative" }
|
|
29190
|
+
}
|
|
29191
|
+
},
|
|
29192
|
+
kpi: {
|
|
29193
|
+
name: "total_revenue",
|
|
29194
|
+
title: "Total Revenue",
|
|
29195
|
+
source: { model: "total_revenue" },
|
|
29196
|
+
chart: {
|
|
29197
|
+
type: "kpi",
|
|
29198
|
+
value: { field: "total_revenue" },
|
|
29199
|
+
format: { type: "currency", currency: "USD", decimals: 0 }
|
|
29200
|
+
}
|
|
29201
|
+
},
|
|
29202
|
+
pie: {
|
|
29203
|
+
name: "revenue_by_category",
|
|
29204
|
+
title: "Revenue by Category",
|
|
29205
|
+
source: { model: "category_revenue" },
|
|
29206
|
+
chart: {
|
|
29207
|
+
type: "pie",
|
|
29208
|
+
x: { field: "category", type: "nominal" },
|
|
29209
|
+
y: { field: "revenue", type: "quantitative" }
|
|
29210
|
+
}
|
|
29211
|
+
},
|
|
29212
|
+
scatter: {
|
|
29213
|
+
name: "jobs_scatter",
|
|
29214
|
+
title: "Jobs Scatter",
|
|
29215
|
+
source: { model: "jobs" },
|
|
29216
|
+
chart: {
|
|
29217
|
+
type: "scatter",
|
|
29218
|
+
x: { field: "cost", type: "quantitative" },
|
|
29219
|
+
y: { field: "revenue", type: "quantitative" }
|
|
29220
|
+
}
|
|
29221
|
+
},
|
|
29222
|
+
table: {
|
|
29223
|
+
name: "jobs_table",
|
|
29224
|
+
title: "Jobs Table",
|
|
29225
|
+
source: { model: "jobs" },
|
|
29226
|
+
chart: {
|
|
29227
|
+
type: "table",
|
|
29228
|
+
columns: [
|
|
29229
|
+
{ field: "job_id", label: "Job ID" },
|
|
29230
|
+
{ field: "revenue", label: "Revenue", format: "$,.0f" }
|
|
29231
|
+
]
|
|
29232
|
+
}
|
|
29233
|
+
}
|
|
29234
|
+
};
|
|
29235
|
+
function getChartSchema(args) {
|
|
29236
|
+
const type = args.type != null && EXAMPLES[args.type] != null ? args.type : "bar";
|
|
29237
|
+
return JSON.stringify({
|
|
29238
|
+
note: "Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }. source is { model } or { sql }.",
|
|
29239
|
+
available_examples: Object.keys(EXAMPLES),
|
|
29240
|
+
example: EXAMPLES[type]
|
|
29241
|
+
}, null, 2);
|
|
29242
|
+
}
|
|
29243
|
+
function validateChart(args) {
|
|
29244
|
+
const parsed = ChartSchema.safeParse(args.spec);
|
|
29245
|
+
if (parsed.success)
|
|
29246
|
+
return JSON.stringify({ valid: true });
|
|
29247
|
+
return JSON.stringify({ valid: false, errors: formatChartErrors(parsed.error) });
|
|
29248
|
+
}
|
|
29249
|
+
function getChartSpec(deps, args) {
|
|
29250
|
+
const spec = deps.configLoader.getChartByName(args.name);
|
|
29251
|
+
if (spec == null) {
|
|
29252
|
+
return JSON.stringify({ error: `Chart "${args.name}" not found. Call list_charts to see available charts.` });
|
|
29253
|
+
}
|
|
29254
|
+
return JSON.stringify(spec, null, 2);
|
|
29255
|
+
}
|
|
29256
|
+
|
|
29257
|
+
// ../../packages/learning/dist/prompt.js
|
|
29258
|
+
function buildGenerationPrompt(profile, metricNames, count, contextBlock = "") {
|
|
29259
|
+
const tables = profile.tables.map((t) => {
|
|
29260
|
+
const cols = t.columns.map((c) => {
|
|
29261
|
+
const top = c.topValues?.length ? ` top:[${c.topValues.slice(0, 6).map((v) => JSON.stringify(v.value)).join(", ")}]` : "";
|
|
29262
|
+
const dist = c.distinctCount != null ? ` distinct:${c.distinctCount}` : "";
|
|
29263
|
+
return ` - ${c.name} (${c.type})${dist}${top}`;
|
|
29264
|
+
}).join("\n");
|
|
29265
|
+
return ` ${t.table} (${t.rowCount ?? "?"} rows):
|
|
29266
|
+
${cols}`;
|
|
29267
|
+
}).join("\n");
|
|
29268
|
+
return [
|
|
29269
|
+
`You are profiling a data warehouse to propose ${count} useful business questions a user would ask, each with correct read-only SQL.`,
|
|
29270
|
+
contextBlock ? `Known business context:
|
|
29271
|
+
${contextBlock}` : "",
|
|
29272
|
+
metricNames.length ? `Prefer these governed metrics when relevant: ${metricNames.join(", ")}.` : "",
|
|
29273
|
+
`Use the ACTUAL column values shown (e.g. status values) \u2014 do not invent values.`,
|
|
29274
|
+
`Tables and profiled columns:`,
|
|
29275
|
+
tables,
|
|
29276
|
+
`Respond with ONLY JSON: {"hunches":[{"question":string,"sql":string,"tablesUsed":string[],"confidence":number,"rationale":string}]}. SQL must be a single read-only SELECT.`
|
|
29277
|
+
].filter(Boolean).join("\n");
|
|
29278
|
+
}
|
|
29279
|
+
|
|
29280
|
+
// ../../packages/learning/dist/json.js
|
|
29281
|
+
function extractJson(text2) {
|
|
29282
|
+
const start = text2.indexOf("{");
|
|
29283
|
+
if (start === -1)
|
|
29284
|
+
return {};
|
|
29285
|
+
let depth = 0;
|
|
29286
|
+
for (let i = start; i < text2.length; i++) {
|
|
29287
|
+
if (text2[i] === "{")
|
|
29288
|
+
depth++;
|
|
29289
|
+
else if (text2[i] === "}") {
|
|
29290
|
+
depth--;
|
|
29291
|
+
if (depth === 0) {
|
|
29292
|
+
try {
|
|
29293
|
+
return JSON.parse(text2.slice(start, i + 1));
|
|
29294
|
+
} catch {
|
|
29295
|
+
return {};
|
|
29296
|
+
}
|
|
29297
|
+
}
|
|
29298
|
+
}
|
|
29299
|
+
}
|
|
29300
|
+
return {};
|
|
29301
|
+
}
|
|
29302
|
+
|
|
29303
|
+
// ../../packages/learning/dist/generator.js
|
|
29304
|
+
async function generateHunches(input) {
|
|
29305
|
+
const count = input.count ?? 20;
|
|
29306
|
+
const prompt = buildGenerationPrompt(input.profile, input.metricNames, count, input.contextBlock ?? "");
|
|
29307
|
+
const res = await input.provider.chat({ system: "You generate governed analytics questions and SQL.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 4e3 });
|
|
29308
|
+
const text2 = res.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
29309
|
+
const parsed = extractJson(text2);
|
|
29310
|
+
const raw = Array.isArray(parsed.hunches) ? parsed.hunches : [];
|
|
29311
|
+
const hunches = [];
|
|
29312
|
+
for (const r of raw) {
|
|
29313
|
+
const sql = typeof r.sql === "string" ? r.sql : "";
|
|
29314
|
+
const question = typeof r.question === "string" ? r.question : "";
|
|
29315
|
+
if (!sql || !question || !isReadOnlySql(sql))
|
|
29316
|
+
continue;
|
|
29317
|
+
hunches.push({
|
|
29318
|
+
connection: input.connection,
|
|
29319
|
+
question,
|
|
29320
|
+
sql,
|
|
29321
|
+
generatedSql: sql,
|
|
29322
|
+
tablesUsed: Array.isArray(r.tablesUsed) ? r.tablesUsed : [],
|
|
29323
|
+
rationale: typeof r.rationale === "string" ? r.rationale : void 0,
|
|
29324
|
+
confidence: typeof r.confidence === "number" ? r.confidence : void 0
|
|
29325
|
+
});
|
|
29326
|
+
}
|
|
29327
|
+
return hunches;
|
|
29328
|
+
}
|
|
29329
|
+
|
|
29330
|
+
// ../../packages/learning/dist/blindspot.js
|
|
29331
|
+
function buildBlindspotPrompt(profile, metricNames, contextBlock, modelGraph) {
|
|
29332
|
+
const tables = profile.tables.map((t) => {
|
|
29333
|
+
const cols = t.columns.map((c) => {
|
|
29334
|
+
const top = c.topValues?.length ? ` values:[${c.topValues.slice(0, 6).map((v) => JSON.stringify(v.value)).join(", ")}]` : "";
|
|
29335
|
+
return ` - ${c.name} (${c.type})${top}`;
|
|
29336
|
+
}).join("\n");
|
|
29337
|
+
return ` ${t.table}:
|
|
29338
|
+
${cols}`;
|
|
29339
|
+
}).join("\n");
|
|
29340
|
+
return [
|
|
29341
|
+
`You are auditing a data warehouse to find where you LACK business context and where useful marts are missing.`,
|
|
29342
|
+
contextBlock ? `Known business context:
|
|
29343
|
+
${contextBlock}` : "",
|
|
29344
|
+
metricNames.length ? `Governed metrics: ${metricNames.join(", ")}.` : "",
|
|
29345
|
+
modelGraph ? `Model lineage (raw \u2192 staging \u2192 marts):
|
|
29346
|
+
${modelGraph}` : "",
|
|
29347
|
+
`Profiled tables:
|
|
29348
|
+
${tables}`,
|
|
29349
|
+
`Return ONLY JSON: {"blindspots":[{"kind":"context","title":string,"detail":string,"subject":string,"confidence":number} | {"kind":"opportunity","title":string,"detail":string,"suggestedModel":{"name":string,"description":string,"rationale":string,"columns":[{"name":string,"description":string}]},"confidence":number}]}.`,
|
|
29350
|
+
`"context" = an ambiguity you need explained to write correct SQL (use the ACTUAL values shown). "opportunity" = a useful mart that does not yet exist.`
|
|
29351
|
+
].filter(Boolean).join("\n\n");
|
|
29352
|
+
}
|
|
29353
|
+
async function scanBlindspots(input) {
|
|
29354
|
+
const prompt = buildBlindspotPrompt(input.profile, input.metricNames, input.contextBlock, input.modelGraph);
|
|
29355
|
+
const res = await input.provider.chat({ system: "You audit data warehouses for missing business context and missing marts.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 4e3 });
|
|
29356
|
+
const text2 = res.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
29357
|
+
const parsed = extractJson(text2);
|
|
29358
|
+
const raw = Array.isArray(parsed.blindspots) ? parsed.blindspots : [];
|
|
29359
|
+
const items = [];
|
|
29360
|
+
for (const r of raw) {
|
|
29361
|
+
const candidate = { ...r, connection: input.connection };
|
|
29362
|
+
const result = NewBlindspotSchema.safeParse(candidate);
|
|
29363
|
+
if (result.success)
|
|
29364
|
+
items.push(result.data);
|
|
29365
|
+
}
|
|
29366
|
+
return items;
|
|
29367
|
+
}
|
|
29368
|
+
|
|
29369
|
+
// ../../packages/learning/dist/context.js
|
|
29370
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync } from "fs";
|
|
29371
|
+
import { join as join9 } from "path";
|
|
29372
|
+
var MAX_DOC = 6e3;
|
|
29373
|
+
var MAX_TOTAL = 16e3;
|
|
29374
|
+
function loadKnowledgeDocs(projectDir) {
|
|
29375
|
+
const dir = join9(projectDir, "knowledge");
|
|
29376
|
+
if (!existsSync2(dir))
|
|
29377
|
+
return [];
|
|
29378
|
+
return readdirSync(dir).filter((f) => /\.md$/i.test(f)).map((f) => readFileSync2(join9(dir, f), "utf8"));
|
|
29379
|
+
}
|
|
29380
|
+
function buildContextBlock(input) {
|
|
29381
|
+
const parts = [];
|
|
29382
|
+
for (const doc of input.docs) {
|
|
29383
|
+
const d = doc.length > MAX_DOC ? doc.slice(0, MAX_DOC) + "\n\u2026(truncated)" : doc;
|
|
29384
|
+
parts.push(d);
|
|
29385
|
+
}
|
|
29386
|
+
if (input.answeredBlindspots.length) {
|
|
29387
|
+
parts.push("Confirmed business context (from answered questions):");
|
|
29388
|
+
for (const a of input.answeredBlindspots)
|
|
29389
|
+
parts.push(`- ${a.title}: ${a.answer}`);
|
|
29390
|
+
}
|
|
29391
|
+
let block = parts.join("\n\n").trim();
|
|
29392
|
+
if (block.length > MAX_TOTAL)
|
|
29393
|
+
block = block.slice(0, MAX_TOTAL) + "\n\u2026(truncated)";
|
|
29394
|
+
return block;
|
|
29395
|
+
}
|
|
29396
|
+
|
|
29397
|
+
// ../../packages/learning/dist/retrieval.js
|
|
29398
|
+
function cosineSimilarity(a, b) {
|
|
29399
|
+
let dot = 0, na = 0, nb = 0;
|
|
29400
|
+
for (let i = 0; i < a.length; i++) {
|
|
29401
|
+
dot += a[i] * b[i];
|
|
29402
|
+
na += a[i] * a[i];
|
|
29403
|
+
nb += b[i] * b[i];
|
|
29404
|
+
}
|
|
29405
|
+
if (na === 0 || nb === 0)
|
|
29406
|
+
return 0;
|
|
29407
|
+
return dot / (Math.sqrt(na) * Math.sqrt(nb));
|
|
29408
|
+
}
|
|
29409
|
+
async function retrieveGrounding(input) {
|
|
29410
|
+
const { question, connection, store, embedder } = input;
|
|
29411
|
+
const topK = input.topK ?? 5;
|
|
29412
|
+
const corpus = [];
|
|
29413
|
+
for (const h of await store.listHunches({ connection, status: "confirmed" }))
|
|
29414
|
+
corpus.push({ kind: "hunch", id: h.id, text: h.question, payload: { sql: h.sql, title: h.question } });
|
|
29415
|
+
for (const b of await store.listBlindspots({ connection, status: "answered" }))
|
|
29416
|
+
if (b.answer)
|
|
29417
|
+
corpus.push({ kind: "blindspot", id: b.id, text: `${b.title}: ${b.answer}`, payload: { title: b.title } });
|
|
29418
|
+
for (const d of await store.listKnowledgeDocs({ connection }))
|
|
29419
|
+
corpus.push({ kind: "knowledge", id: d.id, text: `${d.title}
|
|
29420
|
+
${d.content}`, payload: { title: d.title } });
|
|
29421
|
+
if (corpus.length === 0)
|
|
29422
|
+
return [];
|
|
29423
|
+
const toEmbed = [];
|
|
29424
|
+
const vectors = /* @__PURE__ */ new Map();
|
|
29425
|
+
for (const item of corpus) {
|
|
29426
|
+
const cached = await store.getEmbedding(item.kind, item.id);
|
|
29427
|
+
if (cached && cached.text === item.text)
|
|
29428
|
+
vectors.set(item.id, cached.vector);
|
|
29429
|
+
else
|
|
29430
|
+
toEmbed.push(item);
|
|
29431
|
+
}
|
|
29432
|
+
if (toEmbed.length) {
|
|
29433
|
+
const embs = await embedder.embed(toEmbed.map((i) => i.text));
|
|
29434
|
+
for (let i = 0; i < toEmbed.length; i++) {
|
|
29435
|
+
const item = toEmbed[i];
|
|
29436
|
+
const vec = embs[i];
|
|
29437
|
+
vectors.set(item.id, vec);
|
|
29438
|
+
await store.upsertEmbedding({ connection, source_kind: item.kind, source_id: item.id, text: item.text, vector: vec });
|
|
29439
|
+
}
|
|
29440
|
+
}
|
|
29441
|
+
const [qVec] = await embedder.embed([question]);
|
|
29442
|
+
const ranked = corpus.map((item) => ({ ...item, score: cosineSimilarity(qVec, vectors.get(item.id)) })).sort((a, b) => b.score - a.score).slice(0, topK);
|
|
29443
|
+
return ranked.map((r) => ({ kind: r.kind, id: r.id, text: r.text, score: r.score, payload: r.payload }));
|
|
29444
|
+
}
|
|
29445
|
+
|
|
29446
|
+
// ../../packages/learning/dist/answer.js
|
|
29447
|
+
function extractSql(text2) {
|
|
29448
|
+
const fence = text2.match(/```(?:sql)?\s*([\s\S]*?)```/i);
|
|
29449
|
+
return (fence ? fence[1] : text2).trim();
|
|
29450
|
+
}
|
|
29451
|
+
async function answerQuestion(input) {
|
|
29452
|
+
const { question, connection, store, embedder, provider, executeRawSql } = input;
|
|
29453
|
+
const reuseThreshold = input.reuseThreshold ?? 0.9;
|
|
29454
|
+
const top = await retrieveGrounding({ question, connection, store, embedder, topK: 5 });
|
|
29455
|
+
if (top.length === 0) {
|
|
29456
|
+
return { question, mode: "none", message: "No governed knowledge yet for this source \u2014 confirm some Hunches first." };
|
|
29457
|
+
}
|
|
29458
|
+
const groundedOn = top.map((t) => ({ kind: t.kind, title: t.payload.title, score: t.score }));
|
|
29459
|
+
const topHunch = top.find((t) => t.kind === "hunch");
|
|
29460
|
+
if (topHunch && topHunch === top[0] && topHunch.score >= reuseThreshold && topHunch.payload.sql) {
|
|
29461
|
+
try {
|
|
29462
|
+
const res = await executeRawSql(limit(topHunch.payload.sql));
|
|
29463
|
+
return { question, mode: "reused", sql: topHunch.payload.sql, columns: res.columns, rows: res.rows, groundedOn };
|
|
29464
|
+
} catch (e) {
|
|
29465
|
+
return { question, mode: "reused", sql: topHunch.payload.sql, groundedOn, message: `Query failed: ${e.message}` };
|
|
29466
|
+
}
|
|
29467
|
+
}
|
|
29468
|
+
const examples = top.filter((t) => t.kind === "hunch" && t.payload.sql).map((t) => `Q: ${t.payload.title}
|
|
29469
|
+
SQL: ${t.payload.sql}`).join("\n\n");
|
|
29470
|
+
const context = top.filter((t) => t.kind !== "hunch").map((t) => `- ${t.text}`).join("\n");
|
|
29471
|
+
const prompt = [
|
|
29472
|
+
`Write a single read-only SQL query that answers the question, using ONLY these trusted examples and business context.`,
|
|
29473
|
+
examples ? `Trusted examples:
|
|
29474
|
+
${examples}` : "",
|
|
29475
|
+
context ? `Business context:
|
|
29476
|
+
${context}` : "",
|
|
29477
|
+
`Question: ${question}`,
|
|
29478
|
+
`Respond with ONLY the SQL (a single SELECT).`
|
|
29479
|
+
].filter(Boolean).join("\n\n");
|
|
29480
|
+
const resp = await provider.chat({ system: "You write governed, read-only analytics SQL grounded in trusted examples.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 1e3 });
|
|
29481
|
+
const text2 = resp.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
29482
|
+
const sql = extractSql(text2);
|
|
29483
|
+
if (!isReadOnlySql(sql)) {
|
|
29484
|
+
return { question, mode: "generated", sql, groundedOn, message: "Generated SQL was not read-only; refusing to run it." };
|
|
29485
|
+
}
|
|
29486
|
+
try {
|
|
29487
|
+
const res = await executeRawSql(limit(sql));
|
|
29488
|
+
return { question, mode: "generated", sql, columns: res.columns, rows: res.rows, groundedOn };
|
|
29489
|
+
} catch (e) {
|
|
29490
|
+
return { question, mode: "generated", sql, groundedOn, message: `Query failed: ${e.message}` };
|
|
29491
|
+
}
|
|
29492
|
+
}
|
|
29493
|
+
function limit(sql) {
|
|
29494
|
+
return /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT 1000`;
|
|
29495
|
+
}
|
|
29496
|
+
|
|
29497
|
+
// ../../packages/mcp-core/dist/tools/answer.js
|
|
29498
|
+
async function answerQuestionTool(deps, args) {
|
|
29499
|
+
const connection = args.connection ?? deps.defaultConnectionName ?? "default";
|
|
29500
|
+
const result = await answerQuestion({
|
|
29501
|
+
question: args.question,
|
|
29502
|
+
connection,
|
|
29503
|
+
store: deps.knowledgeStore,
|
|
29504
|
+
embedder: deps.embedder,
|
|
29505
|
+
provider: deps.provider,
|
|
29506
|
+
executeRawSql: (sql) => deps.queryService.executeRawSql(sql),
|
|
29507
|
+
connectionType: deps.connectionType
|
|
29508
|
+
});
|
|
29509
|
+
return JSON.stringify(result, null, 2);
|
|
29510
|
+
}
|
|
29511
|
+
|
|
28726
29512
|
// ../../packages/mcp-core/dist/render/embedResource.js
|
|
28727
29513
|
import { createHash } from "crypto";
|
|
28728
29514
|
import { createUIResource } from "@mcp-ui/server";
|
|
@@ -28830,7 +29616,10 @@ function createMcpServer(deps) {
|
|
|
28830
29616
|
inputSchema: { sql: z2.string(), limit: z2.number().optional() }
|
|
28831
29617
|
}, async (args) => text(await runSql(deps, args)));
|
|
28832
29618
|
server.registerTool("list_tables", { description: "List warehouse tables from the cached catalog (name, schema, connection, column count).", inputSchema: {} }, async () => text(await listTables(deps)));
|
|
28833
|
-
server.registerTool("describe_table", {
|
|
29619
|
+
server.registerTool("describe_table", {
|
|
29620
|
+
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() }
|
|
29622
|
+
}, async (args) => text(await describeTable(deps, args)));
|
|
28834
29623
|
server.registerTool("sample_table", { description: "Return a few sample rows from a table.", inputSchema: { table: z2.string(), limit: z2.number().optional() } }, async (args) => text(await sampleTable(deps, args)));
|
|
28835
29624
|
server.registerTool("visualize", {
|
|
28836
29625
|
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.",
|
|
@@ -28858,6 +29647,66 @@ function createMcpServer(deps) {
|
|
|
28858
29647
|
description: "Save a yamchart chart spec to charts/<name>.yaml (requires editor/admin).",
|
|
28859
29648
|
inputSchema: { spec: z2.record(z2.unknown()) }
|
|
28860
29649
|
}, async (args) => text(await saveChart(deps, args)));
|
|
29650
|
+
if (deps.semanticLayer) {
|
|
29651
|
+
const semanticLayer = deps.semanticLayer;
|
|
29652
|
+
server.registerTool("list_metrics", {
|
|
29653
|
+
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
|
+
}, async () => text(listMetrics({ semanticLayer })));
|
|
29656
|
+
server.registerTool("get_metric", {
|
|
29657
|
+
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() }
|
|
29659
|
+
}, async (args) => text(getMetric({ semanticLayer }, args)));
|
|
29660
|
+
server.registerTool("query_metric", {
|
|
29661
|
+
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.",
|
|
29662
|
+
inputSchema: {
|
|
29663
|
+
metrics: z2.array(z2.string()).min(1),
|
|
29664
|
+
dimensions: z2.array(z2.string()).optional(),
|
|
29665
|
+
grain: z2.enum(["day", "week", "month", "quarter", "year"]).optional(),
|
|
29666
|
+
time_range: z2.union([
|
|
29667
|
+
z2.object({ preset: z2.string() }),
|
|
29668
|
+
z2.object({ start: z2.string(), end: z2.string() })
|
|
29669
|
+
]).optional(),
|
|
29670
|
+
filters: z2.array(z2.object({ dimension: z2.string(), operator: z2.string(), value: z2.any() })).optional(),
|
|
29671
|
+
order_by: z2.object({ field: z2.string(), direction: z2.enum(["asc", "desc"]).optional() }).optional(),
|
|
29672
|
+
limit: z2.number().int().positive().max(1e4).optional()
|
|
29673
|
+
}
|
|
29674
|
+
}, async (args) => {
|
|
29675
|
+
try {
|
|
29676
|
+
return text(await queryMetric({ semanticLayer, queryService: deps.queryService, connectionType: deps.connectionType }, args));
|
|
29677
|
+
} catch (err) {
|
|
29678
|
+
return text(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
29679
|
+
}
|
|
29680
|
+
});
|
|
29681
|
+
}
|
|
29682
|
+
if (deps.knowledgeStore && deps.embedder && deps.answerProvider) {
|
|
29683
|
+
const knowledgeStore = deps.knowledgeStore;
|
|
29684
|
+
const embedder = deps.embedder;
|
|
29685
|
+
const answerProvider = deps.answerProvider;
|
|
29686
|
+
server.registerTool("answer_question", {
|
|
29687
|
+
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
|
+
}, async (args) => text(await answerQuestionTool({
|
|
29690
|
+
knowledgeStore,
|
|
29691
|
+
embedder,
|
|
29692
|
+
provider: answerProvider,
|
|
29693
|
+
queryService: deps.queryService,
|
|
29694
|
+
connectionType: deps.connectionType,
|
|
29695
|
+
defaultConnectionName: deps.aiConnectionName
|
|
29696
|
+
}, args)));
|
|
29697
|
+
}
|
|
29698
|
+
server.registerTool("get_chart_schema", {
|
|
29699
|
+
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() }
|
|
29701
|
+
}, async (args) => text(getChartSchema(args)));
|
|
29702
|
+
server.registerTool("validate_chart", {
|
|
29703
|
+
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()) }
|
|
29705
|
+
}, async (args) => text(validateChart(args)));
|
|
29706
|
+
server.registerTool("get_chart_spec", {
|
|
29707
|
+
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() }
|
|
29709
|
+
}, async (args) => text(getChartSpec({ configLoader: deps.configLoader }, args)));
|
|
28861
29710
|
return server;
|
|
28862
29711
|
}
|
|
28863
29712
|
|
|
@@ -28869,13 +29718,19 @@ function getUser(request) {
|
|
|
28869
29718
|
return { id: user.id, email: user.email, role: user.role, attributes: user.attributes };
|
|
28870
29719
|
}
|
|
28871
29720
|
async function mcpRoutes(fastify, options) {
|
|
28872
|
-
const { configLoader, queryService, projectDir } = options;
|
|
29721
|
+
const { configLoader, queryService, projectDir, semanticLayer, connectionType, knowledgeStore, embedder, answerProvider, aiConnectionName } = options;
|
|
28873
29722
|
fastify.post("/mcp", async (request, reply) => {
|
|
28874
29723
|
const server = createMcpServer({
|
|
28875
29724
|
configLoader,
|
|
28876
29725
|
queryService,
|
|
28877
29726
|
projectDir,
|
|
28878
|
-
user: getUser(request)
|
|
29727
|
+
user: getUser(request),
|
|
29728
|
+
semanticLayer,
|
|
29729
|
+
connectionType,
|
|
29730
|
+
knowledgeStore,
|
|
29731
|
+
embedder,
|
|
29732
|
+
answerProvider,
|
|
29733
|
+
aiConnectionName
|
|
28879
29734
|
});
|
|
28880
29735
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
28881
29736
|
reply.raw.on("close", () => {
|
|
@@ -28899,7 +29754,7 @@ async function mcpRoutes(fastify, options) {
|
|
|
28899
29754
|
}
|
|
28900
29755
|
|
|
28901
29756
|
// ../server/dist/routes/catalog.js
|
|
28902
|
-
import { join as
|
|
29757
|
+
import { join as join10 } from "path";
|
|
28903
29758
|
import { readFile as readFile5 } from "fs/promises";
|
|
28904
29759
|
async function catalogRoutes(fastify, options) {
|
|
28905
29760
|
const { configLoader, projectDir } = options;
|
|
@@ -28912,7 +29767,7 @@ async function catalogRoutes(fastify, options) {
|
|
|
28912
29767
|
name: m.metadata.name,
|
|
28913
29768
|
description: m.metadata.description
|
|
28914
29769
|
}));
|
|
28915
|
-
const catalogPath =
|
|
29770
|
+
const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
|
|
28916
29771
|
let tables = [];
|
|
28917
29772
|
try {
|
|
28918
29773
|
const catalogData = JSON.parse(await readFile5(catalogPath, "utf-8"));
|
|
@@ -28948,8 +29803,310 @@ async function catalogRoutes(fastify, options) {
|
|
|
28948
29803
|
});
|
|
28949
29804
|
}
|
|
28950
29805
|
|
|
29806
|
+
// ../server/dist/routes/learning.js
|
|
29807
|
+
var ReadOnlySqlError = class extends Error {
|
|
29808
|
+
constructor() {
|
|
29809
|
+
super("Only read-only SELECT queries can be confirmed.");
|
|
29810
|
+
this.name = "ReadOnlySqlError";
|
|
29811
|
+
}
|
|
29812
|
+
};
|
|
29813
|
+
async function confirmHunch(deps, id, body) {
|
|
29814
|
+
if (body.sql !== void 0 && !isReadOnlySql(body.sql)) {
|
|
29815
|
+
throw new ReadOnlySqlError();
|
|
29816
|
+
}
|
|
29817
|
+
return deps.store.updateHunch(id, { status: "confirmed", ...body.sql ? { sql: body.sql } : {} });
|
|
29818
|
+
}
|
|
29819
|
+
async function rejectHunch(deps, id) {
|
|
29820
|
+
return deps.store.updateHunch(id, { status: "rejected" });
|
|
29821
|
+
}
|
|
29822
|
+
async function runHunch(deps, id) {
|
|
29823
|
+
const hunch = await deps.store.getHunch(id);
|
|
29824
|
+
if (!hunch)
|
|
29825
|
+
return { error: "Hunch not found." };
|
|
29826
|
+
if (!isReadOnlySql(hunch.sql))
|
|
29827
|
+
return { error: "Only read-only SELECT queries can be run." };
|
|
29828
|
+
const limited = /\blimit\b/i.test(hunch.sql) ? hunch.sql : `${hunch.sql.replace(/;\s*$/, "")} LIMIT 50`;
|
|
29829
|
+
const res = await deps.queryService.executeRawSql(limited);
|
|
29830
|
+
const sample = { columns: res.columns, rows: res.rows, ranAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
29831
|
+
await deps.store.updateHunch(id, { lastRunSample: sample });
|
|
29832
|
+
return { sample };
|
|
29833
|
+
}
|
|
29834
|
+
async function answerBlindspot(deps, id, body) {
|
|
29835
|
+
return deps.store.updateBlindspot(id, { status: "answered", answer: body.answer });
|
|
29836
|
+
}
|
|
29837
|
+
async function dismissBlindspot(deps, id) {
|
|
29838
|
+
return deps.store.updateBlindspot(id, { status: "dismissed" });
|
|
29839
|
+
}
|
|
29840
|
+
async function loadContextBlock(opts, store, connection) {
|
|
29841
|
+
const fileDocs = loadKnowledgeDocs(opts.projectDir);
|
|
29842
|
+
const dbDocs = (await store.listKnowledgeDocs({ connection })).map((d) => `${d.title}
|
|
29843
|
+
${d.content}`);
|
|
29844
|
+
const answered = (await store.listBlindspots({ connection, status: "answered" })).filter((b) => b.answer).map((b) => ({ title: b.title, answer: b.answer }));
|
|
29845
|
+
return buildContextBlock({ docs: [...fileDocs, ...dbDocs], answeredBlindspots: answered });
|
|
29846
|
+
}
|
|
29847
|
+
async function learningRoutes(fastify, opts) {
|
|
29848
|
+
const { store, queryService } = opts;
|
|
29849
|
+
const requireStore = (reply) => {
|
|
29850
|
+
if (!store) {
|
|
29851
|
+
reply.code(503).send({ error: "Knowledge features require a configured database (set KNOWLEDGE_DATABASE_URL)." });
|
|
29852
|
+
return false;
|
|
29853
|
+
}
|
|
29854
|
+
return true;
|
|
29855
|
+
};
|
|
29856
|
+
fastify.post("/api/learn", async (request, reply) => {
|
|
29857
|
+
if (!requireStore(reply))
|
|
29858
|
+
return;
|
|
29859
|
+
if (!opts.canWrite(request)) {
|
|
29860
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29861
|
+
return;
|
|
29862
|
+
}
|
|
29863
|
+
const connection = request.body?.connection ?? opts.defaultConnectionName;
|
|
29864
|
+
reply.raw.writeHead(200, {
|
|
29865
|
+
"Content-Type": "text/event-stream",
|
|
29866
|
+
"Cache-Control": "no-cache",
|
|
29867
|
+
Connection: "keep-alive"
|
|
29868
|
+
});
|
|
29869
|
+
const send = (type, data) => reply.raw.write(formatSSEEvent({ type, data }));
|
|
29870
|
+
try {
|
|
29871
|
+
send("status", { phase: "profiling" });
|
|
29872
|
+
const tables = await opts.getConnectionTables(connection);
|
|
29873
|
+
const profile = await buildSourceProfile({
|
|
29874
|
+
connection,
|
|
29875
|
+
dialect: opts.dialect,
|
|
29876
|
+
tables,
|
|
29877
|
+
execute: (sql) => queryService.executeRawSql(sql)
|
|
29878
|
+
});
|
|
29879
|
+
await store.saveProfile(connection, profile);
|
|
29880
|
+
send("status", { phase: "generating" });
|
|
29881
|
+
let provider;
|
|
29882
|
+
try {
|
|
29883
|
+
provider = createProviderForTask(opts.aiConfig, "hunch_generation", process.env);
|
|
29884
|
+
} catch (e) {
|
|
29885
|
+
send("error", { message: e.message });
|
|
29886
|
+
reply.raw.end();
|
|
29887
|
+
return;
|
|
29888
|
+
}
|
|
29889
|
+
const contextBlock = await loadContextBlock(opts, store, connection);
|
|
29890
|
+
const candidates = await generateHunches({
|
|
29891
|
+
provider,
|
|
29892
|
+
profile,
|
|
29893
|
+
metricNames: opts.metricNames,
|
|
29894
|
+
connection,
|
|
29895
|
+
contextBlock
|
|
29896
|
+
});
|
|
29897
|
+
const created = await store.createHunches(candidates);
|
|
29898
|
+
send("done", { hunches: created });
|
|
29899
|
+
} catch (e) {
|
|
29900
|
+
send("error", { message: e.message });
|
|
29901
|
+
}
|
|
29902
|
+
reply.raw.end();
|
|
29903
|
+
});
|
|
29904
|
+
fastify.get("/api/hunches", async (request, reply) => {
|
|
29905
|
+
if (!requireStore(reply))
|
|
29906
|
+
return;
|
|
29907
|
+
const q = request.query;
|
|
29908
|
+
return store.listHunches({ connection: q.connection, status: q.status });
|
|
29909
|
+
});
|
|
29910
|
+
fastify.post("/api/hunches/:id/confirm", async (request, reply) => {
|
|
29911
|
+
if (!requireStore(reply))
|
|
29912
|
+
return;
|
|
29913
|
+
if (!opts.canWrite(request)) {
|
|
29914
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29915
|
+
return;
|
|
29916
|
+
}
|
|
29917
|
+
try {
|
|
29918
|
+
return await confirmHunch({ store, queryService }, request.params.id, request.body ?? {});
|
|
29919
|
+
} catch (e) {
|
|
29920
|
+
if (e instanceof ReadOnlySqlError) {
|
|
29921
|
+
reply.code(400).send({ error: e.message });
|
|
29922
|
+
return;
|
|
29923
|
+
}
|
|
29924
|
+
throw e;
|
|
29925
|
+
}
|
|
29926
|
+
});
|
|
29927
|
+
fastify.post("/api/hunches/:id/reject", async (request, reply) => {
|
|
29928
|
+
if (!requireStore(reply))
|
|
29929
|
+
return;
|
|
29930
|
+
if (!opts.canWrite(request)) {
|
|
29931
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29932
|
+
return;
|
|
29933
|
+
}
|
|
29934
|
+
return rejectHunch({ store, queryService }, request.params.id);
|
|
29935
|
+
});
|
|
29936
|
+
fastify.post("/api/hunches/:id/run", async (request, reply) => {
|
|
29937
|
+
if (!requireStore(reply))
|
|
29938
|
+
return;
|
|
29939
|
+
if (!opts.canWrite(request)) {
|
|
29940
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29941
|
+
return;
|
|
29942
|
+
}
|
|
29943
|
+
return runHunch({ store, queryService }, request.params.id);
|
|
29944
|
+
});
|
|
29945
|
+
fastify.post("/api/blindspots/scan", async (request, reply) => {
|
|
29946
|
+
if (!requireStore(reply))
|
|
29947
|
+
return;
|
|
29948
|
+
if (!opts.canWrite(request)) {
|
|
29949
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29950
|
+
return;
|
|
29951
|
+
}
|
|
29952
|
+
const connection = request.body?.connection ?? opts.defaultConnectionName;
|
|
29953
|
+
reply.raw.writeHead(200, {
|
|
29954
|
+
"Content-Type": "text/event-stream",
|
|
29955
|
+
"Cache-Control": "no-cache",
|
|
29956
|
+
Connection: "keep-alive"
|
|
29957
|
+
});
|
|
29958
|
+
const send = (type, data) => reply.raw.write(formatSSEEvent({ type, data }));
|
|
29959
|
+
try {
|
|
29960
|
+
send("status", { phase: "scanning" });
|
|
29961
|
+
let profile = await store.getProfile(connection);
|
|
29962
|
+
if (!profile) {
|
|
29963
|
+
const tables = await opts.getConnectionTables(connection);
|
|
29964
|
+
profile = await buildSourceProfile({
|
|
29965
|
+
connection,
|
|
29966
|
+
dialect: opts.dialect,
|
|
29967
|
+
tables,
|
|
29968
|
+
execute: (sql) => queryService.executeRawSql(sql)
|
|
29969
|
+
});
|
|
29970
|
+
await store.saveProfile(connection, profile);
|
|
29971
|
+
}
|
|
29972
|
+
const contextBlock = await loadContextBlock(opts, store, connection);
|
|
29973
|
+
let provider;
|
|
29974
|
+
try {
|
|
29975
|
+
provider = createProviderForTask(opts.aiConfig, "blindspot_scan", process.env);
|
|
29976
|
+
} catch (e) {
|
|
29977
|
+
send("error", { message: e.message });
|
|
29978
|
+
reply.raw.end();
|
|
29979
|
+
return;
|
|
29980
|
+
}
|
|
29981
|
+
const items = await scanBlindspots({
|
|
29982
|
+
provider,
|
|
29983
|
+
profile,
|
|
29984
|
+
metricNames: opts.metricNames,
|
|
29985
|
+
contextBlock,
|
|
29986
|
+
connection
|
|
29987
|
+
});
|
|
29988
|
+
await store.deleteOpenBlindspots(connection);
|
|
29989
|
+
const created = await store.createBlindspots(items);
|
|
29990
|
+
send("done", { blindspots: created });
|
|
29991
|
+
} catch (e) {
|
|
29992
|
+
send("error", { message: e.message });
|
|
29993
|
+
}
|
|
29994
|
+
reply.raw.end();
|
|
29995
|
+
});
|
|
29996
|
+
fastify.get("/api/blindspots", async (request, reply) => {
|
|
29997
|
+
if (!requireStore(reply))
|
|
29998
|
+
return;
|
|
29999
|
+
const q = request.query;
|
|
30000
|
+
return store.listBlindspots({ connection: q.connection, status: q.status, kind: q.kind });
|
|
30001
|
+
});
|
|
30002
|
+
fastify.post("/api/blindspots/:id/answer", async (request, reply) => {
|
|
30003
|
+
if (!requireStore(reply))
|
|
30004
|
+
return;
|
|
30005
|
+
if (!opts.canWrite(request)) {
|
|
30006
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30007
|
+
return;
|
|
30008
|
+
}
|
|
30009
|
+
return answerBlindspot({ store }, request.params.id, request.body ?? { answer: "" });
|
|
30010
|
+
});
|
|
30011
|
+
fastify.post("/api/blindspots/:id/dismiss", async (request, reply) => {
|
|
30012
|
+
if (!requireStore(reply))
|
|
30013
|
+
return;
|
|
30014
|
+
if (!opts.canWrite(request)) {
|
|
30015
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30016
|
+
return;
|
|
30017
|
+
}
|
|
30018
|
+
return dismissBlindspot({ store }, request.params.id);
|
|
30019
|
+
});
|
|
30020
|
+
}
|
|
30021
|
+
|
|
30022
|
+
// ../server/dist/routes/data.js
|
|
30023
|
+
async function createKnowledgeDocHandler(deps, body) {
|
|
30024
|
+
const [doc] = await deps.store.createKnowledgeDoc({ ...body, source: "ui" });
|
|
30025
|
+
return doc;
|
|
30026
|
+
}
|
|
30027
|
+
async function dataRoutes(fastify, opts) {
|
|
30028
|
+
const { store } = opts;
|
|
30029
|
+
fastify.get("/api/sources", async () => {
|
|
30030
|
+
const conns = opts.getConnections();
|
|
30031
|
+
return Promise.all(conns.map(async (c) => {
|
|
30032
|
+
let tables = 0;
|
|
30033
|
+
let pendingHunches = 0;
|
|
30034
|
+
let openBlindspots = 0;
|
|
30035
|
+
try {
|
|
30036
|
+
tables = (await opts.getConnectionTables(c.name)).length;
|
|
30037
|
+
} catch {
|
|
30038
|
+
}
|
|
30039
|
+
if (store) {
|
|
30040
|
+
pendingHunches = (await store.listHunches({ connection: c.name, status: "pending" })).length;
|
|
30041
|
+
openBlindspots = (await store.listBlindspots({ connection: c.name, status: "open" })).length;
|
|
30042
|
+
}
|
|
30043
|
+
return { name: c.name, type: c.type, counts: { tables, pendingHunches, openBlindspots } };
|
|
30044
|
+
}));
|
|
30045
|
+
});
|
|
30046
|
+
fastify.get("/api/sources/:connection/tables", async (request) => {
|
|
30047
|
+
const connection = request.params.connection;
|
|
30048
|
+
const tables = await opts.getConnectionTables(connection);
|
|
30049
|
+
const profile = store ? await store.getProfile(connection) : void 0;
|
|
30050
|
+
return tables.map((t) => {
|
|
30051
|
+
const pt = profile?.tables.find((p) => p.table === t.table);
|
|
30052
|
+
const columns = t.columns.map((c) => {
|
|
30053
|
+
const pc = pt?.columns.find((x) => x.name === c.name);
|
|
30054
|
+
return {
|
|
30055
|
+
name: c.name,
|
|
30056
|
+
type: c.type,
|
|
30057
|
+
nullRate: pc?.nullRate,
|
|
30058
|
+
distinctCount: pc?.distinctCount,
|
|
30059
|
+
topValues: pc?.topValues
|
|
30060
|
+
};
|
|
30061
|
+
});
|
|
30062
|
+
return { table: t.table, columns };
|
|
30063
|
+
});
|
|
30064
|
+
});
|
|
30065
|
+
const requireStore = (reply) => {
|
|
30066
|
+
if (!store) {
|
|
30067
|
+
reply.code(503).send({ error: "Knowledge features require a configured database." });
|
|
30068
|
+
return false;
|
|
30069
|
+
}
|
|
30070
|
+
return true;
|
|
30071
|
+
};
|
|
30072
|
+
fastify.get("/api/knowledge", async (request, reply) => {
|
|
30073
|
+
if (!requireStore(reply))
|
|
30074
|
+
return;
|
|
30075
|
+
return store.listKnowledgeDocs({ connection: request.query.connection });
|
|
30076
|
+
});
|
|
30077
|
+
fastify.post("/api/knowledge", async (request, reply) => {
|
|
30078
|
+
if (!requireStore(reply))
|
|
30079
|
+
return;
|
|
30080
|
+
if (!opts.canWrite(request)) {
|
|
30081
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30082
|
+
return;
|
|
30083
|
+
}
|
|
30084
|
+
return createKnowledgeDocHandler({ store }, request.body);
|
|
30085
|
+
});
|
|
30086
|
+
fastify.put("/api/knowledge/:id", async (request, reply) => {
|
|
30087
|
+
if (!requireStore(reply))
|
|
30088
|
+
return;
|
|
30089
|
+
if (!opts.canWrite(request)) {
|
|
30090
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30091
|
+
return;
|
|
30092
|
+
}
|
|
30093
|
+
const { id } = request.params;
|
|
30094
|
+
return store.updateKnowledgeDoc(id, request.body);
|
|
30095
|
+
});
|
|
30096
|
+
fastify.delete("/api/knowledge/:id", async (request, reply) => {
|
|
30097
|
+
if (!requireStore(reply))
|
|
30098
|
+
return;
|
|
30099
|
+
if (!opts.canWrite(request)) {
|
|
30100
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30101
|
+
return;
|
|
30102
|
+
}
|
|
30103
|
+
await store.deleteKnowledgeDoc(request.params.id);
|
|
30104
|
+
return { ok: true };
|
|
30105
|
+
});
|
|
30106
|
+
}
|
|
30107
|
+
|
|
28951
30108
|
// ../server/dist/oauth/service.js
|
|
28952
|
-
import { randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
30109
|
+
import { randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
28953
30110
|
|
|
28954
30111
|
// ../server/dist/oauth/errors.js
|
|
28955
30112
|
var OAuthError = class extends Error {
|
|
@@ -29017,7 +30174,7 @@ var OAuthService = class {
|
|
|
29017
30174
|
}
|
|
29018
30175
|
const authMethod = input.token_endpoint_auth_method === "client_secret_post" ? "client_secret_post" : "none";
|
|
29019
30176
|
const grantTypes = input.grant_types?.length ? input.grant_types : ["authorization_code", "refresh_token"];
|
|
29020
|
-
const clientId =
|
|
30177
|
+
const clientId = randomUUID3();
|
|
29021
30178
|
let clientSecret;
|
|
29022
30179
|
let clientSecretHash = null;
|
|
29023
30180
|
if (authMethod === "client_secret_post") {
|
|
@@ -29474,17 +30631,17 @@ data: ${data}
|
|
|
29474
30631
|
}
|
|
29475
30632
|
|
|
29476
30633
|
// ../server/dist/server.js
|
|
29477
|
-
import { join as
|
|
30634
|
+
import { join as join11, dirname as dirname2 } from "path";
|
|
29478
30635
|
import { fileURLToPath } from "url";
|
|
29479
30636
|
import { access as access2, readFile as readFile6 } from "fs/promises";
|
|
29480
30637
|
import { watch as fsWatch } from "fs";
|
|
29481
30638
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
29482
|
-
var packageJsonPath =
|
|
30639
|
+
var packageJsonPath = join11(__dirname, "..", "package.json");
|
|
29483
30640
|
var packageJson = JSON.parse(await readFile6(packageJsonPath, "utf-8"));
|
|
29484
30641
|
var VERSION = packageJson.version;
|
|
29485
30642
|
async function enrichRefsFromCatalog(projectDir, refs) {
|
|
29486
30643
|
try {
|
|
29487
|
-
const catalogPath =
|
|
30644
|
+
const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
|
|
29488
30645
|
const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
|
|
29489
30646
|
for (const model of catalogData.models ?? []) {
|
|
29490
30647
|
if (model.name && model.table) {
|
|
@@ -29495,7 +30652,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
|
|
|
29495
30652
|
}
|
|
29496
30653
|
}
|
|
29497
30654
|
async function createServer(options) {
|
|
29498
|
-
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir =
|
|
30655
|
+
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join11(__dirname, "public"), auth } = options;
|
|
29499
30656
|
if (auth) {
|
|
29500
30657
|
initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
|
|
29501
30658
|
}
|
|
@@ -29539,6 +30696,19 @@ async function createServer(options) {
|
|
|
29539
30696
|
maxSize: 1e3,
|
|
29540
30697
|
defaultTtlMs: cacheTtl
|
|
29541
30698
|
});
|
|
30699
|
+
const knowledgeUrlVar = project.knowledge?.database_url_var ?? "KNOWLEDGE_DATABASE_URL";
|
|
30700
|
+
const knowledgeDbUrl = process.env[knowledgeUrlVar];
|
|
30701
|
+
let knowledgeStore;
|
|
30702
|
+
if (knowledgeDbUrl) {
|
|
30703
|
+
const store = new PostgresKnowledgeStore(knowledgeDbUrl);
|
|
30704
|
+
try {
|
|
30705
|
+
await store.init();
|
|
30706
|
+
knowledgeStore = store;
|
|
30707
|
+
} catch (err) {
|
|
30708
|
+
fastify.log.warn(`Knowledge store init failed: ${err.message}`);
|
|
30709
|
+
knowledgeStore = void 0;
|
|
30710
|
+
}
|
|
30711
|
+
}
|
|
29542
30712
|
const models = {};
|
|
29543
30713
|
const refs = {};
|
|
29544
30714
|
for (const model of configLoader.getModels()) {
|
|
@@ -29685,16 +30855,103 @@ async function createServer(options) {
|
|
|
29685
30855
|
await fastify.register(catalogRoutes, { configLoader, projectDir });
|
|
29686
30856
|
}
|
|
29687
30857
|
const mcpBaseUrl = process.env.BASE_URL || project.defaults?.base_url || `http://localhost:${port}`;
|
|
30858
|
+
let semanticLayer;
|
|
30859
|
+
try {
|
|
30860
|
+
const semanticDefs = loadSemanticDefinitions(projectDir);
|
|
30861
|
+
if (semanticDefs.length > 0) {
|
|
30862
|
+
semanticLayer = buildSemanticLayer(semanticDefs, (model) => model);
|
|
30863
|
+
}
|
|
30864
|
+
} catch (err) {
|
|
30865
|
+
fastify.log.warn({ err }, "Failed to load curated semantic definitions; semantic MCP tools disabled");
|
|
30866
|
+
}
|
|
30867
|
+
const connectionType = configLoader.getDefaultConnection()?.type;
|
|
30868
|
+
let embedder;
|
|
30869
|
+
let answerProvider;
|
|
30870
|
+
try {
|
|
30871
|
+
embedder = createEmbedder(project.ai?.embedding, process.env);
|
|
30872
|
+
} catch (err) {
|
|
30873
|
+
fastify.log.warn({ err }, "Failed to build embedder; answer_question tool disabled");
|
|
30874
|
+
}
|
|
30875
|
+
try {
|
|
30876
|
+
answerProvider = createProviderForTask(project.ai, "answer", process.env);
|
|
30877
|
+
} catch (err) {
|
|
30878
|
+
fastify.log.warn({ err }, "Failed to build answer provider; answer_question tool disabled");
|
|
30879
|
+
}
|
|
30880
|
+
const aiConnectionName = defaultConnection?.name ?? "default";
|
|
30881
|
+
const mcpOptions = {
|
|
30882
|
+
configLoader,
|
|
30883
|
+
queryService,
|
|
30884
|
+
projectDir,
|
|
30885
|
+
semanticLayer,
|
|
30886
|
+
connectionType,
|
|
30887
|
+
knowledgeStore,
|
|
30888
|
+
embedder,
|
|
30889
|
+
answerProvider,
|
|
30890
|
+
aiConnectionName
|
|
30891
|
+
};
|
|
29688
30892
|
if (authDb && oauthService) {
|
|
29689
30893
|
const mcpAuth = createMcpAuthPreHandler({ authDb, oauthService, baseUrl: mcpBaseUrl });
|
|
29690
30894
|
await fastify.register(async (mcpScope) => {
|
|
29691
30895
|
mcpScope.addHook("preHandler", mcpAuth);
|
|
29692
|
-
await mcpScope.register(mcpRoutes,
|
|
30896
|
+
await mcpScope.register(mcpRoutes, mcpOptions);
|
|
29693
30897
|
});
|
|
29694
30898
|
} else {
|
|
29695
|
-
await fastify.register(mcpRoutes,
|
|
30899
|
+
await fastify.register(mcpRoutes, mcpOptions);
|
|
29696
30900
|
}
|
|
29697
|
-
const
|
|
30901
|
+
const learningOptions = {
|
|
30902
|
+
store: knowledgeStore,
|
|
30903
|
+
queryService,
|
|
30904
|
+
projectDir,
|
|
30905
|
+
defaultConnectionName: defaultConnection?.name ?? "default",
|
|
30906
|
+
dialect: defaultConnection?.type ?? "duckdb",
|
|
30907
|
+
metricNames: semanticLayer?.listMetrics().map((m) => m.name) ?? [],
|
|
30908
|
+
getConnectionTables: async (connection) => {
|
|
30909
|
+
const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
|
|
30910
|
+
try {
|
|
30911
|
+
const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
|
|
30912
|
+
return (catalogData.models ?? []).filter((m) => !m.connection || m.connection === connection).map((m) => ({
|
|
30913
|
+
table: m.table ?? m.name ?? "",
|
|
30914
|
+
columns: (m.columns ?? []).map((c) => ({ name: c.name, type: c.data_type ?? "string" }))
|
|
30915
|
+
})).filter((t) => t.table && t.columns.length > 0);
|
|
30916
|
+
} catch {
|
|
30917
|
+
return [];
|
|
30918
|
+
}
|
|
30919
|
+
},
|
|
30920
|
+
// Writes (learn/confirm/reject) require editor/admin. When auth is disabled
|
|
30921
|
+
// there is no request.user, so dev/local mode permits writes.
|
|
30922
|
+
canWrite: (req) => {
|
|
30923
|
+
const user = req.user;
|
|
30924
|
+
if (!user)
|
|
30925
|
+
return true;
|
|
30926
|
+
return user.role !== "viewer";
|
|
30927
|
+
},
|
|
30928
|
+
aiConfig: project.ai
|
|
30929
|
+
};
|
|
30930
|
+
const dataOptions = {
|
|
30931
|
+
store: knowledgeStore,
|
|
30932
|
+
getConnections: () => configLoader.getConnections().map((c) => ({ name: c.name, type: c.type })),
|
|
30933
|
+
getConnectionTables: learningOptions.getConnectionTables,
|
|
30934
|
+
canWrite: learningOptions.canWrite
|
|
30935
|
+
};
|
|
30936
|
+
if (auth) {
|
|
30937
|
+
await fastify.register(async (protectedRoutes) => {
|
|
30938
|
+
protectedRoutes.addHook("preHandler", authMiddleware);
|
|
30939
|
+
protectedRoutes.addHook("preHandler", orgMiddleware);
|
|
30940
|
+
await protectedRoutes.register(learningRoutes, learningOptions);
|
|
30941
|
+
await protectedRoutes.register(dataRoutes, dataOptions);
|
|
30942
|
+
});
|
|
30943
|
+
} else if (authDb) {
|
|
30944
|
+
const localMiddleware = createLocalAuthMiddleware(authDb);
|
|
30945
|
+
await fastify.register(async (protectedRoutes) => {
|
|
30946
|
+
protectedRoutes.addHook("preHandler", localMiddleware);
|
|
30947
|
+
await protectedRoutes.register(learningRoutes, learningOptions);
|
|
30948
|
+
await protectedRoutes.register(dataRoutes, dataOptions);
|
|
30949
|
+
});
|
|
30950
|
+
} else {
|
|
30951
|
+
await fastify.register(learningRoutes, learningOptions);
|
|
30952
|
+
await fastify.register(dataRoutes, dataOptions);
|
|
30953
|
+
}
|
|
30954
|
+
const assetsDir = join11(projectDir, "assets");
|
|
29698
30955
|
try {
|
|
29699
30956
|
await access2(assetsDir);
|
|
29700
30957
|
await fastify.register(fastifyStatic, {
|
|
@@ -29705,7 +30962,7 @@ async function createServer(options) {
|
|
|
29705
30962
|
} catch {
|
|
29706
30963
|
}
|
|
29707
30964
|
if (project.theme?.customCss) {
|
|
29708
|
-
const cssPath =
|
|
30965
|
+
const cssPath = join11(projectDir, project.theme.customCss);
|
|
29709
30966
|
fastify.get("/api/theme/custom.css", async (_request2, reply) => {
|
|
29710
30967
|
try {
|
|
29711
30968
|
const content = await readFile6(cssPath, "utf-8");
|
|
@@ -29764,7 +31021,7 @@ async function createServer(options) {
|
|
|
29764
31021
|
fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
29765
31022
|
broadcastConfigChange();
|
|
29766
31023
|
});
|
|
29767
|
-
const catalogPath =
|
|
31024
|
+
const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
|
|
29768
31025
|
try {
|
|
29769
31026
|
catalogWatcher = fsWatch(catalogPath, () => {
|
|
29770
31027
|
semanticService.buildFromDisk();
|
|
@@ -29828,15 +31085,15 @@ async function runDevServer(projectDir, options) {
|
|
|
29828
31085
|
let usesBrowserAuth = false;
|
|
29829
31086
|
let localAuth;
|
|
29830
31087
|
try {
|
|
29831
|
-
const { readFileSync:
|
|
31088
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
29832
31089
|
const { parse: parse2 } = await import("yaml");
|
|
29833
|
-
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-
|
|
29834
|
-
const raw =
|
|
31090
|
+
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-PPAD6KOM.js");
|
|
31091
|
+
const raw = readFileSync3(resolve2(projectDir, "yamchart.yaml"), "utf-8");
|
|
29835
31092
|
const rawConfig = parse2(raw);
|
|
29836
31093
|
const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
|
|
29837
31094
|
let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
|
|
29838
31095
|
try {
|
|
29839
|
-
const localRaw =
|
|
31096
|
+
const localRaw = readFileSync3(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
|
|
29840
31097
|
const localOverrides = parse2(localRaw);
|
|
29841
31098
|
if (localOverrides && typeof localOverrides === "object") {
|
|
29842
31099
|
projectConfig = deepMerge3(projectConfig, localOverrides);
|
|
@@ -29854,7 +31111,7 @@ async function runDevServer(projectDir, options) {
|
|
|
29854
31111
|
const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
|
|
29855
31112
|
if (targetConn) {
|
|
29856
31113
|
try {
|
|
29857
|
-
const connRaw =
|
|
31114
|
+
const connRaw = readFileSync3(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
|
|
29858
31115
|
const connConfig = parse2(connRaw);
|
|
29859
31116
|
if (connConfig?.auth?.type === "externalbrowser") {
|
|
29860
31117
|
usesBrowserAuth = true;
|
|
@@ -29934,4 +31191,4 @@ async function runDevServer(projectDir, options) {
|
|
|
29934
31191
|
export {
|
|
29935
31192
|
runDevServer
|
|
29936
31193
|
};
|
|
29937
|
-
//# sourceMappingURL=dev-
|
|
31194
|
+
//# sourceMappingURL=dev-LZ4YHNDJ.js.map
|