@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.
- package/assets/python/{kaelio_ktx-0.9.0-py3-none-any.whl → kaelio_ktx-0.11.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 +46 -2
- package/dist/cli-runtime.d.ts +5 -0
- package/dist/cli-runtime.js +50 -0
- package/dist/commands/setup-commands.js +2 -3
- package/dist/community-cta.d.ts +11 -0
- package/dist/community-cta.js +19 -0
- 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/core/git-env.d.ts +12 -1
- package/dist/context/core/git-env.js +17 -2
- package/dist/context/core/git.service.js +15 -7
- 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/io/tty.d.ts +9 -0
- package/dist/io/tty.js +5 -0
- package/dist/links.d.ts +1 -0
- package/dist/links.js +1 -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-agents.js +1 -5
- 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 +4 -7
- package/dist/setup-sources.js +7 -7
- package/dist/setup.d.ts +26 -1
- package/dist/setup.js +78 -7
- 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/command-hook.d.ts +24 -0
- package/dist/telemetry/command-hook.js +37 -3
- 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 +4 -3
- package/dist/telemetry/index.js +3 -2
- 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
package/dist/setup-sources.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
-
|
|
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,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
|
-
|
|
13
|
-
|
|
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
|
|
51
|
+
outcome,
|
|
18
52
|
...(errorClass ? { errorClass } : {}),
|
|
19
53
|
...(errorDetail ? { errorDetail } : {}),
|
|
20
54
|
flagsPresent: span.flagsPresent,
|