@agentuity/cli 1.0.40 → 1.0.42

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 (102) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +9 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/build/ast.d.ts.map +1 -1
  5. package/dist/cmd/build/ast.js +3 -3
  6. package/dist/cmd/build/ast.js.map +1 -1
  7. package/dist/cmd/build/typecheck.d.ts.map +1 -1
  8. package/dist/cmd/build/typecheck.js +52 -1
  9. package/dist/cmd/build/typecheck.js.map +1 -1
  10. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/static-renderer.js +22 -8
  12. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  13. package/dist/cmd/cloud/index.d.ts.map +1 -1
  14. package/dist/cmd/cloud/index.js +4 -0
  15. package/dist/cmd/cloud/index.js.map +1 -1
  16. package/dist/cmd/cloud/monitor.d.ts +3 -0
  17. package/dist/cmd/cloud/monitor.d.ts.map +1 -0
  18. package/dist/cmd/cloud/monitor.js +300 -0
  19. package/dist/cmd/cloud/monitor.js.map +1 -0
  20. package/dist/cmd/cloud/oidc/activity.d.ts +2 -0
  21. package/dist/cmd/cloud/oidc/activity.d.ts.map +1 -0
  22. package/dist/cmd/cloud/oidc/activity.js +54 -0
  23. package/dist/cmd/cloud/oidc/activity.js.map +1 -0
  24. package/dist/cmd/cloud/oidc/create.d.ts +2 -0
  25. package/dist/cmd/cloud/oidc/create.d.ts.map +1 -0
  26. package/dist/cmd/cloud/oidc/create.js +201 -0
  27. package/dist/cmd/cloud/oidc/create.js.map +1 -0
  28. package/dist/cmd/cloud/oidc/delete.d.ts +2 -0
  29. package/dist/cmd/cloud/oidc/delete.d.ts.map +1 -0
  30. package/dist/cmd/cloud/oidc/delete.js +56 -0
  31. package/dist/cmd/cloud/oidc/delete.js.map +1 -0
  32. package/dist/cmd/cloud/oidc/get.d.ts +2 -0
  33. package/dist/cmd/cloud/oidc/get.d.ts.map +1 -0
  34. package/dist/cmd/cloud/oidc/get.js +59 -0
  35. package/dist/cmd/cloud/oidc/get.js.map +1 -0
  36. package/dist/cmd/cloud/oidc/index.d.ts +3 -0
  37. package/dist/cmd/cloud/oidc/index.d.ts.map +1 -0
  38. package/dist/cmd/cloud/oidc/index.js +32 -0
  39. package/dist/cmd/cloud/oidc/index.js.map +1 -0
  40. package/dist/cmd/cloud/oidc/list.d.ts +2 -0
  41. package/dist/cmd/cloud/oidc/list.d.ts.map +1 -0
  42. package/dist/cmd/cloud/oidc/list.js +45 -0
  43. package/dist/cmd/cloud/oidc/list.js.map +1 -0
  44. package/dist/cmd/cloud/oidc/rotate-secret.d.ts +2 -0
  45. package/dist/cmd/cloud/oidc/rotate-secret.d.ts.map +1 -0
  46. package/dist/cmd/cloud/oidc/rotate-secret.js +63 -0
  47. package/dist/cmd/cloud/oidc/rotate-secret.js.map +1 -0
  48. package/dist/cmd/cloud/oidc/users.d.ts +2 -0
  49. package/dist/cmd/cloud/oidc/users.d.ts.map +1 -0
  50. package/dist/cmd/cloud/oidc/users.js +50 -0
  51. package/dist/cmd/cloud/oidc/users.js.map +1 -0
  52. package/dist/cmd/project/import.d.ts.map +1 -1
  53. package/dist/cmd/project/import.js +11 -1
  54. package/dist/cmd/project/import.js.map +1 -1
  55. package/dist/cmd/project/reconcile.d.ts +8 -0
  56. package/dist/cmd/project/reconcile.d.ts.map +1 -1
  57. package/dist/cmd/project/reconcile.js +150 -61
  58. package/dist/cmd/project/reconcile.js.map +1 -1
  59. package/dist/cmd/project/remote-import.d.ts +2 -0
  60. package/dist/cmd/project/remote-import.d.ts.map +1 -1
  61. package/dist/cmd/project/remote-import.js +2 -2
  62. package/dist/cmd/project/remote-import.js.map +1 -1
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +22 -6
  65. package/dist/config.js.map +1 -1
  66. package/dist/schema-parser.d.ts.map +1 -1
  67. package/dist/schema-parser.js +6 -2
  68. package/dist/schema-parser.js.map +1 -1
  69. package/dist/utils/jsonc.d.ts +13 -0
  70. package/dist/utils/jsonc.d.ts.map +1 -0
  71. package/dist/utils/jsonc.js +63 -0
  72. package/dist/utils/jsonc.js.map +1 -0
  73. package/dist/utils/route-migration.d.ts +2 -1
  74. package/dist/utils/route-migration.d.ts.map +1 -1
  75. package/dist/utils/route-migration.js +23 -32
  76. package/dist/utils/route-migration.js.map +1 -1
  77. package/dist/utils/zip.d.ts.map +1 -1
  78. package/dist/utils/zip.js +18 -2
  79. package/dist/utils/zip.js.map +1 -1
  80. package/package.json +6 -7
  81. package/src/cli.ts +9 -1
  82. package/src/cmd/build/ast.ts +6 -3
  83. package/src/cmd/build/typecheck.ts +60 -1
  84. package/src/cmd/build/vite/static-renderer.ts +24 -8
  85. package/src/cmd/cloud/index.ts +4 -0
  86. package/src/cmd/cloud/monitor.ts +375 -0
  87. package/src/cmd/cloud/oidc/activity.ts +61 -0
  88. package/src/cmd/cloud/oidc/create.ts +232 -0
  89. package/src/cmd/cloud/oidc/delete.ts +63 -0
  90. package/src/cmd/cloud/oidc/get.ts +65 -0
  91. package/src/cmd/cloud/oidc/index.ts +35 -0
  92. package/src/cmd/cloud/oidc/list.ts +50 -0
  93. package/src/cmd/cloud/oidc/rotate-secret.ts +77 -0
  94. package/src/cmd/cloud/oidc/users.ts +57 -0
  95. package/src/cmd/project/import.ts +11 -1
  96. package/src/cmd/project/reconcile.ts +147 -61
  97. package/src/cmd/project/remote-import.ts +4 -2
  98. package/src/config.ts +24 -6
  99. package/src/schema-parser.ts +8 -2
  100. package/src/utils/jsonc.ts +67 -0
  101. package/src/utils/route-migration.ts +29 -40
  102. package/src/utils/zip.ts +17 -2
@@ -0,0 +1,61 @@
1
+ import { oauthClientActivity, type APIClient } from '@agentuity/core';
2
+ import { z } from 'zod';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import * as tui from '../../../tui';
5
+ import { createSubcommand } from '../../../types';
6
+
7
+ const OAuthClientActivityResponseSchema = z.array(
8
+ z.object({
9
+ activity_date: z.string(),
10
+ total_access: z.number(),
11
+ unique_users: z.number(),
12
+ })
13
+ );
14
+
15
+ export const activitySubcommand = createSubcommand({
16
+ name: 'activity',
17
+ description: 'Show activity for an OAuth application',
18
+ tags: ['read-only', 'requires-auth'],
19
+ examples: [
20
+ { command: getCommand('cloud oidc activity <id>'), description: 'Show OAuth activity' },
21
+ {
22
+ command: getCommand('cloud oidc activity <id> --days=30'),
23
+ description: 'Show OAuth activity for last 30 days',
24
+ },
25
+ ],
26
+ requires: { auth: true, apiClient: true },
27
+ idempotent: true,
28
+ schema: {
29
+ args: z.object({
30
+ id: z.string().describe('the OAuth client id'),
31
+ }),
32
+ options: z.object({
33
+ days: z.coerce.number().int().min(1).max(365).default(7).describe('Number of days'),
34
+ }),
35
+ response: OAuthClientActivityResponseSchema,
36
+ },
37
+
38
+ async handler(ctx) {
39
+ const { args, opts, apiClient, options } = ctx;
40
+
41
+ const activity = await tui.spinner('Fetching OAuth activity', () => {
42
+ return oauthClientActivity(apiClient as APIClient, args.id, opts.days);
43
+ });
44
+
45
+ if (!options.json) {
46
+ if (activity.length === 0) {
47
+ tui.info('No OAuth activity found');
48
+ } else {
49
+ const rows = activity.map((item) => ({
50
+ activity_date: item.activity_date,
51
+ total_access: item.total_access,
52
+ unique_users: item.unique_users,
53
+ }));
54
+
55
+ tui.table(rows);
56
+ }
57
+ }
58
+
59
+ return activity;
60
+ },
61
+ });
@@ -0,0 +1,232 @@
1
+ import {
2
+ oauthClientCreate,
3
+ oauthScopes,
4
+ type APIClient,
5
+ type OAuthClientCreateRequest,
6
+ } from '@agentuity/core';
7
+ import enquirer from 'enquirer';
8
+ import { z } from 'zod';
9
+ import { getCommand } from '../../../command-prefix';
10
+ import * as tui from '../../../tui';
11
+ import { createSubcommand as createSubcommandHelper } from '../../../types';
12
+
13
+ const OAuthClientCreateResponseSchema = z.object({
14
+ client: z.object({
15
+ id: z.string(),
16
+ name: z.string(),
17
+ description: z.string(),
18
+ homepage_url: z.string(),
19
+ client_type: z.enum(['public', 'confidential']),
20
+ redirect_uris: z.array(z.string()),
21
+ scopes: z.array(z.string()),
22
+ created_at: z.string(),
23
+ updated_at: z.string(),
24
+ }),
25
+ client_secret: z.string(),
26
+ });
27
+
28
+ function parseCsv(value?: string): string[] {
29
+ if (!value) return [];
30
+ return value
31
+ .split(',')
32
+ .map((part) => part.trim())
33
+ .filter(Boolean);
34
+ }
35
+
36
+ export const createSubcommand = createSubcommandHelper({
37
+ name: 'create',
38
+ aliases: ['new'],
39
+ description: 'Create a new OAuth application',
40
+ tags: ['creates-resource', 'slow', 'requires-auth'],
41
+ examples: [
42
+ {
43
+ command: getCommand(
44
+ 'cloud oidc create --name "My App" --description "OAuth app" --homepage-url "https://example.com" --type confidential --redirect-uris "https://example.com/callback" --scopes "openid,profile,email"'
45
+ ),
46
+ description: 'Create OAuth application non-interactively',
47
+ },
48
+ {
49
+ command: getCommand('cloud oidc create'),
50
+ description: 'Create OAuth application interactively',
51
+ },
52
+ ],
53
+ requires: { auth: true, apiClient: true },
54
+ idempotent: false,
55
+ schema: {
56
+ options: z.object({
57
+ name: z.string().optional().describe('the OAuth application name'),
58
+ description: z.string().optional().describe('the OAuth application description'),
59
+ 'homepage-url': z.string().optional().describe('the homepage URL'),
60
+ type: z
61
+ .enum(['public', 'confidential'])
62
+ .optional()
63
+ .describe('OAuth client type: public or confidential'),
64
+ 'redirect-uris': z
65
+ .string()
66
+ .optional()
67
+ .describe('comma-separated redirect URIs (e.g. https://app/callback,https://app/alt)'),
68
+ scopes: z
69
+ .string()
70
+ .optional()
71
+ .describe('comma-separated OAuth scopes (e.g. openid,profile,email)'),
72
+ }),
73
+ response: OAuthClientCreateResponseSchema,
74
+ },
75
+
76
+ async handler(ctx) {
77
+ const { opts, apiClient, options } = ctx;
78
+
79
+ const availableScopes = await tui.spinner('Fetching available OAuth scopes', () => {
80
+ return oauthScopes(apiClient as APIClient);
81
+ });
82
+
83
+ const nonInteractive = !process.stdin.isTTY || !process.stdout.isTTY;
84
+
85
+ let name = opts?.name?.trim() || '';
86
+ let description = opts?.description?.trim() || '';
87
+ let homepageUrl = opts?.['homepage-url']?.trim() || '';
88
+ let clientType = opts?.type;
89
+ let redirectUris = parseCsv(opts?.['redirect-uris']);
90
+ let scopes = parseCsv(opts?.scopes);
91
+
92
+ if (!name) {
93
+ if (nonInteractive) {
94
+ tui.fatal('--name is required in non-interactive mode');
95
+ }
96
+ const answer = await enquirer.prompt<{ name: string }>({
97
+ type: 'input',
98
+ name: 'name',
99
+ message: 'Application name:',
100
+ });
101
+ name = answer.name?.trim() || '';
102
+ }
103
+
104
+ if (!description && !nonInteractive) {
105
+ const answer = await enquirer.prompt<{ description: string }>({
106
+ type: 'input',
107
+ name: 'description',
108
+ message: 'Description:',
109
+ });
110
+ description = answer.description?.trim() || '';
111
+ }
112
+
113
+ if (!homepageUrl) {
114
+ if (nonInteractive) {
115
+ tui.fatal('--homepage-url is required in non-interactive mode');
116
+ }
117
+ const answer = await enquirer.prompt<{ homepageUrl: string }>({
118
+ type: 'input',
119
+ name: 'homepageUrl',
120
+ message: 'Homepage URL:',
121
+ });
122
+ homepageUrl = answer.homepageUrl?.trim() || '';
123
+ }
124
+
125
+ if (!clientType) {
126
+ if (nonInteractive) {
127
+ tui.fatal('--type is required in non-interactive mode');
128
+ }
129
+ const answer = await enquirer.prompt<{ clientType: 'public' | 'confidential' }>({
130
+ type: 'select',
131
+ name: 'clientType',
132
+ message: 'Client type:',
133
+ choices: [
134
+ { name: 'public', message: 'public' },
135
+ { name: 'confidential', message: 'confidential' },
136
+ ],
137
+ });
138
+ clientType = answer.clientType;
139
+ }
140
+
141
+ if (redirectUris.length === 0) {
142
+ if (nonInteractive) {
143
+ tui.fatal('--redirect-uris is required in non-interactive mode');
144
+ }
145
+ const answer = await enquirer.prompt<{ redirectUris: string }>({
146
+ type: 'input',
147
+ name: 'redirectUris',
148
+ message: 'Redirect URIs (comma-separated):',
149
+ });
150
+ redirectUris = parseCsv(answer.redirectUris);
151
+ }
152
+
153
+ if (scopes.length === 0) {
154
+ if (nonInteractive) {
155
+ tui.fatal('--scopes is required in non-interactive mode');
156
+ }
157
+
158
+ const choices = availableScopes.scopes.map((scope) => ({
159
+ name: scope.name,
160
+ message: `${scope.name} — ${scope.description}`,
161
+ }));
162
+
163
+ const answer = await enquirer.prompt<{ scopes: string[] }>({
164
+ type: 'multiselect',
165
+ name: 'scopes',
166
+ message: 'Select OAuth scopes:',
167
+ choices,
168
+ });
169
+ scopes = answer.scopes;
170
+ }
171
+
172
+ if (!name) {
173
+ tui.fatal('Name is required');
174
+ }
175
+ if (!homepageUrl) {
176
+ tui.fatal('Homepage URL is required');
177
+ }
178
+ if (!clientType) {
179
+ tui.fatal('Client type is required');
180
+ }
181
+ if (redirectUris.length === 0) {
182
+ tui.fatal('At least one redirect URI is required');
183
+ }
184
+ if (scopes.length === 0) {
185
+ tui.fatal('At least one scope is required');
186
+ }
187
+
188
+ const availableScopeNames = new Set(availableScopes.scopes.map((scope) => scope.name));
189
+ const invalidScopes = scopes.filter((scope) => !availableScopeNames.has(scope));
190
+ if (invalidScopes.length > 0) {
191
+ tui.fatal(`Invalid scopes: ${invalidScopes.join(', ')}`);
192
+ }
193
+
194
+ const request: OAuthClientCreateRequest = {
195
+ name,
196
+ description,
197
+ homepage_url: homepageUrl,
198
+ client_type: clientType,
199
+ redirect_uris: redirectUris,
200
+ scopes,
201
+ };
202
+
203
+ const result = await tui.spinner('Creating OAuth application', () => {
204
+ return oauthClientCreate(apiClient as APIClient, request);
205
+ });
206
+
207
+ if (!options.json) {
208
+ tui.newline();
209
+ tui.success('OAuth application created successfully!');
210
+ tui.newline();
211
+ tui.warning('Copy the client secret now. It will only be shown once.');
212
+ tui.newline();
213
+
214
+ tui.table(
215
+ [
216
+ {
217
+ ID: result.client.id,
218
+ Name: result.client.name,
219
+ Type: result.client.client_type,
220
+ 'Client Secret': result.client_secret,
221
+ 'Redirect URIs': result.client.redirect_uris.join(', '),
222
+ Scopes: result.client.scopes.join(', '),
223
+ },
224
+ ],
225
+ undefined,
226
+ { layout: 'vertical' }
227
+ );
228
+ }
229
+
230
+ return result;
231
+ },
232
+ });
@@ -0,0 +1,63 @@
1
+ import { oauthClientDelete, type APIClient } from '@agentuity/core';
2
+ import { z } from 'zod';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import { ErrorCode } from '../../../errors';
5
+ import * as tui from '../../../tui';
6
+ import { createSubcommand } from '../../../types';
7
+
8
+ const OAuthClientDeleteResponseSchema = z.object({
9
+ success: z.boolean().describe('Whether the operation succeeded'),
10
+ id: z.string().describe('OAuth client id that was deleted'),
11
+ });
12
+
13
+ export const deleteSubcommand = createSubcommand({
14
+ name: 'delete',
15
+ aliases: ['del', 'rm'],
16
+ description: 'Delete an OAuth application',
17
+ tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth'],
18
+ idempotent: true,
19
+ examples: [
20
+ { command: getCommand('cloud oidc delete <id>'), description: 'Delete OAuth application' },
21
+ {
22
+ command: getCommand('cloud oidc delete <id> --force'),
23
+ description: 'Delete OAuth application without confirmation',
24
+ },
25
+ ],
26
+ requires: { auth: true, apiClient: true },
27
+ schema: {
28
+ args: z.object({
29
+ id: z.string().describe('the OAuth client id to delete'),
30
+ }),
31
+ options: z.object({
32
+ force: z.boolean().optional().default(false).describe('Skip confirmation prompt'),
33
+ yes: z.boolean().optional().default(false).describe('Skip confirmation prompt'),
34
+ }),
35
+ response: OAuthClientDeleteResponseSchema,
36
+ },
37
+
38
+ async handler(ctx) {
39
+ const { args, opts, apiClient, options } = ctx;
40
+
41
+ const skipConfirm = opts.force || opts.yes;
42
+
43
+ if (!skipConfirm) {
44
+ const confirmed = await tui.confirm(`Delete OAuth application "${args.id}"?`, false);
45
+ if (!confirmed) {
46
+ tui.fatal('Operation cancelled', ErrorCode.USER_CANCELLED);
47
+ }
48
+ }
49
+
50
+ await tui.spinner('Deleting OAuth application', () => {
51
+ return oauthClientDelete(apiClient as APIClient, args.id);
52
+ });
53
+
54
+ if (!options.json) {
55
+ tui.success(`OAuth application '${args.id}' deleted successfully`);
56
+ }
57
+
58
+ return {
59
+ success: true,
60
+ id: args.id,
61
+ };
62
+ },
63
+ });
@@ -0,0 +1,65 @@
1
+ import { oauthClientGet, type APIClient } from '@agentuity/core';
2
+ import { z } from 'zod';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import { ErrorCode } from '../../../errors';
5
+ import * as tui from '../../../tui';
6
+ import { createSubcommand } from '../../../types';
7
+
8
+ export const getSubcommand = createSubcommand({
9
+ name: 'get',
10
+ description: 'Get a specific OAuth application',
11
+ tags: ['read-only', 'fast', 'requires-auth'],
12
+ examples: [
13
+ { command: getCommand('cloud oidc get <id>'), description: 'Get OAuth application details' },
14
+ ],
15
+ requires: { auth: true, apiClient: true },
16
+ idempotent: true,
17
+ schema: {
18
+ args: z.object({
19
+ id: z.string().describe('the OAuth client id'),
20
+ }),
21
+ },
22
+
23
+ async handler(ctx) {
24
+ const { args, apiClient, options } = ctx;
25
+
26
+ let client: Awaited<ReturnType<typeof oauthClientGet>>;
27
+ try {
28
+ client = await tui.spinner('Fetching OAuth application', () => {
29
+ return oauthClientGet(apiClient as APIClient, args.id);
30
+ });
31
+ } catch (error) {
32
+ if (error instanceof Error && error.message.includes('not found')) {
33
+ tui.fatal(`OAuth application '${args.id}' not found`, ErrorCode.RESOURCE_NOT_FOUND);
34
+ }
35
+ throw error;
36
+ }
37
+
38
+ if (!options.json) {
39
+ if (process.stdout.isTTY) {
40
+ tui.newline();
41
+ tui.success('OAuth Application Details:');
42
+ tui.newline();
43
+ }
44
+
45
+ const rows = [
46
+ {
47
+ ID: client.id,
48
+ Name: client.name,
49
+ Description: client.description || '-',
50
+ Type: client.client_type,
51
+ 'Homepage URL': client.homepage_url || '-',
52
+ 'Redirect URIs':
53
+ client.redirect_uris.length > 0 ? client.redirect_uris.join('\n') : '-',
54
+ Scopes: client.scopes.length > 0 ? client.scopes.join(', ') : '-',
55
+ Created: new Date(client.created_at).toLocaleString(),
56
+ Updated: new Date(client.updated_at).toLocaleString(),
57
+ },
58
+ ];
59
+
60
+ tui.table(rows, undefined, { layout: 'vertical' });
61
+ }
62
+
63
+ return client;
64
+ },
65
+ });
@@ -0,0 +1,35 @@
1
+ import { createCommand } from '../../../types';
2
+ import { getCommand } from '../../../command-prefix';
3
+ import { listSubcommand } from './list';
4
+ import { getSubcommand } from './get';
5
+ import { createSubcommand } from './create';
6
+ import { deleteSubcommand } from './delete';
7
+ import { rotateSecretSubcommand } from './rotate-secret';
8
+ import { activitySubcommand } from './activity';
9
+ import { usersSubcommand } from './users';
10
+
11
+ export const command = createCommand({
12
+ name: 'oidc',
13
+ description: 'Manage OAuth applications',
14
+ tags: ['fast', 'requires-auth'],
15
+ examples: [
16
+ { command: getCommand('cloud oidc list'), description: 'List all OAuth applications' },
17
+ {
18
+ command: getCommand(
19
+ 'cloud oidc create --name "My App" --type confidential --redirect-uris "https://example.com/callback"'
20
+ ),
21
+ description: 'Create a new OAuth application',
22
+ },
23
+ ],
24
+ subcommands: [
25
+ createSubcommand,
26
+ listSubcommand,
27
+ getSubcommand,
28
+ deleteSubcommand,
29
+ rotateSecretSubcommand,
30
+ activitySubcommand,
31
+ usersSubcommand,
32
+ ],
33
+ });
34
+
35
+ export default command;
@@ -0,0 +1,50 @@
1
+ import { oauthClientList, type APIClient } from '@agentuity/core';
2
+ import { getCommand } from '../../../command-prefix';
3
+ import * as tui from '../../../tui';
4
+ import { createSubcommand } from '../../../types';
5
+
6
+ export const listSubcommand = createSubcommand({
7
+ name: 'list',
8
+ aliases: ['ls'],
9
+ description: 'List all OAuth applications',
10
+ tags: ['read-only', 'fast', 'requires-auth'],
11
+ examples: [
12
+ { command: getCommand('cloud oidc list'), description: 'List OAuth applications' },
13
+ { command: getCommand('cloud oidc ls'), description: 'List OAuth applications' },
14
+ ],
15
+ requires: { auth: true, apiClient: true },
16
+ idempotent: true,
17
+
18
+ async handler(ctx) {
19
+ const { apiClient, options } = ctx;
20
+
21
+ const clients = await tui.spinner('Fetching OAuth applications', () => {
22
+ return oauthClientList(apiClient as APIClient);
23
+ });
24
+
25
+ if (!options.json) {
26
+ if (clients.length === 0) {
27
+ tui.info('No OAuth applications found');
28
+ } else {
29
+ if (process.stdout.isTTY) {
30
+ tui.newline();
31
+ tui.success(`OAuth Applications (${clients.length}):`);
32
+ tui.newline();
33
+ }
34
+
35
+ const rows = clients.map((client) => ({
36
+ ID: client.id,
37
+ Name: client.name,
38
+ Type: client.client_type,
39
+ Scopes: client.scopes.length,
40
+ Users: client.user_count,
41
+ Created: new Date(client.created_at).toLocaleString(),
42
+ }));
43
+
44
+ tui.table(rows);
45
+ }
46
+ }
47
+
48
+ return clients;
49
+ },
50
+ });
@@ -0,0 +1,77 @@
1
+ import { oauthClientRotateSecret, type APIClient } from '@agentuity/core';
2
+ import { z } from 'zod';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import { ErrorCode } from '../../../errors';
5
+ import * as tui from '../../../tui';
6
+ import { createSubcommand } from '../../../types';
7
+
8
+ const OAuthClientRotateSecretResponseSchema = z.object({
9
+ client_id: z.string(),
10
+ client_secret: z.string(),
11
+ });
12
+
13
+ export const rotateSecretSubcommand = createSubcommand({
14
+ name: 'rotate-secret',
15
+ description: 'Rotate the client secret for an OAuth application',
16
+ tags: ['destructive', 'requires-auth'],
17
+ examples: [
18
+ {
19
+ command: getCommand('cloud oidc rotate-secret <id>'),
20
+ description: 'Rotate OAuth client secret',
21
+ },
22
+ {
23
+ command: getCommand('cloud oidc rotate-secret <id> --force'),
24
+ description: 'Rotate OAuth client secret without confirmation',
25
+ },
26
+ ],
27
+ requires: { auth: true, apiClient: true },
28
+ idempotent: false,
29
+ schema: {
30
+ args: z.object({
31
+ id: z.string().describe('the OAuth client id'),
32
+ }),
33
+ options: z.object({
34
+ force: z.boolean().optional().default(false).describe('Skip confirmation prompt'),
35
+ }),
36
+ response: OAuthClientRotateSecretResponseSchema,
37
+ },
38
+
39
+ async handler(ctx) {
40
+ const { args, opts, apiClient, options } = ctx;
41
+
42
+ if (!opts.force) {
43
+ const confirmed = await tui.confirm(
44
+ `Rotate secret for OAuth application "${args.id}"?`,
45
+ false
46
+ );
47
+ if (!confirmed) {
48
+ tui.fatal('Operation cancelled', ErrorCode.USER_CANCELLED);
49
+ }
50
+ }
51
+
52
+ const result = await tui.spinner('Rotating OAuth client secret', () => {
53
+ return oauthClientRotateSecret(apiClient as APIClient, args.id);
54
+ });
55
+
56
+ if (!options.json) {
57
+ tui.newline();
58
+ tui.success('OAuth client secret rotated successfully!');
59
+ tui.newline();
60
+ tui.warning('Copy the new client secret now. It will only be shown once.');
61
+ tui.newline();
62
+
63
+ tui.table(
64
+ [
65
+ {
66
+ 'Client ID': result.client_id,
67
+ 'Client Secret': result.client_secret,
68
+ },
69
+ ],
70
+ undefined,
71
+ { layout: 'vertical' }
72
+ );
73
+ }
74
+
75
+ return result;
76
+ },
77
+ });
@@ -0,0 +1,57 @@
1
+ import { oauthClientUsers, type APIClient } from '@agentuity/core';
2
+ import { z } from 'zod';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import * as tui from '../../../tui';
5
+ import { createSubcommand } from '../../../types';
6
+
7
+ const OAuthClientUsersResponseSchema = z.array(
8
+ z.object({
9
+ user_id: z.string(),
10
+ scopes: z.array(z.string()),
11
+ created_at: z.string(),
12
+ })
13
+ );
14
+
15
+ export const usersSubcommand = createSubcommand({
16
+ name: 'users',
17
+ description: 'List connected users for an OAuth application',
18
+ tags: ['read-only', 'requires-auth'],
19
+ examples: [
20
+ {
21
+ command: getCommand('cloud oidc users <id>'),
22
+ description: 'List connected users for OAuth application',
23
+ },
24
+ ],
25
+ requires: { auth: true, apiClient: true },
26
+ idempotent: true,
27
+ schema: {
28
+ args: z.object({
29
+ id: z.string().describe('the OAuth client id'),
30
+ }),
31
+ response: OAuthClientUsersResponseSchema,
32
+ },
33
+
34
+ async handler(ctx) {
35
+ const { args, apiClient, options } = ctx;
36
+
37
+ const users = await tui.spinner('Fetching connected OAuth users', () => {
38
+ return oauthClientUsers(apiClient as APIClient, args.id);
39
+ });
40
+
41
+ if (!options.json) {
42
+ if (users.length === 0) {
43
+ tui.info('No connected users found');
44
+ } else {
45
+ const rows = users.map((user) => ({
46
+ user_id: user.user_id,
47
+ scopes: user.scopes.join(', '),
48
+ connected_at: new Date(user.created_at).toLocaleString(),
49
+ }));
50
+
51
+ tui.table(rows);
52
+ }
53
+ }
54
+
55
+ return users;
56
+ },
57
+ });
@@ -46,6 +46,10 @@ export const importSubcommand = createSubcommand({
46
46
  ),
47
47
  description: 'Import with resource provisioning and push to new repo',
48
48
  },
49
+ {
50
+ command: getCommand('project import --name my-agent --confirm'),
51
+ description: 'Import project non-interactively, skipping prompts',
52
+ },
49
53
  ],
50
54
  requires: { auth: true, apiClient: true },
51
55
  optional: { region: true, org: true },
@@ -71,6 +75,7 @@ export const importSubcommand = createSubcommand({
71
75
  .optional()
72
76
  .describe('Target GitHub repo (owner/repo) to push imported code to'),
73
77
  name: z.string().optional().describe('Project name (for non-interactive mode)'),
78
+ confirm: z.boolean().optional().describe('Skip confirmation prompts'),
74
79
  env: z
75
80
  .array(z.string())
76
81
  .optional()
@@ -100,6 +105,7 @@ export const importSubcommand = createSubcommand({
100
105
  env: opts.env,
101
106
  org: orgId,
102
107
  region,
108
+ confirm: opts.confirm,
103
109
  apiClient,
104
110
  auth,
105
111
  config,
@@ -123,8 +129,12 @@ export const importSubcommand = createSubcommand({
123
129
  apiClient,
124
130
  config,
125
131
  logger,
126
- interactive: validateOnly ? false : isTTY(),
132
+ interactive: validateOnly ? false : opts.confirm ? false : isTTY(),
127
133
  validateOnly,
134
+ confirm: opts.confirm === true,
135
+ orgId,
136
+ region,
137
+ name: opts.name,
128
138
  });
129
139
 
130
140
  if (result.status === 'error') {