glab-agent 0.2.4 โ†’ 0.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glab-agent",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "description": "Multi-agent GitLab To-Do watcher with YAML-defined agents, skills, and GitLab registry.",
6
6
  "license": "MIT",
@@ -91,10 +91,14 @@ export interface GitlabClient {
91
91
  updateUserStatus(emoji: string, message: string, availability?: "busy" | "not_set"): Promise<void>;
92
92
  updateUserBio(bio: string): Promise<void>;
93
93
  listAllPendingTodos(): Promise<GitlabTodoItem[]>;
94
- listWikiPages(projectId: number): Promise<WikiPage[]>;
95
- getWikiPage(projectId: number, slug: string): Promise<WikiPage>;
96
- createWikiPage(projectId: number, title: string, content: string): Promise<WikiPage>;
97
- updateWikiPage(projectId: number, slug: string, title: string, content: string): Promise<WikiPage>;
94
+ listWikiPages(projectId: number | string): Promise<WikiPage[]>;
95
+ getWikiPage(projectId: number | string, slug: string): Promise<WikiPage>;
96
+ createWikiPage(projectId: number | string, title: string, content: string): Promise<WikiPage>;
97
+ updateWikiPage(projectId: number | string, slug: string, title: string, content: string): Promise<WikiPage>;
98
+ getProject(projectIdOrPath: number | string): Promise<{ id: number } | undefined>;
99
+ createProject(name: string, options?: { visibility?: string; initializeWithReadme?: boolean }): Promise<{ id: number }>;
100
+ getRepositoryFile(projectId: number | string, filePath: string, ref?: string): Promise<{ content: string } | undefined>;
101
+ createOrUpdateRepositoryFile(projectId: number | string, filePath: string, content: string, commitMessage: string, options?: { create?: boolean }): Promise<void>;
98
102
  }
99
103
 
100
104
  interface GitlabGlabClientOptions {
@@ -632,7 +636,7 @@ export class GitlabGlabClient implements GitlabClient {
632
636
  });
633
637
  }
634
638
 
635
- async listWikiPages(projectId: number): Promise<WikiPage[]> {
639
+ async listWikiPages(projectId: number | string): Promise<WikiPage[]> {
636
640
  const endpoint = this.withQuery(`projects/${projectId}/wikis`, { with_content: "0" });
637
641
  let payload: unknown;
638
642
  try {
@@ -656,7 +660,7 @@ export class GitlabGlabClient implements GitlabClient {
656
660
  .filter((w): w is WikiPage => w !== undefined);
657
661
  }
658
662
 
659
- async getWikiPage(projectId: number, slug: string): Promise<WikiPage> {
663
+ async getWikiPage(projectId: number | string, slug: string): Promise<WikiPage> {
660
664
  const payload = await this.readJson(`projects/${projectId}/wikis/${encodeURIComponent(slug)}`);
661
665
  const p = (payload ?? {}) as Payload;
662
666
  return {
@@ -666,7 +670,7 @@ export class GitlabGlabClient implements GitlabClient {
666
670
  };
667
671
  }
668
672
 
669
- async createWikiPage(projectId: number, title: string, content: string): Promise<WikiPage> {
673
+ async createWikiPage(projectId: number | string, title: string, content: string): Promise<WikiPage> {
670
674
  const stdout = await this.request(`projects/${projectId}/wikis`, {
671
675
  method: "POST",
672
676
  fields: { title, content }
@@ -679,7 +683,7 @@ export class GitlabGlabClient implements GitlabClient {
679
683
  };
680
684
  }
681
685
 
682
- async updateWikiPage(projectId: number, slug: string, title: string, content: string): Promise<WikiPage> {
686
+ async updateWikiPage(projectId: number | string, slug: string, title: string, content: string): Promise<WikiPage> {
683
687
  // title must be passed to preserve directory prefix in slug (GitLab API quirk)
684
688
  const stdout = await this.request(`projects/${projectId}/wikis/${encodeURIComponent(slug)}`, {
685
689
  method: "PUT",
@@ -693,6 +697,58 @@ export class GitlabGlabClient implements GitlabClient {
693
697
  };
694
698
  }
695
699
 
700
+ async getProject(projectIdOrPath: number | string): Promise<{ id: number } | undefined> {
701
+ try {
702
+ const encoded = typeof projectIdOrPath === "string" ? encodeURIComponent(projectIdOrPath) : projectIdOrPath;
703
+ const payload = await this.readJson(`projects/${encoded}`);
704
+ const p = (payload ?? {}) as Payload;
705
+ const id = Number(p.id);
706
+ return Number.isNaN(id) ? undefined : { id };
707
+ } catch {
708
+ return undefined;
709
+ }
710
+ }
711
+
712
+ async createProject(name: string, options?: { visibility?: string; initializeWithReadme?: boolean }): Promise<{ id: number }> {
713
+ const stdout = await this.request("projects", {
714
+ method: "POST",
715
+ fields: {
716
+ name,
717
+ path: name,
718
+ visibility: options?.visibility ?? "public",
719
+ initialize_with_readme: options?.initializeWithReadme ? "true" : "false"
720
+ }
721
+ });
722
+ const payload = JSON.parse(stdout) as Payload;
723
+ return { id: Number(payload.id) };
724
+ }
725
+
726
+ async getRepositoryFile(projectId: number | string, filePath: string, ref?: string): Promise<{ content: string } | undefined> {
727
+ try {
728
+ const encoded = typeof projectId === "string" ? encodeURIComponent(projectId) : projectId;
729
+ const endpoint = this.withQuery(`projects/${encoded}/repository/files/${encodeURIComponent(filePath)}`, { ref: ref ?? "main" });
730
+ const payload = await this.readJson(endpoint);
731
+ const p = (payload ?? {}) as Payload;
732
+ const content = typeof p.content === "string" ? Buffer.from(p.content, "base64").toString("utf8") : "";
733
+ return { content };
734
+ } catch {
735
+ return undefined;
736
+ }
737
+ }
738
+
739
+ async createOrUpdateRepositoryFile(projectId: number | string, filePath: string, content: string, commitMessage: string, options?: { create?: boolean }): Promise<void> {
740
+ const encoded = typeof projectId === "string" ? encodeURIComponent(projectId) : projectId;
741
+ const method = options?.create ? "POST" : "PUT";
742
+ await this.request(`projects/${encoded}/repository/files/${encodeURIComponent(filePath)}`, {
743
+ method,
744
+ fields: {
745
+ branch: "main",
746
+ content,
747
+ commit_message: commitMessage
748
+ }
749
+ });
750
+ }
751
+
696
752
  /**
697
753
  * Direct HTTP call using Node's built-in fetch.
698
754
  * Used for endpoints where glab CLI's response parsing has issues (e.g. PUT /user/status).
@@ -803,7 +803,22 @@ async function publishAgentProfileWiki(
803
803
  ): Promise<void> {
804
804
  const logger = dependencies.logger ?? console;
805
805
  const def = config.agentDefinition;
806
- if (!def || !config.gitlabProjectId) return;
806
+ if (!def) return;
807
+
808
+ // Resolve project identifier: explicit/inferred ID, or encoded path from git remote
809
+ let projectRef: number | string | undefined = config.gitlabProjectId;
810
+ if (!projectRef) {
811
+ const remoteUrl = readOriginRemote(config.agentRepoPath, execFileSync);
812
+ const remote = remoteUrl ? parseGitRemoteUrl(remoteUrl) : undefined;
813
+ if (remote) {
814
+ // GitLab API accepts URL-encoded project path as project ID
815
+ const projectPath = remote.projectPath.startsWith("git/")
816
+ ? remote.projectPath.replace(/^git\//, "")
817
+ : remote.projectPath;
818
+ projectRef = encodeURIComponent(projectPath);
819
+ }
820
+ }
821
+ if (!projectRef) return;
807
822
 
808
823
  const agentName = def.name;
809
824
  const slug = `agents/${agentName}`;
@@ -854,12 +869,12 @@ async function publishAgentProfileWiki(
854
869
  const content = lines.join("\n");
855
870
 
856
871
  try {
857
- const pages = await dependencies.gitlabClient.listWikiPages(config.gitlabProjectId);
872
+ const pages = await dependencies.gitlabClient.listWikiPages(projectRef);
858
873
  const existing = pages.find(p => p.slug === slug || p.slug === `agents-${agentName}`);
859
874
  if (existing) {
860
- await dependencies.gitlabClient.updateWikiPage(config.gitlabProjectId, existing.slug, slug, content);
875
+ await dependencies.gitlabClient.updateWikiPage(projectRef, existing.slug, slug, content);
861
876
  } else {
862
- await dependencies.gitlabClient.createWikiPage(config.gitlabProjectId, slug, content);
877
+ await dependencies.gitlabClient.createWikiPage(projectRef, slug, content);
863
878
  }
864
879
  logger.info(`Wiki profile published: agents/${agentName}`);
865
880
  } catch (error) {
@@ -867,6 +882,81 @@ async function publishAgentProfileWiki(
867
882
  }
868
883
  }
869
884
 
885
+ async function publishProfileReadme(
886
+ config: LocalAgentConfig,
887
+ dependencies: WatcherDependencies
888
+ ): Promise<void> {
889
+ const logger = dependencies.logger ?? console;
890
+ const def = config.agentDefinition;
891
+ const username = config.botUsername;
892
+ if (!def || !username) return;
893
+
894
+ const projectPath = `${username}/${username}`;
895
+
896
+ // Build README content
897
+ const displayName = def.display_name || def.name;
898
+ const lines: string[] = [
899
+ `## ๐Ÿ‘‹ Hi, I'm ${displayName}`,
900
+ "",
901
+ ];
902
+
903
+ if (def.description) {
904
+ lines.push(def.description, "");
905
+ }
906
+
907
+ const skillNames = def.skill_refs.length > 0 ? def.skill_refs : def.skills.map(s => s.name);
908
+ if (skillNames.length > 0) {
909
+ lines.push("### ๐Ÿ›  Skills", "", ...skillNames.map(s => `- ${s}`), "");
910
+ }
911
+
912
+ if (def.prompt.preamble) {
913
+ lines.push("### ๐Ÿ“‹ What I Do", "", def.prompt.preamble.trim(), "");
914
+ }
915
+
916
+ lines.push(
917
+ "### ๐Ÿ’ฌ How to Use",
918
+ "",
919
+ `ๅœจไปปๆ„ issue ๆˆ– MR ไธญ่ฏ„่ฎบ \`@${username} <ไฝ ็š„้œ€ๆฑ‚>\`๏ผŒๆˆ‘ไผš่‡ชๅŠจๆŽฅๅ•ๅค„็†ใ€‚`,
920
+ "",
921
+ "### ๐Ÿ“Š Status",
922
+ "",
923
+ `- Provider: \`${def.provider}\``,
924
+ `- Poll interval: ${def.poll_interval_seconds ?? 30}s`,
925
+ "",
926
+ "---",
927
+ `*Auto-updated by [glab-agent](https://www.npmjs.com/package/glab-agent)*`,
928
+ );
929
+
930
+ const content = lines.join("\n");
931
+
932
+ try {
933
+ // Check if profile project exists
934
+ let project = await dependencies.gitlabClient.getProject(projectPath);
935
+
936
+ if (!project) {
937
+ // Create profile project
938
+ project = await dependencies.gitlabClient.createProject(username, {
939
+ visibility: "public",
940
+ initializeWithReadme: true
941
+ });
942
+ logger.info(`Created profile project: ${projectPath}`);
943
+ }
944
+
945
+ // Update or create README
946
+ const existing = await dependencies.gitlabClient.getRepositoryFile(project.id, "README.md");
947
+ await dependencies.gitlabClient.createOrUpdateRepositoryFile(
948
+ project.id,
949
+ "README.md",
950
+ content,
951
+ "Update profile README",
952
+ { create: !existing }
953
+ );
954
+ logger.info(`Profile README updated: ${projectPath}`);
955
+ } catch (error) {
956
+ logger.info?.(`Profile README update skipped: ${String(error).slice(0, 150)}`);
957
+ }
958
+ }
959
+
870
960
  export async function markAgentOffline(
871
961
  _config: LocalAgentConfig,
872
962
  gitlabClient: GitlabClient,
@@ -1249,6 +1339,7 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
1249
1339
  // Update GitLab user bio with agent profile (P1: Agent = ๅ‘˜ๅทฅ)
1250
1340
  await updateAgentUserBio(config, dependencies);
1251
1341
  await publishAgentProfileWiki(config, dependencies);
1342
+ await publishProfileReadme(config, dependencies);
1252
1343
  await updateAgentUserStatus(dependencies, "idle", undefined, config);
1253
1344
  } catch (error) {
1254
1345
  logger.error(`Token validation failed for agent "${agentName}": ${String(error)}`);