Discovery panel: show a human-runnable CLI command, not Assistant plumbing

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 <name>
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.
This commit is contained in:
rcourtman
2026-06-07 22:18:30 +01:00
parent 3e0e150f7e
commit 38b3ec4a3a
3 changed files with 99 additions and 6 deletions
@@ -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<DiscoveryTabProps> = (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<DiscoveryTabProps> = (props) => {
<span>CLI Access</span>
<DiscoveryProvenanceMarker showLabel={false} />
</div>
<CopyableCodeRow
value={d().cli_access}
copiedValue={copiedDiscoveryValue}
onCopy={handleCopyDiscoveryValue}
label="Copy CLI access"
/>
<Show
when={cliCommand()}
fallback={
<p class="text-xs text-muted">
<span class="text-muted">How the Pulse Assistant runs commands here: </span>
{d().cli_access}
</p>
}
>
<CopyableCodeRow
value={cliCommand()!}
copiedValue={copiedDiscoveryValue}
onCopy={handleCopyDiscoveryValue}
label="Copy CLI command"
/>
<p class="mt-1.5 text-[11px] text-muted">
Run from the{' '}
{props.resourceType === 'app-container' ? 'Docker host' : 'Proxmox host'}.
</p>
</Show>
</div>
</Show>
@@ -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 <your-command>.';
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 -- <cmd>')).toBeNull();
expect(deriveCliCommand('agent', 'node1', 'Use pulse_control ...')).toBeNull();
});
it('returns null when the resource id is missing', () => {
expect(deriveCliCommand('system-container', '', 'x')).toBeNull();
});
});
@@ -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 <name> …".
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;
}
}