@kaelio/ktx 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/assets/python/{kaelio_ktx-0.8.0-py3-none-any.whl → kaelio_ktx-0.10.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/clack.d.ts +6 -0
  5. package/dist/clack.js +17 -2
  6. package/dist/cli-program.d.ts +3 -0
  7. package/dist/cli-program.js +42 -2
  8. package/dist/cli-runtime.d.ts +3 -0
  9. package/dist/cli-runtime.js +94 -3
  10. package/dist/commands/setup-commands.js +3 -4
  11. package/dist/connection-recovery.d.ts +34 -0
  12. package/dist/connection-recovery.js +82 -0
  13. package/dist/connection.js +26 -2
  14. package/dist/connectors/bigquery/connector.d.ts +2 -5
  15. package/dist/connectors/bigquery/connector.js +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +2 -5
  17. package/dist/connectors/clickhouse/connector.js +2 -2
  18. package/dist/connectors/mysql/connector.d.ts +7 -6
  19. package/dist/connectors/mysql/connector.js +25 -5
  20. package/dist/connectors/mysql/dialect.d.ts +1 -1
  21. package/dist/connectors/mysql/dialect.js +12 -2
  22. package/dist/connectors/postgres/connector.d.ts +2 -5
  23. package/dist/connectors/postgres/connector.js +2 -2
  24. package/dist/connectors/snowflake/connector.d.ts +2 -5
  25. package/dist/connectors/snowflake/connector.js +2 -2
  26. package/dist/connectors/sqlite/connector.d.ts +2 -5
  27. package/dist/connectors/sqlite/connector.js +2 -2
  28. package/dist/connectors/sqlserver/connector.d.ts +2 -5
  29. package/dist/connectors/sqlserver/connector.js +2 -2
  30. package/dist/context/connections/drivers.d.ts +0 -1
  31. package/dist/context/connections/drivers.js +0 -7
  32. package/dist/context/connections/query-executor.d.ts +2 -1
  33. package/dist/context/core/abort.d.ts +9 -0
  34. package/dist/context/core/abort.js +36 -0
  35. package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
  36. package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
  37. package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
  38. package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
  39. package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
  40. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +30 -0
  41. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +194 -0
  42. package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
  43. package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
  44. package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
  45. package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
  46. package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
  47. package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
  48. package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
  49. package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
  50. package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
  51. package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
  52. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  53. package/dist/context/ingest/final-gate-repair.d.ts +1 -0
  54. package/dist/context/ingest/final-gate-repair.js +1 -0
  55. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  56. package/dist/context/ingest/ingest-bundle.runner.js +127 -53
  57. package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
  58. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
  59. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
  60. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  61. package/dist/context/ingest/local-adapters.js +21 -4
  62. package/dist/context/ingest/local-bundle-runtime.js +13 -5
  63. package/dist/context/ingest/local-ingest.d.ts +1 -0
  64. package/dist/context/ingest/local-ingest.js +13 -3
  65. package/dist/context/ingest/memory-flow/events.js +1 -1
  66. package/dist/context/ingest/memory-flow/schema.js +8 -3
  67. package/dist/context/ingest/memory-flow/types.d.ts +7 -3
  68. package/dist/context/ingest/ports.d.ts +3 -5
  69. package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
  70. package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
  71. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
  72. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  73. package/dist/context/ingest/types.d.ts +1 -0
  74. package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
  75. package/dist/context/llm/ai-sdk-runtime.js +152 -16
  76. package/dist/context/llm/claude-code-runtime.d.ts +6 -4
  77. package/dist/context/llm/claude-code-runtime.js +127 -48
  78. package/dist/context/llm/codex-exec-events.d.ts +20 -0
  79. package/dist/context/llm/codex-exec-events.js +155 -0
  80. package/dist/context/llm/codex-isolation.d.ts +3 -0
  81. package/dist/context/llm/codex-isolation.js +5 -0
  82. package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
  83. package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
  84. package/dist/context/llm/codex-models.d.ts +2 -0
  85. package/dist/context/llm/codex-models.js +17 -0
  86. package/dist/context/llm/codex-runtime-config.d.ts +16 -0
  87. package/dist/context/llm/codex-runtime-config.js +19 -0
  88. package/dist/context/llm/codex-runtime.d.ts +37 -0
  89. package/dist/context/llm/codex-runtime.js +347 -0
  90. package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
  91. package/dist/context/llm/codex-sdk-runner.js +63 -0
  92. package/dist/context/llm/local-config.d.ts +16 -4
  93. package/dist/context/llm/local-config.js +18 -2
  94. package/dist/context/llm/rate-limit-governor.d.ts +103 -0
  95. package/dist/context/llm/rate-limit-governor.js +285 -0
  96. package/dist/context/llm/runtime-port.d.ts +3 -6
  97. package/dist/context/mcp/context-tools.js +43 -13
  98. package/dist/context/project/config.d.ts +14 -0
  99. package/dist/context/project/config.js +37 -2
  100. package/dist/context/scan/types.d.ts +15 -2
  101. package/dist/context/scan/types.js +12 -0
  102. package/dist/context/sl/description-normalization.js +4 -14
  103. package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
  104. package/dist/context/sql-analysis/ports.d.ts +12 -2
  105. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  106. package/dist/context-build-view.d.ts +13 -0
  107. package/dist/context-build-view.js +63 -32
  108. package/dist/demo-metrics.d.ts +0 -2
  109. package/dist/demo-metrics.js +1 -11
  110. package/dist/ingest.d.ts +1 -0
  111. package/dist/ingest.js +32 -3
  112. package/dist/io/buffered-command-io.d.ts +11 -0
  113. package/dist/io/buffered-command-io.js +28 -0
  114. package/dist/io/symbols.d.ts +2 -0
  115. package/dist/io/symbols.js +2 -0
  116. package/dist/llm/types.d.ts +1 -1
  117. package/dist/local-adapters.d.ts +10 -2
  118. package/dist/local-adapters.js +19 -3
  119. package/dist/memory-flow-hud.js +8 -16
  120. package/dist/next-steps.js +1 -2
  121. package/dist/progress-port-adapter.d.ts +6 -0
  122. package/dist/progress-port-adapter.js +18 -0
  123. package/dist/public-ingest.d.ts +20 -1
  124. package/dist/public-ingest.js +228 -42
  125. package/dist/reveal-password-prompt.d.ts +24 -0
  126. package/dist/reveal-password-prompt.js +78 -0
  127. package/dist/scan.js +21 -3
  128. package/dist/setup-context.d.ts +2 -0
  129. package/dist/setup-context.js +133 -27
  130. package/dist/setup-databases.d.ts +18 -1
  131. package/dist/setup-databases.js +378 -249
  132. package/dist/setup-demo-tour.js +1 -0
  133. package/dist/setup-embeddings.js +1 -1
  134. package/dist/setup-models.d.ts +11 -15
  135. package/dist/setup-models.js +140 -276
  136. package/dist/setup-prompts.js +3 -2
  137. package/dist/setup-ready-menu.d.ts +16 -2
  138. package/dist/setup-ready-menu.js +37 -5
  139. package/dist/setup-sources.js +115 -35
  140. package/dist/setup.d.ts +1 -1
  141. package/dist/setup.js +23 -11
  142. package/dist/sl.d.ts +2 -2
  143. package/dist/sl.js +20 -4
  144. package/dist/sql.js +18 -2
  145. package/dist/star-prompt/cache.d.ts +16 -0
  146. package/dist/star-prompt/cache.js +45 -0
  147. package/dist/star-prompt/star-count.d.ts +7 -0
  148. package/dist/star-prompt/star-count.js +66 -0
  149. package/dist/star-prompt/star-line.d.ts +12 -0
  150. package/dist/star-prompt/star-line.js +26 -0
  151. package/dist/status-project.d.ts +11 -0
  152. package/dist/status-project.js +50 -1
  153. package/dist/telemetry/command-hook.d.ts +1 -0
  154. package/dist/telemetry/command-hook.js +3 -1
  155. package/dist/telemetry/emitter.d.ts +10 -0
  156. package/dist/telemetry/emitter.js +31 -0
  157. package/dist/telemetry/events.d.ts +35 -6
  158. package/dist/telemetry/events.js +25 -2
  159. package/dist/telemetry/exception.d.ts +18 -0
  160. package/dist/telemetry/exception.js +162 -0
  161. package/dist/telemetry/identity.d.ts +0 -1
  162. package/dist/telemetry/identity.js +6 -6
  163. package/dist/telemetry/index.d.ts +15 -2
  164. package/dist/telemetry/index.js +15 -3
  165. package/dist/telemetry/redaction-secrets.d.ts +11 -0
  166. package/dist/telemetry/redaction-secrets.js +92 -0
  167. package/dist/telemetry/scrubber.d.ts +10 -0
  168. package/dist/telemetry/scrubber.js +20 -0
  169. package/dist/update-check/cache.d.ts +21 -0
  170. package/dist/update-check/cache.js +38 -0
  171. package/dist/update-check/channel.d.ts +15 -0
  172. package/dist/update-check/channel.js +30 -0
  173. package/dist/update-check/registry.d.ts +1 -0
  174. package/dist/update-check/registry.js +45 -0
  175. package/dist/update-check/update-check.d.ts +43 -0
  176. package/dist/update-check/update-check.js +116 -0
  177. package/package.json +12 -4
  178. package/dist/context/connections/local-query-executor.d.ts +0 -6
  179. package/dist/context/connections/local-query-executor.js +0 -39
  180. package/dist/context/connections/postgres-query-executor.d.ts +0 -25
  181. package/dist/context/connections/postgres-query-executor.js +0 -53
  182. package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
  183. package/dist/context/connections/sqlite-query-executor.js +0 -74
@@ -1,6 +1,11 @@
1
1
  import { type KtxSetupPromptOption } from './setup-prompts.js';
2
2
  import type { KtxSetupStatus } from './setup.js';
3
3
  export type KtxSetupReadyAction = 'models' | 'embeddings' | 'databases' | 'sources' | 'runtime' | 'context' | 'agents' | 'exit';
4
+ /**
5
+ * Where a project stands once its `ktx.yaml` exists. Single source of truth for the
6
+ * end-of-setup interception: each state maps to exactly one obvious next action.
7
+ */
8
+ export type KtxSetupCompletion = 'incomplete' | 'needs-context' | 'needs-agents' | 'ready';
4
9
  interface KtxSetupReadyMenuPromptAdapter {
5
10
  select(options: {
6
11
  message: string;
@@ -11,8 +16,17 @@ interface KtxSetupReadyMenuPromptAdapter {
11
16
  export interface KtxSetupReadyMenuDeps {
12
17
  prompts?: KtxSetupReadyMenuPromptAdapter;
13
18
  }
14
- export declare function isKtxPreAgentSetupReady(status: KtxSetupStatus): boolean;
15
- export declare function isKtxSetupReady(status: KtxSetupStatus): boolean;
19
+ export declare function setupHasContextTargets(status: KtxSetupStatus): boolean;
20
+ export declare function classifyKtxSetupCompletion(status: KtxSetupStatus): KtxSetupCompletion;
21
+ /**
22
+ * Shown when a returning user re-runs `ktx setup` on a fully-ready project. Leads with
23
+ * "you're done" (the readiness note is printed by the caller first) and keeps the
24
+ * section editor one explicit step away rather than defaulting into it.
25
+ */
26
+ export declare function runKtxSetupReadyMenu(status: KtxSetupStatus, deps?: KtxSetupReadyMenuDeps): Promise<{
27
+ action: KtxSetupReadyAction;
28
+ }>;
29
+ /** @internal Reached only through {@link runKtxSetupReadyMenu}; exported for unit tests. */
16
30
  export declare function runKtxSetupReadyChangeMenu(status: KtxSetupStatus, deps?: KtxSetupReadyMenuDeps): Promise<{
17
31
  action: KtxSetupReadyAction;
18
32
  }>;
@@ -1,23 +1,55 @@
1
1
  import { createKtxSetupPromptAdapter, } from './setup-prompts.js';
2
- export function isKtxPreAgentSetupReady(status) {
2
+ export function setupHasContextTargets(status) {
3
+ return status.databases.length > 0 || status.sources.length > 0;
4
+ }
5
+ function setupConfigReady(status) {
3
6
  return (status.project.ready &&
4
7
  status.llm.ready &&
5
8
  status.embeddings.ready &&
6
9
  status.databases.every((database) => database.ready) &&
7
10
  status.sources.every((source) => source.ready) &&
8
11
  status.runtime.ready &&
9
- status.context.ready);
12
+ setupHasContextTargets(status));
10
13
  }
11
- export function isKtxSetupReady(status) {
12
- return isKtxPreAgentSetupReady(status) && status.agents.some((agent) => agent.ready);
14
+ export function classifyKtxSetupCompletion(status) {
15
+ if (!setupConfigReady(status)) {
16
+ return 'incomplete';
17
+ }
18
+ if (!status.context.ready) {
19
+ return 'needs-context';
20
+ }
21
+ if (!status.agents.some((agent) => agent.ready)) {
22
+ return 'needs-agents';
23
+ }
24
+ return 'ready';
13
25
  }
14
26
  function createPromptAdapter() {
15
27
  return createKtxSetupPromptAdapter({ selectCancelValue: 'exit' });
16
28
  }
29
+ /**
30
+ * Shown when a returning user re-runs `ktx setup` on a fully-ready project. Leads with
31
+ * "you're done" (the readiness note is printed by the caller first) and keeps the
32
+ * section editor one explicit step away rather than defaulting into it.
33
+ */
34
+ export async function runKtxSetupReadyMenu(status, deps = {}) {
35
+ const prompts = deps.prompts ?? createPromptAdapter();
36
+ const choice = await prompts.select({
37
+ message: 'Anything else?',
38
+ options: [
39
+ { value: 'done', label: "Done — I'll start using ktx" },
40
+ { value: 'change', label: 'Change a setting' },
41
+ ],
42
+ });
43
+ if (choice !== 'change') {
44
+ return { action: 'exit' };
45
+ }
46
+ return runKtxSetupReadyChangeMenu(status, { prompts });
47
+ }
48
+ /** @internal Reached only through {@link runKtxSetupReadyMenu}; exported for unit tests. */
17
49
  export async function runKtxSetupReadyChangeMenu(status, deps = {}) {
18
50
  const prompts = deps.prompts ?? createPromptAdapter();
19
51
  const action = (await prompts.select({
20
- message: `KTX is already set up for ${status.project.name ?? status.project.path}. What would you like to change?`,
52
+ message: 'What would you like to change?',
21
53
  options: [
22
54
  { value: 'models', label: 'Models' },
23
55
  { value: 'embeddings', label: 'Embeddings' },
@@ -19,6 +19,7 @@ import { markKtxSetupStateStepComplete } from './context/project/setup-config.js
19
19
  import { errorMessage, writePrefixedLines } from './clack.js';
20
20
  import { pickNotionRootPages } from './notion-page-picker.js';
21
21
  import { runKtxSourceMapping } from './source-mapping.js';
22
+ import { runConnectionSetupWithRecovery, } from './connection-recovery.js';
22
23
  import { withMultiselectNavigation, withTextInputNavigation } from './prompt-navigation.js';
23
24
  import { runKtxPublicIngest } from './public-ingest.js';
24
25
  import { writeProjectLocalSecretReference } from './setup-secrets.js';
@@ -28,11 +29,11 @@ import { createKtxSetupPromptAdapter, } from './setup-prompts.js';
28
29
  const DEFAULT_NOTION_MAX_KNOWLEDGE_CREATES_PER_RUN = 25;
29
30
  const SOURCE_OPTIONS = [
30
31
  { value: 'dbt', label: 'dbt' },
31
- { value: 'metricflow', label: 'MetricFlow' },
32
32
  { value: 'metabase', label: 'Metabase' },
33
+ { value: 'notion', label: 'Notion' },
34
+ { value: 'metricflow', label: 'MetricFlow' },
33
35
  { value: 'looker', label: 'Looker' },
34
36
  { value: 'lookml', label: 'LookML' },
35
- { value: 'notion', label: 'Notion' },
36
37
  ];
37
38
  const SOURCE_LABELS = Object.fromEntries(SOURCE_OPTIONS.map((option) => [option.value, option.label]));
38
39
  const PRIMARY_SOURCE_DRIVERS = new Set([
@@ -145,8 +146,8 @@ async function chooseSourceCredentialRef(input) {
145
146
  message: `How should KTX find your ${input.label}?`,
146
147
  options: [
147
148
  ...(input.existingRef ? [{ value: 'keep', label: 'Keep existing credential' }] : []),
148
- { value: 'env', label: `Use ${input.envName} from the environment` },
149
149
  { value: 'paste', label: 'Paste a key and save it as a local secret file' },
150
+ { value: 'env', label: `Use ${input.envName} from the environment` },
150
151
  { value: 'back', label: 'Back' },
151
152
  ],
152
153
  });
@@ -178,8 +179,8 @@ async function chooseGitAuthCredentialRef(input) {
178
179
  message: `${label} repo requires authentication.`,
179
180
  options: [
180
181
  ...(input.existingRef ? [{ value: 'keep', label: 'Keep existing credential' }] : []),
181
- { value: 'env', label: 'Use GITHUB_TOKEN from the environment' },
182
182
  { value: 'paste', label: 'Paste a token and save it as a local secret file' },
183
+ { value: 'env', label: 'Use GITHUB_TOKEN from the environment' },
183
184
  { value: 'skip', label: 'Skip — try without authentication' },
184
185
  { value: 'back', label: 'Back' },
185
186
  ],
@@ -799,8 +800,8 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
799
800
  const selectedLocation = await prompts.select({
800
801
  message: `${source} source location`,
801
802
  options: [
802
- { value: 'path', label: 'Local path' },
803
803
  { value: 'git', label: 'Git URL' },
804
+ { value: 'path', label: 'Local path' },
804
805
  { value: 'back', label: 'Back' },
805
806
  ],
806
807
  });
@@ -1100,8 +1101,8 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
1100
1101
  const crawlMode = await prompts.select({
1101
1102
  message: 'Which Notion pages should KTX ingest?',
1102
1103
  options: [
1103
- { value: 'selected_roots', label: 'Specific pages and their subpages (choose them in a picker)' },
1104
1104
  { value: 'all_accessible', label: 'All pages the integration can access' },
1105
+ { value: 'selected_roots', label: 'Specific pages and their subpages (choose them in a picker)' },
1105
1106
  { value: 'back', label: 'Back' },
1106
1107
  ],
1107
1108
  });
@@ -1434,56 +1435,125 @@ async function validateSource(source, args, deps) {
1434
1435
  }
1435
1436
  return await (deps.validateNotion ?? defaultValidateNotion)(args.connection);
1436
1437
  }
1437
- async function saveValidateAndMaybeBuildSource(input) {
1438
- const connectionId = input.sourceChoice.kind === 'existing'
1438
+ async function createSourceSetupRollback(projectDir) {
1439
+ const project = await loadKtxProject({ projectDir });
1440
+ const previousConfig = project.config;
1441
+ const configPath = project.configPath;
1442
+ return async () => {
1443
+ await writeFile(configPath, serializeKtxProjectConfig(previousConfig), 'utf-8');
1444
+ };
1445
+ }
1446
+ function sourceConnectionId(input) {
1447
+ return input.sourceChoice.kind === 'existing' || input.sourceChoice.kind === 'edited'
1439
1448
  ? input.sourceChoice.connectionId
1440
- : input.sourceChoice.kind === 'edited'
1441
- ? input.sourceChoice.connectionId
1442
- : (input.sourceChoice.args.sourceConnectionId ?? `${input.source}-main`);
1443
- const connection = input.sourceChoice.kind === 'existing'
1444
- ? input.sourceChoice.connection
1445
- : buildConnection(input.source, input.sourceChoice.args);
1446
- const rollback = input.sourceChoice.kind === 'existing'
1447
- ? undefined
1448
- : await writeSourceConnection(input.args.projectDir, connectionId, connection, sourceAdapter(input.source), input.io);
1449
- if (input.sourceChoice.kind === 'existing') {
1450
- await ensureSourceAdapterEnabled(input.args.projectDir, input.source);
1451
- }
1452
- const validation = await validateSource(input.source, { projectDir: input.args.projectDir, connectionId, connection }, input.deps);
1449
+ : (input.sourceChoice.args.sourceConnectionId ?? `${input.source}-main`);
1450
+ }
1451
+ async function validateSourceConnectionAndMapping(input) {
1452
+ const validation = await validateSource(input.source, { projectDir: input.args.projectDir, connectionId: input.connectionId, connection: input.connection }, input.deps);
1453
1453
  if (!validation.ok) {
1454
- await rollback?.();
1455
1454
  input.io.stderr.write(`${validation.message}\n`);
1456
1455
  return { status: 'failed' };
1457
1456
  }
1458
1457
  if (input.source === 'metabase' || input.source === 'looker') {
1459
- input.prompts.log?.(`Validating ${sourceLabel(input.source)} mapping…`);
1460
- const mappingCode = await (input.deps.runMapping ?? defaultRunMapping)(input.args.projectDir, connectionId, createSetupPrefixedIo(input.io));
1458
+ input.prompts.log?.(`Validating ${sourceLabel(input.source)} mapping...`);
1459
+ const mappingCode = await (input.deps.runMapping ?? defaultRunMapping)(input.args.projectDir, input.connectionId, createSetupPrefixedIo(input.io));
1461
1460
  if (mappingCode !== 0) {
1462
- await rollback?.();
1463
1461
  return { status: 'failed' };
1464
1462
  }
1465
1463
  }
1464
+ return { status: 'ok' };
1465
+ }
1466
+ async function saveValidateAndMaybeBuildSource(input) {
1467
+ let latestChoice = input.sourceChoice;
1468
+ let latestConnectionId = sourceConnectionId({ source: input.source, sourceChoice: latestChoice });
1469
+ let latestConnection = latestChoice.kind === 'existing'
1470
+ ? latestChoice.connection
1471
+ : buildConnection(input.source, latestChoice.args);
1472
+ let configureCount = 0;
1473
+ let rollbackAfterConfigure;
1474
+ const outcome = await runConnectionSetupWithRecovery({
1475
+ label: latestConnectionId,
1476
+ interactive: input.args.inputMode !== 'disabled',
1477
+ allowSkip: true,
1478
+ io: input.io,
1479
+ prompts: input.prompts,
1480
+ snapshot: async () => {
1481
+ rollbackAfterConfigure = await createSourceSetupRollback(input.args.projectDir);
1482
+ return rollbackAfterConfigure;
1483
+ },
1484
+ configure: async () => {
1485
+ configureCount += 1;
1486
+ if (latestChoice.kind === 'existing' && configureCount === 1) {
1487
+ await ensureSourceAdapterEnabled(input.args.projectDir, input.source);
1488
+ return 'configured';
1489
+ }
1490
+ const project = await loadKtxProject({ projectDir: input.args.projectDir });
1491
+ const currentConnection = project.config.connections[latestConnectionId] ?? latestConnection;
1492
+ const useAlreadyPromptedArgs = configureCount === 1 && latestChoice.kind !== 'existing';
1493
+ const sourceArgs = useAlreadyPromptedArgs && latestChoice.kind !== 'existing'
1494
+ ? latestChoice.args
1495
+ : input.args.inputMode === 'disabled'
1496
+ ? sourceArgsFromExistingConnection({
1497
+ args: input.args,
1498
+ source: input.source,
1499
+ connectionId: latestConnectionId,
1500
+ connection: currentConnection,
1501
+ })
1502
+ : await promptForInteractiveSource(sourceArgsFromExistingConnection({
1503
+ args: input.args,
1504
+ source: input.source,
1505
+ connectionId: latestConnectionId,
1506
+ connection: currentConnection,
1507
+ }), input.source, input.prompts, input.io, {
1508
+ pickNotionRootPages: input.deps.pickNotionRootPages,
1509
+ discoverMetabaseDatabases: input.deps.discoverMetabaseDatabases,
1510
+ }, latestConnectionId, input.deps.testGitRepo, input.deps.discoverMetabaseDatabases);
1511
+ if (sourceArgs === 'back') {
1512
+ return 'back';
1513
+ }
1514
+ latestConnectionId = sourceArgs.sourceConnectionId ?? latestConnectionId;
1515
+ latestConnection = buildConnection(input.source, sourceArgs);
1516
+ latestChoice =
1517
+ latestChoice.kind === 'new'
1518
+ ? { kind: 'new', args: sourceArgs }
1519
+ : { kind: 'edited', connectionId: latestConnectionId, args: sourceArgs };
1520
+ await writeSourceConnection(input.args.projectDir, latestConnectionId, latestConnection, sourceAdapter(input.source), input.io);
1521
+ return 'configured';
1522
+ },
1523
+ validate: () => validateSourceConnectionAndMapping({
1524
+ args: input.args,
1525
+ source: input.source,
1526
+ connectionId: latestConnectionId,
1527
+ connection: latestConnection,
1528
+ prompts: input.prompts,
1529
+ io: input.io,
1530
+ deps: input.deps,
1531
+ }),
1532
+ });
1533
+ if (outcome !== 'ready') {
1534
+ return { status: outcome };
1535
+ }
1466
1536
  if (input.args.runInitialSourceIngest) {
1467
1537
  const ingestResult = await runInitialSourceIngestWithRecovery({
1468
1538
  args: input.args,
1469
- connectionId,
1539
+ connectionId: latestConnectionId,
1470
1540
  io: input.io,
1471
1541
  prompts: input.prompts,
1472
1542
  deps: input.deps,
1473
1543
  });
1474
1544
  if (ingestResult === 'failed') {
1475
- await rollback?.();
1545
+ await rollbackAfterConfigure?.();
1476
1546
  return { status: 'failed' };
1477
1547
  }
1478
1548
  if (ingestResult === 'back') {
1479
- await rollback?.();
1549
+ await rollbackAfterConfigure?.();
1480
1550
  return { status: 'back' };
1481
1551
  }
1482
1552
  }
1483
1553
  else {
1484
- input.io.stdout.write(`│ Context source ${connectionId} saved. It will be built during the context build step.\n`);
1554
+ input.io.stdout.write(`│ Context source ${latestConnectionId} saved. It will be built during the context build step.\n`);
1485
1555
  }
1486
- return { status: 'ready', connectionId };
1556
+ return { status: 'ready', connectionId: latestConnectionId };
1487
1557
  }
1488
1558
  export async function runKtxSetupSourcesStep(args, io, deps = {}) {
1489
1559
  try {
@@ -1579,8 +1649,13 @@ export async function runKtxSetupSourcesStep(args, io, deps = {}) {
1579
1649
  returnToSourceSelection = true;
1580
1650
  break;
1581
1651
  }
1582
- if (!readyConnectionIds.includes(choiceResult.connectionId)) {
1583
- readyConnectionIds.push(choiceResult.connectionId);
1652
+ if (choiceResult.status === 'skip') {
1653
+ continue;
1654
+ }
1655
+ if (choiceResult.status === 'ready') {
1656
+ if (!readyConnectionIds.includes(choiceResult.connectionId)) {
1657
+ readyConnectionIds.push(choiceResult.connectionId);
1658
+ }
1584
1659
  }
1585
1660
  }
1586
1661
  if (returnToSourceSelection) {
@@ -1592,7 +1667,7 @@ export async function runKtxSetupSourcesStep(args, io, deps = {}) {
1592
1667
  const addMore = await prompts.select({
1593
1668
  message: `${readyConnectionIds.length} context source${readyConnectionIds.length > 1 ? 's' : ''} configured (${readyConnectionIds.join(', ')}). Add another?`,
1594
1669
  options: [
1595
- { value: 'done', label: 'Done continue to context build' },
1670
+ { value: 'done', label: 'Done adding context sources' },
1596
1671
  { value: 'edit', label: 'Edit an existing context source' },
1597
1672
  { value: 'add', label: 'Add another context source' },
1598
1673
  ],
@@ -1640,8 +1715,13 @@ export async function runKtxSetupSourcesStep(args, io, deps = {}) {
1640
1715
  if (choiceResult.status === 'back') {
1641
1716
  continue;
1642
1717
  }
1643
- if (!readyConnectionIds.includes(choiceResult.connectionId)) {
1644
- readyConnectionIds.push(choiceResult.connectionId);
1718
+ if (choiceResult.status === 'skip') {
1719
+ continue;
1720
+ }
1721
+ if (choiceResult.status === 'ready') {
1722
+ if (!readyConnectionIds.includes(choiceResult.connectionId)) {
1723
+ readyConnectionIds.push(choiceResult.connectionId);
1724
+ }
1645
1725
  }
1646
1726
  continue;
1647
1727
  }
package/dist/setup.d.ts CHANGED
@@ -58,12 +58,12 @@ export type KtxSetupArgs = {
58
58
  agentScope?: KtxAgentScope;
59
59
  skipAgents?: boolean;
60
60
  inputMode: 'auto' | 'disabled';
61
+ debug?: boolean;
61
62
  yes: boolean;
62
63
  cliVersion: string;
63
64
  llmBackend?: KtxSetupLlmBackend;
64
65
  anthropicApiKeyEnv?: string;
65
66
  anthropicApiKeyFile?: string;
66
- llmModel?: string;
67
67
  vertexProject?: string;
68
68
  vertexLocation?: string;
69
69
  skipLlm: boolean;
package/dist/setup.js CHANGED
@@ -6,7 +6,7 @@ import { ktxLocalStateDbPath } from './context/project/local-state-db.js';
6
6
  import { loadKtxProject } from './context/project/project.js';
7
7
  import { readKtxSetupState } from './context/project/setup-config.js';
8
8
  import { getKtxCliPackageInfo } from './cli-runtime.js';
9
- import { formatSetupNextStepLines } from './next-steps.js';
9
+ import { formatNextStepLines, formatSetupNextStepLines } from './next-steps.js';
10
10
  import { runtimeInstallPolicyFromFlags } from './managed-python-command.js';
11
11
  import { readManagedPythonRuntimeStatus } from './managed-python-runtime.js';
12
12
  import { resolveProjectRuntimeRequirements } from './runtime-requirements.js';
@@ -16,7 +16,7 @@ import { runKtxSetupDatabasesStep, } from './setup-databases.js';
16
16
  import { runKtxSetupEmbeddingsStep } from './setup-embeddings.js';
17
17
  import { isKtxSetupLlmConfigReady, runKtxSetupAnthropicModelStep, } from './setup-models.js';
18
18
  import { runKtxSetupProjectStep } from './setup-project.js';
19
- import { isKtxPreAgentSetupReady, isKtxSetupReady, runKtxSetupReadyChangeMenu, } from './setup-ready-menu.js';
19
+ import { classifyKtxSetupCompletion, runKtxSetupReadyMenu, setupHasContextTargets, } from './setup-ready-menu.js';
20
20
  import { runKtxSetupSourcesStep } from './setup-sources.js';
21
21
  import { runKtxSetupRuntimeStep, } from './setup-runtime.js';
22
22
  import { createKtxSetupPromptAdapter, createKtxSetupUiAdapter, } from './setup-prompts.js';
@@ -47,6 +47,7 @@ async function recordSetupStep(input) {
47
47
  step: input.step,
48
48
  outcome: setupTelemetryOutcome(input.status),
49
49
  durationMs: Math.max(0, performance.now() - input.startedAt),
50
+ ...(input.errorDetail ? { errorDetail: input.errorDetail } : {}),
50
51
  },
51
52
  });
52
53
  }
@@ -286,9 +287,6 @@ function setupStatusReady(status) {
286
287
  status.sources.every((source) => source.ready) &&
287
288
  status.runtime.ready);
288
289
  }
289
- function setupHasContextTargets(status) {
290
- return status.databases.length > 0 || status.sources.length > 0;
291
- }
292
290
  function setupContextReady(status) {
293
291
  return status.context.ready;
294
292
  }
@@ -371,14 +369,22 @@ async function runKtxSetupInner(args, io, deps = {}) {
371
369
  const currentStatus = await readKtxSetupStatus(projectResult.projectDir, { cliVersion: args.cliVersion });
372
370
  let readyAction;
373
371
  if (args.inputMode !== 'disabled' && !agentsRequested) {
374
- if (isKtxSetupReady(currentStatus)) {
375
- readyAction = (await runKtxSetupReadyChangeMenu(currentStatus, deps.readyMenuDeps)).action;
376
- if (readyAction === 'exit')
372
+ const completion = classifyKtxSetupCompletion(currentStatus);
373
+ if (completion === 'ready') {
374
+ setupUi.note(formatNextStepLines().join('\n'), 'ktx is ready', io);
375
+ const choice = (await runKtxSetupReadyMenu(currentStatus, deps.readyMenuDeps)).action;
376
+ if (choice === 'exit')
377
377
  return 0;
378
+ readyAction = choice;
379
+ }
380
+ else if (completion === 'needs-context') {
381
+ // Config is done; skip the re-walk and land straight on the build prompt.
382
+ readyAction = 'context';
378
383
  }
379
- else if (isKtxPreAgentSetupReady(currentStatus)) {
384
+ else if (completion === 'needs-agents') {
380
385
  readyAction = 'agents';
381
386
  }
387
+ // 'incomplete' → readyAction stays undefined → run the full setup walk.
382
388
  }
383
389
  const runOnly = readyAction;
384
390
  const agentOnlySetup = agentsRequested || runOnly === 'agents';
@@ -437,7 +443,6 @@ async function runKtxSetupInner(args, io, deps = {}) {
437
443
  ...(args.llmBackend ? { llmBackend: args.llmBackend } : {}),
438
444
  ...(args.anthropicApiKeyEnv ? { anthropicApiKeyEnv: args.anthropicApiKeyEnv } : {}),
439
445
  ...(args.anthropicApiKeyFile ? { anthropicApiKeyFile: args.anthropicApiKeyFile } : {}),
440
- ...(args.llmModel ? { llmModel: args.llmModel } : {}),
441
446
  ...(args.vertexProject ? { vertexProject: args.vertexProject } : {}),
442
447
  ...(args.vertexLocation ? { vertexLocation: args.vertexLocation } : {}),
443
448
  forcePrompt: forcePromptSteps.has('models') || runOnly === 'models',
@@ -467,6 +472,10 @@ async function runKtxSetupInner(args, io, deps = {}) {
467
472
  const databaseResult = await databasesRunner({
468
473
  projectDir: projectResult.projectDir,
469
474
  inputMode: args.inputMode,
475
+ ...(args.debug !== undefined ? { debug: args.debug } : {}),
476
+ yes: args.yes,
477
+ cliVersion: args.cliVersion,
478
+ runtimeInstallPolicy: setupRuntimeInstallPolicy(args),
470
479
  ...(args.databaseDrivers ? { databaseDrivers: args.databaseDrivers } : {}),
471
480
  ...(args.databaseConnectionIds ? { databaseConnectionIds: args.databaseConnectionIds } : {}),
472
481
  ...(args.databaseConnectionId ? { databaseConnectionId: args.databaseConnectionId } : {}),
@@ -565,6 +574,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
565
574
  startedAt: stepStartedAt,
566
575
  io,
567
576
  cliVersion: args.cliVersion,
577
+ ...(stepResult.errorDetail ? { errorDetail: stepResult.errorDetail } : {}),
568
578
  });
569
579
  if (stepResult.status === 'failed') {
570
580
  return 1;
@@ -589,7 +599,9 @@ async function runKtxSetupInner(args, io, deps = {}) {
589
599
  }
590
600
  if (step === 'context' && stepResult.status !== 'ready') {
591
601
  if (shouldRunAgents && args.skipAgents !== true) {
592
- return 0;
602
+ // Context isn't built, so skip agent install — but still reach the
603
+ // completion screen, which states readiness and points at `ktx ingest`.
604
+ break setupLoop;
593
605
  }
594
606
  }
595
607
  forcePromptSteps.delete(step);
package/dist/sl.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { KtxCliIo } from './cli-runtime.js';
2
2
  import type { KtxSqlQueryExecutorPort } from './context/connections/query-executor.js';
3
3
  import type { KtxSemanticLayerComputePort } from './context/daemon/semantic-layer-compute.js';
4
- import { loadKtxProject } from './context/project/project.js';
4
+ import { loadKtxProject, type KtxLocalProject } from './context/project/project.js';
5
5
  import { searchLocalSlSources as defaultSearchLocalSlSources } from './context/sl/local-sl.js';
6
6
  import type { SemanticLayerQueryInput } from './context/sl/types.js';
7
7
  import { resolveProjectEmbeddingProvider } from './embedding-resolution.js';
@@ -57,7 +57,7 @@ interface KtxSlDeps {
57
57
  io: KtxSlIo;
58
58
  projectDir?: string;
59
59
  }) => Promise<KtxSemanticLayerComputePort>;
60
- createQueryExecutor?: () => KtxSqlQueryExecutorPort;
60
+ createQueryExecutor?: (project: KtxLocalProject) => KtxSqlQueryExecutorPort;
61
61
  }
62
62
  export declare function runKtxSl(args: KtxSlArgs, io?: KtxSlIo, deps?: KtxSlDeps): Promise<number>;
63
63
  export {};
package/dist/sl.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { createDefaultLocalQueryExecutor } from './context/connections/local-query-executor.js';
3
2
  import { KtxIngestEmbeddingPortAdapter } from './context/llm/embedding-port.js';
4
3
  import { loadKtxProject } from './context/project/project.js';
5
4
  import { compileLocalSlQuery } from './context/sl/local-query.js';
6
5
  import { listLocalSlSources, resolveLocalSlSource, searchLocalSlSources as defaultSearchLocalSlSources, validateLocalSlSource, } from './context/sl/local-sl.js';
7
6
  import { resolveProjectEmbeddingProvider, } from './embedding-resolution.js';
7
+ import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js';
8
8
  import { createManagedPythonSemanticLayerComputePort, } from './managed-python-command.js';
9
9
  import { profileMark } from './startup-profile.js';
10
- import { emitTelemetryEvent } from './telemetry/index.js';
10
+ import { emitTelemetryEvent, reportException } from './telemetry/index.js';
11
+ import { collectTelemetryRedactionSecrets } from './telemetry/redaction-secrets.js';
11
12
  import { scrubErrorClass } from './telemetry/scrubber.js';
12
13
  profileMark('module:sl');
13
14
  function resolutionToEmbeddingPort(resolution) {
@@ -91,8 +92,9 @@ function ambiguousSourceMessage(sourceName, connectionIds) {
91
92
  export async function runKtxSl(args, io = process, deps = {}) {
92
93
  const startedAt = performance.now();
93
94
  let queryForTelemetry;
95
+ let project;
94
96
  try {
95
- const project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir });
97
+ project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir });
96
98
  if (args.command === 'list') {
97
99
  const sources = await listLocalSlSources(project, { connectionId: args.connectionId });
98
100
  await printSlSources({
@@ -204,7 +206,7 @@ export async function runKtxSl(args, io = process, deps = {}) {
204
206
  io,
205
207
  projectDir: args.projectDir,
206
208
  });
207
- const queryExecutor = args.execute ? (deps.createQueryExecutor ?? createDefaultLocalQueryExecutor)() : undefined;
209
+ const queryExecutor = args.execute ? (deps.createQueryExecutor ?? createKtxCliIngestQueryExecutor)(project) : undefined;
208
210
  const result = await compileLocalSlQuery(project, {
209
211
  connectionId: args.connectionId,
210
212
  query,
@@ -237,6 +239,20 @@ export async function runKtxSl(args, io = process, deps = {}) {
237
239
  throw new Error(`Unsupported sl command: ${JSON.stringify(_exhaustive)}`);
238
240
  }
239
241
  catch (error) {
242
+ await reportException({
243
+ error,
244
+ context: { source: `sl ${args.command}`, handled: true, fatal: false },
245
+ projectDir: args.projectDir,
246
+ io,
247
+ redactionSecrets: await collectTelemetryRedactionSecrets({
248
+ project,
249
+ projectDir: args.projectDir,
250
+ connectionId: args.connectionId,
251
+ includeLlm: args.command === 'query',
252
+ includeEmbeddings: args.command === 'search' || args.command === 'query',
253
+ env: process.env,
254
+ }),
255
+ });
240
256
  if (args.command === 'validate') {
241
257
  const errorClass = scrubErrorClass(error);
242
258
  await emitTelemetryEvent({
package/dist/sql.js CHANGED
@@ -4,7 +4,8 @@ import { createKtxCliScanConnector } from './local-scan-connectors.js';
4
4
  import { createManagedDaemonSqlAnalysisPort } from './managed-python-http.js';
5
5
  import { profileMark } from './startup-profile.js';
6
6
  import { isDemoConnection } from './telemetry/demo-detect.js';
7
- import { emitTelemetryEvent } from './telemetry/index.js';
7
+ import { emitTelemetryEvent, reportException } from './telemetry/index.js';
8
+ import { collectTelemetryRedactionSecrets } from './telemetry/redaction-secrets.js';
8
9
  import { scrubErrorClass } from './telemetry/scrubber.js';
9
10
  profileMark('module:sql');
10
11
  function sqlAnalysisDialectForDriver(driver) {
@@ -96,8 +97,9 @@ export async function runKtxSql(args, io = process, deps = {}) {
96
97
  const startedAt = performance.now();
97
98
  let driver = 'unknown';
98
99
  let demoConnection = false;
100
+ let project;
99
101
  try {
100
- const project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir });
102
+ project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir });
101
103
  const connection = project.config.connections[args.connectionId];
102
104
  if (!connection) {
103
105
  throw new Error(`Connection "${args.connectionId}" is not configured in ktx.yaml`);
@@ -167,6 +169,20 @@ export async function runKtxSql(args, io = process, deps = {}) {
167
169
  ...(errorClass ? { errorClass } : {}),
168
170
  },
169
171
  });
172
+ await reportException({
173
+ error,
174
+ context: { source: 'sql run', handled: true, fatal: false },
175
+ projectDir: args.projectDir,
176
+ io,
177
+ redactionSecrets: await collectTelemetryRedactionSecrets({
178
+ project,
179
+ projectDir: args.projectDir,
180
+ connectionId: args.connectionId,
181
+ includeLlm: false,
182
+ includeEmbeddings: false,
183
+ env: process.env,
184
+ }),
185
+ });
170
186
  io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
171
187
  return 1;
172
188
  }
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ declare const starCountCacheSchema: z.ZodObject<{
3
+ count: z.ZodNumber;
4
+ fetchedAt: z.ZodString;
5
+ }, z.core.$strict>;
6
+ export type StarCountCache = z.infer<typeof starCountCacheSchema>;
7
+ /** @internal */
8
+ export declare function starCountCachePath(homeDir?: string): string;
9
+ export declare function readStarCountCache(options?: {
10
+ homeDir?: string;
11
+ }): StarCountCache | null;
12
+ export declare function writeStarCountCache(value: StarCountCache, options?: {
13
+ homeDir?: string;
14
+ }): Promise<void>;
15
+ export declare function isFreshStarCountCache(cache: StarCountCache | null, now: Date, ttlMs: number): boolean;
16
+ export {};
@@ -0,0 +1,45 @@
1
+ import { readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { z } from 'zod';
6
+ const starCountCacheSchema = z
7
+ .object({
8
+ count: z.number().int().nonnegative(),
9
+ fetchedAt: z.string(),
10
+ })
11
+ .strict();
12
+ /** @internal */
13
+ export function starCountCachePath(homeDir = homedir()) {
14
+ return join(homeDir, '.ktx', 'star-count.json');
15
+ }
16
+ export function readStarCountCache(options = {}) {
17
+ try {
18
+ return starCountCacheSchema.parse(JSON.parse(readFileSync(starCountCachePath(options.homeDir), 'utf-8')));
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ export async function writeStarCountCache(value, options = {}) {
25
+ try {
26
+ const path = starCountCachePath(options.homeDir);
27
+ await mkdir(dirname(path), { recursive: true });
28
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
29
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
30
+ renameSync(tempPath, path);
31
+ }
32
+ catch {
33
+ return;
34
+ }
35
+ }
36
+ export function isFreshStarCountCache(cache, now, ttlMs) {
37
+ if (!cache) {
38
+ return false;
39
+ }
40
+ const fetchedAtMs = Date.parse(cache.fetchedAt);
41
+ if (Number.isNaN(fetchedAtMs)) {
42
+ return false;
43
+ }
44
+ return now.getTime() - fetchedAtMs < ttlMs;
45
+ }
@@ -0,0 +1,7 @@
1
+ import { request as httpsRequest } from 'node:https';
2
+ type HttpsRequest = typeof httpsRequest;
3
+ export declare function fetchGitHubStarCount(options?: {
4
+ request?: HttpsRequest;
5
+ timeoutMs?: number;
6
+ }): Promise<number | null>;
7
+ export {};