@agentuity/cli 0.1.13 → 0.1.14

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 (140) hide show
  1. package/dist/auth.d.ts +1 -1
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +6 -2
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +44 -91
  7. package/dist/cli.js.map +1 -1
  8. package/dist/cmd/auth/index.d.ts.map +1 -1
  9. package/dist/cmd/auth/index.js +3 -0
  10. package/dist/cmd/auth/index.js.map +1 -1
  11. package/dist/cmd/auth/org/index.d.ts +2 -0
  12. package/dist/cmd/auth/org/index.d.ts.map +1 -0
  13. package/dist/cmd/auth/org/index.js +121 -0
  14. package/dist/cmd/auth/org/index.js.map +1 -0
  15. package/dist/cmd/build/vite/beacon-plugin.d.ts +19 -0
  16. package/dist/cmd/build/vite/beacon-plugin.d.ts.map +1 -0
  17. package/dist/cmd/build/vite/beacon-plugin.js +137 -0
  18. package/dist/cmd/build/vite/beacon-plugin.js.map +1 -0
  19. package/dist/cmd/build/vite/vite-builder.d.ts +2 -0
  20. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/vite-builder.js +12 -2
  22. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  23. package/dist/cmd/build/webanalytics-generator.js +25 -9
  24. package/dist/cmd/build/webanalytics-generator.js.map +1 -1
  25. package/dist/cmd/cloud/db/get.d.ts.map +1 -1
  26. package/dist/cmd/cloud/db/get.js +7 -0
  27. package/dist/cmd/cloud/db/get.js.map +1 -1
  28. package/dist/cmd/cloud/db/list.d.ts.map +1 -1
  29. package/dist/cmd/cloud/db/list.js +19 -6
  30. package/dist/cmd/cloud/db/list.js.map +1 -1
  31. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  32. package/dist/cmd/cloud/deploy.js +24 -1
  33. package/dist/cmd/cloud/deploy.js.map +1 -1
  34. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  35. package/dist/cmd/cloud/deployment/show.js +5 -0
  36. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  37. package/dist/cmd/cloud/index.d.ts.map +1 -1
  38. package/dist/cmd/cloud/index.js +3 -0
  39. package/dist/cmd/cloud/index.js.map +1 -1
  40. package/dist/cmd/cloud/region/index.d.ts +2 -0
  41. package/dist/cmd/cloud/region/index.d.ts.map +1 -0
  42. package/dist/cmd/cloud/region/index.js +136 -0
  43. package/dist/cmd/cloud/region/index.js.map +1 -0
  44. package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
  45. package/dist/cmd/cloud/sandbox/snapshot/build.js +35 -5
  46. package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
  47. package/dist/cmd/cloud/scp/download.d.ts.map +1 -1
  48. package/dist/cmd/cloud/scp/download.js +4 -2
  49. package/dist/cmd/cloud/scp/download.js.map +1 -1
  50. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -1
  51. package/dist/cmd/cloud/scp/upload.js +4 -2
  52. package/dist/cmd/cloud/scp/upload.js.map +1 -1
  53. package/dist/cmd/cloud/ssh.d.ts.map +1 -1
  54. package/dist/cmd/cloud/ssh.js +3 -1
  55. package/dist/cmd/cloud/ssh.js.map +1 -1
  56. package/dist/cmd/cloud/storage/get.d.ts.map +1 -1
  57. package/dist/cmd/cloud/storage/get.js +12 -5
  58. package/dist/cmd/cloud/storage/get.js.map +1 -1
  59. package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
  60. package/dist/cmd/cloud/storage/list.js +10 -0
  61. package/dist/cmd/cloud/storage/list.js.map +1 -1
  62. package/dist/cmd/dev/index.d.ts.map +1 -1
  63. package/dist/cmd/dev/index.js +62 -5
  64. package/dist/cmd/dev/index.js.map +1 -1
  65. package/dist/cmd/help/index.d.ts.map +1 -1
  66. package/dist/cmd/help/index.js +8 -18
  67. package/dist/cmd/help/index.js.map +1 -1
  68. package/dist/cmd/project/create.d.ts.map +1 -1
  69. package/dist/cmd/project/create.js +10 -7
  70. package/dist/cmd/project/create.js.map +1 -1
  71. package/dist/cmd/project/import.d.ts +2 -0
  72. package/dist/cmd/project/import.d.ts.map +1 -0
  73. package/dist/cmd/project/import.js +88 -0
  74. package/dist/cmd/project/import.js.map +1 -0
  75. package/dist/cmd/project/index.d.ts.map +1 -1
  76. package/dist/cmd/project/index.js +3 -0
  77. package/dist/cmd/project/index.js.map +1 -1
  78. package/dist/cmd/project/reconcile.d.ts +67 -0
  79. package/dist/cmd/project/reconcile.d.ts.map +1 -0
  80. package/dist/cmd/project/reconcile.js +458 -0
  81. package/dist/cmd/project/reconcile.js.map +1 -0
  82. package/dist/cmd/project/template-flow.d.ts +11 -1
  83. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  84. package/dist/cmd/project/template-flow.js +25 -7
  85. package/dist/cmd/project/template-flow.js.map +1 -1
  86. package/dist/config.d.ts +8 -3
  87. package/dist/config.d.ts.map +1 -1
  88. package/dist/config.js +50 -21
  89. package/dist/config.js.map +1 -1
  90. package/dist/legacy-check.d.ts.map +1 -1
  91. package/dist/legacy-check.js +8 -0
  92. package/dist/legacy-check.js.map +1 -1
  93. package/dist/program-ref.d.ts +4 -0
  94. package/dist/program-ref.d.ts.map +1 -0
  95. package/dist/program-ref.js +8 -0
  96. package/dist/program-ref.js.map +1 -0
  97. package/dist/regions.d.ts +8 -0
  98. package/dist/regions.d.ts.map +1 -0
  99. package/dist/regions.js +77 -0
  100. package/dist/regions.js.map +1 -0
  101. package/dist/tui.d.ts.map +1 -1
  102. package/dist/tui.js +5 -4
  103. package/dist/tui.js.map +1 -1
  104. package/dist/types.d.ts +1 -0
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/types.js +1 -0
  107. package/dist/types.js.map +1 -1
  108. package/package.json +6 -6
  109. package/src/auth.ts +8 -8
  110. package/src/cli.ts +52 -108
  111. package/src/cmd/auth/index.ts +3 -0
  112. package/src/cmd/auth/org/index.ts +142 -0
  113. package/src/cmd/build/vite/beacon-plugin.ts +162 -0
  114. package/src/cmd/build/vite/vite-builder.ts +15 -2
  115. package/src/cmd/build/webanalytics-generator.ts +25 -9
  116. package/src/cmd/cloud/db/get.ts +7 -0
  117. package/src/cmd/cloud/db/list.ts +20 -6
  118. package/src/cmd/cloud/deploy.ts +32 -1
  119. package/src/cmd/cloud/deployment/show.ts +5 -0
  120. package/src/cmd/cloud/index.ts +3 -0
  121. package/src/cmd/cloud/region/index.ts +157 -0
  122. package/src/cmd/cloud/sandbox/snapshot/build.ts +42 -5
  123. package/src/cmd/cloud/scp/download.ts +6 -2
  124. package/src/cmd/cloud/scp/upload.ts +6 -2
  125. package/src/cmd/cloud/ssh.ts +5 -1
  126. package/src/cmd/cloud/storage/get.ts +12 -5
  127. package/src/cmd/cloud/storage/list.ts +11 -0
  128. package/src/cmd/dev/index.ts +62 -5
  129. package/src/cmd/help/index.ts +8 -22
  130. package/src/cmd/project/create.ts +10 -7
  131. package/src/cmd/project/import.ts +98 -0
  132. package/src/cmd/project/index.ts +3 -0
  133. package/src/cmd/project/reconcile.ts +606 -0
  134. package/src/cmd/project/template-flow.ts +37 -7
  135. package/src/config.ts +58 -22
  136. package/src/legacy-check.ts +10 -0
  137. package/src/program-ref.ts +11 -0
  138. package/src/regions.ts +95 -0
  139. package/src/tui.ts +6 -4
  140. package/src/types.ts +1 -0
@@ -13,6 +13,8 @@ const DBListResponseSchema = z.object({
13
13
  description: z.string().optional().describe('Database description'),
14
14
  url: z.string().optional().describe('Database connection URL'),
15
15
  cloud_region: z.string().optional().describe('Cloud region where database is hosted'),
16
+ org_id: z.string().optional().describe('Organization ID that owns this database'),
17
+ org_name: z.string().optional().describe('Organization name that owns this database'),
16
18
  })
17
19
  )
18
20
  .describe('List of database resources'),
@@ -65,6 +67,10 @@ export const listSubcommand = createSubcommand({
65
67
  const shouldShowCredentials = opts.showCredentials === true;
66
68
  const shouldMask = !options.json && !shouldShowCredentials;
67
69
 
70
+ // Check if resources span multiple orgs
71
+ const uniqueOrgIds = new Set(resources.db.map((db) => db.org_id));
72
+ const showOrgColumn = uniqueOrgIds.size > 1;
73
+
68
74
  if (!options.json) {
69
75
  if (resources.db.length === 0) {
70
76
  tui.info('No databases found');
@@ -73,12 +79,18 @@ export const listSubcommand = createSubcommand({
73
79
  console.log(db.name);
74
80
  }
75
81
  } else {
76
- const tableData = resources.db.map((db) => ({
77
- Name: db.name,
78
- Description: db.description ?? '',
79
- Region: db.cloud_region,
80
- URL: db.url ? (shouldMask ? tui.maskSecret(db.url) : db.url) : '',
81
- }));
82
+ const tableData = resources.db.map((db) => {
83
+ const row: Record<string, string> = {
84
+ Name: db.name,
85
+ };
86
+ if (showOrgColumn) {
87
+ row.Organization = db.org_name || db.org_id;
88
+ }
89
+ row.Description = db.description ?? '';
90
+ row.Region = db.cloud_region;
91
+ row.URL = db.url ? (shouldMask ? tui.maskSecret(db.url) : db.url) : '';
92
+ return row;
93
+ });
82
94
  tui.table(tableData);
83
95
  }
84
96
  }
@@ -89,6 +101,8 @@ export const listSubcommand = createSubcommand({
89
101
  description: db.description ?? undefined,
90
102
  url: db.url ?? undefined,
91
103
  cloud_region: db.cloud_region,
104
+ org_id: db.org_id,
105
+ org_name: db.org_name,
92
106
  })),
93
107
  };
94
108
  },
@@ -121,7 +121,38 @@ export const deploySubcommand = createSubcommand({
121
121
  },
122
122
 
123
123
  async handler(ctx) {
124
- const { project, apiClient, projectDir, config, options, logger, opts } = ctx;
124
+ let { project } = ctx;
125
+ const { apiClient, projectDir, config, options, logger, opts, auth } = ctx;
126
+
127
+ // Verify project access and offer import if needed
128
+ const { reconcileProject } = await import('../project/reconcile');
129
+ const { isTTY } = await import('../../auth');
130
+
131
+ const reconcileResult = await reconcileProject({
132
+ dir: projectDir,
133
+ auth,
134
+ apiClient,
135
+ config: config!,
136
+ logger,
137
+ interactive: isTTY(),
138
+ });
139
+
140
+ if (reconcileResult.status === 'error') {
141
+ tui.fatal(reconcileResult.message!, ErrorCode.PROJECT_NOT_FOUND);
142
+ }
143
+
144
+ if (reconcileResult.status === 'skipped') {
145
+ tui.fatal(
146
+ 'Project must be registered with Agentuity Cloud to deploy.',
147
+ ErrorCode.PROJECT_NOT_FOUND
148
+ );
149
+ }
150
+
151
+ if (reconcileResult.status === 'imported' && reconcileResult.project) {
152
+ // Project was imported - use the new project config
153
+ project = reconcileResult.project;
154
+ tui.newline();
155
+ }
125
156
 
126
157
  // Initialize build report collector if reportFile is specified
127
158
  const collector = new BuildReportCollector();
@@ -19,6 +19,7 @@ const DeploymentShowResponseSchema = z.object({
19
19
  resourceStorage: z.string().nullable().optional().describe('the storage name'),
20
20
  deploymentLogsURL: z.string().nullable().optional().describe('the url to the deployment logs'),
21
21
  buildLogsURL: z.string().nullable().optional().describe('the url to the build logs'),
22
+ dnsRecords: z.array(z.string()).optional().describe('DNS records for custom domains'),
22
23
  metadata: z
23
24
  .object({
24
25
  git: z
@@ -124,6 +125,9 @@ export const showSubcommand = createSubcommand({
124
125
  if (deployment.buildLogsURL) {
125
126
  tableData['Build Logs'] = tui.link(deployment.buildLogsURL);
126
127
  }
128
+ if (deployment.dnsRecords && deployment.dnsRecords.length > 0) {
129
+ tableData['DNS Records'] = deployment.dnsRecords.join(', ');
130
+ }
127
131
 
128
132
  tui.table([tableData], Object.keys(tableData), { layout: 'vertical', padStart: ' ' });
129
133
 
@@ -190,6 +194,7 @@ export const showSubcommand = createSubcommand({
190
194
  resourceStorage: deployment.resourceStorage ?? undefined,
191
195
  deploymentLogsURL: deployment.deploymentLogsURL ?? undefined,
192
196
  buildLogsURL: deployment.buildLogsURL ?? undefined,
197
+ dnsRecords: deployment.dnsRecords ?? undefined,
193
198
  };
194
199
  } catch (ex) {
195
200
  tui.fatal(`Failed to show deployment: ${ex}`);
@@ -15,6 +15,7 @@ import apikeyCommand from './apikey';
15
15
  import streamCommand from './stream';
16
16
  import vectorCommand from './vector';
17
17
  import sandboxCommand from './sandbox';
18
+ import { regionSubcommand } from './region';
18
19
  import { getCommand } from '../../command-prefix';
19
20
 
20
21
  export const command = createCommand({
@@ -24,6 +25,7 @@ export const command = createCommand({
24
25
  examples: [
25
26
  { command: getCommand('cloud deploy'), description: 'Deploy your agent to the cloud' },
26
27
  { command: getCommand('cloud deployment list'), description: 'List all deployments' },
28
+ { command: getCommand('cloud region select'), description: 'Set default region' },
27
29
  ],
28
30
  subcommands: [
29
31
  apikeyCommand,
@@ -42,5 +44,6 @@ export const command = createCommand({
42
44
  sshSubcommand,
43
45
  scpSubcommand,
44
46
  deploymentCommand,
47
+ regionSubcommand,
45
48
  ],
46
49
  });
@@ -0,0 +1,157 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand, createCommand } from '../../../types';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import { saveRegion, clearRegion } from '../../../config';
5
+ import * as tui from '../../../tui';
6
+
7
+ const selectCommand = createSubcommand({
8
+ name: 'select',
9
+ description: 'Set the default cloud region for all commands',
10
+ tags: ['fast', 'requires-auth'],
11
+ requires: { auth: true, regions: true },
12
+ examples: [
13
+ { command: getCommand('cloud region select'), description: 'Select default region' },
14
+ {
15
+ command: getCommand('cloud region select usc'),
16
+ description: 'Set specific region as default',
17
+ },
18
+ ],
19
+ schema: {
20
+ args: z.object({
21
+ region: z.string().optional().describe('Region code to set as default'),
22
+ }),
23
+ response: z.object({
24
+ region: z.string().describe('The selected region code'),
25
+ description: z.string().describe('The region description'),
26
+ }),
27
+ },
28
+
29
+ async handler(ctx) {
30
+ const { regions, args, options } = ctx;
31
+
32
+ let selectedRegion = args.region;
33
+
34
+ if (selectedRegion) {
35
+ const region = regions.find((r) => r.region === selectedRegion);
36
+ if (!region) {
37
+ const available = regions.map((r) => r.region).join(', ');
38
+ tui.fatal(`Region '${selectedRegion}' not found. Available regions: ${available}`);
39
+ }
40
+ } else {
41
+ if (!process.stdin.isTTY) {
42
+ tui.fatal(
43
+ 'Region code required in non-interactive mode. Usage: ' +
44
+ getCommand('cloud region select <region>')
45
+ );
46
+ }
47
+
48
+ if (regions.length === 1) {
49
+ selectedRegion = regions[0].region;
50
+ } else {
51
+ const response = await tui.createPrompt().select<string>({
52
+ message: 'Select a default region',
53
+ options: regions.map((r) => ({
54
+ value: r.region,
55
+ label: `${r.description} ${tui.muted(`(${r.region})`)}`,
56
+ })),
57
+ });
58
+ selectedRegion = response;
59
+ }
60
+ }
61
+
62
+ await saveRegion(selectedRegion);
63
+
64
+ const selectedRegionInfo = regions.find((r) => r.region === selectedRegion)!;
65
+
66
+ if (!options.json) {
67
+ tui.success(
68
+ `Default region set to ${tui.bold(selectedRegionInfo.description)} (${selectedRegion})`
69
+ );
70
+ }
71
+
72
+ return { region: selectedRegion, description: selectedRegionInfo.description };
73
+ },
74
+ });
75
+
76
+ const unselectCommand = createSubcommand({
77
+ name: 'unselect',
78
+ description: 'Clear the default region preference',
79
+ tags: ['fast'],
80
+ examples: [
81
+ { command: getCommand('cloud region unselect'), description: 'Clear default region' },
82
+ ],
83
+ schema: {
84
+ response: z.object({
85
+ cleared: z.boolean().describe('Whether the preference was cleared'),
86
+ }),
87
+ },
88
+
89
+ async handler(ctx) {
90
+ const { options, config } = ctx;
91
+
92
+ const hadRegion = !!config?.preferences?.region;
93
+
94
+ if (hadRegion && process.stdin.isTTY) {
95
+ const confirmed = await tui.confirm('Clear default region preference?', true);
96
+ if (!confirmed) {
97
+ if (!options.json) {
98
+ tui.info('Cancelled');
99
+ }
100
+ return { cleared: false };
101
+ }
102
+ }
103
+
104
+ await clearRegion();
105
+
106
+ if (!options.json) {
107
+ if (hadRegion) {
108
+ tui.success('Default region cleared');
109
+ } else {
110
+ tui.info('No default region was set');
111
+ }
112
+ }
113
+
114
+ return { cleared: hadRegion };
115
+ },
116
+ });
117
+
118
+ const currentCommand = createSubcommand({
119
+ name: 'current',
120
+ description: 'Show the current default region',
121
+ tags: ['read-only', 'fast'],
122
+ idempotent: true,
123
+ examples: [
124
+ { command: getCommand('cloud region current'), description: 'Show default region' },
125
+ {
126
+ command: getCommand('cloud region current --json'),
127
+ description: 'Show output in JSON format',
128
+ },
129
+ ],
130
+ schema: {
131
+ response: z.string().nullable().describe('The current region code or null if not set'),
132
+ },
133
+
134
+ async handler(ctx) {
135
+ const { options, config } = ctx;
136
+ const region = config?.preferences?.region || null;
137
+
138
+ if (!options.json) {
139
+ if (region) {
140
+ console.log(region);
141
+ }
142
+ }
143
+
144
+ return region;
145
+ },
146
+ });
147
+
148
+ export const regionSubcommand = createCommand({
149
+ name: 'region',
150
+ description: 'Manage default cloud region preference',
151
+ tags: ['fast'],
152
+ examples: [
153
+ { command: getCommand('cloud region select'), description: 'Set default region' },
154
+ { command: getCommand('cloud region current'), description: 'Show current default' },
155
+ ],
156
+ subcommands: [selectCommand, unselectCommand, currentCommand],
157
+ });
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { resolve, join, extname } from 'node:path';
3
- import { existsSync, statSync } from 'node:fs';
3
+ import { existsSync, statSync, createReadStream, createWriteStream } from 'node:fs';
4
4
  import { YAML } from 'bun';
5
5
  import * as tar from 'tar';
6
6
  import { createCommand } from '../../../../types';
@@ -14,8 +14,9 @@ import {
14
14
  import type { SnapshotFileInfo } from '@agentuity/server';
15
15
  import { getCatalystAPIClient } from '../../../../config';
16
16
  import { validateAptDependencies } from '../../../../utils/apt-validator';
17
+ import { encryptFIPSKEMDEMStream } from '../../../../crypto/box';
17
18
  import { tmpdir } from 'node:os';
18
- import { randomUUID, createHash } from 'node:crypto';
19
+ import { randomUUID, createHash, createPublicKey } from 'node:crypto';
19
20
  import { rm } from 'node:fs/promises';
20
21
 
21
22
  export const SNAPSHOT_TAG_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
@@ -575,6 +576,7 @@ export const buildSubcommand = createCommand({
575
576
  description: finalDescription,
576
577
  contentHash,
577
578
  force: opts.force,
579
+ encrypt: true,
578
580
  orgId,
579
581
  });
580
582
  },
@@ -609,19 +611,54 @@ export const buildSubcommand = createCommand({
609
611
  };
610
612
  }
611
613
 
614
+ // Encrypt the archive if public key is provided
615
+ let uploadPath = archivePath;
616
+ let uploadSize = archiveSize;
617
+
618
+ if (initResult.publicKey) {
619
+ const encryptedPath = join(tempDir, 'snapshot.tar.gz.enc');
620
+
621
+ await tui.spinner({
622
+ message: 'Encrypting snapshot...',
623
+ type: 'simple',
624
+ clearOnSuccess: true,
625
+ callback: async () => {
626
+ const publicKey = createPublicKey({
627
+ key: initResult.publicKey!,
628
+ format: 'pem',
629
+ type: 'spki',
630
+ });
631
+
632
+ const src = createReadStream(archivePath);
633
+ const dst = createWriteStream(encryptedPath);
634
+
635
+ await encryptFIPSKEMDEMStream(publicKey, src, dst);
636
+
637
+ await new Promise<void>((resolve, reject) => {
638
+ dst.once('finish', resolve);
639
+ dst.once('error', reject);
640
+ dst.end();
641
+ });
642
+ },
643
+ });
644
+
645
+ uploadPath = encryptedPath;
646
+ uploadSize = Bun.file(encryptedPath).size;
647
+ }
648
+
612
649
  await tui.spinner({
613
650
  message: 'Uploading snapshot...',
614
651
  type: 'progress',
615
652
  clearOnSuccess: true,
616
653
  callback: async (updateProgress) => {
617
- const archiveBuffer = await archiveFile.arrayBuffer();
654
+ const uploadFile = Bun.file(uploadPath);
618
655
  const response = await fetch(initResult.uploadUrl!, {
619
656
  method: 'PUT',
620
657
  headers: {
621
658
  'Content-Type': 'application/gzip',
622
- 'Content-Length': String(archiveSize),
659
+ 'Content-Length': String(uploadSize),
623
660
  },
624
- body: archiveBuffer,
661
+ body: uploadFile,
625
662
  });
626
663
 
627
664
  if (!response.ok) {
@@ -52,7 +52,7 @@ export const downloadCommand = createSubcommand({
52
52
  },
53
53
 
54
54
  async handler(ctx) {
55
- const { apiClient, args, opts, project, projectDir, config, logger, auth, orgId } = ctx;
55
+ const { apiClient, args, opts, project, projectDir, config, logger, auth } = ctx;
56
56
 
57
57
  let identifier = opts?.identifier ?? project?.projectId;
58
58
 
@@ -60,8 +60,12 @@ export const downloadCommand = createSubcommand({
60
60
  identifier = await tui.showProjectList(apiClient, true);
61
61
  }
62
62
 
63
- // Look up region from identifier (project/deployment)
63
+ // Look up region from identifier (project/deployment/sandbox)
64
64
  const profileName = config?.name;
65
+
66
+ // For sandbox identifiers, use saved org preference (no prompting)
67
+ const orgId = identifier.startsWith('sbx_') ? config?.preferences?.orgId : undefined;
68
+
65
69
  const region = await getIdentifierRegion(
66
70
  logger,
67
71
  auth,
@@ -55,7 +55,7 @@ export const uploadCommand = createSubcommand({
55
55
  prerequisites: ['cloud deploy'],
56
56
 
57
57
  async handler(ctx) {
58
- const { apiClient, args, opts, project, projectDir, config, logger, auth, orgId } = ctx;
58
+ const { apiClient, args, opts, project, projectDir, config, logger, auth } = ctx;
59
59
 
60
60
  let identifier = opts?.identifier ?? project?.projectId;
61
61
 
@@ -63,8 +63,12 @@ export const uploadCommand = createSubcommand({
63
63
  identifier = await tui.showProjectList(apiClient, true);
64
64
  }
65
65
 
66
- // Look up region from identifier (project/deployment)
66
+ // Look up region from identifier (project/deployment/sandbox)
67
67
  const profileName = config?.name;
68
+
69
+ // For sandbox identifiers, use saved org preference (no prompting)
70
+ const orgId = identifier.startsWith('sbx_') ? config?.preferences?.orgId : undefined;
71
+
68
72
  const region = await getIdentifierRegion(
69
73
  logger,
70
74
  auth,
@@ -46,7 +46,7 @@ export const sshSubcommand = createSubcommand({
46
46
  schema: { args, options },
47
47
 
48
48
  async handler(ctx) {
49
- const { apiClient, project, projectDir, args, config, opts, logger, auth, orgId } = ctx;
49
+ const { apiClient, project, projectDir, args, config, opts, logger, auth } = ctx;
50
50
 
51
51
  let projectId = project?.projectId;
52
52
  let identifier = args?.identifier;
@@ -70,6 +70,10 @@ export const sshSubcommand = createSubcommand({
70
70
  // Look up region from identifier (project/deployment/sandbox)
71
71
  const profileName = config?.name;
72
72
  const targetIdentifier = identifier ?? projectId!;
73
+
74
+ // For sandbox identifiers, use saved org preference (no prompting)
75
+ const orgId = targetIdentifier.startsWith('sbx_') ? config?.preferences?.orgId : undefined;
76
+
73
77
  const region = await getIdentifierRegion(
74
78
  logger,
75
79
  auth,
@@ -13,6 +13,8 @@ const StorageGetResponseSchema = z.object({
13
13
  secret_key: z.string().optional().describe('S3 secret key'),
14
14
  region: z.string().optional().describe('S3 region'),
15
15
  endpoint: z.string().optional().describe('S3 endpoint URL'),
16
+ org_id: z.string().optional().describe('Organization ID that owns this bucket'),
17
+ org_name: z.string().optional().describe('Organization name that owns this bucket'),
16
18
  });
17
19
 
18
20
  export const getSubcommand = createSubcommand({
@@ -100,24 +102,27 @@ export const getSubcommand = createSubcommand({
100
102
  const shouldMask = !options.json && !shouldShowCredentials;
101
103
 
102
104
  if (!options.json) {
103
- console.log(tui.bold('Bucket Name: ') + bucket.bucket_name);
105
+ console.log(tui.bold('Bucket Name: ') + bucket.bucket_name);
106
+ if (bucket.org_name || bucket.org_id) {
107
+ console.log(tui.bold('Organization: ') + (bucket.org_name || bucket.org_id));
108
+ }
104
109
  if (bucket.access_key) {
105
110
  const displayAccessKey = shouldMask
106
111
  ? tui.maskSecret(bucket.access_key)
107
112
  : bucket.access_key;
108
- console.log(tui.bold('Access Key: ') + displayAccessKey);
113
+ console.log(tui.bold('Access Key: ') + displayAccessKey);
109
114
  }
110
115
  if (bucket.secret_key) {
111
116
  const displaySecretKey = shouldMask
112
117
  ? tui.maskSecret(bucket.secret_key)
113
118
  : bucket.secret_key;
114
- console.log(tui.bold('Secret Key: ') + displaySecretKey);
119
+ console.log(tui.bold('Secret Key: ') + displaySecretKey);
115
120
  }
116
121
  if (bucket.region) {
117
- console.log(tui.bold('Region: ') + bucket.region);
122
+ console.log(tui.bold('Region: ') + bucket.region);
118
123
  }
119
124
  if (bucket.endpoint) {
120
- console.log(tui.bold('Endpoint: ') + bucket.endpoint);
125
+ console.log(tui.bold('Endpoint: ') + bucket.endpoint);
121
126
  }
122
127
  }
123
128
 
@@ -127,6 +132,8 @@ export const getSubcommand = createSubcommand({
127
132
  secret_key: bucket.secret_key ?? undefined,
128
133
  region: bucket.region ?? undefined,
129
134
  endpoint: bucket.endpoint ?? undefined,
135
+ org_id: bucket.org_id,
136
+ org_name: bucket.org_name,
130
137
  };
131
138
  },
132
139
  });
@@ -18,6 +18,8 @@ const StorageListResponseSchema = z.object({
18
18
  region: z.string().optional().describe('S3 region'),
19
19
  endpoint: z.string().optional().describe('S3 endpoint URL'),
20
20
  cloud_region: z.string().optional().describe('Cloud region where bucket is hosted'),
21
+ org_id: z.string().optional().describe('Organization ID that owns this bucket'),
22
+ org_name: z.string().optional().describe('Organization name that owns this bucket'),
21
23
  })
22
24
  )
23
25
  .optional()
@@ -189,6 +191,10 @@ export const listSubcommand = createSubcommand({
189
191
  const shouldShowCredentials = opts.showCredentials === true;
190
192
  const shouldMask = !options.json && !shouldShowCredentials;
191
193
 
194
+ // Check if resources span multiple orgs
195
+ const uniqueOrgIds = new Set(resources.s3.map((s3) => s3.org_id));
196
+ const showOrgColumn = uniqueOrgIds.size > 1;
197
+
192
198
  if (!options.json) {
193
199
  if (resources.s3.length === 0) {
194
200
  tui.info('No storage buckets found');
@@ -203,6 +209,9 @@ export const listSubcommand = createSubcommand({
203
209
  continue;
204
210
  }
205
211
  console.log(tui.bold(s3.bucket_name));
212
+ if (showOrgColumn) {
213
+ console.log(` Organization: ${tui.muted(s3.org_name || s3.org_id)}`);
214
+ }
206
215
  if (s3.access_key) {
207
216
  const displayAccessKey = shouldMask
208
217
  ? tui.maskSecret(s3.access_key)
@@ -230,6 +239,8 @@ export const listSubcommand = createSubcommand({
230
239
  region: s3.region ?? undefined,
231
240
  endpoint: s3.endpoint ?? undefined,
232
241
  cloud_region: s3.cloud_region,
242
+ org_id: s3.org_id,
243
+ org_name: s3.org_name,
233
244
  })),
234
245
  };
235
246
  },
@@ -175,8 +175,8 @@ export const command = createCommand({
175
175
  optional: { project: true },
176
176
 
177
177
  async handler(ctx) {
178
- const { opts, logger, project, projectDir } = ctx;
179
- let { config } = ctx;
178
+ const { opts, logger, projectDir } = ctx;
179
+ let { config, project } = ctx;
180
180
 
181
181
  // Get auth state - we handle auth ourselves based on project state
182
182
  let auth = await getAuth();
@@ -227,7 +227,9 @@ export const command = createCommand({
227
227
  tui.newline();
228
228
 
229
229
  const shouldLogin = await tui.confirm(
230
- hasProfile ? 'Would you like to login now?' : 'Would you like to login or create an account?',
230
+ hasProfile
231
+ ? 'Would you like to login now?'
232
+ : 'Would you like to login or create an account?',
231
233
  true
232
234
  );
233
235
 
@@ -272,9 +274,63 @@ export const command = createCommand({
272
274
  );
273
275
  }
274
276
  }
277
+
278
+ // After auth is established, verify project access
279
+ if (auth && config) {
280
+ const { reconcileProject } = await import('../project/reconcile');
281
+ const apiClient = new APIClient(getAPIBaseURL(config), logger, auth.apiKey, config);
282
+
283
+ const result = await reconcileProject({
284
+ dir: rootDir,
285
+ auth,
286
+ apiClient,
287
+ config,
288
+ logger,
289
+ interactive: isTTY(),
290
+ });
291
+
292
+ if (result.status === 'error') {
293
+ tui.fatal(result.message!, ErrorCode.PROJECT_NOT_FOUND);
294
+ } else if (result.status === 'imported' && result.project) {
295
+ // Project was re-imported to user's org
296
+ project = result.project;
297
+ tui.newline();
298
+ } else if (result.status === 'skipped') {
299
+ // User declined import - can't continue with cloud features
300
+ tui.warning('Continuing in local-only mode.');
301
+ project = undefined;
302
+ }
303
+ }
275
304
  } else {
276
- // No agentuity.json - local-only mode, ignore auth state
277
- tui.showLocalOnlyWarning();
305
+ // No agentuity.json - check if this is a valid project that needs importing
306
+ if (auth && config) {
307
+ const { reconcileProject } = await import('../project/reconcile');
308
+ const apiClient = new APIClient(getAPIBaseURL(config), logger, auth.apiKey, config);
309
+
310
+ const result = await reconcileProject({
311
+ dir: rootDir,
312
+ auth,
313
+ apiClient,
314
+ config,
315
+ logger,
316
+ interactive: isTTY(),
317
+ });
318
+
319
+ if (result.status === 'error') {
320
+ // Not a valid project - show local-only warning
321
+ tui.showLocalOnlyWarning();
322
+ } else if (result.status === 'imported' && result.project) {
323
+ // Project was imported - reload project config
324
+ project = result.project;
325
+ tui.newline();
326
+ } else if (result.status === 'skipped') {
327
+ // User declined import - continue in local-only mode
328
+ tui.showLocalOnlyWarning();
329
+ }
330
+ } else {
331
+ // Not authenticated - local-only mode
332
+ tui.showLocalOnlyWarning();
333
+ }
278
334
  }
279
335
 
280
336
  // Prepare dev lock: cleans up stale processes from previous sessions
@@ -924,6 +980,7 @@ export const command = createCommand({
924
980
  }
925
981
 
926
982
  process.env.AGENTUITY_SDK_DEV_MODE = 'true';
983
+ process.env.AGENTUITY_RUNTIME = 'yes';
927
984
  process.env.AGENTUITY_ENV = 'development';
928
985
  process.env.NODE_ENV = 'development';
929
986
  process.env.AGENTUITY_PROJECT_DIR = rootDir;
@@ -1,5 +1,6 @@
1
1
  import { createCommand } from '../../types';
2
2
  import { getCommand } from '../../command-prefix';
3
+ import { getProgram } from '../../program-ref';
3
4
 
4
5
  export const command = createCommand({
5
6
  name: 'help',
@@ -13,28 +14,13 @@ export const command = createCommand({
13
14
  idempotent: true,
14
15
 
15
16
  async handler() {
16
- // Spawn the CLI with no arguments to show help
17
- let spawnArgs: string[];
18
-
19
- if (process.env.AGENTUITY_CLI_VERSION) {
20
- // Compiled binary: spawn only the binary executable with no additional args
21
- spawnArgs = [process.argv[0]];
22
- } else {
23
- // Script mode: spawn runtime and script, omitting the 'help' argument
24
- spawnArgs = [process.argv[0], ...(process.argv.length > 1 ? [process.argv[1]] : [])];
25
- }
26
-
27
- const proc = Bun.spawn(spawnArgs, {
28
- stdio: ['inherit', 'inherit', 'inherit'],
29
- env: process.env,
30
- });
31
-
32
- const exitCode = await proc.exited;
33
-
34
- if (exitCode !== 0) {
35
- throw new Error(`Help command exited with code ${exitCode}`);
17
+ // Get the root program and display its help
18
+ // This avoids spawning a subprocess which doesn't work reliably
19
+ // with Bun compiled binaries
20
+ const program = getProgram();
21
+ if (program) {
22
+ program.outputHelp();
36
23
  }
37
-
38
- return undefined;
24
+ process.exit(0);
39
25
  },
40
26
  });