@agentuity/cli 0.1.28 → 0.1.29

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 (73) hide show
  1. package/dist/banner.js +2 -2
  2. package/dist/banner.js.map +1 -1
  3. package/dist/cmd/auth/signup.js +1 -1
  4. package/dist/cmd/auth/signup.js.map +1 -1
  5. package/dist/cmd/canary/index.d.ts.map +1 -1
  6. package/dist/cmd/canary/index.js +62 -127
  7. package/dist/cmd/canary/index.js.map +1 -1
  8. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  9. package/dist/cmd/cloud/deploy.js +1 -8
  10. package/dist/cmd/cloud/deploy.js.map +1 -1
  11. package/dist/cmd/dev/api.d.ts +3 -1
  12. package/dist/cmd/dev/api.d.ts.map +1 -1
  13. package/dist/cmd/dev/api.js +16 -2
  14. package/dist/cmd/dev/api.js.map +1 -1
  15. package/dist/cmd/dev/index.d.ts.map +1 -1
  16. package/dist/cmd/dev/index.js +16 -3
  17. package/dist/cmd/dev/index.js.map +1 -1
  18. package/dist/cmd/index.d.ts.map +1 -1
  19. package/dist/cmd/index.js +0 -6
  20. package/dist/cmd/index.js.map +1 -1
  21. package/dist/cmd/setup/index.d.ts.map +1 -1
  22. package/dist/cmd/setup/index.js +7 -9
  23. package/dist/cmd/setup/index.js.map +1 -1
  24. package/dist/cmd/upgrade/index.d.ts +7 -24
  25. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  26. package/dist/cmd/upgrade/index.js +78 -263
  27. package/dist/cmd/upgrade/index.js.map +1 -1
  28. package/dist/command-prefix.d.ts +1 -1
  29. package/dist/command-prefix.d.ts.map +1 -1
  30. package/dist/command-prefix.js +4 -14
  31. package/dist/command-prefix.js.map +1 -1
  32. package/dist/download.d.ts.map +1 -1
  33. package/dist/download.js +2 -0
  34. package/dist/download.js.map +1 -1
  35. package/dist/errors.d.ts +2 -1
  36. package/dist/errors.d.ts.map +1 -1
  37. package/dist/errors.js +3 -0
  38. package/dist/errors.js.map +1 -1
  39. package/dist/legacy-check.d.ts.map +1 -1
  40. package/dist/legacy-check.js +2 -2
  41. package/dist/legacy-check.js.map +1 -1
  42. package/dist/types.d.ts +1 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js +1 -0
  45. package/dist/types.js.map +1 -1
  46. package/dist/utils/installation-type.d.ts +25 -0
  47. package/dist/utils/installation-type.d.ts.map +1 -0
  48. package/dist/utils/installation-type.js +43 -0
  49. package/dist/utils/installation-type.js.map +1 -0
  50. package/dist/version-check.d.ts.map +1 -1
  51. package/dist/version-check.js +16 -13
  52. package/dist/version-check.js.map +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/version.js.map +1 -1
  55. package/package.json +6 -6
  56. package/src/api-errors.md +2 -2
  57. package/src/banner.ts +2 -2
  58. package/src/cmd/auth/signup.ts +1 -1
  59. package/src/cmd/canary/index.ts +69 -144
  60. package/src/cmd/cloud/deploy.ts +1 -12
  61. package/src/cmd/dev/api.ts +19 -3
  62. package/src/cmd/dev/index.ts +23 -3
  63. package/src/cmd/index.ts +0 -7
  64. package/src/cmd/setup/index.ts +8 -9
  65. package/src/cmd/upgrade/index.ts +84 -302
  66. package/src/command-prefix.ts +4 -18
  67. package/src/download.ts +3 -0
  68. package/src/errors.ts +4 -0
  69. package/src/legacy-check.ts +2 -4
  70. package/src/types.ts +1 -0
  71. package/src/utils/installation-type.ts +51 -0
  72. package/src/version-check.ts +18 -13
  73. package/src/version.ts +1 -1
@@ -1,22 +1,14 @@
1
1
  import { createCommand } from '../../types';
2
- import { getPlatformInfo } from '../upgrade';
3
- import { downloadWithProgress } from '../../download';
4
2
  import { z } from 'zod';
5
3
  import { $ } from 'bun';
6
- import { join } from 'node:path';
7
- import { homedir } from 'node:os';
8
- import { readdir, rm, mkdir, stat } from 'node:fs/promises';
9
- import { createHash } from 'node:crypto';
10
4
  import * as tui from '../../tui';
11
5
 
12
- const CANARY_CACHE_DIR = join(homedir(), '.agentuity', 'canary');
13
- const CANARY_BASE_URL = 'https://agentuity-sdk-objects.t3.storage.dev/binary';
14
- const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
6
+ const CANARY_BASE_URL = 'https://agentuity-sdk-objects.t3.storage.dev/npm';
15
7
 
16
8
  const CanaryArgsSchema = z.object({
17
9
  args: z
18
10
  .array(z.string())
19
- .describe('Version/URL followed by commands to run (e.g., 0.1.6-abc1234 deploy --force)'),
11
+ .describe('Version followed by commands to run (e.g., 0.1.6-abc1234 deploy --force)'),
20
12
  });
21
13
 
22
14
  const CanaryResponseSchema = z.object({
@@ -25,81 +17,12 @@ const CanaryResponseSchema = z.object({
25
17
  message: z.string().describe('Status message'),
26
18
  });
27
19
 
28
- function isUrl(str: string): boolean {
29
- return str.startsWith('http://') || str.startsWith('https://');
20
+ function isHttpsUrl(str: string): boolean {
21
+ return str.startsWith('https://');
30
22
  }
31
23
 
32
- function getBinaryFilename(platform: { os: string; arch: string }): string {
33
- return `agentuity-${platform.os}-${platform.arch}.gz`;
34
- }
35
-
36
- function getCachePath(version: string): string {
37
- return join(CANARY_CACHE_DIR, version, 'agentuity');
38
- }
39
-
40
- function hashUrl(url: string): string {
41
- return createHash('sha256').update(url).digest('hex').slice(0, 12);
42
- }
43
-
44
- async function cleanupOldCanaries(): Promise<void> {
45
- try {
46
- await mkdir(CANARY_CACHE_DIR, { recursive: true });
47
- const entries = await readdir(CANARY_CACHE_DIR);
48
- const now = Date.now();
49
-
50
- for (const entry of entries) {
51
- const entryPath = join(CANARY_CACHE_DIR, entry);
52
- try {
53
- const stats = await stat(entryPath);
54
- if (now - stats.mtimeMs > CACHE_MAX_AGE_MS) {
55
- await rm(entryPath, { recursive: true, force: true });
56
- }
57
- } catch {
58
- // Ignore errors for individual entries
59
- }
60
- }
61
- } catch {
62
- // Ignore cleanup errors
63
- }
64
- }
65
-
66
- async function downloadCanary(url: string, destPath: string): Promise<void> {
67
- const destDir = join(destPath, '..');
68
- await mkdir(destDir, { recursive: true });
69
-
70
- const gzPath = `${destPath}.gz`;
71
-
72
- const stream = await downloadWithProgress({
73
- url,
74
- message: 'Downloading canary...',
75
- });
76
-
77
- const writer = Bun.file(gzPath).writer();
78
- for await (const chunk of stream) {
79
- writer.write(chunk);
80
- }
81
- await writer.end();
82
-
83
- if (!(await Bun.file(gzPath).exists())) {
84
- throw new Error('Download failed - file not created');
85
- }
86
-
87
- try {
88
- await $`gunzip ${gzPath}`.quiet();
89
- } catch (error) {
90
- if (await Bun.file(gzPath).exists()) {
91
- await $`rm ${gzPath}`.quiet();
92
- }
93
- throw new Error(
94
- `Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}`
95
- );
96
- }
97
-
98
- if (!(await Bun.file(destPath).exists())) {
99
- throw new Error('Decompression failed - file not found');
100
- }
101
-
102
- await $`chmod 755 ${destPath}`.quiet();
24
+ function isHttpUrl(str: string): boolean {
25
+ return str.startsWith('http://') && !str.startsWith('https://');
103
26
  }
104
27
 
105
28
  export const command = createCommand({
@@ -114,22 +37,18 @@ export const command = createCommand({
114
37
  },
115
38
 
116
39
  async handler(ctx) {
117
- const { args } = ctx;
40
+ const { args, logger } = ctx;
118
41
 
119
42
  // Get raw args from process.argv to capture ALL args after 'canary <version>'
120
- // This ensures we forward everything including flags like --json, --force, etc.
121
43
  const argv = process.argv;
122
44
  const canaryIndex = argv.indexOf('canary');
123
45
 
124
46
  if (args.args.length === 0) {
125
- tui.error('Usage: agentuity canary <version|url> [commands...]');
47
+ tui.error('Usage: agentuity canary <version> [commands...]');
126
48
  tui.newline();
127
49
  tui.info('Examples:');
128
50
  tui.info(' agentuity canary 0.1.6-abc1234');
129
51
  tui.info(' agentuity canary 0.1.6-abc1234 deploy --log-level trace');
130
- tui.info(
131
- ' agentuity canary https://agentuity-sdk-objects.t3.storage.dev/binary/0.1.6-abc1234/agentuity-darwin-arm64.gz'
132
- );
133
52
  return {
134
53
  executed: false,
135
54
  version: '',
@@ -138,77 +57,83 @@ export const command = createCommand({
138
57
  }
139
58
 
140
59
  // Get target from parsed args, but get forward args from raw argv
141
- // This captures ALL args after the version including any flags
142
60
  const target = args.args[0];
143
61
  const targetIndex = canaryIndex >= 0 ? argv.indexOf(target, canaryIndex) : -1;
144
62
  const forwardArgs = targetIndex >= 0 ? argv.slice(targetIndex + 1) : args.args.slice(1);
145
63
 
146
- // Clean up old canaries in background
147
- cleanupOldCanaries().catch(() => {});
64
+ // Reject HTTP URLs for security
65
+ if (isHttpUrl(target)) {
66
+ tui.error('HTTP URLs are not allowed. Please use HTTPS.');
67
+ return {
68
+ executed: false,
69
+ version: '',
70
+ message: 'HTTP URLs are not allowed for security reasons',
71
+ };
72
+ }
148
73
 
149
- const platform = getPlatformInfo();
150
74
  let version: string;
151
- let downloadUrl: string;
152
- let cachePath: string;
153
-
154
- if (isUrl(target)) {
155
- // Extract version from URL, or create a unique hash for custom URLs
156
- const match = target.match(/\/binary\/([^/]+)\//);
157
- version = match ? match[1] : `custom-${hashUrl(target)}`;
158
- downloadUrl = target;
159
- cachePath = getCachePath(version);
75
+ let tarballUrl: string;
76
+
77
+ if (isHttpsUrl(target)) {
78
+ // Direct URL to tarball
79
+ tarballUrl = target;
80
+ // Extract version from URL if possible
81
+ const match = target.match(/agentuity-cli-(\d+\.\d+\.\d+-[a-f0-9]+)\.tgz/);
82
+ version = match ? match[1] : 'custom';
160
83
  } else {
161
- // Treat as version string
84
+ // Version string - construct URL
162
85
  version = target;
163
- const filename = getBinaryFilename(platform);
164
- downloadUrl = `${CANARY_BASE_URL}/${version}/${filename}`;
165
- cachePath = getCachePath(version);
86
+ tarballUrl = `${CANARY_BASE_URL}/${version}/agentuity-cli-${version}.tgz`;
166
87
  }
167
88
 
168
- // Check cache
169
- if (await Bun.file(cachePath).exists()) {
170
- tui.info(`Using cached canary ${version}`);
171
- } else {
172
- tui.info(`Downloading canary ${version}...`);
173
- try {
174
- await downloadCanary(downloadUrl, cachePath);
175
- tui.success(`Downloaded canary ${version}`);
176
- } catch (error) {
177
- tui.error(
178
- `Failed to download canary: ${error instanceof Error ? error.message : 'Unknown error'}`
179
- );
89
+ tui.info(`Installing canary CLI version ${version}...`);
90
+ logger.debug('Tarball URL: %s', tarballUrl);
91
+
92
+ try {
93
+ // Install the canary version globally using the tarball URL
94
+ const installResult = await $`bun add -g ${tarballUrl}`.quiet().nothrow();
95
+
96
+ if (installResult.exitCode !== 0) {
97
+ const stderr = installResult.stderr.toString();
98
+ tui.error(`Failed to install canary version: ${stderr}`);
180
99
  return {
181
100
  executed: false,
182
101
  version,
183
- message: `Failed to download: ${error instanceof Error ? error.message : 'Unknown error'}`,
102
+ message: `Installation failed: ${stderr}`,
184
103
  };
185
104
  }
186
- }
187
105
 
188
- // Update access time
189
- try {
190
- await $`touch -a -m ${cachePath}`.quiet();
191
- } catch {
192
- // Ignore touch errors
193
- }
106
+ tui.success(`Installed canary version ${version}`);
107
+
108
+ // If no additional args, just report success
109
+ if (forwardArgs.length === 0) {
110
+ tui.info('Run commands with: agentuity <command>');
111
+ return {
112
+ executed: true,
113
+ version,
114
+ message: `Canary version ${version} installed. Use 'agentuity <command>' to run commands.`,
115
+ };
116
+ }
117
+
118
+ // Execute the command with the newly installed canary
119
+ tui.info(`Running: agentuity ${forwardArgs.join(' ')}`);
120
+ tui.newline();
121
+
122
+ const result = await $`agentuity ${forwardArgs}`.nothrow();
194
123
 
195
- tui.newline();
196
- tui.info(`Running canary ${version}...`);
197
- tui.newline();
198
-
199
- // Execute the canary binary with forwarded args
200
- // Skip version check in the canary binary to avoid upgrade prompts
201
- const proc = Bun.spawn([cachePath, ...forwardArgs], {
202
- stdin: 'inherit',
203
- stdout: 'inherit',
204
- stderr: 'inherit',
205
- env: {
206
- ...process.env,
207
- AGENTUITY_SKIP_VERSION_CHECK: '1',
208
- },
209
- });
210
-
211
- const exitCode = await proc.exited;
212
- process.exit(exitCode);
124
+ return {
125
+ executed: true,
126
+ version,
127
+ message: result.exitCode === 0 ? 'Command executed successfully' : 'Command failed',
128
+ };
129
+ } catch (error) {
130
+ const message = error instanceof Error ? error.message : 'Unknown error';
131
+ tui.error(`Failed to run canary: ${message}`);
132
+ return {
133
+ executed: false,
134
+ version,
135
+ message,
136
+ };
137
+ }
213
138
  },
214
139
  });
@@ -4,7 +4,6 @@ import { createPublicKey } from 'node:crypto';
4
4
  import { createReadStream, createWriteStream, existsSync, mkdirSync, writeFileSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { StructuredError } from '@agentuity/core';
7
- import { isRunningFromExecutable } from '../upgrade';
8
7
  import { createSubcommand, DeployOptionsSchema } from '../../types';
9
8
  import { getUserAgent } from '../../api';
10
9
  import * as tui from '../../tui';
@@ -737,18 +736,8 @@ export const deploySubcommand = createSubcommand({
737
736
  return stepError(errorMsg);
738
737
  }
739
738
 
740
- // Workaround for Bun crash in compiled executables (https://github.com/agentuity/sdk/issues/191)
741
- // Use limited concurrency (1 at a time) for executables to avoid parallel fetch crash
742
- const isExecutable = isRunningFromExecutable();
743
- const concurrency = isExecutable ? 1 : Math.min(4, build.assets.length);
744
-
745
- if (isExecutable) {
746
- ctx.logger.trace(
747
- `Running from executable - using limited concurrency (${concurrency} uploads at a time)`
748
- );
749
- }
750
-
751
739
  // Process assets in batches with limited concurrency
740
+ const concurrency = Math.min(4, build.assets.length);
752
741
  for (let i = 0; i < build.assets.length; i += concurrency) {
753
742
  const batch = build.assets.slice(i, i + concurrency);
754
743
  const promises: Promise<Response>[] = [];
@@ -2,16 +2,28 @@ import { APIResponseSchema } from '@agentuity/server';
2
2
  import { z } from 'zod';
3
3
  import type { APIClient } from '../../api';
4
4
  import { StructuredError } from '@agentuity/core';
5
+ import { createPublicKey } from 'crypto';
5
6
 
6
7
  const DevmodeRequestSchema = z.object({
7
8
  hostname: z.string().optional().describe('the hostname for the endpoint'),
9
+ publicKey: z.string().optional().describe('the public key PEM for the endpoint'),
8
10
  });
9
11
 
10
12
  type DevmodeRequest = z.infer<typeof DevmodeRequestSchema>;
11
13
 
14
+ function extractPublicKeyPEM(privateKeyPEM: string): string | undefined {
15
+ try {
16
+ const publicKey = createPublicKey(privateKeyPEM);
17
+ return publicKey.export({ type: 'spki', format: 'pem' }) as string;
18
+ } catch {
19
+ return undefined;
20
+ }
21
+ }
22
+
12
23
  const DevmodeResponseSchema = z.object({
13
24
  id: z.string(),
14
25
  hostname: z.string(),
26
+ privateKey: z.string().optional(),
15
27
  });
16
28
  export type DevmodeResponse = z.infer<typeof DevmodeResponseSchema>;
17
29
 
@@ -26,18 +38,22 @@ const DevmodeEndpointError = StructuredError('DevmodeEndpointError');
26
38
  * @param apiClient the api client to use
27
39
  * @param projectId the project id
28
40
  * @param hostname the hostname is already configured
41
+ * @param privateKey the private key PEM if already configured
29
42
  * @returns
30
43
  */
31
44
  export async function generateEndpoint(
32
45
  apiClient: APIClient,
33
46
  projectId: string,
34
- hostname?: string
47
+ hostname?: string,
48
+ privateKey?: string
35
49
  ): Promise<DevmodeResponse> {
50
+ const publicKey = privateKey ? extractPublicKeyPEM(privateKey) : undefined;
51
+
36
52
  const resp = await apiClient.request<DevmodeResponseAPI, DevmodeRequest>(
37
53
  'POST',
38
- `/cli/devmode/2/${projectId}`,
54
+ `/cli/devmode/3/${projectId}`,
39
55
  DevmodeResponseAPISchema,
40
- { hostname },
56
+ { hostname, publicKey },
41
57
  DevmodeRequestSchema
42
58
  );
43
59
 
@@ -377,20 +377,34 @@ export const command = createCommand({
377
377
  let gravityBin: string | undefined;
378
378
  let gravityURL: string | undefined;
379
379
  let appURL: string | undefined;
380
+ let savedPrivateKey: string | undefined = config?.devmode?.privateKey
381
+ ? Buffer.from(config.devmode.privateKey, 'base64').toString('utf-8')
382
+ : undefined;
380
383
 
381
384
  if (auth && project && opts.public) {
382
385
  // Generate devmode endpoint for public URL
383
386
  const endpoint = await tui.spinner({
384
387
  message: 'Connecting to Gravity',
385
388
  callback: () => {
386
- return generateEndpoint(apiClient!, project.projectId, config?.devmode?.hostname);
389
+ return generateEndpoint(
390
+ apiClient!,
391
+ project.projectId,
392
+ config?.devmode?.hostname,
393
+ savedPrivateKey
394
+ );
387
395
  },
388
396
  clearOnSuccess: true,
389
397
  });
390
398
 
399
+ if (endpoint.privateKey) {
400
+ savedPrivateKey = endpoint.privateKey;
401
+ }
391
402
  const _config = { ...config } as Config;
392
403
  _config.devmode = {
393
404
  hostname: endpoint.hostname,
405
+ privateKey: savedPrivateKey
406
+ ? Buffer.from(savedPrivateKey).toString('base64')
407
+ : undefined,
394
408
  };
395
409
  await saveConfig(_config);
396
410
  config = _config;
@@ -1062,6 +1076,12 @@ export const command = createCommand({
1062
1076
  rootDir,
1063
1077
  devmode.id
1064
1078
  );
1079
+ const privateKeyPEM = devmode.privateKey ?? savedPrivateKey;
1080
+ if (!privateKeyPEM) {
1081
+ throw new Error(
1082
+ 'No private key available for gravity connection. Please re-run to generate a new key.'
1083
+ );
1084
+ }
1065
1085
  gravityProcess = Bun.spawn(
1066
1086
  [
1067
1087
  gravityBin,
@@ -1077,8 +1097,8 @@ export const command = createCommand({
1077
1097
  project.orgId,
1078
1098
  '--project-id',
1079
1099
  project.projectId,
1080
- '--token',
1081
- process.env.AGENTUITY_SDK_KEY!, // set above
1100
+ '--private-key',
1101
+ Buffer.from(privateKeyPEM).toString('base64'),
1082
1102
  '--health-check',
1083
1103
  ],
1084
1104
  {
package/src/cmd/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { CommandDefinition } from '../types';
2
- import { isRunningFromExecutable } from './upgrade';
3
2
 
4
3
  // Use dynamic imports for bundler compatibility while maintaining lazy loading
5
4
  export async function discoverCommands(): Promise<CommandDefinition[]> {
@@ -21,14 +20,8 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
21
20
  ]);
22
21
 
23
22
  const commands: CommandDefinition[] = [];
24
- const isExecutable = isRunningFromExecutable();
25
23
 
26
24
  for (const cmd of commandModules) {
27
- // Skip commands that require running from an executable when not in one
28
- if (cmd.executable && !isExecutable) {
29
- continue;
30
- }
31
-
32
25
  commands.push(cmd);
33
26
 
34
27
  // Auto-create hidden top-level aliases for subcommands with toplevel: true
@@ -37,15 +37,14 @@ export const command = createCommand({
37
37
  message: 'Validating your identity',
38
38
  clearOnSuccess: true,
39
39
  callback: async () => {
40
- // For compiled binaries, process.argv contains virtual paths (/$bunfs/root/...)
41
- // Use process.execPath which has the actual binary path
42
- const isCompiledBinary = process.argv[1]?.startsWith('/$bunfs/');
43
- const cmd = isCompiledBinary
44
- ? [
45
- process.execPath,
46
- ...process.argv.slice(2).map((x) => (x === 'setup' ? 'login' : x)),
47
- ]
48
- : process.argv.map((x) => (x === 'setup' ? 'login' : x));
40
+ // Re-run the CLI with 'login' instead of 'setup'
41
+ // Only replace the first occurrence of 'setup' to avoid replacing user data
42
+ const argv = process.argv;
43
+ const setupIndex = argv.indexOf('setup');
44
+ const cmd =
45
+ setupIndex >= 0
46
+ ? [...argv.slice(0, setupIndex), 'login', ...argv.slice(setupIndex + 1)]
47
+ : argv;
49
48
  const r = Bun.spawn({
50
49
  cmd: cmd.concat('--json'),
51
50
  stdout: 'pipe',