@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
package/src/lib/env.ts CHANGED
@@ -6,14 +6,13 @@
6
6
  * removed from extension load; seeding now lives in explicit doctor/setup flows.
7
7
  */
8
8
 
9
- import { checkSetup } from "../config/guard.js";
10
9
  import {
11
- GLOBAL_SETTINGS_PATH,
12
10
  ensureConfig,
11
+ GLOBAL_SETTINGS_PATH,
13
12
  loadConfig,
14
13
  } from "../config/loader.js";
15
14
 
16
- export { GLOBAL_SETTINGS_PATH, ensureConfig, loadConfig };
15
+ export { ensureConfig, GLOBAL_SETTINGS_PATH, loadConfig };
17
16
 
18
17
  // ---------------------------------------------------------------------------
19
18
  // Token access
@@ -3,10 +3,26 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import type { ProjectCache } from "../config/types.js";
5
5
 
6
- const CACHE_DIR = join(homedir(), ".pi", "agent", "cache", "pi-gitlab");
6
+ const CACHE_DIR = join(homedir(), ".pi", "agent", ".cache", "pi-gitlab");
7
7
  const CACHE_PATH = join(CACHE_DIR, "projects.json");
8
+ const OLD_CACHE_DIR = join(homedir(), ".pi", "agent", "cache", "pi-gitlab");
9
+ const OLD_CACHE_PATH = join(OLD_CACHE_DIR, "projects.json");
10
+
11
+ /** One-time migration from old cache path to .cache */
12
+ function migrateOldCache(): void {
13
+ if (!existsSync(OLD_CACHE_PATH) || existsSync(CACHE_PATH)) return;
14
+ mkdirSync(CACHE_DIR, { recursive: true });
15
+ try {
16
+ const raw = readFileSync(OLD_CACHE_PATH, "utf-8");
17
+ const cache = JSON.parse(raw) as ProjectCache;
18
+ writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2));
19
+ } catch {
20
+ // If old cache is corrupt, skip migration
21
+ }
22
+ }
8
23
 
9
24
  function readCache(): ProjectCache {
25
+ migrateOldCache();
10
26
  if (!existsSync(CACHE_PATH)) {
11
27
  return { version: 1, entries: {} };
12
28
  }
@@ -3,7 +3,7 @@ import { Type } from "typebox";
3
3
  export const OptionalProject = Type.Optional(
4
4
  Type.String({
5
5
  description:
6
- "Project path (e.g. 'agents/primecodex/packages/pi-gitlab') or numeric ID. Optional — falls back to CWD git remote, then settings default.",
6
+ "Project path (e.g. 'group/project') or numeric ID. Optional — falls back to CWD git remote, then settings default.",
7
7
  }),
8
8
  );
9
9
 
@@ -0,0 +1,138 @@
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 LintResult {
13
+ valid: boolean;
14
+ errors?: string[];
15
+ warnings?: string[];
16
+ merged_yaml?: string;
17
+ }
18
+
19
+ function formatLintResult(result: LintResult) {
20
+ const lines: string[] = [];
21
+
22
+ if (result.valid) {
23
+ lines.push("✅ CI configuration is **valid**.");
24
+ } else {
25
+ lines.push("❌ CI configuration has **errors**.");
26
+ }
27
+
28
+ if (result.errors && result.errors.length > 0) {
29
+ lines.push("");
30
+ lines.push("**Errors:**");
31
+ for (const err of result.errors) {
32
+ lines.push(`- ${err}`);
33
+ }
34
+ }
35
+
36
+ if (result.warnings && result.warnings.length > 0) {
37
+ lines.push("");
38
+ lines.push("**Warnings:**");
39
+ for (const warn of result.warnings) {
40
+ lines.push(`- ${warn}`);
41
+ }
42
+ }
43
+
44
+ return {
45
+ content: [{ type: "text" as const, text: lines.join("\n") }],
46
+ details: {
47
+ success: true,
48
+ valid: result.valid,
49
+ errors: result.errors ?? [],
50
+ warnings: result.warnings ?? [],
51
+ },
52
+ };
53
+ }
54
+
55
+ export function registerGitlabCiLint(pi: ExtensionAPI) {
56
+ pi.registerTool({
57
+ name: "gitlab_ci_lint",
58
+ label: "Lint CI Configuration",
59
+ description:
60
+ "Validate .gitlab-ci.yml configuration via the GitLab CI lint API. Returns errors and warnings.",
61
+ parameters: Type.Object(
62
+ {
63
+ project: OptionalProject,
64
+ content: Type.Optional(
65
+ Type.String({
66
+ description:
67
+ "CI YAML content to validate. Omit to lint the project's existing .gitlab-ci.yml.",
68
+ }),
69
+ ),
70
+ ref: Type.Optional(
71
+ Type.String({
72
+ description:
73
+ "Branch or tag ref for context when linting existing config.",
74
+ }),
75
+ ),
76
+ },
77
+ { additionalProperties: false },
78
+ ),
79
+ async execute(
80
+ _toolCallId,
81
+ params,
82
+ _signal,
83
+ _onUpdate,
84
+ ctx: ExtensionContext,
85
+ ) {
86
+ try {
87
+ requireSetup(ctx.cwd);
88
+ } catch {
89
+ return setupRequiredResult();
90
+ }
91
+
92
+ const projectPath = await resolveProject(params.project, ctx.cwd);
93
+ const projectId = await resolveProjectId(projectPath);
94
+
95
+ const endpoint = `projects/${projectId}/ci/lint`;
96
+ let content = params.content;
97
+ const ref = params.ref;
98
+
99
+ // If no content provided, fetch existing .gitlab-ci.yml
100
+ if (!content) {
101
+ try {
102
+ const fileRef = ref ?? "HEAD";
103
+ content = (await glab([
104
+ "api",
105
+ `projects/${projectId}/repository/files/.gitlab-ci.yml/raw?ref=${fileRef}`,
106
+ ])) as string;
107
+ } catch {
108
+ return {
109
+ content: [
110
+ {
111
+ type: "text",
112
+ text: "Could not fetch `.gitlab-ci.yml` from the repository. Provide `content` parameter with the YAML to validate.",
113
+ },
114
+ ],
115
+ details: {
116
+ success: false,
117
+ error: "file_not_found",
118
+ },
119
+ };
120
+ }
121
+ }
122
+
123
+ const result = (await glab([
124
+ "api",
125
+ "-X",
126
+ "POST",
127
+ endpoint,
128
+ "-f",
129
+ `content=${content}`,
130
+ ...(ref ? ["-f", `ref=${ref}`] : []),
131
+ "-f",
132
+ "include_merged_yaml=true",
133
+ ])) as LintResult;
134
+
135
+ return formatLintResult(result);
136
+ },
137
+ });
138
+ }
@@ -1,15 +1,15 @@
1
+ import { spawn } from "node:child_process";
1
2
  import type {
2
3
  ExtensionAPI,
3
4
  ExtensionContext,
4
5
  } from "@earendil-works/pi-coding-agent";
5
6
  import { Type } from "typebox";
7
+ import { loadConfig } from "../config/loader.js";
8
+ import { requireConfirm } from "../lib/confirm.js";
6
9
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
10
  import { glab } from "../lib/glab.js";
8
- import { requireConfirm } from "../lib/confirm.js";
9
- import { loadConfig } from "../config/loader.js";
10
11
  import { resolveProject } from "../lib/projectFallback.js";
11
12
  import { resolveProjectId } from "../lib/resolveProjectId.js";
12
- import { spawn } from "node:child_process";
13
13
 
14
14
  export function registerGitlabForcePushSafe(pi: ExtensionAPI) {
15
15
  pi.registerTool({
@@ -129,7 +129,12 @@ export function registerGitlabForcePushSafe(pi: ExtensionAPI) {
129
129
  // Step 3: Force push
130
130
  try {
131
131
  await runGitPush(
132
- ["push", "--force-with-lease", remote, `${params.branch}:${remoteBranch}`],
132
+ [
133
+ "push",
134
+ "--force-with-lease",
135
+ remote,
136
+ `${params.branch}:${remoteBranch}`,
137
+ ],
133
138
  ctx.cwd,
134
139
  );
135
140
  } catch (err) {
@@ -185,7 +190,11 @@ export function registerGitlabForcePushSafe(pi: ExtensionAPI) {
185
190
  text: `✅ Force-push complete: \`${params.branch}\` → \`${remote}/${remoteBranch}\`${isProtected ? "\nBranch re-protected." : ""}`,
186
191
  },
187
192
  ],
188
- details: { success: true, branch: remoteBranch, wasProtected: isProtected },
193
+ details: {
194
+ success: true,
195
+ branch: remoteBranch,
196
+ wasProtected: isProtected,
197
+ },
189
198
  };
190
199
  },
191
200
  });
@@ -221,10 +230,7 @@ async function reprotectBranch(
221
230
  }
222
231
  }
223
232
 
224
- async function runGitPush(
225
- args: string[],
226
- cwd?: string,
227
- ): Promise<void> {
233
+ async function runGitPush(args: string[], cwd?: string): Promise<void> {
228
234
  await new Promise<void>((resolve, reject) => {
229
235
  const child = spawn("git", args, {
230
236
  cwd,
@@ -240,7 +246,9 @@ async function runGitPush(
240
246
  resolve();
241
247
  return;
242
248
  }
243
- reject(new Error(stderr.trim() || `git push exited with code ${code ?? -1}`));
249
+ reject(
250
+ new Error(stderr.trim() || `git push exited with code ${code ?? -1}`),
251
+ );
244
252
  });
245
253
  });
246
254
  }
@@ -1,8 +1,11 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
2
5
  import { Type } from "typebox";
6
+ import { requireConfirm } from "../lib/confirm.js";
3
7
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
4
8
  import { glab } from "../lib/glab.js";
5
- import { requireConfirm } from "../lib/confirm.js";
6
9
  import { resolveProject } from "../lib/projectFallback.js";
7
10
  import { resolveProjectId } from "../lib/resolveProjectId.js";
8
11
  import { OptionalProject } from "../lib/schemas.js";
@@ -21,7 +24,13 @@ export function registerGitlabIssueClose(pi: ExtensionAPI) {
21
24
  },
22
25
  { additionalProperties: false },
23
26
  ),
24
- async execute(_toolCallId, params, _signal, _onUpdate, ctx: ExtensionContext) {
27
+ async execute(
28
+ _toolCallId,
29
+ params,
30
+ _signal,
31
+ _onUpdate,
32
+ ctx: ExtensionContext,
33
+ ) {
25
34
  try {
26
35
  requireSetup(ctx.cwd);
27
36
  } catch {
@@ -38,7 +47,10 @@ export function registerGitlabIssueClose(pi: ExtensionAPI) {
38
47
  ])) as Record<string, unknown>;
39
48
 
40
49
  const preview = `Close issue #${params.issueId}: **${issue.title}**\nProject: \`${projectPath}\``;
41
- const blocked = requireConfirm(preview, { confirm: params.confirm, dryRun: params.dryRun });
50
+ const blocked = requireConfirm(preview, {
51
+ confirm: params.confirm,
52
+ dryRun: params.dryRun,
53
+ });
42
54
  if (blocked) return blocked;
43
55
 
44
56
  const result = await glab([
@@ -1,8 +1,11 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
2
5
  import { Type } from "typebox";
6
+ import { requireConfirm } from "../lib/confirm.js";
3
7
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
4
8
  import { glab } from "../lib/glab.js";
5
- import { requireConfirm } from "../lib/confirm.js";
6
9
  import { resolveProject } from "../lib/projectFallback.js";
7
10
  import { resolveProjectId } from "../lib/resolveProjectId.js";
8
11
  import { OptionalProject } from "../lib/schemas.js";
@@ -16,7 +19,9 @@ export function registerGitlabIssueCreate(pi: ExtensionAPI) {
16
19
  {
17
20
  project: OptionalProject,
18
21
  title: Type.String({ description: "Issue title" }),
19
- description: Type.Optional(Type.String({ description: "Issue description" })),
22
+ description: Type.Optional(
23
+ Type.String({ description: "Issue description" }),
24
+ ),
20
25
  labels: Type.Optional(Type.Array(Type.String())),
21
26
  assignee: Type.Optional(Type.String()),
22
27
  milestone: Type.Optional(Type.String()),
@@ -26,7 +31,13 @@ export function registerGitlabIssueCreate(pi: ExtensionAPI) {
26
31
  },
27
32
  { additionalProperties: false },
28
33
  ),
29
- async execute(_toolCallId, params, _signal, _onUpdate, ctx: ExtensionContext) {
34
+ async execute(
35
+ _toolCallId,
36
+ params,
37
+ _signal,
38
+ _onUpdate,
39
+ ctx: ExtensionContext,
40
+ ) {
30
41
  try {
31
42
  requireSetup(ctx.cwd);
32
43
  } catch {
@@ -38,7 +49,10 @@ export function registerGitlabIssueCreate(pi: ExtensionAPI) {
38
49
  const projectId = await resolveProjectId(projectPath);
39
50
 
40
51
  const preview = `Create issue **${params.title}**\nProject: \`${projectPath}\`${params.labels?.length ? `\nLabels: ${params.labels.join(", ")}` : ""}`;
41
- const blocked = requireConfirm(preview, { confirm: params.confirm, dryRun: params.dryRun });
52
+ const blocked = requireConfirm(preview, {
53
+ confirm: params.confirm,
54
+ dryRun: params.dryRun,
55
+ });
42
56
  if (blocked) return blocked;
43
57
 
44
58
  const body: Record<string, unknown> = { title: params.title };
@@ -56,12 +56,12 @@ export function registerGitlabIssueList(pi: ExtensionAPI) {
56
56
  ctx: ExtensionContext,
57
57
  ) {
58
58
  try {
59
- requireSetup(ctx.cwd);
60
- } catch {
61
- return setupRequiredResult();
62
- }
59
+ requireSetup(ctx.cwd);
60
+ } catch {
61
+ return setupRequiredResult();
62
+ }
63
63
 
64
- const projectPath = await resolveProject(params.project, ctx.cwd);
64
+ const projectPath = await resolveProject(params.project, ctx.cwd);
65
65
  const projectId = await resolveProjectId(projectPath);
66
66
 
67
67
  const query = new URLSearchParams();
@@ -39,12 +39,12 @@ export function registerGitlabJobLogs(pi: ExtensionAPI) {
39
39
  ctx: ExtensionContext,
40
40
  ) {
41
41
  try {
42
- requireSetup(ctx.cwd);
43
- } catch {
44
- return setupRequiredResult();
45
- }
42
+ requireSetup(ctx.cwd);
43
+ } catch {
44
+ return setupRequiredResult();
45
+ }
46
46
 
47
- const projectPath = await resolveProject(params.project, ctx.cwd);
47
+ const projectPath = await resolveProject(params.project, ctx.cwd);
48
48
  const projectId = await resolveProjectId(projectPath);
49
49
 
50
50
  const logText = (await glab([
@@ -3,9 +3,9 @@ import type {
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
5
  import { Type } from "typebox";
6
+ import { requireConfirm } from "../lib/confirm.js";
6
7
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
8
  import { glab } from "../lib/glab.js";
8
- import { requireConfirm } from "../lib/confirm.js";
9
9
  import { resolveProject } from "../lib/projectFallback.js";
10
10
  import { resolveProjectId } from "../lib/resolveProjectId.js";
11
11
  import { OptionalProject } from "../lib/schemas.js";
@@ -85,7 +85,9 @@ export function registerGitlabMrBulkApprove(pi: ExtensionAPI) {
85
85
 
86
86
  const lines: string[] = [];
87
87
  if (succeeded.length > 0) {
88
- lines.push(`✅ Approved ${succeeded.length} MR(s): ${succeeded.map((r) => `!${r.mrIid}`).join(", ")}`);
88
+ lines.push(
89
+ `✅ Approved ${succeeded.length} MR(s): ${succeeded.map((r) => `!${r.mrIid}`).join(", ")}`,
90
+ );
89
91
  }
90
92
  if (failed.length > 0) {
91
93
  lines.push(`\n❌ Failed ${failed.length}:`);
@@ -3,9 +3,9 @@ import type {
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
5
  import { Type } from "typebox";
6
+ import { requireConfirm } from "../lib/confirm.js";
6
7
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
8
  import { glab } from "../lib/glab.js";
8
- import { requireConfirm } from "../lib/confirm.js";
9
9
  import { resolveProject } from "../lib/projectFallback.js";
10
10
  import { resolveProjectId } from "../lib/resolveProjectId.js";
11
11
  import { OptionalProject } from "../lib/schemas.js";
@@ -57,12 +57,12 @@ export function registerGitlabMrList(pi: ExtensionAPI) {
57
57
  ctx: ExtensionContext,
58
58
  ) {
59
59
  try {
60
- requireSetup(ctx.cwd);
61
- } catch {
62
- return setupRequiredResult();
63
- }
60
+ requireSetup(ctx.cwd);
61
+ } catch {
62
+ return setupRequiredResult();
63
+ }
64
64
 
65
- const projectPath = await resolveProject(params.project, ctx.cwd);
65
+ const projectPath = await resolveProject(params.project, ctx.cwd);
66
66
  const projectId = await resolveProjectId(projectPath);
67
67
 
68
68
  const query = new URLSearchParams();
@@ -3,9 +3,9 @@ import type {
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
5
  import { Type } from "typebox";
6
+ import { requireConfirm } from "../lib/confirm.js";
6
7
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
8
  import { glab } from "../lib/glab.js";
8
- import { requireConfirm } from "../lib/confirm.js";
9
9
  import { resolveProject } from "../lib/projectFallback.js";
10
10
  import { resolveProjectId } from "../lib/resolveProjectId.js";
11
11
  import { OptionalProject } from "../lib/schemas.js";
@@ -20,9 +20,7 @@ export function registerGitlabMrMerge(pi: ExtensionAPI) {
20
20
  project: OptionalProject,
21
21
  mrId: Type.Number({ description: "MR IID" }),
22
22
  squash: Type.Optional(Type.Boolean({ default: false })),
23
- removeSourceBranch: Type.Optional(
24
- Type.Boolean({ default: false }),
25
- ),
23
+ removeSourceBranch: Type.Optional(Type.Boolean({ default: false })),
26
24
  confirm: Type.Optional(Type.Boolean({ default: false })),
27
25
  dryRun: Type.Optional(Type.Boolean({ default: false })),
28
26
  },
@@ -32,12 +32,12 @@ export function registerGitlabMrView(pi: ExtensionAPI) {
32
32
  ctx: ExtensionContext,
33
33
  ) {
34
34
  try {
35
- requireSetup(ctx.cwd);
36
- } catch {
37
- return setupRequiredResult();
38
- }
35
+ requireSetup(ctx.cwd);
36
+ } catch {
37
+ return setupRequiredResult();
38
+ }
39
39
 
40
- const projectPath = await resolveProject(params.project, ctx.cwd);
40
+ const projectPath = await resolveProject(params.project, ctx.cwd);
41
41
  const projectId = await resolveProjectId(projectPath);
42
42
 
43
43
  const mr = (await glab([
@@ -1,8 +1,11 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
2
5
  import { Type } from "typebox";
6
+ import { requireConfirm } from "../lib/confirm.js";
3
7
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
4
8
  import { glab } from "../lib/glab.js";
5
- import { requireConfirm } from "../lib/confirm.js";
6
9
  import { resolveProject } from "../lib/projectFallback.js";
7
10
  import { resolveProjectId } from "../lib/resolveProjectId.js";
8
11
  import { OptionalProject } from "../lib/schemas.js";
@@ -16,13 +19,21 @@ export function registerGitlabPipelineRun(pi: ExtensionAPI) {
16
19
  {
17
20
  project: OptionalProject,
18
21
  ref: Type.String({ description: "Branch or tag to run pipeline for" }),
19
- variables: Type.Optional(Type.Object({}, { additionalProperties: true })),
22
+ variables: Type.Optional(
23
+ Type.Object({}, { additionalProperties: true }),
24
+ ),
20
25
  confirm: Type.Optional(Type.Boolean({ default: false })),
21
26
  dryRun: Type.Optional(Type.Boolean({ default: false })),
22
27
  },
23
28
  { additionalProperties: false },
24
29
  ),
25
- async execute(_toolCallId, params, _signal, _onUpdate, ctx: ExtensionContext) {
30
+ async execute(
31
+ _toolCallId,
32
+ params,
33
+ _signal,
34
+ _onUpdate,
35
+ ctx: ExtensionContext,
36
+ ) {
26
37
  try {
27
38
  requireSetup(ctx.cwd);
28
39
  } catch {
@@ -34,15 +45,20 @@ export function registerGitlabPipelineRun(pi: ExtensionAPI) {
34
45
  const projectId = await resolveProjectId(projectPath);
35
46
 
36
47
  const preview = `Trigger pipeline for \`${params.ref}\`\nProject: \`${projectPath}\``;
37
- const blocked = requireConfirm(preview, { confirm: params.confirm, dryRun: params.dryRun });
48
+ const blocked = requireConfirm(preview, {
49
+ confirm: params.confirm,
50
+ dryRun: params.dryRun,
51
+ });
38
52
  if (blocked) return blocked;
39
53
 
40
54
  const body: Record<string, unknown> = { ref: params.ref };
41
55
  if (params.variables && Object.keys(params.variables).length > 0) {
42
- body.variables = Object.entries(params.variables).map(([key, value]) => ({
43
- key,
44
- value: String(value),
45
- }));
56
+ body.variables = Object.entries(params.variables).map(
57
+ ([key, value]) => ({
58
+ key,
59
+ value: String(value),
60
+ }),
61
+ );
46
62
  }
47
63
 
48
64
  const result = await glab([
@@ -50,7 +66,10 @@ export function registerGitlabPipelineRun(pi: ExtensionAPI) {
50
66
  "-X",
51
67
  "POST",
52
68
  `projects/${projectId}/pipeline`,
53
- ...Object.entries(body).flatMap(([k, v]) => ["-f", `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`]),
69
+ ...Object.entries(body).flatMap(([k, v]) => [
70
+ "-f",
71
+ `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`,
72
+ ]),
54
73
  ]);
55
74
 
56
75
  const pipeline = result as Record<string, unknown>;
@@ -49,12 +49,12 @@ export function registerGitlabPipelineStatus(pi: ExtensionAPI) {
49
49
  ctx: ExtensionContext,
50
50
  ) {
51
51
  try {
52
- requireSetup(ctx.cwd);
53
- } catch {
54
- return setupRequiredResult();
55
- }
52
+ requireSetup(ctx.cwd);
53
+ } catch {
54
+ return setupRequiredResult();
55
+ }
56
56
 
57
- const projectPath = await resolveProject(params.project, ctx.cwd);
57
+ const projectPath = await resolveProject(params.project, ctx.cwd);
58
58
  const projectId = await resolveProjectId(projectPath);
59
59
 
60
60
  let pipeline: Pipeline;
@@ -38,12 +38,12 @@ export function registerGitlabProjectResolve(pi: ExtensionAPI) {
38
38
  ctx: ExtensionContext,
39
39
  ) {
40
40
  try {
41
- requireSetup(ctx.cwd);
42
- } catch {
43
- return setupRequiredResult();
44
- }
41
+ requireSetup(ctx.cwd);
42
+ } catch {
43
+ return setupRequiredResult();
44
+ }
45
45
 
46
- const project = await resolveProject(params.project, ctx.cwd);
46
+ const project = await resolveProject(params.project, ctx.cwd);
47
47
  const id = await resolveProjectId(project, params.force);
48
48
  return {
49
49
  content: [
@@ -3,9 +3,9 @@ import type {
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
5
  import { Type } from "typebox";
6
+ import { requireConfirm } from "../lib/confirm.js";
6
7
  import { requireSetup, setupRequiredResult } from "../lib/errors.js";
7
8
  import { glab } from "../lib/glab.js";
8
- import { requireConfirm } from "../lib/confirm.js";
9
9
  import { resolveProject } from "../lib/projectFallback.js";
10
10
  import { resolveProjectId } from "../lib/resolveProjectId.js";
11
11
  import { OptionalProject } from "../lib/schemas.js";
@@ -14,8 +14,7 @@ export function registerGitlabReleaseCreate(pi: ExtensionAPI) {
14
14
  pi.registerTool({
15
15
  name: "gitlab_release_create",
16
16
  label: "Create Release",
17
- description:
18
- "Create a new release for a tag. Requires confirmation.",
17
+ description: "Create a new release for a tag. Requires confirmation.",
19
18
  parameters: Type.Object(
20
19
  {
21
20
  project: OptionalProject,
@@ -23,19 +23,14 @@ export function registerGitlabReleaseList(pi: ExtensionAPI) {
23
23
  pi.registerTool({
24
24
  name: "gitlab_release_list",
25
25
  label: "List Releases",
26
- description:
27
- "List releases for a project with transparent pagination.",
26
+ description: "List releases for a project with transparent pagination.",
28
27
  parameters: Type.Object(
29
28
  {
30
29
  project: OptionalProject,
31
30
  sort: Type.Optional(
32
- Type.Union(
33
- [
34
- Type.Literal("desc"),
35
- Type.Literal("asc"),
36
- ],
37
- { default: "desc" },
38
- ),
31
+ Type.Union([Type.Literal("desc"), Type.Literal("asc")], {
32
+ default: "desc",
33
+ }),
39
34
  ),
40
35
  maxRows: MaxRows,
41
36
  },
@@ -44,14 +44,19 @@ export function registerGitlabReleaseView(pi: ExtensionAPI) {
44
44
  ])) as Record<string, unknown>;
45
45
 
46
46
  const assets = release.assets as Record<string, unknown> | undefined;
47
- const links = (assets?.links as Array<Record<string, unknown>> | undefined) ?? [];
47
+ const links =
48
+ (assets?.links as Array<Record<string, unknown>> | undefined) ?? [];
48
49
 
49
50
  const lines: string[] = [];
50
51
  lines.push(`## Release: ${release.tag_name}`);
51
52
  if (release.name) lines.push(`**Name:** ${release.name}`);
52
- lines.push(`**Author:** ${(release.author as Record<string, unknown>)?.name ?? "-"}`);
53
+ lines.push(
54
+ `**Author:** ${(release.author as Record<string, unknown>)?.name ?? "-"}`,
55
+ );
53
56
  if (release.created_at)
54
- lines.push(`**Created:** ${new Date(release.created_at as string).toISOString()}`);
57
+ lines.push(
58
+ `**Created:** ${new Date(release.created_at as string).toISOString()}`,
59
+ );
55
60
  if (release.upcoming_release) lines.push("**Upcoming release:** yes");
56
61
 
57
62
  if (release.description) {