@gitlab/opencode-gitlab-plugin 1.5.5 → 1.5.7

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
4
4
 
5
+ ## [1.5.7](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.5.6...v1.5.7) (2026-02-03)
6
+
7
+
8
+ ### ♻️ Code Refactoring
9
+
10
+ * consolidate epic issue management tools into unified interface ([b8708d7](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/b8708d7fa65e4fcc5fda0e46daaf2391d8bbc094))
11
+
12
+ ## [1.5.6](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.5.5...v1.5.6) (2026-02-03)
13
+
14
+
15
+ ### ♻️ Code Refactoring
16
+
17
+ * consolidate search tools into unified interface ([bb75def](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/bb75def68290ea740a04d09afc5d7efa9da02020))
18
+ * implement GitLab Duo review suggestions ([8fa6738](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/8fa67382436d8edb9bce1868c3330d8853255606))
19
+
5
20
  ## [1.5.5](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.5.4...v1.5.5) (2026-02-03)
6
21
 
7
22
 
package/README.md CHANGED
@@ -134,7 +134,7 @@ graph LR
134
134
  B --> B1[readTokenFromAuthStorage]
135
135
  B --> B2[getGitLabClient]
136
136
 
137
- C --> C1[78 Tool Definitions]
137
+ C --> C1[69 Tool Definitions]
138
138
 
139
139
  D --> A
140
140
  D --> B
@@ -287,7 +287,7 @@ Or for API tokens:
287
287
 
288
288
  ## 🛠️ Available Tools
289
289
 
290
- The plugin provides **78 tools** organized into the following categories:
290
+ The plugin provides **67 tools** organized into the following categories:
291
291
 
292
292
  ### Merge Request Tools (9 tools)
293
293
 
@@ -311,17 +311,15 @@ The plugin provides **78 tools** organized into the following categories:
311
311
  | `gitlab_get_issue` | Get issue details including state, author, assignees, labels, and comments |
312
312
  | `gitlab_list_issues` | List issues with filtering by state, labels, assignee, and milestone |
313
313
 
314
- ### Epic Tools (7 tools)
314
+ ### Epic Tools (5 tools)
315
315
 
316
- | Tool | Description |
317
- | ------------------------------- | ----------------------------------------------------------------------------- |
318
- | `gitlab_get_epic` | Get epic details with title, description, state, dates, and associated issues |
319
- | `gitlab_list_epics` | List epics with filtering by state, author, and labels |
320
- | `gitlab_create_epic` | Create a new epic in a group |
321
- | `gitlab_update_epic` | Update epic title, description, labels, dates, and state |
322
- | `gitlab_list_epic_issues` | Get all issues linked to an epic |
323
- | `gitlab_add_issue_to_epic` | Link an issue to an epic |
324
- | `gitlab_remove_issue_from_epic` | Unlink an issue from an epic |
316
+ | Tool | Description |
317
+ | --------------------------- | ----------------------------------------------------------------------------- |
318
+ | `gitlab_get_epic` | Get epic details with title, description, state, dates, and associated issues |
319
+ | `gitlab_list_epics` | List epics with filtering by state, author, and labels |
320
+ | `gitlab_create_epic` | Create a new epic in a group |
321
+ | `gitlab_update_epic` | Update epic title, description, labels, dates, and state |
322
+ | `gitlab_manage_epic_issues` | Manage issues linked to an epic (list, add, remove) with action parameter |
325
323
 
326
324
  ### Pipeline Tools (8 tools)
327
325
 
@@ -348,21 +346,12 @@ The plugin provides **78 tools** organized into the following categories:
348
346
  | `gitlab_list_branches` | List all branches in a repository |
349
347
  | `gitlab_get_commit_comments` | Get all commit comments in flat structure |
350
348
 
351
- ### Search Tools (11 tools)
352
-
353
- | Tool | Description |
354
- | ----------------------------- | ------------------------------------------------------------------------------ |
355
- | `gitlab_search` | Universal search across projects, issues, MRs, code, commits, users, and wikis |
356
- | `gitlab_issue_search` | Specialized issue search with better filtering |
357
- | `gitlab_merge_request_search` | Specialized merge request search with better filtering |
358
- | `gitlab_blob_search` | Search code/text within repository files |
359
- | `gitlab_commit_search` | Search commits by message, author, or SHA |
360
- | `gitlab_group_project_search` | Search projects within a specific group |
361
- | `gitlab_milestone_search` | Search milestones by title or description |
362
- | `gitlab_note_search` | Search comments across issues, MRs, and commits (Premium/Ultimate) |
363
- | `gitlab_user_search` | Search users by name or email |
364
- | `gitlab_wiki_blob_search` | Search wiki content with advanced filters |
365
- | `gitlab_documentation_search` | Search GitLab official documentation |
349
+ ### Search Tools (2 tools)
350
+
351
+ | Tool | Description |
352
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
353
+ | `gitlab_search` | Unified search across all GitLab resources with scope-specific options (projects, issues, merge_requests, milestones, users, blobs, commits, notes, wiki_blobs, group_projects) |
354
+ | `gitlab_documentation_search` | Search GitLab official documentation at docs.gitlab.com |
366
355
 
367
356
  ### Work Item Tools (6 tools)
368
357
 
package/dist/index.js CHANGED
@@ -2309,6 +2309,9 @@ Can filter by state, labels, assignee, milestone.`,
2309
2309
  // src/tools/epics.ts
2310
2310
  import { tool as tool3 } from "@opencode-ai/plugin";
2311
2311
  var z3 = tool3.schema;
2312
+ function validationError(message) {
2313
+ return JSON.stringify({ error: message }, null, 2);
2314
+ }
2312
2315
  var epicTools = {
2313
2316
  gitlab_get_epic: tool3({
2314
2317
  description: `Get details of a specific epic by group and epic IID.
@@ -2402,49 +2405,55 @@ Can update title, description, labels, dates, state, and confidentiality.`,
2402
2405
  return JSON.stringify(epic, null, 2);
2403
2406
  }
2404
2407
  }),
2405
- gitlab_list_epic_issues: tool3({
2406
- description: `Get all issues associated with an epic.
2407
- Returns the list of issues that are linked to the epic.`,
2408
- args: {
2409
- group_id: z3.string().describe("The group ID or URL-encoded path"),
2410
- epic_iid: z3.number().describe("The internal ID of the epic")
2411
- },
2412
- execute: async (args, _ctx) => {
2413
- const client = getGitLabClient();
2414
- const issues = await client.listEpicIssues(args.group_id, args.epic_iid);
2415
- return JSON.stringify(issues, null, 2);
2416
- }
2417
- }),
2418
- gitlab_add_issue_to_epic: tool3({
2419
- description: `Link an existing issue to an epic.
2420
- The issue can be from any project within the group hierarchy.`,
2421
- args: {
2422
- group_id: z3.string().describe("The group ID or URL-encoded path"),
2423
- epic_iid: z3.number().describe("The internal ID of the epic"),
2424
- issue_id: z3.number().describe("The global ID of the issue to add")
2425
- },
2426
- execute: async (args, _ctx) => {
2427
- const client = getGitLabClient();
2428
- const result = await client.addIssueToEpic(args.group_id, args.epic_iid, args.issue_id);
2429
- return JSON.stringify(result, null, 2);
2430
- }
2431
- }),
2432
- gitlab_remove_issue_from_epic: tool3({
2433
- description: `Unlink an issue from an epic.
2434
- Removes the association between the issue and the epic.`,
2408
+ gitlab_manage_epic_issues: tool3({
2409
+ description: `Manage issues linked to a GitLab epic.
2410
+
2411
+ Actions:
2412
+ - list: Get all issues associated with the epic
2413
+ - add: Link an existing issue to the epic (issue can be from any project within the group hierarchy)
2414
+ - remove: Unlink an issue from the epic
2415
+
2416
+ Examples:
2417
+ - List issues: action="list", group_id="gitlab-org", epic_iid=123
2418
+ - Add issue: action="add", group_id="gitlab-org", epic_iid=123, issue_id=456
2419
+ - Remove issue: action="remove", group_id="gitlab-org", epic_iid=123, epic_issue_id=789
2420
+
2421
+ Note: The epic_issue_id (for remove) is the ID of the epic-issue association, which can be obtained from the list action response.`,
2435
2422
  args: {
2423
+ action: z3.enum(["list", "add", "remove"]).describe("The action to perform"),
2436
2424
  group_id: z3.string().describe("The group ID or URL-encoded path"),
2437
- epic_iid: z3.number().describe("The internal ID of the epic"),
2438
- epic_issue_id: z3.number().describe("The ID of the epic-issue association (from list_epic_issues)")
2425
+ epic_iid: z3.number().describe("The internal ID of the epic within the group"),
2426
+ issue_id: z3.number().optional().describe('The global ID of the issue to add (required for "add" action)'),
2427
+ epic_issue_id: z3.number().optional().describe(
2428
+ 'The ID of the epic-issue association to remove (required for "remove" action, from list response)'
2429
+ )
2439
2430
  },
2440
2431
  execute: async (args, _ctx) => {
2441
2432
  const client = getGitLabClient();
2442
- const result = await client.removeIssueFromEpic(
2443
- args.group_id,
2444
- args.epic_iid,
2445
- args.epic_issue_id
2446
- );
2447
- return JSON.stringify(result, null, 2);
2433
+ switch (args.action) {
2434
+ case "list": {
2435
+ const issues = await client.listEpicIssues(args.group_id, args.epic_iid);
2436
+ return JSON.stringify(issues, null, 2);
2437
+ }
2438
+ case "add": {
2439
+ if (args.issue_id === void 0) {
2440
+ return validationError('issue_id is required for "add" action');
2441
+ }
2442
+ const result = await client.addIssueToEpic(args.group_id, args.epic_iid, args.issue_id);
2443
+ return JSON.stringify(result, null, 2);
2444
+ }
2445
+ case "remove": {
2446
+ if (args.epic_issue_id === void 0) {
2447
+ return validationError('epic_issue_id is required for "remove" action');
2448
+ }
2449
+ const result = await client.removeIssueFromEpic(
2450
+ args.group_id,
2451
+ args.epic_iid,
2452
+ args.epic_issue_id
2453
+ );
2454
+ return JSON.stringify(result, null, 2);
2455
+ }
2456
+ }
2448
2457
  }
2449
2458
  })
2450
2459
  };
@@ -2734,10 +2743,83 @@ This is different from discussions - it returns individual comments in a flat st
2734
2743
  // src/tools/search.ts
2735
2744
  import { tool as tool6 } from "@opencode-ai/plugin";
2736
2745
  var z6 = tool6.schema;
2746
+ var GENERIC_SEARCH_SCOPES = ["projects", "issues", "merge_requests", "blobs"];
2747
+ var SPECIALIZED_SCOPES = [
2748
+ "milestones",
2749
+ "users",
2750
+ "commits",
2751
+ "notes",
2752
+ "wiki_blobs",
2753
+ "group_projects"
2754
+ ];
2755
+ var ALL_SCOPES = [...GENERIC_SEARCH_SCOPES, ...SPECIALIZED_SCOPES];
2756
+ var REF_SUPPORTED_SCOPES = ["commits", "wiki_blobs"];
2757
+ var STATE_SUPPORTED_SCOPES = ["milestones"];
2758
+ function validationError2(param, scope) {
2759
+ return new Error(`Missing required parameter: '${param}' is required for scope '${scope}'`);
2760
+ }
2761
+ function invalidParamError(param, validScopes) {
2762
+ return new Error(
2763
+ `Invalid parameter: '${param}' is only valid for scopes: ${validScopes.join(", ")}`
2764
+ );
2765
+ }
2766
+ function validateSearchParams(scope, args) {
2767
+ switch (scope) {
2768
+ case "notes":
2769
+ if (!args.project_id) throw validationError2("project_id", scope);
2770
+ break;
2771
+ case "group_projects":
2772
+ if (!args.group_id) throw validationError2("group_id", scope);
2773
+ break;
2774
+ }
2775
+ if (args.ref && !REF_SUPPORTED_SCOPES.includes(scope)) {
2776
+ throw invalidParamError("ref", REF_SUPPORTED_SCOPES);
2777
+ }
2778
+ if (args.state && !STATE_SUPPORTED_SCOPES.includes(scope)) {
2779
+ throw invalidParamError("state", STATE_SUPPORTED_SCOPES);
2780
+ }
2781
+ }
2737
2782
  var searchTools = {
2783
+ /**
2784
+ * Unified search across all GitLab resources
2785
+ *
2786
+ * @example
2787
+ * // Search for issues in a project
2788
+ * gitlab_search({ scope: "issues", search: "bug", project_id: "my-group/project" })
2789
+ *
2790
+ * @example
2791
+ * // Search commits on specific branch
2792
+ * gitlab_search({ scope: "commits", search: "fix", ref: "main" })
2793
+ *
2794
+ * @example
2795
+ * // Search for projects within a group
2796
+ * gitlab_search({ scope: "group_projects", group_id: "gitlab-org", search: "runner" })
2797
+ *
2798
+ * @example
2799
+ * // Search notes/comments in a project
2800
+ * gitlab_search({ scope: "notes", search: "LGTM", project_id: "my-group/project" })
2801
+ */
2738
2802
  gitlab_search: tool6({
2739
- description: `Search across GitLab for various resources.
2740
- Scopes: projects, issues, merge_requests, milestones, users, blobs (code), commits, notes, wiki_blobs`,
2803
+ description: `Search across GitLab for various resources with scope-specific options.
2804
+
2805
+ Scopes and their requirements:
2806
+ - projects: Search projects by name/description
2807
+ - issues: Search issues by title/description
2808
+ - merge_requests: Search MRs by title/description
2809
+ - milestones: Search milestones (supports state filter)
2810
+ - users: Search users by name/email
2811
+ - blobs: Search file content (code search)
2812
+ - commits: Search commits by message/author/SHA (supports ref filter)
2813
+ - notes: Search comments/notes (requires project_id)
2814
+ - wiki_blobs: Search wiki content (supports ref filter)
2815
+ - group_projects: Search projects within a group (requires group_id)
2816
+
2817
+ Examples:
2818
+ - Issues: scope="issues", search="bug", project_id="my-group/my-project"
2819
+ - Code: scope="blobs", search="function calculateTotal"
2820
+ - Commits: scope="commits", search="fix login", ref="main"
2821
+ - Group projects: scope="group_projects", group_id="gitlab-org", search="runner"
2822
+ - Notes: scope="notes", search="LGTM", project_id="my-group/my-project"`,
2741
2823
  args: {
2742
2824
  scope: z6.enum([
2743
2825
  "projects",
@@ -2748,238 +2830,104 @@ Scopes: projects, issues, merge_requests, milestones, users, blobs (code), commi
2748
2830
  "blobs",
2749
2831
  "commits",
2750
2832
  "notes",
2751
- "wiki_blobs"
2752
- ]).describe("The scope of the search"),
2833
+ "wiki_blobs",
2834
+ "group_projects"
2835
+ ]).describe("The type of resource to search"),
2753
2836
  search: z6.string().describe("The search query"),
2754
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2755
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2756
- },
2757
- execute: async (args, _ctx) => {
2758
- const client = getGitLabClient();
2759
- const results = await client.search(args.scope, args.search, args.project_id, args.limit);
2760
- return JSON.stringify(results, null, 2);
2761
- }
2762
- }),
2763
- gitlab_issue_search: tool6({
2764
- description: `Search issues by keyword across GitLab.
2765
- Specialized search for issues with better filtering than the generic search.`,
2766
- args: {
2767
- search: z6.string().describe("The search query"),
2768
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2769
- state: z6.enum(["opened", "closed", "all"]).optional().describe("Filter by issue state"),
2770
- labels: z6.string().optional().describe("Comma-separated list of labels to filter by"),
2771
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2772
- },
2773
- execute: async (args, _ctx) => {
2774
- const client = getGitLabClient();
2775
- const results = await client.search("issues", args.search, args.project_id, args.limit);
2776
- return JSON.stringify(results, null, 2);
2777
- }
2778
- }),
2779
- gitlab_blob_search: tool6({
2780
- description: `Search file content in repositories.
2781
- Search for code/text within files across projects.`,
2782
- args: {
2783
- search: z6.string().describe("The search query (code/text to find)"),
2784
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2785
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2786
- },
2787
- execute: async (args, _ctx) => {
2788
- const client = getGitLabClient();
2789
- const results = await client.search("blobs", args.search, args.project_id, args.limit);
2790
- return JSON.stringify(results, null, 2);
2791
- }
2792
- }),
2793
- gitlab_merge_request_search: tool6({
2794
- description: `Search merge requests by keyword.
2795
- Specialized search for merge requests with better filtering than the generic search.`,
2796
- args: {
2797
- search: z6.string().describe("The search query"),
2798
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2799
- state: z6.enum(["opened", "closed", "merged", "all"]).optional().describe("Filter by MR state"),
2800
- labels: z6.string().optional().describe("Comma-separated list of labels to filter by"),
2801
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2802
- },
2803
- execute: async (args, _ctx) => {
2804
- const client = getGitLabClient();
2805
- const results = await client.search(
2806
- "merge_requests",
2807
- args.search,
2808
- args.project_id,
2809
- args.limit
2810
- );
2811
- return JSON.stringify(results, null, 2);
2812
- }
2813
- }),
2814
- gitlab_commit_search: tool6({
2815
- description: `Search for commits in a project or across GitLab.
2816
- Returns commits matching the search query with commit details including SHA, title, message, author, and dates.
2817
-
2818
- Supports searching by:
2819
- - Commit message content
2820
- - Author name or email
2821
- - Commit SHA (partial or full)
2822
-
2823
- Note: Advanced search features (Premium/Ultimate) provide better commit search capabilities.`,
2824
- args: {
2825
- search: z6.string().describe("The search query (commit message, author, or SHA)"),
2826
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2827
- ref: z6.string().optional().describe("Branch or tag name to search on (defaults to default branch)"),
2828
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2829
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2830
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2837
+ // Context parameters
2838
+ project_id: z6.string().optional().describe("Limit search to a specific project. Required for notes scope"),
2839
+ group_id: z6.string().optional().describe("Group ID or path. Required for group_projects scope"),
2840
+ // Common options
2841
+ limit: z6.number().optional().describe("Maximum number of results (default: 20)"),
2842
+ order_by: z6.enum(["created_at"]).optional().describe(
2843
+ "Order results by field (for milestones, users, commits, notes, wiki_blobs, group_projects)"
2844
+ ),
2845
+ sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (for milestones, users, commits, notes, wiki_blobs, group_projects)"),
2846
+ // Scope-specific options
2847
+ ref: z6.string().optional().describe("Branch or tag name (for commits, wiki_blobs scopes)"),
2848
+ state: z6.enum(["active", "closed", "all"]).optional().describe("Filter by state (for milestones scope)")
2831
2849
  },
2832
2850
  execute: async (args, _ctx) => {
2833
- const client = getGitLabClient();
2834
- const results = await client.searchCommits(args.search, args.project_id, {
2851
+ validateSearchParams(args.scope, {
2852
+ project_id: args.project_id,
2853
+ group_id: args.group_id,
2835
2854
  ref: args.ref,
2836
- order_by: args.order_by,
2837
- sort: args.sort,
2838
- limit: args.limit
2839
- });
2840
- return JSON.stringify(results, null, 2);
2841
- }
2842
- }),
2843
- gitlab_group_project_search: tool6({
2844
- description: `Search for projects within a specific group.
2845
- Returns projects matching the search query within the specified group and its subgroups.
2846
-
2847
- Useful for:
2848
- - Finding projects by name or description within a group
2849
- - Discovering projects in large group hierarchies
2850
- - Filtering group projects by keywords`,
2851
- args: {
2852
- group_id: z6.string().describe("The group ID or URL-encoded path"),
2853
- search: z6.string().describe("The search query (project name or description)"),
2854
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2855
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2856
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2857
- },
2858
- execute: async (args, _ctx) => {
2859
- const client = getGitLabClient();
2860
- const results = await client.searchGroupProjects(args.group_id, args.search, {
2861
- order_by: args.order_by,
2862
- sort: args.sort,
2863
- limit: args.limit
2864
- });
2865
- return JSON.stringify(results, null, 2);
2866
- }
2867
- }),
2868
- gitlab_milestone_search: tool6({
2869
- description: `Search for milestones in a project or across GitLab.
2870
- Returns milestones matching the search query with details including title, description, state, and dates.
2871
-
2872
- Useful for:
2873
- - Finding milestones by title or description
2874
- - Discovering active or closed milestones
2875
- - Planning and tracking project milestones`,
2876
- args: {
2877
- search: z6.string().describe("The search query (milestone title or description)"),
2878
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2879
- state: z6.enum(["active", "closed", "all"]).optional().describe("Filter by milestone state"),
2880
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2881
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2882
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2883
- },
2884
- execute: async (args, _ctx) => {
2885
- const client = getGitLabClient();
2886
- const results = await client.searchMilestones(args.search, args.project_id, {
2887
- state: args.state,
2888
- order_by: args.order_by,
2889
- sort: args.sort,
2890
- limit: args.limit
2891
- });
2892
- return JSON.stringify(results, null, 2);
2893
- }
2894
- }),
2895
- gitlab_note_search: tool6({
2896
- description: `Search for notes/comments in a project.
2897
- Returns notes (comments) matching the search query from issues, merge requests, commits, and snippets.
2898
-
2899
- Useful for:
2900
- - Finding specific comments or discussions
2901
- - Tracking feedback and review comments
2902
- - Searching for mentions or keywords in conversations
2903
-
2904
- Note: This scope requires Premium or Ultimate tier with advanced search enabled.`,
2905
- args: {
2906
- search: z6.string().describe("The search query (comment text)"),
2907
- project_id: z6.string().describe("The project ID or URL-encoded path to search in"),
2908
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2909
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2910
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2911
- },
2912
- execute: async (args, _ctx) => {
2913
- const client = getGitLabClient();
2914
- const results = await client.searchNotes(args.search, args.project_id, {
2915
- order_by: args.order_by,
2916
- sort: args.sort,
2917
- limit: args.limit
2918
- });
2919
- return JSON.stringify(results, null, 2);
2920
- }
2921
- }),
2922
- gitlab_user_search: tool6({
2923
- description: `Search for users by name or email across GitLab.
2924
- Returns users matching the search query with details including username, name, state, and avatar.
2925
-
2926
- Useful for:
2927
- - Finding users to assign to issues or merge requests
2928
- - Looking up user information by name or email
2929
- - Discovering team members and collaborators`,
2930
- args: {
2931
- search: z6.string().describe("The search query (user name or email)"),
2932
- project_id: z6.string().optional().describe("Limit search to project members (optional)"),
2933
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2934
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2935
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2936
- },
2937
- execute: async (args, _ctx) => {
2938
- const client = getGitLabClient();
2939
- const results = await client.searchUsers(args.search, args.project_id, {
2940
- order_by: args.order_by,
2941
- sort: args.sort,
2942
- limit: args.limit
2855
+ state: args.state
2943
2856
  });
2944
- return JSON.stringify(results, null, 2);
2945
- }
2946
- }),
2947
- gitlab_wiki_blob_search: tool6({
2948
- description: `Search for wiki content (blobs) in a project or across GitLab.
2949
- Returns wiki pages matching the search query with content snippets and file information.
2950
-
2951
- Supports advanced filters (use in search query):
2952
- - filename:some_name* - Filter by filename with wildcards
2953
- - path:some/path* - Filter by path with wildcards
2954
- - extension:md - Filter by file extension
2955
-
2956
- Examples:
2957
- - "installation" - Search for "installation" in wiki content
2958
- - "setup filename:getting-started*" - Search in files starting with "getting-started"
2959
- - "api extension:md" - Search for "api" in markdown files
2960
-
2961
- Note: Advanced search (Premium/Ultimate) provides better wiki search capabilities.`,
2962
- args: {
2963
- search: z6.string().describe(
2964
- "The search query (supports filters: filename:, path:, extension: with wildcards)"
2965
- ),
2966
- project_id: z6.string().optional().describe("Limit search to a specific project (optional)"),
2967
- ref: z6.string().optional().describe("Branch or tag name to search on (defaults to default branch)"),
2968
- order_by: z6.enum(["created_at"]).optional().describe("Order results by created_at (default: created_at desc)"),
2969
- sort: z6.enum(["asc", "desc"]).optional().describe("Sort order (asc or desc)"),
2970
- limit: z6.number().optional().describe("Maximum number of results (default: 20)")
2971
- },
2972
- execute: async (args, _ctx) => {
2973
2857
  const client = getGitLabClient();
2974
- const results = await client.searchWikiBlobs(args.search, args.project_id, {
2975
- ref: args.ref,
2858
+ const commonOptions = {
2976
2859
  order_by: args.order_by,
2977
2860
  sort: args.sort,
2978
2861
  limit: args.limit
2979
- });
2980
- return JSON.stringify(results, null, 2);
2862
+ };
2863
+ switch (args.scope) {
2864
+ // Generic search API scopes
2865
+ case "projects":
2866
+ case "issues":
2867
+ case "merge_requests":
2868
+ case "blobs":
2869
+ return JSON.stringify(
2870
+ await client.search(args.scope, args.search, args.project_id, args.limit),
2871
+ null,
2872
+ 2
2873
+ );
2874
+ // Specialized scopes with dedicated client methods
2875
+ case "milestones":
2876
+ return JSON.stringify(
2877
+ await client.searchMilestones(args.search, args.project_id, {
2878
+ ...commonOptions,
2879
+ state: args.state
2880
+ }),
2881
+ null,
2882
+ 2
2883
+ );
2884
+ case "users":
2885
+ return JSON.stringify(
2886
+ await client.searchUsers(args.search, args.project_id, commonOptions),
2887
+ null,
2888
+ 2
2889
+ );
2890
+ case "commits":
2891
+ return JSON.stringify(
2892
+ await client.searchCommits(args.search, args.project_id, {
2893
+ ...commonOptions,
2894
+ ref: args.ref
2895
+ }),
2896
+ null,
2897
+ 2
2898
+ );
2899
+ case "notes":
2900
+ return JSON.stringify(
2901
+ await client.searchNotes(args.search, args.project_id, commonOptions),
2902
+ null,
2903
+ 2
2904
+ );
2905
+ case "wiki_blobs":
2906
+ return JSON.stringify(
2907
+ await client.searchWikiBlobs(args.search, args.project_id, {
2908
+ ...commonOptions,
2909
+ ref: args.ref
2910
+ }),
2911
+ null,
2912
+ 2
2913
+ );
2914
+ case "group_projects":
2915
+ return JSON.stringify(
2916
+ await client.searchGroupProjects(args.group_id, args.search, commonOptions),
2917
+ null,
2918
+ 2
2919
+ );
2920
+ default:
2921
+ throw new Error(
2922
+ `Invalid scope '${args.scope}'. Must be one of: ${ALL_SCOPES.join(", ")}`
2923
+ );
2924
+ }
2981
2925
  }
2982
2926
  }),
2927
+ /**
2928
+ * Search GitLab official documentation
2929
+ * Separate tool because it uses a completely different API (docs.gitlab.com)
2930
+ */
2983
2931
  gitlab_documentation_search: tool6({
2984
2932
  description: `Search GitLab official documentation at docs.gitlab.com.
2985
2933
  Returns relevant documentation pages matching the search query.
@@ -3757,7 +3705,7 @@ import { tool as tool13 } from "@opencode-ai/plugin";
3757
3705
  var z13 = tool13.schema;
3758
3706
  var VALID_LIST_CREATE_TYPES = ["merge_request", "issue", "epic", "snippet"];
3759
3707
  var VALID_GET_NOTE_TYPES = ["issue", "epic"];
3760
- function validationError(param, resourceType) {
3708
+ function validationError3(param, resourceType) {
3761
3709
  return new Error(
3762
3710
  `Missing required parameter: '${param}' is required for resource_type '${resourceType}'`
3763
3711
  );
@@ -3771,16 +3719,16 @@ function validateListCreateParams(resourceType, args) {
3771
3719
  switch (resourceType) {
3772
3720
  case "merge_request":
3773
3721
  case "issue":
3774
- if (!args.project_id) throw validationError("project_id", resourceType);
3775
- if (args.iid == null) throw validationError("iid", resourceType);
3722
+ if (!args.project_id) throw validationError3("project_id", resourceType);
3723
+ if (args.iid == null) throw validationError3("iid", resourceType);
3776
3724
  break;
3777
3725
  case "epic":
3778
- if (!args.group_id) throw validationError("group_id", resourceType);
3779
- if (args.iid == null) throw validationError("iid", resourceType);
3726
+ if (!args.group_id) throw validationError3("group_id", resourceType);
3727
+ if (args.iid == null) throw validationError3("iid", resourceType);
3780
3728
  break;
3781
3729
  case "snippet":
3782
- if (!args.project_id) throw validationError("project_id", resourceType);
3783
- if (args.snippet_id == null) throw validationError("snippet_id", resourceType);
3730
+ if (!args.project_id) throw validationError3("project_id", resourceType);
3731
+ if (args.snippet_id == null) throw validationError3("snippet_id", resourceType);
3784
3732
  break;
3785
3733
  }
3786
3734
  }
@@ -3790,15 +3738,15 @@ function validateGetNoteParams(resourceType, args) {
3790
3738
  `Invalid resource_type '${resourceType}'. Must be one of: ${VALID_GET_NOTE_TYPES.join(", ")}`
3791
3739
  );
3792
3740
  }
3793
- if (args.note_id == null) throw validationError("note_id", resourceType);
3741
+ if (args.note_id == null) throw validationError3("note_id", resourceType);
3794
3742
  switch (resourceType) {
3795
3743
  case "issue":
3796
- if (!args.project_id) throw validationError("project_id", resourceType);
3797
- if (args.iid == null) throw validationError("iid", resourceType);
3744
+ if (!args.project_id) throw validationError3("project_id", resourceType);
3745
+ if (args.iid == null) throw validationError3("iid", resourceType);
3798
3746
  break;
3799
3747
  case "epic":
3800
- if (!args.group_id) throw validationError("group_id", resourceType);
3801
- if (args.iid == null) throw validationError("iid", resourceType);
3748
+ if (!args.group_id) throw validationError3("group_id", resourceType);
3749
+ if (args.iid == null) throw validationError3("iid", resourceType);
3802
3750
  break;
3803
3751
  }
3804
3752
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/opencode-gitlab-plugin",
3
- "version": "1.5.5",
3
+ "version": "1.5.7",
4
4
  "description": "GitLab tools plugin for OpenCode - provides GitLab API access for merge requests, issues, pipelines, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",