opencode-diff-viewer 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.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # OpenCode Diff Viewer Plugin
2
+
3
+ 一个 OpenCode 插件,使用 [lumen](https://github.com/jnsahaj/lumen) 提供美观的 TUI diff 查看功能。
4
+
5
+ ## 功能
6
+
7
+ - `/diff` 命令查看所有修改的文件
8
+ - `/diff <file>` 命令查看指定文件的 diff
9
+ - LLM 可调用的 `view_diff` 工具
10
+ - 自动安装 lumen(如果未安装)
11
+ - 自动在新终端窗口中打开
12
+
13
+ ## 安装
14
+
15
+ ### 方式一:npm 安装(推荐)
16
+
17
+ ```bash
18
+ npm install opencode-diff-viewer
19
+ # 或
20
+ pnpm add opencode-diff-viewer
21
+ # 或
22
+ yarn add opencode-diff-viewer
23
+ ```
24
+
25
+ 然后在 `opencode.json` 中添加插件:
26
+
27
+ ```json
28
+ {
29
+ "plugin": ["opencode-diff-viewer"]
30
+ }
31
+ ```
32
+
33
+ ### 方式二:本地开发
34
+
35
+ 将插件复制到你的项目:
36
+
37
+ ```bash
38
+ # 复制插件文件
39
+ cp -r node_modules/opencode-diff-viewer/dist .opencode/plugin/diff-viewer
40
+ cp node_modules/opencode-diff-viewer/command-diff.md .opencode/command/diff.md
41
+
42
+ # 或使用 npm link
43
+ npm link opencode-diff-viewer
44
+ ```
45
+
46
+ ## 前置条件
47
+
48
+ 插件会自动安装 lumen。如果自动安装失败,需要手动安装:
49
+
50
+ ```bash
51
+ # macOS / Linux (Homebrew)
52
+ brew install jnsahaj/lumen/lumen
53
+
54
+ # 或使用 Cargo (Rust)
55
+ cargo install lumen
56
+ ```
57
+
58
+ ## 使用方法
59
+
60
+ ### 通过 / 命令
61
+
62
+ 在 OpenCode TUI 中输入:
63
+
64
+ ```
65
+ /diff # 查看所有修改文件的 diff
66
+ /diff src/app.ts # 查看指定文件的 diff
67
+ ```
68
+
69
+ ### 通过 LLM 工具调用
70
+
71
+ LLM 可以自动调用 `view_diff` 工具来展示代码变更。
72
+
73
+ ## lumen 快捷键
74
+
75
+ 在 lumen diff 查看器中:
76
+
77
+ | 快捷键 | 功能 |
78
+ |--------|------|
79
+ | `j/k` 或 `↑/↓` | 导航 |
80
+ | `{` / `}` | 跳转到上/下一个 hunk |
81
+ | `Tab` | 切换侧边栏 |
82
+ | `e` | 在编辑器中打开文件 |
83
+ | `q` | 退出 |
84
+
85
+ ## 发布到 npm
86
+
87
+ ```bash
88
+ # 登录 npm
89
+ npm login
90
+
91
+ # 发布包
92
+ npm publish
93
+
94
+ # 发布新版本
95
+ npm version patch|minor|major
96
+ npm publish
97
+ ```
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ /**
3
+ * OpenCode Diff Viewer Plugin
4
+ * Uses lumen (https://github.com/jnsahaj/lumen) for visual git diffs.
5
+ * Automatically installs lumen if not present.
6
+ */
7
+ export declare const DiffViewerPlugin: Plugin;
package/dist/index.js ADDED
@@ -0,0 +1,153 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ /**
3
+ * OpenCode Diff Viewer Plugin
4
+ * Uses lumen (https://github.com/jnsahaj/lumen) for visual git diffs.
5
+ * Automatically installs lumen if not present.
6
+ */
7
+ export const DiffViewerPlugin = async ({ project, client, $, directory, worktree }) => {
8
+ const isLumenInstalled = async () => {
9
+ try {
10
+ await $ `which lumen`;
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ };
17
+ const isBrewInstalled = async () => {
18
+ try {
19
+ await $ `which brew`;
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ };
26
+ const isCargoInstalled = async () => {
27
+ try {
28
+ await $ `which cargo`;
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ };
35
+ const installLumen = async () => {
36
+ if (await isBrewInstalled()) {
37
+ try {
38
+ await $ `brew install jnsahaj/lumen/lumen`;
39
+ return { success: true, method: "brew" };
40
+ }
41
+ catch (e) {
42
+ console.warn(`brew install failed: ${e}`);
43
+ }
44
+ }
45
+ if (await isCargoInstalled()) {
46
+ try {
47
+ await $ `cargo install lumen`;
48
+ return { success: true, method: "cargo" };
49
+ }
50
+ catch (e) {
51
+ console.warn(`cargo install failed: ${e}`);
52
+ }
53
+ }
54
+ return {
55
+ success: false,
56
+ error: "Neither brew nor cargo available. Please install lumen manually:\n brew install jnsahaj/lumen/lumen\n # or\n cargo install lumen"
57
+ };
58
+ };
59
+ const ensureLumenInstalled = async () => {
60
+ if (await isLumenInstalled()) {
61
+ return { installed: true };
62
+ }
63
+ const result = await installLumen();
64
+ if (result.success) {
65
+ return { installed: true, message: `✅ lumen installed via ${result.method}` };
66
+ }
67
+ return { installed: false, message: `❌ ${result.error}` };
68
+ };
69
+ const initResult = await ensureLumenInstalled();
70
+ const getModifiedFiles = async () => {
71
+ try {
72
+ const unstaged = await $ `git diff --name-only`.text();
73
+ const staged = await $ `git diff --staged --name-only`.text();
74
+ const files = new Set([
75
+ ...unstaged.trim().split('\n').filter(Boolean),
76
+ ...staged.trim().split('\n').filter(Boolean)
77
+ ]);
78
+ return Array.from(files);
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ };
84
+ const openDiffInNewTerminal = async (fileArgs) => {
85
+ const platform = process.platform;
86
+ if (platform === 'darwin') {
87
+ await $ `osascript -e 'tell application "Terminal" to do script "cd ${directory} && lumen diff ${fileArgs}; exit"'`;
88
+ return;
89
+ }
90
+ if (platform === 'linux') {
91
+ try {
92
+ await $ `which gnome-terminal && gnome-terminal -- bash -c "cd ${directory} && lumen diff ${fileArgs}; read -p 'Press Enter to close...'"`;
93
+ return;
94
+ }
95
+ catch {
96
+ try {
97
+ await $ `which xterm && xterm -e "cd ${directory} && lumen diff ${fileArgs}; read -p 'Press Enter to close...'"`;
98
+ return;
99
+ }
100
+ catch { }
101
+ }
102
+ }
103
+ await $ `lumen diff ${fileArgs}`;
104
+ };
105
+ const launchDiffViewer = async (files) => {
106
+ const lumenCheck = await ensureLumenInstalled();
107
+ if (!lumenCheck.installed) {
108
+ return lumenCheck.message || "❌ lumen is not installed and auto-install failed.";
109
+ }
110
+ const modifiedFiles = files && files.length > 0 ? files : await getModifiedFiles();
111
+ if (modifiedFiles.length === 0) {
112
+ return "📝 No modified files to show diff for.";
113
+ }
114
+ try {
115
+ const fileArgs = modifiedFiles.map(f => `--file "${f}"`).join(' ');
116
+ await openDiffInNewTerminal(fileArgs);
117
+ const prefix = lumenCheck.message ? `${lumenCheck.message}\n\n` : "";
118
+ return `${prefix}✅ Opened lumen diff viewer for ${modifiedFiles.length} file(s):
119
+ ${modifiedFiles.map(f => ` • ${f}`).join('\n')}
120
+
121
+ Keybindings:
122
+ j/k or ↑/↓: Navigate {/}: Jump between hunks
123
+ tab: Toggle sidebar e: Open in editor
124
+ q: Quit`;
125
+ }
126
+ catch (error) {
127
+ return `❌ Failed to launch diff viewer: ${error}`;
128
+ }
129
+ };
130
+ return {
131
+ "tui.command.execute": async (input, output) => {
132
+ if (input.command === "diff") {
133
+ const files = input.args?.trim() ? [input.args.trim()] : undefined;
134
+ output.handled = true;
135
+ output.result = await launchDiffViewer(files);
136
+ }
137
+ },
138
+ "file.edited": async ({ event }) => {
139
+ console.log(`File edited: ${event.path}`);
140
+ },
141
+ tool: {
142
+ view_diff: tool({
143
+ description: "Open the lumen diff viewer to show git diff for modified files. Use this when the user wants to see visual diffs of their changes.",
144
+ args: {
145
+ file: tool.schema.string().optional().describe("Optional: specific file path to show diff for. If not provided, shows all modified files."),
146
+ },
147
+ async execute(args, ctx) {
148
+ return await launchDiffViewer(args.file ? [args.file] : undefined);
149
+ },
150
+ }),
151
+ },
152
+ };
153
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "opencode-diff-viewer",
3
+ "version": "1.0.0",
4
+ "description": "OpenCode plugin for viewing git diffs using lumen TUI",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "command-diff.md"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc"
13
+ },
14
+ "keywords": [
15
+ "opencode",
16
+ "opencode-plugin",
17
+ "diff",
18
+ "lumen",
19
+ "git"
20
+ ],
21
+ "author": "",
22
+ "license": "MIT",
23
+ "peerDependencies": {
24
+ "@opencode-ai/plugin": "^1.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@opencode-ai/plugin": "^1.0.0",
28
+ "@types/node": "^25.0.3",
29
+ "typescript": "^5.0.0"
30
+ }
31
+ }