@agentuity/cli 0.1.23 → 0.1.25

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 (171) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +36 -5
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/ai/cadence/index.d.ts +3 -0
  5. package/dist/cmd/ai/cadence/index.d.ts.map +1 -0
  6. package/dist/cmd/ai/cadence/index.js +35 -0
  7. package/dist/cmd/ai/cadence/index.js.map +1 -0
  8. package/dist/cmd/ai/cadence/list.d.ts +3 -0
  9. package/dist/cmd/ai/cadence/list.d.ts.map +1 -0
  10. package/dist/cmd/ai/cadence/list.js +167 -0
  11. package/dist/cmd/ai/cadence/list.js.map +1 -0
  12. package/dist/cmd/ai/cadence/pause.d.ts +3 -0
  13. package/dist/cmd/ai/cadence/pause.d.ts.map +1 -0
  14. package/dist/cmd/ai/cadence/pause.js +103 -0
  15. package/dist/cmd/ai/cadence/pause.js.map +1 -0
  16. package/dist/cmd/ai/cadence/resume.d.ts +3 -0
  17. package/dist/cmd/ai/cadence/resume.d.ts.map +1 -0
  18. package/dist/cmd/ai/cadence/resume.js +106 -0
  19. package/dist/cmd/ai/cadence/resume.js.map +1 -0
  20. package/dist/cmd/ai/cadence/status.d.ts +3 -0
  21. package/dist/cmd/ai/cadence/status.d.ts.map +1 -0
  22. package/dist/cmd/ai/cadence/status.js +129 -0
  23. package/dist/cmd/ai/cadence/status.js.map +1 -0
  24. package/dist/cmd/ai/cadence/stop.d.ts +3 -0
  25. package/dist/cmd/ai/cadence/stop.d.ts.map +1 -0
  26. package/dist/cmd/ai/cadence/stop.js +107 -0
  27. package/dist/cmd/ai/cadence/stop.js.map +1 -0
  28. package/dist/cmd/ai/cadence/util.d.ts +44 -0
  29. package/dist/cmd/ai/cadence/util.d.ts.map +1 -0
  30. package/dist/cmd/ai/cadence/util.js +52 -0
  31. package/dist/cmd/ai/cadence/util.js.map +1 -0
  32. package/dist/cmd/ai/index.d.ts.map +1 -1
  33. package/dist/cmd/ai/index.js +9 -1
  34. package/dist/cmd/ai/index.js.map +1 -1
  35. package/dist/cmd/auth/machine/setup.js +1 -1
  36. package/dist/cmd/auth/machine/setup.js.map +1 -1
  37. package/dist/cmd/auth/ssh/add.js +1 -1
  38. package/dist/cmd/auth/ssh/add.js.map +1 -1
  39. package/dist/cmd/build/patch/index.d.ts.map +1 -1
  40. package/dist/cmd/build/patch/index.js +4 -0
  41. package/dist/cmd/build/patch/index.js.map +1 -1
  42. package/dist/cmd/build/patch/otel-llm.d.ts +10 -0
  43. package/dist/cmd/build/patch/otel-llm.d.ts.map +1 -0
  44. package/dist/cmd/build/patch/otel-llm.js +374 -0
  45. package/dist/cmd/build/patch/otel-llm.js.map +1 -0
  46. package/dist/cmd/cloud/db/create.js +3 -3
  47. package/dist/cmd/cloud/db/create.js.map +1 -1
  48. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  49. package/dist/cmd/cloud/deploy.js +55 -2
  50. package/dist/cmd/cloud/deploy.js.map +1 -1
  51. package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
  52. package/dist/cmd/cloud/env/pull.js +26 -17
  53. package/dist/cmd/cloud/env/pull.js.map +1 -1
  54. package/dist/cmd/cloud/eval/get.d.ts +2 -0
  55. package/dist/cmd/cloud/eval/get.d.ts.map +1 -0
  56. package/dist/cmd/cloud/eval/get.js +79 -0
  57. package/dist/cmd/cloud/eval/get.js.map +1 -0
  58. package/dist/cmd/cloud/eval/index.d.ts +2 -0
  59. package/dist/cmd/cloud/eval/index.d.ts.map +1 -0
  60. package/dist/cmd/cloud/eval/index.js +15 -0
  61. package/dist/cmd/cloud/eval/index.js.map +1 -0
  62. package/dist/cmd/cloud/eval/list.d.ts +2 -0
  63. package/dist/cmd/cloud/eval/list.d.ts.map +1 -0
  64. package/dist/cmd/cloud/eval/list.js +119 -0
  65. package/dist/cmd/cloud/eval/list.js.map +1 -0
  66. package/dist/cmd/cloud/eval-run/get.d.ts +2 -0
  67. package/dist/cmd/cloud/eval-run/get.d.ts.map +1 -0
  68. package/dist/cmd/cloud/eval-run/get.js +106 -0
  69. package/dist/cmd/cloud/eval-run/get.js.map +1 -0
  70. package/dist/cmd/cloud/eval-run/index.d.ts +2 -0
  71. package/dist/cmd/cloud/eval-run/index.d.ts.map +1 -0
  72. package/dist/cmd/cloud/eval-run/index.js +15 -0
  73. package/dist/cmd/cloud/eval-run/index.js.map +1 -0
  74. package/dist/cmd/cloud/eval-run/list.d.ts +2 -0
  75. package/dist/cmd/cloud/eval-run/list.d.ts.map +1 -0
  76. package/dist/cmd/cloud/eval-run/list.js +144 -0
  77. package/dist/cmd/cloud/eval-run/list.js.map +1 -0
  78. package/dist/cmd/cloud/index.d.ts.map +1 -1
  79. package/dist/cmd/cloud/index.js +4 -0
  80. package/dist/cmd/cloud/index.js.map +1 -1
  81. package/dist/cmd/cloud/machine/list.d.ts.map +1 -1
  82. package/dist/cmd/cloud/machine/list.js +16 -0
  83. package/dist/cmd/cloud/machine/list.js.map +1 -1
  84. package/dist/cmd/cloud/queue/dlq.d.ts.map +1 -1
  85. package/dist/cmd/cloud/queue/dlq.js +15 -10
  86. package/dist/cmd/cloud/queue/dlq.js.map +1 -1
  87. package/dist/cmd/cloud/sandbox/download.d.ts.map +1 -1
  88. package/dist/cmd/cloud/sandbox/download.js +8 -3
  89. package/dist/cmd/cloud/sandbox/download.js.map +1 -1
  90. package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
  91. package/dist/cmd/cloud/sandbox/snapshot/build.js +43 -36
  92. package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
  93. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
  94. package/dist/cmd/cloud/sandbox/snapshot/create.js +5 -1
  95. package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
  96. package/dist/cmd/cloud/sandbox/upload.d.ts.map +1 -1
  97. package/dist/cmd/cloud/sandbox/upload.js +8 -1
  98. package/dist/cmd/cloud/sandbox/upload.js.map +1 -1
  99. package/dist/cmd/cloud/storage/upload.js +1 -1
  100. package/dist/cmd/cloud/storage/upload.js.map +1 -1
  101. package/dist/cmd/cloud/vector/stats.d.ts.map +1 -1
  102. package/dist/cmd/cloud/vector/stats.js +3 -1
  103. package/dist/cmd/cloud/vector/stats.js.map +1 -1
  104. package/dist/cmd/cloud/vector/upsert.js +1 -1
  105. package/dist/cmd/cloud/vector/upsert.js.map +1 -1
  106. package/dist/cmd/profile/create.js +2 -2
  107. package/dist/cmd/profile/create.js.map +1 -1
  108. package/dist/cmd/project/create.d.ts.map +1 -1
  109. package/dist/cmd/project/create.js +6 -3
  110. package/dist/cmd/project/create.js.map +1 -1
  111. package/dist/cmd/project/delete.d.ts.map +1 -1
  112. package/dist/cmd/project/delete.js +46 -10
  113. package/dist/cmd/project/delete.js.map +1 -1
  114. package/dist/cmd/setup/index.d.ts.map +1 -1
  115. package/dist/cmd/setup/index.js +4 -1
  116. package/dist/cmd/setup/index.js.map +1 -1
  117. package/dist/env-util.d.ts.map +1 -1
  118. package/dist/env-util.js +4 -1
  119. package/dist/env-util.js.map +1 -1
  120. package/dist/schema-parser.d.ts +17 -1
  121. package/dist/schema-parser.d.ts.map +1 -1
  122. package/dist/schema-parser.js +131 -2
  123. package/dist/schema-parser.js.map +1 -1
  124. package/dist/tui.d.ts.map +1 -1
  125. package/dist/tui.js +4 -0
  126. package/dist/tui.js.map +1 -1
  127. package/dist/utils/deps.d.ts +8 -0
  128. package/dist/utils/deps.d.ts.map +1 -0
  129. package/dist/utils/deps.js +36 -0
  130. package/dist/utils/deps.js.map +1 -0
  131. package/package.json +6 -6
  132. package/src/cli.ts +36 -5
  133. package/src/cmd/ai/cadence/index.ts +36 -0
  134. package/src/cmd/ai/cadence/list.ts +183 -0
  135. package/src/cmd/ai/cadence/pause.ts +119 -0
  136. package/src/cmd/ai/cadence/resume.ts +124 -0
  137. package/src/cmd/ai/cadence/status.ts +141 -0
  138. package/src/cmd/ai/cadence/stop.ts +124 -0
  139. package/src/cmd/ai/cadence/util.ts +86 -0
  140. package/src/cmd/ai/index.ts +9 -1
  141. package/src/cmd/auth/machine/setup.ts +1 -1
  142. package/src/cmd/auth/ssh/add.ts +1 -1
  143. package/src/cmd/build/patch/index.ts +4 -0
  144. package/src/cmd/build/patch/otel-llm.ts +421 -0
  145. package/src/cmd/cloud/db/create.ts +3 -3
  146. package/src/cmd/cloud/deploy.ts +77 -1
  147. package/src/cmd/cloud/env/pull.ts +29 -19
  148. package/src/cmd/cloud/eval/get.ts +85 -0
  149. package/src/cmd/cloud/eval/index.ts +15 -0
  150. package/src/cmd/cloud/eval/list.ts +129 -0
  151. package/src/cmd/cloud/eval-run/get.ts +113 -0
  152. package/src/cmd/cloud/eval-run/index.ts +15 -0
  153. package/src/cmd/cloud/eval-run/list.ts +154 -0
  154. package/src/cmd/cloud/index.ts +4 -0
  155. package/src/cmd/cloud/machine/list.ts +16 -0
  156. package/src/cmd/cloud/queue/dlq.ts +16 -10
  157. package/src/cmd/cloud/sandbox/download.ts +9 -3
  158. package/src/cmd/cloud/sandbox/snapshot/build.ts +64 -45
  159. package/src/cmd/cloud/sandbox/snapshot/create.ts +5 -1
  160. package/src/cmd/cloud/sandbox/upload.ts +9 -1
  161. package/src/cmd/cloud/storage/upload.ts +1 -1
  162. package/src/cmd/cloud/vector/stats.ts +3 -1
  163. package/src/cmd/cloud/vector/upsert.ts +1 -1
  164. package/src/cmd/profile/create.ts +2 -2
  165. package/src/cmd/project/create.ts +6 -3
  166. package/src/cmd/project/delete.ts +55 -10
  167. package/src/cmd/setup/index.ts +4 -1
  168. package/src/env-util.ts +4 -1
  169. package/src/schema-parser.ts +150 -2
  170. package/src/tui.ts +5 -0
  171. package/src/utils/deps.ts +54 -0
@@ -138,7 +138,8 @@ function parseKeyValueArgs(args: string[] | undefined): Record<string, string> {
138
138
 
139
139
  function substituteVariables(
140
140
  values: Record<string, string>,
141
- variables: Record<string, string>
141
+ variables: Record<string, string>,
142
+ flagName: 'env' | 'metadata'
142
143
  ): Record<string, string> {
143
144
  const result: Record<string, string> = {};
144
145
  const varPattern = /\$\{([^}]+)\}/g;
@@ -152,7 +153,7 @@ function substituteVariables(
152
153
  const varName = match[1];
153
154
  if (!(varName in variables)) {
154
155
  throw new Error(
155
- `Variable "\${${varName}}" in "${key}" is not defined. Use --env ${varName}=value to provide it.`
156
+ `Variable "\${${varName}}" in "${key}" is not defined. Use --${flagName} ${varName}=value to provide it.`
156
157
  );
157
158
  }
158
159
  substituted = substituted.replace(match[0], variables[varName]);
@@ -163,12 +164,15 @@ function substituteVariables(
163
164
  return result;
164
165
  }
165
166
 
167
+ // Default patterns that are always excluded from snapshot builds
168
+ const DEFAULT_EXCLUSIONS = ['.git/**', 'node_modules/**', '.agentuity/**', '.env*'];
169
+
166
170
  async function resolveFileGlobs(
167
171
  directory: string,
168
172
  patterns: string[]
169
173
  ): Promise<Map<string, FileEntry>> {
170
174
  const files = new Map<string, FileEntry>();
171
- const exclusions: string[] = [];
175
+ const exclusions: string[] = [...DEFAULT_EXCLUSIONS];
172
176
  const inclusions: string[] = [];
173
177
 
174
178
  for (const pattern of patterns) {
@@ -200,15 +204,19 @@ async function resolveFileGlobs(
200
204
  }
201
205
 
202
206
  for (let pattern of exclusions) {
203
- // If the pattern refers to a directory, auto-append /** to exclude all contents
204
- const patternPath = join(directory, pattern);
205
- try {
206
- const stat = statSync(patternPath);
207
- if (stat.isDirectory()) {
208
- pattern = pattern.endsWith('/') ? `${pattern}**` : `${pattern}/**`;
207
+ // If pattern already contains glob wildcards, use it as-is
208
+ // Otherwise, check if it refers to a directory and auto-append /** to exclude all contents
209
+ const hasGlobChars = /[*?[\]]/.test(pattern);
210
+ if (!hasGlobChars) {
211
+ const patternPath = join(directory, pattern);
212
+ try {
213
+ const stat = statSync(patternPath);
214
+ if (stat.isDirectory()) {
215
+ pattern = pattern.endsWith('/') ? `${pattern}**` : `${pattern}/**`;
216
+ }
217
+ } catch {
218
+ // Path doesn't exist or can't be stat'd, use pattern as-is
209
219
  }
210
- } catch {
211
- // Path doesn't exist or can't be stat'd, use pattern as-is
212
220
  }
213
221
 
214
222
  const glob = new Bun.Glob(pattern);
@@ -379,37 +387,6 @@ export const buildSubcommand = createCommand({
379
387
  const { args, opts, options, auth, region, config, logger, orgId } = ctx;
380
388
 
381
389
  const dryRun = options.dryRun === true;
382
- const isPublic = opts.public === true;
383
-
384
- if (isPublic && !dryRun) {
385
- if (!opts.confirm) {
386
- if (!tui.isTTYLike()) {
387
- logger.fatal(
388
- `Publishing a public snapshot requires confirmation.\n\n` +
389
- `Public snapshots make all environment variables and files publicly accessible.\n\n` +
390
- `To proceed, add the --confirm flag:\n` +
391
- ` ${getCommand('cloud sandbox snapshot build . --public --confirm')}\n\n` +
392
- `To preview what will be published, use --dry-run first:\n` +
393
- ` ${getCommand('cloud sandbox snapshot build . --public --dry-run')}`
394
- );
395
- }
396
-
397
- tui.warningBox(
398
- 'Public Snapshot',
399
- `You are publishing a public snapshot.\n\n` +
400
- `This will make all environment variables and\n` +
401
- `files in the snapshot publicly accessible.\n\n` +
402
- `Run with --dry-run to preview the contents.`
403
- );
404
- console.log('');
405
-
406
- const confirmed = await tui.confirm('Proceed with public snapshot?', false);
407
-
408
- if (!confirmed) {
409
- logger.fatal('Aborted');
410
- }
411
- }
412
- }
413
390
 
414
391
  const directory = resolve(args.directory);
415
392
  if (!existsSync(directory)) {
@@ -470,6 +447,40 @@ export const buildSubcommand = createCommand({
470
447
 
471
448
  const buildConfig = validationResult.data;
472
449
 
450
+ // Determine if snapshot is public: CLI flag takes precedence, otherwise use build file
451
+ const isPublic =
452
+ opts.public === true || (opts.public === undefined && buildConfig.public === true);
453
+
454
+ if (isPublic && !dryRun) {
455
+ if (!opts.confirm) {
456
+ if (!tui.isTTYLike()) {
457
+ logger.fatal(
458
+ `Publishing a public snapshot requires confirmation.\n\n` +
459
+ `Public snapshots make all environment variables and files publicly accessible.\n\n` +
460
+ `To proceed, add the --confirm flag:\n` +
461
+ ` ${getCommand('cloud sandbox snapshot build . --public --confirm')}\n\n` +
462
+ `To preview what will be published, use --dry-run first:\n` +
463
+ ` ${getCommand('cloud sandbox snapshot build . --public --dry-run')}`
464
+ );
465
+ }
466
+
467
+ tui.warningBox(
468
+ 'Public Snapshot',
469
+ `You are publishing a public snapshot.\n\n` +
470
+ `This will make all environment variables and\n` +
471
+ `files in the snapshot publicly accessible.\n\n` +
472
+ `Run with --dry-run to preview the contents.`
473
+ );
474
+ console.log('');
475
+
476
+ const confirmed = await tui.confirm('Proceed with public snapshot?', false);
477
+
478
+ if (!confirmed) {
479
+ logger.fatal('Aborted');
480
+ }
481
+ }
482
+ }
483
+
473
484
  if (opts.tag) {
474
485
  if (opts.tag.length > MAX_SNAPSHOT_TAG_LENGTH) {
475
486
  logger.fatal(
@@ -502,10 +513,14 @@ export const buildSubcommand = createCommand({
502
513
 
503
514
  try {
504
515
  if (buildConfig.env) {
505
- finalEnv = substituteVariables(buildConfig.env, envSubstitutions);
516
+ finalEnv = substituteVariables(buildConfig.env, envSubstitutions, 'env');
506
517
  }
507
518
  if (buildConfig.metadata) {
508
- finalMetadata = substituteVariables(buildConfig.metadata, metadataSubstitutions);
519
+ finalMetadata = substituteVariables(
520
+ buildConfig.metadata,
521
+ metadataSubstitutions,
522
+ 'metadata'
523
+ );
509
524
  }
510
525
  } catch (err) {
511
526
  logger.fatal(err instanceof Error ? err.message : String(err));
@@ -811,7 +826,11 @@ export const buildSubcommand = createCommand({
811
826
  clearOnError: true,
812
827
  callback: async (updateProgress) => {
813
828
  const uploadFile = Bun.file(uploadPath);
814
- const progressStream = createProgressStream(uploadFile, uploadSize, updateProgress);
829
+ const progressStream = createProgressStream(
830
+ uploadFile,
831
+ uploadSize,
832
+ updateProgress
833
+ );
815
834
  await snapshotUpload(client, {
816
835
  snapshotId: initResult.snapshotId!,
817
836
  body: progressStream,
@@ -55,7 +55,11 @@ export const createSubcommand = createCommand({
55
55
  .describe('Display name for the snapshot (letters, numbers, underscores, dashes only)'),
56
56
  description: z.string().optional().describe('Description of the snapshot'),
57
57
  tag: z.string().optional().describe('Tag for the snapshot (defaults to "latest")'),
58
- public: z.boolean().optional().default(false).describe('Make the snapshot publicly accessible'),
58
+ public: z
59
+ .boolean()
60
+ .optional()
61
+ .default(false)
62
+ .describe('Make the snapshot publicly accessible'),
59
63
  }),
60
64
  response: SnapshotCreateResponseSchema,
61
65
  },
@@ -58,11 +58,13 @@ export const uploadSubcommand = createCommand({
58
58
  const content = readFileSync(args.archive);
59
59
  const bytes = content.length;
60
60
 
61
+ const format = opts.format ?? detectFormat(args.archive);
62
+
61
63
  await sandboxUploadArchive(client, {
62
64
  sandboxId: args.sandboxId,
63
65
  archive: content,
64
66
  path: opts.path || '.',
65
- format: opts.format || '',
67
+ format,
66
68
  orgId,
67
69
  });
68
70
 
@@ -81,4 +83,10 @@ function formatSize(bytes: number): string {
81
83
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
82
84
  }
83
85
 
86
+ function detectFormat(filename: string): 'zip' | 'tar.gz' {
87
+ const lower = filename.toLowerCase();
88
+ if (lower.endsWith('.zip')) return 'zip';
89
+ return 'tar.gz';
90
+ }
91
+
84
92
  export default uploadSubcommand;
@@ -13,7 +13,7 @@ export const uploadSubcommand = createSubcommand({
13
13
  name: 'upload',
14
14
  aliases: ['put'],
15
15
  description: 'Upload a file to storage bucket',
16
- tags: ['write', 'requires-auth'],
16
+ tags: ['write', 'requires-auth', 'uses-stdin'],
17
17
  requires: { auth: true },
18
18
  optional: { org: true },
19
19
  idempotent: false,
@@ -22,7 +22,9 @@ const VectorNamespaceStatsSchema = z.object({
22
22
  });
23
23
 
24
24
  const VectorStatsPaginatedSchema = z.object({
25
- namespaces: z.record(z.string(), VectorNamespaceStatsSchema).describe('Map of namespace names to their statistics'),
25
+ namespaces: z
26
+ .record(z.string(), VectorNamespaceStatsSchema)
27
+ .describe('Map of namespace names to their statistics'),
26
28
  total: z.number().describe('Total number of namespaces across all pages'),
27
29
  limit: z.number().describe('Number of namespaces requested per page'),
28
30
  offset: z.number().describe('Number of namespaces skipped'),
@@ -24,7 +24,7 @@ export const upsertSubcommand = createCommand({
24
24
  name: 'upsert',
25
25
  aliases: ['put', 'add'],
26
26
  description: 'Add or update vectors in the vector storage',
27
- tags: ['mutating', 'updates-resource', 'slow', 'requires-auth'],
27
+ tags: ['mutating', 'updates-resource', 'slow', 'requires-auth', 'uses-stdin'],
28
28
  idempotent: true,
29
29
  requires: { auth: true, region: true },
30
30
  optional: { project: true },
@@ -24,9 +24,9 @@ export const createCommand = createSubcommand({
24
24
  aliases: ['new'],
25
25
  idempotent: false,
26
26
  examples: [
27
- { command: getCommand('profile create production'), description: 'Create new item' },
27
+ { command: getCommand('profile create production'), description: 'Create new profile' },
28
28
  { command: getCommand('profile create staging --switch'), description: 'Use switch option' },
29
- { command: getCommand('profile create development'), description: 'Create new item' },
29
+ { command: getCommand('profile create development'), description: 'Create new profile' },
30
30
  ],
31
31
  schema: {
32
32
  args: z
@@ -27,11 +27,14 @@ export const createProjectSubcommand = createSubcommand({
27
27
  idempotent: false,
28
28
  optional: { auth: true, region: true, apiClient: true },
29
29
  examples: [
30
- { command: getCommand('project create'), description: 'Create new item' },
31
- { command: getCommand('project create --name my-ai-agent'), description: 'Create new item' },
30
+ { command: getCommand('project create'), description: 'Create new project' },
31
+ {
32
+ command: getCommand('project create --name my-ai-agent'),
33
+ description: 'Create new project',
34
+ },
32
35
  {
33
36
  command: getCommand('project create --name customer-service-bot --dir ~/projects/agent'),
34
- description: 'Create new item',
37
+ description: 'Create new project',
35
38
  },
36
39
  {
37
40
  command: getCommand('project create --template basic --no-install'),
@@ -1,10 +1,19 @@
1
1
  import { z } from 'zod';
2
2
  import { createSubcommand } from '../../types';
3
3
  import * as tui from '../../tui';
4
- import { projectDelete, projectList } from '@agentuity/server';
4
+ import { projectDelete, projectList, projectGet } from '@agentuity/server';
5
5
  import enquirer from 'enquirer';
6
6
  import { getCommand } from '../../command-prefix';
7
7
 
8
+ interface ProjectDisplayInfo {
9
+ id: string;
10
+ name: string;
11
+ }
12
+
13
+ function formatProjectDisplay(project: ProjectDisplayInfo): string {
14
+ return `${project.name} (${project.id})`;
15
+ }
16
+
8
17
  export const deleteSubcommand = createSubcommand({
9
18
  name: 'delete',
10
19
  description: 'Delete a project',
@@ -46,11 +55,37 @@ export const deleteSubcommand = createSubcommand({
46
55
  async handler(ctx) {
47
56
  const { args, opts, apiClient } = ctx;
48
57
 
49
- let projectIds: string[] = [];
58
+ let projectsToDelete: ProjectDisplayInfo[] = [];
50
59
 
51
60
  if (args.id) {
52
- // Command line argument provided
53
- projectIds = [args.id];
61
+ // Command line argument provided - validate and fetch project details
62
+ const projectInfo = await tui.spinner({
63
+ message: 'Fetching project details',
64
+ clearOnSuccess: true,
65
+ callback: async () => {
66
+ try {
67
+ const project = await projectGet(apiClient, {
68
+ id: args.id!,
69
+ mask: true,
70
+ keys: false,
71
+ });
72
+
73
+ return {
74
+ id: project.id,
75
+ name: project.name,
76
+ };
77
+ } catch {
78
+ return null;
79
+ }
80
+ },
81
+ });
82
+
83
+ if (!projectInfo) {
84
+ tui.error(`Project not found: ${args.id}`);
85
+ return { success: false, projectIds: [], count: 0 };
86
+ }
87
+
88
+ projectsToDelete = [projectInfo];
54
89
  } else {
55
90
  // Check TTY before attempting to prompt
56
91
  if (!process.stdin.isTTY) {
@@ -87,10 +122,16 @@ export const deleteSubcommand = createSubcommand({
87
122
  choices,
88
123
  });
89
124
 
90
- projectIds = response.projects;
125
+ // Map selected IDs to full project info
126
+ projectsToDelete = response.projects
127
+ .map((id) => {
128
+ const project = projects.find((p) => p.id === id);
129
+ return project ? { id: project.id, name: project.name } : null;
130
+ })
131
+ .filter((p): p is ProjectDisplayInfo => p !== null);
91
132
  }
92
133
 
93
- if (projectIds.length === 0) {
134
+ if (projectsToDelete.length === 0) {
94
135
  tui.info('No projects selected for deletion');
95
136
  return { success: false, projectIds: [], count: 0 };
96
137
  }
@@ -103,13 +144,16 @@ export const deleteSubcommand = createSubcommand({
103
144
 
104
145
  // Confirm deletion
105
146
  if (!skipConfirm) {
106
- const projectNames = projectIds.join(', ');
107
- tui.warning(`You are about to delete: ${tui.bold(projectNames)}`);
147
+ const projectDisplay =
148
+ projectsToDelete.length === 1
149
+ ? formatProjectDisplay(projectsToDelete[0])
150
+ : projectsToDelete.map((p) => `\n • ${formatProjectDisplay(p)}`).join('');
151
+ tui.warning(`You are about to delete: ${tui.bold(projectDisplay)}`);
108
152
 
109
153
  const confirm = await enquirer.prompt<{ confirm: boolean }>({
110
154
  type: 'confirm',
111
155
  name: 'confirm',
112
- message: `Are you sure you want to delete ${projectIds.length > 1 ? 'these projects' : 'this project'}?`,
156
+ message: `Are you sure you want to delete ${projectsToDelete.length > 1 ? 'these projects' : 'this project'}?`,
113
157
  initial: false,
114
158
  });
115
159
 
@@ -119,8 +163,9 @@ export const deleteSubcommand = createSubcommand({
119
163
  }
120
164
  }
121
165
 
166
+ const projectIds = projectsToDelete.map((p) => p.id);
122
167
  const deleted = await tui.spinner({
123
- message: `Deleting ${projectIds.length} project(s)`,
168
+ message: `Deleting ${projectsToDelete.length} project(s)`,
124
169
  clearOnSuccess: true,
125
170
  callback: async () => {
126
171
  return projectDelete(apiClient, ...projectIds);
@@ -41,7 +41,10 @@ export const command = createCommand({
41
41
  // Use process.execPath which has the actual binary path
42
42
  const isCompiledBinary = process.argv[1]?.startsWith('/$bunfs/');
43
43
  const cmd = isCompiledBinary
44
- ? [process.execPath, ...process.argv.slice(2).map((x) => (x === 'setup' ? 'login' : x))]
44
+ ? [
45
+ process.execPath,
46
+ ...process.argv.slice(2).map((x) => (x === 'setup' ? 'login' : x)),
47
+ ]
45
48
  : process.argv.map((x) => (x === 'setup' ? 'login' : x));
46
49
  const r = Bun.spawn({
47
50
  cmd: cmd.concat('--json'),
package/src/env-util.ts CHANGED
@@ -17,7 +17,10 @@ export const PUBLIC_VAR_PREFIXES = ['VITE_', 'AGENTUITY_PUBLIC_', 'PUBLIC_'] as
17
17
  * Specific AGENTUITY_ keys that are allowed to be set by users.
18
18
  * Note: There is also a whitelist in the API that must be kept in sync.
19
19
  */
20
- export const AGENTUITY_ALLOWED_KEYS = ['AGENTUITY_AUTH_SECRET', 'AGENTUITY_CLOUD_BASE_URL'] as const;
20
+ export const AGENTUITY_ALLOWED_KEYS = [
21
+ 'AGENTUITY_AUTH_SECRET',
22
+ 'AGENTUITY_CLOUD_BASE_URL',
23
+ ] as const;
21
24
 
22
25
  /**
23
26
  * Check if a key is a public variable (exposed to frontend)
@@ -254,10 +254,119 @@ export function parseOptionsSchema(schema: ZodType): ParsedOption[] {
254
254
  return options;
255
255
  }
256
256
 
257
+ // Cache for stdin confirmation detection
258
+ let stdinConfirmationChecked = false;
259
+ let stdinConfirmation: boolean | null = null;
260
+
261
+ /**
262
+ * Check if stdin contains a piped "yes" confirmation.
263
+ * This allows commands to be run with `echo "yes" | agentuity <command>` pattern.
264
+ * Only works when stdin is not a TTY (piped input) and command doesn't use stdin.
265
+ */
266
+ async function checkStdinConfirmation(): Promise<boolean> {
267
+ if (stdinConfirmationChecked) {
268
+ return stdinConfirmation === true;
269
+ }
270
+ stdinConfirmationChecked = true;
271
+
272
+ // Only check if stdin is not a TTY (meaning input is piped)
273
+ if (process.stdin.isTTY) {
274
+ stdinConfirmation = null;
275
+ return false;
276
+ }
277
+
278
+ try {
279
+ // Read stdin with a short timeout to avoid blocking
280
+ const reader = process.stdin;
281
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
282
+ let resolved = false;
283
+ const MAX_BYTES = 4096;
284
+
285
+ // Define handlers outside so we can remove them in all paths
286
+ let data = '';
287
+ const onData = (chunk: Buffer) => {
288
+ data += chunk.toString();
289
+ if (data.length >= MAX_BYTES) {
290
+ data = data.slice(0, MAX_BYTES);
291
+ }
292
+ };
293
+
294
+ let onEnd: (() => void) | null = null;
295
+ let onError: ((err: Error) => void) | null = null;
296
+
297
+ const cleanup = () => {
298
+ reader.removeListener('data', onData);
299
+ if (onEnd) {
300
+ reader.removeListener('end', onEnd);
301
+ }
302
+ if (onError) {
303
+ reader.removeListener('error', onError);
304
+ }
305
+ reader.pause();
306
+ };
307
+
308
+ const readPromise = new Promise<string>((resolve) => {
309
+ onEnd = () => {
310
+ if (resolved) return;
311
+ resolved = true;
312
+ if (timeoutId !== null) {
313
+ clearTimeout(timeoutId);
314
+ timeoutId = null;
315
+ }
316
+ cleanup();
317
+ resolve(data.trim().toLowerCase());
318
+ };
319
+ onError = (_err: Error) => {
320
+ if (resolved) return;
321
+ resolved = true;
322
+ stdinConfirmation = null;
323
+ if (timeoutId !== null) {
324
+ clearTimeout(timeoutId);
325
+ timeoutId = null;
326
+ }
327
+ cleanup();
328
+ resolve('');
329
+ };
330
+ reader.on('data', onData);
331
+ reader.on('end', onEnd);
332
+ reader.on('error', onError);
333
+ });
334
+
335
+ // Use a short timeout to avoid blocking
336
+ const timeoutPromise = new Promise<string>((resolve) => {
337
+ timeoutId = setTimeout(() => {
338
+ if (resolved) return;
339
+ resolved = true;
340
+ // Clean up listeners and pause stdin when timeout wins
341
+ cleanup();
342
+ resolve('');
343
+ }, 100);
344
+ });
345
+
346
+ const input = await Promise.race([readPromise, timeoutPromise]);
347
+ // Take first token/line to handle inputs like "yes\nanything"
348
+ const firstToken = input.split(/\s+/)[0] ?? '';
349
+ stdinConfirmation = firstToken === 'yes' || firstToken === 'y';
350
+ return stdinConfirmation;
351
+ } catch {
352
+ stdinConfirmation = null;
353
+ return false;
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Reset stdin confirmation cache (for testing)
359
+ */
360
+ export function resetStdinConfirmationCache(): void {
361
+ stdinConfirmationChecked = false;
362
+ stdinConfirmation = null;
363
+ }
364
+
257
365
  export function buildValidationInput(
258
366
  schemas: CommandSchemas,
259
367
  rawArgs: unknown[],
260
- rawOptions: Record<string, unknown>
368
+ rawOptions: Record<string, unknown>,
369
+ _options?: { usesStdin?: boolean }
261
370
  ): { args: Record<string, unknown>; options: Record<string, unknown> } {
262
371
  const result = { args: {} as Record<string, unknown>, options: {} as Record<string, unknown> };
263
372
 
@@ -274,7 +383,13 @@ export function buildValidationInput(
274
383
  // Only include the option if it has a value - omitting undefined allows Zod to apply defaults
275
384
  // Commander.js converts kebab-case to camelCase, so we need to check both
276
385
  const camelCaseName = opt.name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
277
- const value = rawOptions[opt.name] ?? rawOptions[camelCaseName];
386
+ let value = rawOptions[opt.name] ?? rawOptions[camelCaseName];
387
+
388
+ // Handle --yes alias for --confirm: if confirm is not set but yes is, use yes value
389
+ if (opt.name === 'confirm' && value === undefined && rawOptions.yes === true) {
390
+ value = true;
391
+ }
392
+
278
393
  if (value !== undefined) {
279
394
  result.options[opt.name] = value;
280
395
  }
@@ -283,3 +398,36 @@ export function buildValidationInput(
283
398
 
284
399
  return result;
285
400
  }
401
+
402
+ /**
403
+ * Async version of buildValidationInput that also checks stdin for "yes" confirmation.
404
+ * Use this when the command has a confirm option and doesn't use stdin for other purposes.
405
+ */
406
+ export async function buildValidationInputAsync(
407
+ schemas: CommandSchemas,
408
+ rawArgs: unknown[],
409
+ rawOptions: Record<string, unknown>,
410
+ options?: { usesStdin?: boolean }
411
+ ): Promise<{ args: Record<string, unknown>; options: Record<string, unknown> }> {
412
+ const result = buildValidationInput(schemas, rawArgs, rawOptions, options);
413
+
414
+ // Check for stdin confirmation if:
415
+ // 1. Command has a confirm option in schema
416
+ // 2. Command doesn't use stdin for other purposes
417
+ // 3. confirm is not already set via flags
418
+ if (schemas.options && !options?.usesStdin) {
419
+ // Use getShape() instead of parseOptionsSchema() to avoid re-evaluating function defaults
420
+ const shape = getShape(schemas.options);
421
+ const hasConfirmOption = Object.prototype.hasOwnProperty.call(shape, 'confirm');
422
+ const confirmValue = result.options.confirm;
423
+
424
+ if (hasConfirmOption && confirmValue === undefined) {
425
+ const stdinConfirmed = await checkStdinConfirmation();
426
+ if (stdinConfirmed) {
427
+ result.options.confirm = true;
428
+ }
429
+ }
430
+ }
431
+
432
+ return result;
433
+ }
package/src/tui.ts CHANGED
@@ -1172,6 +1172,11 @@ export async function spinner<T>(
1172
1172
  options = messageOrOptions;
1173
1173
  }
1174
1174
 
1175
+ // assume true by default
1176
+ if (options.clearOnSuccess === undefined) {
1177
+ options.clearOnSuccess = true;
1178
+ }
1179
+
1175
1180
  const message = options.message;
1176
1181
  const reset = getColor('reset');
1177
1182
 
@@ -0,0 +1,54 @@
1
+ import { $ } from 'bun';
2
+ import type { Logger } from '../types';
3
+
4
+ export interface PackageRef {
5
+ name: string;
6
+ version: string;
7
+ }
8
+
9
+ export async function extractDependencies(
10
+ projectDir: string,
11
+ logger: Logger
12
+ ): Promise<PackageRef[]> {
13
+ try {
14
+ logger.debug('Extracting dependencies using bun pm ls --all');
15
+
16
+ const result = await $`bun pm ls --all`.cwd(projectDir).quiet().nothrow();
17
+
18
+ if (result.exitCode !== 0) {
19
+ logger.warn(
20
+ 'Failed to extract dependencies: bun pm ls exited with code %d',
21
+ result.exitCode
22
+ );
23
+ return [];
24
+ }
25
+
26
+ const output = result.stdout.toString();
27
+ const packages = parseBunPmLsOutput(output);
28
+
29
+ logger.debug('Extracted %d unique packages', packages.length);
30
+ return packages;
31
+ } catch (error) {
32
+ logger.warn('Failed to extract dependencies: %s', error);
33
+ return [];
34
+ }
35
+ }
36
+
37
+ export function parseBunPmLsOutput(output: string): PackageRef[] {
38
+ const packages = new Map<string, PackageRef>();
39
+ const lines = output.split('\n');
40
+
41
+ for (const line of lines) {
42
+ const match = line.match(/([^\s]+)@(\d+\.\d+\.\d+[^\s]*)/);
43
+ if (match) {
44
+ const name = match[1];
45
+ const version = match[2];
46
+ const key = `${name}@${version}`;
47
+ if (!packages.has(key)) {
48
+ packages.set(key, { name, version });
49
+ }
50
+ }
51
+ }
52
+
53
+ return Array.from(packages.values());
54
+ }