@agentuity/cli 0.0.6

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 (158) hide show
  1. package/AGENTS.md +139 -0
  2. package/README.md +239 -0
  3. package/bin/cli.ts +71 -0
  4. package/dist/api.d.ts +25 -0
  5. package/dist/api.d.ts.map +1 -0
  6. package/dist/auth.d.ts +7 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/banner.d.ts +2 -0
  9. package/dist/banner.d.ts.map +1 -0
  10. package/dist/cli.d.ts +5 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cmd/auth/api.d.ts +9 -0
  13. package/dist/cmd/auth/api.d.ts.map +1 -0
  14. package/dist/cmd/auth/index.d.ts +2 -0
  15. package/dist/cmd/auth/index.d.ts.map +1 -0
  16. package/dist/cmd/auth/login.d.ts +3 -0
  17. package/dist/cmd/auth/login.d.ts.map +1 -0
  18. package/dist/cmd/auth/logout.d.ts +3 -0
  19. package/dist/cmd/auth/logout.d.ts.map +1 -0
  20. package/dist/cmd/bundle/ast.d.ts +2 -0
  21. package/dist/cmd/bundle/ast.d.ts.map +1 -0
  22. package/dist/cmd/bundle/bundler.d.ts +6 -0
  23. package/dist/cmd/bundle/bundler.d.ts.map +1 -0
  24. package/dist/cmd/bundle/file.d.ts +2 -0
  25. package/dist/cmd/bundle/file.d.ts.map +1 -0
  26. package/dist/cmd/bundle/index.d.ts +2 -0
  27. package/dist/cmd/bundle/index.d.ts.map +1 -0
  28. package/dist/cmd/bundle/plugin.d.ts +4 -0
  29. package/dist/cmd/bundle/plugin.d.ts.map +1 -0
  30. package/dist/cmd/dev/index.d.ts +2 -0
  31. package/dist/cmd/dev/index.d.ts.map +1 -0
  32. package/dist/cmd/example/create-user.d.ts +2 -0
  33. package/dist/cmd/example/create-user.d.ts.map +1 -0
  34. package/dist/cmd/example/create.d.ts +2 -0
  35. package/dist/cmd/example/create.d.ts.map +1 -0
  36. package/dist/cmd/example/deploy.d.ts +2 -0
  37. package/dist/cmd/example/deploy.d.ts.map +1 -0
  38. package/dist/cmd/example/index.d.ts +2 -0
  39. package/dist/cmd/example/index.d.ts.map +1 -0
  40. package/dist/cmd/example/list.d.ts +2 -0
  41. package/dist/cmd/example/list.d.ts.map +1 -0
  42. package/dist/cmd/example/run-command.d.ts +2 -0
  43. package/dist/cmd/example/run-command.d.ts.map +1 -0
  44. package/dist/cmd/example/sound.d.ts +3 -0
  45. package/dist/cmd/example/sound.d.ts.map +1 -0
  46. package/dist/cmd/example/spinner.d.ts +2 -0
  47. package/dist/cmd/example/spinner.d.ts.map +1 -0
  48. package/dist/cmd/example/steps.d.ts +2 -0
  49. package/dist/cmd/example/steps.d.ts.map +1 -0
  50. package/dist/cmd/example/version.d.ts +2 -0
  51. package/dist/cmd/example/version.d.ts.map +1 -0
  52. package/dist/cmd/index.d.ts +3 -0
  53. package/dist/cmd/index.d.ts.map +1 -0
  54. package/dist/cmd/profile/create.d.ts +2 -0
  55. package/dist/cmd/profile/create.d.ts.map +1 -0
  56. package/dist/cmd/profile/delete.d.ts +2 -0
  57. package/dist/cmd/profile/delete.d.ts.map +1 -0
  58. package/dist/cmd/profile/index.d.ts +2 -0
  59. package/dist/cmd/profile/index.d.ts.map +1 -0
  60. package/dist/cmd/profile/list.d.ts +3 -0
  61. package/dist/cmd/profile/list.d.ts.map +1 -0
  62. package/dist/cmd/profile/show.d.ts +2 -0
  63. package/dist/cmd/profile/show.d.ts.map +1 -0
  64. package/dist/cmd/profile/use.d.ts +2 -0
  65. package/dist/cmd/profile/use.d.ts.map +1 -0
  66. package/dist/cmd/project/create.d.ts +2 -0
  67. package/dist/cmd/project/create.d.ts.map +1 -0
  68. package/dist/cmd/project/delete.d.ts +2 -0
  69. package/dist/cmd/project/delete.d.ts.map +1 -0
  70. package/dist/cmd/project/index.d.ts +2 -0
  71. package/dist/cmd/project/index.d.ts.map +1 -0
  72. package/dist/cmd/project/list.d.ts +2 -0
  73. package/dist/cmd/project/list.d.ts.map +1 -0
  74. package/dist/cmd/project/show.d.ts +2 -0
  75. package/dist/cmd/project/show.d.ts.map +1 -0
  76. package/dist/cmd/version/index.d.ts +2 -0
  77. package/dist/cmd/version/index.d.ts.map +1 -0
  78. package/dist/command-prefix.d.ts +11 -0
  79. package/dist/command-prefix.d.ts.map +1 -0
  80. package/dist/config.d.ts +16 -0
  81. package/dist/config.d.ts.map +1 -0
  82. package/dist/index.d.ts +18 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/legacy-check.d.ts +6 -0
  85. package/dist/legacy-check.d.ts.map +1 -0
  86. package/dist/logger.d.ts +24 -0
  87. package/dist/logger.d.ts.map +1 -0
  88. package/dist/runtime.d.ts +3 -0
  89. package/dist/runtime.d.ts.map +1 -0
  90. package/dist/schema-parser.d.ts +24 -0
  91. package/dist/schema-parser.d.ts.map +1 -0
  92. package/dist/sound.d.ts +2 -0
  93. package/dist/sound.d.ts.map +1 -0
  94. package/dist/steps.d.ts +59 -0
  95. package/dist/steps.d.ts.map +1 -0
  96. package/dist/terminal.d.ts +3 -0
  97. package/dist/terminal.d.ts.map +1 -0
  98. package/dist/tui.d.ts +156 -0
  99. package/dist/tui.d.ts.map +1 -0
  100. package/dist/types.d.ts +164 -0
  101. package/dist/types.d.ts.map +1 -0
  102. package/dist/version.d.ts +10 -0
  103. package/dist/version.d.ts.map +1 -0
  104. package/package.json +46 -0
  105. package/src/api-errors.md +115 -0
  106. package/src/api.ts +186 -0
  107. package/src/auth.ts +91 -0
  108. package/src/banner.ts +23 -0
  109. package/src/cli.ts +198 -0
  110. package/src/cmd/auth/README.md +95 -0
  111. package/src/cmd/auth/api.ts +71 -0
  112. package/src/cmd/auth/index.ts +9 -0
  113. package/src/cmd/auth/login.ts +76 -0
  114. package/src/cmd/auth/logout.ts +14 -0
  115. package/src/cmd/bundle/ast.ts +228 -0
  116. package/src/cmd/bundle/bundler.ts +88 -0
  117. package/src/cmd/bundle/file.ts +16 -0
  118. package/src/cmd/bundle/index.ts +38 -0
  119. package/src/cmd/bundle/plugin.ts +259 -0
  120. package/src/cmd/dev/index.ts +83 -0
  121. package/src/cmd/example/create-user.ts +38 -0
  122. package/src/cmd/example/create.ts +31 -0
  123. package/src/cmd/example/deploy.ts +36 -0
  124. package/src/cmd/example/index.ts +27 -0
  125. package/src/cmd/example/list.ts +32 -0
  126. package/src/cmd/example/run-command.ts +45 -0
  127. package/src/cmd/example/sound.ts +14 -0
  128. package/src/cmd/example/spinner.ts +44 -0
  129. package/src/cmd/example/steps.ts +66 -0
  130. package/src/cmd/example/version.ts +13 -0
  131. package/src/cmd/index.ts +46 -0
  132. package/src/cmd/profile/README.md +80 -0
  133. package/src/cmd/profile/create.ts +57 -0
  134. package/src/cmd/profile/delete.ts +52 -0
  135. package/src/cmd/profile/index.ts +12 -0
  136. package/src/cmd/profile/list.ts +27 -0
  137. package/src/cmd/profile/show.ts +54 -0
  138. package/src/cmd/profile/use.ts +30 -0
  139. package/src/cmd/project/create.ts +247 -0
  140. package/src/cmd/project/delete.ts +13 -0
  141. package/src/cmd/project/index.ts +11 -0
  142. package/src/cmd/project/list.ts +13 -0
  143. package/src/cmd/project/show.ts +12 -0
  144. package/src/cmd/version/index.ts +16 -0
  145. package/src/command-prefix.ts +43 -0
  146. package/src/config.ts +304 -0
  147. package/src/index.ts +40 -0
  148. package/src/legacy-check.ts +127 -0
  149. package/src/logger.ts +235 -0
  150. package/src/runtime.ts +22 -0
  151. package/src/schema-parser.ts +213 -0
  152. package/src/sound.ts +25 -0
  153. package/src/steps.ts +245 -0
  154. package/src/terminal.ts +151 -0
  155. package/src/tui.md +254 -0
  156. package/src/tui.ts +838 -0
  157. package/src/types.ts +243 -0
  158. package/src/version.ts +29 -0
package/src/cli.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandDefinition, SubcommandDefinition, CommandContext } from './types';
3
+ import { showBanner } from './banner';
4
+ import { requireAuth } from './auth';
5
+ import { parseArgsSchema, parseOptionsSchema, buildValidationInput } from './schema-parser';
6
+
7
+ export async function createCLI(version: string): Promise<Command> {
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('agentuity')
12
+ .description('Agentuity CLI')
13
+ .version(version, '-V, --version', 'Display version')
14
+ .helpOption('-h, --help', 'Display help');
15
+
16
+ program
17
+ .option('--config <path>', 'Config file path', '~/.config/agentuity/config.yaml')
18
+ .option('--log-level <level>', 'Log level', 'info')
19
+ .option('--log-timestamp', 'Show timestamps in log output', false)
20
+ .option('--no-log-prefix', 'Hide log level prefixes', false)
21
+ .option('--color-scheme <scheme>', 'Color scheme: light or dark');
22
+
23
+ const skipVersionCheckOption = program.createOption(
24
+ '--skip-version-check',
25
+ 'Skip version compatibility check (dev only)'
26
+ );
27
+ skipVersionCheckOption.hideHelp();
28
+ program.addOption(skipVersionCheckOption);
29
+
30
+ program.action(() => {
31
+ showBanner(version);
32
+ program.help();
33
+ });
34
+
35
+ return program;
36
+ }
37
+
38
+ async function registerSubcommand(
39
+ parent: Command,
40
+ subcommand: SubcommandDefinition,
41
+ baseCtx: CommandContext,
42
+ hidden?: boolean
43
+ ): Promise<void> {
44
+ const cmd = parent.command(subcommand.name, { hidden }).description(subcommand.description);
45
+
46
+ if (subcommand.aliases) {
47
+ cmd.aliases(subcommand.aliases);
48
+ }
49
+
50
+ // Auto-generate arguments and options from schemas
51
+ if (subcommand.schema?.args) {
52
+ const parsed = parseArgsSchema(subcommand.schema.args);
53
+ for (const argMeta of parsed.metadata) {
54
+ let argSyntax: string;
55
+ if (argMeta.variadic) {
56
+ argSyntax = argMeta.optional ? `[${argMeta.name}...]` : `<${argMeta.name}...>`;
57
+ } else {
58
+ argSyntax = argMeta.optional ? `[${argMeta.name}]` : `<${argMeta.name}>`;
59
+ }
60
+ cmd.argument(argSyntax);
61
+ }
62
+ }
63
+
64
+ if (subcommand.schema?.options) {
65
+ const parsed = parseOptionsSchema(subcommand.schema.options);
66
+ for (const opt of parsed) {
67
+ const flag = opt.name.replace(/([A-Z])/g, '-$1').toLowerCase();
68
+ const desc = opt.description || '';
69
+ if (opt.type === 'boolean') {
70
+ // Support negatable boolean options (--no-flag) when they have a default
71
+ if (opt.hasDefault) {
72
+ cmd.option(`--no-${flag}`, desc);
73
+ }
74
+ cmd.option(`--${flag}`, desc);
75
+ } else if (opt.type === 'number') {
76
+ cmd.option(`--${flag} <${opt.name}>`, desc, parseFloat);
77
+ } else {
78
+ cmd.option(`--${flag} <${opt.name}>`, desc);
79
+ }
80
+ }
81
+ }
82
+
83
+ cmd.action(async (...rawArgs: unknown[]) => {
84
+ const cmdObj = rawArgs[rawArgs.length - 1] as { opts: () => Record<string, unknown> };
85
+ const options = cmdObj.opts();
86
+ const args = rawArgs.slice(0, -1);
87
+
88
+ if (subcommand.requiresAuth) {
89
+ const auth = await requireAuth(baseCtx as CommandContext<false>);
90
+
91
+ if (subcommand.schema) {
92
+ try {
93
+ const input = buildValidationInput(subcommand.schema, args, options);
94
+ const ctx: Record<string, unknown> = {
95
+ ...baseCtx,
96
+ auth,
97
+ };
98
+ if (subcommand.schema.args) {
99
+ ctx.args = subcommand.schema.args.parse(input.args);
100
+ }
101
+ if (subcommand.schema.options) {
102
+ ctx.opts = subcommand.schema.options.parse(input.options);
103
+ }
104
+ await subcommand.handler(ctx as CommandContext);
105
+ } catch (error) {
106
+ if (error && typeof error === 'object' && 'issues' in error) {
107
+ baseCtx.logger.error('Validation error:');
108
+ const issues = (error as { issues: Array<{ path: string[]; message: string }> })
109
+ .issues;
110
+ for (const issue of issues) {
111
+ baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
112
+ }
113
+ process.exit(1);
114
+ }
115
+ throw error;
116
+ }
117
+ } else {
118
+ const ctx: CommandContext<true> = {
119
+ ...baseCtx,
120
+ auth,
121
+ };
122
+ await subcommand.handler(ctx);
123
+ }
124
+ } else {
125
+ if (subcommand.schema) {
126
+ try {
127
+ const input = buildValidationInput(subcommand.schema, args, options);
128
+ const ctx: Record<string, unknown> = {
129
+ ...baseCtx,
130
+ };
131
+ if (subcommand.schema.args) {
132
+ ctx.args = subcommand.schema.args.parse(input.args);
133
+ }
134
+ if (subcommand.schema.options) {
135
+ ctx.opts = subcommand.schema.options.parse(input.options);
136
+ }
137
+ await subcommand.handler(ctx as CommandContext);
138
+ } catch (error) {
139
+ if (error && typeof error === 'object' && 'issues' in error) {
140
+ baseCtx.logger.error('Validation error:');
141
+ const issues = (error as { issues: Array<{ path: string[]; message: string }> })
142
+ .issues;
143
+ for (const issue of issues) {
144
+ baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
145
+ }
146
+ process.exit(1);
147
+ }
148
+ throw error;
149
+ }
150
+ } else {
151
+ await subcommand.handler(baseCtx as CommandContext<false>);
152
+ }
153
+ }
154
+ });
155
+ }
156
+
157
+ export async function registerCommands(
158
+ program: Command,
159
+ commands: CommandDefinition[],
160
+ baseCtx: CommandContext
161
+ ): Promise<void> {
162
+ for (const cmdDef of commands) {
163
+ if (cmdDef.subcommands) {
164
+ const cmd = program
165
+ .command(cmdDef.name, { hidden: cmdDef.hidden })
166
+ .description(cmdDef.description);
167
+
168
+ if (cmdDef.aliases) {
169
+ cmd.aliases(cmdDef.aliases);
170
+ }
171
+
172
+ if (cmdDef.handler) {
173
+ cmd.action(async () => {
174
+ if (cmdDef.requiresAuth) {
175
+ const auth = await requireAuth(baseCtx as CommandContext<false>);
176
+ const ctx: CommandContext<true> = { ...baseCtx, auth };
177
+ await cmdDef.handler!(ctx);
178
+ } else {
179
+ await cmdDef.handler!(baseCtx as CommandContext<false>);
180
+ }
181
+ });
182
+ } else {
183
+ cmd.action(() => cmd.help());
184
+ }
185
+
186
+ for (const sub of cmdDef.subcommands) {
187
+ await registerSubcommand(cmd, sub, baseCtx);
188
+ }
189
+ } else {
190
+ await registerSubcommand(
191
+ program,
192
+ cmdDef as unknown as SubcommandDefinition,
193
+ baseCtx,
194
+ cmdDef.hidden
195
+ );
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,95 @@
1
+ # Authentication Commands
2
+
3
+ The auth command provides authentication and authorization functionality for the Agentuity Platform.
4
+
5
+ ## Commands
6
+
7
+ ### `auth login`
8
+
9
+ Login to the Agentuity Platform using a browser-based authentication flow.
10
+
11
+ ```bash
12
+ agentuity auth login
13
+ # or
14
+ agentuity login
15
+ ```
16
+
17
+ **How it works:**
18
+
19
+ 1. Generates a one-time password (OTP) by calling `/cli/auth/start`
20
+ 2. Displays the OTP and authentication URL to the user
21
+ 3. Automatically opens the browser to the auth URL (on supported platforms)
22
+ 4. Polls `/cli/auth/check` every 2 seconds for up to 60 seconds
23
+ 5. Once authenticated, saves the API key, user ID, and expiration to config
24
+
25
+ **Stored data:**
26
+
27
+ ```yaml
28
+ auth:
29
+ api_key: 'your-api-key'
30
+ user_id: 'user-id'
31
+ expires: 1234567890
32
+ preferences:
33
+ orgId: ''
34
+ ```
35
+
36
+ ### `auth logout`
37
+
38
+ Logout of the Agentuity Cloud Platform by clearing authentication credentials.
39
+
40
+ ```bash
41
+ agentuity auth logout
42
+ # or
43
+ agentuity logout
44
+ ```
45
+
46
+ **What it does:**
47
+
48
+ - Clears `auth.api_key`, `auth.user_id`, and sets `auth.expires` to current time
49
+ - Clears `preferences.orgId`
50
+ - Writes changes to the active profile config file
51
+
52
+ ## API Endpoints
53
+
54
+ The authentication flow uses the following API endpoints:
55
+
56
+ - `GET /cli/auth/start` - Generate OTP for login
57
+ - `POST /cli/auth/check` - Poll for login completion with OTP
58
+
59
+ ## URL Configuration
60
+
61
+ The API and App URLs are determined in the following priority order:
62
+
63
+ 1. **Environment Variables** (highest priority)
64
+ - `AGENTUITY_API_URL` - Override the API base URL
65
+ - `AGENTUITY_APP_URL` - Override the app base URL
66
+
67
+ 2. **Config File Overrides**
68
+
69
+ ```yaml
70
+ overrides:
71
+ api_url: https://api.agentuity.io
72
+ app_url: https://app.agentuity.io
73
+ ```
74
+
75
+ 3. **Default Values** (lowest priority)
76
+ - API URL: `https://api.agentuity.com`
77
+ - App URL: `https://app.agentuity.com`
78
+
79
+ This allows different profiles (e.g., `local`, `production`) to point to different environments.
80
+
81
+ ## Implementation Details
82
+
83
+ - **Generic API Client**: [../../api.ts](../../api.ts) provides the generic `APIClient` class for HTTP requests
84
+ - **Auth-specific APIs**: [api.ts](./api.ts) provides `generateLoginOTP()` and `pollForLoginCompletion()` functions
85
+ - **Config Management**: [../../config.ts](../../config.ts) provides `saveAuth()`, `clearAuth()`, and `getAuth()` helpers
86
+ - **Browser Opening**: Uses `Bun.spawn(['open', authURL])` on non-Windows platforms to auto-open browser
87
+ - **Polling**: Polls every 2 seconds with 60-second timeout
88
+ - **Error Handling**: All errors are caught and displayed to user with appropriate exit codes
89
+ - `UpgradeRequiredError`: Shows upgrade instructions when CLI version is outdated
90
+ - Generic API errors: Display the error message from the server
91
+ - See [../../api-errors.md](../../api-errors.md) for details
92
+
93
+ ## Architecture
94
+
95
+ Each command has its own `api.ts` file for command-specific API methods and types. The generic `APIClient` class and URL helpers are in the root `api.ts` file, promoting reusability while keeping command-specific logic isolated.
@@ -0,0 +1,71 @@
1
+ import { APIClient } from '@/api';
2
+ import type { Config } from '@/types';
3
+
4
+ interface APIResponse<T> {
5
+ success: boolean;
6
+ message: string;
7
+ data?: T;
8
+ }
9
+
10
+ interface OTPStartData {
11
+ otp: string;
12
+ }
13
+
14
+ interface OTPCompleteData {
15
+ apiKey: string;
16
+ userId: string;
17
+ expires: number;
18
+ }
19
+
20
+ export interface LoginResult {
21
+ apiKey: string;
22
+ userId: string;
23
+ expires: Date;
24
+ }
25
+
26
+ export async function generateLoginOTP(apiUrl: string, config?: Config | null): Promise<string> {
27
+ const client = new APIClient(apiUrl, undefined, config);
28
+ const resp = await client.request<APIResponse<OTPStartData>>('GET', '/cli/auth/start');
29
+
30
+ if (!resp.success) {
31
+ throw new Error(resp.message);
32
+ }
33
+
34
+ if (!resp.data) {
35
+ throw new Error('No OTP returned from server');
36
+ }
37
+
38
+ return resp.data.otp;
39
+ }
40
+
41
+ export async function pollForLoginCompletion(
42
+ apiUrl: string,
43
+ otp: string,
44
+ config?: Config | null,
45
+ timeoutMs = 60000
46
+ ): Promise<LoginResult> {
47
+ const client = new APIClient(apiUrl, undefined, config);
48
+ const started = Date.now();
49
+
50
+ while (Date.now() - started < timeoutMs) {
51
+ const resp = await client.request<APIResponse<OTPCompleteData>>('POST', '/cli/auth/check', {
52
+ otp,
53
+ });
54
+
55
+ if (!resp.success) {
56
+ throw new Error(resp.message);
57
+ }
58
+
59
+ if (resp.data) {
60
+ return {
61
+ apiKey: resp.data.apiKey,
62
+ userId: resp.data.userId,
63
+ expires: new Date(resp.data.expires),
64
+ };
65
+ }
66
+
67
+ await new Promise((resolve) => setTimeout(resolve, 2000));
68
+ }
69
+
70
+ throw new Error('Login timed out');
71
+ }
@@ -0,0 +1,9 @@
1
+ import { createCommand } from '@/types';
2
+ import { loginCommand } from './login';
3
+ import { logoutCommand } from './logout';
4
+
5
+ export const command = createCommand({
6
+ name: 'auth',
7
+ description: 'Authentication and authorization related commands',
8
+ subcommands: [loginCommand, logoutCommand],
9
+ });
@@ -0,0 +1,76 @@
1
+ import type { SubcommandDefinition } from '@/types';
2
+ import { getAPIBaseURL, getAppBaseURL, UpgradeRequiredError } from '@/api';
3
+ import { saveAuth } from '@/config';
4
+ import { generateLoginOTP, pollForLoginCompletion } from './api';
5
+ import * as tui from '@/tui';
6
+
7
+ export const loginCommand: SubcommandDefinition = {
8
+ name: 'login',
9
+ description: 'Login to the Agentuity Platform using a browser-based authentication flow',
10
+ toplevel: true,
11
+
12
+ async handler(ctx) {
13
+ const { logger, config } = ctx;
14
+ const apiUrl = getAPIBaseURL(config);
15
+ const appUrl = getAppBaseURL(config);
16
+
17
+ try {
18
+ console.log('Generating login OTP...');
19
+
20
+ const otp = await generateLoginOTP(apiUrl, config);
21
+ const authURL = `${appUrl}/auth/cli`;
22
+
23
+ const copied = await tui.copyToClipboard(otp);
24
+
25
+ tui.newline();
26
+ if (copied) {
27
+ console.log(`Code copied to clipboard: ${tui.bold(otp)}`);
28
+ } else {
29
+ console.log('Copy the following code:');
30
+ tui.newline();
31
+ console.log(` ${tui.bold(otp)}`);
32
+ }
33
+ tui.newline();
34
+ console.log('Then open the URL in your browser and paste the code:');
35
+ tui.newline();
36
+ console.log(` ${tui.link(authURL)}`);
37
+ tui.newline();
38
+ console.log(tui.muted('This code will expire in 60 seconds'));
39
+ tui.newline();
40
+
41
+ if (process.platform === 'darwin') {
42
+ await tui.waitForAnyKey('Press Enter to open the URL...');
43
+ try {
44
+ Bun.spawn(['open', authURL], {
45
+ stdio: ['ignore', 'ignore', 'ignore'],
46
+ });
47
+ } catch {
48
+ // Ignore browser open errors
49
+ }
50
+ }
51
+
52
+ console.log('Waiting for login to complete...');
53
+
54
+ const result = await pollForLoginCompletion(apiUrl, otp, config);
55
+
56
+ await saveAuth({
57
+ apiKey: result.apiKey,
58
+ userId: result.userId,
59
+ expires: result.expires,
60
+ });
61
+
62
+ tui.newline();
63
+ tui.success('Welcome to Agentuity! You are now logged in');
64
+ } catch (error) {
65
+ if (error instanceof UpgradeRequiredError) {
66
+ const bannerBody = `${error.message}\n\nVisit: ${tui.link('https://agentuity.dev/CLI/installation')}`;
67
+ tui.banner('CLI Upgrade Required', bannerBody);
68
+ process.exit(1);
69
+ } else if (error instanceof Error) {
70
+ logger.fatal(`Login failed: ${error.message}`);
71
+ } else {
72
+ logger.fatal('Login failed');
73
+ }
74
+ }
75
+ },
76
+ };
@@ -0,0 +1,14 @@
1
+ import type { SubcommandDefinition } from '@/types';
2
+ import { clearAuth } from '@/config';
3
+ import * as tui from '@/tui';
4
+
5
+ export const logoutCommand: SubcommandDefinition = {
6
+ name: 'logout',
7
+ description: 'Logout of the Agentuity Cloud Platform',
8
+ toplevel: true,
9
+
10
+ async handler() {
11
+ await clearAuth();
12
+ tui.success('You have been logged out');
13
+ },
14
+ };
@@ -0,0 +1,228 @@
1
+ import * as acornLoose from 'acorn-loose';
2
+ import { basename, dirname, relative } from 'node:path';
3
+ import { generate } from 'astring';
4
+
5
+ interface ASTNode {
6
+ type: string;
7
+ }
8
+
9
+ interface ASTNodeIdentifier extends ASTNode {
10
+ name: string;
11
+ }
12
+
13
+ interface ASTCallExpression extends ASTNode {
14
+ arguments: unknown[];
15
+ callee: {
16
+ name: string;
17
+ };
18
+ }
19
+
20
+ interface ASTPropertyNode {
21
+ type: string;
22
+ kind: string;
23
+ key: ASTNodeIdentifier;
24
+ value: ASTNode;
25
+ }
26
+
27
+ interface ASTObjectExpression extends ASTNode {
28
+ properties: ASTPropertyNode[];
29
+ }
30
+
31
+ interface ASTLiteral {
32
+ value: string;
33
+ }
34
+
35
+ function parseObjectExpressionToMap(expr: ASTObjectExpression): Map<string, string> {
36
+ const result = new Map<string, string>();
37
+ for (const prop of expr.properties) {
38
+ switch (prop.value.type) {
39
+ case 'Literal': {
40
+ const value = prop.value as unknown as ASTLiteral;
41
+ result.set(prop.key.name, value.value);
42
+ break;
43
+ }
44
+ default: {
45
+ console.warn(
46
+ 'AST value type %s of metadata key: %s not supported',
47
+ prop.value.type,
48
+ prop.key.name
49
+ );
50
+ }
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+
56
+ function createObjectPropertyNode(key: string, value: string) {
57
+ return {
58
+ type: 'Property',
59
+ kind: 'init',
60
+ key: {
61
+ type: 'Identifier',
62
+ name: key,
63
+ },
64
+ value: {
65
+ type: 'Literal',
66
+ value,
67
+ },
68
+ };
69
+ }
70
+
71
+ function createNewMetadataNode() {
72
+ return {
73
+ type: 'Property',
74
+ kind: 'init',
75
+ key: {
76
+ type: 'Identifier',
77
+ name: 'metadata',
78
+ },
79
+ value: {
80
+ type: 'ObjectExpression',
81
+ properties: [] as ASTPropertyNode[],
82
+ },
83
+ };
84
+ }
85
+
86
+ const projectId = process.env.AGENTUITY_CLOUD_PROJECT_ID ?? '';
87
+
88
+ function hash(...val: string[]): string {
89
+ const hasher = new Bun.CryptoHasher('sha256');
90
+ val.forEach((val) => hasher.update(val));
91
+ return hasher.digest().toHex();
92
+ }
93
+
94
+ function getAgentId(identifier: string): string {
95
+ return hash(projectId, identifier);
96
+ }
97
+
98
+ type AcornParseResultType = ReturnType<typeof acornLoose.parse>;
99
+
100
+ function augmentAgentMetadataNode(
101
+ id: string,
102
+ name: string,
103
+ rel: string,
104
+ version: string,
105
+ ast: AcornParseResultType,
106
+ propvalue: ASTObjectExpression
107
+ ): [string, Map<string, string>] {
108
+ const metadata = parseObjectExpressionToMap(propvalue);
109
+ if (!metadata.has('name')) {
110
+ metadata.set('name', name);
111
+ propvalue.properties.push(createObjectPropertyNode('name', name));
112
+ }
113
+ metadata.set('version', version);
114
+ metadata.set('identifier', name);
115
+ metadata.set('filename', rel);
116
+ metadata.set('id', id);
117
+ propvalue.properties.push(
118
+ createObjectPropertyNode('id', id),
119
+ createObjectPropertyNode('version', version),
120
+ createObjectPropertyNode('identifier', name),
121
+ createObjectPropertyNode('filename', rel)
122
+ );
123
+ const newsource = generate(ast);
124
+ return [newsource, metadata];
125
+ }
126
+
127
+ function createAgentMetadataNode(
128
+ id: string,
129
+ name: string,
130
+ rel: string,
131
+ version: string,
132
+ ast: AcornParseResultType,
133
+ callargexp: ASTObjectExpression
134
+ ): [string, Map<string, string>] {
135
+ const newmetadata = createNewMetadataNode();
136
+ const md = new Map<string, string>();
137
+ md.set('id', id);
138
+ md.set('version', version);
139
+ md.set('name', name);
140
+ md.set('identifier', name);
141
+ md.set('filename', rel);
142
+ for (const [key, value] of md) {
143
+ newmetadata.value.properties.push(createObjectPropertyNode(key, value));
144
+ }
145
+ callargexp.properties.push(newmetadata);
146
+ const newsource = generate(ast);
147
+ return [newsource, md];
148
+ }
149
+
150
+ export function parseAgentMetadata(
151
+ rootDir: string,
152
+ filename: string,
153
+ contents: string
154
+ ): [string, Map<string, string>] {
155
+ const ast = acornLoose.parse(contents, { ecmaVersion: 'latest', sourceType: 'module' });
156
+ let exportName: string | undefined;
157
+ const rel = relative(rootDir, filename);
158
+ const name = basename(dirname(filename));
159
+ const id = getAgentId(name);
160
+ const version = hash(contents);
161
+
162
+ for (const body of ast.body) {
163
+ if (body.type === 'ExportDefaultDeclaration') {
164
+ if (body.declaration?.type === 'CallExpression') {
165
+ const call = body.declaration as ASTCallExpression;
166
+ if (call.callee.name === 'createAgent') {
167
+ for (const callarg of call.arguments) {
168
+ const callargexp = callarg as ASTObjectExpression;
169
+ for (const prop of callargexp.properties) {
170
+ if (prop.key.type === 'Identifier' && prop.key.name === 'metadata') {
171
+ return augmentAgentMetadataNode(
172
+ id,
173
+ name,
174
+ rel,
175
+ version,
176
+ ast,
177
+ prop.value as ASTObjectExpression
178
+ );
179
+ }
180
+ }
181
+ return createAgentMetadataNode(id, name, rel, version, ast, callargexp);
182
+ }
183
+ }
184
+ }
185
+ const identifier = body.declaration as ASTNodeIdentifier;
186
+ exportName = identifier.name;
187
+ break;
188
+ }
189
+ }
190
+ if (!exportName) {
191
+ throw new Error(`could not find default export for ${filename} using ${rootDir}`);
192
+ }
193
+ for (const body of ast.body) {
194
+ if (body.type === 'VariableDeclaration') {
195
+ for (const vardecl of body.declarations) {
196
+ if (vardecl.type === 'VariableDeclarator' && vardecl.id.type === 'Identifier') {
197
+ const identifier = vardecl.id as ASTNodeIdentifier;
198
+ if (identifier.name === exportName) {
199
+ if (vardecl.init?.type === 'CallExpression') {
200
+ const call = vardecl.init as ASTCallExpression;
201
+ if (call.callee.name === 'createAgent') {
202
+ for (const callarg of call.arguments) {
203
+ const callargexp = callarg as ASTObjectExpression;
204
+ for (const prop of callargexp.properties) {
205
+ if (prop.key.type === 'Identifier' && prop.key.name === 'metadata') {
206
+ return augmentAgentMetadataNode(
207
+ id,
208
+ name,
209
+ rel,
210
+ version,
211
+ ast,
212
+ prop.value as ASTObjectExpression
213
+ );
214
+ }
215
+ }
216
+ return createAgentMetadataNode(id, name, rel, version, ast, callargexp);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ }
225
+ throw new Error(
226
+ `error parsing: ${filename}. could not find an proper createAgent defined in this file`
227
+ );
228
+ }