agent-changelog 1.0.1

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/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # agent-changelog
2
+
3
+ A versioning skill for OpenClaw that keeps a clear history of workspace changes with sender attribution.
4
+
5
+ Use it to answer questions like:
6
+
7
+ - Who changed this file?
8
+ - What changed between two points in time?
9
+ - Can I roll back to a known good state?
10
+
11
+ ## What you get
12
+
13
+ - Automatic capture of tracked file changes between turns
14
+ - Batched git commits every 10 minutes with per-sender attribution
15
+ - Chat/CLI commands for status, log, diff, rollback, and restore
16
+ - Optional push to your remote after each batched commit
17
+
18
+ ## Quick start
19
+
20
+ Requirements: `git`, `jq`, Node.js
21
+
22
+ 1. Install the skill into your OpenClaw workspace:
23
+
24
+ ```bash
25
+ npx agent-changelog
26
+ ```
27
+ Defaults to `~/.openclaw/workspace` (or `OPENCLAW_WORKSPACE` if set).
28
+ 2. In your terminal, restart the gateway so the skill is picked up:
29
+
30
+ ```bash
31
+ openclaw gateway restart
32
+ ```
33
+
34
+ 3. In chat, run:
35
+
36
+ ```bash
37
+ /agent-changelog setup
38
+ ```
39
+ ![Setup command flow in chat](images/setup.gif)
40
+
41
+ 4. Restart the gateway again to activate the installed hooks:
42
+
43
+ ```bash
44
+ openclaw gateway restart
45
+ ```
46
+
47
+ 5. Verify:
48
+
49
+ ```bash
50
+ /agent-changelog status
51
+ ```
52
+ ![Status command output example](images/status.gif)
53
+
54
+ **Optional — connect to GitHub:**
55
+ After setup, the agent can walk you through linking the workspace to a GitHub repo. Just ask:
56
+ ```text
57
+ /agent-changelog ok help me set up github
58
+ ```
59
+ It will handle git identity, auth (SSH or HTTPS), remote configuration, and the initial push — no prep work needed on your end.
60
+
61
+ ## Example usages
62
+
63
+ Check the latest commit and pending changes:
64
+ ```text
65
+ /agent-changelog show me the recent changes
66
+ ```
67
+
68
+ Browse specific recent history:
69
+ ```text
70
+ /agent-changelog show me the last 10 changes made to the SOUL file
71
+ ```
72
+
73
+ See what is pending before the next batch commit:
74
+ ```text
75
+ /agent-changelog what are the uncommitted changes?
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ After setup, `.agent-changelog.json` is created (if missing) and defaults to tracking the entire workspace:
81
+
82
+ ```json
83
+ {
84
+ "tracked": [
85
+ "."
86
+ ]
87
+ }
88
+ ```
89
+
90
+ Edit this file to narrow or expand what gets tracked.
91
+
92
+ ## A note on secrets
93
+
94
+ By default, agent-changelog tracks your entire workspace. Setup creates a `.gitignore` that excludes common secret patterns — `.env` files, API keys, tokens, credentials, cloud config directories, and more.
95
+
96
+ A few things to be careful about:
97
+
98
+ - **If your workspace already has a `.gitignore`**, setup leaves it untouched. Make sure it excludes anything sensitive before enabling tracking.
99
+ - **If you're pushing to a remote**, audit your workspace for hardcoded secrets in tracked files (SOUL.md, AGENTS.md, etc.) before the first push.
100
+ - **Narrow your tracking** if you're unsure. Edit `.agent-changelog.json` to list only the specific files you want versioned instead of `.`.
101
+ - **Auto-push is on if a remote exists.** If a git remote is configured in your workspace, every batch commit will be pushed automatically. Remove the remote or don't connect to GitHub if you want local-only history.
102
+
103
+ ## In one minute: how it behaves
104
+
105
+ - On `message:received`, sender details are captured.
106
+ - On `message:sent`, tracked file changes are staged and queued with attribution.
107
+ - Every 10 minutes, queued entries are committed together with grouped attribution.
108
+
109
+ This gives you low-noise, attributable history without manual git bookkeeping every turn.
110
+
111
+ ## FAQ
112
+
113
+ **Does this work without OpenClaw?**
114
+ No. The hooks rely on OpenClaw's event system (`message:received` / `message:sent`), and setup uses the `openclaw` CLI to register crons and enable hooks. It's built specifically for OpenClaw and won't run on another platform without significant rework.
115
+
116
+ **Can I sync history to GitHub?**
117
+ Yes. After setup, ask the agent to help you connect to GitHub and it will handle everything — git identity, auth, remote configuration, and the initial push. Once a remote is configured, every future batch commit is pushed automatically.
118
+
119
+ ## Workspace files
120
+
121
+ | File | Purpose |
122
+ | --------------------------- | ----------------------------------------------------------------------------- |
123
+ | `.agent-changelog.json` | Your tracked-files configuration |
124
+ | `.version-context` | Temporary sender handoff between hooks (not committed) |
125
+ | `pending_commits.jsonl` | Pending attribution entries waiting for the next batch commit (not committed) |
package/SKILL.md ADDED
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: agent-changelog
3
+ description: Advanced handling for agent-changelog requests (history, diffs, restores, rollbacks, snapshots) using git and OpenClaw scripts with clear, user-focused summaries and outputs.
4
+ user-invocable: true
5
+ metadata: {"openclaw":{"requires":{"bins":["git","jq","node"],"env":["OPENCLAW_WORKSPACE","OPENCLAW_CONFIG"]}}}
6
+ ---
7
+
8
+ # Agent Changelog
9
+
10
+ OpenClaw tracks workspace file changes between turns and attributes them to the user who triggered the change. Use this skill to answer history and diff questions and to apply controlled restores or rollbacks.
11
+
12
+ ## When To Use
13
+
14
+ Use this skill when the user asks about:
15
+ - What changed, who changed it, or when it changed
16
+ - Diffs between versions or commits
17
+ - Rolling back or restoring files
18
+ - Taking or inspecting snapshots or status
19
+ - Setting up or verifying auto-versioning
20
+
21
+ ## Response Framework
22
+
23
+ 1. **Clarify intent and scope.**
24
+ - Determine whether the user wants a quick summary or raw output.
25
+ - Pin down file(s), time range, and commit identifiers if needed.
26
+
27
+ 2. **Choose the evidence source.**
28
+ - Casual queries: use git to gather a compact view.
29
+ - Explicit `/agent-changelog` invocations: run the matching script and return stdout verbatim.
30
+
31
+ 3. **Present results clearly.**
32
+ - Summarize what changed, who triggered it, and the rough size.
33
+ - Offer the next most likely action (diff, restore, rollback, or log).
34
+
35
+ 4. **Handle destructive actions safely.**
36
+ - Always show what will change before a rollback or restore.
37
+ - Prefer `restore` for single-file fixes; use `rollback` only when the user wants to revert everything.
38
+ - If the target commit is ambiguous, ask a clarification question.
39
+
40
+ 5. **Guide GitHub onboarding for setup.**
41
+ - After `setup`, proactively ask: "ok do you want help with github?"
42
+ - If yes, walk them through GitHub onboarding with no extra setup steps required on their side.
43
+ - Confirm account status, git identity, auth method, and remote configuration.
44
+
45
+ ## Output Style
46
+
47
+ - For summaries, keep it short and conversational.
48
+ - For script-driven output, do not reformat or summarize; if onboarding guidance is needed, provide it after the raw output.
49
+ - If an argument looks like a typo, confirm before running.
50
+
51
+ ## File Content Rules
52
+
53
+ **Never embed attribution metadata inside file content.** Do not add inline annotations like `(updated by X on date)`, `# changed by Y`, status footnotes, or any other authorship/timestamp markers into the files you edit. Attribution belongs exclusively in the git commit message, which is handled automatically by the hooks and `commit.sh`. Files should contain only their actual content — clean, annotation-free.
54
+
55
+ ## Implementation Notes
56
+
57
+ - **Casual history or diff:** use a small git window (last 5-10 commits) and include stat output.
58
+ - **Slash commands:** use the scripts in `setup.sh` and `scripts/` with the user-provided arguments.
59
+ - **Setup:** run the setup script, then ask "ok do you want help with github?" and proceed if they confirm.
60
+ - **Restore or rollback:** locate the commit via `log`, then perform the change after showing what will be modified.
61
+ - **Semantic summary:** before every commit, run a quick diff and generate a sparse one-line summary of what changed and why (e.g. "added rate-limit rule to AGENTS.md, updated memory skill"). Always pass it via `--summary` and always include it in any history output presented to the user.
62
+ - **Log output:** `log.sh` outputs raw structured data — present it conversationally based on what the user asked. Don't dump raw script output. Format each entry using the `│`-prefixed box style (same as status output), one entry per block.
63
+
64
+ ## Command Reference (Compact)
65
+
66
+ Use this only for explicit `/agent-changelog` invocations, and return stdout verbatim.
67
+
68
+ - `setup` -> `bash {baseDir}/setup.sh`
69
+ - `setup` follow-up -> GitHub onboarding guidance
70
+ - `status` -> `bash {baseDir}/scripts/status.sh`
71
+ - `log` -> `bash {baseDir}/scripts/log.sh [count]`
72
+ - `diff` -> `bash {baseDir}/scripts/diff.sh [commit] [commit2]`
73
+ - `rollback` -> `bash {baseDir}/scripts/rollback.sh <commit> ["reason"]`
74
+ - `restore` -> `bash {baseDir}/scripts/restore.sh <file> <commit> ["reason"]`
75
+ - `commit` (user-requested) -> `bash {baseDir}/scripts/commit.sh --manual ["message"] [--summary "one-line semantic summary"]`
76
+ - `commit` (cron-triggered) -> `bash {baseDir}/scripts/commit.sh [--summary "one-line semantic summary"]`
77
+
78
+ ## Auto-Versioning Overview
79
+
80
+ Two hooks capture and commit changes between turns and attribute them to the active user. Defaults can be overridden via `.agent-changelog.json`.
81
+
82
+ Tracked by default: `.` (entire workspace). Secrets and runtime files are excluded via the `.gitignore` that setup creates — note that if a `.gitignore` already exists in the workspace, setup leaves it untouched, so ensure it covers secrets before enabling tracking.
83
+
84
+ To track a specific subset instead, edit `<workspace>/.agent-changelog.json` with a `tracked` array (this fully replaces the default):
85
+ ```json
86
+ { "tracked": ["<file-or-folder>", "<file-or-folder>"] }
87
+ ```
88
+
89
+ ## GitHub Onboarding (Setup Add-on)
90
+
91
+ Use this flow after setup to help users connect the workspace to GitHub. The user will need to authenticate (SSH key or HTTPS credential) — walk them through it step by step:
92
+
93
+ 1. **Account and intent.** Confirm they have a GitHub account and want this repo linked.
94
+ 2. **Git identity.** Ensure `user.name` and `user.email` are set for commits.
95
+ 3. **Auth method.** Offer SSH or HTTPS; proceed with their preference.
96
+ 4. **Remote and verify.** Ensure an `origin` remote exists and verify access.
97
+ 5. **Next action.** Create or select the GitHub repo, then push or fetch as needed.
98
+
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+
7
+ const skillName = "agent-changelog";
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const workspace =
10
+ process.env.OPENCLAW_WORKSPACE ||
11
+ path.join(os.homedir(), ".openclaw", "workspace");
12
+ const skillsDir = path.join(workspace, "skills");
13
+ const targetDir = path.join(skillsDir, skillName);
14
+
15
+ function copyFile(src, dest) {
16
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
17
+ fs.copyFileSync(src, dest);
18
+ }
19
+
20
+ function copyDir(srcDir, destDir) {
21
+ if (!fs.existsSync(srcDir)) return;
22
+ fs.mkdirSync(destDir, { recursive: true });
23
+
24
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
25
+ const src = path.join(srcDir, entry.name);
26
+ const dest = path.join(destDir, entry.name);
27
+
28
+ if (entry.isDirectory()) {
29
+ copyDir(src, dest);
30
+ } else if (entry.isFile()) {
31
+ copyFile(src, dest);
32
+ }
33
+ }
34
+ }
35
+
36
+ if (!fs.existsSync(workspace)) {
37
+ console.error(`OpenClaw workspace not found: ${workspace}`);
38
+ console.error("Set OPENCLAW_WORKSPACE or run OpenClaw once to create it.");
39
+ process.exit(1);
40
+ }
41
+
42
+ fs.mkdirSync(skillsDir, { recursive: true });
43
+ fs.mkdirSync(targetDir, { recursive: true });
44
+
45
+ for (const file of ["SKILL.md", "setup.sh"]) {
46
+ const src = path.join(packageRoot, file);
47
+ if (!fs.existsSync(src)) {
48
+ console.error(`Missing required file: ${file}`);
49
+ process.exit(1);
50
+ }
51
+ copyFile(src, path.join(targetDir, file));
52
+ }
53
+
54
+ for (const dir of ["hooks", "scripts"]) {
55
+ const src = path.join(packageRoot, dir);
56
+ copyDir(src, path.join(targetDir, dir));
57
+ }
58
+
59
+ console.log(`Installed ${skillName} to ${targetDir}`);
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: agent-changelog-capture
3
+ description: "Captures sender identity before each agent turn for commit attribution"
4
+ metadata: { "openclaw": { "emoji": "📸", "events": ["message:received"] } }
5
+ ---
6
+
7
+ # Agent Changelog — Capture
8
+
9
+ Writes sender identity to `.version-context` in the workspace before each agent turn. The companion `agent-changelog-commit` hook reads this to attribute git commits to the correct user.
10
+
11
+ Part of the `agent-changelog` skill. Install via `setup.sh`.
@@ -0,0 +1,35 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ const handler = async (event: any) => {
5
+ if (event.type !== "message" || event.action !== "received") return;
6
+
7
+ const workspace =
8
+ process.env.OPENCLAW_WORKSPACE ?? `${process.env.HOME}/.openclaw/workspace`;
9
+
10
+ const ctx = {
11
+ user:
12
+ event.context?.senderName ??
13
+ event.context?.metadata?.senderName ??
14
+ event.context?.from ??
15
+ "unknown",
16
+ userId:
17
+ event.context?.senderId ??
18
+ event.context?.metadata?.senderId ??
19
+ event.context?.from ??
20
+ "unknown",
21
+ channel: event.context?.channelId ?? "unknown",
22
+ ts: Date.now(),
23
+ };
24
+
25
+ try {
26
+ writeFileSync(join(workspace, ".version-context"), JSON.stringify(ctx));
27
+ } catch (err) {
28
+ console.error(
29
+ "[agent-changelog-capture] Failed to write context:",
30
+ err instanceof Error ? err.message : String(err)
31
+ );
32
+ }
33
+ };
34
+
35
+ export default handler;
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: agent-changelog-commit
3
+ description: "Auto-commits workspace file changes with sender attribution after each agent turn"
4
+ metadata: { "openclaw": { "emoji": "📝", "events": ["message:sent"], "requires": { "bins": ["git"] } } }
5
+ ---
6
+
7
+ # Agent Changelog — Commit
8
+
9
+ After each outbound message, stages tracked workspace files and queues sender attribution in the commit message body.
10
+
11
+ Tracked files are read from `.agent-changelog.json` in the workspace, written by `setup.sh` on install.
12
+
13
+ Part of the `agent-changelog` skill. Install via `setup.sh`.
@@ -0,0 +1,97 @@
1
+ import { execSync } from "node:child_process";
2
+ import {
3
+ appendFileSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ rmdirSync,
8
+ unlinkSync,
9
+ } from "node:fs";
10
+ import { join } from "node:path";
11
+
12
+ function run(cmd: string, cwd: string): string {
13
+ try {
14
+ return execSync(cmd, { cwd, encoding: "utf-8", timeout: 15_000 }).trim();
15
+ } catch {
16
+ return "";
17
+ }
18
+ }
19
+
20
+ function getTracked(workspace: string): string[] {
21
+ const cfgPath = join(workspace, ".agent-changelog.json");
22
+ try {
23
+ const cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
24
+ if (Array.isArray(cfg.tracked) && cfg.tracked.length > 0) return cfg.tracked;
25
+ } catch {}
26
+ return [];
27
+ }
28
+
29
+ async function acquireLock(lockDir: string): Promise<boolean> {
30
+ for (let i = 0; i < 50; i++) {
31
+ try {
32
+ mkdirSync(lockDir);
33
+ return true;
34
+ } catch {
35
+ await new Promise((r) => setTimeout(r, 100));
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+
41
+ const handler = async (event: any) => {
42
+ if (event.type !== "message" || event.action !== "sent") return;
43
+
44
+ const workspace =
45
+ process.env.OPENCLAW_WORKSPACE ?? `${process.env.HOME}/.openclaw/workspace`;
46
+
47
+ if (!existsSync(join(workspace, ".git"))) return;
48
+
49
+ const lockDir = join(workspace, ".version-lock");
50
+ const acquired = await acquireLock(lockDir);
51
+ if (!acquired) {
52
+ console.error("[agent-changelog-commit] Could not acquire lock, skipping");
53
+ return;
54
+ }
55
+
56
+ try {
57
+ // Detect changes since the last git add (working tree vs index).
58
+ const changed = run("git diff --name-only", workspace)
59
+ .split("\n")
60
+ .filter(Boolean);
61
+
62
+ if (changed.length === 0) return;
63
+
64
+ // Read sender identity written by the capture hook
65
+ let user = "unknown";
66
+ let userId = "unknown";
67
+ let channel = "unknown";
68
+ const ctxPath = join(workspace, ".version-context");
69
+ if (existsSync(ctxPath)) {
70
+ try {
71
+ const ctx = JSON.parse(readFileSync(ctxPath, "utf-8"));
72
+ user = ctx.user ?? "unknown";
73
+ userId = ctx.userId ?? "unknown";
74
+ channel = ctx.channel ?? "unknown";
75
+ } catch {}
76
+ }
77
+
78
+ // Append entry to pending log
79
+ const entry = JSON.stringify({ ts: Date.now(), user, userId, channel, files: changed });
80
+ appendFileSync(join(workspace, "pending_commits.jsonl"), entry + "\n");
81
+
82
+ // Stage tracked files
83
+ for (const f of getTracked(workspace)) {
84
+ run(`git add "${f}" 2>/dev/null || true`, workspace);
85
+ }
86
+ } catch (err) {
87
+ console.error(
88
+ "[agent-changelog-commit] Error:",
89
+ err instanceof Error ? err.message : String(err)
90
+ );
91
+ } finally {
92
+ try { rmdirSync(lockDir); } catch {}
93
+ try { unlinkSync(join(workspace, ".version-context")); } catch {}
94
+ }
95
+ };
96
+
97
+ export default handler;
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "agent-changelog",
3
+ "version": "1.0.1",
4
+ "description": "Install the agent-changelog OpenClaw skill into your OpenClaw workspace.",
5
+ "bin": {
6
+ "agent-changelog": "bin/agent-changelog.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "SKILL.md",
11
+ "setup.sh",
12
+ "hooks/",
13
+ "scripts/",
14
+ "README.md"
15
+ ]
16
+ }
@@ -0,0 +1,174 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
5
+ cd "$WORKSPACE"
6
+
7
+ if [ ! -d .git ]; then
8
+ echo "⚠️ Versioning not initialized"
9
+ echo "Run \`/agent-changelog setup\` to get started."
10
+ exit 1
11
+ fi
12
+
13
+ MANUAL=false
14
+ MESSAGE=""
15
+ SUMMARY=""
16
+ for arg in "$@"; do
17
+ case "$arg" in
18
+ --manual) MANUAL=true ;;
19
+ --summary) shift_next=true ;;
20
+ *)
21
+ if [ "${shift_next:-false}" = true ]; then
22
+ SUMMARY="$arg"
23
+ shift_next=false
24
+ elif [ -z "$MESSAGE" ]; then
25
+ MESSAGE="$arg"
26
+ fi
27
+ ;;
28
+ esac
29
+ done
30
+
31
+ PENDING="$WORKSPACE/pending_commits.jsonl"
32
+
33
+ # ─── Resolve tracked files ────────────────────────────────────────────
34
+ TRACKED=()
35
+ while IFS= read -r item; do
36
+ TRACKED+=("$item")
37
+ done < <(jq -r '.tracked[]?' "$WORKSPACE/.agent-changelog.json" 2>/dev/null)
38
+
39
+ # ─── Stage any unstaged changes to tracked files ─────────────────────
40
+ for f in "${TRACKED[@]+"${TRACKED[@]}"}"; do
41
+ git add "$f" 2>/dev/null || true
42
+ done
43
+
44
+ # ─── Nothing staged at all → nothing to do ───────────────────────────
45
+ if git diff --cached --quiet 2>/dev/null; then
46
+ [ -f "$PENDING" ] && > "$PENDING"
47
+ echo "✓ No changes to commit"
48
+ exit 0
49
+ fi
50
+
51
+ # ─── Build commit message ─────────────────────────────────────────────
52
+ STAGED_FILES=$(git diff --cached --name-only | tr '\n' ' ' | sed 's/ $//')
53
+ USERS=""
54
+ COUNT=0
55
+ HAS_PENDING=false
56
+ CHANGELOG=""
57
+
58
+ if [ -f "$PENDING" ] && [ -s "$PENDING" ]; then
59
+ HAS_PENDING=true
60
+ while IFS= read -r line; do
61
+ [ -z "$line" ] && continue
62
+ COUNT=$((COUNT + 1))
63
+
64
+ if command -v jq &>/dev/null; then
65
+ user=$(echo "$line" | jq -r '.user // "unknown"' 2>/dev/null || echo "unknown")
66
+ ts=$(echo "$line" | jq -r '.ts // 0' 2>/dev/null || echo "0")
67
+ channel=$(echo "$line" | jq -r '.channel // "unknown"' 2>/dev/null || echo "unknown")
68
+ files=$(echo "$line" | jq -r '(.files // []) | join(", ")' 2>/dev/null || echo "")
69
+ action=$(echo "$line" | jq -r '.action // ""' 2>/dev/null || echo "")
70
+ action_target=$(echo "$line" | jq -r '.target // ""' 2>/dev/null || echo "")
71
+ action_file=$(echo "$line" | jq -r '.file // ""' 2>/dev/null || echo "")
72
+ action_from=$(echo "$line" | jq -r '.from // ""' 2>/dev/null || echo "")
73
+ action_reason=$(echo "$line" | jq -r '.reason // ""' 2>/dev/null || echo "")
74
+ else
75
+ user=$(echo "$line" | grep -o '"user":"[^"]*"' | cut -d'"' -f4 || echo "unknown")
76
+ ts="0"; channel="unknown"; files=""; action=""; action_target=""; action_file=""; action_from=""; action_reason=""
77
+ fi
78
+
79
+ # Format timestamp as readable date
80
+ if [ "$ts" != "0" ] && command -v date &>/dev/null; then
81
+ ts_sec=$((ts / 1000))
82
+ readable=$(date -r "$ts_sec" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$ts")
83
+ else
84
+ readable="$ts"
85
+ fi
86
+
87
+ # Accumulate unique users
88
+ if [ -n "$user" ] && [ "$user" != "unknown" ]; then
89
+ if [ -z "$USERS" ]; then
90
+ USERS="$user"
91
+ elif ! echo "$USERS" | grep -qF "$user"; then
92
+ USERS="$USERS, $user"
93
+ fi
94
+ fi
95
+
96
+ # Build per-turn changelog line
97
+ if [ "$action" = "rollback" ]; then
98
+ CHANGELOG="${CHANGELOG} [$readable] $user\n"
99
+ CHANGELOG="${CHANGELOG} action: rollback → $action_target\n"
100
+ [ -n "$action_reason" ] && CHANGELOG="${CHANGELOG} reason: $action_reason\n"
101
+ elif [ "$action" = "restore" ]; then
102
+ CHANGELOG="${CHANGELOG} [$readable] $user\n"
103
+ CHANGELOG="${CHANGELOG} action: restore $action_file from $action_from\n"
104
+ [ -n "$action_reason" ] && CHANGELOG="${CHANGELOG} reason: $action_reason\n"
105
+ else
106
+ CHANGELOG="${CHANGELOG} [$readable] $user ($channel): $files\n"
107
+ fi
108
+ done < "$PENDING"
109
+ fi
110
+
111
+ # ─── Determine prefix ─────────────────────────────────────────────────
112
+ CTX="$WORKSPACE/.version-context"
113
+
114
+ if [ "$MANUAL" = true ]; then
115
+ PREFIX="Manual commit"
116
+ # For manual commits, prefer identity from version-context (set by capture hook)
117
+ if [ -z "$USERS" ] || [ "$USERS" = "unknown" ]; then
118
+ if [ -f "$CTX" ] && command -v jq &>/dev/null; then
119
+ CTX_USER=$(jq -r '.user // ""' "$CTX" 2>/dev/null || true)
120
+ [ -n "$CTX_USER" ] && [ "$CTX_USER" != "unknown" ] && USERS="$CTX_USER"
121
+ fi
122
+ fi
123
+ if [ -z "$USERS" ] || [ "$USERS" = "unknown" ]; then USERS="skill invocation"; fi
124
+ elif [ "$HAS_PENDING" = false ] || [ "$COUNT" -eq 0 ]; then
125
+ PREFIX="Auto-commit (cli)"
126
+ USERS="cli"
127
+ else
128
+ PREFIX="Auto-commit"
129
+ fi
130
+
131
+ if [ -z "$USERS" ]; then USERS="unknown"; fi
132
+
133
+ # ─── Assemble full message ────────────────────────────────────────────
134
+ if [ -n "$MESSAGE" ]; then
135
+ SUBJECT="$MESSAGE"
136
+ else
137
+ SUBJECT="${PREFIX}: $STAGED_FILES"
138
+ fi
139
+
140
+ MSG="$SUBJECT
141
+
142
+ Triggered by: ${USERS}
143
+ Turns: ${COUNT}"
144
+
145
+ [ -n "$SUMMARY" ] && MSG="${MSG}
146
+ Summary: ${SUMMARY}"
147
+
148
+ if [ -n "$CHANGELOG" ]; then
149
+ MSG="${MSG}
150
+
151
+ --- Change log ---
152
+ $(printf "%b" "$CHANGELOG")"
153
+ fi
154
+
155
+ git commit -m "$MSG"
156
+ SHORT_HASH=$(git rev-parse --short HEAD)
157
+
158
+ [ -f "$PENDING" ] && > "$PENDING"
159
+
160
+ echo "✅ **Committed** \`$SHORT_HASH\`"
161
+ echo "$SUBJECT"
162
+ echo "_by ${USERS}_"
163
+ [ -n "$SUMMARY" ] && echo "$SUMMARY"
164
+
165
+ # ─── Push if remote is configured ────────────────────────────────────
166
+ GIT_REMOTE=$(git remote 2>/dev/null | head -1)
167
+
168
+ if [ -n "$GIT_REMOTE" ]; then
169
+ if git push "$GIT_REMOTE" 2>/dev/null; then
170
+ echo "↑ Pushed to \`$GIT_REMOTE\`"
171
+ else
172
+ echo "⚠️ Push to \`$GIT_REMOTE\` failed"
173
+ fi
174
+ fi
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
5
+ cd "$WORKSPACE"
6
+
7
+ if [ ! -d .git ]; then
8
+ echo "⚠️ Versioning not initialized"
9
+ echo "Run \`/agent-changelog setup\` to get started."
10
+ exit 1
11
+ fi
12
+
13
+ if [ $# -eq 0 ]; then
14
+ CHANGED=$(git diff HEAD --name-only --no-color 2>/dev/null | tr '\n' ' ' | sed 's/ $//' || true)
15
+ if [ -z "$CHANGED" ]; then
16
+ echo "✓ No uncommitted changes"
17
+ exit 0
18
+ fi
19
+ echo "✏️ **Uncommitted changes**"
20
+ echo "\`$CHANGED\`"
21
+ echo ""
22
+ echo '```diff'
23
+ git diff HEAD --no-color 2>/dev/null || git diff --no-color
24
+ echo '```'
25
+ elif [ $# -eq 1 ]; then
26
+ SUBJECT=$(git log --format="%s" -1 "$1" 2>/dev/null || true)
27
+ DATE=$(git log --format="%ad" --date=format:"%b %d, %H:%M" -1 "$1" 2>/dev/null || true)
28
+ echo "🔍 **\`$1\`** · $DATE"
29
+ echo "_$SUBJECT_"
30
+ echo ""
31
+ echo '```diff'
32
+ git show "$1" --stat --patch --no-color
33
+ echo '```'
34
+ elif [ $# -eq 2 ]; then
35
+ echo "🔍 **Diff** \`$1\` → \`$2\`"
36
+ echo ""
37
+ echo '```diff'
38
+ git diff "$1" "$2" --no-color
39
+ echo '```'
40
+ fi
package/scripts/log.sh ADDED
@@ -0,0 +1,39 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
5
+ cd "$WORKSPACE"
6
+
7
+ if [ ! -d .git ]; then
8
+ echo "⚠️ Versioning not initialized"
9
+ echo "Run \`/agent-changelog setup\` to get started."
10
+ exit 1
11
+ fi
12
+
13
+ COUNT=10
14
+ for arg in "$@"; do
15
+ case "$arg" in
16
+ [0-9]*) COUNT="$arg" ;;
17
+ esac
18
+ done
19
+
20
+ [ "${COUNT:-0}" -le 0 ] && COUNT=10
21
+
22
+ while IFS= read -r hash; do
23
+ date=$(git log --format="%ad" --date=format:"%b %d, %H:%M" -1 "$hash")
24
+ subject=$(git log --format="%s" -1 "$hash")
25
+ body=$(git log --format="%b" -1 "$hash")
26
+ triggered=$(echo "$body" | grep "^Triggered by:" | sed 's/Triggered by: //' || true)
27
+ turns=$(echo "$body" | grep "^Turns:" | sed 's/Turns: //' || true)
28
+ summary=$(echo "$body" | grep "^Summary:" | sed 's/Summary: //' || true)
29
+ changelog=$(echo "$body" | awk '/^--- Change log ---/{found=1; next} found{print}' || true)
30
+
31
+ echo "commit $hash"
32
+ echo "date: $date"
33
+ echo "subject: $subject"
34
+ [ -n "$triggered" ] && echo "by: $triggered"
35
+ [ -n "$turns" ] && echo "turns: $turns"
36
+ [ -n "$summary" ] && echo "summary: $summary"
37
+ [ -n "$changelog" ] && printf "changelog:\n%s\n" "$changelog"
38
+ echo "---"
39
+ done < <(git log --format="%h" -n "$COUNT")
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
5
+ cd "$WORKSPACE"
6
+
7
+ if [ ! -d .git ]; then
8
+ echo "⚠️ Versioning not initialized"
9
+ echo "Run \`/agent-changelog setup\` to get started."
10
+ exit 1
11
+ fi
12
+
13
+ FILE="${1:-}"
14
+ COMMIT="${2:-}"
15
+ REASON="${3:-}"
16
+
17
+ if [ -z "$FILE" ] || [ -z "$COMMIT" ]; then
18
+ echo "**Usage:** \`/agent-changelog restore <file> <commit> [reason]\`"
19
+ echo ""
20
+ echo "Restores a single file to its state before the given commit."
21
+ echo "Run \`/agent-changelog log\` to find the right commit."
22
+ exit 1
23
+ fi
24
+
25
+ if ! git cat-file -e "$COMMIT" 2>/dev/null; then
26
+ echo "⚠️ Commit \`$COMMIT\` not found"
27
+ exit 1
28
+ fi
29
+
30
+ if ! git diff-tree --no-commit-id -r --name-only "$COMMIT" | grep -qF "$FILE"; then
31
+ echo "⚠️ \`$FILE\` was not changed in \`$COMMIT\`"
32
+ exit 1
33
+ fi
34
+
35
+ TARGET_SHORT=$(git rev-parse --short "$COMMIT")
36
+
37
+ # Read sender identity from capture hook context
38
+ USER="unknown"
39
+ USER_ID="unknown"
40
+ CHANNEL="unknown"
41
+ CTX="$WORKSPACE/.version-context"
42
+ if [ -f "$CTX" ] && command -v jq &>/dev/null; then
43
+ USER=$(jq -r '.user // "unknown"' "$CTX" 2>/dev/null || echo "unknown")
44
+ USER_ID=$(jq -r '.userId // "unknown"' "$CTX" 2>/dev/null || echo "unknown")
45
+ CHANNEL=$(jq -r '.channel // "unknown"' "$CTX" 2>/dev/null || echo "unknown")
46
+ fi
47
+
48
+ # Restore file to its state before the target commit and stage it
49
+ git checkout "${COMMIT}^" -- "$FILE"
50
+ git add "$FILE"
51
+
52
+ # Log to pending so the next commit includes restore attribution
53
+ ENTRY=$(jq -n \
54
+ --argjson ts "$(date +%s000)" \
55
+ --arg user "$USER" \
56
+ --arg userId "$USER_ID" \
57
+ --arg channel "$CHANNEL" \
58
+ --arg file "$FILE" \
59
+ --arg from "$TARGET_SHORT" \
60
+ --arg reason "$REASON" \
61
+ '{"ts":$ts,"user":$user,"userId":$userId,"channel":$channel,"action":"restore","file":$file,"from":$from,"reason":$reason,"files":[]}')
62
+ printf '%s\n' "$ENTRY" >> "$WORKSPACE/pending_commits.jsonl"
63
+
64
+ echo "📌 **Staged restore**"
65
+ echo "\`$FILE\` → before \`$TARGET_SHORT\`"
66
+ echo "_by ${USER}_"
67
+ echo ""
68
+ echo "Commit with \`/agent-changelog commit\`"
@@ -0,0 +1,80 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
5
+ cd "$WORKSPACE"
6
+
7
+ if [ ! -d .git ]; then
8
+ echo "⚠️ Versioning not initialized"
9
+ echo "Run \`/agent-changelog setup\` to get started."
10
+ exit 1
11
+ fi
12
+
13
+ TARGET="${1:-}"
14
+ REASON="${2:-}"
15
+
16
+ if [ -z "$TARGET" ]; then
17
+ echo "**Usage:** \`/agent-changelog rollback <commit> [reason]\`"
18
+ echo ""
19
+ echo "**Recent commits:**"
20
+ while IFS= read -r hash; do
21
+ date=$(git log --format="%ad" --date=format:"%b %d, %H:%M" -1 "$hash")
22
+ subject=$(git log --format="%s" -1 "$hash")
23
+ echo "• \`$hash\` $date — $subject"
24
+ done < <(git log --format="%h" -10)
25
+ exit 1
26
+ fi
27
+
28
+ if ! git cat-file -e "$TARGET" 2>/dev/null; then
29
+ echo "⚠️ Commit \`$TARGET\` not found"
30
+ exit 1
31
+ fi
32
+
33
+ CURRENT_SHORT=$(git rev-parse --short HEAD)
34
+ TARGET_SHORT=$(git rev-parse --short "$TARGET")
35
+ TARGET_MSG=$(git log --format="%s" -1 "$TARGET")
36
+
37
+ # Read identity from version context
38
+ CTX="$WORKSPACE/.version-context"
39
+ ACTOR="unknown"
40
+ ACTOR_ID="unknown"
41
+ CHANNEL="unknown"
42
+ if [ -f "$CTX" ] && command -v jq &>/dev/null; then
43
+ ACTOR=$(jq -r '.user // "unknown"' "$CTX" 2>/dev/null || echo "unknown")
44
+ ACTOR_ID=$(jq -r '.userId // "unknown"' "$CTX" 2>/dev/null || echo "unknown")
45
+ CHANNEL=$(jq -r '.channel // "unknown"' "$CTX" 2>/dev/null || echo "unknown")
46
+ fi
47
+ [ "$ACTOR" = "unknown" ] && ACTOR="skill invocation"
48
+ [ "$ACTOR_ID" = "unknown" ] && ACTOR_ID="skill invocation"
49
+
50
+ # Restore only tracked files
51
+ while IFS= read -r f; do
52
+ git checkout "$TARGET" -- "$f" 2>/dev/null || true
53
+ done < <(jq -r '.tracked[]?' "$WORKSPACE/.agent-changelog.json" 2>/dev/null)
54
+
55
+ # Stage the same tracked files
56
+ while IFS= read -r f; do
57
+ git add "$f" 2>/dev/null || true
58
+ done < <(jq -r '.tracked[]?' "$WORKSPACE/.agent-changelog.json" 2>/dev/null)
59
+
60
+ if ! git diff --cached --quiet; then
61
+ ENTRY=$(jq -n \
62
+ --argjson ts "$(date +%s000)" \
63
+ --arg user "$ACTOR" \
64
+ --arg userId "$ACTOR_ID" \
65
+ --arg channel "$CHANNEL" \
66
+ --arg target "$TARGET_SHORT" \
67
+ --arg reason "$REASON" \
68
+ '{"ts":$ts,"user":$user,"userId":$userId,"channel":$channel,"action":"rollback","target":$target,"reason":$reason,"files":[]}')
69
+ printf '%s\n' "$ENTRY" >> "$WORKSPACE/pending_commits.jsonl"
70
+
71
+ echo "⏪ **Staged rollback**"
72
+ echo "\`$CURRENT_SHORT\` → \`$TARGET_SHORT\`"
73
+ echo "_$TARGET_MSG_"
74
+ echo ""
75
+ echo "_by ${ACTOR}_"
76
+ echo "Commit with \`/agent-changelog commit\`"
77
+ else
78
+ echo "✓ Already at \`$TARGET_SHORT\`"
79
+ exit 0
80
+ fi
@@ -0,0 +1,74 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
5
+ cd "$WORKSPACE"
6
+
7
+ if [ ! -d .git ]; then
8
+ echo "⚠️ Versioning not initialized"
9
+ echo "Run \`/agent-changelog setup\` to get started."
10
+ exit 0
11
+ fi
12
+
13
+ HASH=$(git log --format="%h" -1 2>/dev/null || true)
14
+
15
+ if [ -z "$HASH" ]; then
16
+ echo "📸 No commits yet"
17
+ else
18
+ DATE=$(git log --format="%ad" --date=format:"%b %d, %H:%M" -1)
19
+ SUBJECT=$(git log --format="%s" -1)
20
+ BODY=$(git log --format="%b" -1)
21
+
22
+ # Parse commit type from subject prefix
23
+ if echo "$SUBJECT" | grep -qi "^auto-commit"; then
24
+ TYPE="Auto"
25
+ FILES=$(echo "$SUBJECT" | sed 's/^[Aa]uto-commit[^:]*: //')
26
+ elif echo "$SUBJECT" | grep -qi "^manual commit"; then
27
+ TYPE="Manual"
28
+ FILES=$(echo "$SUBJECT" | sed 's/^[Mm]anual commit[^:]*: //')
29
+ elif echo "$SUBJECT" | grep -qi "^rollback"; then
30
+ TYPE="Rollback"
31
+ FILES=""
32
+ else
33
+ TYPE=""
34
+ FILES="$SUBJECT"
35
+ fi
36
+
37
+ # Parse identity from commit body
38
+ IDENTITY=$(echo "$BODY" | grep "^Triggered by:" | sed 's/Triggered by: //' | tr -d '\n' | sed 's/cli/CLI/g' || true)
39
+
40
+ # Build output line
41
+ echo "📸 \`$HASH\` · $DATE"
42
+
43
+ META_PARTS=()
44
+ [ -n "$TYPE" ] && META_PARTS+=("$TYPE")
45
+ [ -n "$IDENTITY" ] && META_PARTS+=("by $IDENTITY")
46
+ [ -n "$FILES" ] && META_PARTS+=("$FILES")
47
+ if [ ${#META_PARTS[@]} -gt 0 ]; then
48
+ (IFS=' · '; echo "_${META_PARTS[*]}_")
49
+ fi
50
+ fi
51
+
52
+ echo ""
53
+
54
+ MODIFIED=$(git diff HEAD --name-only 2>/dev/null)
55
+ UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null)
56
+ ALL_CHANGES=$(printf '%s\n%s\n' "$MODIFIED" "$UNTRACKED" | grep -v '^$' || true)
57
+ CHANGES=$(printf '%s\n' "$ALL_CHANGES" | grep -c . || true)
58
+
59
+ if [ "$CHANGES" -gt 0 ]; then
60
+ LABEL=$([ "$CHANGES" -eq 1 ] && echo "file" || echo "files")
61
+ echo "✏️ **$CHANGES uncommitted $LABEL:**"
62
+ while IFS= read -r file; do
63
+ [ -z "$file" ] && continue
64
+ if echo "$MODIFIED" | grep -qF "$file"; then
65
+ added=$(git diff HEAD -- "$file" 2>/dev/null | grep -c '^+[^+]' || true)
66
+ removed=$(git diff HEAD -- "$file" 2>/dev/null | grep -c '^-[^-]' || true)
67
+ echo "• \`$file\` +$added/-$removed"
68
+ else
69
+ echo "• \`$file\` new"
70
+ fi
71
+ done <<< "$ALL_CHANGES"
72
+ else
73
+ echo "✓ No uncommitted changes"
74
+ fi
package/setup.sh ADDED
@@ -0,0 +1,217 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
6
+ HOOKS_DEST="$WORKSPACE/hooks"
7
+
8
+ success() { echo "✅ $*"; echo ""; }
9
+ warn() { echo "⚠️ $*"; echo ""; }
10
+ fail() { echo "❌ $*"; exit 1; }
11
+ header() { echo ""; echo "**$***"; echo ""; }
12
+
13
+ echo "> 🗂️ **agent-changelog**"
14
+ echo "> _workspace version control — setup_"
15
+
16
+ # ─── Prerequisites ────────────────────────────────────────────────────
17
+ header "Prerequisites"
18
+
19
+ command -v git &>/dev/null || fail "git not found — install it first"
20
+ success "git $(git --version | awk '{print $3}')"
21
+
22
+ command -v jq &>/dev/null || fail "jq not found — install it first"
23
+ success "jq $(jq --version)"
24
+
25
+ [ -d "$WORKSPACE" ] || fail "Workspace not found: $WORKSPACE"
26
+ success "workspace \`$WORKSPACE\`"
27
+
28
+ if command -v openclaw &>/dev/null; then
29
+ success "openclaw $(openclaw --version 2>/dev/null | head -1 | awk '{print $3}')"
30
+ else
31
+ warn "openclaw CLI not found — you'll need to enable hooks manually"
32
+ fi
33
+
34
+ # ─── Install hooks ────────────────────────────────────────────────────
35
+ header "🪝 Installing hooks"
36
+
37
+ mkdir -p "$HOOKS_DEST"
38
+
39
+ HOOKS=("agent-changelog-capture" "agent-changelog-commit")
40
+ for hook in "${HOOKS[@]}"; do
41
+ src="$SCRIPT_DIR/hooks/$hook"
42
+ dest="$HOOKS_DEST/$hook"
43
+
44
+ if [ ! -d "$src" ]; then
45
+ warn "Hook source not found: \`$src\`"
46
+ continue
47
+ fi
48
+
49
+ if [ -L "$dest" ]; then
50
+ rm "$dest"
51
+ elif [ -d "$dest" ]; then
52
+ rm -rf "$dest"
53
+ fi
54
+
55
+ cp -r "$src" "$dest"
56
+ success "Installed \`$hook\`"
57
+ done
58
+
59
+ # ─── Enable hooks via config ──────────────────────────────────────────
60
+ header "⚡ Activating hooks"
61
+
62
+ OPENCLAW_CFG="${OPENCLAW_CONFIG:-$HOME/.openclaw/openclaw.json}"
63
+
64
+ if [ -f "$OPENCLAW_CFG" ] && command -v jq &>/dev/null; then
65
+ TMP=$(mktemp)
66
+ jq '
67
+ .hooks.internal.enabled = true |
68
+ .hooks.internal.entries["agent-changelog-capture"].enabled = true |
69
+ .hooks.internal.entries["agent-changelog-commit"].enabled = true
70
+ ' "$OPENCLAW_CFG" > "$TMP" && mv "$TMP" "$OPENCLAW_CFG"
71
+ success "Hooks enabled in config"
72
+ else
73
+ warn "Could not update hook config — enable manually after restarting:"
74
+ for hook in "${HOOKS[@]}"; do
75
+ echo " - \`openclaw hooks enable $hook\`"
76
+ done
77
+ fi
78
+
79
+ # ─── Register cron ────────────────────────────────────────────────────
80
+ header "⏱️ Registering cron"
81
+
82
+ if command -v openclaw &>/dev/null; then
83
+ CRON_NAME="agent-changelog-commit"
84
+ CRON_CMD="bash $SCRIPT_DIR/scripts/commit.sh"
85
+ if openclaw cron list --json 2>/dev/null | jq -e --arg name "$CRON_NAME" '.jobs[] | select(.name == $name)' >/dev/null 2>&1; then
86
+ success "Cron \`$CRON_NAME\` already registered"
87
+ elif openclaw cron add \
88
+ --name "$CRON_NAME" \
89
+ --cron "*/10 * * * *" \
90
+ --message "$CRON_CMD" \
91
+ --session isolated \
92
+ --no-deliver >/dev/null 2>&1; then
93
+ success "Registered \`$CRON_NAME\` _(every 10 min)_"
94
+ else
95
+ warn "Cron registration failed — check with: \`openclaw cron list\`"
96
+ fi
97
+ else
98
+ warn "Register cron manually after gateway starts:"
99
+ echo " \`openclaw cron add --name agent-changelog-commit --cron '*/10 * * * *' --message 'bash $SCRIPT_DIR/scripts/commit.sh' --session isolated --no-deliver\`"
100
+ fi
101
+
102
+ # ─── Initialize git repo ──────────────────────────────────────────────
103
+ header "📦 Git repository"
104
+
105
+ if [ -d "$WORKSPACE/.git" ]; then
106
+ COMMIT_COUNT=$(cd "$WORKSPACE" && git rev-list --count HEAD 2>/dev/null || echo "0")
107
+ success "Already initialized _($COMMIT_COUNT commits)_"
108
+ else
109
+ (cd "$WORKSPACE" && git init -b main) >/dev/null 2>&1
110
+ success "Initialized git repository"
111
+ fi
112
+
113
+ if [ ! -f "$WORKSPACE/.gitignore" ]; then
114
+ cat > "$WORKSPACE/.gitignore" << 'GITIGNORE'
115
+ # secrets
116
+ .env
117
+ .env.*
118
+ *.env
119
+ .envrc
120
+ **/credentials/
121
+ **/secrets/
122
+ **/.credentials
123
+ **/.secrets
124
+ **/api_key*
125
+ **/apikey*
126
+ **/*_key.txt
127
+ **/*_key.json
128
+ **/*_token*
129
+ **/*_secret*
130
+ **/auth_token*
131
+ **/access_token*
132
+ **/refresh_token*
133
+ *.pem
134
+ *.key
135
+ *.p12
136
+ *.pfx
137
+ id_rsa
138
+ id_ed25519
139
+ *.ppk
140
+ client_secret*.json
141
+ service_account*.json
142
+ *-credentials.json
143
+ .aws/
144
+ .azure/
145
+ .gcloud/
146
+ gcloud*.json
147
+ aws_credentials
148
+
149
+ # runtime
150
+ *.log
151
+ *.tmp
152
+ *.temp
153
+ *.swp
154
+ *.swo
155
+ *~
156
+ *.jsonl
157
+ .version-context
158
+ state/
159
+ state.json
160
+ memory/
161
+ .DS_Store
162
+ Thumbs.db
163
+ desktop.ini
164
+
165
+ # openclaw internal
166
+ .openclaw/
167
+
168
+ # build
169
+ node_modules/
170
+ __pycache__/
171
+ *.pyc
172
+ .cache/
173
+ dist/
174
+ build/
175
+ *.egg-info/
176
+ GITIGNORE
177
+ success "Created \`.gitignore\`"
178
+ else
179
+ warn ".gitignore already exists and was left untouched. Review it before pushing to a remote to make sure secrets are excluded."
180
+ fi
181
+
182
+ # ─── Seed workspace config ────────────────────────────────────────────
183
+ header "🔧 Workspace config"
184
+
185
+ WORKSPACE_CFG="$WORKSPACE/.agent-changelog.json"
186
+ if [ ! -f "$WORKSPACE_CFG" ]; then
187
+ cat > "$WORKSPACE_CFG" << 'EOF'
188
+ {
189
+ "tracked": [
190
+ "."
191
+ ]
192
+ }
193
+ EOF
194
+ success "Created \`.agent-changelog.json\`"
195
+ else
196
+ success "\`.agent-changelog.json\` already exists — leaving as-is"
197
+ fi
198
+
199
+ # ─── First snapshot ───────────────────────────────────────────────────
200
+ header "📸 First snapshot"
201
+
202
+ cd "$WORKSPACE"
203
+ while IFS= read -r f; do
204
+ git add "$f" 2>/dev/null || true
205
+ done < <(jq -r '.tracked[]?' "$WORKSPACE/.agent-changelog.json" 2>/dev/null)
206
+
207
+ if ! git diff --cached --quiet 2>/dev/null; then
208
+ git commit -m "Initial snapshot — agent versioning setup" >/dev/null 2>&1
209
+ HASH=$(git rev-parse --short HEAD)
210
+ success "Snapshot \`$HASH\` created"
211
+ else
212
+ echo "_No new files to commit_"
213
+ fi
214
+
215
+ # ─── Done ─────────────────────────────────────────────────────────────
216
+ echo ""
217
+ echo "🎉 **Setup complete!**"