@kaelio/ktx 0.9.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 (131) hide show
  1. package/assets/python/{kaelio_ktx-0.9.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 +44 -0
  10. package/dist/commands/setup-commands.js +2 -3
  11. package/dist/connection.js +23 -1
  12. package/dist/connectors/bigquery/connector.d.ts +2 -5
  13. package/dist/connectors/bigquery/connector.js +2 -2
  14. package/dist/connectors/clickhouse/connector.d.ts +2 -5
  15. package/dist/connectors/clickhouse/connector.js +2 -2
  16. package/dist/connectors/mysql/connector.d.ts +7 -6
  17. package/dist/connectors/mysql/connector.js +25 -5
  18. package/dist/connectors/mysql/dialect.d.ts +1 -1
  19. package/dist/connectors/mysql/dialect.js +12 -2
  20. package/dist/connectors/postgres/connector.d.ts +2 -5
  21. package/dist/connectors/postgres/connector.js +2 -2
  22. package/dist/connectors/snowflake/connector.d.ts +2 -5
  23. package/dist/connectors/snowflake/connector.js +2 -2
  24. package/dist/connectors/sqlite/connector.d.ts +2 -5
  25. package/dist/connectors/sqlite/connector.js +2 -2
  26. package/dist/connectors/sqlserver/connector.d.ts +2 -5
  27. package/dist/connectors/sqlserver/connector.js +2 -2
  28. package/dist/context/connections/drivers.d.ts +0 -1
  29. package/dist/context/connections/drivers.js +0 -7
  30. package/dist/context/connections/query-executor.d.ts +2 -1
  31. package/dist/context/core/abort.d.ts +9 -0
  32. package/dist/context/core/abort.js +36 -0
  33. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +1 -0
  34. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +6 -2
  35. package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
  36. package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
  37. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  38. package/dist/context/ingest/final-gate-repair.d.ts +1 -0
  39. package/dist/context/ingest/final-gate-repair.js +1 -0
  40. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  41. package/dist/context/ingest/ingest-bundle.runner.js +127 -53
  42. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
  43. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
  44. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  45. package/dist/context/ingest/local-bundle-runtime.js +11 -4
  46. package/dist/context/ingest/local-ingest.d.ts +1 -0
  47. package/dist/context/ingest/local-ingest.js +13 -3
  48. package/dist/context/ingest/memory-flow/events.js +1 -1
  49. package/dist/context/ingest/memory-flow/schema.js +8 -3
  50. package/dist/context/ingest/memory-flow/types.d.ts +7 -3
  51. package/dist/context/ingest/ports.d.ts +3 -5
  52. package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
  53. package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
  54. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
  55. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  56. package/dist/context/ingest/types.d.ts +1 -0
  57. package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
  58. package/dist/context/llm/ai-sdk-runtime.js +152 -16
  59. package/dist/context/llm/claude-code-runtime.d.ts +6 -4
  60. package/dist/context/llm/claude-code-runtime.js +127 -48
  61. package/dist/context/llm/codex-runtime.d.ts +3 -3
  62. package/dist/context/llm/codex-runtime.js +90 -47
  63. package/dist/context/llm/local-config.d.ts +15 -5
  64. package/dist/context/llm/local-config.js +6 -1
  65. package/dist/context/llm/rate-limit-governor.d.ts +103 -0
  66. package/dist/context/llm/rate-limit-governor.js +285 -0
  67. package/dist/context/llm/runtime-port.d.ts +3 -6
  68. package/dist/context/mcp/context-tools.js +43 -13
  69. package/dist/context/project/config.d.ts +12 -0
  70. package/dist/context/project/config.js +35 -0
  71. package/dist/context/scan/types.d.ts +15 -2
  72. package/dist/context/scan/types.js +12 -0
  73. package/dist/context/sl/description-normalization.js +4 -14
  74. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  75. package/dist/context-build-view.d.ts +13 -0
  76. package/dist/context-build-view.js +60 -1
  77. package/dist/demo-metrics.d.ts +0 -2
  78. package/dist/demo-metrics.js +1 -11
  79. package/dist/ingest.d.ts +1 -0
  80. package/dist/ingest.js +32 -3
  81. package/dist/io/symbols.d.ts +2 -0
  82. package/dist/io/symbols.js +2 -0
  83. package/dist/memory-flow-hud.js +8 -16
  84. package/dist/public-ingest.js +50 -15
  85. package/dist/reveal-password-prompt.d.ts +24 -0
  86. package/dist/reveal-password-prompt.js +78 -0
  87. package/dist/scan.js +18 -2
  88. package/dist/setup-databases.d.ts +1 -0
  89. package/dist/setup-databases.js +23 -3
  90. package/dist/setup-demo-tour.js +1 -0
  91. package/dist/setup-embeddings.js +1 -1
  92. package/dist/setup-models.d.ts +1 -14
  93. package/dist/setup-models.js +116 -340
  94. package/dist/setup-prompts.js +3 -2
  95. package/dist/setup-sources.js +7 -7
  96. package/dist/setup.d.ts +1 -1
  97. package/dist/setup.js +1 -1
  98. package/dist/sl.d.ts +2 -2
  99. package/dist/sl.js +20 -4
  100. package/dist/sql.js +18 -2
  101. package/dist/star-prompt/cache.d.ts +16 -0
  102. package/dist/star-prompt/cache.js +45 -0
  103. package/dist/star-prompt/star-count.d.ts +7 -0
  104. package/dist/star-prompt/star-count.js +66 -0
  105. package/dist/star-prompt/star-line.d.ts +12 -0
  106. package/dist/star-prompt/star-line.js +26 -0
  107. package/dist/telemetry/emitter.d.ts +10 -0
  108. package/dist/telemetry/emitter.js +31 -0
  109. package/dist/telemetry/events.d.ts +24 -0
  110. package/dist/telemetry/events.js +15 -0
  111. package/dist/telemetry/exception.d.ts +18 -0
  112. package/dist/telemetry/exception.js +162 -0
  113. package/dist/telemetry/index.d.ts +3 -2
  114. package/dist/telemetry/index.js +2 -1
  115. package/dist/telemetry/redaction-secrets.d.ts +11 -0
  116. package/dist/telemetry/redaction-secrets.js +92 -0
  117. package/dist/update-check/cache.d.ts +21 -0
  118. package/dist/update-check/cache.js +38 -0
  119. package/dist/update-check/channel.d.ts +15 -0
  120. package/dist/update-check/channel.js +30 -0
  121. package/dist/update-check/registry.d.ts +1 -0
  122. package/dist/update-check/registry.js +45 -0
  123. package/dist/update-check/update-check.d.ts +43 -0
  124. package/dist/update-check/update-check.js +116 -0
  125. package/package.json +8 -1
  126. package/dist/context/connections/local-query-executor.d.ts +0 -6
  127. package/dist/context/connections/local-query-executor.js +0 -39
  128. package/dist/context/connections/postgres-query-executor.d.ts +0 -25
  129. package/dist/context/connections/postgres-query-executor.js +0 -53
  130. package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
  131. package/dist/context/connections/sqlite-query-executor.js +0 -74
@@ -1,5 +1,6 @@
1
- import { autocomplete, autocompleteMultiselect, cancel, confirm, intro, isCancel, log, multiselect, note, password, select, text, } from '@clack/prompts';
1
+ import { autocomplete, autocompleteMultiselect, cancel, confirm, intro, isCancel, log, multiselect, note, select, text, } from '@clack/prompts';
2
2
  import { withMenuOptionsSpacing, withTextInputNavigation } from './prompt-navigation.js';
3
+ import { revealPassword } from './reveal-password-prompt.js';
3
4
  import { withSetupInterruptConfirmation } from './setup-interrupt.js';
4
5
  const DEFAULT_SETUP_CANCEL_MESSAGE = 'Setup cancelled.';
5
6
  export function createKtxSetupPromptAdapter(options) {
@@ -89,7 +90,7 @@ export function createKtxSetupPromptAdapter(options) {
89
90
  return isCancel(value) ? undefined : String(value);
90
91
  },
91
92
  async password(promptOptions) {
92
- const value = await withSetupInterruptConfirmation(() => password({ ...promptOptions, message: withTextInputNavigation(promptOptions.message) }));
93
+ const value = await withSetupInterruptConfirmation(() => revealPassword({ ...promptOptions, message: withTextInputNavigation(promptOptions.message) }));
93
94
  return isCancel(value) ? undefined : String(value);
94
95
  },
95
96
  cancel(message) {
@@ -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
@@ -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
@@ -443,7 +443,6 @@ async function runKtxSetupInner(args, io, deps = {}) {
443
443
  ...(args.llmBackend ? { llmBackend: args.llmBackend } : {}),
444
444
  ...(args.anthropicApiKeyEnv ? { anthropicApiKeyEnv: args.anthropicApiKeyEnv } : {}),
445
445
  ...(args.anthropicApiKeyFile ? { anthropicApiKeyFile: args.anthropicApiKeyFile } : {}),
446
- ...(args.llmModel ? { llmModel: args.llmModel } : {}),
447
446
  ...(args.vertexProject ? { vertexProject: args.vertexProject } : {}),
448
447
  ...(args.vertexLocation ? { vertexLocation: args.vertexLocation } : {}),
449
448
  forcePrompt: forcePromptSteps.has('models') || runOnly === 'models',
@@ -473,6 +472,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
473
472
  const databaseResult = await databasesRunner({
474
473
  projectDir: projectResult.projectDir,
475
474
  inputMode: args.inputMode,
475
+ ...(args.debug !== undefined ? { debug: args.debug } : {}),
476
476
  yes: args.yes,
477
477
  cliVersion: args.cliVersion,
478
478
  runtimeInstallPolicy: setupRuntimeInstallPolicy(args),
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
+ }
@@ -15,6 +15,16 @@ export declare function trackTelemetryEvent(input: {
15
15
  projectApiKey?: string;
16
16
  host?: string;
17
17
  }): Promise<void>;
18
+ export declare function trackTelemetryException(input: {
19
+ error: Error;
20
+ distinctId: string;
21
+ properties: Record<string, unknown>;
22
+ env?: TelemetryEmitterEnv;
23
+ stderr: TelemetrySink;
24
+ projectApiKey?: string;
25
+ host?: string;
26
+ immediate?: boolean;
27
+ }): Promise<void>;
18
28
  export declare function shutdownTelemetryEmitter(): Promise<void>;
19
29
  /** @internal */
20
30
  export declare function __resetTelemetryEmitterForTests(): void;
@@ -56,6 +56,37 @@ export async function trackTelemetryEvent(input) {
56
56
  return;
57
57
  }
58
58
  }
59
+ function writeDebugExceptionPayload(input) {
60
+ input.stderr.write(`[telemetry-exception] ${JSON.stringify({
61
+ distinctId: input.distinctId,
62
+ message: input.error.message,
63
+ name: input.error.name,
64
+ properties: input.properties,
65
+ })}\n`);
66
+ }
67
+ export async function trackTelemetryException(input) {
68
+ const env = input.env ?? process.env;
69
+ if (debugEnabled(env)) {
70
+ writeDebugExceptionPayload(input);
71
+ return;
72
+ }
73
+ const projectApiKey = telemetryProjectApiKey(input.projectApiKey);
74
+ const host = telemetryHost(env, input.host);
75
+ const client = await getPostHogClient(projectApiKey, host);
76
+ if (!client) {
77
+ return;
78
+ }
79
+ try {
80
+ if (input.immediate) {
81
+ await client.captureExceptionImmediate(input.error, input.distinctId, input.properties);
82
+ return;
83
+ }
84
+ client.captureException(input.error, input.distinctId, input.properties);
85
+ }
86
+ catch {
87
+ return;
88
+ }
89
+ }
59
90
  export async function shutdownTelemetryEmitter() {
60
91
  const client = await clientPromise;
61
92
  if (!client) {
@@ -386,6 +386,26 @@ export declare const telemetryEventSchemas: {
386
386
  errorClass: z.ZodOptional<z.ZodString>;
387
387
  durationMs: z.ZodNumber;
388
388
  }, z.core.$strict>;
389
+ readonly query_history_filter_completed: z.ZodObject<{
390
+ cliVersion: z.ZodString;
391
+ nodeVersion: z.ZodString;
392
+ osPlatform: z.ZodString;
393
+ osRelease: z.ZodString;
394
+ arch: z.ZodString;
395
+ runtime: z.ZodEnum<{
396
+ node: "node";
397
+ "daemon-py": "daemon-py";
398
+ }>;
399
+ isCi: z.ZodBoolean;
400
+ dialect: z.ZodString;
401
+ consideredRoleCount: z.ZodNumber;
402
+ excludedRoleCount: z.ZodNumber;
403
+ parseFailedCount: z.ZodNumber;
404
+ outcome: z.ZodEnum<{
405
+ ok: "ok";
406
+ error: "error";
407
+ }>;
408
+ }, z.core.$strict>;
389
409
  };
390
410
  /** @internal */
391
411
  export declare const telemetryEventCatalog: readonly [{
@@ -456,6 +476,10 @@ export declare const telemetryEventCatalog: readonly [{
456
476
  readonly name: "sql_gen_completed";
457
477
  readonly description: "Emitted after daemon SQL generation completes.";
458
478
  readonly fields: readonly ["outcome", "dialect", "errorClass", "durationMs"];
479
+ }, {
480
+ readonly name: "query_history_filter_completed";
481
+ readonly description: "Emitted after the setup query-history service-account filter picker runs.";
482
+ readonly fields: readonly ["dialect", "consideredRoleCount", "excludedRoleCount", "parseFailedCount", "outcome"];
459
483
  }];
460
484
  export type TelemetryEventName = keyof typeof telemetryEventSchemas;
461
485
  export type TelemetryCommonEnvelope = z.infer<typeof telemetryCommonEnvelopeSchema>;
@@ -185,6 +185,15 @@ const sqlGenCompletedSchema = telemetryCommonEnvelopeSchema
185
185
  durationMs: z.number().nonnegative(),
186
186
  })
187
187
  .strict();
188
+ const queryHistoryFilterCompletedSchema = telemetryCommonEnvelopeSchema
189
+ .extend({
190
+ dialect: z.string(),
191
+ consideredRoleCount: z.number().int().nonnegative(),
192
+ excludedRoleCount: z.number().int().nonnegative(),
193
+ parseFailedCount: z.number().int().nonnegative(),
194
+ outcome: outcomeSchema,
195
+ })
196
+ .strict();
188
197
  /** @internal */
189
198
  export const telemetryEventSchemas = {
190
199
  install_first_run: installFirstRunSchema,
@@ -204,6 +213,7 @@ export const telemetryEventSchemas = {
204
213
  daemon_stopped: daemonStoppedSchema,
205
214
  sl_plan_completed: slPlanCompletedSchema,
206
215
  sql_gen_completed: sqlGenCompletedSchema,
216
+ query_history_filter_completed: queryHistoryFilterCompletedSchema,
207
217
  };
208
218
  /** @internal */
209
219
  export const telemetryEventCatalog = [
@@ -338,6 +348,11 @@ export const telemetryEventCatalog = [
338
348
  description: 'Emitted after daemon SQL generation completes.',
339
349
  fields: ['outcome', 'dialect', 'errorClass', 'durationMs'],
340
350
  },
351
+ {
352
+ name: 'query_history_filter_completed',
353
+ description: 'Emitted after the setup query-history service-account filter picker runs.',
354
+ fields: ['dialect', 'consideredRoleCount', 'excludedRoleCount', 'parseFailedCount', 'outcome'],
355
+ },
341
356
  ];
342
357
  export function buildCommonEnvelope(input) {
343
358
  return {
@@ -0,0 +1,18 @@
1
+ import { type KtxCliIo, type KtxCliPackageInfo } from '../cli-runtime.js';
2
+ export interface ExceptionContext {
3
+ source: string;
4
+ handled: boolean;
5
+ fatal: boolean;
6
+ extra?: Record<string, string | number | boolean>;
7
+ }
8
+ export declare function reportException(input: {
9
+ error: unknown;
10
+ context: ExceptionContext;
11
+ io: KtxCliIo;
12
+ packageInfo?: KtxCliPackageInfo;
13
+ projectDir?: string;
14
+ immediate?: boolean;
15
+ redactionSecrets?: ReadonlyArray<string>;
16
+ }): Promise<void>;
17
+ /** @internal */
18
+ export declare function __resetTelemetryExceptionStateForTests(): void;