@kaelio/ktx 0.13.1 → 0.14.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.
Files changed (241) hide show
  1. package/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.14.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-program.js +1 -1
  5. package/dist/commands/ingest-commands.d.ts +9 -0
  6. package/dist/commands/ingest-commands.js +37 -1
  7. package/dist/commands/knowledge-commands.js +3 -0
  8. package/dist/commands/setup-commands.js +14 -1
  9. package/dist/connection-drivers.d.ts +2 -0
  10. package/dist/connection-drivers.js +7 -3
  11. package/dist/connection.d.ts +3 -0
  12. package/dist/connection.js +27 -0
  13. package/dist/connectors/bigquery/connector.d.ts +17 -6
  14. package/dist/connectors/bigquery/connector.js +152 -76
  15. package/dist/connectors/bigquery/dialect.d.ts +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +1 -0
  17. package/dist/connectors/clickhouse/connector.js +45 -16
  18. package/dist/connectors/clickhouse/dialect.d.ts +2 -2
  19. package/dist/connectors/mongodb/connector.d.ts +69 -0
  20. package/dist/connectors/mongodb/connector.js +307 -0
  21. package/dist/connectors/mongodb/dialect.d.ts +17 -0
  22. package/dist/connectors/mongodb/dialect.js +50 -0
  23. package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
  24. package/dist/connectors/mongodb/live-database-introspection.js +24 -0
  25. package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
  26. package/dist/connectors/mongodb/schema-inference.js +110 -0
  27. package/dist/connectors/mysql/connector.d.ts +1 -0
  28. package/dist/connectors/mysql/connector.js +18 -2
  29. package/dist/connectors/mysql/dialect.d.ts +2 -2
  30. package/dist/connectors/postgres/connector.d.ts +1 -0
  31. package/dist/connectors/postgres/connector.js +20 -3
  32. package/dist/connectors/postgres/dialect.d.ts +2 -2
  33. package/dist/connectors/snowflake/connector.d.ts +1 -0
  34. package/dist/connectors/snowflake/connector.js +27 -3
  35. package/dist/connectors/snowflake/dialect.d.ts +2 -2
  36. package/dist/connectors/sqlite/connector.d.ts +11 -1
  37. package/dist/connectors/sqlite/connector.js +120 -17
  38. package/dist/connectors/sqlite/dialect.d.ts +2 -2
  39. package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
  40. package/dist/connectors/sqlite/read-query-child.js +23 -0
  41. package/dist/connectors/sqlserver/connector.d.ts +2 -0
  42. package/dist/connectors/sqlserver/connector.js +20 -3
  43. package/dist/connectors/sqlserver/dialect.d.ts +2 -2
  44. package/dist/context/cache/content-result-cache.d.ts +36 -0
  45. package/dist/context/cache/content-result-cache.js +14 -0
  46. package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
  47. package/dist/context/cache/sqlite-content-result-cache.js +223 -0
  48. package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
  49. package/dist/context/connections/bigquery-identifiers.js +7 -0
  50. package/dist/context/connections/configured-connections.d.ts +9 -0
  51. package/dist/context/connections/configured-connections.js +18 -0
  52. package/dist/context/connections/dialects.d.ts +30 -4
  53. package/dist/context/connections/dialects.js +38 -11
  54. package/dist/context/connections/drivers.js +21 -0
  55. package/dist/context/connections/gdrive-config.d.ts +19 -0
  56. package/dist/context/connections/gdrive-config.js +57 -0
  57. package/dist/context/connections/query-deadline.d.ts +31 -0
  58. package/dist/context/connections/query-deadline.js +37 -0
  59. package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
  60. package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
  61. package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
  62. package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
  63. package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
  64. package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
  65. package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
  66. package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
  67. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
  68. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
  69. package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
  70. package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
  71. package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
  72. package/dist/context/ingest/adapters/gdrive/types.js +36 -0
  73. package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
  74. package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
  75. package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
  76. package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
  77. package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
  78. package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
  79. package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
  80. package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
  81. package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
  82. package/dist/context/ingest/adapters/live-database/stage.js +5 -5
  83. package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
  84. package/dist/context/ingest/adapters/metabase/types.js +1 -1
  85. package/dist/context/ingest/artifact-gates.d.ts +32 -1
  86. package/dist/context/ingest/artifact-gates.js +85 -18
  87. package/dist/context/ingest/final-gate-prune.d.ts +35 -0
  88. package/dist/context/ingest/final-gate-prune.js +229 -0
  89. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  90. package/dist/context/ingest/ingest-bundle.runner.js +459 -240
  91. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
  92. package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
  93. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  94. package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
  95. package/dist/context/ingest/local-adapters.js +6 -0
  96. package/dist/context/ingest/local-bundle-runtime.js +7 -2
  97. package/dist/context/ingest/local-ingest.js +1 -1
  98. package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
  99. package/dist/context/ingest/local-stage-ingest.js +3 -1
  100. package/dist/context/ingest/ports.d.ts +3 -0
  101. package/dist/context/ingest/report-snapshot.js +13 -3
  102. package/dist/context/ingest/reports.d.ts +3 -3
  103. package/dist/context/ingest/reports.js +3 -1
  104. package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
  105. package/dist/context/ingest/stages/build-wu-context.js +2 -1
  106. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
  107. package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
  108. package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
  109. package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
  110. package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
  111. package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
  112. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
  113. package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
  114. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
  115. package/dist/context/ingest/types.d.ts +3 -0
  116. package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
  117. package/dist/context/ingest/wiki-body-refs.js +37 -8
  118. package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
  119. package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
  120. package/dist/context/ingest/work-unit-cache.d.ts +67 -0
  121. package/dist/context/ingest/work-unit-cache.js +230 -0
  122. package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
  123. package/dist/context/llm/ai-sdk-runtime.js +5 -0
  124. package/dist/context/llm/claude-code-runtime.d.ts +4 -1
  125. package/dist/context/llm/claude-code-runtime.js +38 -8
  126. package/dist/context/llm/codex-runtime.d.ts +4 -1
  127. package/dist/context/llm/codex-runtime.js +48 -35
  128. package/dist/context/llm/runtime-port.d.ts +26 -0
  129. package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
  130. package/dist/context/llm/subprocess-generate-object-child.js +34 -0
  131. package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
  132. package/dist/context/llm/subprocess-generate-object.js +122 -0
  133. package/dist/context/mcp/context-tools.d.ts +2 -0
  134. package/dist/context/mcp/context-tools.js +111 -17
  135. package/dist/context/mcp/local-project-ports.d.ts +7 -0
  136. package/dist/context/mcp/local-project-ports.js +29 -0
  137. package/dist/context/mcp/logger.d.ts +24 -0
  138. package/dist/context/mcp/logger.js +49 -0
  139. package/dist/context/mcp/server.js +2 -0
  140. package/dist/context/mcp/types.d.ts +17 -0
  141. package/dist/context/memory/memory-agent.service.js +1 -1
  142. package/dist/context/project/config.d.ts +42 -0
  143. package/dist/context/project/config.js +5 -0
  144. package/dist/context/project/driver-schemas.d.ts +20 -0
  145. package/dist/context/project/driver-schemas.js +50 -1
  146. package/dist/context/project/project.js +1 -1
  147. package/dist/context/project/setup-config.js +1 -0
  148. package/dist/context/scan/description-generation.d.ts +4 -0
  149. package/dist/context/scan/description-generation.js +100 -13
  150. package/dist/context/scan/enabled-tables.d.ts +5 -0
  151. package/dist/context/scan/enabled-tables.js +13 -1
  152. package/dist/context/scan/enrichment-state.d.ts +50 -7
  153. package/dist/context/scan/enrichment-state.js +30 -13
  154. package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
  155. package/dist/context/scan/local-enrichment-artifacts.js +155 -32
  156. package/dist/context/scan/local-enrichment.d.ts +39 -4
  157. package/dist/context/scan/local-enrichment.js +345 -92
  158. package/dist/context/scan/local-scan.d.ts +4 -1
  159. package/dist/context/scan/local-scan.js +54 -13
  160. package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
  161. package/dist/context/scan/local-structural-artifacts.js +5 -0
  162. package/dist/context/scan/object-introspection.d.ts +21 -0
  163. package/dist/context/scan/object-introspection.js +35 -0
  164. package/dist/context/scan/relationship-benchmarks.js +5 -3
  165. package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
  166. package/dist/context/scan/relationship-composite-candidates.js +13 -1
  167. package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
  168. package/dist/context/scan/relationship-detection-budget.js +53 -0
  169. package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
  170. package/dist/context/scan/relationship-diagnostics.js +2 -0
  171. package/dist/context/scan/relationship-discovery.d.ts +9 -3
  172. package/dist/context/scan/relationship-discovery.js +27 -4
  173. package/dist/context/scan/relationship-llm-proposal.js +36 -27
  174. package/dist/context/scan/relationship-profiling.d.ts +7 -3
  175. package/dist/context/scan/relationship-profiling.js +53 -41
  176. package/dist/context/scan/relationship-validation.d.ts +6 -4
  177. package/dist/context/scan/relationship-validation.js +46 -32
  178. package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
  179. package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
  180. package/dist/context/scan/types.d.ts +5 -3
  181. package/dist/context/scan/types.js +2 -0
  182. package/dist/context/sl/local-query.js +5 -0
  183. package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
  184. package/dist/context/sl/semantic-layer.service.js +1 -1
  185. package/dist/context/sl/source-files.d.ts +5 -0
  186. package/dist/context/sl/source-files.js +17 -11
  187. package/dist/context/sl/tools/connection-id-schema.js +1 -1
  188. package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
  189. package/dist/context/sql-analysis/dialect-notes.js +40 -0
  190. package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
  191. package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
  192. package/dist/context/sql-analysis/dialects/mysql.md +9 -0
  193. package/dist/context/sql-analysis/dialects/postgres.md +10 -0
  194. package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
  195. package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
  196. package/dist/context/sql-analysis/dialects/tsql.md +10 -0
  197. package/dist/context/wiki/keys.js +1 -1
  198. package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
  199. package/dist/context/wiki/knowledge-wiki.service.js +2 -1
  200. package/dist/context/wiki/local-knowledge.d.ts +13 -0
  201. package/dist/context/wiki/local-knowledge.js +50 -6
  202. package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
  203. package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
  204. package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
  205. package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
  206. package/dist/context/wiki/types.d.ts +6 -0
  207. package/dist/context-build-view.d.ts +3 -0
  208. package/dist/context-build-view.js +1 -0
  209. package/dist/knowledge.d.ts +2 -0
  210. package/dist/knowledge.js +12 -1
  211. package/dist/local-adapters.js +11 -0
  212. package/dist/mcp-http-server.js +15 -1
  213. package/dist/mcp-server-factory.d.ts +2 -0
  214. package/dist/mcp-server-factory.js +15 -1
  215. package/dist/mcp-stdio-server.js +10 -2
  216. package/dist/notion-page-picker.js +1 -1
  217. package/dist/prompts/memory_agent_external_ingest.md +2 -0
  218. package/dist/public-ingest.d.ts +3 -1
  219. package/dist/public-ingest.js +3 -0
  220. package/dist/scan.d.ts +3 -1
  221. package/dist/scan.js +7 -0
  222. package/dist/setup-databases.d.ts +1 -1
  223. package/dist/setup-databases.js +43 -1
  224. package/dist/setup-sources.d.ts +5 -1
  225. package/dist/setup-sources.js +124 -48
  226. package/dist/setup.d.ts +3 -0
  227. package/dist/setup.js +6 -1
  228. package/dist/skills/analytics/SKILL.md +200 -2
  229. package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
  230. package/dist/skills/wiki_capture/SKILL.md +24 -0
  231. package/dist/sql.js +4 -0
  232. package/dist/status-project.d.ts +4 -0
  233. package/dist/status-project.js +54 -2
  234. package/dist/telemetry/events.d.ts +2 -2
  235. package/dist/text-ingest.d.ts +4 -0
  236. package/dist/text-ingest.js +58 -29
  237. package/dist/verbatim-ingest.d.ts +46 -0
  238. package/dist/verbatim-ingest.js +193 -0
  239. package/package.json +5 -1
  240. package/dist/context/ingest/final-gate-repair.d.ts +0 -28
  241. package/dist/context/ingest/final-gate-repair.js +0 -91
@@ -68,7 +68,7 @@ export function parseBooleanStringOption(value) {
68
68
  throw new InvalidArgumentError('must be true or false');
69
69
  }
70
70
  export function parseSafeConnectionIdOption(value) {
71
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
71
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(value)) {
72
72
  throw new InvalidArgumentError(`Unsafe connection id: ${value}`);
73
73
  }
74
74
  return value;
@@ -1,7 +1,16 @@
1
1
  import { type Command } from '@commander-js/extra-typings';
2
2
  import { type KtxCliCommandContext } from '../cli-program.js';
3
+ import type { KtxScanEnrichmentStage } from '../context/scan/types.js';
3
4
  import type { KtxCliDeps, KtxCliIo } from '../index.js';
4
5
  import type { KtxTextIngestArgs } from '../text-ingest.js';
6
+ /**
7
+ * Parses `--stages` into an ordered, de-duplicated subset of the canonical
8
+ * enrichment-stage registry. An unknown or empty name is a hard parse error so
9
+ * a typo never silently degrades to "run everything."
10
+ *
11
+ * @internal
12
+ */
13
+ export declare function parseEnrichmentStagesOption(value: string): KtxScanEnrichmentStage[];
5
14
  interface IngestCommandOptions {
6
15
  runTextIngest: (args: KtxTextIngestArgs, io: KtxCliIo, deps: KtxCliDeps) => Promise<number>;
7
16
  }
@@ -1,9 +1,35 @@
1
- import { Option } from '@commander-js/extra-typings';
1
+ import { InvalidArgumentError, Option } from '@commander-js/extra-typings';
2
2
  import { collectOption, parsePositiveIntegerOption, resolveCommandProjectDir, } from '../cli-program.js';
3
+ import { KTX_SCAN_ENRICHMENT_STAGES } from '../context/scan/enrichment-state.js';
3
4
  import { runtimeInstallPolicyFromFlags } from '../managed-python-command.js';
4
5
  import { profileMark } from '../startup-profile.js';
5
6
  import { resolveConnectionSelection } from './connection-selection.js';
6
7
  profileMark('module:commands/ingest-commands');
8
+ /**
9
+ * Parses `--stages` into an ordered, de-duplicated subset of the canonical
10
+ * enrichment-stage registry. An unknown or empty name is a hard parse error so
11
+ * a typo never silently degrades to "run everything."
12
+ *
13
+ * @internal
14
+ */
15
+ export function parseEnrichmentStagesOption(value) {
16
+ const names = value
17
+ .split(',')
18
+ .map((name) => name.trim())
19
+ .filter((name) => name.length > 0);
20
+ if (names.length === 0) {
21
+ throw new InvalidArgumentError(`must be a non-empty comma-separated list of stages (${KTX_SCAN_ENRICHMENT_STAGES.join(', ')})`);
22
+ }
23
+ const valid = new Set(KTX_SCAN_ENRICHMENT_STAGES);
24
+ const selected = new Set();
25
+ for (const name of names) {
26
+ if (!valid.has(name)) {
27
+ throw new InvalidArgumentError(`unknown stage "${name}"; valid stages are ${KTX_SCAN_ENRICHMENT_STAGES.join(', ')}`);
28
+ }
29
+ selected.add(name);
30
+ }
31
+ return KTX_SCAN_ENRICHMENT_STAGES.filter((stage) => selected.has(stage));
32
+ }
7
33
  export function registerIngestCommands(program, context, commandOptions) {
8
34
  const ingest = program
9
35
  .command('ingest')
@@ -14,8 +40,10 @@ export function registerIngestCommands(program, context, commandOptions) {
14
40
  .addOption(new Option('--query-history', 'Include database query-history usage patterns').conflicts('noQueryHistory'))
15
41
  .addOption(new Option('--no-query-history', 'Skip database query-history usage patterns'))
16
42
  .option('--query-history-window-days <days>', 'Query-history lookback window for this run', parsePositiveIntegerOption)
43
+ .option('--stages <stages>', 'Comma-separated enrichment stages to (re)run (descriptions,embeddings,relationships); omit to run all', parseEnrichmentStagesOption)
17
44
  .option('--text <content>', 'Capture inline text into ktx memory; repeatable', collectOption, [])
18
45
  .option('--file <path>', 'Capture a text file into ktx memory; use - for stdin; repeatable', collectOption, [])
46
+ .option('--verbatim', 'Store each --text/--file document body unchanged as a GLOBAL wiki page; the LLM derives only metadata', false)
19
47
  .option('--connection-id <connectionId>', 'ktx connection id to tag captured text/file notes')
20
48
  .option('--user-id <id>', 'Memory user id for text/file capture attribution', 'local-cli')
21
49
  .option('--fail-fast', 'Stop after the first failed text/file item', false)
@@ -27,6 +55,12 @@ export function registerIngestCommands(program, context, commandOptions) {
27
55
  ingest.action(async (connectionId, options, command) => {
28
56
  const projectDir = resolveCommandProjectDir(command);
29
57
  const hasTextCapture = options.text.length > 0 || options.file.length > 0;
58
+ if (options.verbatim === true && !hasTextCapture) {
59
+ command.error('error: --verbatim requires --text or --file');
60
+ }
61
+ if (options.stages !== undefined && hasTextCapture) {
62
+ command.error('error: --stages applies to database ingest only; it cannot be combined with --text or --file');
63
+ }
30
64
  if (hasTextCapture) {
31
65
  if (connectionId !== undefined) {
32
66
  command.error('error: --text/--file does not accept a positional connection id; use --connection-id <id> to tag captured notes');
@@ -42,6 +76,7 @@ export function registerIngestCommands(program, context, commandOptions) {
42
76
  userId: options.userId,
43
77
  json: options.json === true,
44
78
  failFast: options.failFast === true,
79
+ ...(options.verbatim === true ? { verbatim: true } : {}),
45
80
  }, context.io, context.deps));
46
81
  return;
47
82
  }
@@ -57,6 +92,7 @@ export function registerIngestCommands(program, context, commandOptions) {
57
92
  inputMode: options.input === false ? 'disabled' : 'auto',
58
93
  queryHistory,
59
94
  ...(options.queryHistoryWindowDays !== undefined ? { queryHistoryWindowDays: options.queryHistoryWindowDays } : {}),
95
+ ...(options.stages ? { stages: options.stages } : {}),
60
96
  cliVersion: context.packageInfo.version,
61
97
  runtimeInstallPolicy: runtimeInstallPolicyFromFlags(options),
62
98
  };
@@ -17,6 +17,7 @@ export function registerWikiCommands(program, context) {
17
17
  .usage('[options] [query...]')
18
18
  .argument('[query...]', 'Search query; omit to list all pages')
19
19
  .option('--user-id <id>', 'Local user id', 'local')
20
+ .option('-c, --connection <id>', 'Scope results to one connection (unscoped pages plus pages tagged with it)')
20
21
  .option('--limit <number>', 'Maximum search results (search mode only)', parsePositiveIntegerOption)
21
22
  .addOption(new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
22
23
  'pretty',
@@ -32,6 +33,7 @@ export function registerWikiCommands(program, context) {
32
33
  command: 'list',
33
34
  projectDir: resolveCommandProjectDir(command),
34
35
  userId: options.userId,
36
+ ...(options.connection !== undefined ? { connectionId: options.connection } : {}),
35
37
  output: options.output,
36
38
  json: options.json,
37
39
  cliVersion: context.packageInfo.version,
@@ -43,6 +45,7 @@ export function registerWikiCommands(program, context) {
43
45
  projectDir: resolveCommandProjectDir(command),
44
46
  query: query.join(' '),
45
47
  userId: options.userId,
48
+ ...(options.connection !== undefined ? { connectionId: options.connection } : {}),
46
49
  output: options.output,
47
50
  json: options.json,
48
51
  ...(isDebugEnabled(command) ? { debug: true } : {}),
@@ -42,7 +42,8 @@ function sourceType(value) {
42
42
  value === 'metabase' ||
43
43
  value === 'looker' ||
44
44
  value === 'lookml' ||
45
- value === 'notion') {
45
+ value === 'notion' ||
46
+ value === 'gdrive') {
46
47
  return value;
47
48
  }
48
49
  throw new InvalidArgumentError(`invalid choice '${value}'`);
@@ -121,6 +122,9 @@ function shouldShowSetupEntryMenu(options, command) {
121
122
  'sourceTarget',
122
123
  'metabaseDatabaseId',
123
124
  'notionCrawlMode',
125
+ 'gdriveServiceAccountKeyRef',
126
+ 'gdriveFolderId',
127
+ 'gdriveRecursive',
124
128
  'skipSources',
125
129
  ].some((optionName) => optionWasSpecified(command, optionName));
126
130
  }
@@ -213,6 +217,10 @@ export function registerSetupCommands(program, context) {
213
217
  .argParser((value, previous) => [...previous, value])
214
218
  .default([])
215
219
  .hideHelp())
220
+ .addOption(new Option('--gdrive-service-account-key-ref <ref>', 'file: reference to a Google service account JSON key')
221
+ .hideHelp())
222
+ .addOption(new Option('--gdrive-folder-id <id>', 'Google Drive folder id to ingest').hideHelp())
223
+ .addOption(new Option('--gdrive-recursive', 'Recursively traverse Google Drive subfolders').hideHelp().default(false))
216
224
  .addOption(new Option('--skip-sources', 'Mark optional source setup complete with no sources').hideHelp().default(false))
217
225
  .showHelpAfterError();
218
226
  setup.hook('preAction', (_thisCommand, actionCommand) => {
@@ -349,6 +357,11 @@ export function registerSetupCommands(program, context) {
349
357
  ...(options.metabaseDatabaseId !== undefined ? { metabaseDatabaseId: options.metabaseDatabaseId } : {}),
350
358
  ...(options.notionCrawlMode ? { notionCrawlMode: options.notionCrawlMode } : {}),
351
359
  ...(options.notionRootPageId.length > 0 ? { notionRootPageIds: options.notionRootPageId } : {}),
360
+ ...(options.gdriveServiceAccountKeyRef
361
+ ? { gdriveServiceAccountKeyRef: options.gdriveServiceAccountKeyRef }
362
+ : {}),
363
+ ...(options.gdriveFolderId ? { gdriveFolderId: options.gdriveFolderId } : {}),
364
+ ...(options.gdriveRecursive ? { gdriveRecursive: true } : {}),
352
365
  runInitialSourceIngest: false,
353
366
  skipSources: options.skipSources === true,
354
367
  showEntryMenu: shouldShowSetupEntryMenu(options, command),
@@ -1,3 +1,5 @@
1
1
  import type { KtxProjectConnectionConfig } from './context/project/config.js';
2
+ /** @internal Canonical SQL-warehouse driver ids; the dialect-notes coverage test derives its required coverage from this set. */
3
+ export declare const KTX_DATABASE_DRIVER_IDS: readonly ["sqlite", "postgres", "mysql", "clickhouse", "sqlserver", "bigquery", "snowflake"];
2
4
  export declare function normalizeConnectionDriver(connection: KtxProjectConnectionConfig): string;
3
5
  export declare function isDatabaseDriver(driver: string): boolean;
@@ -1,4 +1,5 @@
1
- const KTX_DATABASE_DRIVER_IDS = new Set([
1
+ /** @internal Canonical SQL-warehouse driver ids; the dialect-notes coverage test derives its required coverage from this set. */
2
+ export const KTX_DATABASE_DRIVER_IDS = [
2
3
  'sqlite',
3
4
  'postgres',
4
5
  'mysql',
@@ -6,12 +7,15 @@ const KTX_DATABASE_DRIVER_IDS = new Set([
6
7
  'sqlserver',
7
8
  'bigquery',
8
9
  'snowflake',
9
- ]);
10
+ ];
11
+ // mongodb is a database driver but has no SQL dialect, so it sits outside the
12
+ // dialect-notes coverage set above.
13
+ const databaseDriverIds = new Set([...KTX_DATABASE_DRIVER_IDS, 'mongodb']);
10
14
  export function normalizeConnectionDriver(connection) {
11
15
  return String(connection.driver ?? '')
12
16
  .trim()
13
17
  .toLowerCase();
14
18
  }
15
19
  export function isDatabaseDriver(driver) {
16
- return KTX_DATABASE_DRIVER_IDS.has(driver.trim().toLowerCase());
20
+ return databaseDriverIds.has(driver.trim().toLowerCase());
17
21
  }
@@ -1,6 +1,7 @@
1
1
  import type { LookerClient } from './context/ingest/adapters/looker/client.js';
2
2
  import type { MetabaseRuntimeClient } from './context/ingest/adapters/metabase/client-port.js';
3
3
  import { NotionClient } from './context/ingest/adapters/notion/notion-client.js';
4
+ import { createGoogleDocsClients } from './context/ingest/adapters/gdrive/gdrive-client.js';
4
5
  import { testRepoConnection } from './context/ingest/repo-fetch.js';
5
6
  import { type KtxLocalProject } from './context/project/project.js';
6
7
  import type { KtxCliIo } from './index.js';
@@ -19,12 +20,14 @@ export type KtxConnectionArgs = {
19
20
  type MetabaseTestPort = Pick<MetabaseRuntimeClient, 'testConnection' | 'getDatabases' | 'cleanup'>;
20
21
  type LookerTestPort = Pick<LookerClient, 'testConnection'>;
21
22
  type NotionTestPort = Pick<NotionClient, 'retrieveBotUser'>;
23
+ type GdriveTestPort = Pick<ReturnType<typeof createGoogleDocsClients>['drive'], 'listFiles' | 'getFile'>;
22
24
  type TestRepoConnection = typeof testRepoConnection;
23
25
  export interface KtxConnectionDeps {
24
26
  createScanConnector?: typeof createKtxCliScanConnector;
25
27
  createMetabaseClient?: (project: KtxLocalProject, connectionId: string) => Promise<MetabaseTestPort>;
26
28
  createLookerClient?: (project: KtxLocalProject, connectionId: string) => Promise<LookerTestPort>;
27
29
  createNotionClient?: (project: KtxLocalProject, connectionId: string) => Promise<NotionTestPort>;
30
+ createGdriveClient?: (project: KtxLocalProject, connectionId: string) => Promise<GdriveTestPort>;
28
31
  testRepoConnection?: TestRepoConnection;
29
32
  }
30
33
  export declare function runKtxConnection(args: KtxConnectionArgs, io?: KtxCliIo, deps?: KtxConnectionDeps): Promise<number>;
@@ -1,8 +1,11 @@
1
1
  import { DEFAULT_METABASE_CLIENT_CONFIG, DefaultMetabaseConnectionClientFactory } from './context/ingest/adapters/metabase/client.js';
2
2
  import { DefaultLookerConnectionClientFactory } from './context/ingest/adapters/looker/factory.js';
3
3
  import { NotionClient } from './context/ingest/adapters/notion/notion-client.js';
4
+ import { parseGdriveConnectionConfig, resolveGdriveServiceAccountKey } from './context/connections/gdrive-config.js';
4
5
  import { createLocalLookerCredentialResolver } from './context/ingest/adapters/looker/local-looker.adapter.js';
5
6
  import { metabaseRuntimeConfigFromLocalConnection } from './context/ingest/adapters/metabase/local-metabase.adapter.js';
7
+ import { createGoogleDocsClients, verifyGdriveFolderAndCountDocs } from './context/ingest/adapters/gdrive/gdrive-client.js';
8
+ import { gdriveServiceAccountKeySchema } from './context/ingest/adapters/gdrive/types.js';
6
9
  import { testRepoConnection } from './context/ingest/repo-fetch.js';
7
10
  import { federatedConnectionListing } from './context/connections/federation.js';
8
11
  import { getDriverRegistration } from './context/connections/drivers.js';
@@ -28,6 +31,7 @@ const SUPPORTED_TEST_DRIVERS = [
28
31
  'metabase',
29
32
  'looker',
30
33
  'notion',
34
+ 'gdrive',
31
35
  'dbt',
32
36
  'metricflow',
33
37
  'lookml',
@@ -120,6 +124,25 @@ async function testNotionConnection(project, connectionId, createClient) {
120
124
  const bot = await client.retrieveBotUser();
121
125
  return { bot: describeNotionBot(bot) };
122
126
  }
127
+ async function createDefaultGdriveClient(project, connectionId) {
128
+ const connection = project.config.connections[connectionId];
129
+ if (!connection) {
130
+ throw new Error(`Connection "${connectionId}" is not configured in ktx.yaml`);
131
+ }
132
+ const parsed = parseGdriveConnectionConfig(connection);
133
+ const keyText = await resolveGdriveServiceAccountKey(parsed.service_account_key_ref);
134
+ const key = gdriveServiceAccountKeySchema.parse(JSON.parse(keyText));
135
+ return createGoogleDocsClients(key).drive;
136
+ }
137
+ async function testGdriveConnection(project, connectionId, createClient) {
138
+ const connection = project.config.connections[connectionId];
139
+ if (!connection) {
140
+ throw new Error(`Connection "${connectionId}" is not configured in ktx.yaml`);
141
+ }
142
+ const parsed = parseGdriveConnectionConfig(connection);
143
+ const client = await createClient(project, connectionId);
144
+ return { docs: await verifyGdriveFolderAndCountDocs(client, parsed.folder_id) };
145
+ }
123
146
  function extractGitConnectionFields(project, connectionId, driver) {
124
147
  const connection = project.config.connections[connectionId];
125
148
  if (!connection) {
@@ -164,6 +187,10 @@ async function testConnectionByDriver(project, connectionId, deps) {
164
187
  const result = await testNotionConnection(project, connectionId, deps.createNotionClient ?? createDefaultNotionClient);
165
188
  return { driver, detailKey: 'Bot', detailValue: result.bot };
166
189
  }
190
+ if (driver === 'gdrive') {
191
+ const result = await testGdriveConnection(project, connectionId, deps.createGdriveClient ?? createDefaultGdriveClient);
192
+ return { driver, detailKey: 'Docs', detailValue: String(result.docs) };
193
+ }
167
194
  if (driver === 'dbt' || driver === 'metricflow' || driver === 'lookml') {
168
195
  const result = await testGitRepoConnection(project, connectionId, driver, deps.testRepoConnection ?? testRepoConnection);
169
196
  return { driver, detailKey: 'Repo', detailValue: result.repoUrl };
@@ -7,13 +7,23 @@ export interface KtxBigQueryConnectionConfig {
7
7
  credentials_json?: string;
8
8
  location?: string;
9
9
  max_bytes_billed?: number | string;
10
- job_timeout_ms?: number;
10
+ query_timeout_ms?: number;
11
11
  [key: string]: unknown;
12
12
  }
13
+ /**
14
+ * A dataset to introspect, paired with the project that hosts it. `project`
15
+ * defaults to the billing project (`credentials.project_id`) when an entry has
16
+ * no `project.` prefix; a fully-qualified `project.dataset` entry resolves to
17
+ * its own host project. Jobs always bill in `credentials.project_id`.
18
+ */
19
+ export interface BigQueryDatasetRef {
20
+ project: string;
21
+ dataset: string;
22
+ }
13
23
  export interface KtxBigQueryResolvedConnectionConfig {
14
24
  projectId: string;
15
25
  credentials: Record<string, unknown>;
16
- datasetIds: string[];
26
+ datasetIds: BigQueryDatasetRef[];
17
27
  location?: string;
18
28
  }
19
29
  export interface KtxBigQueryReadOnlyQueryInput extends KtxReadOnlyQueryInput {
@@ -72,7 +82,7 @@ export interface KtxBigQueryClient {
72
82
  }): Promise<[Array<{
73
83
  id?: string;
74
84
  }>, ...unknown[]]>;
75
- dataset(datasetId: string): KtxBigQueryDataset;
85
+ dataset(datasetId: string, projectId: string): KtxBigQueryDataset;
76
86
  createQueryJob(input: {
77
87
  query: string;
78
88
  location?: string;
@@ -94,7 +104,6 @@ export interface KtxBigQueryScanConnectorOptions {
94
104
  env?: NodeJS.ProcessEnv;
95
105
  now?: () => Date;
96
106
  maxBytesBilled?: number | string;
97
- queryTimeoutMs?: number;
98
107
  }
99
108
  /** @internal */
100
109
  export declare function prepareBigQueryReadOnlyQuery(sql: string, params?: Record<string, unknown>): {
@@ -117,7 +126,7 @@ export declare class KtxBigQueryScanConnector implements KtxScanConnector {
117
126
  private readonly clientFactory;
118
127
  private readonly now;
119
128
  private readonly maxBytesBilled?;
120
- private readonly queryTimeoutMs?;
129
+ private readonly deadlineMs;
121
130
  private readonly dialect;
122
131
  private client;
123
132
  constructor(options: KtxBigQueryScanConnectorOptions);
@@ -130,14 +139,16 @@ export declare class KtxBigQueryScanConnector implements KtxScanConnector {
130
139
  columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise<KtxColumnStatsResult | null>;
131
140
  executeReadOnly(input: KtxBigQueryReadOnlyQueryInput, _ctx: KtxScanContext): Promise<KtxQueryResult>;
132
141
  getColumnDistinctValues(table: KtxTableRef, columnName: string, options: KtxBigQueryColumnDistinctValuesOptions): Promise<KtxBigQueryColumnDistinctValuesResult | null>;
133
- getTableRowCount(tableName: string, datasetId?: string): Promise<number>;
142
+ getTableRowCount(tableName: string, ref?: BigQueryDatasetRef | undefined): Promise<number>;
134
143
  qTableName(table: Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>): string;
135
144
  quoteIdentifier(identifier: string): string;
136
145
  listSchemas(): Promise<string[]>;
137
146
  listTables(datasetIds?: string[]): Promise<KtxTableListEntry[]>;
147
+ private listTablesInProject;
138
148
  cleanup(): Promise<void>;
139
149
  private getClient;
140
150
  private requireDatasetIdsForScan;
151
+ private qualifiedDatasetLabel;
141
152
  private query;
142
153
  private queryRaw;
143
154
  private singleNumber;