matterbridge-lg-thinq-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +43 -0
- package/.claude/agents/compiler.md +91 -0
- package/.claude/agents/direct-executor.md +98 -0
- package/.claude/agents/documenter.md +78 -0
- package/.claude/agents/finalizer.md +233 -0
- package/.claude/agents/implementer.md +112 -0
- package/.claude/agents/investigator.md +100 -0
- package/.claude/agents/release-manager.md +203 -0
- package/.claude/agents/reviewer.md +131 -0
- package/.claude/agents/technical-architect.md +403 -0
- package/.claude/agents/test-writer.md +159 -0
- package/.claude/agents/wiki-manager.md +147 -0
- package/.claude/instructions/agent-prompts.md +90 -0
- package/.claude/instructions/shared-rules.md +11 -0
- package/.claude/instructions/team-orchestrator-policy.md +165 -0
- package/.claude/memory.md +186 -0
- package/.claude/output-styles/engineer-manager.md +38 -0
- package/.claude/settings.json +40 -0
- package/.claude/settings.local.json +30 -0
- package/.claude/skills/load-policy/SKILL.md +13 -0
- package/.claude/skills/ref-idea/SKILL.md +44 -0
- package/.claude/skills/status-of/SKILL.md +38 -0
- package/.claude/skills/workspace-console/SKILL.md +49 -0
- package/.claude/statusline-command.sh +130 -0
- package/.claude/statusline.md +53 -0
- package/.claude/templates/approval-packet.template.html +171 -0
- package/.claude/templates/reference-workspaces.md +16 -0
- package/.claude/tools/cli-runner-server.cjs +220 -0
- package/.claude/tools/discord-send-server.cjs +188 -0
- package/.claude/tools/github-release-server.cjs +181 -0
- package/.claude/tools/glob-grep-server.cjs +267 -0
- package/.claude/tools/read-log-server.cjs +175 -0
- package/.github/codeql/codeql-config.yml +14 -0
- package/.github/workflows/build.yml +54 -0
- package/.github/workflows/codeql.yml +59 -0
- package/.github/workflows/coverage.yml +70 -0
- package/.github/workflows/publish.yml +103 -0
- package/.mcp.json +34 -0
- package/CHANGELOG.md +195 -0
- package/LICENSE +21 -0
- package/dist/cli/commands/devices.js +52 -0
- package/dist/cli/commands/energy.js +122 -0
- package/dist/cli/commands/login.js +54 -0
- package/dist/cli/help.js +50 -0
- package/dist/cli/main.js +56 -0
- package/dist/cli/session.js +13 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils.js +29 -0
- package/dist/cli.js +2 -0
- package/dist/constants/index.js +1 -0
- package/dist/core/domain/entities/ThinqDevice.js +43 -0
- package/dist/core/domain/value-objects/AirConditionerCapabilities.js +22 -0
- package/dist/core/domain/value-objects/ThinqFilterState.js +35 -0
- package/dist/core/domain/value-objects/ThinqSnapshot.js +125 -0
- package/dist/errors/AuthenticationError.js +26 -0
- package/dist/errors/BaseError.js +23 -0
- package/dist/errors/index.js +2 -0
- package/dist/model/LgThinkqPluginPlatformConfig.js +23 -0
- package/dist/model/LgThinqPluginPlatformConfig.js +23 -0
- package/dist/module.js +212 -0
- package/dist/platform/deviceRegistry.js +26 -0
- package/dist/platform/platformConfigManager.js +89 -0
- package/dist/platform/platformState.js +9 -0
- package/dist/platform/thinq/thinqAirConditionerCommandHandlers.js +190 -0
- package/dist/platform/thinq/thinqAirConditionerEndpointFactory.js +53 -0
- package/dist/platform/thinq/thinqAirConditionerFilterResetCommandHandler.js +54 -0
- package/dist/platform/thinq/thinqAirConditionerFilterStateSync.js +19 -0
- package/dist/platform/thinq/thinqAirConditionerStateSync.js +205 -0
- package/dist/platform/thinq/thinqDeviceConfigReconciler.js +26 -0
- package/dist/platform/thinq/thinqDeviceConfigurator.js +124 -0
- package/dist/platform/thinq/thinqDeviceDiscovery.js +33 -0
- package/dist/platform/thinq/thinqWasherCommandHandlers.js +54 -0
- package/dist/platform/thinq/thinqWasherEndpointFactory.js +6 -0
- package/dist/platform/thinq/thinqWasherStateSync.js +31 -0
- package/dist/platform/thinq/thinqWasherStopCommandResolver.js +38 -0
- package/dist/services/authentication/AccountAuthStrategy.js +135 -0
- package/dist/services/authentication/AuthContext.js +1 -0
- package/dist/services/authentication/AuthenticationCoordinator.js +29 -0
- package/dist/services/authentication/IAuthStrategy.js +1 -0
- package/dist/services/authentication/TokenAuthStrategy.js +27 -0
- package/dist/services/authentication/UserDataRepository.js +32 -0
- package/dist/services/thinq/gateway.js +61 -0
- package/dist/services/thinq/mqtt/mqttCertificate.js +63 -0
- package/dist/services/thinq/mqtt/mqttConnectionEvents.js +37 -0
- package/dist/services/thinq/mqtt/mqttKeyRepository.js +33 -0
- package/dist/services/thinq/mqtt/mqttRetry.js +25 -0
- package/dist/services/thinq/mqtt/thinqMqttListener.js +97 -0
- package/dist/services/thinq/serviceContainer.js +73 -0
- package/dist/services/thinq/session.js +48 -0
- package/dist/services/thinq/thinqApiClient.js +258 -0
- package/dist/services/thinq/thinqDeviceService.js +75 -0
- package/dist/services/thinq/thinqFilterMonitoringService.js +40 -0
- package/dist/settings.js +9 -0
- package/dist/tests/helpers/testUtils.js +87 -0
- package/dist/utils/mask.js +39 -0
- package/docs/README_CLI.md +166 -0
- package/eslint.config.js +194 -0
- package/matterbridge-lg-thinq-plugin.config.json +27 -0
- package/matterbridge-lg-thinq-plugin.schema.json +520 -0
- package/package.json +107 -0
- package/prettier.config.js +49 -0
- package/scripts/parse-junit-failures.mjs +45 -0
- package/scripts/parse-tsc-failures.mjs +48 -0
- package/scripts/run-build-local-summary.mjs +29 -0
- package/scripts/run-format-summary.mjs +36 -0
- package/scripts/run-lint-fix-summary.mjs +38 -0
- package/scripts/run-precommit-summary.mjs +94 -0
- package/src/cli/commands/devices.ts +67 -0
- package/src/cli/commands/energy.ts +160 -0
- package/src/cli/commands/login.ts +74 -0
- package/src/cli/help.ts +53 -0
- package/src/cli/main.ts +66 -0
- package/src/cli/session.ts +15 -0
- package/src/cli/types.ts +10 -0
- package/src/cli/utils.ts +30 -0
- package/src/cli.ts +3 -0
- package/src/constants/index.ts +8 -0
- package/src/core/domain/entities/ThinqDevice.test.ts +224 -0
- package/src/core/domain/entities/ThinqDevice.ts +74 -0
- package/src/core/domain/value-objects/AirConditionerCapabilities.ts +50 -0
- package/src/core/domain/value-objects/ThinqFilterState.ts +50 -0
- package/src/core/domain/value-objects/ThinqSnapshot.ts +162 -0
- package/src/errors/AuthenticationError.ts +37 -0
- package/src/errors/BaseError.ts +38 -0
- package/src/errors/index.ts +24 -0
- package/src/model/LgThinqPluginPlatformConfig.ts +94 -0
- package/src/module.ts +298 -0
- package/src/platform/deviceRegistry.ts +58 -0
- package/src/platform/platformConfigManager.ts +133 -0
- package/src/platform/platformState.ts +16 -0
- package/src/platform/thinq/thinqAirConditionerCommandHandlers.ts +291 -0
- package/src/platform/thinq/thinqAirConditionerEndpointFactory.ts +145 -0
- package/src/platform/thinq/thinqAirConditionerFilterResetCommandHandler.ts +96 -0
- package/src/platform/thinq/thinqAirConditionerFilterStateSync.ts +42 -0
- package/src/platform/thinq/thinqAirConditionerStateSync.ts +343 -0
- package/src/platform/thinq/thinqDeviceConfigReconciler.ts +42 -0
- package/src/platform/thinq/thinqDeviceConfigurator.test.ts +90 -0
- package/src/platform/thinq/thinqDeviceConfigurator.ts +191 -0
- package/src/platform/thinq/thinqDeviceDiscovery.test.ts +173 -0
- package/src/platform/thinq/thinqDeviceDiscovery.ts +51 -0
- package/src/platform/thinq/thinqWasherCommandHandlers.ts +120 -0
- package/src/platform/thinq/thinqWasherEndpointFactory.ts +37 -0
- package/src/platform/thinq/thinqWasherStateSync.ts +60 -0
- package/src/platform/thinq/thinqWasherStopCommandResolver.ts +62 -0
- package/src/services/authentication/AccountAuthStrategy.test.ts +358 -0
- package/src/services/authentication/AccountAuthStrategy.ts +209 -0
- package/src/services/authentication/AuthContext.ts +19 -0
- package/src/services/authentication/AuthenticationCoordinator.test.ts +217 -0
- package/src/services/authentication/AuthenticationCoordinator.ts +59 -0
- package/src/services/authentication/IAuthStrategy.ts +10 -0
- package/src/services/authentication/TokenAuthStrategy.test.ts +167 -0
- package/src/services/authentication/TokenAuthStrategy.ts +40 -0
- package/src/services/authentication/UserDataRepository.test.ts +149 -0
- package/src/services/authentication/UserDataRepository.ts +46 -0
- package/src/services/thinq/gateway.ts +86 -0
- package/src/services/thinq/mqtt/mqttCertificate.test.ts +344 -0
- package/src/services/thinq/mqtt/mqttCertificate.ts +102 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.test.ts +276 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.ts +76 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.test.ts +164 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.ts +43 -0
- package/src/services/thinq/mqtt/mqttRetry.test.ts +239 -0
- package/src/services/thinq/mqtt/mqttRetry.ts +43 -0
- package/src/services/thinq/mqtt/thinqMqttListener.test.ts +349 -0
- package/src/services/thinq/mqtt/thinqMqttListener.ts +120 -0
- package/src/services/thinq/serviceContainer.test.ts +203 -0
- package/src/services/thinq/serviceContainer.ts +105 -0
- package/src/services/thinq/session.test.ts +287 -0
- package/src/services/thinq/session.ts +69 -0
- package/src/services/thinq/thinqApiClient.test.ts +702 -0
- package/src/services/thinq/thinqApiClient.ts +418 -0
- package/src/services/thinq/thinqDeviceService.test.ts +415 -0
- package/src/services/thinq/thinqDeviceService.ts +101 -0
- package/src/services/thinq/thinqFilterMonitoringService.test.ts +264 -0
- package/src/services/thinq/thinqFilterMonitoringService.ts +53 -0
- package/src/settings.ts +18 -0
- package/src/tests/cli/commands/devices.test.ts +293 -0
- package/src/tests/cli/commands/login.test.ts +240 -0
- package/src/tests/cli/help.test.ts +46 -0
- package/src/tests/cli/main.test.ts +214 -0
- package/src/tests/cli/session.test.ts +198 -0
- package/src/tests/cli/utils.test.ts +194 -0
- package/src/tests/core/domain/value-objects/AirConditionerCapabilities.test.ts +685 -0
- package/src/tests/core/domain/value-objects/ThinqFilterState.test.ts +210 -0
- package/src/tests/core/domain/value-objects/ThinqSnapshot.test.ts +1035 -0
- package/src/tests/helpers/testUtils.ts +118 -0
- package/src/tests/model/LgThinqPluginPlatformConfig.test.ts +81 -0
- package/src/tests/platform/deviceRegistry.test.ts +177 -0
- package/src/tests/platform/platformConfigManager.test.ts +790 -0
- package/src/tests/platform/platformState.test.ts +41 -0
- package/src/tests/platform/thinq/thinqAirConditionerCommandHandlers.test.ts +879 -0
- package/src/tests/platform/thinq/thinqAirConditionerEndpointFactory.test.ts +993 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterResetCommandHandler.test.ts +508 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterStateSync.test.ts +212 -0
- package/src/tests/platform/thinq/thinqAirConditionerStateSync.test.ts +1586 -0
- package/src/tests/platform/thinq/thinqDeviceConfigReconciler.test.ts +269 -0
- package/src/tests/platform/thinq/thinqDeviceConfigurator.test.ts +492 -0
- package/src/tests/platform/thinq/thinqWasherCommandHandlers.test.ts +594 -0
- package/src/tests/platform/thinq/thinqWasherEndpointFactory.test.ts +84 -0
- package/src/tests/platform/thinq/thinqWasherStateSync.test.ts +373 -0
- package/src/tests/platform/thinq/thinqWasherStopCommandResolver.test.ts +392 -0
- package/src/tests/settings.test.ts +33 -0
- package/src/utils/mask.ts +50 -0
- package/tsconfig.base.json +25 -0
- package/tsconfig.json +9 -0
- package/tsconfig.production.json +13 -0
- package/tsconfig.vitest.json +5 -0
- package/vite.config.ts +43 -0
- package/wiki/memory-archive.md +12 -0
- package/wiki/reference-workspaces.md +23 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
input=$(cat)
|
|
3
|
+
|
|
4
|
+
model=$(echo "$input" | jq -r '.model.display_name // "Claude"')
|
|
5
|
+
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
6
|
+
effort=$(echo "$input" | jq -r '.effort.level // empty')
|
|
7
|
+
thinking=$(echo "$input" | jq -r '.thinking.enabled // false')
|
|
8
|
+
used=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
|
9
|
+
ctx_used_tokens=$(echo "$input" | jq -r '(.context_window.total_input_tokens // 0) + (.context_window.total_output_tokens // 0)')
|
|
10
|
+
ctx_size_tokens=$(echo "$input" | jq -r '.context_window.context_window_size // empty')
|
|
11
|
+
five_hour_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
|
12
|
+
five_hour_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
|
|
13
|
+
seven_day_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
|
14
|
+
seven_day_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
|
|
15
|
+
|
|
16
|
+
RESET="\033[0m"
|
|
17
|
+
DIM="\033[38;5;240m"
|
|
18
|
+
FAINT="\033[38;5;244m"
|
|
19
|
+
GREEN="\033[38;5;114m"
|
|
20
|
+
YELLOW="\033[38;5;221m"
|
|
21
|
+
RED="\033[38;5;203m"
|
|
22
|
+
CYAN="\033[38;5;80m"
|
|
23
|
+
MAGENTA="\033[38;5;176m"
|
|
24
|
+
BLUE="\033[38;5;110m"
|
|
25
|
+
|
|
26
|
+
# pct → green/yellow/red color
|
|
27
|
+
pct_color() {
|
|
28
|
+
local pct=$1
|
|
29
|
+
if [ "$pct" -ge 75 ]; then printf '%b' "$RED"
|
|
30
|
+
elif [ "$pct" -ge 50 ]; then printf '%b' "$YELLOW"
|
|
31
|
+
else printf '%b' "$GREEN"; fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# 97923 → 97.9k, 1000000 → 1M
|
|
35
|
+
fmt_tokens() {
|
|
36
|
+
local n=$1
|
|
37
|
+
if [ "$n" -ge 1000000 ]; then awk -v n="$n" 'BEGIN { v = n / 1000000; printf (v == int(v)) ? "%.0fM" : "%.1fM", v }'
|
|
38
|
+
elif [ "$n" -ge 1000 ]; then awk -v n="$n" 'BEGIN { printf "%.1fk", n / 1000 }'
|
|
39
|
+
else printf '%d' "$n"; fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
render_bar() {
|
|
43
|
+
local pct=$1 bar_len=$2 color=$3
|
|
44
|
+
local filled=$(( (pct * bar_len + 50) / 100 ))
|
|
45
|
+
local empty=$(( bar_len - filled ))
|
|
46
|
+
local bar=""
|
|
47
|
+
[ "$filled" -gt 0 ] && for i in $(seq 1 $filled); do bar="${bar}▰"; done
|
|
48
|
+
bar="${color}${bar}${RESET}${DIM}"
|
|
49
|
+
[ "$empty" -gt 0 ] && for i in $(seq 1 $empty); do bar="${bar}▱"; done
|
|
50
|
+
printf '%b%b' "$bar" "$RESET"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Row 1: model + effort + thinking
|
|
54
|
+
printf "${MAGENTA}✦${RESET} ${CYAN}\033[1m%s${RESET}" "$model"
|
|
55
|
+
if [ -n "$effort" ]; then
|
|
56
|
+
case "$effort" in
|
|
57
|
+
low) ecolor="$GREEN" ;;
|
|
58
|
+
medium) ecolor="$CYAN" ;;
|
|
59
|
+
high|xhigh|max) ecolor="$RED" ;;
|
|
60
|
+
*) ecolor="$MAGENTA";;
|
|
61
|
+
esac
|
|
62
|
+
printf " ${DIM}·${RESET} ${ecolor}⚡%s${RESET}" "$effort"
|
|
63
|
+
fi
|
|
64
|
+
if [ "$thinking" = "true" ]; then
|
|
65
|
+
printf " ${DIM}·${RESET} ${MAGENTA}✻ think:on${RESET}"
|
|
66
|
+
else
|
|
67
|
+
printf " ${DIM}·${RESET} ${DIM}✻ think:off${RESET}"
|
|
68
|
+
fi
|
|
69
|
+
printf "\n"
|
|
70
|
+
|
|
71
|
+
# Row 2: context bar
|
|
72
|
+
if [ -n "$used" ]; then
|
|
73
|
+
used_int=$(printf "%.0f" "$used")
|
|
74
|
+
color=$(pct_color "$used_int")
|
|
75
|
+
printf "${FAINT}◍ ctx${RESET} "
|
|
76
|
+
render_bar "$used_int" 18 "$color"
|
|
77
|
+
printf " ${color}%3d%%${RESET}" "$used_int"
|
|
78
|
+
if [ "$ctx_used_tokens" -gt 0 ] && [ -n "$ctx_size_tokens" ]; then
|
|
79
|
+
printf " ${DIM}⛁ %s/%s${RESET}" "$(fmt_tokens "$ctx_used_tokens")" "$(fmt_tokens "$ctx_size_tokens")"
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
printf "\n"
|
|
83
|
+
|
|
84
|
+
# Row 3: 5h / 7d rate-limit bars
|
|
85
|
+
if [ -n "$five_hour_pct" ]; then
|
|
86
|
+
pct_int=$(printf "%.0f" "$five_hour_pct")
|
|
87
|
+
color=$(pct_color "$pct_int")
|
|
88
|
+
printf "${FAINT}◔ 5h${RESET} "
|
|
89
|
+
render_bar "$pct_int" 18 "$color"
|
|
90
|
+
printf " ${color}%3d%%${RESET}" "$pct_int"
|
|
91
|
+
if [ -n "$five_hour_reset" ]; then
|
|
92
|
+
reset_time=$(date -r "$five_hour_reset" +"%-I:%M%p" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
|
93
|
+
[ -n "$reset_time" ] && printf " ${DIM}↻ %s${RESET}" "$reset_time"
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
if [ -n "$seven_day_pct" ]; then
|
|
97
|
+
pct_int=$(printf "%.0f" "$seven_day_pct")
|
|
98
|
+
color=$(pct_color "$pct_int")
|
|
99
|
+
[ -n "$five_hour_pct" ] && printf " ${DIM}│${RESET} "
|
|
100
|
+
printf "${FAINT}◷ 7d${RESET} "
|
|
101
|
+
render_bar "$pct_int" 18 "$color"
|
|
102
|
+
printf " ${color}%d%%${RESET}" "$pct_int"
|
|
103
|
+
if [ -n "$seven_day_reset" ]; then
|
|
104
|
+
reset_time=$(date -r "$seven_day_reset" +"%d/%m %-I:%M%p" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
|
105
|
+
[ -n "$reset_time" ] && printf " ${DIM}↻ %s${RESET}" "$reset_time"
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
108
|
+
printf "\n"
|
|
109
|
+
|
|
110
|
+
# Row 4: cwd with ~ for $HOME
|
|
111
|
+
if [ -n "$cwd" ]; then
|
|
112
|
+
printf "${BLUE}📁 %s${RESET}" "${cwd/#$HOME/~}"
|
|
113
|
+
fi
|
|
114
|
+
printf "\n"
|
|
115
|
+
|
|
116
|
+
# Row 5: git branch + commits ahead of origin/dev
|
|
117
|
+
if [ -n "$cwd" ] && git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
118
|
+
branch=$(git -C "$cwd" branch --show-current 2>/dev/null)
|
|
119
|
+
if [ -n "$branch" ]; then
|
|
120
|
+
printf "${GREEN}⎇ %s${RESET}" "$branch"
|
|
121
|
+
if git -C "$cwd" rev-parse --verify --quiet origin/dev >/dev/null 2>&1; then
|
|
122
|
+
ahead=$(git -C "$cwd" rev-list --count origin/dev..HEAD 2>/dev/null)
|
|
123
|
+
if [ -n "$ahead" ]; then
|
|
124
|
+
if [ "$ahead" -gt 0 ]; then acolor="$YELLOW"; else acolor="$FAINT"; fi
|
|
125
|
+
printf " ${DIM}·${RESET} ${acolor}↑%s vs origin/dev${RESET}" "$ahead"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
printf "\n"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Claude Code Status Line
|
|
2
|
+
|
|
3
|
+
Shared status line for this repo. Enabled automatically via `.claude/settings.json` when you open the project in Claude Code.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
No manual config needed for this repo — `statusLine` is already in `.claude/settings.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"statusLine": {
|
|
12
|
+
"type": "command",
|
|
13
|
+
"command": "bash .claude/statusline-command.sh"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Make the script executable once after clone:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
chmod +x .claude/statusline-command.sh
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Use globally (optional)
|
|
25
|
+
|
|
26
|
+
To use the same status line in other repos, add to `~/.claude/settings.json`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"statusLine": {
|
|
31
|
+
"type": "command",
|
|
32
|
+
"command": "bash /absolute/path/to/repo/.claude/statusline-command.sh"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Replace `/absolute/path/to/repo` with your local clone path.
|
|
38
|
+
|
|
39
|
+
## What it shows
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Sonnet ~medium | context:34% ███████░░░░░░░░░░░░░ | 5h:12% █░░░░░░░░░░░░░░ reset: 3:45pm | 7d:45% ███████░░░░░░░░ reset: 29/06/2026 3:45pm | /path/to/project
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- **Model name** — current model in cyan
|
|
46
|
+
- **Effort level** — `~low`, `~medium`, `~high` (color-coded)
|
|
47
|
+
- **Context bar** — 20-block bar, green < 70%, red ≥ 70%
|
|
48
|
+
- **5h / 7d bars** — rate-limit usage with reset times
|
|
49
|
+
- **Directory** — current working directory in magenta
|
|
50
|
+
|
|
51
|
+
## Cursor CLI
|
|
52
|
+
|
|
53
|
+
For Cursor, see [`.cursor/statusline.md`](../.cursor/statusline.md).
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Approval Packet template — filled by technical-architect (implement mode).
|
|
3
|
+
Copy to workspace/<task>/approval-packet.html and replace every <!--{{SLOT_*}}--> marker.
|
|
4
|
+
The EM publishes the result as an Artifact at the approval gate. Self-contained: no external assets.
|
|
5
|
+
Slots:
|
|
6
|
+
{{TASK_TITLE}} plain task name (also used in <title>)
|
|
7
|
+
{{COMPLEXITY}} low | medium | high
|
|
8
|
+
{{SLOT_BRIEF}} business-brief.md as HTML (h3 sections + p / ul.keys)
|
|
9
|
+
{{SLOT_CHANGEMAP}} the <pre class="mermaid">…</pre> block from change-map.md + legend/notes
|
|
10
|
+
{{SLOT_PLAN}} Files to Modify/Create as a table + Implementation Steps as ul.keys
|
|
11
|
+
{{SLOT_TESTS}} Cases to Cover as ul.keys, or a "no test step" note
|
|
12
|
+
-->
|
|
13
|
+
<title><!--{{TASK_TITLE}}--> — Approval Packet</title>
|
|
14
|
+
<style>
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #eef1f5; --surface: #ffffff; --surface-2: #f6f8fb; --ink: #16202b; --ink-soft: #33404e;
|
|
17
|
+
--muted: #5c6b7a; --border: #dbe2ea; --accent: #0b8f94; --accent-ink: #ffffff;
|
|
18
|
+
--done-fg: #1a7d4b; --done-bg: #e0f2e6; --pending-fg: #a9640a; --pending-bg: #f7ebd6;
|
|
19
|
+
--defer-fg: #5a6b7c; --defer-bg: #e7edf3; --info-fg: #2f6fb0; --info-bg: #e2edf8;
|
|
20
|
+
--shadow: 0 1px 2px rgba(20,32,43,.06), 0 8px 24px rgba(20,32,43,.06);
|
|
21
|
+
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
|
22
|
+
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
23
|
+
}
|
|
24
|
+
@media (prefers-color-scheme: dark) {
|
|
25
|
+
:root {
|
|
26
|
+
--bg: #0c1219; --surface: #141d28; --surface-2: #101823; --ink: #e7eef5; --ink-soft: #c2cedb;
|
|
27
|
+
--muted: #8394a4; --border: #223041; --accent: #2bd4d9; --accent-ink: #052226;
|
|
28
|
+
--done-fg: #48d38c; --done-bg: #123528; --pending-fg: #e6ab48; --pending-bg: #382a13;
|
|
29
|
+
--defer-fg: #93a3b4; --defer-bg: #1c2833; --info-fg: #6db0ec; --info-bg: #14293c;
|
|
30
|
+
--shadow: 0 1px 2px rgba(0,0,0,.3), 0 10px 30px rgba(0,0,0,.35);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
:root[data-theme="light"] {
|
|
34
|
+
--bg: #eef1f5; --surface: #ffffff; --surface-2: #f6f8fb; --ink: #16202b; --ink-soft: #33404e;
|
|
35
|
+
--muted: #5c6b7a; --border: #dbe2ea; --accent: #0b8f94; --accent-ink: #ffffff;
|
|
36
|
+
--done-fg: #1a7d4b; --done-bg: #e0f2e6; --pending-fg: #a9640a; --pending-bg: #f7ebd6;
|
|
37
|
+
--defer-fg: #5a6b7c; --defer-bg: #e7edf3; --info-fg: #2f6fb0; --info-bg: #e2edf8;
|
|
38
|
+
--shadow: 0 1px 2px rgba(20,32,43,.06), 0 8px 24px rgba(20,32,43,.06);
|
|
39
|
+
}
|
|
40
|
+
:root[data-theme="dark"] {
|
|
41
|
+
--bg: #0c1219; --surface: #141d28; --surface-2: #101823; --ink: #e7eef5; --ink-soft: #c2cedb;
|
|
42
|
+
--muted: #8394a4; --border: #223041; --accent: #2bd4d9; --accent-ink: #052226;
|
|
43
|
+
--done-fg: #48d38c; --done-bg: #123528; --pending-fg: #e6ab48; --pending-bg: #382a13;
|
|
44
|
+
--defer-fg: #93a3b4; --defer-bg: #1c2833; --info-fg: #6db0ec; --info-bg: #14293c;
|
|
45
|
+
--shadow: 0 1px 2px rgba(0,0,0,.3), 0 10px 30px rgba(0,0,0,.35);
|
|
46
|
+
}
|
|
47
|
+
* { box-sizing: border-box; }
|
|
48
|
+
body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--sans); line-height: 1.55; -webkit-font-smoothing: antialiased; }
|
|
49
|
+
.wrap { max-width: 940px; margin: 0 auto; padding: 30px 20px 64px; }
|
|
50
|
+
header.masthead { display: flex; flex-direction: column; gap: 8px; }
|
|
51
|
+
.eyebrow { font-family: var(--mono); font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); font-weight: 600; }
|
|
52
|
+
h1 { font-size: clamp(21px, 3.2vw, 28px); line-height: 1.15; margin: 0; letter-spacing: -.02em; text-wrap: balance; }
|
|
53
|
+
.metaline { display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: center; margin: 6px 0 0; }
|
|
54
|
+
.chip { font-family: var(--mono); font-size: 11.5px; font-weight: 600; letter-spacing: .02em; padding: 3px 9px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap; }
|
|
55
|
+
.chip.done { color: var(--done-fg); background: var(--done-bg); }
|
|
56
|
+
.chip.pending { color: var(--pending-fg); background: var(--pending-bg); }
|
|
57
|
+
.chip.defer { color: var(--defer-fg); background: var(--defer-bg); }
|
|
58
|
+
.chip.info { color: var(--info-fg); background: var(--info-bg); }
|
|
59
|
+
.chip.file { color: var(--muted); background: var(--surface-2); border-color: var(--border); }
|
|
60
|
+
|
|
61
|
+
.tabbar { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 22px 0; overflow-x: auto; scrollbar-width: thin; position: sticky; top: 0; background: var(--bg); z-index: 5; padding-top: 4px; }
|
|
62
|
+
.tab { appearance: none; border: 0; background: transparent; color: var(--muted); cursor: pointer; font-family: var(--sans); font-size: 13.5px; font-weight: 550; padding: 11px 15px 13px; white-space: nowrap; border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 8px 8px 0 0; transition: color .15s, background .15s; }
|
|
63
|
+
.tab:hover { color: var(--ink-soft); background: var(--surface-2); }
|
|
64
|
+
.tab[aria-selected="true"] { color: var(--accent); border-bottom-color: var(--accent); }
|
|
65
|
+
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
66
|
+
|
|
67
|
+
.panel { display: none; animation: fade .22s ease; }
|
|
68
|
+
.panel.active { display: block; }
|
|
69
|
+
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
|
|
70
|
+
@media (prefers-reduced-motion: reduce) { .panel { animation: none; } }
|
|
71
|
+
|
|
72
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 22px 24px; box-shadow: var(--shadow); }
|
|
73
|
+
.card h2 { margin: 0 0 6px; font-size: 18px; letter-spacing: -.015em; text-wrap: balance; }
|
|
74
|
+
.card h3 { margin: 20px 0 8px; font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
|
|
75
|
+
.card h3:first-child, .card h2:first-child { margin-top: 0; }
|
|
76
|
+
.card p { margin: 10px 0; color: var(--ink-soft); font-size: 14.5px; }
|
|
77
|
+
.card p.lede { color: var(--ink); font-size: 15.5px; }
|
|
78
|
+
code { font-family: var(--mono); font-size: .88em; background: var(--surface-2); border: 1px solid var(--border); padding: 1px 5px; border-radius: 5px; color: var(--ink); }
|
|
79
|
+
|
|
80
|
+
ul.keys { margin: 8px 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
|
|
81
|
+
ul.keys li { position: relative; padding-left: 20px; font-size: 14.5px; color: var(--ink-soft); }
|
|
82
|
+
ul.keys li::before { content: ""; position: absolute; left: 3px; top: 9px; width: 6px; height: 6px; border-radius: 2px; background: var(--accent); }
|
|
83
|
+
|
|
84
|
+
.tablewrap { overflow-x: auto; margin: 10px 0; border: 1px solid var(--border); border-radius: 10px; }
|
|
85
|
+
table { border-collapse: collapse; width: 100%; font-size: 13.5px; min-width: 440px; }
|
|
86
|
+
th, td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
87
|
+
th { background: var(--surface-2); font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 600; }
|
|
88
|
+
tr:last-child td { border-bottom: 0; }
|
|
89
|
+
td code { background: transparent; border: 0; padding: 0; color: var(--accent); }
|
|
90
|
+
|
|
91
|
+
.diagram { background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; padding: 14px; margin: 12px 0; overflow-x: auto; }
|
|
92
|
+
.diagram pre.mermaid { margin: 0; text-align: center; }
|
|
93
|
+
.legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 8px 2px 0; font-size: 12px; color: var(--muted); font-family: var(--mono); }
|
|
94
|
+
|
|
95
|
+
footer { margin-top: 26px; padding-top: 14px; border-top: 1px solid var(--border); color: var(--muted); font-size: 12.5px; }
|
|
96
|
+
@media (max-width: 620px) { .card { padding: 18px 16px; } }
|
|
97
|
+
</style>
|
|
98
|
+
|
|
99
|
+
<div class="wrap">
|
|
100
|
+
<header class="masthead">
|
|
101
|
+
<span class="eyebrow">Approval Packet</span>
|
|
102
|
+
<h1><!--{{TASK_TITLE}}--></h1>
|
|
103
|
+
<div class="metaline">
|
|
104
|
+
<span class="chip pending">AWAITING APPROVAL</span>
|
|
105
|
+
<span class="chip defer">Complexity: <!--{{COMPLEXITY}}--></span>
|
|
106
|
+
</div>
|
|
107
|
+
</header>
|
|
108
|
+
|
|
109
|
+
<div class="tabbar" role="tablist" aria-label="Approval packet">
|
|
110
|
+
<button class="tab" role="tab" aria-selected="true" aria-controls="p-brief" id="t-brief">Brief</button>
|
|
111
|
+
<button class="tab" role="tab" aria-selected="false" aria-controls="p-map" id="t-map" tabindex="-1">Change Map</button>
|
|
112
|
+
<button class="tab" role="tab" aria-selected="false" aria-controls="p-plan" id="t-plan" tabindex="-1">Plan</button>
|
|
113
|
+
<button class="tab" role="tab" aria-selected="false" aria-controls="p-tests" id="t-tests" tabindex="-1">Tests</button>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<section class="panel active" role="tabpanel" id="p-brief" aria-labelledby="t-brief">
|
|
117
|
+
<div class="card">
|
|
118
|
+
<!--{{SLOT_BRIEF}}-->
|
|
119
|
+
</div>
|
|
120
|
+
</section>
|
|
121
|
+
|
|
122
|
+
<section class="panel" role="tabpanel" id="p-map" aria-labelledby="t-map" hidden>
|
|
123
|
+
<div class="card">
|
|
124
|
+
<h2>Change Map — before → after</h2>
|
|
125
|
+
<!--{{SLOT_CHANGEMAP}}-->
|
|
126
|
+
</div>
|
|
127
|
+
</section>
|
|
128
|
+
|
|
129
|
+
<section class="panel" role="tabpanel" id="p-plan" aria-labelledby="t-plan" hidden>
|
|
130
|
+
<div class="card">
|
|
131
|
+
<h2>Plan at a glance</h2>
|
|
132
|
+
<!--{{SLOT_PLAN}}-->
|
|
133
|
+
</div>
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<section class="panel" role="tabpanel" id="p-tests" aria-labelledby="t-tests" hidden>
|
|
137
|
+
<div class="card">
|
|
138
|
+
<h2>Test plan</h2>
|
|
139
|
+
<!--{{SLOT_TESTS}}-->
|
|
140
|
+
</div>
|
|
141
|
+
</section>
|
|
142
|
+
|
|
143
|
+
<footer>Approve or request changes in chat. This packet is a private snapshot of the task folder.</footer>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<script>
|
|
147
|
+
(function () {
|
|
148
|
+
var tabs = Array.prototype.slice.call(document.querySelectorAll('.tab'));
|
|
149
|
+
var panels = Array.prototype.slice.call(document.querySelectorAll('.panel'));
|
|
150
|
+
function select(i) {
|
|
151
|
+
tabs.forEach(function (t, j) {
|
|
152
|
+
var on = i === j;
|
|
153
|
+
t.setAttribute('aria-selected', on ? 'true' : 'false');
|
|
154
|
+
t.tabIndex = on ? 0 : -1;
|
|
155
|
+
panels[j].classList.toggle('active', on);
|
|
156
|
+
if (on) { panels[j].removeAttribute('hidden'); } else { panels[j].setAttribute('hidden', ''); }
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
tabs.forEach(function (t, i) {
|
|
160
|
+
t.addEventListener('click', function () { select(i); });
|
|
161
|
+
t.addEventListener('keydown', function (e) {
|
|
162
|
+
var n = null;
|
|
163
|
+
if (e.key === 'ArrowRight') n = (i + 1) % tabs.length;
|
|
164
|
+
else if (e.key === 'ArrowLeft') n = (i - 1 + tabs.length) % tabs.length;
|
|
165
|
+
else if (e.key === 'Home') n = 0;
|
|
166
|
+
else if (e.key === 'End') n = tabs.length - 1;
|
|
167
|
+
if (n !== null) { e.preventDefault(); select(n); tabs[n].focus(); }
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
})();
|
|
171
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Reference Workspaces (allowlist)
|
|
2
|
+
|
|
3
|
+
External codebases agents may consult, **read-only**. Investigator and technical-architect may ONLY read paths listed here — anything not listed is out of bounds.
|
|
4
|
+
|
|
5
|
+
**Priority = list order.** Agents consult top-first and stop early once the question is answered.
|
|
6
|
+
|
|
7
|
+
| Name | Path | Notes |
|
|
8
|
+
| --------------------------------------------------- | ---------- | -------------------------- |
|
|
9
|
+
| `/Volumes/ExternalSSD/code/references/matterbridge` | TypeScript | Matterbridge SDK reference |
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
- Read-only — never modify, format, or stage files in these paths.
|
|
14
|
+
- CodeGraph does NOT work here (it indexes this repo only) — use Grep/Glob/Read.
|
|
15
|
+
- Cite findings as `<workspace-name>/path/to/file:line` in answers.
|
|
16
|
+
- To add a workspace: append a row here (with a one-line note on what it is good for) — no other change needed.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// MCP stdio server — runs this project's compiled CLI (dist/cli.js) and
|
|
4
|
+
// captures its output for investigation. Node built-ins only; no npm deps.
|
|
5
|
+
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
|
|
11
|
+
const CLI_PATH = path.resolve(__dirname, '..', '..', 'dist', 'cli.js');
|
|
12
|
+
|
|
13
|
+
const TIMEOUT_MS = 30000;
|
|
14
|
+
|
|
15
|
+
// Commands allowed through this tool. `login` is intentionally excluded —
|
|
16
|
+
// the user handles authentication manually via `npm run cli -- --command login`.
|
|
17
|
+
const ALLOWED_COMMANDS = [
|
|
18
|
+
'devices',
|
|
19
|
+
'status',
|
|
20
|
+
'start',
|
|
21
|
+
'stop',
|
|
22
|
+
'pause',
|
|
23
|
+
'resume',
|
|
24
|
+
'ping',
|
|
25
|
+
'clean-mode',
|
|
26
|
+
'room-info',
|
|
27
|
+
'map-info',
|
|
28
|
+
'legacy-map-info',
|
|
29
|
+
'legacy-map-info-v2',
|
|
30
|
+
'b01-pose-info',
|
|
31
|
+
'scenes',
|
|
32
|
+
'network-info',
|
|
33
|
+
'custom',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Transport helpers
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
function send(obj) {
|
|
41
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sendResult(id, content) {
|
|
45
|
+
send({
|
|
46
|
+
jsonrpc: '2.0',
|
|
47
|
+
id,
|
|
48
|
+
result: {
|
|
49
|
+
content: [{ type: 'text', text: content }],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sendError(id, code, message) {
|
|
55
|
+
send({
|
|
56
|
+
jsonrpc: '2.0',
|
|
57
|
+
id,
|
|
58
|
+
error: { code, message },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// RunCli tool implementation
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
function runCli({ command, duid, local, debug, detail, method, params, send: sendFlag }) {
|
|
67
|
+
if (!command) {
|
|
68
|
+
return 'Error: "command" is required.';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (command === 'login') {
|
|
72
|
+
return 'Error: the "login" command is not available through this tool. Run it manually yourself: npm run cli -- --command login';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!ALLOWED_COMMANDS.includes(command)) {
|
|
76
|
+
return `Error: unknown or disallowed command "${command}". Allowed commands: ${ALLOWED_COMMANDS.join(', ')}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!fs.existsSync(CLI_PATH)) {
|
|
80
|
+
return `Error: compiled CLI not found at ${CLI_PATH}. Build the project first: npm run build`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const args = ['--command', command];
|
|
84
|
+
if (duid) args.push('--duid', String(duid));
|
|
85
|
+
if (local) args.push('--local', 'true');
|
|
86
|
+
if (debug) args.push('--debug', 'true');
|
|
87
|
+
if (detail) args.push('--detail', 'true');
|
|
88
|
+
if (method) args.push('--method', String(method));
|
|
89
|
+
if (params) args.push('--params', String(params));
|
|
90
|
+
if (sendFlag) args.push('--send', 'true');
|
|
91
|
+
|
|
92
|
+
let stdout = '';
|
|
93
|
+
let stderr = '';
|
|
94
|
+
let timedOut = false;
|
|
95
|
+
let exitCode = 0;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
stdout = execFileSync(process.execPath, [CLI_PATH, ...args], {
|
|
99
|
+
encoding: 'utf8',
|
|
100
|
+
timeout: TIMEOUT_MS,
|
|
101
|
+
killSignal: 'SIGKILL',
|
|
102
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
stdout = err.stdout ? err.stdout.toString() : '';
|
|
106
|
+
stderr = err.stderr ? err.stderr.toString() : '';
|
|
107
|
+
timedOut = err.signal === 'SIGKILL' || err.signal === 'SIGTERM' || err.killed === true;
|
|
108
|
+
exitCode = typeof err.status === 'number' ? err.status : 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parts = [];
|
|
112
|
+
if (stdout.trim().length > 0) parts.push(`--- stdout ---\n${stdout.trim()}`);
|
|
113
|
+
if (stderr.trim().length > 0) parts.push(`--- stderr ---\n${stderr.trim()}`);
|
|
114
|
+
if (parts.length === 0) parts.push('(no output)');
|
|
115
|
+
if (timedOut) parts.push(`--- note ---\nProcess timed out after ${TIMEOUT_MS}ms and was killed. Output above is what was captured before the timeout.`);
|
|
116
|
+
else if (exitCode !== 0) parts.push(`--- note ---\nProcess exited with non-zero exit code: ${exitCode}`);
|
|
117
|
+
|
|
118
|
+
return parts.join('\n\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Tool definitions
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
const TOOL_DEFINITIONS = [
|
|
126
|
+
{
|
|
127
|
+
name: 'RunCli',
|
|
128
|
+
description:
|
|
129
|
+
"Run this project's compiled CLI (dist/cli.js) and capture stdout/stderr for investigation. Does not support the 'login' command — handle authentication manually first.",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
command: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
enum: ALLOWED_COMMANDS,
|
|
136
|
+
description: 'CLI command to run (login is not supported here — run it manually)',
|
|
137
|
+
},
|
|
138
|
+
duid: { type: 'string', description: 'Device UID (required by most device commands)' },
|
|
139
|
+
local: { type: 'boolean', description: 'Send via local network (TCP) instead of MQTT' },
|
|
140
|
+
debug: { type: 'boolean', description: 'Enable debug logging' },
|
|
141
|
+
detail: { type: 'boolean', description: 'Show extra detail (only meaningful for "scenes")' },
|
|
142
|
+
method: { type: 'string', description: 'MQTT method name (required by "custom")' },
|
|
143
|
+
params: { type: 'string', description: 'JSON string of params (used by "custom")' },
|
|
144
|
+
send: { type: 'boolean', description: 'Fire-and-forget send (used by "custom")' },
|
|
145
|
+
},
|
|
146
|
+
required: ['command'],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Request dispatcher
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
function handleRequest(req) {
|
|
156
|
+
const { id, method, params } = req;
|
|
157
|
+
|
|
158
|
+
// Notifications (no id) — no response
|
|
159
|
+
if (id === undefined || id === null) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (method === 'initialize') {
|
|
164
|
+
send({
|
|
165
|
+
jsonrpc: '2.0',
|
|
166
|
+
id,
|
|
167
|
+
result: {
|
|
168
|
+
protocolVersion: '2024-11-05',
|
|
169
|
+
serverInfo: { name: 'cli-runner-server', version: '1.0.0' },
|
|
170
|
+
capabilities: { tools: {} },
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (method === 'tools/list') {
|
|
177
|
+
send({
|
|
178
|
+
jsonrpc: '2.0',
|
|
179
|
+
id,
|
|
180
|
+
result: { tools: TOOL_DEFINITIONS },
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (method === 'tools/call') {
|
|
186
|
+
const { name, arguments: args } = params || {};
|
|
187
|
+
try {
|
|
188
|
+
let text;
|
|
189
|
+
if (name === 'RunCli') {
|
|
190
|
+
text = runCli(args || {});
|
|
191
|
+
} else {
|
|
192
|
+
sendError(id, -32601, `Unknown tool: ${name}`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
sendResult(id, text);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
sendResult(id, `Error: ${err.message}`);
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
sendError(id, -32601, `Method not found: ${method}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Main — read newline-delimited JSON from stdin
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
210
|
+
|
|
211
|
+
rl.on('line', (line) => {
|
|
212
|
+
const trimmed = line.trim();
|
|
213
|
+
if (!trimmed) return;
|
|
214
|
+
try {
|
|
215
|
+
const req = JSON.parse(trimmed);
|
|
216
|
+
handleRequest(req);
|
|
217
|
+
} catch (_) {
|
|
218
|
+
// Malformed JSON — ignore
|
|
219
|
+
}
|
|
220
|
+
});
|