@loopstack/github-oauth-example 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.
Files changed (42) hide show
  1. package/README.md +114 -0
  2. package/dist/github-example.module.d.ts +3 -0
  3. package/dist/github-example.module.d.ts.map +1 -0
  4. package/dist/github-example.module.js +27 -0
  5. package/dist/github-example.module.js.map +1 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +20 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/tools/authenticate-github-task.tool.d.ts +17 -0
  11. package/dist/tools/authenticate-github-task.tool.d.ts.map +1 -0
  12. package/dist/tools/authenticate-github-task.tool.js +107 -0
  13. package/dist/tools/authenticate-github-task.tool.js.map +1 -0
  14. package/dist/tools/index.d.ts +2 -0
  15. package/dist/tools/index.d.ts.map +1 -0
  16. package/dist/tools/index.js +18 -0
  17. package/dist/tools/index.js.map +1 -0
  18. package/dist/workflows/github-agent.workflow.d.ts +48 -0
  19. package/dist/workflows/github-agent.workflow.d.ts.map +1 -0
  20. package/dist/workflows/github-agent.workflow.js +215 -0
  21. package/dist/workflows/github-agent.workflow.js.map +1 -0
  22. package/dist/workflows/github-agent.workflow.yaml +154 -0
  23. package/dist/workflows/github-repos-overview.workflow.d.ts +102 -0
  24. package/dist/workflows/github-repos-overview.workflow.d.ts.map +1 -0
  25. package/dist/workflows/github-repos-overview.workflow.js +300 -0
  26. package/dist/workflows/github-repos-overview.workflow.js.map +1 -0
  27. package/dist/workflows/github-repos-overview.workflow.yaml +249 -0
  28. package/dist/workflows/index.d.ts +3 -0
  29. package/dist/workflows/index.d.ts.map +1 -0
  30. package/dist/workflows/index.js +19 -0
  31. package/dist/workflows/index.js.map +1 -0
  32. package/package.json +80 -0
  33. package/src/github-example.module.ts +14 -0
  34. package/src/index.ts +3 -0
  35. package/src/tools/authenticate-github-task.tool.ts +125 -0
  36. package/src/tools/index.ts +1 -0
  37. package/src/workflows/__tests__/github-repos-overview-workflow.spec.ts +725 -0
  38. package/src/workflows/github-agent.workflow.ts +118 -0
  39. package/src/workflows/github-agent.workflow.yaml +154 -0
  40. package/src/workflows/github-repos-overview.workflow.ts +254 -0
  41. package/src/workflows/github-repos-overview.workflow.yaml +249 -0
  42. package/src/workflows/index.ts +2 -0
@@ -0,0 +1,249 @@
1
+ title: 'GitHub Repository Overview'
2
+
3
+ description: |
4
+ Comprehensive GitHub example that exercises every GitHub tool.
5
+ Fetches user info, repository details, issues, pull requests, branches,
6
+ directory contents, workflow runs, and search results for a given repository.
7
+ If not authenticated, launches the OAuth sub-workflow and retries.
8
+
9
+ ui:
10
+ form:
11
+ properties:
12
+ owner:
13
+ title: 'Repository Owner'
14
+ placeholder: 'octocat'
15
+ repo:
16
+ title: 'Repository Name'
17
+ placeholder: 'Hello-World'
18
+
19
+ transitions:
20
+ # Step 1: Fetch authenticated user and orgs
21
+ - id: fetch_user
22
+ from: start
23
+ to: user_fetched
24
+ call:
25
+ - tool: gitHubGetAuthenticatedUser
26
+ id: user_fetch
27
+ args: {}
28
+ assign:
29
+ requiresAuthentication: ${{ result.data.error == "unauthorized" }}
30
+ user: ${{ result.data.user }}
31
+
32
+ # If unauthorized -> launch OAuth
33
+ - id: auth_required
34
+ from: user_fetched
35
+ to: awaiting_auth
36
+ if: ${{ state.requiresAuthentication }}
37
+ call:
38
+ - tool: task
39
+ id: launchAuth
40
+ args:
41
+ workflow: oAuth
42
+ args:
43
+ provider: 'github'
44
+ scopes:
45
+ - 'repo'
46
+ - 'read:org'
47
+ - 'workflow'
48
+ callback:
49
+ transition: auth_completed
50
+
51
+ - tool: createDocument
52
+ args:
53
+ id: authStatus
54
+ document: linkDocument
55
+ update:
56
+ content:
57
+ icon: 'LockKeyhole'
58
+ label: 'GitHub authentication required'
59
+ caption: 'Complete sign-in to continue'
60
+ href: '/pipelines/{{ runtime.tools.auth_required.launchAuth.data.pipelineId }}'
61
+ embed: true
62
+ expanded: true
63
+
64
+ # Auth completed -> retry from start
65
+ - id: auth_completed
66
+ from: awaiting_auth
67
+ to: start
68
+ trigger: manual
69
+ call:
70
+ - tool: createDocument
71
+ args:
72
+ id: authStatus
73
+ document: linkDocument
74
+ update:
75
+ content:
76
+ icon: 'ShieldCheck'
77
+ type: 'success'
78
+ label: 'GitHub authentication completed'
79
+ caption: 'You are now authenticated with GitHub.'
80
+ href: '/pipelines/{{ runtime.transition.payload.pipelineId }}'
81
+
82
+ # Step 2: Fetch user orgs
83
+ - id: fetch_orgs
84
+ from: user_fetched
85
+ to: orgs_fetched
86
+ call:
87
+ - tool: gitHubListUserOrgs
88
+ id: orgs_fetch
89
+ args:
90
+ perPage: 10
91
+ assign:
92
+ orgs: ${{ result.data.orgs }}
93
+
94
+ # Step 3: Fetch repo details and branches
95
+ - id: fetch_repo_details
96
+ from: orgs_fetched
97
+ to: repo_fetched
98
+ call:
99
+ - tool: gitHubGetRepo
100
+ id: repo_fetch
101
+ args:
102
+ owner: ${{ args.owner }}
103
+ repo: ${{ args.repo }}
104
+ assign:
105
+ repo: ${{ result.data.repo }}
106
+
107
+ - tool: gitHubListBranches
108
+ id: branches_fetch
109
+ args:
110
+ owner: ${{ args.owner }}
111
+ repo: ${{ args.repo }}
112
+ assign:
113
+ branches: ${{ result.data.branches }}
114
+
115
+ # Step 4: Fetch issues and PRs
116
+ - id: fetch_issues_prs
117
+ from: repo_fetched
118
+ to: issues_prs_fetched
119
+ call:
120
+ - tool: gitHubListIssues
121
+ id: issues_fetch
122
+ args:
123
+ owner: ${{ args.owner }}
124
+ repo: ${{ args.repo }}
125
+ state: 'open'
126
+ perPage: 10
127
+ assign:
128
+ issues: ${{ result.data.issues }}
129
+
130
+ - tool: gitHubListPullRequests
131
+ id: prs_fetch
132
+ args:
133
+ owner: ${{ args.owner }}
134
+ repo: ${{ args.repo }}
135
+ state: 'open'
136
+ perPage: 10
137
+ assign:
138
+ pullRequests: ${{ result.data.pullRequests }}
139
+
140
+ # Step 5: Fetch directory listing and workflow runs
141
+ - id: fetch_content_actions
142
+ from: issues_prs_fetched
143
+ to: content_actions_fetched
144
+ call:
145
+ - tool: gitHubListDirectory
146
+ id: dir_fetch
147
+ args:
148
+ owner: ${{ args.owner }}
149
+ repo: ${{ args.repo }}
150
+ assign:
151
+ directoryEntries: ${{ result.data.entries }}
152
+
153
+ - tool: gitHubListWorkflowRuns
154
+ id: runs_fetch
155
+ args:
156
+ owner: ${{ args.owner }}
157
+ repo: ${{ args.repo }}
158
+ perPage: 5
159
+ assign:
160
+ workflowRuns: ${{ result.data.runs }}
161
+
162
+ # Step 6: Search code in the repo
163
+ - id: fetch_search
164
+ from: content_actions_fetched
165
+ to: search_done
166
+ call:
167
+ - tool: gitHubSearchCode
168
+ id: code_search
169
+ args:
170
+ query: ${{ searchQuery() }}
171
+ perPage: 5
172
+ assign:
173
+ searchResults: ${{ result.data.results }}
174
+
175
+ # Display all results
176
+ - id: display_results
177
+ from: search_done
178
+ to: end
179
+ call:
180
+ - tool: createDocument
181
+ args:
182
+ document: markdown
183
+ update:
184
+ content:
185
+ markdown: |
186
+ ## Authenticated User
187
+ **{{ state.user.login }}** {{#if state.user.name}}({{ state.user.name }}){{/if}} — {{ state.user.publicRepos }} public repos
188
+
189
+ {{#if state.orgs}}
190
+ ### Organizations
191
+ {{#each state.orgs}}
192
+ - **{{ this.login }}** {{#if this.description}}— {{ this.description }}{{/if}}
193
+ {{/each}}
194
+ {{/if}}
195
+
196
+ ---
197
+
198
+ ## Repository: [{{ state.repo.fullName }}]({{ state.repo.htmlUrl }})
199
+ {{ state.repo.description }}
200
+
201
+ | Language | Stars | Forks | Open Issues | Default Branch |
202
+ |---|---|---|---|---|
203
+ | {{ state.repo.language }} | {{ state.repo.stars }} | {{ state.repo.forks }} | {{ state.repo.openIssues }} | {{ state.repo.defaultBranch }} |
204
+
205
+ ### Branches
206
+ {{#each state.branches}}
207
+ - `{{ this.name }}` {{#if this.protected}}(protected){{/if}}
208
+ {{/each}}
209
+
210
+ ---
211
+
212
+ ### Open Issues
213
+ {{#each state.issues}}
214
+ - [#{{ this.number }}]({{ this.htmlUrl }}) {{ this.title }} — @{{ this.user }}
215
+ {{/each}}
216
+ {{#unless state.issues}}
217
+ No open issues.
218
+ {{/unless}}
219
+
220
+ ### Open Pull Requests
221
+ {{#each state.pullRequests}}
222
+ - [#{{ this.number }}]({{ this.htmlUrl }}) {{ this.title }} — @{{ this.user }} {{#if this.draft}}(draft){{/if}}
223
+ {{/each}}
224
+ {{#unless state.pullRequests}}
225
+ No open pull requests.
226
+ {{/unless}}
227
+
228
+ ---
229
+
230
+ ### Root Directory
231
+ {{#each state.directoryEntries}}
232
+ - {{ this.type }} `{{ this.name }}`
233
+ {{/each}}
234
+
235
+ ### Recent Workflow Runs
236
+ {{#each state.workflowRuns}}
237
+ - [{{ this.name }}]({{ this.htmlUrl }}) — {{ this.status }} {{#if this.conclusion}}({{ this.conclusion }}){{/if}}
238
+ {{/each}}
239
+ {{#unless state.workflowRuns}}
240
+ No workflow runs found.
241
+ {{/unless}}
242
+
243
+ ### Code Search Results
244
+ {{#each state.searchResults}}
245
+ - `{{ this.path }}` in {{ this.repository }}
246
+ {{/each}}
247
+ {{#unless state.searchResults}}
248
+ No code search results.
249
+ {{/unless}}
@@ -0,0 +1,3 @@
1
+ export * from './github-agent.workflow';
2
+ export * from './github-repos-overview.workflow';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workflows/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./github-agent.workflow"), exports);
18
+ __exportStar(require("./github-repos-overview.workflow"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/workflows/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,mEAAiD"}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@loopstack/github-oauth-example",
3
+ "displayName": "GitHub OAuth Example",
4
+ "description": "An example module demonstrating how to list GitHub repositories and issues with OAuth authentication using the Loopstack automation framework. Shows the sub-workflow pattern for handling OAuth flows.",
5
+ "keywords": [
6
+ "example",
7
+ "github",
8
+ "loopstack",
9
+ "oauth"
10
+ ],
11
+ "version": "0.1.1",
12
+ "license": "Apache-2.0",
13
+ "author": {
14
+ "name": "Jakob Klippel",
15
+ "url": "https://github.com/loopstack-ai"
16
+ },
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": "./dist/index.js",
21
+ "./src/*": "./src/*"
22
+ },
23
+ "scripts": {
24
+ "build": "nest build",
25
+ "compile": "tsc --noEmit",
26
+ "format": "prettier --write .",
27
+ "lint": "eslint .",
28
+ "test": "jest --passWithNoTests",
29
+ "watch": "nest build --watch"
30
+ },
31
+ "dependencies": {
32
+ "@loopstack/claude-module": "^0.21.1",
33
+ "@loopstack/common": "^0.24.0",
34
+ "@loopstack/core": "^0.24.0",
35
+ "@loopstack/create-chat-message-tool": "^0.20.7",
36
+ "@loopstack/github-module": "^0.1.0",
37
+ "@loopstack/oauth-module": "^0.1.6",
38
+ "@nestjs/common": "^11.1.14",
39
+ "zod": "^4.3.6"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "src"
44
+ ],
45
+ "jest": {
46
+ "testEnvironment": "node",
47
+ "rootDir": "src",
48
+ "testRegex": ".*\\.spec\\.ts$",
49
+ "transform": {
50
+ "^.+\\.ts$": "ts-jest"
51
+ },
52
+ "testTimeout": 10000,
53
+ "forceExit": true,
54
+ "maxWorkers": 1
55
+ },
56
+ "loopstack": {
57
+ "installModes": [
58
+ "add",
59
+ "install"
60
+ ],
61
+ "modules": [
62
+ {
63
+ "path": "src/github-example.module.ts",
64
+ "className": "GitHubExampleModule"
65
+ }
66
+ ],
67
+ "workflows": [
68
+ {
69
+ "path": "src/workflows/github-repos-overview.workflow.ts",
70
+ "className": "GitHubReposOverviewWorkflow",
71
+ "propertyName": "gitHubReposOverview"
72
+ },
73
+ {
74
+ "path": "src/workflows/github-agent.workflow.ts",
75
+ "className": "GitHubAgentWorkflow",
76
+ "propertyName": "gitHubAgent"
77
+ }
78
+ ]
79
+ }
80
+ }
@@ -0,0 +1,14 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { ClaudeModule } from '@loopstack/claude-module';
3
+ import { LoopCoreModule } from '@loopstack/core';
4
+ import { CreateChatMessageToolModule } from '@loopstack/create-chat-message-tool';
5
+ import { GitHubModule } from '@loopstack/github-module';
6
+ import { AuthenticateGitHubTask } from './tools';
7
+ import { GitHubAgentWorkflow, GitHubReposOverviewWorkflow } from './workflows';
8
+
9
+ @Module({
10
+ imports: [LoopCoreModule, CreateChatMessageToolModule, ClaudeModule, GitHubModule],
11
+ providers: [AuthenticateGitHubTask, GitHubReposOverviewWorkflow, GitHubAgentWorkflow],
12
+ exports: [AuthenticateGitHubTask, GitHubReposOverviewWorkflow, GitHubAgentWorkflow],
13
+ })
14
+ export class GitHubExampleModule {}
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './github-example.module';
2
+ export * from './tools';
3
+ export * from './workflows';
@@ -0,0 +1,125 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { z } from 'zod';
3
+ import {
4
+ InjectDocument,
5
+ InjectTool,
6
+ Input,
7
+ RunContext,
8
+ Tool,
9
+ ToolInterface,
10
+ ToolResult,
11
+ ToolSideEffects,
12
+ WorkflowInterface,
13
+ WorkflowMetadataInterface,
14
+ } from '@loopstack/common';
15
+ import { CreateDocument, LinkDocument, Task } from '@loopstack/core';
16
+
17
+ const AuthenticateGitHubTaskInputSchema = z
18
+ .object({
19
+ scopes: z.array(z.string()).describe('The OAuth scopes to request (e.g. repo, user, workflow, read:org)'),
20
+ })
21
+ .strict();
22
+
23
+ type AuthenticateGitHubTaskInput = z.infer<typeof AuthenticateGitHubTaskInputSchema>;
24
+
25
+ @Injectable()
26
+ @Tool({
27
+ config: {
28
+ description:
29
+ 'Launches GitHub OAuth authentication. Shows the user a sign-in prompt to authorize access to GitHub. ' +
30
+ 'Use this when a GitHub tool returns an "unauthorized" error. ' +
31
+ 'Pass the required OAuth scopes for the GitHub APIs you need access to. ' +
32
+ 'IMPORTANT: When using this tool, it must be the ONLY tool call in your response. Do not combine it with other tool calls.',
33
+ },
34
+ })
35
+ export class AuthenticateGitHubTask implements ToolInterface<AuthenticateGitHubTaskInput> {
36
+ private readonly logger = new Logger(AuthenticateGitHubTask.name);
37
+
38
+ @InjectTool() private task: Task;
39
+ @InjectTool() private createDocument: CreateDocument;
40
+ @InjectDocument() private linkDocument: LinkDocument;
41
+
42
+ @Input({ schema: AuthenticateGitHubTaskInputSchema })
43
+ args: AuthenticateGitHubTaskInput;
44
+
45
+ async execute(
46
+ args: AuthenticateGitHubTaskInput,
47
+ ctx: RunContext,
48
+ parent: WorkflowInterface | ToolInterface,
49
+ metadata: WorkflowMetadataInterface,
50
+ ): Promise<ToolResult> {
51
+ const taskResult = await this.task.execute(
52
+ { workflow: 'oAuth', args: { provider: 'github', scopes: args.scopes } },
53
+ ctx,
54
+ parent,
55
+ );
56
+
57
+ const effects: ToolSideEffects[] = [];
58
+
59
+ const linkResult = await this.createDocument.execute(
60
+ {
61
+ document: 'linkDocument',
62
+ id: 'github_auth_link',
63
+ validate: 'skip' as const,
64
+ update: {
65
+ content: {
66
+ icon: 'LockKeyhole',
67
+ label: 'GitHub authentication required',
68
+ caption: 'Complete sign-in to access GitHub',
69
+ href: `/pipelines/${String((taskResult.data as Record<string, unknown>).pipelineId)}`,
70
+ embed: true,
71
+ expanded: true,
72
+ },
73
+ },
74
+ },
75
+ ctx,
76
+ this,
77
+ metadata,
78
+ );
79
+ if (linkResult.effects) {
80
+ effects.push(...linkResult.effects);
81
+ }
82
+
83
+ return {
84
+ data: taskResult.data as Record<string, unknown>,
85
+ effects,
86
+ };
87
+ }
88
+
89
+ async complete(
90
+ result: Record<string, unknown>,
91
+ ctx: RunContext,
92
+ parent: WorkflowInterface | ToolInterface,
93
+ metadata: WorkflowMetadataInterface,
94
+ ): Promise<ToolResult> {
95
+ const data = result as { pipelineId?: string };
96
+
97
+ const effects: ToolSideEffects[] = [];
98
+
99
+ const linkResult = await this.createDocument.execute(
100
+ {
101
+ document: 'linkDocument',
102
+ id: 'github_auth_link',
103
+ validate: 'skip' as const,
104
+ update: {
105
+ content: {
106
+ icon: 'ShieldCheck',
107
+ label: 'GitHub authentication completed',
108
+ href: `/pipelines/${data.pipelineId}`,
109
+ },
110
+ },
111
+ },
112
+ ctx,
113
+ this,
114
+ metadata,
115
+ );
116
+ if (linkResult.effects) {
117
+ effects.push(...linkResult.effects);
118
+ }
119
+
120
+ return {
121
+ data: 'GitHub authentication completed successfully. You can now use GitHub tools.',
122
+ effects,
123
+ };
124
+ }
125
+ }
@@ -0,0 +1 @@
1
+ export * from './authenticate-github-task.tool';