{"id":"137c9b20-905c-43be-8364-5c3a4a6348f9","entityType":"agent","slug":"clawhub-olares-olares-dashboard","name":"Olares Dashboard (olares-cli dashboard)","canonicalUrl":"https://www.xpersona.co/agent/clawhub-olares-olares-dashboard","canonicalPath":"/agent/clawhub-olares-olares-dashboard","generatedAt":"2026-10-14T12:13:49.559Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T21:43:38.829Z","emptyReason":null},"description":"Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olare... Skill: Olares Dashboard (olares-cli dashboard) Owner: olares Summary: Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olare... Tags: latest:4.0.1 Version history: v4.0.1 | 2026-05-29T05:54:52.953Z | user Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0) v4.0.0 | 2026-05-29T04:53:57.489Z | use","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 989 downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-dashboard","sourceUrl":"https://clawhub.ai/olares/olares-dashboard","homepage":"https://clawhub.ai/olares/skills/olares-dashboard","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/olares/olares-dashboard","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/olares/skills/olares-dashboard","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olare..."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T21:43:38.829Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T21:43:38.829Z","emptyReason":null},"stars":null,"forks":null,"downloads":989,"likes":null,"task":null,"library":null,"packageName":null,"latestVersion":"4.0.1","tractionLabel":"989 downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T21:43:38.815Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T21:43:38.829Z","lastCrawledAt":"2026-10-11T21:43:38.815Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T21:43:38.815Z","lastVerifiedAt":null,"highlights":[{"version":"4.0.1","createdAt":"2026-05-29T05:54:52.953Z","changelog":"Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0)","fileCount":5,"zipByteSize":12400},{"version":"4.0.0","createdAt":"2026-05-29T04:53:57.489Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":5,"zipByteSize":12397},{"version":"2.1.0","createdAt":"2026-05-28T09:28:15.523Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":3,"zipByteSize":31280},{"version":"2.0.0","createdAt":"2026-04-30T13:23:35.497Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":2,"zipByteSize":24765}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-dashboard","setupComplexity":"low","setupSteps":["Setup complexity is LOW. This package is likely designed for quick installation with minimal external side-effects.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-14T12:13:49.558Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-dashboard/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T21:43:38.829Z","emptyReason":null},"readme":"Skill: Olares Dashboard (olares-cli dashboard)\n\nOwner: olares\n\nSummary: Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olare...\n\nTags: latest:4.0.1\n\nVersion history:\n\nv4.0.1 | 2026-05-29T05:54:52.953Z | user\n\nMigrate ownership from @pengpeng to @olares (no content change vs 4.0.0)\n\nv4.0.0 | 2026-05-29T04:53:57.489Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nv2.1.0 | 2026-05-28T09:28:15.523Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nv2.0.0 | 2026-04-30T13:23:35.497Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nArchive index:\n\nArchive v4.0.1: 5 files, 12400 bytes\n\nFiles: references/olares-dashboard-overview.md (6418b), references/olares-dashboard-watch.md (6631b), skill-card.md (2362b), SKILL.md (12162b), _meta.json (135b)\n\nFile v4.0.1:SKILL.md\n\n---\r\nname: olares-dashboard\r\nversion: 4.0.1\r\ndescription: \"Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olares ID. An AI-agent-first JSON mirror with a strict dual-shape envelope (leaf items / aggregated sections), stable Kind constants, and human-readable table output. Covers Overview leaves (CPU, memory, disk, network, pods, fan, GPU, ranking), Applications listing and detail, the --watch HTTP-polling loop (interval / iterations / timeout / NDJSON-per-iteration / SIGINT-graceful), --since (sliding window) vs --start/--end (fixed window) selection, three-state empty-data semantics (no_<feature>_integration / no_<feature>_detected / vgpu_unavailable), and capability gates (fan is hard-gated to Olares One hardware; GPU is a soft advisory mirroring the SPA sidebar). Use when the user mentions Olares, Olares ID, Olares Dashboard, Olares One (fan / cooling), olares-cli dashboard, the Olares overview / applications view, 'show CPU / memory / disk / pods / network / fan / GPU / ranking on Olares', wants JSON for an AI agent, or hits errors like 'fan is only available on Olares One devices' or 'gpu data temporarily unavailable'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli dashboard --help\"\r\n---\r\n\r\n# dashboard (overview + applications, AI-agent first)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, automatic token refresh, and the auth-error recovery table.**\r\n\r\n> **Source of truth for flags is always `olares-cli dashboard --help` (global flags) and `olares-cli dashboard <verb> --help` (per-leaf flags).** This file only carries what `--help` cannot give: the dual-shape JSON envelope contract, three-state empty-data semantics, capability gates, watch / window rules, and the verb index.\r\n\r\n## Routing\r\n\r\nThis subtree is an **AI-agent-first JSON mirror of the Olares Dashboard SPA's Overview and Applications routes**. Use it when:\r\n\r\n- The user asks for runtime metrics: CPU / memory / disk / pods / network / fan / GPU.\r\n- The user wants the workload-grain or application-grain resource ranking.\r\n- The user wants the JSON form of what the SPA Overview / Applications pages show.\r\n- The user wants `--watch` for live-tailing one of the above.\r\n\r\nSibling-skill routing:\r\n\r\n| User intent | Use instead |\r\n|---|---|\r\n| \"What pods / workloads / nodes / namespaces exist?\" (object inventory) | [`olares-cluster`](../olares-cluster/SKILL.md) |\r\n| \"Install / start / stop an Olares app\" | [`olares-market`](../olares-market/SKILL.md) |\r\n| \"Configure my user settings\" | [`olares-settings`](../olares-settings/SKILL.md) |\r\n| \"Browse files / drives\" | [`olares-files`](../olares-files/SKILL.md) |\r\n\r\n> **Mental model:** dashboard answers *\"what's the resource usage and health\"*. For inventory and lifecycle, route elsewhere.\r\n\r\n## JSON envelope (two shapes, frozen)\r\n\r\nEvery command emits exactly one of two shapes. The choice is fixed per command. **Agents pin on `kind` and `meta.empty_reason`; the CLI does not change these values across releases.**\r\n\r\n### Shape A — leaf items\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.<area>.<verb>\",\r\n  \"meta\": {\r\n    \"fetched_at\": \"...\",\r\n    \"iteration\": 0,\r\n    \"recommended_poll_seconds\": 60,\r\n    \"empty\": false,\r\n    \"empty_reason\": \"\",\r\n    \"error\": \"\",\r\n    \"http_status\": 200\r\n  },\r\n  \"items\": [\r\n    { \"raw\": { /* upstream wire shape */ }, \"display\": { /* table-friendly strings */ } }\r\n  ]\r\n}\r\n```\r\n\r\n- `raw` is the canonical machine-friendly shape — numbers as numbers, timestamps as Unix seconds, temperatures as raw Celsius. **Agents pin on `raw`.**\r\n- `display` is human-presentation only, formatted with current `--temp-unit` / `--timezone`. **Agents MUST NOT pin on `display`** — it can change with locale or format fixes.\r\n- `meta.recommended_poll_seconds` — the page-level cadence the SPA uses; respect it when driving `--watch`.\r\n- `meta.iteration` — 1-based, present in every `--watch` payload.\r\n\r\n### Shape B — sections envelope\r\n\r\nUsed by **parent commands that aggregate multiple sub-views**:\r\n\r\n| Parent command | Sections |\r\n|---|---|\r\n| `dashboard overview` | `physical` / `user` / `ranking` |\r\n| `dashboard overview disk` | `main` / `partitions` |\r\n| `dashboard overview fan` | `live` / `curve` |\r\n| `dashboard overview gpu` | `graphics` / `tasks` |\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview\",\r\n  \"meta\": {...},\r\n  \"sections\": {\r\n    \"physical\": { \"kind\": \"dashboard.overview.physical\", \"meta\": {...}, \"items\": [...] },\r\n    \"user\":     { \"kind\": \"dashboard.overview.user\",     \"meta\": {...}, \"items\": [...] },\r\n    \"ranking\":  { \"kind\": \"dashboard.overview.ranking\",  \"meta\": {...}, \"items\": [...] }\r\n  }\r\n}\r\n```\r\n\r\nSections are fetched **concurrently**. A single failed section degrades to `meta.error=\"...\"` on that section — **the others still return**. Surface partial outputs to the user, don't blackout the whole envelope.\r\n\r\nTo enumerate every kind live: `olares-cli dashboard schema -o json`.\r\n\r\n## Three-state empty data (`meta.empty_reason`)\r\n\r\nOptional hardware (GPU / fan) and optional integrations have three legitimate \"empty\" states. The CLI distinguishes them so agents can branch without parsing prose.\r\n\r\n| Upstream | `meta.empty` | `meta.empty_reason` | Meaning |\r\n|---|---|---|---|\r\n| HTTP 404 | `true` | `no_<feature>_integration` | Integration absent (e.g. HAMI vGPU not installed) |\r\n| HTTP 200, empty body | `true` | `no_<feature>_detected` | Integration present but hardware empty |\r\n| HTTP 200, non-empty | `false` | `\"\"` | Normal — items[] populated |\r\n| Any 4xx / 5xx | n/a (envelope `meta.error`) | n/a | Real failure |\r\n\r\n**Forbidden**: turning a 404 into an error, or merging the two reasons into a single \"empty=true\" string. Agents currently key on the reason.\r\n\r\nSpecific reason values:\r\n\r\n| Reason | Where |\r\n|---|---|\r\n| `not_olares_one` | fan default / live / curve (active device's `device_name` is not `Olares One`) |\r\n| `no_fan_integration` | fan live (HTTP 404 fallback) |\r\n| `no_vgpu_integration` | gpu list / tasks / get / task (HTTP 404) |\r\n| `vgpu_unavailable` | gpu list / tasks / get / task (HTTP 5xx); `meta.error` carries the upstream message, `meta.http_status` the original status |\r\n| `no_gpu_detected` | gpu list / tasks / get / task (HTTP 200, empty body) |\r\n\r\n## Capability gates\r\n\r\nThe fan / GPU subtrees mirror SPA gates, but with **two different strengths**.\r\n\r\n### Hard gate — fan (Olares One only)\r\n\r\n`overview fan *` returns an empty envelope BEFORE any fetch on non-Olares-One hardware:\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview.fan.live\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"not_olares_one\",\r\n    \"note\": \"Fan / cooling integration is only available on Olares One devices\",\r\n    \"device_name\": \"DIY-PC\"\r\n  },\r\n  \"items\": []\r\n}\r\n```\r\n\r\nExit code is still `0` — this is a predictable state, not a failure.\r\n\r\n### Soft gate — GPU (advisory)\r\n\r\nThe CLI ALWAYS calls HAMI for GPU verbs. The SPA-equivalent \"would-have-been-hidden\" reason is recorded as `meta.note` and (in table mode) as a one-liner on stderr. The actual `meta.empty` / `empty_reason` are decided by HAMI's response.\r\n\r\nAdvisories you may see in `meta.note`:\r\n\r\n- `GPU sidebar entry is hidden for non-admin profiles in the SPA; HAMI was queried directly`\r\n- `no node carries gpu.bytetrade.io/cuda-supported=true; SPA hides the GPU card. HAMI was queried directly`\r\n\r\nWhen BOTH a HAMI failure AND a soft advisory apply, `meta.note` is `\"<advisory> | <hami-explanation>\"`.\r\n\r\n## Agent decision tree (recommended)\r\n\r\n```\r\ninspect meta.empty + meta.empty_reason + meta.note →\r\n  not_olares_one        → skip the fan subtree on this device\r\n  no_*_integration      → upstream component absent (HAMI / capi system fan)\r\n  vgpu_unavailable      → transient: retry; check meta.http_status / meta.error\r\n  no_*_detected         → integration up but hardware empty\r\n  (none) + meta.note    → data is present but the SPA would have hidden the entry; surface the note to the user\r\n  (none) + (no note)    → items[] populated, proceed normally\r\n```\r\n\r\n## Verb index\r\n\r\nFor flags & examples, **always start with `olares-cli dashboard <verb> --help`**.\r\n\r\n| Verb | Purpose | `--help` first, then... |\r\n|---|---|---|\r\n| `applications` (alias `apps`) | Workload-grain application table (mirrors SPA Applications page) | `olares-cli dashboard applications --help` |\r\n| `overview` (no subverb) | Default sections envelope (physical + user + ranking) | [references/olares-dashboard-overview.md](references/olares-dashboard-overview.md) |\r\n| `overview <section>` | Per-section snapshot (10 sub-verbs: cpu / memory / disk / pods / network / fan / gpu / physical / user / ranking) | [references/olares-dashboard-overview.md](references/olares-dashboard-overview.md) |\r\n| `schema` | Introspect the JSON Schemas served by `olares-cli dashboard` | `olares-cli dashboard schema --help` |\r\n\r\nFor `--watch`, `--since` / `--start/--end`, `--user`, `--timezone`, and the NDJSON contract, see [references/olares-dashboard-watch.md](references/olares-dashboard-watch.md).\r\n\r\n## Global flags (cross-cutting)\r\n\r\nThese are bound on the `dashboard` root and inherited by every leaf. **Defaults are sensible — don't pass them unless the user explicitly asks for the non-default.**\r\n\r\n| Flag | Purpose |\r\n|---|---|\r\n| `-o, --output table\\|json` | Output format (default `table`). JSON is the agent-facing form |\r\n| `--head N` | Truncate to the first N rows after sorting (0 = no truncation) |\r\n| `--limit N` / `--page N` | Pagination knobs (0 = upstream defaults) |\r\n| `--since D` | Relative window (e.g. `5m`, `1h`). **Sliding when `--watch`** |\r\n| `--start <RFC3339>` / `--end <RFC3339>` | Absolute window. **Fixed across iterations when `--watch`**. Mutually exclusive with `--since` |\r\n| `--temp-unit C\\|F\\|K` | Display unit for temperature (JSON `raw` always Celsius) |\r\n| `--timezone <IANA>` | Display TZ for table rendering (does NOT affect wire-format TZ) |\r\n| `--user <olaresId>` | Target a different user than the active profile — **platform-admin only**, returns 403 otherwise |\r\n| `--watch` | Enable HTTP-polling loop (NDJSON per iteration in JSON mode) |\r\n| `--watch-interval D` / `--watch-iterations N` / `--watch-timeout D` | Watch-loop knobs (require `--watch`; rejected otherwise) |\r\n\r\n## Exit codes & error semantics\r\n\r\n- **Exit `0`** for every gated / advisory / empty path — these are predictable states, not failures.\r\n- **Exit non-zero** only on:\r\n  - Auth-class errors (`ErrTokenInvalidated` / `ErrNotLoggedIn`) — propagated immediately\r\n  - 3 consecutive iteration failures inside a `--watch` loop\r\n  - A real upstream `meta.error` on a one-shot invocation\r\n- **Stderr** carries one human-readable line in non-JSON modes. JSON / NDJSON modes stay silent on stderr — agents read stdout exclusively.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `fan is only available on Olares One devices (current: <device_name>)` (stderr) | Hard gate; `meta.empty_reason=not_olares_one` | Stop probing fan on this device |\r\n| `(advisory) GPU sidebar entry is hidden for non-admin profiles ...` (stderr) | Soft gate; data still returned | Surface to user as a note; don't treat as error |\r\n| `gpu data temporarily unavailable: HAMI returned HTTP 500` | `vgpu_unavailable`; transient | Retry; if persistent, file a server-side issue |\r\n| `--user requires platform-admin role` | Non-admin profile passing `--user` | Use the active profile, or switch with `olares-cli profile use` |\r\n| `--watch-iterations requires --watch` (or `--interval` / `--timeout` similarly) | Polling knob without gate flag | Add `--watch` or drop the knob |\r\n| `--since and --start/--end are mutually exclusive` | Both window forms set | Pick one |\r\n| 401/403 from any dashboard verb | Token rotation / invalidation | See [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) |\r\n\r\nFor the full auth-error matrix see [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md).\n\nFile v4.0.1:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-dashboard\",\n  \"version\": \"4.0.1\",\n  \"publishedAt\": 1780034092953\n}\n\nFile v4.0.1:references/olares-dashboard-overview.md\n\n# dashboard overview\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shapes:** `olares-cli dashboard overview --help` and `olares-cli dashboard overview <section> --help`.\r\n\r\nThe most flag-rich verb in this subtree. Three \"framing\" sections (`physical` / `user` / `ranking`) live at the top of the overview tree; seven \"hardware\" sections (`cpu` / `memory` / `disk` / `pods` / `network` / `fan` / `gpu`) live alongside.\r\n\r\n## Default action (no subverb)\r\n\r\n```bash\r\nolares-cli dashboard overview\r\nolares-cli dashboard overview -o json\r\n```\r\n\r\nEmits a **Shape B sections envelope** with the three \"framing\" sections fetched concurrently:\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview\",\r\n  \"sections\": {\r\n    \"physical\": { \"kind\": \"dashboard.overview.physical\", \"items\": [...] },\r\n    \"user\":     { \"kind\": \"dashboard.overview.user\",     \"items\": [...] },\r\n    \"ranking\":  { \"kind\": \"dashboard.overview.ranking\",  \"items\": [...] }\r\n  }\r\n}\r\n```\r\n\r\nFor per-hardware sections (CPU / disk / fan / GPU / etc.), invoke them explicitly — they are NOT part of the default envelope.\r\n\r\n## 10-section matrix\r\n\r\n| Section | Kind | When to use |\r\n|---|---|---|\r\n| `physical` | `dashboard.overview.physical` | 9-row cluster-level snapshot (CPU/Memory/Disk/Pods/Net + extras). The \"single screen vital signs\" view |\r\n| `user` | `dashboard.overview.user` | Per-user CPU / memory quota usage. Accepts optional `<username>` positional — **admin-only** for non-self lookups. Mirrors SPA's User Resources panel |\r\n| `ranking` | `dashboard.overview.ranking` | Workload-grain resource ranking (per-application). `--sort asc|desc` + `--head N` for top-N. Mirrors SPA's UsageRanking widget |\r\n| `cpu` | `dashboard.overview.cpu` | Per-node CPU details (model / freq / cores / utilisation breakdown / temp / load avg) |\r\n| `memory` | `dashboard.overview.memory` | Per-node memory breakdown. `--mode physical|swap` (default `physical`) |\r\n| `disk` | `dashboard.overview.disk` (Shape B) | **Sections envelope**: `main` (per-disk table) + `partitions` (per-device partition tables). `disk main` and `disk partitions <device>` give individual views |\r\n| `pods` | `dashboard.overview.pods` | Per-node pod count snapshot (last/avg/max running) |\r\n| `network` | `dashboard.overview.network` | Per-physical-NIC table. `--test-connectivity` (default true) probes internet/IPv6 per interface |\r\n| `fan` | `dashboard.overview.fan` (Shape B) | **Olares One only.** Sections envelope: `live` (real-time fan/temp/power) + `curve` (hardcoded fan-curve spec). Hard-gated on non-Olares-One: `meta.empty_reason=not_olares_one` |\r\n| `gpu` | `dashboard.overview.gpu` (Shape B) | Sections envelope: `graphics` (GPU list) + `tasks` (vGPU tasks). **Soft-gated**; `meta.note` advises when the SPA would have hidden the entry. See parent SKILL.md for the soft-gate decision tree |\r\n\r\n## Drilling into a section\r\n\r\n```bash\r\n# Per-section snapshots.\r\nolares-cli dashboard overview cpu -o json\r\nolares-cli dashboard overview memory --mode swap\r\nolares-cli dashboard overview network --test-connectivity=false\r\n\r\n# Sections envelope (disk has its own Shape B):\r\nolares-cli dashboard overview disk -o json                    # both sections\r\nolares-cli dashboard overview disk main                       # just per-disk table\r\nolares-cli dashboard overview disk partitions sda             # per-device partitions\r\n\r\n# Fan (Olares One only; otherwise empty envelope with reason=not_olares_one):\r\nolares-cli dashboard overview fan -o json\r\nolares-cli dashboard overview fan live\r\nolares-cli dashboard overview fan curve\r\n\r\n# GPU (soft-gated):\r\nolares-cli dashboard overview gpu                             # graphics + tasks sections\r\nolares-cli dashboard overview gpu graphics                    # just GPU list\r\nolares-cli dashboard overview gpu graphics <uuid>             # per-GPU detail page (gauges + trends)\r\nolares-cli dashboard overview gpu tasks                       # task list\r\nolares-cli dashboard overview gpu tasks <name-or-pod-uid>     # per-task detail page\r\n\r\n# Ranking (top-N apps by CPU).\r\nolares-cli dashboard overview ranking --sort desc --head 5\r\n```\r\n\r\n## GPU detail pages\r\n\r\n`gpu graphics <uuid>` and `gpu tasks <name-or-pod-uid>` are **full SPA-mirror detail pages** — they assemble a sections envelope with `detail` / `gauges` / `trends`:\r\n\r\n- `detail` — HAMI metadata\r\n- `gauges` — instant-vector queries (current values)\r\n- `trends` — range-vector queries (default 8h for GPU, 1h for task). Override with `--since` / `--start` / `--end`\r\n\r\nPartial-failure semantics: a single gauge / trend item failing does NOT abort the envelope; it carries `raw.error` and contributes to `meta.warnings`. **Branch on `len(meta.warnings) > 0` to detect partial data.**\r\n\r\nFor `gpu tasks <ref>`, `<ref>` accepts EITHER the TASK column (`name`) OR the POD_UID column (`podUid`) — copy-paste from either column works. If two tasks share a name, the CLI errors with the candidate pod-uids; re-run with one.\r\n\r\n## Agent notes\r\n\r\n- For \"give me a single-screen vital-signs view\", **`olares-cli dashboard overview -o json` is the right verb** (Shape B envelope with physical + user + ranking).\r\n- For \"tail CPU / memory live\", **add `--watch -o json`** to a per-section command. The output is NDJSON, one envelope per iteration. See [olares-dashboard-watch.md](olares-dashboard-watch.md).\r\n- For \"what apps are using the most CPU\", **`overview ranking --sort desc --head 10`** is the right entry point — it returns a workload-grain ranking with both `raw` (machine-friendly) and `display` (human-friendly) columns.\r\n- For `--user <other>` cross-tenant queries, **only platform-admins succeed**; non-admins get an immediate error. Run `cluster context` first if you need to confirm the active role.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `not_olares_one` empty envelope on `fan *` | Hard gate | Don't ask for fan data on this device |\r\n| `(advisory) ...` line on stderr from `gpu *` | Soft gate; data still returned | Surface the note to the user as informational |\r\n| `--user requires platform-admin role` | Non-admin tried `--user <other>` | Use the active profile only |\r\n| 404 / 5xx on `overview gpu *` | HAMI absent or unhealthy (`no_vgpu_integration` / `vgpu_unavailable`) | Branch on `meta.empty_reason` |\n\nFile v4.0.1:references/olares-dashboard-watch.md\n\n# dashboard --watch / windows / NDJSON contract\r\n\r\n> **Prerequisite:** Read the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags:** `olares-cli dashboard --help` (every flag here is a `dashboard`-root global flag inherited by every leaf).\r\n\r\n`--watch` opts every leaf verb into an HTTP-polling loop that mirrors the SPA's `setTimeout` cadence. **Polling, not streaming** — every iteration is a fresh HTTP request, no chunked transfer encoding.\r\n\r\n## `--watch` behaviour\r\n\r\n```\r\ninvoke leaf cmd\r\n   │\r\n   ▼\r\n--watch set? ── no ──> single execution → emit one envelope → exit 0/1\r\n   │\r\n   yes\r\n   ▼\r\nRunner.Run loop\r\n   │\r\n   ▼\r\nRunOnce iteration N\r\n   │\r\n   ├── success         → emit envelope (Shape A or B) with meta.iteration=N\r\n   │                       JSON mode → NDJSON line on stdout\r\n   │                       table mode → clear-screen redraw\r\n   │\r\n   ├── transient err   → emit envelope with meta.error=\"...\", items:[]\r\n   │                       continue\r\n   │\r\n   └── ErrTokenInvalidated / ErrNotLoggedIn\r\n                       → exit non-zero IMMEDIATELY\r\n   ▼\r\n3 consecutive errors? ──> exit non-zero\r\n   │\r\n   no\r\n   ▼\r\n--watch-iterations cap reached? ──> exit 0\r\n   │\r\n   no\r\n   ▼\r\nSIGINT (Ctrl-C) ──> exit 0 gracefully\r\n   │\r\n   no\r\n   ▼\r\nwait --watch-interval, loop back\r\n```\r\n\r\n## NDJSON contract (JSON mode)\r\n\r\n- **One envelope per iteration; nothing else on stdout.** Pretty `-o json` is for one-shot mode; `--watch -o json` is **always NDJSON** (line-delimited, no leading newline, no trailing array bracket).\r\n- A failed iteration emits `items: []` + `meta.error: \"<msg>\"` — **NDJSON keeps streaming**, the failure does not raise to process exit unless it's auth-class or the 3-consecutive cap trips.\r\n- `meta.iteration` is **1-based** and present in every payload.\r\n- Each envelope's `meta.fetched_at` reflects that iteration's HTTP-request start time.\r\n\r\n```jsonc\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 1, ... }, \"items\": [...] }\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 2, \"error\": \"...\", ... }, \"items\": [] }\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 3, ... }, \"items\": [...] }\r\n```\r\n\r\n## Watch loop knobs\r\n\r\n| Flag | Purpose | Default |\r\n|---|---|---|\r\n| `--watch` | Enable the loop | off |\r\n| `--watch-interval D` | Time between iterations | command's `recommended_poll_seconds` (e.g. 30s for GPU, 60s for most others) |\r\n| `--watch-iterations N` | Stop after N iterations | unbounded |\r\n| `--watch-timeout D` | Stop after this much wall-clock time | unbounded |\r\n\r\n**All three `--watch-*` knobs are REJECTED without `--watch`** — don't silently waste a flag.\r\n\r\n## Window semantics — `--since` vs `--start/--end`\r\n\r\nMutually exclusive. Pick one.\r\n\r\n| Form | Semantics | Behaviour under `--watch` |\r\n|---|---|---|\r\n| `--since 5m` | Window = `[now - 5m, now]` | **Sliding** — each iteration recomputes `now` |\r\n| `--start <RFC3339> --end <RFC3339>` | Window = `[start, end]` | **Fixed** — same window every iteration |\r\n\r\nIf neither is set, each leaf has its own SPA-derived default (e.g. 8h for GPU detail page, 1h for GPU task detail page, no window for cluster-level instant snapshots).\r\n\r\nThe chosen window appears in `meta.window` so an agent can replay the exact same query without recomputing:\r\n\r\n```json\r\n\"meta\": {\r\n  \"window\": { \"since\": \"8h\", \"start\": \"...\", \"end\": \"...\", \"step\": \"30m\" }\r\n}\r\n```\r\n\r\n## Examples\r\n\r\n```bash\r\n# Tail CPU every 30s as NDJSON until Ctrl-C.\r\nolares-cli dashboard overview cpu --watch -o json --watch-interval 30s\r\n\r\n# Tail CPU but stop after 10 iterations.\r\nolares-cli dashboard overview cpu --watch -o json --watch-iterations 10\r\n\r\n# Watch a GPU detail page for 5 minutes with a sliding 15m window.\r\nolares-cli dashboard overview gpu graphics <uuid> --watch -o json --since 15m --watch-timeout 5m\r\n\r\n# Fixed window across all iterations (useful for retrospective tailing).\r\nolares-cli dashboard overview ranking --watch -o json \\\r\n  --start 2026-05-28T00:00:00Z --end 2026-05-28T01:00:00Z\r\n```\r\n\r\n## SIGINT / graceful shutdown\r\n\r\n- Ctrl-C (SIGINT) and SIGTERM trigger a graceful stop — the current iteration is allowed to finish, then the loop exits with `0`.\r\n- Inside a `for line in cmd.stdout` loop, the parent agent process sees the stream end cleanly (no half-line, no half-envelope).\r\n\r\n## `--user <olaresId>` interaction\r\n\r\n- `--user` only changes upstream filter args. The auth header is still the active profile's token.\r\n- **Platform-admin only.** Non-admin callers get an immediate error; the loop never starts.\r\n- The admin check is cached per-Client (`sync.Once`) — there is zero per-iteration auth-check overhead.\r\n\r\n## `--timezone <IANA>` interaction\r\n\r\n- Affects DISPLAY only — `meta.window` / table-side timestamps / header rendering.\r\n- The wire format sent to HAMI's monitor query endpoints stays in the backend pod's TZ (default `Asia/Shanghai`) so trends resolve correctly even when `--timezone` differs.\r\n- Override the backend TZ with `OLARES_HAMI_BACKEND_TZ=<IANA>` env if your HAMI deployment is in a different zone (hidden from `--help`; only operators of non-default HAMI deployments need it).\r\n\r\n## Agent best practices\r\n\r\n- **Use `--watch -o json` for live tailing.** Always NDJSON, always one envelope per line.\r\n- **Respect `meta.recommended_poll_seconds`.** It mirrors the SPA's polling cadence; overriding with a much shorter `--watch-interval` adds upstream load.\r\n- **Handle the empty-envelope iterations.** `meta.error` is normal during transient upstream issues; do NOT abort the consumer loop unless 3 consecutive errors trip the safety net (the CLI exits non-zero in that case anyway).\r\n- **Stop polling on `meta.empty_reason == not_olares_one`.** It's a hard gate that won't change without a hardware change.\r\n- **For `vgpu_unavailable`**, the CLI keeps streaming — your agent should keep iterating too. Real failures still surface via process exit on auth-class errors or 3 consecutive iteration failures.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `--watch-iterations requires --watch` | Knob without gate | Add `--watch` or drop the knob |\r\n| `--since and --start/--end are mutually exclusive` | Both window forms set | Pick one |\r\n| Process exits non-zero immediately on the first iteration | `ErrNotLoggedIn` / `ErrTokenInvalidated` | Run `olares-cli profile login` and retry |\r\n| Process exits non-zero after a few iterations | 3 consecutive transient errors | Investigate upstream (HAMI / capi); restart the loop |\n\nFile v4.0.1:skill-card.md\n\n## Description:\n\nOlares Dashboard helps agents query Olares Dashboard Overview and Applications data through olares-cli dashboard, including resource metrics, application rankings, JSON envelopes, and watch-mode output.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[olares](https://clawhub.ai/user/olares)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and operators use this skill to inspect Olares dashboard metrics and application resource usage from an active olares-cli profile. It guides agents toward the correct dashboard commands, JSON output shapes, watch behavior, and empty-data semantics for CPU, memory, disk, pods, network, fan, GPU, ranking, and applications views.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can guide an agent to query dashboard metrics through the active olares-cli profile, including admin-scoped views when --user targets another Olares ID.\n\nMitigation: Use the active profile intentionally, reserve --user for deliberate platform-admin visibility, and review profile scope before querying sensitive dashboard data.\n\nRisk: Large result sets or live polling can add load or produce more output than needed.\n\nMitigation: Use output caps such as --head, --limit, --watch-iterations, and --watch-timeout, and respect the recommended polling cadence.\n\n## Reference(s):\n\n- [dashboard overview](references/olares-dashboard-overview.md)\n- [dashboard --watch / windows / NDJSON contract](references/olares-dashboard-watch.md)\n- [Olares Dashboard ClawHub skill page](https://clawhub.ai/olares/skills/olares-dashboard)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with inline shell commands and JSON shape descriptions]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Agent-facing guidance centers on olares-cli dashboard output, including table, JSON, and NDJSON watch-mode behavior.]\n\n## Skill Version(s):\n\n4.0.1 (source: frontmatter and server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v4.0.0: 5 files, 12397 bytes\n\nFiles: references/olares-dashboard-overview.md (6418b), references/olares-dashboard-watch.md (6631b), skill-card.md (2399b), SKILL.md (12162b), _meta.json (135b)\n\nFile v4.0.0:SKILL.md\n\n---\r\nname: olares-dashboard\r\nversion: 4.0.0\r\ndescription: \"Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olares ID. An AI-agent-first JSON mirror with a strict dual-shape envelope (leaf items / aggregated sections), stable Kind constants, and human-readable table output. Covers Overview leaves (CPU, memory, disk, network, pods, fan, GPU, ranking), Applications listing and detail, the --watch HTTP-polling loop (interval / iterations / timeout / NDJSON-per-iteration / SIGINT-graceful), --since (sliding window) vs --start/--end (fixed window) selection, three-state empty-data semantics (no_<feature>_integration / no_<feature>_detected / vgpu_unavailable), and capability gates (fan is hard-gated to Olares One hardware; GPU is a soft advisory mirroring the SPA sidebar). Use when the user mentions Olares, Olares ID, Olares Dashboard, Olares One (fan / cooling), olares-cli dashboard, the Olares overview / applications view, 'show CPU / memory / disk / pods / network / fan / GPU / ranking on Olares', wants JSON for an AI agent, or hits errors like 'fan is only available on Olares One devices' or 'gpu data temporarily unavailable'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli dashboard --help\"\r\n---\r\n\r\n# dashboard (overview + applications, AI-agent first)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, automatic token refresh, and the auth-error recovery table.**\r\n\r\n> **Source of truth for flags is always `olares-cli dashboard --help` (global flags) and `olares-cli dashboard <verb> --help` (per-leaf flags).** This file only carries what `--help` cannot give: the dual-shape JSON envelope contract, three-state empty-data semantics, capability gates, watch / window rules, and the verb index.\r\n\r\n## Routing\r\n\r\nThis subtree is an **AI-agent-first JSON mirror of the Olares Dashboard SPA's Overview and Applications routes**. Use it when:\r\n\r\n- The user asks for runtime metrics: CPU / memory / disk / pods / network / fan / GPU.\r\n- The user wants the workload-grain or application-grain resource ranking.\r\n- The user wants the JSON form of what the SPA Overview / Applications pages show.\r\n- The user wants `--watch` for live-tailing one of the above.\r\n\r\nSibling-skill routing:\r\n\r\n| User intent | Use instead |\r\n|---|---|\r\n| \"What pods / workloads / nodes / namespaces exist?\" (object inventory) | [`olares-cluster`](../olares-cluster/SKILL.md) |\r\n| \"Install / start / stop an Olares app\" | [`olares-market`](../olares-market/SKILL.md) |\r\n| \"Configure my user settings\" | [`olares-settings`](../olares-settings/SKILL.md) |\r\n| \"Browse files / drives\" | [`olares-files`](../olares-files/SKILL.md) |\r\n\r\n> **Mental model:** dashboard answers *\"what's the resource usage and health\"*. For inventory and lifecycle, route elsewhere.\r\n\r\n## JSON envelope (two shapes, frozen)\r\n\r\nEvery command emits exactly one of two shapes. The choice is fixed per command. **Agents pin on `kind` and `meta.empty_reason`; the CLI does not change these values across releases.**\r\n\r\n### Shape A — leaf items\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.<area>.<verb>\",\r\n  \"meta\": {\r\n    \"fetched_at\": \"...\",\r\n    \"iteration\": 0,\r\n    \"recommended_poll_seconds\": 60,\r\n    \"empty\": false,\r\n    \"empty_reason\": \"\",\r\n    \"error\": \"\",\r\n    \"http_status\": 200\r\n  },\r\n  \"items\": [\r\n    { \"raw\": { /* upstream wire shape */ }, \"display\": { /* table-friendly strings */ } }\r\n  ]\r\n}\r\n```\r\n\r\n- `raw` is the canonical machine-friendly shape — numbers as numbers, timestamps as Unix seconds, temperatures as raw Celsius. **Agents pin on `raw`.**\r\n- `display` is human-presentation only, formatted with current `--temp-unit` / `--timezone`. **Agents MUST NOT pin on `display`** — it can change with locale or format fixes.\r\n- `meta.recommended_poll_seconds` — the page-level cadence the SPA uses; respect it when driving `--watch`.\r\n- `meta.iteration` — 1-based, present in every `--watch` payload.\r\n\r\n### Shape B — sections envelope\r\n\r\nUsed by **parent commands that aggregate multiple sub-views**:\r\n\r\n| Parent command | Sections |\r\n|---|---|\r\n| `dashboard overview` | `physical` / `user` / `ranking` |\r\n| `dashboard overview disk` | `main` / `partitions` |\r\n| `dashboard overview fan` | `live` / `curve` |\r\n| `dashboard overview gpu` | `graphics` / `tasks` |\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview\",\r\n  \"meta\": {...},\r\n  \"sections\": {\r\n    \"physical\": { \"kind\": \"dashboard.overview.physical\", \"meta\": {...}, \"items\": [...] },\r\n    \"user\":     { \"kind\": \"dashboard.overview.user\",     \"meta\": {...}, \"items\": [...] },\r\n    \"ranking\":  { \"kind\": \"dashboard.overview.ranking\",  \"meta\": {...}, \"items\": [...] }\r\n  }\r\n}\r\n```\r\n\r\nSections are fetched **concurrently**. A single failed section degrades to `meta.error=\"...\"` on that section — **the others still return**. Surface partial outputs to the user, don't blackout the whole envelope.\r\n\r\nTo enumerate every kind live: `olares-cli dashboard schema -o json`.\r\n\r\n## Three-state empty data (`meta.empty_reason`)\r\n\r\nOptional hardware (GPU / fan) and optional integrations have three legitimate \"empty\" states. The CLI distinguishes them so agents can branch without parsing prose.\r\n\r\n| Upstream | `meta.empty` | `meta.empty_reason` | Meaning |\r\n|---|---|---|---|\r\n| HTTP 404 | `true` | `no_<feature>_integration` | Integration absent (e.g. HAMI vGPU not installed) |\r\n| HTTP 200, empty body | `true` | `no_<feature>_detected` | Integration present but hardware empty |\r\n| HTTP 200, non-empty | `false` | `\"\"` | Normal — items[] populated |\r\n| Any 4xx / 5xx | n/a (envelope `meta.error`) | n/a | Real failure |\r\n\r\n**Forbidden**: turning a 404 into an error, or merging the two reasons into a single \"empty=true\" string. Agents currently key on the reason.\r\n\r\nSpecific reason values:\r\n\r\n| Reason | Where |\r\n|---|---|\r\n| `not_olares_one` | fan default / live / curve (active device's `device_name` is not `Olares One`) |\r\n| `no_fan_integration` | fan live (HTTP 404 fallback) |\r\n| `no_vgpu_integration` | gpu list / tasks / get / task (HTTP 404) |\r\n| `vgpu_unavailable` | gpu list / tasks / get / task (HTTP 5xx); `meta.error` carries the upstream message, `meta.http_status` the original status |\r\n| `no_gpu_detected` | gpu list / tasks / get / task (HTTP 200, empty body) |\r\n\r\n## Capability gates\r\n\r\nThe fan / GPU subtrees mirror SPA gates, but with **two different strengths**.\r\n\r\n### Hard gate — fan (Olares One only)\r\n\r\n`overview fan *` returns an empty envelope BEFORE any fetch on non-Olares-One hardware:\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview.fan.live\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"not_olares_one\",\r\n    \"note\": \"Fan / cooling integration is only available on Olares One devices\",\r\n    \"device_name\": \"DIY-PC\"\r\n  },\r\n  \"items\": []\r\n}\r\n```\r\n\r\nExit code is still `0` — this is a predictable state, not a failure.\r\n\r\n### Soft gate — GPU (advisory)\r\n\r\nThe CLI ALWAYS calls HAMI for GPU verbs. The SPA-equivalent \"would-have-been-hidden\" reason is recorded as `meta.note` and (in table mode) as a one-liner on stderr. The actual `meta.empty` / `empty_reason` are decided by HAMI's response.\r\n\r\nAdvisories you may see in `meta.note`:\r\n\r\n- `GPU sidebar entry is hidden for non-admin profiles in the SPA; HAMI was queried directly`\r\n- `no node carries gpu.bytetrade.io/cuda-supported=true; SPA hides the GPU card. HAMI was queried directly`\r\n\r\nWhen BOTH a HAMI failure AND a soft advisory apply, `meta.note` is `\"<advisory> | <hami-explanation>\"`.\r\n\r\n## Agent decision tree (recommended)\r\n\r\n```\r\ninspect meta.empty + meta.empty_reason + meta.note →\r\n  not_olares_one        → skip the fan subtree on this device\r\n  no_*_integration      → upstream component absent (HAMI / capi system fan)\r\n  vgpu_unavailable      → transient: retry; check meta.http_status / meta.error\r\n  no_*_detected         → integration up but hardware empty\r\n  (none) + meta.note    → data is present but the SPA would have hidden the entry; surface the note to the user\r\n  (none) + (no note)    → items[] populated, proceed normally\r\n```\r\n\r\n## Verb index\r\n\r\nFor flags & examples, **always start with `olares-cli dashboard <verb> --help`**.\r\n\r\n| Verb | Purpose | `--help` first, then... |\r\n|---|---|---|\r\n| `applications` (alias `apps`) | Workload-grain application table (mirrors SPA Applications page) | `olares-cli dashboard applications --help` |\r\n| `overview` (no subverb) | Default sections envelope (physical + user + ranking) | [references/olares-dashboard-overview.md](references/olares-dashboard-overview.md) |\r\n| `overview <section>` | Per-section snapshot (10 sub-verbs: cpu / memory / disk / pods / network / fan / gpu / physical / user / ranking) | [references/olares-dashboard-overview.md](references/olares-dashboard-overview.md) |\r\n| `schema` | Introspect the JSON Schemas served by `olares-cli dashboard` | `olares-cli dashboard schema --help` |\r\n\r\nFor `--watch`, `--since` / `--start/--end`, `--user`, `--timezone`, and the NDJSON contract, see [references/olares-dashboard-watch.md](references/olares-dashboard-watch.md).\r\n\r\n## Global flags (cross-cutting)\r\n\r\nThese are bound on the `dashboard` root and inherited by every leaf. **Defaults are sensible — don't pass them unless the user explicitly asks for the non-default.**\r\n\r\n| Flag | Purpose |\r\n|---|---|\r\n| `-o, --output table\\|json` | Output format (default `table`). JSON is the agent-facing form |\r\n| `--head N` | Truncate to the first N rows after sorting (0 = no truncation) |\r\n| `--limit N` / `--page N` | Pagination knobs (0 = upstream defaults) |\r\n| `--since D` | Relative window (e.g. `5m`, `1h`). **Sliding when `--watch`** |\r\n| `--start <RFC3339>` / `--end <RFC3339>` | Absolute window. **Fixed across iterations when `--watch`**. Mutually exclusive with `--since` |\r\n| `--temp-unit C\\|F\\|K` | Display unit for temperature (JSON `raw` always Celsius) |\r\n| `--timezone <IANA>` | Display TZ for table rendering (does NOT affect wire-format TZ) |\r\n| `--user <olaresId>` | Target a different user than the active profile — **platform-admin only**, returns 403 otherwise |\r\n| `--watch` | Enable HTTP-polling loop (NDJSON per iteration in JSON mode) |\r\n| `--watch-interval D` / `--watch-iterations N` / `--watch-timeout D` | Watch-loop knobs (require `--watch`; rejected otherwise) |\r\n\r\n## Exit codes & error semantics\r\n\r\n- **Exit `0`** for every gated / advisory / empty path — these are predictable states, not failures.\r\n- **Exit non-zero** only on:\r\n  - Auth-class errors (`ErrTokenInvalidated` / `ErrNotLoggedIn`) — propagated immediately\r\n  - 3 consecutive iteration failures inside a `--watch` loop\r\n  - A real upstream `meta.error` on a one-shot invocation\r\n- **Stderr** carries one human-readable line in non-JSON modes. JSON / NDJSON modes stay silent on stderr — agents read stdout exclusively.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `fan is only available on Olares One devices (current: <device_name>)` (stderr) | Hard gate; `meta.empty_reason=not_olares_one` | Stop probing fan on this device |\r\n| `(advisory) GPU sidebar entry is hidden for non-admin profiles ...` (stderr) | Soft gate; data still returned | Surface to user as a note; don't treat as error |\r\n| `gpu data temporarily unavailable: HAMI returned HTTP 500` | `vgpu_unavailable`; transient | Retry; if persistent, file a server-side issue |\r\n| `--user requires platform-admin role` | Non-admin profile passing `--user` | Use the active profile, or switch with `olares-cli profile use` |\r\n| `--watch-iterations requires --watch` (or `--interval` / `--timeout` similarly) | Polling knob without gate flag | Add `--watch` or drop the knob |\r\n| `--since and --start/--end are mutually exclusive` | Both window forms set | Pick one |\r\n| 401/403 from any dashboard verb | Token rotation / invalidation | See [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) |\r\n\r\nFor the full auth-error matrix see [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md).\n\nFile v4.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-dashboard\",\n  \"version\": \"4.0.0\",\n  \"publishedAt\": 1780030437489\n}\n\nFile v4.0.0:references/olares-dashboard-overview.md\n\n# dashboard overview\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shapes:** `olares-cli dashboard overview --help` and `olares-cli dashboard overview <section> --help`.\r\n\r\nThe most flag-rich verb in this subtree. Three \"framing\" sections (`physical` / `user` / `ranking`) live at the top of the overview tree; seven \"hardware\" sections (`cpu` / `memory` / `disk` / `pods` / `network` / `fan` / `gpu`) live alongside.\r\n\r\n## Default action (no subverb)\r\n\r\n```bash\r\nolares-cli dashboard overview\r\nolares-cli dashboard overview -o json\r\n```\r\n\r\nEmits a **Shape B sections envelope** with the three \"framing\" sections fetched concurrently:\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview\",\r\n  \"sections\": {\r\n    \"physical\": { \"kind\": \"dashboard.overview.physical\", \"items\": [...] },\r\n    \"user\":     { \"kind\": \"dashboard.overview.user\",     \"items\": [...] },\r\n    \"ranking\":  { \"kind\": \"dashboard.overview.ranking\",  \"items\": [...] }\r\n  }\r\n}\r\n```\r\n\r\nFor per-hardware sections (CPU / disk / fan / GPU / etc.), invoke them explicitly — they are NOT part of the default envelope.\r\n\r\n## 10-section matrix\r\n\r\n| Section | Kind | When to use |\r\n|---|---|---|\r\n| `physical` | `dashboard.overview.physical` | 9-row cluster-level snapshot (CPU/Memory/Disk/Pods/Net + extras). The \"single screen vital signs\" view |\r\n| `user` | `dashboard.overview.user` | Per-user CPU / memory quota usage. Accepts optional `<username>` positional — **admin-only** for non-self lookups. Mirrors SPA's User Resources panel |\r\n| `ranking` | `dashboard.overview.ranking` | Workload-grain resource ranking (per-application). `--sort asc|desc` + `--head N` for top-N. Mirrors SPA's UsageRanking widget |\r\n| `cpu` | `dashboard.overview.cpu` | Per-node CPU details (model / freq / cores / utilisation breakdown / temp / load avg) |\r\n| `memory` | `dashboard.overview.memory` | Per-node memory breakdown. `--mode physical|swap` (default `physical`) |\r\n| `disk` | `dashboard.overview.disk` (Shape B) | **Sections envelope**: `main` (per-disk table) + `partitions` (per-device partition tables). `disk main` and `disk partitions <device>` give individual views |\r\n| `pods` | `dashboard.overview.pods` | Per-node pod count snapshot (last/avg/max running) |\r\n| `network` | `dashboard.overview.network` | Per-physical-NIC table. `--test-connectivity` (default true) probes internet/IPv6 per interface |\r\n| `fan` | `dashboard.overview.fan` (Shape B) | **Olares One only.** Sections envelope: `live` (real-time fan/temp/power) + `curve` (hardcoded fan-curve spec). Hard-gated on non-Olares-One: `meta.empty_reason=not_olares_one` |\r\n| `gpu` | `dashboard.overview.gpu` (Shape B) | Sections envelope: `graphics` (GPU list) + `tasks` (vGPU tasks). **Soft-gated**; `meta.note` advises when the SPA would have hidden the entry. See parent SKILL.md for the soft-gate decision tree |\r\n\r\n## Drilling into a section\r\n\r\n```bash\r\n# Per-section snapshots.\r\nolares-cli dashboard overview cpu -o json\r\nolares-cli dashboard overview memory --mode swap\r\nolares-cli dashboard overview network --test-connectivity=false\r\n\r\n# Sections envelope (disk has its own Shape B):\r\nolares-cli dashboard overview disk -o json                    # both sections\r\nolares-cli dashboard overview disk main                       # just per-disk table\r\nolares-cli dashboard overview disk partitions sda             # per-device partitions\r\n\r\n# Fan (Olares One only; otherwise empty envelope with reason=not_olares_one):\r\nolares-cli dashboard overview fan -o json\r\nolares-cli dashboard overview fan live\r\nolares-cli dashboard overview fan curve\r\n\r\n# GPU (soft-gated):\r\nolares-cli dashboard overview gpu                             # graphics + tasks sections\r\nolares-cli dashboard overview gpu graphics                    # just GPU list\r\nolares-cli dashboard overview gpu graphics <uuid>             # per-GPU detail page (gauges + trends)\r\nolares-cli dashboard overview gpu tasks                       # task list\r\nolares-cli dashboard overview gpu tasks <name-or-pod-uid>     # per-task detail page\r\n\r\n# Ranking (top-N apps by CPU).\r\nolares-cli dashboard overview ranking --sort desc --head 5\r\n```\r\n\r\n## GPU detail pages\r\n\r\n`gpu graphics <uuid>` and `gpu tasks <name-or-pod-uid>` are **full SPA-mirror detail pages** — they assemble a sections envelope with `detail` / `gauges` / `trends`:\r\n\r\n- `detail` — HAMI metadata\r\n- `gauges` — instant-vector queries (current values)\r\n- `trends` — range-vector queries (default 8h for GPU, 1h for task). Override with `--since` / `--start` / `--end`\r\n\r\nPartial-failure semantics: a single gauge / trend item failing does NOT abort the envelope; it carries `raw.error` and contributes to `meta.warnings`. **Branch on `len(meta.warnings) > 0` to detect partial data.**\r\n\r\nFor `gpu tasks <ref>`, `<ref>` accepts EITHER the TASK column (`name`) OR the POD_UID column (`podUid`) — copy-paste from either column works. If two tasks share a name, the CLI errors with the candidate pod-uids; re-run with one.\r\n\r\n## Agent notes\r\n\r\n- For \"give me a single-screen vital-signs view\", **`olares-cli dashboard overview -o json` is the right verb** (Shape B envelope with physical + user + ranking).\r\n- For \"tail CPU / memory live\", **add `--watch -o json`** to a per-section command. The output is NDJSON, one envelope per iteration. See [olares-dashboard-watch.md](olares-dashboard-watch.md).\r\n- For \"what apps are using the most CPU\", **`overview ranking --sort desc --head 10`** is the right entry point — it returns a workload-grain ranking with both `raw` (machine-friendly) and `display` (human-friendly) columns.\r\n- For `--user <other>` cross-tenant queries, **only platform-admins succeed**; non-admins get an immediate error. Run `cluster context` first if you need to confirm the active role.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `not_olares_one` empty envelope on `fan *` | Hard gate | Don't ask for fan data on this device |\r\n| `(advisory) ...` line on stderr from `gpu *` | Soft gate; data still returned | Surface the note to the user as informational |\r\n| `--user requires platform-admin role` | Non-admin tried `--user <other>` | Use the active profile only |\r\n| 404 / 5xx on `overview gpu *` | HAMI absent or unhealthy (`no_vgpu_integration` / `vgpu_unavailable`) | Branch on `meta.empty_reason` |\n\nFile v4.0.0:references/olares-dashboard-watch.md\n\n# dashboard --watch / windows / NDJSON contract\r\n\r\n> **Prerequisite:** Read the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags:** `olares-cli dashboard --help` (every flag here is a `dashboard`-root global flag inherited by every leaf).\r\n\r\n`--watch` opts every leaf verb into an HTTP-polling loop that mirrors the SPA's `setTimeout` cadence. **Polling, not streaming** — every iteration is a fresh HTTP request, no chunked transfer encoding.\r\n\r\n## `--watch` behaviour\r\n\r\n```\r\ninvoke leaf cmd\r\n   │\r\n   ▼\r\n--watch set? ── no ──> single execution → emit one envelope → exit 0/1\r\n   │\r\n   yes\r\n   ▼\r\nRunner.Run loop\r\n   │\r\n   ▼\r\nRunOnce iteration N\r\n   │\r\n   ├── success         → emit envelope (Shape A or B) with meta.iteration=N\r\n   │                       JSON mode → NDJSON line on stdout\r\n   │                       table mode → clear-screen redraw\r\n   │\r\n   ├── transient err   → emit envelope with meta.error=\"...\", items:[]\r\n   │                       continue\r\n   │\r\n   └── ErrTokenInvalidated / ErrNotLoggedIn\r\n                       → exit non-zero IMMEDIATELY\r\n   ▼\r\n3 consecutive errors? ──> exit non-zero\r\n   │\r\n   no\r\n   ▼\r\n--watch-iterations cap reached? ──> exit 0\r\n   │\r\n   no\r\n   ▼\r\nSIGINT (Ctrl-C) ──> exit 0 gracefully\r\n   │\r\n   no\r\n   ▼\r\nwait --watch-interval, loop back\r\n```\r\n\r\n## NDJSON contract (JSON mode)\r\n\r\n- **One envelope per iteration; nothing else on stdout.** Pretty `-o json` is for one-shot mode; `--watch -o json` is **always NDJSON** (line-delimited, no leading newline, no trailing array bracket).\r\n- A failed iteration emits `items: []` + `meta.error: \"<msg>\"` — **NDJSON keeps streaming**, the failure does not raise to process exit unless it's auth-class or the 3-consecutive cap trips.\r\n- `meta.iteration` is **1-based** and present in every payload.\r\n- Each envelope's `meta.fetched_at` reflects that iteration's HTTP-request start time.\r\n\r\n```jsonc\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 1, ... }, \"items\": [...] }\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 2, \"error\": \"...\", ... }, \"items\": [] }\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 3, ... }, \"items\": [...] }\r\n```\r\n\r\n## Watch loop knobs\r\n\r\n| Flag | Purpose | Default |\r\n|---|---|---|\r\n| `--watch` | Enable the loop | off |\r\n| `--watch-interval D` | Time between iterations | command's `recommended_poll_seconds` (e.g. 30s for GPU, 60s for most others) |\r\n| `--watch-iterations N` | Stop after N iterations | unbounded |\r\n| `--watch-timeout D` | Stop after this much wall-clock time | unbounded |\r\n\r\n**All three `--watch-*` knobs are REJECTED without `--watch`** — don't silently waste a flag.\r\n\r\n## Window semantics — `--since` vs `--start/--end`\r\n\r\nMutually exclusive. Pick one.\r\n\r\n| Form | Semantics | Behaviour under `--watch` |\r\n|---|---|---|\r\n| `--since 5m` | Window = `[now - 5m, now]` | **Sliding** — each iteration recomputes `now` |\r\n| `--start <RFC3339> --end <RFC3339>` | Window = `[start, end]` | **Fixed** — same window every iteration |\r\n\r\nIf neither is set, each leaf has its own SPA-derived default (e.g. 8h for GPU detail page, 1h for GPU task detail page, no window for cluster-level instant snapshots).\r\n\r\nThe chosen window appears in `meta.window` so an agent can replay the exact same query without recomputing:\r\n\r\n```json\r\n\"meta\": {\r\n  \"window\": { \"since\": \"8h\", \"start\": \"...\", \"end\": \"...\", \"step\": \"30m\" }\r\n}\r\n```\r\n\r\n## Examples\r\n\r\n```bash\r\n# Tail CPU every 30s as NDJSON until Ctrl-C.\r\nolares-cli dashboard overview cpu --watch -o json --watch-interval 30s\r\n\r\n# Tail CPU but stop after 10 iterations.\r\nolares-cli dashboard overview cpu --watch -o json --watch-iterations 10\r\n\r\n# Watch a GPU detail page for 5 minutes with a sliding 15m window.\r\nolares-cli dashboard overview gpu graphics <uuid> --watch -o json --since 15m --watch-timeout 5m\r\n\r\n# Fixed window across all iterations (useful for retrospective tailing).\r\nolares-cli dashboard overview ranking --watch -o json \\\r\n  --start 2026-05-28T00:00:00Z --end 2026-05-28T01:00:00Z\r\n```\r\n\r\n## SIGINT / graceful shutdown\r\n\r\n- Ctrl-C (SIGINT) and SIGTERM trigger a graceful stop — the current iteration is allowed to finish, then the loop exits with `0`.\r\n- Inside a `for line in cmd.stdout` loop, the parent agent process sees the stream end cleanly (no half-line, no half-envelope).\r\n\r\n## `--user <olaresId>` interaction\r\n\r\n- `--user` only changes upstream filter args. The auth header is still the active profile's token.\r\n- **Platform-admin only.** Non-admin callers get an immediate error; the loop never starts.\r\n- The admin check is cached per-Client (`sync.Once`) — there is zero per-iteration auth-check overhead.\r\n\r\n## `--timezone <IANA>` interaction\r\n\r\n- Affects DISPLAY only — `meta.window` / table-side timestamps / header rendering.\r\n- The wire format sent to HAMI's monitor query endpoints stays in the backend pod's TZ (default `Asia/Shanghai`) so trends resolve correctly even when `--timezone` differs.\r\n- Override the backend TZ with `OLARES_HAMI_BACKEND_TZ=<IANA>` env if your HAMI deployment is in a different zone (hidden from `--help`; only operators of non-default HAMI deployments need it).\r\n\r\n## Agent best practices\r\n\r\n- **Use `--watch -o json` for live tailing.** Always NDJSON, always one envelope per line.\r\n- **Respect `meta.recommended_poll_seconds`.** It mirrors the SPA's polling cadence; overriding with a much shorter `--watch-interval` adds upstream load.\r\n- **Handle the empty-envelope iterations.** `meta.error` is normal during transient upstream issues; do NOT abort the consumer loop unless 3 consecutive errors trip the safety net (the CLI exits non-zero in that case anyway).\r\n- **Stop polling on `meta.empty_reason == not_olares_one`.** It's a hard gate that won't change without a hardware change.\r\n- **For `vgpu_unavailable`**, the CLI keeps streaming — your agent should keep iterating too. Real failures still surface via process exit on auth-class errors or 3 consecutive iteration failures.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `--watch-iterations requires --watch` | Knob without gate | Add `--watch` or drop the knob |\r\n| `--since and --start/--end are mutually exclusive` | Both window forms set | Pick one |\r\n| Process exits non-zero immediately on the first iteration | `ErrNotLoggedIn` / `ErrTokenInvalidated` | Run `olares-cli profile login` and retry |\r\n| Process exits non-zero after a few iterations | 3 consecutive transient errors | Investigate upstream (HAMI / capi); restart the loop |\n\nFile v4.0.0:skill-card.md\n\n## Description: <br>\nHelps agents query Olares Dashboard overview and application metrics through olares-cli dashboard, including JSON envelopes, watch-mode NDJSON, capability gates, and operational error handling. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[olares](https://clawhub.ai/user/olares) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers and Olares operators use this skill to inspect runtime metrics, application resource usage, hardware-specific dashboard views, and live watch output from an existing Olares CLI profile. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: The skill can expose operational metrics available to the active Olares CLI profile. <br>\nMitigation: Install and use it only when the local olares-cli profile is trusted and authorized for the target Olares environment. <br>\nRisk: Admin profiles can target another user with --user. <br>\nMitigation: Use least-privilege profiles for routine checks and confirm the intended Olares ID before running cross-user queries. <br>\nRisk: Unbounded watch loops can repeatedly poll dashboard endpoints. <br>\nMitigation: Set --watch-iterations or --watch-timeout when continuous polling is not needed, and respect the recommended poll interval. <br>\n\n\n## Reference(s): <br>\n- [ClawHub skill page](https://clawhub.ai/olares/olares-dashboard) <br>\n- [Olares Dashboard overview reference](references/olares-dashboard-overview.md) <br>\n- [Olares Dashboard watch and window reference](references/olares-dashboard-watch.md) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [guidance, shell commands, configuration] <br>\n**Output Format:** [Markdown guidance with inline shell commands and JSON or NDJSON output contracts] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Guides read-only olares-cli dashboard queries; JSON output uses stable envelopes and watch mode emits one NDJSON envelope per iteration.] <br>\n\n## Skill Version(s): <br>\n4.0.0 (source: frontmatter and server release evidence) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v2.1.0: 3 files, 31280 bytes\n\nFiles: skill-card.md (2375b), SKILL.md (78144b), _meta.json (135b)\n\nFile v2.1.0:SKILL.md\n\n---\r\nname: olares-dashboard\r\nversion: 2.1.0\r\ndescription: \"Query the Olares Dashboard SPA's Overview and Applications routes via olares-cli dashboard — an AI-agent-first JSON mirror with a strict dual-shape envelope (leaf items / aggregated sections), stable Kind constants, and human-readable table output. Covers Overview leaves (CPU, memory, disk, network, pods, fan, GPU, ranking), Applications listing and detail, the --watch HTTP-polling loop (interval / iterations / timeout / NDJSON-per-iteration / SIGINT-graceful), --since (sliding window) vs --start/--end (fixed window) selection, three-state empty-data semantics (no_<feature>_integration / no_<feature>_detected / vgpu_unavailable), and capability gates (fan is hard-gated to Olares One; GPU is a soft advisory mirroring the SPA sidebar). Use when the user mentions Olares Dashboard, olares-cli dashboard, the Olares overview / applications view, 'show CPU / memory / disk / pods / network / fan / GPU / ranking on Olares', wants JSON for an AI agent, or hits errors like 'fan is only available on Olares One devices' or 'gpu data temporarily unavailable'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli dashboard --help\"\r\n---\r\n\r\n# dashboard (overview + applications, AI-agent first)\r\n\r\n**CRITICAL — before doing ANYTHING in this subtree, MUST Read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, factory-injected `*http.Client`, and HTTP 401/403 recovery rules. Every dashboard verb depends on that foundation.**\r\n\r\n**This file is the source of truth for the dashboard subtree.** Any code generation, refactor or fix touching `cli/cmd/ctl/dashboard/**`, `cli/pkg/dashboard/**`, `cli/pkg/dashboard/format/**`, `cli/pkg/olares/id.go::DashboardURL`, or `cli/pkg/credential/{types,default_provider}.go::DashboardURL` MUST first Read this file end-to-end and respect the **Iteration red-lines** section. Do not \"modernize\", \"simplify\" or \"consolidate\" anything listed under [Frozen modules](#frozen-modules) without an explicit user-approved plan that supersedes this skill.\r\n\r\n## Scope (frozen)\r\n\r\nThe dashboard CLI mirrors **only** the SPA routes that are still wired in [`packages/app/src/apps/dashboard/router/routes.ts`](dashboard/packages/app/src/apps/dashboard/router/routes.ts):\r\n\r\n- `Overview2/IndexPage.vue` (overview tree) + the per-detail-page subtrees:\r\n  `Overview2/CPU`, `Memory`, `Disk`, `Pods`, `Network`, `Fan`, `GPU`.\r\n- `Applications2/IndexPage.vue` (applications tree).\r\n\r\nEverything else in the SPA (legacy `Overview/`, audit, settings, …) is **out of scope**. Do not add commands for deprecated routes; reject any request that asks for them with a pointer to the route file.\r\n\r\n## Architecture\r\n\r\nThe dashboard CLI is a **two-layer split with mirrored subpackage\r\ntrees on BOTH sides**:\r\n\r\n- **cmd shell** under `cli/cmd/ctl/dashboard/` — one directory per\r\n  command-tree parent, one `.go` file per leaf. Each leaf's RunE is a\r\n  thin `validate flags → prepareClient → pkg<area>.RunXxx(...)` body.\r\n  Per-area `common.go` is ≤ 70 lines and carries only `var common`,\r\n  `prepareClient`, `unknownSubcommandRunE`.\r\n- **pkg layer** under `cli/pkg/dashboard/`, split in two:\r\n  - the **pkg ROOT** (`cli/pkg/dashboard/*.go`) owns infrastructure\r\n    and shared fetchers — `Client`, `Runner`, `CommonFlags`,\r\n    `Envelope`/`Item`/`Meta`, `Fetch*` helpers, `BuildRankingEnvelope`,\r\n    capability gates, the `format` pkg, the schema bundle. **No\r\n    command-specific envelope builders or table writers live here.**\r\n  - the **pkg subpackages** (`cli/pkg/dashboard/applications/`,\r\n    `cli/pkg/dashboard/overview/`,\r\n    `cli/pkg/dashboard/overview/{disk,fan,gpu}/`) mirror the cmd\r\n    parent dirs. Each carries one `<verb>.go` file per leaf, exposing\r\n    a single `RunXxx(ctx, c, cf, ...)` entry plus its\r\n    `BuildXxxEnvelope` / `WriteXxxTable` triplet. Tests sit\r\n    alongside as `<verb>_test.go` (one shared `helpers_test.go` per\r\n    area for httptest fixtures).\r\n\r\nEvery fetch / merge / aggregate / format call goes through pkg —\r\neither the root infra or, for envelope assembly, the matching pkg\r\nsubpackage. The cmd shell never touches HTTP, never builds an\r\nenvelope, never writes a table, never runs `Runner` directly.\r\n\r\n```mermaid\r\nflowchart LR\r\n    User[olares-cli dashboard <verb>] --> Factory[cmdutil.Factory<br/>refreshingTransport]\r\n    Factory --> CmdShell[\"cli/cmd/ctl/dashboard/&lt;area&gt;/&lt;leaf&gt;.go<br/>(thin cobra shell:<br/>validate → prepareClient →<br/>pkg&lt;area&gt;.RunXxx)\"]\r\n    CmdShell --> PkgArea[\"cli/pkg/dashboard/&lt;area&gt;/&lt;leaf&gt;.go<br/>(RunXxx + BuildXxxEnvelope +<br/>WriteXxxTable + watch loop)\"]\r\n    PkgArea --> PkgRoot[\"cli/pkg/dashboard/<br/>(infra: Envelope, Client, Runner,<br/>fetchers, aggregators, gates, schema)\"]\r\n    PkgRoot -->|/capi/* + /kapis/* + /hami/*| BFF[\"dashboard.&lt;terminus&gt;<br/>(ks-apiserver-proxy)\"]\r\n    BFF -->|wildcard proxy| KsAPI[ks-apiserver]\r\n    PkgRoot --> Format[\"cli/pkg/dashboard/format<br/>(1:1 JS port)\"]\r\n    PkgArea --> Output[\"WriteJSON / WriteTable<br/>(pkgdashboard.*)\"]\r\n    Output --> User\r\n```\r\n\r\ncmd area ↔ pkg subpackage mapping — every cmd leaf is a thin\r\n`prepareClient` + `pkg<area>.RunXxx(...)` shell. The heavy logic\r\n(envelope builders, table writers, fan-out, watch loops) lives in a\r\nmirror subpackage under `cli/pkg/dashboard/<area>/`. The pkg root\r\n(`cli/pkg/dashboard/`) only owns infrastructure and shared fetchers.\r\n\r\n| cmd area | pkg subpackage entry points (`Run*`) | shared infra it pulls from `cli/pkg/dashboard/` |\r\n|---|---|---|\r\n| `cmd/ctl/dashboard/root.go` + `options.go` | n/a — pure cobra wiring + `bindPersistentFlags` | `flags.go` (`CommonFlags`), `runner.go`, `emit.go`, `output.go` |\r\n| `cmd/ctl/dashboard/applications/root.go` | `pkg/dashboard/applications/list.go` → `RunList` | `ranking.go`, `workloads.go`, `apps.go`, `format/...` |\r\n| `cmd/ctl/dashboard/overview/{physical,user,ranking,root}.go` | `pkg/dashboard/overview/{physical,user,ranking,default}.go` → `RunPhysical`/`RunUser`/`RunRanking`/`RunDefault` | `monitoring.go`, `workloads.go`, `ranking.go`, `users.go`, `numbers.go` |\r\n| `cmd/ctl/dashboard/overview/{cpu,memory,pods,network}.go` | `pkg/dashboard/overview/{cpu,memory,pods,network}.go` → `RunCPU`/`RunMemory`/`RunPods`/`RunNetwork` (built on `nodes.go`'s `RunPerNodeMetric` scaffold) | `monitoring.go`, `system.go` (network → capi `/system/ifs`), `numbers.go` |\r\n| `cmd/ctl/dashboard/overview/disk/{root,main,partitions}.go` | `pkg/dashboard/overview/disk/{default,main,partitions}.go` → `RunDefault`/`RunMain`/`RunPartitions` | `monitoring.go` (node\\_disk\\_*), `lsblk.go`, `numbers.go`, `format/...` |\r\n| `cmd/ctl/dashboard/overview/fan/{root,live,curve}.go` | `pkg/dashboard/overview/fan/{default,live,curve}.go` → `RunDefault`/`RunLive`/`RunCurve` | `system.go` (`FetchSystemFan`), `gates.go` (`GateOlaresOne`), `gpu.go` (graphics list co-render), `fan_curve.go` |\r\n| `cmd/ctl/dashboard/overview/gpu/{root,graphics,tasks,list,get,task,detail,task_detail}.go` | `pkg/dashboard/overview/gpu/{list,tasks,get,task,detail,task_detail}.go` → `RunList`/`RunTasks`/`RunGet`/`RunTask`/`RunDetail`/`RunTaskDetail`/`RunTaskByRef` (+ `specs.go` for the gauge/trend catalogue and concurrent fan-out) | `gpu.go`, `gpu_format.go`, `gpu_query.go`, `gates.go`, `client.go` |\r\n| `cmd/ctl/dashboard/schema/root.go` | n/a — schema loader stays in pkg root | `schema.go` (loader + `go:embed` bundle) |\r\n\r\n- HTTP base = `ResolvedProfile.DashboardURL = https://dashboard.<localPrefix><terminusName>` (derived in [`cli/pkg/olares/id.go`](cli/pkg/olares/id.go) `DashboardURL`).\r\n- All requests go through factory's `refreshingTransport` — header injection + 401 retry happen for free; **dashboard code MUST NOT touch `X-Authorization`** or instantiate its own `http.Client`.\r\n- Per-command leaf / aggregate decision is hard-coded; do not flip them.\r\n- The shared `*pkgdashboard.CommonFlags` is bound once in cmd-root (`bindPersistentFlags(&common, cmd)`), then passed by pointer to every area factory (`overview.NewOverviewCommand(f, &common)` etc.). Each area subpackage stores it in a package-level `var common *pkgdashboard.CommonFlags` so leaf RunE bodies keep `common.Output / common.Validate() / common.Timezone` selectors.\r\n\r\n\r\n## JSON envelope (frozen shapes)\r\n\r\nEvery dashboard command emits exactly one of two shapes; the choice is fixed per command and MUST NOT be changed.\r\n\r\n### Shape A — leaf\r\n\r\nUsed by every command except parent commands with a sections envelope (see Shape B).\r\n\r\n```json\r\n{\r\n  \"kind\":  \"dashboard.<area>.<verb>\",\r\n  \"meta\":  { \"fetched_at\": \"...\", \"iteration\": 0, \"recommended_poll_seconds\": 60, \"empty\": false, \"empty_reason\": \"\", \"error\": \"\", \"http_status\": 200 },\r\n  \"items\": [ { \"raw\": { /* upstream-shape, units in source unit */ }, \"display\": { /* table-friendly strings */ } } ]\r\n}\r\n```\r\n\r\n- `raw` is the canonical machine-friendly shape: numbers as numbers, timestamps as Unix seconds, temperatures as raw Celsius (NOT converted by `--temp-unit`). Agents pin on `raw`.\r\n- `display` is human-presentation only; values are formatted via the `format` pkg with current `--temp-unit` / `--timezone`. **Agents MUST NOT pin on `display`** — it can change with locale / format-pkg fixes.\r\n- `meta.recommended_poll_seconds` — the page-level polling cadence the SPA uses; agents driving `--watch` SHOULD respect it.\r\n- `meta.iteration` — present in every `--watch` payload, 1-based.\r\n- `meta.empty` / `meta.empty_reason` — three-state empty data; see [Three-state empty data](#three-state-empty-data).\r\n- `meta.error` — only set on a failed `--watch` iteration or per-section failure inside Shape B.\r\n\r\n### Shape B — sections envelope\r\n\r\nUsed by **parent commands that aggregate multiple sub-views**:\r\n\r\n| Parent command           | Sections                            | Section kinds |\r\n|---|---|---|\r\n| `dashboard overview`     | `physical` / `user` / `ranking`     | `dashboard.overview.physical` / `.user` / `.ranking` |\r\n| `dashboard overview disk`| `main` / `partitions`               | `dashboard.overview.disk.main` / `.partitions` (the latter is itself a sections envelope, keyed by device) |\r\n| `dashboard overview fan` | `live` / `curve`                    | `dashboard.overview.fan.live` / `.curve` |\r\n| `dashboard overview gpu` | `graphics` / `tasks`                | `dashboard.overview.gpu.list` / `.tasks` (mirrors the SPA's GPU overview tabs preloaded together) |\r\n| `dashboard schema`       | n/a — emits Shape A index           | `dashboard.schema.index` |\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview\",\r\n  \"meta\": { ... },\r\n  \"sections\": {\r\n    \"physical\": { \"kind\": \"dashboard.overview.physical\", \"meta\": {...}, \"items\": [...] },\r\n    \"user\":     { \"kind\": \"dashboard.overview.user\",     \"meta\": {...}, \"items\": [...] },\r\n    \"ranking\":  { \"kind\": \"dashboard.overview.ranking\",  \"meta\": {...}, \"items\": [...] }\r\n  }\r\n}\r\n```\r\n\r\n- Sections are fetched concurrently; a single failed section degrades to `meta.error=\"...\"` on that section, the others still return.\r\n- The section names (the Section column above) are part of the contract — do not rename or drop.\r\n\r\n### Kind constants\r\n\r\nDeclared in [`cli/pkg/dashboard/schema.go`](cli/pkg/dashboard/schema.go). **`AllKinds()` MUST stay 1:1 with the actual command surface.** Adding a command means:\r\n\r\n1. Add a `KindXxx` constant in `pkg/dashboard/schema.go`.\r\n2. Append it to `AllKinds()` (same file).\r\n3. Add `cli/pkg/dashboard/schemas/<kind-without-namespace>.json`.\r\n4. Register in `LoadSchemaIndex()` (same file).\r\n5. Bump golden tests if the new shape appears in fixtures.\r\n6. Add a leaf file in the matching `cmd/ctl/dashboard/<area>/` subdirectory and AddCommand it from the area's `root.go`.\r\n\r\nDo NOT rename existing Kind values — agents rely on string equality.\r\n\r\nThe authoritative listing of every command, its envelope shape, and\r\nits default-action semantics is the cmd/pkg directory tree itself\r\n(see [File layout](#file-layout-frozen--directory-tree-mirrors-command-tree-on-both-sides)) and `olares-cli dashboard --help`. Run `olares-cli dashboard schema -o\r\njson` for the live `dashboard.<area>.<verb>` Kind index.\r\n\r\n## Frozen modules\r\n\r\nThe following are **load-bearing**. Touching them requires a separate user-confirmed plan; otherwise reject the request with this list.\r\n\r\n| Frozen module | Why it's frozen | Where |\r\n|---|---|---|\r\n| Two-layer split with mirrored `cmd/<area>` ↔ `pkg/dashboard/<area>` subpackages: cmd is a thin cobra shell that calls `pkg<area>.RunXxx(...)`; pkg owns every envelope builder, table writer, watch loop and fan-out. The pkg root holds only infrastructure + shared fetchers. | Architectural decision (P1–P7); each leaf's heavy logic is testable directly via `RunXxx` without dragging cobra in, and cmd-side line budgets stay flat as the surface grows | [`cli/cmd/ctl/dashboard/`](cli/cmd/ctl/dashboard/) + [`cli/pkg/dashboard/`](cli/pkg/dashboard/) |\r\n| Directory tree mirrors command tree on BOTH sides | One file per leaf in cmd (`<verb>.go`) AND in the matching pkg subpackage (`<verb>.go` exposing one `RunXxx` entry); `root.go`+`common.go` per cmd parent, `default.go` per pkg parent — prevents drift between `dashboard --help`, the cmd `ls`, and the pkg `ls` | every cmd subdirectory under [`cli/cmd/ctl/dashboard/`](cli/cmd/ctl/dashboard/) and every pkg subpackage under [`cli/pkg/dashboard/<area>/`](cli/pkg/dashboard/) |\r\n| Kind constants + `AllKinds()` | Public agent contract | [`pkg/dashboard/schema.go`](cli/pkg/dashboard/schema.go) |\r\n| Envelope shapes A / B | Public agent contract | [`pkg/dashboard/output.go`](cli/pkg/dashboard/output.go) |\r\n| `format` pkg signatures + JS-parity behaviour | Backed by `golden.json` oracle; even rounding quirks are intentional | [`pkg/dashboard/format/format.go`](cli/pkg/dashboard/format/format.go) |\r\n| `FetchClusterMetrics` / `FetchNodeMetrics` / `FetchUserMetric` shared fetchers | Single upstream call powers multiple commands; must not fork | [`pkg/dashboard/monitoring.go`](cli/pkg/dashboard/monitoring.go) |\r\n| `FetchWorkloadsMetrics` dual-fetch + merge + `BuildRankingEnvelope` | Single pkg function powers `overview ranking` + `applications` first-row parity; keep the dual-fetch shape | [`pkg/dashboard/workloads.go`](cli/pkg/dashboard/workloads.go), [`pkg/dashboard/ranking.go`](cli/pkg/dashboard/ranking.go) |\r\n| `FetchSystemIFS` / `FetchSystemFan` / graphics+task helpers | Wire shape pinned in tests; capi vs hami URL prefixes are part of the contract | [`pkg/dashboard/system.go`](cli/pkg/dashboard/system.go), [`pkg/dashboard/gpu.go`](cli/pkg/dashboard/gpu.go) |\r\n| `FanCurveTable` + `FanSpeedMaxCPU` + `FanSpeedMaxGPU` constants | 1:1 with SPA `Fan/config.ts.tableData`; CLI ships them as Go constants because the SPA does too | [`pkg/dashboard/fan_curve.go`](cli/pkg/dashboard/fan_curve.go) |\r\n| Parent-command default = sections envelope (overview, overview disk, overview fan) | Plan decision; consumers branch on `kind == \"dashboard.overview.*\"` to know whether to expect `sections` or `items` | [`overview/root.go`](cli/cmd/ctl/dashboard/overview/root.go), [`overview/disk/root.go`](cli/cmd/ctl/dashboard/overview/disk/root.go), [`overview/fan/root.go`](cli/cmd/ctl/dashboard/overview/fan/root.go) |\r\n| `Runner` + `--watch` semantics (interval / iterations / timeout / 3-consecutive-failure exit / NDJSON per-iteration / SIGINT graceful / `ErrTokenInvalidated` short-circuit) | Plan decision; agents script around it | [`pkg/dashboard/runner.go`](cli/pkg/dashboard/runner.go) |\r\n| `--since` vs `--start --end` mutual-exclusion + sliding-window rules | Plan decision (`since_slides_abs_repeats`) | [`pkg/dashboard/flags.go`](cli/pkg/dashboard/flags.go) `Validate` / `ResolveWindow` |\r\n| Three-state empty-data semantics | Distinguishes \"feature not installed\" from \"no hardware\" without throwing | `overview gpu {list,tasks}` / `overview fan live` (cmd) ↔ [`pkg/dashboard/gates.go`](cli/pkg/dashboard/gates.go) |\r\n| `Client.EnsureUser` sync.Once cache + `RequireAdmin` / `ResolveTargetUser` guards | Auth correctness for `--user` / `overview user <name>` | [`pkg/dashboard/client.go`](cli/pkg/dashboard/client.go) |\r\n| `ResolvedProfile.DashboardURL` derivation chain | URL is derived, not configured | [`cli/pkg/olares/id.go`](cli/pkg/olares/id.go), [`cli/pkg/credential/types.go`](cli/pkg/credential/types.go), [`cli/pkg/credential/default_provider.go`](cli/pkg/credential/default_provider.go) |\r\n| Cobra `SilenceUsage = true` + `SilenceErrors = true` on every dashboard cmd | Clean machine-readable error text; pinned by `TestAllLeafCommandsSilenced` in cmd-root | every `cobra.Command` literal under `cmd/ctl/dashboard/` |\r\n| Default output = `table` | Plan decision (`table_default`) | [`pkg/dashboard/output.go`](cli/pkg/dashboard/output.go) `ParseOutputFormat` |\r\n| **Dependency direction (cmd side)** — `cli/cmd/ctl/dashboard/<area>` may import `cli/pkg/dashboard`, `cli/pkg/dashboard/<area>/...`, and its own cobra children only; **horizontal cmd↔cmd imports are FORBIDDEN**. The cmd shell is forbidden from owning envelope shapes / table writers / fan-out — those belong in the matching `pkg/dashboard/<area>` subpackage. | Architectural integrity; prevents the circular-import class of bugs that killed the 1.x layout, and keeps the cmd surface trivially line-budgeted | every `import` block under [`cli/cmd/ctl/dashboard/`](cli/cmd/ctl/dashboard/) |\r\n| **Dependency direction (pkg side)** — `cli/pkg/dashboard/<area>/...` may import `cli/pkg/dashboard` (the root infra: `Client`, `Runner`, fetchers, `format`, `numbers`, `gpu_format`, `gpu_query`, `gates`, `schema`, `output`, `emit`, `lsblk`, `fan_curve`); the pkg root MUST NOT import any subpackage (no upward import). Sibling subpackages (e.g. `overview/disk` ↔ `overview/fan`) MUST NOT import each other — if two areas need the same heavy helper, hoist it to `cli/pkg/dashboard/` (the `BuildRankingEnvelope` precedent). | Keeps the pkg root independently testable + reusable, and keeps each `<area>` subpackage a self-contained mirror of its cmd sibling | every `import` block under [`cli/pkg/dashboard/`](cli/pkg/dashboard/) |\r\n\r\n## Three-state empty data\r\n\r\nOptional hardware (GPU / fan) and optional integrations have three legitimate \"empty\" states; the CLI distinguishes them in `meta.empty_reason` so agents can branch without parsing prose.\r\n\r\n| Upstream response | `meta.empty` | `meta.empty_reason` | Example |\r\n|---|---|---|---|\r\n| HTTP 404 | `true` | `no_<feature>_integration` | HAMI vGPU not installed → `dashboard overview gpu list` |\r\n| HTTP 200, empty body / empty metric | `true` | `no_<feature>_detected` | Server has no fans → `dashboard overview fan live` |\r\n| HTTP 200, non-empty | `false` | `\"\"` | Normal case |\r\n| Any other 4xx/5xx | n/a (envelope `meta.error`) | n/a | Real failure → propagate via watch error path |\r\n\r\nForbidden: turning a 404 into an error, or merging the two reasons into a single \"empty=true\" — agents currently key on the reason.\r\n\r\n## Capability gates (overview fan / overview gpu)\r\n\r\nThe fan / gpu subtrees mirror the SPA's gates — but with **two different\r\nstrengths** because the SPA itself treats them differently:\r\n\r\n| Subtree | Gate strength | Source check |\r\n|---|---|---|\r\n| `overview fan *` (default / live / curve) | **Hard** — empty envelope before any fetch | [`Fan.ts:67`](dashboard/packages/app/src/apps/dashboard/stores/Fan.ts) (`isOlaresOneDevice`); fan data is meaningless on non-Olares One hardware |\r\n| `overview gpu *` (list / tasks / get / task) | **Soft** — advisory `meta.note` + stderr hint, then proceed with the HAMI fetch | [`ClusterResource.vue:232+278`](dashboard/packages/app/src/apps/dashboard/pages/Overview2/ClusterResource.vue) hides the **sidebar entry** for non-admins / no-CUDA clusters; the GPU detail pages themselves do **not** hard-block |\r\n\r\n### Hard gate (fan) — empty envelope shape\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview.fan.live\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"not_olares_one\",\r\n    \"note\": \"Fan / cooling integration is only available on Olares One devices\",\r\n    \"device_name\": \"DIY-PC\",\r\n    \"fetched_at\": \"...\"\r\n  },\r\n  \"items\": []\r\n}\r\n```\r\n\r\n### Soft gate (gpu) — advisory, not a block\r\n\r\nThe CLI ALWAYS calls HAMI for GPU verbs. The SPA-equivalent \"would-have-been-hidden\" reason is recorded as `meta.note` and (in table mode) as a one-liner on stderr. The actual `meta.empty` / `empty_reason` are determined by HAMI's response:\r\n\r\n```jsonc\r\n// non-admin caller, HAMI returned an empty list:\r\n{\r\n  \"kind\": \"dashboard.overview.gpu.list\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"no_gpu_detected\",\r\n    \"note\": \"GPU sidebar entry is hidden for non-admin profiles in the SPA; HAMI was queried directly\",\r\n    ...\r\n  },\r\n  \"items\": []\r\n}\r\n\r\n// any caller, HAMI returned 5xx:\r\n{\r\n  \"kind\": \"dashboard.overview.gpu.list\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"vgpu_unavailable\",\r\n    \"http_status\": 500,\r\n    \"error\": \"unknown request error\",\r\n    \"note\": \"HAMI vGPU controller responded with Internal Server Error; the integration is installed but unhealthy\",\r\n    ...\r\n  },\r\n  \"items\": []\r\n}\r\n```\r\n\r\n`empty_reason` taxonomy:\r\n\r\n| Reason | Where | Meaning |\r\n|---|---|---|\r\n| `not_olares_one` | fan default / live / curve (hard gate) | Active device's `device_name` is not `Olares One` |\r\n| `no_fan_integration` | fan live (HTTP 404 fallback) | `/user-service/api/mdns/olares-one/cpu-gpu` returned 404 |\r\n| `no_vgpu_integration` | gpu list / tasks / get / task (HTTP 404) | HAMI vGPU controller absent |\r\n| `vgpu_unavailable` | gpu list / tasks / get / task (HTTP 5xx) | HAMI installed but unhealthy. `meta.error` carries the upstream `message`; `meta.http_status` carries the original status |\r\n| `no_gpu_detected` | gpu list / tasks / get / task (HTTP 200, empty body) | HAMI healthy but device list / detail is empty |\r\n\r\nSoft-gate advisories never appear as `empty_reason`; they live in `meta.note`:\r\n\r\n| Note | When |\r\n|---|---|\r\n| `GPU sidebar entry is hidden for non-admin profiles in the SPA; HAMI was queried directly` | `meta.user.globalrole != platform-admin` (and not `admin`) |\r\n| `no node carries gpu.bytetrade.io/cuda-supported=true; SPA hides the GPU card. HAMI was queried directly` | Admin caller, but the cluster's node list has no CUDA-supported label |\r\n\r\nWhen BOTH a HAMI failure AND a soft advisory apply, `meta.note` is the concatenation `\"<advisory> | <hami-explanation>\"`.\r\n\r\n### HAMI wire-shape contract (paid in blood)\r\n\r\nHAMI's WebUI returns the payload **at the top level** for every endpoint we call — there is no `data` envelope around it. Adding one in the decoder yields a silent \"0 GPUs\" / \"0 tasks\" failure even on machines where the SPA shows devices.\r\n\r\n| Endpoint | Real wire shape | Decoder target |\r\n|---|---|---|\r\n| `POST /hami/api/vgpu/v1/gpus` | `{\"list\":[ {graphics...} ]}` | `struct{ List []map[string]any }`, return `raw.List` |\r\n| `POST /hami/api/vgpu/v1/containers` | `{\"items\":[ {task...} ]}` | `struct{ Items []map[string]any }`, return `raw.Items` |\r\n| `GET /hami/api/vgpu/v1/gpu?uid=…` | `{ ...graphics fields... }` | `map[string]any`, return verbatim |\r\n| `GET /hami/api/vgpu/v1/container?name=…&podUid=…` | `{ ...task fields... }` | `map[string]any`, return verbatim |\r\n\r\nThe fetchers in [`pkg/dashboard/gpu.go`](cli/pkg/dashboard/gpu.go) are pinned to this shape; `TestFetchGraphicsList_ParsesTopLevelList` / `TestFetchTaskList_ParsesTopLevelItems` / `TestFetchGraphicsDetail_ReturnsBodyAsIs` / `TestFetchTaskDetail_ReturnsBodyAsIs` enforce the contract (in `cli/pkg/dashboard/dashboard_test.go`).\r\n\r\nGPU/task field names that round-trip from HAMI (used in `Raw` envelopes — agents can pull anything not surfaced in the table):\r\n\r\n| `gpu list` raw fields | `gpu tasks` raw fields |\r\n|---|---|\r\n| `uuid`, `type`, `shareMode`, `nodeName`, `nodeUid`, `health` (bool), `coreUtilizedPercent` (already a percent — NOT a 0..1 ratio), `coreUsed`, `coreTotal`, `memoryUsed` / `memoryUtilized` / `memoryTotal` (all MiB), `memoryUtilizedPercent`, `vgpuUsed`, `vgpuTotal`, `power`, `powerLimit` (W), `temperature` (°C), `node`, `mode` | `name`, `status`, `podUid`, `nodeName`, `nodeUid`, `type`, `deviceShareModes[]`, `devicesCoreUtilizedPercent[]`, `devicesMemUtilized[]`, `allocatedDevices`, `allocatedCores`, `allocatedMem`, `createTime`, `startTime`, `endTime`, `namespace`, `deviceIds[]`, `resourcePool`, `flavor`, `priority`, `appName` |\r\n\r\nCommon renames the SPA does NOT do (we don't either — earlier revisions guessed at e.g. `modelName` / `hostname` / `usedMem` / `totalMem` / `coreUtilization` / `sharemode`; none of those field names appear on the wire):\r\n\r\n- \"model\" → `type` (yes, the field is literally called `type`)\r\n- \"host_node\" → `nodeName`\r\n- \"core_util\" → `coreUtilizedPercent` (already a percent)\r\n- \"vram total / used\" → `memoryTotal` / `memoryUsed` (both MiB; multiply by 1024² before passing to `format.GetDiskSize`)\r\n- \"mode\" → `shareMode` (string `\"0\"`/`\"1\"`/`\"2\"`/`\"3\"`; SPA's `VRAMModeLabel` enum only knows 0/1/2 — the CLI surfaces unknown values as `mode=<raw>`)\r\n\r\n### Monitor query endpoints (instant-vector / range-vector)\r\n\r\nThe wire-shape contract above ONLY covers the list/detail endpoints. The two **monitor query** endpoints behave the opposite way — they DO wrap the result in a single-level `data` envelope, matching the SPA's `InstantVectorResponse` / `RangeVectorResponse` types in [`packages/app/src/apps/dashboard/types/gpu.ts`](packages/app/src/apps/dashboard/types/gpu.ts).\r\n\r\n| Endpoint | Wire shape | Decoder |\r\n|---|---|---|\r\n| `POST /hami/api/vgpu/v1/monitor/query/instant-vector` | `{\"data\":[ {metric, value, timestamp} ]}` | `struct{ Data []instantVectorSample }`, return `raw.Data` |\r\n| `POST /hami/api/vgpu/v1/monitor/query/range-vector` | `{\"data\":[ {metric, values:[{value, timestamp}]} ]}` | `struct{ Data []rangeVectorSeries }`, return `raw.Data` |\r\n\r\nBoth fetchers (`fetchInstantVector` / `fetchRangeVector`) are pinned by `TestFetchInstantVector_ParsesDataEnvelope` / `TestFetchRangeVector_ParsesDataEnvelope`. Do NOT \"harmonise\" with the list endpoints — different microservices behind HAMI, different wire shapes, period.\r\n\r\nRange body shape: `{\"query\":\"<promql>\",\"range\":{\"start\":\"YYYY-MM-DD HH:mm:ss\",\"end\":\"…\",\"step\":\"30m\"}}`. `start`/`end` are SPA's `timeParse(date)` (local clock, no offset suffix); the `step` is computed by `gpuTrendStep(start, end)` — a 1:1 port of [`timeRangeFormate(diff_s, 16)`](packages/app/src/apps/controlPanelCommon/containers/Monitoring/utils.js) which preserves the SPA's preset table (10m→1m, 60m→10m, 480m→30m, 1440m→60m, etc.) and falls back to `floor(minutes/16)m` capped at `[1m..60m]`.\r\n\r\n**Window timezone contract (paid in blood)** — the offset-less string is re-parsed by the HAMI WebUI backend with `time.ParseInLocation` against the backend pod's `TZ` (today: `Asia/Shanghai`, see [`infrastructure/gpu/.olares/config/gpu/hami/values.yaml`](infrastructure/gpu/.olares/config/gpu/hami/values.yaml) `webui.env.backend.TZ`; verified empirically that Unix-epoch payloads are rejected with `data: []`, only the human-readable form works). The SPA gets this right by accident: a browser running in CST emits CST wall-clock strings, which the backend re-stamps as CST → correct UTC. CLI does NOT have that coincidence — a UTC host (typical Linux server / container) was sending UTC wall clock that HAMI re-stamped as CST, shifting `[start,end]` 8h into the future where Prometheus has no samples → the user-visible symptom was \"Gauges 正常 / Trends 全空，无 warnings\".\r\n\r\n**The fix splits the TZ in two**, on purpose:\r\n\r\n| Purpose | TZ | Helper | Knob |\r\n|---|---|---|---|\r\n| Render `meta.window.start/end`, table-side trend timestamps, table headers | `cf.Timezone` (user's preference, default `time.Local`) | `GPUTrendTimestampISO(t, cf.Timezone.Time())` | `--timezone <IANA>` |\r\n| Build the wire body sent to `/monitor/query/{instant,range}-vector` | `HAMIBackendTimezone()` (the backend pod's TZ, default `Asia/Shanghai`) | `GPUTrendTimestampWire(t)` | `OLARES_HAMI_BACKEND_TZ=<IANA>` env (hidden from `--help` — only operators of non-default HAMI deployments need it) |\r\n\r\nBoth helpers live in [`pkg/dashboard/gpu.go`](cli/pkg/dashboard/gpu.go); never reuse one for the other's job. The split lets a user in Los Angeles see PDT timestamps in their tables while the CLI still gets correct data from a Shanghai-deployed HAMI backend.\r\n\r\nPinned by:\r\n- `TestGPUTrendTimestampISO_RespectsTimezone`, `TestGPUTrendTimestampISO_NilTimezoneFallsBackToLocal` (pkg root) — display helper.\r\n- `TestGPUTrendTimestampWire_FormatsInHAMIBackendTZ`, `TestGPUTrendTimestampWire_RespectsEnvOverride`, `TestHAMIBackendTimezone_DefaultIsAsiaShanghai` (pkg root) — wire helper + env override + the \"default matches chart values.yaml\" drift alarm.\r\n- `TestBuildDetailFullEnvelope_WireAndRenderTZsAreSeparate`, `TestBuildTaskDetailFullEnvelope_WireAndRenderTZsAreSeparate` (pkg area) — end-to-end with cf.Timezone ≠ HAMI backend TZ; assert `meta.window` and captured wire body are in DIFFERENT zones.\r\n- `TestBuildDetailFullEnvelope_UTCHostStillGetsData` (pkg area) — the explicit \"original bug\" regression: cf.Timezone=UTC, HAMI backend TZ default → meta.window in UTC, wire in CST.\r\n\r\nIf you ever feel tempted to \"simplify\" by collapsing wire+render back into one TZ argument: don't. The two-table arrangement is the whole point.\r\n\r\n### `dashboard overview gpu` — SPA-aligned command surface\r\n\r\nThe cobra surface mirrors the SPA's `Overview2/GPU` route exactly — two parent commands matching the two tabs, each accepting an optional positional argument that drills into the per-row \"View details\" page:\r\n\r\n| Public command | Behavior | Kind | SPA equivalent |\r\n|---|---|---|---|\r\n| `gpu` (no subverb) | Sections envelope: graphics + tasks loaded concurrently | `dashboard.overview.gpu` (Shape B) | Both tabs preloaded on GPU overview page mount |\r\n| `gpu graphics` | List every discovered vGPU | `dashboard.overview.gpu.list` | Graphics management tab |\r\n| `gpu graphics <uuid>` | Per-GPU detail page (info + 6 gauges + 4 trends) | `dashboard.overview.gpu.detail.full` | GPUsDetails.vue (route /:uuid) |\r\n| `gpu tasks` | List every running vGPU task | `dashboard.overview.gpu.tasks` | Task management tab |\r\n| `gpu tasks <ref>` | Per-task detail page; `<ref>` = TASK column (`name`) **or** POD_UID column (`podUid`); auto-resolves the missing half + sharemode | `dashboard.overview.gpu.task.detail.full` | TasksDetails.vue (route /:name/:pod_uid) |\r\n\r\n**Naming**: the `graphics` / `tasks` parents use the SPA's tab labels (`Graphics management` / `Task management`) verbatim; the optional positional arg dispatches to the matching detail-page builder. SKILL forbids \"putting fan-out logic in cmd\" — argument-count routing is NOT fan-out, it's the canonical cmd responsibility, so the dispatch lives in `cmd/.../graphics.go` + `cmd/.../tasks.go`.\r\n\r\n**Auto-resolution for `gpu tasks <ref>`**: the SPA passes `podUid` + `deviceShareModes[0]` through the route param when the user clicks a TasksTable row. The CLI mirrors that by calling `pkgdashboard.FetchTaskList`, looking up the row by EITHER `podUid` (first pass — globally unique, wins on tie) OR `name` (second pass), and forwarding the resolved triple to `RunTaskDetail`. Both columns are surfaced in the bare `gpu` and `gpu tasks` listings, so copy-paste from either column \"just works\" — accepting only `name` was the original UX bug (users naturally copy the rightmost POD_UID column).\r\n\r\nAmbiguity (two rows share a name) surfaces as a typed error listing the candidate pod-uids; users re-run with one of those pod-uids (still `gpu tasks <pod-uid>` — pod-uids ARE valid refs, no need to invoke the legacy two-arg form):\r\n\r\n```\r\ntask name \"trainer\" matches 2 running pods (pod-A, pod-B); rerun with one of the\r\npod-uids: olares-cli dashboard overview gpu tasks <pod-uid>\r\n```\r\n\r\nNot-found in table mode prints `(no task matches \"<ref>\" — neither a name nor a pod-uid in HAMI's container list)` followed by a `hint: try one of: <name> (<pod-uid>), …` line listing up to 5 real candidates pulled from the same task list (so the user doesn't need to re-list to recover). JSON mode emits the standard `no_gpu_detected` empty envelope.\r\n\r\nPinned by `TestRunTaskByRef_ResolvesByPodUID` / `TestRunTaskByRef_NotFoundTableShowsHint` / `TestRunTaskByRef_AmbiguousErrorsWithCandidates`.\r\n\r\n#### Legacy (hidden + deprecated) cobra surface\r\n\r\nThe pre-refactor `list` / `get` / `detail` / `task` / `task-detail` cobra commands remain functional for back-compat with existing agent scripts. They are marked `Hidden: true` (removed from `--help`) and `Deprecated: \"use 'gpu graphics …' / 'gpu tasks …' instead\"`; cobra prints a one-line deprecation hint when they run. The underlying `Run*` package functions still exist (some agents may call them via the Go API) and their tests are intact:\r\n\r\n| Legacy cobra | Status | Replacement |\r\n|---|---|---|\r\n| `gpu list` | Hidden + deprecated | `gpu graphics` |\r\n| `gpu get <uuid>` | Hidden + deprecated. Note: emits `dashboard.overview.gpu.detail` (HAMI raw, single-shot) — NOT the same envelope as `gpu graphics <uuid>` (which emits `.detail.full` with gauges + trends). Kept because the raw-passthrough verb has no SPA analogue and removing it would break callers that pin on the flat HAMI body. | `gpu graphics <uuid>` (different envelope; opt-in semantic upgrade) |\r\n| `gpu detail <uuid>` | Hidden + deprecated | `gpu graphics <uuid>` (same envelope) |\r\n| `gpu task <name> <pod-uid>` | Hidden + deprecated. Same caveat as `gpu get`: emits raw `dashboard.overview.gpu.task.detail`. | `gpu tasks <name>` (different envelope; opt-in semantic upgrade) |\r\n| `gpu task-detail <name> <pod-uid>` | Hidden + deprecated. Still the only path that takes a manual `--sharemode` override; the new `gpu tasks <name>` always auto-detects sharemode from HAMI's task list. | `gpu tasks <name>` |\r\n\r\nSchema introspection (`dashboard schema -o json`) lists both the new surface and the legacy entries (suffixed `(deprecated)`) so an agent can discover the migration mapping programmatically.\r\n\r\n### `gpu graphics <uuid>` / `gpu tasks <name>` — full detail pages\r\n\r\nBoth parent-with-arg variants mirror the SPA's `Overview2/GPU/GPUsDetails.vue` and `Overview2/GPU/TasksDetails.vue` pages: basic info (HAMI `/v1/gpu` or `/v1/container`) + top gauges (instant-vector) + trend charts (range-vector), all assembled into a single sections envelope.\r\n\r\n| Command | Kind | Default window | Sections | PromQL count |\r\n|---|---|---|---|---|\r\n| `overview gpu graphics <uuid>` (legacy: `overview gpu detail <uuid>`) | `dashboard.overview.gpu.detail.full` | 8h | `detail` / `gauges` / `trends` | 4 instant + 6 range |\r\n| `overview gpu tasks <name>` (legacy: `overview gpu task-detail <name> <pod-uid> [--sharemode]`) | `dashboard.overview.gpu.task.detail.full` | 1h | `detail` / `gauges` / `trends` | 2 instant + 2 range |\r\n\r\nSection item ordering is fixed and stable across releases — agents can index by position OR by `raw.key`:\r\n\r\nGPU `detail.full` `gauges` keys (in order): `alloc_core` / `alloc_mem` / `util_core` / `util_mem` / `power` / `temperature`.\r\nGPU `detail.full` `trends` keys (in order): `alloc_trend` (lines: VRAM+GPU_MEMORY) / `usage_trend` (VRAM+GPU_MEMORY) / `power_trend` / `temp_trend`.\r\n\r\nTask `task-detail.full` `gauges` keys (in order): `compute_usage` / `vram_usage`.\r\nTask `task-detail.full` `trends` keys (in order): `compute_trend` / `vram_trend` (each one line, label `usage`).\r\n\r\n#### Display-vs-Raw formatting invariants (frozen)\r\n\r\nThe `detail` / `gauges` / `trends` sections each split formatted user-facing values from the wire shape. **`Item.Display` MUST mirror the SPA's chart/text rendering 1:1**, while `Item.Raw` keeps the un-rounded / un-formatted upstream values for agents that need arbitrary precision. Pinned by `format_test.go`. The contracts:\r\n\r\n1. **Detail card field whitelist** — `gpuDetailDisplayCopy` emits ONLY the 6 fields SPA's `GPUsDetails.vue:112-137 columns` array displays: `health` / `uuid` / `nodeName` / `type` / `device_no` / `driver_version`. `gpuTaskDetailDisplayCopy` emits ONLY the 8 (6 unconditional + 2 conditional) fields from `TasksDetails.vue:134-174`: `status` / `deviceIds` / `nodeName` / `type` / [`allocatedCores`] / [`allocatedMem`] / `appName` / `createTime` (allocations are emitted only when present in the HAMI body — matches SPA's `displayAllocation = sharemode !== TimeSlicing` gate). HAMI fields that look detail-worthy but **are not in the SPA's `columns` array** (`memoryTotal/Used`, `power`, `powerLimit`, `temperature`, `coreTotal/Used`, `vgpuTotal/Used`, `mode`, `shareMode`, `nodeUid`, etc.) are **deliberately excluded from `Display`** because HAMI's flat `/v1/gpu` body returns placeholders (`memoryUsed: 0`, `power: 0`, `temperature: 0`) — the live values live in instant-vector queries (the `gauges` section) and re-surfacing them under \"Detail\" misleads users into thinking the GPU is reporting zero everything. `Raw` still carries the entire HAMI body so agents that prefer the wire shape are unaffected. Pinned by `TestGpuDetailDisplayCopy_SPAFieldWhitelist` / `TestGpuTaskDetailDisplayCopy_SPAFieldWhitelist`.\r\n\r\n2. **Gauge values are `<number> <unit>` with lodash.round(2)** — `formatGaugeValue` mirrors `<MyGaugeChart unit=…>`. Examples: `23.89 Gi`, `7.87 W`, `46 ℃`. Unit-less ratio gauges (the SPA passes `unit: ' '`) emit just the number. `used_total` is `\"0.29/23.89\"` — both halves go through `roundedNumberString` (lodash.round(2) + trailing-zero strip). Pinned by `TestFormatGaugeValue` / `TestRoundedNumberString_StripsTrailingZeros`.\r\n\r\n3. **Trend point timestamps are SPA-format `YYYY-MM-DD HH:mm:ss` in `cf.Timezone`** — NOT the wire-shape epoch milliseconds (HAMI returns `\"1779636713000\"`). `formatTrendTimestamp` parses three observed shapes (epoch-ms 13-digit, epoch-s 10-digit, float-s with sub-second decimals) and emits the SPA's `timeParse` shape; unparseable inputs fall through to the raw string so agents can debug HAMI quirks. The wire epoch-ms integer is preserved on `Raw.points[i].timestamp_ms` (`int64`) so chart libraries that re-derive from epoch can round-trip without re-parsing. Pinned by `TestFormatTrendTimestamp_AllShapes` / `TestFormatTrendTimestamp_RespectsTimezone`.\r\n\r\n4. **Trend point values use lodash.round(value, 2)** — same call SPA's `pages/Overview2/GPU/config.ts:84` makes before handing the array to ECharts. The full-precision float lives on `Raw.points[i].value_raw`. Use `roundDP(v, dp)` (half-away-from-zero — matches lodash, NOT banker's). Pinned by `TestRoundDP_HalfAwayFromZero` / `TestRunTrend_RoundsValueTo2dp` (via the integration tests in `detail_test.go`).\r\n\r\nThe `formatTrendTimestamp` / `roundDP` / `roundedNumberString` / `formatGaugeValue` helpers are private to `pkg/dashboard/overview/gpu/specs.go` — they encode SPA-specific render conventions, NOT general-purpose formatters; resist promoting them to `pkgdashboard` until another area starts mirroring SPA chart axes.\r\n\r\nTime window flags reuse the existing `--since` / `--start` / `--end`. With neither set the SPA defaults apply (8h for GPU detail, 1h for task detail). `meta.window` carries `{since, start, end, step}` so an agent can replay the exact same query without recomputing.\r\n\r\n`--sharemode` on `task-detail` mirrors the SPA's `displayAllocation` toggle: when set to `\"2\"` (Time slicing) the CLI skips the allocation gauges, matching the SPA's hidden-rows behaviour. Other values (`\"0\"`, `\"1\"`) keep the full gauge set. Pinned by `TestBuildGPUTaskDetailFullEnvelope_TimeSlicingSkipsAllocation`.\r\n\r\nSoft-failure semantics — a single instant/range query failing does NOT abort the envelope:\r\n\r\n- the failed gauge / trend item carries `raw.error` (and `display.error` in the table);\r\n- the parent envelope's `meta.warnings` collects `gauges \"<key>\": <err>` / `trends \"<key>\": <err>` lines;\r\n- agents should branch on `len(meta.warnings) > 0` to detect partial data;\r\n- pinned by `TestBuildGPUDetailFullEnvelope_PartialFailure`.\r\n\r\nHard-failure semantics still apply on the **detail** fetch itself: HAMI 404 → `empty_reason=no_vgpu_integration`, 5xx → `vgpu_unavailable` (`meta.error` + `meta.http_status`), 200 with empty body → `no_gpu_detected`. Watch mode polls every 30s by default (`Recommended = 30 * time.Second` — the monitor endpoints are slower than plain Prom queries).\r\n\r\n`device_no` / `driver_version` are surfaced on the detail item — the CLI mirrors the SPA's trick of harvesting them from the `power_trend` range query's `metric` labels (HAMI doesn't ship them on `/v1/gpu`).\r\n\r\nBehavior contract:\r\n\r\n- **Exit code is `0`** for every gated / advisory / empty path — these are predictable states, not failures. Real failures still propagate via `meta.error` (or non-zero exit for top-level fatal errors).\r\n- **Stderr** carries a single human-readable line in non-JSON modes; JSON / NDJSON modes stay silent on stderr (agents read stdout exclusively). Examples:\r\n  - `fan is only available on Olares One devices (current: <device_name>)`\r\n  - `(advisory) GPU sidebar entry is hidden for non-admin profiles in the SPA; current user (<u>) is <role>`\r\n  - `(advisory) no node carries gpu.bytetrade.io/cuda-supported=true; SPA hides the GPU card. HAMI was queried directly`\r\n  - `gpu data temporarily unavailable: HAMI returned HTTP 500 (unknown request error)`\r\n- Capability calls (`/user-service/api/system/status`, `/kapis/.../nodes`, `/capi/app/detail`) are **cached per-Client** (`sync.Once` for system status; per-Client map for CUDA presence; `sync.Once` for user detail) — repeated calls inside an aggregated `dashboard overview` cost zero extra HTTP.\r\n- Hard gate runs **inside `Runner.Iter`** for `--watch` so a stream against the wrong device terminates with consistent empty envelopes per tick. Soft advisories likewise re-evaluate per iteration.\r\n\r\nAgent decision tree (recommended):\r\n\r\n```\r\ninspect meta.empty + meta.empty_reason + meta.note  →\r\n  not_olares_one        → skip the fan subtree on this device\r\n  no_*_integration      → upstream component absent (HAMI / capi system fan)\r\n  vgpu_unavailable      → transient: retry; check meta.http_status / meta.error\r\n  no_*_detected         → integration up but hardware empty\r\n  (none) + meta.note    → data is present but the SPA would have hidden the entry; surface the note to the user\r\n  (none) + (no note)    → items[] populated, proceed normally\r\n```\r\n\r\nForbidden:\r\n\r\n- Collapsing the empty reasons into a single \"not supported\" string.\r\n- Skipping the soft advisory call to \"save a request\" — the cache makes that a non-issue.\r\n- Treating a closed gate or HAMI 5xx as `meta.error`-only — `error` is reserved for the upstream message; the canonical signal is `empty_reason`.\r\n- Treating `vgpu_unavailable` as a hard error / non-zero exit — agents must keep iterating in `--watch`.\r\n\r\n## `--watch` semantics (frozen)\r\n\r\nDefault behaviour: **single execution**. `--watch` opts into HTTP-polling that mirrors the SPA's `setTimeout` cadence.\r\n\r\n```mermaid\r\nflowchart TD\r\n    A[invoke leaf cmd] --> B{--watch set?}\r\n    B -- no --> C[RunOnce → emit one envelope → exit 0/1]\r\n    B -- yes --> D[Runner.Run]\r\n    D --> E[RunOnce iteration N]\r\n    E --> F{success?}\r\n    F -- yes --> G[emit envelope NDJSON line<br/>meta.iteration=N]\r\n    F -- transient err --> H[emit envelope NDJSON<br/>meta.error set, items:[]]\r\n    F -- ErrTokenInvalidated / ErrNotLoggedIn --> X[exit non-zero immediately]\r\n    G --> I{--watch-iterations cap reached?}\r\n    H --> J{N consecutive errors >= 3?}\r\n    J -- yes --> X\r\n    I -- yes --> Y[exit 0]\r\n    J -- no --> K[wait --watch-interval]\r\n    I -- no --> K\r\n    K --> L{SIGINT?}\r\n    L -- yes --> Y\r\n    L -- no --> E\r\n```\r\n\r\nHard rules:\r\n\r\n- One envelope per iteration; nothing else on stdout. Pretty `-o json` is for one-shot mode; `--watch -o json` is **always NDJSON**.\r\n- Failed iteration ⇒ `items:[]` + `meta.error=\"<msg>\"`; never raise to process exit unless it's an auth-class error or the 3-consecutive cap trips.\r\n- `--since N` slides forward each iteration (window = [now-N, now]); `--start/--end` stays fixed across all iterations. Mutual exclusion is enforced in `CommonFlags.Validate`; do NOT remove the check.\r\n- `--watch-iterations` without `--watch` is rejected. Same for `--watch-timeout` / `--watch-interval`.\r\n- `Runner` exposes `Iter` (the per-iteration callback). It used to be called `Run` and was renamed to dodge a field/method-name clash with `Runner.Run()` — leave it.\r\n\r\n## Multi-user / `--user` flag\r\n\r\n- `Client.EnsureUser(ctx)` lazily fetches `/capi/app/detail` (once per process) and caches `globalrole`. Subsequent calls return the cached value.\r\n- `RequireAdmin(ctx)` is the **only** admit gate for `--user <olaresId>`; admin-only verbs (cross-tenant queries) MUST call it before issuing any upstream request.\r\n- `resolveTargetUser(ctx, c, requested)` is the helper for commands that accept a user via positional arg OR `--user` (today: `overview user [<username>]`). Empty / self → returns the active profile; explicit + admin → returns synthetic `UserDetail{Name: requested}`; explicit + non-admin → error mentioning \"platform-admin\".\r\n- The CLI never spoofs identity via headers — `--user` only changes upstream filter args; auth header is still the active profile's token.\r\n\r\n## Format pkg (frozen, JS-parity)\r\n\r\nThe `format` pkg is a **byte-for-byte port** of:\r\n\r\n- [`@bytetrade/core/src/monitoring.ts`](dashboard/packages/core/src/monitoring.ts) → `UnitTypes`, `GetValueByUnit`, `GetSuitableUnit`, `GetSuitableValue`\r\n- [`apps/packages/app/src/apps/dashboard/utils/`](dashboard/packages/app/src/apps/dashboard/utils/) → `WorthValue`, `FormatFrequency`, `ConvertTemperature`, `GetDiskSize`, `GetThroughput`, `FormatTime`, `GetMinuteValue`, `GetLastMonitoringData`, `GetResult`\r\n\r\nRules:\r\n\r\n1. The reference is the JS source. `format/testdata/golden-gen.js` runs the JS oracle directly and writes `golden.json`; `TestFormat_GoldenOracle` asserts byte equality. Any divergence is a Go bug, not a JS bug.\r\n2. Use [`github.com/shopspring/decimal`](https://github.com/shopspring/decimal) for arithmetic that the JS uses BigNumber for (e.g. `WorthValue`'s cascading thresholds, `FormatFrequency` rounding). Do **not** introduce a different decimal lib.\r\n3. Quirks ARE the contract. Examples:\r\n   - JS `toFixed(2)` of `1.005` returns `\"1.00\"` because of float repr — the Go port replicates that. Test cases must use unambiguous values like `1.234567 → 1.23`.\r\n   - `WorthValue` uses cascading `if` thresholds, not generic ladder logic — keep the structure.\r\n4. No display-side number formatting outside this pkg. Never `fmt.Sprintf(\"%.2f\", x)` in `overview.go` / `applications.go` for a value that is shown to the user (the few `%.2f` left in `overview.go` are pinned to non-user-facing CPU core counts and are documented inline).\r\n5. Temperature: JSON `raw` carries Celsius (the upstream value); `display` is rendered with `--temp-unit` (default Celsius). Never strip Celsius from `raw`.\r\n\r\n## Shared helpers (the only legal call sites)\r\n\r\nAll shared infrastructure helpers live under\r\n[`cli/pkg/dashboard/`](cli/pkg/dashboard/) (the pkg root) and are\r\nimported as `pkgdashboard \"github.com/beclab/Olares/cli/pkg/dashboard\"`\r\nby the pkg subpackages (`pkg/dashboard/<area>/...`) and — for the\r\nplumbing types like `CommonFlags` / `Client` / `ErrAlreadyReported` —\r\nalso by the cmd shells. The cmd shells additionally import\r\n`pkg<area> \"github.com/beclab/Olares/cli/pkg/dashboard/<area>\"` to\r\nreach `RunXxx`.\r\n\r\n### `pkg/dashboard/` (root) API surface — infrastructure + shared fetchers\r\n\r\n| Domain file | Exposed surface | Used by |\r\n|---|---|---|\r\n| `flags.go` | `CommonFlags` struct (raw + parsed fields), `Validate`, `ResolveWindow`, `OutputFormat`, `ParseOutputFormat` | every cmd leaf RunE (validate), every pkg `RunXxx` (read fields), every fetcher; cobra binding lives in cmd-root `options.go` |\r\n| `output.go` / `emit.go` / `envelope.go` | `Envelope` / `Item` / `Meta` / `TableColumn`, `WriteJSON` (NDJSON-aware), `WriteTable`, `HeadItems`, `DisplayString`, `EmitDefault`, `NewMeta` | every `BuildXxxEnvelope` / `WriteXxxTable` site under `pkg/dashboard/<area>/...` (hand-rolling `json.Marshal` / `fmt.Println` for envelope output is forbidden) |\r\n| `client.go` / `users.go` | `Client.DoJSON / DoEmpty / DoRaw`, `EnsureUser` (sync.Once), `RequireAdmin`, `ResolveTargetUser`, `UserDetail`, `EnsureSystemStatus`, `IsOlaresOne`, `HTTPError`, `ClassifyTransportErr`, `IsHTTPError`, `ErrAlreadyReported` | every HTTP call (direct `httpClient.Do` forbidden); admin gating in `pkg/dashboard/overview/user.go` |\r\n| `runner.go` | `Runner{Iter, RunOnce}`, `ParseStep` | every `--watch`-aware `RunXxx` (instantiated and driven from inside the pkg subpackage, never from cmd) |\r\n| `monitoring.go` | `FetchClusterMetrics(ctx,c,cf,metrics,window,now,instant)`, `FetchNodeMetrics`, `FetchUserMetric`, `MonitoringQuery`, `MonitoringWindow`, `DefaultClusterWindow`, `DefaultDetailWindow` | `pkg/dashboard/overview/{physical,cpu,memory,pods,user}.go`, `pkg/dashboard/overview/disk/{main,partitions}.go` |\r\n| `workloads.go` | `FetchWorkloadsMetrics(ctx,c,cf,req,window,now)`, `MergeWorkloadMetrics`, `WorkloadAggregate / WorkloadApp / WorkloadRequest`, `AggregateByDeployment`, `AggregateByNamespace`, `PodCountByDeployment`, `SortWorkloadAggregates` | only via `BuildRankingEnvelope` (do NOT call `FetchWorkloadsMetrics` directly from `<area>` subpackages) |\r\n| `apps.go` | `FetchAppsList`, `RawAppListItem` (with empty-`entrances[]` filter mirroring SPA `appsWithNamespace`) | only via `BuildRankingEnvelope` |\r\n| `ranking.go` | **`BuildRankingEnvelope(ctx,c,cf,target,sortBy,sortDir,now)`** — assembles a workload-grain ranking envelope | consumed by `pkg/dashboard/overview/ranking.go` and `pkg/dashboard/applications/l\n\nFile v2.1.0:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-dashboard\",\n  \"version\": \"2.1.0\",\n  \"publishedAt\": 1779960495523\n}\n\nFile v2.1.0:skill-card.md\n\n## Description: <br>\nGuides an agent to query Olares Dashboard Overview and Applications data through the authenticated olares-cli dashboard CLI, returning dashboard metrics, application details, watch output, and schema-aligned JSON or table views. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[pengpeng](https://clawhub.ai/user/pengpeng) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers and operators managing Olares environments use this skill to ask an agent for dashboard metric and application queries, command selection, and interpretation of olares-cli dashboard JSON or table output. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: Dashboard metrics, application lists, GPU or fan data, and admin cross-user views can expose private operational information. <br>\nMitigation: Use an account with only the intended Olares access level and treat returned dashboard data as private. <br>\nRisk: The skill requires authenticated olares-cli access and sensitive credentials may be involved in the surrounding workflow. <br>\nMitigation: Authenticate through the existing olares-cli profile flow and keep tokens or credentials out of prompts, logs, and generated examples. <br>\nRisk: Admin-capable profiles may allow broader cross-user inspection than the user intended. <br>\nMitigation: Prefer a least-privileged profile and use admin access only when the requested dashboard query requires it. <br>\n\n\n## Reference(s): <br>\n- [ClawHub skill page](https://clawhub.ai/pengpeng/olares-dashboard) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [Guidance, Shell commands, JSON, Markdown] <br>\n**Output Format:** [Markdown guidance with inline shell commands and expected JSON or table output descriptions.] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Requires an authenticated olares-cli profile and can surface private operational dashboard data.] <br>\n\n## Skill Version(s): <br>\n2.1.0 (source: frontmatter and server release evidence) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v2.0.0: 2 files, 24765 bytes\n\nFiles: SKILL.md (67858b), _meta.json (135b)\n\nFile v2.0.0:SKILL.md\n\n---\r\nname: olares-dashboard\r\nversion: 2.0.0\r\ndescription: \"olares-cli dashboard command tree — AI-agent-oriented mirror of the dashboard SPA's Overview2 + Applications2 routes. Covers: the strict dual-shape JSON envelope (leaf items{raw|display}+meta vs aggregated sections+meta), the stable `dashboard.<area>.<verb>` Kind constants under cli/pkg/dashboard/schema.go, the 1:1 Go port of @bytetrade/core/src/monitoring.ts (UnitTypes / GetValueByUnit / GetSuitableUnit / GetSuitableValue / WorthValue / FormatFrequency / ConvertTemperature / GetDiskSize / GetThroughput / FormatTime / GetMinuteValue / GetLastMonitoringData / GetResult) under cli/pkg/dashboard/format/, the shared fetch helpers (FetchClusterMetrics / FetchNodeMetrics / FetchUserMetric / FetchWorkloadsMetrics / BuildRankingEnvelope / FetchSystemIFS / FetchSystemFan / FetchGraphicsList / FetchTaskList / FetchGraphicsDetail / FetchTaskDetail) at the cli/pkg/dashboard/ root, the two-layer architecture with MIRRORED subpackage trees on both sides — cmd shell under cli/cmd/ctl/dashboard/ (directory tree mirrors command tree, one thin .go file per leaf whose RunE is just `pkg<area>.RunXxx(...)`, slim ≤70-line common.go per area carrying var common+prepareClient+unknownSubcommandRunE only) vs pkg layer split into cli/pkg/dashboard/ (infra-only: Envelope/Item/Meta/CommonFlags/Client/Runner/all fetchers/all aggregators/the schema bundle) PLUS cli/pkg/dashboard/<area>/ subpackages (cli/pkg/dashboard/applications/, cli/pkg/dashboard/overview/, cli/pkg/dashboard/overview/disk/, cli/pkg/dashboard/overview/fan/, cli/pkg/dashboard/overview/gpu/) owning every leaf's Run*/Build*Envelope/Write*Table + tests next to the code, with strict no-upward (subpackage→root only, root→nothing) and no-sibling (overview/disk ↛ overview/fan) imports inside pkg — the parent-command sections-envelope defaults (overview = physical+user+ranking, overview disk = main+per-disk partitions, overview fan = live+curve) emit through pkg <area>/default.go's RunDefault, the hardcoded FanCurveTable / FanSpeedMaxCPU / FanSpeedMaxGPU constants kept 1:1 with SPA Fan/config.ts, the `--watch` HTTP-polling semantics (interval / iterations / timeout / 3-consecutive-failure exit / NDJSON-per-iteration / SIGINT-graceful / ErrTokenInvalidated short-circuit), the `--since` (sliding window in --watch only) vs `--start --end` (fixed window) mutual-exclusion rule, the three-state empty-data semantics (HTTP 404 → no_<feature>_integration / HTTP 200 empty → no_<feature>_detected), the capability gates (overview fan = HARD-gated to Olares One via /user-service/api/system/status; overview gpu = SOFT advisory mirroring the SPA sidebar — admin role + CUDA-node check populates `meta.note` and a stderr advisory but lets the HAMI fetch proceed) producing `empty_reason ∈ {not_olares_one, no_fan_integration, no_vgpu_integration, vgpu_unavailable (HAMI 5xx — meta.error / meta.http_status), no_gpu_detected}` plus stderr hints, the `Client.EnsureUser` sync.Once globalrole cache + `Client.EnsureSystemStatus` + `IsOlaresOne` + `HasCUDANode` capability cache, plus `RequireAdmin` guard for `--user` and admin-only commands, and the iteration red-lines that forbid horizontal cmd-area imports (any cross-area share must be hoisted to cli/pkg/dashboard/ — the BuildRankingEnvelope precedent), upward pkg-root←pkg-subpackage imports, sibling pkg subpackage imports, putting leaves directly under cmd/ctl/dashboard/ root instead of an area subpackage, putting envelope/table/watch/fan-out logic in cmd, reintroducing trampoline-alias common.go files, or altering envelope/Kind/watch/sections-default/capability-gate semantics. Use whenever the user mentions `olares-cli dashboard`, asks 'show overview / cpu / memory / disk / pods / network / fan / gpu / ranking / applications / pods', wants JSON for an AI agent, mentions --watch / --since / --start / --end / --user / --head / --limit / --mode (memory) / --temp-unit / --timezone / --test-connectivity (network) on the dashboard tree, hits errors like 'mutually exclusive', 'watch requires', 'platform-admin', or sees `meta.empty=true` / `empty_reason ∈ {no_vgpu_integration, vgpu_unavailable, no_gpu_detected, not_olares_one, no_fan_integration}` / stderr lines like 'fan is only available on Olares One devices', '(advisory) GPU sidebar entry is hidden ...', or 'gpu data temporarily unavailable: HAMI returned HTTP 5xx ...'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli dashboard --help\"\r\n---\r\n\r\n# dashboard (overview + applications, AI-agent first)\r\n\r\n**CRITICAL — before doing ANYTHING in this subtree, MUST Read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, factory-injected `*http.Client`, and HTTP 401/403 recovery rules. Every dashboard verb depends on that foundation.**\r\n\r\n**This file is the source of truth for the dashboard subtree.** Any code generation, refactor or fix touching `cli/cmd/ctl/dashboard/**`, `cli/pkg/dashboard/**`, `cli/pkg/dashboard/format/**`, `cli/pkg/olares/id.go::DashboardURL`, or `cli/pkg/credential/{types,default_provider}.go::DashboardURL` MUST first Read this file end-to-end and respect the **Iteration red-lines** section. Do not \"modernize\", \"simplify\" or \"consolidate\" anything listed under [Frozen modules](#frozen-modules) without an explicit user-approved plan that supersedes this skill.\r\n\r\n## Scope (frozen)\r\n\r\nThe dashboard CLI mirrors **only** the SPA routes that are still wired in [`packages/app/src/apps/dashboard/router/routes.ts`](dashboard/packages/app/src/apps/dashboard/router/routes.ts):\r\n\r\n- `Overview2/IndexPage.vue` (overview tree) + the per-detail-page subtrees:\r\n  `Overview2/CPU`, `Memory`, `Disk`, `Pods`, `Network`, `Fan`, `GPU`.\r\n- `Applications2/IndexPage.vue` (applications tree).\r\n\r\nEverything else in the SPA (legacy `Overview/`, audit, settings, …) is **out of scope**. Do not add commands for deprecated routes; reject any request that asks for them with a pointer to the route file.\r\n\r\n## Architecture\r\n\r\nThe dashboard CLI is a **two-layer split with mirrored subpackage\r\ntrees on BOTH sides**:\r\n\r\n- **cmd shell** under `cli/cmd/ctl/dashboard/` — one directory per\r\n  command-tree parent, one `.go` file per leaf. Each leaf's RunE is a\r\n  thin `validate flags → prepareClient → pkg<area>.RunXxx(...)` body.\r\n  Per-area `common.go` is ≤ 70 lines and carries only `var common`,\r\n  `prepareClient`, `unknownSubcommandRunE`.\r\n- **pkg layer** under `cli/pkg/dashboard/`, split in two:\r\n  - the **pkg ROOT** (`cli/pkg/dashboard/*.go`) owns infrastructure\r\n    and shared fetchers — `Client`, `Runner`, `CommonFlags`,\r\n    `Envelope`/`Item`/`Meta`, `Fetch*` helpers, `BuildRankingEnvelope`,\r\n    capability gates, the `format` pkg, the schema bundle. **No\r\n    command-specific envelope builders or table writers live here.**\r\n  - the **pkg subpackages** (`cli/pkg/dashboard/applications/`,\r\n    `cli/pkg/dashboard/overview/`,\r\n    `cli/pkg/dashboard/overview/{disk,fan,gpu}/`) mirror the cmd\r\n    parent dirs. Each carries one `<verb>.go` file per leaf, exposing\r\n    a single `RunXxx(ctx, c, cf, ...)` entry plus its\r\n    `BuildXxxEnvelope` / `WriteXxxTable` triplet. Tests sit\r\n    alongside as `<verb>_test.go` (one shared `helpers_test.go` per\r\n    area for httptest fixtures).\r\n\r\nEvery fetch / merge / aggregate / format call goes through pkg —\r\neither the root infra or, for envelope assembly, the matching pkg\r\nsubpackage. The cmd shell never touches HTTP, never builds an\r\nenvelope, never writes a table, never runs `Runner` directly.\r\n\r\n```mermaid\r\nflowchart LR\r\n    User[olares-cli dashboard <verb>] --> Factory[cmdutil.Factory<br/>refreshingTransport]\r\n    Factory --> CmdShell[\"cli/cmd/ctl/dashboard/&lt;area&gt;/&lt;leaf&gt;.go<br/>(thin cobra shell:<br/>validate → prepareClient →<br/>pkg&lt;area&gt;.RunXxx)\"]\r\n    CmdShell --> PkgArea[\"cli/pkg/dashboard/&lt;area&gt;/&lt;leaf&gt;.go<br/>(RunXxx + BuildXxxEnvelope +<br/>WriteXxxTable + watch loop)\"]\r\n    PkgArea --> PkgRoot[\"cli/pkg/dashboard/<br/>(infra: Envelope, Client, Runner,<br/>fetchers, aggregators, gates, schema)\"]\r\n    PkgRoot -->|/capi/* + /kapis/* + /hami/*| BFF[\"dashboard.&lt;terminus&gt;<br/>(ks-apiserver-proxy)\"]\r\n    BFF -->|wildcard proxy| KsAPI[ks-apiserver]\r\n    PkgRoot --> Format[\"cli/pkg/dashboard/format<br/>(1:1 JS port)\"]\r\n    PkgArea --> Output[\"WriteJSON / WriteTable<br/>(pkgdashboard.*)\"]\r\n    Output --> User\r\n```\r\n\r\ncmd area ↔ pkg subpackage mapping — every cmd leaf is a thin\r\n`prepareClient` + `pkg<area>.RunXxx(...)` shell. The heavy logic\r\n(envelope builders, table writers, fan-out, watch loops) lives in a\r\nmirror subpackage under `cli/pkg/dashboard/<area>/`. The pkg root\r\n(`cli/pkg/dashboard/`) only owns infrastructure and shared fetchers.\r\n\r\n| cmd area | pkg subpackage entry points (`Run*`) | shared infra it pulls from `cli/pkg/dashboard/` |\r\n|---|---|---|\r\n| `cmd/ctl/dashboard/root.go` + `options.go` | n/a — pure cobra wiring + `bindPersistentFlags` | `flags.go` (`CommonFlags`), `runner.go`, `emit.go`, `output.go` |\r\n| `cmd/ctl/dashboard/applications/root.go` | `pkg/dashboard/applications/list.go` → `RunList` | `ranking.go`, `workloads.go`, `apps.go`, `format/...` |\r\n| `cmd/ctl/dashboard/overview/{physical,user,ranking,root}.go` | `pkg/dashboard/overview/{physical,user,ranking,default}.go` → `RunPhysical`/`RunUser`/`RunRanking`/`RunDefault` | `monitoring.go`, `workloads.go`, `ranking.go`, `users.go`, `numbers.go` |\r\n| `cmd/ctl/dashboard/overview/{cpu,memory,pods,network}.go` | `pkg/dashboard/overview/{cpu,memory,pods,network}.go` → `RunCPU`/`RunMemory`/`RunPods`/`RunNetwork` (built on `nodes.go`'s `RunPerNodeMetric` scaffold) | `monitoring.go`, `system.go` (network → capi `/system/ifs`), `numbers.go` |\r\n| `cmd/ctl/dashboard/overview/disk/{root,main,partitions}.go` | `pkg/dashboard/overview/disk/{default,main,partitions}.go` → `RunDefault`/`RunMain`/`RunPartitions` | `monitoring.go` (node\\_disk\\_*), `lsblk.go`, `numbers.go`, `format/...` |\r\n| `cmd/ctl/dashboard/overview/fan/{root,live,curve}.go` | `pkg/dashboard/overview/fan/{default,live,curve}.go` → `RunDefault`/`RunLive`/`RunCurve` | `system.go` (`FetchSystemFan`), `gates.go` (`GateOlaresOne`), `gpu.go` (graphics list co-render), `fan_curve.go` |\r\n| `cmd/ctl/dashboard/overview/gpu/{root,list,tasks,get,task,detail,task_detail}.go` | `pkg/dashboard/overview/gpu/{list,tasks,get,task,detail,task_detail}.go` → `RunList`/`RunTasks`/`RunGet`/`RunTask`/`RunDetail`/`RunTaskDetail` (+ `specs.go` for the gauge/trend catalogue and concurrent fan-out) | `gpu.go`, `gpu_format.go`, `gpu_query.go`, `gates.go`, `client.go` |\r\n| `cmd/ctl/dashboard/schema/root.go` | n/a — schema loader stays in pkg root | `schema.go` (loader + `go:embed` bundle) |\r\n\r\n- HTTP base = `ResolvedProfile.DashboardURL = https://dashboard.<localPrefix><terminusName>` (derived in [`cli/pkg/olares/id.go`](cli/pkg/olares/id.go) `DashboardURL`).\r\n- All requests go through factory's `refreshingTransport` — header injection + 401 retry happen for free; **dashboard code MUST NOT touch `X-Authorization`** or instantiate its own `http.Client`.\r\n- Per-command leaf / aggregate decision is hard-coded; do not flip them.\r\n- The shared `*pkgdashboard.CommonFlags` is bound once in cmd-root (`bindPersistentFlags(&common, cmd)`), then passed by pointer to every area factory (`overview.NewOverviewCommand(f, &common)` etc.). Each area subpackage stores it in a package-level `var common *pkgdashboard.CommonFlags` so leaf RunE bodies keep `common.Output / common.Validate() / common.Timezone` selectors.\r\n\r\n\r\n## JSON envelope (frozen shapes)\r\n\r\nEvery dashboard command emits exactly one of two shapes; the choice is fixed per command and MUST NOT be changed.\r\n\r\n### Shape A — leaf\r\n\r\nUsed by every command except parent commands with a sections envelope (see Shape B).\r\n\r\n```json\r\n{\r\n  \"kind\":  \"dashboard.<area>.<verb>\",\r\n  \"meta\":  { \"fetched_at\": \"...\", \"iteration\": 0, \"recommended_poll_seconds\": 60, \"empty\": false, \"empty_reason\": \"\", \"error\": \"\", \"http_status\": 200 },\r\n  \"items\": [ { \"raw\": { /* upstream-shape, units in source unit */ }, \"display\": { /* table-friendly strings */ } } ]\r\n}\r\n```\r\n\r\n- `raw` is the canonical machine-friendly shape: numbers as numbers, timestamps as Unix seconds, temperatures as raw Celsius (NOT converted by `--temp-unit`). Agents pin on `raw`.\r\n- `display` is human-presentation only; values are formatted via the `format` pkg with current `--temp-unit` / `--timezone`. **Agents MUST NOT pin on `display`** — it can change with locale / format-pkg fixes.\r\n- `meta.recommended_poll_seconds` — the page-level polling cadence the SPA uses; agents driving `--watch` SHOULD respect it.\r\n- `meta.iteration` — present in every `--watch` payload, 1-based.\r\n- `meta.empty` / `meta.empty_reason` — three-state empty data; see [Three-state empty data](#three-state-empty-data).\r\n- `meta.error` — only set on a failed `--watch` iteration or per-section failure inside Shape B.\r\n\r\n### Shape B — sections envelope\r\n\r\nUsed by **parent commands that aggregate multiple sub-views**:\r\n\r\n| Parent command           | Sections                            | Section kinds |\r\n|---|---|---|\r\n| `dashboard overview`     | `physical` / `user` / `ranking`     | `dashboard.overview.physical` / `.user` / `.ranking` |\r\n| `dashboard overview disk`| `main` / `partitions`               | `dashboard.overview.disk.main` / `.partitions` (the latter is itself a sections envelope, keyed by device) |\r\n| `dashboard overview fan` | `live` / `curve`                    | `dashboard.overview.fan.live` / `.curve` |\r\n| `dashboard schema`       | n/a — emits Shape A index           | `dashboard.schema.index` |\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview\",\r\n  \"meta\": { ... },\r\n  \"sections\": {\r\n    \"physical\": { \"kind\": \"dashboard.overview.physical\", \"meta\": {...}, \"items\": [...] },\r\n    \"user\":     { \"kind\": \"dashboard.overview.user\",     \"meta\": {...}, \"items\": [...] },\r\n    \"ranking\":  { \"kind\": \"dashboard.overview.ranking\",  \"meta\": {...}, \"items\": [...] }\r\n  }\r\n}\r\n```\r\n\r\n- Sections are fetched concurrently; a single failed section degrades to `meta.error=\"...\"` on that section, the others still return.\r\n- The section names (the Section column above) are part of the contract — do not rename or drop.\r\n\r\n### Kind constants\r\n\r\nDeclared in [`cli/pkg/dashboard/schema.go`](cli/pkg/dashboard/schema.go). **`AllKinds()` MUST stay 1:1 with the actual command surface.** Adding a command means:\r\n\r\n1. Add a `KindXxx` constant in `pkg/dashboard/schema.go`.\r\n2. Append it to `AllKinds()` (same file).\r\n3. Add `cli/pkg/dashboard/schemas/<kind-without-namespace>.json`.\r\n4. Register in `LoadSchemaIndex()` (same file).\r\n5. Bump golden tests if the new shape appears in fixtures.\r\n6. Add a leaf file in the matching `cmd/ctl/dashboard/<area>/` subdirectory and AddCommand it from the area's `root.go`.\r\n\r\nDo NOT rename existing Kind values — agents rely on string equality.\r\n\r\nThe authoritative listing of every command, its envelope shape, and\r\nits default-action semantics is the cmd/pkg directory tree itself\r\n(see [File layout](#file-layout-frozen--directory-tree-mirrors-command-tree-on-both-sides)) and `olares-cli dashboard --help`. Run `olares-cli dashboard schema -o\r\njson` for the live `dashboard.<area>.<verb>` Kind index.\r\n\r\n## Frozen modules\r\n\r\nThe following are **load-bearing**. Touching them requires a separate user-confirmed plan; otherwise reject the request with this list.\r\n\r\n| Frozen module | Why it's frozen | Where |\r\n|---|---|---|\r\n| Two-layer split with mirrored `cmd/<area>` ↔ `pkg/dashboard/<area>` subpackages: cmd is a thin cobra shell that calls `pkg<area>.RunXxx(...)`; pkg owns every envelope builder, table writer, watch loop and fan-out. The pkg root holds only infrastructure + shared fetchers. | Architectural decision (P1–P7); each leaf's heavy logic is testable directly via `RunXxx` without dragging cobra in, and cmd-side line budgets stay flat as the surface grows | [`cli/cmd/ctl/dashboard/`](cli/cmd/ctl/dashboard/) + [`cli/pkg/dashboard/`](cli/pkg/dashboard/) |\r\n| Directory tree mirrors command tree on BOTH sides | One file per leaf in cmd (`<verb>.go`) AND in the matching pkg subpackage (`<verb>.go` exposing one `RunXxx` entry); `root.go`+`common.go` per cmd parent, `default.go` per pkg parent — prevents drift between `dashboard --help`, the cmd `ls`, and the pkg `ls` | every cmd subdirectory under [`cli/cmd/ctl/dashboard/`](cli/cmd/ctl/dashboard/) and every pkg subpackage under [`cli/pkg/dashboard/<area>/`](cli/pkg/dashboard/) |\r\n| Kind constants + `AllKinds()` | Public agent contract | [`pkg/dashboard/schema.go`](cli/pkg/dashboard/schema.go) |\r\n| Envelope shapes A / B | Public agent contract | [`pkg/dashboard/output.go`](cli/pkg/dashboard/output.go) |\r\n| `format` pkg signatures + JS-parity behaviour | Backed by `golden.json` oracle; even rounding quirks are intentional | [`pkg/dashboard/format/format.go`](cli/pkg/dashboard/format/format.go) |\r\n| `FetchClusterMetrics` / `FetchNodeMetrics` / `FetchUserMetric` shared fetchers | Single upstream call powers multiple commands; must not fork | [`pkg/dashboard/monitoring.go`](cli/pkg/dashboard/monitoring.go) |\r\n| `FetchWorkloadsMetrics` dual-fetch + merge + `BuildRankingEnvelope` | Single pkg function powers `overview ranking` + `applications` first-row parity; keep the dual-fetch shape | [`pkg/dashboard/workloads.go`](cli/pkg/dashboard/workloads.go), [`pkg/dashboard/ranking.go`](cli/pkg/dashboard/ranking.go) |\r\n| `FetchSystemIFS` / `FetchSystemFan` / graphics+task helpers | Wire shape pinned in tests; capi vs hami URL prefixes are part of the contract | [`pkg/dashboard/system.go`](cli/pkg/dashboard/system.go), [`pkg/dashboard/gpu.go`](cli/pkg/dashboard/gpu.go) |\r\n| `FanCurveTable` + `FanSpeedMaxCPU` + `FanSpeedMaxGPU` constants | 1:1 with SPA `Fan/config.ts.tableData`; CLI ships them as Go constants because the SPA does too | [`pkg/dashboard/fan_curve.go`](cli/pkg/dashboard/fan_curve.go) |\r\n| Parent-command default = sections envelope (overview, overview disk, overview fan) | Plan decision; consumers branch on `kind == \"dashboard.overview.*\"` to know whether to expect `sections` or `items` | [`overview/root.go`](cli/cmd/ctl/dashboard/overview/root.go), [`overview/disk/root.go`](cli/cmd/ctl/dashboard/overview/disk/root.go), [`overview/fan/root.go`](cli/cmd/ctl/dashboard/overview/fan/root.go) |\r\n| `Runner` + `--watch` semantics (interval / iterations / timeout / 3-consecutive-failure exit / NDJSON per-iteration / SIGINT graceful / `ErrTokenInvalidated` short-circuit) | Plan decision; agents script around it | [`pkg/dashboard/runner.go`](cli/pkg/dashboard/runner.go) |\r\n| `--since` vs `--start --end` mutual-exclusion + sliding-window rules | Plan decision (`since_slides_abs_repeats`) | [`pkg/dashboard/flags.go`](cli/pkg/dashboard/flags.go) `Validate` / `ResolveWindow` |\r\n| Three-state empty-data semantics | Distinguishes \"feature not installed\" from \"no hardware\" without throwing | `overview gpu {list,tasks}` / `overview fan live` (cmd) ↔ [`pkg/dashboard/gates.go`](cli/pkg/dashboard/gates.go) |\r\n| `Client.EnsureUser` sync.Once cache + `RequireAdmin` / `ResolveTargetUser` guards | Auth correctness for `--user` / `overview user <name>` | [`pkg/dashboard/client.go`](cli/pkg/dashboard/client.go) |\r\n| `ResolvedProfile.DashboardURL` derivation chain | URL is derived, not configured | [`cli/pkg/olares/id.go`](cli/pkg/olares/id.go), [`cli/pkg/credential/types.go`](cli/pkg/credential/types.go), [`cli/pkg/credential/default_provider.go`](cli/pkg/credential/default_provider.go) |\r\n| Cobra `SilenceUsage = true` + `SilenceErrors = true` on every dashboard cmd | Clean machine-readable error text; pinned by `TestAllLeafCommandsSilenced` in cmd-root | every `cobra.Command` literal under `cmd/ctl/dashboard/` |\r\n| Default output = `table` | Plan decision (`table_default`) | [`pkg/dashboard/output.go`](cli/pkg/dashboard/output.go) `ParseOutputFormat` |\r\n| **Dependency direction (cmd side)** — `cli/cmd/ctl/dashboard/<area>` may import `cli/pkg/dashboard`, `cli/pkg/dashboard/<area>/...`, and its own cobra children only; **horizontal cmd↔cmd imports are FORBIDDEN**. The cmd shell is forbidden from owning envelope shapes / table writers / fan-out — those belong in the matching `pkg/dashboard/<area>` subpackage. | Architectural integrity; prevents the circular-import class of bugs that killed the 1.x layout, and keeps the cmd surface trivially line-budgeted | every `import` block under [`cli/cmd/ctl/dashboard/`](cli/cmd/ctl/dashboard/) |\r\n| **Dependency direction (pkg side)** — `cli/pkg/dashboard/<area>/...` may import `cli/pkg/dashboard` (the root infra: `Client`, `Runner`, fetchers, `format`, `numbers`, `gpu_format`, `gpu_query`, `gates`, `schema`, `output`, `emit`, `lsblk`, `fan_curve`); the pkg root MUST NOT import any subpackage (no upward import). Sibling subpackages (e.g. `overview/disk` ↔ `overview/fan`) MUST NOT import each other — if two areas need the same heavy helper, hoist it to `cli/pkg/dashboard/` (the `BuildRankingEnvelope` precedent). | Keeps the pkg root independently testable + reusable, and keeps each `<area>` subpackage a self-contained mirror of its cmd sibling | every `import` block under [`cli/pkg/dashboard/`](cli/pkg/dashboard/) |\r\n\r\n## Three-state empty data\r\n\r\nOptional hardware (GPU / fan) and optional integrations have three legitimate \"empty\" states; the CLI distinguishes them in `meta.empty_reason` so agents can branch without parsing prose.\r\n\r\n| Upstream response | `meta.empty` | `meta.empty_reason` | Example |\r\n|---|---|---|---|\r\n| HTTP 404 | `true` | `no_<feature>_integration` | HAMI vGPU not installed → `dashboard overview gpu list` |\r\n| HTTP 200, empty body / empty metric | `true` | `no_<feature>_detected` | Server has no fans → `dashboard overview fan live` |\r\n| HTTP 200, non-empty | `false` | `\"\"` | Normal case |\r\n| Any other 4xx/5xx | n/a (envelope `meta.error`) | n/a | Real failure → propagate via watch error path |\r\n\r\nForbidden: turning a 404 into an error, or merging the two reasons into a single \"empty=true\" — agents currently key on the reason.\r\n\r\n## Capability gates (overview fan / overview gpu)\r\n\r\nThe fan / gpu subtrees mirror the SPA's gates — but with **two different\r\nstrengths** because the SPA itself treats them differently:\r\n\r\n| Subtree | Gate strength | Source check |\r\n|---|---|---|\r\n| `overview fan *` (default / live / curve) | **Hard** — empty envelope before any fetch | [`Fan.ts:67`](dashboard/packages/app/src/apps/dashboard/stores/Fan.ts) (`isOlaresOneDevice`); fan data is meaningless on non-Olares One hardware |\r\n| `overview gpu *` (list / tasks / get / task) | **Soft** — advisory `meta.note` + stderr hint, then proceed with the HAMI fetch | [`ClusterResource.vue:232+278`](dashboard/packages/app/src/apps/dashboard/pages/Overview2/ClusterResource.vue) hides the **sidebar entry** for non-admins / no-CUDA clusters; the GPU detail pages themselves do **not** hard-block |\r\n\r\n### Hard gate (fan) — empty envelope shape\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview.fan.live\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"not_olares_one\",\r\n    \"note\": \"Fan / cooling integration is only available on Olares One devices\",\r\n    \"device_name\": \"DIY-PC\",\r\n    \"fetched_at\": \"...\"\r\n  },\r\n  \"items\": []\r\n}\r\n```\r\n\r\n### Soft gate (gpu) — advisory, not a block\r\n\r\nThe CLI ALWAYS calls HAMI for GPU verbs. The SPA-equivalent \"would-have-been-hidden\" reason is recorded as `meta.note` and (in table mode) as a one-liner on stderr. The actual `meta.empty` / `empty_reason` are determined by HAMI's response:\r\n\r\n```jsonc\r\n// non-admin caller, HAMI returned an empty list:\r\n{\r\n  \"kind\": \"dashboard.overview.gpu.list\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"no_gpu_detected\",\r\n    \"note\": \"GPU sidebar entry is hidden for non-admin profiles in the SPA; HAMI was queried directly\",\r\n    ...\r\n  },\r\n  \"items\": []\r\n}\r\n\r\n// any caller, HAMI returned 5xx:\r\n{\r\n  \"kind\": \"dashboard.overview.gpu.list\",\r\n  \"meta\": {\r\n    \"empty\": true,\r\n    \"empty_reason\": \"vgpu_unavailable\",\r\n    \"http_status\": 500,\r\n    \"error\": \"unknown request error\",\r\n    \"note\": \"HAMI vGPU controller responded with Internal Server Error; the integration is installed but unhealthy\",\r\n    ...\r\n  },\r\n  \"items\": []\r\n}\r\n```\r\n\r\n`empty_reason` taxonomy:\r\n\r\n| Reason | Where | Meaning |\r\n|---|---|---|\r\n| `not_olares_one` | fan default / live / curve (hard gate) | Active device's `device_name` is not `Olares One` |\r\n| `no_fan_integration` | fan live (HTTP 404 fallback) | `/user-service/api/mdns/olares-one/cpu-gpu` returned 404 |\r\n| `no_vgpu_integration` | gpu list / tasks / get / task (HTTP 404) | HAMI vGPU controller absent |\r\n| `vgpu_unavailable` | gpu list / tasks / get / task (HTTP 5xx) | HAMI installed but unhealthy. `meta.error` carries the upstream `message`; `meta.http_status` carries the original status |\r\n| `no_gpu_detected` | gpu list / tasks / get / task (HTTP 200, empty body) | HAMI healthy but device list / detail is empty |\r\n\r\nSoft-gate advisories never appear as `empty_reason`; they live in `meta.note`:\r\n\r\n| Note | When |\r\n|---|---|\r\n| `GPU sidebar entry is hidden for non-admin profiles in the SPA; HAMI was queried directly` | `meta.user.globalrole != platform-admin` (and not `admin`) |\r\n| `no node carries gpu.bytetrade.io/cuda-supported=true; SPA hides the GPU card. HAMI was queried directly` | Admin caller, but the cluster's node list has no CUDA-supported label |\r\n\r\nWhen BOTH a HAMI failure AND a soft advisory apply, `meta.note` is the concatenation `\"<advisory> | <hami-explanation>\"`.\r\n\r\n### HAMI wire-shape contract (paid in blood)\r\n\r\nHAMI's WebUI returns the payload **at the top level** for every endpoint we call — there is no `data` envelope around it. Adding one in the decoder yields a silent \"0 GPUs\" / \"0 tasks\" failure even on machines where the SPA shows devices.\r\n\r\n| Endpoint | Real wire shape | Decoder target |\r\n|---|---|---|\r\n| `POST /hami/api/vgpu/v1/gpus` | `{\"list\":[ {graphics...} ]}` | `struct{ List []map[string]any }`, return `raw.List` |\r\n| `POST /hami/api/vgpu/v1/containers` | `{\"items\":[ {task...} ]}` | `struct{ Items []map[string]any }`, return `raw.Items` |\r\n| `GET /hami/api/vgpu/v1/gpu?uuid=…` | `{ ...graphics fields... }` | `map[string]any`, return verbatim |\r\n| `GET /hami/api/vgpu/v1/container?name=…&podUid=…` | `{ ...task fields... }` | `map[string]any`, return verbatim |\r\n\r\nThe fetchers in [`pkg/dashboard/gpu.go`](cli/pkg/dashboard/gpu.go) are pinned to this shape; `TestFetchGraphicsList_ParsesTopLevelList` / `TestFetchTaskList_ParsesTopLevelItems` / `TestFetchGraphicsDetail_ReturnsBodyAsIs` / `TestFetchTaskDetail_ReturnsBodyAsIs` enforce the contract (in `cli/pkg/dashboard/dashboard_test.go`).\r\n\r\nGPU/task field names that round-trip from HAMI (used in `Raw` envelopes — agents can pull anything not surfaced in the table):\r\n\r\n| `gpu list` raw fields | `gpu tasks` raw fields |\r\n|---|---|\r\n| `uuid`, `type`, `shareMode`, `nodeName`, `nodeUid`, `health` (bool), `coreUtilizedPercent` (already a percent — NOT a 0..1 ratio), `coreUsed`, `coreTotal`, `memoryUsed` / `memoryUtilized` / `memoryTotal` (all MiB), `memoryUtilizedPercent`, `vgpuUsed`, `vgpuTotal`, `power`, `powerLimit` (W), `temperature` (°C), `node`, `mode` | `name`, `status`, `podUid`, `nodeName`, `nodeUid`, `type`, `deviceShareModes[]`, `devicesCoreUtilizedPercent[]`, `devicesMemUtilized[]`, `allocatedDevices`, `allocatedCores`, `allocatedMem`, `createTime`, `startTime`, `endTime`, `namespace`, `deviceIds[]`, `resourcePool`, `flavor`, `priority`, `appName` |\r\n\r\nCommon renames the SPA does NOT do (we don't either — earlier revisions guessed at e.g. `modelName` / `hostname` / `usedMem` / `totalMem` / `coreUtilization` / `sharemode`; none of those field names appear on the wire):\r\n\r\n- \"model\" → `type` (yes, the field is literally called `type`)\r\n- \"host_node\" → `nodeName`\r\n- \"core_util\" → `coreUtilizedPercent` (already a percent)\r\n- \"vram total / used\" → `memoryTotal` / `memoryUsed` (both MiB; multiply by 1024² before passing to `format.GetDiskSize`)\r\n- \"mode\" → `shareMode` (string `\"0\"`/`\"1\"`/`\"2\"`/`\"3\"`; SPA's `VRAMModeLabel` enum only knows 0/1/2 — the CLI surfaces unknown values as `mode=<raw>`)\r\n\r\n### Monitor query endpoints (instant-vector / range-vector)\r\n\r\nThe wire-shape contract above ONLY covers the list/detail endpoints. The two **monitor query** endpoints behave the opposite way — they DO wrap the result in a single-level `data` envelope, matching the SPA's `InstantVectorResponse` / `RangeVectorResponse` types in [`packages/app/src/apps/dashboard/types/gpu.ts`](packages/app/src/apps/dashboard/types/gpu.ts).\r\n\r\n| Endpoint | Wire shape | Decoder |\r\n|---|---|---|\r\n| `POST /hami/api/vgpu/v1/monitor/query/instant-vector` | `{\"data\":[ {metric, value, timestamp} ]}` | `struct{ Data []instantVectorSample }`, return `raw.Data` |\r\n| `POST /hami/api/vgpu/v1/monitor/query/range-vector` | `{\"data\":[ {metric, values:[{value, timestamp}]} ]}` | `struct{ Data []rangeVectorSeries }`, return `raw.Data` |\r\n\r\nBoth fetchers (`fetchInstantVector` / `fetchRangeVector`) are pinned by `TestFetchInstantVector_ParsesDataEnvelope` / `TestFetchRangeVector_ParsesDataEnvelope`. Do NOT \"harmonise\" with the list endpoints — different microservices behind HAMI, different wire shapes, period.\r\n\r\nRange body shape: `{\"query\":\"<promql>\",\"range\":{\"start\":\"YYYY-MM-DD HH:mm:ss\",\"end\":\"…\",\"step\":\"30m\"}}`. `start`/`end` are SPA's `timeParse(date)` (local clock, no offset suffix); the `step` is computed by `gpuTrendStep(start, end)` — a 1:1 port of [`timeRangeFormate(diff_s, 16)`](packages/app/src/apps/controlPanelCommon/containers/Monitoring/utils.js) which preserves the SPA's preset table (10m→1m, 60m→10m, 480m→30m, 1440m→60m, etc.) and falls back to `floor(minutes/16)m` capped at `[1m..60m]`.\r\n\r\n### `dashboard overview gpu detail <uuid>` / `task-detail <name> <pod-uid>` — full detail pages\r\n\r\nTwo new commands mirror the SPA's `Overview2/GPU/GPUsDetails.vue` and `Overview2/GPU/TasksDetails.vue` pages: basic info (HAMI `/v1/gpu` or `/v1/container`) + top gauges (instant-vector) + trend charts (range-vector), all assembled into a single sections envelope.\r\n\r\n| Command | Kind | Default window | Sections | PromQL count |\r\n|---|---|---|---|---|\r\n| `overview gpu detail <uuid>` | `dashboard.overview.gpu.detail.full` | 8h | `detail` / `gauges` / `trends` | 4 instant + 6 range |\r\n| `overview gpu task-detail <name> <pod-uid> [--sharemode]` | `dashboard.overview.gpu.task.detail.full` | 1h | `detail` / `gauges` / `trends` | 2 instant + 2 range |\r\n\r\nSection item ordering is fixed and stable across releases — agents can index by position OR by `raw.key`:\r\n\r\nGPU `detail.full` `gauges` keys (in order): `alloc_core` / `alloc_mem` / `util_core` / `util_mem` / `power` / `temperature`.\r\nGPU `detail.full` `trends` keys (in order): `alloc_trend` (lines: core+memory) / `usage_trend` (core+memory) / `power_trend` / `temp_trend`.\r\n\r\nTask `task-detail.full` `gauges` keys (in order): `compute_usage` / `vram_usage`.\r\nTask `task-detail.full` `trends` keys (in order): `compute_trend` / `vram_trend` (each one line, label `usage`).\r\n\r\nTime window flags reuse the existing `--since` / `--start` / `--end`. With neither set the SPA defaults apply (8h for GPU detail, 1h for task detail). `meta.window` carries `{since, start, end, step}` so an agent can replay the exact same query without recomputing.\r\n\r\n`--sharemode` on `task-detail` mirrors the SPA's `displayAllocation` toggle: when set to `\"2\"` (Time slicing) the CLI skips the allocation gauges, matching the SPA's hidden-rows behaviour. Other values (`\"0\"`, `\"1\"`) keep the full gauge set. Pinned by `TestBuildGPUTaskDetailFullEnvelope_TimeSlicingSkipsAllocation`.\r\n\r\nSoft-failure semantics — a single instant/range query failing does NOT abort the envelope:\r\n\r\n- the failed gauge / trend item carries `raw.error` (and `display.error` in the table);\r\n- the parent envelope's `meta.warnings` collects `gauges \"<key>\": <err>` / `trends \"<key>\": <err>` lines;\r\n- agents should branch on `len(meta.warnings) > 0` to detect partial data;\r\n- pinned by `TestBuildGPUDetailFullEnvelope_PartialFailure`.\r\n\r\nHard-failure semantics still apply on the **detail** fetch itself: HAMI 404 → `empty_reason=no_vgpu_integration`, 5xx → `vgpu_unavailable` (`meta.error` + `meta.http_status`),","readmeExcerpt":"Skill: Olares Dashboard (olares-cli dashboard) Owner: olares Summary: Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olare... Tags: latest:4.0.1 Version history: v4.0.1 | 2026-05-29T05:54:52.953Z | user Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0) v4.0.0 | 2026-05-29T04:53:57.489Z | use","codeSnippets":[],"executableExamples":[],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\r\nname: olares-dashboard\r\nversion: 4.0.1\r\ndescription: \"Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olares ID. An AI-agent-first JSON mirror with a strict dual-shape envelope (leaf items / aggregated sections), stable Kind constants, and human-readable table output. Covers Overview leaves (CPU, memory, disk, network, pods, fan, GPU, ranking), Applications listing and detail, the --watch HTTP-polling loop (interval / iterations / timeout / NDJSON-per-iteration / SIGINT-graceful), --since (sliding window) vs --start/--end (fixed window) selection, three-state empty-data semantics (no_<feature>_integration / no_<feature>_detected / vgpu_unavailable), and capability gates (fan is hard-gated to Olares One hardware; GPU is a soft advisory mirroring the SPA sidebar). Use when the user mentions Olares, Olares ID, Olares Dashboard, Olares One (fan / cooling), olares-cli dashboard, the Olares overview / applications view, 'show CPU / memory / disk / pods / network / fan / GPU / ranking on Olares', wants JSON for an AI agent, or hits errors like 'fan is only available on Olares One devices' or 'gpu data temporarily unavailable'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli dashboard --help\"\r\n---\r\n\r\n# dashboard (overview + applications, AI-agent first)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, automatic token refresh, and the auth-error recovery table.**\r\n\r\n> **Source of truth for flags is always `olares-cli dashboard --help` (global flags) and `olares-cli dashboard <verb> --help` (per-leaf flags).** This file only carries what `--help` cannot give: the dual-shape JSON envelope contract, three-state empty-data semantics, capability gates, watch / window rules, and the verb index.\r\n\r\n## Routing\r\n\r\nThis subtree is an **AI-agent-first JSON mirror of the Olares Dashboard SPA's Overview and Applications routes**. Use it when:\r\n\r\n- The user asks for runtime metrics: CPU / memory / disk / pods / network / fan / GPU.\r\n- The user wants the workload-grain or application-grain resource ranking.\r\n- The user wants the JSON form of what the SPA Overview / Applications pages show.\r\n- The user wants `--watch` for live-tailing one of the above.\r\n\r\nSibling-skill routing:\r\n\r\n| User intent | Use instead |\r\n|---|---|\r\n| \"What pods / workloads / nodes / namespaces exist?\" (object inventory) | [`olares-cluster`](../olares-cluster/SKILL.md) |\r\n| \"Install / start / stop an Olares app\" | [`olares-market`](../olares-market/SKILL.md) |\r\n| \"Configure my user settings\" | [`olares-settings`](../olares-settings/SKILL.md) |\r\n| \"Browse files / drives\" | [`olares-files`](../olares-files/SKILL.md) |\r\n\r\n> **Mental model:** dashboard answers *\"what's the resource usage and health\"*. For inventory and lifecycle, route elsewhere.\r\n\r\n## JSON envel"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-dashboard\",\n  \"version\": \"4.0.1\",\n  \"publishedAt\": 1780034092953\n}"},{"path":"references/olares-dashboard-overview.md","content":"# dashboard overview\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shapes:** `olares-cli dashboard overview --help` and `olares-cli dashboard overview <section> --help`.\r\n\r\nThe most flag-rich verb in this subtree. Three \"framing\" sections (`physical` / `user` / `ranking`) live at the top of the overview tree; seven \"hardware\" sections (`cpu` / `memory` / `disk` / `pods` / `network` / `fan` / `gpu`) live alongside.\r\n\r\n## Default action (no subverb)\r\n\r\n```bash\r\nolares-cli dashboard overview\r\nolares-cli dashboard overview -o json\r\n```\r\n\r\nEmits a **Shape B sections envelope** with the three \"framing\" sections fetched concurrently:\r\n\r\n```json\r\n{\r\n  \"kind\": \"dashboard.overview\",\r\n  \"sections\": {\r\n    \"physical\": { \"kind\": \"dashboard.overview.physical\", \"items\": [...] },\r\n    \"user\":     { \"kind\": \"dashboard.overview.user\",     \"items\": [...] },\r\n    \"ranking\":  { \"kind\": \"dashboard.overview.ranking\",  \"items\": [...] }\r\n  }\r\n}\r\n```\r\n\r\nFor per-hardware sections (CPU / disk / fan / GPU / etc.), invoke them explicitly — they are NOT part of the default envelope.\r\n\r\n## 10-section matrix\r\n\r\n| Section | Kind | When to use |\r\n|---|---|---|\r\n| `physical` | `dashboard.overview.physical` | 9-row cluster-level snapshot (CPU/Memory/Disk/Pods/Net + extras). The \"single screen vital signs\" view |\r\n| `user` | `dashboard.overview.user` | Per-user CPU / memory quota usage. Accepts optional `<username>` positional — **admin-only** for non-self lookups. Mirrors SPA's User Resources panel |\r\n| `ranking` | `dashboard.overview.ranking` | Workload-grain resource ranking (per-application). `--sort asc|desc` + `--head N` for top-N. Mirrors SPA's UsageRanking widget |\r\n| `cpu` | `dashboard.overview.cpu` | Per-node CPU details (model / freq / cores / utilisation breakdown / temp / load avg) |\r\n| `memory` | `dashboard.overview.memory` | Per-node memory breakdown. `--mode physical|swap` (default `physical`) |\r\n| `disk` | `dashboard.overview.disk` (Shape B) | **Sections envelope**: `main` (per-disk table) + `partitions` (per-device partition tables). `disk main` and `disk partitions <device>` give individual views |\r\n| `pods` | `dashboard.overview.pods` | Per-node pod count snapshot (last/avg/max running) |\r\n| `network` | `dashboard.overview.network` | Per-physical-NIC table. `--test-connectivity` (default true) probes internet/IPv6 per interface |\r\n| `fan` | `dashboard.overview.fan` (Shape B) | **Olares One only.** Sections envelope: `live` (real-time fan/temp/power) + `curve` (hardcoded fan-curve spec). Hard-gated on non-Olares-One: `meta.empty_reason=not_olares_one` |\r\n| `gpu` | `dashboard.overview.gpu` (Shape B) | Sections envelope: `graphics` (GPU list) + `tasks` (vGPU tasks). **Soft-gated**; `meta.note` advises when the SPA would have hidden the entry. See parent SKILL.md for the soft-gate decision tree |\r\n\r\n## Drilling into a section\r\n\r\n```bash\r\n# Per-section snap"},{"path":"references/olares-dashboard-watch.md","content":"# dashboard --watch / windows / NDJSON contract\r\n\r\n> **Prerequisite:** Read the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags:** `olares-cli dashboard --help` (every flag here is a `dashboard`-root global flag inherited by every leaf).\r\n\r\n`--watch` opts every leaf verb into an HTTP-polling loop that mirrors the SPA's `setTimeout` cadence. **Polling, not streaming** — every iteration is a fresh HTTP request, no chunked transfer encoding.\r\n\r\n## `--watch` behaviour\r\n\r\n```\r\ninvoke leaf cmd\r\n   │\r\n   ▼\r\n--watch set? ── no ──> single execution → emit one envelope → exit 0/1\r\n   │\r\n   yes\r\n   ▼\r\nRunner.Run loop\r\n   │\r\n   ▼\r\nRunOnce iteration N\r\n   │\r\n   ├── success         → emit envelope (Shape A or B) with meta.iteration=N\r\n   │                       JSON mode → NDJSON line on stdout\r\n   │                       table mode → clear-screen redraw\r\n   │\r\n   ├── transient err   → emit envelope with meta.error=\"...\", items:[]\r\n   │                       continue\r\n   │\r\n   └── ErrTokenInvalidated / ErrNotLoggedIn\r\n                       → exit non-zero IMMEDIATELY\r\n   ▼\r\n3 consecutive errors? ──> exit non-zero\r\n   │\r\n   no\r\n   ▼\r\n--watch-iterations cap reached? ──> exit 0\r\n   │\r\n   no\r\n   ▼\r\nSIGINT (Ctrl-C) ──> exit 0 gracefully\r\n   │\r\n   no\r\n   ▼\r\nwait --watch-interval, loop back\r\n```\r\n\r\n## NDJSON contract (JSON mode)\r\n\r\n- **One envelope per iteration; nothing else on stdout.** Pretty `-o json` is for one-shot mode; `--watch -o json` is **always NDJSON** (line-delimited, no leading newline, no trailing array bracket).\r\n- A failed iteration emits `items: []` + `meta.error: \"<msg>\"` — **NDJSON keeps streaming**, the failure does not raise to process exit unless it's auth-class or the 3-consecutive cap trips.\r\n- `meta.iteration` is **1-based** and present in every payload.\r\n- Each envelope's `meta.fetched_at` reflects that iteration's HTTP-request start time.\r\n\r\n```jsonc\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 1, ... }, \"items\": [...] }\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 2, \"error\": \"...\", ... }, \"items\": [] }\r\n{ \"kind\": \"dashboard.overview.cpu\", \"meta\": { \"iteration\": 3, ... }, \"items\": [...] }\r\n```\r\n\r\n## Watch loop knobs\r\n\r\n| Flag | Purpose | Default |\r\n|---|---|---|\r\n| `--watch` | Enable the loop | off |\r\n| `--watch-interval D` | Time between iterations | command's `recommended_poll_seconds` (e.g. 30s for GPU, 60s for most others) |\r\n| `--watch-iterations N` | Stop after N iterations | unbounded |\r\n| `--watch-timeout D` | Stop after this much wall-clock time | unbounded |\r\n\r\n**All three `--watch-*` knobs are REJECTED without `--watch`** — don't silently waste a flag.\r\n\r\n## Window semantics — `--since` vs `--start/--end`\r\n\r\nMutually exclusive. Pick one.\r\n\r\n| Form | Semantics | Behaviour under `--watch` |\r\n|---|---|---|\r\n| `--since 5m` | Window = `[now - 5m, now]` | **Sliding** — each iteration recomputes `now` |\r\n| `--start <RFC3339> --end <RFC3339>` | Window = `[start, end]` | **Fixed** — same window ever"},{"path":"skill-card.md","content":"## Description:\n\nOlares Dashboard helps agents query Olares Dashboard Overview and Applications data through olares-cli dashboard, including resource metrics, application rankings, JSON envelopes, and watch-mode output.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[olares](https://clawhub.ai/user/olares)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and operators use this skill to inspect Olares dashboard metrics and application resource usage from an active olares-cli profile. It guides agents toward the correct dashboard commands, JSON output shapes, watch behavior, and empty-data semantics for CPU, memory, disk, pods, network, fan, GPU, ranking, and applications views.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can guide an agent to query dashboard metrics through the active olares-cli profile, including admin-scoped views when --user targets another Olares ID.\n\nMitigation: Use the active profile intentionally, reserve --user for deliberate platform-admin visibility, and review profile scope before querying sensitive dashboard data.\n\nRisk: Large result sets or live polling can add load or produce more output than needed.\n\nMitigation: Use output caps such as --head, --limit, --watch-iterations, and --watch-timeout, and respect the recommended polling cadence.\n\n## Reference(s):\n\n- [dashboard overview](references/olares-dashboard-overview.md)\n- [dashboard --watch / windows / NDJSON contract](references/olares-dashboard-watch.md)\n- [Olares Dashboard ClawHub skill page](https://clawhub.ai/olares/skills/olares-dashboard)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with inline shell commands and JSON shape descriptions]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Agent-facing guidance centers on olares-cli dashboard output, including table, JSON, and NDJSON watch-mode behavior.]\n\n## Skill Version(s):\n\n4.0.1 (source: frontmatter and server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olare... Skill: Olares Dashboard (olares-cli dashboard) Owner: olares Summary: Olares Dashboard (olares-cli dashboard) — query the Olares Dashboard SPA's Overview and Applications routes from the command line, scoped to the active Olare... Tags: latest:4.0.1 Version history: v4.0.1 | 2026-05-29T05:54:52.953Z | user Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0) v4.0.0 | 2026-05-29T04:53:57.489Z | use","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1663,"uniquenessScore":45,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T21:43:38.829Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T21:43:38.829Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-14T12:13:49.559Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}