feat: v0.9.2 Ask Claude on an alert starts an investigation session
- Ops Alerts → Ask Claude now calls `devdesk attach --investigate SESSION UID`. The server looks the alert up, writes the prompt and starts Claude in a new alert-<name> session with it as the first prompt, already sent. Prompts are stored in prompts/<session>.txt and consumed once by devdesk-claude-run. A session that is already running is just opened. - The prompt has the rule, query, summary, details and labels, and where it runs: the CT/VM id, the node it is on now, status and HA state, and `ssh <node> pct exec <id> --`. It reminds Claude that HA guests move (`devdesk guests <id>`), and gives aggregate rules a hint. - `devdesk guests`: Proxmox inventory over SSH (DEVDESK_PVE_NODES). pvesh gives the cluster view; each node gives lxc-info and guest-agent IPs. Cached in guests.json, keeping last-seen IPs for stopped guests; --alerts refreshes it in the background every 5 minutes. - Alerts carry uid, query, description and targets, matched by instance IP/host or by name; each card shows the target. - cmd_alerts moves to libexec/devdesk-ops.sh. Closes #39 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+13
-38
@@ -7,11 +7,12 @@
|
||||
# devdesk end NAME stop session NAME and forget it
|
||||
# devdesk repo NAME git + Gitea CI status of the session's folder (JSON)
|
||||
# devdesk alerts firing/pending Grafana alerts (JSON)
|
||||
# devdesk guests [ID|IP|NAME] Proxmox CTs/VMs: the node each runs on now, state, IPs
|
||||
# devdesk ls list sessions
|
||||
# devdesk pick interactive picker
|
||||
#
|
||||
# The web UI calls `devdesk attach --list | --repo NAME | --alerts | --upload SESSION FILE BYTES |
|
||||
# --new NAME [PROJECT] | --rename OLD NEW | --end NAME | --existing NAME`
|
||||
# The web UI calls `devdesk attach --list | --repo NAME | --alerts | --investigate SESSION UID |
|
||||
# --upload SESSION FILE BYTES | --new NAME [PROJECT] | --rename OLD NEW | --end NAME | --existing NAME`
|
||||
# through ttyd; those print JSON.
|
||||
set -euo pipefail
|
||||
|
||||
@@ -23,9 +24,11 @@ set -euo pipefail
|
||||
. "$DEVDESK_LIBEXEC/devdesk-inbox.sh"
|
||||
# shellcheck source=libexec/devdesk-history.sh
|
||||
. "$DEVDESK_LIBEXEC/devdesk-history.sh"
|
||||
# shellcheck source=libexec/devdesk-ops.sh
|
||||
. "$DEVDESK_LIBEXEC/devdesk-ops.sh"
|
||||
|
||||
usage() {
|
||||
sed -n '2,12s/^# \{0,1\}//p' "$0"
|
||||
sed -n '2,13s/^# \{0,1\}//p' "$0"
|
||||
}
|
||||
|
||||
# Tab-separated running sessions: name, dir, attached clients, last activity, windows.
|
||||
@@ -230,41 +233,6 @@ cmd_repo() {
|
||||
prs: $prs}'
|
||||
}
|
||||
|
||||
# Firing and pending Grafana alert rules, most severe first.
|
||||
cmd_alerts() {
|
||||
if [ -z "$DEVDESK_GRAFANA_URL" ]; then
|
||||
jq -cMn '{configured: false, alerts: []}'
|
||||
return
|
||||
fi
|
||||
local raw
|
||||
if ! raw=$({
|
||||
if [ -r "$DEVDESK_GRAFANA_TOKEN_FILE" ]; then
|
||||
# shellcheck disable=SC1003 # tr deletes whitespace, quotes and backslashes from the token
|
||||
printf 'header = "Authorization: Bearer %s"\n' "$(tr -d '[:space:]"\\' <"$DEVDESK_GRAFANA_TOKEN_FILE")"
|
||||
fi
|
||||
} | curl -fsS --max-time 6 -K - "$DEVDESK_GRAFANA_URL/api/prometheus/grafana/api/v1/rules" 2>&1); then
|
||||
jq -cMn --arg e "${raw:-no response}" '{configured: true, error: $e, alerts: []}'
|
||||
return
|
||||
fi
|
||||
jq -cM --arg pub "$DEVDESK_GRAFANA_PUBLIC_URL" '
|
||||
def num: (tonumber? // .) | tostring;
|
||||
{configured: true, checked: (now | floor), alerts: [
|
||||
.data.groups[] | .name as $group | .file as $folder | .rules[]
|
||||
| select(.state == "firing" or .state == "pending")
|
||||
| . as $r
|
||||
| ([.alerts[]? | select(.state == "Alerting" or .state == "Pending")]) as $active
|
||||
| {name, state, severity: (.labels.severity // null), group: $group, folder: $folder,
|
||||
since: (.activeAt // $active[0].activeAt // null),
|
||||
summary: ((.annotations.summary // .annotations.description // "")
|
||||
| gsub("\\{\\{ *\\$value *\\}\\}"; ($active[0].value // "?" | num))),
|
||||
instances: [$active[] | .labels
|
||||
| del(.__alert_rule_uid__, .__grafana_origin__, .alertname, .grafana_folder, .__name__, .severity)
|
||||
| to_entries | map("\(.key)=\(.value)") | join(" ")
|
||||
| select(. != "")],
|
||||
url: "\($pub)/alerting/grafana/\(.uid)/view"}
|
||||
] | sort_by(if .severity == "critical" then 0 elif .severity == "warning" then 1 else 2 end)}' <<<"$raw"
|
||||
}
|
||||
|
||||
# Receive a file for SESSION over the terminal: the browser waits for the ready
|
||||
# line, then sends exactly ceil(BYTES/3)*4 bytes of base64 as input.
|
||||
cmd_upload() {
|
||||
@@ -323,6 +291,12 @@ cmd_attach() {
|
||||
dd_linger
|
||||
return
|
||||
;;
|
||||
--investigate)
|
||||
shift
|
||||
cmd_investigate "$@"
|
||||
dd_linger
|
||||
return
|
||||
;;
|
||||
--upload)
|
||||
shift
|
||||
cmd_upload "$@"
|
||||
@@ -408,6 +382,7 @@ new) cmd_new "$@" ;;
|
||||
rename | mv) cmd_rename "$@" ;;
|
||||
repo) cmd_repo "$@" ;;
|
||||
alerts) cmd_alerts ;;
|
||||
guests) cmd_guests "$@" ;;
|
||||
ls | list) cmd_ls ;;
|
||||
--list) cmd_list ;;
|
||||
end) cmd_end "$@" ;;
|
||||
|
||||
Reference in New Issue
Block a user