@kaelio/ktx 0.9.0 → 0.11.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 (143) hide show
  1. package/assets/python/{kaelio_ktx-0.9.0-py3-none-any.whl → kaelio_ktx-0.11.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 +46 -2
  8. package/dist/cli-runtime.d.ts +5 -0
  9. package/dist/cli-runtime.js +50 -0
  10. package/dist/commands/setup-commands.js +2 -3
  11. package/dist/community-cta.d.ts +11 -0
  12. package/dist/community-cta.js +19 -0
  13. package/dist/connection.js +23 -1
  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/core/git-env.d.ts +12 -1
  36. package/dist/context/core/git-env.js +17 -2
  37. package/dist/context/core/git.service.js +15 -7
  38. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +1 -0
  39. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +6 -2
  40. package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
  41. package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
  42. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  43. package/dist/context/ingest/final-gate-repair.d.ts +1 -0
  44. package/dist/context/ingest/final-gate-repair.js +1 -0
  45. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  46. package/dist/context/ingest/ingest-bundle.runner.js +127 -53
  47. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
  48. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
  49. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  50. package/dist/context/ingest/local-bundle-runtime.js +11 -4
  51. package/dist/context/ingest/local-ingest.d.ts +1 -0
  52. package/dist/context/ingest/local-ingest.js +13 -3
  53. package/dist/context/ingest/memory-flow/events.js +1 -1
  54. package/dist/context/ingest/memory-flow/schema.js +8 -3
  55. package/dist/context/ingest/memory-flow/types.d.ts +7 -3
  56. package/dist/context/ingest/ports.d.ts +3 -5
  57. package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
  58. package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
  59. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
  60. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  61. package/dist/context/ingest/types.d.ts +1 -0
  62. package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
  63. package/dist/context/llm/ai-sdk-runtime.js +152 -16
  64. package/dist/context/llm/claude-code-runtime.d.ts +6 -4
  65. package/dist/context/llm/claude-code-runtime.js +127 -48
  66. package/dist/context/llm/codex-runtime.d.ts +3 -3
  67. package/dist/context/llm/codex-runtime.js +90 -47
  68. package/dist/context/llm/local-config.d.ts +15 -5
  69. package/dist/context/llm/local-config.js +6 -1
  70. package/dist/context/llm/rate-limit-governor.d.ts +103 -0
  71. package/dist/context/llm/rate-limit-governor.js +285 -0
  72. package/dist/context/llm/runtime-port.d.ts +3 -6
  73. package/dist/context/mcp/context-tools.js +43 -13
  74. package/dist/context/project/config.d.ts +12 -0
  75. package/dist/context/project/config.js +35 -0
  76. package/dist/context/scan/types.d.ts +15 -2
  77. package/dist/context/scan/types.js +12 -0
  78. package/dist/context/sl/description-normalization.js +4 -14
  79. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  80. package/dist/context-build-view.d.ts +13 -0
  81. package/dist/context-build-view.js +60 -1
  82. package/dist/demo-metrics.d.ts +0 -2
  83. package/dist/demo-metrics.js +1 -11
  84. package/dist/ingest.d.ts +1 -0
  85. package/dist/ingest.js +32 -3
  86. package/dist/io/symbols.d.ts +2 -0
  87. package/dist/io/symbols.js +2 -0
  88. package/dist/io/tty.d.ts +9 -0
  89. package/dist/io/tty.js +5 -0
  90. package/dist/links.d.ts +1 -0
  91. package/dist/links.js +1 -0
  92. package/dist/memory-flow-hud.js +8 -16
  93. package/dist/public-ingest.js +50 -15
  94. package/dist/reveal-password-prompt.d.ts +24 -0
  95. package/dist/reveal-password-prompt.js +78 -0
  96. package/dist/scan.js +18 -2
  97. package/dist/setup-agents.js +1 -5
  98. package/dist/setup-databases.d.ts +1 -0
  99. package/dist/setup-databases.js +23 -3
  100. package/dist/setup-demo-tour.js +1 -0
  101. package/dist/setup-embeddings.js +1 -1
  102. package/dist/setup-models.d.ts +1 -14
  103. package/dist/setup-models.js +116 -340
  104. package/dist/setup-prompts.js +4 -7
  105. package/dist/setup-sources.js +7 -7
  106. package/dist/setup.d.ts +26 -1
  107. package/dist/setup.js +78 -7
  108. package/dist/sl.d.ts +2 -2
  109. package/dist/sl.js +20 -4
  110. package/dist/sql.js +18 -2
  111. package/dist/star-prompt/cache.d.ts +16 -0
  112. package/dist/star-prompt/cache.js +45 -0
  113. package/dist/star-prompt/star-count.d.ts +7 -0
  114. package/dist/star-prompt/star-count.js +66 -0
  115. package/dist/star-prompt/star-line.d.ts +12 -0
  116. package/dist/star-prompt/star-line.js +26 -0
  117. package/dist/telemetry/command-hook.d.ts +24 -0
  118. package/dist/telemetry/command-hook.js +37 -3
  119. package/dist/telemetry/emitter.d.ts +10 -0
  120. package/dist/telemetry/emitter.js +31 -0
  121. package/dist/telemetry/events.d.ts +24 -0
  122. package/dist/telemetry/events.js +15 -0
  123. package/dist/telemetry/exception.d.ts +18 -0
  124. package/dist/telemetry/exception.js +162 -0
  125. package/dist/telemetry/index.d.ts +4 -3
  126. package/dist/telemetry/index.js +3 -2
  127. package/dist/telemetry/redaction-secrets.d.ts +11 -0
  128. package/dist/telemetry/redaction-secrets.js +92 -0
  129. package/dist/update-check/cache.d.ts +21 -0
  130. package/dist/update-check/cache.js +38 -0
  131. package/dist/update-check/channel.d.ts +15 -0
  132. package/dist/update-check/channel.js +30 -0
  133. package/dist/update-check/registry.d.ts +1 -0
  134. package/dist/update-check/registry.js +45 -0
  135. package/dist/update-check/update-check.d.ts +43 -0
  136. package/dist/update-check/update-check.js +116 -0
  137. package/package.json +8 -1
  138. package/dist/context/connections/local-query-executor.d.ts +0 -6
  139. package/dist/context/connections/local-query-executor.js +0 -39
  140. package/dist/context/connections/postgres-query-executor.d.ts +0 -25
  141. package/dist/context/connections/postgres-query-executor.js +0 -53
  142. package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
  143. package/dist/context/connections/sqlite-query-executor.js +0 -74
@@ -29,11 +29,11 @@ import { createKtxSetupPromptAdapter, } from './setup-prompts.js';
29
29
  const DEFAULT_NOTION_MAX_KNOWLEDGE_CREATES_PER_RUN = 25;
30
30
  const SOURCE_OPTIONS = [
31
31
  { value: 'dbt', label: 'dbt' },
32
- { value: 'metricflow', label: 'MetricFlow' },
33
32
  { value: 'metabase', label: 'Metabase' },
33
+ { value: 'notion', label: 'Notion' },
34
+ { value: 'metricflow', label: 'MetricFlow' },
34
35
  { value: 'looker', label: 'Looker' },
35
36
  { value: 'lookml', label: 'LookML' },
36
- { value: 'notion', label: 'Notion' },
37
37
  ];
38
38
  const SOURCE_LABELS = Object.fromEntries(SOURCE_OPTIONS.map((option) => [option.value, option.label]));
39
39
  const PRIMARY_SOURCE_DRIVERS = new Set([
@@ -146,8 +146,8 @@ async function chooseSourceCredentialRef(input) {
146
146
  message: `How should KTX find your ${input.label}?`,
147
147
  options: [
148
148
  ...(input.existingRef ? [{ value: 'keep', label: 'Keep existing credential' }] : []),
149
- { value: 'env', label: `Use ${input.envName} from the environment` },
150
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` },
151
151
  { value: 'back', label: 'Back' },
152
152
  ],
153
153
  });
@@ -179,8 +179,8 @@ async function chooseGitAuthCredentialRef(input) {
179
179
  message: `${label} repo requires authentication.`,
180
180
  options: [
181
181
  ...(input.existingRef ? [{ value: 'keep', label: 'Keep existing credential' }] : []),
182
- { value: 'env', label: 'Use GITHUB_TOKEN from the environment' },
183
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' },
184
184
  { value: 'skip', label: 'Skip — try without authentication' },
185
185
  { value: 'back', label: 'Back' },
186
186
  ],
@@ -800,8 +800,8 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
800
800
  const selectedLocation = await prompts.select({
801
801
  message: `${source} source location`,
802
802
  options: [
803
- { value: 'path', label: 'Local path' },
804
803
  { value: 'git', label: 'Git URL' },
804
+ { value: 'path', label: 'Local path' },
805
805
  { value: 'back', label: 'Back' },
806
806
  ],
807
807
  });
@@ -1101,8 +1101,8 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
1101
1101
  const crawlMode = await prompts.select({
1102
1102
  message: 'Which Notion pages should KTX ingest?',
1103
1103
  options: [
1104
- { value: 'selected_roots', label: 'Specific pages and their subpages (choose them in a picker)' },
1105
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)' },
1106
1106
  { value: 'back', label: 'Back' },
1107
1107
  ],
1108
1108
  });
@@ -1667,7 +1667,7 @@ export async function runKtxSetupSourcesStep(args, io, deps = {}) {
1667
1667
  const addMore = await prompts.select({
1668
1668
  message: `${readyConnectionIds.length} context source${readyConnectionIds.length > 1 ? 's' : ''} configured (${readyConnectionIds.join(', ')}). Add another?`,
1669
1669
  options: [
1670
- { value: 'done', label: 'Done continue to context build' },
1670
+ { value: 'done', label: 'Done adding context sources' },
1671
1671
  { value: 'edit', label: 'Edit an existing context source' },
1672
1672
  { value: 'add', label: 'Add another context source' },
1673
1673
  ],
package/dist/setup.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type KtxCliIo } from './cli-runtime.js';
2
2
  import { readManagedPythonRuntimeStatus } from './managed-python-runtime.js';
3
+ import type { CommandOutcome } from './telemetry/index.js';
3
4
  import { type KtxAgentScope, type KtxAgentTarget, type KtxSetupAgentsDeps, runKtxSetupAgentsStep } from './setup-agents.js';
4
5
  import { type KtxSetupDatabaseDriver, type KtxSetupDatabasesDeps, runKtxSetupDatabasesStep } from './setup-databases.js';
5
6
  import { type KtxSetupEmbeddingsDeps, runKtxSetupEmbeddingsStep } from './setup-embeddings.js';
@@ -58,12 +59,12 @@ export type KtxSetupArgs = {
58
59
  agentScope?: KtxAgentScope;
59
60
  skipAgents?: boolean;
60
61
  inputMode: 'auto' | 'disabled';
62
+ debug?: boolean;
61
63
  yes: boolean;
62
64
  cliVersion: string;
63
65
  llmBackend?: KtxSetupLlmBackend;
64
66
  anthropicApiKeyEnv?: string;
65
67
  anthropicApiKeyFile?: string;
66
- llmModel?: string;
67
68
  vertexProject?: string;
68
69
  vertexLocation?: string;
69
70
  skipLlm: boolean;
@@ -125,6 +126,7 @@ export interface KtxSetupDeps {
125
126
  entryMenuDeps?: KtxSetupEntryMenuDeps;
126
127
  setupUi?: KtxSetupUiAdapter;
127
128
  }
129
+ type TelemetrySetupStep = 'project' | 'runtime' | 'models' | 'embeddings' | 'databases' | 'sources' | 'context' | 'agents' | 'demo-tour';
128
130
  export interface KtxSetupEntryMenuPromptAdapter {
129
131
  select(options: {
130
132
  message: string;
@@ -135,6 +137,28 @@ export interface KtxSetupEntryMenuPromptAdapter {
135
137
  export interface KtxSetupEntryMenuDeps {
136
138
  prompts?: KtxSetupEntryMenuPromptAdapter;
137
139
  }
140
+ interface SetupCommandAnnotation {
141
+ outcome: CommandOutcome;
142
+ errorClass?: string;
143
+ errorDetail?: string;
144
+ }
145
+ /**
146
+ * Single source of truth for how a non-ready setup step ends: the process exit
147
+ * code and the telemetry annotation are both derived from one classification,
148
+ * so they can never disagree. A genuine failure (`error`) exits non-zero; an
149
+ * abort — the user leaving an interactive wizard — exits 0, matching the entry
150
+ * menu's "Exit", a project cancellation, and a confirmed Ctrl+C.
151
+ */
152
+ /** @internal */
153
+ export declare function setupTerminalOutcome(input: {
154
+ status: 'failed' | 'missing-input' | 'cancelled';
155
+ step: TelemetrySetupStep;
156
+ interactive: boolean;
157
+ errorDetail?: string;
158
+ }): {
159
+ exitCode: number;
160
+ annotation: SetupCommandAnnotation;
161
+ };
138
162
  export interface ReadKtxSetupStatusOptions {
139
163
  cliVersion?: string;
140
164
  env?: NodeJS.ProcessEnv;
@@ -146,3 +170,4 @@ export declare function formatKtxSetupCompletionSummary(status: KtxSetupStatus,
146
170
  agentNextActions?: string;
147
171
  }): string;
148
172
  export declare function runKtxSetup(args: KtxSetupArgs, io: KtxCliIo, deps?: KtxSetupDeps): Promise<number>;
173
+ export {};
package/dist/setup.js CHANGED
@@ -6,6 +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 { SLACK_SETUP_NOTE } from './community-cta.js';
9
10
  import { formatNextStepLines, formatSetupNextStepLines } from './next-steps.js';
10
11
  import { runtimeInstallPolicyFromFlags } from './managed-python-command.js';
11
12
  import { readManagedPythonRuntimeStatus } from './managed-python-runtime.js';
@@ -36,6 +37,61 @@ function setupTelemetryOutcome(status) {
36
37
  return 'skipped';
37
38
  return 'abandoned';
38
39
  }
40
+ /**
41
+ * Classify a terminal non-ready setup status into the `command` telemetry
42
+ * outcome. The setup flow is the decision-maker and knows the difference:
43
+ * - `failed` is a genuine error; attach a step-scoped reason so the dashboard
44
+ * shows an actionable signature instead of a blank.
45
+ * - `missing-input` from a *non-interactive* run is an automation error
46
+ * (required flags absent and no prompt was possible); attach a reason too.
47
+ * - `missing-input` from an interactive prompt, or a project `cancelled`, is the
48
+ * user backing out of the wizard — an abort, not a failure. Keep it out of
49
+ * error telemetry so it stops inflating the error count.
50
+ *
51
+ * `interactive` must reflect whether a prompt could actually be shown — input
52
+ * is enabled AND a TTY is attached. `inputMode: 'auto'` alone is not enough: a
53
+ * piped/CI run without `--no-input` is still non-interactive, and steps such as
54
+ * the project step return `missing-input` ("pass --yes …") there without ever
55
+ * prompting. Treating that as an abort would make a broken automation run exit
56
+ * 0, so it must classify as an error.
57
+ *
58
+ * Reasons are synthetic, step-scoped strings (no user input), so they satisfy
59
+ * the telemetry privacy rules. The step's own `errorDetail`, when present, has
60
+ * already been vetted for the `setup_step` event and is safe to reuse.
61
+ */
62
+ function setupCommandOutcomeAnnotation(input) {
63
+ if (input.status === 'failed') {
64
+ return {
65
+ outcome: 'error',
66
+ errorClass: 'KtxSetupStepFailed',
67
+ errorDetail: input.errorDetail ?? `${input.step} setup step failed`,
68
+ };
69
+ }
70
+ if (input.status === 'missing-input' && !input.interactive) {
71
+ return {
72
+ outcome: 'error',
73
+ errorClass: 'KtxSetupMissingInput',
74
+ errorDetail: `${input.step} setup step requires input not provided in a non-interactive run`,
75
+ };
76
+ }
77
+ return { outcome: 'aborted' };
78
+ }
79
+ /**
80
+ * Single source of truth for how a non-ready setup step ends: the process exit
81
+ * code and the telemetry annotation are both derived from one classification,
82
+ * so they can never disagree. A genuine failure (`error`) exits non-zero; an
83
+ * abort — the user leaving an interactive wizard — exits 0, matching the entry
84
+ * menu's "Exit", a project cancellation, and a confirmed Ctrl+C.
85
+ */
86
+ /** @internal */
87
+ export function setupTerminalOutcome(input) {
88
+ const annotation = setupCommandOutcomeAnnotation(input);
89
+ return { exitCode: annotation.outcome === 'error' ? 1 : 0, annotation };
90
+ }
91
+ async function annotateSetupCommandOutcome(annotation) {
92
+ const { annotateCommandOutcome } = await import('./telemetry/index.js');
93
+ annotateCommandOutcome(annotation);
94
+ }
39
95
  async function recordSetupStep(input) {
40
96
  const { emitTelemetryEvent } = await import('./telemetry/index.js');
41
97
  await emitTelemetryEvent({
@@ -325,6 +381,10 @@ async function runKtxSetupInner(args, io, deps = {}) {
325
381
  args.inputMode !== 'disabled' &&
326
382
  !args.agents &&
327
383
  (io.stdout.isTTY === true || deps.entryMenuDeps?.prompts !== undefined);
384
+ // A prompt is only possible when input is enabled AND a TTY is attached. A
385
+ // piped/CI `ktx setup` without `--no-input` is still `inputMode: 'auto'` but
386
+ // cannot prompt, so its `missing-input` is an automation error, not an abort.
387
+ const interactive = args.inputMode !== 'disabled' && io.stdout.isTTY === true;
328
388
  setupLoop: while (true) {
329
389
  entryAction = undefined;
330
390
  if (canShowEntryMenu) {
@@ -363,7 +423,13 @@ async function runKtxSetupInner(args, io, deps = {}) {
363
423
  continue;
364
424
  }
365
425
  if (projectResult.status !== 'ready') {
366
- return projectResult.status === 'cancelled' ? 0 : 1;
426
+ const terminal = setupTerminalOutcome({
427
+ status: projectResult.status,
428
+ step: 'project',
429
+ interactive,
430
+ });
431
+ await annotateSetupCommandOutcome(terminal.annotation);
432
+ return terminal.exitCode;
367
433
  }
368
434
  const agentsRequested = args.agents || entryAction === 'agents';
369
435
  const currentStatus = await readKtxSetupStatus(projectResult.projectDir, { cliVersion: args.cliVersion });
@@ -443,7 +509,6 @@ async function runKtxSetupInner(args, io, deps = {}) {
443
509
  ...(args.llmBackend ? { llmBackend: args.llmBackend } : {}),
444
510
  ...(args.anthropicApiKeyEnv ? { anthropicApiKeyEnv: args.anthropicApiKeyEnv } : {}),
445
511
  ...(args.anthropicApiKeyFile ? { anthropicApiKeyFile: args.anthropicApiKeyFile } : {}),
446
- ...(args.llmModel ? { llmModel: args.llmModel } : {}),
447
512
  ...(args.vertexProject ? { vertexProject: args.vertexProject } : {}),
448
513
  ...(args.vertexLocation ? { vertexLocation: args.vertexLocation } : {}),
449
514
  forcePrompt: forcePromptSteps.has('models') || runOnly === 'models',
@@ -473,6 +538,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
473
538
  const databaseResult = await databasesRunner({
474
539
  projectDir: projectResult.projectDir,
475
540
  inputMode: args.inputMode,
541
+ ...(args.debug !== undefined ? { debug: args.debug } : {}),
476
542
  yes: args.yes,
477
543
  cliVersion: args.cliVersion,
478
544
  runtimeInstallPolicy: setupRuntimeInstallPolicy(args),
@@ -576,11 +642,15 @@ async function runKtxSetupInner(args, io, deps = {}) {
576
642
  cliVersion: args.cliVersion,
577
643
  ...(stepResult.errorDetail ? { errorDetail: stepResult.errorDetail } : {}),
578
644
  });
579
- if (stepResult.status === 'failed') {
580
- return 1;
581
- }
582
- if (stepResult.status === 'missing-input') {
583
- return 1;
645
+ if (stepResult.status === 'failed' || stepResult.status === 'missing-input') {
646
+ const terminal = setupTerminalOutcome({
647
+ status: stepResult.status,
648
+ step,
649
+ interactive,
650
+ ...(stepResult.errorDetail ? { errorDetail: stepResult.errorDetail } : {}),
651
+ });
652
+ await annotateSetupCommandOutcome(terminal.annotation);
653
+ return terminal.exitCode;
584
654
  }
585
655
  if (stepResult.status === 'back') {
586
656
  const previousIndex = previousNavigableStepIndex(stepIndex);
@@ -630,5 +700,6 @@ async function runKtxSetupInner(args, io, deps = {}) {
630
700
  }).join('\n'), 'What you can do next', io);
631
701
  }
632
702
  }
703
+ setupUi.note(SLACK_SETUP_NOTE.body, SLACK_SETUP_NOTE.title, io);
633
704
  return 0;
634
705
  }
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 {};
@@ -0,0 +1,66 @@
1
+ import { request as httpsRequest } from 'node:https';
2
+ import { URL } from 'node:url';
3
+ import { z } from 'zod';
4
+ const GITHUB_REPO_URL = new URL('https://api.github.com/repos/Kaelio/ktx');
5
+ const DEFAULT_TIMEOUT_MS = 5000;
6
+ const githubRepoSchema = z.object({
7
+ stargazers_count: z.number().int().nonnegative(),
8
+ });
9
+ function parseStarCount(raw) {
10
+ return githubRepoSchema.parse(JSON.parse(raw)).stargazers_count;
11
+ }
12
+ export function fetchGitHubStarCount(options = {}) {
13
+ const requestImpl = options.request ?? httpsRequest;
14
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
15
+ return new Promise((resolve) => {
16
+ let settled = false;
17
+ const finish = (count) => {
18
+ if (settled) {
19
+ return;
20
+ }
21
+ settled = true;
22
+ resolve(count);
23
+ };
24
+ try {
25
+ const request = requestImpl(GITHUB_REPO_URL, {
26
+ method: 'GET',
27
+ headers: {
28
+ accept: 'application/vnd.github+json',
29
+ 'user-agent': 'ktx-star-prompt',
30
+ },
31
+ }, (response) => {
32
+ const chunks = [];
33
+ response.on('data', (chunk) => {
34
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
35
+ });
36
+ response.on('end', () => {
37
+ const statusCode = response.statusCode ?? 0;
38
+ if (statusCode < 200 || statusCode >= 300) {
39
+ finish(null);
40
+ return;
41
+ }
42
+ try {
43
+ finish(parseStarCount(Buffer.concat(chunks).toString('utf8')));
44
+ }
45
+ catch {
46
+ finish(null);
47
+ }
48
+ });
49
+ });
50
+ request.on('socket', (socket) => {
51
+ socket.unref();
52
+ });
53
+ request.on('error', () => {
54
+ finish(null);
55
+ });
56
+ request.setTimeout(timeoutMs, () => {
57
+ request.destroy(new Error('GitHub star count request timed out'));
58
+ finish(null);
59
+ });
60
+ request.end();
61
+ }
62
+ catch {
63
+ finish(null);
64
+ }
65
+ });
66
+ }
@@ -0,0 +1,12 @@
1
+ interface StarPromptSymbols {
2
+ star: string;
3
+ middot: string;
4
+ rightArrow: string;
5
+ }
6
+ export interface RenderStarPromptLineOptions {
7
+ columns: number;
8
+ count?: number | null;
9
+ symbols?: StarPromptSymbols;
10
+ }
11
+ export declare function renderStarPromptLine(options: RenderStarPromptLineOptions): string;
12
+ export {};
@@ -0,0 +1,26 @@
1
+ import { SYMBOLS } from '../io/symbols.js';
2
+ const STAR_PROMPT_URL = 'github.com/Kaelio/ktx';
3
+ const STAR_PROMPT_TEXT = 'This takes a few minutes - mind giving ktx a star while you wait?';
4
+ function usableColumns(columns) {
5
+ return Number.isFinite(columns) && columns > 0 ? Math.floor(columns) : 80;
6
+ }
7
+ function starCountSegment(count, symbols) {
8
+ if (typeof count !== 'number' || !Number.isFinite(count)) {
9
+ return '';
10
+ }
11
+ const formatted = new Intl.NumberFormat('en-US').format(count);
12
+ return ` ${symbols.middot} ${formatted} ${symbols.star}`;
13
+ }
14
+ export function renderStarPromptLine(options) {
15
+ const symbols = options.symbols ?? SYMBOLS;
16
+ const columns = usableColumns(options.columns);
17
+ const base = ` ${symbols.star} ${STAR_PROMPT_TEXT} ${STAR_PROMPT_URL}`;
18
+ const withCount = `${base}${starCountSegment(options.count, symbols)}`;
19
+ if (withCount.length <= columns) {
20
+ return withCount;
21
+ }
22
+ if (base.length <= columns) {
23
+ return base;
24
+ }
25
+ return ` ${symbols.star} Star ktx ${symbols.rightArrow} ${STAR_PROMPT_URL}`;
26
+ }
@@ -6,6 +6,9 @@ interface CommandSpan {
6
6
  hasProject: boolean;
7
7
  attachProjectGroup: boolean;
8
8
  startedAt: number;
9
+ annotatedOutcome?: CommandOutcome;
10
+ annotatedErrorClass?: string;
11
+ annotatedErrorDetail?: string;
9
12
  }
10
13
  export interface CompletedCommandSpan {
11
14
  commandPath: string[];
@@ -19,6 +22,27 @@ export interface CompletedCommandSpan {
19
22
  projectGroupAttached: boolean;
20
23
  }
21
24
  export declare function beginCommandSpan(input: CommandSpan): void;
25
+ /**
26
+ * Let a command action record the true outcome and reason on the active span.
27
+ *
28
+ * The Commander wrapper can only derive an outcome from a thrown error or the
29
+ * process exit code, so a command that exits non-zero *without throwing* (e.g.
30
+ * `ktx setup` when the user abandons the wizard) lands as `outcome: 'error'`
31
+ * with no `errorClass`/`errorDetail` — an unactionable blank in the dashboard.
32
+ * The action is the decision-maker: it can mark the run `aborted`, or attach a
33
+ * scrubbed reason so the next occurrence is self-diagnosing. A later thrown
34
+ * error still wins (see {@link completeCommandSpan}), since that is the most
35
+ * authoritative signal and also feeds the `$exception` stream. No-ops when no
36
+ * span is active so call sites stay safe in tests and bare-help paths.
37
+ *
38
+ * Values are emitted verbatim and must already satisfy the telemetry privacy
39
+ * rules — pass synthetic or already-scrubbed strings, never raw user input.
40
+ */
41
+ export declare function annotateCommandOutcome(input: {
42
+ outcome?: CommandOutcome;
43
+ errorClass?: string;
44
+ errorDetail?: string;
45
+ }): void;
22
46
  export declare function completeCommandSpan(input: {
23
47
  completedAt: number;
24
48
  outcome: CommandOutcome;
@@ -3,18 +3,52 @@ let activeCommandSpan;
3
3
  export function beginCommandSpan(input) {
4
4
  activeCommandSpan = input;
5
5
  }
6
+ /**
7
+ * Let a command action record the true outcome and reason on the active span.
8
+ *
9
+ * The Commander wrapper can only derive an outcome from a thrown error or the
10
+ * process exit code, so a command that exits non-zero *without throwing* (e.g.
11
+ * `ktx setup` when the user abandons the wizard) lands as `outcome: 'error'`
12
+ * with no `errorClass`/`errorDetail` — an unactionable blank in the dashboard.
13
+ * The action is the decision-maker: it can mark the run `aborted`, or attach a
14
+ * scrubbed reason so the next occurrence is self-diagnosing. A later thrown
15
+ * error still wins (see {@link completeCommandSpan}), since that is the most
16
+ * authoritative signal and also feeds the `$exception` stream. No-ops when no
17
+ * span is active so call sites stay safe in tests and bare-help paths.
18
+ *
19
+ * Values are emitted verbatim and must already satisfy the telemetry privacy
20
+ * rules — pass synthetic or already-scrubbed strings, never raw user input.
21
+ */
22
+ export function annotateCommandOutcome(input) {
23
+ if (!activeCommandSpan) {
24
+ return;
25
+ }
26
+ if (input.outcome !== undefined) {
27
+ activeCommandSpan.annotatedOutcome = input.outcome;
28
+ }
29
+ if (input.errorClass !== undefined) {
30
+ activeCommandSpan.annotatedErrorClass = input.errorClass;
31
+ }
32
+ if (input.errorDetail !== undefined) {
33
+ activeCommandSpan.annotatedErrorDetail = input.errorDetail;
34
+ }
35
+ }
6
36
  export function completeCommandSpan(input) {
7
37
  const span = activeCommandSpan;
8
38
  activeCommandSpan = undefined;
9
39
  if (!span) {
10
40
  return undefined;
11
41
  }
12
- const errorClass = input.error ? scrubErrorClass(input.error) : undefined;
13
- const errorDetail = input.error ? formatErrorDetail(input.error) : undefined;
42
+ // Precedence: a thrown error is authoritative; otherwise an action's own
43
+ // annotation; otherwise the wrapper's exit-code-derived outcome.
44
+ const thrown = Boolean(input.error);
45
+ const outcome = thrown ? input.outcome : (span.annotatedOutcome ?? input.outcome);
46
+ const errorClass = thrown ? scrubErrorClass(input.error) : span.annotatedErrorClass;
47
+ const errorDetail = thrown ? formatErrorDetail(input.error) : span.annotatedErrorDetail;
14
48
  return {
15
49
  commandPath: span.commandPath,
16
50
  durationMs: Math.max(0, input.completedAt - span.startedAt),
17
- outcome: input.outcome,
51
+ outcome,
18
52
  ...(errorClass ? { errorClass } : {}),
19
53
  ...(errorDetail ? { errorDetail } : {}),
20
54
  flagsPresent: span.flagsPresent,