opencode-gitwt 0.2.1 → 0.2.2

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.
Files changed (2) hide show
  1. package/package.json +2 -3
  2. package/src/index.js +95 -59
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitwt",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Git worktree tools for OpenCode, powered by gitwt",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -23,8 +23,7 @@
23
23
  "release": "node scripts/release.js"
24
24
  },
25
25
  "dependencies": {
26
- "@voischev/gitwt": "^0.1.4",
27
- "zod": "4.1.8"
26
+ "@voischev/gitwt": "^0.1.4"
28
27
  },
29
28
  "devDependencies": {
30
29
  "@biomejs/biome": "2.6.3"
package/src/index.js CHANGED
@@ -1,67 +1,103 @@
1
- import { createWorktree, listWorktrees, removeWorktree, resolveWorktree } from "@voischev/gitwt";
2
- import { z } from "zod";
1
+ import {
2
+ createWorktree,
3
+ GitwtError,
4
+ listWorktrees,
5
+ removeWorktree,
6
+ resolveWorktree,
7
+ } from "@voischev/gitwt";
3
8
 
4
9
  export async function GitwtPlugin({ directory }, config = {}) {
5
- return {
6
- tool: {
7
- gitwt_create: {
8
- description:
9
- "Create and verify a new worktree. Use worktreePath for commands and files in the current session. Keep the worktree when pausing or handing off a task.",
10
- args: {
11
- branch: z.string().describe("New branch name"),
12
- base: z.string().optional().describe("Remote base branch"),
13
- from: z
14
- .string()
15
- .optional()
16
- .describe("Local ref or verified commit instead of a remote base"),
17
- path: z.string().optional().describe("Destination path"),
18
- noHooks: z.boolean().optional().describe("Disable hooks for this worktree"),
10
+ const tool = {
11
+ gitwt_create: {
12
+ description:
13
+ "Create and verify a new worktree. Use worktreePath for commands and files in the current session. Keep the worktree when pausing or handing off a task.",
14
+ args: {
15
+ branch: { type: "string", description: "New branch name" },
16
+ base: { type: "string", description: "Remote base branch" },
17
+ from: {
18
+ type: "string",
19
+ description: "Local ref or verified commit instead of a remote base",
19
20
  },
20
- execute: async (args, ctx) =>
21
- JSON.stringify(
22
- await createWorktree(ctx.directory ?? directory, {
23
- ...args,
24
- signal: ctx.abort,
25
- config: { ...config, base: args.base ?? config.base },
26
- }),
27
- ),
28
- },
29
- gitwt_list: {
30
- description:
31
- "List Git worktrees. With status, check dirty and available ahead/behind counts; null means unknown.",
32
- args: { status: z.boolean().optional().describe("Check live worktree status") },
33
- execute: async (args, ctx) =>
34
- JSON.stringify(await listWorktrees(ctx.directory ?? directory, args)),
35
- },
36
- gitwt_cd: {
37
- description:
38
- "Resolve a branch or full path to a worktree with HEAD and status. Use worktreePath explicitly; this does not change the session directory.",
39
- args: { target: z.string().describe("Exact branch name or full worktree path") },
40
- execute: async ({ target }, ctx) =>
41
- JSON.stringify(
42
- await resolveWorktree(ctx.directory ?? directory, target, { status: true }),
43
- ),
21
+ path: { type: "string", description: "Destination path" },
22
+ noHooks: { type: "boolean", description: "Disable hooks for this worktree" },
44
23
  },
45
- gitwt_remove: {
46
- description:
47
- "Remove a worktree only when explicitly requested and no longer in use. Force discards local changes but does not bypass a failing preRemove hook.",
48
- args: {
49
- target: z.string().describe("Exact branch name or full worktree path"),
50
- force: z.boolean().optional().describe("Allow discarding local changes"),
51
- keepBranch: z
52
- .boolean()
53
- .optional()
54
- .describe("Keep the branch after removing the worktree"),
55
- },
56
- execute: async ({ target, force, keepBranch }, ctx) =>
57
- JSON.stringify(
58
- await removeWorktree(ctx.directory ?? directory, target, {
59
- force,
60
- deleteBranch: !keepBranch,
61
- signal: ctx.abort,
62
- }),
63
- ),
24
+ execute: async (args, ctx) =>
25
+ JSON.stringify(
26
+ await createWorktree(ctx.directory ?? directory, {
27
+ ...args,
28
+ signal: ctx.abort,
29
+ config: { ...config, base: args.base ?? config.base },
30
+ }),
31
+ ),
32
+ },
33
+ gitwt_list: {
34
+ description:
35
+ "List Git worktrees. With status, check dirty and available ahead/behind counts; null means unknown.",
36
+ args: { status: { type: "boolean", description: "Check live worktree status" } },
37
+ execute: async (args, ctx) =>
38
+ JSON.stringify(await listWorktrees(ctx.directory ?? directory, args)),
39
+ },
40
+ gitwt_cd: {
41
+ description:
42
+ "Resolve a branch or full path to a worktree with HEAD and status. Use worktreePath explicitly; this does not change the session directory.",
43
+ args: { target: { type: "string", description: "Exact branch name or full worktree path" } },
44
+ execute: async ({ target }, ctx) =>
45
+ JSON.stringify(await resolveWorktree(ctx.directory ?? directory, target, { status: true })),
46
+ },
47
+ gitwt_remove: {
48
+ description:
49
+ "Remove a worktree only when explicitly requested and no longer in use. Force discards local changes but does not bypass a failing preRemove hook.",
50
+ args: {
51
+ target: { type: "string", description: "Exact branch name or full worktree path" },
52
+ force: { type: "boolean", description: "Allow discarding local changes" },
53
+ keepBranch: { type: "boolean", description: "Keep the branch after removing the worktree" },
64
54
  },
55
+ execute: async ({ target, force, keepBranch }, ctx) =>
56
+ JSON.stringify(
57
+ await removeWorktree(ctx.directory ?? directory, target, {
58
+ force,
59
+ deleteBranch: !keepBranch,
60
+ signal: ctx.abort,
61
+ }),
62
+ ),
63
+ },
64
+ };
65
+ const required = {
66
+ gitwt_create: ["branch"],
67
+ gitwt_list: [],
68
+ gitwt_cd: ["target"],
69
+ gitwt_remove: ["target"],
70
+ };
71
+ for (const [id, definition] of Object.entries(tool)) {
72
+ const execute = definition.execute;
73
+ definition.execute = async (args, ctx) => {
74
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
75
+ throw new GitwtError("invalid_arguments", `${id}: expected an argument object`);
76
+ }
77
+ for (const key of Object.keys(args)) {
78
+ if (!Object.hasOwn(definition.args, key)) {
79
+ throw new GitwtError("invalid_arguments", `${id}: unknown argument ${key}`);
80
+ }
81
+ }
82
+ for (const [key, schema] of Object.entries(definition.args)) {
83
+ if (args[key] === undefined && !required[id].includes(key)) continue;
84
+ if (typeof args[key] !== schema.type) {
85
+ throw new GitwtError("invalid_arguments", `${id}: ${key} must be ${schema.type}`);
86
+ }
87
+ }
88
+ return execute(args, ctx);
89
+ };
90
+ }
91
+ return {
92
+ tool,
93
+ "tool.definition": async ({ toolID }, output) => {
94
+ if (!Object.hasOwn(tool, toolID)) return;
95
+ output.jsonSchema = {
96
+ type: "object",
97
+ properties: tool[toolID].args,
98
+ required: required[toolID],
99
+ additionalProperties: false,
100
+ };
65
101
  },
66
102
  };
67
103
  }