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.
Files changed (210) hide show
  1. package/.claude/CLAUDE.md +43 -0
  2. package/.claude/agents/compiler.md +91 -0
  3. package/.claude/agents/direct-executor.md +98 -0
  4. package/.claude/agents/documenter.md +78 -0
  5. package/.claude/agents/finalizer.md +233 -0
  6. package/.claude/agents/implementer.md +112 -0
  7. package/.claude/agents/investigator.md +100 -0
  8. package/.claude/agents/release-manager.md +203 -0
  9. package/.claude/agents/reviewer.md +131 -0
  10. package/.claude/agents/technical-architect.md +403 -0
  11. package/.claude/agents/test-writer.md +159 -0
  12. package/.claude/agents/wiki-manager.md +147 -0
  13. package/.claude/instructions/agent-prompts.md +90 -0
  14. package/.claude/instructions/shared-rules.md +11 -0
  15. package/.claude/instructions/team-orchestrator-policy.md +165 -0
  16. package/.claude/memory.md +186 -0
  17. package/.claude/output-styles/engineer-manager.md +38 -0
  18. package/.claude/settings.json +40 -0
  19. package/.claude/settings.local.json +30 -0
  20. package/.claude/skills/load-policy/SKILL.md +13 -0
  21. package/.claude/skills/ref-idea/SKILL.md +44 -0
  22. package/.claude/skills/status-of/SKILL.md +38 -0
  23. package/.claude/skills/workspace-console/SKILL.md +49 -0
  24. package/.claude/statusline-command.sh +130 -0
  25. package/.claude/statusline.md +53 -0
  26. package/.claude/templates/approval-packet.template.html +171 -0
  27. package/.claude/templates/reference-workspaces.md +16 -0
  28. package/.claude/tools/cli-runner-server.cjs +220 -0
  29. package/.claude/tools/discord-send-server.cjs +188 -0
  30. package/.claude/tools/github-release-server.cjs +181 -0
  31. package/.claude/tools/glob-grep-server.cjs +267 -0
  32. package/.claude/tools/read-log-server.cjs +175 -0
  33. package/.github/codeql/codeql-config.yml +14 -0
  34. package/.github/workflows/build.yml +54 -0
  35. package/.github/workflows/codeql.yml +59 -0
  36. package/.github/workflows/coverage.yml +70 -0
  37. package/.github/workflows/publish.yml +103 -0
  38. package/.mcp.json +34 -0
  39. package/CHANGELOG.md +195 -0
  40. package/LICENSE +21 -0
  41. package/dist/cli/commands/devices.js +52 -0
  42. package/dist/cli/commands/energy.js +122 -0
  43. package/dist/cli/commands/login.js +54 -0
  44. package/dist/cli/help.js +50 -0
  45. package/dist/cli/main.js +56 -0
  46. package/dist/cli/session.js +13 -0
  47. package/dist/cli/types.js +1 -0
  48. package/dist/cli/utils.js +29 -0
  49. package/dist/cli.js +2 -0
  50. package/dist/constants/index.js +1 -0
  51. package/dist/core/domain/entities/ThinqDevice.js +43 -0
  52. package/dist/core/domain/value-objects/AirConditionerCapabilities.js +22 -0
  53. package/dist/core/domain/value-objects/ThinqFilterState.js +35 -0
  54. package/dist/core/domain/value-objects/ThinqSnapshot.js +125 -0
  55. package/dist/errors/AuthenticationError.js +26 -0
  56. package/dist/errors/BaseError.js +23 -0
  57. package/dist/errors/index.js +2 -0
  58. package/dist/model/LgThinkqPluginPlatformConfig.js +23 -0
  59. package/dist/model/LgThinqPluginPlatformConfig.js +23 -0
  60. package/dist/module.js +212 -0
  61. package/dist/platform/deviceRegistry.js +26 -0
  62. package/dist/platform/platformConfigManager.js +89 -0
  63. package/dist/platform/platformState.js +9 -0
  64. package/dist/platform/thinq/thinqAirConditionerCommandHandlers.js +190 -0
  65. package/dist/platform/thinq/thinqAirConditionerEndpointFactory.js +53 -0
  66. package/dist/platform/thinq/thinqAirConditionerFilterResetCommandHandler.js +54 -0
  67. package/dist/platform/thinq/thinqAirConditionerFilterStateSync.js +19 -0
  68. package/dist/platform/thinq/thinqAirConditionerStateSync.js +205 -0
  69. package/dist/platform/thinq/thinqDeviceConfigReconciler.js +26 -0
  70. package/dist/platform/thinq/thinqDeviceConfigurator.js +124 -0
  71. package/dist/platform/thinq/thinqDeviceDiscovery.js +33 -0
  72. package/dist/platform/thinq/thinqWasherCommandHandlers.js +54 -0
  73. package/dist/platform/thinq/thinqWasherEndpointFactory.js +6 -0
  74. package/dist/platform/thinq/thinqWasherStateSync.js +31 -0
  75. package/dist/platform/thinq/thinqWasherStopCommandResolver.js +38 -0
  76. package/dist/services/authentication/AccountAuthStrategy.js +135 -0
  77. package/dist/services/authentication/AuthContext.js +1 -0
  78. package/dist/services/authentication/AuthenticationCoordinator.js +29 -0
  79. package/dist/services/authentication/IAuthStrategy.js +1 -0
  80. package/dist/services/authentication/TokenAuthStrategy.js +27 -0
  81. package/dist/services/authentication/UserDataRepository.js +32 -0
  82. package/dist/services/thinq/gateway.js +61 -0
  83. package/dist/services/thinq/mqtt/mqttCertificate.js +63 -0
  84. package/dist/services/thinq/mqtt/mqttConnectionEvents.js +37 -0
  85. package/dist/services/thinq/mqtt/mqttKeyRepository.js +33 -0
  86. package/dist/services/thinq/mqtt/mqttRetry.js +25 -0
  87. package/dist/services/thinq/mqtt/thinqMqttListener.js +97 -0
  88. package/dist/services/thinq/serviceContainer.js +73 -0
  89. package/dist/services/thinq/session.js +48 -0
  90. package/dist/services/thinq/thinqApiClient.js +258 -0
  91. package/dist/services/thinq/thinqDeviceService.js +75 -0
  92. package/dist/services/thinq/thinqFilterMonitoringService.js +40 -0
  93. package/dist/settings.js +9 -0
  94. package/dist/tests/helpers/testUtils.js +87 -0
  95. package/dist/utils/mask.js +39 -0
  96. package/docs/README_CLI.md +166 -0
  97. package/eslint.config.js +194 -0
  98. package/matterbridge-lg-thinq-plugin.config.json +27 -0
  99. package/matterbridge-lg-thinq-plugin.schema.json +520 -0
  100. package/package.json +107 -0
  101. package/prettier.config.js +49 -0
  102. package/scripts/parse-junit-failures.mjs +45 -0
  103. package/scripts/parse-tsc-failures.mjs +48 -0
  104. package/scripts/run-build-local-summary.mjs +29 -0
  105. package/scripts/run-format-summary.mjs +36 -0
  106. package/scripts/run-lint-fix-summary.mjs +38 -0
  107. package/scripts/run-precommit-summary.mjs +94 -0
  108. package/src/cli/commands/devices.ts +67 -0
  109. package/src/cli/commands/energy.ts +160 -0
  110. package/src/cli/commands/login.ts +74 -0
  111. package/src/cli/help.ts +53 -0
  112. package/src/cli/main.ts +66 -0
  113. package/src/cli/session.ts +15 -0
  114. package/src/cli/types.ts +10 -0
  115. package/src/cli/utils.ts +30 -0
  116. package/src/cli.ts +3 -0
  117. package/src/constants/index.ts +8 -0
  118. package/src/core/domain/entities/ThinqDevice.test.ts +224 -0
  119. package/src/core/domain/entities/ThinqDevice.ts +74 -0
  120. package/src/core/domain/value-objects/AirConditionerCapabilities.ts +50 -0
  121. package/src/core/domain/value-objects/ThinqFilterState.ts +50 -0
  122. package/src/core/domain/value-objects/ThinqSnapshot.ts +162 -0
  123. package/src/errors/AuthenticationError.ts +37 -0
  124. package/src/errors/BaseError.ts +38 -0
  125. package/src/errors/index.ts +24 -0
  126. package/src/model/LgThinqPluginPlatformConfig.ts +94 -0
  127. package/src/module.ts +298 -0
  128. package/src/platform/deviceRegistry.ts +58 -0
  129. package/src/platform/platformConfigManager.ts +133 -0
  130. package/src/platform/platformState.ts +16 -0
  131. package/src/platform/thinq/thinqAirConditionerCommandHandlers.ts +291 -0
  132. package/src/platform/thinq/thinqAirConditionerEndpointFactory.ts +145 -0
  133. package/src/platform/thinq/thinqAirConditionerFilterResetCommandHandler.ts +96 -0
  134. package/src/platform/thinq/thinqAirConditionerFilterStateSync.ts +42 -0
  135. package/src/platform/thinq/thinqAirConditionerStateSync.ts +343 -0
  136. package/src/platform/thinq/thinqDeviceConfigReconciler.ts +42 -0
  137. package/src/platform/thinq/thinqDeviceConfigurator.test.ts +90 -0
  138. package/src/platform/thinq/thinqDeviceConfigurator.ts +191 -0
  139. package/src/platform/thinq/thinqDeviceDiscovery.test.ts +173 -0
  140. package/src/platform/thinq/thinqDeviceDiscovery.ts +51 -0
  141. package/src/platform/thinq/thinqWasherCommandHandlers.ts +120 -0
  142. package/src/platform/thinq/thinqWasherEndpointFactory.ts +37 -0
  143. package/src/platform/thinq/thinqWasherStateSync.ts +60 -0
  144. package/src/platform/thinq/thinqWasherStopCommandResolver.ts +62 -0
  145. package/src/services/authentication/AccountAuthStrategy.test.ts +358 -0
  146. package/src/services/authentication/AccountAuthStrategy.ts +209 -0
  147. package/src/services/authentication/AuthContext.ts +19 -0
  148. package/src/services/authentication/AuthenticationCoordinator.test.ts +217 -0
  149. package/src/services/authentication/AuthenticationCoordinator.ts +59 -0
  150. package/src/services/authentication/IAuthStrategy.ts +10 -0
  151. package/src/services/authentication/TokenAuthStrategy.test.ts +167 -0
  152. package/src/services/authentication/TokenAuthStrategy.ts +40 -0
  153. package/src/services/authentication/UserDataRepository.test.ts +149 -0
  154. package/src/services/authentication/UserDataRepository.ts +46 -0
  155. package/src/services/thinq/gateway.ts +86 -0
  156. package/src/services/thinq/mqtt/mqttCertificate.test.ts +344 -0
  157. package/src/services/thinq/mqtt/mqttCertificate.ts +102 -0
  158. package/src/services/thinq/mqtt/mqttConnectionEvents.test.ts +276 -0
  159. package/src/services/thinq/mqtt/mqttConnectionEvents.ts +76 -0
  160. package/src/services/thinq/mqtt/mqttKeyRepository.test.ts +164 -0
  161. package/src/services/thinq/mqtt/mqttKeyRepository.ts +43 -0
  162. package/src/services/thinq/mqtt/mqttRetry.test.ts +239 -0
  163. package/src/services/thinq/mqtt/mqttRetry.ts +43 -0
  164. package/src/services/thinq/mqtt/thinqMqttListener.test.ts +349 -0
  165. package/src/services/thinq/mqtt/thinqMqttListener.ts +120 -0
  166. package/src/services/thinq/serviceContainer.test.ts +203 -0
  167. package/src/services/thinq/serviceContainer.ts +105 -0
  168. package/src/services/thinq/session.test.ts +287 -0
  169. package/src/services/thinq/session.ts +69 -0
  170. package/src/services/thinq/thinqApiClient.test.ts +702 -0
  171. package/src/services/thinq/thinqApiClient.ts +418 -0
  172. package/src/services/thinq/thinqDeviceService.test.ts +415 -0
  173. package/src/services/thinq/thinqDeviceService.ts +101 -0
  174. package/src/services/thinq/thinqFilterMonitoringService.test.ts +264 -0
  175. package/src/services/thinq/thinqFilterMonitoringService.ts +53 -0
  176. package/src/settings.ts +18 -0
  177. package/src/tests/cli/commands/devices.test.ts +293 -0
  178. package/src/tests/cli/commands/login.test.ts +240 -0
  179. package/src/tests/cli/help.test.ts +46 -0
  180. package/src/tests/cli/main.test.ts +214 -0
  181. package/src/tests/cli/session.test.ts +198 -0
  182. package/src/tests/cli/utils.test.ts +194 -0
  183. package/src/tests/core/domain/value-objects/AirConditionerCapabilities.test.ts +685 -0
  184. package/src/tests/core/domain/value-objects/ThinqFilterState.test.ts +210 -0
  185. package/src/tests/core/domain/value-objects/ThinqSnapshot.test.ts +1035 -0
  186. package/src/tests/helpers/testUtils.ts +118 -0
  187. package/src/tests/model/LgThinqPluginPlatformConfig.test.ts +81 -0
  188. package/src/tests/platform/deviceRegistry.test.ts +177 -0
  189. package/src/tests/platform/platformConfigManager.test.ts +790 -0
  190. package/src/tests/platform/platformState.test.ts +41 -0
  191. package/src/tests/platform/thinq/thinqAirConditionerCommandHandlers.test.ts +879 -0
  192. package/src/tests/platform/thinq/thinqAirConditionerEndpointFactory.test.ts +993 -0
  193. package/src/tests/platform/thinq/thinqAirConditionerFilterResetCommandHandler.test.ts +508 -0
  194. package/src/tests/platform/thinq/thinqAirConditionerFilterStateSync.test.ts +212 -0
  195. package/src/tests/platform/thinq/thinqAirConditionerStateSync.test.ts +1586 -0
  196. package/src/tests/platform/thinq/thinqDeviceConfigReconciler.test.ts +269 -0
  197. package/src/tests/platform/thinq/thinqDeviceConfigurator.test.ts +492 -0
  198. package/src/tests/platform/thinq/thinqWasherCommandHandlers.test.ts +594 -0
  199. package/src/tests/platform/thinq/thinqWasherEndpointFactory.test.ts +84 -0
  200. package/src/tests/platform/thinq/thinqWasherStateSync.test.ts +373 -0
  201. package/src/tests/platform/thinq/thinqWasherStopCommandResolver.test.ts +392 -0
  202. package/src/tests/settings.test.ts +33 -0
  203. package/src/utils/mask.ts +50 -0
  204. package/tsconfig.base.json +25 -0
  205. package/tsconfig.json +9 -0
  206. package/tsconfig.production.json +13 -0
  207. package/tsconfig.vitest.json +5 -0
  208. package/vite.config.ts +43 -0
  209. package/wiki/memory-archive.md +12 -0
  210. 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
+ });