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.
Files changed (78) hide show
  1. package/README.md +3 -1
  2. package/build/index.js +26 -4
  3. package/build/index.js.map +1 -1
  4. package/build/lib/advisor/envelope.d.ts +1 -1
  5. package/build/lib/advisor/reporter-forwarders.d.ts +32 -13
  6. package/build/lib/advisor/reporter-forwarders.js +179 -94
  7. package/build/lib/advisor/reporter-forwarders.js.map +1 -1
  8. package/build/lib/advisor/reporter.d.ts +18 -10
  9. package/build/lib/advisor/reporter.js +43 -16
  10. package/build/lib/advisor/reporter.js.map +1 -1
  11. package/build/lib/advisor/retriever.d.ts +6 -0
  12. package/build/lib/advisor/retriever.js +5 -3
  13. package/build/lib/advisor/retriever.js.map +1 -1
  14. package/build/lib/advisor/types.d.ts +3 -1
  15. package/build/lib/cdk-recipes.d.ts +1 -1
  16. package/build/lib/cdk-recipes.js +21 -10
  17. package/build/lib/cdk-recipes.js.map +1 -1
  18. package/build/lib/discovery/types.d.ts +15 -10
  19. package/build/lib/fenced.d.ts +2 -0
  20. package/build/lib/fenced.js +2 -2
  21. package/build/lib/fenced.js.map +1 -1
  22. package/build/lib/offload-recipes.d.ts +2 -1
  23. package/build/lib/offload-recipes.js +13 -10
  24. package/build/lib/offload-recipes.js.map +1 -1
  25. package/build/lib/poc-report-renderer.js +17 -11
  26. package/build/lib/poc-report-renderer.js.map +1 -1
  27. package/build/lib/retriever-api.d.ts +14 -0
  28. package/build/lib/retriever-api.js +40 -10
  29. package/build/lib/retriever-api.js.map +1 -1
  30. package/build/lib/runtime-image.d.ts +7 -2
  31. package/build/lib/runtime-image.js +7 -2
  32. package/build/lib/runtime-image.js.map +1 -1
  33. package/build/lib/siem/_sampling.js +4 -0
  34. package/build/lib/siem/_sampling.js.map +1 -1
  35. package/build/lib/siem/cloudwatch.d.ts +18 -0
  36. package/build/lib/siem/cloudwatch.js +106 -2
  37. package/build/lib/siem/cloudwatch.js.map +1 -1
  38. package/build/lib/siem/datadog.js +1 -1
  39. package/build/lib/siem/datadog.js.map +1 -1
  40. package/build/lib/siem/elasticsearch.js +13 -1
  41. package/build/lib/siem/elasticsearch.js.map +1 -1
  42. package/build/lib/siem/splunk.d.ts +18 -0
  43. package/build/lib/siem/splunk.js +71 -7
  44. package/build/lib/siem/splunk.js.map +1 -1
  45. package/build/product-kb/docs/apps/receiver/compact/index.md +3 -3
  46. package/build/product-kb/docs/apps/receiver/compact/splunk.md +81 -67
  47. package/build/product-kb/docs/apps/retriever/faq.md +2 -2
  48. package/build/product-kb/docs/engine/launcher/extension.md +20 -20
  49. package/build/product-kb/docs/faq/apps/retriever.md +2 -2
  50. package/build/product-kb/docs/faq/general.md +1 -1
  51. package/build/product-kb/docs/faq/stacks/splunk/comparisons.md +3 -3
  52. package/build/product-kb/docs/faq/stacks/splunk/compatibility.md +19 -242
  53. package/build/product-kb/docs/faq/stacks/splunk/index.md +1 -1
  54. package/build/product-kb/docs/faq/stacks/splunk/kv-store.md +41 -159
  55. package/build/product-kb/docs/faq/stacks/splunk/optimization.md +4 -4
  56. package/build/product-kb/docs/run/input/forwarder/fluentbit/index.md +1 -1
  57. package/build/product-kb/docs/run/input/forwarder/fluentd/index.md +1 -1
  58. package/build/product-kb/docs/run/input/forwarder/otel-collector/index.md +1 -1
  59. package/build/product-kb/docs/run/input/forwarder/vector/index.md +1 -1
  60. package/build/product-kb/docs/summary.md +0 -1
  61. package/build/tools/advise-install.d.ts +25 -18
  62. package/build/tools/advise-install.js +156 -90
  63. package/build/tools/advise-install.js.map +1 -1
  64. package/build/tools/advise-retriever.d.ts +5 -5
  65. package/build/tools/advise-retriever.js +25 -9
  66. package/build/tools/advise-retriever.js.map +1 -1
  67. package/build/tools/doctor.js +3 -2
  68. package/build/tools/doctor.js.map +1 -1
  69. package/build/tools/emit-sample-plan.js +7 -1
  70. package/build/tools/emit-sample-plan.js.map +1 -1
  71. package/build/tools/poc-from-local.js +7 -6
  72. package/build/tools/poc-from-local.js.map +1 -1
  73. package/build/tools/poc-from-siem.js +71 -4
  74. package/build/tools/poc-from-siem.js.map +1 -1
  75. package/build/tools/retriever-query.js +1 -1
  76. package/build/tools/retriever-query.js.map +1 -1
  77. package/package.json +1 -1
  78. 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 with the
11
- * license JWT pre-filled (auto-fetched from the gateway if absent).
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. Default suggestion:
20
- * log10x SaaS unless airgapped or a backend agent is already
21
- * detected in the cluster.
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 — auto-fetched from `/api/v1/license/demo` if the user
26
- * hasn't signed in. When `airgapped=true` and the only available
27
- * license is a demo, the wizard surfaces a soft warning that
28
- * demo licenses can't run airgapped (engine downgrades silently
29
- * to online mode), and offers sign-in or proceed-without-airgapped.
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.** The wizard mints a LICENSE JWT (not an api_key)
36
- * for the install plan. The license JWT is the credential the deployed
37
- * engine pods consume — it goes into the helm chart's `log10xLicenseJwt`
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. Multi-destination — a user can report to log10x SaaS AND their own backend simultaneously, e.g. `["log10x", "datadog"]`. Choices: **log10x** (optional Log10x-hosted backend, for evaluation), **datadog**, **elastic**, **cloudwatch**, **prometheus** (customer-owned). The wizard pre-fills detected backends from the snapshot. The only mutual exclusion is `airgapped: true` + `"log10x"` in this list.'),
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 (must be one of: log10x, datadog, elastic, cloudwatch, prometheus). **Only set for non-`log10x` backends** — `log10x` SaaS uses the license JWT and needs no extra credentials. 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" } } }`.'),
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
- .default('signin')
119
- .describe('How the wizard should acquire the engine\'s license JWT. **Defaults to `"signin"`** when omitted — the wizard tries to mint a user-scoped license via the user\'s Auth0 session, and emits `signin_required` mode (chain through `log10x_signin_start` then re-invoke) when no session exists. Pass **`"demo"`** ONLY when the user explicitly asks for a quick 14-day anonymous demo (transient, can\'t run airgapped). Pass **`"paste"`** with `license_jwt_paste: "<jwt>"` when the user already has a JWT.'),
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"`. Mints from `POST /api/v1/license` (signed-in) or `POST /api/v1/license/demo` (anonymous). Maps to the chart\'s license Secret.'),
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 no longer
181
- // elicits the license source — signin is the implicit default and the
182
- // wizard emits `signin_required` mode when no Auth0 session exists.
183
- // Demo and paste remain accessible via the explicit `license_source`
184
- // arg but never as a wizard-rendered question.
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, backends and licence one question at a time from there.`;
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 extension layer is not published yet — this plan declares the shape and configuration, not a deployable artifact',
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 — the user picked sign-in, but the wizard cannot proceed until log10x_signin_start has been called and completed successfully. Do NOT proceed without it; do NOT silently fall back to demo.',
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 — re-run with `license_source: "signin"` before the 14-day window expires to get a user-scoped one');
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 — the engine verifies offline (proven on a live Lambda estate) — but the license is single-node and expires in 14 days, so an isolated estate stops processing when it lapses. Mint a user license and place it before the window closes.');
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: 'verify the install once the helm release rolls out — checks engine pods Ready, metrics flowing, license-Secret mounted',
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, backends and licence one question at a time from there, so the ' +
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
- if (snapshot.recommendations.estateShape === 'serverless') {
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: args.license_source,
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. Three paths driven by session.licenseSource:
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
- if (!session.licenseJwt && session.licenseSource !== 'paste') {
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 — and we need to know where to publish them. Pick the TSDB(s) where you want to read these metrics from (the MCP and your dashboards query them back from here). You can publish to multiple backends at the same time.',
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: SUPPORTED_BACKENDS.map((b) => ({
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 'signin'.
1320
+ // Q6: license source — IMPLICIT 'builtin' (no licence; the engine runs
1321
+ // its built-in evaluation licence). Not elicited.
1269
1322
  //
1270
- // We deliberately do NOT elicit this. Three attempts to get Claude
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
- // Resolution: signin is the IMPLICIT default. If acquireLicense
1280
- // can't mint a real user-scoped license, the wizard emits
1281
- // `signin_required` and stops — the agent chains through
1282
- // log10x_signin_start, then re-invokes. Demo and paste remain
1283
- // reachable via the schema's `license_source` arg, but as
1284
- // explicit escape hatches (the agent passes them when the USER
1285
- // says "I just want to play with demo" or "I have a JWT").
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-managed Prometheus — no infra to run; the MCP queries metrics straight from here.',
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: SUPPORTED_BACKENDS.map((b) => {
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, drop airgapped — engine reports to log10x SaaS + your own backend(s).',
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 'signin'. Not elicited. See the
1523
- // matching block in elicitMissingAnswers() above for the rationale.
1524
- // Demo / paste remain accessible as explicit `license_source` args.
1525
- // No explicit defaulting needed — the Zod schema's `.default('signin')`
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 SaaS',
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 **multiple backends in parallel** — for example, to Log10x SaaS for MCP queries AND your existing Datadog for unified dashboards.');
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 SUPPORTED_BACKENDS) {
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
- lines.push('Re-invoke `log10x_advise_install` with `backends: ["<choice>", ...]` — one or more. Examples:');
1647
- lines.push('- `backends: ["log10x"]` — just SaaS, default for first install');
1648
- lines.push('- `backends: ["datadog"]` — your own backend only');
1649
- lines.push('- `backends: ["log10x", "datadog"]` — both, side-by-side');
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. Sign-in is the implicit default
1716
- // (see signin_required mode); demo / paste remain accessible via the
1717
- // explicit `license_source` arg on advise_install.
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, but \`"log10x"\` is the SaaS Prometheus endpoint at 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\` (engine reports to both log10x SaaS AND your own backend(s))`,
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
- if (session.airgapped && session.isDemoLicense) {
1857
- lines.push('> ⚠ Plan emitted with `airgapped: true` and a **demo license** — the engine will detect this combo and downgrade to online mode at startup. The plan below leaves `airgapped` set to true so you can sign in later and the chart will then enforce it for real.');
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
- licenseJwt: session.licenseJwt,
1876
- isDemoLicense: session.isDemoLicense,
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 ?? ['log10x']).map((b) => `\`${b}\``).join(' + ');
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
- if (session.isDemoLicense)
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