@agentuity/cli 0.0.43 → 0.0.45

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 (209) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/api.d.ts +3 -3
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/auth.d.ts +10 -2
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/banner.d.ts.map +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cmd/auth/api.d.ts +4 -4
  10. package/dist/cmd/auth/api.d.ts.map +1 -1
  11. package/dist/cmd/auth/index.d.ts.map +1 -1
  12. package/dist/cmd/auth/login.d.ts.map +1 -1
  13. package/dist/cmd/auth/signup.d.ts.map +1 -1
  14. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  15. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  16. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  17. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  18. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  20. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  21. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  22. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  23. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  24. package/dist/cmd/auth/whoami.d.ts.map +1 -1
  25. package/dist/cmd/bundle/ast.d.ts +14 -3
  26. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  27. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  28. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  29. package/dist/cmd/bundle/bundler.d.ts +6 -1
  30. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  31. package/dist/cmd/bundle/file.d.ts.map +1 -1
  32. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  33. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  34. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  36. package/dist/cmd/bundle/plugin.d.ts +2 -0
  37. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  38. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  39. package/dist/cmd/cloud/domain.d.ts +17 -0
  40. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  41. package/dist/cmd/cloud/index.d.ts.map +1 -1
  42. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  43. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  44. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  45. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  47. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  50. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  51. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  52. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  53. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  56. package/dist/cmd/cloud/ssh.d.ts +2 -0
  57. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  58. package/dist/cmd/dev/api.d.ts +18 -0
  59. package/dist/cmd/dev/api.d.ts.map +1 -0
  60. package/dist/cmd/dev/download.d.ts +11 -0
  61. package/dist/cmd/dev/download.d.ts.map +1 -0
  62. package/dist/cmd/dev/index.d.ts.map +1 -1
  63. package/dist/cmd/dev/templates.d.ts +3 -0
  64. package/dist/cmd/dev/templates.d.ts.map +1 -0
  65. package/dist/cmd/env/delete.d.ts.map +1 -1
  66. package/dist/cmd/env/get.d.ts.map +1 -1
  67. package/dist/cmd/env/import.d.ts.map +1 -1
  68. package/dist/cmd/env/list.d.ts.map +1 -1
  69. package/dist/cmd/env/pull.d.ts.map +1 -1
  70. package/dist/cmd/env/push.d.ts.map +1 -1
  71. package/dist/cmd/env/set.d.ts.map +1 -1
  72. package/dist/cmd/profile/show.d.ts.map +1 -1
  73. package/dist/cmd/project/create.d.ts.map +1 -1
  74. package/dist/cmd/project/delete.d.ts.map +1 -1
  75. package/dist/cmd/project/list.d.ts.map +1 -1
  76. package/dist/cmd/project/show.d.ts.map +1 -1
  77. package/dist/cmd/project/template-flow.d.ts +4 -0
  78. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  79. package/dist/cmd/secret/delete.d.ts.map +1 -1
  80. package/dist/cmd/secret/get.d.ts.map +1 -1
  81. package/dist/cmd/secret/import.d.ts.map +1 -1
  82. package/dist/cmd/secret/list.d.ts.map +1 -1
  83. package/dist/cmd/secret/pull.d.ts.map +1 -1
  84. package/dist/cmd/secret/push.d.ts.map +1 -1
  85. package/dist/cmd/secret/set.d.ts.map +1 -1
  86. package/dist/config.d.ts +9 -3
  87. package/dist/config.d.ts.map +1 -1
  88. package/dist/crypto/box.d.ts +65 -0
  89. package/dist/crypto/box.d.ts.map +1 -0
  90. package/dist/crypto/box.test.d.ts +2 -0
  91. package/dist/crypto/box.test.d.ts.map +1 -0
  92. package/dist/download.d.ts.map +1 -1
  93. package/dist/steps.d.ts +4 -1
  94. package/dist/steps.d.ts.map +1 -1
  95. package/dist/terminal.d.ts.map +1 -1
  96. package/dist/tui.d.ts +31 -1
  97. package/dist/tui.d.ts.map +1 -1
  98. package/dist/types.d.ts +249 -126
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/utils/detectSubagent.d.ts +15 -0
  101. package/dist/utils/detectSubagent.d.ts.map +1 -0
  102. package/dist/utils/zip.d.ts +7 -0
  103. package/dist/utils/zip.d.ts.map +1 -0
  104. package/package.json +11 -3
  105. package/src/api-errors.md +2 -2
  106. package/src/api.ts +12 -7
  107. package/src/auth.ts +116 -7
  108. package/src/banner.ts +13 -6
  109. package/src/cli.ts +695 -63
  110. package/src/cmd/auth/api.ts +10 -16
  111. package/src/cmd/auth/index.ts +2 -1
  112. package/src/cmd/auth/login.ts +24 -8
  113. package/src/cmd/auth/signup.ts +15 -11
  114. package/src/cmd/auth/ssh/add.ts +263 -0
  115. package/src/cmd/auth/ssh/api.ts +94 -0
  116. package/src/cmd/auth/ssh/delete.ts +102 -0
  117. package/src/cmd/auth/ssh/index.ts +10 -0
  118. package/src/cmd/auth/ssh/list.ts +74 -0
  119. package/src/cmd/auth/whoami.ts +13 -13
  120. package/src/cmd/bundle/ast.test.ts +565 -0
  121. package/src/cmd/bundle/ast.ts +457 -44
  122. package/src/cmd/bundle/bundler.ts +255 -57
  123. package/src/cmd/bundle/file.ts +6 -12
  124. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  125. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  126. package/src/cmd/bundle/index.ts +9 -9
  127. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  128. package/src/cmd/bundle/plugin.ts +373 -53
  129. package/src/cmd/cloud/deploy.ts +300 -93
  130. package/src/cmd/cloud/domain.ts +92 -0
  131. package/src/cmd/cloud/index.ts +4 -1
  132. package/src/cmd/cloud/resource/add.ts +56 -0
  133. package/src/cmd/cloud/resource/delete.ts +120 -0
  134. package/src/cmd/cloud/resource/index.ts +11 -0
  135. package/src/cmd/cloud/resource/list.ts +69 -0
  136. package/src/cmd/cloud/scp/download.ts +59 -0
  137. package/src/cmd/cloud/scp/index.ts +9 -0
  138. package/src/cmd/cloud/scp/upload.ts +62 -0
  139. package/src/cmd/cloud/ssh.ts +68 -0
  140. package/src/cmd/dev/api.ts +46 -0
  141. package/src/cmd/dev/download.ts +111 -0
  142. package/src/cmd/dev/index.ts +360 -34
  143. package/src/cmd/dev/templates.ts +84 -0
  144. package/src/cmd/env/delete.ts +5 -20
  145. package/src/cmd/env/get.ts +5 -18
  146. package/src/cmd/env/import.ts +5 -20
  147. package/src/cmd/env/list.ts +5 -18
  148. package/src/cmd/env/pull.ts +10 -23
  149. package/src/cmd/env/push.ts +5 -23
  150. package/src/cmd/env/set.ts +5 -20
  151. package/src/cmd/index.ts +2 -2
  152. package/src/cmd/profile/show.ts +15 -6
  153. package/src/cmd/project/create.ts +7 -2
  154. package/src/cmd/project/delete.ts +75 -18
  155. package/src/cmd/project/download.ts +2 -2
  156. package/src/cmd/project/list.ts +8 -8
  157. package/src/cmd/project/show.ts +3 -7
  158. package/src/cmd/project/template-flow.ts +170 -72
  159. package/src/cmd/secret/delete.ts +5 -20
  160. package/src/cmd/secret/get.ts +5 -18
  161. package/src/cmd/secret/import.ts +5 -20
  162. package/src/cmd/secret/list.ts +5 -18
  163. package/src/cmd/secret/pull.ts +10 -23
  164. package/src/cmd/secret/push.ts +5 -23
  165. package/src/cmd/secret/set.ts +5 -20
  166. package/src/config.ts +224 -24
  167. package/src/crypto/box.test.ts +431 -0
  168. package/src/crypto/box.ts +477 -0
  169. package/src/download.ts +1 -0
  170. package/src/env-util.test.ts +1 -1
  171. package/src/steps.ts +65 -6
  172. package/src/terminal.ts +24 -23
  173. package/src/tui.ts +192 -61
  174. package/src/types.ts +291 -201
  175. package/src/utils/detectSubagent.ts +31 -0
  176. package/src/utils/zip.ts +38 -0
  177. package/dist/cmd/example/create-user.d.ts +0 -2
  178. package/dist/cmd/example/create-user.d.ts.map +0 -1
  179. package/dist/cmd/example/create.d.ts +0 -2
  180. package/dist/cmd/example/create.d.ts.map +0 -1
  181. package/dist/cmd/example/deploy.d.ts +0 -2
  182. package/dist/cmd/example/deploy.d.ts.map +0 -1
  183. package/dist/cmd/example/index.d.ts +0 -2
  184. package/dist/cmd/example/index.d.ts.map +0 -1
  185. package/dist/cmd/example/list.d.ts +0 -2
  186. package/dist/cmd/example/list.d.ts.map +0 -1
  187. package/dist/cmd/example/optional-auth.d.ts +0 -3
  188. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  189. package/dist/cmd/example/run-command.d.ts +0 -2
  190. package/dist/cmd/example/run-command.d.ts.map +0 -1
  191. package/dist/cmd/example/sound.d.ts +0 -3
  192. package/dist/cmd/example/sound.d.ts.map +0 -1
  193. package/dist/cmd/example/spinner.d.ts +0 -2
  194. package/dist/cmd/example/spinner.d.ts.map +0 -1
  195. package/dist/cmd/example/steps.d.ts +0 -2
  196. package/dist/cmd/example/steps.d.ts.map +0 -1
  197. package/dist/cmd/example/version.d.ts +0 -2
  198. package/dist/cmd/example/version.d.ts.map +0 -1
  199. package/src/cmd/example/create-user.ts +0 -38
  200. package/src/cmd/example/create.ts +0 -31
  201. package/src/cmd/example/deploy.ts +0 -36
  202. package/src/cmd/example/index.ts +0 -29
  203. package/src/cmd/example/list.ts +0 -32
  204. package/src/cmd/example/optional-auth.ts +0 -38
  205. package/src/cmd/example/run-command.ts +0 -45
  206. package/src/cmd/example/sound.ts +0 -14
  207. package/src/cmd/example/spinner.ts +0 -44
  208. package/src/cmd/example/steps.ts +0 -66
  209. package/src/cmd/example/version.ts +0 -13
@@ -0,0 +1,387 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { join } from 'node:path';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { fixDuplicateExportsInDirectory } from './fix-duplicate-exports';
6
+
7
+ describe('fix-duplicate-exports', () => {
8
+ let tempDir: string;
9
+
10
+ beforeEach(async () => {
11
+ tempDir = await mkdtemp(join(tmpdir(), 'fix-duplicate-exports-test-'));
12
+ });
13
+
14
+ afterEach(async () => {
15
+ await rm(tempDir, { recursive: true, force: true });
16
+ });
17
+
18
+ describe('export alias syntax preservation', () => {
19
+ test('preserves "qux as baz" when removing duplicate foo', async () => {
20
+ // This is the critical bug case from the feedback
21
+ const code = `export { foo };
22
+ export { qux as baz, foo };`;
23
+
24
+ const expected = `export { foo };
25
+ export { qux as baz };`;
26
+
27
+ const testFile = join(tempDir, 'test.js');
28
+ await Bun.write(testFile, code);
29
+ await fixDuplicateExportsInDirectory(tempDir, false);
30
+ const result = await Bun.file(testFile).text();
31
+
32
+ expect(result).toBe(expected);
33
+ });
34
+
35
+ test('preserves "foo as bar" when it is not duplicate', async () => {
36
+ const code = `export { foo as bar };
37
+ export { baz };`;
38
+
39
+ const testFile = join(tempDir, 'test.js');
40
+ await Bun.write(testFile, code);
41
+ await fixDuplicateExportsInDirectory(tempDir, false);
42
+ const result = await Bun.file(testFile).text();
43
+
44
+ // Should not change - no duplicates
45
+ expect(result).toBe(code);
46
+ });
47
+
48
+ test('handles multiple aliases in same export statement', async () => {
49
+ const code = `export { foo as bar, baz as qux };
50
+ export { bar, simple };`;
51
+
52
+ // "bar" is duplicate (exported in both statements)
53
+ const expected = `export { foo as bar, baz as qux };
54
+ export { simple };`;
55
+
56
+ const testFile = join(tempDir, 'test.js');
57
+ await Bun.write(testFile, code);
58
+ await fixDuplicateExportsInDirectory(tempDir, false);
59
+ const result = await Bun.file(testFile).text();
60
+
61
+ expect(result).toBe(expected);
62
+ });
63
+
64
+ test('preserves alias when removing from middle of export list', async () => {
65
+ const code = `export { alpha, beta };
66
+ export { alpha, gamma as delta, epsilon };`;
67
+
68
+ const expected = `export { alpha, beta };
69
+ export { gamma as delta, epsilon };`;
70
+
71
+ const testFile = join(tempDir, 'test.js');
72
+ await Bun.write(testFile, code);
73
+ await fixDuplicateExportsInDirectory(tempDir, false);
74
+ const result = await Bun.file(testFile).text();
75
+
76
+ expect(result).toBe(expected);
77
+ });
78
+
79
+ test('handles exported name being the duplicate (alias target)', async () => {
80
+ const code = `export { foo as bar };
81
+ export { bar };`;
82
+
83
+ // "bar" from first export and "bar" from second export are DIFFERENT
84
+ // First one exports the identifier "foo" with the name "bar"
85
+ // Second one exports the identifier "bar" with the name "bar"
86
+ // These are duplicates from export name perspective
87
+ const expected = `export { foo as bar };
88
+ `;
89
+
90
+ const testFile = join(tempDir, 'test.js');
91
+ await Bun.write(testFile, code);
92
+ await fixDuplicateExportsInDirectory(tempDir, false);
93
+ const result = await Bun.file(testFile).text();
94
+
95
+ expect(result).toBe(expected);
96
+ });
97
+ });
98
+
99
+ describe('duplicate removal', () => {
100
+ test('removes entire duplicate export statement', async () => {
101
+ const code = `export { foo, bar };
102
+ export { foo, bar };`;
103
+
104
+ const expected = `export { foo, bar };
105
+ `;
106
+
107
+ const testFile = join(tempDir, 'test.js');
108
+ await Bun.write(testFile, code);
109
+ await fixDuplicateExportsInDirectory(tempDir, false);
110
+ const result = await Bun.file(testFile).text();
111
+
112
+ expect(result).toBe(expected);
113
+ });
114
+
115
+ test('removes only duplicate names from partial duplicate', async () => {
116
+ const code = `export { foo, bar };
117
+ export { foo, baz };`;
118
+
119
+ const expected = `export { foo, bar };
120
+ export { baz };`;
121
+
122
+ const testFile = join(tempDir, 'test.js');
123
+ await Bun.write(testFile, code);
124
+ await fixDuplicateExportsInDirectory(tempDir, false);
125
+ const result = await Bun.file(testFile).text();
126
+
127
+ expect(result).toBe(expected);
128
+ });
129
+
130
+ test('handles three-way duplicates', async () => {
131
+ const code = `export { foo };
132
+ export { bar };
133
+ export { foo };`;
134
+
135
+ const expected = `export { foo };
136
+ export { bar };
137
+ `;
138
+
139
+ const testFile = join(tempDir, 'test.js');
140
+ await Bun.write(testFile, code);
141
+ await fixDuplicateExportsInDirectory(tempDir, false);
142
+ const result = await Bun.file(testFile).text();
143
+
144
+ expect(result).toBe(expected);
145
+ });
146
+
147
+ test('preserves non-duplicate exports', async () => {
148
+ const code = `export { foo };
149
+ export { bar };
150
+ export { baz };`;
151
+
152
+ const testFile = join(tempDir, 'test.js');
153
+ await Bun.write(testFile, code);
154
+ await fixDuplicateExportsInDirectory(tempDir, false);
155
+ const result = await Bun.file(testFile).text();
156
+
157
+ // Should not change - no duplicates
158
+ expect(result).toBe(code);
159
+ });
160
+ });
161
+
162
+ describe('__INVALID__REF__ removal', () => {
163
+ test('removes __INVALID__REF__ at start with comma', async () => {
164
+ const code = `export { __INVALID__REF__, foo, bar };`;
165
+ const expected = `export { foo, bar };`;
166
+
167
+ const testFile = join(tempDir, 'test.js');
168
+ await Bun.write(testFile, code);
169
+ await fixDuplicateExportsInDirectory(tempDir, false);
170
+ const result = await Bun.file(testFile).text();
171
+
172
+ expect(result).toBe(expected);
173
+ });
174
+
175
+ test('removes __INVALID__REF__ at end with comma', async () => {
176
+ const code = `export { foo, bar, __INVALID__REF__ };`;
177
+ const expected = `export { foo, bar };`;
178
+
179
+ const testFile = join(tempDir, 'test.js');
180
+ await Bun.write(testFile, code);
181
+ await fixDuplicateExportsInDirectory(tempDir, false);
182
+ const result = await Bun.file(testFile).text();
183
+
184
+ expect(result).toBe(expected);
185
+ });
186
+
187
+ test('removes __INVALID__REF__ in middle', async () => {
188
+ const code = `export { foo, __INVALID__REF__, bar };`;
189
+ const expected = `export { foo, bar };`;
190
+
191
+ const testFile = join(tempDir, 'test.js');
192
+ await Bun.write(testFile, code);
193
+ await fixDuplicateExportsInDirectory(tempDir, false);
194
+ const result = await Bun.file(testFile).text();
195
+
196
+ expect(result).toBe(expected);
197
+ });
198
+
199
+ test('removes __INVALID__REF__ from imports', async () => {
200
+ const code = `import { __INVALID__REF__, foo } from 'bar';
201
+ export { foo };`;
202
+
203
+ const expected = `import { foo } from 'bar';
204
+ export { foo };`;
205
+
206
+ const testFile = join(tempDir, 'test.js');
207
+ await Bun.write(testFile, code);
208
+ await fixDuplicateExportsInDirectory(tempDir, false);
209
+ const result = await Bun.file(testFile).text();
210
+
211
+ expect(result).toBe(expected);
212
+ });
213
+ });
214
+
215
+ describe('complex scenarios', () => {
216
+ test('handles combination of aliases and duplicates', async () => {
217
+ const code = `export { foo as exportedFoo, bar };
218
+ export { baz as exportedBaz };
219
+ export { bar, qux };`;
220
+
221
+ const expected = `export { foo as exportedFoo, bar };
222
+ export { baz as exportedBaz };
223
+ export { qux };`;
224
+
225
+ const testFile = join(tempDir, 'test.js');
226
+ await Bun.write(testFile, code);
227
+ await fixDuplicateExportsInDirectory(tempDir, false);
228
+ const result = await Bun.file(testFile).text();
229
+
230
+ expect(result).toBe(expected);
231
+ });
232
+
233
+ test('handles whitespace variations', async () => {
234
+ const code = `export { foo };
235
+ export { bar as baz , foo };`;
236
+
237
+ const expected = `export { foo };
238
+ export { bar as baz };`;
239
+
240
+ const testFile = join(tempDir, 'test.js');
241
+ await Bun.write(testFile, code);
242
+ await fixDuplicateExportsInDirectory(tempDir, false);
243
+ const result = await Bun.file(testFile).text();
244
+
245
+ expect(result).toBe(expected);
246
+ });
247
+
248
+ test('does not modify non-matching export patterns', async () => {
249
+ const code = `export default function foo() {}
250
+ export const bar = 1;
251
+ export { baz };
252
+ export * from './other';`;
253
+
254
+ const testFile = join(tempDir, 'test.js');
255
+ await Bun.write(testFile, code);
256
+ await fixDuplicateExportsInDirectory(tempDir, false);
257
+ const result = await Bun.file(testFile).text();
258
+
259
+ // Should only process export { ... } statements
260
+ expect(result).toBe(code);
261
+ });
262
+
263
+ test('handles multiple files in directory', async () => {
264
+ const file1 = join(tempDir, 'file1.js');
265
+ const file2 = join(tempDir, 'file2.js');
266
+
267
+ await Bun.write(file1, `export { foo };\nexport { foo };`);
268
+ await Bun.write(file2, `export { qux as baz };\nexport { baz };`);
269
+
270
+ await fixDuplicateExportsInDirectory(tempDir, false);
271
+
272
+ const result1 = await Bun.file(file1).text();
273
+ const result2 = await Bun.file(file2).text();
274
+
275
+ expect(result1).toBe(`export { foo };
276
+ `);
277
+ expect(result2).toBe(`export { qux as baz };
278
+ `);
279
+ });
280
+ });
281
+
282
+ describe('patch order edge cases', () => {
283
+ test('handles partial duplicate followed by full duplicate', async () => {
284
+ // This tests the scenario where modification shifts indices for subsequent removal
285
+ const code = `export { foo };
286
+ export { foo, bar };
287
+ export { foo };`;
288
+
289
+ // Expected: first foo kept, second becomes just bar, third removed
290
+ const expected = `export { foo };
291
+ export { bar };
292
+ `;
293
+
294
+ const testFile = join(tempDir, 'test.js');
295
+ await Bun.write(testFile, code);
296
+ await fixDuplicateExportsInDirectory(tempDir, false);
297
+ const result = await Bun.file(testFile).text();
298
+
299
+ expect(result).toBe(expected);
300
+ });
301
+
302
+ test('handles multiple partial duplicates with full duplicate at end', async () => {
303
+ const code = `export { foo };
304
+ export { foo, bar };
305
+ export { foo, baz };
306
+ export { foo };`;
307
+
308
+ // First foo kept, second becomes bar only, third becomes baz only, fourth removed
309
+ const expected = `export { foo };
310
+ export { bar };
311
+ export { baz };
312
+ `;
313
+
314
+ const testFile = join(tempDir, 'test.js');
315
+ await Bun.write(testFile, code);
316
+ await fixDuplicateExportsInDirectory(tempDir, false);
317
+ const result = await Bun.file(testFile).text();
318
+
319
+ expect(result).toBe(expected);
320
+ });
321
+
322
+ test('handles full duplicate followed by partial duplicate', async () => {
323
+ const code = `export { foo, bar };
324
+ export { foo, bar };
325
+ export { bar, baz };`;
326
+
327
+ // First kept, second removed (full dup - leaves blank line), third becomes just baz
328
+ const expected = `export { foo, bar };
329
+
330
+ export { baz };`;
331
+
332
+ const testFile = join(tempDir, 'test.js');
333
+ await Bun.write(testFile, code);
334
+ await fixDuplicateExportsInDirectory(tempDir, false);
335
+ const result = await Bun.file(testFile).text();
336
+
337
+ expect(result).toBe(expected);
338
+ });
339
+ });
340
+
341
+ describe('edge cases', () => {
342
+ test('handles empty file', async () => {
343
+ const code = '';
344
+ const testFile = join(tempDir, 'test.js');
345
+ await Bun.write(testFile, code);
346
+ await fixDuplicateExportsInDirectory(tempDir, false);
347
+ const result = await Bun.file(testFile).text();
348
+
349
+ expect(result).toBe(code);
350
+ });
351
+
352
+ test('handles file with no exports', async () => {
353
+ const code = `const foo = 1;\nconst bar = 2;`;
354
+ const testFile = join(tempDir, 'test.js');
355
+ await Bun.write(testFile, code);
356
+ await fixDuplicateExportsInDirectory(tempDir, false);
357
+ const result = await Bun.file(testFile).text();
358
+
359
+ expect(result).toBe(code);
360
+ });
361
+
362
+ test('handles single export with alias', async () => {
363
+ const code = `export { foo as bar };`;
364
+ const testFile = join(tempDir, 'test.js');
365
+ await Bun.write(testFile, code);
366
+ await fixDuplicateExportsInDirectory(tempDir, false);
367
+ const result = await Bun.file(testFile).text();
368
+
369
+ expect(result).toBe(code);
370
+ });
371
+
372
+ test('handles export with semicolon vs without', async () => {
373
+ const code = `export { foo }
374
+ export { foo };`;
375
+
376
+ const expected = `export { foo }
377
+ `;
378
+
379
+ const testFile = join(tempDir, 'test.js');
380
+ await Bun.write(testFile, code);
381
+ await fixDuplicateExportsInDirectory(tempDir, false);
382
+ const result = await Bun.file(testFile).text();
383
+
384
+ expect(result).toBe(expected);
385
+ });
386
+ });
387
+ });
@@ -0,0 +1,204 @@
1
+ import path from 'node:path';
2
+
3
+ export async function fixDuplicateExportsInDirectory(dir: string, verbose = false) {
4
+ if (verbose) {
5
+ console.log(`Scanning for .js files in: ${dir}`);
6
+ }
7
+
8
+ const jsFiles = await getAllJsFiles(dir);
9
+ if (verbose) {
10
+ console.log(`Found ${jsFiles.length} .js files`);
11
+ }
12
+
13
+ if (jsFiles.length === 0) {
14
+ if (verbose) {
15
+ console.log('No .js files found');
16
+ }
17
+ return;
18
+ }
19
+
20
+ // Process all files in parallel
21
+ const results = await Promise.all(
22
+ jsFiles.map(async (filePath) => {
23
+ try {
24
+ const wasFixed = await fixDuplicateExportsInFile(filePath, verbose);
25
+ return { filePath, wasFixed, error: null };
26
+ } catch (error) {
27
+ return { filePath, wasFixed: false, error };
28
+ }
29
+ })
30
+ );
31
+
32
+ // Report results
33
+ const fixed = results.filter((r) => r.wasFixed);
34
+ const errors = results.filter((r) => r.error);
35
+
36
+ if (verbose) {
37
+ console.log(`\nResults:`);
38
+ console.log(`- Total files: ${jsFiles.length}`);
39
+ console.log(`- Files fixed: ${fixed.length}`);
40
+ console.log(`- Errors: ${errors.length}`);
41
+
42
+ if (fixed.length > 0) {
43
+ console.log('\nFixed files:');
44
+ fixed.forEach(({ filePath }) => {
45
+ console.log(` - ${filePath}`);
46
+ });
47
+ }
48
+
49
+ if (errors.length > 0) {
50
+ console.log('\nErrors:');
51
+ errors.forEach(({ filePath, error }) => {
52
+ console.log(` - ${filePath}: ${error}`);
53
+ });
54
+ }
55
+ }
56
+ }
57
+
58
+ async function fixDuplicateExportsInFile(filePath: string, verbose = false): Promise<boolean> {
59
+ const originalCode = await Bun.file(filePath).text();
60
+
61
+ // Only fix __INVALID__REF__ - remove it from imports and exports
62
+ let code = originalCode;
63
+
64
+ // Pattern 1: __INVALID__REF__ at start with comma after: "__INVALID__REF__, foo" -> "foo"
65
+ code = code.replace(/\b__INVALID__REF__\s*,\s*/g, '');
66
+
67
+ // Pattern 2: __INVALID__REF__ at end with comma before: "foo, __INVALID__REF__" -> "foo"
68
+ code = code.replace(/,\s*__INVALID__REF__\b/g, '');
69
+
70
+ // Pattern 3: __INVALID__REF__ alone (shouldn't happen but handle it)
71
+ code = code.replace(/\b__INVALID__REF__\b/g, '');
72
+
73
+ // Remove duplicate export statements
74
+ // Find all export { ... } statements (allow leading whitespace)
75
+ const exportPattern = /^\s*export\s*\{([^}]+)\}\s*;?\s*$/gm;
76
+ const exports: Array<{
77
+ match: string;
78
+ names: Set<string>;
79
+ nameToSyntax: Map<string, string>;
80
+ start: number;
81
+ end: number;
82
+ }> = [];
83
+ let match;
84
+
85
+ while ((match = exportPattern.exec(code)) !== null) {
86
+ const nameToSyntax = new Map<string, string>();
87
+ const names: string[] = [];
88
+
89
+ match[1].split(',').forEach((n) => {
90
+ const fullSyntax = n.trim();
91
+ const parts = fullSyntax.split(/\s+as\s+/);
92
+ const exportedName = parts.length > 1 ? parts[1].trim() : parts[0].trim();
93
+ if (exportedName) {
94
+ names.push(exportedName);
95
+ nameToSyntax.set(exportedName, fullSyntax);
96
+ }
97
+ });
98
+
99
+ exports.push({
100
+ match: match[0],
101
+ names: new Set(names),
102
+ nameToSyntax,
103
+ start: match.index,
104
+ end: match.index + match[0].length,
105
+ });
106
+ }
107
+
108
+ // Track which names we've seen and which export statements to remove/modify
109
+ const seenNames = new Set<string>();
110
+ const indicesToRemove: number[] = [];
111
+ const modificationsNeeded = new Map<number, Set<string>>(); // index -> names to keep
112
+
113
+ for (let i = 0; i < exports.length; i++) {
114
+ const exp = exports[i];
115
+ const duplicateNames = [...exp.names].filter((name) => seenNames.has(name));
116
+ const newNames = [...exp.names].filter((name) => !seenNames.has(name));
117
+ const allDuplicates = duplicateNames.length === exp.names.size;
118
+
119
+ if (verbose && duplicateNames.length > 0) {
120
+ console.log(` Duplicate exports found in statement ${i}: ${duplicateNames.join(', ')}`);
121
+ }
122
+
123
+ if (allDuplicates && exp.names.size > 0) {
124
+ // This entire export statement is a duplicate - remove it
125
+ indicesToRemove.push(i);
126
+ if (verbose) {
127
+ console.log(` -> Will remove entire statement`);
128
+ }
129
+ } else if (duplicateNames.length > 0) {
130
+ // Partial duplicates - need to remove just the duplicate names
131
+ modificationsNeeded.set(i, new Set(newNames));
132
+ if (verbose) {
133
+ console.log(` -> Will keep only: ${newNames.join(', ')}`);
134
+ }
135
+ // Mark the new names as seen
136
+ newNames.forEach((name) => seenNames.add(name));
137
+ } else {
138
+ // No duplicates - mark these names as seen
139
+ exp.names.forEach((name) => seenNames.add(name));
140
+ }
141
+ }
142
+
143
+ // Build patches for modifications and removals, then apply from end to preserve indices
144
+ const patches: Array<{ start: number; end: number; replacement: string }> = [];
145
+
146
+ // Partial duplicates: replace the export statement with only the kept names
147
+ for (const [i, namesToKeep] of modificationsNeeded.entries()) {
148
+ const exp = exports[i];
149
+ const syntaxToKeep = [...namesToKeep].map((name) => exp.nameToSyntax.get(name)!);
150
+ const newExport = `export { ${syntaxToKeep.join(', ')} };`;
151
+ patches.push({ start: exp.start, end: exp.end, replacement: newExport });
152
+ }
153
+
154
+ // Full duplicates: remove the entire export statement
155
+ for (const idx of indicesToRemove) {
156
+ const exp = exports[idx];
157
+ patches.push({ start: exp.start, end: exp.end, replacement: '' });
158
+ }
159
+
160
+ // Apply all patches from right to left so earlier indices remain valid
161
+ patches.sort((a, b) => b.start - a.start);
162
+ for (const { start, end, replacement } of patches) {
163
+ code = code.slice(0, start) + replacement + code.slice(end);
164
+ }
165
+ // Nothing changed
166
+ if (code === originalCode) {
167
+ return false;
168
+ }
169
+
170
+ // Write the fixed content back to the file
171
+ await Bun.write(filePath, code);
172
+
173
+ if (verbose) {
174
+ console.log(`\nšŸ”§ Fixed exports in: ${filePath}`);
175
+ }
176
+
177
+ return true;
178
+ }
179
+
180
+ async function getAllJsFiles(dir: string): Promise<string[]> {
181
+ const glob = new Bun.Glob('**/*.js');
182
+ const files = await Array.fromAsync(glob.scan({ cwd: dir }));
183
+ return files.map((file) => path.join(dir, file));
184
+ }
185
+
186
+ async function main() {
187
+ const dir = process.argv[2];
188
+ if (!dir) {
189
+ console.error('Usage: bun fix-duplicate-exports.ts <directory>');
190
+ process.exit(1);
191
+ }
192
+
193
+ const { existsSync } = await import('node:fs');
194
+ if (!existsSync(dir)) {
195
+ console.error(`Error: Directory does not exist: ${dir}`);
196
+ process.exit(1);
197
+ }
198
+
199
+ await fixDuplicateExportsInDirectory(dir, true);
200
+ }
201
+
202
+ if (import.meta.main) {
203
+ await main();
204
+ }
@@ -1,37 +1,37 @@
1
1
  import { createCommand } from '../../types';
2
2
  import { z } from 'zod';
3
- import { resolve } from 'node:path';
4
3
  import { bundle } from './bundler';
4
+ import * as tui from '../../tui';
5
5
 
6
6
  export const command = createCommand({
7
7
  name: 'bundle',
8
8
  description: 'Bundle Agentuity application for deployment',
9
9
  aliases: ['build'],
10
+ optional: { project: true },
10
11
  schema: {
11
12
  options: z.object({
12
- dir: z.string().optional().describe('Root directory of the project'),
13
13
  dev: z.boolean().optional().describe('Enable development mode'),
14
14
  }),
15
15
  },
16
16
 
17
17
  async handler(ctx) {
18
- const { logger, opts } = ctx;
19
- const rootDir = resolve(opts.dir || process.cwd());
18
+ const { opts, projectDir, project } = ctx;
20
19
 
21
20
  try {
22
- logger.info(`Bundling project at: ${rootDir}`);
21
+ tui.info(`Bundling project at: ${projectDir}`);
23
22
 
24
23
  await bundle({
25
- rootDir,
24
+ rootDir: projectDir,
26
25
  dev: opts.dev || false,
26
+ project,
27
27
  });
28
28
 
29
- logger.info('āœ“ Bundle complete');
29
+ tui.success('Bundle complete');
30
30
  } catch (error) {
31
31
  if (error instanceof Error) {
32
- logger.fatal(`Bundle failed: ${error.message}`);
32
+ tui.fatal(`Bundle failed: ${error.message}`);
33
33
  } else {
34
- logger.fatal('Bundle failed');
34
+ tui.fatal('Bundle failed');
35
35
  }
36
36
  }
37
37
  },
@@ -121,7 +121,7 @@ function createVercelAIProviderPatch(
121
121
  before: generateEnvGuard(
122
122
  envkey,
123
123
  generateVercelAIProvider(provider, envkey),
124
- `console.log("User provided API Key set for ${provider}. Switch to Agentuity AI Gateway for better logs, metrics and billing.");`
124
+ `if (!process.env.AGENTUITY_SDK_KEY) console.log("User provided ${provider} api key set. Use the Agentuity AI Gateway more features.");`
125
125
  ),
126
126
  },
127
127
  },