finch-git-branch 0.2.8

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,43 @@
1
+ ## Git Branch for Finch
2
+
3
+ Git Branch is a mini tool for [Finch](https://finchwork.app/) — a desktop AI agent you can download at [finchwork.app](https://finchwork.app/).
4
+
5
+ ![Git Branch](https://raw.githubusercontent.com/finchtoys/finch-releases/refs/heads/main/extensions/git-branch/shot.png)
6
+
7
+ It puts a handy Git branch widget right inside your chat dialog.
8
+
9
+ ## What it does
10
+
11
+ - Shows which branch you're currently on, right in the top bar of your chat.
12
+ - Puts main branches like `main` and `master` at the top.
13
+ - Puts other branches inside a grouped menu, showing up to 6 at a time.
14
+ - Before switching branches, it checks if you have unsaved changes and shows you what's been modified.
15
+ - Highlights added and deleted lines in green and red.
16
+ - If you have unsaved changes, it can save a checkpoint commit first, then switch.
17
+ - After switching, it tells you which commit was saved and which branch you're now on.
18
+ - You can also ask the Agent to create a new branch for you.
19
+
20
+ ## How to use
21
+
22
+ 1. Open a Finch chat inside a Git project folder.
23
+ 2. Click the Git branch button on the top bar of the chat.
24
+ 3. Pick a branch to switch to.
25
+ 4. If there are unsaved changes, the dialog asks: save a checkpoint first, or cancel.
26
+ 5. Click **Create and checkout branch...** to let the Agent help you name a new branch.
27
+
28
+ ## Permissions
29
+
30
+ This tool needs shell permission because it runs `git` commands locally. No network access needed.
31
+
32
+ ## Development
33
+
34
+ ```bash
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ Install or update locally:
40
+
41
+ ```bash
42
+ npx @finchtoys/minitools update git-branch
43
+ ```
@@ -0,0 +1,43 @@
1
+ ## Git 分支小工具
2
+
3
+ Git 分支小工具是 [Finch](https://finchwork.app/) 的扩展。Finch 是一款桌面 AI Agent,可在 [finchwork.app](https://finchwork.app/) 下载。
4
+
5
+ ![Git Branch](https://raw.githubusercontent.com/finchtoys/finch-releases/refs/heads/main/extensions/git-branch/shot.png)
6
+
7
+ 可在对话框里切换 Git 分支,方便你管理 Git 项目的分支。
8
+
9
+ ## 功能
10
+
11
+ - 在对话框顶部工具栏显示当前在哪个分支。
12
+ - 把 `main`、`master` 这些常用分支放在前面。
13
+ - 其他分支放在一个分组菜单里,一次最多显示 6 个。
14
+ - 切分支前会检查你有没有没保存的改动,并列出改了哪些文件。
15
+ - 新增和删除的行数分别用绿色和红色标出来。
16
+ - 如果有没保存的改动,可以先自动存一个 checkpoint,再切过去。
17
+ - 切完后告诉你存到了哪个 commit、切到了哪个分支。
18
+ - 也可以让 Agent 帮你创建新分支。
19
+
20
+ ## 用法
21
+
22
+ 1. 在 Git 项目目录里打开一个 Finch 对话。
23
+ 2. 点击对话顶部工具栏的 Git 分支按钮。
24
+ 3. 选一个分支切换过去。
25
+ 4. 如果有没保存的改动,对话框会问你先存档再切,还是取消。
26
+ 5. 点「创建并检出分支...」让 Agent 帮你输入新分支名。
27
+
28
+ ## 权限
29
+
30
+ 这个工具需要在本地执行 `git` 命令,所以需要 shell 权限。不需要联网。
31
+
32
+ ## 开发
33
+
34
+ ```bash
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ 本地安装或更新:
40
+
41
+ ```bash
42
+ npx @finchtoys/minitools update git-branch
43
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,370 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { execFile } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ const execFileAsync = promisify(execFile);
6
+ const CURRENT_BRANCH_KEY = 'currentBranch';
7
+ // Tracks the most-recently-seen cwd so the background poller can use it.
8
+ let activeCwd;
9
+ function readIconSvg(name) {
10
+ return readFileSync(new URL(`../icons/${name}.svg`, import.meta.url), 'utf-8');
11
+ }
12
+ // ── Git helpers ──────────────────────────────────────────────────────────────
13
+ async function git(cwd, args, timeout = 5000) {
14
+ const { stdout } = await execFileAsync('git', ['-C', cwd, ...args], {
15
+ timeout,
16
+ maxBuffer: 10 * 1024 * 1024,
17
+ });
18
+ return stdout.trim();
19
+ }
20
+ function isGitRepo(cwd) {
21
+ return Boolean(cwd) && existsSync(join(cwd, '.git'));
22
+ }
23
+ /** Get ahead/behind commit counts between HEAD and another branch. */
24
+ async function getAheadBehind(cwd, branch) {
25
+ try {
26
+ const out = await git(cwd, [
27
+ 'rev-list', '--left-right', '--count',
28
+ `HEAD...${branch}`,
29
+ ]);
30
+ const [ahead, behind] = out.split('\t').map(Number);
31
+ return { ahead: ahead || 0, behind: behind || 0 };
32
+ }
33
+ catch {
34
+ return { ahead: 0, behind: 0 };
35
+ }
36
+ }
37
+ /** Count all changed files (staged + unstaged + untracked) via `git status --porcelain`. */
38
+ async function getChangedFileCount(cwd) {
39
+ try {
40
+ const out = await git(cwd, ['status', '--porcelain']);
41
+ return out ? out.split('\n').filter(Boolean).length : 0;
42
+ }
43
+ catch {
44
+ return 0;
45
+ }
46
+ }
47
+ function renderAheadBehindDesc(diff) {
48
+ if (diff.ahead === 0 && diff.behind === 0)
49
+ return '';
50
+ const parts = [];
51
+ if (diff.ahead > 0)
52
+ parts.push(`↑${diff.ahead}`);
53
+ if (diff.behind > 0)
54
+ parts.push(`↓${diff.behind}`);
55
+ return parts.join(' ');
56
+ }
57
+ function normalizeGitPath(path) {
58
+ return path
59
+ .trim()
60
+ .replace(/^"|"$/g, '')
61
+ .replace(/\\t/g, '\t')
62
+ .replace(/\\n/g, '\n')
63
+ .replace(/\\"/g, '"');
64
+ }
65
+ /**
66
+ * Build a structured message for the ModalDialog showing uncommitted changes.
67
+ * Format: warning line, blank, each file with +/- counts, blank, total.
68
+ */
69
+ async function buildDiffMessage(cwd, i18n) {
70
+ // Use one primary source of truth for tracked changes. `git diff --numstat HEAD`
71
+ // covers staged + unstaged tracked files, avoiding mismatches/duplicates between
72
+ // `status --porcelain` and `diff --numstat` path parsing.
73
+ const numstat = await git(cwd, ['diff', '--numstat', 'HEAD', '--']).catch(() => '');
74
+ const status = await git(cwd, ['status', '--porcelain']).catch(() => '');
75
+ const lines = [];
76
+ lines.push(i18n.t('git.branch.diff.title'));
77
+ lines.push('');
78
+ const seen = new Set();
79
+ let totalAdd = 0;
80
+ let totalDel = 0;
81
+ if (numstat) {
82
+ for (const line of numstat.split('\n').filter(Boolean)) {
83
+ const parts = line.split('\t');
84
+ if (parts.length < 3)
85
+ continue;
86
+ const [addRaw, delRaw, ...nameParts] = parts;
87
+ const file = normalizeGitPath(nameParts.join('\t'));
88
+ if (!file || seen.has(file))
89
+ continue;
90
+ const add = parseInt(addRaw || '0', 10) || 0;
91
+ const del = parseInt(delRaw || '0', 10) || 0;
92
+ lines.push(`${file} {+${add}}\\g {-${del}}\\r`);
93
+ totalAdd += add;
94
+ totalDel += del;
95
+ seen.add(file);
96
+ }
97
+ }
98
+ // Only append untracked files from status; tracked files are already covered by numstat.
99
+ if (status) {
100
+ for (const line of status.split('\n').filter(Boolean)) {
101
+ const state = line.slice(0, 2);
102
+ if (state !== '??')
103
+ continue;
104
+ const file = normalizeGitPath(line.slice(3));
105
+ if (!file || seen.has(file))
106
+ continue;
107
+ lines.push(`${file} {${i18n.t('git.branch.diff.new')}}\\g`);
108
+ totalAdd += 1;
109
+ seen.add(file);
110
+ }
111
+ }
112
+ lines.push('');
113
+ lines.push(i18n.t('git.branch.diff.total', {
114
+ files: String(seen.size),
115
+ add: `{+${totalAdd}}\\g`,
116
+ del: `{-${totalDel}}\\r`,
117
+ }));
118
+ return lines.join('\n');
119
+ }
120
+ // ── Activation ───────────────────────────────────────────────────────────────
121
+ export function activate(ctx) {
122
+ ctx.subscriptions.push(ctx.icons.register('git-branch', {
123
+ plus: { svg: readIconSvg('plus'), description: 'Create branch' },
124
+ 'plus-circle': { svg: readIconSvg('plus-circle'), description: 'Create branch' },
125
+ }));
126
+ const composerAction = ctx.composerActions.register('git-branch', {
127
+ async getBadge({ cwd }) {
128
+ if (!cwd || !isGitRepo(cwd))
129
+ throw new Error('not a git repo');
130
+ // Keep activeCwd up-to-date so the background poller can use it.
131
+ activeCwd = cwd;
132
+ // 工具/菜单分支变更会写入 storage;getBadge 每次同步真实 git 状态。
133
+ // 不删除缓存,避免一次内部查询提前消费,导致入口文案仍停留在旧值。
134
+ const branch = await git(cwd, ['branch', '--show-current']);
135
+ if (branch) {
136
+ await ctx.storage.set(CURRENT_BRANCH_KEY, branch);
137
+ return branch;
138
+ }
139
+ return await ctx.storage.get(CURRENT_BRANCH_KEY);
140
+ },
141
+ async getMenu({ cwd }) {
142
+ if (!cwd)
143
+ return [];
144
+ try {
145
+ const currentBranch = await git(cwd, ['branch', '--show-current']);
146
+ if (!currentBranch)
147
+ return [];
148
+ const raw = await git(cwd, ['branch']);
149
+ const allBranches = raw
150
+ .split('\n')
151
+ .filter(Boolean)
152
+ .map((l) => l.replace(/^\*?\s+/, '').trim())
153
+ .filter(Boolean);
154
+ const pinned = ['main', 'master'].filter((b) => b !== currentBranch && allBranches.includes(b));
155
+ const otherBranches = allBranches.filter((b) => b !== currentBranch && !pinned.includes(b));
156
+ const changedFiles = await getChangedFileCount(cwd);
157
+ const currentDesc = changedFiles > 0
158
+ ? ctx.i18n.t('git.branch.changes', { count: String(changedFiles) })
159
+ : undefined;
160
+ const pinnedDiffs = new Map();
161
+ await Promise.all(pinned.map(async (b) => {
162
+ const d = await getAheadBehind(cwd, b);
163
+ const desc = renderAheadBehindDesc(d);
164
+ if (desc)
165
+ pinnedDiffs.set(b, desc);
166
+ }));
167
+ const items = [];
168
+ items.push({
169
+ id: currentBranch,
170
+ label: currentBranch,
171
+ current: true,
172
+ description: currentDesc,
173
+ iconName: 'git-branch',
174
+ });
175
+ for (const b of pinned) {
176
+ items.push({
177
+ id: b,
178
+ label: b,
179
+ description: pinnedDiffs.get(b) || undefined,
180
+ iconName: 'git-branch',
181
+ });
182
+ }
183
+ if (otherBranches.length > 0) {
184
+ items.push({ id: '__sep1__', label: '', separator: true });
185
+ const otherDiffs = new Map();
186
+ await Promise.all(otherBranches.map(async (b) => {
187
+ const d = await getAheadBehind(cwd, b);
188
+ const desc = renderAheadBehindDesc(d);
189
+ if (desc)
190
+ otherDiffs.set(b, desc);
191
+ }));
192
+ const children = otherBranches.map((b, index) => ({
193
+ id: b,
194
+ label: b,
195
+ description: otherDiffs.get(b) || undefined,
196
+ iconName: 'git-branch',
197
+ group: 'branches',
198
+ groupLabel: index === 0 ? ctx.i18n.t('git.branch.more.group') : undefined,
199
+ groupMaxVisible: index === 0 ? 6 : undefined,
200
+ }));
201
+ items.push({
202
+ id: '__submenu__',
203
+ label: ctx.i18n.t('git.branch.more'),
204
+ description: `${otherBranches.length}`,
205
+ iconName: 'git-commit-horizontal',
206
+ children,
207
+ });
208
+ }
209
+ items.push({ id: '__sep__', label: '', separator: true });
210
+ items.push({
211
+ id: '__create_branch__',
212
+ label: ctx.i18n.t('git.branch.create'),
213
+ iconName: 'ext:git-branch/plus',
214
+ });
215
+ return items;
216
+ }
217
+ catch (err) {
218
+ ctx.logger.error('getMenu failed', err);
219
+ return [{ id: '__error__', label: ctx.i18n.t('git.branch.fetch.error'), disabled: true }];
220
+ }
221
+ },
222
+ async execute({ cwd }, itemId, actions) {
223
+ if (!cwd || !itemId)
224
+ return;
225
+ // ── Create branch: 直接填入 Prompt ────────────────────────────
226
+ if (itemId === '__create_branch__') {
227
+ await actions.fillComposer(ctx.i18n.t('git.branch.create.prompt'));
228
+ return;
229
+ }
230
+ if (itemId.startsWith('__'))
231
+ return;
232
+ // ── Branch switch ──────────────────────────────────────────────
233
+ try {
234
+ const fromBranch = await git(cwd, ['branch', '--show-current']).catch(() => '');
235
+ const status = await git(cwd, ['status', '--porcelain']);
236
+ let checkpointCommit;
237
+ if (status) {
238
+ const message = await buildDiffMessage(cwd, ctx.i18n);
239
+ const result = await ctx.ui.showModalDialog({
240
+ title: ctx.i18n.t('git.branch.switch.title'),
241
+ description: ctx.i18n.t('git.branch.switch.desc', { branch: itemId }),
242
+ message,
243
+ actions: [
244
+ { id: 'cancel', label: ctx.i18n.t('git.branch.switch.cancel'), variant: 'secondary' },
245
+ { id: 'commit', label: ctx.i18n.t('git.branch.switch.commit'), variant: 'primary' },
246
+ ],
247
+ });
248
+ if (result.action === 'dismissed' || result.action === 'cancel') {
249
+ return;
250
+ }
251
+ await git(cwd, ['add', '-A']);
252
+ await git(cwd, [
253
+ 'commit', '-m',
254
+ ctx.i18n.t('git.branch.switch.commit.msg', { branch: itemId }),
255
+ ]);
256
+ checkpointCommit = await git(cwd, ['rev-parse', '--short', 'HEAD']).catch(() => undefined);
257
+ }
258
+ await git(cwd, ['checkout', itemId], 10_000);
259
+ await ctx.storage.set(CURRENT_BRANCH_KEY, itemId);
260
+ if (checkpointCommit) {
261
+ await ctx.ui.showToast({
262
+ title: ctx.i18n.t('git.branch.switch.toast.title'),
263
+ description: ctx.i18n.t('git.branch.switch.toast.desc', {
264
+ from: fromBranch || 'HEAD',
265
+ to: itemId,
266
+ commit: checkpointCommit,
267
+ }),
268
+ variant: 'success',
269
+ position: 'TC',
270
+ });
271
+ }
272
+ }
273
+ catch (err) {
274
+ ctx.logger.error('checkout failed', err);
275
+ ctx.ui.showMessage(ctx.i18n.t('git.branch.switch.fail'), 'error');
276
+ }
277
+ },
278
+ });
279
+ // ── Agent tool: create branch with form dialog ─────────────────────────
280
+ ctx.subscriptions.push(ctx.tools.register({
281
+ name: 'create_git_branch',
282
+ title: ctx.i18n.t('tool.create.title'),
283
+ description: ctx.i18n.t('tool.create.desc'),
284
+ inputSchema: {
285
+ type: 'object',
286
+ properties: {},
287
+ required: [],
288
+ },
289
+ risk: 'medium',
290
+ async execute(_input, exec) {
291
+ await exec.storage.delete('pendingCreateBranch').catch(() => { });
292
+ const result = await exec.ui.requestForm({
293
+ title: ctx.i18n.t('git.branch.create.title'),
294
+ description: ctx.i18n.t('git.branch.create.desc'),
295
+ submitLabel: ctx.i18n.t('git.branch.create.submit'),
296
+ fields: [
297
+ {
298
+ key: 'branchName',
299
+ label: ctx.i18n.t('git.branch.create.field'),
300
+ type: 'text',
301
+ required: true,
302
+ placeholder: ctx.i18n.t('git.branch.create.ph'),
303
+ },
304
+ ],
305
+ timeoutMs: 120_000,
306
+ });
307
+ if (!result.submitted) {
308
+ return { content: [{ type: 'text', text: ctx.i18n.t('git.branch.create.cancelled') }] };
309
+ }
310
+ const branchName = result.values.branchName;
311
+ if (!branchName || !/^[a-zA-Z0-9_./-]+$/.test(branchName)) {
312
+ return {
313
+ content: [
314
+ { type: 'text', text: ctx.i18n.t('git.branch.create.invalid', { name: branchName }) },
315
+ ],
316
+ isError: true,
317
+ };
318
+ }
319
+ const cwd = exec.cwd;
320
+ if (!cwd) {
321
+ return { content: [{ type: 'text', text: ctx.i18n.t('git.branch.create.nocwd') }], isError: true };
322
+ }
323
+ const status = await git(cwd, ['status', '--porcelain']).catch(() => '');
324
+ if (status) {
325
+ await git(cwd, ['add', '-A']);
326
+ await git(cwd, [
327
+ 'commit', '-m',
328
+ `checkpoint: before creating branch ${branchName}`,
329
+ ]);
330
+ }
331
+ await git(cwd, ['checkout', '-b', branchName], 10_000);
332
+ await ctx.storage.set(CURRENT_BRANCH_KEY, branchName);
333
+ let msg = ctx.i18n.t('git.branch.create.success', { name: branchName });
334
+ if (status) {
335
+ msg += ctx.i18n.t('git.branch.create.checkpoint');
336
+ }
337
+ return {
338
+ content: [{ type: 'text', text: msg }],
339
+ };
340
+ },
341
+ }));
342
+ ctx.subscriptions.push(composerAction);
343
+ // ── Background poller: notify badge refresh when branch changes externally ──
344
+ // Reads .git/HEAD directly (no process spawn) every 3 s.
345
+ // Calls composerAction.notifyUpdate() when branch differs, which triggers
346
+ // a getBadge re-fetch and updates the toolbar badge immediately.
347
+ let lastPolledBranch;
348
+ const pollInterval = setInterval(() => {
349
+ const cwd = activeCwd;
350
+ if (!cwd || !isGitRepo(cwd))
351
+ return;
352
+ try {
353
+ const head = readFileSync(join(cwd, '.git/HEAD'), 'utf-8').trim();
354
+ const match = head.match(/^ref: refs\/heads\/(.+)$/);
355
+ const branch = match?.[1];
356
+ if (!branch)
357
+ return;
358
+ if (branch !== lastPolledBranch) {
359
+ lastPolledBranch = branch;
360
+ composerAction.notifyUpdate();
361
+ }
362
+ }
363
+ catch {
364
+ // ignore transient errors (detached HEAD, missing .git/HEAD, etc.)
365
+ }
366
+ }, 3000);
367
+ ctx.subscriptions.push({ dispose: () => clearInterval(pollInterval) });
368
+ ctx.logger.info('git-branch v2 activated');
369
+ }
370
+ export function deactivate() { }
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "Git Branch",
3
+ "description": "Manage Git branches from the dialog box.",
4
+ "systemPrompt": "When the user wants to create a new Git branch, use the create_git_branch tool which shows a form to collect the branch name.",
5
+ "toolMeta": {
6
+ "name": "Git Branch"
7
+ },
8
+ "composerActions": {
9
+ "git-branch": {
10
+ "tooltip": "Git Branch Manager"
11
+ }
12
+ },
13
+
14
+ "git.branch.changes": "{count} changes",
15
+ "git.branch.more": "More branches",
16
+ "git.branch.more.group": "Branches",
17
+ "git.branch.create": "Create and checkout branch...",
18
+ "git.branch.create.prompt": "Create and checkout a new git branch",
19
+ "git.branch.fetch.error": "Failed to fetch branches",
20
+ "git.branch.switch.title": "Uncommitted Changes",
21
+ "git.branch.switch.desc": "Please handle uncommitted files before switching to {branch}",
22
+ "git.branch.switch.cancel": "Cancel",
23
+ "git.branch.switch.commit": "Commit & Switch",
24
+ "git.branch.switch.commit.msg": "checkpoint: before switching to {branch}",
25
+ "git.branch.switch.toast.title": "Committed and switched branch",
26
+ "git.branch.switch.toast.desc": "Committed checkpoint `{commit}` on `{from}`, then switched to `{to}`.",
27
+ "git.branch.switch.fail": "Branch switch failed, please check your workspace and retry",
28
+ "git.branch.diff.title": "! There are uncommitted changes in the current branch. Please commit before switching.",
29
+ "git.branch.diff.new": "(new file)",
30
+ "git.branch.diff.total": "> Total: {files} files, {add} {del}",
31
+ "git.branch.create.title": "Create Git Branch",
32
+ "git.branch.create.desc": "Enter the name for the new branch",
33
+ "git.branch.create.submit": "Create & Checkout",
34
+ "git.branch.create.field": "Branch Name",
35
+ "git.branch.create.ph": "feature/my-feature",
36
+ "git.branch.create.cancelled": "Branch creation cancelled",
37
+ "git.branch.create.invalid": "Branch name \"{name}\" is invalid. Use letters, numbers, underscores, slashes, and hyphens only.",
38
+ "git.branch.create.nocwd": "No working directory",
39
+ "git.branch.create.success": "✅ Created and switched to branch `{name}`",
40
+ "git.branch.create.checkpoint": "\n\nUncommitted changes were auto-committed as a checkpoint.",
41
+ "tool.create.title": "Create Git Branch",
42
+ "tool.create.desc": "Create and switch to a new Git branch. Opens a form to collect the branch name. Call when the user wants to create a new branch or says a branch name after clicking \"Create and checkout branch...\"."
43
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "Git 分支",
3
+ "description": "在对话框里管理 Git 分支的小工具",
4
+ "systemPrompt": "当用户需要创建 Git 分支时,使用 create_git_branch 工具。该工具会弹出表单让用户输入分支名称。",
5
+ "toolMeta": {
6
+ "name": "Git 分支"
7
+ },
8
+ "composerActions": {
9
+ "git-branch": {
10
+ "tooltip": "Git 分支管理"
11
+ }
12
+ },
13
+
14
+ "git.branch.changes": "{count} 个改动",
15
+ "git.branch.more": "更多分支",
16
+ "git.branch.more.group": "分支",
17
+ "git.branch.create": "创建并检出分支...",
18
+ "git.branch.create.prompt": "帮我创建并检出一个新分支",
19
+ "git.branch.fetch.error": "获取分支失败",
20
+ "git.branch.switch.title": "未提交的更改",
21
+ "git.branch.switch.desc": "切换到 {branch} 前需要先处理未提交的文件",
22
+ "git.branch.switch.cancel": "取消",
23
+ "git.branch.switch.commit": "提交并切换",
24
+ "git.branch.switch.commit.msg": "checkpoint: before switching to {branch}",
25
+ "git.branch.switch.toast.title": "已提交并切换分支",
26
+ "git.branch.switch.toast.desc": "已在 `{from}` 提交 checkpoint `{commit}`,并切换到 `{to}`。",
27
+ "git.branch.switch.fail": "切换分支失败,请检查工作区状态后重试",
28
+ "git.branch.diff.title": "! 当前分支有未提交的更改,请先提交再切换",
29
+ "git.branch.diff.new": "(new file)",
30
+ "git.branch.diff.total": "> 总计: {files} 个文件,{add} {del}",
31
+ "git.branch.create.title": "创建 Git 分支",
32
+ "git.branch.create.desc": "输入新分支的名称",
33
+ "git.branch.create.submit": "创建并检出",
34
+ "git.branch.create.field": "分支名称",
35
+ "git.branch.create.ph": "feature/my-feature",
36
+ "git.branch.create.cancelled": "已取消创建分支",
37
+ "git.branch.create.invalid": "分支名称 \"{name}\" 不合法,请使用字母、数字、下划线、斜杠和连字符",
38
+ "git.branch.create.nocwd": "没有工作目录",
39
+ "git.branch.create.success": "✅ 已创建并切换到分支 `{name}`",
40
+ "git.branch.create.checkpoint": "\n\n已自动提交当前工作区的更改作为 checkpoint。",
41
+ "tool.create.title": "Create Git Branch",
42
+ "tool.create.desc": "Create and switch to a new Git branch. Opens a form to collect the branch name. Call when the user wants to create a new branch or says a branch name after clicking \"创建并检出分支...\"."
43
+ }
package/icon.png ADDED
Binary file
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <circle cx="12" cy="12" r="10" />
3
+ <line x1="12" y1="8" x2="12" y2="16" />
4
+ <line x1="8" y1="12" x2="16" y2="12" />
5
+ </svg>
package/icons/plus.svg ADDED
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <line x1="12" y1="5" x2="12" y2="19" />
3
+ <line x1="5" y1="12" x2="19" y2="12" />
4
+ </svg>
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "finch-git-branch",
3
+ "version": "0.2.8",
4
+ "description": "Git Branch Manager for Finch Composer toolbar.",
5
+ "author": {
6
+ "name": "Finch Team",
7
+ "url": "https://finchwork.app"
8
+ },
9
+ "homepage": "https://finchwork.app",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/finchtoys/finch-releases.git",
13
+ "directory": "extensions/git-branch"
14
+ },
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json"
20
+ },
21
+ "finch": {
22
+ "manifestVersion": 1,
23
+ "id": "git-branch",
24
+ "name": "Git Branch",
25
+ "description": "Switch Git branches from the Composer toolbar with change previews.",
26
+ "systemPrompt": "当用户需要创建 Git 分支时,使用 create_git_branch 工具。该工具会弹出表单让用户输入分支名称。",
27
+ "main": "dist/index.js",
28
+ "activationEvents": [
29
+ "onStartup"
30
+ ],
31
+ "extensionType": "official",
32
+ "categories": [
33
+ "developer"
34
+ ],
35
+ "privacyPolicyUrl": "https://finchwork.app/privacy",
36
+ "termsOfServiceUrl": "https://finchwork.app/terms",
37
+ "contributes": {
38
+ "tools": true,
39
+ "composerActions": [
40
+ {
41
+ "id": "git-branch",
42
+ "icon": "git-branch",
43
+ "tooltip": "Git 分支管理"
44
+ }
45
+ ],
46
+ "iconPacks": [
47
+ {
48
+ "id": "git-branch",
49
+ "label": "Git Branch Icons"
50
+ }
51
+ ],
52
+ "skills": false
53
+ },
54
+ "permissions": {
55
+ "filesystem": "none",
56
+ "network": false,
57
+ "shell": true
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "@finchtoys/minitool-api": "^0.1.16",
62
+ "@types/node": "^26.1.0",
63
+ "typescript": "^6.0.3"
64
+ },
65
+ "files": [
66
+ "dist/",
67
+ "i18n/",
68
+ "icons/",
69
+ "icon.png",
70
+ "README.md",
71
+ "README.zh-CN.md"
72
+ ]
73
+ }