@duckcodeailabs/dql-cli 1.4.1 → 1.4.4

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 (281) hide show
  1. package/apps-api.d.ts +77 -0
  2. package/apps-api.d.ts.map +1 -0
  3. package/apps-api.js +612 -0
  4. package/apps-api.js.map +1 -0
  5. package/apps-api.test.d.ts +2 -0
  6. package/apps-api.test.d.ts.map +1 -0
  7. package/apps-api.test.js +111 -0
  8. package/apps-api.test.js.map +1 -0
  9. package/{dist/assets/dql-notebook/assets/index-BJ7MV8Gv.js → assets/dql-notebook/assets/index-DUTeFz5j.js} +145 -134
  10. package/{dist/assets → assets}/dql-notebook/index.html +1 -1
  11. package/{dist/commands → commands}/agent.d.ts.map +1 -1
  12. package/{dist/commands → commands}/agent.js +50 -2
  13. package/commands/agent.js.map +1 -0
  14. package/governance-runtime.d.ts +15 -0
  15. package/governance-runtime.d.ts.map +1 -0
  16. package/governance-runtime.js +50 -0
  17. package/governance-runtime.js.map +1 -0
  18. package/{dist/llm → llm}/index.d.ts.map +1 -1
  19. package/llm/index.js +19 -0
  20. package/llm/index.js.map +1 -0
  21. package/llm/providers/dql-agent-provider.d.ts +5 -0
  22. package/llm/providers/dql-agent-provider.d.ts.map +1 -0
  23. package/llm/providers/dql-agent-provider.js +99 -0
  24. package/llm/providers/dql-agent-provider.js.map +1 -0
  25. package/{dist/llm → llm}/types.d.ts +1 -1
  26. package/llm/types.d.ts.map +1 -0
  27. package/local-runtime.d.ts.map +1 -0
  28. package/{dist/local-runtime.js → local-runtime.js} +557 -36
  29. package/local-runtime.js.map +1 -0
  30. package/metricflow.d.ts +35 -0
  31. package/metricflow.d.ts.map +1 -0
  32. package/metricflow.js +122 -0
  33. package/metricflow.js.map +1 -0
  34. package/metricflow.test.d.ts +2 -0
  35. package/metricflow.test.d.ts.map +1 -0
  36. package/metricflow.test.js +54 -0
  37. package/metricflow.test.js.map +1 -0
  38. package/package.json +20 -30
  39. package/{dist/schedule → schedule}/runner.d.ts +3 -0
  40. package/schedule/runner.d.ts.map +1 -0
  41. package/schedule/runner.js +221 -0
  42. package/schedule/runner.js.map +1 -0
  43. package/{dist/schedule → schedule}/service.d.ts +2 -1
  44. package/schedule/service.d.ts.map +1 -0
  45. package/{dist/schedule → schedule}/service.js +39 -1
  46. package/schedule/service.js.map +1 -0
  47. package/{dist/schedule → schedule}/types.d.ts +6 -0
  48. package/schedule/types.d.ts.map +1 -0
  49. package/{dist/semantic-import.d.ts → semantic-import.d.ts} +9 -1
  50. package/semantic-import.d.ts.map +1 -0
  51. package/{dist/semantic-import.js → semantic-import.js} +269 -3
  52. package/semantic-import.js.map +1 -0
  53. package/LICENSE +0 -123
  54. package/README.md +0 -71
  55. package/dist/apps-api.d.ts +0 -16
  56. package/dist/apps-api.d.ts.map +0 -1
  57. package/dist/apps-api.js +0 -249
  58. package/dist/apps-api.js.map +0 -1
  59. package/dist/commands/agent.js.map +0 -1
  60. package/dist/llm/index.js +0 -16
  61. package/dist/llm/index.js.map +0 -1
  62. package/dist/llm/types.d.ts.map +0 -1
  63. package/dist/local-runtime.d.ts.map +0 -1
  64. package/dist/local-runtime.js.map +0 -1
  65. package/dist/schedule/runner.d.ts.map +0 -1
  66. package/dist/schedule/runner.js +0 -109
  67. package/dist/schedule/runner.js.map +0 -1
  68. package/dist/schedule/service.d.ts.map +0 -1
  69. package/dist/schedule/service.js.map +0 -1
  70. package/dist/schedule/types.d.ts.map +0 -1
  71. package/dist/semantic-import.d.ts.map +0 -1
  72. package/dist/semantic-import.js.map +0 -1
  73. /package/{dist/args.d.ts → args.d.ts} +0 -0
  74. /package/{dist/args.d.ts.map → args.d.ts.map} +0 -0
  75. /package/{dist/args.js → args.js} +0 -0
  76. /package/{dist/args.js.map → args.js.map} +0 -0
  77. /package/{dist/args.test.d.ts → args.test.d.ts} +0 -0
  78. /package/{dist/args.test.d.ts.map → args.test.d.ts.map} +0 -0
  79. /package/{dist/args.test.js → args.test.js} +0 -0
  80. /package/{dist/args.test.js.map → args.test.js.map} +0 -0
  81. /package/{dist/assets → assets}/dql-notebook/assets/codemirror-DJYUkPr1.js +0 -0
  82. /package/{dist/assets → assets}/dql-notebook/assets/index-DrhoZmtv.css +0 -0
  83. /package/{dist/assets → assets}/dql-notebook/assets/react-CRB3T2We.js +0 -0
  84. /package/{dist/assets → assets}/notebook-browser/app.js +0 -0
  85. /package/{dist/assets → assets}/notebook-browser/index.html +0 -0
  86. /package/{dist/assets → assets}/notebook-browser/styles.css +0 -0
  87. /package/{dist/block-templates.d.ts → block-templates.d.ts} +0 -0
  88. /package/{dist/block-templates.d.ts.map → block-templates.d.ts.map} +0 -0
  89. /package/{dist/block-templates.js → block-templates.js} +0 -0
  90. /package/{dist/block-templates.js.map → block-templates.js.map} +0 -0
  91. /package/{dist/commands → commands}/agent.d.ts +0 -0
  92. /package/{dist/commands → commands}/app.d.ts +0 -0
  93. /package/{dist/commands → commands}/app.d.ts.map +0 -0
  94. /package/{dist/commands → commands}/app.js +0 -0
  95. /package/{dist/commands → commands}/app.js.map +0 -0
  96. /package/{dist/commands → commands}/build.d.ts +0 -0
  97. /package/{dist/commands → commands}/build.d.ts.map +0 -0
  98. /package/{dist/commands → commands}/build.js +0 -0
  99. /package/{dist/commands → commands}/build.js.map +0 -0
  100. /package/{dist/commands → commands}/build.test.d.ts +0 -0
  101. /package/{dist/commands → commands}/build.test.d.ts.map +0 -0
  102. /package/{dist/commands → commands}/build.test.js +0 -0
  103. /package/{dist/commands → commands}/build.test.js.map +0 -0
  104. /package/{dist/commands → commands}/certify.d.ts +0 -0
  105. /package/{dist/commands → commands}/certify.d.ts.map +0 -0
  106. /package/{dist/commands → commands}/certify.js +0 -0
  107. /package/{dist/commands → commands}/certify.js.map +0 -0
  108. /package/{dist/commands → commands}/compile.d.ts +0 -0
  109. /package/{dist/commands → commands}/compile.d.ts.map +0 -0
  110. /package/{dist/commands → commands}/compile.js +0 -0
  111. /package/{dist/commands → commands}/compile.js.map +0 -0
  112. /package/{dist/commands → commands}/compile.test.d.ts +0 -0
  113. /package/{dist/commands → commands}/compile.test.d.ts.map +0 -0
  114. /package/{dist/commands → commands}/compile.test.js +0 -0
  115. /package/{dist/commands → commands}/compile.test.js.map +0 -0
  116. /package/{dist/commands → commands}/diff.d.ts +0 -0
  117. /package/{dist/commands → commands}/diff.d.ts.map +0 -0
  118. /package/{dist/commands → commands}/diff.js +0 -0
  119. /package/{dist/commands → commands}/diff.js.map +0 -0
  120. /package/{dist/commands → commands}/doctor.d.ts +0 -0
  121. /package/{dist/commands → commands}/doctor.d.ts.map +0 -0
  122. /package/{dist/commands → commands}/doctor.js +0 -0
  123. /package/{dist/commands → commands}/doctor.js.map +0 -0
  124. /package/{dist/commands → commands}/doctor.test.d.ts +0 -0
  125. /package/{dist/commands → commands}/doctor.test.d.ts.map +0 -0
  126. /package/{dist/commands → commands}/doctor.test.js +0 -0
  127. /package/{dist/commands → commands}/doctor.test.js.map +0 -0
  128. /package/{dist/commands → commands}/fmt.d.ts +0 -0
  129. /package/{dist/commands → commands}/fmt.d.ts.map +0 -0
  130. /package/{dist/commands → commands}/fmt.js +0 -0
  131. /package/{dist/commands → commands}/fmt.js.map +0 -0
  132. /package/{dist/commands → commands}/info.d.ts +0 -0
  133. /package/{dist/commands → commands}/info.d.ts.map +0 -0
  134. /package/{dist/commands → commands}/info.js +0 -0
  135. /package/{dist/commands → commands}/info.js.map +0 -0
  136. /package/{dist/commands → commands}/init.d.ts +0 -0
  137. /package/{dist/commands → commands}/init.d.ts.map +0 -0
  138. /package/{dist/commands → commands}/init.js +0 -0
  139. /package/{dist/commands → commands}/init.js.map +0 -0
  140. /package/{dist/commands → commands}/init.test.d.ts +0 -0
  141. /package/{dist/commands → commands}/init.test.d.ts.map +0 -0
  142. /package/{dist/commands → commands}/init.test.js +0 -0
  143. /package/{dist/commands → commands}/init.test.js.map +0 -0
  144. /package/{dist/commands → commands}/lineage.d.ts +0 -0
  145. /package/{dist/commands → commands}/lineage.d.ts.map +0 -0
  146. /package/{dist/commands → commands}/lineage.js +0 -0
  147. /package/{dist/commands → commands}/lineage.js.map +0 -0
  148. /package/{dist/commands → commands}/mcp.d.ts +0 -0
  149. /package/{dist/commands → commands}/mcp.d.ts.map +0 -0
  150. /package/{dist/commands → commands}/mcp.js +0 -0
  151. /package/{dist/commands → commands}/mcp.js.map +0 -0
  152. /package/{dist/commands → commands}/migrate.d.ts +0 -0
  153. /package/{dist/commands → commands}/migrate.d.ts.map +0 -0
  154. /package/{dist/commands → commands}/migrate.js +0 -0
  155. /package/{dist/commands → commands}/migrate.js.map +0 -0
  156. /package/{dist/commands → commands}/new.d.ts +0 -0
  157. /package/{dist/commands → commands}/new.d.ts.map +0 -0
  158. /package/{dist/commands → commands}/new.js +0 -0
  159. /package/{dist/commands → commands}/new.js.map +0 -0
  160. /package/{dist/commands → commands}/new.test.d.ts +0 -0
  161. /package/{dist/commands → commands}/new.test.d.ts.map +0 -0
  162. /package/{dist/commands → commands}/new.test.js +0 -0
  163. /package/{dist/commands → commands}/new.test.js.map +0 -0
  164. /package/{dist/commands → commands}/notebook.d.ts +0 -0
  165. /package/{dist/commands → commands}/notebook.d.ts.map +0 -0
  166. /package/{dist/commands → commands}/notebook.js +0 -0
  167. /package/{dist/commands → commands}/notebook.js.map +0 -0
  168. /package/{dist/commands → commands}/parse.d.ts +0 -0
  169. /package/{dist/commands → commands}/parse.d.ts.map +0 -0
  170. /package/{dist/commands → commands}/parse.js +0 -0
  171. /package/{dist/commands → commands}/parse.js.map +0 -0
  172. /package/{dist/commands → commands}/preview.d.ts +0 -0
  173. /package/{dist/commands → commands}/preview.d.ts.map +0 -0
  174. /package/{dist/commands → commands}/preview.js +0 -0
  175. /package/{dist/commands → commands}/preview.js.map +0 -0
  176. /package/{dist/commands → commands}/schedule.d.ts +0 -0
  177. /package/{dist/commands → commands}/schedule.d.ts.map +0 -0
  178. /package/{dist/commands → commands}/schedule.js +0 -0
  179. /package/{dist/commands → commands}/schedule.js.map +0 -0
  180. /package/{dist/commands → commands}/semantic.d.ts +0 -0
  181. /package/{dist/commands → commands}/semantic.d.ts.map +0 -0
  182. /package/{dist/commands → commands}/semantic.js +0 -0
  183. /package/{dist/commands → commands}/semantic.js.map +0 -0
  184. /package/{dist/commands → commands}/serve.d.ts +0 -0
  185. /package/{dist/commands → commands}/serve.d.ts.map +0 -0
  186. /package/{dist/commands → commands}/serve.js +0 -0
  187. /package/{dist/commands → commands}/serve.js.map +0 -0
  188. /package/{dist/commands → commands}/slack.d.ts +0 -0
  189. /package/{dist/commands → commands}/slack.d.ts.map +0 -0
  190. /package/{dist/commands → commands}/slack.js +0 -0
  191. /package/{dist/commands → commands}/slack.js.map +0 -0
  192. /package/{dist/commands → commands}/sync.d.ts +0 -0
  193. /package/{dist/commands → commands}/sync.d.ts.map +0 -0
  194. /package/{dist/commands → commands}/sync.js +0 -0
  195. /package/{dist/commands → commands}/sync.js.map +0 -0
  196. /package/{dist/commands → commands}/sync.test.d.ts +0 -0
  197. /package/{dist/commands → commands}/sync.test.d.ts.map +0 -0
  198. /package/{dist/commands → commands}/sync.test.js +0 -0
  199. /package/{dist/commands → commands}/sync.test.js.map +0 -0
  200. /package/{dist/commands → commands}/test.d.ts +0 -0
  201. /package/{dist/commands → commands}/test.d.ts.map +0 -0
  202. /package/{dist/commands → commands}/test.js +0 -0
  203. /package/{dist/commands → commands}/test.js.map +0 -0
  204. /package/{dist/commands → commands}/validate.d.ts +0 -0
  205. /package/{dist/commands → commands}/validate.d.ts.map +0 -0
  206. /package/{dist/commands → commands}/validate.js +0 -0
  207. /package/{dist/commands → commands}/validate.js.map +0 -0
  208. /package/{dist/commands → commands}/verify.d.ts +0 -0
  209. /package/{dist/commands → commands}/verify.d.ts.map +0 -0
  210. /package/{dist/commands → commands}/verify.js +0 -0
  211. /package/{dist/commands → commands}/verify.js.map +0 -0
  212. /package/{dist/digest.d.ts → digest.d.ts} +0 -0
  213. /package/{dist/digest.d.ts.map → digest.d.ts.map} +0 -0
  214. /package/{dist/digest.js → digest.js} +0 -0
  215. /package/{dist/digest.js.map → digest.js.map} +0 -0
  216. /package/{dist/git-service.d.ts → git-service.d.ts} +0 -0
  217. /package/{dist/git-service.d.ts.map → git-service.d.ts.map} +0 -0
  218. /package/{dist/git-service.js → git-service.js} +0 -0
  219. /package/{dist/git-service.js.map → git-service.js.map} +0 -0
  220. /package/{dist/index.d.ts → index.d.ts} +0 -0
  221. /package/{dist/index.d.ts.map → index.d.ts.map} +0 -0
  222. /package/{dist/index.js → index.js} +0 -0
  223. /package/{dist/index.js.map → index.js.map} +0 -0
  224. /package/{dist/llm → llm}/index.d.ts +0 -0
  225. /package/{dist/llm → llm}/providers/claude-agent-sdk.d.ts +0 -0
  226. /package/{dist/llm → llm}/providers/claude-agent-sdk.d.ts.map +0 -0
  227. /package/{dist/llm → llm}/providers/claude-agent-sdk.js +0 -0
  228. /package/{dist/llm → llm}/providers/claude-agent-sdk.js.map +0 -0
  229. /package/{dist/llm → llm}/providers/claude-code.d.ts +0 -0
  230. /package/{dist/llm → llm}/providers/claude-code.d.ts.map +0 -0
  231. /package/{dist/llm → llm}/providers/claude-code.js +0 -0
  232. /package/{dist/llm → llm}/providers/claude-code.js.map +0 -0
  233. /package/{dist/llm → llm}/tools.d.ts +0 -0
  234. /package/{dist/llm → llm}/tools.d.ts.map +0 -0
  235. /package/{dist/llm → llm}/tools.js +0 -0
  236. /package/{dist/llm → llm}/tools.js.map +0 -0
  237. /package/{dist/llm → llm}/types.js +0 -0
  238. /package/{dist/llm → llm}/types.js.map +0 -0
  239. /package/{dist/local-runtime.d.ts → local-runtime.d.ts} +0 -0
  240. /package/{dist/local-runtime.test.d.ts → local-runtime.test.d.ts} +0 -0
  241. /package/{dist/local-runtime.test.d.ts.map → local-runtime.test.d.ts.map} +0 -0
  242. /package/{dist/local-runtime.test.js → local-runtime.test.js} +0 -0
  243. /package/{dist/local-runtime.test.js.map → local-runtime.test.js.map} +0 -0
  244. /package/{dist/open-browser.d.ts → open-browser.d.ts} +0 -0
  245. /package/{dist/open-browser.d.ts.map → open-browser.d.ts.map} +0 -0
  246. /package/{dist/open-browser.js → open-browser.js} +0 -0
  247. /package/{dist/open-browser.js.map → open-browser.js.map} +0 -0
  248. /package/{dist/schedule → schedule}/alerts.d.ts +0 -0
  249. /package/{dist/schedule → schedule}/alerts.d.ts.map +0 -0
  250. /package/{dist/schedule → schedule}/alerts.js +0 -0
  251. /package/{dist/schedule → schedule}/alerts.js.map +0 -0
  252. /package/{dist/schedule → schedule}/discovery.d.ts +0 -0
  253. /package/{dist/schedule → schedule}/discovery.d.ts.map +0 -0
  254. /package/{dist/schedule → schedule}/discovery.js +0 -0
  255. /package/{dist/schedule → schedule}/discovery.js.map +0 -0
  256. /package/{dist/schedule → schedule}/notifiers/email.d.ts +0 -0
  257. /package/{dist/schedule → schedule}/notifiers/email.d.ts.map +0 -0
  258. /package/{dist/schedule → schedule}/notifiers/email.js +0 -0
  259. /package/{dist/schedule → schedule}/notifiers/email.js.map +0 -0
  260. /package/{dist/schedule → schedule}/notifiers/file.d.ts +0 -0
  261. /package/{dist/schedule → schedule}/notifiers/file.d.ts.map +0 -0
  262. /package/{dist/schedule → schedule}/notifiers/file.js +0 -0
  263. /package/{dist/schedule → schedule}/notifiers/file.js.map +0 -0
  264. /package/{dist/schedule → schedule}/notifiers/index.d.ts +0 -0
  265. /package/{dist/schedule → schedule}/notifiers/index.d.ts.map +0 -0
  266. /package/{dist/schedule → schedule}/notifiers/index.js +0 -0
  267. /package/{dist/schedule → schedule}/notifiers/index.js.map +0 -0
  268. /package/{dist/schedule → schedule}/notifiers/slack.d.ts +0 -0
  269. /package/{dist/schedule → schedule}/notifiers/slack.d.ts.map +0 -0
  270. /package/{dist/schedule → schedule}/notifiers/slack.js +0 -0
  271. /package/{dist/schedule → schedule}/notifiers/slack.js.map +0 -0
  272. /package/{dist/schedule → schedule}/runs.d.ts +0 -0
  273. /package/{dist/schedule → schedule}/runs.d.ts.map +0 -0
  274. /package/{dist/schedule → schedule}/runs.js +0 -0
  275. /package/{dist/schedule → schedule}/runs.js.map +0 -0
  276. /package/{dist/schedule → schedule}/types.js +0 -0
  277. /package/{dist/schedule → schedule}/types.js.map +0 -0
  278. /package/{dist/semantic-import.test.d.ts → semantic-import.test.d.ts} +0 -0
  279. /package/{dist/semantic-import.test.d.ts.map → semantic-import.test.d.ts.map} +0 -0
  280. /package/{dist/semantic-import.test.js → semantic-import.test.js} +0 -0
  281. /package/{dist/semantic-import.test.js.map → semantic-import.test.js.map} +0 -0
@@ -3,12 +3,14 @@ import { createServer } from 'node:http';
3
3
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, watch, writeFileSync } from 'node:fs';
4
4
  import { dirname, extname, join, normalize, relative, resolve } from 'node:path';
5
5
  import { buildExecutionPlan, createWelcomeNotebook, deserializeNotebook, getConnectorFormSchemas, hasSemanticRefs, resolveSemanticRefs, } from '@duckcodeailabs/dql-notebook';
6
- import { loadSemanticLayerFromDir, resolveSemanticLayerAsync, Parser, buildLineageGraph, buildManifest, analyzeImpact, buildTrustChain, detectDomainFlows, getDomainTrustOverview, queryLineage, queryCompleteLineagePaths, LineageGraph, canonicalize, canonicalizeNotebook, diffDQL, diffNotebook, } from '@duckcodeailabs/dql-core';
6
+ import { loadSemanticLayerFromDir, resolveSemanticLayerAsync, Parser, buildLineageGraph, buildManifest, findAppDocuments, findDashboardsForApp, isBlockIdRef, loadAppDocument, loadDashboardDocument, analyzeImpact, buildTrustChain, detectDomainFlows, getDomainTrustOverview, queryLineage, queryCompleteLineagePaths, LineageGraph, canonicalize, canonicalizeNotebook, diffDQL, diffNotebook, } from '@duckcodeailabs/dql-core';
7
7
  import { load as loadYaml } from 'js-yaml';
8
8
  import { listBlockTemplates } from './block-templates.js';
9
9
  import { getRunner as getLLMRunner } from './llm/index.js';
10
10
  import { handleAppsApi } from './apps-api.js';
11
+ import { DQLAccessDeniedError, activePersonaAppId, assertAppAccess, loadRuntimeApp, runtimeVariables, } from './governance-runtime.js';
11
12
  import { buildSemanticObjectDetail, buildSemanticTree, computeSyncDiff, loadSemanticImportManifest, performSemanticImport, previewSemanticImport, syncSemanticImport, } from './semantic-import.js';
13
+ import { MetricFlowUnavailableError, compileMetricFlowQuery, hasDbtSemanticManifest, } from './metricflow.js';
12
14
  export async function startLocalServer(opts) {
13
15
  const { rootDir, executor, connection: rawConnection, preferredPort, projectRoot = process.cwd() } = opts;
14
16
  const bindHost = opts.host ?? process.env.DQL_HOST ?? '127.0.0.1';
@@ -138,6 +140,117 @@ export async function startLocalServer(opts) {
138
140
  res.end(serializeJSON({ status: 'ok' }));
139
141
  return;
140
142
  }
143
+ if (req.method === 'GET' && path === '/api/settings/env-status') {
144
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
145
+ res.end(serializeJSON({ groups: collectSettingsEnvStatus() }));
146
+ return;
147
+ }
148
+ const appDashRun = path.match(/^\/api\/apps\/([^/]+)\/dashboards\/([^/]+)\/run$/);
149
+ if (req.method === 'POST' && appDashRun) {
150
+ try {
151
+ const appId = decodeURIComponent(appDashRun[1]);
152
+ const dashboardId = decodeURIComponent(appDashRun[2]);
153
+ const body = await readJSON(req).catch(() => ({}));
154
+ const loaded = loadAppDashboard(projectRoot, appId, dashboardId);
155
+ if (!loaded) {
156
+ res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
157
+ res.end(serializeJSON({ error: `Dashboard "${dashboardId}" not found in app "${appId}"` }));
158
+ return;
159
+ }
160
+ const manifest = buildManifest({ projectRoot });
161
+ const variables = body.variables && typeof body.variables === 'object'
162
+ ? body.variables
163
+ : {};
164
+ const tiles = [];
165
+ for (const item of loaded.dashboard.layout.items) {
166
+ const block = resolveDashboardItemBlock(item, manifest);
167
+ if (!block) {
168
+ tiles.push({
169
+ tileId: item.i,
170
+ status: 'unresolved',
171
+ blockRef: isBlockIdRef(item.block) ? item.block.blockId : item.block.ref,
172
+ error: 'Block reference could not be resolved',
173
+ });
174
+ continue;
175
+ }
176
+ try {
177
+ assertAppAccess({
178
+ app: loaded.app,
179
+ domain: block.domain ?? loaded.dashboard.metadata.domain ?? loaded.app.domain,
180
+ level: 'execute',
181
+ });
182
+ const absBlockPath = join(projectRoot, block.filePath);
183
+ const source = readFileSync(absBlockPath, 'utf-8');
184
+ const semanticCompose = semanticLayer
185
+ ? composeSemanticBlockSql(source, semanticLayer, {
186
+ driver: connection.driver,
187
+ projectRoot,
188
+ projectConfig,
189
+ detectedProvider: semanticDetectedProvider,
190
+ })
191
+ : null;
192
+ const plan = buildExecutionPlan({ id: item.i, type: 'dql', source, title: item.title ?? block.name }, { semanticLayer, driver: connection.driver });
193
+ if (!plan && !semanticCompose?.sql) {
194
+ tiles.push({
195
+ tileId: item.i,
196
+ status: 'error',
197
+ blockId: block.name,
198
+ error: semanticCompose?.diagnostics.find((diagnostic) => diagnostic.severity === 'error')?.message ?? 'Block produced no executable plan',
199
+ });
200
+ continue;
201
+ }
202
+ const prepared = prepareLocalExecution(semanticCompose?.sql ?? plan.sql, isConnectionConfig(body.connection) ? body.connection : connection, projectRoot, projectConfig);
203
+ const result = await executor.executeQuery(prepared.sql, plan?.sqlParams ?? [], runtimeVariables({ ...(plan?.variables ?? {}), ...variables }), prepared.connection);
204
+ tiles.push({
205
+ tileId: item.i,
206
+ status: 'ok',
207
+ blockId: block.name,
208
+ blockPath: block.filePath,
209
+ certificationStatus: block.status ?? null,
210
+ title: item.title ?? block.name,
211
+ viz: item.viz,
212
+ chartConfig: plan?.chartConfig ?? { chart: item.viz.type },
213
+ result: normalizeQueryResult(result),
214
+ citation: {
215
+ kind: 'block',
216
+ name: block.name,
217
+ path: block.filePath,
218
+ },
219
+ });
220
+ }
221
+ catch (err) {
222
+ if (err instanceof DQLAccessDeniedError) {
223
+ tiles.push({
224
+ tileId: item.i,
225
+ status: 'unauthorized',
226
+ blockId: block.name,
227
+ error: err.message,
228
+ });
229
+ }
230
+ else {
231
+ tiles.push({
232
+ tileId: item.i,
233
+ status: 'error',
234
+ blockId: block.name,
235
+ error: err instanceof Error ? err.message : String(err),
236
+ });
237
+ }
238
+ }
239
+ }
240
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
241
+ res.end(serializeJSON({
242
+ appId,
243
+ dashboardId,
244
+ persona: activePersonaAppId() ? { appId: activePersonaAppId() } : null,
245
+ tiles,
246
+ }));
247
+ }
248
+ catch (err) {
249
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
250
+ res.end(serializeJSON({ error: err instanceof Error ? err.message : String(err) }));
251
+ }
252
+ return;
253
+ }
141
254
  // Apps, dashboards, persona — see apps-api.ts. Returns true if handled.
142
255
  if (path.startsWith('/api/apps') || path === '/api/persona') {
143
256
  try {
@@ -218,6 +331,11 @@ export async function startLocalServer(opts) {
218
331
  res.end(serializeJSON({ path: `notebooks/${slug}.dqlnb`, content }));
219
332
  }
220
333
  catch (error) {
334
+ if (error instanceof DQLAccessDeniedError) {
335
+ res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
336
+ res.end(serializeJSON({ error: error.message, code: 'unauthorized' }));
337
+ return;
338
+ }
221
339
  res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
222
340
  res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
223
341
  }
@@ -249,6 +367,11 @@ export async function startLocalServer(opts) {
249
367
  res.end(serializeJSON({ ok: true }));
250
368
  }
251
369
  catch (error) {
370
+ if (error instanceof DQLAccessDeniedError) {
371
+ res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
372
+ res.end(serializeJSON({ error: error.message, code: 'unauthorized' }));
373
+ return;
374
+ }
252
375
  res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
253
376
  res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
254
377
  }
@@ -584,6 +707,11 @@ export async function startLocalServer(opts) {
584
707
  res.end(serializeJSON({ blocks }));
585
708
  }
586
709
  catch (error) {
710
+ if (error instanceof DQLAccessDeniedError) {
711
+ res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
712
+ res.end(serializeJSON({ error: error.message, code: 'unauthorized' }));
713
+ return;
714
+ }
587
715
  res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
588
716
  res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
589
717
  }
@@ -636,8 +764,12 @@ export async function startLocalServer(opts) {
636
764
  res.end(serializeJSON({ apps }));
637
765
  }
638
766
  catch (error) {
639
- res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
640
- res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
767
+ const status = error instanceof DQLAccessDeniedError ? 403 : 500;
768
+ res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
769
+ res.end(serializeJSON({
770
+ error: error instanceof Error ? error.message : String(error),
771
+ ...(status === 403 ? { code: 'unauthorized' } : {}),
772
+ }));
641
773
  }
642
774
  return;
643
775
  }
@@ -918,7 +1050,13 @@ export async function startLocalServer(opts) {
918
1050
  }
919
1051
  }
920
1052
  const semanticCompose = semanticLayer
921
- ? composeSemanticBlockSql(source, semanticLayer, { driver: targetConnection.driver, tableMapping })
1053
+ ? composeSemanticBlockSql(source, semanticLayer, {
1054
+ driver: targetConnection.driver,
1055
+ tableMapping,
1056
+ projectRoot,
1057
+ projectConfig,
1058
+ detectedProvider: semanticDetectedProvider,
1059
+ })
922
1060
  : null;
923
1061
  const validation = validateBlockStudioSource(source, semanticLayer);
924
1062
  const executableSql = semanticCompose?.sql ?? validation.executableSql;
@@ -930,13 +1068,14 @@ export async function startLocalServer(opts) {
930
1068
  res.end(serializeJSON({ error: message, diagnostics: validation.diagnostics }));
931
1069
  return;
932
1070
  }
933
- const sql = resolveProjectRelativeSqlPaths(executableSql, projectRoot, projectConfig.dataDir);
934
- const result = await executor.executeQuery(sql, [], {}, targetConnection);
1071
+ const plan = buildExecutionPlan({ id: 'block-studio', type: 'dql', source, title: 'Block Studio' }, { semanticLayer, driver: targetConnection.driver });
1072
+ const sql = resolveProjectRelativeSqlPaths(semanticCompose?.sql ?? plan?.sql ?? executableSql, projectRoot, projectConfig.dataDir);
1073
+ const result = await executor.executeQuery(sql, plan?.sqlParams ?? [], runtimeVariables(plan?.variables ?? {}), targetConnection);
935
1074
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
936
1075
  res.end(serializeJSON({
937
- sql: executableSql,
1076
+ sql: plan?.sql ?? executableSql,
938
1077
  result: normalizeQueryResult(result),
939
- chartConfig: validation.chartConfig ?? null,
1078
+ chartConfig: plan?.chartConfig ?? validation.chartConfig ?? null,
940
1079
  }));
941
1080
  }
942
1081
  catch (error) {
@@ -1062,8 +1201,13 @@ export async function startLocalServer(opts) {
1062
1201
  provider: projectConfig.semanticLayer?.provider ?? semanticDetectedProvider ?? null,
1063
1202
  errors: semanticLayerErrors,
1064
1203
  metrics: [],
1204
+ measures: [],
1065
1205
  dimensions: [],
1206
+ timeDimensions: [],
1207
+ entities: [],
1066
1208
  hierarchies: [],
1209
+ semanticModels: [],
1210
+ savedQueries: [],
1067
1211
  domains: [],
1068
1212
  tags: [],
1069
1213
  favorites: userPrefs.favorites,
@@ -1082,6 +1226,25 @@ export async function startLocalServer(opts) {
1082
1226
  table: m.table,
1083
1227
  tags: m.tags ?? [],
1084
1228
  owner: m.owner ?? null,
1229
+ metricType: m.metricType ?? null,
1230
+ typeParams: m.typeParams ?? null,
1231
+ filter: m.filter ?? null,
1232
+ source: m.source ?? null,
1233
+ }));
1234
+ const measures = semanticLayer.listMeasures().map((m) => ({
1235
+ name: m.name,
1236
+ label: m.label,
1237
+ description: m.description,
1238
+ domain: m.domain,
1239
+ agg: m.agg,
1240
+ expr: m.expr ?? null,
1241
+ table: m.table,
1242
+ cube: m.cube ?? null,
1243
+ aggTimeDimension: m.aggTimeDimension ?? null,
1244
+ nonAdditiveDimension: m.nonAdditiveDimension ?? null,
1245
+ tags: m.tags ?? [],
1246
+ owner: m.owner ?? null,
1247
+ source: m.source ?? null,
1085
1248
  }));
1086
1249
  const dimensions = semanticLayer.listDimensions().map((d) => ({
1087
1250
  name: d.name,
@@ -1093,6 +1256,40 @@ export async function startLocalServer(opts) {
1093
1256
  table: d.table,
1094
1257
  tags: d.tags ?? [],
1095
1258
  owner: d.owner ?? null,
1259
+ cube: d.cube ?? null,
1260
+ isTimeDimension: d.isTimeDimension ?? false,
1261
+ typeParams: d.typeParams ?? null,
1262
+ source: d.source ?? null,
1263
+ }));
1264
+ const timeDimensions = semanticLayer.listTimeDimensions().map((d) => ({
1265
+ name: d.name,
1266
+ label: d.label,
1267
+ description: d.description,
1268
+ domain: d.domain,
1269
+ sql: d.sql,
1270
+ type: d.type,
1271
+ table: d.table,
1272
+ cube: d.cube ?? null,
1273
+ granularities: d.granularities ?? [],
1274
+ primaryTime: d.primaryTime ?? false,
1275
+ tags: d.tags ?? [],
1276
+ owner: d.owner ?? null,
1277
+ typeParams: d.typeParams ?? null,
1278
+ source: d.source ?? null,
1279
+ }));
1280
+ const entities = semanticLayer.listEntities().map((e) => ({
1281
+ name: e.name,
1282
+ label: e.label,
1283
+ description: e.description,
1284
+ domain: e.domain,
1285
+ type: e.type,
1286
+ expr: e.expr ?? null,
1287
+ table: e.table,
1288
+ cube: e.cube ?? null,
1289
+ role: e.role ?? null,
1290
+ tags: e.tags ?? [],
1291
+ owner: e.owner ?? null,
1292
+ source: e.source ?? null,
1096
1293
  }));
1097
1294
  const hierarchies = semanticLayer.listHierarchies().map((h) => ({
1098
1295
  name: h.name,
@@ -1101,14 +1298,61 @@ export async function startLocalServer(opts) {
1101
1298
  domain: h.domain,
1102
1299
  levels: h.levels.map((l) => ({ name: l.name, label: l.label })),
1103
1300
  }));
1301
+ const semanticModels = semanticLayer.listSemanticModels().map((m) => ({
1302
+ name: m.name,
1303
+ label: m.label,
1304
+ description: m.description,
1305
+ domain: m.domain,
1306
+ model: m.model ?? null,
1307
+ table: m.table,
1308
+ entities: m.entities,
1309
+ measures: m.measures,
1310
+ dimensions: m.dimensions,
1311
+ timeDimensions: m.timeDimensions,
1312
+ tags: m.tags ?? [],
1313
+ owner: m.owner ?? null,
1314
+ source: m.source ?? null,
1315
+ }));
1316
+ const savedQueries = semanticLayer.listSavedQueries().map((q) => ({
1317
+ name: q.name,
1318
+ label: q.label,
1319
+ description: q.description,
1320
+ domain: q.domain,
1321
+ metrics: q.metrics,
1322
+ dimensions: q.dimensions,
1323
+ timeDimension: q.timeDimension ?? null,
1324
+ granularity: q.granularity ?? null,
1325
+ filters: q.filters ?? null,
1326
+ tags: q.tags ?? [],
1327
+ owner: q.owner ?? null,
1328
+ source: q.source ?? null,
1329
+ }));
1330
+ const provider = projectConfig.semanticLayer?.provider ?? semanticDetectedProvider ?? 'dql';
1331
+ const dbtExecutionReady = provider === 'dbt'
1332
+ ? hasDbtSemanticManifest(projectRoot, projectConfig.semanticLayer?.projectPath)
1333
+ : false;
1104
1334
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1105
1335
  res.end(serializeJSON({
1106
1336
  available: true,
1107
- provider: projectConfig.semanticLayer?.provider ?? semanticDetectedProvider ?? 'dql',
1337
+ provider,
1338
+ execution: provider === 'dbt'
1339
+ ? {
1340
+ engine: 'metricflow',
1341
+ ready: dbtExecutionReady,
1342
+ setup: dbtExecutionReady
1343
+ ? null
1344
+ : 'Run `dbt parse` or `dbt build` so target/semantic_manifest.json exists, and install MetricFlow so `mf` is on PATH.',
1345
+ }
1346
+ : { engine: 'native', ready: true, setup: null },
1108
1347
  errors: semanticLayerErrors,
1109
1348
  metrics,
1349
+ measures,
1110
1350
  dimensions,
1351
+ timeDimensions,
1352
+ entities,
1111
1353
  hierarchies,
1354
+ semanticModels,
1355
+ savedQueries,
1112
1356
  domains: semanticLayer.listDomains(),
1113
1357
  tags: semanticLayer.listTags(),
1114
1358
  favorites: userPrefs.favorites,
@@ -1329,7 +1573,7 @@ export async function startLocalServer(opts) {
1329
1573
  if (req.method === 'GET' && path === '/api/semantic-layer/search') {
1330
1574
  if (!semanticLayer) {
1331
1575
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1332
- res.end(serializeJSON({ metrics: [], dimensions: [], hierarchies: [] }));
1576
+ res.end(serializeJSON({ metrics: [], measures: [], dimensions: [], timeDimensions: [], entities: [], hierarchies: [], semanticModels: [], savedQueries: [] }));
1333
1577
  return;
1334
1578
  }
1335
1579
  const q = url.searchParams.get('q') ?? '';
@@ -1339,7 +1583,7 @@ export async function startLocalServer(opts) {
1339
1583
  const results = semanticLayer.searchAdvanced(q, {
1340
1584
  domains: domain ? [domain] : undefined,
1341
1585
  tags: tag ? [tag] : undefined,
1342
- types: type === 'metric' || type === 'dimension' || type === 'hierarchy' ? [type] : undefined,
1586
+ types: ['metric', 'measure', 'dimension', 'hierarchy', 'entity', 'semantic_model', 'saved_query'].includes(type) ? [type] : undefined,
1343
1587
  });
1344
1588
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1345
1589
  res.end(serializeJSON({
@@ -1354,6 +1598,18 @@ export async function startLocalServer(opts) {
1354
1598
  tags: m.tags ?? [],
1355
1599
  owner: m.owner ?? null,
1356
1600
  })),
1601
+ measures: results.measures.map((m) => ({
1602
+ name: m.name,
1603
+ label: m.label,
1604
+ description: m.description,
1605
+ domain: m.domain,
1606
+ agg: m.agg,
1607
+ expr: m.expr,
1608
+ table: m.table,
1609
+ cube: m.cube,
1610
+ tags: m.tags ?? [],
1611
+ owner: m.owner ?? null,
1612
+ })),
1357
1613
  dimensions: results.dimensions.map((d) => ({
1358
1614
  name: d.name,
1359
1615
  label: d.label,
@@ -1365,6 +1621,27 @@ export async function startLocalServer(opts) {
1365
1621
  tags: d.tags ?? [],
1366
1622
  owner: d.owner ?? null,
1367
1623
  })),
1624
+ timeDimensions: semanticLayer.listTimeDimensions().filter((d) => results.dimensions.some((dim) => dim.name === d.name)).map((d) => ({
1625
+ name: d.name,
1626
+ label: d.label,
1627
+ description: d.description,
1628
+ domain: d.domain,
1629
+ sql: d.sql,
1630
+ type: d.type,
1631
+ table: d.table,
1632
+ tags: d.tags ?? [],
1633
+ owner: d.owner ?? null,
1634
+ })),
1635
+ entities: results.entities.map((e) => ({
1636
+ name: e.name,
1637
+ label: e.label,
1638
+ description: e.description,
1639
+ domain: e.domain,
1640
+ type: e.type,
1641
+ table: e.table,
1642
+ tags: e.tags ?? [],
1643
+ owner: e.owner ?? null,
1644
+ })),
1368
1645
  hierarchies: results.hierarchies.map((h) => ({
1369
1646
  name: h.name,
1370
1647
  label: h.label,
@@ -1372,6 +1649,24 @@ export async function startLocalServer(opts) {
1372
1649
  domain: h.domain,
1373
1650
  levels: h.levels.map((l) => ({ name: l.name, label: l.label })),
1374
1651
  })),
1652
+ semanticModels: results.semanticModels.map((m) => ({
1653
+ name: m.name,
1654
+ label: m.label,
1655
+ description: m.description,
1656
+ domain: m.domain,
1657
+ table: m.table,
1658
+ measures: m.measures,
1659
+ dimensions: m.dimensions,
1660
+ timeDimensions: m.timeDimensions,
1661
+ })),
1662
+ savedQueries: results.savedQueries.map((q) => ({
1663
+ name: q.name,
1664
+ label: q.label,
1665
+ description: q.description,
1666
+ domain: q.domain,
1667
+ metrics: q.metrics,
1668
+ dimensions: q.dimensions,
1669
+ })),
1375
1670
  }));
1376
1671
  return;
1377
1672
  }
@@ -1538,7 +1833,7 @@ export async function startLocalServer(opts) {
1538
1833
  return;
1539
1834
  }
1540
1835
  const { provider, messages, upstream } = body;
1541
- const runner = provider === 'claude-agent-sdk' || provider === 'claude-code' ? getLLMRunner(provider) : null;
1836
+ const runner = isLLMProviderId(provider) ? getLLMRunner(provider) : null;
1542
1837
  if (!runner) {
1543
1838
  res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1544
1839
  res.end(serializeJSON({ error: `Unknown provider: ${provider}` }));
@@ -1587,7 +1882,10 @@ export async function startLocalServer(opts) {
1587
1882
  return;
1588
1883
  }
1589
1884
  const prepared = prepareLocalExecution(semantic.sql, isConnectionConfig(body.connection) ? body.connection : connection, projectRoot, projectConfig);
1590
- const result = await executor.executeQuery(prepared.sql, Array.isArray(body.sqlParams) ? body.sqlParams : [], body.variables && typeof body.variables === 'object' ? body.variables : {}, prepared.connection);
1885
+ const app = loadRuntimeApp(projectRoot, typeof body.appId === 'string' ? body.appId : activePersonaAppId());
1886
+ const domain = typeof body.domain === 'string' ? body.domain : app?.domain;
1887
+ assertAppAccess({ app, domain, level: 'execute' });
1888
+ const result = await executor.executeQuery(prepared.sql, Array.isArray(body.sqlParams) ? body.sqlParams : [], runtimeVariables(body.variables && typeof body.variables === 'object' ? body.variables : {}), prepared.connection);
1591
1889
  const payload = serializeJSON(normalizeQueryResult(result, semantic.semanticRefs));
1592
1890
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1593
1891
  res.end(payload);
@@ -1597,6 +1895,16 @@ export async function startLocalServer(opts) {
1597
1895
  res.end();
1598
1896
  return;
1599
1897
  }
1898
+ if (error instanceof DQLAccessDeniedError) {
1899
+ res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
1900
+ res.end(serializeJSON({
1901
+ columns: [],
1902
+ rows: [],
1903
+ error: error.message,
1904
+ code: 'unauthorized',
1905
+ }));
1906
+ return;
1907
+ }
1600
1908
  res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1601
1909
  res.end(serializeJSON({
1602
1910
  columns: [],
@@ -1615,7 +1923,7 @@ export async function startLocalServer(opts) {
1615
1923
  return;
1616
1924
  }
1617
1925
  const body = await readJSON(req);
1618
- const { metrics = [], dimensions = [], filters = [], limit, timeDimension, orderBy } = body;
1926
+ const { metrics = [], dimensions = [], filters = [], limit, timeDimension, orderBy, savedQuery, engine } = body;
1619
1927
  // Resolve which connection to use — request can override default
1620
1928
  const targetConnection = isConnectionConfig(body.connection) ? body.connection : connection;
1621
1929
  const driver = targetConnection.driver;
@@ -1649,7 +1957,22 @@ export async function startLocalServer(opts) {
1649
1957
  catch {
1650
1958
  // Non-fatal: proceed without table mapping
1651
1959
  }
1652
- const composed = semanticLayer.composeQuery({ metrics, dimensions, filters, limit, timeDimension, orderBy, driver, tableMapping });
1960
+ const composed = composeRuntimeSemanticQuery({
1961
+ metrics,
1962
+ dimensions,
1963
+ filters,
1964
+ limit,
1965
+ timeDimension,
1966
+ orderBy,
1967
+ savedQuery,
1968
+ engine,
1969
+ }, semanticLayer, {
1970
+ projectRoot,
1971
+ projectConfig,
1972
+ detectedProvider: semanticDetectedProvider,
1973
+ driver,
1974
+ tableMapping,
1975
+ });
1653
1976
  if (!composed) {
1654
1977
  res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1655
1978
  res.end(serializeJSON({ error: `Could not compose query for metrics: [${metrics.join(', ')}]` }));
@@ -1663,12 +1986,25 @@ export async function startLocalServer(opts) {
1663
1986
  sql: composed.sql,
1664
1987
  tables: composed.tables,
1665
1988
  joins: composed.joins,
1989
+ engine: composed.engine,
1666
1990
  result: normalizeQueryResult(result),
1667
1991
  }));
1668
1992
  }
1669
1993
  catch (error) {
1670
- res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1671
- res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
1994
+ if (error instanceof DQLAccessDeniedError) {
1995
+ res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
1996
+ res.end(serializeJSON({ error: error.message, code: 'unauthorized' }));
1997
+ return;
1998
+ }
1999
+ const status = error instanceof MetricFlowUnavailableError ? 400 : 500;
2000
+ res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
2001
+ res.end(serializeJSON({
2002
+ error: error instanceof Error ? error.message : String(error),
2003
+ code: error instanceof MetricFlowUnavailableError ? 'metricflow_unavailable' : undefined,
2004
+ hint: error instanceof MetricFlowUnavailableError
2005
+ ? 'Install dbt Semantic Layer dependencies, run dbt parse/build to create target/semantic_manifest.json, then retry.'
2006
+ : undefined,
2007
+ }));
1672
2008
  }
1673
2009
  return;
1674
2010
  }
@@ -1680,7 +2016,7 @@ export async function startLocalServer(opts) {
1680
2016
  return;
1681
2017
  }
1682
2018
  const body = await readJSON(req);
1683
- const { metrics = [], dimensions = [], filters = [], limit, timeDimension, orderBy } = body;
2019
+ const { metrics = [], dimensions = [], filters = [], limit, timeDimension, orderBy, savedQuery, engine } = body;
1684
2020
  const targetConnection = isConnectionConfig(body.connection) ? body.connection : connection;
1685
2021
  const driver = targetConnection.driver;
1686
2022
  let tableMapping;
@@ -1707,7 +2043,22 @@ export async function startLocalServer(opts) {
1707
2043
  catch {
1708
2044
  tableMapping = undefined;
1709
2045
  }
1710
- const composed = semanticLayer.composeQuery({ metrics, dimensions, filters, limit, timeDimension, orderBy, driver, tableMapping });
2046
+ const composed = composeRuntimeSemanticQuery({
2047
+ metrics,
2048
+ dimensions,
2049
+ filters,
2050
+ limit,
2051
+ timeDimension,
2052
+ orderBy,
2053
+ savedQuery,
2054
+ engine,
2055
+ }, semanticLayer, {
2056
+ projectRoot,
2057
+ projectConfig,
2058
+ detectedProvider: semanticDetectedProvider,
2059
+ driver,
2060
+ tableMapping,
2061
+ });
1711
2062
  if (!composed) {
1712
2063
  res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1713
2064
  res.end(serializeJSON({ error: 'Could not compose semantic block preview SQL.' }));
@@ -1720,12 +2071,20 @@ export async function startLocalServer(opts) {
1720
2071
  sql: composed.sql,
1721
2072
  joins: composed.joins,
1722
2073
  tables: composed.tables,
2074
+ engine: composed.engine,
1723
2075
  result: normalizeQueryResult(result),
1724
2076
  }));
1725
2077
  }
1726
2078
  catch (error) {
1727
- res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1728
- res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
2079
+ const status = error instanceof MetricFlowUnavailableError ? 400 : 500;
2080
+ res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
2081
+ res.end(serializeJSON({
2082
+ error: error instanceof Error ? error.message : String(error),
2083
+ code: error instanceof MetricFlowUnavailableError ? 'metricflow_unavailable' : undefined,
2084
+ hint: error instanceof MetricFlowUnavailableError
2085
+ ? 'Install dbt Semantic Layer dependencies, run dbt parse/build to create target/semantic_manifest.json, then retry.'
2086
+ : undefined,
2087
+ }));
1729
2088
  }
1730
2089
  return;
1731
2090
  }
@@ -1737,18 +2096,23 @@ export async function startLocalServer(opts) {
1737
2096
  return;
1738
2097
  }
1739
2098
  const body = await readJSON(req);
1740
- const { name, domain, description, owner, tags, metrics = [], dimensions = [], timeDimension, filters = [], chart = 'table', blockType = 'semantic', } = body;
2099
+ const { name, domain, description, owner, tags, metrics = [], dimensions = [], timeDimension, filters = [], chart = 'table', blockType = 'semantic', engine, } = body;
1741
2100
  if (!name || metrics.length === 0) {
1742
2101
  res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1743
2102
  res.end(serializeJSON({ error: 'name and at least one metric are required.' }));
1744
2103
  return;
1745
2104
  }
1746
2105
  const targetConnection = isConnectionConfig(body.connection) ? body.connection : connection;
1747
- const composed = semanticLayer.composeQuery({
2106
+ const composed = composeRuntimeSemanticQuery({
1748
2107
  metrics,
1749
2108
  dimensions,
1750
2109
  filters,
1751
2110
  timeDimension,
2111
+ engine,
2112
+ }, semanticLayer, {
2113
+ projectRoot,
2114
+ projectConfig,
2115
+ detectedProvider: semanticDetectedProvider,
1752
2116
  driver: targetConnection.driver,
1753
2117
  });
1754
2118
  if (!composed) {
@@ -1780,8 +2144,15 @@ export async function startLocalServer(opts) {
1780
2144
  res.end(serializeJSON({ error: 'Block already exists' }));
1781
2145
  return;
1782
2146
  }
1783
- res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1784
- res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
2147
+ const status = error instanceof MetricFlowUnavailableError ? 400 : 500;
2148
+ res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
2149
+ res.end(serializeJSON({
2150
+ error: error instanceof Error ? error.message : String(error),
2151
+ code: error instanceof MetricFlowUnavailableError ? 'metricflow_unavailable' : undefined,
2152
+ hint: error instanceof MetricFlowUnavailableError
2153
+ ? 'Install dbt Semantic Layer dependencies, run dbt parse/build to create target/semantic_manifest.json, then retry.'
2154
+ : undefined,
2155
+ }));
1785
2156
  }
1786
2157
  return;
1787
2158
  }
@@ -2041,7 +2412,9 @@ export async function startLocalServer(opts) {
2041
2412
  return;
2042
2413
  }
2043
2414
  const prepared = prepareLocalExecution(plan.sql, isConnectionConfig(body.connection) ? body.connection : connection, projectRoot, projectConfig);
2044
- const rawResult = await executor.executeQuery(prepared.sql, plan.sqlParams, plan.variables, prepared.connection);
2415
+ const app = loadRuntimeApp(projectRoot, typeof body.appId === 'string' ? body.appId : activePersonaAppId());
2416
+ assertAppAccess({ app, domain: app?.domain, level: 'execute' });
2417
+ const rawResult = await executor.executeQuery(prepared.sql, plan.sqlParams, runtimeVariables(plan.variables), prepared.connection);
2045
2418
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
2046
2419
  res.end(serializeJSON({
2047
2420
  cellType: cell.type,
@@ -2171,6 +2544,34 @@ function normalizeQueryResult(result, semanticRefs) {
2171
2544
  ...(hasRefs ? { semanticRefs } : {}),
2172
2545
  };
2173
2546
  }
2547
+ function isLLMProviderId(value) {
2548
+ return value === 'claude-agent-sdk'
2549
+ || value === 'claude-code'
2550
+ || value === 'openai'
2551
+ || value === 'gemini'
2552
+ || value === 'ollama';
2553
+ }
2554
+ function loadAppDashboard(projectRoot, appId, dashboardId) {
2555
+ for (const p of findAppDocuments(projectRoot)) {
2556
+ const { document: app } = loadAppDocument(p);
2557
+ if (!app || app.id !== appId)
2558
+ continue;
2559
+ const appDir = p.slice(0, -'/dql.app.json'.length);
2560
+ for (const d of findDashboardsForApp(appDir)) {
2561
+ const { document: dashboard } = loadDashboardDocument(d);
2562
+ if (dashboard?.id === dashboardId)
2563
+ return { app, dashboard };
2564
+ }
2565
+ }
2566
+ return null;
2567
+ }
2568
+ function resolveDashboardItemBlock(item, manifest) {
2569
+ if (isBlockIdRef(item.block)) {
2570
+ return manifest.blocks[item.block.blockId] ?? null;
2571
+ }
2572
+ const normalizedRef = normalize(item.block.ref).replaceAll('\\', '/');
2573
+ return Object.values(manifest.blocks).find((b) => normalize(b.filePath).replaceAll('\\', '/') === normalizedRef) ?? null;
2574
+ }
2174
2575
  export function serializeJSON(value) {
2175
2576
  return JSON.stringify(value, (_key, current) => {
2176
2577
  if (typeof current === 'bigint') {
@@ -2679,6 +3080,46 @@ function buildSemanticTableMapping(semanticLayer, rows) {
2679
3080
  }
2680
3081
  return Object.keys(tableMapping).length > 0 ? tableMapping : undefined;
2681
3082
  }
3083
+ function isDbtSemanticRuntime(projectConfig, detectedProvider, semanticLayer) {
3084
+ if (projectConfig.semanticLayer?.provider === 'dbt' || detectedProvider === 'dbt')
3085
+ return true;
3086
+ return Boolean(semanticLayer?.listMetrics().some((metric) => metric.source?.provider === 'dbt'));
3087
+ }
3088
+ function composeRuntimeSemanticQuery(request, semanticLayer, context) {
3089
+ const useMetricFlow = request.engine === 'metricflow' || (request.engine !== 'native' &&
3090
+ isDbtSemanticRuntime(context.projectConfig, context.detectedProvider, semanticLayer));
3091
+ if (useMetricFlow) {
3092
+ const dbtProjectPath = context.projectConfig.semanticLayer?.projectPath;
3093
+ const compiled = compileMetricFlowQuery({
3094
+ projectRoot: context.projectRoot,
3095
+ dbtProjectPath,
3096
+ metrics: request.metrics,
3097
+ dimensions: request.dimensions,
3098
+ filters: request.filters,
3099
+ timeDimension: request.timeDimension,
3100
+ orderBy: request.orderBy,
3101
+ limit: request.limit,
3102
+ savedQuery: request.savedQuery,
3103
+ });
3104
+ return {
3105
+ sql: compiled.sql,
3106
+ joins: [],
3107
+ tables: [],
3108
+ engine: 'metricflow',
3109
+ };
3110
+ }
3111
+ const composed = semanticLayer.composeQuery({
3112
+ metrics: request.metrics,
3113
+ dimensions: request.dimensions,
3114
+ filters: request.filters,
3115
+ limit: request.limit,
3116
+ timeDimension: request.timeDimension,
3117
+ orderBy: request.orderBy,
3118
+ driver: context.driver,
3119
+ tableMapping: context.tableMapping,
3120
+ });
3121
+ return composed ? { ...composed, engine: 'native' } : null;
3122
+ }
2682
3123
  function composeSemanticBlockSql(source, semanticLayer, options) {
2683
3124
  const config = parseSemanticBlockConfig(source);
2684
3125
  const metrics = config.metrics.length > 0
@@ -2721,16 +3162,42 @@ function composeSemanticBlockSql(source, semanticLayer, options) {
2721
3162
  if (diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
2722
3163
  return { sql: null, diagnostics, semanticRefs };
2723
3164
  }
2724
- const composed = semanticLayer.composeQuery({
2725
- metrics,
2726
- dimensions: config.dimensions,
2727
- timeDimension: config.timeDimension && config.granularity
2728
- ? { name: config.timeDimension, granularity: config.granularity }
2729
- : undefined,
2730
- limit: config.limit,
2731
- driver: options?.driver,
2732
- tableMapping: options?.tableMapping,
2733
- });
3165
+ let composed;
3166
+ try {
3167
+ composed = options?.projectRoot && options.projectConfig
3168
+ ? composeRuntimeSemanticQuery({
3169
+ metrics,
3170
+ dimensions: config.dimensions,
3171
+ timeDimension: config.timeDimension && config.granularity
3172
+ ? { name: config.timeDimension, granularity: config.granularity }
3173
+ : undefined,
3174
+ limit: config.limit,
3175
+ }, semanticLayer, {
3176
+ projectRoot: options.projectRoot,
3177
+ projectConfig: options.projectConfig,
3178
+ detectedProvider: options.detectedProvider ?? null,
3179
+ driver: options.driver,
3180
+ tableMapping: options.tableMapping,
3181
+ })
3182
+ : semanticLayer.composeQuery({
3183
+ metrics,
3184
+ dimensions: config.dimensions,
3185
+ timeDimension: config.timeDimension && config.granularity
3186
+ ? { name: config.timeDimension, granularity: config.granularity }
3187
+ : undefined,
3188
+ limit: config.limit,
3189
+ driver: options?.driver,
3190
+ tableMapping: options?.tableMapping,
3191
+ });
3192
+ }
3193
+ catch (error) {
3194
+ diagnostics.push({
3195
+ severity: 'error',
3196
+ code: error instanceof MetricFlowUnavailableError ? 'metricflow_unavailable' : 'semantic_compose_failed',
3197
+ message: error instanceof Error ? error.message : String(error),
3198
+ });
3199
+ return { sql: null, diagnostics, semanticRefs };
3200
+ }
2734
3201
  if (!composed) {
2735
3202
  diagnostics.push({
2736
3203
  severity: 'error',
@@ -3833,4 +4300,58 @@ function computeSemanticDiff(filePath, before, after) {
3833
4300
  return null;
3834
4301
  }
3835
4302
  }
4303
+ function collectSettingsEnvStatus() {
4304
+ const v = (key, label, description, optional = true) => ({
4305
+ key,
4306
+ label,
4307
+ description,
4308
+ optional,
4309
+ present: typeof process.env[key] === 'string' && process.env[key].trim().length > 0,
4310
+ });
4311
+ return [
4312
+ {
4313
+ id: 'ai',
4314
+ title: 'AI Chat Providers',
4315
+ description: 'Configure one or more providers. Missing keys are only a problem when that provider is selected.',
4316
+ vars: [
4317
+ v('ANTHROPIC_API_KEY', 'Claude Agent SDK', 'Hosted Claude provider for notebook Chat and agent commands.'),
4318
+ v('OPENAI_API_KEY', 'OpenAI', 'Hosted OpenAI provider for notebook Chat and agent commands.'),
4319
+ v('OPENAI_MODEL', 'OpenAI model', 'Optional override such as gpt-4.1-mini or the model your account uses.'),
4320
+ v('GEMINI_API_KEY', 'Gemini', 'Hosted Gemini provider for notebook Chat and agent commands.'),
4321
+ v('GEMINI_MODEL', 'Gemini model', 'Optional Gemini model override.'),
4322
+ v('OLLAMA_BASE_URL', 'Ollama base URL', 'Local Ollama HTTP endpoint. Docker defaults to http://ollama:11434.'),
4323
+ v('OLLAMA_MODEL', 'Ollama model', 'Optional local model name such as llama3.1.'),
4324
+ ],
4325
+ },
4326
+ {
4327
+ id: 'slack',
4328
+ title: 'Slack',
4329
+ description: 'Use webhooks for scheduled App deliveries, or bot credentials for the Slack chat front-end.',
4330
+ vars: [
4331
+ v('DQL_SLACK_WEBHOOK', 'Schedule webhook', 'Incoming webhook used by App schedules that deliver to Slack.'),
4332
+ v('SLACK_SIGNING_SECRET', 'Slack signing secret', 'Required only when running `dql slack serve`.'),
4333
+ v('SLACK_BOT_TOKEN', 'Slack bot token', 'Bot token used by Slack chat commands when `dql slack serve` is enabled.'),
4334
+ ],
4335
+ },
4336
+ {
4337
+ id: 'email',
4338
+ title: 'Email',
4339
+ description: 'SMTP is optional. Without it, email schedules stay in stub mode with a clear delivery message.',
4340
+ vars: [
4341
+ v('DQL_SMTP_URL', 'SMTP URL', 'SMTP connection URL for email schedule delivery.'),
4342
+ v('DQL_SMTP_FROM', 'SMTP sender', 'Optional sender address for scheduled emails.'),
4343
+ ],
4344
+ },
4345
+ {
4346
+ id: 'runtime',
4347
+ title: 'Runtime',
4348
+ description: 'Local server and runtime toggles used by Docker and native notebook sessions.',
4349
+ vars: [
4350
+ v('DQL_HOST', 'Notebook bind host', 'Host interface for the local notebook server. Docker uses 0.0.0.0 inside the container.'),
4351
+ v('DQL_RUNTIME_URL', 'Runtime URL', 'Optional URL used by headless agent commands to call an existing runtime.'),
4352
+ v('DQL_LLM_KEY', 'Legacy LLM key', 'Fallback key accepted by older Claude provider configurations.'),
4353
+ ],
4354
+ },
4355
+ ];
4356
+ }
3836
4357
  //# sourceMappingURL=local-runtime.js.map