@loopstack/oauth-examples 0.1.1
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/README.md +111 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth-examples.module.d.ts +3 -0
- package/dist/oauth-examples.module.d.ts.map +1 -0
- package/dist/oauth-examples.module.js +42 -0
- package/dist/oauth-examples.module.js.map +1 -0
- package/dist/shared/github/authenticate-github-task.tool.d.ts +26 -0
- package/dist/shared/github/authenticate-github-task.tool.d.ts.map +1 -0
- package/dist/shared/github/authenticate-github-task.tool.js +67 -0
- package/dist/shared/github/authenticate-github-task.tool.js.map +1 -0
- package/dist/shared/github/index.d.ts +2 -0
- package/dist/shared/github/index.d.ts.map +1 -0
- package/dist/shared/github/index.js +18 -0
- package/dist/shared/github/index.js.map +1 -0
- package/dist/shared/google/authenticate-google-task.tool.d.ts +26 -0
- package/dist/shared/google/authenticate-google-task.tool.d.ts.map +1 -0
- package/dist/shared/google/authenticate-google-task.tool.js +69 -0
- package/dist/shared/google/authenticate-google-task.tool.js.map +1 -0
- package/dist/shared/google/google-calendar-fetch-events.tool.d.ts +28 -0
- package/dist/shared/google/google-calendar-fetch-events.tool.d.ts.map +1 -0
- package/dist/shared/google/google-calendar-fetch-events.tool.js +96 -0
- package/dist/shared/google/google-calendar-fetch-events.tool.js.map +1 -0
- package/dist/shared/google/index.d.ts +3 -0
- package/dist/shared/google/index.d.ts.map +1 -0
- package/dist/shared/google/index.js +19 -0
- package/dist/shared/google/index.js.map +1 -0
- package/dist/workflows/github-agent/github-agent-example.ui.yaml +6 -0
- package/dist/workflows/github-agent/github-agent-example.workflow.d.ts +55 -0
- package/dist/workflows/github-agent/github-agent-example.workflow.d.ts.map +1 -0
- package/dist/workflows/github-agent/github-agent-example.workflow.js +243 -0
- package/dist/workflows/github-agent/github-agent-example.workflow.js.map +1 -0
- package/dist/workflows/github-agent/templates/systemMessage.md +23 -0
- package/dist/workflows/github-agent/templates/welcomeMessage.md +10 -0
- package/dist/workflows/github-overview/github-overview-example.workflow.d.ts +96 -0
- package/dist/workflows/github-overview/github-overview-example.workflow.d.ts.map +1 -0
- package/dist/workflows/github-overview/github-overview-example.workflow.js +216 -0
- package/dist/workflows/github-overview/github-overview-example.workflow.js.map +1 -0
- package/dist/workflows/github-overview/templates/repoOverview.md +81 -0
- package/dist/workflows/google-calendar-summary/google-calendar-summary-example.workflow.d.ts +32 -0
- package/dist/workflows/google-calendar-summary/google-calendar-summary-example.workflow.d.ts.map +1 -0
- package/dist/workflows/google-calendar-summary/google-calendar-summary-example.workflow.js +113 -0
- package/dist/workflows/google-calendar-summary/google-calendar-summary-example.workflow.js.map +1 -0
- package/dist/workflows/google-calendar-summary/templates/calendarSummary.md +10 -0
- package/dist/workflows/google-workspace-agent/google-workspace-agent-example.ui.yaml +6 -0
- package/dist/workflows/google-workspace-agent/google-workspace-agent-example.workflow.d.ts +41 -0
- package/dist/workflows/google-workspace-agent/google-workspace-agent-example.workflow.d.ts.map +1 -0
- package/dist/workflows/google-workspace-agent/google-workspace-agent-example.workflow.js +187 -0
- package/dist/workflows/google-workspace-agent/google-workspace-agent-example.workflow.js.map +1 -0
- package/dist/workflows/google-workspace-agent/templates/calendarSummary.md +10 -0
- package/dist/workflows/google-workspace-agent/templates/systemMessage.md +24 -0
- package/dist/workflows/google-workspace-agent/templates/welcomeMessage.md +7 -0
- package/package.json +52 -0
- package/src/index.ts +7 -0
- package/src/oauth-examples.module.ts +30 -0
- package/src/shared/github/authenticate-github-task.tool.ts +70 -0
- package/src/shared/github/index.ts +1 -0
- package/src/shared/google/authenticate-google-task.tool.ts +72 -0
- package/src/shared/google/google-calendar-fetch-events.tool.ts +117 -0
- package/src/shared/google/index.ts +2 -0
- package/src/workflows/github-agent/github-agent-example.ui.yaml +6 -0
- package/src/workflows/github-agent/github-agent-example.workflow.ts +193 -0
- package/src/workflows/github-agent/templates/systemMessage.md +23 -0
- package/src/workflows/github-agent/templates/welcomeMessage.md +10 -0
- package/src/workflows/github-overview/github-overview-example.workflow.ts +223 -0
- package/src/workflows/github-overview/templates/repoOverview.md +81 -0
- package/src/workflows/google-calendar-summary/google-calendar-summary-example.workflow.ts +102 -0
- package/src/workflows/google-calendar-summary/templates/calendarSummary.md +10 -0
- package/src/workflows/google-workspace-agent/google-workspace-agent-example.ui.yaml +6 -0
- package/src/workflows/google-workspace-agent/google-workspace-agent-example.workflow.ts +147 -0
- package/src/workflows/google-workspace-agent/templates/calendarSummary.md +10 -0
- package/src/workflows/google-workspace-agent/templates/systemMessage.md +24 -0
- package/src/workflows/google-workspace-agent/templates/welcomeMessage.md +7 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseWorkflow, Guard, Transition, Workflow } from '@loopstack/common';
|
|
4
|
+
import type { TransitionInput } from '@loopstack/common';
|
|
5
|
+
import {
|
|
6
|
+
GitHubCreateIssueCommentTool,
|
|
7
|
+
GitHubCreateIssueTool,
|
|
8
|
+
GitHubCreateOrUpdateFileTool,
|
|
9
|
+
GitHubCreatePullRequestTool,
|
|
10
|
+
GitHubCreateRepoTool,
|
|
11
|
+
GitHubGetAuthenticatedUserTool,
|
|
12
|
+
GitHubGetCommitTool,
|
|
13
|
+
GitHubGetFileContentTool,
|
|
14
|
+
GitHubGetIssueTool,
|
|
15
|
+
GitHubGetPullRequestTool,
|
|
16
|
+
GitHubGetRepoTool,
|
|
17
|
+
GitHubGetWorkflowRunTool,
|
|
18
|
+
GitHubListBranchesTool,
|
|
19
|
+
GitHubListDirectoryTool,
|
|
20
|
+
GitHubListIssuesTool,
|
|
21
|
+
GitHubListPrReviewsTool,
|
|
22
|
+
GitHubListPullRequestsTool,
|
|
23
|
+
GitHubListReposTool,
|
|
24
|
+
GitHubListUserOrgsTool,
|
|
25
|
+
GitHubListWorkflowRunsTool,
|
|
26
|
+
GitHubMergePullRequestTool,
|
|
27
|
+
GitHubSearchCodeTool,
|
|
28
|
+
GitHubSearchIssuesTool,
|
|
29
|
+
GitHubSearchReposTool,
|
|
30
|
+
GitHubTriggerWorkflowTool,
|
|
31
|
+
} from '@loopstack/github-module';
|
|
32
|
+
import type { LlmDelegateResult, LlmGenerateTextResult } from '@loopstack/llm-provider-module';
|
|
33
|
+
import {
|
|
34
|
+
LlmContextDocument,
|
|
35
|
+
LlmDelegateToolCallsTool,
|
|
36
|
+
LlmGenerateTextTool,
|
|
37
|
+
LlmMessageDocument,
|
|
38
|
+
LlmUpdateToolResultTool,
|
|
39
|
+
} from '@loopstack/llm-provider-module';
|
|
40
|
+
import { OAuthWorkflow } from '@loopstack/oauth-module';
|
|
41
|
+
import { AuthenticateGitHubTask } from '../../shared/github/authenticate-github-task.tool';
|
|
42
|
+
|
|
43
|
+
interface GitHubAgentState {
|
|
44
|
+
llmResult?: LlmGenerateTextResult;
|
|
45
|
+
delegateResult?: LlmDelegateResult;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Workflow({
|
|
49
|
+
title: 'OAuth - GitHub Agent Example',
|
|
50
|
+
description:
|
|
51
|
+
'An interactive chat agent with access to GitHub. Manages repositories, issues, pull requests, code, CI/CD, and search. Handles OAuth authentication automatically — the agent detects unauthorized errors and launches authentication on its own.',
|
|
52
|
+
name: 'github_agent_example',
|
|
53
|
+
widget: './github-agent-example.ui.yaml',
|
|
54
|
+
})
|
|
55
|
+
export class GithubAgentExampleWorkflow extends BaseWorkflow {
|
|
56
|
+
constructor(
|
|
57
|
+
private readonly llmGenerateText: LlmGenerateTextTool,
|
|
58
|
+
private readonly llmDelegateToolCalls: LlmDelegateToolCallsTool,
|
|
59
|
+
private readonly llmUpdateToolResult: LlmUpdateToolResultTool,
|
|
60
|
+
private readonly authenticateGitHub: AuthenticateGitHubTask,
|
|
61
|
+
// GitHub Repos tools
|
|
62
|
+
private readonly gitHubListRepos: GitHubListReposTool,
|
|
63
|
+
private readonly gitHubGetRepo: GitHubGetRepoTool,
|
|
64
|
+
private readonly gitHubCreateRepo: GitHubCreateRepoTool,
|
|
65
|
+
private readonly gitHubListBranches: GitHubListBranchesTool,
|
|
66
|
+
// GitHub Issues tools
|
|
67
|
+
private readonly gitHubListIssues: GitHubListIssuesTool,
|
|
68
|
+
private readonly gitHubGetIssue: GitHubGetIssueTool,
|
|
69
|
+
private readonly gitHubCreateIssue: GitHubCreateIssueTool,
|
|
70
|
+
private readonly gitHubCreateIssueComment: GitHubCreateIssueCommentTool,
|
|
71
|
+
// GitHub Pull Requests tools
|
|
72
|
+
private readonly gitHubListPullRequests: GitHubListPullRequestsTool,
|
|
73
|
+
private readonly gitHubGetPullRequest: GitHubGetPullRequestTool,
|
|
74
|
+
private readonly gitHubCreatePullRequest: GitHubCreatePullRequestTool,
|
|
75
|
+
private readonly gitHubMergePullRequest: GitHubMergePullRequestTool,
|
|
76
|
+
private readonly gitHubListPrReviews: GitHubListPrReviewsTool,
|
|
77
|
+
// GitHub Content / Git Ops tools
|
|
78
|
+
private readonly gitHubGetFileContent: GitHubGetFileContentTool,
|
|
79
|
+
private readonly gitHubCreateOrUpdateFile: GitHubCreateOrUpdateFileTool,
|
|
80
|
+
private readonly gitHubListDirectory: GitHubListDirectoryTool,
|
|
81
|
+
private readonly gitHubGetCommit: GitHubGetCommitTool,
|
|
82
|
+
// GitHub Actions tools
|
|
83
|
+
private readonly gitHubListWorkflowRuns: GitHubListWorkflowRunsTool,
|
|
84
|
+
private readonly gitHubTriggerWorkflow: GitHubTriggerWorkflowTool,
|
|
85
|
+
private readonly gitHubGetWorkflowRun: GitHubGetWorkflowRunTool,
|
|
86
|
+
// GitHub Search tools
|
|
87
|
+
private readonly gitHubSearchCode: GitHubSearchCodeTool,
|
|
88
|
+
private readonly gitHubSearchRepos: GitHubSearchReposTool,
|
|
89
|
+
private readonly gitHubSearchIssues: GitHubSearchIssuesTool,
|
|
90
|
+
// GitHub Users & Orgs tools
|
|
91
|
+
private readonly gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool,
|
|
92
|
+
private readonly gitHubListUserOrgs: GitHubListUserOrgsTool,
|
|
93
|
+
private readonly oAuth: OAuthWorkflow,
|
|
94
|
+
) {
|
|
95
|
+
super();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Transition({ to: 'waiting_for_user' })
|
|
99
|
+
async setup(_state: GitHubAgentState) {
|
|
100
|
+
await this.documentStore.save(LlmContextDocument, {
|
|
101
|
+
role: 'user',
|
|
102
|
+
text: this.render(join(__dirname, 'templates', 'systemMessage.md')),
|
|
103
|
+
});
|
|
104
|
+
await this.documentStore.save(LlmMessageDocument, {
|
|
105
|
+
role: 'assistant',
|
|
106
|
+
text: this.render(join(__dirname, 'templates', 'welcomeMessage.md')),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@Transition({ from: 'waiting_for_user', to: 'ready', wait: true, schema: z.string() })
|
|
111
|
+
async userMessage(_state: GitHubAgentState, input: TransitionInput<string>) {
|
|
112
|
+
await this.documentStore.save(LlmMessageDocument, { role: 'user', text: input.data });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Transition({ from: 'ready', to: 'prompt_executed' })
|
|
116
|
+
async llmTurn(_state: GitHubAgentState) {
|
|
117
|
+
const result = await this.llmGenerateText.call(
|
|
118
|
+
{},
|
|
119
|
+
{
|
|
120
|
+
config: {
|
|
121
|
+
provider: 'claude',
|
|
122
|
+
model: 'claude-sonnet-4-6',
|
|
123
|
+
system: `You are a helpful GitHub assistant with access to repository, issue, PR, code, actions,
|
|
124
|
+
and search tools. When a tool returns an unauthorized error, use authenticateGitHub
|
|
125
|
+
to let the user sign in, then retry. Be concise and format results using markdown.`,
|
|
126
|
+
tools: [
|
|
127
|
+
'github_list_repos',
|
|
128
|
+
'github_get_repo',
|
|
129
|
+
'github_create_repo',
|
|
130
|
+
'github_list_branches',
|
|
131
|
+
'github_list_issues',
|
|
132
|
+
'github_get_issue',
|
|
133
|
+
'github_create_issue',
|
|
134
|
+
'github_create_issue_comment',
|
|
135
|
+
'github_list_pull_requests',
|
|
136
|
+
'github_get_pull_request',
|
|
137
|
+
'github_create_pull_request',
|
|
138
|
+
'github_merge_pull_request',
|
|
139
|
+
'github_list_pr_reviews',
|
|
140
|
+
'github_get_file_content',
|
|
141
|
+
'github_create_or_update_file',
|
|
142
|
+
'github_list_directory',
|
|
143
|
+
'github_get_commit',
|
|
144
|
+
'github_list_workflow_runs',
|
|
145
|
+
'github_trigger_workflow',
|
|
146
|
+
'github_get_workflow_run',
|
|
147
|
+
'github_search_code',
|
|
148
|
+
'github_search_repos',
|
|
149
|
+
'github_search_issues',
|
|
150
|
+
'github_get_authenticated_user',
|
|
151
|
+
'github_list_user_orgs',
|
|
152
|
+
'authenticate_github',
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
this.assignState({ llmResult: result.data });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@Transition({ from: 'prompt_executed', to: 'awaiting_tools', priority: 10 })
|
|
161
|
+
@Guard('hasToolCalls')
|
|
162
|
+
async executeToolCalls(state: GitHubAgentState) {
|
|
163
|
+
const result = await this.llmDelegateToolCalls.call({
|
|
164
|
+
message: state.llmResult!.message,
|
|
165
|
+
callback: { transition: 'toolResultReceived' },
|
|
166
|
+
});
|
|
167
|
+
this.assignState({ delegateResult: result.data });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
hasToolCalls(state: GitHubAgentState): boolean {
|
|
171
|
+
return state.llmResult?.message.stopReason === 'tool_use';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@Transition({ from: 'awaiting_tools', to: 'awaiting_tools', wait: true, schema: z.record(z.string(), z.unknown()) })
|
|
175
|
+
async toolResultReceived(state: GitHubAgentState, input: TransitionInput<Record<string, unknown>>) {
|
|
176
|
+
const result = await this.llmUpdateToolResult.call({
|
|
177
|
+
delegateResult: state.delegateResult!,
|
|
178
|
+
completedTool: input,
|
|
179
|
+
});
|
|
180
|
+
this.assignState({ delegateResult: result.data });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@Transition({ from: 'awaiting_tools', to: 'ready' })
|
|
184
|
+
@Guard('allToolsComplete')
|
|
185
|
+
allToolsCompleteTransition(_state: GitHubAgentState) {}
|
|
186
|
+
|
|
187
|
+
allToolsComplete(state: GitHubAgentState): boolean {
|
|
188
|
+
return state.delegateResult?.allCompleted ?? false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Transition({ from: 'prompt_executed', to: 'waiting_for_user' })
|
|
192
|
+
respond(_state: GitHubAgentState) {}
|
|
193
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
You are a helpful GitHub assistant. You have access to the user's
|
|
2
|
+
GitHub account through the tools provided to you.
|
|
3
|
+
|
|
4
|
+
You can help with:
|
|
5
|
+
|
|
6
|
+
- **Repositories**: List repos, get repo details, create repos, list branches
|
|
7
|
+
- **Issues**: List issues, get issue details, create issues, comment on issues
|
|
8
|
+
- **Pull Requests**: List PRs, get PR details, create PRs, merge PRs, list reviews
|
|
9
|
+
- **Code & Content**: Read files, create/update files, browse directories, view commits
|
|
10
|
+
- **Actions (CI/CD)**: List workflow runs, trigger workflows, check run status
|
|
11
|
+
- **Search**: Search code, repositories, and issues across GitHub
|
|
12
|
+
- **Users & Orgs**: Get user profile, list organizations
|
|
13
|
+
|
|
14
|
+
When a tool returns `{ error: "unauthorized" }` or `{ error: "401" }`, call the
|
|
15
|
+
`authenticateGitHub` tool with the required OAuth scopes to let the user sign in.
|
|
16
|
+
After authentication completes, retry the original request.
|
|
17
|
+
|
|
18
|
+
Common scopes: repo, user, workflow, read:org
|
|
19
|
+
|
|
20
|
+
IMPORTANT: When using authenticateGitHub, it must be the ONLY tool call in your response.
|
|
21
|
+
|
|
22
|
+
Be concise and helpful. Format results clearly using markdown.
|
|
23
|
+
When showing repository or issue information, include links where available.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Hi! I'm your GitHub assistant. I can help you with:
|
|
2
|
+
|
|
3
|
+
- **Repositories** — list, create, view branches and details
|
|
4
|
+
- **Issues** — list, create, comment, view details
|
|
5
|
+
- **Pull Requests** — list, create, merge, review
|
|
6
|
+
- **Code & Content** — read files, browse directories, view commits
|
|
7
|
+
- **Actions (CI/CD)** — list runs, trigger workflows, check status
|
|
8
|
+
- **Search** — search code, repositories, and issues across GitHub
|
|
9
|
+
|
|
10
|
+
I'll handle GitHub sign-in automatically when needed. What would you like to do?
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseWorkflow, Guard, MarkdownDocument, Transition, Workflow } from '@loopstack/common';
|
|
4
|
+
import type { RunContext, TransitionInput } from '@loopstack/common';
|
|
5
|
+
import {
|
|
6
|
+
GitHubGetAuthenticatedUserTool,
|
|
7
|
+
GitHubGetRepoTool,
|
|
8
|
+
GitHubListBranchesTool,
|
|
9
|
+
GitHubListDirectoryTool,
|
|
10
|
+
GitHubListIssuesTool,
|
|
11
|
+
GitHubListPullRequestsTool,
|
|
12
|
+
GitHubListUserOrgsTool,
|
|
13
|
+
GitHubListWorkflowRunsTool,
|
|
14
|
+
GitHubSearchCodeTool,
|
|
15
|
+
} from '@loopstack/github-module';
|
|
16
|
+
import { OAuthWorkflow } from '@loopstack/oauth-module';
|
|
17
|
+
|
|
18
|
+
interface GitHubReposOverviewState {
|
|
19
|
+
owner: string;
|
|
20
|
+
repo: string;
|
|
21
|
+
requiresAuthentication?: boolean;
|
|
22
|
+
user?: { login: string; name: string | null; htmlUrl: string; publicRepos: number };
|
|
23
|
+
orgs?: Array<{ login: string; description: string | null }>;
|
|
24
|
+
repoDetails?: {
|
|
25
|
+
fullName: string;
|
|
26
|
+
description: string | null;
|
|
27
|
+
language: string | null;
|
|
28
|
+
stars: number;
|
|
29
|
+
forks: number;
|
|
30
|
+
openIssues: number;
|
|
31
|
+
defaultBranch: string;
|
|
32
|
+
htmlUrl: string;
|
|
33
|
+
};
|
|
34
|
+
branches?: Array<{ name: string; protected: boolean }>;
|
|
35
|
+
issues?: Array<{ number: number; title: string; state: string; user: string; htmlUrl: string }>;
|
|
36
|
+
pullRequests?: Array<{
|
|
37
|
+
number: number;
|
|
38
|
+
title: string;
|
|
39
|
+
state: string;
|
|
40
|
+
user: string;
|
|
41
|
+
draft: boolean;
|
|
42
|
+
htmlUrl: string;
|
|
43
|
+
}>;
|
|
44
|
+
directoryEntries?: Array<{ name: string; type: string; path: string }>;
|
|
45
|
+
workflowRuns?: Array<{
|
|
46
|
+
id: number;
|
|
47
|
+
name: string;
|
|
48
|
+
status: string;
|
|
49
|
+
conclusion: string | null;
|
|
50
|
+
htmlUrl: string;
|
|
51
|
+
}>;
|
|
52
|
+
searchResults?: Array<{ name: string; path: string; repository: string }>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const GitHubReposOverviewArgsSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
owner: z.string().default('octocat'),
|
|
58
|
+
repo: z.string().default('Hello-World'),
|
|
59
|
+
})
|
|
60
|
+
.strict();
|
|
61
|
+
|
|
62
|
+
type GitHubReposOverviewArgs = z.infer<typeof GitHubReposOverviewArgsSchema>;
|
|
63
|
+
|
|
64
|
+
@Workflow({
|
|
65
|
+
title: 'OAuth - GitHub Overview Example',
|
|
66
|
+
description:
|
|
67
|
+
'Comprehensive GitHub example that exercises every GitHub tool. Fetches user info, repository details, issues, pull requests, branches, directory contents, workflow runs, and search results. Launches the OAuth sub-workflow on unauthorized errors and retries.',
|
|
68
|
+
name: 'github_overview_example',
|
|
69
|
+
schema: GitHubReposOverviewArgsSchema,
|
|
70
|
+
})
|
|
71
|
+
export class GithubOverviewExampleWorkflow extends BaseWorkflow<GitHubReposOverviewArgs> {
|
|
72
|
+
constructor(
|
|
73
|
+
private readonly gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool,
|
|
74
|
+
private readonly gitHubListUserOrgs: GitHubListUserOrgsTool,
|
|
75
|
+
private readonly gitHubGetRepo: GitHubGetRepoTool,
|
|
76
|
+
private readonly gitHubListBranches: GitHubListBranchesTool,
|
|
77
|
+
private readonly gitHubListIssues: GitHubListIssuesTool,
|
|
78
|
+
private readonly gitHubListPullRequests: GitHubListPullRequestsTool,
|
|
79
|
+
private readonly gitHubListDirectory: GitHubListDirectoryTool,
|
|
80
|
+
private readonly gitHubListWorkflowRuns: GitHubListWorkflowRunsTool,
|
|
81
|
+
private readonly gitHubSearchCode: GitHubSearchCodeTool,
|
|
82
|
+
private readonly oAuthWorkflow: OAuthWorkflow,
|
|
83
|
+
) {
|
|
84
|
+
super();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --- Step 1: Fetch authenticated user ---
|
|
88
|
+
|
|
89
|
+
@Transition({ to: 'user_fetched' })
|
|
90
|
+
async fetchUser(state: GitHubReposOverviewState, ctx: RunContext<GitHubReposOverviewArgs>) {
|
|
91
|
+
try {
|
|
92
|
+
const result = await this.gitHubGetAuthenticatedUser.call();
|
|
93
|
+
this.assignState({
|
|
94
|
+
owner: ctx.args.owner,
|
|
95
|
+
repo: ctx.args.repo,
|
|
96
|
+
requiresAuthentication: false,
|
|
97
|
+
user: result.data.user,
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// GitHub tools throw with a human-readable message on auth failure
|
|
101
|
+
// ("No valid GitHub token found. Please authenticate first." / "GitHub token was rejected. Please re-authenticate.").
|
|
102
|
+
// Match on "authenticate" to route through the OAuth sub-workflow; rethrow everything else.
|
|
103
|
+
if (error instanceof Error && /authenticate/i.test(error.message)) {
|
|
104
|
+
this.assignState({ owner: ctx.args.owner, repo: ctx.args.repo, requiresAuthentication: true });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// If unauthorized -> launch OAuth
|
|
112
|
+
@Transition({ from: 'user_fetched', to: 'awaiting_auth', priority: 10 })
|
|
113
|
+
@Guard('needsAuth')
|
|
114
|
+
async authRequired(_state: GitHubReposOverviewState) {
|
|
115
|
+
await this.oAuthWorkflow.run(
|
|
116
|
+
{ provider: 'github', scopes: ['repo', 'read:org', 'workflow'] },
|
|
117
|
+
{ callback: { transition: 'authCompleted' }, show: 'inline', label: 'GitHub authentication required' },
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
needsAuth(state: GitHubReposOverviewState): boolean {
|
|
122
|
+
return !!state.requiresAuthentication;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Auth completed -> retry from start
|
|
126
|
+
@Transition({
|
|
127
|
+
from: 'awaiting_auth',
|
|
128
|
+
to: 'start',
|
|
129
|
+
wait: true,
|
|
130
|
+
})
|
|
131
|
+
authCompleted(_state: GitHubReposOverviewState, _input: TransitionInput) {}
|
|
132
|
+
|
|
133
|
+
// --- Step 2: Fetch user orgs ---
|
|
134
|
+
|
|
135
|
+
@Transition({ from: 'user_fetched', to: 'orgs_fetched' })
|
|
136
|
+
async fetchOrgs(_state: GitHubReposOverviewState) {
|
|
137
|
+
const result = await this.gitHubListUserOrgs.call({ perPage: 10 });
|
|
138
|
+
this.assignState({ orgs: result.data.orgs });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Step 3: Fetch repo details and branches ---
|
|
142
|
+
|
|
143
|
+
@Transition({ from: 'orgs_fetched', to: 'repo_fetched' })
|
|
144
|
+
async fetchRepoDetails(state: GitHubReposOverviewState) {
|
|
145
|
+
const repoResult = await this.gitHubGetRepo.call({
|
|
146
|
+
owner: state.owner,
|
|
147
|
+
repo: state.repo,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const branchesResult = await this.gitHubListBranches.call({
|
|
151
|
+
owner: state.owner,
|
|
152
|
+
repo: state.repo,
|
|
153
|
+
});
|
|
154
|
+
this.assignState({ repoDetails: repoResult.data.repo, branches: branchesResult.data.branches });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- Step 4: Fetch issues and PRs ---
|
|
158
|
+
|
|
159
|
+
@Transition({ from: 'repo_fetched', to: 'issues_prs_fetched' })
|
|
160
|
+
async fetchIssuesPrs(state: GitHubReposOverviewState) {
|
|
161
|
+
const issuesResult = await this.gitHubListIssues.call({
|
|
162
|
+
owner: state.owner,
|
|
163
|
+
repo: state.repo,
|
|
164
|
+
state: 'open',
|
|
165
|
+
perPage: 10,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const prsResult = await this.gitHubListPullRequests.call({
|
|
169
|
+
owner: state.owner,
|
|
170
|
+
repo: state.repo,
|
|
171
|
+
state: 'open',
|
|
172
|
+
perPage: 10,
|
|
173
|
+
});
|
|
174
|
+
this.assignState({ issues: issuesResult.data.issues, pullRequests: prsResult.data.pullRequests });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- Step 5: Fetch directory listing and workflow runs ---
|
|
178
|
+
|
|
179
|
+
@Transition({ from: 'issues_prs_fetched', to: 'content_actions_fetched' })
|
|
180
|
+
async fetchContentActions(state: GitHubReposOverviewState) {
|
|
181
|
+
const dirResult = await this.gitHubListDirectory.call({
|
|
182
|
+
owner: state.owner,
|
|
183
|
+
repo: state.repo,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const runsResult = await this.gitHubListWorkflowRuns.call({
|
|
187
|
+
owner: state.owner,
|
|
188
|
+
repo: state.repo,
|
|
189
|
+
perPage: 5,
|
|
190
|
+
});
|
|
191
|
+
this.assignState({ directoryEntries: dirResult.data.entries, workflowRuns: runsResult.data.runs });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// --- Step 6: Search code in the repo ---
|
|
195
|
+
|
|
196
|
+
@Transition({ from: 'content_actions_fetched', to: 'search_done' })
|
|
197
|
+
async fetchSearch(state: GitHubReposOverviewState) {
|
|
198
|
+
const result = await this.gitHubSearchCode.call({
|
|
199
|
+
query: `repo:${state.owner}/${state.repo}`,
|
|
200
|
+
perPage: 5,
|
|
201
|
+
});
|
|
202
|
+
this.assignState({ searchResults: result.data.results });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// --- Display all results ---
|
|
206
|
+
|
|
207
|
+
@Transition({ from: 'search_done', to: 'end' })
|
|
208
|
+
async displayResults(state: GitHubReposOverviewState) {
|
|
209
|
+
await this.documentStore.save(MarkdownDocument, {
|
|
210
|
+
markdown: this.render(join(__dirname, 'templates', 'repoOverview.md'), {
|
|
211
|
+
user: state.user,
|
|
212
|
+
orgs: state.orgs,
|
|
213
|
+
repo: state.repoDetails,
|
|
214
|
+
branches: state.branches,
|
|
215
|
+
issues: state.issues,
|
|
216
|
+
pullRequests: state.pullRequests,
|
|
217
|
+
directoryEntries: state.directoryEntries,
|
|
218
|
+
workflowRuns: state.workflowRuns,
|
|
219
|
+
searchResults: state.searchResults,
|
|
220
|
+
}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
## Authenticated User
|
|
2
|
+
|
|
3
|
+
**{{ user.login }}** {{#if user.name}}({{ user.name }}){{/if}} — {{ user.publicRepos }} public repos
|
|
4
|
+
|
|
5
|
+
{{#if orgs}}
|
|
6
|
+
|
|
7
|
+
### Organizations
|
|
8
|
+
|
|
9
|
+
{{#each orgs}}
|
|
10
|
+
|
|
11
|
+
- **{{ this.login }}** {{#if this.description}}— {{ this.description }}{{/if}}
|
|
12
|
+
{{/each}}
|
|
13
|
+
{{/if}}
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Repository: [{{ repo.fullName }}]({{ repo.htmlUrl }})
|
|
18
|
+
|
|
19
|
+
{{ repo.description }}
|
|
20
|
+
|
|
21
|
+
| Language | Stars | Forks | Open Issues | Default Branch |
|
|
22
|
+
| ------------------- | ---------------- | ---------------- | --------------------- | ------------------------ |
|
|
23
|
+
| {{ repo.language }} | {{ repo.stars }} | {{ repo.forks }} | {{ repo.openIssues }} | {{ repo.defaultBranch }} |
|
|
24
|
+
|
|
25
|
+
### Branches
|
|
26
|
+
|
|
27
|
+
{{#each branches}}
|
|
28
|
+
|
|
29
|
+
- `{{ this.name }}` {{#if this.protected}}(protected){{/if}}
|
|
30
|
+
{{/each}}
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### Open Issues
|
|
35
|
+
|
|
36
|
+
{{#each issues}}
|
|
37
|
+
|
|
38
|
+
- [#{{ this.number }}]({{ this.htmlUrl }}) {{ this.title }} — @{{ this.user }}
|
|
39
|
+
{{/each}}
|
|
40
|
+
{{#unless issues}}
|
|
41
|
+
No open issues.
|
|
42
|
+
{{/unless}}
|
|
43
|
+
|
|
44
|
+
### Open Pull Requests
|
|
45
|
+
|
|
46
|
+
{{#each pullRequests}}
|
|
47
|
+
|
|
48
|
+
- [#{{ this.number }}]({{ this.htmlUrl }}) {{ this.title }} — @{{ this.user }} {{#if this.draft}}(draft){{/if}}
|
|
49
|
+
{{/each}}
|
|
50
|
+
{{#unless pullRequests}}
|
|
51
|
+
No open pull requests.
|
|
52
|
+
{{/unless}}
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### Root Directory
|
|
57
|
+
|
|
58
|
+
{{#each directoryEntries}}
|
|
59
|
+
|
|
60
|
+
- {{ this.type }} `{{ this.name }}`
|
|
61
|
+
{{/each}}
|
|
62
|
+
|
|
63
|
+
### Recent Workflow Runs
|
|
64
|
+
|
|
65
|
+
{{#each workflowRuns}}
|
|
66
|
+
|
|
67
|
+
- [{{ this.name }}]({{ this.htmlUrl }}) — {{ this.status }} {{#if this.conclusion}}({{ this.conclusion }}){{/if}}
|
|
68
|
+
{{/each}}
|
|
69
|
+
{{#unless workflowRuns}}
|
|
70
|
+
No workflow runs found.
|
|
71
|
+
{{/unless}}
|
|
72
|
+
|
|
73
|
+
### Code Search Results
|
|
74
|
+
|
|
75
|
+
{{#each searchResults}}
|
|
76
|
+
|
|
77
|
+
- `{{ this.path }}` in {{ this.repository }}
|
|
78
|
+
{{/each}}
|
|
79
|
+
{{#unless searchResults}}
|
|
80
|
+
No code search results.
|
|
81
|
+
{{/unless}}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseWorkflow, Guard, MarkdownDocument, Transition, Workflow } from '@loopstack/common';
|
|
4
|
+
import type { RunContext, TransitionInput } from '@loopstack/common';
|
|
5
|
+
import { OAuthWorkflow } from '@loopstack/oauth-module';
|
|
6
|
+
import { GoogleCalendarFetchEventsTool } from '../../shared/google';
|
|
7
|
+
|
|
8
|
+
interface CalendarSummaryState {
|
|
9
|
+
events?: Array<{ id: string; summary: string; start?: string; end?: string }>;
|
|
10
|
+
requiresAuthentication?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CalendarSummaryArgsSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
calendarId: z.string().default('primary'),
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
|
|
19
|
+
type CalendarSummaryArgs = z.infer<typeof CalendarSummaryArgsSchema>;
|
|
20
|
+
|
|
21
|
+
@Workflow({
|
|
22
|
+
title: 'OAuth - Google Calendar Summary Example',
|
|
23
|
+
description:
|
|
24
|
+
'Fetches upcoming Google Calendar events and displays a summary. Launches the OAuth workflow as a sub-workflow on unauthorized errors and retries automatically.',
|
|
25
|
+
name: 'google_calendar_summary_example',
|
|
26
|
+
schema: CalendarSummaryArgsSchema,
|
|
27
|
+
})
|
|
28
|
+
export class GoogleCalendarSummaryExampleWorkflow extends BaseWorkflow<CalendarSummaryArgs> {
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly googleCalendarFetchEvents: GoogleCalendarFetchEventsTool,
|
|
31
|
+
private readonly oAuthWorkflow: OAuthWorkflow,
|
|
32
|
+
) {
|
|
33
|
+
super();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- Fetch events from Google Calendar ---
|
|
37
|
+
|
|
38
|
+
@Transition({ to: 'calendar_fetched' })
|
|
39
|
+
async fetchEvents(state: CalendarSummaryState, ctx: RunContext<CalendarSummaryArgs>) {
|
|
40
|
+
try {
|
|
41
|
+
const result = await this.googleCalendarFetchEvents.call({
|
|
42
|
+
calendarId: ctx.args.calendarId,
|
|
43
|
+
timeMin: this.now(),
|
|
44
|
+
timeMax: this.endOfWeek(),
|
|
45
|
+
});
|
|
46
|
+
this.assignState({ requiresAuthentication: false, events: result.data.events });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// The Google tools throw with their human-readable message on auth failure
|
|
49
|
+
// ("No valid Google token found. Please authenticate first." / "Google token was rejected. Please re-authenticate.").
|
|
50
|
+
// Both contain "authenticate" — match on that to route through the OAuth sub-workflow.
|
|
51
|
+
if (error instanceof Error && /authenticate/i.test(error.message)) {
|
|
52
|
+
this.assignState({ requiresAuthentication: true });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// If unauthorized -> launch OAuth as sub-workflow
|
|
60
|
+
@Transition({ from: 'calendar_fetched', to: 'awaiting_auth', priority: 10 })
|
|
61
|
+
@Guard('needsAuth')
|
|
62
|
+
async authRequired(_state: CalendarSummaryState) {
|
|
63
|
+
await this.oAuthWorkflow.run(
|
|
64
|
+
{ provider: 'google', scopes: ['https://www.googleapis.com/auth/calendar.readonly'] },
|
|
65
|
+
{ callback: { transition: 'authCompleted' }, show: 'inline', label: 'Google authentication required' },
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
needsAuth(state: CalendarSummaryState): boolean {
|
|
70
|
+
return !!state.requiresAuthentication;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Auth sub-workflow completed -> retry from start
|
|
74
|
+
@Transition({
|
|
75
|
+
from: 'awaiting_auth',
|
|
76
|
+
to: 'start',
|
|
77
|
+
wait: true,
|
|
78
|
+
})
|
|
79
|
+
authCompleted(_state: CalendarSummaryState, _input: TransitionInput) {}
|
|
80
|
+
|
|
81
|
+
// Success -> display summary
|
|
82
|
+
@Transition({ from: 'calendar_fetched', to: 'end' })
|
|
83
|
+
async displayResults(state: CalendarSummaryState) {
|
|
84
|
+
await this.documentStore.save(MarkdownDocument, {
|
|
85
|
+
markdown: this.render(join(__dirname, 'templates', 'calendarSummary.md'), { events: state.events }),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private now(): string {
|
|
90
|
+
return new Date().toISOString();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private endOfWeek(): string {
|
|
94
|
+
const now = new Date();
|
|
95
|
+
const dayOfWeek = now.getDay();
|
|
96
|
+
const daysUntilSunday = dayOfWeek === 0 ? 7 : 7 - dayOfWeek;
|
|
97
|
+
const endOfWeek = new Date(now);
|
|
98
|
+
endOfWeek.setDate(now.getDate() + daysUntilSunday);
|
|
99
|
+
endOfWeek.setHours(23, 59, 59, 999);
|
|
100
|
+
return endOfWeek.toISOString();
|
|
101
|
+
}
|
|
102
|
+
}
|