@gaodes/pi-gitlab 0.3.0 → 0.4.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 (48) hide show
  1. package/.primecodex.json +3 -3
  2. package/CHANGELOG.md +40 -1
  3. package/README.md +4 -4
  4. package/package.json +1 -1
  5. package/skills/gitlab-assistant/SKILL.md +1 -1
  6. package/skills/gitlab-badge/SKILL.md +77 -0
  7. package/skills/gitlab-ci/SKILL.md +69 -0
  8. package/skills/gitlab-container/SKILL.md +67 -0
  9. package/skills/gitlab-discussion/SKILL.md +97 -0
  10. package/skills/gitlab-file/SKILL.md +86 -0
  11. package/skills/gitlab-group/SKILL.md +92 -0
  12. package/skills/gitlab-label/SKILL.md +64 -0
  13. package/skills/gitlab-milestone/SKILL.md +68 -0
  14. package/skills/gitlab-protected-branch/SKILL.md +61 -0
  15. package/skills/gitlab-release/SKILL.md +1 -1
  16. package/skills/gitlab-repo/SKILL.md +72 -0
  17. package/skills/gitlab-search/SKILL.md +47 -0
  18. package/skills/gitlab-variable/SKILL.md +69 -0
  19. package/skills/gitlab-vulnerability/SKILL.md +74 -0
  20. package/skills/gitlab-webhook/SKILL.md +79 -0
  21. package/skills/gitlab-wiki/SKILL.md +70 -0
  22. package/skills/gitlab-workflow/SKILL.md +1 -1
  23. package/src/commands/gitlab-doctor.ts +12 -4
  24. package/src/config/loader.ts +13 -1
  25. package/src/config/types.ts +4 -4
  26. package/src/index.ts +14 -1
  27. package/src/lib/env.ts +2 -3
  28. package/src/lib/projectCache.ts +17 -1
  29. package/src/lib/schemas.ts +1 -1
  30. package/src/tools/gitlab_ci_lint.ts +138 -0
  31. package/src/tools/gitlab_force_push_safe.ts +18 -10
  32. package/src/tools/gitlab_issue_close.ts +16 -4
  33. package/src/tools/gitlab_issue_create.ts +19 -5
  34. package/src/tools/gitlab_issue_list.ts +5 -5
  35. package/src/tools/gitlab_job_logs.ts +5 -5
  36. package/src/tools/gitlab_mr_bulk_approve.ts +4 -2
  37. package/src/tools/gitlab_mr_create.ts +1 -1
  38. package/src/tools/gitlab_mr_list.ts +5 -5
  39. package/src/tools/gitlab_mr_merge.ts +2 -4
  40. package/src/tools/gitlab_mr_view.ts +5 -5
  41. package/src/tools/gitlab_pipeline_run.ts +29 -10
  42. package/src/tools/gitlab_pipeline_status.ts +5 -5
  43. package/src/tools/gitlab_project_resolve.ts +5 -5
  44. package/src/tools/gitlab_release_create.ts +2 -3
  45. package/src/tools/gitlab_release_list.ts +4 -9
  46. package/src/tools/gitlab_release_view.ts +8 -3
  47. package/src/tools/gitlab_repo_view.ts +116 -0
  48. package/src/tools/gitlab_search_query.ts +221 -0
@@ -0,0 +1,116 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
+ import { glab } from "../lib/glab.js";
8
+ import { resolveProject } from "../lib/projectFallback.js";
9
+ import { resolveProjectId } from "../lib/resolveProjectId.js";
10
+ import { OptionalProject } from "../lib/schemas.js";
11
+
12
+ interface ProjectInfo {
13
+ id: number;
14
+ name: string;
15
+ path: string;
16
+ path_with_namespace: string;
17
+ description?: string;
18
+ default_branch?: string;
19
+ visibility?: string;
20
+ web_url?: string;
21
+ ssh_url_to_repo?: string;
22
+ http_url_to_repo?: string;
23
+ created_at?: string;
24
+ last_activity_at?: string;
25
+ star_count?: number;
26
+ forks_count?: number;
27
+ open_issues_count?: number;
28
+ namespace?: { name?: string; path?: string; kind?: string };
29
+ topics?: string[];
30
+ archived?: boolean;
31
+ empty_repo?: boolean;
32
+ language?: string;
33
+ }
34
+
35
+ export function registerGitlabRepoView(pi: ExtensionAPI) {
36
+ pi.registerTool({
37
+ name: "gitlab_repo_view",
38
+ label: "View Repository Info",
39
+ description:
40
+ "View detailed information about a GitLab project including URLs, stats, and metadata.",
41
+ parameters: Type.Object(
42
+ {
43
+ project: OptionalProject,
44
+ },
45
+ { additionalProperties: false },
46
+ ),
47
+ async execute(
48
+ _toolCallId,
49
+ params,
50
+ _signal,
51
+ _onUpdate,
52
+ ctx: ExtensionContext,
53
+ ) {
54
+ try {
55
+ requireSetup(ctx.cwd);
56
+ } catch {
57
+ return setupRequiredResult();
58
+ }
59
+
60
+ const projectPath = await resolveProject(params.project, ctx.cwd);
61
+ const projectId = await resolveProjectId(projectPath);
62
+
63
+ const info = (await glab([
64
+ "api",
65
+ `projects/${projectId}`,
66
+ ])) as ProjectInfo;
67
+
68
+ const lines = [
69
+ `## ${info.name}`,
70
+ "",
71
+ `| Field | Value |`,
72
+ `|---|---|`,
73
+ `| **Path** | \`${info.path_with_namespace}\` |`,
74
+ `| **ID** | ${info.id} |`,
75
+ `| **Visibility** | ${info.visibility ?? "-"} |`,
76
+ `| **Default branch** | \`${info.default_branch ?? "-"}\` |`,
77
+ `| **Language** | ${info.language ?? "-"} |`,
78
+ `| **Archived** | ${info.archived ? "Yes" : "No"} |`,
79
+ `| **Empty** | ${info.empty_repo ? "Yes" : "No"} |`,
80
+ `| **Stars** | ${info.star_count ?? 0} |`,
81
+ `| **Forks** | ${info.forks_count ?? 0} |`,
82
+ `| **Open issues** | ${info.open_issues_count ?? 0} |`,
83
+ ...(info.created_at
84
+ ? [
85
+ `| **Created** | ${new Date(info.created_at).toISOString().split("T")[0]} |`,
86
+ ]
87
+ : []),
88
+ ...(info.last_activity_at
89
+ ? [
90
+ `| **Last activity** | ${new Date(info.last_activity_at).toISOString().split("T")[0]} |`,
91
+ ]
92
+ : []),
93
+ ];
94
+
95
+ if (info.description) {
96
+ lines.push("", `> ${info.description}`);
97
+ }
98
+
99
+ if (info.topics && info.topics.length > 0) {
100
+ lines.push(
101
+ "",
102
+ `**Topics:** ${info.topics.map((t) => `\`${t}\``).join(", ")}`,
103
+ );
104
+ }
105
+
106
+ if (info.web_url) {
107
+ lines.push("", `**URL:** ${info.web_url}`);
108
+ }
109
+
110
+ return {
111
+ content: [{ type: "text", text: lines.join("\n") }],
112
+ details: { success: true, project: info },
113
+ };
114
+ },
115
+ });
116
+ }
@@ -0,0 +1,221 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
+ import { glab } from "../lib/glab.js";
8
+ import { limitRows } from "../lib/pagination.js";
9
+ import { resolveProject } from "../lib/projectFallback.js";
10
+ import { resolveProjectId } from "../lib/resolveProjectId.js";
11
+ import { OptionalProject } from "../lib/schemas.js";
12
+
13
+ interface SearchResult {
14
+ id?: number;
15
+ iid?: number;
16
+ name?: string;
17
+ title?: string;
18
+ path?: string;
19
+ path_with_namespace?: string;
20
+ description?: string;
21
+ filename?: string;
22
+ startline?: number;
23
+ data?: string;
24
+ username?: string;
25
+ state?: string;
26
+ web_url?: string;
27
+ ref?: string;
28
+ message?: string;
29
+ }
30
+
31
+ export function registerGitlabSearchQuery(pi: ExtensionAPI) {
32
+ pi.registerTool({
33
+ name: "gitlab_search_query",
34
+ label: "Search GitLab",
35
+ description:
36
+ "Search GitLab globally, within a group, or within a project. Supports projects, issues, merge requests, code blobs, commits, users, and wiki content.",
37
+ parameters: Type.Object(
38
+ {
39
+ query: Type.String({
40
+ description: "Search query string.",
41
+ minLength: 1,
42
+ maxLength: 200,
43
+ }),
44
+ scope: Type.Optional(
45
+ Type.Union(
46
+ [
47
+ Type.Literal("projects"),
48
+ Type.Literal("issues"),
49
+ Type.Literal("merge_requests"),
50
+ Type.Literal("milestones"),
51
+ Type.Literal("blobs"),
52
+ Type.Literal("commits"),
53
+ Type.Literal("users"),
54
+ Type.Literal("wiki_blobs"),
55
+ ],
56
+ {
57
+ description:
58
+ "Search scope. Omit to search across common scopes (projects, issues, merge_requests, blobs).",
59
+ },
60
+ ),
61
+ ),
62
+ project: OptionalProject,
63
+ group: Type.Optional(
64
+ Type.String({
65
+ description: "Group ID. Set to restrict search to this group.",
66
+ }),
67
+ ),
68
+ maxRows: Type.Optional(
69
+ Type.Number({
70
+ default: 25,
71
+ maximum: 100,
72
+ description: "Maximum results to return.",
73
+ }),
74
+ ),
75
+ },
76
+ { additionalProperties: false },
77
+ ),
78
+ async execute(
79
+ _toolCallId,
80
+ params,
81
+ _signal,
82
+ _onUpdate,
83
+ ctx: ExtensionContext,
84
+ ) {
85
+ try {
86
+ requireSetup(ctx.cwd);
87
+ } catch {
88
+ return setupRequiredResult();
89
+ }
90
+
91
+ const searchParams = new URLSearchParams();
92
+ searchParams.set("search", params.query);
93
+
94
+ // Determine endpoint based on scope
95
+ let endpoint: string;
96
+ let scopeDescription: string;
97
+
98
+ if (params.project) {
99
+ const projectPath = await resolveProject(params.project, ctx.cwd);
100
+ const projectId = await resolveProjectId(projectPath);
101
+ endpoint = `projects/${projectId}/search`;
102
+ scopeDescription = `project ${params.project}`;
103
+ } else if (params.group) {
104
+ endpoint = `groups/${params.group}/search`;
105
+ scopeDescription = `group ${params.group}`;
106
+ } else {
107
+ endpoint = "search";
108
+ scopeDescription = "global";
109
+ }
110
+
111
+ // Run searches — if no specific scope given, search across key scopes
112
+ const scopes = params.scope
113
+ ? [params.scope]
114
+ : ["projects", "issues", "merge_requests", "blobs"];
115
+
116
+ const allResults: Array<{
117
+ scope: string;
118
+ items: SearchResult[];
119
+ }> = [];
120
+
121
+ for (const scope of scopes) {
122
+ searchParams.set("scope", scope);
123
+ try {
124
+ const results = (await glab([
125
+ "api",
126
+ `${endpoint}?${searchParams.toString()}`,
127
+ ])) as SearchResult[];
128
+ const limited = limitRows(
129
+ Array.isArray(results) ? results : [],
130
+ params.maxRows ?? 25,
131
+ );
132
+ if (limited.length > 0) {
133
+ allResults.push({ scope, items: limited });
134
+ }
135
+ } catch {
136
+ // Scope may not be available at this level — skip silently
137
+ }
138
+ }
139
+
140
+ if (allResults.length === 0) {
141
+ return {
142
+ content: [
143
+ {
144
+ type: "text",
145
+ text: `No results found for "${params.query}" (${scopeDescription}).`,
146
+ },
147
+ ],
148
+ details: {
149
+ success: true,
150
+ count: 0,
151
+ query: params.query,
152
+ scope: scopeDescription,
153
+ },
154
+ };
155
+ }
156
+
157
+ const lines: string[] = [];
158
+ let totalCount = 0;
159
+
160
+ for (const group of allResults) {
161
+ totalCount += group.items.length;
162
+ lines.push(`\n### ${group.scope}`);
163
+ lines.push("");
164
+
165
+ for (const item of group.items) {
166
+ if (group.scope === "projects") {
167
+ lines.push(
168
+ `- **${item.name ?? item.path ?? "-"}** ${item.path_with_namespace ? `(\`${item.path_with_namespace}\`)` : ""} ${item.description ? `— ${item.description}` : ""}`,
169
+ );
170
+ } else if (group.scope === "issues") {
171
+ lines.push(
172
+ `- #${item.iid ?? "?"} **${item.title ?? "-"}** (${item.state ?? "?"}) ${item.web_url ?? ""}`,
173
+ );
174
+ } else if (group.scope === "merge_requests") {
175
+ lines.push(
176
+ `- !${item.iid ?? "?"} **${item.title ?? "-"}** (${item.state ?? "?"}) ${item.web_url ?? ""}`,
177
+ );
178
+ } else if (group.scope === "blobs") {
179
+ lines.push(
180
+ `- \`${item.filename ?? "?"}\`${item.startline ? `:${item.startline}` : ""} — ${item.ref ?? ""}`,
181
+ );
182
+ } else if (group.scope === "users") {
183
+ lines.push(
184
+ `- **${item.name ?? item.username ?? "-"}** (\`${item.username ?? "?"}\`)`,
185
+ );
186
+ } else if (group.scope === "commits") {
187
+ lines.push(
188
+ `- \`${(item.id ?? "").toString().slice(0, 8)}\` **${item.title ?? item.message ?? "-"}**`,
189
+ );
190
+ } else if (group.scope === "wiki_blobs") {
191
+ lines.push(
192
+ `- \`${item.filename ?? "?"}\`${item.startline ? `:${item.startline}` : ""}`,
193
+ );
194
+ } else if (group.scope === "milestones") {
195
+ lines.push(`- **${item.title ?? "-"}** (${item.state ?? "?"})`);
196
+ } else {
197
+ lines.push(
198
+ `- ${item.title ?? item.name ?? item.path ?? JSON.stringify(item)}`,
199
+ );
200
+ }
201
+ }
202
+ }
203
+
204
+ return {
205
+ content: [
206
+ {
207
+ type: "text",
208
+ text: `Found **${totalCount}** results for "${params.query}" (${scopeDescription}):${lines.join("\n")}`,
209
+ },
210
+ ],
211
+ details: {
212
+ success: true,
213
+ count: totalCount,
214
+ query: params.query,
215
+ scope: scopeDescription,
216
+ scopes: allResults.map((g) => g.scope),
217
+ },
218
+ };
219
+ },
220
+ });
221
+ }