@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.
Files changed (135) hide show
  1. package/bin/cli.ts +8 -0
  2. package/dist/agent-detection.js +2 -2
  3. package/dist/agent-detection.js.map +1 -1
  4. package/dist/banner.js +2 -2
  5. package/dist/banner.js.map +1 -1
  6. package/dist/bun-path.d.ts.map +1 -1
  7. package/dist/bun-path.js +2 -1
  8. package/dist/bun-path.js.map +1 -1
  9. package/dist/cmd/build/ast.d.ts.map +1 -1
  10. package/dist/cmd/build/ast.js +87 -14
  11. package/dist/cmd/build/ast.js.map +1 -1
  12. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  13. package/dist/cmd/build/vite/agent-discovery.js +3 -2
  14. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  15. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  16. package/dist/cmd/build/vite/metadata-generator.js +2 -1
  17. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  18. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  19. package/dist/cmd/build/vite/registry-generator.js +9 -7
  20. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  21. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  22. package/dist/cmd/build/vite/route-discovery.js +3 -2
  23. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  24. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  25. package/dist/cmd/cloud/deploy.js +19 -21
  26. package/dist/cmd/cloud/deploy.js.map +1 -1
  27. package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
  28. package/dist/cmd/cloud/env/delete.js +3 -26
  29. package/dist/cmd/cloud/env/delete.js.map +1 -1
  30. package/dist/cmd/cloud/env/import.d.ts.map +1 -1
  31. package/dist/cmd/cloud/env/import.js +3 -16
  32. package/dist/cmd/cloud/env/import.js.map +1 -1
  33. package/dist/cmd/cloud/env/set.d.ts.map +1 -1
  34. package/dist/cmd/cloud/env/set.js +3 -19
  35. package/dist/cmd/cloud/env/set.js.map +1 -1
  36. package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -1
  37. package/dist/cmd/cloud/sandbox/cp.js +2 -1
  38. package/dist/cmd/cloud/sandbox/cp.js.map +1 -1
  39. package/dist/cmd/git/account/add.d.ts +6 -8
  40. package/dist/cmd/git/account/add.d.ts.map +1 -1
  41. package/dist/cmd/git/account/add.js +37 -151
  42. package/dist/cmd/git/account/add.js.map +1 -1
  43. package/dist/cmd/git/account/index.d.ts +0 -1
  44. package/dist/cmd/git/account/index.d.ts.map +1 -1
  45. package/dist/cmd/git/account/index.js +3 -4
  46. package/dist/cmd/git/account/index.js.map +1 -1
  47. package/dist/cmd/git/account/list.d.ts.map +1 -1
  48. package/dist/cmd/git/account/list.js +35 -67
  49. package/dist/cmd/git/account/list.js.map +1 -1
  50. package/dist/cmd/git/account/remove.d.ts.map +1 -1
  51. package/dist/cmd/git/account/remove.js +42 -84
  52. package/dist/cmd/git/account/remove.js.map +1 -1
  53. package/dist/cmd/git/api.d.ts +19 -23
  54. package/dist/cmd/git/api.d.ts.map +1 -1
  55. package/dist/cmd/git/api.js +38 -56
  56. package/dist/cmd/git/api.js.map +1 -1
  57. package/dist/cmd/git/identity/connect.d.ts +15 -0
  58. package/dist/cmd/git/identity/connect.d.ts.map +1 -0
  59. package/dist/cmd/git/identity/connect.js +135 -0
  60. package/dist/cmd/git/identity/connect.js.map +1 -0
  61. package/dist/cmd/git/identity/disconnect.d.ts +2 -0
  62. package/dist/cmd/git/identity/disconnect.d.ts.map +1 -0
  63. package/dist/cmd/git/identity/disconnect.js +83 -0
  64. package/dist/cmd/git/identity/disconnect.js.map +1 -0
  65. package/dist/cmd/git/identity/index.d.ts +2 -0
  66. package/dist/cmd/git/identity/index.d.ts.map +1 -0
  67. package/dist/cmd/git/identity/index.js +10 -0
  68. package/dist/cmd/git/identity/index.js.map +1 -0
  69. package/dist/cmd/git/identity/status.d.ts +2 -0
  70. package/dist/cmd/git/identity/status.d.ts.map +1 -0
  71. package/dist/cmd/git/identity/status.js +77 -0
  72. package/dist/cmd/git/identity/status.js.map +1 -0
  73. package/dist/cmd/git/index.d.ts +0 -1
  74. package/dist/cmd/git/index.d.ts.map +1 -1
  75. package/dist/cmd/git/index.js +3 -2
  76. package/dist/cmd/git/index.js.map +1 -1
  77. package/dist/cmd/git/link.d.ts +2 -3
  78. package/dist/cmd/git/link.d.ts.map +1 -1
  79. package/dist/cmd/git/link.js +22 -28
  80. package/dist/cmd/git/link.js.map +1 -1
  81. package/dist/cmd/git/list.d.ts.map +1 -1
  82. package/dist/cmd/git/list.js +42 -55
  83. package/dist/cmd/git/list.js.map +1 -1
  84. package/dist/cmd/git/status.d.ts.map +1 -1
  85. package/dist/cmd/git/status.js +51 -38
  86. package/dist/cmd/git/status.js.map +1 -1
  87. package/dist/cmd/upgrade/npm-availability.d.ts +6 -7
  88. package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -1
  89. package/dist/cmd/upgrade/npm-availability.js +9 -18
  90. package/dist/cmd/upgrade/npm-availability.js.map +1 -1
  91. package/dist/config.d.ts.map +1 -1
  92. package/dist/config.js +14 -4
  93. package/dist/config.js.map +1 -1
  94. package/dist/utils/detectSubagent.d.ts.map +1 -1
  95. package/dist/utils/detectSubagent.js +3 -0
  96. package/dist/utils/detectSubagent.js.map +1 -1
  97. package/dist/utils/normalize-path.d.ts +11 -0
  98. package/dist/utils/normalize-path.d.ts.map +1 -0
  99. package/dist/utils/normalize-path.js +13 -0
  100. package/dist/utils/normalize-path.js.map +1 -0
  101. package/dist/utils/zip.d.ts.map +1 -1
  102. package/dist/utils/zip.js +2 -1
  103. package/dist/utils/zip.js.map +1 -1
  104. package/package.json +6 -6
  105. package/src/agent-detection.ts +2 -2
  106. package/src/banner.ts +2 -2
  107. package/src/bun-path.ts +2 -1
  108. package/src/cmd/build/ast.ts +96 -15
  109. package/src/cmd/build/vite/agent-discovery.ts +3 -2
  110. package/src/cmd/build/vite/metadata-generator.ts +2 -1
  111. package/src/cmd/build/vite/registry-generator.ts +9 -7
  112. package/src/cmd/build/vite/route-discovery.ts +3 -2
  113. package/src/cmd/cloud/deploy.ts +54 -61
  114. package/src/cmd/cloud/env/delete.ts +3 -34
  115. package/src/cmd/cloud/env/import.ts +2 -18
  116. package/src/cmd/cloud/env/set.ts +2 -21
  117. package/src/cmd/cloud/sandbox/cp.ts +2 -1
  118. package/src/cmd/git/account/add.ts +51 -190
  119. package/src/cmd/git/account/index.ts +3 -5
  120. package/src/cmd/git/account/list.ts +51 -82
  121. package/src/cmd/git/account/remove.ts +45 -95
  122. package/src/cmd/git/api.ts +49 -111
  123. package/src/cmd/git/identity/connect.ts +178 -0
  124. package/src/cmd/git/identity/disconnect.ts +103 -0
  125. package/src/cmd/git/identity/index.ts +10 -0
  126. package/src/cmd/git/identity/status.ts +96 -0
  127. package/src/cmd/git/index.ts +3 -3
  128. package/src/cmd/git/link.ts +32 -35
  129. package/src/cmd/git/list.ts +48 -59
  130. package/src/cmd/git/status.ts +55 -40
  131. package/src/cmd/upgrade/npm-availability.ts +14 -23
  132. package/src/config.ts +14 -5
  133. package/src/utils/detectSubagent.ts +5 -0
  134. package/src/utils/normalize-path.ts +12 -0
  135. 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 GithubIntegration,
9
+ type GithubInstallation,
10
+ getGithubIntegrationStatus,
12
11
  } from '../api';
13
12
 
14
13
  const RemoveOptionsSchema = z.object({
15
- org: z.string().optional().describe('Organization ID'),
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 account was removed'),
22
- orgId: z.string().optional().describe('Organization ID'),
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 account from your organization',
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 account from your organization',
36
+ description: 'Remove a GitHub App installation',
40
37
  },
41
38
  {
42
- command: getCommand('git account remove --org org_abc --account int_xyz --confirm'),
43
- description: 'Remove a specific account without prompts',
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 --org org_abc --account int_xyz --confirm'),
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 both org and account provided, skip interactive flow
56
- if (opts.org && opts.account) {
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
- `Are you sure you want to remove this GitHub account?`
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 account...',
65
+ message: 'Removing GitHub installation...',
69
66
  clearOnSuccess: true,
70
- callback: () => disconnectGithubIntegration(apiClient, opts.org!, opts.account!),
67
+ callback: () => disconnectGithubIntegration(apiClient, opts.account!),
71
68
  });
72
69
 
73
70
  if (!options.json) {
74
71
  tui.newline();
75
- tui.success('Removed GitHub account');
72
+ tui.success('Removed GitHub installation');
76
73
  }
77
74
 
78
- return { removed: true, orgId: opts.org, integrationId: opts.account };
75
+ return { removed: true, installationId: opts.account };
79
76
  }
80
77
 
81
- // Fetch organizations
82
- const orgs = await tui.spinner({
83
- message: 'Fetching organizations...',
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: () => listOrganizations(apiClient),
82
+ callback: () => getGithubIntegrationStatus(apiClient),
86
83
  });
87
84
 
88
- if (orgs.length === 0) {
89
- tui.fatal('No organizations found for your account');
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 (allIntegrations.length === 0) {
93
+ if (status.installations.length === 0) {
129
94
  if (!options.json) {
130
95
  tui.newline();
131
- tui.info('No GitHub accounts are connected.');
96
+ tui.info('No installations found.');
132
97
  }
133
98
  return { removed: false };
134
99
  }
135
100
 
136
- // Build choices showing GitHub account and org
137
- const choices = allIntegrations.map((item) => ({
138
- name: `${tui.bold(item.integration.githubAccountName)} ${tui.muted(`(${item.integration.githubAccountType})`)} → ${tui.bold(item.orgName)}`,
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
- // Show picker
106
+ tui.newline();
107
+
143
108
  const response = await enquirer.prompt<{ selection: string }>({
144
109
  type: 'select',
145
110
  name: 'selection',
146
- message: 'Select a GitHub account to remove',
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 colonIdx = response.selection.indexOf(':');
156
- if (colonIdx === -1) {
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 account...',
131
+ message: 'Removing GitHub installation...',
181
132
  clearOnSuccess: true,
182
- callback: () => disconnectGithubIntegration(apiClient, orgId, integrationId),
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, orgId, integrationId };
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 account: %s',
156
+ 'Failed to remove GitHub installation: %s',
207
157
  error,
208
158
  ErrorCode.INTEGRATION_FAILED
209
159
  );
@@ -1,40 +1,54 @@
1
- import { z } from 'zod';
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 GithubIntegrationSchema = z.object({
11
- id: z.string(),
12
- githubAccountName: z.string(),
13
- githubAccountType: z.enum(['user', 'org']),
14
- connectedBy: z.string(),
15
- connectedAt: z.string(),
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
- integrations: z.array(GithubIntegrationSchema).optional(),
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 GithubIntegration {
24
- id: string;
25
- githubAccountName: string;
26
- githubAccountType: 'user' | 'org';
27
- connectedBy: string;
28
- connectedAt: string;
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
- integrations: GithubIntegration[];
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
- `/cli/github/status?orgId=${encodeURIComponent(orgId)}`,
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
- integrations: resp.data.integrations ?? [],
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
- orgId: string,
109
- integrationId: string
118
+ installationId?: string
110
119
  ): Promise<GithubDisconnectResult> {
111
- const resp = await apiClient.delete(
112
- `/cli/github/disconnect?orgId=${encodeURIComponent(orgId)}&integrationId=${encodeURIComponent(integrationId)}`,
113
- APIResponseSchema(GithubDisconnectDataSchema)
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
- `/cli/github/status?orgId=${encodeURIComponent(orgId)}`,
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.integrations?.length ?? 0;
164
+ const currentCount = resp.data.installations?.length ?? 0;
226
165
  if (currentCount > initialCount) {
227
166
  return {
228
167
  connected: true,
229
- integrations: resp.data.integrations ?? [],
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 = `/cli/github/repos?orgId=${encodeURIComponent(orgId)}`;
210
+ let url = '/cli/github/repos';
272
211
  if (integrationId) {
273
- url += `&integrationId=${encodeURIComponent(integrationId)}`;
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
+ });