@duckcodeailabs/dql-cli 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +123 -0
- package/README.md +72 -0
- package/{apps-api.d.ts → dist/apps-api.d.ts} +2 -0
- package/dist/apps-api.d.ts.map +1 -0
- package/{apps-api.js → dist/apps-api.js} +326 -4
- package/dist/apps-api.js.map +1 -0
- package/{args.test.js → dist/args.test.js} +8 -0
- package/{args.test.js.map → dist/args.test.js.map} +1 -1
- package/dist/assets/dql-notebook/assets/index-R3UrqjLQ.css +1 -0
- package/dist/assets/dql-notebook/assets/index-mlfOQ2me.js +857 -0
- package/{assets → dist/assets}/dql-notebook/index.html +2 -2
- package/dist/block-studio-import.d.ts +58 -0
- package/dist/block-studio-import.d.ts.map +1 -0
- package/dist/block-studio-import.js +390 -0
- package/dist/block-studio-import.js.map +1 -0
- package/dist/block-studio-import.test.d.ts +2 -0
- package/dist/block-studio-import.test.d.ts.map +1 -0
- package/dist/block-studio-import.test.js +106 -0
- package/dist/block-studio-import.test.js.map +1 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/{commands → dist/commands}/agent.js +98 -5
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/import.d.ts +3 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +50 -0
- package/dist/commands/import.js.map +1 -0
- package/{commands → dist/commands}/migrate.d.ts.map +1 -1
- package/{commands → dist/commands}/migrate.js +5 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +163 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/validate.test.d.ts +2 -0
- package/dist/commands/validate.test.d.ts.map +1 -0
- package/dist/commands/validate.test.js +55 -0
- package/dist/commands/validate.test.js.map +1 -0
- package/{index.js → dist/index.js} +5 -0
- package/dist/index.js.map +1 -0
- package/{llm → dist/llm}/index.d.ts.map +1 -1
- package/{llm → dist/llm}/index.js +4 -3
- package/{llm → dist/llm}/index.js.map +1 -1
- package/{llm → dist/llm}/providers/dql-agent-provider.d.ts +1 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -0
- package/dist/llm/providers/dql-agent-provider.js +287 -0
- package/dist/llm/providers/dql-agent-provider.js.map +1 -0
- package/{llm → dist/llm}/types.d.ts +3 -1
- package/dist/llm/types.d.ts.map +1 -0
- package/{local-runtime.d.ts.map → dist/local-runtime.d.ts.map} +1 -1
- package/{local-runtime.js → dist/local-runtime.js} +551 -49
- package/dist/local-runtime.js.map +1 -0
- package/{schedule → dist/schedule}/runner.d.ts.map +1 -1
- package/{schedule → dist/schedule}/runner.js +4 -0
- package/{schedule → dist/schedule}/runner.js.map +1 -1
- package/dist/settings/provider-settings.d.ts +33 -0
- package/dist/settings/provider-settings.d.ts.map +1 -0
- package/dist/settings/provider-settings.js +91 -0
- package/dist/settings/provider-settings.js.map +1 -0
- package/package.json +31 -20
- package/apps-api.d.ts.map +0 -1
- package/apps-api.js.map +0 -1
- package/assets/dql-notebook/assets/index-DUTeFz5j.js +0 -858
- package/assets/dql-notebook/assets/index-DrhoZmtv.css +0 -1
- package/commands/agent.d.ts.map +0 -1
- package/commands/agent.js.map +0 -1
- package/commands/migrate.js.map +0 -1
- package/commands/validate.d.ts.map +0 -1
- package/commands/validate.js +0 -116
- package/commands/validate.js.map +0 -1
- package/index.js.map +0 -1
- package/llm/providers/dql-agent-provider.d.ts.map +0 -1
- package/llm/providers/dql-agent-provider.js +0 -99
- package/llm/providers/dql-agent-provider.js.map +0 -1
- package/llm/types.d.ts.map +0 -1
- package/local-runtime.js.map +0 -1
- /package/{apps-api.test.d.ts → dist/apps-api.test.d.ts} +0 -0
- /package/{apps-api.test.d.ts.map → dist/apps-api.test.d.ts.map} +0 -0
- /package/{apps-api.test.js → dist/apps-api.test.js} +0 -0
- /package/{apps-api.test.js.map → dist/apps-api.test.js.map} +0 -0
- /package/{args.d.ts → dist/args.d.ts} +0 -0
- /package/{args.d.ts.map → dist/args.d.ts.map} +0 -0
- /package/{args.js → dist/args.js} +0 -0
- /package/{args.js.map → dist/args.js.map} +0 -0
- /package/{args.test.d.ts → dist/args.test.d.ts} +0 -0
- /package/{args.test.d.ts.map → dist/args.test.d.ts.map} +0 -0
- /package/{assets → dist/assets}/dql-notebook/assets/codemirror-DJYUkPr1.js +0 -0
- /package/{assets → dist/assets}/dql-notebook/assets/react-CRB3T2We.js +0 -0
- /package/{assets → dist/assets}/notebook-browser/app.js +0 -0
- /package/{assets → dist/assets}/notebook-browser/index.html +0 -0
- /package/{assets → dist/assets}/notebook-browser/styles.css +0 -0
- /package/{block-templates.d.ts → dist/block-templates.d.ts} +0 -0
- /package/{block-templates.d.ts.map → dist/block-templates.d.ts.map} +0 -0
- /package/{block-templates.js → dist/block-templates.js} +0 -0
- /package/{block-templates.js.map → dist/block-templates.js.map} +0 -0
- /package/{commands → dist/commands}/agent.d.ts +0 -0
- /package/{commands → dist/commands}/app.d.ts +0 -0
- /package/{commands → dist/commands}/app.d.ts.map +0 -0
- /package/{commands → dist/commands}/app.js +0 -0
- /package/{commands → dist/commands}/app.js.map +0 -0
- /package/{commands → dist/commands}/build.d.ts +0 -0
- /package/{commands → dist/commands}/build.d.ts.map +0 -0
- /package/{commands → dist/commands}/build.js +0 -0
- /package/{commands → dist/commands}/build.js.map +0 -0
- /package/{commands → dist/commands}/build.test.d.ts +0 -0
- /package/{commands → dist/commands}/build.test.d.ts.map +0 -0
- /package/{commands → dist/commands}/build.test.js +0 -0
- /package/{commands → dist/commands}/build.test.js.map +0 -0
- /package/{commands → dist/commands}/certify.d.ts +0 -0
- /package/{commands → dist/commands}/certify.d.ts.map +0 -0
- /package/{commands → dist/commands}/certify.js +0 -0
- /package/{commands → dist/commands}/certify.js.map +0 -0
- /package/{commands → dist/commands}/compile.d.ts +0 -0
- /package/{commands → dist/commands}/compile.d.ts.map +0 -0
- /package/{commands → dist/commands}/compile.js +0 -0
- /package/{commands → dist/commands}/compile.js.map +0 -0
- /package/{commands → dist/commands}/compile.test.d.ts +0 -0
- /package/{commands → dist/commands}/compile.test.d.ts.map +0 -0
- /package/{commands → dist/commands}/compile.test.js +0 -0
- /package/{commands → dist/commands}/compile.test.js.map +0 -0
- /package/{commands → dist/commands}/diff.d.ts +0 -0
- /package/{commands → dist/commands}/diff.d.ts.map +0 -0
- /package/{commands → dist/commands}/diff.js +0 -0
- /package/{commands → dist/commands}/diff.js.map +0 -0
- /package/{commands → dist/commands}/doctor.d.ts +0 -0
- /package/{commands → dist/commands}/doctor.d.ts.map +0 -0
- /package/{commands → dist/commands}/doctor.js +0 -0
- /package/{commands → dist/commands}/doctor.js.map +0 -0
- /package/{commands → dist/commands}/doctor.test.d.ts +0 -0
- /package/{commands → dist/commands}/doctor.test.d.ts.map +0 -0
- /package/{commands → dist/commands}/doctor.test.js +0 -0
- /package/{commands → dist/commands}/doctor.test.js.map +0 -0
- /package/{commands → dist/commands}/fmt.d.ts +0 -0
- /package/{commands → dist/commands}/fmt.d.ts.map +0 -0
- /package/{commands → dist/commands}/fmt.js +0 -0
- /package/{commands → dist/commands}/fmt.js.map +0 -0
- /package/{commands → dist/commands}/info.d.ts +0 -0
- /package/{commands → dist/commands}/info.d.ts.map +0 -0
- /package/{commands → dist/commands}/info.js +0 -0
- /package/{commands → dist/commands}/info.js.map +0 -0
- /package/{commands → dist/commands}/init.d.ts +0 -0
- /package/{commands → dist/commands}/init.d.ts.map +0 -0
- /package/{commands → dist/commands}/init.js +0 -0
- /package/{commands → dist/commands}/init.js.map +0 -0
- /package/{commands → dist/commands}/init.test.d.ts +0 -0
- /package/{commands → dist/commands}/init.test.d.ts.map +0 -0
- /package/{commands → dist/commands}/init.test.js +0 -0
- /package/{commands → dist/commands}/init.test.js.map +0 -0
- /package/{commands → dist/commands}/lineage.d.ts +0 -0
- /package/{commands → dist/commands}/lineage.d.ts.map +0 -0
- /package/{commands → dist/commands}/lineage.js +0 -0
- /package/{commands → dist/commands}/lineage.js.map +0 -0
- /package/{commands → dist/commands}/mcp.d.ts +0 -0
- /package/{commands → dist/commands}/mcp.d.ts.map +0 -0
- /package/{commands → dist/commands}/mcp.js +0 -0
- /package/{commands → dist/commands}/mcp.js.map +0 -0
- /package/{commands → dist/commands}/migrate.d.ts +0 -0
- /package/{commands → dist/commands}/new.d.ts +0 -0
- /package/{commands → dist/commands}/new.d.ts.map +0 -0
- /package/{commands → dist/commands}/new.js +0 -0
- /package/{commands → dist/commands}/new.js.map +0 -0
- /package/{commands → dist/commands}/new.test.d.ts +0 -0
- /package/{commands → dist/commands}/new.test.d.ts.map +0 -0
- /package/{commands → dist/commands}/new.test.js +0 -0
- /package/{commands → dist/commands}/new.test.js.map +0 -0
- /package/{commands → dist/commands}/notebook.d.ts +0 -0
- /package/{commands → dist/commands}/notebook.d.ts.map +0 -0
- /package/{commands → dist/commands}/notebook.js +0 -0
- /package/{commands → dist/commands}/notebook.js.map +0 -0
- /package/{commands → dist/commands}/parse.d.ts +0 -0
- /package/{commands → dist/commands}/parse.d.ts.map +0 -0
- /package/{commands → dist/commands}/parse.js +0 -0
- /package/{commands → dist/commands}/parse.js.map +0 -0
- /package/{commands → dist/commands}/preview.d.ts +0 -0
- /package/{commands → dist/commands}/preview.d.ts.map +0 -0
- /package/{commands → dist/commands}/preview.js +0 -0
- /package/{commands → dist/commands}/preview.js.map +0 -0
- /package/{commands → dist/commands}/schedule.d.ts +0 -0
- /package/{commands → dist/commands}/schedule.d.ts.map +0 -0
- /package/{commands → dist/commands}/schedule.js +0 -0
- /package/{commands → dist/commands}/schedule.js.map +0 -0
- /package/{commands → dist/commands}/semantic.d.ts +0 -0
- /package/{commands → dist/commands}/semantic.d.ts.map +0 -0
- /package/{commands → dist/commands}/semantic.js +0 -0
- /package/{commands → dist/commands}/semantic.js.map +0 -0
- /package/{commands → dist/commands}/serve.d.ts +0 -0
- /package/{commands → dist/commands}/serve.d.ts.map +0 -0
- /package/{commands → dist/commands}/serve.js +0 -0
- /package/{commands → dist/commands}/serve.js.map +0 -0
- /package/{commands → dist/commands}/slack.d.ts +0 -0
- /package/{commands → dist/commands}/slack.d.ts.map +0 -0
- /package/{commands → dist/commands}/slack.js +0 -0
- /package/{commands → dist/commands}/slack.js.map +0 -0
- /package/{commands → dist/commands}/sync.d.ts +0 -0
- /package/{commands → dist/commands}/sync.d.ts.map +0 -0
- /package/{commands → dist/commands}/sync.js +0 -0
- /package/{commands → dist/commands}/sync.js.map +0 -0
- /package/{commands → dist/commands}/sync.test.d.ts +0 -0
- /package/{commands → dist/commands}/sync.test.d.ts.map +0 -0
- /package/{commands → dist/commands}/sync.test.js +0 -0
- /package/{commands → dist/commands}/sync.test.js.map +0 -0
- /package/{commands → dist/commands}/test.d.ts +0 -0
- /package/{commands → dist/commands}/test.d.ts.map +0 -0
- /package/{commands → dist/commands}/test.js +0 -0
- /package/{commands → dist/commands}/test.js.map +0 -0
- /package/{commands → dist/commands}/validate.d.ts +0 -0
- /package/{commands → dist/commands}/verify.d.ts +0 -0
- /package/{commands → dist/commands}/verify.d.ts.map +0 -0
- /package/{commands → dist/commands}/verify.js +0 -0
- /package/{commands → dist/commands}/verify.js.map +0 -0
- /package/{digest.d.ts → dist/digest.d.ts} +0 -0
- /package/{digest.d.ts.map → dist/digest.d.ts.map} +0 -0
- /package/{digest.js → dist/digest.js} +0 -0
- /package/{digest.js.map → dist/digest.js.map} +0 -0
- /package/{git-service.d.ts → dist/git-service.d.ts} +0 -0
- /package/{git-service.d.ts.map → dist/git-service.d.ts.map} +0 -0
- /package/{git-service.js → dist/git-service.js} +0 -0
- /package/{git-service.js.map → dist/git-service.js.map} +0 -0
- /package/{governance-runtime.d.ts → dist/governance-runtime.d.ts} +0 -0
- /package/{governance-runtime.d.ts.map → dist/governance-runtime.d.ts.map} +0 -0
- /package/{governance-runtime.js → dist/governance-runtime.js} +0 -0
- /package/{governance-runtime.js.map → dist/governance-runtime.js.map} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.d.ts.map → dist/index.d.ts.map} +0 -0
- /package/{llm → dist/llm}/index.d.ts +0 -0
- /package/{llm → dist/llm}/providers/claude-agent-sdk.d.ts +0 -0
- /package/{llm → dist/llm}/providers/claude-agent-sdk.d.ts.map +0 -0
- /package/{llm → dist/llm}/providers/claude-agent-sdk.js +0 -0
- /package/{llm → dist/llm}/providers/claude-agent-sdk.js.map +0 -0
- /package/{llm → dist/llm}/providers/claude-code.d.ts +0 -0
- /package/{llm → dist/llm}/providers/claude-code.d.ts.map +0 -0
- /package/{llm → dist/llm}/providers/claude-code.js +0 -0
- /package/{llm → dist/llm}/providers/claude-code.js.map +0 -0
- /package/{llm → dist/llm}/tools.d.ts +0 -0
- /package/{llm → dist/llm}/tools.d.ts.map +0 -0
- /package/{llm → dist/llm}/tools.js +0 -0
- /package/{llm → dist/llm}/tools.js.map +0 -0
- /package/{llm → dist/llm}/types.js +0 -0
- /package/{llm → dist/llm}/types.js.map +0 -0
- /package/{local-runtime.d.ts → dist/local-runtime.d.ts} +0 -0
- /package/{local-runtime.test.d.ts → dist/local-runtime.test.d.ts} +0 -0
- /package/{local-runtime.test.d.ts.map → dist/local-runtime.test.d.ts.map} +0 -0
- /package/{local-runtime.test.js → dist/local-runtime.test.js} +0 -0
- /package/{local-runtime.test.js.map → dist/local-runtime.test.js.map} +0 -0
- /package/{metricflow.d.ts → dist/metricflow.d.ts} +0 -0
- /package/{metricflow.d.ts.map → dist/metricflow.d.ts.map} +0 -0
- /package/{metricflow.js → dist/metricflow.js} +0 -0
- /package/{metricflow.js.map → dist/metricflow.js.map} +0 -0
- /package/{metricflow.test.d.ts → dist/metricflow.test.d.ts} +0 -0
- /package/{metricflow.test.d.ts.map → dist/metricflow.test.d.ts.map} +0 -0
- /package/{metricflow.test.js → dist/metricflow.test.js} +0 -0
- /package/{metricflow.test.js.map → dist/metricflow.test.js.map} +0 -0
- /package/{open-browser.d.ts → dist/open-browser.d.ts} +0 -0
- /package/{open-browser.d.ts.map → dist/open-browser.d.ts.map} +0 -0
- /package/{open-browser.js → dist/open-browser.js} +0 -0
- /package/{open-browser.js.map → dist/open-browser.js.map} +0 -0
- /package/{schedule → dist/schedule}/alerts.d.ts +0 -0
- /package/{schedule → dist/schedule}/alerts.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/alerts.js +0 -0
- /package/{schedule → dist/schedule}/alerts.js.map +0 -0
- /package/{schedule → dist/schedule}/discovery.d.ts +0 -0
- /package/{schedule → dist/schedule}/discovery.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/discovery.js +0 -0
- /package/{schedule → dist/schedule}/discovery.js.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/email.d.ts +0 -0
- /package/{schedule → dist/schedule}/notifiers/email.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/email.js +0 -0
- /package/{schedule → dist/schedule}/notifiers/email.js.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/file.d.ts +0 -0
- /package/{schedule → dist/schedule}/notifiers/file.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/file.js +0 -0
- /package/{schedule → dist/schedule}/notifiers/file.js.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/index.d.ts +0 -0
- /package/{schedule → dist/schedule}/notifiers/index.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/index.js +0 -0
- /package/{schedule → dist/schedule}/notifiers/index.js.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/slack.d.ts +0 -0
- /package/{schedule → dist/schedule}/notifiers/slack.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/notifiers/slack.js +0 -0
- /package/{schedule → dist/schedule}/notifiers/slack.js.map +0 -0
- /package/{schedule → dist/schedule}/runner.d.ts +0 -0
- /package/{schedule → dist/schedule}/runs.d.ts +0 -0
- /package/{schedule → dist/schedule}/runs.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/runs.js +0 -0
- /package/{schedule → dist/schedule}/runs.js.map +0 -0
- /package/{schedule → dist/schedule}/service.d.ts +0 -0
- /package/{schedule → dist/schedule}/service.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/service.js +0 -0
- /package/{schedule → dist/schedule}/service.js.map +0 -0
- /package/{schedule → dist/schedule}/types.d.ts +0 -0
- /package/{schedule → dist/schedule}/types.d.ts.map +0 -0
- /package/{schedule → dist/schedule}/types.js +0 -0
- /package/{schedule → dist/schedule}/types.js.map +0 -0
- /package/{semantic-import.d.ts → dist/semantic-import.d.ts} +0 -0
- /package/{semantic-import.d.ts.map → dist/semantic-import.d.ts.map} +0 -0
- /package/{semantic-import.js → dist/semantic-import.js} +0 -0
- /package/{semantic-import.js.map → dist/semantic-import.js.map} +0 -0
- /package/{semantic-import.test.d.ts → dist/semantic-import.test.d.ts} +0 -0
- /package/{semantic-import.test.d.ts.map → dist/semantic-import.test.d.ts.map} +0 -0
- /package/{semantic-import.test.js → dist/semantic-import.test.js} +0 -0
- /package/{semantic-import.test.js.map → dist/semantic-import.test.js.map} +0 -0
|
@@ -7,9 +7,13 @@ import { loadSemanticLayerFromDir, resolveSemanticLayerAsync, Parser, buildLinea
|
|
|
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
|
+
import { ClaudeProvider, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, defaultMemoryPath, ensureDefaultMemoryFiles, } from '@duckcodeailabs/dql-agent';
|
|
10
11
|
import { handleAppsApi } from './apps-api.js';
|
|
12
|
+
import { getEffectiveProviderConfig, listProviderSettings, saveProviderSettings, } from './settings/provider-settings.js';
|
|
11
13
|
import { DQLAccessDeniedError, activePersonaAppId, assertAppAccess, loadRuntimeApp, runtimeVariables, } from './governance-runtime.js';
|
|
14
|
+
import { LocalAppStorage, defaultLocalAppsDbPath } from '@duckcodeailabs/dql-project';
|
|
12
15
|
import { buildSemanticObjectDetail, buildSemanticTree, computeSyncDiff, loadSemanticImportManifest, performSemanticImport, previewSemanticImport, syncSemanticImport, } from './semantic-import.js';
|
|
16
|
+
import { createBlockStudioImportSession, loadBlockStudioImportSession, readBlockStudioImportCandidate, updateBlockStudioImportCandidate, writeBlockStudioImportCandidate, } from './block-studio-import.js';
|
|
13
17
|
import { MetricFlowUnavailableError, compileMetricFlowQuery, hasDbtSemanticManifest, } from './metricflow.js';
|
|
14
18
|
export async function startLocalServer(opts) {
|
|
15
19
|
const { rootDir, executor, connection: rawConnection, preferredPort, projectRoot = process.cwd() } = opts;
|
|
@@ -68,6 +72,55 @@ export async function startLocalServer(opts) {
|
|
|
68
72
|
catch { /* non-fatal */ }
|
|
69
73
|
}
|
|
70
74
|
}
|
|
75
|
+
const executeLocalSqlForStoredResult = async (sql) => {
|
|
76
|
+
const semantic = prepareSemanticSql(sql, semanticLayer);
|
|
77
|
+
if (semantic.unresolvedRefs.length > 0) {
|
|
78
|
+
throw new Error(`Unknown semantic reference${semantic.unresolvedRefs.length > 1 ? 's' : ''}: ${semantic.unresolvedRefs.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
const prepared = prepareLocalExecution(semantic.sql, connection, projectRoot, projectConfig);
|
|
81
|
+
const result = await executor.executeQuery(prepared.sql, [], runtimeVariables({}), prepared.connection);
|
|
82
|
+
return normalizeQueryResult(result, semantic.semanticRefs);
|
|
83
|
+
};
|
|
84
|
+
const executeCertifiedBlockForAgent = async (node) => {
|
|
85
|
+
if (node.kind !== 'block') {
|
|
86
|
+
throw new Error(`Certified ${node.kind} "${node.name}" is a navigation artifact and cannot be executed as a block.`);
|
|
87
|
+
}
|
|
88
|
+
const manifest = buildManifest({ projectRoot });
|
|
89
|
+
const block = manifest.blocks[node.name] ?? manifest.blocks[node.nodeId.replace(/^block:/, '')];
|
|
90
|
+
if (!block) {
|
|
91
|
+
throw new Error(`Matched block "${node.name}" is not present in the project manifest.`);
|
|
92
|
+
}
|
|
93
|
+
const absBlockPath = join(projectRoot, block.filePath);
|
|
94
|
+
const source = readFileSync(absBlockPath, 'utf-8');
|
|
95
|
+
const semanticCompose = semanticLayer
|
|
96
|
+
? composeSemanticBlockSql(source, semanticLayer, {
|
|
97
|
+
driver: connection.driver,
|
|
98
|
+
projectRoot,
|
|
99
|
+
projectConfig,
|
|
100
|
+
detectedProvider: semanticDetectedProvider,
|
|
101
|
+
})
|
|
102
|
+
: null;
|
|
103
|
+
const plan = buildExecutionPlan({ id: `agent-${block.name}`, type: 'dql', source, title: block.name }, { semanticLayer, driver: connection.driver });
|
|
104
|
+
if (!plan && !semanticCompose?.sql) {
|
|
105
|
+
const semanticError = semanticCompose?.diagnostics.find((diagnostic) => diagnostic.severity === 'error')?.message;
|
|
106
|
+
throw new Error(semanticError ?? `Block "${block.name}" produced no executable SQL.`);
|
|
107
|
+
}
|
|
108
|
+
const prepared = prepareLocalExecution(semanticCompose?.sql ?? plan.sql, connection, projectRoot, projectConfig);
|
|
109
|
+
const app = loadRuntimeApp(projectRoot, activePersonaAppId());
|
|
110
|
+
assertAppAccess({ app, domain: block.domain ?? app?.domain, level: 'execute' });
|
|
111
|
+
const rawResult = await executor.executeQuery(prepared.sql, plan?.sqlParams ?? [], runtimeVariables(plan?.variables ?? {}), prepared.connection);
|
|
112
|
+
const normalized = normalizeQueryResult(rawResult);
|
|
113
|
+
return {
|
|
114
|
+
columns: normalized.columns,
|
|
115
|
+
rows: normalized.rows,
|
|
116
|
+
rowCount: normalized.rowCount,
|
|
117
|
+
executionTime: normalized.executionTime,
|
|
118
|
+
chartConfig: plan?.chartConfig ?? (block.chartType ? { chart: block.chartType } : undefined),
|
|
119
|
+
sql: prepared.sql,
|
|
120
|
+
blockName: block.name,
|
|
121
|
+
blockPath: block.filePath,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
71
124
|
// SSE clients for /api/watch hot-reload
|
|
72
125
|
const sseClients = new Set();
|
|
73
126
|
// Watch notebooks/, workbooks/, semantic-layer/, and data/ dirs for changes
|
|
@@ -122,13 +175,56 @@ export async function startLocalServer(opts) {
|
|
|
122
175
|
catch { /* dir not watchable */ }
|
|
123
176
|
}
|
|
124
177
|
}
|
|
178
|
+
const validateImportCandidate = (candidate) => ({
|
|
179
|
+
...candidate,
|
|
180
|
+
validation: validateBlockStudioSource(candidate.dqlSource, semanticLayer),
|
|
181
|
+
});
|
|
182
|
+
const runBlockStudioPreviewSource = async (source, targetConnection = connection) => {
|
|
183
|
+
let tableMapping;
|
|
184
|
+
if (semanticLayer) {
|
|
185
|
+
try {
|
|
186
|
+
const tablesResult = await executor.executeQuery(`SELECT table_schema, table_name
|
|
187
|
+
FROM information_schema.tables
|
|
188
|
+
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')`, [], {}, targetConnection);
|
|
189
|
+
tableMapping = buildSemanticTableMapping(semanticLayer, tablesResult.rows);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
tableMapping = undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const semanticCompose = semanticLayer
|
|
196
|
+
? composeSemanticBlockSql(source, semanticLayer, {
|
|
197
|
+
driver: targetConnection.driver,
|
|
198
|
+
tableMapping,
|
|
199
|
+
projectRoot,
|
|
200
|
+
projectConfig,
|
|
201
|
+
detectedProvider: semanticDetectedProvider,
|
|
202
|
+
})
|
|
203
|
+
: null;
|
|
204
|
+
const validation = validateBlockStudioSource(source, semanticLayer);
|
|
205
|
+
const executableSql = semanticCompose?.sql ?? validation.executableSql;
|
|
206
|
+
if (!executableSql) {
|
|
207
|
+
const message = semanticCompose?.diagnostics.find((item) => item.severity === 'error')?.message
|
|
208
|
+
?? validation.diagnostics.find((item) => item.severity === 'error')?.message
|
|
209
|
+
?? 'No executable SQL found in block source.';
|
|
210
|
+
throw new Error(message);
|
|
211
|
+
}
|
|
212
|
+
const plan = buildExecutionPlan({ id: 'block-studio', type: 'dql', source, title: 'Block Studio' }, { semanticLayer, driver: targetConnection.driver });
|
|
213
|
+
const sql = resolveProjectRelativeSqlPaths(semanticCompose?.sql ?? plan?.sql ?? executableSql, projectRoot, projectConfig.dataDir);
|
|
214
|
+
const result = await executor.executeQuery(sql, plan?.sqlParams ?? [], runtimeVariables(plan?.variables ?? {}), targetConnection);
|
|
215
|
+
return {
|
|
216
|
+
sql: plan?.sql ?? executableSql,
|
|
217
|
+
result: normalizeQueryResult(result),
|
|
218
|
+
chartConfig: plan?.chartConfig ?? validation.chartConfig ?? null,
|
|
219
|
+
};
|
|
220
|
+
};
|
|
125
221
|
const server = createServer(async (req, res) => {
|
|
126
222
|
const requestUrl = req.url || '/';
|
|
127
223
|
const url = new URL(requestUrl, 'http://127.0.0.1');
|
|
128
224
|
const path = url.pathname || '/';
|
|
129
225
|
// CORS — needed for dql-notebook SPA dev mode
|
|
130
226
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
131
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS');
|
|
227
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
132
228
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
133
229
|
if (req.method === 'OPTIONS') {
|
|
134
230
|
res.writeHead(204);
|
|
@@ -145,6 +241,121 @@ export async function startLocalServer(opts) {
|
|
|
145
241
|
res.end(serializeJSON({ groups: collectSettingsEnvStatus() }));
|
|
146
242
|
return;
|
|
147
243
|
}
|
|
244
|
+
if (req.method === 'GET' && path === '/api/settings/providers') {
|
|
245
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
246
|
+
res.end(serializeJSON({ providers: listProviderSettings(projectRoot) }));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (req.method === 'POST' && path === '/api/settings/providers') {
|
|
250
|
+
try {
|
|
251
|
+
const body = await readJSON(req);
|
|
252
|
+
if (!isProviderSettingsId(body?.id)) {
|
|
253
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
254
|
+
res.end(serializeJSON({ error: 'Unknown provider id.' }));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const providers = saveProviderSettings(projectRoot, {
|
|
258
|
+
id: body.id,
|
|
259
|
+
enabled: typeof body.enabled === 'boolean' ? body.enabled : undefined,
|
|
260
|
+
apiKey: typeof body.apiKey === 'string' ? body.apiKey : undefined,
|
|
261
|
+
baseUrl: typeof body.baseUrl === 'string' ? body.baseUrl : undefined,
|
|
262
|
+
model: typeof body.model === 'string' ? body.model : undefined,
|
|
263
|
+
});
|
|
264
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
265
|
+
res.end(serializeJSON({ ok: true, providers }));
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
269
|
+
res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (req.method === 'POST' && path === '/api/settings/providers/test') {
|
|
274
|
+
try {
|
|
275
|
+
const body = await readJSON(req);
|
|
276
|
+
if (!isProviderSettingsId(body?.id)) {
|
|
277
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
278
|
+
res.end(serializeJSON({ ok: false, error: 'Unknown provider id.' }));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const ok = await testProviderConfig(projectRoot, body.id);
|
|
282
|
+
res.writeHead(ok.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
283
|
+
res.end(serializeJSON(ok));
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
287
|
+
res.end(serializeJSON({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (req.method === 'GET' && path === '/api/agent/memory') {
|
|
292
|
+
const memory = new MemoryStore(defaultMemoryPath(projectRoot));
|
|
293
|
+
try {
|
|
294
|
+
const scope = url.searchParams.get('scope') ?? undefined;
|
|
295
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
296
|
+
res.end(serializeJSON({ memories: memory.list(isMemoryScope(scope) ? scope : undefined) }));
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
memory.close();
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (req.method === 'POST' && path === '/api/agent/memory') {
|
|
304
|
+
const body = await readJSON(req).catch(() => null);
|
|
305
|
+
if (!body || !isMemoryScope(body.scope) || typeof body.title !== 'string' || typeof body.content !== 'string') {
|
|
306
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
307
|
+
res.end(serializeJSON({ error: 'scope, title, and content are required.' }));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const memory = new MemoryStore(defaultMemoryPath(projectRoot));
|
|
311
|
+
try {
|
|
312
|
+
const saved = memory.upsert({
|
|
313
|
+
id: typeof body.id === 'string' ? body.id : undefined,
|
|
314
|
+
scope: body.scope,
|
|
315
|
+
scopeId: typeof body.scopeId === 'string' ? body.scopeId : undefined,
|
|
316
|
+
title: body.title,
|
|
317
|
+
content: body.content,
|
|
318
|
+
tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
|
|
319
|
+
source: typeof body.source === 'string' ? body.source : 'settings-ui',
|
|
320
|
+
confidence: typeof body.confidence === 'number' ? body.confidence : undefined,
|
|
321
|
+
importance: typeof body.importance === 'number' ? body.importance : undefined,
|
|
322
|
+
validFrom: typeof body.validFrom === 'string' ? body.validFrom : undefined,
|
|
323
|
+
validTo: typeof body.validTo === 'string' ? body.validTo : undefined,
|
|
324
|
+
supersedes: typeof body.supersedes === 'string' ? body.supersedes : undefined,
|
|
325
|
+
enabled: typeof body.enabled === 'boolean' ? body.enabled : undefined,
|
|
326
|
+
});
|
|
327
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
328
|
+
res.end(serializeJSON({ ok: true, memory: saved }));
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
memory.close();
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (req.method === 'DELETE' && path === '/api/agent/memory') {
|
|
336
|
+
const id = url.searchParams.get('id');
|
|
337
|
+
if (!id) {
|
|
338
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
339
|
+
res.end(serializeJSON({ error: 'id is required.' }));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const memory = new MemoryStore(defaultMemoryPath(projectRoot));
|
|
343
|
+
try {
|
|
344
|
+
memory.delete(id);
|
|
345
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
346
|
+
res.end(serializeJSON({ ok: true }));
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
memory.close();
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (req.method === 'POST' && path === '/api/agent/memory/default-files') {
|
|
354
|
+
const files = ensureDefaultMemoryFiles(projectRoot).map((p) => relative(projectRoot, p));
|
|
355
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
356
|
+
res.end(serializeJSON({ ok: true, files }));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
148
359
|
const appDashRun = path.match(/^\/api\/apps\/([^/]+)\/dashboards\/([^/]+)\/run$/);
|
|
149
360
|
if (req.method === 'POST' && appDashRun) {
|
|
150
361
|
try {
|
|
@@ -162,13 +373,61 @@ export async function startLocalServer(opts) {
|
|
|
162
373
|
? body.variables
|
|
163
374
|
: {};
|
|
164
375
|
const tiles = [];
|
|
376
|
+
const localApps = new LocalAppStorage(defaultLocalAppsDbPath(projectRoot));
|
|
165
377
|
for (const item of loaded.dashboard.layout.items) {
|
|
378
|
+
if (item.text) {
|
|
379
|
+
tiles.push({
|
|
380
|
+
tileId: item.i,
|
|
381
|
+
status: 'ok',
|
|
382
|
+
tileType: 'text',
|
|
383
|
+
title: item.title,
|
|
384
|
+
viz: item.viz,
|
|
385
|
+
text: item.text,
|
|
386
|
+
});
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (item.aiPin) {
|
|
390
|
+
let pin = localApps.getAiPin(item.aiPin.id);
|
|
391
|
+
if (!pin) {
|
|
392
|
+
tiles.push({
|
|
393
|
+
tileId: item.i,
|
|
394
|
+
status: 'unresolved',
|
|
395
|
+
tileType: 'aiPin',
|
|
396
|
+
error: `AI pin "${item.aiPin.id}" could not be found`,
|
|
397
|
+
});
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (pin.refreshCadence === 'daily' && pin.sql && isAiPinRefreshDue(pin.lastRefreshedAt)) {
|
|
401
|
+
try {
|
|
402
|
+
const refreshed = await executeLocalSqlForStoredResult(pin.sql);
|
|
403
|
+
pin = localApps.updateAiPinResult(pin.id, refreshed) ?? pin;
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
pin = localApps.updateAiPinResult(pin.id, pin.result, err instanceof Error ? err.message : String(err)) ?? pin;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
tiles.push({
|
|
410
|
+
tileId: item.i,
|
|
411
|
+
status: 'ok',
|
|
412
|
+
tileType: 'aiPin',
|
|
413
|
+
title: item.title ?? pin.title,
|
|
414
|
+
viz: item.viz,
|
|
415
|
+
chartConfig: pin.chartConfig ?? { chart: item.viz.type },
|
|
416
|
+
result: pin.result,
|
|
417
|
+
aiPin: pin,
|
|
418
|
+
citation: {
|
|
419
|
+
kind: 'ai_pin',
|
|
420
|
+
name: pin.title,
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
166
425
|
const block = resolveDashboardItemBlock(item, manifest);
|
|
167
426
|
if (!block) {
|
|
168
427
|
tiles.push({
|
|
169
428
|
tileId: item.i,
|
|
170
429
|
status: 'unresolved',
|
|
171
|
-
blockRef: isBlockIdRef(item.block) ? item.block.blockId : item.block.ref,
|
|
430
|
+
blockRef: item.block ? (isBlockIdRef(item.block) ? item.block.blockId : item.block.ref) : '(missing source)',
|
|
172
431
|
error: 'Block reference could not be resolved',
|
|
173
432
|
});
|
|
174
433
|
continue;
|
|
@@ -237,6 +496,7 @@ export async function startLocalServer(opts) {
|
|
|
237
496
|
}
|
|
238
497
|
}
|
|
239
498
|
}
|
|
499
|
+
localApps.close();
|
|
240
500
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
241
501
|
res.end(serializeJSON({
|
|
242
502
|
appId,
|
|
@@ -254,7 +514,14 @@ export async function startLocalServer(opts) {
|
|
|
254
514
|
// Apps, dashboards, persona — see apps-api.ts. Returns true if handled.
|
|
255
515
|
if (path.startsWith('/api/apps') || path === '/api/persona') {
|
|
256
516
|
try {
|
|
257
|
-
const handled = await handleAppsApi({
|
|
517
|
+
const handled = await handleAppsApi({
|
|
518
|
+
req,
|
|
519
|
+
res,
|
|
520
|
+
url,
|
|
521
|
+
path,
|
|
522
|
+
projectRoot,
|
|
523
|
+
executeSql: executeLocalSqlForStoredResult,
|
|
524
|
+
});
|
|
258
525
|
if (handled)
|
|
259
526
|
return;
|
|
260
527
|
}
|
|
@@ -972,6 +1239,110 @@ export async function startLocalServer(opts) {
|
|
|
972
1239
|
}
|
|
973
1240
|
return;
|
|
974
1241
|
}
|
|
1242
|
+
if (req.method === 'POST' && path === '/api/block-studio/import/preview') {
|
|
1243
|
+
try {
|
|
1244
|
+
const body = await readJSON(req);
|
|
1245
|
+
const inputPath = typeof body.path === 'string' ? body.path : '';
|
|
1246
|
+
const session = createBlockStudioImportSession(projectRoot, {
|
|
1247
|
+
inputPath,
|
|
1248
|
+
sourceKind: typeof body.sourceKind === 'string' ? body.sourceKind : 'raw-sql',
|
|
1249
|
+
domain: typeof body.domain === 'string' ? body.domain : undefined,
|
|
1250
|
+
owner: typeof body.owner === 'string' ? body.owner : undefined,
|
|
1251
|
+
tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
|
|
1252
|
+
});
|
|
1253
|
+
const candidates = session.candidates.map(validateImportCandidate);
|
|
1254
|
+
const validatedSession = { ...session, candidates };
|
|
1255
|
+
for (const candidate of candidates) {
|
|
1256
|
+
writeBlockStudioImportCandidate(projectRoot, session.id, candidate);
|
|
1257
|
+
}
|
|
1258
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1259
|
+
res.end(serializeJSON(validatedSession));
|
|
1260
|
+
}
|
|
1261
|
+
catch (error) {
|
|
1262
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1263
|
+
res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
|
|
1264
|
+
}
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const importPathMatch = path.match(/^\/api\/block-studio\/imports\/([^/]+)(?:\/candidates\/([^/]+)(?:\/(run|save))?)?$/);
|
|
1268
|
+
if (importPathMatch) {
|
|
1269
|
+
const importId = decodeURIComponent(importPathMatch[1]);
|
|
1270
|
+
const candidateId = importPathMatch[2] ? decodeURIComponent(importPathMatch[2]) : null;
|
|
1271
|
+
const action = importPathMatch[3] ?? null;
|
|
1272
|
+
try {
|
|
1273
|
+
if (req.method === 'GET' && !candidateId) {
|
|
1274
|
+
const session = loadBlockStudioImportSession(projectRoot, importId);
|
|
1275
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1276
|
+
res.end(serializeJSON(session));
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
if (req.method === 'PATCH' && candidateId && !action) {
|
|
1280
|
+
const body = await readJSON(req);
|
|
1281
|
+
const reviewStatus = typeof body.reviewStatus === 'string' && ['draft', 'saved', 'rejected'].includes(body.reviewStatus)
|
|
1282
|
+
? body.reviewStatus
|
|
1283
|
+
: undefined;
|
|
1284
|
+
const candidate = updateBlockStudioImportCandidate(projectRoot, importId, candidateId, {
|
|
1285
|
+
name: typeof body.name === 'string' ? body.name : undefined,
|
|
1286
|
+
domain: typeof body.domain === 'string' ? body.domain : undefined,
|
|
1287
|
+
description: typeof body.description === 'string' ? body.description : undefined,
|
|
1288
|
+
owner: typeof body.owner === 'string' ? body.owner : undefined,
|
|
1289
|
+
tags: Array.isArray(body.tags) ? body.tags.map(String) : undefined,
|
|
1290
|
+
sql: typeof body.sql === 'string' ? body.sql : undefined,
|
|
1291
|
+
reviewStatus,
|
|
1292
|
+
});
|
|
1293
|
+
const validated = validateImportCandidate(candidate);
|
|
1294
|
+
writeBlockStudioImportCandidate(projectRoot, importId, validated);
|
|
1295
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1296
|
+
res.end(serializeJSON(validated));
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (req.method === 'POST' && candidateId && action === 'run') {
|
|
1300
|
+
const candidate = readBlockStudioImportCandidate(projectRoot, importId, candidateId);
|
|
1301
|
+
const preview = await runBlockStudioPreviewSource(candidate.dqlSource);
|
|
1302
|
+
const next = { ...candidate, preview, validation: validateBlockStudioSource(candidate.dqlSource, semanticLayer) };
|
|
1303
|
+
writeBlockStudioImportCandidate(projectRoot, importId, next);
|
|
1304
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1305
|
+
res.end(serializeJSON(next));
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (req.method === 'POST' && candidateId && action === 'save') {
|
|
1309
|
+
const candidate = readBlockStudioImportCandidate(projectRoot, importId, candidateId);
|
|
1310
|
+
const savedPath = saveBlockStudioArtifacts(projectRoot, {
|
|
1311
|
+
source: candidate.dqlSource,
|
|
1312
|
+
name: candidate.name,
|
|
1313
|
+
domain: candidate.domain,
|
|
1314
|
+
description: candidate.description,
|
|
1315
|
+
owner: candidate.owner,
|
|
1316
|
+
tags: candidate.tags,
|
|
1317
|
+
lineage: candidate.lineage.sourceTables,
|
|
1318
|
+
importMeta: {
|
|
1319
|
+
importId,
|
|
1320
|
+
candidateId,
|
|
1321
|
+
sourceKind: candidate.sourceKind,
|
|
1322
|
+
sourcePath: candidate.sourcePath,
|
|
1323
|
+
},
|
|
1324
|
+
});
|
|
1325
|
+
const next = { ...candidate, reviewStatus: 'saved', savedPath };
|
|
1326
|
+
writeBlockStudioImportCandidate(projectRoot, importId, next);
|
|
1327
|
+
const payload = openBlockStudioDocument(projectRoot, savedPath, semanticLayer);
|
|
1328
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1329
|
+
res.end(serializeJSON({ candidate: next, block: payload }));
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1333
|
+
res.end(serializeJSON({ error: 'Unsupported import operation.' }));
|
|
1334
|
+
}
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
if (error instanceof Error && error.message === 'BLOCK_EXISTS') {
|
|
1337
|
+
res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1338
|
+
res.end(serializeJSON({ error: 'Block already exists' }));
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1342
|
+
res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
|
|
1343
|
+
}
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
975
1346
|
if (req.method === 'GET' && path === '/api/block-studio/catalog') {
|
|
976
1347
|
try {
|
|
977
1348
|
const cfg = loadProjectConfig(projectRoot);
|
|
@@ -1037,46 +1408,9 @@ export async function startLocalServer(opts) {
|
|
|
1037
1408
|
const body = await readJSON(req);
|
|
1038
1409
|
const source = typeof body.source === 'string' ? body.source : '';
|
|
1039
1410
|
const targetConnection = isConnectionConfig(body.connection) ? body.connection : connection;
|
|
1040
|
-
|
|
1041
|
-
if (semanticLayer) {
|
|
1042
|
-
try {
|
|
1043
|
-
const tablesResult = await executor.executeQuery(`SELECT table_schema, table_name
|
|
1044
|
-
FROM information_schema.tables
|
|
1045
|
-
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')`, [], {}, targetConnection);
|
|
1046
|
-
tableMapping = buildSemanticTableMapping(semanticLayer, tablesResult.rows);
|
|
1047
|
-
}
|
|
1048
|
-
catch {
|
|
1049
|
-
tableMapping = undefined;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
const semanticCompose = semanticLayer
|
|
1053
|
-
? composeSemanticBlockSql(source, semanticLayer, {
|
|
1054
|
-
driver: targetConnection.driver,
|
|
1055
|
-
tableMapping,
|
|
1056
|
-
projectRoot,
|
|
1057
|
-
projectConfig,
|
|
1058
|
-
detectedProvider: semanticDetectedProvider,
|
|
1059
|
-
})
|
|
1060
|
-
: null;
|
|
1061
|
-
const validation = validateBlockStudioSource(source, semanticLayer);
|
|
1062
|
-
const executableSql = semanticCompose?.sql ?? validation.executableSql;
|
|
1063
|
-
if (!executableSql) {
|
|
1064
|
-
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1065
|
-
const message = semanticCompose?.diagnostics.find((item) => item.severity === 'error')?.message
|
|
1066
|
-
?? validation.diagnostics.find((item) => item.severity === 'error')?.message
|
|
1067
|
-
?? 'No executable SQL found in block source.';
|
|
1068
|
-
res.end(serializeJSON({ error: message, diagnostics: validation.diagnostics }));
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
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);
|
|
1411
|
+
const preview = await runBlockStudioPreviewSource(source, targetConnection);
|
|
1074
1412
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1075
|
-
res.end(serializeJSON(
|
|
1076
|
-
sql: plan?.sql ?? executableSql,
|
|
1077
|
-
result: normalizeQueryResult(result),
|
|
1078
|
-
chartConfig: plan?.chartConfig ?? validation.chartConfig ?? null,
|
|
1079
|
-
}));
|
|
1413
|
+
res.end(serializeJSON(preview));
|
|
1080
1414
|
}
|
|
1081
1415
|
catch (error) {
|
|
1082
1416
|
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
@@ -1109,6 +1443,15 @@ export async function startLocalServer(opts) {
|
|
|
1109
1443
|
description: metadata.description,
|
|
1110
1444
|
owner: metadata.owner,
|
|
1111
1445
|
tags: Array.isArray(metadata.tags) ? metadata.tags.map(String) : [],
|
|
1446
|
+
lineage: Array.isArray(metadata.lineage) ? metadata.lineage.map(String) : undefined,
|
|
1447
|
+
importMeta: metadata.sourceKind || metadata.sourcePath || metadata.importId || metadata.candidateId
|
|
1448
|
+
? {
|
|
1449
|
+
importId: metadata.importId,
|
|
1450
|
+
candidateId: metadata.candidateId,
|
|
1451
|
+
sourceKind: metadata.sourceKind,
|
|
1452
|
+
sourcePath: metadata.sourcePath,
|
|
1453
|
+
}
|
|
1454
|
+
: undefined,
|
|
1112
1455
|
});
|
|
1113
1456
|
const payload = openBlockStudioDocument(projectRoot, savedPath, semanticLayer);
|
|
1114
1457
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
@@ -1833,10 +2176,11 @@ export async function startLocalServer(opts) {
|
|
|
1833
2176
|
return;
|
|
1834
2177
|
}
|
|
1835
2178
|
const { provider, messages, upstream } = body;
|
|
1836
|
-
const
|
|
1837
|
-
|
|
2179
|
+
const resolvedProvider = isLLMProviderId(provider) ? provider : resolveDefaultLLMProvider(projectRoot);
|
|
2180
|
+
const runner = resolvedProvider ? getLLMRunner(resolvedProvider) : null;
|
|
2181
|
+
if (!resolvedProvider || !runner) {
|
|
1838
2182
|
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1839
|
-
res.end(serializeJSON({ error:
|
|
2183
|
+
res.end(serializeJSON({ error: 'No AI provider is configured. Configure OpenAI, Gemini, Ollama, or a custom OpenAI-compatible endpoint in Settings.' }));
|
|
1840
2184
|
return;
|
|
1841
2185
|
}
|
|
1842
2186
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
@@ -1853,7 +2197,7 @@ export async function startLocalServer(opts) {
|
|
|
1853
2197
|
req.on('close', () => controller.abort());
|
|
1854
2198
|
const emit = (turn) => { res.write(`data: ${JSON.stringify(turn)}\n\n`); };
|
|
1855
2199
|
try {
|
|
1856
|
-
await runner.run({ provider:
|
|
2200
|
+
await runner.run({ provider: resolvedProvider, messages, upstream, projectRoot, executeCertifiedBlock: executeCertifiedBlockForAgent }, emit, controller.signal);
|
|
1857
2201
|
}
|
|
1858
2202
|
catch (err) {
|
|
1859
2203
|
emit({ kind: 'error', message: err instanceof Error ? err.message : String(err) });
|
|
@@ -2205,6 +2549,24 @@ export async function startLocalServer(opts) {
|
|
|
2205
2549
|
}
|
|
2206
2550
|
return;
|
|
2207
2551
|
}
|
|
2552
|
+
if (req.method === 'GET' && path === '/api/lineage/scope') {
|
|
2553
|
+
try {
|
|
2554
|
+
const graph = buildProjectLineageGraph(projectRoot, semanticLayer);
|
|
2555
|
+
const result = buildScopedLineage(graph, {
|
|
2556
|
+
domain: url.searchParams.get('domain') ?? undefined,
|
|
2557
|
+
appId: url.searchParams.get('appId') ?? undefined,
|
|
2558
|
+
dashboardId: url.searchParams.get('dashboardId') ?? undefined,
|
|
2559
|
+
blockId: url.searchParams.get('blockId') ?? undefined,
|
|
2560
|
+
});
|
|
2561
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
2562
|
+
res.end(serializeJSON(result));
|
|
2563
|
+
}
|
|
2564
|
+
catch (error) {
|
|
2565
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
2566
|
+
res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
|
|
2567
|
+
}
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2208
2570
|
if (req.method === 'GET' && path === '/api/lineage/query') {
|
|
2209
2571
|
try {
|
|
2210
2572
|
const graph = buildProjectLineageGraph(projectRoot, semanticLayer);
|
|
@@ -2404,8 +2766,10 @@ export async function startLocalServer(opts) {
|
|
|
2404
2766
|
res.end(serializeJSON({ error: 'Missing notebook cell payload.' }));
|
|
2405
2767
|
return;
|
|
2406
2768
|
}
|
|
2769
|
+
const resolved = resolveNotebookBlockReferenceCell(cell, projectRoot);
|
|
2770
|
+
const executableCell = resolved.cell;
|
|
2407
2771
|
const cellConnection = isConnectionConfig(body.connection) ? body.connection : connection;
|
|
2408
|
-
const plan = buildExecutionPlan(
|
|
2772
|
+
const plan = buildExecutionPlan(executableCell, { semanticLayer, driver: cellConnection.driver });
|
|
2409
2773
|
if (!plan) {
|
|
2410
2774
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
2411
2775
|
res.end(serializeJSON({ cellType: cell.type, result: null }));
|
|
@@ -2413,12 +2777,14 @@ export async function startLocalServer(opts) {
|
|
|
2413
2777
|
}
|
|
2414
2778
|
const prepared = prepareLocalExecution(plan.sql, isConnectionConfig(body.connection) ? body.connection : connection, projectRoot, projectConfig);
|
|
2415
2779
|
const app = loadRuntimeApp(projectRoot, typeof body.appId === 'string' ? body.appId : activePersonaAppId());
|
|
2416
|
-
assertAppAccess({ app, domain: app?.domain, level: 'execute' });
|
|
2780
|
+
assertAppAccess({ app, domain: resolved.domain ?? app?.domain, level: 'execute' });
|
|
2417
2781
|
const rawResult = await executor.executeQuery(prepared.sql, plan.sqlParams, runtimeVariables(plan.variables), prepared.connection);
|
|
2418
2782
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
2419
2783
|
res.end(serializeJSON({
|
|
2420
2784
|
cellType: cell.type,
|
|
2421
2785
|
title: plan.title,
|
|
2786
|
+
blockName: resolved.blockName,
|
|
2787
|
+
blockPath: resolved.blockPath,
|
|
2422
2788
|
chartConfig: plan.chartConfig,
|
|
2423
2789
|
tests: plan.tests,
|
|
2424
2790
|
result: normalizeQueryResult(rawResult),
|
|
@@ -2549,7 +2915,18 @@ function isLLMProviderId(value) {
|
|
|
2549
2915
|
|| value === 'claude-code'
|
|
2550
2916
|
|| value === 'openai'
|
|
2551
2917
|
|| value === 'gemini'
|
|
2552
|
-
|| value === 'ollama'
|
|
2918
|
+
|| value === 'ollama'
|
|
2919
|
+
|| value === 'custom-openai';
|
|
2920
|
+
}
|
|
2921
|
+
function resolveDefaultLLMProvider(projectRoot) {
|
|
2922
|
+
const settings = listProviderSettings(projectRoot);
|
|
2923
|
+
const preferred = ['openai', 'gemini', 'ollama', 'custom-openai'];
|
|
2924
|
+
for (const id of preferred) {
|
|
2925
|
+
const provider = settings.find((item) => item.id === id);
|
|
2926
|
+
if (provider?.enabled && provider.hasApiKey)
|
|
2927
|
+
return id;
|
|
2928
|
+
}
|
|
2929
|
+
return null;
|
|
2553
2930
|
}
|
|
2554
2931
|
function loadAppDashboard(projectRoot, appId, dashboardId) {
|
|
2555
2932
|
for (const p of findAppDocuments(projectRoot)) {
|
|
@@ -2566,6 +2943,8 @@ function loadAppDashboard(projectRoot, appId, dashboardId) {
|
|
|
2566
2943
|
return null;
|
|
2567
2944
|
}
|
|
2568
2945
|
function resolveDashboardItemBlock(item, manifest) {
|
|
2946
|
+
if (!item.block)
|
|
2947
|
+
return null;
|
|
2569
2948
|
if (isBlockIdRef(item.block)) {
|
|
2570
2949
|
return manifest.blocks[item.block.blockId] ?? null;
|
|
2571
2950
|
}
|
|
@@ -2785,6 +3164,33 @@ function normalizeNotebookCell(value) {
|
|
|
2785
3164
|
config: candidate.config,
|
|
2786
3165
|
};
|
|
2787
3166
|
}
|
|
3167
|
+
function resolveNotebookBlockReferenceCell(cell, projectRoot) {
|
|
3168
|
+
if (cell.type !== 'dql')
|
|
3169
|
+
return { cell };
|
|
3170
|
+
const match = cell.source.trim().match(/^@block\(\s*["']([^"']+)["']\s*\)$/i);
|
|
3171
|
+
if (!match)
|
|
3172
|
+
return { cell };
|
|
3173
|
+
const ref = match[1].trim();
|
|
3174
|
+
const manifest = buildManifest({ projectRoot });
|
|
3175
|
+
const block = manifest.blocks[ref] ?? Object.values(manifest.blocks).find((candidate) => candidate.filePath === ref);
|
|
3176
|
+
if (!block) {
|
|
3177
|
+
throw new Error(`Block reference "${ref}" could not be resolved.`);
|
|
3178
|
+
}
|
|
3179
|
+
const blockPath = join(projectRoot, block.filePath);
|
|
3180
|
+
if (!existsSync(blockPath)) {
|
|
3181
|
+
throw new Error(`Block "${block.name}" file is missing: ${block.filePath}`);
|
|
3182
|
+
}
|
|
3183
|
+
return {
|
|
3184
|
+
cell: {
|
|
3185
|
+
...cell,
|
|
3186
|
+
title: cell.title ?? block.name,
|
|
3187
|
+
source: readFileSync(blockPath, 'utf-8'),
|
|
3188
|
+
},
|
|
3189
|
+
blockName: block.name,
|
|
3190
|
+
blockPath: block.filePath,
|
|
3191
|
+
domain: block.domain,
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
2788
3194
|
function isConnectionConfig(value) {
|
|
2789
3195
|
return Boolean(value && typeof value === 'object' && 'driver' in value);
|
|
2790
3196
|
}
|
|
@@ -3362,6 +3768,8 @@ function saveBlockStudioArtifacts(projectRoot, options) {
|
|
|
3362
3768
|
tags: options.tags,
|
|
3363
3769
|
provider: 'dql',
|
|
3364
3770
|
content: options.source,
|
|
3771
|
+
lineage: options.lineage,
|
|
3772
|
+
importMeta: options.importMeta,
|
|
3365
3773
|
});
|
|
3366
3774
|
if (previousPath && previousPath !== targetRelativePath) {
|
|
3367
3775
|
const previousAbsPath = join(projectRoot, previousPath);
|
|
@@ -3769,6 +4177,17 @@ function writeBlockCompanionFile(projectRoot, options) {
|
|
|
3769
4177
|
if (options.gitPath)
|
|
3770
4178
|
lines.push(` path: ${yamlScalar(options.gitPath)}`);
|
|
3771
4179
|
}
|
|
4180
|
+
if (options.importMeta) {
|
|
4181
|
+
lines.push('import:');
|
|
4182
|
+
if (options.importMeta.importId)
|
|
4183
|
+
lines.push(` importId: ${yamlScalar(options.importMeta.importId)}`);
|
|
4184
|
+
if (options.importMeta.candidateId)
|
|
4185
|
+
lines.push(` candidateId: ${yamlScalar(options.importMeta.candidateId)}`);
|
|
4186
|
+
if (options.importMeta.sourceKind)
|
|
4187
|
+
lines.push(` sourceKind: ${yamlScalar(options.importMeta.sourceKind)}`);
|
|
4188
|
+
if (options.importMeta.sourcePath)
|
|
4189
|
+
lines.push(` sourcePath: ${yamlScalar(options.importMeta.sourcePath)}`);
|
|
4190
|
+
}
|
|
3772
4191
|
lines.push('reviewStatus: draft');
|
|
3773
4192
|
writeFileSync(companionPath, lines.join('\n') + '\n', 'utf-8');
|
|
3774
4193
|
return relative(projectRoot, companionPath).replaceAll('\\', '/');
|
|
@@ -3994,6 +4413,38 @@ function resolveLineageNode(graph, rawNodeId) {
|
|
|
3994
4413
|
const result = queryLineage(graph, { focus: rawNodeId });
|
|
3995
4414
|
return result.focalNode;
|
|
3996
4415
|
}
|
|
4416
|
+
function buildScopedLineage(graph, scope) {
|
|
4417
|
+
const focus = scope.blockId
|
|
4418
|
+
? `block:${scope.blockId}`
|
|
4419
|
+
: scope.dashboardId
|
|
4420
|
+
? `dashboard:${scope.appId ? `${scope.appId}/${scope.dashboardId}` : scope.dashboardId}`
|
|
4421
|
+
: scope.appId
|
|
4422
|
+
? `app:${scope.appId}`
|
|
4423
|
+
: undefined;
|
|
4424
|
+
const result = queryLineage(graph, {
|
|
4425
|
+
focus,
|
|
4426
|
+
domain: focus ? undefined : scope.domain,
|
|
4427
|
+
upstreamDepth: 8,
|
|
4428
|
+
downstreamDepth: 4,
|
|
4429
|
+
});
|
|
4430
|
+
const graphJson = result.graph ?? graph.toJSON();
|
|
4431
|
+
const breadcrumbs = [
|
|
4432
|
+
scope.domain ? graph.getNode(`domain:${scope.domain}`) : null,
|
|
4433
|
+
scope.appId ? graph.getNode(`app:${scope.appId}`) : null,
|
|
4434
|
+
scope.dashboardId ? graph.getNode(`dashboard:${scope.appId ? `${scope.appId}/${scope.dashboardId}` : scope.dashboardId}`) : null,
|
|
4435
|
+
scope.blockId ? graph.getNode(`block:${scope.blockId}`) : null,
|
|
4436
|
+
].filter(Boolean);
|
|
4437
|
+
const paths = focus ? queryCompleteLineagePaths(graph, focus, { maxDepth: 12, maxPaths: 20 }) : null;
|
|
4438
|
+
return {
|
|
4439
|
+
scope,
|
|
4440
|
+
focus,
|
|
4441
|
+
graph: graphJson,
|
|
4442
|
+
focalNode: result.focalNode,
|
|
4443
|
+
breadcrumbs,
|
|
4444
|
+
paths,
|
|
4445
|
+
view: 'Domain > App > Dashboard tab > Tile > Block > Semantic/dbt/source',
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
3997
4448
|
function extractProp(block, key) {
|
|
3998
4449
|
// Check direct AST fields first (parser puts domain, owner, type directly on the node)
|
|
3999
4450
|
if (block[key] !== undefined && block[key] !== null)
|
|
@@ -4354,4 +4805,55 @@ function collectSettingsEnvStatus() {
|
|
|
4354
4805
|
},
|
|
4355
4806
|
];
|
|
4356
4807
|
}
|
|
4808
|
+
function isProviderSettingsId(value) {
|
|
4809
|
+
return value === 'anthropic'
|
|
4810
|
+
|| value === 'openai'
|
|
4811
|
+
|| value === 'gemini'
|
|
4812
|
+
|| value === 'ollama'
|
|
4813
|
+
|| value === 'custom-openai';
|
|
4814
|
+
}
|
|
4815
|
+
async function testProviderConfig(projectRoot, id) {
|
|
4816
|
+
const config = getEffectiveProviderConfig(projectRoot, id);
|
|
4817
|
+
let provider;
|
|
4818
|
+
switch (id) {
|
|
4819
|
+
case 'anthropic':
|
|
4820
|
+
provider = new ClaudeProvider({ apiKey: config.apiKey, model: config.model });
|
|
4821
|
+
break;
|
|
4822
|
+
case 'gemini':
|
|
4823
|
+
provider = new GeminiProvider({ apiKey: config.apiKey, model: config.model });
|
|
4824
|
+
break;
|
|
4825
|
+
case 'ollama':
|
|
4826
|
+
provider = new OllamaProvider({ baseUrl: config.baseUrl, model: config.model });
|
|
4827
|
+
break;
|
|
4828
|
+
case 'custom-openai':
|
|
4829
|
+
provider = new OpenAIProvider({ apiKey: config.apiKey, baseUrl: config.baseUrl, model: config.model, allowNoApiKey: true });
|
|
4830
|
+
break;
|
|
4831
|
+
case 'openai':
|
|
4832
|
+
default:
|
|
4833
|
+
provider = new OpenAIProvider({ apiKey: config.apiKey, baseUrl: config.baseUrl, model: config.model });
|
|
4834
|
+
break;
|
|
4835
|
+
}
|
|
4836
|
+
const ok = await provider.available();
|
|
4837
|
+
return {
|
|
4838
|
+
ok,
|
|
4839
|
+
message: ok
|
|
4840
|
+
? `${id} is configured.`
|
|
4841
|
+
: `${id} is not configured or not reachable. Check API key, base URL, and local service state.`,
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4844
|
+
function isAiPinRefreshDue(lastRefreshedAt) {
|
|
4845
|
+
if (!lastRefreshedAt)
|
|
4846
|
+
return true;
|
|
4847
|
+
const last = Date.parse(lastRefreshedAt);
|
|
4848
|
+
if (!Number.isFinite(last))
|
|
4849
|
+
return true;
|
|
4850
|
+
return Date.now() - last >= 24 * 60 * 60 * 1000;
|
|
4851
|
+
}
|
|
4852
|
+
function isMemoryScope(value) {
|
|
4853
|
+
return value === 'thread'
|
|
4854
|
+
|| value === 'notebook'
|
|
4855
|
+
|| value === 'project'
|
|
4856
|
+
|| value === 'user'
|
|
4857
|
+
|| value === 'artifact';
|
|
4858
|
+
}
|
|
4357
4859
|
//# sourceMappingURL=local-runtime.js.map
|