log10x-mcp 1.30.21 → 1.30.23
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/README.md +3 -1
- package/build/index.js +26 -4
- package/build/index.js.map +1 -1
- package/build/lib/advisor/envelope.d.ts +1 -1
- package/build/lib/advisor/reporter-forwarders.d.ts +32 -13
- package/build/lib/advisor/reporter-forwarders.js +179 -94
- package/build/lib/advisor/reporter-forwarders.js.map +1 -1
- package/build/lib/advisor/reporter.d.ts +18 -10
- package/build/lib/advisor/reporter.js +43 -16
- package/build/lib/advisor/reporter.js.map +1 -1
- package/build/lib/advisor/retriever.d.ts +6 -0
- package/build/lib/advisor/retriever.js +5 -3
- package/build/lib/advisor/retriever.js.map +1 -1
- package/build/lib/advisor/types.d.ts +3 -1
- package/build/lib/cdk-recipes.d.ts +1 -1
- package/build/lib/cdk-recipes.js +21 -10
- package/build/lib/cdk-recipes.js.map +1 -1
- package/build/lib/discovery/types.d.ts +15 -10
- package/build/lib/fenced.d.ts +2 -0
- package/build/lib/fenced.js +2 -2
- package/build/lib/fenced.js.map +1 -1
- package/build/lib/offload-recipes.d.ts +2 -1
- package/build/lib/offload-recipes.js +13 -10
- package/build/lib/offload-recipes.js.map +1 -1
- package/build/lib/poc-report-renderer.js +17 -11
- package/build/lib/poc-report-renderer.js.map +1 -1
- package/build/lib/retriever-api.d.ts +14 -0
- package/build/lib/retriever-api.js +40 -10
- package/build/lib/retriever-api.js.map +1 -1
- package/build/lib/runtime-image.d.ts +7 -2
- package/build/lib/runtime-image.js +7 -2
- package/build/lib/runtime-image.js.map +1 -1
- package/build/lib/siem/_sampling.js +4 -0
- package/build/lib/siem/_sampling.js.map +1 -1
- package/build/lib/siem/cloudwatch.d.ts +18 -0
- package/build/lib/siem/cloudwatch.js +106 -2
- package/build/lib/siem/cloudwatch.js.map +1 -1
- package/build/lib/siem/datadog.js +1 -1
- package/build/lib/siem/datadog.js.map +1 -1
- package/build/lib/siem/elasticsearch.js +13 -1
- package/build/lib/siem/elasticsearch.js.map +1 -1
- package/build/lib/siem/splunk.d.ts +18 -0
- package/build/lib/siem/splunk.js +71 -7
- package/build/lib/siem/splunk.js.map +1 -1
- package/build/product-kb/docs/apps/receiver/compact/index.md +3 -3
- package/build/product-kb/docs/apps/receiver/compact/splunk.md +81 -67
- package/build/product-kb/docs/apps/retriever/faq.md +2 -2
- package/build/product-kb/docs/engine/launcher/extension.md +20 -20
- package/build/product-kb/docs/faq/apps/retriever.md +2 -2
- package/build/product-kb/docs/faq/general.md +1 -1
- package/build/product-kb/docs/faq/stacks/splunk/comparisons.md +3 -3
- package/build/product-kb/docs/faq/stacks/splunk/compatibility.md +19 -242
- package/build/product-kb/docs/faq/stacks/splunk/index.md +1 -1
- package/build/product-kb/docs/faq/stacks/splunk/kv-store.md +41 -159
- package/build/product-kb/docs/faq/stacks/splunk/optimization.md +4 -4
- package/build/product-kb/docs/run/input/forwarder/fluentbit/index.md +1 -1
- package/build/product-kb/docs/run/input/forwarder/fluentd/index.md +1 -1
- package/build/product-kb/docs/run/input/forwarder/otel-collector/index.md +1 -1
- package/build/product-kb/docs/run/input/forwarder/vector/index.md +1 -1
- package/build/product-kb/docs/summary.md +0 -1
- package/build/tools/advise-install.d.ts +25 -18
- package/build/tools/advise-install.js +156 -90
- package/build/tools/advise-install.js.map +1 -1
- package/build/tools/advise-retriever.d.ts +5 -5
- package/build/tools/advise-retriever.js +25 -9
- package/build/tools/advise-retriever.js.map +1 -1
- package/build/tools/doctor.js +3 -2
- package/build/tools/doctor.js.map +1 -1
- package/build/tools/emit-sample-plan.js +7 -1
- package/build/tools/emit-sample-plan.js.map +1 -1
- package/build/tools/poc-from-local.js +7 -6
- package/build/tools/poc-from-local.js.map +1 -1
- package/build/tools/poc-from-siem.js +71 -4
- package/build/tools/poc-from-siem.js.map +1 -1
- package/build/tools/retriever-query.js +1 -1
- package/build/tools/retriever-query.js.map +1 -1
- package/package.json +1 -1
- package/build/product-kb/docs/run/output/event/dev/index.md +0 -351
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 1. Asks the next missing question (returning markdown that the agent
|
|
9
9
|
* surfaces to the user), or
|
|
10
|
-
* 2. Once all answers are in, emits a concrete install plan
|
|
11
|
-
*
|
|
10
|
+
* 2. Once all answers are in, emits a concrete install plan. By default
|
|
11
|
+
* the plan carries no licence and the engine runs its built-in
|
|
12
|
+
* evaluation licence; a signed-in, pasted or demo JWT is wired in
|
|
13
|
+
* only when the caller asks for one via `license_source`.
|
|
12
14
|
*
|
|
13
15
|
* The five decisions, by dependency order:
|
|
14
16
|
*
|
|
@@ -16,26 +18,31 @@
|
|
|
16
18
|
* "plug into existing forwarder" (Receiver). The biggest fork.
|
|
17
19
|
* Q2. (Receiver only) Which forwarder — auto-uses the snapshot's
|
|
18
20
|
* detected forwarder when there's exactly one; asks if multiple.
|
|
19
|
-
* Q3. Backend — where TenXSummary metrics go
|
|
20
|
-
*
|
|
21
|
-
* detected
|
|
21
|
+
* Q3. Backend — where TenXSummary metrics go: the user's own TSDB
|
|
22
|
+
* (prometheus, datadog, elastic, cloudwatch). Backends whose agent
|
|
23
|
+
* discovery detected are listed first. The log10x-hosted backend is
|
|
24
|
+
* for the public demo, not customer installs, so it is never offered
|
|
25
|
+
* or defaulted; it is accepted only when passed explicitly, and then
|
|
26
|
+
* needs a user licence (signin / paste) to receive anything.
|
|
22
27
|
* Q4. (Backend ≠ log10x only) Airgapped — opt-in for CISO-friction
|
|
23
28
|
* reduction. Skipped silently when backend=log10x (SaaS implies
|
|
24
29
|
* not-airgapped).
|
|
25
|
-
* Q5. License —
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* Q5. License — not asked. The default (`license_source: "builtin"`)
|
|
31
|
+
* puts no licence in the plan: the engine runs its built-in
|
|
32
|
+
* evaluation licence (10 nodes, 30 days from each start,
|
|
33
|
+
* airgapped), so there is nothing to mint, store or mount.
|
|
34
|
+
* `signin` (user-scoped JWT), `paste` (a JWT the user holds) and
|
|
35
|
+
* `demo` (1-node, 14-day JWT) stay available as explicit choices,
|
|
36
|
+
* and each adds a step that creates the licence Secret.
|
|
30
37
|
*
|
|
31
38
|
* State retention is via WizardSession on the snapshot store — the
|
|
32
39
|
* MCP itself is stateless per call, but the snapshot store's 30-min TTL
|
|
33
40
|
* gives the agent a coherent conversation thread.
|
|
34
41
|
*
|
|
35
|
-
* **Credential note.**
|
|
36
|
-
* for the install plan. The license JWT is
|
|
37
|
-
* engine pods consume
|
|
38
|
-
* value. The wizard never touches the user's api_key; the api_key stays
|
|
42
|
+
* **Credential note.** When asked to (`signin` / `demo`), the wizard mints
|
|
43
|
+
* a LICENSE JWT (not an api_key) for the install plan. The license JWT is
|
|
44
|
+
* the credential the deployed engine pods consume: it goes into the helm
|
|
45
|
+
* chart's `log10xLicenseJwt` value or a mounted Secret. The wizard never touches the user's api_key; the api_key stays
|
|
39
46
|
* with the MCP for user-action calls (queries, env management, etc.).
|
|
40
47
|
* See `../lib/auth-model.ts` for the full split.
|
|
41
48
|
*/
|
|
@@ -72,6 +79,21 @@ const SUPPORTED_BACKENDS = [
|
|
|
72
79
|
'cloudwatch',
|
|
73
80
|
'prometheus',
|
|
74
81
|
];
|
|
82
|
+
/**
|
|
83
|
+
* The backends the wizard OFFERS: the user's own TSDBs. `log10x` stays in
|
|
84
|
+
* SUPPORTED_BACKENDS so an explicit `backends: ["log10x"]` still validates,
|
|
85
|
+
* but the log10x-hosted TSDB serves the public demo, not customer installs
|
|
86
|
+
* (customer data stays off the hosted backend), so no question lists it and
|
|
87
|
+
* nothing defaults to it.
|
|
88
|
+
*/
|
|
89
|
+
const OFFERED_BACKENDS = SUPPORTED_BACKENDS.filter((b) => b !== 'log10x');
|
|
90
|
+
/** Offered backends with the ones discovery detected first, order otherwise kept. */
|
|
91
|
+
function offeredBackendsDetectedFirst(detected) {
|
|
92
|
+
return [
|
|
93
|
+
...OFFERED_BACKENDS.filter((b) => detected.has(b)),
|
|
94
|
+
...OFFERED_BACKENDS.filter((b) => !detected.has(b)),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
75
97
|
export const adviseInstallSchema = {
|
|
76
98
|
// Required in substance, optional in the schema on purpose: a first call
|
|
77
99
|
// with `{}` then reaches the handler, which answers with the
|
|
@@ -93,7 +115,7 @@ export const adviseInstallSchema = {
|
|
|
93
115
|
backends: z
|
|
94
116
|
.array(z.enum(SUPPORTED_BACKENDS))
|
|
95
117
|
.optional()
|
|
96
|
-
.describe('Where the engine emits TenXSummary metrics
|
|
118
|
+
.describe('Where the engine emits TenXSummary metrics: the user\'s own TSDB(s), one or more of **prometheus**, **datadog**, **elastic**, **cloudwatch**. When omitted, the wizard asks, listing any backend whose agent discovery detected first. **log10x** (the Log10x-hosted TSDB) is for the public demo, not customer installs: it is never suggested, and is accepted only when the user explicitly asks for it. It then needs a user licence (`license_source: "signin"` or `"paste"`); on the default built-in evaluation licence it receives nothing. `airgapped: true` + `"log10x"` is rejected.'),
|
|
97
119
|
airgapped: z
|
|
98
120
|
.boolean()
|
|
99
121
|
.optional()
|
|
@@ -112,15 +134,19 @@ export const adviseInstallSchema = {
|
|
|
112
134
|
.describe('Non-sensitive env var overrides keyed by env var name (e.g., `{ DD_SITE: "us5.datadoghq.com" }`).'),
|
|
113
135
|
}))
|
|
114
136
|
.optional()
|
|
115
|
-
.describe('Per-backend credential configuration, keyed by backend kind (
|
|
137
|
+
.describe('Per-backend credential configuration, keyed by backend kind (one of: datadog, elastic, cloudwatch, prometheus; `log10x` needs none, since it authenticates with the licence JWT). Each entry has a `secretName` (the Kubernetes Secret the user creates out-of-band holding sensitive env vars like `DD_API_KEY`; default per backend is `<backend>-credentials`) and optional `plainValues` (overrides for non-sensitive env vars like `DD_SITE`). Example: `{ "datadog": { "secretName": "datadog-secret", "plainValues": { "DD_SITE": "us5.datadoghq.com" } } }`.'),
|
|
138
|
+
// Optional rather than `.default()`: an omitted value keeps whatever the
|
|
139
|
+
// session already chose (so the re-invoke after log10x_signin_start, or
|
|
140
|
+
// the call carrying license_jwt_paste, stays on signin / paste), and only
|
|
141
|
+
// a session that never chose falls to 'builtin'.
|
|
116
142
|
license_source: z
|
|
117
|
-
.enum(['signin', 'demo', 'paste'])
|
|
118
|
-
.
|
|
119
|
-
.describe('How the
|
|
143
|
+
.enum(['builtin', 'signin', 'demo', 'paste'])
|
|
144
|
+
.optional()
|
|
145
|
+
.describe('How the engine is licensed. **Defaults to `"builtin"`** when omitted: the plan carries no licence and the engine runs its built-in evaluation licence (10 nodes, 30 days from each start, airgapped). Nothing is minted or stored, and no licence Secret is created. Pass **`"signin"`** when the user wants their own user-scoped licence: the wizard mints it via the user\'s Auth0 session and emits `signin_required` mode (chain through `log10x_signin_start`, then re-invoke) when no session exists. Pass **`"paste"`** with `license_jwt_paste: "<jwt>"` when the user already has a JWT. Pass **`"demo"`** only when the user explicitly asks for the anonymous 1-node, 14-day demo JWT. Every choice other than `builtin` adds a step that creates the licence Secret. Omitting this arg on a later call keeps the earlier choice.'),
|
|
120
146
|
license_jwt_paste: z
|
|
121
147
|
.string()
|
|
122
148
|
.optional()
|
|
123
|
-
.describe('License JWT supplied by the user when `license_source: "paste"
|
|
149
|
+
.describe('License JWT supplied by the user when `license_source: "paste"` (passing it alone implies `paste`). Minted from `POST /api/v1/license` (signed-in) or `POST /api/v1/license/demo` (anonymous). Maps to the chart\'s license Secret.'),
|
|
124
150
|
namespace: z.string().optional().describe('Target namespace. Default: snapshot.recommendations.suggestedNamespace.'),
|
|
125
151
|
release_name: z
|
|
126
152
|
.string()
|
|
@@ -177,11 +203,11 @@ const QUESTION_META = {
|
|
|
177
203
|
headline: 'Wizard Q5: run the engine fully airgapped (zero outbound calls to log10x.com)?',
|
|
178
204
|
answer_field: 'airgapped',
|
|
179
205
|
},
|
|
180
|
-
// 'license-source' is intentionally absent. The wizard
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
206
|
+
// 'license-source' is intentionally absent. The wizard does not elicit
|
|
207
|
+
// the license source: 'builtin' (the engine's built-in evaluation
|
|
208
|
+
// licence) is the implicit default. Signin, paste and demo remain
|
|
209
|
+
// accessible via the explicit `license_source` arg but never as a
|
|
210
|
+
// wizard-rendered question.
|
|
185
211
|
'license-paste': {
|
|
186
212
|
headline: 'Wizard Q6: paste the license JWT you already have.',
|
|
187
213
|
answer_field: 'license_jwt_paste',
|
|
@@ -330,7 +356,7 @@ function buildWizardHumanSummary(data, headline) {
|
|
|
330
356
|
case 'next_question':
|
|
331
357
|
return `Install wizard needs an answer to question "${data.question_id}" before it can emit a plan. Re-invoke log10x_advise_install with the answer in tool args and the same snapshot_id. Answers already given are remembered.`;
|
|
332
358
|
case 'missing_snapshot_id':
|
|
333
|
-
return `Install wizard needs a snapshot_id and none was supplied. Run log10x_discover_env (no args) to mint one, then re-invoke log10x_advise_install with it. The wizard asks for app, forwarder
|
|
359
|
+
return `Install wizard needs a snapshot_id and none was supplied. Run log10x_discover_env (no args) to mint one, then re-invoke log10x_advise_install with it. The wizard asks for app, forwarder and backends one question at a time from there.`;
|
|
334
360
|
case 'missing_snapshot':
|
|
335
361
|
return `Install wizard refused: snapshot ${data.snapshot_id} is missing or expired (snapshots live 30 minutes). Run log10x_discover_env again and re-invoke with the new snapshot_id.`;
|
|
336
362
|
case 'session_error':
|
|
@@ -618,7 +644,7 @@ function wizardEnvelopeMeta(data) {
|
|
|
618
644
|
`pairing plan instead of the Kubernetes wizard.`,
|
|
619
645
|
actions: [],
|
|
620
646
|
warnings: [
|
|
621
|
-
'the engine
|
|
647
|
+
'without TENX_LICENSE_KEY each function runs the engine\'s built-in evaluation license (10 nodes, 30 days from each start, airgapped); set TENX_LICENSE_KEY on each function to license it',
|
|
622
648
|
],
|
|
623
649
|
};
|
|
624
650
|
case 'license_error':
|
|
@@ -647,10 +673,10 @@ function wizardEnvelopeMeta(data) {
|
|
|
647
673
|
headline: `Wizard cannot mint a real license without sign-in. CHAIN: call log10x_signin_start NEXT, then re-invoke log10x_advise_install — every prior answer is preserved.`,
|
|
648
674
|
actions: [
|
|
649
675
|
{ tool: 'log10x_signin_start', args: {}, reason: 'opens the device-code browser flow to sign in to Log10x (gets Auth0 tokens needed to mint a user-scoped license). Must complete before the next action.', role: 'required-next' },
|
|
650
|
-
{ tool: 'log10x_advise_install', args: { snapshot_id: data.snapshot_id }, reason: 'after signin_start completes, re-invoke the wizard with the same snapshot_id; the wizard will auto-mint a real user-scoped license now that Auth0 tokens exist', role: 'required-next' },
|
|
676
|
+
{ tool: 'log10x_advise_install', args: { snapshot_id: data.snapshot_id, license_source: 'signin' }, reason: 'after signin_start completes, re-invoke the wizard with the same snapshot_id; the wizard will auto-mint a real user-scoped license now that Auth0 tokens exist', role: 'required-next' },
|
|
651
677
|
],
|
|
652
678
|
warnings: [
|
|
653
|
-
'plan NOT emitted yet
|
|
679
|
+
'plan NOT emitted yet: the user picked sign-in, and the wizard cannot proceed until log10x_signin_start has been called and completed successfully. Do NOT silently fall back to demo. If the user would rather not sign in, re-invoke with license_source: "builtin" to run on the engine\'s built-in evaluation licence (10 nodes, 30 days from each start, airgapped).',
|
|
654
680
|
],
|
|
655
681
|
};
|
|
656
682
|
case 'unknown_args': {
|
|
@@ -694,13 +720,23 @@ function wizardEnvelopeMeta(data) {
|
|
|
694
720
|
const isDemoLicense = data.license_kind === 'demo' ||
|
|
695
721
|
(data.license_kind === undefined && data.notes.some((n) => /demo license/i.test(n)));
|
|
696
722
|
if (isDemoLicense) {
|
|
697
|
-
warnings.push('plan emitted with a demo license
|
|
723
|
+
warnings.push('plan emitted with a demo license: 1 node, expires 14 days after it was minted. Re-run with `license_source: "signin"` before then for a user-scoped one, or with `license_source: "builtin"` to drop the JWT and run the engine\'s built-in evaluation licence (10 nodes, 30 days from each start, airgapped).');
|
|
698
724
|
// The demo license runs airgapped (engine 1.1.61+ verifies offline
|
|
699
725
|
// and never reads the license type), so an airgapped plan is emitted
|
|
700
726
|
// rather than refused. What still bites is scale and the clock, and
|
|
701
727
|
// an operator reading the plan has to see both.
|
|
702
728
|
if (data.notes.some((n) => /airgapped/i.test(n))) {
|
|
703
|
-
warnings.push('airgapped on a demo license works
|
|
729
|
+
warnings.push('airgapped on a demo license works, since the engine verifies it offline, but the license covers 1 node and expires 14 days after minting, so an isolated estate stops processing when it lapses. Place a user license before then.');
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// No licence in the plan: not a fault, but the limits are real and an
|
|
733
|
+
// operator reading the plan has to see them.
|
|
734
|
+
if (data.license_kind === 'builtin') {
|
|
735
|
+
warnings.push('no licence in the plan: the engine runs its built-in evaluation licence, 10 nodes, 30 days from each start, airgapped. To license it, re-run with `license_source: "signin"` or `license_source: "paste"`; the plan then adds a step that creates the licence Secret.');
|
|
736
|
+
// Airgapped means nothing reaches log10x, including the log10x
|
|
737
|
+
// metrics backend. The preflight row carries the detail.
|
|
738
|
+
if (data.preflight.some((c) => c.name === 'license' && c.status === 'warn')) {
|
|
739
|
+
warnings.push('the `log10x` metrics backend receives nothing on the built-in evaluation licence: the engine runs airgapped and sends nothing to log10x. Your other backends are unaffected. Sign in or paste a licence to report to log10x.');
|
|
704
740
|
}
|
|
705
741
|
}
|
|
706
742
|
const actions = [];
|
|
@@ -708,7 +744,9 @@ function wizardEnvelopeMeta(data) {
|
|
|
708
744
|
actions.push({
|
|
709
745
|
tool: 'log10x_doctor',
|
|
710
746
|
args: {},
|
|
711
|
-
reason:
|
|
747
|
+
reason: data.license_kind === 'builtin'
|
|
748
|
+
? 'verify the install once the helm release rolls out: checks engine pods Ready and metrics flowing'
|
|
749
|
+
: 'verify the install once the helm release rolls out — checks engine pods Ready, metrics flowing, license-Secret mounted',
|
|
712
750
|
role: 'optional-followup',
|
|
713
751
|
});
|
|
714
752
|
// Receiver path: post-install pattern-mitigation is the natural follow-up.
|
|
@@ -812,7 +850,7 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
812
850
|
'{ "snapshot_id": "<id from log10x_discover_env>" }',
|
|
813
851
|
'```',
|
|
814
852
|
'',
|
|
815
|
-
'The wizard asks for app, forwarder
|
|
853
|
+
'The wizard asks for app, forwarder and backends one question at a time from there, so the ' +
|
|
816
854
|
'first call carries the snapshot_id alone.',
|
|
817
855
|
].join('\n');
|
|
818
856
|
return wizardReturn({
|
|
@@ -842,7 +880,12 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
842
880
|
// Kubernetes wizard's every question (helm release, namespace, sidecar
|
|
843
881
|
// forwarder) is meaningless here — emit the OTel-extension pairing plan
|
|
844
882
|
// instead of walking the user into a helm plan they cannot apply.
|
|
845
|
-
|
|
883
|
+
// A forwarder named in the call is a Kubernetes sidecar request, and wins
|
|
884
|
+
// over the estate guess: an account with Lambdas and no reachable cluster
|
|
885
|
+
// from this machine answered `forwarder: "fluentbit"` with a Lambda plan.
|
|
886
|
+
// otel-collector stays ambiguous (it is also the Lambda extension's collector).
|
|
887
|
+
const namedSidecar = args.forwarder !== undefined && args.forwarder !== 'otel-collector';
|
|
888
|
+
if (snapshot.recommendations.estateShape === 'serverless' && !namedSidecar) {
|
|
846
889
|
return serverlessPlanReturn(snapshot);
|
|
847
890
|
}
|
|
848
891
|
if (snapshot.recommendations.estateShape === 'azure_serverless') {
|
|
@@ -854,6 +897,13 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
854
897
|
// already there rather than replacing the whole map — the user might
|
|
855
898
|
// answer credentials for one backend at a time across multiple turns.
|
|
856
899
|
const prior = getWizardSession(snapshotId);
|
|
900
|
+
// Licence source: an explicit arg wins; a pasted JWT alone means paste;
|
|
901
|
+
// otherwise keep the session's earlier choice, and a session that never
|
|
902
|
+
// chose runs on the engine's built-in evaluation licence.
|
|
903
|
+
const licenseSource = args.license_source ??
|
|
904
|
+
(args.license_jwt_paste ? 'paste' : undefined) ??
|
|
905
|
+
prior?.licenseSource ??
|
|
906
|
+
'builtin';
|
|
857
907
|
const mergedBackendCredentials = args.backend_credentials
|
|
858
908
|
? {
|
|
859
909
|
...(prior?.backendCredentials ?? {}),
|
|
@@ -866,10 +916,11 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
866
916
|
backends: args.backends,
|
|
867
917
|
backendCredentials: mergedBackendCredentials,
|
|
868
918
|
airgapped: args.airgapped,
|
|
869
|
-
licenseSource
|
|
919
|
+
licenseSource,
|
|
870
920
|
// license_jwt_paste fills licenseJwt directly — it's the actual JWT
|
|
871
921
|
// the user supplied. The session's licenseJwt is overwritten below
|
|
872
|
-
// by acquireLicenseForWizard() for the signin/demo paths
|
|
922
|
+
// by acquireLicenseForWizard() for the signin/demo paths, and ignored
|
|
923
|
+
// on the builtin path.
|
|
873
924
|
licenseJwt: args.license_jwt_paste,
|
|
874
925
|
releaseName: args.release_name,
|
|
875
926
|
namespace: args.namespace,
|
|
@@ -937,7 +988,10 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
937
988
|
});
|
|
938
989
|
}
|
|
939
990
|
}
|
|
940
|
-
// License acquisition.
|
|
991
|
+
// License acquisition. Four paths driven by session.licenseSource:
|
|
992
|
+
// - 'builtin' — the default. Nothing to acquire: the plan carries no
|
|
993
|
+
// licence and the engine runs its built-in evaluation licence (10
|
|
994
|
+
// nodes, 30 days from each start, airgapped). No gateway call.
|
|
941
995
|
// - 'signin' — the user said "sign me in". We require a real
|
|
942
996
|
// user-scoped license; if acquireLicenseForWizard would return a
|
|
943
997
|
// demo (because the user isn't actually signed in via the device
|
|
@@ -949,7 +1003,8 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
949
1003
|
// - 'demo' — explicit demo. Skip the signed-in check, mint a demo.
|
|
950
1004
|
// - 'paste' — the user supplied license_jwt_paste; session.licenseJwt
|
|
951
1005
|
// is already populated from that arg. Nothing to do here.
|
|
952
|
-
|
|
1006
|
+
const builtinLicense = session.licenseSource === 'builtin';
|
|
1007
|
+
if (!builtinLicense && !session.licenseJwt && session.licenseSource !== 'paste') {
|
|
953
1008
|
try {
|
|
954
1009
|
const lic = await acquireLicenseForWizard();
|
|
955
1010
|
const isRealUserLicense = lic.reason === 'signed-in-user' || lic.reason === 'refreshed-then-user';
|
|
@@ -998,6 +1053,7 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
998
1053
|
`> ${msg}`,
|
|
999
1054
|
``,
|
|
1000
1055
|
`Options:`,
|
|
1056
|
+
`- Re-invoke with \`license_source: "builtin"\` to install with no licence: the engine runs its built-in evaluation licence (10 nodes, 30 days from each start, airgapped)`,
|
|
1001
1057
|
`- Sign in via \`log10x_signin_start\` if you haven't already`,
|
|
1002
1058
|
`- Re-invoke with \`license_source: "paste"\` and \`license_jwt_paste: "<your-jwt>"\` if you have one`,
|
|
1003
1059
|
`- Retry — the gateway may have been transiently unavailable`,
|
|
@@ -1027,6 +1083,7 @@ export async function executeAdviseInstall(args, envs, mcpServer) {
|
|
|
1027
1083
|
// engine older than 1.1.61, which silently downgraded demo/limited
|
|
1028
1084
|
// licenses to online mode.
|
|
1029
1085
|
if (process.env.LOG10X_DEMO_AIRGAPPED_REFUSE === '1' &&
|
|
1086
|
+
!builtinLicense &&
|
|
1030
1087
|
session.airgapped === true &&
|
|
1031
1088
|
session.isDemoLicense === true) {
|
|
1032
1089
|
// `is_signed_in` here means "had Auth0 tokens" — i.e., the device
|
|
@@ -1156,7 +1213,7 @@ async function elicitMissingAnswers(server, snapshot, session) {
|
|
|
1156
1213
|
if (!session.backends || session.backends.length === 0) {
|
|
1157
1214
|
const detectedSet = new Set(snapshot.kubectl.backendAgents.map((a) => a.kind));
|
|
1158
1215
|
const result = await server.server.elicitInput({
|
|
1159
|
-
message: 'The engine emits event statistics, cost attribution, and per-pattern enrichments as time-series metrics
|
|
1216
|
+
message: 'The engine emits event statistics, cost attribution, and per-pattern enrichments as time-series metrics, and we need to know where to publish them. Pick your own TSDB(s) to read these metrics from (the MCP and your dashboards query them back from there). You can publish to more than one at the same time.',
|
|
1160
1217
|
requestedSchema: {
|
|
1161
1218
|
type: 'object',
|
|
1162
1219
|
properties: {
|
|
@@ -1165,14 +1222,9 @@ async function elicitMissingAnswers(server, snapshot, session) {
|
|
|
1165
1222
|
title: 'Metrics backends',
|
|
1166
1223
|
minItems: 1,
|
|
1167
1224
|
items: {
|
|
1168
|
-
anyOf:
|
|
1225
|
+
anyOf: offeredBackendsDetectedFirst(detectedSet).map((b) => ({
|
|
1169
1226
|
const: b,
|
|
1170
|
-
title: BACKEND_LABEL[b] +
|
|
1171
|
-
(b === 'log10x'
|
|
1172
|
-
? ' (recommended for first install)'
|
|
1173
|
-
: detectedSet.has(b)
|
|
1174
|
-
? ' (detected in your cluster)'
|
|
1175
|
-
: ''),
|
|
1227
|
+
title: BACKEND_LABEL[b] + (detectedSet.has(b) ? ' (detected in your cluster)' : ''),
|
|
1176
1228
|
})),
|
|
1177
1229
|
},
|
|
1178
1230
|
},
|
|
@@ -1265,9 +1317,11 @@ async function elicitMissingAnswers(server, snapshot, session) {
|
|
|
1265
1317
|
updateWizardSession(session.snapshotId, { airgapped: false });
|
|
1266
1318
|
session.airgapped = false;
|
|
1267
1319
|
}
|
|
1268
|
-
// Q6: license source — IMPLICIT '
|
|
1320
|
+
// Q6: license source — IMPLICIT 'builtin' (no licence; the engine runs
|
|
1321
|
+
// its built-in evaluation licence). Not elicited.
|
|
1269
1322
|
//
|
|
1270
|
-
//
|
|
1323
|
+
// History: when sign-in was the implicit default,
|
|
1324
|
+
// it was deliberately NOT elicited: three attempts to get Claude
|
|
1271
1325
|
// Desktop's UI to mark "sign in" as the recommended choice all
|
|
1272
1326
|
// failed: regardless of enumNames text, `default:` field, or
|
|
1273
1327
|
// "(Recommended)" suffix, the host's auto-rephraser keeps stripping
|
|
@@ -1276,18 +1330,13 @@ async function elicitMissingAnswers(server, snapshot, session) {
|
|
|
1276
1330
|
// host overrule our intent means users routinely pick demo without
|
|
1277
1331
|
// realizing it's the wrong path for real installs.
|
|
1278
1332
|
//
|
|
1279
|
-
//
|
|
1280
|
-
//
|
|
1281
|
-
// `
|
|
1282
|
-
//
|
|
1283
|
-
//
|
|
1284
|
-
//
|
|
1285
|
-
//
|
|
1286
|
-
//
|
|
1287
|
-
// The Zod schema `.default('signin')` on license_source already
|
|
1288
|
-
// populates args.license_source on every call, and the session
|
|
1289
|
-
// merge below folds it into session.licenseSource — no explicit
|
|
1290
|
-
// defaulting needed here.
|
|
1333
|
+
// Now the default needs no choice at all: an install with no licence
|
|
1334
|
+
// runs. Signin, paste and demo remain reachable via the schema's
|
|
1335
|
+
// `license_source` arg as explicit choices (the agent passes them when
|
|
1336
|
+
// the USER says "use my licence", "I have a JWT" or "give me the
|
|
1337
|
+
// demo"). The session merge in executeAdviseInstall resolves the
|
|
1338
|
+
// effective source (explicit arg, else pasted JWT, else the session's
|
|
1339
|
+
// earlier choice, else 'builtin'), so no defaulting is needed here.
|
|
1291
1340
|
// Q7: paste path — ask for the JWT itself.
|
|
1292
1341
|
if (session.licenseSource === 'paste' && !session.licenseJwt) {
|
|
1293
1342
|
const result = await server.server.elicitInput({
|
|
@@ -1413,7 +1462,7 @@ function nextQuestion(snapshot, session) {
|
|
|
1413
1462
|
if (!session.backends || session.backends.length === 0) {
|
|
1414
1463
|
const detectedSet = new Set(snapshot.kubectl.backendAgents.map((a) => a.kind));
|
|
1415
1464
|
const backendDetails = {
|
|
1416
|
-
log10x: 'Log10x-
|
|
1465
|
+
log10x: 'Log10x-hosted TSDB, used by the public demo. Not offered for customer installs.',
|
|
1417
1466
|
datadog: 'Datadog metric API (DD-API-KEY auth).',
|
|
1418
1467
|
elastic: 'Elasticsearch metric ingest API.',
|
|
1419
1468
|
cloudwatch: 'AWS CloudWatch metric streams.',
|
|
@@ -1428,7 +1477,7 @@ function nextQuestion(snapshot, session) {
|
|
|
1428
1477
|
type: 'multi-choice',
|
|
1429
1478
|
answer_field: 'backends',
|
|
1430
1479
|
min_items: 1,
|
|
1431
|
-
choices:
|
|
1480
|
+
choices: offeredBackendsDetectedFirst(detectedSet).map((b) => {
|
|
1432
1481
|
const detected = detectedSet.has(b);
|
|
1433
1482
|
const detail = backendDetails[b] ?? '';
|
|
1434
1483
|
return {
|
|
@@ -1456,7 +1505,7 @@ function nextQuestion(snapshot, session) {
|
|
|
1456
1505
|
},
|
|
1457
1506
|
{
|
|
1458
1507
|
args: { snapshot_id: session.snapshotId, airgapped: false },
|
|
1459
|
-
description: 'Keep "log10x" in backends
|
|
1508
|
+
description: 'Keep "log10x" in backends and drop airgapped. The log10x-hosted TSDB is for the public demo, and it receives metrics only on a user licence (license_source "signin" or "paste").',
|
|
1460
1509
|
},
|
|
1461
1510
|
],
|
|
1462
1511
|
},
|
|
@@ -1519,12 +1568,10 @@ function nextQuestion(snapshot, session) {
|
|
|
1519
1568
|
updateWizardSession(session.snapshotId, { airgapped: false });
|
|
1520
1569
|
session.airgapped = false;
|
|
1521
1570
|
}
|
|
1522
|
-
// Q6 (license source) — IMPLICIT '
|
|
1523
|
-
// matching block in elicitMissingAnswers() above
|
|
1524
|
-
//
|
|
1525
|
-
//
|
|
1526
|
-
// already populates args.license_source and the session merge folds
|
|
1527
|
-
// it into session.licenseSource on every call.
|
|
1571
|
+
// Q6 (license source) — IMPLICIT 'builtin'. Not elicited. See the
|
|
1572
|
+
// matching block in elicitMissingAnswers() above. Signin / paste / demo
|
|
1573
|
+
// remain accessible as explicit `license_source` args; the session
|
|
1574
|
+
// merge in executeAdviseInstall resolves the effective source.
|
|
1528
1575
|
// Q7: when license_source=paste and the JWT hasn't been provided yet,
|
|
1529
1576
|
// ask for it. (The agent passes it via `license_jwt_paste`, which the
|
|
1530
1577
|
// session merge folds into `licenseJwt`.)
|
|
@@ -1609,7 +1656,7 @@ function askForwarder(detected) {
|
|
|
1609
1656
|
* as the option text.
|
|
1610
1657
|
*/
|
|
1611
1658
|
const BACKEND_LABEL = {
|
|
1612
|
-
log10x: 'Log10x
|
|
1659
|
+
log10x: 'Log10x-hosted TSDB (public demo)',
|
|
1613
1660
|
datadog: 'Datadog',
|
|
1614
1661
|
elastic: 'Elasticsearch',
|
|
1615
1662
|
cloudwatch: 'AWS CloudWatch',
|
|
@@ -1622,7 +1669,7 @@ function askBackends(detectedAgents) {
|
|
|
1622
1669
|
lines.push('');
|
|
1623
1670
|
lines.push('The Log10x engine publishes event statistics (volumes, cost attribution, pattern fingerprints) and per-pattern enrichments as time-series metrics. We need to know **which TSDB(s) to push them to** — that\'s where the MCP and your dashboards will read them back from.');
|
|
1624
1671
|
lines.push('');
|
|
1625
|
-
lines.push('You can ship the same metrics to **
|
|
1672
|
+
lines.push('The metrics go to a TSDB you already run. You can ship the same metrics to **more than one in parallel**, for example Prometheus and Datadog.');
|
|
1626
1673
|
lines.push('');
|
|
1627
1674
|
lines.push('Options (pick one or more):');
|
|
1628
1675
|
lines.push('');
|
|
@@ -1632,21 +1679,19 @@ function askBackends(detectedAgents) {
|
|
|
1632
1679
|
// "Other backends:" line causes those UIs to collapse them into a
|
|
1633
1680
|
// single "Something else" option, hiding choices.
|
|
1634
1681
|
const detectedSet = new Set(detectedAgents.map((a) => a.kind));
|
|
1635
|
-
for (const kind of
|
|
1682
|
+
for (const kind of offeredBackendsDetectedFirst(detectedSet)) {
|
|
1636
1683
|
const label = BACKEND_LABEL[kind];
|
|
1637
1684
|
const annotations = [];
|
|
1638
|
-
if (kind === 'log10x')
|
|
1639
|
-
annotations.push('optional Log10x-hosted backend, for evaluation');
|
|
1640
1685
|
if (detectedSet.has(kind))
|
|
1641
1686
|
annotations.push('detected in your cluster');
|
|
1642
1687
|
const suffix = annotations.length > 0 ? ` — ${annotations.join(', ')}` : '';
|
|
1643
1688
|
lines.push(`- **${label}** (\`${kind}\`)${suffix}`);
|
|
1644
1689
|
}
|
|
1645
1690
|
lines.push('');
|
|
1646
|
-
|
|
1647
|
-
lines.push('- `backends: ["
|
|
1648
|
-
lines.push(
|
|
1649
|
-
lines.push('- `backends: ["
|
|
1691
|
+
const first = offeredBackendsDetectedFirst(detectedSet)[0] ?? 'prometheus';
|
|
1692
|
+
lines.push('Re-invoke `log10x_advise_install` with `backends: ["<choice>", ...]`, one or more. Examples:');
|
|
1693
|
+
lines.push(`- \`backends: ["${first}"]\`: one backend`);
|
|
1694
|
+
lines.push('- `backends: ["prometheus", "datadog"]`: two, side by side');
|
|
1650
1695
|
return lines.join('\n');
|
|
1651
1696
|
}
|
|
1652
1697
|
function askBackendCredentials(backends) {
|
|
@@ -1712,9 +1757,10 @@ function askBackendCredentials(backends) {
|
|
|
1712
1757
|
return lines.join('\n');
|
|
1713
1758
|
}
|
|
1714
1759
|
// There is no askLicenseSource(): the wizard does not surface the
|
|
1715
|
-
// license-source choice as a question.
|
|
1716
|
-
//
|
|
1717
|
-
// explicit `license_source` arg on
|
|
1760
|
+
// license-source choice as a question. 'builtin' (no licence; the engine
|
|
1761
|
+
// runs its built-in evaluation licence) is the implicit default; signin /
|
|
1762
|
+
// paste / demo remain accessible via the explicit `license_source` arg on
|
|
1763
|
+
// advise_install.
|
|
1718
1764
|
function askLicensePaste() {
|
|
1719
1765
|
return [
|
|
1720
1766
|
'# Install wizard — paste the license JWT',
|
|
@@ -1740,11 +1786,11 @@ function airgappedLog10xConflict(backends) {
|
|
|
1740
1786
|
return [
|
|
1741
1787
|
'# Install wizard — airgapped + log10x is impossible',
|
|
1742
1788
|
'',
|
|
1743
|
-
`You picked \`airgapped: true\` AND included \`"log10x"\` in \`backends\` (\`${backends.map((b) => `"${b}"`).join(', ')}\`). These conflict: airgapped means the engine sends NOTHING to log10x.com,
|
|
1789
|
+
`You picked \`airgapped: true\` AND included \`"log10x"\` in \`backends\` (\`${backends.map((b) => `"${b}"`).join(', ')}\`). These conflict: airgapped means the engine sends NOTHING to log10x.com, and \`"log10x"\` is the Log10x-hosted TSDB at log10x.com, which serves the public demo.`,
|
|
1744
1790
|
'',
|
|
1745
1791
|
'Pick one:',
|
|
1746
1792
|
`- **Keep airgapped, drop log10x**: re-invoke with \`backends: [${backends.filter((b) => b !== 'log10x').map((b) => `"${b}"`).join(', ') || '"<your-backend>"'}]\` (engine emits only to your own backend(s))`,
|
|
1747
|
-
`- **Keep log10x, drop airgapped**: re-invoke with \`airgapped: false\` (
|
|
1793
|
+
`- **Keep log10x, drop airgapped**: re-invoke with \`airgapped: false\` and a user licence (\`license_source: "signin"\` or \`"paste"\`); on the built-in evaluation licence the engine still sends nothing to log10x`,
|
|
1748
1794
|
].join('\n');
|
|
1749
1795
|
}
|
|
1750
1796
|
/**
|
|
@@ -1853,8 +1899,9 @@ async function renderInstallPlan(snapshot, session, args) {
|
|
|
1853
1899
|
// Demo + airgapped: at this point either the user opted out of
|
|
1854
1900
|
// airgapped OR signed in. If we still see demo+airgapped together,
|
|
1855
1901
|
// the engine will downgrade — emit a banner.
|
|
1856
|
-
|
|
1857
|
-
|
|
1902
|
+
const builtinLicense = session.licenseSource === 'builtin';
|
|
1903
|
+
if (!builtinLicense && session.airgapped && session.isDemoLicense) {
|
|
1904
|
+
lines.push('> ⚠ Plan emitted with `airgapped: true` and a **demo license**. The engine verifies the license offline, so the pods stay airgapped, but the demo license covers 1 node and expires 14 days after it was minted: an isolated install stops processing when it lapses. Place a user license before then, or re-run with `license_source: "builtin"` to run the engine\'s built-in evaluation licence instead (10 nodes, 30 days from each start, airgapped).');
|
|
1858
1905
|
lines.push('');
|
|
1859
1906
|
}
|
|
1860
1907
|
// Destination defaults to `mock` — the install wizard no longer asks
|
|
@@ -1872,8 +1919,11 @@ async function renderInstallPlan(snapshot, session, args) {
|
|
|
1872
1919
|
forwarder: session.forwarder,
|
|
1873
1920
|
releaseName: session.releaseName,
|
|
1874
1921
|
namespace: session.namespace,
|
|
1875
|
-
|
|
1876
|
-
|
|
1922
|
+
// Builtin: no JWT reaches the plan even if an earlier turn stored one,
|
|
1923
|
+
// so no Secret step and no licence mount are emitted.
|
|
1924
|
+
licenseJwt: builtinLicense ? undefined : session.licenseJwt,
|
|
1925
|
+
isDemoLicense: builtinLicense ? undefined : session.isDemoLicense,
|
|
1926
|
+
builtinLicense,
|
|
1877
1927
|
destination,
|
|
1878
1928
|
backends: session.backends,
|
|
1879
1929
|
backendCredentials: session.backendCredentials,
|
|
@@ -1891,12 +1941,28 @@ function renderChoiceSummary(session) {
|
|
|
1891
1941
|
if (session.app === 'receiver' && session.forwarder) {
|
|
1892
1942
|
rows.push(`- **Forwarder**: \`${session.forwarder}\``);
|
|
1893
1943
|
}
|
|
1894
|
-
const backendList = (session.backends ?? [
|
|
1944
|
+
const backendList = (session.backends ?? []).map((b) => `\`${b}\``).join(' + ') || 'none chosen';
|
|
1895
1945
|
rows.push(`- **Metrics backends**: ${backendList}`);
|
|
1896
1946
|
if (session.airgapped)
|
|
1897
1947
|
rows.push(`- **Airgapped**: yes (engine emits only to user-owned backends)`);
|
|
1898
|
-
|
|
1899
|
-
rows.push(`- **License**: anonymous 14-day demo (sign in for a user-scoped one)`);
|
|
1948
|
+
rows.push(`- **License**: ${licenseSummary(session)}`);
|
|
1900
1949
|
return rows.join('\n');
|
|
1901
1950
|
}
|
|
1951
|
+
/** One line on what the engine will run on, with the limits as numbers. */
|
|
1952
|
+
function licenseSummary(session) {
|
|
1953
|
+
if (session.licenseSource === 'builtin') {
|
|
1954
|
+
const toLog10x = (session.backends ?? []).includes('log10x')
|
|
1955
|
+
? ' Airgapped means the engine sends nothing to log10x, so the `log10x` metrics backend receives no metrics until the engine is licensed.'
|
|
1956
|
+
: '';
|
|
1957
|
+
return 'none in the plan. The engine runs its built-in evaluation licence: 10 nodes, 30 days from each start, airgapped. Nothing is minted or stored and no licence Secret is created.' +
|
|
1958
|
+
toLog10x +
|
|
1959
|
+
' To license it later, re-run with `license_source: "signin"` or `license_source: "paste"`; the plan then adds the step that creates the licence Secret.';
|
|
1960
|
+
}
|
|
1961
|
+
if (session.isDemoLicense) {
|
|
1962
|
+
return 'anonymous demo JWT: 1 node, expires 14 days after it was minted. Sign in for a user-scoped one, or re-run with `license_source: "builtin"` for the built-in evaluation licence (10 nodes, 30 days from each start, airgapped).';
|
|
1963
|
+
}
|
|
1964
|
+
if (session.licenseSource === 'paste')
|
|
1965
|
+
return 'the JWT you pasted, delivered to the engine through a Kubernetes Secret.';
|
|
1966
|
+
return 'your user-scoped licence, delivered to the engine through a Kubernetes Secret.';
|
|
1967
|
+
}
|
|
1902
1968
|
//# sourceMappingURL=advise-install.js.map
|