Default to using Bun instead of Node.js.
- Use
bun <file>instead ofnode <file>orts-node <file> - Use
bun testinstead ofjestorvitest - Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuild - Use
bun installinstead ofnpm installoryarn installorpnpm install - Use
bun run <script>instead ofnpm run <script>oryarn run <script>orpnpm run <script> - Use
bunx <package> <command>instead ofnpx <package> <command> - Bun automatically loads .env, so don't use dotenv.
- Safe by default:
git status/diff/log. Push only when user asks. git checkoutok for PR review / explicit request.- Branch changes require user consent.
- Destructive ops forbidden unless explicit (
reset --hard,clean,restore,rm, …). - Don't delete/rename unexpected stuff; stop + ask.
- No repo-wide S/R scripts; keep edits small/reviewable.
- Avoid manual
git stash; if Git auto-stashes during pull/rebase, that's fine (hint, not hard guardrail). - If user types a command ("pull and push"), that's consent for that command.
- No amend unless asked.
- Big review:
git --no-pager diff --color=never. - Multi-agent: check
git status/diffbefore edits; ship small commits. - For commit messages use the conventional commits specification: https://www.conventionalcommits.org/en/v1.0.0/ more info here: https://github.com/conventional-commits/conventionalcommits.org/tree/master/content/v1.0.0/index.md
When the user asks to commit, use the committer script (available on PATH):
committer "feat: description of change" file1.ts file2.tsThe script handles staging and committing. Use conventional commit format for the message.
Do NOT manually run git add and git commit - always use committer instead.
Format markdown files after editing using prettier (globally installed or via npx):
prettier --write "**/*.md"
# or if not globally installed:
npx prettier --write "**/*.md"Bun.serve()supports WebSockets, HTTPS, and routes. Don't useexpress.bun:sqlitefor SQLite. Don't usebetter-sqlite3.Bun.redisfor Redis. Don't useioredis.Bun.sqlfor Postgres. Don't usepgorpostgres.js.WebSocketis built-in. Don't usews.- Prefer
Bun.fileovernode:fs's readFile/writeFile - Bun.$
lsinstead of execa.
Use bun test to run tests.
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});Use HTML imports with Bun.serve(). Don't use vite. HTML imports fully support React, CSS, Tailwind.
Server:
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. <link> tags can point to stylesheets and Bun's CSS bundler will bundle.
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>With the following frontend.tsx:
import React from "react";
import { createRoot } from "react-dom/client";
// import .css files directly and it works
import './index.css';
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);Then, run index.ts
bun --hot ./index.tsFor more information, read the Bun API docs in node_modules/bun-types/docs/**.mdx.
Use the summarize command to fetch and summarize documentation from the web.
# Summarize a web page
summarize "https://example.com/docs"
# Extract raw content without LLM summary
summarize "https://example.com/docs" --extract
# Extract as markdown (preferred for documentation)
summarize "https://example.com/docs" --extract --format md
# Summarize YouTube videos (auto-extracts transcript)
summarize "https://www.youtube.com/watch?v=..."--extract- Get raw content without summarization (useful for full docs)--format md- Extract content as markdown--length <short|medium|long|xl|xxl>- Control summary length (default: xl)--plain- Output without ANSI formatting (for piping/saving)--prompt <text>- Custom instruction for the summary--no-cache- Bypass cache for fresh content
# Get full API documentation as markdown
summarize "https://bun.sh/docs/api/file-io" --extract --format md
# Summarize with specific focus
summarize "https://docs.example.com/api" --prompt "Focus on authentication methods and code examples"
# Save extracted docs to a file
summarize "https://example.com/docs" --extract --format md --plain > docs.mdUse gh for GitHub interactions instead of the GitHub API directly.
# Open repo in browser
gh browse --repo owner/repo
# Get raw file content URL (use with summarize for docs)
gh browse --repo owner/repo --no-browser path/to/file.md
# View README
gh repo view owner/repo# View issue/PR details
gh issue view 123 --repo owner/repo
gh pr view 456 --repo owner/repo
# List issues/PRs
gh issue list --repo owner/repo
gh pr list --repo owner/repo# Raw API calls when needed
gh api repos/owner/repo/contents/path/to/fileBreak implementation into small discrete tasks to avoid AWS Bedrock output token limits. Use TodoWrite to track progress and complete one task at a time before moving to the next.
TypeScript interfaces for automation. Import from ./scripts:
import { acli, peekaboo, chrome, gh, markdownToAdf } from "./scripts";| Tool | Purpose | Source |
|---|---|---|
acli |
Jira workitems, projects, boards | scripts/lib/acli/ |
peekaboo |
macOS UI automation (screenshots, clicks, typing) | scripts/lib/peekaboo/ |
chrome |
Browser automation (navigate, click, screenshot) | scripts/lib/chrome/ |
gh |
GitHub Actions workflow development | scripts/lib/gh/ |
markdownToAdf |
Convert markdown to Atlassian Document Format | scripts/lib/md-to-adf.ts |
For incremental exploration of available functions, each library includes:
manifest.json- Index of all functions organized by categorydocs/*.md- Focused documentation per category (~50-80 lines each)
Discovery workflow:
- Read
scripts/lib/{library}/manifest.jsonto see available categories - Read
scripts/lib/{library}/docs/{category}.mdfor specific functions - Import and use:
import { chrome } from "./scripts"
Example:
# See what's available in chrome library
cat scripts/lib/chrome/manifest.json
# Read specific category docs
cat scripts/lib/chrome/docs/input.mdFor full API reference, use the skills: /chrome-devtools, /peekaboo-macos, /acli-jira, /gh-workflows.
// Search and view
const issues = await acli.workitem.search({ jql: "project = TEAM" });
const issue = await acli.workitem.view("TEAM-123");
// Create/edit (use descriptionMarkdown for auto-conversion)
await acli.workitem.create({
project: "TEAM",
type: "Task",
summary: "Title",
descriptionMarkdown: "# Desc",
});
await acli.workitem.edit({ key: "TEAM-123", descriptionMarkdown: "Updated" });
// Comments and transitions
await acli.workitem.comment.create({
key: "TEAM-123",
bodyMarkdown: "Comment text",
});
await acli.workitem.transition({ key: "TEAM-123", status: "Done" });// Vision and screenshots
const { data } = await peekaboo.see({ annotate: true }); // Detect UI elements
await peekaboo.image({ path: "/tmp/screenshot.png" });
// Interaction (use element IDs from see())
await peekaboo.click({ on: "B1" });
await peekaboo.type({ text: "Hello" });
await peekaboo.hotkey({ keys: ["cmd", "c"] });
// Apps and windows
await peekaboo.app.launch({ name: "Safari" });
await peekaboo.window.focus({ app: "Safari" });// Navigate and snapshot
await chrome.navigate({ url: "https://example.com" });
const snapshot = await chrome.snapshot(); // Returns element UIDs
// Interact with elements (UIDs from snapshot)
await chrome.click({ uid: "button-123" });
await chrome.fill({ uid: "input-456", value: "hello" });
await chrome.pressKey({ key: "Enter" });
// Wait and screenshot
await chrome.waitFor({ text: "Success" });
await chrome.screenshot({ filePath: "/tmp/screen.png" });
// Clean up
await chrome.close();For full APIs, read the TypeScript source files or use the skills in .claude/skills/.
Version is stored in VERSION file (semver format: M.m.p).
- Update
VERSIONfile with new version - Commit and push:
committer "chore: bump version to X.Y.Z" VERSION && git push - Trigger release workflow:
gh workflow run release.yaml
Or use the gh library:
import { runAndWatch } from "./scripts";
const run = await runAndWatch("release.yaml");
console.log(run.conclusion); // "success" or "failure"The .github/workflows/release.yaml workflow:
- Parses
VERSIONfile from HEAD of main - Creates/updates three git tags:
vM- Latest in major version (e.g.,v0)vM.m- Latest in minor version (e.g.,v0.7)vM.m.p- Exact release (e.g.,v0.7.0)
- Creates GitHub release on
vM.m.ptag - Generates release notes with:
- Full version with build metadata:
0.7.0+gh.<run_id>.<short_sha> - Install command
- Traceability links to commit and workflow run
- Full version with build metadata:
| Reference | Example | Use Case |
|---|---|---|
vM |
v0 |
Always latest, may have breaking changes |
vM.m |
v0.7 |
Latest patches, stable features |
vM.m.p |
v0.7.0 |
Exact version, fully reproducible |
Use the setup script to install agent-scripts into another project:
curl -fsSL https://raw.githubusercontent.com/ajbeck/agent-scripts/main/scripts/setup.ts | bun run - --target /path/to/projectOptions:
--target <path>- Target project directory (default: current directory)--project <key>- Default Jira project key for examples--dry-run- Preview changes without applying--skip-deps- Skip dependency installation
The script installs:
agent-scripts/- Libraries withmanifest.jsonanddocs/for each tool.claude/rules/agent-scripts.md- Auto-loaded instructions (no CLAUDE.md changes needed).claude/skills/- Full API reference skills
Claude Code automatically discovers everything - no manual configuration required.