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.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/extensions/answer.ts +601 -0
- package/extensions/branch-term.ts +405 -0
- package/extensions/btw.ts +444 -0
- package/extensions/ghostty.ts +301 -0
- package/extensions/git-diff-stats.ts +277 -0
- package/extensions/git-pr-status.ts +286 -0
- package/extensions/insights.ts +2367 -0
- package/extensions/interlude.ts +144 -0
- package/extensions/loop.ts +529 -0
- package/extensions/memory.ts +1889 -0
- package/extensions/notify.ts +161 -0
- package/extensions/openai-fast.ts +227 -0
- package/extensions/openai-verbosity.ts +223 -0
- package/extensions/review.ts +4347 -0
- package/extensions/sandbox/index.ts +2578 -0
- package/extensions/telegram/README.md +81 -0
- package/extensions/telegram/daemon.mjs +1942 -0
- package/extensions/telegram/index.ts +1073 -0
- package/extensions/telegram/message-text.d.mts +1 -0
- package/extensions/telegram/message-text.mjs +16 -0
- package/extensions/usage/anthropic.ts +198 -0
- package/extensions/usage/github-copilot.ts +204 -0
- package/extensions/usage/google-gemini-cli.ts +232 -0
- package/extensions/usage/index.ts +2310 -0
- package/extensions/usage/minimax.ts +208 -0
- package/extensions/usage/openai-codex.ts +180 -0
- package/extensions/usage/openrouter.ts +168 -0
- package/extensions/usage/providers.ts +26 -0
- package/extensions/usage/shared.ts +156 -0
- package/extensions/usage/types.ts +53 -0
- package/extensions/usage/zai.ts +186 -0
- package/extensions/websearch/README.md +68 -0
- package/extensions/websearch/browser/chromium.ts +330 -0
- package/extensions/websearch/browser/discovery.ts +75 -0
- package/extensions/websearch/browser/firefox.ts +150 -0
- package/extensions/websearch/browser/sqlite.ts +38 -0
- package/extensions/websearch/config.ts +76 -0
- package/extensions/websearch/index.ts +312 -0
- package/extensions/websearch/normalize.ts +155 -0
- package/extensions/websearch/providers/anthropic.pi.ts +139 -0
- package/extensions/websearch/providers/gemini.browser.ts +185 -0
- package/extensions/websearch/providers/gemini.pi.ts +108 -0
- package/extensions/websearch/providers/openai-codex.browser.ts +77 -0
- package/extensions/websearch/providers/openai-codex.pi.ts +49 -0
- package/extensions/websearch/providers/openai-codex.shared.ts +123 -0
- package/extensions/websearch/providers/pi-model.shared.ts +86 -0
- package/extensions/websearch/providers/search-prompt.shared.ts +17 -0
- package/extensions/websearch/providers/shared.ts +76 -0
- package/extensions/websearch/types.ts +52 -0
- package/extensions/worktree.ts +2223 -0
- package/package.json +76 -0
- package/skills/browser-tools/SKILL.md +253 -0
- package/skills/browser-tools/scripts/browser-content.js +100 -0
- package/skills/browser-tools/scripts/browser-cookies.js +33 -0
- package/skills/browser-tools/scripts/browser-dismiss-cookies.js +455 -0
- package/skills/browser-tools/scripts/browser-eval.js +75 -0
- package/skills/browser-tools/scripts/browser-logs-tail.js +89 -0
- package/skills/browser-tools/scripts/browser-nav.js +28 -0
- package/skills/browser-tools/scripts/browser-net-summary.js +115 -0
- package/skills/browser-tools/scripts/browser-pick.js +133 -0
- package/skills/browser-tools/scripts/browser-screenshot.js +18 -0
- package/skills/browser-tools/scripts/browser-start.js +281 -0
- package/skills/browser-tools/scripts/browser-watch.js +321 -0
- package/skills/browser-tools/scripts/utils.js +60 -0
- package/skills/git-clean-history/SKILL.md +45 -0
- package/skills/git-commit/SKILL.md +55 -0
- package/skills/homeassistant-ops/SKILL.md +184 -0
- package/skills/homeassistant-ops/references/api.md +64 -0
- package/skills/homeassistant-ops/references/id_conventions.md +71 -0
- package/skills/homeassistant-ops/references/playbook.md +150 -0
- package/skills/homeassistant-ops/scripts/ha_ops.js +2942 -0
- package/skills/openscad/SKILL.md +149 -0
- package/skills/openscad/examples/parametric_box.scad +92 -0
- package/skills/openscad/examples/phone_stand.scad +95 -0
- package/skills/openscad/scripts/common.sh +49 -0
- package/skills/openscad/scripts/export-stl.sh +56 -0
- package/skills/openscad/scripts/extract-params.sh +147 -0
- package/skills/openscad/scripts/multi-preview.sh +68 -0
- package/skills/openscad/scripts/preview.sh +74 -0
- package/skills/openscad/scripts/render-with-params.sh +91 -0
- package/skills/openscad/scripts/validate.sh +46 -0
- package/skills/oracle/SKILL.md +64 -0
- package/skills/oracle/scripts/oracle +249 -0
- package/skills/oracle/scripts/oracle-bundle +227 -0
- package/skills/sentry/SKILL.md +198 -0
- package/skills/sentry/lib/auth.js +99 -0
- package/skills/sentry/scripts/fetch-event.js +325 -0
- package/skills/sentry/scripts/fetch-issue.js +368 -0
- package/skills/sentry/scripts/list-issues.js +245 -0
- package/skills/sentry/scripts/search-events.js +291 -0
- package/skills/sentry/scripts/search-logs.js +234 -0
- package/skills/update-changelog/SKILL.md +135 -0
- 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.
|