@agentuity/cli 0.0.41 → 0.0.43

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 (117) hide show
  1. package/bin/cli.ts +7 -5
  2. package/dist/banner.d.ts.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cmd/auth/index.d.ts.map +1 -1
  5. package/dist/cmd/auth/login.d.ts.map +1 -1
  6. package/dist/cmd/auth/whoami.d.ts +2 -0
  7. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  8. package/dist/cmd/bundle/ast.d.ts +2 -0
  9. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  10. package/dist/cmd/bundle/index.d.ts +1 -1
  11. package/dist/cmd/bundle/index.d.ts.map +1 -1
  12. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  13. package/dist/cmd/cloud/deploy.d.ts +2 -0
  14. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  15. package/dist/cmd/cloud/index.d.ts +2 -0
  16. package/dist/cmd/cloud/index.d.ts.map +1 -0
  17. package/dist/cmd/dev/index.d.ts.map +1 -1
  18. package/dist/cmd/env/delete.d.ts +2 -0
  19. package/dist/cmd/env/delete.d.ts.map +1 -0
  20. package/dist/cmd/env/get.d.ts +2 -0
  21. package/dist/cmd/env/get.d.ts.map +1 -0
  22. package/dist/cmd/env/import.d.ts +2 -0
  23. package/dist/cmd/env/import.d.ts.map +1 -0
  24. package/dist/cmd/env/index.d.ts +2 -0
  25. package/dist/cmd/env/index.d.ts.map +1 -0
  26. package/dist/cmd/env/list.d.ts +2 -0
  27. package/dist/cmd/env/list.d.ts.map +1 -0
  28. package/dist/cmd/env/pull.d.ts +2 -0
  29. package/dist/cmd/env/pull.d.ts.map +1 -0
  30. package/dist/cmd/env/push.d.ts +2 -0
  31. package/dist/cmd/env/push.d.ts.map +1 -0
  32. package/dist/cmd/env/set.d.ts +2 -0
  33. package/dist/cmd/env/set.d.ts.map +1 -0
  34. package/dist/cmd/project/delete.d.ts.map +1 -1
  35. package/dist/cmd/project/download.d.ts +1 -1
  36. package/dist/cmd/project/download.d.ts.map +1 -1
  37. package/dist/cmd/project/list.d.ts.map +1 -1
  38. package/dist/cmd/project/show.d.ts.map +1 -1
  39. package/dist/cmd/project/template-flow.d.ts +1 -1
  40. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  41. package/dist/cmd/secret/delete.d.ts +2 -0
  42. package/dist/cmd/secret/delete.d.ts.map +1 -0
  43. package/dist/cmd/secret/get.d.ts +2 -0
  44. package/dist/cmd/secret/get.d.ts.map +1 -0
  45. package/dist/cmd/secret/import.d.ts +2 -0
  46. package/dist/cmd/secret/import.d.ts.map +1 -0
  47. package/dist/cmd/secret/index.d.ts +2 -0
  48. package/dist/cmd/secret/index.d.ts.map +1 -0
  49. package/dist/cmd/secret/list.d.ts +2 -0
  50. package/dist/cmd/secret/list.d.ts.map +1 -0
  51. package/dist/cmd/secret/pull.d.ts +2 -0
  52. package/dist/cmd/secret/pull.d.ts.map +1 -0
  53. package/dist/cmd/secret/push.d.ts +2 -0
  54. package/dist/cmd/secret/push.d.ts.map +1 -0
  55. package/dist/cmd/secret/set.d.ts +2 -0
  56. package/dist/cmd/secret/set.d.ts.map +1 -0
  57. package/dist/cmd/version/index.d.ts.map +1 -1
  58. package/dist/config.d.ts +4 -1
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/env-util.d.ts +67 -0
  61. package/dist/env-util.d.ts.map +1 -0
  62. package/dist/env-util.test.d.ts +2 -0
  63. package/dist/env-util.test.d.ts.map +1 -0
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/schema-parser.d.ts.map +1 -1
  67. package/dist/steps.d.ts.map +1 -1
  68. package/dist/tui.d.ts +1 -1
  69. package/dist/tui.d.ts.map +1 -1
  70. package/dist/types.d.ts +35 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/src/banner.ts +7 -2
  74. package/src/cli.ts +46 -5
  75. package/src/cmd/auth/index.ts +2 -1
  76. package/src/cmd/auth/login.ts +2 -4
  77. package/src/cmd/auth/whoami.ts +69 -0
  78. package/src/cmd/bundle/ast.ts +169 -4
  79. package/src/cmd/bundle/index.ts +2 -2
  80. package/src/cmd/bundle/plugin.ts +42 -1
  81. package/src/cmd/cloud/deploy.ts +129 -0
  82. package/src/cmd/cloud/index.ts +8 -0
  83. package/src/cmd/dev/index.ts +93 -9
  84. package/src/cmd/env/delete.ts +62 -0
  85. package/src/cmd/env/get.ts +66 -0
  86. package/src/cmd/env/import.ts +117 -0
  87. package/src/cmd/env/index.ts +22 -0
  88. package/src/cmd/env/list.ts +69 -0
  89. package/src/cmd/env/pull.ts +93 -0
  90. package/src/cmd/env/push.ts +55 -0
  91. package/src/cmd/env/set.ts +86 -0
  92. package/src/cmd/project/create.ts +1 -1
  93. package/src/cmd/project/delete.ts +43 -2
  94. package/src/cmd/project/download.ts +1 -1
  95. package/src/cmd/project/list.ts +33 -2
  96. package/src/cmd/project/show.ts +35 -3
  97. package/src/cmd/project/template-flow.ts +53 -12
  98. package/src/cmd/secret/delete.ts +55 -0
  99. package/src/cmd/secret/get.ts +67 -0
  100. package/src/cmd/secret/import.ts +79 -0
  101. package/src/cmd/secret/index.ts +22 -0
  102. package/src/cmd/secret/list.ts +69 -0
  103. package/src/cmd/secret/pull.ts +91 -0
  104. package/src/cmd/secret/push.ts +55 -0
  105. package/src/cmd/secret/set.ts +60 -0
  106. package/src/cmd/version/index.ts +2 -1
  107. package/src/config.ts +60 -7
  108. package/src/env-util.test.ts +194 -0
  109. package/src/env-util.ts +290 -0
  110. package/src/index.ts +5 -1
  111. package/src/schema-parser.ts +2 -3
  112. package/src/steps.ts +79 -4
  113. package/src/tui.ts +92 -56
  114. package/src/types.ts +30 -1
  115. package/dist/logger.d.ts +0 -24
  116. package/dist/logger.d.ts.map +0 -1
  117. package/src/logger.ts +0 -235
@@ -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
@@ -7,6 +7,58 @@
7
7
 
8
8
  import type { ColorScheme } from './terminal';
9
9
 
10
+ /**
11
+ * Get the appropriate exit function (Bun.exit or process.exit)
12
+ */
13
+ function getExitFn(): (code: number) => never {
14
+ const bunExit = (globalThis as { Bun?: { exit?: (code: number) => never } }).Bun?.exit;
15
+ return typeof bunExit === 'function' ? bunExit : process.exit.bind(process);
16
+ }
17
+
18
+ /**
19
+ * Install interrupt handlers (SIGINT/SIGTERM + TTY raw mode for Ctrl+C)
20
+ */
21
+ function installInterruptHandlers(onInterrupt: () => void): () => void {
22
+ const cleanupFns: Array<() => void> = [];
23
+
24
+ const sigHandler = () => onInterrupt();
25
+ process.on('SIGINT', sigHandler);
26
+ process.on('SIGTERM', sigHandler);
27
+ cleanupFns.push(() => {
28
+ process.off('SIGINT', sigHandler);
29
+ process.off('SIGTERM', sigHandler);
30
+ });
31
+
32
+ // TTY raw mode fallback for Bun/Windows/inconsistent SIGINT delivery
33
+ const stdin = process.stdin as unknown as NodeJS.ReadStream;
34
+ if (stdin && stdin.isTTY) {
35
+ const onData = (buf: Buffer) => {
36
+ // Ctrl+C is ASCII ETX (0x03)
37
+ if (buf.length === 1 && buf[0] === 0x03) onInterrupt();
38
+ };
39
+ try {
40
+ stdin.setRawMode?.(true);
41
+ } catch {
42
+ // ignore if not supported
43
+ }
44
+ stdin.resume?.();
45
+ stdin.on('data', onData);
46
+ cleanupFns.push(() => {
47
+ stdin.off?.('data', onData);
48
+ stdin.pause?.();
49
+ try {
50
+ stdin.setRawMode?.(false);
51
+ } catch {
52
+ // ignore if setRawMode fails
53
+ }
54
+ });
55
+ }
56
+
57
+ return () => {
58
+ for (const fn of cleanupFns.splice(0)) fn();
59
+ };
60
+ }
61
+
10
62
  // Spinner frames
11
63
  const FRAMES = ['◐', '◓', '◑', '◒'];
12
64
 
@@ -35,7 +87,7 @@ const COLORS = {
35
87
  // Spinner color sequence
36
88
  const SPINNER_COLORS = ['cyan', 'blue', 'magenta', 'cyan'] as const;
37
89
 
38
- let currentColorScheme: ColorScheme = 'dark';
90
+ let currentColorScheme: ColorScheme = process.env.CI ? 'light' : 'dark';
39
91
 
40
92
  export function setStepsColorScheme(scheme: ColorScheme): void {
41
93
  currentColorScheme = scheme;
@@ -133,16 +185,33 @@ export async function runSteps(steps: Step[]): Promise<void> {
133
185
  // Hide cursor
134
186
  process.stdout.write('\x1B[?25l');
135
187
 
188
+ // Track active interval and interrupted state
189
+ let activeInterval: ReturnType<typeof setInterval> | null = null;
190
+ let interrupted = false;
191
+
192
+ // Set up Ctrl+C handler for graceful exit
193
+ const exit = getExitFn();
194
+ const onInterrupt = () => {
195
+ if (interrupted) return;
196
+ interrupted = true;
197
+ if (activeInterval) clearInterval(activeInterval);
198
+ process.stdout.write('\x1B[?25h\n'); // Show cursor
199
+ exit(130);
200
+ };
201
+ const restoreInterrupts = installInterruptHandlers(onInterrupt);
202
+
136
203
  try {
137
204
  // Initial render
138
205
  process.stdout.write(renderSteps(state, -1) + '\n');
139
206
 
140
207
  for (let stepIndex = 0; stepIndex < state.length; stepIndex++) {
208
+ if (interrupted) break;
209
+
141
210
  const step = state[stepIndex];
142
211
  let frameIndex = 0;
143
212
 
144
213
  // Start spinner animation
145
- const interval = setInterval(() => {
214
+ activeInterval = setInterval(() => {
146
215
  const colorKey = SPINNER_COLORS[frameIndex % SPINNER_COLORS.length];
147
216
  const color = getColor(colorKey);
148
217
  const frame = `${color}${COLORS.bold}${FRAMES[frameIndex % FRAMES.length]}${COLORS.reset}`;
@@ -175,7 +244,10 @@ export async function runSteps(steps: Step[]): Promise<void> {
175
244
  };
176
245
  }
177
246
 
178
- clearInterval(interval);
247
+ if (activeInterval) {
248
+ clearInterval(activeInterval);
249
+ activeInterval = null;
250
+ }
179
251
 
180
252
  // Clear progress and final render with outcome
181
253
  step.progress = undefined;
@@ -192,11 +264,14 @@ export async function runSteps(steps: Step[]): Promise<void> {
192
264
  }
193
265
 
194
266
  // Show cursor again
195
- process.stdout.write('\x1B[?25h\n');
267
+ process.stdout.write('\x1B[?25h');
196
268
  } catch (err) {
197
269
  // Ensure cursor is shown even if something goes wrong
198
270
  process.stdout.write('\x1B[?25h');
199
271
  throw err;
272
+ } finally {
273
+ // Remove signal/TTY handlers
274
+ restoreInterrupts();
200
275
  }
201
276
  }
202
277