yamchart 0.10.2 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/{advisor-5F73GF54.js → advisor-XTJQU7E4.js} +12 -12
  2. package/dist/{chunk-5CRXV5PQ.js → chunk-2XJZJ6IA.js} +2 -2
  3. package/dist/{chunk-X3UG6T3Q.js → chunk-6ELSQNIH.js} +2 -2
  4. package/dist/{chunk-UOSOZT47.js → chunk-KDDO4UII.js} +57 -5
  5. package/dist/chunk-KDDO4UII.js.map +1 -0
  6. package/dist/{chunk-25ES7MGF.js → chunk-ODSPBOJW.js} +4 -4
  7. package/dist/{chunk-7HWFVUGP.js → chunk-OYBI433Z.js} +59 -2
  8. package/dist/chunk-OYBI433Z.js.map +1 -0
  9. package/dist/{chunk-WVSDQNAN.js → chunk-PXFTWXSV.js} +2 -2
  10. package/dist/{chunk-PYVROL7Z.js → chunk-XNMB25GT.js} +4 -4
  11. package/dist/{connection-utils-HNX3473B.js → connection-utils-S7EFPTLY.js} +5 -5
  12. package/dist/{describe-KDGXFPDQ.js → describe-TA2CTODC.js} +6 -6
  13. package/dist/{dev-KOWFGDOP.js → dev-PHFYLJPJ.js} +1336 -796
  14. package/dist/dev-PHFYLJPJ.js.map +1 -0
  15. package/dist/{dist-WMP7BFKP.js → dist-F6TVLP6C.js} +12 -4
  16. package/dist/{dist-YB5RBWCC.js → dist-Q5ETETUU.js} +4 -4
  17. package/dist/{dist-LK3ZG74Q.js → dist-YJOOH65Y.js} +9 -5
  18. package/dist/index.js +26 -26
  19. package/dist/public/assets/DataView-V-E6oxy6.js +9 -0
  20. package/dist/public/assets/{EventManagement-BIkVUSCe.js → EventManagement-Do2xU5lV.js} +1 -1
  21. package/dist/public/assets/{ExplorePage-CeaAulqT.js → ExplorePage-Dg50mvGB.js} +1 -1
  22. package/dist/public/assets/{LoginPage-4WeNcdRi.js → LoginPage-DmYGIXcu.js} +1 -1
  23. package/dist/public/assets/{PublicViewer-CkW-G56D.js → PublicViewer-CJTgc9OF.js} +1 -1
  24. package/dist/public/assets/{SetupWizard-CwTr3PCy.js → SetupWizard-Bo-zmh43.js} +1 -1
  25. package/dist/public/assets/{ShareManagement-DSon6CoC.js → ShareManagement-6Wp6b2Gp.js} +1 -1
  26. package/dist/public/assets/{SourceDetailView-BA4XVKqV.js → SourceDetailView-CTzSXhZc.js} +1 -1
  27. package/dist/public/assets/{UserManagement-BWD2Ugkv.js → UserManagement-DQhB13T0.js} +1 -1
  28. package/dist/public/assets/{index-iR9gLdl9.js → index-CY8xsfQH.js} +31 -31
  29. package/dist/public/assets/{index-Cb4omrVn.css → index-Dkn-tJCT.css} +1 -1
  30. package/dist/public/assets/{index.es-BuEtjxhO.js → index.es-Bc3yys5L.js} +1 -1
  31. package/dist/public/assets/{jspdf.es.min-CScx2_oK.js → jspdf.es.min-D94JEH-J.js} +3 -3
  32. package/dist/public/index.html +2 -2
  33. package/dist/{query-7XHKSZ5H.js → query-AX4BOPMH.js} +5 -5
  34. package/dist/{sample-YNSJ3I3T.js → sample-4NUBJGIL.js} +5 -5
  35. package/dist/{search-NKRXXLRS.js → search-IBVVYN4I.js} +6 -6
  36. package/dist/{semantic-NBAX4WU4.js → semantic-33YIUMUV.js} +3 -3
  37. package/dist/{semantic-DD7P4634.js → semantic-5WGVZ7HY.js} +3 -3
  38. package/dist/{source-resolver-7NY3KEZ2.js → source-resolver-GYXIRIVK.js} +6 -6
  39. package/dist/{sync-warehouse-PK7HXW7U.js → sync-warehouse-5NKENCOZ.js} +5 -5
  40. package/dist/{tables-WQTRZSXR.js → tables-QMZMYYV5.js} +6 -6
  41. package/dist/{test-MKNKUR3N.js → test-6MCPGXJP.js} +5 -5
  42. package/package.json +5 -5
  43. package/dist/chunk-7HWFVUGP.js.map +0 -1
  44. package/dist/chunk-UOSOZT47.js.map +0 -1
  45. package/dist/dev-KOWFGDOP.js.map +0 -1
  46. package/dist/public/assets/DataView-BfwnWnLH.js +0 -9
  47. /package/dist/{advisor-5F73GF54.js.map → advisor-XTJQU7E4.js.map} +0 -0
  48. /package/dist/{chunk-5CRXV5PQ.js.map → chunk-2XJZJ6IA.js.map} +0 -0
  49. /package/dist/{chunk-X3UG6T3Q.js.map → chunk-6ELSQNIH.js.map} +0 -0
  50. /package/dist/{chunk-25ES7MGF.js.map → chunk-ODSPBOJW.js.map} +0 -0
  51. /package/dist/{chunk-WVSDQNAN.js.map → chunk-PXFTWXSV.js.map} +0 -0
  52. /package/dist/{chunk-PYVROL7Z.js.map → chunk-XNMB25GT.js.map} +0 -0
  53. /package/dist/{connection-utils-HNX3473B.js.map → connection-utils-S7EFPTLY.js.map} +0 -0
  54. /package/dist/{describe-KDGXFPDQ.js.map → describe-TA2CTODC.js.map} +0 -0
  55. /package/dist/{dist-LK3ZG74Q.js.map → dist-F6TVLP6C.js.map} +0 -0
  56. /package/dist/{dist-WMP7BFKP.js.map → dist-Q5ETETUU.js.map} +0 -0
  57. /package/dist/{dist-YB5RBWCC.js.map → dist-YJOOH65Y.js.map} +0 -0
  58. /package/dist/{query-7XHKSZ5H.js.map → query-AX4BOPMH.js.map} +0 -0
  59. /package/dist/{sample-YNSJ3I3T.js.map → sample-4NUBJGIL.js.map} +0 -0
  60. /package/dist/{search-NKRXXLRS.js.map → search-IBVVYN4I.js.map} +0 -0
  61. /package/dist/{semantic-NBAX4WU4.js.map → semantic-33YIUMUV.js.map} +0 -0
  62. /package/dist/{semantic-DD7P4634.js.map → semantic-5WGVZ7HY.js.map} +0 -0
  63. /package/dist/{source-resolver-7NY3KEZ2.js.map → source-resolver-GYXIRIVK.js.map} +0 -0
  64. /package/dist/{sync-warehouse-PK7HXW7U.js.map → sync-warehouse-5NKENCOZ.js.map} +0 -0
  65. /package/dist/{tables-WQTRZSXR.js.map → tables-QMZMYYV5.js.map} +0 -0
  66. /package/dist/{test-MKNKUR3N.js.map → test-6MCPGXJP.js.map} +0 -0
@@ -2,7 +2,7 @@ import { createRequire as __yamchartCreateRequire } from 'module'; if (!globalTh
2
2
  import {
3
3
  loadEnvFile,
4
4
  validateProject
5
- } from "./chunk-25ES7MGF.js";
5
+ } from "./chunk-ODSPBOJW.js";
6
6
  import {
7
7
  box,
8
8
  detail,
@@ -18,10 +18,16 @@ import {
18
18
  filterCatalogEntries
19
19
  } from "./chunk-ZMJPRNOA.js";
20
20
  import {
21
+ buildModelPath,
21
22
  buildProvider,
22
23
  createEmbedder,
23
- createProviderForTask
24
- } from "./chunk-UOSOZT47.js";
24
+ createProviderForTask,
25
+ detectConventions,
26
+ formatModelSql,
27
+ scaffoldModelSql,
28
+ updateSchemaYml,
29
+ writeDbtModel
30
+ } from "./chunk-KDDO4UII.js";
25
31
  import {
26
32
  BigQueryConnector,
27
33
  DuckDBConnector,
@@ -40,7 +46,7 @@ import {
40
46
  resolveMySQLAuth,
41
47
  resolvePostgresAuth,
42
48
  resolveSnowflakeAuth
43
- } from "./chunk-5CRXV5PQ.js";
49
+ } from "./chunk-2XJZJ6IA.js";
44
50
  import {
45
51
  SemanticModelBuilder,
46
52
  SemanticQueryCompiler,
@@ -57,7 +63,7 @@ import {
57
63
  isRelativeDateRange,
58
64
  loadSemanticDefinitions,
59
65
  validateSemanticQuery
60
- } from "./chunk-X3UG6T3Q.js";
66
+ } from "./chunk-6ELSQNIH.js";
61
67
  import {
62
68
  AiConfigSchema,
63
69
  ChartSchema,
@@ -74,7 +80,7 @@ import {
74
80
  SemanticQuerySchema,
75
81
  deepMerge,
76
82
  resolveProjectConfig
77
- } from "./chunk-7HWFVUGP.js";
83
+ } from "./chunk-OYBI433Z.js";
78
84
  import {
79
85
  AuthDatabase,
80
86
  generateOpaqueToken,
@@ -3040,7 +3046,7 @@ var require_compile = __commonJS({
3040
3046
  const schOrFunc = root.refs[ref];
3041
3047
  if (schOrFunc)
3042
3048
  return schOrFunc;
3043
- let _sch = resolve3.call(this, root, ref);
3049
+ let _sch = resolve4.call(this, root, ref);
3044
3050
  if (_sch === void 0) {
3045
3051
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
3046
3052
  const { schemaId } = this.opts;
@@ -3067,7 +3073,7 @@ var require_compile = __commonJS({
3067
3073
  function sameSchemaEnv(s1, s2) {
3068
3074
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3069
3075
  }
3070
- function resolve3(root, ref) {
3076
+ function resolve4(root, ref) {
3071
3077
  let sch;
3072
3078
  while (typeof (sch = this.refs[ref]) == "string")
3073
3079
  ref = sch;
@@ -3642,7 +3648,7 @@ var require_fast_uri = __commonJS({
3642
3648
  }
3643
3649
  return uri;
3644
3650
  }
3645
- function resolve3(baseURI, relativeURI, options) {
3651
+ function resolve4(baseURI, relativeURI, options) {
3646
3652
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3647
3653
  const resolved = resolveComponent(parse2(baseURI, schemelessOptions), parse2(relativeURI, schemelessOptions), schemelessOptions, true);
3648
3654
  schemelessOptions.skipEscape = true;
@@ -3869,7 +3875,7 @@ var require_fast_uri = __commonJS({
3869
3875
  var fastUri = {
3870
3876
  SCHEMES,
3871
3877
  normalize,
3872
- resolve: resolve3,
3878
+ resolve: resolve4,
3873
3879
  resolveComponent,
3874
3880
  equal,
3875
3881
  serialize,
@@ -6850,7 +6856,7 @@ var require_dist = __commonJS({
6850
6856
  });
6851
6857
 
6852
6858
  // src/commands/dev.ts
6853
- import { resolve as resolve2 } from "path";
6859
+ import { resolve as resolve3 } from "path";
6854
6860
  import { homedir } from "os";
6855
6861
 
6856
6862
  // ../server/dist/server.js
@@ -6858,9 +6864,476 @@ import Fastify from "fastify";
6858
6864
  import cors from "@fastify/cors";
6859
6865
  import fastifyStatic from "@fastify/static";
6860
6866
 
6861
- // ../server/dist/services/config-loader.js
6867
+ // ../../packages/engine/dist/cache.js
6868
+ import { LRUCache } from "lru-cache";
6869
+ var MemoryCache = class {
6870
+ cache;
6871
+ defaultTtlMs;
6872
+ hits = 0;
6873
+ misses = 0;
6874
+ constructor(options) {
6875
+ this.defaultTtlMs = options.defaultTtlMs;
6876
+ this.cache = new LRUCache({
6877
+ max: options.maxSize,
6878
+ ttl: options.defaultTtlMs,
6879
+ ttlAutopurge: true
6880
+ });
6881
+ }
6882
+ async get(key) {
6883
+ const value = this.cache.get(key);
6884
+ if (value) {
6885
+ this.hits++;
6886
+ return value;
6887
+ }
6888
+ this.misses++;
6889
+ return null;
6890
+ }
6891
+ async set(key, value, ttlMs) {
6892
+ this.cache.set(key, value, { ttl: ttlMs ?? this.defaultTtlMs });
6893
+ }
6894
+ async invalidate(key) {
6895
+ this.cache.delete(key);
6896
+ }
6897
+ async invalidatePattern(pattern) {
6898
+ const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
6899
+ const regex = new RegExp(`^${regexPattern}$`);
6900
+ for (const key of this.cache.keys()) {
6901
+ if (regex.test(key)) {
6902
+ this.cache.delete(key);
6903
+ }
6904
+ }
6905
+ }
6906
+ async clear() {
6907
+ this.cache.clear();
6908
+ }
6909
+ getStats() {
6910
+ return {
6911
+ size: this.cache.size,
6912
+ hits: this.hits,
6913
+ misses: this.misses
6914
+ };
6915
+ }
6916
+ };
6917
+ function parseTtl2(ttl) {
6918
+ const match = ttl.match(/^(\d+)(ms|s|m|h|d)$/);
6919
+ if (!match) {
6920
+ throw new Error(`Invalid TTL format: ${ttl}`);
6921
+ }
6922
+ const value = parseInt(match[1] ?? "0", 10);
6923
+ const unit = match[2];
6924
+ switch (unit) {
6925
+ case "ms":
6926
+ return value;
6927
+ case "s":
6928
+ return value * 1e3;
6929
+ case "m":
6930
+ return value * 60 * 1e3;
6931
+ case "h":
6932
+ return value * 60 * 60 * 1e3;
6933
+ case "d":
6934
+ return value * 24 * 60 * 60 * 1e3;
6935
+ default:
6936
+ throw new Error(`Unknown TTL unit: ${unit}`);
6937
+ }
6938
+ }
6939
+
6940
+ // ../../packages/engine/dist/connector-factory.js
6941
+ import { join } from "path";
6942
+ function createConnectorFromConfig(connection, options) {
6943
+ const { projectDir } = options;
6944
+ switch (connection.type) {
6945
+ case "duckdb": {
6946
+ const duckdbConfig = connection;
6947
+ const dbPath = duckdbConfig.config.path.startsWith("/") ? duckdbConfig.config.path : join(projectDir, duckdbConfig.config.path);
6948
+ return new DuckDBConnector({ path: dbPath });
6949
+ }
6950
+ case "postgres": {
6951
+ const pgConnection = connection;
6952
+ const credentials = resolvePostgresAuth(pgConnection, options.secrets);
6953
+ return new PostgresConnector({
6954
+ host: pgConnection.config.host,
6955
+ port: pgConnection.config.port,
6956
+ database: pgConnection.config.database,
6957
+ schema: pgConnection.config.schema,
6958
+ ssl: pgConnection.config.ssl,
6959
+ user: credentials.user,
6960
+ password: credentials.password,
6961
+ min: pgConnection.pool?.min_connections,
6962
+ max: pgConnection.pool?.max_connections,
6963
+ idleTimeoutMillis: pgConnection.pool?.idle_timeout,
6964
+ statementTimeout: pgConnection.query?.timeout
6965
+ });
6966
+ }
6967
+ case "mysql": {
6968
+ const mysqlConnection = connection;
6969
+ const credentials = resolveMySQLAuth(mysqlConnection, options.secrets);
6970
+ return new MySQLConnector({
6971
+ host: mysqlConnection.config.host,
6972
+ port: mysqlConnection.config.port,
6973
+ database: mysqlConnection.config.database,
6974
+ ssl: mysqlConnection.config.ssl,
6975
+ user: credentials.user,
6976
+ password: credentials.password,
6977
+ min: mysqlConnection.pool?.min_connections,
6978
+ max: mysqlConnection.pool?.max_connections,
6979
+ idleTimeoutMillis: mysqlConnection.pool?.idle_timeout,
6980
+ statementTimeout: mysqlConnection.query?.timeout
6981
+ });
6982
+ }
6983
+ case "sqlite": {
6984
+ const sqliteConnection = connection;
6985
+ const dbPath = sqliteConnection.config.path.startsWith("/") ? sqliteConnection.config.path : sqliteConnection.config.path === ":memory:" ? ":memory:" : join(projectDir, sqliteConnection.config.path);
6986
+ return new SQLiteConnector({ path: dbPath });
6987
+ }
6988
+ case "snowflake": {
6989
+ const sfConnection = connection;
6990
+ const credentials = resolveSnowflakeAuth(sfConnection, options.secrets);
6991
+ return new SnowflakeConnector({
6992
+ account: sfConnection.config.account,
6993
+ username: credentials.username,
6994
+ password: credentials.password,
6995
+ privateKey: credentials.privateKey,
6996
+ authenticator: credentials.authenticator,
6997
+ clientStoreTemporaryCredential: credentials.clientStoreTemporaryCredential,
6998
+ warehouse: sfConnection.config.warehouse,
6999
+ database: sfConnection.config.database,
7000
+ schema: sfConnection.config.schema,
7001
+ role: sfConnection.config.role,
7002
+ statementTimeout: sfConnection.query?.timeout
7003
+ });
7004
+ }
7005
+ case "bigquery": {
7006
+ const bqConnection = connection;
7007
+ const creds = resolveBigQueryAuth(bqConnection, options.secrets);
7008
+ const keyFilename = creds.keyFilename ? creds.keyFilename.startsWith("/") ? creds.keyFilename : join(projectDir, creds.keyFilename) : void 0;
7009
+ return new BigQueryConnector({
7010
+ projectId: bqConnection.config.project_id,
7011
+ dataset: bqConnection.config.dataset,
7012
+ location: bqConnection.config.location,
7013
+ maxBytesBilled: bqConnection.config.max_bytes_billed,
7014
+ credentials: creds.credentials,
7015
+ keyFilename
7016
+ });
7017
+ }
7018
+ default:
7019
+ throw new Error(`Unsupported connection type: ${connection.type}`);
7020
+ }
7021
+ }
7022
+ function buildConnectionInfo(connection) {
7023
+ const info2 = {
7024
+ name: connection.name,
7025
+ type: connection.type
7026
+ };
7027
+ switch (connection.type) {
7028
+ case "duckdb": {
7029
+ const c = connection;
7030
+ info2.path = c.config.path;
7031
+ break;
7032
+ }
7033
+ case "postgres": {
7034
+ const c = connection;
7035
+ info2.host = c.config.host;
7036
+ info2.port = c.config.port;
7037
+ info2.database = c.config.database;
7038
+ info2.schema = c.config.schema;
7039
+ break;
7040
+ }
7041
+ case "mysql": {
7042
+ const c = connection;
7043
+ info2.host = c.config.host;
7044
+ info2.port = c.config.port;
7045
+ info2.database = c.config.database;
7046
+ break;
7047
+ }
7048
+ case "sqlite": {
7049
+ const c = connection;
7050
+ info2.path = c.config.path;
7051
+ break;
7052
+ }
7053
+ case "snowflake": {
7054
+ const c = connection;
7055
+ info2.account = c.config.account;
7056
+ info2.warehouse = c.config.warehouse;
7057
+ info2.database = c.config.database;
7058
+ info2.schema = c.config.schema;
7059
+ info2.role = c.config.role;
7060
+ break;
7061
+ }
7062
+ case "bigquery": {
7063
+ const c = connection;
7064
+ info2.database = c.config.project_id;
7065
+ info2.schema = c.config.dataset;
7066
+ break;
7067
+ }
7068
+ }
7069
+ return info2;
7070
+ }
7071
+ async function testConnection(connection, options) {
7072
+ const start = performance.now();
7073
+ let connector = null;
7074
+ try {
7075
+ connector = createConnectorFromConfig(connection, options);
7076
+ await connector.connect();
7077
+ const healthQuery = connection.type === "duckdb" ? "SELECT 1 AS health" : "SELECT 1";
7078
+ await connector.execute(healthQuery);
7079
+ const latencyMs = performance.now() - start;
7080
+ return {
7081
+ name: connection.name,
7082
+ type: connection.type,
7083
+ status: "healthy",
7084
+ latencyMs: Math.round(latencyMs)
7085
+ };
7086
+ } catch (err) {
7087
+ const latencyMs = performance.now() - start;
7088
+ let errorMessage = "Unknown error";
7089
+ if (err instanceof Error) {
7090
+ const pgError = err;
7091
+ errorMessage = err.message || pgError.code || "Connection failed";
7092
+ } else {
7093
+ errorMessage = String(err);
7094
+ }
7095
+ return {
7096
+ name: connection.name,
7097
+ type: connection.type,
7098
+ status: "unhealthy",
7099
+ latencyMs: Math.round(latencyMs),
7100
+ error: errorMessage
7101
+ };
7102
+ } finally {
7103
+ if (connector) {
7104
+ try {
7105
+ await connector.disconnect();
7106
+ } catch {
7107
+ }
7108
+ }
7109
+ }
7110
+ }
7111
+
7112
+ // ../../packages/engine/dist/query-service.js
7113
+ function quoteIdentifier(name, dialect) {
7114
+ if (dialect === "mysql" || dialect === "bigquery")
7115
+ return `\`${name.replace(/`/g, "``")}\``;
7116
+ return `"${name.replace(/"/g, '""')}"`;
7117
+ }
7118
+ var QueryService = class {
7119
+ compiler;
7120
+ connector;
7121
+ cache;
7122
+ models;
7123
+ refs;
7124
+ dialect;
7125
+ constructor(config) {
7126
+ this.compiler = new QueryCompiler({
7127
+ models: config.models,
7128
+ refs: config.refs
7129
+ });
7130
+ this.connector = config.connector;
7131
+ this.cache = config.cache;
7132
+ this.models = new Map(Object.entries(config.models));
7133
+ this.refs = config.refs;
7134
+ this.dialect = config.dialect ?? "duckdb";
7135
+ }
7136
+ async executeChart(chart, params, userContext) {
7137
+ const compiled = this.compiler.compile(chart, params, userContext);
7138
+ const cached = await this.cache.get(compiled.cacheKey);
7139
+ if (cached) {
7140
+ return {
7141
+ columns: cached.columns,
7142
+ rows: cached.rows,
7143
+ rowCount: cached.rowCount,
7144
+ durationMs: cached.durationMs,
7145
+ cached: true,
7146
+ cacheKey: compiled.cacheKey,
7147
+ compiledSql: compiled.sql
7148
+ };
7149
+ }
7150
+ const result = await this.connector.execute(compiled.sql);
7151
+ const cacheEntry = {
7152
+ columns: result.columns,
7153
+ rows: result.rows,
7154
+ rowCount: result.rowCount,
7155
+ durationMs: result.durationMs,
7156
+ cachedAt: Date.now()
7157
+ };
7158
+ await this.cache.set(compiled.cacheKey, cacheEntry);
7159
+ return {
7160
+ ...result,
7161
+ cached: false,
7162
+ cacheKey: compiled.cacheKey,
7163
+ compiledSql: compiled.sql
7164
+ };
7165
+ }
7166
+ /**
7167
+ * Execute a table chart with server-side pagination.
7168
+ * Wraps the compiled SQL with LIMIT/OFFSET and runs a parallel COUNT(*) query.
7169
+ * Optionally computes summary aggregations for specified columns.
7170
+ */
7171
+ async executeChartPaginated(chart, params, options, userContext) {
7172
+ const compiled = this.compiler.compile(chart, params, userContext);
7173
+ const baseSql = compiled.sql;
7174
+ const q = quoteIdentifier;
7175
+ const d = this.dialect;
7176
+ let orderBy = "";
7177
+ if (options.sortField) {
7178
+ const cleanField = options.sortField.replace(/[^\w\s]/g, "");
7179
+ if (cleanField.length > 0) {
7180
+ orderBy = `ORDER BY ${q(cleanField, d)} ${options.sortDirection === "desc" ? "DESC" : "ASC"}`;
7181
+ }
7182
+ }
7183
+ const offset = options.page * options.pageSize;
7184
+ const dataSql = `SELECT * FROM (${baseSql}) _t ${orderBy} LIMIT ${options.pageSize} OFFSET ${offset}`;
7185
+ const countCacheKey = `${compiled.cacheKey}:count`;
7186
+ let totalCount;
7187
+ const cachedCount = await this.cache.get(countCacheKey);
7188
+ if (cachedCount) {
7189
+ totalCount = cachedCount.rowCount;
7190
+ } else {
7191
+ const countSql = `SELECT COUNT(*) AS _count FROM (${baseSql}) _t`;
7192
+ const countResult = await this.connector.execute(countSql);
7193
+ totalCount = Number(countResult.rows[0]?._count ?? 0);
7194
+ await this.cache.set(countCacheKey, {
7195
+ columns: [],
7196
+ rows: [],
7197
+ rowCount: totalCount,
7198
+ durationMs: countResult.durationMs,
7199
+ cachedAt: Date.now()
7200
+ });
7201
+ }
7202
+ let summaryValues;
7203
+ if (options.summaryColumns && options.summaryColumns.length > 0) {
7204
+ const summaryCacheKey = `${compiled.cacheKey}:summary`;
7205
+ const cachedSummary = await this.cache.get(summaryCacheKey);
7206
+ if (cachedSummary && cachedSummary.rows[0]) {
7207
+ summaryValues = cachedSummary.rows[0];
7208
+ } else {
7209
+ const aggExprs = options.summaryColumns.map(({ field, summary }) => {
7210
+ const col = q(field, d);
7211
+ switch (summary) {
7212
+ case "sum":
7213
+ return `SUM(${col}) AS ${q(`_sum_${field}`, d)}`;
7214
+ case "avg":
7215
+ return `AVG(${col}) AS ${q(`_avg_${field}`, d)}`;
7216
+ case "min":
7217
+ return `MIN(${col}) AS ${q(`_min_${field}`, d)}`;
7218
+ case "max":
7219
+ return `MAX(${col}) AS ${q(`_max_${field}`, d)}`;
7220
+ case "count":
7221
+ return `COUNT(${col}) AS ${q(`_count_${field}`, d)}`;
7222
+ default:
7223
+ return null;
7224
+ }
7225
+ }).filter(Boolean);
7226
+ if (aggExprs.length > 0) {
7227
+ const summarySql = `SELECT ${aggExprs.join(", ")} FROM (${baseSql}) _t`;
7228
+ const summaryResult = await this.connector.execute(summarySql);
7229
+ if (summaryResult.rows[0]) {
7230
+ summaryValues = {};
7231
+ for (const { field, summary } of options.summaryColumns) {
7232
+ const key = `_${summary}_${field}`;
7233
+ summaryValues[`${summary}:${field}`] = Number(summaryResult.rows[0][key] ?? 0);
7234
+ }
7235
+ await this.cache.set(summaryCacheKey, {
7236
+ columns: [],
7237
+ rows: [summaryValues],
7238
+ rowCount: 0,
7239
+ durationMs: summaryResult.durationMs,
7240
+ cachedAt: Date.now()
7241
+ });
7242
+ }
7243
+ }
7244
+ }
7245
+ }
7246
+ const result = await this.connector.execute(dataSql);
7247
+ return {
7248
+ ...result,
7249
+ cached: false,
7250
+ cacheKey: compiled.cacheKey,
7251
+ compiledSql: dataSql,
7252
+ totalCount,
7253
+ page: options.page,
7254
+ pageSize: options.pageSize,
7255
+ summaryValues
7256
+ };
7257
+ }
7258
+ /**
7259
+ * Execute a chart query for CSV export with a bounded row limit.
7260
+ * Returns full QueryResult — caller streams to HTTP response.
7261
+ */
7262
+ async executeChartForExport(chart, params, maxRows, userContext) {
7263
+ const compiled = this.compiler.compile(chart, params, userContext);
7264
+ const sql = `SELECT * FROM (${compiled.sql}) _t LIMIT ${maxRows}`;
7265
+ const result = await this.connector.execute(sql);
7266
+ return {
7267
+ ...result,
7268
+ cached: false,
7269
+ cacheKey: compiled.cacheKey,
7270
+ compiledSql: sql
7271
+ };
7272
+ }
7273
+ /**
7274
+ * Compile a chart query without executing it.
7275
+ * Returns the compiled SQL and cache key.
7276
+ */
7277
+ compileChart(chart, params, userContext) {
7278
+ return this.compiler.compile(chart, params, userContext);
7279
+ }
7280
+ invalidateChart(chartName) {
7281
+ this.cache.invalidatePattern(`${chartName}:*`);
7282
+ }
7283
+ invalidateAll() {
7284
+ this.cache.clear();
7285
+ }
7286
+ /**
7287
+ * Update the compiler with new models and refs.
7288
+ * Called when config is reloaded.
7289
+ */
7290
+ updateCompiler(config) {
7291
+ this.compiler = new QueryCompiler({
7292
+ models: config.models,
7293
+ refs: config.refs
7294
+ });
7295
+ this.models = new Map(Object.entries(config.models));
7296
+ this.refs = config.refs;
7297
+ }
7298
+ getCacheStats() {
7299
+ return this.cache.getStats();
7300
+ }
7301
+ async executeRawSql(sql) {
7302
+ return this.connector.execute(sql);
7303
+ }
7304
+ async executeModel(modelName, params, userContext, limit2 = 1e3, filters) {
7305
+ const model = this.models.get(modelName);
7306
+ if (!model) {
7307
+ throw new Error(`Unknown model: ${modelName}`);
7308
+ }
7309
+ const context = createTemplateContext(params, this.refs, userContext);
7310
+ let sql = renderTemplate(model.sql, context);
7311
+ if (filters && Object.keys(filters).length > 0) {
7312
+ const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
7313
+ sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit2}`;
7314
+ } else {
7315
+ sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit2}`;
7316
+ }
7317
+ return this.connector.execute(sql);
7318
+ }
7319
+ async executeParameterOptions(modelName, valueField, labelField) {
7320
+ const model = this.models.get(modelName);
7321
+ if (!model) {
7322
+ throw new Error(`Unknown model: ${modelName}`);
7323
+ }
7324
+ const context = createTemplateContext({}, this.refs);
7325
+ const sql = renderTemplate(model.sql, context);
7326
+ const result = await this.connector.execute(sql);
7327
+ return result.rows.map((row) => ({
7328
+ value: String(row[valueField] ?? ""),
7329
+ label: String(row[labelField] ?? row[valueField] ?? "")
7330
+ }));
7331
+ }
7332
+ };
7333
+
7334
+ // ../../packages/engine/dist/config-loader.js
6862
7335
  import { readFile, readdir, access } from "fs/promises";
6863
- import { join, extname, relative, dirname } from "path";
7336
+ import { join as join2, extname, relative, dirname } from "path";
6864
7337
  import { parse as parseYaml } from "yaml";
6865
7338
  import { watch } from "chokidar";
6866
7339
 
@@ -7113,7 +7586,7 @@ data: ${JSON.stringify(event.data)}
7113
7586
  `;
7114
7587
  }
7115
7588
 
7116
- // ../server/dist/services/config-loader.js
7589
+ // ../../packages/engine/dist/config-loader.js
7117
7590
  var ConfigLoader = class {
7118
7591
  projectDir;
7119
7592
  project = null;
@@ -7164,7 +7637,7 @@ var ConfigLoader = class {
7164
7637
  await this.loadAgents();
7165
7638
  }
7166
7639
  async loadProject() {
7167
- const projectPath = join(this.projectDir, "yamchart.yaml");
7640
+ const projectPath = join2(this.projectDir, "yamchart.yaml");
7168
7641
  try {
7169
7642
  await access(projectPath);
7170
7643
  } catch {
@@ -7173,7 +7646,7 @@ var ConfigLoader = class {
7173
7646
  const content = await readFile(projectPath, "utf-8");
7174
7647
  const raw = parseYaml(content);
7175
7648
  let merged = resolveProjectConfig(raw, this.env);
7176
- const localPath = join(this.projectDir, "yamchart.local.yaml");
7649
+ const localPath = join2(this.projectDir, "yamchart.local.yaml");
7177
7650
  try {
7178
7651
  await access(localPath);
7179
7652
  const localContent = await readFile(localPath, "utf-8");
@@ -7190,7 +7663,7 @@ var ConfigLoader = class {
7190
7663
  this.project = result.data;
7191
7664
  }
7192
7665
  async loadConnections() {
7193
- const connectionsDir = join(this.projectDir, "connections");
7666
+ const connectionsDir = join2(this.projectDir, "connections");
7194
7667
  try {
7195
7668
  await access(connectionsDir);
7196
7669
  } catch {
@@ -7200,7 +7673,7 @@ var ConfigLoader = class {
7200
7673
  for (const file of files) {
7201
7674
  if (extname(file) !== ".yaml" && extname(file) !== ".yml")
7202
7675
  continue;
7203
- const filePath = join(connectionsDir, file);
7676
+ const filePath = join2(connectionsDir, file);
7204
7677
  const content = await readFile(filePath, "utf-8");
7205
7678
  const parsed = parseYaml(content);
7206
7679
  const result = ConnectionSchema.safeParse(parsed);
@@ -7212,7 +7685,7 @@ var ConfigLoader = class {
7212
7685
  }
7213
7686
  }
7214
7687
  async loadCharts() {
7215
- const chartsDir = join(this.projectDir, "charts");
7688
+ const chartsDir = join2(this.projectDir, "charts");
7216
7689
  try {
7217
7690
  await access(chartsDir);
7218
7691
  } catch {
@@ -7223,7 +7696,7 @@ var ConfigLoader = class {
7223
7696
  async loadChartsFromDir(dir, baseDir) {
7224
7697
  const entries = await readdir(dir, { withFileTypes: true });
7225
7698
  for (const entry of entries) {
7226
- const fullPath = join(dir, entry.name);
7699
+ const fullPath = join2(dir, entry.name);
7227
7700
  if (entry.isDirectory() && dir === baseDir) {
7228
7701
  await this.loadChartsFromDir(fullPath, baseDir);
7229
7702
  } else if (entry.isFile() && (extname(entry.name) === ".yaml" || extname(entry.name) === ".yml")) {
@@ -7256,7 +7729,7 @@ var ConfigLoader = class {
7256
7729
  }
7257
7730
  }
7258
7731
  async loadModels() {
7259
- const modelsDir = join(this.projectDir, "models");
7732
+ const modelsDir = join2(this.projectDir, "models");
7260
7733
  try {
7261
7734
  await access(modelsDir);
7262
7735
  } catch {
@@ -7267,7 +7740,7 @@ var ConfigLoader = class {
7267
7740
  async loadModelsFromDir(dir) {
7268
7741
  const entries = await readdir(dir, { withFileTypes: true });
7269
7742
  for (const entry of entries) {
7270
- const fullPath = join(dir, entry.name);
7743
+ const fullPath = join2(dir, entry.name);
7271
7744
  if (entry.isDirectory()) {
7272
7745
  await this.loadModelsFromDir(fullPath);
7273
7746
  } else if (extname(entry.name) === ".sql") {
@@ -7286,7 +7759,7 @@ var ConfigLoader = class {
7286
7759
  }
7287
7760
  }
7288
7761
  async loadDashboards() {
7289
- const dashboardsDir = join(this.projectDir, "dashboards");
7762
+ const dashboardsDir = join2(this.projectDir, "dashboards");
7290
7763
  try {
7291
7764
  await access(dashboardsDir);
7292
7765
  } catch {
@@ -7297,7 +7770,7 @@ var ConfigLoader = class {
7297
7770
  async loadDashboardsFromDir(dir, baseDir) {
7298
7771
  const entries = await readdir(dir, { withFileTypes: true });
7299
7772
  for (const entry of entries) {
7300
- const fullPath = join(dir, entry.name);
7773
+ const fullPath = join2(dir, entry.name);
7301
7774
  if (entry.isDirectory() && dir === baseDir) {
7302
7775
  await this.loadDashboardsFromDir(fullPath, baseDir);
7303
7776
  } else if (entry.isFile() && (extname(entry.name) === ".yaml" || extname(entry.name) === ".yml")) {
@@ -7330,7 +7803,7 @@ var ConfigLoader = class {
7330
7803
  }
7331
7804
  }
7332
7805
  async loadSchedules() {
7333
- const schedulesDir = join(this.projectDir, "schedules");
7806
+ const schedulesDir = join2(this.projectDir, "schedules");
7334
7807
  try {
7335
7808
  await access(schedulesDir);
7336
7809
  } catch {
@@ -7340,7 +7813,7 @@ var ConfigLoader = class {
7340
7813
  for (const file of files) {
7341
7814
  if (extname(file) !== ".yaml" && extname(file) !== ".yml")
7342
7815
  continue;
7343
- const filePath = join(schedulesDir, file);
7816
+ const filePath = join2(schedulesDir, file);
7344
7817
  const content = await readFile(filePath, "utf-8");
7345
7818
  const parsed = parseYaml(content);
7346
7819
  const result = ScheduleSchema.safeParse(parsed);
@@ -7352,7 +7825,7 @@ var ConfigLoader = class {
7352
7825
  }
7353
7826
  }
7354
7827
  async loadEvents() {
7355
- const eventsPath = join(this.projectDir, "events.yaml");
7828
+ const eventsPath = join2(this.projectDir, "events.yaml");
7356
7829
  try {
7357
7830
  await access(eventsPath);
7358
7831
  } catch {
@@ -7368,12 +7841,12 @@ var ConfigLoader = class {
7368
7841
  }
7369
7842
  }
7370
7843
  async loadAgents() {
7371
- const agentsDir = join(this.projectDir, "agents");
7844
+ const agentsDir = join2(this.projectDir, "agents");
7372
7845
  this.agentRegistry.clearProjectAgents();
7373
7846
  try {
7374
7847
  const files = await readdir(agentsDir);
7375
7848
  for (const file of files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"))) {
7376
- const content = await readFile(join(agentsDir, file), "utf-8");
7849
+ const content = await readFile(join2(agentsDir, file), "utf-8");
7377
7850
  this.agentRegistry.loadFromYaml(content, `agents/${file}`, (msg) => {
7378
7851
  console.warn(msg);
7379
7852
  });
@@ -7385,15 +7858,15 @@ var ConfigLoader = class {
7385
7858
  if (this.watcher)
7386
7859
  return;
7387
7860
  const watchPaths = [
7388
- join(this.projectDir, "yamchart.yaml"),
7389
- join(this.projectDir, "yamchart.local.yaml"),
7390
- join(this.projectDir, "connections"),
7391
- join(this.projectDir, "charts"),
7392
- join(this.projectDir, "models"),
7393
- join(this.projectDir, "dashboards"),
7394
- join(this.projectDir, "schedules"),
7395
- join(this.projectDir, "events.yaml"),
7396
- join(this.projectDir, "agents")
7861
+ join2(this.projectDir, "yamchart.yaml"),
7862
+ join2(this.projectDir, "yamchart.local.yaml"),
7863
+ join2(this.projectDir, "connections"),
7864
+ join2(this.projectDir, "charts"),
7865
+ join2(this.projectDir, "models"),
7866
+ join2(this.projectDir, "dashboards"),
7867
+ join2(this.projectDir, "schedules"),
7868
+ join2(this.projectDir, "events.yaml"),
7869
+ join2(this.projectDir, "agents")
7397
7870
  ];
7398
7871
  this.watcher = watch(watchPaths, {
7399
7872
  ignored: /(^|[\/\\])\../,
@@ -7469,692 +7942,231 @@ Fix: run "ulimit -n 10240" before starting yamchart dev, or add it to your shell
7469
7942
  this.runtimeKnowledgeUrl = rc.knowledge?.database_url;
7470
7943
  this.runtimeAi = rc.ai;
7471
7944
  }
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
- }
7486
- getCharts() {
7487
- return Array.from(this.charts.values());
7488
- }
7489
- getChartByName(name) {
7490
- return this.charts.get(name);
7491
- }
7492
- getChartFolder(name) {
7493
- return this.chartFolders.get(name) ?? "";
7494
- }
7495
- getChartFilePath(name) {
7496
- return this.chartFilePaths.get(name);
7497
- }
7498
- getModels() {
7499
- return Array.from(this.models.values());
7500
- }
7501
- getModelByName(name) {
7502
- return this.models.get(name);
7503
- }
7504
- getDefaultConnection() {
7505
- const envConnection = process.env.YAMCHART_CONNECTION;
7506
- if (envConnection) {
7507
- return this.connections.get(envConnection);
7508
- }
7509
- const defaultName = this.project?.defaults?.connection;
7510
- if (defaultName) {
7511
- return this.connections.get(defaultName);
7512
- }
7513
- return this.connections.values().next().value;
7514
- }
7515
- getDashboards() {
7516
- return Array.from(this.dashboards.values());
7517
- }
7518
- getDashboardByName(name) {
7519
- return this.dashboards.get(name);
7520
- }
7521
- getDashboardFolder(name) {
7522
- return this.dashboardFolders.get(name) ?? "";
7523
- }
7524
- getDashboardFilePath(name) {
7525
- return this.dashboardFilePaths.get(name);
7526
- }
7527
- getSchedules() {
7528
- return Array.from(this.schedules.values());
7529
- }
7530
- getEvents() {
7531
- return this.events;
7532
- }
7533
- getProjectDir() {
7534
- return this.projectDir;
7535
- }
7536
- getValidationError(name) {
7537
- return this.validationErrors.get(name);
7538
- }
7539
- getValidationErrors() {
7540
- return this.validationErrors;
7541
- }
7542
- getAgentRegistry() {
7543
- return this.agentRegistry;
7544
- }
7545
- };
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
-
7693
- // ../server/dist/services/cache.js
7694
- import { LRUCache } from "lru-cache";
7695
- var MemoryCache = class {
7696
- cache;
7697
- defaultTtlMs;
7698
- hits = 0;
7699
- misses = 0;
7700
- constructor(options) {
7701
- this.defaultTtlMs = options.defaultTtlMs;
7702
- this.cache = new LRUCache({
7703
- max: options.maxSize,
7704
- ttl: options.defaultTtlMs,
7705
- ttlAutopurge: true
7706
- });
7707
- }
7708
- async get(key) {
7709
- const value = this.cache.get(key);
7710
- if (value) {
7711
- this.hits++;
7712
- return value;
7713
- }
7714
- this.misses++;
7715
- return null;
7716
- }
7717
- async set(key, value, ttlMs) {
7718
- this.cache.set(key, value, { ttl: ttlMs ?? this.defaultTtlMs });
7719
- }
7720
- async invalidate(key) {
7721
- this.cache.delete(key);
7722
- }
7723
- async invalidatePattern(pattern) {
7724
- const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
7725
- const regex = new RegExp(`^${regexPattern}$`);
7726
- for (const key of this.cache.keys()) {
7727
- if (regex.test(key)) {
7728
- this.cache.delete(key);
7729
- }
7730
- }
7945
+ getConnectionSecrets(name) {
7946
+ return this.connectionSecrets.get(name);
7731
7947
  }
7732
- async clear() {
7733
- this.cache.clear();
7948
+ /** Knowledge DB URL: runtime override wins, else the named env var. */
7949
+ getKnowledgeDbUrl(envVarName, env) {
7950
+ return this.runtimeKnowledgeUrl ?? env[envVarName];
7734
7951
  }
7735
- getStats() {
7736
- return {
7737
- size: this.cache.size,
7738
- hits: this.hits,
7739
- misses: this.misses
7740
- };
7952
+ /** Effective AI config: Git project.ai with the runtime ai deep-merged over it. */
7953
+ getAiConfig() {
7954
+ const base2 = this.project?.ai;
7955
+ if (!this.runtimeAi)
7956
+ return base2;
7957
+ return deepMerge(base2 ?? {}, this.runtimeAi);
7741
7958
  }
7742
- };
7743
- function parseTtl2(ttl) {
7744
- const match = ttl.match(/^(\d+)(ms|s|m|h|d)$/);
7745
- if (!match) {
7746
- throw new Error(`Invalid TTL format: ${ttl}`);
7959
+ getCharts() {
7960
+ return Array.from(this.charts.values());
7747
7961
  }
7748
- const value = parseInt(match[1] ?? "0", 10);
7749
- const unit = match[2];
7750
- switch (unit) {
7751
- case "ms":
7752
- return value;
7753
- case "s":
7754
- return value * 1e3;
7755
- case "m":
7756
- return value * 60 * 1e3;
7757
- case "h":
7758
- return value * 60 * 60 * 1e3;
7759
- case "d":
7760
- return value * 24 * 60 * 60 * 1e3;
7761
- default:
7762
- throw new Error(`Unknown TTL unit: ${unit}`);
7962
+ getChartByName(name) {
7963
+ return this.charts.get(name);
7763
7964
  }
7764
- }
7765
-
7766
- // ../server/dist/services/query-service.js
7767
- function quoteIdentifier(name, dialect) {
7768
- if (dialect === "mysql" || dialect === "bigquery")
7769
- return `\`${name.replace(/`/g, "``")}\``;
7770
- return `"${name.replace(/"/g, '""')}"`;
7771
- }
7772
- var QueryService = class {
7773
- compiler;
7774
- connector;
7775
- cache;
7776
- models;
7777
- refs;
7778
- dialect;
7779
- constructor(config) {
7780
- this.compiler = new QueryCompiler({
7781
- models: config.models,
7782
- refs: config.refs
7783
- });
7784
- this.connector = config.connector;
7785
- this.cache = config.cache;
7786
- this.models = new Map(Object.entries(config.models));
7787
- this.refs = config.refs;
7788
- this.dialect = config.dialect ?? "duckdb";
7965
+ getChartFolder(name) {
7966
+ return this.chartFolders.get(name) ?? "";
7789
7967
  }
7790
- async executeChart(chart, params, userContext) {
7791
- const compiled = this.compiler.compile(chart, params, userContext);
7792
- const cached = await this.cache.get(compiled.cacheKey);
7793
- if (cached) {
7794
- return {
7795
- columns: cached.columns,
7796
- rows: cached.rows,
7797
- rowCount: cached.rowCount,
7798
- durationMs: cached.durationMs,
7799
- cached: true,
7800
- cacheKey: compiled.cacheKey,
7801
- compiledSql: compiled.sql
7802
- };
7803
- }
7804
- const result = await this.connector.execute(compiled.sql);
7805
- const cacheEntry = {
7806
- columns: result.columns,
7807
- rows: result.rows,
7808
- rowCount: result.rowCount,
7809
- durationMs: result.durationMs,
7810
- cachedAt: Date.now()
7811
- };
7812
- await this.cache.set(compiled.cacheKey, cacheEntry);
7813
- return {
7814
- ...result,
7815
- cached: false,
7816
- cacheKey: compiled.cacheKey,
7817
- compiledSql: compiled.sql
7818
- };
7968
+ getChartFilePath(name) {
7969
+ return this.chartFilePaths.get(name);
7819
7970
  }
7820
- /**
7821
- * Execute a table chart with server-side pagination.
7822
- * Wraps the compiled SQL with LIMIT/OFFSET and runs a parallel COUNT(*) query.
7823
- * Optionally computes summary aggregations for specified columns.
7824
- */
7825
- async executeChartPaginated(chart, params, options, userContext) {
7826
- const compiled = this.compiler.compile(chart, params, userContext);
7827
- const baseSql = compiled.sql;
7828
- const q = quoteIdentifier;
7829
- const d = this.dialect;
7830
- let orderBy = "";
7831
- if (options.sortField) {
7832
- const cleanField = options.sortField.replace(/[^\w\s]/g, "");
7833
- if (cleanField.length > 0) {
7834
- orderBy = `ORDER BY ${q(cleanField, d)} ${options.sortDirection === "desc" ? "DESC" : "ASC"}`;
7835
- }
7836
- }
7837
- const offset = options.page * options.pageSize;
7838
- const dataSql = `SELECT * FROM (${baseSql}) _t ${orderBy} LIMIT ${options.pageSize} OFFSET ${offset}`;
7839
- const countCacheKey = `${compiled.cacheKey}:count`;
7840
- let totalCount;
7841
- const cachedCount = await this.cache.get(countCacheKey);
7842
- if (cachedCount) {
7843
- totalCount = cachedCount.rowCount;
7844
- } else {
7845
- const countSql = `SELECT COUNT(*) AS _count FROM (${baseSql}) _t`;
7846
- const countResult = await this.connector.execute(countSql);
7847
- totalCount = Number(countResult.rows[0]?._count ?? 0);
7848
- await this.cache.set(countCacheKey, {
7849
- columns: [],
7850
- rows: [],
7851
- rowCount: totalCount,
7852
- durationMs: countResult.durationMs,
7853
- cachedAt: Date.now()
7854
- });
7971
+ getModels() {
7972
+ return Array.from(this.models.values());
7973
+ }
7974
+ getModelByName(name) {
7975
+ return this.models.get(name);
7976
+ }
7977
+ getDefaultConnection() {
7978
+ const envConnection = process.env.YAMCHART_CONNECTION;
7979
+ if (envConnection) {
7980
+ return this.connections.get(envConnection);
7855
7981
  }
7856
- let summaryValues;
7857
- if (options.summaryColumns && options.summaryColumns.length > 0) {
7858
- const summaryCacheKey = `${compiled.cacheKey}:summary`;
7859
- const cachedSummary = await this.cache.get(summaryCacheKey);
7860
- if (cachedSummary && cachedSummary.rows[0]) {
7861
- summaryValues = cachedSummary.rows[0];
7862
- } else {
7863
- const aggExprs = options.summaryColumns.map(({ field, summary }) => {
7864
- const col = q(field, d);
7865
- switch (summary) {
7866
- case "sum":
7867
- return `SUM(${col}) AS ${q(`_sum_${field}`, d)}`;
7868
- case "avg":
7869
- return `AVG(${col}) AS ${q(`_avg_${field}`, d)}`;
7870
- case "min":
7871
- return `MIN(${col}) AS ${q(`_min_${field}`, d)}`;
7872
- case "max":
7873
- return `MAX(${col}) AS ${q(`_max_${field}`, d)}`;
7874
- case "count":
7875
- return `COUNT(${col}) AS ${q(`_count_${field}`, d)}`;
7876
- default:
7877
- return null;
7878
- }
7879
- }).filter(Boolean);
7880
- if (aggExprs.length > 0) {
7881
- const summarySql = `SELECT ${aggExprs.join(", ")} FROM (${baseSql}) _t`;
7882
- const summaryResult = await this.connector.execute(summarySql);
7883
- if (summaryResult.rows[0]) {
7884
- summaryValues = {};
7885
- for (const { field, summary } of options.summaryColumns) {
7886
- const key = `_${summary}_${field}`;
7887
- summaryValues[`${summary}:${field}`] = Number(summaryResult.rows[0][key] ?? 0);
7888
- }
7889
- await this.cache.set(summaryCacheKey, {
7890
- columns: [],
7891
- rows: [summaryValues],
7892
- rowCount: 0,
7893
- durationMs: summaryResult.durationMs,
7894
- cachedAt: Date.now()
7895
- });
7896
- }
7897
- }
7898
- }
7982
+ const defaultName = this.project?.defaults?.connection;
7983
+ if (defaultName) {
7984
+ return this.connections.get(defaultName);
7899
7985
  }
7900
- const result = await this.connector.execute(dataSql);
7901
- return {
7902
- ...result,
7903
- cached: false,
7904
- cacheKey: compiled.cacheKey,
7905
- compiledSql: dataSql,
7906
- totalCount,
7907
- page: options.page,
7908
- pageSize: options.pageSize,
7909
- summaryValues
7910
- };
7986
+ return this.connections.values().next().value;
7911
7987
  }
7912
- /**
7913
- * Execute a chart query for CSV export with a bounded row limit.
7914
- * Returns full QueryResult — caller streams to HTTP response.
7915
- */
7916
- async executeChartForExport(chart, params, maxRows, userContext) {
7917
- const compiled = this.compiler.compile(chart, params, userContext);
7918
- const sql = `SELECT * FROM (${compiled.sql}) _t LIMIT ${maxRows}`;
7919
- const result = await this.connector.execute(sql);
7920
- return {
7921
- ...result,
7922
- cached: false,
7923
- cacheKey: compiled.cacheKey,
7924
- compiledSql: sql
7925
- };
7988
+ getDashboards() {
7989
+ return Array.from(this.dashboards.values());
7926
7990
  }
7927
- /**
7928
- * Compile a chart query without executing it.
7929
- * Returns the compiled SQL and cache key.
7930
- */
7931
- compileChart(chart, params, userContext) {
7932
- return this.compiler.compile(chart, params, userContext);
7991
+ getDashboardByName(name) {
7992
+ return this.dashboards.get(name);
7933
7993
  }
7934
- invalidateChart(chartName) {
7935
- this.cache.invalidatePattern(`${chartName}:*`);
7994
+ getDashboardFolder(name) {
7995
+ return this.dashboardFolders.get(name) ?? "";
7936
7996
  }
7937
- invalidateAll() {
7938
- this.cache.clear();
7997
+ getDashboardFilePath(name) {
7998
+ return this.dashboardFilePaths.get(name);
7939
7999
  }
7940
- /**
7941
- * Update the compiler with new models and refs.
7942
- * Called when config is reloaded.
7943
- */
7944
- updateCompiler(config) {
7945
- this.compiler = new QueryCompiler({
7946
- models: config.models,
7947
- refs: config.refs
7948
- });
7949
- this.models = new Map(Object.entries(config.models));
7950
- this.refs = config.refs;
8000
+ getSchedules() {
8001
+ return Array.from(this.schedules.values());
7951
8002
  }
7952
- getCacheStats() {
7953
- return this.cache.getStats();
8003
+ getEvents() {
8004
+ return this.events;
7954
8005
  }
7955
- async executeRawSql(sql) {
7956
- return this.connector.execute(sql);
8006
+ getProjectDir() {
8007
+ return this.projectDir;
7957
8008
  }
7958
- async executeModel(modelName, params, userContext, limit2 = 1e3, filters) {
7959
- const model = this.models.get(modelName);
7960
- if (!model) {
7961
- throw new Error(`Unknown model: ${modelName}`);
7962
- }
7963
- const context = createTemplateContext(params, this.refs, userContext);
7964
- let sql = renderTemplate(model.sql, context);
7965
- if (filters && Object.keys(filters).length > 0) {
7966
- const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
7967
- sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit2}`;
7968
- } else {
7969
- sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit2}`;
7970
- }
7971
- return this.connector.execute(sql);
8009
+ getValidationError(name) {
8010
+ return this.validationErrors.get(name);
7972
8011
  }
7973
- async executeParameterOptions(modelName, valueField, labelField) {
7974
- const model = this.models.get(modelName);
7975
- if (!model) {
7976
- throw new Error(`Unknown model: ${modelName}`);
7977
- }
7978
- const context = createTemplateContext({}, this.refs);
7979
- const sql = renderTemplate(model.sql, context);
7980
- const result = await this.connector.execute(sql);
7981
- return result.rows.map((row) => ({
7982
- value: String(row[valueField] ?? ""),
7983
- label: String(row[labelField] ?? row[valueField] ?? "")
7984
- }));
8012
+ getValidationErrors() {
8013
+ return this.validationErrors;
8014
+ }
8015
+ getAgentRegistry() {
8016
+ return this.agentRegistry;
7985
8017
  }
7986
8018
  };
7987
8019
 
7988
- // ../server/dist/services/connector-factory.js
7989
- import { join as join4 } from "path";
7990
- function createConnectorFromConfig(connection, options) {
7991
- const { projectDir } = options;
7992
- switch (connection.type) {
7993
- case "duckdb": {
7994
- const duckdbConfig = connection;
7995
- const dbPath = duckdbConfig.config.path.startsWith("/") ? duckdbConfig.config.path : join4(projectDir, duckdbConfig.config.path);
7996
- return new DuckDBConnector({ path: dbPath });
7997
- }
7998
- case "postgres": {
7999
- const pgConnection = connection;
8000
- const credentials = resolvePostgresAuth(pgConnection, options.secrets);
8001
- return new PostgresConnector({
8002
- host: pgConnection.config.host,
8003
- port: pgConnection.config.port,
8004
- database: pgConnection.config.database,
8005
- schema: pgConnection.config.schema,
8006
- ssl: pgConnection.config.ssl,
8007
- user: credentials.user,
8008
- password: credentials.password,
8009
- min: pgConnection.pool?.min_connections,
8010
- max: pgConnection.pool?.max_connections,
8011
- idleTimeoutMillis: pgConnection.pool?.idle_timeout,
8012
- statementTimeout: pgConnection.query?.timeout
8013
- });
8014
- }
8015
- case "mysql": {
8016
- const mysqlConnection = connection;
8017
- const credentials = resolveMySQLAuth(mysqlConnection, options.secrets);
8018
- return new MySQLConnector({
8019
- host: mysqlConnection.config.host,
8020
- port: mysqlConnection.config.port,
8021
- database: mysqlConnection.config.database,
8022
- ssl: mysqlConnection.config.ssl,
8023
- user: credentials.user,
8024
- password: credentials.password,
8025
- min: mysqlConnection.pool?.min_connections,
8026
- max: mysqlConnection.pool?.max_connections,
8027
- idleTimeoutMillis: mysqlConnection.pool?.idle_timeout,
8028
- statementTimeout: mysqlConnection.query?.timeout
8029
- });
8030
- }
8031
- case "sqlite": {
8032
- const sqliteConnection = connection;
8033
- const dbPath = sqliteConnection.config.path.startsWith("/") ? sqliteConnection.config.path : sqliteConnection.config.path === ":memory:" ? ":memory:" : join4(projectDir, sqliteConnection.config.path);
8034
- return new SQLiteConnector({ path: dbPath });
8035
- }
8036
- case "snowflake": {
8037
- const sfConnection = connection;
8038
- const credentials = resolveSnowflakeAuth(sfConnection, options.secrets);
8039
- return new SnowflakeConnector({
8040
- account: sfConnection.config.account,
8041
- username: credentials.username,
8042
- password: credentials.password,
8043
- privateKey: credentials.privateKey,
8044
- authenticator: credentials.authenticator,
8045
- clientStoreTemporaryCredential: credentials.clientStoreTemporaryCredential,
8046
- warehouse: sfConnection.config.warehouse,
8047
- database: sfConnection.config.database,
8048
- schema: sfConnection.config.schema,
8049
- role: sfConnection.config.role,
8050
- statementTimeout: sfConnection.query?.timeout
8051
- });
8052
- }
8053
- case "bigquery": {
8054
- const bqConnection = connection;
8055
- const creds = resolveBigQueryAuth(bqConnection, options.secrets);
8056
- const keyFilename = creds.keyFilename ? creds.keyFilename.startsWith("/") ? creds.keyFilename : join4(projectDir, creds.keyFilename) : void 0;
8057
- return new BigQueryConnector({
8058
- projectId: bqConnection.config.project_id,
8059
- dataset: bqConnection.config.dataset,
8060
- location: bqConnection.config.location,
8061
- maxBytesBilled: bqConnection.config.max_bytes_billed,
8062
- credentials: creds.credentials,
8063
- keyFilename
8064
- });
8065
- }
8066
- default:
8067
- throw new Error(`Unsupported connection type: ${connection.type}`);
8020
+ // ../server/dist/services/runtime-config-store.js
8021
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
8022
+ import { dirname as dirname3, join as join4, isAbsolute } from "path";
8023
+
8024
+ // ../../packages/crypto/dist/index.js
8025
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
8026
+ var ALGO = "aes-256-gcm";
8027
+ function deriveKeyFromEnv(env) {
8028
+ const b64 = env.YAMCHART_CONFIG_SECRET;
8029
+ if (!b64)
8030
+ return null;
8031
+ const key = Buffer.from(b64, "base64");
8032
+ if (key.length !== 32) {
8033
+ throw new Error("YAMCHART_CONFIG_SECRET must be a base64-encoded 32-byte key");
8068
8034
  }
8035
+ return key;
8069
8036
  }
8070
- function buildConnectionInfo(connection) {
8071
- const info2 = {
8072
- name: connection.name,
8073
- type: connection.type
8074
- };
8075
- switch (connection.type) {
8076
- case "duckdb": {
8077
- const c = connection;
8078
- info2.path = c.config.path;
8079
- break;
8080
- }
8081
- case "postgres": {
8082
- const c = connection;
8083
- info2.host = c.config.host;
8084
- info2.port = c.config.port;
8085
- info2.database = c.config.database;
8086
- info2.schema = c.config.schema;
8087
- break;
8088
- }
8089
- case "mysql": {
8090
- const c = connection;
8091
- info2.host = c.config.host;
8092
- info2.port = c.config.port;
8093
- info2.database = c.config.database;
8094
- break;
8095
- }
8096
- case "sqlite": {
8097
- const c = connection;
8098
- info2.path = c.config.path;
8099
- break;
8100
- }
8101
- case "snowflake": {
8102
- const c = connection;
8103
- info2.account = c.config.account;
8104
- info2.warehouse = c.config.warehouse;
8105
- info2.database = c.config.database;
8106
- info2.schema = c.config.schema;
8107
- info2.role = c.config.role;
8108
- break;
8037
+ function encryptSecret(plain, key) {
8038
+ const iv = randomBytes(12);
8039
+ const cipher = createCipheriv(ALGO, key, iv);
8040
+ const ct = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
8041
+ const tag = cipher.getAuthTag();
8042
+ return `${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
8043
+ }
8044
+ function decryptSecret(enc, key) {
8045
+ const parts = enc.split(":");
8046
+ if (parts.length !== 3)
8047
+ throw new Error("malformed ciphertext");
8048
+ const ivB64 = parts[0];
8049
+ const tagB64 = parts[1];
8050
+ const ctB64 = parts[2];
8051
+ const decipher = createDecipheriv(ALGO, key, Buffer.from(ivB64, "base64"));
8052
+ decipher.setAuthTag(Buffer.from(tagB64, "base64"));
8053
+ return Buffer.concat([decipher.update(Buffer.from(ctB64, "base64")), decipher.final()]).toString("utf8");
8054
+ }
8055
+ function maskSecret(plain) {
8056
+ if (plain.length <= 4)
8057
+ return "\u2022\u2022\u2022\u2022";
8058
+ return "\u2022\u2022\u2022\u2022" + plain.slice(-4);
8059
+ }
8060
+
8061
+ // ../server/dist/services/secret-crypto.js
8062
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
8063
+ import { dirname as dirname2, join as join3 } from "path";
8064
+ import { randomBytes as randomBytes2 } from "crypto";
8065
+ function getConfigKey(dataDir, env, log) {
8066
+ const fromEnv = deriveKeyFromEnv(env);
8067
+ if (fromEnv)
8068
+ return fromEnv;
8069
+ const keyPath = join3(dataDir, ".config-secret");
8070
+ if (existsSync(keyPath)) {
8071
+ const key2 = Buffer.from(readFileSync(keyPath, "utf8").trim(), "base64");
8072
+ if (key2.length !== 32) {
8073
+ throw new Error(`Key file at ${keyPath} does not contain a 32-byte key (got ${key2.length} bytes). Delete it to regenerate.`);
8109
8074
  }
8110
- case "bigquery": {
8111
- const c = connection;
8112
- info2.database = c.config.project_id;
8113
- info2.schema = c.config.dataset;
8114
- break;
8075
+ return key2;
8076
+ }
8077
+ mkdirSync(dirname2(keyPath), { recursive: true });
8078
+ const key = randomBytes2(32);
8079
+ writeFileSync(keyPath, key.toString("base64"), { mode: 384 });
8080
+ 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.`);
8081
+ return key;
8082
+ }
8083
+
8084
+ // ../server/dist/services/runtime-config-store.js
8085
+ var RuntimeConfigError = class extends Error {
8086
+ };
8087
+ var SECRET_LEAF_KEYS = /* @__PURE__ */ new Set(["password", "private_key", "credentials_json", "database_url", "api_key"]);
8088
+ function transformSecretLeaves(obj, fn) {
8089
+ if (Array.isArray(obj))
8090
+ return obj.map((v) => transformSecretLeaves(v, fn));
8091
+ if (obj && typeof obj === "object") {
8092
+ const out = {};
8093
+ for (const [k, v] of Object.entries(obj)) {
8094
+ out[k] = SECRET_LEAF_KEYS.has(k) && typeof v === "string" ? fn(v) : transformSecretLeaves(v, fn);
8115
8095
  }
8096
+ return out;
8116
8097
  }
8117
- return info2;
8098
+ return obj;
8118
8099
  }
8119
- async function testConnection(connection, options) {
8120
- const start = performance.now();
8121
- let connector = null;
8122
- try {
8123
- connector = createConnectorFromConfig(connection, options);
8124
- await connector.connect();
8125
- const healthQuery = connection.type === "duckdb" ? "SELECT 1 AS health" : "SELECT 1";
8126
- await connector.execute(healthQuery);
8127
- const latencyMs = performance.now() - start;
8128
- return {
8129
- name: connection.name,
8130
- type: connection.type,
8131
- status: "healthy",
8132
- latencyMs: Math.round(latencyMs)
8133
- };
8134
- } catch (err) {
8135
- const latencyMs = performance.now() - start;
8136
- let errorMessage = "Unknown error";
8137
- if (err instanceof Error) {
8138
- const pgError = err;
8139
- errorMessage = err.message || pgError.code || "Connection failed";
8140
- } else {
8141
- errorMessage = String(err);
8100
+ var RuntimeConfigStore = class {
8101
+ filePath;
8102
+ key;
8103
+ constructor(filePath, key) {
8104
+ this.filePath = filePath;
8105
+ this.key = key;
8106
+ }
8107
+ /** Directory the store (and key file) live in — useful for getConfigKey(). */
8108
+ get dataDir() {
8109
+ return dirname3(this.filePath);
8110
+ }
8111
+ static resolvePath(opts) {
8112
+ if (opts.env.YAMCHART_RUNTIME_CONFIG)
8113
+ return opts.env.YAMCHART_RUNTIME_CONFIG;
8114
+ if (opts.authDbPath && isAbsolute(opts.authDbPath))
8115
+ return join4(dirname3(opts.authDbPath), "runtime-config.json");
8116
+ return join4(opts.projectDir, ".yamchart", "runtime-config.json");
8117
+ }
8118
+ read() {
8119
+ if (!existsSync2(this.filePath))
8120
+ return { version: 1 };
8121
+ let parsed;
8122
+ try {
8123
+ parsed = JSON.parse(readFileSync2(this.filePath, "utf8"));
8124
+ } catch (err) {
8125
+ throw new RuntimeConfigError(`runtime-config.json is not valid JSON: ${err.message}`);
8142
8126
  }
8143
- return {
8144
- name: connection.name,
8145
- type: connection.type,
8146
- status: "unhealthy",
8147
- latencyMs: Math.round(latencyMs),
8148
- error: errorMessage
8149
- };
8150
- } finally {
8151
- if (connector) {
8152
- try {
8153
- await connector.disconnect();
8154
- } catch {
8155
- }
8127
+ let decrypted;
8128
+ try {
8129
+ decrypted = transformSecretLeaves(parsed, (s) => decryptSecret(s, this.key));
8130
+ } catch (err) {
8131
+ throw new RuntimeConfigError(`Could not decrypt runtime-config.json (wrong YAMCHART_CONFIG_SECRET?): ${err.message}`);
8156
8132
  }
8133
+ const result = RuntimeConfigSchema.safeParse(decrypted);
8134
+ if (!result.success)
8135
+ throw new RuntimeConfigError(`Invalid runtime-config.json: ${result.error.message}`);
8136
+ return result.data;
8137
+ }
8138
+ write(config) {
8139
+ const validated = RuntimeConfigSchema.parse(config);
8140
+ const encrypted = transformSecretLeaves(validated, (s) => encryptSecret(s, this.key));
8141
+ mkdirSync2(dirname3(this.filePath), { recursive: true, mode: 448 });
8142
+ writeFileSync2(this.filePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
8143
+ }
8144
+ };
8145
+
8146
+ // ../server/dist/services/restart.js
8147
+ function requestRestart(env, exit = (c) => process.exit(c), schedule = (fn, ms) => {
8148
+ setTimeout(fn, ms).unref?.();
8149
+ }) {
8150
+ if (env.YAMCHART_DEV_CHILD === "1") {
8151
+ schedule(() => exit(75), 150);
8152
+ return { applying: true, restartRequired: false };
8153
+ }
8154
+ return { applying: false, restartRequired: true };
8155
+ }
8156
+
8157
+ // ../server/dist/services/connection-refs.js
8158
+ function findConnectionRefs(connectionName, input) {
8159
+ const refs = [];
8160
+ const resolves = (explicit) => (explicit ?? input.defaultConnection) === connectionName;
8161
+ for (const c of input.charts) {
8162
+ if (resolves(c.source?.connection))
8163
+ refs.push({ kind: "chart", name: c.name });
8164
+ }
8165
+ for (const m of input.models) {
8166
+ if (resolves(m.metadata.connection))
8167
+ refs.push({ kind: "model", name: m.metadata.name });
8157
8168
  }
8169
+ return refs;
8158
8170
  }
8159
8171
 
8160
8172
  // ../server/dist/routes/config.js
@@ -9019,8 +9031,8 @@ async function semanticRoutes(fastify, options) {
9019
9031
  });
9020
9032
  }
9021
9033
  const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = await import("fs");
9022
- const { join: join14, dirname: dirname5 } = await import("path");
9023
- const fullPath = join14(projectDir, filePath);
9034
+ const { join: join15, dirname: dirname5 } = await import("path");
9035
+ const fullPath = join15(projectDir, filePath);
9024
9036
  mkdirSync3(dirname5(fullPath), { recursive: true });
9025
9037
  writeFileSync3(fullPath, sql, "utf-8");
9026
9038
  return { saved: true, path: filePath };
@@ -9133,7 +9145,7 @@ var SlackNotifier = class {
9133
9145
  let response = await fetch(webhookUrl, options);
9134
9146
  if (!response.ok) {
9135
9147
  if (this.retryDelayMs > 0) {
9136
- await new Promise((resolve3) => setTimeout(resolve3, this.retryDelayMs));
9148
+ await new Promise((resolve4) => setTimeout(resolve4, this.retryDelayMs));
9137
9149
  }
9138
9150
  response = await fetch(webhookUrl, options);
9139
9151
  if (!response.ok) {
@@ -9449,6 +9461,8 @@ function rowToBlindspot(r) {
9449
9461
  confidence: r.confidence ?? void 0,
9450
9462
  status: r.status,
9451
9463
  answer: r.answer ?? void 0,
9464
+ prUrl: r.pr_url ?? void 0,
9465
+ resolvedByKnowledge: r.resolved_by_knowledge ?? false,
9452
9466
  createdAt: new Date(r.created_at).toISOString(),
9453
9467
  updatedAt: new Date(r.updated_at).toISOString()
9454
9468
  };
@@ -9476,6 +9490,7 @@ function rowToHunch(r) {
9476
9490
  rationale: r.rationale ?? void 0,
9477
9491
  confidence: r.confidence ?? void 0,
9478
9492
  lastRunSample: r.last_run_sample ?? void 0,
9493
+ autoConfirmed: r.auto_confirmed ?? false,
9479
9494
  createdAt: new Date(r.created_at).toISOString(),
9480
9495
  updatedAt: new Date(r.updated_at).toISOString()
9481
9496
  };
@@ -9595,6 +9610,14 @@ var PostgresKnowledgeStore = class {
9595
9610
  params.push(patch.answer);
9596
9611
  sets.push(`answer = $${params.length}`);
9597
9612
  }
9613
+ if (patch.resolvedByKnowledge !== void 0) {
9614
+ params.push(patch.resolvedByKnowledge);
9615
+ sets.push(`resolved_by_knowledge = $${params.length}`);
9616
+ }
9617
+ if (patch.prUrl !== void 0) {
9618
+ params.push(patch.prUrl);
9619
+ sets.push(`pr_url = $${params.length}`);
9620
+ }
9598
9621
  params.push((/* @__PURE__ */ new Date()).toISOString());
9599
9622
  sets.push(`updated_at = $${params.length}`);
9600
9623
  params.push(id);
@@ -9701,8 +9724,8 @@ var PostgresKnowledgeStore = class {
9701
9724
  ]);
9702
9725
  }
9703
9726
  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`, [
9727
+ await this.pool.query(`INSERT INTO blindspots (id, connection, kind, title, detail, subject, suggested_model, confidence, status, answer, resolved_by_knowledge, pr_url, created_at, updated_at)
9728
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT (id) DO NOTHING`, [
9706
9729
  b.id,
9707
9730
  b.connection,
9708
9731
  b.kind,
@@ -9713,6 +9736,8 @@ var PostgresKnowledgeStore = class {
9713
9736
  b.confidence ?? null,
9714
9737
  b.status,
9715
9738
  b.answer ?? null,
9739
+ b.resolvedByKnowledge ?? false,
9740
+ b.prUrl ?? null,
9716
9741
  b.createdAt,
9717
9742
  b.updatedAt
9718
9743
  ]);
@@ -9743,11 +9768,11 @@ function __rest(s, e) {
9743
9768
  }
9744
9769
  function __awaiter(thisArg, _arguments, P, generator) {
9745
9770
  function adopt(value) {
9746
- return value instanceof P ? value : new P(function(resolve3) {
9747
- resolve3(value);
9771
+ return value instanceof P ? value : new P(function(resolve4) {
9772
+ resolve4(value);
9748
9773
  });
9749
9774
  }
9750
- return new (P || (P = Promise))(function(resolve3, reject) {
9775
+ return new (P || (P = Promise))(function(resolve4, reject) {
9751
9776
  function fulfilled(value) {
9752
9777
  try {
9753
9778
  step(generator.next(value));
@@ -9763,7 +9788,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
9763
9788
  }
9764
9789
  }
9765
9790
  function step(result) {
9766
- result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
9791
+ result.done ? resolve4(result.value) : adopt(result.value).then(fulfilled, rejected);
9767
9792
  }
9768
9793
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9769
9794
  });
@@ -12294,15 +12319,15 @@ var RealtimeChannel = class _RealtimeChannel {
12294
12319
  }
12295
12320
  }
12296
12321
  } else {
12297
- return new Promise((resolve3) => {
12322
+ return new Promise((resolve4) => {
12298
12323
  var _a2, _b2, _c;
12299
12324
  const push = this._push(args.type, args, opts.timeout || this.timeout);
12300
12325
  if (args.type === "broadcast" && !((_c = (_b2 = (_a2 = this.params) === null || _a2 === void 0 ? void 0 : _a2.config) === null || _b2 === void 0 ? void 0 : _b2.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
12301
- resolve3("ok");
12326
+ resolve4("ok");
12302
12327
  }
12303
- push.receive("ok", () => resolve3("ok"));
12304
- push.receive("error", () => resolve3("error"));
12305
- push.receive("timeout", () => resolve3("timed out"));
12328
+ push.receive("ok", () => resolve4("ok"));
12329
+ push.receive("error", () => resolve4("error"));
12330
+ push.receive("timeout", () => resolve4("timed out"));
12306
12331
  });
12307
12332
  }
12308
12333
  }
@@ -12330,16 +12355,16 @@ var RealtimeChannel = class _RealtimeChannel {
12330
12355
  };
12331
12356
  this.joinPush.destroy();
12332
12357
  let leavePush = null;
12333
- return new Promise((resolve3) => {
12358
+ return new Promise((resolve4) => {
12334
12359
  leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
12335
12360
  leavePush.receive("ok", () => {
12336
12361
  onClose();
12337
- resolve3("ok");
12362
+ resolve4("ok");
12338
12363
  }).receive("timeout", () => {
12339
12364
  onClose();
12340
- resolve3("timed out");
12365
+ resolve4("timed out");
12341
12366
  }).receive("error", () => {
12342
- resolve3("error");
12367
+ resolve4("error");
12343
12368
  });
12344
12369
  leavePush.send();
12345
12370
  if (!this._canPush()) {
@@ -12418,8 +12443,8 @@ var RealtimeChannel = class _RealtimeChannel {
12418
12443
  _trigger(type, payload, ref) {
12419
12444
  var _a, _b;
12420
12445
  const typeLower = type.toLocaleLowerCase();
12421
- const { close, error: error2, leave, join: join14 } = CHANNEL_EVENTS;
12422
- const events = [close, error2, leave, join14];
12446
+ const { close, error: error2, leave, join: join15 } = CHANNEL_EVENTS;
12447
+ const events = [close, error2, leave, join15];
12423
12448
  if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
12424
12449
  return;
12425
12450
  }
@@ -14035,7 +14060,7 @@ var _getRequestParams = (method, options, parameters, body) => {
14035
14060
  return _objectSpread2(_objectSpread2({}, params), parameters);
14036
14061
  };
14037
14062
  async function _handleRequest(fetcher, method, url, options, parameters, body, namespace) {
14038
- return new Promise((resolve3, reject) => {
14063
+ return new Promise((resolve4, reject) => {
14039
14064
  fetcher(url, _getRequestParams(method, options, parameters, body)).then((result) => {
14040
14065
  if (!result.ok) throw result;
14041
14066
  if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result;
@@ -14045,7 +14070,7 @@ async function _handleRequest(fetcher, method, url, options, parameters, body, n
14045
14070
  if (!contentType || !contentType.includes("application/json")) return {};
14046
14071
  }
14047
14072
  return result.json();
14048
- }).then((data) => resolve3(data)).catch((error2) => handleError(error2, reject, options, namespace));
14073
+ }).then((data) => resolve4(data)).catch((error2) => handleError(error2, reject, options, namespace));
14049
14074
  });
14050
14075
  }
14051
14076
  function createFetchApi(namespace = "storage") {
@@ -22642,7 +22667,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
22642
22667
  });
22643
22668
  if (!chunk) {
22644
22669
  if (i === 1) {
22645
- await new Promise((resolve3) => setTimeout(resolve3));
22670
+ await new Promise((resolve4) => setTimeout(resolve4));
22646
22671
  maxReadCount = 3;
22647
22672
  continue;
22648
22673
  }
@@ -24676,9 +24701,9 @@ data:
24676
24701
  const initRequest = messages.find((m) => isInitializeRequest(m));
24677
24702
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
24678
24703
  if (this._enableJsonResponse) {
24679
- return new Promise((resolve3) => {
24704
+ return new Promise((resolve4) => {
24680
24705
  this._streamMapping.set(streamId, {
24681
- resolveJson: resolve3,
24706
+ resolveJson: resolve4,
24682
24707
  cleanup: () => {
24683
24708
  this._streamMapping.delete(streamId);
24684
24709
  }
@@ -26995,7 +27020,7 @@ var Protocol = class {
26995
27020
  return;
26996
27021
  }
26997
27022
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
26998
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
27023
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
26999
27024
  options?.signal?.throwIfAborted();
27000
27025
  }
27001
27026
  } catch (error2) {
@@ -27012,7 +27037,7 @@ var Protocol = class {
27012
27037
  */
27013
27038
  request(request, resultSchema, options) {
27014
27039
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
27015
- return new Promise((resolve3, reject) => {
27040
+ return new Promise((resolve4, reject) => {
27016
27041
  const earlyReject = (error2) => {
27017
27042
  reject(error2);
27018
27043
  };
@@ -27090,7 +27115,7 @@ var Protocol = class {
27090
27115
  if (!parseResult.success) {
27091
27116
  reject(parseResult.error);
27092
27117
  } else {
27093
- resolve3(parseResult.data);
27118
+ resolve4(parseResult.data);
27094
27119
  }
27095
27120
  } catch (error2) {
27096
27121
  reject(error2);
@@ -27351,12 +27376,12 @@ var Protocol = class {
27351
27376
  }
27352
27377
  } catch {
27353
27378
  }
27354
- return new Promise((resolve3, reject) => {
27379
+ return new Promise((resolve4, reject) => {
27355
27380
  if (signal.aborted) {
27356
27381
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
27357
27382
  return;
27358
27383
  }
27359
- const timeoutId = setTimeout(resolve3, interval);
27384
+ const timeoutId = setTimeout(resolve4, interval);
27360
27385
  signal.addEventListener("abort", () => {
27361
27386
  clearTimeout(timeoutId);
27362
27387
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -28457,7 +28482,7 @@ var McpServer = class {
28457
28482
  let task = createTaskResult.task;
28458
28483
  const pollInterval = task.pollInterval ?? 5e3;
28459
28484
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
28460
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
28485
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
28461
28486
  const updatedTask = await extra.taskStore.getTask(taskId);
28462
28487
  if (!updatedTask) {
28463
28488
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -29032,13 +29057,20 @@ import { z as z2 } from "zod";
29032
29057
  async function listCharts(deps) {
29033
29058
  const charts = deps.configLoader.getCharts().map((c) => {
29034
29059
  const chart = c;
29060
+ const model = chart.source?.model ? deps.configLoader.getModelByName(chart.source.model) : void 0;
29061
+ const modelParameters = model?.metadata?.params ?? [];
29062
+ const parameterDetails = [
29063
+ ...modelParameters.map((p) => ({ ...p, source: "model" })),
29064
+ ...(chart.parameters ?? []).map((p) => ({ ...p, source: "chart" }))
29065
+ ];
29035
29066
  return {
29036
29067
  name: chart.name,
29037
29068
  title: chart.title,
29038
29069
  ...chart.description ? { description: chart.description } : {},
29039
29070
  type: chart.chart?.type,
29040
29071
  model: chart.source?.model,
29041
- parameters: (chart.parameters ?? []).map((p) => p.name)
29072
+ parameters: (chart.parameters ?? []).map((p) => p.name),
29073
+ parameter_details: parameterDetails
29042
29074
  };
29043
29075
  });
29044
29076
  return JSON.stringify(charts);
@@ -29083,10 +29115,91 @@ async function runSql(deps, args) {
29083
29115
  async function runModel(deps, args) {
29084
29116
  const chart = deps.configLoader.getChartByName(args.name);
29085
29117
  if (!chart) {
29118
+ const model = deps.configLoader.getModelByName(args.name);
29119
+ if (model) {
29120
+ const chartsForModel = deps.configLoader.getCharts().filter((c) => c.source?.model === args.name).map((c) => c.name);
29121
+ return [
29122
+ `Error: "${args.name}" is a model, but run_model currently executes saved charts by chart name.`,
29123
+ chartsForModel.length ? `Use one of these chart names for that model: ${chartsForModel.join(", ")}.` : "Create a chart for this model or use run_sql to query the model SQL directly."
29124
+ ].join(" ");
29125
+ }
29086
29126
  return `Error: Chart "${args.name}" not found. Use list_charts to see available charts.`;
29087
29127
  }
29088
- const result = await deps.queryService.executeChart(chart, args.params ?? {}, buildUserContext(deps.user));
29089
- return JSON.stringify({ columns: result.columns, rows: result.rows });
29128
+ const normalized = normalizeChartParams(deps, chart, args.params ?? {});
29129
+ if (normalized.error)
29130
+ return `Error: ${JSON.stringify(normalized.error)}`;
29131
+ try {
29132
+ const result = await deps.queryService.executeChart(chart, normalized.params, buildUserContext(deps.user));
29133
+ return JSON.stringify({ columns: result.columns, rows: result.rows });
29134
+ } catch (err) {
29135
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
29136
+ }
29137
+ }
29138
+ function normalizeChartParams(deps, chart, input) {
29139
+ const definitions = /* @__PURE__ */ new Map();
29140
+ const modelName = chart.source?.model;
29141
+ const model = modelName ? deps.configLoader.getModelByName(modelName) : void 0;
29142
+ for (const param of model?.metadata?.params ?? []) {
29143
+ definitions.set(param.name, param);
29144
+ }
29145
+ for (const param of chart.parameters ?? []) {
29146
+ definitions.set(param.name, param);
29147
+ }
29148
+ const params = { ...input };
29149
+ for (const [name, value] of Object.entries(input)) {
29150
+ const definition = definitions.get(name);
29151
+ if (!definition)
29152
+ continue;
29153
+ const type = definition.type;
29154
+ if (type === "multi_select" || type === "string[]") {
29155
+ if (isAllValue(value)) {
29156
+ params[name] = [];
29157
+ } else if (!Array.isArray(value)) {
29158
+ params[name] = [String(value)];
29159
+ } else {
29160
+ params[name] = value.map((v) => String(v));
29161
+ }
29162
+ continue;
29163
+ }
29164
+ if (type === "number[]") {
29165
+ const values = Array.isArray(value) ? value : [value];
29166
+ const nums = values.map((v) => Number(v));
29167
+ if (nums.some((v) => !Number.isFinite(v))) {
29168
+ return {
29169
+ params,
29170
+ error: {
29171
+ error_type: "invalid_parameter",
29172
+ parameter: name,
29173
+ expected: "array<number>",
29174
+ received: Array.isArray(value) ? "array" : typeof value
29175
+ }
29176
+ };
29177
+ }
29178
+ params[name] = nums;
29179
+ continue;
29180
+ }
29181
+ if (type === "number" && typeof value === "string") {
29182
+ const num2 = Number(value);
29183
+ if (!Number.isFinite(num2)) {
29184
+ return {
29185
+ params,
29186
+ error: {
29187
+ error_type: "invalid_parameter",
29188
+ parameter: name,
29189
+ expected: "number",
29190
+ received: "string"
29191
+ }
29192
+ };
29193
+ }
29194
+ params[name] = num2;
29195
+ }
29196
+ }
29197
+ return { params };
29198
+ }
29199
+ function isAllValue(value) {
29200
+ if (typeof value === "string")
29201
+ return value.trim().toLowerCase() === "all";
29202
+ return Array.isArray(value) && value.length === 1 && isAllValue(value[0]);
29090
29203
  }
29091
29204
 
29092
29205
  // ../../packages/mcp-core/dist/catalog.js
@@ -29124,22 +29237,38 @@ function includeFilter(deps) {
29124
29237
  }
29125
29238
  async function listTables(deps) {
29126
29239
  const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
29127
- return JSON.stringify(tables.map((t) => ({
29128
- name: t.name,
29129
- ...t.schema ? { schema: t.schema } : {},
29130
- connection: t.connection,
29131
- columnCount: t.columns.length
29132
- })));
29240
+ if (tables.length === 0) {
29241
+ const live = await discoverLiveTables(deps);
29242
+ return JSON.stringify({
29243
+ tables: live.tables.map(tableSummary),
29244
+ ...live.warning ? { warning: live.warning } : {}
29245
+ });
29246
+ }
29247
+ return JSON.stringify({ tables: tables.map(tableSummary) });
29133
29248
  }
29134
29249
  async function describeTable(deps, args) {
29135
- const normalizedTable = args.schema != null && !args.table.includes(".") ? `${args.schema}.${args.table}` : args.table;
29136
- const parts = normalizedTable.split(".");
29250
+ const identifier = parseTableIdentifier(args.table, args.schema);
29251
+ if (!identifier.valid)
29252
+ return `Error: ${identifier.error}`;
29253
+ const normalizedTable = identifier.normalized;
29254
+ const parts = identifier.parts;
29137
29255
  const tableName = parts[parts.length - 1] ?? normalizedTable;
29138
29256
  const schema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
29139
29257
  const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
29140
- const match = tables.find((t) => t.name === tableName && (schema === void 0 || t.schema === schema));
29258
+ const match = tables.find((t) => t.name === tableName && (schema === void 0 || schemaMatches(t.schema, schema)));
29141
29259
  if (!match) {
29142
- return `Error: Table "${args.table}" not found in catalog. Use list_tables to see available tables.`;
29260
+ try {
29261
+ const result = await deps.queryService.executeRawSql(`SELECT * FROM ${identifier.sql} LIMIT 0`);
29262
+ return JSON.stringify({
29263
+ name: tableName,
29264
+ ...schema ? { schema } : {},
29265
+ columns: result.columns,
29266
+ warning: "Table was not found in the cached catalog; columns were discovered with a live LIMIT 0 query."
29267
+ });
29268
+ } catch (err) {
29269
+ const detail2 = err instanceof Error ? err.message : String(err);
29270
+ return `Error: Table "${args.table}" not found in catalog, and live discovery failed: ${detail2}`;
29271
+ }
29143
29272
  }
29144
29273
  return JSON.stringify({
29145
29274
  name: match.name,
@@ -29147,24 +29276,111 @@ async function describeTable(deps, args) {
29147
29276
  columns: match.columns
29148
29277
  });
29149
29278
  }
29150
- var TABLE_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
29151
29279
  async function sampleTable(deps, args) {
29152
- if (!TABLE_IDENT.test(args.table)) {
29153
- return `Error: Invalid table identifier "${args.table}".`;
29154
- }
29280
+ const identifier = parseTableIdentifier(args.table, args.schema);
29281
+ if (!identifier.valid)
29282
+ return `Error: ${identifier.error}`;
29155
29283
  const limit2 = Math.min(Math.max(args.limit ?? 5, 1), 100);
29156
- const parts = args.table.split(".");
29284
+ const parts = identifier.parts;
29157
29285
  const tableName = parts[parts.length - 1] ?? args.table;
29158
29286
  const providedSchema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
29159
29287
  const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
29160
- const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || t.schema === providedSchema));
29161
- const from = providedSchema === void 0 && match?.schema ? `${match.schema}.${args.table}` : args.table;
29288
+ const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || schemaMatches(t.schema, providedSchema)));
29289
+ const from = providedSchema === void 0 && match?.schema ? sqlIdentifier(`${match.schema}.${args.table}`) : identifier.sql;
29162
29290
  const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit2}`);
29163
29291
  return JSON.stringify({ columns: result.columns, rows: result.rows });
29164
29292
  }
29293
+ function tableSummary(t) {
29294
+ return {
29295
+ name: t.name,
29296
+ ...t.schema ? { schema: t.schema } : {},
29297
+ connection: t.connection,
29298
+ columnCount: t.columns.length
29299
+ };
29300
+ }
29301
+ function schemaMatches(catalogSchema, requestedSchema) {
29302
+ if (catalogSchema === requestedSchema)
29303
+ return true;
29304
+ return catalogSchema?.endsWith(`.${requestedSchema}`) ?? false;
29305
+ }
29306
+ function parseTableIdentifier(table, schema) {
29307
+ const normalizedInput = table.trim().replace(/^`(.+)`$/, "$1");
29308
+ const normalized = schema != null && !normalizedInput.includes(".") ? `${schema}.${normalizedInput}` : normalizedInput;
29309
+ const parts = normalized.split(".");
29310
+ if (parts.length === 0 || parts.length > 3 || parts.some((p) => !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(p))) {
29311
+ return {
29312
+ valid: false,
29313
+ error: `Invalid table identifier "${table}". Use table, schema.table, project.schema.table, or a backticked fully qualified name.`
29314
+ };
29315
+ }
29316
+ return { valid: true, normalized, parts, sql: sqlIdentifier(normalized) };
29317
+ }
29318
+ function sqlIdentifier(identifier) {
29319
+ const needsQuoting = identifier.split(".").some((p) => p.includes("-"));
29320
+ return needsQuoting ? `\`${identifier}\`` : identifier;
29321
+ }
29322
+ async function discoverLiveTables(deps) {
29323
+ const include = includeFilter(deps);
29324
+ const connectionType = deps.connectionType?.toLowerCase();
29325
+ try {
29326
+ if (connectionType === "bigquery") {
29327
+ const connection2 = deps.configLoader.getDefaultConnection?.();
29328
+ const config = connection2?.type === "bigquery" ? connection2.config : void 0;
29329
+ const projectId = config?.project_id;
29330
+ if (!projectId) {
29331
+ return {
29332
+ tables: [],
29333
+ warning: "Cached catalog is empty. Live BigQuery discovery needs a default BigQuery connection with config.project_id."
29334
+ };
29335
+ }
29336
+ const region = bigQueryRegion(config.location);
29337
+ const schemas = include?.schemas ?? [];
29338
+ const schemaFilter = schemas.length ? ` AND table_schema IN (${schemas.map(sqlStringLiteral).join(", ")})` : "";
29339
+ const result2 = await deps.queryService.executeRawSql(`SELECT table_schema AS schema, table_name AS name FROM \`${region}\`.INFORMATION_SCHEMA.TABLES WHERE table_catalog = ${sqlStringLiteral(projectId)} AND table_type = 'BASE TABLE'${schemaFilter} ORDER BY table_schema, table_name LIMIT 1000`);
29340
+ return {
29341
+ tables: rowsToTables(result2.rows, connection2?.name ?? "default"),
29342
+ warning: "Cached catalog is empty; returned live BigQuery region-level INFORMATION_SCHEMA results."
29343
+ };
29344
+ }
29345
+ const connection = deps.configLoader.getDefaultConnection?.();
29346
+ if (connection?.type === "postgres" && connection.config.schema) {
29347
+ const result2 = await deps.queryService.executeRawSql(`SELECT table_schema AS schema, table_name AS name FROM information_schema.tables WHERE table_type IN ('BASE TABLE', 'VIEW') AND table_schema = ${sqlStringLiteral(connection.config.schema)} LIMIT 1000`);
29348
+ return {
29349
+ tables: rowsToTables(result2.rows, connection.name),
29350
+ warning: "Cached catalog is empty; returned live INFORMATION_SCHEMA results."
29351
+ };
29352
+ }
29353
+ const result = await deps.queryService.executeRawSql("SELECT table_schema AS schema, table_name AS name FROM information_schema.tables WHERE table_type IN ('BASE TABLE', 'VIEW') LIMIT 1000");
29354
+ return {
29355
+ tables: rowsToTables(result.rows, "default"),
29356
+ warning: "Cached catalog is empty; returned live INFORMATION_SCHEMA results."
29357
+ };
29358
+ } catch (err) {
29359
+ const detail2 = err instanceof Error ? err.message : String(err);
29360
+ return {
29361
+ tables: [],
29362
+ warning: `Cached catalog is empty and live INFORMATION_SCHEMA discovery failed: ${detail2}`
29363
+ };
29364
+ }
29365
+ }
29366
+ function rowsToTables(rows, connection) {
29367
+ return rows.map((r) => ({
29368
+ name: String(r["name"] ?? r["table_name"] ?? ""),
29369
+ schema: String(r["schema"] ?? r["table_schema"] ?? ""),
29370
+ connection,
29371
+ columns: []
29372
+ })).filter((t) => t.name.length > 0);
29373
+ }
29374
+ function sqlStringLiteral(value) {
29375
+ return `'${value.replace(/'/g, "''")}'`;
29376
+ }
29377
+ function bigQueryRegion(location) {
29378
+ const loc = (location ?? "US").trim().toLowerCase().replace(/_/g, "-");
29379
+ return loc.startsWith("region-") ? loc : `region-${loc}`;
29380
+ }
29165
29381
 
29166
29382
  // ../../packages/mcp-core/dist/render/optionBuilder.js
29167
- var SUPPORTED = /* @__PURE__ */ new Set(["line", "bar", "area", "pie", "donut", "scatter"]);
29383
+ var SUPPORTED = /* @__PURE__ */ new Set(["line", "bar", "area", "pie", "donut", "scatter", "kpi", "table"]);
29168
29384
  function num(v) {
29169
29385
  if (v === null || v === void 0 || v === "")
29170
29386
  return null;
@@ -29184,6 +29400,64 @@ function buildEChartsOption(chart, rows) {
29184
29400
  left: "center"
29185
29401
  };
29186
29402
  const grid = { left: "3%", right: "4%", bottom: "8%", top: 60, containLabel: true };
29403
+ if (type === "kpi") {
29404
+ const valueField = block.value?.field ?? "";
29405
+ const value = rows[0]?.[valueField];
29406
+ return {
29407
+ title,
29408
+ graphic: [
29409
+ {
29410
+ type: "text",
29411
+ left: "center",
29412
+ top: "42%",
29413
+ style: {
29414
+ text: value === null || value === void 0 ? "No data" : String(value),
29415
+ fontSize: 56,
29416
+ fontWeight: 700,
29417
+ fill: "#111827",
29418
+ textAlign: "center"
29419
+ }
29420
+ },
29421
+ {
29422
+ type: "text",
29423
+ left: "center",
29424
+ top: "58%",
29425
+ style: {
29426
+ text: block.value?.label ?? valueField,
29427
+ fontSize: 18,
29428
+ fill: "#6b7280",
29429
+ textAlign: "center"
29430
+ }
29431
+ }
29432
+ ]
29433
+ };
29434
+ }
29435
+ if (type === "table") {
29436
+ const columns = block.columns?.length ? block.columns : Object.keys(rows[0] ?? {}).map((field) => ({ field, label: field }));
29437
+ return {
29438
+ title,
29439
+ tooltip: { trigger: "item" },
29440
+ grid,
29441
+ xAxis: {
29442
+ type: "category",
29443
+ data: columns.map((c) => c.label ?? c.field),
29444
+ axisLabel: { rotate: 30 }
29445
+ },
29446
+ yAxis: { type: "category", data: rows.slice(0, 10).map((_, i) => `Row ${i + 1}`) },
29447
+ series: [
29448
+ {
29449
+ type: "scatter",
29450
+ symbolSize: 1,
29451
+ label: {
29452
+ show: true,
29453
+ formatter: "{@[2]}",
29454
+ color: "#111827"
29455
+ },
29456
+ data: rows.slice(0, 10).flatMap((row, rowIndex) => columns.map((column, colIndex) => [colIndex, rowIndex, row[column.field] ?? ""]))
29457
+ }
29458
+ ]
29459
+ };
29460
+ }
29187
29461
  if (type === "pie" || type === "donut") {
29188
29462
  return {
29189
29463
  title,
@@ -29208,6 +29482,47 @@ function buildEChartsOption(chart, rows) {
29208
29482
  };
29209
29483
  }
29210
29484
  const echartType = type === "bar" ? "bar" : "line";
29485
+ if (block.series?.columns?.length) {
29486
+ return {
29487
+ title,
29488
+ legend: { bottom: 0 },
29489
+ grid,
29490
+ tooltip: { trigger: "axis" },
29491
+ xAxis: { type: "category", data: rows.map((r) => String(r[xField] ?? "")), name: block.x?.label ?? xField },
29492
+ yAxis: { type: "value", name: block.y?.label ?? block.series.columns.map((c) => c.name ?? c.field).join(", ") },
29493
+ series: block.series.columns.map((column) => {
29494
+ const seriesType = column.type === "bar" ? "bar" : "line";
29495
+ return {
29496
+ name: column.name ?? column.field,
29497
+ type: type === "bar" ? "bar" : seriesType,
29498
+ data: rows.map((r) => num(r[column.field])),
29499
+ ...type === "area" || column.type === "area" ? { areaStyle: {} } : {}
29500
+ };
29501
+ })
29502
+ };
29503
+ }
29504
+ if (block.series?.field) {
29505
+ const seriesField = block.series.field;
29506
+ const groups = [...new Set(rows.map((r) => String(r[seriesField] ?? "")))];
29507
+ const xValues = [...new Set(rows.map((r) => String(r[xField] ?? "")))];
29508
+ return {
29509
+ title,
29510
+ legend: { bottom: 0 },
29511
+ grid,
29512
+ tooltip: { trigger: "axis" },
29513
+ xAxis: { type: "category", data: xValues, name: block.x?.label ?? xField },
29514
+ yAxis: { type: "value", name: block.y?.label ?? yField },
29515
+ series: groups.map((group) => ({
29516
+ name: group,
29517
+ type: echartType,
29518
+ data: xValues.map((x) => {
29519
+ const row = rows.find((r) => String(r[xField] ?? "") === x && String(r[seriesField] ?? "") === group);
29520
+ return row ? num(row[yField]) : null;
29521
+ }),
29522
+ ...type === "area" ? { areaStyle: {} } : {}
29523
+ }))
29524
+ };
29525
+ }
29211
29526
  return {
29212
29527
  title,
29213
29528
  grid,
@@ -29420,6 +29735,18 @@ var EXAMPLES = {
29420
29735
  y: { field: "invoiced_revenue", type: "quantitative", format: "$,.0f" }
29421
29736
  }
29422
29737
  },
29738
+ bar_multi_series_long: {
29739
+ name: "jobs_by_month",
29740
+ title: "Jobs by Month",
29741
+ source: { model: "jobs_by_month" },
29742
+ chart: {
29743
+ type: "bar",
29744
+ x: { field: "month", type: "temporal" },
29745
+ y: { field: "job_count", type: "quantitative", label: "Jobs" },
29746
+ series: { field: "job_type" },
29747
+ legend: { show: true, position: "bottom" }
29748
+ }
29749
+ },
29423
29750
  line: {
29424
29751
  name: "revenue_trend",
29425
29752
  title: "Revenue Trend",
@@ -29430,6 +29757,22 @@ var EXAMPLES = {
29430
29757
  y: { field: "invoiced_revenue", type: "quantitative" }
29431
29758
  }
29432
29759
  },
29760
+ line_multi_series_wide: {
29761
+ name: "jobs_completed_vs_scheduled",
29762
+ title: "Completed vs Scheduled Jobs",
29763
+ source: { model: "jobs_by_month" },
29764
+ chart: {
29765
+ type: "line",
29766
+ x: { field: "month", type: "temporal" },
29767
+ series: {
29768
+ columns: [
29769
+ { field: "completed_jobs", name: "Completed" },
29770
+ { field: "scheduled_jobs", name: "Scheduled", style: "dashed" }
29771
+ ]
29772
+ },
29773
+ legend: { show: true, position: "bottom" }
29774
+ }
29775
+ },
29433
29776
  area: {
29434
29777
  name: "revenue_area",
29435
29778
  title: "Revenue Area",
@@ -29486,7 +29829,7 @@ var EXAMPLES = {
29486
29829
  function getChartSchema(args) {
29487
29830
  const type = args.type != null && EXAMPLES[args.type] != null ? args.type : "bar";
29488
29831
  return JSON.stringify({
29489
- note: "Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }. source is { model } or { sql }.",
29832
+ note: "Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }. source is { model } or { sql }. For long-format multi-series charts use chart.series.field; for wide-format multi-series charts use chart.series.columns. chart.color is a field-name string or conditional color config, not an axis object.",
29490
29833
  available_examples: Object.keys(EXAMPLES),
29491
29834
  example: EXAMPLES[type]
29492
29835
  }, null, 2);
@@ -29776,16 +30119,52 @@ ${d.content}` });
29776
30119
  // ../../packages/mcp-core/dist/tools/answer.js
29777
30120
  async function answerQuestionTool(deps, args) {
29778
30121
  const connection = args.connection ?? deps.defaultConnectionName ?? "default";
29779
- const result = await answerQuestion({
29780
- question: args.question,
29781
- connection,
29782
- store: deps.knowledgeStore,
29783
- embedder: deps.embedder,
29784
- provider: deps.provider,
29785
- executeRawSql: (sql) => deps.queryService.executeRawSql(sql),
29786
- connectionType: deps.connectionType
29787
- });
29788
- return JSON.stringify(result, null, 2);
30122
+ try {
30123
+ const result = await answerQuestion({
30124
+ question: args.question,
30125
+ connection,
30126
+ store: deps.knowledgeStore,
30127
+ embedder: deps.embedder,
30128
+ provider: deps.provider,
30129
+ executeRawSql: (sql) => deps.queryService.executeRawSql(sql),
30130
+ connectionType: deps.connectionType
30131
+ });
30132
+ return JSON.stringify(result, null, 2);
30133
+ } catch (err) {
30134
+ const message = err instanceof Error ? err.message : String(err);
30135
+ const lower = message.toLowerCase();
30136
+ const isEmbeddingProvider = lower.includes("embedding") || lower.includes("embeddings") || lower.includes("generativelanguage.googleapis.com") || lower.includes("premature close");
30137
+ return JSON.stringify({
30138
+ error_type: isEmbeddingProvider ? "embedding_provider_unavailable" : "answer_question_failed",
30139
+ retryable: isEmbeddingProvider,
30140
+ fallback_available: true,
30141
+ fallback_attempted: false,
30142
+ ...suggestFallback(deps, args.question),
30143
+ message: isEmbeddingProvider ? "Natural-language retrieval failed, but governed metric, model, chart, and SQL tools are available." : "Natural-language answering failed, but governed metric, model, chart, and SQL tools are available.",
30144
+ detail: message
30145
+ }, null, 2);
30146
+ }
30147
+ }
30148
+ function suggestFallback(deps, question) {
30149
+ const charts = deps.configLoader?.getCharts?.() ?? [];
30150
+ const models = deps.configLoader?.getModels?.() ?? [];
30151
+ const terms = question.toLowerCase().split(/[^a-z0-9_]+/).filter((t) => t.length >= 3);
30152
+ const score = (text2) => terms.reduce((total, term) => total + (text2.includes(term) ? 1 : 0), 0);
30153
+ const rankedChart = charts.map((chart) => ({
30154
+ chart,
30155
+ score: score(`${chart.name} ${chart.title ?? ""} ${chart.source?.model ?? ""}`.toLowerCase())
30156
+ })).sort((a, b) => b.score - a.score)[0];
30157
+ if (rankedChart && rankedChart.score > 0) {
30158
+ return { suggested_tool: "run_model", suggested_args: { name: rankedChart.chart.name } };
30159
+ }
30160
+ const rankedModel = models.map((model) => ({
30161
+ model,
30162
+ score: score(`${model.metadata.name} ${model.metadata.description ?? ""}`.toLowerCase())
30163
+ })).sort((a, b) => b.score - a.score)[0];
30164
+ if (rankedModel && rankedModel.score > 0) {
30165
+ return { suggested_tool: "run_sql", suggested_args: { sql: rankedModel.model.sql } };
30166
+ }
30167
+ return {};
29789
30168
  }
29790
30169
 
29791
30170
  // ../../packages/mcp-core/dist/render/embedResource.js
@@ -29878,17 +30257,30 @@ var text = (s) => ({
29878
30257
  content: [{ type: "text", text: s }],
29879
30258
  ...s.startsWith("Error:") ? { isError: true } : {}
29880
30259
  });
30260
+ var textOut = (s) => ({
30261
+ content: [{ type: "text", text: s }],
30262
+ structuredContent: { result: s },
30263
+ ...s.startsWith("Error:") ? { isError: true } : {}
30264
+ });
30265
+ var TABULAR_OUTPUT = {
30266
+ rowCount: z2.number(),
30267
+ columns: z2.array(z2.string()),
30268
+ rows: z2.array(z2.record(z2.unknown())),
30269
+ preview: z2.array(z2.record(z2.unknown()))
30270
+ };
30271
+ var TEXT_OUTPUT = { result: z2.string() };
29881
30272
  var READ_ONLY = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
29882
30273
  var WRITE_SAFE = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
29883
30274
  function buildResourceMeta(baseUrl) {
30275
+ const RESOURCE_DOMAINS = ["https://cdn.jsdelivr.net", "https://persistent.oaistatic.com"];
30276
+ const csp = { connectDomains: [baseUrl], resourceDomains: RESOURCE_DOMAINS };
29884
30277
  return {
29885
- ui: {
29886
- domain: baseUrl,
29887
- csp: {
29888
- connectDomains: [baseUrl],
29889
- resourceDomains: ["https://persistent.oaistatic.com"]
29890
- }
29891
- }
30278
+ // OpenAI Apps SDK (ChatGPT submission) — clears "Widget CSP / domain not set".
30279
+ "openai/widgetCSP": { connect_domains: [baseUrl], resource_domains: RESOURCE_DOMAINS },
30280
+ "openai/widgetDomain": baseUrl,
30281
+ "openai/widgetDescription": "Interactive yamchart chart renderer",
30282
+ // Standard MCP-UI camelCase form (kept for forward-compat).
30283
+ ui: { domain: baseUrl, csp }
29892
30284
  };
29893
30285
  }
29894
30286
  function createMcpServer(deps) {
@@ -29900,11 +30292,21 @@ function createMcpServer(deps) {
29900
30292
  _meta: buildResourceMeta(baseUrl)
29901
30293
  }, async () => {
29902
30294
  const tpl = await buildTemplateResource();
29903
- return { contents: [{ uri: TEMPLATE_URI, mimeType: tpl.resource.mimeType, text: tpl.resource.text ?? "" }] };
30295
+ return {
30296
+ contents: [
30297
+ {
30298
+ uri: TEMPLATE_URI,
30299
+ mimeType: tpl.resource.mimeType,
30300
+ text: tpl.resource.text ?? "",
30301
+ _meta: buildResourceMeta(baseUrl)
30302
+ }
30303
+ ]
30304
+ };
29904
30305
  });
29905
30306
  server.registerTool("list_charts", {
29906
30307
  description: "List governed yamchart charts (name, title, type, model, parameters).",
29907
30308
  inputSchema: {},
30309
+ outputSchema: { charts: z2.array(z2.unknown()) },
29908
30310
  annotations: READ_ONLY
29909
30311
  }, async () => {
29910
30312
  const raw = await listCharts(deps);
@@ -29917,6 +30319,7 @@ function createMcpServer(deps) {
29917
30319
  server.registerTool("list_models", {
29918
30320
  description: "List governed SQL models (name, description).",
29919
30321
  inputSchema: {},
30322
+ outputSchema: { models: z2.array(z2.unknown()) },
29920
30323
  annotations: READ_ONLY
29921
30324
  }, async () => {
29922
30325
  const raw = await listModels(deps);
@@ -29927,8 +30330,9 @@ function createMcpServer(deps) {
29927
30330
  };
29928
30331
  });
29929
30332
  server.registerTool("run_model", {
29930
- description: "Run a governed chart/model by name with parameters and return its data.",
30333
+ description: "Execute a saved governed chart by its chart name (from list_charts) with parameters and return its data. Note: this runs CHARTS, not models \u2014 passing a model name returns an actionable error listing the chart names defined for that model.",
29931
30334
  inputSchema: { name: z2.string(), params: z2.record(z2.unknown()).optional() },
30335
+ outputSchema: TABULAR_OUTPUT,
29932
30336
  annotations: READ_ONLY
29933
30337
  }, async (args) => {
29934
30338
  const raw = await runModel(deps, args);
@@ -29940,6 +30344,7 @@ function createMcpServer(deps) {
29940
30344
  structuredContent: {
29941
30345
  rowCount: parsed.rows.length,
29942
30346
  columns: parsed.columns.map((c) => c.name),
30347
+ rows: parsed.rows,
29943
30348
  preview: parsed.rows.slice(0, 5)
29944
30349
  }
29945
30350
  };
@@ -29947,6 +30352,7 @@ function createMcpServer(deps) {
29947
30352
  server.registerTool("run_sql", {
29948
30353
  description: "Run a read-only ad-hoc SQL query (destructive statements blocked, row-capped).",
29949
30354
  inputSchema: { sql: z2.string(), limit: z2.number().optional() },
30355
+ outputSchema: TABULAR_OUTPUT,
29950
30356
  annotations: READ_ONLY
29951
30357
  }, async (args) => {
29952
30358
  const raw = await runSql(deps, args);
@@ -29958,6 +30364,7 @@ function createMcpServer(deps) {
29958
30364
  structuredContent: {
29959
30365
  rowCount: parsed.rows.length,
29960
30366
  columns: parsed.columns.map((c) => c.name),
30367
+ rows: parsed.rows,
29961
30368
  preview: parsed.rows.slice(0, 5)
29962
30369
  }
29963
30370
  };
@@ -29965,23 +30372,26 @@ function createMcpServer(deps) {
29965
30372
  server.registerTool("list_tables", {
29966
30373
  description: "List warehouse tables from the cached catalog (name, schema, connection, column count).",
29967
30374
  inputSchema: {},
30375
+ outputSchema: { tables: z2.array(z2.unknown()), warning: z2.string().optional() },
29968
30376
  annotations: READ_ONLY
29969
30377
  }, async () => {
29970
30378
  const raw = await listTables(deps);
29971
- const tables = JSON.parse(raw);
30379
+ const parsed = JSON.parse(raw);
29972
30380
  return {
29973
30381
  content: [{ type: "text", text: raw }],
29974
- structuredContent: { tables }
30382
+ structuredContent: parsed
29975
30383
  };
29976
30384
  });
29977
30385
  server.registerTool("describe_table", {
29978
30386
  description: 'Show columns and types for a catalog table. Accepts { table } (with optional dot-notation schema: "schema.table") or { table, schema } separately.',
29979
30387
  inputSchema: { table: z2.string(), schema: z2.string().optional() },
30388
+ outputSchema: TEXT_OUTPUT,
29980
30389
  annotations: READ_ONLY
29981
- }, async (args) => text(await describeTable(deps, args)));
30390
+ }, async (args) => textOut(await describeTable(deps, args)));
29982
30391
  server.registerTool("sample_table", {
29983
30392
  description: "Return a few sample rows from a table.",
29984
- inputSchema: { table: z2.string(), limit: z2.number().optional() },
30393
+ inputSchema: { table: z2.string(), schema: z2.string().optional(), limit: z2.number().optional() },
30394
+ outputSchema: TABULAR_OUTPUT,
29985
30395
  annotations: READ_ONLY
29986
30396
  }, async (args) => {
29987
30397
  const raw = await sampleTable(deps, args);
@@ -29993,6 +30403,7 @@ function createMcpServer(deps) {
29993
30403
  structuredContent: {
29994
30404
  rowCount: parsed.rows.length,
29995
30405
  columns: parsed.columns.map((c) => c.name),
30406
+ rows: parsed.rows,
29996
30407
  preview: parsed.rows.slice(0, 5)
29997
30408
  }
29998
30409
  };
@@ -30000,6 +30411,7 @@ function createMcpServer(deps) {
30000
30411
  server.registerTool("visualize", {
30001
30412
  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.",
30002
30413
  inputSchema: { spec: z2.record(z2.unknown()), data: z2.array(z2.record(z2.unknown())) },
30414
+ outputSchema: { spec: z2.record(z2.unknown()), option: z2.record(z2.unknown()) },
30003
30415
  annotations: READ_ONLY,
30004
30416
  _meta: {
30005
30417
  ui: { resourceUri: TEMPLATE_URI },
@@ -30020,16 +30432,47 @@ function createMcpServer(deps) {
30020
30432
  structuredContent: { spec: result.spec, option: result.option }
30021
30433
  };
30022
30434
  });
30435
+ server.registerTool("get_permissions", {
30436
+ description: "Return the current MCP user role and permissions, including whether save_chart is allowed.",
30437
+ inputSchema: {},
30438
+ outputSchema: {
30439
+ role: z2.string(),
30440
+ permissions: z2.record(z2.boolean()),
30441
+ required_role: z2.object({ save_chart: z2.string() })
30442
+ },
30443
+ annotations: READ_ONLY
30444
+ }, async () => {
30445
+ const role = deps.user?.role ?? "anonymous";
30446
+ const permissions = {
30447
+ can_view_charts: true,
30448
+ can_run_sql: true,
30449
+ can_validate_charts: true,
30450
+ can_visualize: true,
30451
+ can_save_charts: canWrite(deps.user)
30452
+ };
30453
+ const result = {
30454
+ role,
30455
+ permissions,
30456
+ required_role: { save_chart: "editor_or_admin" }
30457
+ };
30458
+ const raw = JSON.stringify(result);
30459
+ return {
30460
+ content: [{ type: "text", text: raw }],
30461
+ structuredContent: result
30462
+ };
30463
+ });
30023
30464
  server.registerTool("save_chart", {
30024
30465
  description: "Save a yamchart chart spec to charts/<name>.yaml (requires editor/admin).",
30025
30466
  inputSchema: { spec: z2.record(z2.unknown()) },
30467
+ outputSchema: TEXT_OUTPUT,
30026
30468
  annotations: WRITE_SAFE
30027
- }, async (args) => text(await saveChart(deps, args)));
30469
+ }, async (args) => textOut(await saveChart(deps, args)));
30028
30470
  if (deps.semanticLayer) {
30029
30471
  const semanticLayer = deps.semanticLayer;
30030
30472
  server.registerTool("list_metrics", {
30031
30473
  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.",
30032
30474
  inputSchema: {},
30475
+ outputSchema: { metrics: z2.array(z2.unknown()) },
30033
30476
  annotations: READ_ONLY
30034
30477
  }, async () => {
30035
30478
  const raw = listMetrics({ semanticLayer });
@@ -30042,8 +30485,9 @@ function createMcpServer(deps) {
30042
30485
  server.registerTool("get_metric", {
30043
30486
  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.",
30044
30487
  inputSchema: { name: z2.string() },
30488
+ outputSchema: TEXT_OUTPUT,
30045
30489
  annotations: READ_ONLY
30046
- }, async (args) => text(getMetric({ semanticLayer }, args)));
30490
+ }, async (args) => textOut(getMetric({ semanticLayer }, args)));
30047
30491
  server.registerTool("query_metric", {
30048
30492
  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.",
30049
30493
  inputSchema: {
@@ -30058,12 +30502,20 @@ function createMcpServer(deps) {
30058
30502
  order_by: z2.object({ field: z2.string(), direction: z2.enum(["asc", "desc"]).optional() }).optional(),
30059
30503
  limit: z2.number().int().positive().max(1e4).optional()
30060
30504
  },
30505
+ outputSchema: {
30506
+ rowCount: z2.number(),
30507
+ columns: z2.array(z2.string()),
30508
+ rows: z2.array(z2.record(z2.unknown())),
30509
+ preview: z2.array(z2.record(z2.unknown())),
30510
+ sql: z2.string(),
30511
+ suggested_chart: z2.unknown()
30512
+ },
30061
30513
  annotations: READ_ONLY
30062
30514
  }, async (args) => {
30063
30515
  try {
30064
30516
  const raw = await queryMetric({ semanticLayer, queryService: deps.queryService, connectionType: deps.connectionType }, args);
30065
30517
  if (raw.startsWith("Error:") || raw.startsWith("{") && JSON.parse(raw).error) {
30066
- return text(raw);
30518
+ return { content: [{ type: "text", text: raw }], isError: true };
30067
30519
  }
30068
30520
  const parsed = JSON.parse(raw);
30069
30521
  return {
@@ -30071,6 +30523,7 @@ function createMcpServer(deps) {
30071
30523
  structuredContent: {
30072
30524
  rowCount: parsed.rows.length,
30073
30525
  columns: parsed.columns.map((c) => c.name),
30526
+ rows: parsed.rows,
30074
30527
  preview: parsed.rows.slice(0, 5),
30075
30528
  sql: parsed.sql,
30076
30529
  suggested_chart: parsed.suggested_chart
@@ -30088,12 +30541,21 @@ function createMcpServer(deps) {
30088
30541
  server.registerTool("answer_question", {
30089
30542
  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.',
30090
30543
  inputSchema: { question: z2.string(), connection: z2.string().optional() },
30544
+ outputSchema: {
30545
+ rowCount: z2.number(),
30546
+ columns: z2.array(z2.string()),
30547
+ rows: z2.array(z2.record(z2.unknown())),
30548
+ preview: z2.array(z2.record(z2.unknown())),
30549
+ sql: z2.unknown().optional(),
30550
+ answer: z2.unknown().optional()
30551
+ },
30091
30552
  annotations: READ_ONLY
30092
30553
  }, async (args) => {
30093
30554
  const raw = await answerQuestionTool({
30094
30555
  knowledgeStore,
30095
30556
  embedder,
30096
30557
  provider: answerProvider,
30558
+ configLoader: deps.configLoader,
30097
30559
  queryService: deps.queryService,
30098
30560
  connectionType: deps.connectionType,
30099
30561
  defaultConnectionName: deps.aiConnectionName
@@ -30101,6 +30563,9 @@ function createMcpServer(deps) {
30101
30563
  if (raw.startsWith("Error:"))
30102
30564
  return { content: [{ type: "text", text: raw }], isError: true };
30103
30565
  const parsed = JSON.parse(raw);
30566
+ if (parsed["error_type"]) {
30567
+ return { content: [{ type: "text", text: raw }], isError: true };
30568
+ }
30104
30569
  const rows = parsed.rows ?? [];
30105
30570
  const columns = parsed.columns ?? [];
30106
30571
  return {
@@ -30108,6 +30573,7 @@ function createMcpServer(deps) {
30108
30573
  structuredContent: {
30109
30574
  rowCount: rows.length,
30110
30575
  columns: columns.map((c) => c.name),
30576
+ rows,
30111
30577
  preview: rows.slice(0, 5),
30112
30578
  sql: parsed.sql,
30113
30579
  answer: parsed.answer
@@ -30118,18 +30584,21 @@ function createMcpServer(deps) {
30118
30584
  server.registerTool("get_chart_schema", {
30119
30585
  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 }.",
30120
30586
  inputSchema: { type: z2.string().optional() },
30587
+ outputSchema: TEXT_OUTPUT,
30121
30588
  annotations: READ_ONLY
30122
- }, async (args) => text(getChartSchema(args)));
30589
+ }, async (args) => textOut(getChartSchema(args)));
30123
30590
  server.registerTool("validate_chart", {
30124
30591
  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.",
30125
30592
  inputSchema: { spec: z2.record(z2.unknown()) },
30593
+ outputSchema: TEXT_OUTPUT,
30126
30594
  annotations: READ_ONLY
30127
- }, async (args) => text(validateChart(args)));
30595
+ }, async (args) => textOut(validateChart(args)));
30128
30596
  server.registerTool("get_chart_spec", {
30129
30597
  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.",
30130
30598
  inputSchema: { name: z2.string() },
30599
+ outputSchema: TEXT_OUTPUT,
30131
30600
  annotations: READ_ONLY
30132
- }, async (args) => text(getChartSpec({ configLoader: deps.configLoader }, args)));
30601
+ }, async (args) => textOut(getChartSpec({ configLoader: deps.configLoader }, args)));
30133
30602
  return server;
30134
30603
  }
30135
30604
 
@@ -30227,6 +30696,22 @@ async function catalogRoutes(fastify, options) {
30227
30696
  });
30228
30697
  }
30229
30698
 
30699
+ // ../server/dist/services/dbt-path.js
30700
+ import { readFile as readFile6 } from "fs/promises";
30701
+ import { join as join13, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
30702
+ import { parse as parseYaml3 } from "yaml";
30703
+ async function resolveDbtProjectPath(projectDir) {
30704
+ try {
30705
+ const raw = await readFile6(join13(projectDir, ".yamchart", "dbt-source.yaml"), "utf-8");
30706
+ const parsed = parseYaml3(raw);
30707
+ if (!parsed?.path)
30708
+ return null;
30709
+ return isAbsolute2(parsed.path) ? parsed.path : resolve2(projectDir, parsed.path);
30710
+ } catch {
30711
+ return null;
30712
+ }
30713
+ }
30714
+
30230
30715
  // ../server/dist/routes/learning.js
30231
30716
  var ReadOnlySqlError = class extends Error {
30232
30717
  constructor() {
@@ -30265,6 +30750,51 @@ async function answerBlindspot(deps, id, body) {
30265
30750
  async function dismissBlindspot(deps, id) {
30266
30751
  return deps.store.updateBlindspot(id, { status: "dismissed" });
30267
30752
  }
30753
+ async function acceptBlindspot(deps, id) {
30754
+ const bs = await deps.store.getBlindspot(id);
30755
+ if (!bs || bs.kind !== "opportunity" || !bs.suggestedModel) {
30756
+ return { status: 400, body: { error: "Not an opportunity blindspot with a suggested model." } };
30757
+ }
30758
+ if (!/^[a-zA-Z0-9_]+$/.test(bs.suggestedModel.name)) {
30759
+ return {
30760
+ status: 400,
30761
+ body: { error: "Invalid suggested model name \u2014 dbt model names may contain only letters, digits, and underscores." }
30762
+ };
30763
+ }
30764
+ const dbtPath = await resolveDbtProjectPath(deps.projectDir);
30765
+ if (!dbtPath) {
30766
+ return {
30767
+ status: 400,
30768
+ body: { error: 'No connected dbt project. Run "yamchart sync-dbt" to link one.' }
30769
+ };
30770
+ }
30771
+ try {
30772
+ const sm = bs.suggestedModel;
30773
+ const conventions = await detectConventions(dbtPath);
30774
+ const modelPath = buildModelPath(sm.name, void 0, conventions);
30775
+ const layer = modelPath.split("/")[1] ?? "marts";
30776
+ const materialization = conventions.commonMaterializations[layer] ?? "view";
30777
+ const sql = formatModelSql({
30778
+ materialization,
30779
+ sql: scaffoldModelSql(sm),
30780
+ description: sm.description
30781
+ });
30782
+ await writeDbtModel(dbtPath, modelPath, sql);
30783
+ const schemaPath = modelPath.replace(/[^/]+\.sql$/, "schema.yml");
30784
+ await updateSchemaYml(dbtPath, schemaPath, {
30785
+ name: sm.name,
30786
+ description: sm.description,
30787
+ columns: sm.columns
30788
+ });
30789
+ const blindspot = await deps.store.updateBlindspot(id, {
30790
+ status: "answered",
30791
+ answer: `Scaffolded dbt model: ${modelPath}`
30792
+ });
30793
+ return { status: 200, body: { modelPath, schemaPath, blindspot } };
30794
+ } catch (e) {
30795
+ return { status: 500, body: { error: e.message } };
30796
+ }
30797
+ }
30268
30798
  async function loadContextBlock(opts, store, connection) {
30269
30799
  const fileDocs = loadKnowledgeDocs(opts.projectDir);
30270
30800
  const dbDocs = (await store.listKnowledgeDocs({ connection })).map((d) => `${d.title}
@@ -30460,6 +30990,16 @@ async function learningRoutes(fastify, opts) {
30460
30990
  }
30461
30991
  return dismissBlindspot({ store }, request.params.id);
30462
30992
  });
30993
+ fastify.post("/api/blindspots/:id/accept", async (request, reply) => {
30994
+ if (!requireStore(reply))
30995
+ return;
30996
+ if (!opts.canWrite(request)) {
30997
+ reply.code(403).send({ error: "Requires editor or admin role." });
30998
+ return;
30999
+ }
31000
+ const result = await acceptBlindspot({ store, projectDir: opts.projectDir }, request.params.id);
31001
+ reply.code(result.status).send(result.body);
31002
+ });
30463
31003
  fastify.get("/api/coverage", async (request, reply) => {
30464
31004
  if (!requireStore(reply))
30465
31005
  return;
@@ -31355,18 +31895,18 @@ data: ${data}
31355
31895
  }
31356
31896
 
31357
31897
  // ../server/dist/server.js
31358
- import { join as join13, dirname as dirname4 } from "path";
31898
+ import { join as join14, dirname as dirname4 } from "path";
31359
31899
  import { fileURLToPath } from "url";
31360
- import { access as access2, readFile as readFile6 } from "fs/promises";
31900
+ import { access as access2, readFile as readFile7 } from "fs/promises";
31361
31901
  import { watch as fsWatch } from "fs";
31362
31902
  var __dirname = dirname4(fileURLToPath(import.meta.url));
31363
- var packageJsonPath = join13(__dirname, "..", "package.json");
31364
- var packageJson = JSON.parse(await readFile6(packageJsonPath, "utf-8"));
31903
+ var packageJsonPath = join14(__dirname, "..", "package.json");
31904
+ var packageJson = JSON.parse(await readFile7(packageJsonPath, "utf-8"));
31365
31905
  var VERSION = packageJson.version;
31366
31906
  async function enrichRefsFromCatalog(projectDir, refs) {
31367
31907
  try {
31368
- const catalogPath = join13(projectDir, ".yamchart", "catalog.json");
31369
- const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
31908
+ const catalogPath = join14(projectDir, ".yamchart", "catalog.json");
31909
+ const catalogData = JSON.parse(await readFile7(catalogPath, "utf-8"));
31370
31910
  for (const model of catalogData.models ?? []) {
31371
31911
  if (model.name && model.table) {
31372
31912
  refs[model.name] = model.table;
@@ -31376,7 +31916,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
31376
31916
  }
31377
31917
  }
31378
31918
  async function createServer(options) {
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;
31919
+ const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join14(__dirname, "public"), auth } = options;
31380
31920
  if (auth) {
31381
31921
  initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
31382
31922
  }
@@ -31639,9 +32179,9 @@ async function createServer(options) {
31639
32179
  dialect: defaultConnection?.type ?? "duckdb",
31640
32180
  metricNames: semanticLayer?.listMetrics().map((m) => m.name) ?? [],
31641
32181
  getConnectionTables: async (connection) => {
31642
- const catalogPath = join13(projectDir, ".yamchart", "catalog.json");
32182
+ const catalogPath = join14(projectDir, ".yamchart", "catalog.json");
31643
32183
  try {
31644
- const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
32184
+ const catalogData = JSON.parse(await readFile7(catalogPath, "utf-8"));
31645
32185
  return (catalogData.models ?? []).filter((m) => !m.connection || m.connection === connection).map((m) => ({
31646
32186
  table: m.table ?? m.name ?? "",
31647
32187
  columns: (m.columns ?? []).map((c) => ({ name: c.name, type: c.data_type ?? "string" }))
@@ -31756,7 +32296,7 @@ async function createServer(options) {
31756
32296
  await fastify.register(adminConfigRoutes, adminConfigOptions);
31757
32297
  await registerReconnect(fastify);
31758
32298
  }
31759
- const assetsDir = join13(projectDir, "assets");
32299
+ const assetsDir = join14(projectDir, "assets");
31760
32300
  try {
31761
32301
  await access2(assetsDir);
31762
32302
  await fastify.register(fastifyStatic, {
@@ -31767,10 +32307,10 @@ async function createServer(options) {
31767
32307
  } catch {
31768
32308
  }
31769
32309
  if (project.theme?.customCss) {
31770
- const cssPath = join13(projectDir, project.theme.customCss);
32310
+ const cssPath = join14(projectDir, project.theme.customCss);
31771
32311
  fastify.get("/api/theme/custom.css", async (_request2, reply) => {
31772
32312
  try {
31773
- const content = await readFile6(cssPath, "utf-8");
32313
+ const content = await readFile7(cssPath, "utf-8");
31774
32314
  return reply.type("text/css").send(content);
31775
32315
  } catch {
31776
32316
  return reply.status(404).send("");
@@ -31827,7 +32367,7 @@ async function createServer(options) {
31827
32367
  fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
31828
32368
  broadcastConfigChange();
31829
32369
  });
31830
- const catalogPath = join13(projectDir, ".yamchart", "catalog.json");
32370
+ const catalogPath = join14(projectDir, ".yamchart", "catalog.json");
31831
32371
  try {
31832
32372
  catalogWatcher = fsWatch(catalogPath, () => {
31833
32373
  semanticService.buildFromDisk();
@@ -31893,13 +32433,13 @@ async function runDevServer(projectDir, options) {
31893
32433
  try {
31894
32434
  const { readFileSync: readFileSync5 } = await import("fs");
31895
32435
  const { parse: parse2 } = await import("yaml");
31896
- const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-WMP7BFKP.js");
31897
- const raw = readFileSync5(resolve2(projectDir, "yamchart.yaml"), "utf-8");
32436
+ const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-F6TVLP6C.js");
32437
+ const raw = readFileSync5(resolve3(projectDir, "yamchart.yaml"), "utf-8");
31898
32438
  const rawConfig = parse2(raw);
31899
32439
  const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
31900
32440
  let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
31901
32441
  try {
31902
- const localRaw = readFileSync5(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
32442
+ const localRaw = readFileSync5(resolve3(projectDir, "yamchart.local.yaml"), "utf-8");
31903
32443
  const localOverrides = parse2(localRaw);
31904
32444
  if (localOverrides && typeof localOverrides === "object") {
31905
32445
  projectConfig = deepMerge3(projectConfig, localOverrides);
@@ -31909,15 +32449,15 @@ async function runDevServer(projectDir, options) {
31909
32449
  if (projectConfig?.auth?.enabled) {
31910
32450
  localAuth = {
31911
32451
  enabled: true,
31912
- dbPath: projectConfig.auth.db_path ? resolve2(projectConfig.auth.db_path.replace(/^~/, homedir())) : resolve2(homedir(), ".yamchart", "auth.db"),
32452
+ dbPath: projectConfig.auth.db_path ? resolve3(projectConfig.auth.db_path.replace(/^~/, homedir())) : resolve3(homedir(), ".yamchart", "auth.db"),
31913
32453
  sessionTtlMs: projectConfig.auth.session_ttl ? parseTtl(projectConfig.auth.session_ttl) : 30 * 24 * 60 * 60 * 1e3
31914
32454
  };
31915
32455
  }
31916
- const connectionDir = resolve2(projectDir, "connections");
32456
+ const connectionDir = resolve3(projectDir, "connections");
31917
32457
  const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
31918
32458
  if (targetConn) {
31919
32459
  try {
31920
- const connRaw = readFileSync5(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
32460
+ const connRaw = readFileSync5(resolve3(connectionDir, `${targetConn}.yaml`), "utf-8");
31921
32461
  const connConfig = parse2(connRaw);
31922
32462
  if (connConfig?.auth?.type === "externalbrowser") {
31923
32463
  usesBrowserAuth = true;
@@ -31997,4 +32537,4 @@ async function runDevServer(projectDir, options) {
31997
32537
  export {
31998
32538
  runDevServer
31999
32539
  };
32000
- //# sourceMappingURL=dev-KOWFGDOP.js.map
32540
+ //# sourceMappingURL=dev-PHFYLJPJ.js.map