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
|
@@ -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
|
|
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(
|
|
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(
|
|
875
|
+
await dependencies.gitlabClient.updateWikiPage(projectRef, existing.slug, slug, content);
|
|
861
876
|
} else {
|
|
862
|
-
await dependencies.gitlabClient.createWikiPage(
|
|
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)}`);
|