code-agent-auto-commit 1.0.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.
@@ -0,0 +1,53 @@
1
+ {
2
+ "version": 1,
3
+ "enabled": true,
4
+ "worktree": "/path/to/your/repo",
5
+ "commit": {
6
+ "mode": "single",
7
+ "fallbackPrefix": "chore(auto)",
8
+ "maxMessageLength": 72
9
+ },
10
+ "ai": {
11
+ "enabled": true,
12
+ "timeoutMs": 15000,
13
+ "model": "openai/gpt-4.1-mini",
14
+ "defaultProvider": "openai",
15
+ "providers": {
16
+ "openai": {
17
+ "api": "openai-completions",
18
+ "baseUrl": "https://api.openai.com/v1",
19
+ "apiKeyEnv": "OPENAI_API_KEY"
20
+ },
21
+ "anthropic": {
22
+ "api": "anthropic-messages",
23
+ "baseUrl": "https://api.anthropic.com/v1",
24
+ "apiKeyEnv": "ANTHROPIC_API_KEY"
25
+ },
26
+ "moonshot": {
27
+ "api": "openai-completions",
28
+ "baseUrl": "https://api.moonshot.ai/v1",
29
+ "apiKeyEnv": "MOONSHOT_API_KEY"
30
+ },
31
+ "minimax": {
32
+ "api": "openai-completions",
33
+ "baseUrl": "https://api.minimax.chat/v1",
34
+ "apiKeyEnv": "MINIMAX_API_KEY"
35
+ },
36
+ "ollama": {
37
+ "api": "openai-completions",
38
+ "baseUrl": "http://127.0.0.1:11434/v1",
39
+ "apiKeyEnv": "OLLAMA_API_KEY"
40
+ }
41
+ }
42
+ },
43
+ "push": {
44
+ "enabled": false,
45
+ "provider": "github",
46
+ "remote": "origin",
47
+ "branch": "main"
48
+ },
49
+ "filters": {
50
+ "include": [],
51
+ "exclude": [".env", ".env.*", "*.pem", "*.key", "*.p12"]
52
+ }
53
+ }
@@ -0,0 +1,15 @@
1
+ # Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We are committed to a harassment-free experience for everyone.
6
+
7
+ ## Standards
8
+
9
+ - Be respectful and constructive.
10
+ - Assume good intent.
11
+ - Focus feedback on code and ideas, not people.
12
+
13
+ ## Enforcement
14
+
15
+ Project maintainers may remove comments, commits, issues, or contributors that violate this policy.
@@ -0,0 +1,23 @@
1
+ # Contributing
2
+
3
+ Thanks for contributing.
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm run typecheck
10
+ pnpm run build
11
+ pnpm test
12
+ ```
13
+
14
+ ## Pull requests
15
+
16
+ - Keep changes focused and small.
17
+ - Update docs when behavior changes.
18
+ - Add or update tests for logic changes.
19
+ - Do not commit secrets or personal API keys.
20
+
21
+ ## Commit style
22
+
23
+ Use concise, imperative commit messages.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chat-auto-commit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # code-agent-auto-commit
2
+
3
+ `code-agent-auto-commit` (`cac`) provides configurable code-agent-end auto-commit(using your git account) for:
4
+
5
+ - OpenCode
6
+ - Claude Code
7
+ - OpenAI Codex CLI
8
+
9
+ ## Features
10
+
11
+ - Auto-commit when a chat/agent turn ends
12
+ - Commit strategies:
13
+ - `single`: all changed files in one commit
14
+ - `per-file`: one file per commit
15
+ - AI-generated commit messages with multi-provider model configuration
16
+ - OpenAI-compatible mode (`openai-completions`)
17
+ - Anthropic-compatible mode (`anthropic-messages`)
18
+ - Optional auto-push to GitHub, GitLab, or generic remotes
19
+ - Tool installers for OpenCode, Codex, and Claude Code
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pnpm add -g code-agent-auto-commit
25
+ ```
26
+
27
+ Then use the short command:
28
+
29
+ ```bash
30
+ cac --help
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ # 1. Initialize config
37
+ cac init
38
+
39
+ # 2. Configure AI API key for commit messages
40
+ # Edit .code-agent-auto-commit.json — set your provider, model, and API key env var.
41
+ # Or export the key in your shell:
42
+ export MINIMAX_API_KEY='your-api-key' # or OPENAI_API_KEY, etc.
43
+
44
+ # 3. Install hooks
45
+ cac install --tool all --scope project
46
+
47
+ # 4. Verify
48
+ cac status --scope project
49
+
50
+ # 5. Git config
51
+ git init
52
+
53
+ # 6. Agentic coding
54
+ opencode / claude / codex
55
+ ```
56
+
57
+ > **Important:** After `cac init`, you **must** configure an AI provider API key.
58
+ > Without a valid key, AI commit messages will not work and `cac` falls back to
59
+ > generic `chore(auto): ...` prefixed messages.
60
+ >
61
+ > **Model tip:** Choose a fast, lightweight model (e.g. `gpt-4.1-mini`,
62
+ > `MiniMax-M2.1-highspeed`). Commit messages are short — speed matters more
63
+ > than intelligence here.
64
+
65
+ ## Commands
66
+
67
+ ```bash
68
+ cac init [--worktree <path>] [--config <path>]
69
+ cac install [--tool all|opencode|codex|claude] [--scope project|global] [--worktree <path>] [--config <path>]
70
+ cac uninstall [--tool all|opencode|codex|claude] [--scope project|global] [--worktree <path>]
71
+ cac status [--scope project|global] [--worktree <path>] [--config <path>]
72
+ cac run [--tool opencode|codex|claude|manual] [--worktree <path>] [--config <path>] [--event-json <json>] [--event-stdin]
73
+ cac set-worktree <path> [--config <path>]
74
+ ```
75
+
76
+ ### Command Details
77
+
78
+ - `cac init`: creates a config file for a worktree. It resolves the target path from `--config` or defaults to `<worktree>/.code-agent-auto-commit.json`.
79
+ - `cac install`: installs adapters/hooks for selected tools (`opencode`, `codex`, `claude`) in `project` or `global` scope. If no config exists at the resolved path, it creates one first.
80
+ - `cac uninstall`: removes previously installed adapters/hooks for selected tools and scope.
81
+ - `cac status`: prints resolved config path, worktree, commit mode, AI/push toggles, and install status of each adapter.
82
+ - `cac run`: executes one auto-commit pass (manual or hook-triggered). It reads config, filters changed files, stages/commits by configured mode, and optionally pushes.
83
+ - `cac set-worktree`: updates only the `worktree` field in the resolved config file.
84
+
85
+ ## Config File
86
+
87
+ Default project config file:
88
+
89
+ `.code-agent-auto-commit.json`
90
+
91
+ You can copy from:
92
+
93
+ `.code-agent-auto-commit.example.json`
94
+
95
+ Full schema and options:
96
+
97
+ - `docs/CONFIG.md`
98
+ - `docs/zh-CN.md`
99
+
100
+ ### AI Key Fields
101
+
102
+ - `ai.providers.<name>.apiKeyEnv` expects an environment variable name (for example, `MINIMAX_API_KEY`), not the raw key value.
103
+ - If you prefer storing a key directly in config, use `ai.providers.<name>.apiKey`.
104
+ - If AI request fails (missing key, invalid provider/model, or non-2xx response), `cac` falls back to `commit.fallbackPrefix`-style messages.
105
+
106
+ ## AI Models (Multi-Provider)
107
+
108
+ Model format follows `provider/model` (OpenClaw-style). Example:
109
+
110
+ ```json
111
+ {
112
+ "ai": {
113
+ "enabled": true,
114
+ "model": "openai/gpt-4.1-mini",
115
+ "defaultProvider": "openai",
116
+ "providers": {
117
+ "openai": {
118
+ "api": "openai-completions",
119
+ "baseUrl": "https://api.openai.com/v1",
120
+ "apiKeyEnv": "OPENAI_API_KEY"
121
+ },
122
+ "anthropic": {
123
+ "api": "anthropic-messages",
124
+ "baseUrl": "https://api.anthropic.com/v1",
125
+ "apiKeyEnv": "ANTHROPIC_API_KEY"
126
+ },
127
+ "moonshot": {
128
+ "api": "openai-completions",
129
+ "baseUrl": "https://api.moonshot.ai/v1",
130
+ "apiKeyEnv": "MOONSHOT_API_KEY"
131
+ }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ ## Integration Notes
138
+
139
+ - OpenCode: installs plugin under `.opencode/plugins/` or `~/.config/opencode/plugins/`
140
+ - Codex CLI: writes managed `notify` block in `.codex/config.toml` or `~/.codex/config.toml`
141
+ - Claude Code: installs `Stop` hook in `.claude/settings.json` or `~/.claude/settings.json`
142
+
143
+ If Codex config already has a custom `notify = ...`, installer stops and asks for manual merge.
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ pnpm install
149
+ pnpm run typecheck
150
+ pnpm run build
151
+ pnpm test
152
+ ```
153
+
154
+ ## Open Source
155
+
156
+ - Contribution guide: `CONTRIBUTING.md`
157
+ - Security policy: `SECURITY.md`
158
+ - Code of conduct: `CODE_OF_CONDUCT.md`
159
+
160
+ ## License
161
+
162
+ MIT
package/SECURITY.md ADDED
@@ -0,0 +1,17 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report security issues privately to project maintainers instead of opening a public issue.
6
+
7
+ Include:
8
+
9
+ - affected version
10
+ - reproduction steps
11
+ - impact assessment
12
+
13
+ ## Secret handling
14
+
15
+ - Never commit API keys.
16
+ - Prefer environment variables (`OPENAI_API_KEY` by default).
17
+ - Keep `.code-agent-auto-commit.json` free of plaintext secrets when possible.
@@ -0,0 +1,20 @@
1
+ import type { InstallScope } from "../types";
2
+ export interface ClaudeInstallInput {
3
+ scope: InstallScope;
4
+ worktree: string;
5
+ configPath: string;
6
+ runnerCommand: string;
7
+ }
8
+ export declare function installClaudeAdapter(input: ClaudeInstallInput): {
9
+ settingsPath: string;
10
+ scriptPath: string;
11
+ };
12
+ export declare function uninstallClaudeAdapter(scope: InstallScope, worktree: string): {
13
+ settingsPath: string;
14
+ scriptPath: string;
15
+ };
16
+ export declare function claudeAdapterStatus(scope: InstallScope, worktree: string): {
17
+ settingsPath: string;
18
+ scriptPath: string;
19
+ installed: boolean;
20
+ };
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installClaudeAdapter = installClaudeAdapter;
7
+ exports.uninstallClaudeAdapter = uninstallClaudeAdapter;
8
+ exports.claudeAdapterStatus = claudeAdapterStatus;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_os_1 = __importDefault(require("node:os"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const fs_1 = require("../core/fs");
13
+ function settingsPath(scope, worktree) {
14
+ if (scope === "global") {
15
+ return node_path_1.default.join(node_os_1.default.homedir(), ".claude", "settings.json");
16
+ }
17
+ return node_path_1.default.join(worktree, ".claude", "settings.json");
18
+ }
19
+ function hookScriptPath(scope, worktree) {
20
+ if (scope === "global") {
21
+ return node_path_1.default.join(node_os_1.default.homedir(), ".claude", "hooks", "code-agent-auto-commit.sh");
22
+ }
23
+ return node_path_1.default.join(worktree, ".claude", "hooks", "code-agent-auto-commit.sh");
24
+ }
25
+ function readSettings(filePath) {
26
+ if (!node_fs_1.default.existsSync(filePath)) {
27
+ return {};
28
+ }
29
+ const raw = node_fs_1.default.readFileSync(filePath, "utf8");
30
+ if (!raw.trim()) {
31
+ return {};
32
+ }
33
+ return JSON.parse(raw);
34
+ }
35
+ function writeSettings(filePath, settings) {
36
+ (0, fs_1.ensureDirForFile)(filePath);
37
+ node_fs_1.default.writeFileSync(filePath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
38
+ }
39
+ function buildScript(configPath, runnerCommand) {
40
+ return `#!/usr/bin/env bash
41
+ set -euo pipefail
42
+
43
+ WORKTREE="\${CLAUDE_PROJECT_DIR:-$PWD}"
44
+ ${runnerCommand} run --tool claude --worktree "$WORKTREE" --config ${JSON.stringify(configPath)} --event-stdin || true
45
+ `;
46
+ }
47
+ function installClaudeAdapter(input) {
48
+ const resolvedWorktree = node_path_1.default.resolve(input.worktree);
49
+ const resolvedConfig = node_path_1.default.resolve(input.configPath);
50
+ const targetSettingsPath = settingsPath(input.scope, resolvedWorktree);
51
+ const targetScriptPath = hookScriptPath(input.scope, resolvedWorktree);
52
+ const command = `bash ${JSON.stringify(targetScriptPath)}`;
53
+ (0, fs_1.writeTextFile)(targetScriptPath, buildScript(resolvedConfig, input.runnerCommand));
54
+ node_fs_1.default.chmodSync(targetScriptPath, 0o755);
55
+ const settings = readSettings(targetSettingsPath);
56
+ if (!settings.hooks) {
57
+ settings.hooks = {};
58
+ }
59
+ const stopHooks = settings.hooks.Stop ?? [];
60
+ const alreadyExists = stopHooks.some((entry) => entry.hooks.some((hook) => hook.command === command));
61
+ if (!alreadyExists) {
62
+ stopHooks.push({
63
+ matcher: "",
64
+ hooks: [
65
+ {
66
+ type: "command",
67
+ command,
68
+ },
69
+ ],
70
+ });
71
+ }
72
+ settings.hooks.Stop = stopHooks;
73
+ writeSettings(targetSettingsPath, settings);
74
+ return {
75
+ settingsPath: targetSettingsPath,
76
+ scriptPath: targetScriptPath,
77
+ };
78
+ }
79
+ function uninstallClaudeAdapter(scope, worktree) {
80
+ const resolvedWorktree = node_path_1.default.resolve(worktree);
81
+ const targetSettingsPath = settingsPath(scope, resolvedWorktree);
82
+ const targetScriptPath = hookScriptPath(scope, resolvedWorktree);
83
+ const command = `bash ${JSON.stringify(targetScriptPath)}`;
84
+ if (node_fs_1.default.existsSync(targetSettingsPath)) {
85
+ const settings = readSettings(targetSettingsPath);
86
+ const stopHooks = settings.hooks?.Stop ?? [];
87
+ const cleaned = stopHooks
88
+ .map((entry) => ({
89
+ ...entry,
90
+ hooks: entry.hooks.filter((hook) => hook.command !== command),
91
+ }))
92
+ .filter((entry) => entry.hooks.length > 0);
93
+ if (!settings.hooks) {
94
+ settings.hooks = {};
95
+ }
96
+ if (cleaned.length > 0) {
97
+ settings.hooks.Stop = cleaned;
98
+ }
99
+ else {
100
+ delete settings.hooks.Stop;
101
+ }
102
+ if (Object.keys(settings.hooks).length === 0) {
103
+ delete settings.hooks;
104
+ }
105
+ writeSettings(targetSettingsPath, settings);
106
+ }
107
+ if (node_fs_1.default.existsSync(targetScriptPath)) {
108
+ node_fs_1.default.unlinkSync(targetScriptPath);
109
+ }
110
+ return {
111
+ settingsPath: targetSettingsPath,
112
+ scriptPath: targetScriptPath,
113
+ };
114
+ }
115
+ function claudeAdapterStatus(scope, worktree) {
116
+ const resolvedWorktree = node_path_1.default.resolve(worktree);
117
+ const targetSettingsPath = settingsPath(scope, resolvedWorktree);
118
+ const targetScriptPath = hookScriptPath(scope, resolvedWorktree);
119
+ const command = `bash ${JSON.stringify(targetScriptPath)}`;
120
+ if (!node_fs_1.default.existsSync(targetSettingsPath)) {
121
+ return {
122
+ settingsPath: targetSettingsPath,
123
+ scriptPath: targetScriptPath,
124
+ installed: false,
125
+ };
126
+ }
127
+ const settings = readSettings(targetSettingsPath);
128
+ const hasHook = (settings.hooks?.Stop ?? []).some((entry) => entry.hooks.some((hook) => hook.command === command));
129
+ return {
130
+ settingsPath: targetSettingsPath,
131
+ scriptPath: targetScriptPath,
132
+ installed: hasHook && node_fs_1.default.existsSync(targetScriptPath),
133
+ };
134
+ }
@@ -0,0 +1,13 @@
1
+ import type { InstallScope } from "../types";
2
+ export interface CodexInstallInput {
3
+ scope: InstallScope;
4
+ worktree: string;
5
+ configPath: string;
6
+ runnerCommand: string;
7
+ }
8
+ export declare function installCodexAdapter(input: CodexInstallInput): string;
9
+ export declare function uninstallCodexAdapter(scope: InstallScope, worktree: string): string;
10
+ export declare function codexAdapterStatus(scope: InstallScope, worktree: string): {
11
+ path: string;
12
+ installed: boolean;
13
+ };
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installCodexAdapter = installCodexAdapter;
7
+ exports.uninstallCodexAdapter = uninstallCodexAdapter;
8
+ exports.codexAdapterStatus = codexAdapterStatus;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_os_1 = __importDefault(require("node:os"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const fs_1 = require("../core/fs");
13
+ const START_MARKER = "# BEGIN code-agent-auto-commit";
14
+ const END_MARKER = "# END code-agent-auto-commit";
15
+ function codexConfigPath(scope, worktree) {
16
+ if (scope === "global") {
17
+ return node_path_1.default.join(node_os_1.default.homedir(), ".codex", "config.toml");
18
+ }
19
+ return node_path_1.default.join(worktree, ".codex", "config.toml");
20
+ }
21
+ function tomlString(value) {
22
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
23
+ return `"${escaped}"`;
24
+ }
25
+ function buildNotifyBlock(worktree, configPath, runnerCommand) {
26
+ const command = [runnerCommand, "run", "--tool", "codex", "--worktree", worktree, "--config", configPath];
27
+ const rendered = command.map((item) => tomlString(item)).join(", ");
28
+ return [
29
+ START_MARKER,
30
+ `notify = [${rendered}]`,
31
+ END_MARKER,
32
+ ].join("\n");
33
+ }
34
+ function stripManagedBlock(content) {
35
+ const blockRegex = new RegExp(`${START_MARKER}[\\s\\S]*?${END_MARKER}\\n?`, "g");
36
+ return content.replace(blockRegex, "");
37
+ }
38
+ function installCodexAdapter(input) {
39
+ const filePath = codexConfigPath(input.scope, node_path_1.default.resolve(input.worktree));
40
+ (0, fs_1.ensureDirForFile)(filePath);
41
+ const existing = node_fs_1.default.existsSync(filePath) ? node_fs_1.default.readFileSync(filePath, "utf8") : "";
42
+ const withoutBlock = stripManagedBlock(existing);
43
+ if (/^\s*notify\s*=\s*/m.test(withoutBlock)) {
44
+ throw new Error(`Codex config already contains notify=. Merge manually in ${filePath}`);
45
+ }
46
+ const block = buildNotifyBlock(node_path_1.default.resolve(input.worktree), node_path_1.default.resolve(input.configPath), input.runnerCommand);
47
+ const output = withoutBlock.trimEnd().length > 0 ? `${withoutBlock.trimEnd()}\n\n${block}\n` : `${block}\n`;
48
+ node_fs_1.default.writeFileSync(filePath, output, "utf8");
49
+ return filePath;
50
+ }
51
+ function uninstallCodexAdapter(scope, worktree) {
52
+ const filePath = codexConfigPath(scope, node_path_1.default.resolve(worktree));
53
+ if (!node_fs_1.default.existsSync(filePath)) {
54
+ return filePath;
55
+ }
56
+ const existing = node_fs_1.default.readFileSync(filePath, "utf8");
57
+ const updated = stripManagedBlock(existing).trimEnd();
58
+ node_fs_1.default.writeFileSync(filePath, updated.length > 0 ? `${updated}\n` : "", "utf8");
59
+ return filePath;
60
+ }
61
+ function codexAdapterStatus(scope, worktree) {
62
+ const filePath = codexConfigPath(scope, node_path_1.default.resolve(worktree));
63
+ if (!node_fs_1.default.existsSync(filePath)) {
64
+ return { path: filePath, installed: false };
65
+ }
66
+ const content = node_fs_1.default.readFileSync(filePath, "utf8");
67
+ return {
68
+ path: filePath,
69
+ installed: content.includes(START_MARKER),
70
+ };
71
+ }
@@ -0,0 +1,13 @@
1
+ import type { InstallScope } from "../types";
2
+ export interface AdapterInstallInput {
3
+ scope: InstallScope;
4
+ worktree: string;
5
+ configPath: string;
6
+ runnerCommand: string;
7
+ }
8
+ export declare function installOpenCodeAdapter(input: AdapterInstallInput): string;
9
+ export declare function uninstallOpenCodeAdapter(scope: InstallScope, worktree: string): string;
10
+ export declare function opencodeAdapterStatus(scope: InstallScope, worktree: string): {
11
+ path: string;
12
+ installed: boolean;
13
+ };
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installOpenCodeAdapter = installOpenCodeAdapter;
7
+ exports.uninstallOpenCodeAdapter = uninstallOpenCodeAdapter;
8
+ exports.opencodeAdapterStatus = opencodeAdapterStatus;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const fs_1 = require("../core/fs");
12
+ const PLUGIN_FILENAME = "code-agent-auto-commit.ts";
13
+ function resolvePluginPath(scope, worktree) {
14
+ if (scope === "global") {
15
+ return node_path_1.default.join((0, fs_1.getUserConfigHome)(), "opencode", "plugins", PLUGIN_FILENAME);
16
+ }
17
+ return node_path_1.default.join(worktree, ".opencode", "plugins", PLUGIN_FILENAME);
18
+ }
19
+ function pluginContent(worktree, configPath, runnerCommand) {
20
+ const target = JSON.stringify(worktree);
21
+ const cfg = JSON.stringify(configPath);
22
+ const runner = JSON.stringify(runnerCommand);
23
+ return `import type { Plugin } from "@opencode-ai/plugin"
24
+
25
+ const TARGET_WORKTREE = ${target}
26
+ const CONFIG_PATH = ${cfg}
27
+ const RUNNER_COMMAND = ${runner}
28
+
29
+ export const ChatAutoCommitPlugin: Plugin = async ({ $, client, worktree }) => {
30
+ const done = new Set<string>()
31
+
32
+ return {
33
+ event: async ({ event }) => {
34
+ if (event.type !== "session.status") return
35
+ if (worktree !== TARGET_WORKTREE) return
36
+
37
+ if (event.properties.status.type === "busy") {
38
+ done.delete(event.properties.sessionID)
39
+ return
40
+ }
41
+
42
+ if (event.properties.status.type !== "idle") return
43
+ if (done.has(event.properties.sessionID)) return
44
+
45
+ const result = await $\`${"${RUNNER_COMMAND}"} run --tool opencode --worktree ${"${worktree}"} --config ${"${CONFIG_PATH}"} --session-id ${"${event.properties.sessionID}"}\`.quiet().nothrow()
46
+ if (result.exitCode !== 0) {
47
+ await client.app.log({
48
+ body: {
49
+ service: "code-agent-auto-commit",
50
+ level: "warn",
51
+ message: "auto-commit runner failed",
52
+ extra: {
53
+ sessionID: event.properties.sessionID,
54
+ stderr: result.stderr.toString(),
55
+ },
56
+ },
57
+ })
58
+ done.add(event.properties.sessionID)
59
+ return
60
+ }
61
+
62
+ done.add(event.properties.sessionID)
63
+ await client.app.log({
64
+ body: {
65
+ service: "code-agent-auto-commit",
66
+ level: "info",
67
+ message: "auto-commit runner finished",
68
+ extra: {
69
+ sessionID: event.properties.sessionID,
70
+ },
71
+ },
72
+ })
73
+ },
74
+ }
75
+ }
76
+
77
+ `;
78
+ }
79
+ function installOpenCodeAdapter(input) {
80
+ const targetPath = resolvePluginPath(input.scope, node_path_1.default.resolve(input.worktree));
81
+ (0, fs_1.ensureDirForFile)(targetPath);
82
+ (0, fs_1.writeTextFile)(targetPath, pluginContent(node_path_1.default.resolve(input.worktree), node_path_1.default.resolve(input.configPath), input.runnerCommand));
83
+ return targetPath;
84
+ }
85
+ function uninstallOpenCodeAdapter(scope, worktree) {
86
+ const targetPath = resolvePluginPath(scope, node_path_1.default.resolve(worktree));
87
+ if (node_fs_1.default.existsSync(targetPath)) {
88
+ node_fs_1.default.unlinkSync(targetPath);
89
+ }
90
+ return targetPath;
91
+ }
92
+ function opencodeAdapterStatus(scope, worktree) {
93
+ const targetPath = resolvePluginPath(scope, node_path_1.default.resolve(worktree));
94
+ return {
95
+ path: targetPath,
96
+ installed: node_fs_1.default.existsSync(targetPath),
97
+ };
98
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};