Skip to content

Commit 1e5e3d4

Browse files
feat(dream-cli): --json flag on list/status and document doctor --json (#1000)
* feat(dream-cli): --json flag on list/status and document doctor --json 'dream list --json' and 'dream status --json' previously were silently dropped as unknown args — scripts piping to jq got parse errors. The main case dispatch forwarded no arguments to cmd_list / cmd_status, so any flag passed was ignored. Add explicit --json parsing: - cmd_list --json emits a JSON array of {id, category, status}. - cmd_status --json delegates to the existing cmd_status_json. - Unknown flags on these subcommands now error out cleanly (exit 1). - cmd_help mentions 'doctor --json' which was previously undocumented, and advertises the new 'list --json' / 'status --json' flags. 'dream status-json' remains as a hyphenated alias for one release. Platform impact: identical on macOS / Linux / Windows (WSL2) — no platform branching. * fix(dream-cli): escape list json strings --------- Co-authored-by: Lightheartdevs <Lightheartdevs@users.noreply.github.com>
1 parent 428dd6f commit 1e5e3d4

2 files changed

Lines changed: 116 additions & 6 deletions

File tree

dream-server/dream-cli

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ _status_color() {
7575
esac
7676
}
7777

78+
_json_escape() {
79+
local s="${1-}"
80+
s=${s//\\/\\\\}
81+
s=${s//\"/\\\"}
82+
s=${s//$'\b'/\\b}
83+
s=${s//$'\f'/\\f}
84+
s=${s//$'\n'/\\n}
85+
s=${s//$'\r'/\\r}
86+
s=${s//$'\t'/\\t}
87+
printf '%s' "$s"
88+
}
89+
7890
# Print a separator of repeated characters filling the given width.
7991
# Usage: hr <width> [<char=─>]
8092
hr() {
@@ -658,6 +670,21 @@ _dream_cli_rebuild_images() {
658670
#=============================================================================
659671

660672
cmd_status() {
673+
local json_mode="false"
674+
while [[ $# -gt 0 ]]; do
675+
case "$1" in
676+
--json) json_mode="true"; shift ;;
677+
*) error "Unknown argument to 'dream status': $1" ;;
678+
esac
679+
done
680+
681+
if [[ "$json_mode" == "true" ]]; then
682+
# Subshell isolates cmd_status_json's RETURN trap so it can't leak
683+
# into this caller's frame and re-fire with an unbound $tmp under set -u.
684+
( cmd_status_json )
685+
return $?
686+
fi
687+
661688
check_install
662689
cd "$INSTALL_DIR"
663690
load_env
@@ -1887,10 +1914,45 @@ cmd_purge() {
18871914
}
18881915

18891916
cmd_list() {
1917+
local json_mode="false"
1918+
while [[ $# -gt 0 ]]; do
1919+
case "$1" in
1920+
--json) json_mode="true"; shift ;;
1921+
*) error "Unknown argument to 'dream list': $1" ;;
1922+
esac
1923+
done
1924+
18901925
sr_load
18911926
load_env 2>/dev/null || true
18921927
local active_flags
18931928
active_flags=$(get_compose_flags)
1929+
1930+
if [[ "$json_mode" == "true" ]]; then
1931+
# JSON output: array of {id, category, status}
1932+
local _first=1
1933+
printf '['
1934+
for sid in "${SERVICE_IDS[@]}"; do
1935+
local cat="${SERVICE_CATEGORIES[$sid]}"
1936+
local cf="${SERVICE_COMPOSE[$sid]}"
1937+
local status
1938+
if [[ "$cat" == "core" ]]; then
1939+
status="always-on"
1940+
elif [[ -n "$cf" && -f "$cf" && "$active_flags" == *"${cf#"$INSTALL_DIR/"}"* ]]; then
1941+
status="enabled"
1942+
else
1943+
status="disabled"
1944+
fi
1945+
(( _first == 1 )) || printf ','
1946+
printf '{"id":"%s","category":"%s","status":"%s"}' \
1947+
"$(_json_escape "$sid")" \
1948+
"$(_json_escape "$cat")" \
1949+
"$(_json_escape "$status")"
1950+
_first=0
1951+
done
1952+
printf ']\n'
1953+
return 0
1954+
fi
1955+
18941956
echo -e "${BLUE}━━━ Available Services ━━━${NC}"
18951957
printf "%-20s %-12s %-10s\n" "SERVICE" "CATEGORY" "STATUS"
18961958
printf "%-20s %-12s %-10s\n" "$(hr 20)" "$(hr 12)" "$(hr 10)"
@@ -3561,9 +3623,9 @@ Usage: dream <command> [options]
35613623
${CYAN}Commands:${NC}
35623624
gpu [status|topology|assignment|validate|reassign]
35633625
Inspect and manage multi-GPU configuration
3564-
status Show service health and GPU status
3565-
status-json Machine-readable status (JSON) with mode/tier/model
3566-
list List all services and their status
3626+
status [--json] Show service health and GPU status (--json = machine-readable)
3627+
status-json Alias for 'status --json' (kept for back-compat)
3628+
list [--json] List all services and their status (--json = machine-readable)
35673629
enable <service> Enable an extension service
35683630
disable <service> Disable an extension service
35693631
purge <service> Permanently delete service data
@@ -3592,7 +3654,7 @@ ${CYAN}Commands:${NC}
35923654
View, edit, or validate configuration
35933655
chat "<message>" Quick chat with the LLM
35943656
benchmark Run a quick performance test
3595-
doctor [report] Run diagnostics and write JSON report
3657+
doctor [report|--json] Run diagnostics (--json writes JSON to stdout)
35963658
repair|fix Run basic repairs (currently redirects to doctor)
35973659
template [action] Apply pre-built service templates (list|preview|apply)
35983660
audit [extensions] Audit extension manifests and compose contracts
@@ -3690,9 +3752,9 @@ EOF
36903752
#=============================================================================
36913753
case "${1:-help}" in
36923754
gpu|g) shift; cmd_gpu "$@" ;;
3693-
status|s) cmd_status ;;
3755+
status|s) shift; cmd_status "$@" ;;
36943756
status-json) cmd_status_json ;;
3695-
list|ls) cmd_list ;;
3757+
list|ls) shift; cmd_list "$@" ;;
36963758
enable) shift; cmd_enable "$@" ;;
36973759
disable) shift; cmd_disable "$@" ;;
36983760
purge) shift; cmd_purge "$@" ;;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bash
2+
# Regression: dream list --json must escape registry strings from user extensions.
3+
4+
set -euo pipefail
5+
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
8+
USER_EXT_DIR="$PROJECT_DIR/data/user-extensions/json-escape-test"
9+
10+
cleanup() {
11+
rm -rf "$USER_EXT_DIR"
12+
}
13+
trap cleanup EXIT
14+
15+
if ! command -v python3 >/dev/null 2>&1; then
16+
echo "[SKIP] python3 not available"
17+
exit 0
18+
fi
19+
20+
if ! python3 -c 'import yaml' >/dev/null 2>&1; then
21+
echo "[SKIP] PyYAML not available"
22+
exit 0
23+
fi
24+
25+
mkdir -p "$USER_EXT_DIR"
26+
cat > "$USER_EXT_DIR/manifest.yaml" <<'YAML'
27+
schema_version: dream.services.v1
28+
service:
29+
id: json-escape-test
30+
name: JSON Escape Test
31+
port: 65535
32+
health: /health
33+
category: 'optional "quoted" \ slash'
34+
YAML
35+
36+
output=$(DREAM_HOME="$PROJECT_DIR" NO_COLOR=1 "$PROJECT_DIR/dream-cli" list --json)
37+
38+
python3 - "$output" <<'PY'
39+
import json
40+
import sys
41+
42+
payload = json.loads(sys.argv[1])
43+
service = next(item for item in payload if item["id"] == "json-escape-test")
44+
assert service["category"] == 'optional "quoted" \\ slash'
45+
assert service["status"] == "disabled"
46+
PY
47+
48+
echo "[PASS] dream list --json escapes user-extension strings"

0 commit comments

Comments
 (0)