From 38b3ec4a3a4d979160c1f82e4fcde7ec3832376f Mon Sep 17 00:00:00 2001
From: rcourtman
Date: Sun, 7 Jun 2026 22:18:30 +0100
Subject: [PATCH] Discovery panel: show a human-runnable CLI command, not
Assistant plumbing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The per-guest Discovery panel dropped the raw cli_access string into a
copyable code row — but cli_access is guidance written for the Assistant
('Use pulse_control with target_host ...'), which is meaningless to a
person and reads as a fake command.
Derive a concrete, human-runnable command from the resource coordinates
(deriveCliCommand): 'pct exec 101 -- bash' for an LXC, 'docker exec
bash' for Docker, and the layered 'pct exec 101 -- docker exec
homeassistant bash' for a service in a nested container. Show that
(copyable) with a 'run from the Proxmox/Docker host' hint. For types with
no clean human command (VM, k8s, agent) fall back to the cli_access text,
now clearly labelled as how the Assistant reaches it. The cli_access field
itself (Assistant context) is unchanged.
Unit-tested (deriveCliCommand); type-check + eslint clean.
---
.../src/components/Discovery/DiscoveryTab.tsx | 35 +++++++++++++++---
.../components/Discovery/cliCommand.test.ts | 33 +++++++++++++++++
.../src/components/Discovery/cliCommand.ts | 37 +++++++++++++++++++
3 files changed, 99 insertions(+), 6 deletions(-)
create mode 100644 frontend-modern/src/components/Discovery/cliCommand.test.ts
create mode 100644 frontend-modern/src/components/Discovery/cliCommand.ts
diff --git a/frontend-modern/src/components/Discovery/DiscoveryTab.tsx b/frontend-modern/src/components/Discovery/DiscoveryTab.tsx
index 9a26f106d..24566653e 100644
--- a/frontend-modern/src/components/Discovery/DiscoveryTab.tsx
+++ b/frontend-modern/src/components/Discovery/DiscoveryTab.tsx
@@ -33,6 +33,7 @@ import {
} from '@/utils/resourceAnalysisPresentation';
import { useDiscoveryTabState } from './useDiscoveryTabState';
import { orderFactsByActionability } from './factOrdering';
+import { deriveCliCommand } from './cliCommand';
interface DiscoveryTabProps {
resourceType: ResourceType;
@@ -151,6 +152,14 @@ export const DiscoveryTab: Component = (props) => {
}
return getConfidenceLevel(current.confidence);
});
+ // A concrete, human-runnable command for this workload (pct exec / docker
+ // exec, including the nested-container layer). null when there is no clean
+ // human command for the type, in which case we show the Assistant guidance.
+ const cliCommand = createMemo(() => {
+ const current = discovery();
+ if (!current) return null;
+ return deriveCliCommand(props.resourceType, current.resource_id, current.cli_access);
+ });
const commandSettingsTarget = getDiscoveryCommandSettingsTarget();
const apiAccessSettingsTarget = getDiscoveryApiAccessSettingsTarget();
const showManualRunAction = () => props.showManualRunAction === true;
@@ -824,12 +833,26 @@ export const DiscoveryTab: Component = (props) => {
CLI Access
-
+
+ How the Pulse Assistant runs commands here:
+ {d().cli_access}
+
+ }
+ >
+
+
+ Run from the{' '}
+ {props.resourceType === 'app-container' ? 'Docker host' : 'Proxmox host'}.
+
+
diff --git a/frontend-modern/src/components/Discovery/cliCommand.test.ts b/frontend-modern/src/components/Discovery/cliCommand.test.ts
new file mode 100644
index 000000000..93cbcc444
--- /dev/null
+++ b/frontend-modern/src/components/Discovery/cliCommand.test.ts
@@ -0,0 +1,33 @@
+import { describe, it, expect } from 'vitest';
+import { deriveCliCommand } from './cliCommand';
+
+describe('deriveCliCommand', () => {
+ it('derives pct exec for a native LXC', () => {
+ expect(
+ deriveCliCommand('system-container', '101', 'Use pulse_control ... directly inside the container.'),
+ ).toBe('pct exec 101 -- bash');
+ });
+
+ it('layers docker exec for a service in a nested container (HA-in-LXC)', () => {
+ const cliAccess =
+ 'Use pulse_control ... The service runs inside a Docker container named "homeassistant" ' +
+ '— prefix commands with: docker exec homeassistant .';
+ expect(deriveCliCommand('system-container', '101', cliAccess)).toBe(
+ 'pct exec 101 -- docker exec homeassistant bash',
+ );
+ });
+
+ it('uses docker exec for a Docker container', () => {
+ expect(deriveCliCommand('app-container', 'redis', undefined)).toBe('docker exec redis bash');
+ });
+
+ it('returns null for types without a clean human command (VM, k8s, agent)', () => {
+ expect(deriveCliCommand('vm', '200', 'Use pulse_control ... inside the VM.')).toBeNull();
+ expect(deriveCliCommand('pod', 'web-0', 'Use kubectl exec -n default web-0 -- ')).toBeNull();
+ expect(deriveCliCommand('agent', 'node1', 'Use pulse_control ...')).toBeNull();
+ });
+
+ it('returns null when the resource id is missing', () => {
+ expect(deriveCliCommand('system-container', '', 'x')).toBeNull();
+ });
+});
diff --git a/frontend-modern/src/components/Discovery/cliCommand.ts b/frontend-modern/src/components/Discovery/cliCommand.ts
new file mode 100644
index 000000000..cd67c8ad0
--- /dev/null
+++ b/frontend-modern/src/components/Discovery/cliCommand.ts
@@ -0,0 +1,37 @@
+import type { ResourceType } from '../../types/discovery';
+
+/**
+ * Derives a concrete, human-runnable command for reaching a workload from its
+ * resource coordinates.
+ *
+ * The discovery `cli_access` field is guidance written for the Pulse Assistant
+ * ("Use pulse_control with target_host …") — it is not something a person types.
+ * This returns what a human would actually run (e.g. `pct exec 101 -- bash`),
+ * including the nested-container layer when the service runs in Docker inside an
+ * LXC/VM. Returns `null` when there is no clean human command for the type (the
+ * caller then falls back to showing the guidance text, e.g. k8s kubectl which is
+ * already human-readable, or VMs where SSH/credentials are the real path).
+ */
+export function deriveCliCommand(
+ resourceType: ResourceType,
+ resourceId: string,
+ cliAccess: string | undefined,
+): string | null {
+ const id = (resourceId || '').trim();
+ if (!id) return null;
+
+ // A service running in a nested Docker container changes the access path; the
+ // backend records the container in cli_access as "docker exec …".
+ const nested = /docker exec (\S+)/.exec(cliAccess || '')?.[1];
+
+ switch (resourceType) {
+ case 'system-container':
+ return nested ? `pct exec ${id} -- docker exec ${nested} bash` : `pct exec ${id} -- bash`;
+ case 'app-container':
+ return `docker exec ${id} bash`;
+ default:
+ // vm (qm guest exec is non-interactive; SSH is the real path), pod (the
+ // guidance already shows a runnable kubectl exec), agent, etc.
+ return null;
+ }
+}