tau-all-agent 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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/extensions/answer.ts +601 -0
  4. package/extensions/branch-term.ts +405 -0
  5. package/extensions/btw.ts +444 -0
  6. package/extensions/ghostty.ts +301 -0
  7. package/extensions/git-diff-stats.ts +277 -0
  8. package/extensions/git-pr-status.ts +286 -0
  9. package/extensions/insights.ts +2367 -0
  10. package/extensions/interlude.ts +144 -0
  11. package/extensions/loop.ts +529 -0
  12. package/extensions/memory.ts +1889 -0
  13. package/extensions/notify.ts +161 -0
  14. package/extensions/openai-fast.ts +227 -0
  15. package/extensions/openai-verbosity.ts +223 -0
  16. package/extensions/review.ts +4347 -0
  17. package/extensions/sandbox/index.ts +2578 -0
  18. package/extensions/telegram/README.md +81 -0
  19. package/extensions/telegram/daemon.mjs +1942 -0
  20. package/extensions/telegram/index.ts +1073 -0
  21. package/extensions/telegram/message-text.d.mts +1 -0
  22. package/extensions/telegram/message-text.mjs +16 -0
  23. package/extensions/usage/anthropic.ts +198 -0
  24. package/extensions/usage/github-copilot.ts +204 -0
  25. package/extensions/usage/google-gemini-cli.ts +232 -0
  26. package/extensions/usage/index.ts +2310 -0
  27. package/extensions/usage/minimax.ts +208 -0
  28. package/extensions/usage/openai-codex.ts +180 -0
  29. package/extensions/usage/openrouter.ts +168 -0
  30. package/extensions/usage/providers.ts +26 -0
  31. package/extensions/usage/shared.ts +156 -0
  32. package/extensions/usage/types.ts +53 -0
  33. package/extensions/usage/zai.ts +186 -0
  34. package/extensions/websearch/README.md +68 -0
  35. package/extensions/websearch/browser/chromium.ts +330 -0
  36. package/extensions/websearch/browser/discovery.ts +75 -0
  37. package/extensions/websearch/browser/firefox.ts +150 -0
  38. package/extensions/websearch/browser/sqlite.ts +38 -0
  39. package/extensions/websearch/config.ts +76 -0
  40. package/extensions/websearch/index.ts +312 -0
  41. package/extensions/websearch/normalize.ts +155 -0
  42. package/extensions/websearch/providers/anthropic.pi.ts +139 -0
  43. package/extensions/websearch/providers/gemini.browser.ts +185 -0
  44. package/extensions/websearch/providers/gemini.pi.ts +108 -0
  45. package/extensions/websearch/providers/openai-codex.browser.ts +77 -0
  46. package/extensions/websearch/providers/openai-codex.pi.ts +49 -0
  47. package/extensions/websearch/providers/openai-codex.shared.ts +123 -0
  48. package/extensions/websearch/providers/pi-model.shared.ts +86 -0
  49. package/extensions/websearch/providers/search-prompt.shared.ts +17 -0
  50. package/extensions/websearch/providers/shared.ts +76 -0
  51. package/extensions/websearch/types.ts +52 -0
  52. package/extensions/worktree.ts +2223 -0
  53. package/package.json +76 -0
  54. package/skills/browser-tools/SKILL.md +253 -0
  55. package/skills/browser-tools/scripts/browser-content.js +100 -0
  56. package/skills/browser-tools/scripts/browser-cookies.js +33 -0
  57. package/skills/browser-tools/scripts/browser-dismiss-cookies.js +455 -0
  58. package/skills/browser-tools/scripts/browser-eval.js +75 -0
  59. package/skills/browser-tools/scripts/browser-logs-tail.js +89 -0
  60. package/skills/browser-tools/scripts/browser-nav.js +28 -0
  61. package/skills/browser-tools/scripts/browser-net-summary.js +115 -0
  62. package/skills/browser-tools/scripts/browser-pick.js +133 -0
  63. package/skills/browser-tools/scripts/browser-screenshot.js +18 -0
  64. package/skills/browser-tools/scripts/browser-start.js +281 -0
  65. package/skills/browser-tools/scripts/browser-watch.js +321 -0
  66. package/skills/browser-tools/scripts/utils.js +60 -0
  67. package/skills/git-clean-history/SKILL.md +45 -0
  68. package/skills/git-commit/SKILL.md +55 -0
  69. package/skills/homeassistant-ops/SKILL.md +184 -0
  70. package/skills/homeassistant-ops/references/api.md +64 -0
  71. package/skills/homeassistant-ops/references/id_conventions.md +71 -0
  72. package/skills/homeassistant-ops/references/playbook.md +150 -0
  73. package/skills/homeassistant-ops/scripts/ha_ops.js +2942 -0
  74. package/skills/openscad/SKILL.md +149 -0
  75. package/skills/openscad/examples/parametric_box.scad +92 -0
  76. package/skills/openscad/examples/phone_stand.scad +95 -0
  77. package/skills/openscad/scripts/common.sh +49 -0
  78. package/skills/openscad/scripts/export-stl.sh +56 -0
  79. package/skills/openscad/scripts/extract-params.sh +147 -0
  80. package/skills/openscad/scripts/multi-preview.sh +68 -0
  81. package/skills/openscad/scripts/preview.sh +74 -0
  82. package/skills/openscad/scripts/render-with-params.sh +91 -0
  83. package/skills/openscad/scripts/validate.sh +46 -0
  84. package/skills/oracle/SKILL.md +64 -0
  85. package/skills/oracle/scripts/oracle +249 -0
  86. package/skills/oracle/scripts/oracle-bundle +227 -0
  87. package/skills/sentry/SKILL.md +198 -0
  88. package/skills/sentry/lib/auth.js +99 -0
  89. package/skills/sentry/scripts/fetch-event.js +325 -0
  90. package/skills/sentry/scripts/fetch-issue.js +368 -0
  91. package/skills/sentry/scripts/list-issues.js +245 -0
  92. package/skills/sentry/scripts/search-events.js +291 -0
  93. package/skills/sentry/scripts/search-logs.js +234 -0
  94. package/skills/update-changelog/SKILL.md +135 -0
  95. package/skills/web-design/SKILL.md +117 -0
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Background logging for browser-tools.
5
+ *
6
+ * Logs console output, page errors, and network activity to JSONL.
7
+ *
8
+ * Default log root:
9
+ * $TMPDIR/agent-browser-tools/logs
10
+ *
11
+ * Override with:
12
+ * BROWSER_TOOLS_LOG_ROOT=/some/dir
13
+ */
14
+
15
+ import {
16
+ createWriteStream,
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ writeFileSync,
21
+ unlinkSync,
22
+ } from "node:fs";
23
+ import { tmpdir } from "node:os";
24
+ import { join } from "node:path";
25
+ import puppeteer from "puppeteer-core";
26
+
27
+ const DEFAULT_TMP = process.platform === "win32" ? tmpdir() : "/tmp";
28
+
29
+ const LOG_ROOT =
30
+ process.env.BROWSER_TOOLS_LOG_ROOT || join(DEFAULT_TMP, "agent-browser-tools", "logs");
31
+
32
+ const PID_FILE = join(LOG_ROOT, ".watch.pid");
33
+
34
+ function ensureDir(dir) {
35
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
36
+ }
37
+
38
+ function isProcessAlive(pid) {
39
+ try {
40
+ process.kill(pid, 0);
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ function getDateDir() {
48
+ const now = new Date();
49
+ const yyyy = String(now.getFullYear());
50
+ const mm = String(now.getMonth() + 1).padStart(2, "0");
51
+ const dd = String(now.getDate()).padStart(2, "0");
52
+ return join(LOG_ROOT, `${yyyy}-${mm}-${dd}`);
53
+ }
54
+
55
+ function safeFileName(value) {
56
+ return String(value).replace(/[^a-zA-Z0-9._-]/g, "_");
57
+ }
58
+
59
+ function nowIso() {
60
+ return new Date().toISOString();
61
+ }
62
+
63
+ async function connectBrowser(timeout = 5000) {
64
+ return Promise.race([
65
+ puppeteer.connect({ browserURL: "http://127.0.0.1:9222", defaultViewport: null }),
66
+ new Promise((_, reject) => {
67
+ setTimeout(() => reject(new Error("timeout")), timeout).unref();
68
+ }),
69
+ ]);
70
+ }
71
+
72
+ ensureDir(LOG_ROOT);
73
+
74
+ // Prevent duplicate watchers.
75
+ if (existsSync(PID_FILE)) {
76
+ try {
77
+ const existing = Number(readFileSync(PID_FILE, "utf8").trim());
78
+ if (existing && isProcessAlive(existing)) {
79
+ console.log("✓ watch already running");
80
+ process.exit(0);
81
+ }
82
+ } catch {
83
+ // ignore stale pid
84
+ }
85
+ }
86
+
87
+ writeFileSync(PID_FILE, String(process.pid));
88
+
89
+ const dateDir = getDateDir();
90
+ ensureDir(dateDir);
91
+
92
+ // targetId -> { stream, filepath }
93
+ const targetState = new Map();
94
+ // request object -> requestId
95
+ const requestIds = new WeakMap();
96
+ // page -> targetId
97
+ const pageToTarget = new WeakMap();
98
+ const instrumentedPages = new WeakSet();
99
+ let nextSyntheticPageId = 1;
100
+
101
+ function getOrCreateTargetState(targetId) {
102
+ let state = targetState.get(targetId);
103
+ if (state) return state;
104
+
105
+ const filename = `${safeFileName(targetId)}.jsonl`;
106
+ const filepath = join(dateDir, filename);
107
+ const stream = createWriteStream(filepath, { flags: "a" });
108
+
109
+ state = { stream, filepath };
110
+ targetState.set(targetId, state);
111
+ return state;
112
+ }
113
+
114
+ function writeLog(targetId, payload) {
115
+ const state = getOrCreateTargetState(targetId);
116
+ const record = {
117
+ ts: nowIso(),
118
+ targetId,
119
+ ...payload,
120
+ };
121
+ state.stream.write(`${JSON.stringify(record)}\n`);
122
+ }
123
+
124
+ function internalTargetId(page) {
125
+ try {
126
+ const t = page.target?.();
127
+ return t?._targetId || t?._targetInfo?.targetId || t?._id || null;
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ function ensureTargetId(page) {
134
+ const existing = pageToTarget.get(page);
135
+ if (existing) return existing;
136
+
137
+ const internal = internalTargetId(page);
138
+ const chosen = internal ? String(internal) : `page-${nextSyntheticPageId++}`;
139
+ pageToTarget.set(page, chosen);
140
+ return chosen;
141
+ }
142
+
143
+ function ensureRequestId(targetId, request) {
144
+ if (requestIds.has(request)) return requestIds.get(request);
145
+ const id = `${targetId}-${Math.random().toString(16).slice(2)}-${Date.now()}`;
146
+ requestIds.set(request, id);
147
+ return id;
148
+ }
149
+
150
+ function closeTarget(targetId) {
151
+ const state = targetState.get(targetId);
152
+ if (!state) return;
153
+ try {
154
+ state.stream.end();
155
+ } catch {
156
+ // ignore
157
+ }
158
+ targetState.delete(targetId);
159
+ }
160
+
161
+ async function instrumentPage(page) {
162
+ if (instrumentedPages.has(page)) return;
163
+ instrumentedPages.add(page);
164
+
165
+ const targetId = ensureTargetId(page);
166
+
167
+ // Do not double-instrument.
168
+ if (targetState.has(targetId)) {
169
+ // Note: this assumes a 1:1 mapping between targetId and page.
170
+ // That’s fine for our usage.
171
+ }
172
+
173
+ writeLog(targetId, {
174
+ type: "target.attached",
175
+ url: page.url?.() || null,
176
+ });
177
+
178
+ page.on("console", (msg) => {
179
+ try {
180
+ writeLog(targetId, {
181
+ type: "console",
182
+ level: msg.type?.() || null,
183
+ text: msg.text?.() || null,
184
+ args: (msg.args?.() || []).map((a) => a.toString()),
185
+ });
186
+ } catch {
187
+ // ignore
188
+ }
189
+ });
190
+
191
+ page.on("pageerror", (err) => {
192
+ writeLog(targetId, {
193
+ type: "exception",
194
+ text: err?.message || String(err),
195
+ stack: err?.stack || null,
196
+ });
197
+ });
198
+
199
+ page.on("error", (err) => {
200
+ writeLog(targetId, {
201
+ type: "page.crash",
202
+ text: err?.message || String(err),
203
+ stack: err?.stack || null,
204
+ });
205
+ });
206
+
207
+ page.on("request", (req) => {
208
+ const requestId = ensureRequestId(targetId, req);
209
+ writeLog(targetId, {
210
+ type: "network.request",
211
+ requestId,
212
+ method: req.method?.() || null,
213
+ url: req.url?.() || null,
214
+ resourceType: req.resourceType?.() || null,
215
+ isNavigationRequest: !!req.isNavigationRequest?.(),
216
+ hasPostData: !!req.postData?.(),
217
+ });
218
+ });
219
+
220
+ page.on("response", (res) => {
221
+ try {
222
+ const req = res.request();
223
+ const requestId = ensureRequestId(targetId, req);
224
+ const headers = res.headers?.() || {};
225
+ const contentType = headers["content-type"] || headers["Content-Type"] || null;
226
+
227
+ writeLog(targetId, {
228
+ type: "network.response",
229
+ requestId,
230
+ url: res.url?.() || null,
231
+ status: res.status?.(),
232
+ statusText: res.statusText?.() || null,
233
+ mimeType: contentType,
234
+ fromDiskCache: !!res.fromCache?.(),
235
+ fromServiceWorker: !!res.fromServiceWorker?.(),
236
+ });
237
+ } catch (e) {
238
+ writeLog(targetId, {
239
+ type: "network.response",
240
+ requestId: null,
241
+ url: null,
242
+ status: null,
243
+ statusText: null,
244
+ mimeType: null,
245
+ error: e?.message || String(e),
246
+ });
247
+ }
248
+ });
249
+
250
+ page.on("requestfailed", (req) => {
251
+ const requestId = ensureRequestId(targetId, req);
252
+ writeLog(targetId, {
253
+ type: "network.failure",
254
+ requestId,
255
+ url: req.url?.() || null,
256
+ errorText: req.failure?.()?.errorText || null,
257
+ canceled: req.failure?.()?.errorText === "net::ERR_ABORTED",
258
+ });
259
+ });
260
+
261
+ page.on("framenavigated", (frame) => {
262
+ if (frame === page.mainFrame()) {
263
+ writeLog(targetId, {
264
+ type: "page.navigated",
265
+ url: frame.url?.() || null,
266
+ });
267
+ }
268
+ });
269
+
270
+ page.on("close", () => {
271
+ writeLog(targetId, { type: "target.closed" });
272
+ closeTarget(targetId);
273
+ });
274
+ }
275
+
276
+ function cleanupAndExit(code = 0) {
277
+ try {
278
+ for (const targetId of targetState.keys()) {
279
+ closeTarget(targetId);
280
+ }
281
+ try {
282
+ unlinkSync(PID_FILE);
283
+ } catch {
284
+ // ignore
285
+ }
286
+ } finally {
287
+ process.exit(code);
288
+ }
289
+ }
290
+
291
+ process.on("SIGINT", () => cleanupAndExit(0));
292
+ process.on("SIGTERM", () => cleanupAndExit(0));
293
+
294
+ async function main() {
295
+ const browser = await connectBrowser(5000).catch((e) => {
296
+ console.error("✗ Could not connect to browser:", e.message);
297
+ console.error(" Run: browser-start.js");
298
+ process.exit(1);
299
+ });
300
+
301
+ // Existing pages.
302
+ const pages = await browser.pages();
303
+ for (const page of pages) {
304
+ await instrumentPage(page);
305
+ }
306
+
307
+ // New pages.
308
+ browser.on("targetcreated", async (target) => {
309
+ try {
310
+ if (target.type?.() !== "page") return;
311
+ const page = await target.page();
312
+ if (page) await instrumentPage(page);
313
+ } catch {
314
+ // ignore
315
+ }
316
+ });
317
+
318
+ console.log(`✓ watch started (logs: ${dateDir})`);
319
+ }
320
+
321
+ await main();
@@ -0,0 +1,60 @@
1
+ import puppeteer from "puppeteer-core";
2
+
3
+ /**
4
+ * Connect to browser with timeout, exit on failure.
5
+ * @param {number} timeout - Connection timeout in ms (default: 5000)
6
+ * @returns {Promise<Browser>}
7
+ */
8
+ export async function connectBrowser(timeout = 5000) {
9
+ const browser = await Promise.race([
10
+ puppeteer.connect({
11
+ browserURL: "http://127.0.0.1:9222",
12
+ defaultViewport: null,
13
+ }),
14
+ new Promise((_, reject) => {
15
+ setTimeout(() => reject(new Error("timeout")), timeout).unref();
16
+ }),
17
+ ]).catch((e) => {
18
+ console.error("✗ Could not connect to browser:", e.message);
19
+ console.error(" Run: browser-start.js");
20
+ process.exit(1);
21
+ });
22
+ return browser;
23
+ }
24
+
25
+ /**
26
+ * Get the active/focused page. Fall back to the last http/https page, then the last page.
27
+ * Exit if none found.
28
+ * @param {Browser} browser
29
+ * @returns {Promise<Page>}
30
+ */
31
+ export async function getActivePage(browser) {
32
+ const pages = await browser.pages();
33
+ if (pages.length === 0) {
34
+ console.error("✗ No active tab found");
35
+ process.exit(1);
36
+ }
37
+
38
+ for (const page of [...pages].reverse()) {
39
+ try {
40
+ const isActive = await page.evaluate(
41
+ () => document.visibilityState === "visible" && document.hasFocus(),
42
+ );
43
+ if (isActive) return page;
44
+ } catch {}
45
+ }
46
+
47
+ return pages.filter((page) => page.url().startsWith("http")).at(-1) || pages.at(-1);
48
+ }
49
+
50
+ /**
51
+ * Print result, using JSON for objects/arrays.
52
+ * @param {any} result
53
+ */
54
+ export function printResult(result) {
55
+ if (typeof result === "object" && result !== null) {
56
+ console.log(JSON.stringify(result, null, 2));
57
+ } else {
58
+ console.log(result);
59
+ }
60
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: git-clean-history
3
+ description: "Reimplement the current Git branch on a fresh branch off `main` with a clean, narrative-quality commit history."
4
+ ---
5
+
6
+ # Git Rebase
7
+
8
+ Use this skill to reimplement the current branch on a new branch with a clean, narrative-quality git commit history suitable for reviewer comprehension.
9
+
10
+ ## Steps
11
+
12
+ 1. **Validate the source branch**
13
+ - Ensure no uncommitted changes or merge conflicts
14
+ - Confirm it is up to date with `main`
15
+
16
+ 2. **Analyze the diff**
17
+ - Study all changes between source branch and `main`
18
+ - Form a clear understanding of the final intended state
19
+
20
+ 3. **Create the clean branch**
21
+ - Create a new branch off of `main` using the new branch name
22
+ - Use the `{source_branch}-clean` name unless another name is provided by the user
23
+
24
+ 4. **Plan the commit storyline**
25
+ - Break the implementation into self-contained logical steps
26
+ - Each step should reflect a stage of development, as if writing a tutorial
27
+
28
+ 5. **Reimplement the work**
29
+ - Recreate changes in the clean branch, committing step by step
30
+ - Each commit must:
31
+ - Introduce a single coherent idea
32
+ - Include a clear commit message and description
33
+ - Follow best practices for the message as outlined by the `git-commit` skill
34
+ - **Use `git commit --no-verify` for all intermediate commits**
35
+ - Pre-commit hooks check tests, types, and imports that may not pass until the full implementation is complete; do not waste time fixing issues in intermediate commits that will be resolved by later commits
36
+
37
+ 6. **Verify correctness**
38
+ - Confirm the final state exactly matches the source branch
39
+ - Run the final commit **without** `--no-verify` to ensure all checks pass
40
+
41
+ ### Rules
42
+
43
+ - Do not add yourself as an author or contributor
44
+ - Do not include "Generated with ...", "Co-Authored-By: ...", or any AI attribution
45
+ - The end state of the clean branch must be identical to the source branch
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: git-commit
3
+ description: "Commit changes with an emphasis on tidiness and reviewability: small, focused commits, with clear rationale in messages."
4
+ ---
5
+
6
+ # Git Commit
7
+
8
+ Use this skill as the playbook for producing reviewable commits and a clean, consistent commit history.
9
+
10
+ ## Phase 1: Review changes
11
+
12
+ 1. Check what files have changed
13
+ 2. If there are no changes to commit, inform the user and stop
14
+ 3. If there are unstaged changes, stage them with `git add`
15
+
16
+ Rules:
17
+
18
+ - Keep commits small, focused, and atomic
19
+ - Avoid commits that mix unrelated changes (e.g., "Address feedback")
20
+ - Never stage or commit files that look like secrets (e.g. `.env`, credentials, API keys)
21
+
22
+ ## Phase 2: Generate commit message
23
+
24
+ 1. Remember commit messages best practices
25
+ 2. Write a short subject line:
26
+ - Up to 50 chars
27
+ - Start subject with a capital letter, don’t end with a period
28
+ - Use imperative mood (e.g. "Fix memory leak while scrolling widget")
29
+ 3. Leave a blank line between subject and body
30
+ 4. Only when needed (don’t force it for trivial changes), write a body:
31
+ - Wrap lines at 72 chars
32
+ - Focus on "what" and "why", not the "how"
33
+ - Explain the motivation
34
+ - Mention side effects, trade-offs, or alternatives considered
35
+ - Reference relevant issue IDs / tickets at the bottom if known
36
+ 5. Check style of recent commits in the repo (tone, structure, capitalization, length, etc): `git log -10 --oneline`
37
+ 6. Generate a message that adheres to the style in the repo while following best practices
38
+
39
+ Rules:
40
+
41
+ - Never stage files that look like secrets (e.g. `.env`, credentials, API keys)
42
+ - Do not add yourself as an author or contributor
43
+ - Do not include "Generated with ...", "Co-Authored-By: ...", or any AI attribution
44
+
45
+ ## Phase 3: Commit
46
+
47
+ 1. Commit staged changes with `git commit`
48
+
49
+ Rules:
50
+
51
+ - Avoid `git commit --amend` unless explicitly requested
52
+ - Do not bypass hooks with `git commit --no-verify`
53
+ - If `git commit` fails due to hooks, fix issues and retry
54
+ - Use `git commit -F` (or heredoc) when there is a body, never newline escapes in `-m`.
55
+ - Never push to remotes unless explicitly requested
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: homeassistant-ops
3
+ description: "Operate a Home Assistant instance via the official REST/WebSocket APIs and backups, with safe plan/apply workflows for bulk, reviewable changes."
4
+ ---
5
+
6
+ # Home Assistant Ops
7
+
8
+ Use this skill as the operator's playbook for making bulk, reviewable changes
9
+ to a Home Assistant instance without SSHing into the host.
10
+
11
+ ## Setup
12
+
13
+ Set environment variables (never pass tokens on the command line):
14
+
15
+ ```bash
16
+ export HA_URL="http://<home-assistant-host>:8123"
17
+ export HA_TOKEN="<long-lived-access-token>"
18
+ ```
19
+
20
+ Explore the operations:
21
+
22
+ ```bash
23
+ "./scripts/ha_ops.js" --help
24
+ ```
25
+
26
+ Keep logs in a working folder (scripts write timestamped `.md` files by default).
27
+
28
+ ## First questions (to avoid rework)
29
+
30
+ - What's the HA version and deployment type (OS / Container / Core)?
31
+ - Which Zigbee stack is used (ZHA vs Zigbee2MQTT) and which devices are affected?
32
+ - Which parts are YAML-managed vs UI-managed (automations, scripts, scenes, dashboards)?
33
+ - Do we have a recent backup to inspect before making bulk changes?
34
+ - Is the goal UI clarity, automation correctness, performance/latency, or all of the above?
35
+
36
+ ## Core concepts (what to change where)
37
+
38
+ - **Entity registry**: source of truth for friendly-name overrides, entity_id renames, hidden/disabled, and some area assignment.
39
+ - **Device registry**: best place to assign areas for physical devices (entity area often inherits).
40
+ - **Blueprint inputs**: not templatable; state triggers need a static entity list at config-load time.
41
+ - **HA "group helpers"**: great for UI/targeting/maintenance, but they don't make Zigbee unicast faster (they expand to member calls).
42
+
43
+ ## Scripts
44
+
45
+ All scripts read `HA_URL` and `HA_TOKEN` from environment variables.
46
+
47
+ ### `ha_ops.js` - Single CLI with subcommands
48
+
49
+ All operations are available under a single CLI:
50
+
51
+ ```bash
52
+ # List commands
53
+ node scripts/ha_ops.js --help
54
+
55
+ # Command help
56
+ node scripts/ha_ops.js cleanup --help
57
+ ```
58
+
59
+ ### `ha_ops.js cleanup` - Bulk cleanup (dry-run by default)
60
+
61
+ ```bash
62
+ # Dry-run all default steps (default)
63
+ node scripts/ha_ops.js cleanup
64
+
65
+ # Apply specific steps
66
+ node scripts/ha_ops.js cleanup --apply \
67
+ --steps rename-switch-suffix,prefix-lights-cove
68
+
69
+ # Prefix custom patterns with area names
70
+ node scripts/ha_ops.js cleanup --apply \
71
+ --steps prefix-generic \
72
+ --pattern "Thermometer:^Thermometer" \
73
+ --pattern "Blinds:^Blinds"
74
+
75
+ # Output proposed changes as JSON
76
+ node scripts/ha_ops.js cleanup --json
77
+ ```
78
+
79
+ Available steps:
80
+
81
+ - `rename-switch-suffix`: Rename "Lights ... Switch" to "Lights ..."
82
+ - `create-groups`: Create/update switch groups for sync automations
83
+ - `prefix-lights-cove`: Prefix Lights/Cove names with area
84
+ - `prefix-generic`: Prefix entities matching `--pattern` with area
85
+
86
+ ### `ha_ops.js snapshot` - Capture state for diffing
87
+
88
+ ```bash
89
+ # Full snapshot
90
+ node scripts/ha_ops.js snapshot
91
+
92
+ # Skip noisy sections
93
+ node scripts/ha_ops.js snapshot \
94
+ --no-lovelace --no-scenes
95
+
96
+ # Include runtime states (noisy for diffs)
97
+ node scripts/ha_ops.js snapshot --include-states
98
+ ```
99
+
100
+ ### `ha_ops.js rollback` - Revert registry changes from a snapshot
101
+
102
+ ```bash
103
+ # Preview what would be rolled back
104
+ node scripts/ha_ops.js rollback \
105
+ snapshot_before.json --dry-run
106
+
107
+ # Apply rollback
108
+ node scripts/ha_ops.js rollback \
109
+ snapshot_before.json --yes
110
+ ```
111
+
112
+ ### `ha_ops.js find-references` - Find entity_id usage
113
+
114
+ ```bash
115
+ # Search for entity references before renaming
116
+ node scripts/ha_ops.js find-references \
117
+ --needle "switch.bedroom_lights"
118
+
119
+ # Search from a rename mapping file
120
+ node scripts/ha_ops.js find-references \
121
+ --map-json rename_map.json --backup-root /path/to/backup
122
+ ```
123
+
124
+ ### `ha_ops.js update-groups` - Update group memberships after renames
125
+
126
+ Entity ID renames (via the entity registry) do **not** propagate to config
127
+ entry–based group helpers. This command rewrites group member lists using a
128
+ rename map.
129
+
130
+ ```bash
131
+ # Preview which groups would be updated
132
+ node scripts/ha_ops.js update-groups \
133
+ --map-json rename_map.json
134
+
135
+ # Apply changes
136
+ node scripts/ha_ops.js update-groups \
137
+ --map-json rename_map.json --apply
138
+
139
+ # Output plan as JSON
140
+ node scripts/ha_ops.js update-groups \
141
+ --map-json rename_map.json --json
142
+ ```
143
+
144
+ ### `ha_ops.js tail-events` - Monitor events in real-time
145
+
146
+ ```bash
147
+ # Tail state changes
148
+ node scripts/ha_ops.js tail-events
149
+
150
+ # Filter to specific entities
151
+ node scripts/ha_ops.js tail-events \
152
+ --entity switch.bedroom_lights --entity switch.bedroom_lights_2
153
+
154
+ # Include ZHA events
155
+ node scripts/ha_ops.js tail-events \
156
+ --event-type state_changed --event-type zha_event
157
+ ```
158
+
159
+ ### `ha_ops.js name-review-from-backup` - Offline naming analysis
160
+
161
+ ```bash
162
+ # Analyze backup for naming candidates
163
+ node scripts/ha_ops.js name-review-from-backup \
164
+ --backup-root /path/to/backup
165
+ ```
166
+
167
+ ## Error recovery
168
+
169
+ If a script fails mid-way:
170
+
171
+ 1. Check the log file for what was applied before the failure.
172
+ 2. Use `ha_ops.js rollback` with your before-snapshot to revert registry changes.
173
+ 3. Fix the underlying issue (network, permissions, entity conflicts).
174
+ 4. Re-run the script (operations are generally idempotent).
175
+
176
+ ## Resources
177
+
178
+ - API reference: `references/api.md`
179
+ - Ops playbook: `references/playbook.md`
180
+ - Entity ID conventions: `references/id_conventions.md`
181
+
182
+ ## Logging convention
183
+
184
+ Prefer one markdown log per run (timestamped), listing every entity/automation changed and the before/after values.