@agentuity/cli 0.0.42 → 0.0.44

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 (249) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/cli.ts +7 -5
  4. package/dist/api.d.ts +3 -3
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/auth.d.ts +10 -2
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/banner.d.ts.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cmd/auth/api.d.ts +4 -4
  11. package/dist/cmd/auth/api.d.ts.map +1 -1
  12. package/dist/cmd/auth/index.d.ts.map +1 -1
  13. package/dist/cmd/auth/login.d.ts.map +1 -1
  14. package/dist/cmd/auth/signup.d.ts.map +1 -1
  15. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  16. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  17. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  18. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  20. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  21. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  22. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  23. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  24. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  25. package/dist/cmd/auth/whoami.d.ts +2 -0
  26. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  27. package/dist/cmd/bundle/ast.d.ts +14 -3
  28. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  29. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  30. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  31. package/dist/cmd/bundle/bundler.d.ts +6 -1
  32. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  33. package/dist/cmd/bundle/file.d.ts.map +1 -1
  34. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  36. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  37. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  38. package/dist/cmd/bundle/index.d.ts +1 -1
  39. package/dist/cmd/bundle/index.d.ts.map +1 -1
  40. package/dist/cmd/bundle/plugin.d.ts +2 -0
  41. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  42. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  43. package/dist/cmd/cloud/domain.d.ts +17 -0
  44. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  45. package/dist/cmd/cloud/index.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  47. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  50. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  51. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  52. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  53. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  56. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  57. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  58. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  59. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  60. package/dist/cmd/cloud/ssh.d.ts +2 -0
  61. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  62. package/dist/cmd/dev/api.d.ts +18 -0
  63. package/dist/cmd/dev/api.d.ts.map +1 -0
  64. package/dist/cmd/dev/download.d.ts +11 -0
  65. package/dist/cmd/dev/download.d.ts.map +1 -0
  66. package/dist/cmd/dev/index.d.ts.map +1 -1
  67. package/dist/cmd/dev/templates.d.ts +3 -0
  68. package/dist/cmd/dev/templates.d.ts.map +1 -0
  69. package/dist/cmd/env/delete.d.ts +2 -0
  70. package/dist/cmd/env/delete.d.ts.map +1 -0
  71. package/dist/cmd/env/get.d.ts +2 -0
  72. package/dist/cmd/env/get.d.ts.map +1 -0
  73. package/dist/cmd/env/import.d.ts +2 -0
  74. package/dist/cmd/env/import.d.ts.map +1 -0
  75. package/dist/cmd/env/index.d.ts +2 -0
  76. package/dist/cmd/env/index.d.ts.map +1 -0
  77. package/dist/cmd/env/list.d.ts.map +1 -0
  78. package/dist/cmd/env/pull.d.ts +2 -0
  79. package/dist/cmd/env/pull.d.ts.map +1 -0
  80. package/dist/cmd/env/push.d.ts +2 -0
  81. package/dist/cmd/env/push.d.ts.map +1 -0
  82. package/dist/cmd/env/set.d.ts +2 -0
  83. package/dist/cmd/env/set.d.ts.map +1 -0
  84. package/dist/cmd/profile/show.d.ts.map +1 -1
  85. package/dist/cmd/project/create.d.ts.map +1 -1
  86. package/dist/cmd/project/delete.d.ts.map +1 -1
  87. package/dist/cmd/project/download.d.ts +1 -1
  88. package/dist/cmd/project/download.d.ts.map +1 -1
  89. package/dist/cmd/project/list.d.ts.map +1 -1
  90. package/dist/cmd/project/show.d.ts.map +1 -1
  91. package/dist/cmd/project/template-flow.d.ts +5 -1
  92. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  93. package/dist/cmd/secret/delete.d.ts +2 -0
  94. package/dist/cmd/secret/delete.d.ts.map +1 -0
  95. package/dist/cmd/secret/get.d.ts +2 -0
  96. package/dist/cmd/secret/get.d.ts.map +1 -0
  97. package/dist/cmd/secret/import.d.ts +2 -0
  98. package/dist/cmd/secret/import.d.ts.map +1 -0
  99. package/dist/cmd/secret/index.d.ts +2 -0
  100. package/dist/cmd/secret/index.d.ts.map +1 -0
  101. package/dist/cmd/secret/list.d.ts +2 -0
  102. package/dist/cmd/secret/list.d.ts.map +1 -0
  103. package/dist/cmd/secret/pull.d.ts +2 -0
  104. package/dist/cmd/secret/pull.d.ts.map +1 -0
  105. package/dist/cmd/secret/push.d.ts +2 -0
  106. package/dist/cmd/secret/push.d.ts.map +1 -0
  107. package/dist/cmd/secret/set.d.ts +2 -0
  108. package/dist/cmd/secret/set.d.ts.map +1 -0
  109. package/dist/cmd/version/index.d.ts.map +1 -1
  110. package/dist/config.d.ts +11 -3
  111. package/dist/config.d.ts.map +1 -1
  112. package/dist/crypto/box.d.ts +65 -0
  113. package/dist/crypto/box.d.ts.map +1 -0
  114. package/dist/crypto/box.test.d.ts +2 -0
  115. package/dist/crypto/box.test.d.ts.map +1 -0
  116. package/dist/download.d.ts.map +1 -1
  117. package/dist/env-util.d.ts +67 -0
  118. package/dist/env-util.d.ts.map +1 -0
  119. package/dist/env-util.test.d.ts +2 -0
  120. package/dist/env-util.test.d.ts.map +1 -0
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/schema-parser.d.ts.map +1 -1
  124. package/dist/steps.d.ts +4 -1
  125. package/dist/steps.d.ts.map +1 -1
  126. package/dist/terminal.d.ts.map +1 -1
  127. package/dist/tui.d.ts +32 -2
  128. package/dist/tui.d.ts.map +1 -1
  129. package/dist/types.d.ts +250 -127
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/utils/detectSubagent.d.ts +15 -0
  132. package/dist/utils/detectSubagent.d.ts.map +1 -0
  133. package/dist/utils/zip.d.ts +7 -0
  134. package/dist/utils/zip.d.ts.map +1 -0
  135. package/package.json +11 -3
  136. package/src/api-errors.md +2 -2
  137. package/src/api.ts +12 -7
  138. package/src/auth.ts +116 -7
  139. package/src/banner.ts +13 -6
  140. package/src/cli.ts +709 -36
  141. package/src/cmd/auth/api.ts +10 -16
  142. package/src/cmd/auth/index.ts +3 -1
  143. package/src/cmd/auth/login.ts +24 -8
  144. package/src/cmd/auth/signup.ts +15 -11
  145. package/src/cmd/auth/ssh/add.ts +263 -0
  146. package/src/cmd/auth/ssh/api.ts +94 -0
  147. package/src/cmd/auth/ssh/delete.ts +102 -0
  148. package/src/cmd/auth/ssh/index.ts +10 -0
  149. package/src/cmd/auth/ssh/list.ts +74 -0
  150. package/src/cmd/auth/whoami.ts +69 -0
  151. package/src/cmd/bundle/ast.test.ts +565 -0
  152. package/src/cmd/bundle/ast.ts +457 -44
  153. package/src/cmd/bundle/bundler.ts +255 -57
  154. package/src/cmd/bundle/file.ts +6 -12
  155. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  156. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  157. package/src/cmd/bundle/index.ts +11 -11
  158. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  159. package/src/cmd/bundle/plugin.ts +373 -53
  160. package/src/cmd/cloud/deploy.ts +336 -0
  161. package/src/cmd/cloud/domain.ts +92 -0
  162. package/src/cmd/cloud/index.ts +11 -0
  163. package/src/cmd/cloud/resource/add.ts +56 -0
  164. package/src/cmd/cloud/resource/delete.ts +120 -0
  165. package/src/cmd/cloud/resource/index.ts +11 -0
  166. package/src/cmd/cloud/resource/list.ts +69 -0
  167. package/src/cmd/cloud/scp/download.ts +59 -0
  168. package/src/cmd/cloud/scp/index.ts +9 -0
  169. package/src/cmd/cloud/scp/upload.ts +62 -0
  170. package/src/cmd/cloud/ssh.ts +68 -0
  171. package/src/cmd/dev/api.ts +46 -0
  172. package/src/cmd/dev/download.ts +111 -0
  173. package/src/cmd/dev/index.ts +362 -34
  174. package/src/cmd/dev/templates.ts +84 -0
  175. package/src/cmd/env/delete.ts +47 -0
  176. package/src/cmd/env/get.ts +53 -0
  177. package/src/cmd/env/import.ts +102 -0
  178. package/src/cmd/env/index.ts +22 -0
  179. package/src/cmd/env/list.ts +56 -0
  180. package/src/cmd/env/pull.ts +80 -0
  181. package/src/cmd/env/push.ts +37 -0
  182. package/src/cmd/env/set.ts +71 -0
  183. package/src/cmd/index.ts +2 -2
  184. package/src/cmd/profile/show.ts +15 -6
  185. package/src/cmd/project/create.ts +7 -2
  186. package/src/cmd/project/delete.ts +75 -18
  187. package/src/cmd/project/download.ts +3 -3
  188. package/src/cmd/project/list.ts +8 -8
  189. package/src/cmd/project/show.ts +3 -7
  190. package/src/cmd/project/template-flow.ts +186 -48
  191. package/src/cmd/secret/delete.ts +40 -0
  192. package/src/cmd/secret/get.ts +54 -0
  193. package/src/cmd/secret/import.ts +64 -0
  194. package/src/cmd/secret/index.ts +22 -0
  195. package/src/cmd/secret/list.ts +56 -0
  196. package/src/cmd/secret/pull.ts +78 -0
  197. package/src/cmd/secret/push.ts +37 -0
  198. package/src/cmd/secret/set.ts +45 -0
  199. package/src/cmd/version/index.ts +2 -1
  200. package/src/config.ts +257 -27
  201. package/src/crypto/box.test.ts +431 -0
  202. package/src/crypto/box.ts +477 -0
  203. package/src/download.ts +1 -0
  204. package/src/env-util.test.ts +194 -0
  205. package/src/env-util.ts +290 -0
  206. package/src/index.ts +5 -1
  207. package/src/schema-parser.ts +2 -3
  208. package/src/steps.ts +144 -10
  209. package/src/terminal.ts +24 -23
  210. package/src/tui.ts +208 -68
  211. package/src/types.ts +292 -202
  212. package/src/utils/detectSubagent.ts +31 -0
  213. package/src/utils/zip.ts +38 -0
  214. package/dist/cmd/example/create-user.d.ts +0 -2
  215. package/dist/cmd/example/create-user.d.ts.map +0 -1
  216. package/dist/cmd/example/create.d.ts +0 -2
  217. package/dist/cmd/example/create.d.ts.map +0 -1
  218. package/dist/cmd/example/deploy.d.ts.map +0 -1
  219. package/dist/cmd/example/index.d.ts.map +0 -1
  220. package/dist/cmd/example/list.d.ts.map +0 -1
  221. package/dist/cmd/example/optional-auth.d.ts +0 -3
  222. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  223. package/dist/cmd/example/run-command.d.ts +0 -2
  224. package/dist/cmd/example/run-command.d.ts.map +0 -1
  225. package/dist/cmd/example/sound.d.ts +0 -3
  226. package/dist/cmd/example/sound.d.ts.map +0 -1
  227. package/dist/cmd/example/spinner.d.ts +0 -2
  228. package/dist/cmd/example/spinner.d.ts.map +0 -1
  229. package/dist/cmd/example/steps.d.ts +0 -2
  230. package/dist/cmd/example/steps.d.ts.map +0 -1
  231. package/dist/cmd/example/version.d.ts +0 -2
  232. package/dist/cmd/example/version.d.ts.map +0 -1
  233. package/dist/logger.d.ts +0 -24
  234. package/dist/logger.d.ts.map +0 -1
  235. package/src/cmd/example/create-user.ts +0 -38
  236. package/src/cmd/example/create.ts +0 -31
  237. package/src/cmd/example/deploy.ts +0 -36
  238. package/src/cmd/example/index.ts +0 -29
  239. package/src/cmd/example/list.ts +0 -32
  240. package/src/cmd/example/optional-auth.ts +0 -38
  241. package/src/cmd/example/run-command.ts +0 -45
  242. package/src/cmd/example/sound.ts +0 -14
  243. package/src/cmd/example/spinner.ts +0 -44
  244. package/src/cmd/example/steps.ts +0 -66
  245. package/src/cmd/example/version.ts +0 -13
  246. package/src/logger.ts +0 -235
  247. /package/dist/cmd/{example → cloud}/deploy.d.ts +0 -0
  248. /package/dist/cmd/{example → cloud}/index.d.ts +0 -0
  249. /package/dist/cmd/{example → env}/list.d.ts +0 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Utility functions for handling .env files
3
+ */
4
+
5
+ import { join } from 'node:path';
6
+
7
+ export interface EnvVars {
8
+ [key: string]: string;
9
+ }
10
+
11
+ /**
12
+ * Find the appropriate .env file to use for user environment variables.
13
+ * Always returns .env.production path (will be created if needed).
14
+ * .env should only contain AGENTUITY_SDK_KEY.
15
+ */
16
+ export async function findEnvFile(dir: string): Promise<string> {
17
+ return join(dir, '.env.production');
18
+ }
19
+
20
+ /**
21
+ * Find an existing env file for reading.
22
+ * Preference: .env.production > .env
23
+ */
24
+ export async function findExistingEnvFile(dir: string): Promise<string> {
25
+ const productionEnv = join(dir, '.env.production');
26
+ const defaultEnv = join(dir, '.env');
27
+
28
+ if (await Bun.file(productionEnv).exists()) {
29
+ return productionEnv;
30
+ }
31
+
32
+ return defaultEnv;
33
+ }
34
+
35
+ /**
36
+ * Parse a single line from an .env file
37
+ * Handles comments, empty lines, and quoted values
38
+ */
39
+ export function parseEnvLine(line: string): { key: string; value: string } | null {
40
+ const trimmed = line.trim();
41
+
42
+ // Skip empty lines and comments
43
+ if (!trimmed || trimmed.startsWith('#')) {
44
+ return null;
45
+ }
46
+
47
+ const equalIndex = trimmed.indexOf('=');
48
+ if (equalIndex === -1) {
49
+ return null;
50
+ }
51
+
52
+ const key = trimmed.slice(0, equalIndex).trim();
53
+ let value = trimmed.slice(equalIndex + 1).trim();
54
+
55
+ // Remove surrounding quotes if present
56
+ if (
57
+ (value.startsWith('"') && value.endsWith('"')) ||
58
+ (value.startsWith("'") && value.endsWith("'"))
59
+ ) {
60
+ value = value.slice(1, -1);
61
+ }
62
+
63
+ return { key, value };
64
+ }
65
+
66
+ /**
67
+ * Read and parse an .env file
68
+ */
69
+ export async function readEnvFile(path: string): Promise<EnvVars> {
70
+ const file = Bun.file(path);
71
+
72
+ if (!(await file.exists())) {
73
+ return {};
74
+ }
75
+
76
+ const content = await file.text();
77
+ const lines = content.split('\n');
78
+ const env: EnvVars = {};
79
+
80
+ for (const line of lines) {
81
+ const parsed = parseEnvLine(line);
82
+ if (parsed) {
83
+ env[parsed.key] = parsed.value;
84
+ }
85
+ }
86
+
87
+ return env;
88
+ }
89
+
90
+ /**
91
+ * Write environment variables to an .env file
92
+ * Optionally skip certain keys (like AGENTUITY_SDK_KEY)
93
+ */
94
+ export async function writeEnvFile(
95
+ path: string,
96
+ vars: EnvVars,
97
+ options?: {
98
+ skipKeys?: string[];
99
+ addComment?: (key: string) => string | null;
100
+ }
101
+ ): Promise<void> {
102
+ const skipKeys = options?.skipKeys || [];
103
+ const lines: string[] = [];
104
+
105
+ // Sort keys for consistent output
106
+ const sortedKeys = Object.keys(vars).sort();
107
+
108
+ for (const key of sortedKeys) {
109
+ if (skipKeys.includes(key)) {
110
+ continue;
111
+ }
112
+
113
+ const value = vars[key];
114
+
115
+ // Add comment if provided
116
+ if (options?.addComment) {
117
+ const comment = options.addComment(key);
118
+ if (comment) {
119
+ lines.push(`# ${comment}`);
120
+ }
121
+ }
122
+
123
+ // Write key=value
124
+ lines.push(`${key}=${value}`);
125
+ }
126
+
127
+ const content = lines.join('\n') + '\n';
128
+ await Bun.write(path, content);
129
+ }
130
+
131
+ /**
132
+ * Merge environment variables with special handling
133
+ * - Later values override earlier values
134
+ * - Can filter out keys (like AGENTUITY_* keys)
135
+ */
136
+ export function mergeEnvVars(
137
+ base: EnvVars,
138
+ updates: EnvVars,
139
+ options?: {
140
+ filterPrefix?: string;
141
+ }
142
+ ): EnvVars {
143
+ const merged = { ...base };
144
+ const filterPrefix = options?.filterPrefix;
145
+
146
+ for (const [key, value] of Object.entries(updates)) {
147
+ // Skip keys with filter prefix if specified
148
+ if (filterPrefix && key.startsWith(filterPrefix)) {
149
+ continue;
150
+ }
151
+
152
+ merged[key] = value;
153
+ }
154
+
155
+ return merged;
156
+ }
157
+
158
+ /**
159
+ * Filter out AGENTUITY_ prefixed keys from env vars
160
+ * This is used when pushing to the cloud to avoid sending SDK keys
161
+ */
162
+ export function filterAgentuitySdkKeys(vars: EnvVars): EnvVars {
163
+ const filtered: EnvVars = {};
164
+
165
+ for (const [key, value] of Object.entries(vars)) {
166
+ if (!key.startsWith('AGENTUITY_')) {
167
+ filtered[key] = value;
168
+ }
169
+ }
170
+
171
+ return filtered;
172
+ }
173
+
174
+ /**
175
+ * Split env vars into env and secrets based on key names
176
+ * Convention: Keys ending with _SECRET, _KEY, _TOKEN, _PASSWORD are secrets
177
+ */
178
+ export function splitEnvAndSecrets(vars: EnvVars): {
179
+ env: EnvVars;
180
+ secrets: EnvVars;
181
+ } {
182
+ const env: EnvVars = {};
183
+ const secrets: EnvVars = {};
184
+
185
+ const secretSuffixes = ['_SECRET', '_KEY', '_TOKEN', '_PASSWORD', '_PRIVATE'];
186
+
187
+ for (const [key, value] of Object.entries(vars)) {
188
+ // Skip AGENTUITY_ prefixed keys
189
+ if (key.startsWith('AGENTUITY_')) {
190
+ continue;
191
+ }
192
+
193
+ const isSecret = secretSuffixes.some((suffix) => key.endsWith(suffix));
194
+
195
+ if (isSecret) {
196
+ secrets[key] = value;
197
+ } else {
198
+ env[key] = value;
199
+ }
200
+ }
201
+
202
+ return { env, secrets };
203
+ }
204
+
205
+ /**
206
+ * Mask a secret value for display
207
+ */
208
+ export function maskSecret(value: string): string {
209
+ if (!value) {
210
+ return '';
211
+ }
212
+
213
+ if (value.length <= 8) {
214
+ return '***';
215
+ }
216
+
217
+ // Show first 4 and last 4 characters
218
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
219
+ }
220
+
221
+ /**
222
+ * Detect if a key or value looks like it should be a secret
223
+ */
224
+ export function looksLikeSecret(key: string, value: string): boolean {
225
+ // Check key name for secret-like patterns
226
+ const secretKeyPatterns = [
227
+ /_SECRET$/i,
228
+ /_KEY$/i,
229
+ /_TOKEN$/i,
230
+ /_PASSWORD$/i,
231
+ /_PRIVATE$/i,
232
+ /_CERT$/i,
233
+ /_CERTIFICATE$/i,
234
+ /^SECRET_/i,
235
+ /^API_?KEY/i,
236
+ /^JWT/i,
237
+ /PASSWORD/i,
238
+ /CREDENTIAL/i,
239
+ /AUTH.*KEY/i,
240
+ ];
241
+
242
+ const keyLooksSecret = secretKeyPatterns.some((pattern) => pattern.test(key));
243
+ if (keyLooksSecret) {
244
+ return true;
245
+ }
246
+
247
+ // Check value for secret-like patterns
248
+ if (!value || value.length < 8) {
249
+ return false;
250
+ }
251
+
252
+ // JWT pattern (header.payload.signature)
253
+ if (/^eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value)) {
254
+ return true;
255
+ }
256
+
257
+ // Bearer token pattern
258
+ if (/^Bearer\s+[A-Za-z0-9_-]{20,}$/i.test(value)) {
259
+ return true;
260
+ }
261
+
262
+ // AWS/Cloud provider key patterns
263
+ if (/^(AKIA|ASIA)[A-Z0-9]{16}$/.test(value)) {
264
+ // AWS access key
265
+ return true;
266
+ }
267
+
268
+ // GitHub token patterns
269
+ if (/^gh[ps]_[A-Za-z0-9_]{36,}$/.test(value)) {
270
+ return true;
271
+ }
272
+
273
+ // Generic long alphanumeric strings (likely API keys)
274
+ // Exclude UUIDs (8-4-4-4-12 format) and simple alphanumeric IDs
275
+ const isUUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value);
276
+ if (!isUUID && /^[A-Za-z0-9_-]{32,}$/.test(value) && !/^[0-9]+$/.test(value)) {
277
+ return true;
278
+ }
279
+
280
+ // PEM-encoded certificates or private keys
281
+ if (
282
+ value.includes('BEGIN CERTIFICATE') ||
283
+ value.includes('BEGIN PRIVATE KEY') ||
284
+ value.includes('BEGIN RSA PRIVATE KEY')
285
+ ) {
286
+ return true;
287
+ }
288
+
289
+ return false;
290
+ }
package/src/index.ts CHANGED
@@ -17,7 +17,11 @@ export {
17
17
  getAuth,
18
18
  } from './config';
19
19
  export { APIClient, getAPIBaseURL, getAppBaseURL } from './api';
20
- export { Logger, logger } from './logger';
20
+ export {
21
+ ConsoleLogger,
22
+ createLogger,
23
+ type ColorScheme as LoggerColorScheme,
24
+ } from '@agentuity/server';
21
25
  export { showBanner } from './banner';
22
26
  export { discoverCommands } from './cmd';
23
27
  export { detectColorScheme } from './terminal';
@@ -203,9 +203,8 @@ export function buildValidationInput(
203
203
  if (schemas.options) {
204
204
  const parsed = parseOptionsSchema(schemas.options);
205
205
  for (const opt of parsed) {
206
- if (rawOptions[opt.name] !== undefined) {
207
- result.options[opt.name] = rawOptions[opt.name];
208
- }
206
+ // Always include the option value (even if undefined) so zod can apply defaults
207
+ result.options[opt.name] = rawOptions[opt.name];
209
208
  }
210
209
  }
211
210
 
package/src/steps.ts CHANGED
@@ -6,6 +6,59 @@
6
6
  */
7
7
 
8
8
  import type { ColorScheme } from './terminal';
9
+ import type { LogLevel } from './types';
10
+
11
+ /**
12
+ * Get the appropriate exit function (Bun.exit or process.exit)
13
+ */
14
+ function getExitFn(): (code: number) => never {
15
+ const bunExit = (globalThis as { Bun?: { exit?: (code: number) => never } }).Bun?.exit;
16
+ return typeof bunExit === 'function' ? bunExit : process.exit.bind(process);
17
+ }
18
+
19
+ /**
20
+ * Install interrupt handlers (SIGINT/SIGTERM + TTY raw mode for Ctrl+C)
21
+ */
22
+ function installInterruptHandlers(onInterrupt: () => void): () => void {
23
+ const cleanupFns: Array<() => void> = [];
24
+
25
+ const sigHandler = () => onInterrupt();
26
+ process.on('SIGINT', sigHandler);
27
+ process.on('SIGTERM', sigHandler);
28
+ cleanupFns.push(() => {
29
+ process.off('SIGINT', sigHandler);
30
+ process.off('SIGTERM', sigHandler);
31
+ });
32
+
33
+ // TTY raw mode fallback for Bun/Windows/inconsistent SIGINT delivery
34
+ const stdin = process.stdin as unknown as NodeJS.ReadStream;
35
+ if (stdin && stdin.isTTY) {
36
+ const onData = (buf: Buffer) => {
37
+ // Ctrl+C is ASCII ETX (0x03)
38
+ if (buf.length === 1 && buf[0] === 0x03) onInterrupt();
39
+ };
40
+ try {
41
+ stdin.setRawMode?.(true);
42
+ } catch {
43
+ // ignore if not supported
44
+ }
45
+ stdin.resume?.();
46
+ stdin.on('data', onData);
47
+ cleanupFns.push(() => {
48
+ stdin.off?.('data', onData);
49
+ stdin.pause?.();
50
+ try {
51
+ stdin.setRawMode?.(false);
52
+ } catch {
53
+ // ignore if setRawMode fails
54
+ }
55
+ });
56
+ }
57
+
58
+ return () => {
59
+ for (const fn of cleanupFns.splice(0)) fn();
60
+ };
61
+ }
9
62
 
10
63
  // Spinner frames
11
64
  const FRAMES = ['◐', '◓', '◑', '◒'];
@@ -35,7 +88,7 @@ const COLORS = {
35
88
  // Spinner color sequence
36
89
  const SPINNER_COLORS = ['cyan', 'blue', 'magenta', 'cyan'] as const;
37
90
 
38
- let currentColorScheme: ColorScheme = 'dark';
91
+ let currentColorScheme: ColorScheme = process.env.CI ? 'light' : 'dark';
39
92
 
40
93
  export function setStepsColorScheme(scheme: ColorScheme): void {
41
94
  currentColorScheme = scheme;
@@ -117,8 +170,10 @@ type StepState =
117
170
  * Each step runs its callback while showing a spinner animation.
118
171
  * Steps can complete with success, skipped, or error status.
119
172
  * Exits with code 1 if any step errors.
173
+ *
174
+ * When there's no TTY or log level is debug/trace, uses plain output instead of TUI.
120
175
  */
121
- export async function runSteps(steps: Step[]): Promise<void> {
176
+ export async function runSteps(steps: Step[], logLevel?: LogLevel): Promise<void> {
122
177
  const state: StepState[] = steps.map((s) => {
123
178
  const stepType = s.type === 'progress' ? 'progress' : 'simple';
124
179
  return stepType === 'progress'
@@ -130,26 +185,59 @@ export async function runSteps(steps: Step[]): Promise<void> {
130
185
  : { type: 'simple' as const, label: s.label, run: s.run as () => Promise<StepOutcome> };
131
186
  });
132
187
 
188
+ // Detect if we should use TUI (animated) or plain mode
189
+ const useTUI =
190
+ process.stdout.isTTY && (!logLevel || ['info', 'warn', 'error'].includes(logLevel));
191
+
192
+ if (useTUI) {
193
+ await runStepsTUI(state);
194
+ } else {
195
+ await runStepsPlain(state);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Run steps with animated TUI (original behavior)
201
+ */
202
+ async function runStepsTUI(state: StepState[]): Promise<void> {
133
203
  // Hide cursor
134
204
  process.stdout.write('\x1B[?25l');
135
205
 
206
+ // Track active interval and interrupted state
207
+ let activeInterval: ReturnType<typeof setInterval> | null = null;
208
+ let interrupted = false;
209
+
210
+ // Set up Ctrl+C handler for graceful exit
211
+ const exit = getExitFn();
212
+ const onInterrupt = () => {
213
+ if (interrupted) return;
214
+ interrupted = true;
215
+ if (activeInterval) clearInterval(activeInterval);
216
+ process.stdout.write('\x1B[?25h\n'); // Show cursor
217
+ exit(130);
218
+ };
219
+ const restoreInterrupts = installInterruptHandlers(onInterrupt);
220
+
136
221
  try {
137
222
  // Initial render
138
223
  process.stdout.write(renderSteps(state, -1) + '\n');
139
224
 
140
225
  for (let stepIndex = 0; stepIndex < state.length; stepIndex++) {
226
+ if (interrupted) break;
227
+
141
228
  const step = state[stepIndex];
142
229
  let frameIndex = 0;
230
+ let currentFrame = '';
143
231
 
144
232
  // Start spinner animation
145
- const interval = setInterval(() => {
233
+ activeInterval = setInterval(() => {
146
234
  const colorKey = SPINNER_COLORS[frameIndex % SPINNER_COLORS.length];
147
235
  const color = getColor(colorKey);
148
- const frame = `${color}${COLORS.bold}${FRAMES[frameIndex % FRAMES.length]}${COLORS.reset}`;
236
+ currentFrame = `${color}${COLORS.bold}${FRAMES[frameIndex % FRAMES.length]}${COLORS.reset}`;
149
237
 
150
238
  // Move cursor up to the top of checklist
151
239
  process.stdout.write(`\x1B[${state.length}A`);
152
- process.stdout.write(renderSteps(state, stepIndex, frame) + '\n');
240
+ process.stdout.write(renderSteps(state, stepIndex, currentFrame) + '\n');
153
241
 
154
242
  frameIndex++;
155
243
  }, 120);
@@ -158,9 +246,9 @@ export async function runSteps(steps: Step[]): Promise<void> {
158
246
  const progressCallback: ProgressCallback = (progress: number) => {
159
247
  step.progress = Math.min(100, Math.max(0, progress));
160
248
 
161
- // Move cursor up
249
+ // Move cursor up and render with current spinner frame
162
250
  process.stdout.write(`\x1B[${state.length}A`);
163
- process.stdout.write(renderSteps(state, stepIndex) + '\n');
251
+ process.stdout.write(renderSteps(state, stepIndex, currentFrame) + '\n');
164
252
  };
165
253
 
166
254
  try {
@@ -175,12 +263,15 @@ export async function runSteps(steps: Step[]): Promise<void> {
175
263
  };
176
264
  }
177
265
 
178
- clearInterval(interval);
266
+ if (activeInterval) {
267
+ clearInterval(activeInterval);
268
+ activeInterval = null;
269
+ }
179
270
 
180
271
  // Clear progress and final render with outcome
181
272
  step.progress = undefined;
182
273
  process.stdout.write(`\x1B[${state.length}A`);
183
- process.stdout.write(renderSteps(state, stepIndex) + '\n');
274
+ process.stdout.write(renderSteps(state, -1) + '\n');
184
275
 
185
276
  // If error, show error message and exit
186
277
  if (step.outcome?.status === 'error') {
@@ -192,11 +283,52 @@ export async function runSteps(steps: Step[]): Promise<void> {
192
283
  }
193
284
 
194
285
  // Show cursor again
195
- process.stdout.write('\x1B[?25h\n');
286
+ process.stdout.write('\x1B[?25h');
196
287
  } catch (err) {
197
288
  // Ensure cursor is shown even if something goes wrong
198
289
  process.stdout.write('\x1B[?25h');
199
290
  throw err;
291
+ } finally {
292
+ // Remove signal/TTY handlers
293
+ restoreInterrupts();
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Run steps in plain mode (no TUI animations)
299
+ */
300
+ async function runStepsPlain(state: StepState[]): Promise<void> {
301
+ const grayColor = getColor('gray');
302
+ const greenColor = getColor('green');
303
+ const yellowColor = getColor('yellow');
304
+ const redColor = getColor('red');
305
+
306
+ for (const step of state) {
307
+ // Run the step (no progress callback for plain mode)
308
+ try {
309
+ const outcome = step.type === 'progress' ? await step.run(() => {}) : await step.run();
310
+ step.outcome = outcome;
311
+ } catch (err) {
312
+ step.outcome = {
313
+ status: 'error',
314
+ message: err instanceof Error ? err.message : String(err),
315
+ };
316
+ }
317
+
318
+ // Print final state only
319
+ if (step.outcome?.status === 'success') {
320
+ console.log(`${greenColor}${ICONS.success}${COLORS.reset} ${step.label}`);
321
+ } else if (step.outcome?.status === 'skipped') {
322
+ const reason = step.outcome.reason
323
+ ? ` ${grayColor}(${step.outcome.reason})${COLORS.reset}`
324
+ : '';
325
+ console.log(`${yellowColor}${ICONS.skipped}${COLORS.reset} ${step.label}${reason}`);
326
+ } else if (step.outcome?.status === 'error') {
327
+ console.log(`${redColor}${ICONS.error}${COLORS.reset} ${step.label}`);
328
+ const errorColor = getColor('red');
329
+ console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}\n`);
330
+ process.exit(1);
331
+ }
200
332
  }
201
333
  }
202
334
 
@@ -222,6 +354,7 @@ function renderSteps(steps: StepState[], activeIndex: number, spinner?: string):
222
354
  const lines: string[] = [];
223
355
 
224
356
  steps.forEach((s, i) => {
357
+ // Don't show progress indicator for steps with outcomes (success/skipped/error)
225
358
  if (s.outcome?.status === 'success') {
226
359
  lines.push(
227
360
  `${greenColor}${ICONS.success}${COLORS.reset} ${grayColor}${COLORS.strikethrough}${s.label}${COLORS.reset}`
@@ -234,6 +367,7 @@ function renderSteps(steps: StepState[], activeIndex: number, spinner?: string):
234
367
  } else if (s.outcome?.status === 'error') {
235
368
  lines.push(`${redColor}${ICONS.error}${COLORS.reset} ${s.label}`);
236
369
  } else if (i === activeIndex && spinner) {
370
+ // Only show progress for active step with spinner
237
371
  const progressIndicator = s.progress !== undefined ? renderProgress(s.progress) : '';
238
372
  lines.push(`${spinner} ${s.label}${progressIndicator}`);
239
373
  } else {
package/src/terminal.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export type ColorScheme = 'light' | 'dark';
2
2
 
3
+ const defaultMode = process.env.CI ? 'light' : 'dark';
4
+
3
5
  export async function detectColorScheme(): Promise<ColorScheme> {
4
6
  const debug = process.env.DEBUG_COLORS === 'true';
5
7
 
@@ -14,28 +16,27 @@ export async function detectColorScheme(): Promise<ColorScheme> {
14
16
  }
15
17
 
16
18
  // Check if we have stdout TTY at minimum
17
- if (!process.stdout.isTTY) {
18
- if (debug) console.log('[DEBUG] stdout not a TTY, defaulting to dark');
19
- return 'dark'; // Default to dark mode
20
- }
21
-
22
- // Try to query terminal background color using OSC 11 (most reliable)
23
- if (debug) console.log('[DEBUG] Querying terminal background with OSC 11...');
24
- try {
25
- const bgColor = await queryTerminalBackground();
26
- if (bgColor) {
27
- const luminance = calculateLuminance(bgColor);
28
- const scheme = luminance > 0.5 ? 'light' : 'dark';
29
- if (debug)
30
- console.log(
31
- `[DEBUG] OSC 11 response: rgb(${bgColor.r},${bgColor.g},${bgColor.b}), luminance: ${luminance.toFixed(2)}, scheme: ${scheme}`
32
- );
33
- return scheme;
34
- } else {
35
- if (debug) console.log('[DEBUG] OSC 11 query timed out or no response');
19
+ if (process.stdout.isTTY) {
20
+ if (debug) console.log('[DEBUG] stdout is a TTY, defaulting to dark');
21
+
22
+ // Try to query terminal background color using OSC 11 (most reliable)
23
+ if (debug) console.log('[DEBUG] Querying terminal background with OSC 11...');
24
+ try {
25
+ const bgColor = await queryTerminalBackground();
26
+ if (bgColor) {
27
+ const luminance = calculateLuminance(bgColor);
28
+ const scheme = luminance > 0.5 ? 'light' : 'dark';
29
+ if (debug)
30
+ console.log(
31
+ `[DEBUG] OSC 11 response: rgb(${bgColor.r},${bgColor.g},${bgColor.b}), luminance: ${luminance.toFixed(2)}, scheme: ${scheme}`
32
+ );
33
+ return scheme;
34
+ } else {
35
+ if (debug) console.log('[DEBUG] OSC 11 query timed out or no response');
36
+ }
37
+ } catch (error) {
38
+ if (debug) console.log('[DEBUG] OSC 11 query failed:', error);
36
39
  }
37
- } catch (error) {
38
- if (debug) console.log('[DEBUG] OSC 11 query failed:', error);
39
40
  }
40
41
 
41
42
  // Fall back to COLORFGBG environment variable (less reliable)
@@ -56,8 +57,8 @@ export async function detectColorScheme(): Promise<ColorScheme> {
56
57
  return scheme;
57
58
  }
58
59
 
59
- if (debug) console.log('[DEBUG] Defaulting to dark mode');
60
- return 'dark'; // Default to dark mode
60
+ if (debug) console.log('[DEBUG] Defaulting to %s', defaultMode);
61
+ return defaultMode;
61
62
  }
62
63
 
63
64
  interface RGBColor {