@loopstack/github-integration 0.2.0

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.
@@ -0,0 +1,3 @@
1
+ export declare class GitHubIntegrationModule {
2
+ }
3
+ //# sourceMappingURL=github-integration.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-integration.module.d.ts","sourceRoot":"","sources":["../src/github-integration.module.ts"],"names":[],"mappings":"AASA,qBAKa,uBAAuB;CAAG"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.GitHubIntegrationModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const core_1 = require("@loopstack/core");
12
+ const git_module_1 = require("@loopstack/git-module");
13
+ const github_module_1 = require("@loopstack/github-module");
14
+ const hitl_1 = require("@loopstack/hitl");
15
+ const oauth_module_1 = require("@loopstack/oauth-module");
16
+ const remote_client_1 = require("@loopstack/remote-client");
17
+ const connect_github_workflow_1 = require("./workflows/connect-github/connect-github.workflow");
18
+ let GitHubIntegrationModule = class GitHubIntegrationModule {
19
+ };
20
+ exports.GitHubIntegrationModule = GitHubIntegrationModule;
21
+ exports.GitHubIntegrationModule = GitHubIntegrationModule = __decorate([
22
+ (0, common_1.Module)({
23
+ imports: [core_1.LoopCoreModule, git_module_1.GitModule, github_module_1.GitHubModule, hitl_1.HitlModule, oauth_module_1.OAuthModule, remote_client_1.RemoteClientModule],
24
+ providers: [connect_github_workflow_1.ConnectGitHubWorkflow],
25
+ exports: [connect_github_workflow_1.ConnectGitHubWorkflow],
26
+ })
27
+ ], GitHubIntegrationModule);
28
+ //# sourceMappingURL=github-integration.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-integration.module.js","sourceRoot":"","sources":["../src/github-integration.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,0CAAiD;AACjD,sDAAkD;AAClD,4DAAwD;AACxD,0CAA6C;AAC7C,0DAAsD;AACtD,4DAA8D;AAC9D,gGAA2F;AAOpF,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;CAAG,CAAA;AAA1B,0DAAuB;kCAAvB,uBAAuB;IALnC,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,qBAAc,EAAE,sBAAS,EAAE,4BAAY,EAAE,iBAAU,EAAE,0BAAW,EAAE,kCAAkB,CAAC;QAC/F,SAAS,EAAE,CAAC,+CAAqB,CAAC;QAClC,OAAO,EAAE,CAAC,+CAAqB,CAAC;KACjC,CAAC;GACW,uBAAuB,CAAG"}
@@ -0,0 +1,3 @@
1
+ export * from './workflows';
2
+ export * from './github-integration.module';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -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("./workflows"), exports);
18
+ __exportStar(require("./github-integration.module"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,8DAA4C"}
@@ -0,0 +1,5 @@
1
+ title: 'Connect to GitHub'
2
+
3
+ description: |
4
+ Connects your workspace to a GitHub repository.
5
+ Authenticates with GitHub, lets you create or link a repo, and configures git push access.
@@ -0,0 +1,88 @@
1
+ import { BaseWorkflow } from '@loopstack/common';
2
+ import { AskUserWorkflow } from '@loopstack/hitl';
3
+ import { OAuthWorkflow } from '@loopstack/oauth-module';
4
+ interface GitHubUser {
5
+ login: string;
6
+ name: string | null;
7
+ email: string | null;
8
+ }
9
+ interface GitHubRepo {
10
+ fullName: string;
11
+ name: string;
12
+ htmlUrl: string;
13
+ private: boolean;
14
+ defaultBranch: string;
15
+ }
16
+ export declare class ConnectGitHubWorkflow extends BaseWorkflow {
17
+ private gitHubGetAuthenticatedUser;
18
+ private gitHubCreateRepo;
19
+ private gitHubListRepos;
20
+ private gitRemoteConfigure;
21
+ private gitConfigUser;
22
+ private gitStatus;
23
+ private gitPush;
24
+ private gitFetch;
25
+ private bash;
26
+ oAuth: OAuthWorkflow;
27
+ askUser: AskUserWorkflow;
28
+ private tokenStore;
29
+ private clientMessageService;
30
+ private getGitHubToken;
31
+ requiresAuth?: boolean;
32
+ user?: GitHubUser;
33
+ repo?: GitHubRepo;
34
+ isNewRepo?: boolean;
35
+ divergenceState?: 'none' | 'local_ahead' | 'remote_ahead' | 'diverged';
36
+ start(): Promise<void>;
37
+ launchOAuth(): Promise<void>;
38
+ private needsAuth;
39
+ authCompleted(payload: {
40
+ workflowId: string;
41
+ }): Promise<void>;
42
+ askCreateOrLink(): Promise<void>;
43
+ choiceReceived(payload: {
44
+ data: {
45
+ answer: string;
46
+ };
47
+ }): Promise<void>;
48
+ createRepo(payload: {
49
+ data: {
50
+ answer: string;
51
+ };
52
+ }): Promise<void>;
53
+ repoSelected(payload: {
54
+ data: {
55
+ answer: string;
56
+ };
57
+ }): Promise<void>;
58
+ hasUncommittedChanges?: boolean;
59
+ checkForUncommittedChanges(): Promise<void>;
60
+ skipCommitCheck(): Promise<void>;
61
+ private isCleanWorkspace;
62
+ askCommitChanges(): Promise<void>;
63
+ uncommittedChangesHandled(payload: {
64
+ data: {
65
+ answer: string;
66
+ };
67
+ }): Promise<void>;
68
+ setupRemote(): Promise<void>;
69
+ pushDirectly(): Promise<void>;
70
+ private canPushDirectly;
71
+ askSyncStrategy(): Promise<void>;
72
+ syncStrategyChosen(payload: {
73
+ data: {
74
+ answer: string;
75
+ };
76
+ }): Promise<void>;
77
+ showSuccess(): Promise<{
78
+ cancelled: boolean;
79
+ repo?: undefined;
80
+ url?: undefined;
81
+ } | {
82
+ repo: string;
83
+ url: string;
84
+ cancelled?: undefined;
85
+ }>;
86
+ }
87
+ export {};
88
+ //# sourceMappingURL=connect-github.workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-github.workflow.d.ts","sourceRoot":"","sources":["../../../src/workflows/connect-github/connect-github.workflow.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EAYb,MAAM,mBAAmB,CAAC;AAU3B,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAmB,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGzE,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,qBAIa,qBAAsB,SAAQ,YAAY;IACvC,OAAO,CAAC,0BAA0B,CAAiC;IACnE,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,IAAI,CAAW;IAEnB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,eAAe,CAAC;IAEjC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,oBAAoB,CAAuB;YAE/C,cAAc;IAI5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,UAAU,CAAC;IAKjE,KAAK;IAUL,WAAW;IAkBjB,OAAO,CAAC,SAAS;IAKX,aAAa,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE;IAqB7C,eAAe;IAkBf,cAAc,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;IA0CpD,UAAU,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;IAqBhD,YAAY,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;IAsBxD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAG1B,0BAA0B;IAwB1B,eAAe;IAErB,OAAO,CAAC,gBAAgB;IAMlB,gBAAgB;IAmBhB,yBAAyB,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;IAmB/D,WAAW;IA8CX,YAAY;IAYlB,OAAO,CAAC,eAAe;IAOjB,eAAe;IA4Bf,kBAAkB,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;IA6BxD,WAAW;;;;;;;;;CAqBlB"}
@@ -0,0 +1,414 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ConnectGitHubWorkflow = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const zod_1 = require("zod");
15
+ const common_2 = require("@loopstack/common");
16
+ const core_1 = require("@loopstack/core");
17
+ const git_module_1 = require("@loopstack/git-module");
18
+ const github_module_1 = require("@loopstack/github-module");
19
+ const hitl_1 = require("@loopstack/hitl");
20
+ const oauth_module_1 = require("@loopstack/oauth-module");
21
+ const remote_client_1 = require("@loopstack/remote-client");
22
+ let ConnectGitHubWorkflow = class ConnectGitHubWorkflow extends common_2.BaseWorkflow {
23
+ gitHubGetAuthenticatedUser;
24
+ gitHubCreateRepo;
25
+ gitHubListRepos;
26
+ gitRemoteConfigure;
27
+ gitConfigUser;
28
+ gitStatus;
29
+ gitPush;
30
+ gitFetch;
31
+ bash;
32
+ oAuth;
33
+ askUser;
34
+ tokenStore;
35
+ clientMessageService;
36
+ async getGitHubToken() {
37
+ return (await this.tokenStore.getValidAccessToken(this.ctx.context.userId, 'github')) ?? undefined;
38
+ }
39
+ requiresAuth;
40
+ user;
41
+ repo;
42
+ isNewRepo;
43
+ divergenceState;
44
+ async start() {
45
+ const result = await this.gitHubGetAuthenticatedUser.call({});
46
+ this.requiresAuth = result.data.error === 'unauthorized';
47
+ this.user = result.data.user;
48
+ }
49
+ async launchOAuth() {
50
+ const result = await this.oAuth.run({ provider: 'github', scopes: ['repo', 'user'] }, { alias: 'oAuth', callback: { transition: 'authCompleted' } });
51
+ await this.repository.save(common_2.LinkDocument, {
52
+ label: 'Sign in with GitHub',
53
+ workflowId: result.workflowId,
54
+ embed: true,
55
+ expanded: true,
56
+ }, { id: `link_${result.workflowId}` });
57
+ }
58
+ needsAuth() {
59
+ return !!this.requiresAuth;
60
+ }
61
+ async authCompleted(payload) {
62
+ await this.repository.save(common_2.LinkDocument, {
63
+ status: 'success',
64
+ label: 'GitHub authentication completed',
65
+ workflowId: payload.workflowId,
66
+ embed: true,
67
+ expanded: false,
68
+ }, { id: `link_${payload.workflowId}` });
69
+ const result = await this.gitHubGetAuthenticatedUser.call({});
70
+ this.user = result.data.user;
71
+ this.requiresAuth = false;
72
+ }
73
+ async askCreateOrLink() {
74
+ const result = await this.askUser.run({
75
+ question: 'Would you like to create a new GitHub repository or connect an existing one?',
76
+ mode: 'options',
77
+ options: ['Create new repository', 'Connect existing repository'],
78
+ }, { alias: 'chooseAction', callback: { transition: 'choiceReceived' } });
79
+ await this.repository.save(common_2.LinkDocument, { label: 'Create or connect repository', workflowId: result.workflowId, embed: true, expanded: true }, { id: `link_choice` });
80
+ }
81
+ async choiceReceived(payload) {
82
+ await this.repository.save(common_2.LinkDocument, { label: 'Create or connect repository', status: 'success', embed: true, expanded: false }, { id: `link_choice` });
83
+ if (payload.data.answer === 'Connect existing repository') {
84
+ const listResult = await this.gitHubListRepos.call({ visibility: 'all', sort: 'updated', perPage: 30 });
85
+ const repos = listResult.data.repos ?? [];
86
+ const repoNames = repos.map((r) => r.fullName);
87
+ const askResult = await this.askUser.run({ question: 'Select a repository to connect:', mode: 'options', options: repoNames }, { alias: 'chooseRepo', callback: { transition: 'repoSelected' } });
88
+ await this.repository.save(common_2.LinkDocument, { label: 'Select repository', workflowId: askResult.workflowId, embed: true, expanded: true }, { id: `link_repo_select` });
89
+ }
90
+ else {
91
+ const askResult = await this.askUser.run({ question: 'Enter a name for your new repository:' }, { alias: 'repoName', callback: { transition: 'createRepo' } });
92
+ await this.repository.save(common_2.LinkDocument, { label: 'Repository name', workflowId: askResult.workflowId, embed: true, expanded: true }, { id: `link_repo_name` });
93
+ }
94
+ }
95
+ async createRepo(payload) {
96
+ await this.repository.save(common_2.LinkDocument, { label: 'Repository name', status: 'success', embed: true, expanded: false }, { id: `link_repo_name` });
97
+ const repoName = payload.data.answer.trim();
98
+ const createResult = await this.gitHubCreateRepo.call({
99
+ name: repoName,
100
+ private: true,
101
+ autoInit: false,
102
+ });
103
+ this.repo = createResult.data.repo;
104
+ this.isNewRepo = true;
105
+ }
106
+ async repoSelected(payload) {
107
+ await this.repository.save(common_2.LinkDocument, { label: 'Select repository', status: 'success', embed: true, expanded: false }, { id: `link_repo_select` });
108
+ const fullName = payload.data.answer;
109
+ const [, name] = fullName.split('/');
110
+ this.repo = {
111
+ fullName,
112
+ name,
113
+ htmlUrl: `https://github.com/${fullName}`,
114
+ private: false,
115
+ defaultBranch: 'main',
116
+ };
117
+ this.isNewRepo = false;
118
+ }
119
+ hasUncommittedChanges;
120
+ async checkForUncommittedChanges() {
121
+ const user = this.user;
122
+ if (user) {
123
+ await this.gitConfigUser.call({
124
+ name: user.name || user.login,
125
+ email: user.email || `${user.login}@users.noreply.github.com`,
126
+ });
127
+ }
128
+ const statusResult = await this.gitStatus.call();
129
+ const status = statusResult.data;
130
+ this.hasUncommittedChanges =
131
+ status.staged.length > 0 ||
132
+ status.modified.length > 0 ||
133
+ status.untracked.length > 0 ||
134
+ status.deleted.length > 0;
135
+ }
136
+ async skipCommitCheck() { }
137
+ isCleanWorkspace() {
138
+ return !this.hasUncommittedChanges;
139
+ }
140
+ async askCommitChanges() {
141
+ const confirmResult = await this.askUser.run({
142
+ question: 'There are uncommitted changes in your workspace. They need to be committed before connecting to a remote repository. Would you like to commit them now?',
143
+ mode: 'options',
144
+ options: ['Commit changes and continue', 'Cancel'],
145
+ }, { alias: 'commitChanges', callback: { transition: 'uncommittedChangesHandled' } });
146
+ await this.repository.save(common_2.LinkDocument, { label: 'Uncommitted changes', workflowId: confirmResult.workflowId, embed: true, expanded: true }, { id: `link_uncommitted` });
147
+ }
148
+ async uncommittedChangesHandled(payload) {
149
+ await this.repository.save(common_2.LinkDocument, { label: 'Uncommitted changes', status: 'success', embed: true, expanded: false }, { id: `link_uncommitted` });
150
+ if (payload.data.answer === 'Cancel') {
151
+ this.repo = undefined;
152
+ return;
153
+ }
154
+ await this.bash.call({ command: 'git add -A' });
155
+ await this.bash.call({ command: 'git commit -m "Auto-commit before connecting to GitHub"' });
156
+ }
157
+ async setupRemote() {
158
+ if (!this.repo) {
159
+ this.divergenceState = 'none';
160
+ return;
161
+ }
162
+ const repo = this.repo;
163
+ const remoteUrl = `https://github.com/${repo.fullName}.git`;
164
+ await this.gitRemoteConfigure.call({ url: remoteUrl });
165
+ const token = await this.getGitHubToken();
166
+ await this.gitFetch.call({ remote: 'origin', token });
167
+ if (!this.isNewRepo) {
168
+ const checkResult = (await this.bash.call({
169
+ command: [
170
+ 'REMOTE_EXISTS=$(git rev-parse --verify origin/main 2>/dev/null && echo yes || echo no)',
171
+ 'if [ "$REMOTE_EXISTS" = "no" ]; then echo "no_remote"; exit 0; fi',
172
+ 'LOCAL=$(git rev-parse main)',
173
+ 'REMOTE=$(git rev-parse origin/main)',
174
+ 'if [ "$LOCAL" = "$REMOTE" ]; then echo "same"; exit 0; fi',
175
+ 'git merge-base --is-ancestor main origin/main && echo "remote_ahead" && exit 0',
176
+ 'git merge-base --is-ancestor origin/main main && echo "local_ahead" && exit 0',
177
+ 'echo "diverged"',
178
+ ].join(' && '),
179
+ }));
180
+ const state = checkResult.data.stdout.trim().split('\n').pop().trim();
181
+ if (state === 'same' || state === 'no_remote') {
182
+ this.divergenceState = 'none';
183
+ }
184
+ else {
185
+ this.divergenceState = state;
186
+ }
187
+ }
188
+ else {
189
+ this.divergenceState = 'none';
190
+ }
191
+ }
192
+ async pushDirectly() {
193
+ if (this.divergenceState === 'none') {
194
+ return;
195
+ }
196
+ const statusResult = await this.gitStatus.call();
197
+ const branch = statusResult.data.branch ?? 'main';
198
+ const token = await this.getGitHubToken();
199
+ await this.gitPush.call({ remote: 'origin', branch, token });
200
+ }
201
+ canPushDirectly() {
202
+ return this.divergenceState === 'none' || this.divergenceState === 'local_ahead';
203
+ }
204
+ async askSyncStrategy() {
205
+ const isRemoteAhead = this.divergenceState === 'remote_ahead';
206
+ const question = isRemoteAhead
207
+ ? 'The remote repository has newer commits than your workspace. How would you like to proceed?'
208
+ : 'The remote repository has a different commit history than your workspace. How would you like to proceed?';
209
+ const options = isRemoteAhead
210
+ ? ['Pull remote changes into workspace', 'Push workspace code (overwrite remote)', 'Cancel (disconnect remote)']
211
+ : [
212
+ 'Use remote code (replace local files with remote)',
213
+ 'Merge remote changes into workspace',
214
+ 'Push workspace code (overwrite remote)',
215
+ 'Cancel (disconnect remote)',
216
+ ];
217
+ const result = await this.askUser.run({ question, mode: 'options', options }, { alias: 'syncStrategy', callback: { transition: 'syncStrategyChosen' } });
218
+ await this.repository.save(common_2.LinkDocument, { label: 'Resolve differences', workflowId: result.workflowId, embed: true, expanded: true }, { id: `link_sync` });
219
+ }
220
+ async syncStrategyChosen(payload) {
221
+ await this.repository.save(common_2.LinkDocument, { label: 'Resolve differences', status: 'success', embed: true, expanded: false }, { id: `link_sync` });
222
+ const answer = payload.data.answer;
223
+ const statusResult = await this.gitStatus.call();
224
+ const branch = statusResult.data.branch ?? 'main';
225
+ const token = await this.getGitHubToken();
226
+ if (answer.startsWith('Use remote code')) {
227
+ await this.bash.call({ command: `git reset --hard origin/${branch}` });
228
+ }
229
+ else if (answer.startsWith('Pull remote changes') || answer.startsWith('Merge remote changes')) {
230
+ await this.bash.call({ command: `git merge origin/${branch} --allow-unrelated-histories --no-edit` });
231
+ await this.gitPush.call({ remote: 'origin', branch, token });
232
+ }
233
+ else if (answer.startsWith('Push workspace code')) {
234
+ await this.gitPush.call({ remote: 'origin', branch, force: true, token });
235
+ }
236
+ else {
237
+ await this.bash.call({ command: 'git remote remove origin' });
238
+ this.repo = undefined;
239
+ }
240
+ }
241
+ async showSuccess() {
242
+ if (!this.repo) {
243
+ await this.repository.save(common_2.MarkdownDocument, {
244
+ markdown: '### Cancelled\n\nThe remote connection was removed. No changes were made.',
245
+ });
246
+ return { cancelled: true };
247
+ }
248
+ const repo = this.repo;
249
+ await this.repository.save(common_2.MarkdownDocument, {
250
+ markdown: `### Repository Connected\n\nYour workspace is now connected to [${repo.fullName}](${repo.htmlUrl}).\n\nAll future commits can be pushed to this repository using the git tools in your workflows.`,
251
+ });
252
+ this.clientMessageService.dispatchWorkspaceEvent('git.updated', this.ctx.context.workspaceId, this.ctx.context.userId);
253
+ return { repo: repo.fullName, url: repo.htmlUrl };
254
+ }
255
+ };
256
+ exports.ConnectGitHubWorkflow = ConnectGitHubWorkflow;
257
+ __decorate([
258
+ (0, common_2.InjectTool)(),
259
+ __metadata("design:type", github_module_1.GitHubGetAuthenticatedUserTool)
260
+ ], ConnectGitHubWorkflow.prototype, "gitHubGetAuthenticatedUser", void 0);
261
+ __decorate([
262
+ (0, common_2.InjectTool)(),
263
+ __metadata("design:type", github_module_1.GitHubCreateRepoTool)
264
+ ], ConnectGitHubWorkflow.prototype, "gitHubCreateRepo", void 0);
265
+ __decorate([
266
+ (0, common_2.InjectTool)(),
267
+ __metadata("design:type", github_module_1.GitHubListReposTool)
268
+ ], ConnectGitHubWorkflow.prototype, "gitHubListRepos", void 0);
269
+ __decorate([
270
+ (0, common_2.InjectTool)(),
271
+ __metadata("design:type", git_module_1.GitRemoteConfigureTool)
272
+ ], ConnectGitHubWorkflow.prototype, "gitRemoteConfigure", void 0);
273
+ __decorate([
274
+ (0, common_2.InjectTool)(),
275
+ __metadata("design:type", git_module_1.GitConfigUserTool)
276
+ ], ConnectGitHubWorkflow.prototype, "gitConfigUser", void 0);
277
+ __decorate([
278
+ (0, common_2.InjectTool)(),
279
+ __metadata("design:type", git_module_1.GitStatusTool)
280
+ ], ConnectGitHubWorkflow.prototype, "gitStatus", void 0);
281
+ __decorate([
282
+ (0, common_2.InjectTool)(),
283
+ __metadata("design:type", git_module_1.GitPushTool)
284
+ ], ConnectGitHubWorkflow.prototype, "gitPush", void 0);
285
+ __decorate([
286
+ (0, common_2.InjectTool)(),
287
+ __metadata("design:type", git_module_1.GitFetchTool)
288
+ ], ConnectGitHubWorkflow.prototype, "gitFetch", void 0);
289
+ __decorate([
290
+ (0, common_2.InjectTool)(),
291
+ __metadata("design:type", remote_client_1.BashTool)
292
+ ], ConnectGitHubWorkflow.prototype, "bash", void 0);
293
+ __decorate([
294
+ (0, common_2.InjectWorkflow)(),
295
+ __metadata("design:type", oauth_module_1.OAuthWorkflow)
296
+ ], ConnectGitHubWorkflow.prototype, "oAuth", void 0);
297
+ __decorate([
298
+ (0, common_2.InjectWorkflow)(),
299
+ __metadata("design:type", hitl_1.AskUserWorkflow)
300
+ ], ConnectGitHubWorkflow.prototype, "askUser", void 0);
301
+ __decorate([
302
+ (0, common_1.Inject)(),
303
+ __metadata("design:type", oauth_module_1.OAuthTokenStore)
304
+ ], ConnectGitHubWorkflow.prototype, "tokenStore", void 0);
305
+ __decorate([
306
+ (0, common_1.Inject)(),
307
+ __metadata("design:type", core_1.ClientMessageService)
308
+ ], ConnectGitHubWorkflow.prototype, "clientMessageService", void 0);
309
+ __decorate([
310
+ (0, common_2.Initial)({ to: 'check_auth' }),
311
+ __metadata("design:type", Function),
312
+ __metadata("design:paramtypes", []),
313
+ __metadata("design:returntype", Promise)
314
+ ], ConnectGitHubWorkflow.prototype, "start", null);
315
+ __decorate([
316
+ (0, common_2.Transition)({ from: 'check_auth', to: 'awaiting_auth', priority: 10 }),
317
+ (0, common_2.Guard)('needsAuth'),
318
+ __metadata("design:type", Function),
319
+ __metadata("design:paramtypes", []),
320
+ __metadata("design:returntype", Promise)
321
+ ], ConnectGitHubWorkflow.prototype, "launchOAuth", null);
322
+ __decorate([
323
+ (0, common_2.Transition)({ from: 'awaiting_auth', to: 'check_auth', wait: true, schema: common_2.CallbackSchema }),
324
+ __metadata("design:type", Function),
325
+ __metadata("design:paramtypes", [Object]),
326
+ __metadata("design:returntype", Promise)
327
+ ], ConnectGitHubWorkflow.prototype, "authCompleted", null);
328
+ __decorate([
329
+ (0, common_2.Transition)({ from: 'check_auth', to: 'awaiting_choice' }),
330
+ __metadata("design:type", Function),
331
+ __metadata("design:paramtypes", []),
332
+ __metadata("design:returntype", Promise)
333
+ ], ConnectGitHubWorkflow.prototype, "askCreateOrLink", null);
334
+ __decorate([
335
+ (0, common_2.Transition)({ from: 'awaiting_choice', to: 'route_choice', wait: true, schema: common_2.CallbackSchema }),
336
+ __metadata("design:type", Function),
337
+ __metadata("design:paramtypes", [Object]),
338
+ __metadata("design:returntype", Promise)
339
+ ], ConnectGitHubWorkflow.prototype, "choiceReceived", null);
340
+ __decorate([
341
+ (0, common_2.Transition)({ from: 'route_choice', to: 'configure_remote', wait: true, schema: common_2.CallbackSchema }),
342
+ __metadata("design:type", Function),
343
+ __metadata("design:paramtypes", [Object]),
344
+ __metadata("design:returntype", Promise)
345
+ ], ConnectGitHubWorkflow.prototype, "createRepo", null);
346
+ __decorate([
347
+ (0, common_2.Transition)({ from: 'route_choice', to: 'configure_remote', wait: true, schema: common_2.CallbackSchema }),
348
+ __metadata("design:type", Function),
349
+ __metadata("design:paramtypes", [Object]),
350
+ __metadata("design:returntype", Promise)
351
+ ], ConnectGitHubWorkflow.prototype, "repoSelected", null);
352
+ __decorate([
353
+ (0, common_2.Transition)({ from: 'configure_remote', to: 'check_uncommitted' }),
354
+ __metadata("design:type", Function),
355
+ __metadata("design:paramtypes", []),
356
+ __metadata("design:returntype", Promise)
357
+ ], ConnectGitHubWorkflow.prototype, "checkForUncommittedChanges", null);
358
+ __decorate([
359
+ (0, common_2.Transition)({ from: 'check_uncommitted', to: 'setup_remote' }),
360
+ (0, common_2.Guard)('isCleanWorkspace'),
361
+ __metadata("design:type", Function),
362
+ __metadata("design:paramtypes", []),
363
+ __metadata("design:returntype", Promise)
364
+ ], ConnectGitHubWorkflow.prototype, "skipCommitCheck", null);
365
+ __decorate([
366
+ (0, common_2.Transition)({ from: 'check_uncommitted', to: 'awaiting_commit_confirm' }),
367
+ __metadata("design:type", Function),
368
+ __metadata("design:paramtypes", []),
369
+ __metadata("design:returntype", Promise)
370
+ ], ConnectGitHubWorkflow.prototype, "askCommitChanges", null);
371
+ __decorate([
372
+ (0, common_2.Transition)({ from: 'awaiting_commit_confirm', to: 'setup_remote', wait: true, schema: common_2.CallbackSchema }),
373
+ __metadata("design:type", Function),
374
+ __metadata("design:paramtypes", [Object]),
375
+ __metadata("design:returntype", Promise)
376
+ ], ConnectGitHubWorkflow.prototype, "uncommittedChangesHandled", null);
377
+ __decorate([
378
+ (0, common_2.Transition)({ from: 'setup_remote', to: 'check_divergence' }),
379
+ __metadata("design:type", Function),
380
+ __metadata("design:paramtypes", []),
381
+ __metadata("design:returntype", Promise)
382
+ ], ConnectGitHubWorkflow.prototype, "setupRemote", null);
383
+ __decorate([
384
+ (0, common_2.Transition)({ from: 'check_divergence', to: 'done' }),
385
+ (0, common_2.Guard)('canPushDirectly'),
386
+ __metadata("design:type", Function),
387
+ __metadata("design:paramtypes", []),
388
+ __metadata("design:returntype", Promise)
389
+ ], ConnectGitHubWorkflow.prototype, "pushDirectly", null);
390
+ __decorate([
391
+ (0, common_2.Transition)({ from: 'check_divergence', to: 'awaiting_sync_choice' }),
392
+ __metadata("design:type", Function),
393
+ __metadata("design:paramtypes", []),
394
+ __metadata("design:returntype", Promise)
395
+ ], ConnectGitHubWorkflow.prototype, "askSyncStrategy", null);
396
+ __decorate([
397
+ (0, common_2.Transition)({ from: 'awaiting_sync_choice', to: 'done', wait: true, schema: common_2.CallbackSchema }),
398
+ __metadata("design:type", Function),
399
+ __metadata("design:paramtypes", [Object]),
400
+ __metadata("design:returntype", Promise)
401
+ ], ConnectGitHubWorkflow.prototype, "syncStrategyChosen", null);
402
+ __decorate([
403
+ (0, common_2.Final)({ from: 'done' }),
404
+ __metadata("design:type", Function),
405
+ __metadata("design:paramtypes", []),
406
+ __metadata("design:returntype", Promise)
407
+ ], ConnectGitHubWorkflow.prototype, "showSuccess", null);
408
+ exports.ConnectGitHubWorkflow = ConnectGitHubWorkflow = __decorate([
409
+ (0, common_2.Workflow)({
410
+ uiConfig: __dirname + '/connect-github.ui.yaml',
411
+ schema: zod_1.z.object({}).strict(),
412
+ })
413
+ ], ConnectGitHubWorkflow);
414
+ //# sourceMappingURL=connect-github.workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-github.workflow.js","sourceRoot":"","sources":["../../../src/workflows/connect-github/connect-github.workflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { ConnectGitHubWorkflow } from './connect-github/connect-github.workflow';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workflows/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectGitHubWorkflow = void 0;
4
+ var connect_github_workflow_1 = require("./connect-github/connect-github.workflow");
5
+ Object.defineProperty(exports, "ConnectGitHubWorkflow", { enumerable: true, get: function () { return connect_github_workflow_1.ConnectGitHubWorkflow; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/workflows/index.ts"],"names":[],"mappings":";;;AAAA,oFAAiF;AAAxE,gIAAA,qBAAqB,OAAA"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@loopstack/github-integration",
3
+ "displayName": "Loopstack GitHub Integration",
4
+ "description": "GitHub integration workflows for Loopstack — connect workspaces to GitHub repositories with OAuth, push, pull, and sync",
5
+ "keywords": [
6
+ "loopstack",
7
+ "github",
8
+ "git",
9
+ "integration",
10
+ "workflow"
11
+ ],
12
+ "version": "0.2.0",
13
+ "license": "MIT",
14
+ "author": {
15
+ "name": "Jakob Klippel",
16
+ "url": "https://www.linkedin.com/in/jakob-klippel/"
17
+ },
18
+ "main": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "exports": {
21
+ ".": "./dist/index.js",
22
+ "./src/*": "./src/*"
23
+ },
24
+ "scripts": {
25
+ "build": "nest build",
26
+ "compile": "tsc --noEmit",
27
+ "format": "prettier --write .",
28
+ "lint": "eslint .",
29
+ "test": "jest --passWithNoTests",
30
+ "watch": "nest build --watch"
31
+ },
32
+ "dependencies": {
33
+ "@loopstack/common": "^0.27.0",
34
+ "@loopstack/core": "^0.27.0",
35
+ "@loopstack/git-module": "^0.1.1",
36
+ "@loopstack/github-module": "^0.2.3",
37
+ "@loopstack/hitl": "^0.1.1",
38
+ "@loopstack/oauth-module": "^0.2.5",
39
+ "@loopstack/remote-client": "^0.23.2",
40
+ "@nestjs/common": "^11.1.19",
41
+ "zod": "^4.3.6"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src"
46
+ ],
47
+ "jest": {
48
+ "moduleFileExtensions": [
49
+ "js",
50
+ "json",
51
+ "ts"
52
+ ],
53
+ "rootDir": "src",
54
+ "testRegex": ".*\\.spec\\.ts$",
55
+ "transform": {
56
+ "^.+\\.ts$": "ts-jest"
57
+ },
58
+ "collectCoverageFrom": [
59
+ "**/*.(t|j)s"
60
+ ],
61
+ "coverageDirectory": "../coverage",
62
+ "testEnvironment": "node",
63
+ "maxWorkers": 1
64
+ },
65
+ "loopstack": {
66
+ "modules": [
67
+ {
68
+ "path": "src/github-integration.module.ts",
69
+ "className": "GitHubIntegrationModule"
70
+ }
71
+ ],
72
+ "installModes": [
73
+ "add",
74
+ "install"
75
+ ]
76
+ }
77
+ }
@@ -0,0 +1,15 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { LoopCoreModule } from '@loopstack/core';
3
+ import { GitModule } from '@loopstack/git-module';
4
+ import { GitHubModule } from '@loopstack/github-module';
5
+ import { HitlModule } from '@loopstack/hitl';
6
+ import { OAuthModule } from '@loopstack/oauth-module';
7
+ import { RemoteClientModule } from '@loopstack/remote-client';
8
+ import { ConnectGitHubWorkflow } from './workflows/connect-github/connect-github.workflow';
9
+
10
+ @Module({
11
+ imports: [LoopCoreModule, GitModule, GitHubModule, HitlModule, OAuthModule, RemoteClientModule],
12
+ providers: [ConnectGitHubWorkflow],
13
+ exports: [ConnectGitHubWorkflow],
14
+ })
15
+ export class GitHubIntegrationModule {}
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './workflows';
2
+ export * from './github-integration.module';
@@ -0,0 +1,5 @@
1
+ title: 'Connect to GitHub'
2
+
3
+ description: |
4
+ Connects your workspace to a GitHub repository.
5
+ Authenticates with GitHub, lets you create or link a repo, and configures git push access.
@@ -0,0 +1,451 @@
1
+ import { Inject } from '@nestjs/common';
2
+ import { z } from 'zod';
3
+ import {
4
+ BaseWorkflow,
5
+ CallbackSchema,
6
+ Final,
7
+ Guard,
8
+ Initial,
9
+ InjectTool,
10
+ InjectWorkflow,
11
+ LinkDocument,
12
+ MarkdownDocument,
13
+ ToolResult,
14
+ Transition,
15
+ Workflow,
16
+ } from '@loopstack/common';
17
+ import { ClientMessageService } from '@loopstack/core';
18
+ import {
19
+ GitConfigUserTool,
20
+ GitFetchTool,
21
+ GitPushTool,
22
+ GitRemoteConfigureTool,
23
+ GitStatusTool,
24
+ } from '@loopstack/git-module';
25
+ import { GitHubCreateRepoTool, GitHubGetAuthenticatedUserTool, GitHubListReposTool } from '@loopstack/github-module';
26
+ import { AskUserWorkflow } from '@loopstack/hitl';
27
+ import { OAuthTokenStore, OAuthWorkflow } from '@loopstack/oauth-module';
28
+ import { BashTool } from '@loopstack/remote-client';
29
+
30
+ interface GitHubUser {
31
+ login: string;
32
+ name: string | null;
33
+ email: string | null;
34
+ }
35
+
36
+ interface GitHubRepo {
37
+ fullName: string;
38
+ name: string;
39
+ htmlUrl: string;
40
+ private: boolean;
41
+ defaultBranch: string;
42
+ }
43
+
44
+ @Workflow({
45
+ uiConfig: __dirname + '/connect-github.ui.yaml',
46
+ schema: z.object({}).strict(),
47
+ })
48
+ export class ConnectGitHubWorkflow extends BaseWorkflow {
49
+ @InjectTool() private gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool;
50
+ @InjectTool() private gitHubCreateRepo: GitHubCreateRepoTool;
51
+ @InjectTool() private gitHubListRepos: GitHubListReposTool;
52
+ @InjectTool() private gitRemoteConfigure: GitRemoteConfigureTool;
53
+ @InjectTool() private gitConfigUser: GitConfigUserTool;
54
+ @InjectTool() private gitStatus: GitStatusTool;
55
+ @InjectTool() private gitPush: GitPushTool;
56
+ @InjectTool() private gitFetch: GitFetchTool;
57
+ @InjectTool() private bash: BashTool;
58
+
59
+ @InjectWorkflow() oAuth: OAuthWorkflow;
60
+ @InjectWorkflow() askUser: AskUserWorkflow;
61
+
62
+ @Inject() private tokenStore: OAuthTokenStore;
63
+ @Inject() private clientMessageService: ClientMessageService;
64
+
65
+ private async getGitHubToken(): Promise<string | undefined> {
66
+ return (await this.tokenStore.getValidAccessToken(this.ctx.context.userId, 'github')) ?? undefined;
67
+ }
68
+
69
+ requiresAuth?: boolean;
70
+ user?: GitHubUser;
71
+ repo?: GitHubRepo;
72
+ isNewRepo?: boolean;
73
+ divergenceState?: 'none' | 'local_ahead' | 'remote_ahead' | 'diverged';
74
+
75
+ // ── Step 1: Check if already authenticated ──────────────────────────
76
+
77
+ @Initial({ to: 'check_auth' })
78
+ async start() {
79
+ const result: ToolResult<{ error?: string; user?: GitHubUser }> = await this.gitHubGetAuthenticatedUser.call({});
80
+ this.requiresAuth = result.data!.error === 'unauthorized';
81
+ this.user = result.data!.user;
82
+ }
83
+
84
+ // ── Step 2a: OAuth if needed ────────────────────────────────────────
85
+
86
+ @Transition({ from: 'check_auth', to: 'awaiting_auth', priority: 10 })
87
+ @Guard('needsAuth')
88
+ async launchOAuth() {
89
+ const result = await this.oAuth.run(
90
+ { provider: 'github', scopes: ['repo', 'user'] },
91
+ { alias: 'oAuth', callback: { transition: 'authCompleted' } },
92
+ );
93
+
94
+ await this.repository.save(
95
+ LinkDocument,
96
+ {
97
+ label: 'Sign in with GitHub',
98
+ workflowId: result.workflowId,
99
+ embed: true,
100
+ expanded: true,
101
+ },
102
+ { id: `link_${result.workflowId}` },
103
+ );
104
+ }
105
+
106
+ private needsAuth(): boolean {
107
+ return !!this.requiresAuth;
108
+ }
109
+
110
+ @Transition({ from: 'awaiting_auth', to: 'check_auth', wait: true, schema: CallbackSchema })
111
+ async authCompleted(payload: { workflowId: string }) {
112
+ await this.repository.save(
113
+ LinkDocument,
114
+ {
115
+ status: 'success',
116
+ label: 'GitHub authentication completed',
117
+ workflowId: payload.workflowId,
118
+ embed: true,
119
+ expanded: false,
120
+ },
121
+ { id: `link_${payload.workflowId}` },
122
+ );
123
+
124
+ const result: ToolResult<{ user?: GitHubUser }> = await this.gitHubGetAuthenticatedUser.call({});
125
+ this.user = result.data!.user;
126
+ this.requiresAuth = false;
127
+ }
128
+
129
+ // ── Step 2b: Ask create or link ─────────────────────────────────────
130
+
131
+ @Transition({ from: 'check_auth', to: 'awaiting_choice' })
132
+ async askCreateOrLink() {
133
+ const result = await this.askUser.run(
134
+ {
135
+ question: 'Would you like to create a new GitHub repository or connect an existing one?',
136
+ mode: 'options',
137
+ options: ['Create new repository', 'Connect existing repository'],
138
+ },
139
+ { alias: 'chooseAction', callback: { transition: 'choiceReceived' } },
140
+ );
141
+
142
+ await this.repository.save(
143
+ LinkDocument,
144
+ { label: 'Create or connect repository', workflowId: result.workflowId, embed: true, expanded: true },
145
+ { id: `link_choice` },
146
+ );
147
+ }
148
+
149
+ @Transition({ from: 'awaiting_choice', to: 'route_choice', wait: true, schema: CallbackSchema })
150
+ async choiceReceived(payload: { data: { answer: string } }) {
151
+ await this.repository.save(
152
+ LinkDocument,
153
+ { label: 'Create or connect repository', status: 'success', embed: true, expanded: false },
154
+ { id: `link_choice` },
155
+ );
156
+
157
+ if (payload.data.answer === 'Connect existing repository') {
158
+ const listResult: ToolResult<{
159
+ repos: Array<{ fullName: string; name: string; htmlUrl: string; private: boolean; defaultBranch: string }>;
160
+ }> = await this.gitHubListRepos.call({ visibility: 'all', sort: 'updated', perPage: 30 });
161
+
162
+ const repos = listResult.data!.repos ?? [];
163
+ const repoNames = repos.map((r) => r.fullName);
164
+
165
+ const askResult = await this.askUser.run(
166
+ { question: 'Select a repository to connect:', mode: 'options', options: repoNames },
167
+ { alias: 'chooseRepo', callback: { transition: 'repoSelected' } },
168
+ );
169
+
170
+ await this.repository.save(
171
+ LinkDocument,
172
+ { label: 'Select repository', workflowId: askResult.workflowId, embed: true, expanded: true },
173
+ { id: `link_repo_select` },
174
+ );
175
+ } else {
176
+ const askResult = await this.askUser.run(
177
+ { question: 'Enter a name for your new repository:' },
178
+ { alias: 'repoName', callback: { transition: 'createRepo' } },
179
+ );
180
+
181
+ await this.repository.save(
182
+ LinkDocument,
183
+ { label: 'Repository name', workflowId: askResult.workflowId, embed: true, expanded: true },
184
+ { id: `link_repo_name` },
185
+ );
186
+ }
187
+ }
188
+
189
+ // ── Step 3a: Create new repo ────────────────────────────────────────
190
+
191
+ @Transition({ from: 'route_choice', to: 'configure_remote', wait: true, schema: CallbackSchema })
192
+ async createRepo(payload: { data: { answer: string } }) {
193
+ await this.repository.save(
194
+ LinkDocument,
195
+ { label: 'Repository name', status: 'success', embed: true, expanded: false },
196
+ { id: `link_repo_name` },
197
+ );
198
+
199
+ const repoName = payload.data.answer.trim();
200
+ const createResult: ToolResult<{ repo: GitHubRepo }> = await this.gitHubCreateRepo.call({
201
+ name: repoName,
202
+ private: true,
203
+ autoInit: false,
204
+ });
205
+
206
+ this.repo = createResult.data!.repo;
207
+ this.isNewRepo = true;
208
+ }
209
+
210
+ // ── Step 3b: Link existing repo ─────────────────────────────────────
211
+
212
+ @Transition({ from: 'route_choice', to: 'configure_remote', wait: true, schema: CallbackSchema })
213
+ async repoSelected(payload: { data: { answer: string } }) {
214
+ await this.repository.save(
215
+ LinkDocument,
216
+ { label: 'Select repository', status: 'success', embed: true, expanded: false },
217
+ { id: `link_repo_select` },
218
+ );
219
+
220
+ const fullName = payload.data.answer;
221
+ const [, name] = fullName.split('/');
222
+
223
+ this.repo = {
224
+ fullName,
225
+ name,
226
+ htmlUrl: `https://github.com/${fullName}`,
227
+ private: false,
228
+ defaultBranch: 'main',
229
+ };
230
+ this.isNewRepo = false;
231
+ }
232
+
233
+ // ── Step 4a: Check for uncommitted changes ───────────────────────────
234
+
235
+ hasUncommittedChanges?: boolean;
236
+
237
+ @Transition({ from: 'configure_remote', to: 'check_uncommitted' })
238
+ async checkForUncommittedChanges() {
239
+ const user = this.user;
240
+
241
+ // Configure git user identity early
242
+ if (user) {
243
+ await this.gitConfigUser.call({
244
+ name: user.name || user.login,
245
+ email: user.email || `${user.login}@users.noreply.github.com`,
246
+ });
247
+ }
248
+
249
+ const statusResult: ToolResult<{ staged: string[]; modified: string[]; untracked: string[]; deleted: string[] }> =
250
+ await this.gitStatus.call();
251
+ const status = statusResult.data!;
252
+ this.hasUncommittedChanges =
253
+ status.staged.length > 0 ||
254
+ status.modified.length > 0 ||
255
+ status.untracked.length > 0 ||
256
+ status.deleted.length > 0;
257
+ }
258
+
259
+ // Clean workspace — skip straight to remote setup
260
+ @Transition({ from: 'check_uncommitted', to: 'setup_remote' })
261
+ @Guard('isCleanWorkspace')
262
+ async skipCommitCheck() {}
263
+
264
+ private isCleanWorkspace(): boolean {
265
+ return !this.hasUncommittedChanges;
266
+ }
267
+
268
+ // Uncommitted changes — ask user
269
+ @Transition({ from: 'check_uncommitted', to: 'awaiting_commit_confirm' })
270
+ async askCommitChanges() {
271
+ const confirmResult = await this.askUser.run(
272
+ {
273
+ question:
274
+ 'There are uncommitted changes in your workspace. They need to be committed before connecting to a remote repository. Would you like to commit them now?',
275
+ mode: 'options',
276
+ options: ['Commit changes and continue', 'Cancel'],
277
+ },
278
+ { alias: 'commitChanges', callback: { transition: 'uncommittedChangesHandled' } },
279
+ );
280
+
281
+ await this.repository.save(
282
+ LinkDocument,
283
+ { label: 'Uncommitted changes', workflowId: confirmResult.workflowId, embed: true, expanded: true },
284
+ { id: `link_uncommitted` },
285
+ );
286
+ }
287
+
288
+ @Transition({ from: 'awaiting_commit_confirm', to: 'setup_remote', wait: true, schema: CallbackSchema })
289
+ async uncommittedChangesHandled(payload: { data: { answer: string } }) {
290
+ await this.repository.save(
291
+ LinkDocument,
292
+ { label: 'Uncommitted changes', status: 'success', embed: true, expanded: false },
293
+ { id: `link_uncommitted` },
294
+ );
295
+
296
+ if (payload.data.answer === 'Cancel') {
297
+ this.repo = undefined;
298
+ return;
299
+ }
300
+
301
+ await this.bash.call({ command: 'git add -A' });
302
+ await this.bash.call({ command: 'git commit -m "Auto-commit before connecting to GitHub"' });
303
+ }
304
+
305
+ // ── Step 4b: Configure remote and check for divergence ──────────────
306
+
307
+ @Transition({ from: 'setup_remote', to: 'check_divergence' })
308
+ async setupRemote() {
309
+ // If cancelled during commit confirmation, skip to done
310
+ if (!this.repo) {
311
+ this.divergenceState = 'none';
312
+ return;
313
+ }
314
+
315
+ const repo = this.repo;
316
+
317
+ // Configure the remote URL (no credentials stored)
318
+ const remoteUrl = `https://github.com/${repo.fullName}.git`;
319
+ await this.gitRemoteConfigure.call({ url: remoteUrl });
320
+
321
+ // Fetch remote refs using token for auth (token only exists in memory during the command)
322
+ const token = await this.getGitHubToken();
323
+ await this.gitFetch.call({ remote: 'origin', token });
324
+
325
+ // Determine relationship between local and remote branches
326
+ if (!this.isNewRepo) {
327
+ const checkResult = (await this.bash.call({
328
+ command: [
329
+ 'REMOTE_EXISTS=$(git rev-parse --verify origin/main 2>/dev/null && echo yes || echo no)',
330
+ 'if [ "$REMOTE_EXISTS" = "no" ]; then echo "no_remote"; exit 0; fi',
331
+ 'LOCAL=$(git rev-parse main)',
332
+ 'REMOTE=$(git rev-parse origin/main)',
333
+ 'if [ "$LOCAL" = "$REMOTE" ]; then echo "same"; exit 0; fi',
334
+ 'git merge-base --is-ancestor main origin/main && echo "remote_ahead" && exit 0',
335
+ 'git merge-base --is-ancestor origin/main main && echo "local_ahead" && exit 0',
336
+ 'echo "diverged"',
337
+ ].join(' && '),
338
+ })) as ToolResult<{ stdout: string }>;
339
+ const state = checkResult.data!.stdout.trim().split('\n').pop()!.trim();
340
+ if (state === 'same' || state === 'no_remote') {
341
+ this.divergenceState = 'none';
342
+ } else {
343
+ this.divergenceState = state as 'local_ahead' | 'remote_ahead' | 'diverged';
344
+ }
345
+ } else {
346
+ this.divergenceState = 'none';
347
+ }
348
+ }
349
+
350
+ // ── Step 5a: No divergence or local ahead — just push ────────────────
351
+
352
+ @Transition({ from: 'check_divergence', to: 'done' })
353
+ @Guard('canPushDirectly')
354
+ async pushDirectly() {
355
+ if (this.divergenceState === 'none') {
356
+ // Already in sync — nothing to push
357
+ return;
358
+ }
359
+ // local_ahead — fast-forward push
360
+ const statusResult: ToolResult<{ branch: string }> = await this.gitStatus.call();
361
+ const branch = statusResult.data!.branch ?? 'main';
362
+ const token = await this.getGitHubToken();
363
+ await this.gitPush.call({ remote: 'origin', branch, token });
364
+ }
365
+
366
+ private canPushDirectly(): boolean {
367
+ return this.divergenceState === 'none' || this.divergenceState === 'local_ahead';
368
+ }
369
+
370
+ // ── Step 5b: Divergence detected — ask user how to resolve ──────────
371
+
372
+ @Transition({ from: 'check_divergence', to: 'awaiting_sync_choice' })
373
+ async askSyncStrategy() {
374
+ const isRemoteAhead = this.divergenceState === 'remote_ahead';
375
+ const question = isRemoteAhead
376
+ ? 'The remote repository has newer commits than your workspace. How would you like to proceed?'
377
+ : 'The remote repository has a different commit history than your workspace. How would you like to proceed?';
378
+
379
+ const options = isRemoteAhead
380
+ ? ['Pull remote changes into workspace', 'Push workspace code (overwrite remote)', 'Cancel (disconnect remote)']
381
+ : [
382
+ 'Use remote code (replace local files with remote)',
383
+ 'Merge remote changes into workspace',
384
+ 'Push workspace code (overwrite remote)',
385
+ 'Cancel (disconnect remote)',
386
+ ];
387
+
388
+ const result = await this.askUser.run(
389
+ { question, mode: 'options', options },
390
+ { alias: 'syncStrategy', callback: { transition: 'syncStrategyChosen' } },
391
+ );
392
+
393
+ await this.repository.save(
394
+ LinkDocument,
395
+ { label: 'Resolve differences', workflowId: result.workflowId, embed: true, expanded: true },
396
+ { id: `link_sync` },
397
+ );
398
+ }
399
+
400
+ @Transition({ from: 'awaiting_sync_choice', to: 'done', wait: true, schema: CallbackSchema })
401
+ async syncStrategyChosen(payload: { data: { answer: string } }) {
402
+ await this.repository.save(
403
+ LinkDocument,
404
+ { label: 'Resolve differences', status: 'success', embed: true, expanded: false },
405
+ { id: `link_sync` },
406
+ );
407
+
408
+ const answer = payload.data.answer;
409
+ const statusResult: ToolResult<{ branch: string }> = await this.gitStatus.call();
410
+ const branch = statusResult.data!.branch ?? 'main';
411
+
412
+ const token = await this.getGitHubToken();
413
+
414
+ if (answer.startsWith('Use remote code')) {
415
+ await this.bash.call({ command: `git reset --hard origin/${branch}` });
416
+ } else if (answer.startsWith('Pull remote changes') || answer.startsWith('Merge remote changes')) {
417
+ await this.bash.call({ command: `git merge origin/${branch} --allow-unrelated-histories --no-edit` });
418
+ await this.gitPush.call({ remote: 'origin', branch, token });
419
+ } else if (answer.startsWith('Push workspace code')) {
420
+ await this.gitPush.call({ remote: 'origin', branch, force: true, token });
421
+ } else {
422
+ await this.bash.call({ command: 'git remote remove origin' });
423
+ this.repo = undefined;
424
+ }
425
+ }
426
+
427
+ // ── Final: Show result ──────────────────────────────────────────────
428
+
429
+ @Final({ from: 'done' })
430
+ async showSuccess() {
431
+ if (!this.repo) {
432
+ await this.repository.save(MarkdownDocument, {
433
+ markdown: '### Cancelled\n\nThe remote connection was removed. No changes were made.',
434
+ });
435
+ return { cancelled: true };
436
+ }
437
+
438
+ const repo = this.repo;
439
+ await this.repository.save(MarkdownDocument, {
440
+ markdown: `### Repository Connected\n\nYour workspace is now connected to [${repo.fullName}](${repo.htmlUrl}).\n\nAll future commits can be pushed to this repository using the git tools in your workflows.`,
441
+ });
442
+
443
+ this.clientMessageService.dispatchWorkspaceEvent(
444
+ 'git.updated',
445
+ this.ctx.context.workspaceId,
446
+ this.ctx.context.userId,
447
+ );
448
+
449
+ return { repo: repo.fullName, url: repo.htmlUrl };
450
+ }
451
+ }
@@ -0,0 +1 @@
1
+ export { ConnectGitHubWorkflow } from './connect-github/connect-github.workflow';