@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.
- package/assets/python/{kaelio_ktx-0.9.0-py3-none-any.whl → kaelio_ktx-0.10.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/clack.d.ts +6 -0
- package/dist/clack.js +17 -2
- package/dist/cli-program.d.ts +3 -0
- package/dist/cli-program.js +42 -2
- package/dist/cli-runtime.d.ts +3 -0
- package/dist/cli-runtime.js +44 -0
- package/dist/commands/setup-commands.js +2 -3
- package/dist/connection.js +23 -1
- package/dist/connectors/bigquery/connector.d.ts +2 -5
- package/dist/connectors/bigquery/connector.js +2 -2
- package/dist/connectors/clickhouse/connector.d.ts +2 -5
- package/dist/connectors/clickhouse/connector.js +2 -2
- package/dist/connectors/mysql/connector.d.ts +7 -6
- package/dist/connectors/mysql/connector.js +25 -5
- package/dist/connectors/mysql/dialect.d.ts +1 -1
- package/dist/connectors/mysql/dialect.js +12 -2
- package/dist/connectors/postgres/connector.d.ts +2 -5
- package/dist/connectors/postgres/connector.js +2 -2
- package/dist/connectors/snowflake/connector.d.ts +2 -5
- package/dist/connectors/snowflake/connector.js +2 -2
- package/dist/connectors/sqlite/connector.d.ts +2 -5
- package/dist/connectors/sqlite/connector.js +2 -2
- package/dist/connectors/sqlserver/connector.d.ts +2 -5
- package/dist/connectors/sqlserver/connector.js +2 -2
- package/dist/context/connections/drivers.d.ts +0 -1
- package/dist/context/connections/drivers.js +0 -7
- package/dist/context/connections/query-executor.d.ts +2 -1
- package/dist/context/core/abort.d.ts +9 -0
- package/dist/context/core/abort.js +36 -0
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +1 -0
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +6 -2
- package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
- package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
- package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
- package/dist/context/ingest/final-gate-repair.d.ts +1 -0
- package/dist/context/ingest/final-gate-repair.js +1 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
- package/dist/context/ingest/ingest-bundle.runner.js +127 -53
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
- package/dist/context/ingest/local-bundle-runtime.js +11 -4
- package/dist/context/ingest/local-ingest.d.ts +1 -0
- package/dist/context/ingest/local-ingest.js +13 -3
- package/dist/context/ingest/memory-flow/events.js +1 -1
- package/dist/context/ingest/memory-flow/schema.js +8 -3
- package/dist/context/ingest/memory-flow/types.d.ts +7 -3
- package/dist/context/ingest/ports.d.ts +3 -5
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
- package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
- package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
- package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
- package/dist/context/ingest/types.d.ts +1 -0
- package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
- package/dist/context/llm/ai-sdk-runtime.js +152 -16
- package/dist/context/llm/claude-code-runtime.d.ts +6 -4
- package/dist/context/llm/claude-code-runtime.js +127 -48
- package/dist/context/llm/codex-runtime.d.ts +3 -3
- package/dist/context/llm/codex-runtime.js +90 -47
- package/dist/context/llm/local-config.d.ts +15 -5
- package/dist/context/llm/local-config.js +6 -1
- package/dist/context/llm/rate-limit-governor.d.ts +103 -0
- package/dist/context/llm/rate-limit-governor.js +285 -0
- package/dist/context/llm/runtime-port.d.ts +3 -6
- package/dist/context/mcp/context-tools.js +43 -13
- package/dist/context/project/config.d.ts +12 -0
- package/dist/context/project/config.js +35 -0
- package/dist/context/scan/types.d.ts +15 -2
- package/dist/context/scan/types.js +12 -0
- package/dist/context/sl/description-normalization.js +4 -14
- package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
- package/dist/context-build-view.d.ts +13 -0
- package/dist/context-build-view.js +60 -1
- package/dist/demo-metrics.d.ts +0 -2
- package/dist/demo-metrics.js +1 -11
- package/dist/ingest.d.ts +1 -0
- package/dist/ingest.js +32 -3
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/memory-flow-hud.js +8 -16
- package/dist/public-ingest.js +50 -15
- package/dist/reveal-password-prompt.d.ts +24 -0
- package/dist/reveal-password-prompt.js +78 -0
- package/dist/scan.js +18 -2
- package/dist/setup-databases.d.ts +1 -0
- package/dist/setup-databases.js +23 -3
- package/dist/setup-demo-tour.js +1 -0
- package/dist/setup-embeddings.js +1 -1
- package/dist/setup-models.d.ts +1 -14
- package/dist/setup-models.js +116 -340
- package/dist/setup-prompts.js +3 -2
- package/dist/setup-sources.js +7 -7
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +1 -1
- package/dist/sl.d.ts +2 -2
- package/dist/sl.js +20 -4
- package/dist/sql.js +18 -2
- package/dist/star-prompt/cache.d.ts +16 -0
- package/dist/star-prompt/cache.js +45 -0
- package/dist/star-prompt/star-count.d.ts +7 -0
- package/dist/star-prompt/star-count.js +66 -0
- package/dist/star-prompt/star-line.d.ts +12 -0
- package/dist/star-prompt/star-line.js +26 -0
- package/dist/telemetry/emitter.d.ts +10 -0
- package/dist/telemetry/emitter.js +31 -0
- package/dist/telemetry/events.d.ts +24 -0
- package/dist/telemetry/events.js +15 -0
- package/dist/telemetry/exception.d.ts +18 -0
- package/dist/telemetry/exception.js +162 -0
- package/dist/telemetry/index.d.ts +3 -2
- package/dist/telemetry/index.js +2 -1
- package/dist/telemetry/redaction-secrets.d.ts +11 -0
- package/dist/telemetry/redaction-secrets.js +92 -0
- package/dist/update-check/cache.d.ts +21 -0
- package/dist/update-check/cache.js +38 -0
- package/dist/update-check/channel.d.ts +15 -0
- package/dist/update-check/channel.js +30 -0
- package/dist/update-check/registry.d.ts +1 -0
- package/dist/update-check/registry.js +45 -0
- package/dist/update-check/update-check.d.ts +43 -0
- package/dist/update-check/update-check.js +116 -0
- package/package.json +8 -1
- package/dist/context/connections/local-query-executor.d.ts +0 -6
- package/dist/context/connections/local-query-executor.js +0 -39
- package/dist/context/connections/postgres-query-executor.d.ts +0 -25
- package/dist/context/connections/postgres-query-executor.js +0 -53
- package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
- package/dist/context/connections/sqlite-query-executor.js +0 -74
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import { getKtxCliPackageInfo } from '../cli-runtime.js';
|
|
3
|
+
import { buildCommonEnvelope } from './events.js';
|
|
4
|
+
import { trackTelemetryException } from './emitter.js';
|
|
5
|
+
import { computeTelemetryProjectId, loadTelemetryIdentity } from './identity.js';
|
|
6
|
+
const reportedObjects = new WeakSet();
|
|
7
|
+
const recentHandledPrimitives = [];
|
|
8
|
+
const RECENT_PRIMITIVE_LIMIT = 128;
|
|
9
|
+
function primitiveKey(value) {
|
|
10
|
+
return `${typeof value}:${String(value)}`;
|
|
11
|
+
}
|
|
12
|
+
function rememberHandledPrimitive(value) {
|
|
13
|
+
recentHandledPrimitives.push(primitiveKey(value));
|
|
14
|
+
if (recentHandledPrimitives.length > RECENT_PRIMITIVE_LIMIT) {
|
|
15
|
+
recentHandledPrimitives.splice(0, recentHandledPrimitives.length - RECENT_PRIMITIVE_LIMIT);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function consumeHandledPrimitive(value) {
|
|
19
|
+
const key = primitiveKey(value);
|
|
20
|
+
const index = recentHandledPrimitives.indexOf(key);
|
|
21
|
+
if (index < 0) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
recentHandledPrimitives.splice(index, 1);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function shouldSkipAsAlreadyReported(error, handled) {
|
|
28
|
+
if ((typeof error === 'object' || typeof error === 'function') && error !== null) {
|
|
29
|
+
if (reportedObjects.has(error)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
reportedObjects.add(error);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (handled) {
|
|
36
|
+
rememberHandledPrimitive(error);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return consumeHandledPrimitive(error);
|
|
40
|
+
}
|
|
41
|
+
function escapeRegExp(value) {
|
|
42
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
}
|
|
44
|
+
function redactStaticPatterns(value) {
|
|
45
|
+
return value
|
|
46
|
+
.replace(/([a-z][a-z0-9+.-]*:\/\/[^:\s/@]+:)([^@\s/]+)(@)/gi, '$1[redacted]$3')
|
|
47
|
+
.replace(/\b(password|pwd)=([^;&\s]+)/gi, '$1=[redacted]')
|
|
48
|
+
.replace(/\bAuthorization\s*:\s*[^\r\n,;]+/gi, 'Authorization: [redacted]')
|
|
49
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]')
|
|
50
|
+
.replace(/\b(api[_-]?key)\s*[:=]\s*([^\s,;]+)/gi, '$1=[redacted]')
|
|
51
|
+
.replace(/\b(KTX_[A-Z0-9_]*|[A-Z0-9_]*(?:TOKEN|SECRET))\s*[:=]\s*([^\s,;]+)/g, '$1=[redacted]')
|
|
52
|
+
.replace(/([?&](?:X-Amz-Signature|X-Goog-Signature|sig)=)[^&\s]+/gi, '$1[redacted]');
|
|
53
|
+
}
|
|
54
|
+
function redactText(value, secrets) {
|
|
55
|
+
let redacted = value;
|
|
56
|
+
for (const secret of secrets) {
|
|
57
|
+
if (secret) {
|
|
58
|
+
redacted = redacted.replace(new RegExp(escapeRegExp(secret), 'g'), '[redacted]');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return redactStaticPatterns(redacted);
|
|
62
|
+
}
|
|
63
|
+
const FORBIDDEN_EXTRA_PROPERTY_KEYS = new Set([
|
|
64
|
+
'argv',
|
|
65
|
+
'args',
|
|
66
|
+
'env',
|
|
67
|
+
'environment',
|
|
68
|
+
'sql',
|
|
69
|
+
'query',
|
|
70
|
+
'prompt',
|
|
71
|
+
'mcparguments',
|
|
72
|
+
'mcpargs',
|
|
73
|
+
'tablename',
|
|
74
|
+
'schemaname',
|
|
75
|
+
'columnname',
|
|
76
|
+
'databaseurl',
|
|
77
|
+
'connectionstring',
|
|
78
|
+
'url',
|
|
79
|
+
'password',
|
|
80
|
+
'token',
|
|
81
|
+
'apikey',
|
|
82
|
+
'api_key',
|
|
83
|
+
'authorization',
|
|
84
|
+
]);
|
|
85
|
+
function safeExtraProperties(extra) {
|
|
86
|
+
const safe = {};
|
|
87
|
+
for (const [key, value] of Object.entries(extra ?? {})) {
|
|
88
|
+
if (!FORBIDDEN_EXTRA_PROPERTY_KEYS.has(key.replace(/[^a-z0-9_]/gi, '').toLowerCase())) {
|
|
89
|
+
safe[key] = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return safe;
|
|
93
|
+
}
|
|
94
|
+
function toMessage(error) {
|
|
95
|
+
if (error instanceof Error) {
|
|
96
|
+
return error.message;
|
|
97
|
+
}
|
|
98
|
+
if (typeof error === 'string') {
|
|
99
|
+
return error;
|
|
100
|
+
}
|
|
101
|
+
return inspect(error, { depth: 4, breakLength: 120 });
|
|
102
|
+
}
|
|
103
|
+
function sanitizedError(error, secrets) {
|
|
104
|
+
if (error instanceof Error) {
|
|
105
|
+
const cause = 'cause' in error ? error.cause : undefined;
|
|
106
|
+
const clone = new Error(redactText(error.message, secrets), {
|
|
107
|
+
...(cause !== undefined ? { cause: sanitizedError(cause, secrets) } : {}),
|
|
108
|
+
});
|
|
109
|
+
clone.name = error.name;
|
|
110
|
+
if (error.stack) {
|
|
111
|
+
clone.stack = redactText(error.stack, secrets);
|
|
112
|
+
}
|
|
113
|
+
return clone;
|
|
114
|
+
}
|
|
115
|
+
return new Error(redactText(toMessage(error), secrets));
|
|
116
|
+
}
|
|
117
|
+
export async function reportException(input) {
|
|
118
|
+
try {
|
|
119
|
+
if (shouldSkipAsAlreadyReported(input.error, input.context.handled)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const debug = process.env.KTX_TELEMETRY_DEBUG === '1';
|
|
123
|
+
const identity = await loadTelemetryIdentity({
|
|
124
|
+
stderr: input.io.stderr,
|
|
125
|
+
env: process.env,
|
|
126
|
+
});
|
|
127
|
+
if ((!identity.enabled || !identity.installId) && !debug) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const packageInfo = input.packageInfo ?? getKtxCliPackageInfo();
|
|
131
|
+
const installId = identity.installId ?? 'debug';
|
|
132
|
+
const projectId = input.projectDir ? computeTelemetryProjectId(installId, input.projectDir) : undefined;
|
|
133
|
+
const safeError = sanitizedError(input.error, input.redactionSecrets ?? []);
|
|
134
|
+
const properties = {
|
|
135
|
+
...buildCommonEnvelope({
|
|
136
|
+
cliVersion: packageInfo.version,
|
|
137
|
+
isCi: Boolean(process.env.CI),
|
|
138
|
+
}),
|
|
139
|
+
source: input.context.source,
|
|
140
|
+
handled: input.context.handled,
|
|
141
|
+
fatal: input.context.fatal,
|
|
142
|
+
...(projectId ? { projectId } : {}),
|
|
143
|
+
...safeExtraProperties(input.context.extra),
|
|
144
|
+
};
|
|
145
|
+
delete properties.$groups;
|
|
146
|
+
await trackTelemetryException({
|
|
147
|
+
error: safeError,
|
|
148
|
+
distinctId: installId,
|
|
149
|
+
properties,
|
|
150
|
+
env: process.env,
|
|
151
|
+
stderr: input.io.stderr,
|
|
152
|
+
immediate: input.immediate,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** @internal */
|
|
160
|
+
export function __resetTelemetryExceptionStateForTests() {
|
|
161
|
+
recentHandledPrimitives.length = 0;
|
|
162
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type KtxCliIo, type KtxCliPackageInfo } from '../cli-runtime.js';
|
|
2
2
|
import { beginCommandSpan, completeCommandSpan, type CommandOutcome, type CompletedCommandSpan } from './command-hook.js';
|
|
3
3
|
import { shutdownTelemetryEmitter } from './emitter.js';
|
|
4
|
+
import { reportException, type ExceptionContext } from './exception.js';
|
|
4
5
|
import { type TelemetryCommonEnvelope, type TelemetryEventName, type TelemetryEventProperties } from './events.js';
|
|
5
|
-
export { beginCommandSpan, completeCommandSpan, shutdownTelemetryEmitter };
|
|
6
|
-
export type { CommandOutcome, CompletedCommandSpan };
|
|
6
|
+
export { beginCommandSpan, completeCommandSpan, reportException, shutdownTelemetryEmitter };
|
|
7
|
+
export type { CommandOutcome, CompletedCommandSpan, ExceptionContext };
|
|
7
8
|
export declare function showTelemetryNoticeIfNeeded(io: KtxCliIo, packageInfo: KtxCliPackageInfo): Promise<void>;
|
|
8
9
|
type TelemetryEventFields<Name extends TelemetryEventName> = Omit<TelemetryEventProperties<Name>, keyof TelemetryCommonEnvelope>;
|
|
9
10
|
export declare function shouldEmitMcpTelemetry(): boolean;
|
package/dist/telemetry/index.js
CHANGED
|
@@ -2,10 +2,11 @@ import { getKtxCliPackageInfo } from '../cli-runtime.js';
|
|
|
2
2
|
import { loadKtxProject } from '../context/project/project.js';
|
|
3
3
|
import { beginCommandSpan, completeCommandSpan, } from './command-hook.js';
|
|
4
4
|
import { shutdownTelemetryEmitter, trackTelemetryEvent } from './emitter.js';
|
|
5
|
+
import { reportException } from './exception.js';
|
|
5
6
|
import { buildCommonEnvelope, buildTelemetryEvent, } from './events.js';
|
|
6
7
|
import { computeTelemetryProjectId, loadTelemetryIdentity } from './identity.js';
|
|
7
8
|
import { buildProjectStackSnapshotFields } from './project-snapshot.js';
|
|
8
|
-
export { beginCommandSpan, completeCommandSpan, shutdownTelemetryEmitter };
|
|
9
|
+
export { beginCommandSpan, completeCommandSpan, reportException, shutdownTelemetryEmitter };
|
|
9
10
|
export async function showTelemetryNoticeIfNeeded(io, packageInfo) {
|
|
10
11
|
const identity = await loadTelemetryIdentity({
|
|
11
12
|
stderr: io.stderr,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type KtxLocalProject } from '../context/project/project.js';
|
|
2
|
+
type TelemetryRedactionProject = Pick<KtxLocalProject, 'config' | 'projectDir'>;
|
|
3
|
+
export declare function collectTelemetryRedactionSecrets(input: {
|
|
4
|
+
project?: TelemetryRedactionProject;
|
|
5
|
+
projectDir?: string;
|
|
6
|
+
connectionId?: string;
|
|
7
|
+
includeLlm?: boolean;
|
|
8
|
+
includeEmbeddings?: boolean;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
}): Promise<string[]>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { resolveKtxConfigReference } from '../context/core/config-reference.js';
|
|
2
|
+
import { loadKtxProject } from '../context/project/project.js';
|
|
3
|
+
const SENSITIVE_KEY = /(password|secret|token|api[_-]?key|auth[_-]?token|auth_token_ref|private[_-]?key|passphrase|credential|authorization|url)$/i;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function addSecret(values, value) {
|
|
8
|
+
const trimmed = value?.trim();
|
|
9
|
+
if (trimmed && !values.includes(trimmed)) {
|
|
10
|
+
values.push(trimmed);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function tryResolve(value, env) {
|
|
14
|
+
try {
|
|
15
|
+
return resolveKtxConfigReference(value, env);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function addUrlCredentials(values, value) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = new URL(value);
|
|
24
|
+
addSecret(values, parsed.password ? decodeURIComponent(parsed.password) : undefined);
|
|
25
|
+
addSecret(values, parsed.username ? decodeURIComponent(parsed.username) : undefined);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function collectFromRecord(input, env, values) {
|
|
32
|
+
if (Array.isArray(input)) {
|
|
33
|
+
for (const item of input) {
|
|
34
|
+
collectFromRecord(item, env, values);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!isRecord(input)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const [key, raw] of Object.entries(input)) {
|
|
42
|
+
if (isRecord(raw) || Array.isArray(raw)) {
|
|
43
|
+
collectFromRecord(raw, env, values);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (typeof raw !== 'string' || !SENSITIVE_KEY.test(key)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const resolved = tryResolve(raw, env);
|
|
50
|
+
addSecret(values, resolved);
|
|
51
|
+
if (resolved) {
|
|
52
|
+
addUrlCredentials(values, resolved);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function collectLlmSecrets(project, env, values) {
|
|
57
|
+
collectFromRecord(project.config.llm.provider, env, values);
|
|
58
|
+
}
|
|
59
|
+
function collectEmbeddingSecrets(project, env, values) {
|
|
60
|
+
collectFromRecord(project.config.ingest.embeddings, env, values);
|
|
61
|
+
collectFromRecord(project.config.scan.enrichment.embeddings, env, values);
|
|
62
|
+
}
|
|
63
|
+
function collectConnectionSecrets(project, connectionId, env, values) {
|
|
64
|
+
if (!connectionId) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
collectFromRecord(project.config.connections[connectionId], env, values);
|
|
68
|
+
}
|
|
69
|
+
export async function collectTelemetryRedactionSecrets(input) {
|
|
70
|
+
const env = input.env ?? process.env;
|
|
71
|
+
let project = input.project;
|
|
72
|
+
if (!project && input.projectDir) {
|
|
73
|
+
try {
|
|
74
|
+
project = await loadKtxProject({ projectDir: input.projectDir });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
project = undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!project) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const values = [];
|
|
84
|
+
if (input.includeLlm) {
|
|
85
|
+
collectLlmSecrets(project, env, values);
|
|
86
|
+
}
|
|
87
|
+
if (input.includeEmbeddings) {
|
|
88
|
+
collectEmbeddingSecrets(project, env, values);
|
|
89
|
+
}
|
|
90
|
+
collectConnectionSecrets(project, input.connectionId, env, values);
|
|
91
|
+
return values;
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const updateCheckCacheSchema: z.ZodObject<{
|
|
3
|
+
checkedAt: z.ZodString;
|
|
4
|
+
channel: z.ZodEnum<{
|
|
5
|
+
next: "next";
|
|
6
|
+
latest: "latest";
|
|
7
|
+
}>;
|
|
8
|
+
installedVersion: z.ZodString;
|
|
9
|
+
latestForChannel: z.ZodString;
|
|
10
|
+
lastNoticeAt: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export type UpdateCheckCache = z.infer<typeof updateCheckCacheSchema>;
|
|
13
|
+
/** @internal */
|
|
14
|
+
export declare function updateCheckCachePath(homeDir?: string): string;
|
|
15
|
+
export declare function readUpdateCheckCache(options?: {
|
|
16
|
+
homeDir?: string;
|
|
17
|
+
}): Promise<UpdateCheckCache | null>;
|
|
18
|
+
export declare function writeUpdateCheckCache(value: UpdateCheckCache, options?: {
|
|
19
|
+
homeDir?: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const updateCheckCacheSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
checkedAt: z.string(),
|
|
9
|
+
channel: z.enum(['latest', 'next']),
|
|
10
|
+
installedVersion: z.string(),
|
|
11
|
+
latestForChannel: z.string(),
|
|
12
|
+
lastNoticeAt: z.string().optional(),
|
|
13
|
+
})
|
|
14
|
+
.strict();
|
|
15
|
+
/** @internal */
|
|
16
|
+
export function updateCheckCachePath(homeDir = homedir()) {
|
|
17
|
+
return join(homeDir, '.ktx', 'update-check.json');
|
|
18
|
+
}
|
|
19
|
+
export async function readUpdateCheckCache(options = {}) {
|
|
20
|
+
try {
|
|
21
|
+
return updateCheckCacheSchema.parse(JSON.parse(await readFile(updateCheckCachePath(options.homeDir), 'utf-8')));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeUpdateCheckCache(value, options = {}) {
|
|
28
|
+
try {
|
|
29
|
+
const path = updateCheckCachePath(options.homeDir);
|
|
30
|
+
await mkdir(dirname(path), { recursive: true });
|
|
31
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
32
|
+
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
33
|
+
renameSync(tempPath, path);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type UpdateChannel = 'latest' | 'next';
|
|
2
|
+
export type UpdateDecision = {
|
|
3
|
+
status: 'skip';
|
|
4
|
+
} | {
|
|
5
|
+
status: 'upToDate';
|
|
6
|
+
channel: UpdateChannel;
|
|
7
|
+
target: string;
|
|
8
|
+
} | {
|
|
9
|
+
status: 'available';
|
|
10
|
+
channel: UpdateChannel;
|
|
11
|
+
target: string;
|
|
12
|
+
};
|
|
13
|
+
/** @internal */
|
|
14
|
+
export declare function inferUpdateChannel(installed: string): UpdateChannel | null;
|
|
15
|
+
export declare function decideUpdate(installed: string, distTags: Record<string, string>): UpdateDecision;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
/** @internal */
|
|
3
|
+
export function inferUpdateChannel(installed) {
|
|
4
|
+
const parsed = semver.parse(installed);
|
|
5
|
+
if (!parsed || installed === '0.0.0') {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const [prereleaseId] = parsed.prerelease;
|
|
9
|
+
if (prereleaseId === undefined) {
|
|
10
|
+
return 'latest';
|
|
11
|
+
}
|
|
12
|
+
if (prereleaseId === 'rc') {
|
|
13
|
+
return 'next';
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
export function decideUpdate(installed, distTags) {
|
|
18
|
+
const channel = inferUpdateChannel(installed);
|
|
19
|
+
if (!channel || !semver.valid(installed)) {
|
|
20
|
+
return { status: 'skip' };
|
|
21
|
+
}
|
|
22
|
+
const target = distTags[channel];
|
|
23
|
+
if (!target || !semver.valid(target)) {
|
|
24
|
+
return { status: 'skip' };
|
|
25
|
+
}
|
|
26
|
+
if (semver.gt(target, installed)) {
|
|
27
|
+
return { status: 'available', channel, target };
|
|
28
|
+
}
|
|
29
|
+
return { status: 'upToDate', channel, target };
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fetchDistTags(): Promise<Record<string, string>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { request as httpsRequest } from 'node:https';
|
|
2
|
+
import { URL } from 'node:url';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const DIST_TAGS_URL = new URL('https://registry.npmjs.org/-/package/@kaelio/ktx/dist-tags');
|
|
5
|
+
const distTagsSchema = z.record(z.string(), z.string());
|
|
6
|
+
function parseDistTags(raw) {
|
|
7
|
+
return distTagsSchema.parse(JSON.parse(raw));
|
|
8
|
+
}
|
|
9
|
+
export function fetchDistTags() {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const request = httpsRequest(DIST_TAGS_URL, {
|
|
12
|
+
method: 'GET',
|
|
13
|
+
headers: {
|
|
14
|
+
accept: 'application/json',
|
|
15
|
+
},
|
|
16
|
+
}, (response) => {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
response.on('data', (chunk) => {
|
|
19
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
20
|
+
});
|
|
21
|
+
response.on('end', () => {
|
|
22
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
23
|
+
const statusCode = response.statusCode ?? 0;
|
|
24
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
25
|
+
reject(new Error(`npm dist-tags request failed with ${statusCode}: ${text}`));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
resolve(parseDistTags(text));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
reject(error);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
request.on('socket', (socket) => {
|
|
37
|
+
socket.unref();
|
|
38
|
+
});
|
|
39
|
+
request.on('error', reject);
|
|
40
|
+
request.setTimeout(5000, () => {
|
|
41
|
+
request.destroy(new Error('npm dist-tags request timed out'));
|
|
42
|
+
});
|
|
43
|
+
request.end();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { KtxCliIo } from '../cli-runtime.js';
|
|
2
|
+
import { type CliStyleEnv } from '../clack.js';
|
|
3
|
+
import { type UpdateChannel } from './channel.js';
|
|
4
|
+
/** @internal */
|
|
5
|
+
export interface UpdateCheckEnv extends NodeJS.ProcessEnv, CliStyleEnv {
|
|
6
|
+
CI?: string;
|
|
7
|
+
DO_NOT_TRACK?: string;
|
|
8
|
+
KTX_NO_UPDATE_CHECK?: string;
|
|
9
|
+
KTX_OUTPUT?: string;
|
|
10
|
+
NO_UPDATE_NOTIFIER?: string;
|
|
11
|
+
}
|
|
12
|
+
/** @internal */
|
|
13
|
+
export interface UpdateCheckCommandOptions {
|
|
14
|
+
format?: unknown;
|
|
15
|
+
json?: unknown;
|
|
16
|
+
output?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface PrepareUpdateCheckNoticeOptions {
|
|
19
|
+
commandOptions?: UpdateCheckCommandOptions;
|
|
20
|
+
env?: UpdateCheckEnv;
|
|
21
|
+
fetchDistTags?: () => Promise<Record<string, string>>;
|
|
22
|
+
homeDir?: string;
|
|
23
|
+
installedVersion: string;
|
|
24
|
+
io: KtxCliIo;
|
|
25
|
+
now?: () => Date;
|
|
26
|
+
}
|
|
27
|
+
export interface PreparedUpdateCheckNotice {
|
|
28
|
+
notice: string | null;
|
|
29
|
+
}
|
|
30
|
+
/** @internal */
|
|
31
|
+
export declare function shouldSuppressUpdateCheck(args: {
|
|
32
|
+
commandOptions?: UpdateCheckCommandOptions;
|
|
33
|
+
env?: UpdateCheckEnv;
|
|
34
|
+
io: KtxCliIo;
|
|
35
|
+
}): boolean;
|
|
36
|
+
/** @internal */
|
|
37
|
+
export declare function renderUpdateNotice(args: {
|
|
38
|
+
channel: UpdateChannel;
|
|
39
|
+
env?: CliStyleEnv;
|
|
40
|
+
installedVersion: string;
|
|
41
|
+
targetVersion: string;
|
|
42
|
+
}): string;
|
|
43
|
+
export declare function prepareUpdateCheckNotice(options: PrepareUpdateCheckNoticeOptions): Promise<PreparedUpdateCheckNotice>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { cyan, dim } from '../clack.js';
|
|
2
|
+
import { resolveOutputMode } from '../io/mode.js';
|
|
3
|
+
import { readUpdateCheckCache, writeUpdateCheckCache } from './cache.js';
|
|
4
|
+
import { decideUpdate, inferUpdateChannel } from './channel.js';
|
|
5
|
+
import { fetchDistTags as defaultFetchDistTags } from './registry.js';
|
|
6
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
function truthy(value) {
|
|
8
|
+
return value !== undefined && value !== '' && value !== '0' && value !== 'false';
|
|
9
|
+
}
|
|
10
|
+
function commandRequestsJson(options) {
|
|
11
|
+
return options?.json === true || options?.output === 'json' || options?.format === 'json';
|
|
12
|
+
}
|
|
13
|
+
/** @internal */
|
|
14
|
+
export function shouldSuppressUpdateCheck(args) {
|
|
15
|
+
const env = args.env ?? process.env;
|
|
16
|
+
if (truthy(env.KTX_NO_UPDATE_CHECK) || truthy(env.NO_UPDATE_NOTIFIER) || truthy(env.DO_NOT_TRACK)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (commandRequestsJson(args.commandOptions) || truthy(env.CI) || args.io.stdout.isTTY !== true) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const mode = resolveOutputMode({
|
|
24
|
+
json: false,
|
|
25
|
+
io: args.io,
|
|
26
|
+
env,
|
|
27
|
+
});
|
|
28
|
+
return mode !== 'pretty';
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** @internal */
|
|
35
|
+
export function renderUpdateNotice(args) {
|
|
36
|
+
const command = args.channel === 'next' ? 'npm i -g @kaelio/ktx@next' : 'npm i -g @kaelio/ktx';
|
|
37
|
+
return `${cyan('↑', args.env)} Update available: ktx ${args.installedVersion} → ${args.targetVersion}\n ${dim(command, args.env)}\n`;
|
|
38
|
+
}
|
|
39
|
+
function timestampMs(value) {
|
|
40
|
+
if (!value) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const parsed = Date.parse(value);
|
|
44
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
45
|
+
}
|
|
46
|
+
function elapsedAtLeast(value, now, intervalMs) {
|
|
47
|
+
const previous = timestampMs(value);
|
|
48
|
+
if (previous === null) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return now.getTime() - previous >= intervalMs;
|
|
52
|
+
}
|
|
53
|
+
function shouldRefreshCache(cache, installedVersion, now) {
|
|
54
|
+
if (!cache || cache.installedVersion !== installedVersion) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return elapsedAtLeast(cache.checkedAt, now, DAY_MS);
|
|
58
|
+
}
|
|
59
|
+
async function refreshUpdateCache(args) {
|
|
60
|
+
const distTags = await args.fetchDistTags();
|
|
61
|
+
const decision = decideUpdate(args.installedVersion, distTags);
|
|
62
|
+
if (decision.status === 'skip') {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await writeUpdateCheckCache({
|
|
66
|
+
checkedAt: args.now.toISOString(),
|
|
67
|
+
channel: decision.channel,
|
|
68
|
+
installedVersion: args.installedVersion,
|
|
69
|
+
latestForChannel: decision.target,
|
|
70
|
+
...(args.cache?.installedVersion === args.installedVersion && args.cache.channel === decision.channel
|
|
71
|
+
? { lastNoticeAt: args.cache.lastNoticeAt }
|
|
72
|
+
: {}),
|
|
73
|
+
}, { homeDir: args.homeDir });
|
|
74
|
+
}
|
|
75
|
+
export async function prepareUpdateCheckNotice(options) {
|
|
76
|
+
const env = options.env ?? process.env;
|
|
77
|
+
const now = (options.now ?? (() => new Date()))();
|
|
78
|
+
const fetchDistTags = options.fetchDistTags ?? defaultFetchDistTags;
|
|
79
|
+
if (shouldSuppressUpdateCheck({
|
|
80
|
+
commandOptions: options.commandOptions,
|
|
81
|
+
env,
|
|
82
|
+
io: options.io,
|
|
83
|
+
})) {
|
|
84
|
+
return { notice: null };
|
|
85
|
+
}
|
|
86
|
+
if (!inferUpdateChannel(options.installedVersion)) {
|
|
87
|
+
return { notice: null };
|
|
88
|
+
}
|
|
89
|
+
let cache = await readUpdateCheckCache({ homeDir: options.homeDir });
|
|
90
|
+
let notice = null;
|
|
91
|
+
if (cache?.installedVersion === options.installedVersion) {
|
|
92
|
+
const decision = decideUpdate(options.installedVersion, {
|
|
93
|
+
[cache.channel]: cache.latestForChannel,
|
|
94
|
+
});
|
|
95
|
+
if (decision.status === 'available' && elapsedAtLeast(cache.lastNoticeAt, now, DAY_MS)) {
|
|
96
|
+
notice = renderUpdateNotice({
|
|
97
|
+
channel: decision.channel,
|
|
98
|
+
env,
|
|
99
|
+
installedVersion: options.installedVersion,
|
|
100
|
+
targetVersion: decision.target,
|
|
101
|
+
});
|
|
102
|
+
cache = { ...cache, lastNoticeAt: now.toISOString() };
|
|
103
|
+
await writeUpdateCheckCache(cache, { homeDir: options.homeDir });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (shouldRefreshCache(cache, options.installedVersion, now)) {
|
|
107
|
+
void refreshUpdateCache({
|
|
108
|
+
cache,
|
|
109
|
+
fetchDistTags,
|
|
110
|
+
homeDir: options.homeDir,
|
|
111
|
+
installedVersion: options.installedVersion,
|
|
112
|
+
now,
|
|
113
|
+
}).catch(() => { });
|
|
114
|
+
}
|
|
115
|
+
return { notice };
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaelio/ktx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Standalone ktx context layer for data agents",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Kaelio",
|
|
7
|
+
"url": "https://www.kaelio.com"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"engines": {
|
|
7
11
|
"node": ">=22.0.0"
|
|
@@ -32,6 +36,7 @@
|
|
|
32
36
|
"@ai-sdk/devtools": "0.0.18",
|
|
33
37
|
"@ai-sdk/google-vertex": "^4.0.134",
|
|
34
38
|
"@anthropic-ai/claude-agent-sdk": "0.3.146",
|
|
39
|
+
"@clack/core": "1.3.1",
|
|
35
40
|
"@clack/prompts": "1.4.0",
|
|
36
41
|
"@clickhouse/client": "^1.18.5",
|
|
37
42
|
"@commander-js/extra-typings": "14.0.0",
|
|
@@ -57,6 +62,7 @@
|
|
|
57
62
|
"pg": "^8.21.0",
|
|
58
63
|
"posthog-node": "^5.34.9",
|
|
59
64
|
"react": "^19.2.6",
|
|
65
|
+
"semver": "^7.8.1",
|
|
60
66
|
"simple-git": "3.36.0",
|
|
61
67
|
"snowflake-sdk": "^2.4.2",
|
|
62
68
|
"yaml": "^2.9.0",
|
|
@@ -70,6 +76,7 @@
|
|
|
70
76
|
"@types/node": "^25.9.1",
|
|
71
77
|
"@types/pg": "^8.20.0",
|
|
72
78
|
"@types/react": "^19.2.15",
|
|
79
|
+
"@types/semver": "^7.7.1",
|
|
73
80
|
"@vitest/coverage-v8": "^4.1.7",
|
|
74
81
|
"ajv": "8.20.0",
|
|
75
82
|
"ink-testing-library": "^4.0.0",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { KtxSqlQueryExecutorPort } from './query-executor.js';
|
|
2
|
-
export interface DefaultLocalQueryExecutorOptions {
|
|
3
|
-
postgres?: KtxSqlQueryExecutorPort;
|
|
4
|
-
sqlite?: KtxSqlQueryExecutorPort;
|
|
5
|
-
}
|
|
6
|
-
export declare function createDefaultLocalQueryExecutor(options?: DefaultLocalQueryExecutorOptions): KtxSqlQueryExecutorPort;
|