@agentuity/cli 1.0.13 → 1.0.15
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/bin/cli.ts +8 -0
- package/dist/agent-detection.js +2 -2
- package/dist/agent-detection.js.map +1 -1
- package/dist/banner.js +2 -2
- package/dist/banner.js.map +1 -1
- package/dist/bun-path.d.ts.map +1 -1
- package/dist/bun-path.js +2 -1
- package/dist/bun-path.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +87 -14
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +3 -2
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +2 -1
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +9 -7
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +3 -2
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +19 -21
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/env/delete.js +3 -26
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/import.d.ts.map +1 -1
- package/dist/cmd/cloud/env/import.js +3 -16
- package/dist/cmd/cloud/env/import.js.map +1 -1
- package/dist/cmd/cloud/env/set.d.ts.map +1 -1
- package/dist/cmd/cloud/env/set.js +3 -19
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/cp.js +2 -1
- package/dist/cmd/cloud/sandbox/cp.js.map +1 -1
- package/dist/cmd/git/account/add.d.ts +6 -8
- package/dist/cmd/git/account/add.d.ts.map +1 -1
- package/dist/cmd/git/account/add.js +37 -151
- package/dist/cmd/git/account/add.js.map +1 -1
- package/dist/cmd/git/account/index.d.ts +0 -1
- package/dist/cmd/git/account/index.d.ts.map +1 -1
- package/dist/cmd/git/account/index.js +3 -4
- package/dist/cmd/git/account/index.js.map +1 -1
- package/dist/cmd/git/account/list.d.ts.map +1 -1
- package/dist/cmd/git/account/list.js +35 -67
- package/dist/cmd/git/account/list.js.map +1 -1
- package/dist/cmd/git/account/remove.d.ts.map +1 -1
- package/dist/cmd/git/account/remove.js +42 -84
- package/dist/cmd/git/account/remove.js.map +1 -1
- package/dist/cmd/git/api.d.ts +19 -23
- package/dist/cmd/git/api.d.ts.map +1 -1
- package/dist/cmd/git/api.js +38 -56
- package/dist/cmd/git/api.js.map +1 -1
- package/dist/cmd/git/identity/connect.d.ts +15 -0
- package/dist/cmd/git/identity/connect.d.ts.map +1 -0
- package/dist/cmd/git/identity/connect.js +135 -0
- package/dist/cmd/git/identity/connect.js.map +1 -0
- package/dist/cmd/git/identity/disconnect.d.ts +2 -0
- package/dist/cmd/git/identity/disconnect.d.ts.map +1 -0
- package/dist/cmd/git/identity/disconnect.js +83 -0
- package/dist/cmd/git/identity/disconnect.js.map +1 -0
- package/dist/cmd/git/identity/index.d.ts +2 -0
- package/dist/cmd/git/identity/index.d.ts.map +1 -0
- package/dist/cmd/git/identity/index.js +10 -0
- package/dist/cmd/git/identity/index.js.map +1 -0
- package/dist/cmd/git/identity/status.d.ts +2 -0
- package/dist/cmd/git/identity/status.d.ts.map +1 -0
- package/dist/cmd/git/identity/status.js +77 -0
- package/dist/cmd/git/identity/status.js.map +1 -0
- package/dist/cmd/git/index.d.ts +0 -1
- package/dist/cmd/git/index.d.ts.map +1 -1
- package/dist/cmd/git/index.js +3 -2
- package/dist/cmd/git/index.js.map +1 -1
- package/dist/cmd/git/link.d.ts +2 -3
- package/dist/cmd/git/link.d.ts.map +1 -1
- package/dist/cmd/git/link.js +22 -28
- package/dist/cmd/git/link.js.map +1 -1
- package/dist/cmd/git/list.d.ts.map +1 -1
- package/dist/cmd/git/list.js +42 -55
- package/dist/cmd/git/list.js.map +1 -1
- package/dist/cmd/git/status.d.ts.map +1 -1
- package/dist/cmd/git/status.js +51 -38
- package/dist/cmd/git/status.js.map +1 -1
- package/dist/cmd/upgrade/npm-availability.d.ts +6 -7
- package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -1
- package/dist/cmd/upgrade/npm-availability.js +9 -18
- package/dist/cmd/upgrade/npm-availability.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -4
- package/dist/config.js.map +1 -1
- package/dist/utils/detectSubagent.d.ts.map +1 -1
- package/dist/utils/detectSubagent.js +3 -0
- package/dist/utils/detectSubagent.js.map +1 -1
- package/dist/utils/normalize-path.d.ts +11 -0
- package/dist/utils/normalize-path.d.ts.map +1 -0
- package/dist/utils/normalize-path.js +13 -0
- package/dist/utils/normalize-path.js.map +1 -0
- package/dist/utils/zip.d.ts.map +1 -1
- package/dist/utils/zip.js +2 -1
- package/dist/utils/zip.js.map +1 -1
- package/package.json +6 -6
- package/src/agent-detection.ts +2 -2
- package/src/banner.ts +2 -2
- package/src/bun-path.ts +2 -1
- package/src/cmd/build/ast.ts +96 -15
- package/src/cmd/build/vite/agent-discovery.ts +3 -2
- package/src/cmd/build/vite/metadata-generator.ts +2 -1
- package/src/cmd/build/vite/registry-generator.ts +9 -7
- package/src/cmd/build/vite/route-discovery.ts +3 -2
- package/src/cmd/cloud/deploy.ts +54 -61
- package/src/cmd/cloud/env/delete.ts +3 -34
- package/src/cmd/cloud/env/import.ts +2 -18
- package/src/cmd/cloud/env/set.ts +2 -21
- package/src/cmd/cloud/sandbox/cp.ts +2 -1
- package/src/cmd/git/account/add.ts +51 -190
- package/src/cmd/git/account/index.ts +3 -5
- package/src/cmd/git/account/list.ts +51 -82
- package/src/cmd/git/account/remove.ts +45 -95
- package/src/cmd/git/api.ts +49 -111
- package/src/cmd/git/identity/connect.ts +178 -0
- package/src/cmd/git/identity/disconnect.ts +103 -0
- package/src/cmd/git/identity/index.ts +10 -0
- package/src/cmd/git/identity/status.ts +96 -0
- package/src/cmd/git/index.ts +3 -3
- package/src/cmd/git/link.ts +32 -35
- package/src/cmd/git/list.ts +48 -59
- package/src/cmd/git/status.ts +55 -40
- package/src/cmd/upgrade/npm-availability.ts +14 -23
- package/src/config.ts +14 -5
- package/src/utils/detectSubagent.ts +5 -0
- package/src/utils/normalize-path.ts +12 -0
- package/src/utils/zip.ts +2 -1
|
@@ -1,31 +1,28 @@
|
|
|
1
|
-
import { createSubcommand } from '../../../types';
|
|
2
|
-
import * as tui from '../../../tui';
|
|
3
|
-
import { getCommand } from '../../../command-prefix';
|
|
4
|
-
import { ErrorCode } from '../../../errors';
|
|
5
|
-
import { listOrganizations } from '@agentuity/server';
|
|
6
1
|
import enquirer from 'enquirer';
|
|
7
2
|
import { z } from 'zod';
|
|
3
|
+
import { getCommand } from '../../../command-prefix';
|
|
4
|
+
import { ErrorCode } from '../../../errors';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { createSubcommand } from '../../../types';
|
|
8
7
|
import {
|
|
9
|
-
getGithubIntegrationStatus,
|
|
10
8
|
disconnectGithubIntegration,
|
|
11
|
-
type
|
|
9
|
+
type GithubInstallation,
|
|
10
|
+
getGithubIntegrationStatus,
|
|
12
11
|
} from '../api';
|
|
13
12
|
|
|
14
13
|
const RemoveOptionsSchema = z.object({
|
|
15
|
-
|
|
16
|
-
account: z.string().optional().describe('GitHub integration ID to remove'),
|
|
14
|
+
account: z.string().optional().describe('Installation ID to remove'),
|
|
17
15
|
confirm: z.boolean().optional().describe('Skip confirmation prompt'),
|
|
18
16
|
});
|
|
19
17
|
|
|
20
18
|
const RemoveResponseSchema = z.object({
|
|
21
|
-
removed: z.boolean().describe('Whether the
|
|
22
|
-
|
|
23
|
-
integrationId: z.string().optional().describe('Integration ID that was removed'),
|
|
19
|
+
removed: z.boolean().describe('Whether the installation was removed'),
|
|
20
|
+
installationId: z.string().optional().describe('Installation ID that was removed'),
|
|
24
21
|
});
|
|
25
22
|
|
|
26
23
|
export const removeSubcommand = createSubcommand({
|
|
27
24
|
name: 'remove',
|
|
28
|
-
description: 'Remove a GitHub
|
|
25
|
+
description: 'Remove a GitHub App installation',
|
|
29
26
|
tags: ['mutating', 'destructive', 'slow'],
|
|
30
27
|
idempotent: false,
|
|
31
28
|
requires: { auth: true, apiClient: true },
|
|
@@ -36,14 +33,14 @@ export const removeSubcommand = createSubcommand({
|
|
|
36
33
|
examples: [
|
|
37
34
|
{
|
|
38
35
|
command: getCommand('git account remove'),
|
|
39
|
-
description: 'Remove a GitHub
|
|
36
|
+
description: 'Remove a GitHub App installation',
|
|
40
37
|
},
|
|
41
38
|
{
|
|
42
|
-
command: getCommand('git account remove --
|
|
43
|
-
description: 'Remove a specific
|
|
39
|
+
command: getCommand('git account remove --account inst_xyz --confirm'),
|
|
40
|
+
description: 'Remove a specific installation without prompts',
|
|
44
41
|
},
|
|
45
42
|
{
|
|
46
|
-
command: getCommand('--json git account remove --
|
|
43
|
+
command: getCommand('--json git account remove --account inst_xyz --confirm'),
|
|
47
44
|
description: 'Remove and return JSON result',
|
|
48
45
|
},
|
|
49
46
|
],
|
|
@@ -52,11 +49,11 @@ export const removeSubcommand = createSubcommand({
|
|
|
52
49
|
const { logger, apiClient, opts, options } = ctx;
|
|
53
50
|
|
|
54
51
|
try {
|
|
55
|
-
// If
|
|
56
|
-
if (opts.
|
|
52
|
+
// If --account provided directly, skip interactive flow
|
|
53
|
+
if (opts.account) {
|
|
57
54
|
if (!opts.confirm) {
|
|
58
55
|
const confirmed = await tui.confirm(
|
|
59
|
-
|
|
56
|
+
'Are you sure you want to remove this GitHub installation?'
|
|
60
57
|
);
|
|
61
58
|
if (!confirmed) {
|
|
62
59
|
tui.info('Cancelled');
|
|
@@ -65,107 +62,61 @@ export const removeSubcommand = createSubcommand({
|
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
await tui.spinner({
|
|
68
|
-
message: 'Removing GitHub
|
|
65
|
+
message: 'Removing GitHub installation...',
|
|
69
66
|
clearOnSuccess: true,
|
|
70
|
-
callback: () => disconnectGithubIntegration(apiClient, opts.
|
|
67
|
+
callback: () => disconnectGithubIntegration(apiClient, opts.account!),
|
|
71
68
|
});
|
|
72
69
|
|
|
73
70
|
if (!options.json) {
|
|
74
71
|
tui.newline();
|
|
75
|
-
tui.success('Removed GitHub
|
|
72
|
+
tui.success('Removed GitHub installation');
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
return { removed: true,
|
|
75
|
+
return { removed: true, installationId: opts.account };
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
message: '
|
|
78
|
+
// Interactive flow: get status and show picker
|
|
79
|
+
const status = await tui.spinner({
|
|
80
|
+
message: 'Checking GitHub connection...',
|
|
84
81
|
clearOnSuccess: true,
|
|
85
|
-
callback: () =>
|
|
82
|
+
callback: () => getGithubIntegrationStatus(apiClient),
|
|
86
83
|
});
|
|
87
84
|
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Check GitHub status for each org
|
|
93
|
-
const orgStatuses = await tui.spinner({
|
|
94
|
-
message: 'Checking GitHub integration status...',
|
|
95
|
-
clearOnSuccess: true,
|
|
96
|
-
callback: async () => {
|
|
97
|
-
const statuses = await Promise.all(
|
|
98
|
-
orgs.map(async (org) => {
|
|
99
|
-
const status = await getGithubIntegrationStatus(apiClient, org.id);
|
|
100
|
-
return {
|
|
101
|
-
...org,
|
|
102
|
-
connected: status.connected,
|
|
103
|
-
integrations: status.integrations,
|
|
104
|
-
};
|
|
105
|
-
})
|
|
106
|
-
);
|
|
107
|
-
return statuses;
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Flatten all integrations across orgs
|
|
112
|
-
const allIntegrations: Array<{
|
|
113
|
-
orgId: string;
|
|
114
|
-
orgName: string;
|
|
115
|
-
integration: GithubIntegration;
|
|
116
|
-
}> = [];
|
|
117
|
-
|
|
118
|
-
for (const org of orgStatuses) {
|
|
119
|
-
for (const integration of org.integrations) {
|
|
120
|
-
allIntegrations.push({
|
|
121
|
-
orgId: org.id,
|
|
122
|
-
orgName: org.name,
|
|
123
|
-
integration,
|
|
124
|
-
});
|
|
85
|
+
if (!status.connected || !status.identity) {
|
|
86
|
+
if (!options.json) {
|
|
87
|
+
tui.newline();
|
|
88
|
+
tui.info('No GitHub identity connected.');
|
|
125
89
|
}
|
|
90
|
+
return { removed: false };
|
|
126
91
|
}
|
|
127
92
|
|
|
128
|
-
if (
|
|
93
|
+
if (status.installations.length === 0) {
|
|
129
94
|
if (!options.json) {
|
|
130
95
|
tui.newline();
|
|
131
|
-
tui.info('No
|
|
96
|
+
tui.info('No installations found.');
|
|
132
97
|
}
|
|
133
98
|
return { removed: false };
|
|
134
99
|
}
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
value: `${item.orgId}:${item.integration.id}`,
|
|
101
|
+
const choices = status.installations.map((inst: GithubInstallation) => ({
|
|
102
|
+
name: inst.installationId,
|
|
103
|
+
message: `${tui.bold(inst.accountName)} ${tui.muted(`(${inst.accountType})`)}`,
|
|
140
104
|
}));
|
|
141
105
|
|
|
142
|
-
|
|
106
|
+
tui.newline();
|
|
107
|
+
|
|
143
108
|
const response = await enquirer.prompt<{ selection: string }>({
|
|
144
109
|
type: 'select',
|
|
145
110
|
name: 'selection',
|
|
146
|
-
message: 'Select
|
|
111
|
+
message: 'Select an installation to remove',
|
|
147
112
|
choices,
|
|
148
|
-
result(name: string) {
|
|
149
|
-
// Return the value (IDs) instead of the display name
|
|
150
|
-
const choice = choices.find((c) => c.name === name);
|
|
151
|
-
return choice?.value ?? name;
|
|
152
|
-
},
|
|
153
113
|
});
|
|
154
114
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
logger.fatal('Invalid selection format');
|
|
158
|
-
}
|
|
159
|
-
const orgId = response.selection.slice(0, colonIdx);
|
|
160
|
-
const integrationId = response.selection.slice(colonIdx + 1);
|
|
161
|
-
const selected = allIntegrations.find(
|
|
162
|
-
(i) => i.orgId === orgId && i.integration.id === integrationId
|
|
115
|
+
const selected = status.installations.find(
|
|
116
|
+
(i: GithubInstallation) => i.installationId === response.selection
|
|
163
117
|
);
|
|
164
|
-
const displayName = selected
|
|
165
|
-
? `${tui.bold(selected.integration.githubAccountName)} from ${tui.bold(selected.orgName)}`
|
|
166
|
-
: response.selection;
|
|
118
|
+
const displayName = selected ? tui.bold(selected.accountName) : response.selection;
|
|
167
119
|
|
|
168
|
-
// Confirm
|
|
169
120
|
if (!opts.confirm) {
|
|
170
121
|
const confirmed = await tui.confirm(`Are you sure you want to remove ${displayName}?`);
|
|
171
122
|
|
|
@@ -177,9 +128,9 @@ export const removeSubcommand = createSubcommand({
|
|
|
177
128
|
}
|
|
178
129
|
|
|
179
130
|
await tui.spinner({
|
|
180
|
-
message: 'Removing GitHub
|
|
131
|
+
message: 'Removing GitHub installation...',
|
|
181
132
|
clearOnSuccess: true,
|
|
182
|
-
callback: () => disconnectGithubIntegration(apiClient,
|
|
133
|
+
callback: () => disconnectGithubIntegration(apiClient, response.selection),
|
|
183
134
|
});
|
|
184
135
|
|
|
185
136
|
if (!options.json) {
|
|
@@ -187,9 +138,8 @@ export const removeSubcommand = createSubcommand({
|
|
|
187
138
|
tui.success(`Removed ${displayName}`);
|
|
188
139
|
}
|
|
189
140
|
|
|
190
|
-
return { removed: true,
|
|
141
|
+
return { removed: true, installationId: response.selection };
|
|
191
142
|
} catch (error) {
|
|
192
|
-
// Handle user cancellation (Ctrl+C)
|
|
193
143
|
const isCancel =
|
|
194
144
|
error === '' ||
|
|
195
145
|
(error instanceof Error &&
|
|
@@ -203,7 +153,7 @@ export const removeSubcommand = createSubcommand({
|
|
|
203
153
|
|
|
204
154
|
logger.trace(error);
|
|
205
155
|
return logger.fatal(
|
|
206
|
-
'Failed to remove GitHub
|
|
156
|
+
'Failed to remove GitHub installation: %s',
|
|
207
157
|
error,
|
|
208
158
|
ErrorCode.INTEGRATION_FAILED
|
|
209
159
|
);
|
package/src/cmd/git/api.ts
CHANGED
|
@@ -1,40 +1,54 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StructuredError } from '@agentuity/core';
|
|
2
2
|
import { APIResponseSchema } from '@agentuity/server';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
import type { APIClient } from '../../api';
|
|
4
|
-
import { StructuredError } from '@agentuity/core';
|
|
5
5
|
|
|
6
6
|
const GithubStartDataSchema = z.object({
|
|
7
7
|
shortId: z.string(),
|
|
8
|
+
hasIdentity: z.boolean(),
|
|
8
9
|
});
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const GithubInstallationSchema = z.object({
|
|
12
|
+
installationId: z.string(),
|
|
13
|
+
integrationId: z.string().optional(),
|
|
14
|
+
accountName: z.string(),
|
|
15
|
+
accountType: z.enum(['User', 'Organization']),
|
|
16
|
+
avatarUrl: z.string().optional(),
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
const GithubStatusDataSchema = z.object({
|
|
19
20
|
connected: z.boolean(),
|
|
20
|
-
|
|
21
|
+
identity: z
|
|
22
|
+
.object({
|
|
23
|
+
githubUsername: z.string(),
|
|
24
|
+
githubEmail: z.string().optional(),
|
|
25
|
+
avatarUrl: z.string().optional(),
|
|
26
|
+
})
|
|
27
|
+
.nullable(),
|
|
28
|
+
installations: z.array(GithubInstallationSchema),
|
|
21
29
|
});
|
|
22
30
|
|
|
23
|
-
export interface
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
export interface GithubInstallation {
|
|
32
|
+
installationId: string;
|
|
33
|
+
integrationId?: string;
|
|
34
|
+
accountName: string;
|
|
35
|
+
accountType: 'User' | 'Organization';
|
|
36
|
+
avatarUrl?: string;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
export interface GithubIntegrationStartResult {
|
|
32
40
|
shortId: string;
|
|
41
|
+
hasIdentity: boolean;
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
export interface GithubIntegrationStatusResult {
|
|
36
45
|
connected: boolean;
|
|
37
|
-
|
|
46
|
+
identity: {
|
|
47
|
+
githubUsername: string;
|
|
48
|
+
githubEmail?: string;
|
|
49
|
+
avatarUrl?: string;
|
|
50
|
+
} | null;
|
|
51
|
+
installations: GithubInstallation[];
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
const GithubIntegrationStartError = StructuredError(
|
|
@@ -43,13 +57,9 @@ const GithubIntegrationStartError = StructuredError(
|
|
|
43
57
|
);
|
|
44
58
|
|
|
45
59
|
export async function startGithubIntegration(
|
|
46
|
-
apiClient: APIClient
|
|
47
|
-
orgId: string
|
|
60
|
+
apiClient: APIClient
|
|
48
61
|
): Promise<GithubIntegrationStartResult> {
|
|
49
|
-
const resp = await apiClient.get(
|
|
50
|
-
`/cli/github/start?orgId=${encodeURIComponent(orgId)}`,
|
|
51
|
-
APIResponseSchema(GithubStartDataSchema)
|
|
52
|
-
);
|
|
62
|
+
const resp = await apiClient.get('/cli/github/start', APIResponseSchema(GithubStartDataSchema));
|
|
53
63
|
|
|
54
64
|
if (!resp.success) {
|
|
55
65
|
throw new GithubIntegrationStartError();
|
|
@@ -59,7 +69,7 @@ export async function startGithubIntegration(
|
|
|
59
69
|
throw new GithubIntegrationStartError();
|
|
60
70
|
}
|
|
61
71
|
|
|
62
|
-
return { shortId: resp.data.shortId };
|
|
72
|
+
return { shortId: resp.data.shortId, hasIdentity: resp.data.hasIdentity };
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
const GithubIntegrationStatusError = StructuredError(
|
|
@@ -68,11 +78,10 @@ const GithubIntegrationStatusError = StructuredError(
|
|
|
68
78
|
);
|
|
69
79
|
|
|
70
80
|
export async function getGithubIntegrationStatus(
|
|
71
|
-
apiClient: APIClient
|
|
72
|
-
orgId: string
|
|
81
|
+
apiClient: APIClient
|
|
73
82
|
): Promise<GithubIntegrationStatusResult> {
|
|
74
83
|
const resp = await apiClient.get(
|
|
75
|
-
|
|
84
|
+
'/cli/github/status',
|
|
76
85
|
APIResponseSchema(GithubStatusDataSchema)
|
|
77
86
|
);
|
|
78
87
|
|
|
@@ -86,7 +95,8 @@ export async function getGithubIntegrationStatus(
|
|
|
86
95
|
|
|
87
96
|
return {
|
|
88
97
|
connected: resp.data.connected,
|
|
89
|
-
|
|
98
|
+
identity: resp.data.identity,
|
|
99
|
+
installations: resp.data.installations,
|
|
90
100
|
};
|
|
91
101
|
}
|
|
92
102
|
|
|
@@ -105,13 +115,13 @@ const GithubDisconnectError = StructuredError(
|
|
|
105
115
|
|
|
106
116
|
export async function disconnectGithubIntegration(
|
|
107
117
|
apiClient: APIClient,
|
|
108
|
-
|
|
109
|
-
integrationId: string
|
|
118
|
+
installationId?: string
|
|
110
119
|
): Promise<GithubDisconnectResult> {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
let url = '/cli/github/disconnect';
|
|
121
|
+
if (installationId) {
|
|
122
|
+
url += `?installationId=${encodeURIComponent(installationId)}`;
|
|
123
|
+
}
|
|
124
|
+
const resp = await apiClient.delete(url, APIResponseSchema(GithubDisconnectDataSchema));
|
|
115
125
|
|
|
116
126
|
if (!resp.success) {
|
|
117
127
|
throw new GithubDisconnectError();
|
|
@@ -124,76 +134,6 @@ export async function disconnectGithubIntegration(
|
|
|
124
134
|
return { disconnected: resp.data.disconnected };
|
|
125
135
|
}
|
|
126
136
|
|
|
127
|
-
// Existing integrations
|
|
128
|
-
|
|
129
|
-
const GithubExistingIntegrationSchema = z.object({
|
|
130
|
-
id: z.string(),
|
|
131
|
-
integrationId: z.string().nullable(),
|
|
132
|
-
orgId: z.string(),
|
|
133
|
-
orgName: z.string(),
|
|
134
|
-
githubAccountName: z.string(),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const GithubExistingDataSchema = z.object({
|
|
138
|
-
integrations: z.array(GithubExistingIntegrationSchema),
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
export interface ExistingGithubIntegration {
|
|
142
|
-
id: string;
|
|
143
|
-
integrationId: string | null;
|
|
144
|
-
orgId: string;
|
|
145
|
-
orgName: string;
|
|
146
|
-
githubAccountName: string;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const GithubExistingError = StructuredError(
|
|
150
|
-
'GithubExistingError',
|
|
151
|
-
'Error fetching existing GitHub integrations'
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
export async function getExistingGithubIntegrations(
|
|
155
|
-
apiClient: APIClient,
|
|
156
|
-
excludeOrgId?: string
|
|
157
|
-
): Promise<ExistingGithubIntegration[]> {
|
|
158
|
-
const query = excludeOrgId ? `?excludeOrgId=${encodeURIComponent(excludeOrgId)}` : '';
|
|
159
|
-
const resp = await apiClient.get(
|
|
160
|
-
`/cli/github/existing${query}`,
|
|
161
|
-
APIResponseSchema(GithubExistingDataSchema)
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
if (!resp.success || !resp.data) {
|
|
165
|
-
throw new GithubExistingError();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return resp.data.integrations;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Copy integration
|
|
172
|
-
|
|
173
|
-
const GithubCopyDataSchema = z.object({
|
|
174
|
-
copied: z.boolean(),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const GithubCopyError = StructuredError('GithubCopyError', 'Error copying GitHub integration');
|
|
178
|
-
|
|
179
|
-
export async function copyGithubIntegration(
|
|
180
|
-
apiClient: APIClient,
|
|
181
|
-
fromOrgId: string,
|
|
182
|
-
toOrgId: string
|
|
183
|
-
): Promise<boolean> {
|
|
184
|
-
const resp = await apiClient.post(
|
|
185
|
-
'/cli/github/copy',
|
|
186
|
-
{ fromOrgId, toOrgId },
|
|
187
|
-
APIResponseSchema(GithubCopyDataSchema)
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
if (!resp.success || !resp.data) {
|
|
191
|
-
throw new GithubCopyError();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return resp.data.copied;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
137
|
// Polling
|
|
198
138
|
|
|
199
139
|
const PollForGithubIntegrationError = StructuredError('PollForGithubIntegrationError');
|
|
@@ -204,7 +144,6 @@ const PollForGithubIntegrationTimeout = StructuredError(
|
|
|
204
144
|
|
|
205
145
|
export async function pollForGithubIntegration(
|
|
206
146
|
apiClient: APIClient,
|
|
207
|
-
orgId: string,
|
|
208
147
|
initialCount: number,
|
|
209
148
|
timeoutMs = 600000 // 10 minutes
|
|
210
149
|
): Promise<GithubIntegrationStatusResult> {
|
|
@@ -214,7 +153,7 @@ export async function pollForGithubIntegration(
|
|
|
214
153
|
|
|
215
154
|
while (Date.now() - started < timeoutMs) {
|
|
216
155
|
const resp = await apiClient.get(
|
|
217
|
-
|
|
156
|
+
'/cli/github/status',
|
|
218
157
|
APIResponseSchema(GithubStatusDataSchema)
|
|
219
158
|
);
|
|
220
159
|
|
|
@@ -222,11 +161,12 @@ export async function pollForGithubIntegration(
|
|
|
222
161
|
throw new PollForGithubIntegrationError();
|
|
223
162
|
}
|
|
224
163
|
|
|
225
|
-
const currentCount = resp.data.
|
|
164
|
+
const currentCount = resp.data.installations?.length ?? 0;
|
|
226
165
|
if (currentCount > initialCount) {
|
|
227
166
|
return {
|
|
228
167
|
connected: true,
|
|
229
|
-
|
|
168
|
+
identity: resp.data.identity,
|
|
169
|
+
installations: resp.data.installations,
|
|
230
170
|
};
|
|
231
171
|
}
|
|
232
172
|
|
|
@@ -265,12 +205,11 @@ const GithubReposError = StructuredError('GithubReposError', 'Error fetching Git
|
|
|
265
205
|
|
|
266
206
|
export async function listGithubRepos(
|
|
267
207
|
apiClient: APIClient,
|
|
268
|
-
orgId: string,
|
|
269
208
|
integrationId?: string
|
|
270
209
|
): Promise<GithubRepo[]> {
|
|
271
|
-
let url =
|
|
210
|
+
let url = '/cli/github/repos';
|
|
272
211
|
if (integrationId) {
|
|
273
|
-
url +=
|
|
212
|
+
url += `?integrationId=${encodeURIComponent(integrationId)}`;
|
|
274
213
|
}
|
|
275
214
|
const resp = await apiClient.get(url, APIResponseSchema(GithubReposDataSchema));
|
|
276
215
|
|
|
@@ -292,7 +231,6 @@ export interface LinkProjectOptions {
|
|
|
292
231
|
autoDeploy: boolean;
|
|
293
232
|
previewDeploy: boolean;
|
|
294
233
|
directory?: string;
|
|
295
|
-
integrationId?: string;
|
|
296
234
|
}
|
|
297
235
|
|
|
298
236
|
const ProjectLinkError = StructuredError('ProjectLinkError', 'Error linking project to repository');
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { Logger } from '@agentuity/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { APIClient } from '../../../api';
|
|
4
|
+
import { getAPIBaseURL } from '../../../api';
|
|
5
|
+
import { getCommand } from '../../../command-prefix';
|
|
6
|
+
import { ErrorCode } from '../../../errors';
|
|
7
|
+
import * as tui from '../../../tui';
|
|
8
|
+
import type { Config } from '../../../types';
|
|
9
|
+
import { createSubcommand } from '../../../types';
|
|
10
|
+
import {
|
|
11
|
+
getGithubIntegrationStatus,
|
|
12
|
+
pollForGithubIntegration,
|
|
13
|
+
startGithubIntegration,
|
|
14
|
+
} from '../api';
|
|
15
|
+
|
|
16
|
+
export interface RunGitIdentityConnectOptions {
|
|
17
|
+
apiClient: APIClient;
|
|
18
|
+
logger: Logger;
|
|
19
|
+
config?: Config | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RunGitIdentityConnectResult {
|
|
23
|
+
connected: boolean;
|
|
24
|
+
cancelled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runGitIdentityConnect(
|
|
28
|
+
options: RunGitIdentityConnectOptions
|
|
29
|
+
): Promise<RunGitIdentityConnectResult> {
|
|
30
|
+
const { apiClient, logger, config } = options;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const currentStatus = await getGithubIntegrationStatus(apiClient);
|
|
34
|
+
const initialCount = currentStatus.installations?.length ?? 0;
|
|
35
|
+
|
|
36
|
+
if (currentStatus.connected && currentStatus.identity) {
|
|
37
|
+
tui.newline();
|
|
38
|
+
tui.info(
|
|
39
|
+
`Already connected as ${tui.bold(currentStatus.identity.githubUsername)}. Use ${tui.bold('agentuity git account add')} to install the GitHub App on additional accounts.`
|
|
40
|
+
);
|
|
41
|
+
tui.newline();
|
|
42
|
+
return { connected: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const startResult = await tui.spinner({
|
|
46
|
+
message: 'Getting GitHub authorization URL...',
|
|
47
|
+
clearOnSuccess: true,
|
|
48
|
+
callback: () => startGithubIntegration(apiClient),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!startResult) {
|
|
52
|
+
tui.error('Failed to start GitHub authorization');
|
|
53
|
+
return { connected: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { shortId } = startResult;
|
|
57
|
+
const apiBaseUrl = getAPIBaseURL(config);
|
|
58
|
+
const url = `${apiBaseUrl}/github/connect/${shortId}`;
|
|
59
|
+
|
|
60
|
+
const copied = await tui.copyToClipboard(url);
|
|
61
|
+
|
|
62
|
+
tui.newline();
|
|
63
|
+
if (copied) {
|
|
64
|
+
tui.output('GitHub authorization URL copied to clipboard! Open it in your browser:');
|
|
65
|
+
} else {
|
|
66
|
+
tui.output('Open this URL in your browser to authorize GitHub access:');
|
|
67
|
+
}
|
|
68
|
+
tui.newline();
|
|
69
|
+
tui.output(` ${tui.link(url)}`);
|
|
70
|
+
tui.newline();
|
|
71
|
+
tui.output(tui.muted('Press Enter to open in your browser, or Ctrl+C to cancel'));
|
|
72
|
+
tui.newline();
|
|
73
|
+
|
|
74
|
+
const result = await tui.spinner({
|
|
75
|
+
type: 'countdown',
|
|
76
|
+
message: 'Waiting for GitHub authorization',
|
|
77
|
+
timeoutMs: 600000,
|
|
78
|
+
clearOnSuccess: true,
|
|
79
|
+
onEnterPress: () => {
|
|
80
|
+
const platform = process.platform;
|
|
81
|
+
if (platform === 'win32') {
|
|
82
|
+
Bun.spawn(['cmd', '/c', 'start', '', url], {
|
|
83
|
+
stdout: 'ignore',
|
|
84
|
+
stderr: 'ignore',
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
const command = platform === 'darwin' ? 'open' : 'xdg-open';
|
|
88
|
+
Bun.spawn([command, url], { stdout: 'ignore', stderr: 'ignore' });
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
callback: async () => {
|
|
92
|
+
return await pollForGithubIntegration(apiClient, initialCount);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
tui.newline();
|
|
97
|
+
if (result.connected) {
|
|
98
|
+
const username = result.identity?.githubUsername;
|
|
99
|
+
if (username) {
|
|
100
|
+
tui.success(`GitHub identity connected as ${tui.bold(username)}`);
|
|
101
|
+
} else {
|
|
102
|
+
tui.success('GitHub identity connected');
|
|
103
|
+
}
|
|
104
|
+
return { connected: true };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { connected: false };
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const isCancel =
|
|
110
|
+
error === '' ||
|
|
111
|
+
(error instanceof Error && (error.message === '' || error.message === 'User cancelled'));
|
|
112
|
+
|
|
113
|
+
if (isCancel) {
|
|
114
|
+
tui.newline();
|
|
115
|
+
tui.info('Cancelled');
|
|
116
|
+
return { connected: false, cancelled: true };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
logger.trace(error);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ConnectOptionsSchema = z.object({});
|
|
125
|
+
|
|
126
|
+
const ConnectResponseSchema = z.object({
|
|
127
|
+
connected: z.boolean().describe('Whether the identity was connected'),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export const connectSubcommand = createSubcommand({
|
|
131
|
+
name: 'connect',
|
|
132
|
+
description: 'Connect your GitHub identity',
|
|
133
|
+
tags: ['mutating', 'creates-resource', 'slow', 'api-intensive'],
|
|
134
|
+
idempotent: false,
|
|
135
|
+
requires: { auth: true, apiClient: true },
|
|
136
|
+
schema: {
|
|
137
|
+
options: ConnectOptionsSchema,
|
|
138
|
+
response: ConnectResponseSchema,
|
|
139
|
+
},
|
|
140
|
+
examples: [
|
|
141
|
+
{
|
|
142
|
+
command: getCommand('git identity connect'),
|
|
143
|
+
description: 'Connect your GitHub identity',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
|
|
147
|
+
async handler(ctx) {
|
|
148
|
+
const { logger, apiClient, config } = ctx;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const result = await runGitIdentityConnect({
|
|
152
|
+
apiClient,
|
|
153
|
+
logger,
|
|
154
|
+
config,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return { connected: result.connected };
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const isCancel =
|
|
160
|
+
error === '' ||
|
|
161
|
+
(error instanceof Error &&
|
|
162
|
+
(error.message === '' || error.message === 'User cancelled'));
|
|
163
|
+
|
|
164
|
+
if (isCancel) {
|
|
165
|
+
tui.newline();
|
|
166
|
+
tui.info('Cancelled');
|
|
167
|
+
return { connected: false };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
logger.trace(error);
|
|
171
|
+
return logger.fatal(
|
|
172
|
+
'Failed to connect GitHub identity: %s',
|
|
173
|
+
error,
|
|
174
|
+
ErrorCode.INTEGRATION_FAILED
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
});
|