@agentuity/cli 0.0.109 → 0.0.111

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 (253) hide show
  1. package/dist/build-report.d.ts +201 -0
  2. package/dist/build-report.d.ts.map +1 -0
  3. package/dist/build-report.js +335 -0
  4. package/dist/build-report.js.map +1 -0
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +19 -4
  7. package/dist/cli.js.map +1 -1
  8. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  9. package/dist/cmd/build/entry-generator.js +3 -1
  10. package/dist/cmd/build/entry-generator.js.map +1 -1
  11. package/dist/cmd/build/index.d.ts.map +1 -1
  12. package/dist/cmd/build/index.js +44 -1
  13. package/dist/cmd/build/index.js.map +1 -1
  14. package/dist/cmd/build/typecheck.d.ts +7 -1
  15. package/dist/cmd/build/typecheck.d.ts.map +1 -1
  16. package/dist/cmd/build/typecheck.js +11 -1
  17. package/dist/cmd/build/typecheck.js.map +1 -1
  18. package/dist/cmd/build/vite/agent-discovery.d.ts +1 -1
  19. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  20. package/dist/cmd/build/vite/agent-discovery.js +3 -3
  21. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  22. package/dist/cmd/build/vite/index.d.ts +2 -1
  23. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  24. package/dist/cmd/build/vite/index.js +3 -2
  25. package/dist/cmd/build/vite/index.js.map +1 -1
  26. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  27. package/dist/cmd/build/vite/metadata-generator.js +3 -5
  28. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  29. package/dist/cmd/build/vite/registry-generator.d.ts +1 -1
  30. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  31. package/dist/cmd/build/vite/registry-generator.js +126 -41
  32. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  33. package/dist/cmd/build/vite/route-discovery.d.ts +6 -0
  34. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  35. package/dist/cmd/build/vite/route-discovery.js +19 -0
  36. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  37. package/dist/cmd/build/vite/vite-builder.d.ts +3 -0
  38. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  39. package/dist/cmd/build/vite/vite-builder.js +10 -2
  40. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  41. package/dist/cmd/build/vite-bundler.d.ts +3 -0
  42. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  43. package/dist/cmd/build/vite-bundler.js +14 -5
  44. package/dist/cmd/build/vite-bundler.js.map +1 -1
  45. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  46. package/dist/cmd/cloud/deploy.js +149 -10
  47. package/dist/cmd/cloud/deploy.js.map +1 -1
  48. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  49. package/dist/cmd/cloud/deployment/show.js +0 -1
  50. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  51. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
  52. package/dist/cmd/cloud/sandbox/create.js +18 -0
  53. package/dist/cmd/cloud/sandbox/create.js.map +1 -1
  54. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
  55. package/dist/cmd/cloud/sandbox/delete.js +2 -6
  56. package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
  57. package/dist/cmd/cloud/sandbox/download.d.ts +3 -0
  58. package/dist/cmd/cloud/sandbox/download.d.ts.map +1 -0
  59. package/dist/cmd/cloud/sandbox/download.js +89 -0
  60. package/dist/cmd/cloud/sandbox/download.js.map +1 -0
  61. package/dist/cmd/cloud/sandbox/env.d.ts +3 -0
  62. package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -0
  63. package/dist/cmd/cloud/sandbox/env.js +90 -0
  64. package/dist/cmd/cloud/sandbox/env.js.map +1 -0
  65. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  66. package/dist/cmd/cloud/sandbox/get.js +5 -0
  67. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  68. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
  69. package/dist/cmd/cloud/sandbox/index.js +14 -0
  70. package/dist/cmd/cloud/sandbox/index.js.map +1 -1
  71. package/dist/cmd/cloud/sandbox/ls.d.ts +3 -0
  72. package/dist/cmd/cloud/sandbox/ls.d.ts.map +1 -0
  73. package/dist/cmd/cloud/sandbox/ls.js +119 -0
  74. package/dist/cmd/cloud/sandbox/ls.js.map +1 -0
  75. package/dist/cmd/cloud/sandbox/mkdir.d.ts +3 -0
  76. package/dist/cmd/cloud/sandbox/mkdir.d.ts.map +1 -0
  77. package/dist/cmd/cloud/sandbox/mkdir.js +59 -0
  78. package/dist/cmd/cloud/sandbox/mkdir.js.map +1 -0
  79. package/dist/cmd/cloud/sandbox/rm.d.ts +3 -0
  80. package/dist/cmd/cloud/sandbox/rm.d.ts.map +1 -0
  81. package/dist/cmd/cloud/sandbox/rm.js +45 -0
  82. package/dist/cmd/cloud/sandbox/rm.js.map +1 -0
  83. package/dist/cmd/cloud/sandbox/rmdir.d.ts +3 -0
  84. package/dist/cmd/cloud/sandbox/rmdir.d.ts.map +1 -0
  85. package/dist/cmd/cloud/sandbox/rmdir.js +59 -0
  86. package/dist/cmd/cloud/sandbox/rmdir.js.map +1 -0
  87. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
  88. package/dist/cmd/cloud/sandbox/snapshot/create.js +0 -2
  89. package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
  90. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -1
  91. package/dist/cmd/cloud/sandbox/snapshot/get.js +0 -2
  92. package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -1
  93. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
  94. package/dist/cmd/cloud/sandbox/snapshot/list.js +0 -3
  95. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
  96. package/dist/cmd/cloud/sandbox/upload.d.ts +3 -0
  97. package/dist/cmd/cloud/sandbox/upload.d.ts.map +1 -0
  98. package/dist/cmd/cloud/sandbox/upload.js +77 -0
  99. package/dist/cmd/cloud/sandbox/upload.js.map +1 -0
  100. package/dist/cmd/dev/index.d.ts.map +1 -1
  101. package/dist/cmd/dev/index.js +126 -23
  102. package/dist/cmd/dev/index.js.map +1 -1
  103. package/dist/cmd/dev/sync.d.ts.map +1 -1
  104. package/dist/cmd/dev/sync.js +8 -14
  105. package/dist/cmd/dev/sync.js.map +1 -1
  106. package/dist/cmd/git/account/add.d.ts +17 -0
  107. package/dist/cmd/git/account/add.d.ts.map +1 -0
  108. package/dist/cmd/git/account/add.js +244 -0
  109. package/dist/cmd/git/account/add.js.map +1 -0
  110. package/dist/cmd/git/account/index.d.ts +3 -0
  111. package/dist/cmd/git/account/index.d.ts.map +1 -0
  112. package/dist/cmd/git/account/index.js +11 -0
  113. package/dist/cmd/git/account/index.js.map +1 -0
  114. package/dist/cmd/git/account/list.d.ts +2 -0
  115. package/dist/cmd/git/account/list.d.ts.map +1 -0
  116. package/dist/cmd/git/account/list.js +111 -0
  117. package/dist/cmd/git/account/list.js.map +1 -0
  118. package/dist/cmd/git/account/remove.d.ts +2 -0
  119. package/dist/cmd/git/account/remove.d.ts.map +1 -0
  120. package/dist/cmd/git/account/remove.js +171 -0
  121. package/dist/cmd/git/account/remove.js.map +1 -0
  122. package/dist/cmd/git/index.d.ts +3 -0
  123. package/dist/cmd/git/index.d.ts.map +1 -0
  124. package/dist/cmd/git/index.js +19 -0
  125. package/dist/cmd/git/index.js.map +1 -0
  126. package/dist/cmd/git/link.d.ts +32 -0
  127. package/dist/cmd/git/link.d.ts.map +1 -0
  128. package/dist/cmd/git/link.js +357 -0
  129. package/dist/cmd/git/link.js.map +1 -0
  130. package/dist/cmd/git/list.d.ts +2 -0
  131. package/dist/cmd/git/list.d.ts.map +1 -0
  132. package/dist/cmd/git/list.js +137 -0
  133. package/dist/cmd/git/list.js.map +1 -0
  134. package/dist/cmd/git/status.d.ts +2 -0
  135. package/dist/cmd/git/status.d.ts.map +1 -0
  136. package/dist/cmd/git/status.js +119 -0
  137. package/dist/cmd/git/status.js.map +1 -0
  138. package/dist/cmd/git/unlink.d.ts +2 -0
  139. package/dist/cmd/git/unlink.d.ts.map +1 -0
  140. package/dist/cmd/git/unlink.js +98 -0
  141. package/dist/cmd/git/unlink.js.map +1 -0
  142. package/dist/cmd/index.d.ts.map +1 -1
  143. package/dist/cmd/index.js +2 -0
  144. package/dist/cmd/index.js.map +1 -1
  145. package/dist/cmd/integration/api.d.ts +61 -0
  146. package/dist/cmd/integration/api.d.ts.map +1 -0
  147. package/dist/cmd/integration/api.js +176 -0
  148. package/dist/cmd/integration/api.js.map +1 -0
  149. package/dist/cmd/integration/github/connect.d.ts +2 -0
  150. package/dist/cmd/integration/github/connect.d.ts.map +1 -0
  151. package/dist/cmd/integration/github/connect.js +197 -0
  152. package/dist/cmd/integration/github/connect.js.map +1 -0
  153. package/dist/cmd/integration/github/disconnect.d.ts +2 -0
  154. package/dist/cmd/integration/github/disconnect.d.ts.map +1 -0
  155. package/dist/cmd/integration/github/disconnect.js +121 -0
  156. package/dist/cmd/integration/github/disconnect.js.map +1 -0
  157. package/dist/cmd/integration/github/index.d.ts +2 -0
  158. package/dist/cmd/integration/github/index.d.ts.map +1 -0
  159. package/dist/cmd/integration/github/index.js +21 -0
  160. package/dist/cmd/integration/github/index.js.map +1 -0
  161. package/dist/cmd/integration/index.d.ts +2 -0
  162. package/dist/cmd/integration/index.d.ts.map +1 -0
  163. package/dist/cmd/integration/index.js +16 -0
  164. package/dist/cmd/integration/index.js.map +1 -0
  165. package/dist/cmd/project/auth/generate.d.ts +5 -0
  166. package/dist/cmd/project/auth/generate.d.ts.map +1 -0
  167. package/dist/cmd/project/auth/generate.js +102 -0
  168. package/dist/cmd/project/auth/generate.js.map +1 -0
  169. package/dist/cmd/project/auth/index.d.ts +2 -0
  170. package/dist/cmd/project/auth/index.d.ts.map +1 -0
  171. package/dist/cmd/project/auth/index.js +21 -0
  172. package/dist/cmd/project/auth/index.js.map +1 -0
  173. package/dist/cmd/project/auth/init.d.ts +2 -0
  174. package/dist/cmd/project/auth/init.d.ts.map +1 -0
  175. package/dist/cmd/project/auth/init.js +220 -0
  176. package/dist/cmd/project/auth/init.js.map +1 -0
  177. package/dist/cmd/project/auth/shared.d.ts +88 -0
  178. package/dist/cmd/project/auth/shared.d.ts.map +1 -0
  179. package/dist/cmd/project/auth/shared.js +435 -0
  180. package/dist/cmd/project/auth/shared.js.map +1 -0
  181. package/dist/cmd/project/index.d.ts.map +1 -1
  182. package/dist/cmd/project/index.js +9 -1
  183. package/dist/cmd/project/index.js.map +1 -1
  184. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  185. package/dist/cmd/project/template-flow.js +106 -0
  186. package/dist/cmd/project/template-flow.js.map +1 -1
  187. package/dist/config.d.ts +2 -0
  188. package/dist/config.d.ts.map +1 -1
  189. package/dist/config.js +24 -0
  190. package/dist/config.js.map +1 -1
  191. package/dist/errors.d.ts +2 -1
  192. package/dist/errors.d.ts.map +1 -1
  193. package/dist/errors.js +5 -0
  194. package/dist/errors.js.map +1 -1
  195. package/dist/types.d.ts +3 -4
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/types.js +5 -2
  198. package/dist/types.js.map +1 -1
  199. package/package.json +6 -5
  200. package/src/build-report.ts +457 -0
  201. package/src/cli.ts +20 -4
  202. package/src/cmd/build/entry-generator.ts +3 -1
  203. package/src/cmd/build/index.ts +51 -1
  204. package/src/cmd/build/typecheck.ts +19 -1
  205. package/src/cmd/build/vite/agent-discovery.ts +4 -4
  206. package/src/cmd/build/vite/index.ts +5 -2
  207. package/src/cmd/build/vite/metadata-generator.ts +5 -7
  208. package/src/cmd/build/vite/registry-generator.ts +136 -43
  209. package/src/cmd/build/vite/route-discovery.ts +20 -0
  210. package/src/cmd/build/vite/vite-builder.ts +13 -2
  211. package/src/cmd/build/vite-bundler.ts +17 -4
  212. package/src/cmd/cloud/deploy.ts +183 -12
  213. package/src/cmd/cloud/deployment/show.ts +0 -1
  214. package/src/cmd/cloud/sandbox/create.ts +22 -0
  215. package/src/cmd/cloud/sandbox/delete.ts +2 -6
  216. package/src/cmd/cloud/sandbox/download.ts +96 -0
  217. package/src/cmd/cloud/sandbox/env.ts +104 -0
  218. package/src/cmd/cloud/sandbox/get.ts +5 -0
  219. package/src/cmd/cloud/sandbox/index.ts +14 -0
  220. package/src/cmd/cloud/sandbox/ls.ts +126 -0
  221. package/src/cmd/cloud/sandbox/mkdir.ts +65 -0
  222. package/src/cmd/cloud/sandbox/rm.ts +51 -0
  223. package/src/cmd/cloud/sandbox/rmdir.ts +65 -0
  224. package/src/cmd/cloud/sandbox/snapshot/create.ts +0 -2
  225. package/src/cmd/cloud/sandbox/snapshot/get.ts +0 -2
  226. package/src/cmd/cloud/sandbox/snapshot/list.ts +0 -3
  227. package/src/cmd/cloud/sandbox/upload.ts +83 -0
  228. package/src/cmd/dev/index.ts +147 -33
  229. package/src/cmd/dev/sync.ts +26 -30
  230. package/src/cmd/git/account/add.ts +317 -0
  231. package/src/cmd/git/account/index.ts +12 -0
  232. package/src/cmd/git/account/list.ts +139 -0
  233. package/src/cmd/git/account/remove.ts +212 -0
  234. package/src/cmd/git/index.ts +20 -0
  235. package/src/cmd/git/link.ts +468 -0
  236. package/src/cmd/git/list.ts +161 -0
  237. package/src/cmd/git/status.ts +144 -0
  238. package/src/cmd/git/unlink.ts +117 -0
  239. package/src/cmd/index.ts +2 -0
  240. package/src/cmd/integration/api.ts +379 -0
  241. package/src/cmd/integration/github/connect.ts +242 -0
  242. package/src/cmd/integration/github/disconnect.ts +149 -0
  243. package/src/cmd/integration/github/index.ts +21 -0
  244. package/src/cmd/integration/index.ts +16 -0
  245. package/src/cmd/project/auth/generate.ts +116 -0
  246. package/src/cmd/project/auth/index.ts +21 -0
  247. package/src/cmd/project/auth/init.ts +263 -0
  248. package/src/cmd/project/auth/shared.ts +534 -0
  249. package/src/cmd/project/index.ts +9 -1
  250. package/src/cmd/project/template-flow.ts +125 -0
  251. package/src/config.ts +34 -0
  252. package/src/errors.ts +7 -0
  253. package/src/types.ts +5 -2
@@ -0,0 +1,468 @@
1
+ import { createSubcommand, type Config } from '../../types';
2
+ import * as tui from '../../tui';
3
+ import { getCommand } from '../../command-prefix';
4
+ import { ErrorCode } from '../../errors';
5
+ import enquirer from 'enquirer';
6
+ import { z } from 'zod';
7
+ import {
8
+ getGithubIntegrationStatus,
9
+ listGithubRepos,
10
+ linkProjectToRepo,
11
+ getProjectGithubStatus,
12
+ type GithubRepo,
13
+ } from '../integration/api';
14
+ import type { APIClient } from '../../api';
15
+ import type { Logger } from '@agentuity/core';
16
+ import { runGitAccountConnect } from './account/add';
17
+
18
+ export interface DetectedGitInfo {
19
+ repo: string | null;
20
+ branch: string | null;
21
+ }
22
+
23
+ export function detectGitInfo(): DetectedGitInfo {
24
+ let repo: string | null = null;
25
+ let branch: string | null = null;
26
+
27
+ try {
28
+ // Detect repo from origin remote
29
+ const remoteResult = Bun.spawnSync(['git', 'remote', 'get-url', 'origin'], {
30
+ stdout: 'pipe',
31
+ stderr: 'pipe',
32
+ });
33
+ if (remoteResult.exitCode === 0) {
34
+ const url = remoteResult.stdout.toString().trim();
35
+ // Parse GitHub URL formats:
36
+ // https://github.com/owner/repo.git
37
+ // git@github.com:owner/repo.git
38
+ const httpsMatch = url.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/);
39
+ if (httpsMatch) repo = httpsMatch[1];
40
+
41
+ const sshMatch = url.match(/github\.com:([^/]+\/[^/]+?)(?:\.git)?$/);
42
+ if (sshMatch) repo = sshMatch[1];
43
+ }
44
+
45
+ // Detect current branch
46
+ const branchResult = Bun.spawnSync(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], {
47
+ stdout: 'pipe',
48
+ stderr: 'pipe',
49
+ });
50
+ if (branchResult.exitCode === 0) {
51
+ branch = branchResult.stdout.toString().trim();
52
+ }
53
+ } catch {
54
+ // Ignore errors
55
+ }
56
+
57
+ return { repo, branch };
58
+ }
59
+
60
+ export interface RunGitLinkOptions {
61
+ apiClient: APIClient;
62
+ projectId: string;
63
+ orgId: string;
64
+ logger: Logger;
65
+ branchOption?: string;
66
+ rootOption?: string;
67
+ noAuto?: boolean;
68
+ noPreview?: boolean;
69
+ skipAlreadyLinkedCheck?: boolean;
70
+ config?: Config | null;
71
+ }
72
+
73
+ export interface RunGitLinkResult {
74
+ linked: boolean;
75
+ repoFullName?: string;
76
+ branch?: string;
77
+ autoDeploy?: boolean;
78
+ cancelled?: boolean;
79
+ noGithubConnected?: boolean;
80
+ noReposFound?: boolean;
81
+ }
82
+
83
+ export async function runGitLink(options: RunGitLinkOptions): Promise<RunGitLinkResult> {
84
+ const {
85
+ apiClient,
86
+ projectId,
87
+ orgId,
88
+ logger,
89
+ branchOption,
90
+ rootOption,
91
+ noAuto = false,
92
+ noPreview = false,
93
+ skipAlreadyLinkedCheck = false,
94
+ config,
95
+ } = options;
96
+
97
+ try {
98
+ if (!skipAlreadyLinkedCheck) {
99
+ const currentStatus = await tui.spinner({
100
+ message: 'Checking current status...',
101
+ clearOnSuccess: true,
102
+ callback: () => getProjectGithubStatus(apiClient, projectId),
103
+ });
104
+
105
+ if (currentStatus.linked) {
106
+ tui.newline();
107
+ tui.warning(
108
+ `This project is already linked to ${tui.bold(currentStatus.repoFullName ?? 'a repository')}`
109
+ );
110
+ tui.newline();
111
+
112
+ const confirmed = await tui.confirm('Do you want to change the linked repository?');
113
+ if (!confirmed) {
114
+ tui.info('Cancelled');
115
+ return { linked: false, cancelled: true };
116
+ }
117
+ }
118
+ }
119
+
120
+ let githubStatus = await tui.spinner({
121
+ message: 'Checking GitHub connection...',
122
+ clearOnSuccess: true,
123
+ callback: () => getGithubIntegrationStatus(apiClient, orgId),
124
+ });
125
+
126
+ if (!githubStatus.connected || githubStatus.integrations.length === 0) {
127
+ tui.newline();
128
+ tui.warning('No GitHub accounts connected to this organization.');
129
+ tui.newline();
130
+
131
+ const wantConnect = await tui.confirm('Would you like to connect a GitHub account now?');
132
+ if (!wantConnect) {
133
+ tui.info('Cancelled');
134
+ return { linked: false, cancelled: true };
135
+ }
136
+
137
+ const connectResult = await runGitAccountConnect({
138
+ apiClient,
139
+ orgId,
140
+ logger,
141
+ config,
142
+ });
143
+
144
+ if (!connectResult.connected) {
145
+ if (connectResult.cancelled) {
146
+ return { linked: false, cancelled: true };
147
+ }
148
+ return { linked: false, noGithubConnected: true };
149
+ }
150
+
151
+ githubStatus = await getGithubIntegrationStatus(apiClient, orgId);
152
+
153
+ if (!githubStatus.connected || githubStatus.integrations.length === 0) {
154
+ tui.error('GitHub connection failed. Please try again.');
155
+ return { linked: false, noGithubConnected: true };
156
+ }
157
+
158
+ tui.newline();
159
+ tui.info('Now continuing with repository linking...');
160
+ tui.newline();
161
+ }
162
+
163
+ const gitInfo = detectGitInfo();
164
+
165
+ const allRepos = await tui.spinner({
166
+ message: 'Fetching available repositories...',
167
+ clearOnSuccess: true,
168
+ callback: () => listGithubRepos(apiClient, orgId),
169
+ });
170
+
171
+ if (allRepos.length === 0) {
172
+ tui.newline();
173
+ tui.error('No repositories found.');
174
+ tui.newline();
175
+ console.log('Make sure your GitHub App has access to the repositories you want to link.');
176
+ return { linked: false, noReposFound: true };
177
+ }
178
+
179
+ let selectedRepo: GithubRepo | undefined;
180
+ let confirmed = false;
181
+
182
+ if (gitInfo.repo) {
183
+ const detectedRepo = allRepos.find(
184
+ (r) => r.fullName.toLowerCase() === gitInfo.repo!.toLowerCase()
185
+ );
186
+ if (detectedRepo) {
187
+ tui.newline();
188
+ tui.info(`Detected repository: ${tui.bold(detectedRepo.fullName)}`);
189
+ tui.newline();
190
+
191
+ const useDetected = await tui.confirm('Use this repository?');
192
+ if (useDetected) {
193
+ selectedRepo = detectedRepo;
194
+ }
195
+ }
196
+ }
197
+
198
+ if (!selectedRepo) {
199
+ let repos = allRepos;
200
+
201
+ if (githubStatus.integrations.length > 1) {
202
+ tui.newline();
203
+
204
+ const accountChoices = githubStatus.integrations.map((integration) => ({
205
+ name: integration.githubAccountName,
206
+ value: integration.id,
207
+ message: `${integration.githubAccountName} ${tui.muted(`(${integration.githubAccountType})`)}`,
208
+ }));
209
+
210
+ const accountResponse = await enquirer.prompt<{ integrationId: string }>({
211
+ type: 'select',
212
+ name: 'integrationId',
213
+ message: 'Select a GitHub account',
214
+ choices: accountChoices,
215
+ result(name: string) {
216
+ // Return the value (id) instead of the name
217
+ const choice = accountChoices.find((c) => c.name === name);
218
+ return choice?.value ?? name;
219
+ },
220
+ });
221
+
222
+ repos = await tui.spinner({
223
+ message: 'Fetching repositories...',
224
+ clearOnSuccess: true,
225
+ callback: () => listGithubRepos(apiClient, orgId, accountResponse.integrationId),
226
+ });
227
+
228
+ if (repos.length === 0) {
229
+ tui.newline();
230
+ tui.error('No repositories found for this account.');
231
+ return { linked: false, noReposFound: true };
232
+ }
233
+ }
234
+
235
+ const repoChoices = repos.map((repo) => ({
236
+ name: repo.fullName,
237
+ message: `${repo.fullName} ${repo.private ? tui.muted('(private)') : ''} ${tui.muted(`[${repo.defaultBranch}]`)}`,
238
+ }));
239
+
240
+ tui.newline();
241
+
242
+ const repoResponse = await enquirer.prompt<{ repoFullName: string }>({
243
+ type: 'autocomplete',
244
+ name: 'repoFullName',
245
+ message: 'Select a repository',
246
+ choices: repoChoices,
247
+ });
248
+
249
+ selectedRepo = repos.find((r) => r.fullName === repoResponse.repoFullName);
250
+ if (!selectedRepo) {
251
+ tui.error('Repository not found');
252
+ return { linked: false };
253
+ }
254
+ }
255
+
256
+ // Prompt for settings with defaults
257
+ const defaultBranch = branchOption ?? gitInfo.branch ?? selectedRepo.defaultBranch;
258
+ const defaultRoot = rootOption ?? '.';
259
+
260
+ tui.newline();
261
+
262
+ const { directory } = await enquirer.prompt<{ directory: string }>({
263
+ type: 'input',
264
+ name: 'directory',
265
+ message: 'Root directory',
266
+ initial: defaultRoot,
267
+ });
268
+
269
+ const { branch } = await enquirer.prompt<{ branch: string }>({
270
+ type: 'input',
271
+ name: 'branch',
272
+ message: 'Branch to deploy from',
273
+ initial: defaultBranch,
274
+ });
275
+
276
+ const finalAutoDeploy = await tui.confirm('Enable automatic deployments on push?', !noAuto);
277
+ const finalPreviewDeploy = await tui.confirm(
278
+ 'Enable preview deployments on PRs?',
279
+ !noPreview
280
+ );
281
+
282
+ tui.newline();
283
+ console.log(tui.bold('Link Settings:'));
284
+ console.log(` Repository: ${selectedRepo.fullName}`);
285
+ console.log(` Branch: ${branch}`);
286
+ console.log(` Directory: ${directory}`);
287
+ console.log(
288
+ ` Auto-deploy: ${finalAutoDeploy ? tui.colorSuccess('enabled') : tui.muted('disabled')}`
289
+ );
290
+ console.log(
291
+ ` Preview deploys: ${finalPreviewDeploy ? tui.colorSuccess('enabled') : tui.muted('disabled')}`
292
+ );
293
+ tui.newline();
294
+
295
+ confirmed = await tui.confirm('Link this repository?');
296
+ if (!confirmed) {
297
+ tui.info('Cancelled');
298
+ return { linked: false, cancelled: true };
299
+ }
300
+
301
+ await tui.spinner({
302
+ message: 'Linking repository...',
303
+ clearOnSuccess: true,
304
+ callback: () =>
305
+ linkProjectToRepo(apiClient, {
306
+ projectId,
307
+ repoFullName: selectedRepo.fullName,
308
+ branch,
309
+ autoDeploy: finalAutoDeploy,
310
+ previewDeploy: finalPreviewDeploy,
311
+ directory: directory === '.' ? undefined : directory,
312
+ integrationId: selectedRepo.integrationId,
313
+ }),
314
+ });
315
+
316
+ tui.newline();
317
+ tui.success(`Linked project to ${tui.bold(selectedRepo.fullName)}`);
318
+ tui.newline();
319
+
320
+ if (finalAutoDeploy) {
321
+ console.log(`Pushes to ${tui.bold(branch)} will trigger automatic deployments.`);
322
+ }
323
+ if (finalPreviewDeploy) {
324
+ console.log('Pull requests will create preview deployments.');
325
+ }
326
+
327
+ return {
328
+ linked: true,
329
+ repoFullName: selectedRepo.fullName,
330
+ branch,
331
+ autoDeploy: finalAutoDeploy,
332
+ };
333
+ } catch (error) {
334
+ const isCancel =
335
+ error === '' ||
336
+ (error instanceof Error && (error.message === '' || error.message === 'User cancelled'));
337
+
338
+ if (isCancel) {
339
+ tui.newline();
340
+ tui.info('Cancelled');
341
+ return { linked: false, cancelled: true };
342
+ }
343
+
344
+ logger.trace(error);
345
+ throw error;
346
+ }
347
+ }
348
+
349
+ const LinkOptionsSchema = z.object({
350
+ repo: z.string().optional().describe('Repository full name (owner/repo) to link'),
351
+ deploy: z.boolean().optional().describe('Enable automatic deployments on push (default: true)'),
352
+ preview: z
353
+ .boolean()
354
+ .optional()
355
+ .describe('Enable preview deployments on pull requests (default: true)'),
356
+ branch: z.string().optional().describe('Branch to deploy from (default: repo default branch)'),
357
+ root: z.string().optional().describe('Root directory containing agentuity.json (default: .)'),
358
+ confirm: z.boolean().optional().describe('Skip confirmation prompts'),
359
+ });
360
+
361
+ const LinkResponseSchema = z.object({
362
+ linked: z.boolean().describe('Whether the project was linked'),
363
+ repoFullName: z.string().optional().describe('Repository that was linked'),
364
+ branch: z.string().optional().describe('Branch configured'),
365
+ });
366
+
367
+ export const linkSubcommand = createSubcommand({
368
+ name: 'link',
369
+ description: 'Link a project to a GitHub repository',
370
+ tags: ['mutating', 'creates-resource'],
371
+ idempotent: false,
372
+ requires: { auth: true, apiClient: true, project: true },
373
+ schema: {
374
+ options: LinkOptionsSchema,
375
+ response: LinkResponseSchema,
376
+ },
377
+ examples: [
378
+ {
379
+ command: getCommand('git link'),
380
+ description: 'Link current project to a GitHub repository',
381
+ },
382
+ {
383
+ command: getCommand('git link --repo owner/repo --branch main --confirm'),
384
+ description: 'Link to a specific repo non-interactively',
385
+ },
386
+ {
387
+ command: getCommand('git link --root .'),
388
+ description: 'Link from the current directory',
389
+ },
390
+ {
391
+ command: getCommand('git link --branch main'),
392
+ description: 'Link to a specific branch',
393
+ },
394
+ {
395
+ command: getCommand('git link --preview true'),
396
+ description: 'Enable preview deployments on PRs',
397
+ },
398
+ {
399
+ command: getCommand('git link --deploy false'),
400
+ description: 'Disable automatic deployments on push',
401
+ },
402
+ {
403
+ command: getCommand('git link --root packages/my-agent'),
404
+ description: 'Link a subdirectory in a monorepo',
405
+ },
406
+ {
407
+ command: getCommand('--json git link --repo owner/repo --branch main --confirm'),
408
+ description: 'Link and return JSON result',
409
+ },
410
+ ],
411
+
412
+ async handler(ctx) {
413
+ const { apiClient, project, opts, config, logger, options } = ctx;
414
+
415
+ try {
416
+ // Non-interactive mode when repo is provided
417
+ // Note: integrationId is not passed in non-interactive mode. The API will
418
+ // attempt to find a matching integration based on the repo owner. This may
419
+ // fail if the org has multiple GitHub integrations with access to the same repo.
420
+ if (opts.repo && opts.confirm) {
421
+ const branch = opts.branch ?? 'main';
422
+ const directory = opts.root === '.' ? undefined : opts.root;
423
+
424
+ await tui.spinner({
425
+ message: 'Linking repository...',
426
+ clearOnSuccess: true,
427
+ callback: () =>
428
+ linkProjectToRepo(apiClient, {
429
+ projectId: project.projectId,
430
+ repoFullName: opts.repo!,
431
+ branch,
432
+ autoDeploy: opts.deploy !== false,
433
+ previewDeploy: opts.preview !== false,
434
+ directory,
435
+ }),
436
+ });
437
+
438
+ if (!options.json) {
439
+ tui.newline();
440
+ tui.success(`Linked project to ${tui.bold(opts.repo)}`);
441
+ }
442
+
443
+ return { linked: true, repoFullName: opts.repo, branch };
444
+ }
445
+
446
+ const result = await runGitLink({
447
+ apiClient,
448
+ projectId: project.projectId,
449
+ orgId: project.orgId,
450
+ logger,
451
+ branchOption: opts.branch,
452
+ rootOption: opts.root,
453
+ noAuto: opts.deploy === false,
454
+ noPreview: opts.preview === false,
455
+ config,
456
+ });
457
+
458
+ return {
459
+ linked: result.linked,
460
+ repoFullName: result.repoFullName,
461
+ branch: result.branch,
462
+ };
463
+ } catch (error) {
464
+ logger.trace(error);
465
+ return logger.fatal('Failed to link repository: %s', error, ErrorCode.INTEGRATION_FAILED);
466
+ }
467
+ },
468
+ });
@@ -0,0 +1,161 @@
1
+ import { createSubcommand } from '../../types';
2
+ import * as tui from '../../tui';
3
+ import { getCommand } from '../../command-prefix';
4
+ import enquirer from 'enquirer';
5
+ import { z } from 'zod';
6
+ import { getGithubIntegrationStatus, listGithubRepos } from '../integration/api';
7
+ import { ErrorCode } from '../../errors';
8
+ import { listOrganizations } from '@agentuity/server';
9
+
10
+ const ListOptionsSchema = z.object({
11
+ org: z.string().optional().describe('Organization ID to list repos for'),
12
+ account: z.string().optional().describe('GitHub account/integration ID to filter by'),
13
+ });
14
+
15
+ export const listSubcommand = createSubcommand({
16
+ name: 'list',
17
+ description: 'List GitHub repositories accessible to your organization',
18
+ aliases: ['ls'],
19
+ tags: ['read-only'],
20
+ idempotent: true,
21
+ requires: { auth: true, apiClient: true },
22
+ schema: {
23
+ options: ListOptionsSchema,
24
+ },
25
+ examples: [
26
+ {
27
+ command: getCommand('git list'),
28
+ description: 'List all accessible GitHub repositories',
29
+ },
30
+ {
31
+ command: getCommand('git list --org org_abc123'),
32
+ description: 'List repos for a specific organization',
33
+ },
34
+ {
35
+ command: getCommand('--json git list'),
36
+ description: 'List repos in JSON format',
37
+ },
38
+ ],
39
+
40
+ async handler(ctx) {
41
+ const { logger, apiClient, opts, options } = ctx;
42
+
43
+ try {
44
+ // Get orgs
45
+ const orgs = await tui.spinner({
46
+ message: 'Fetching organizations...',
47
+ clearOnSuccess: true,
48
+ callback: () => listOrganizations(apiClient),
49
+ });
50
+
51
+ if (orgs.length === 0) {
52
+ tui.error('No organizations found');
53
+ return [];
54
+ }
55
+
56
+ // Select org
57
+ let orgId = opts.org;
58
+ if (!orgId) {
59
+ if (orgs.length === 1) {
60
+ orgId = orgs[0].id;
61
+ } else {
62
+ tui.newline();
63
+ const orgChoices = orgs.map((o) => ({
64
+ name: o.id,
65
+ message: o.name,
66
+ }));
67
+
68
+ const response = await enquirer.prompt<{ orgId: string }>({
69
+ type: 'select',
70
+ name: 'orgId',
71
+ message: 'Select an organization',
72
+ choices: orgChoices,
73
+ });
74
+ orgId = response.orgId;
75
+ }
76
+ }
77
+
78
+ // Check GitHub integrations
79
+ const githubStatus = await tui.spinner({
80
+ message: 'Checking GitHub connection...',
81
+ clearOnSuccess: true,
82
+ callback: () => getGithubIntegrationStatus(apiClient, orgId!),
83
+ });
84
+
85
+ if (!githubStatus.connected || githubStatus.integrations.length === 0) {
86
+ tui.newline();
87
+ tui.error('No GitHub accounts connected to this organization.');
88
+ console.log(tui.muted(`Run ${tui.bold('agentuity git account add')} to connect one`));
89
+ return [];
90
+ }
91
+
92
+ // Select account if multiple and not specified
93
+ let integrationId = opts.account;
94
+ if (!integrationId && githubStatus.integrations.length > 1) {
95
+ tui.newline();
96
+ const accountChoices = githubStatus.integrations.map((integration) => ({
97
+ name: integration.id,
98
+ message: `${integration.githubAccountName} ${tui.muted(`(${integration.githubAccountType})`)}`,
99
+ }));
100
+
101
+ const response = await enquirer.prompt<{ integrationId: string }>({
102
+ type: 'select',
103
+ name: 'integrationId',
104
+ message: 'Select a GitHub account',
105
+ choices: accountChoices,
106
+ });
107
+ integrationId = response.integrationId;
108
+ }
109
+
110
+ // Fetch repos
111
+ const repos = await tui.spinner({
112
+ message: 'Fetching repositories...',
113
+ clearOnSuccess: true,
114
+ callback: () => listGithubRepos(apiClient, orgId!, integrationId),
115
+ });
116
+
117
+ if (repos.length === 0) {
118
+ tui.newline();
119
+ tui.info('No repositories found.');
120
+ console.log(
121
+ tui.muted('Make sure your GitHub App has access to the repositories you want.')
122
+ );
123
+ return [];
124
+ }
125
+
126
+ if (!options.json) {
127
+ tui.newline();
128
+ console.log(tui.bold(`${repos.length} repositories`));
129
+ tui.newline();
130
+
131
+ for (const repo of repos) {
132
+ const visibility = repo.private ? tui.muted('private') : 'public';
133
+ console.log(
134
+ ` ${repo.fullName} ${tui.muted(`[${repo.defaultBranch}]`)} ${visibility}`
135
+ );
136
+ }
137
+ tui.newline();
138
+ }
139
+
140
+ return repos;
141
+ } catch (error) {
142
+ const isCancel =
143
+ error === '' ||
144
+ (error instanceof Error &&
145
+ (error.message === '' || error.message === 'User cancelled'));
146
+
147
+ if (isCancel) {
148
+ tui.newline();
149
+ tui.info('Cancelled');
150
+ return [];
151
+ }
152
+
153
+ logger.trace(error);
154
+ return logger.fatal(
155
+ 'Failed to list repositories: %s',
156
+ error,
157
+ ErrorCode.INTEGRATION_FAILED
158
+ );
159
+ }
160
+ },
161
+ });