@agentuity/cli 0.0.42 → 0.0.44

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 (249) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/cli.ts +7 -5
  4. package/dist/api.d.ts +3 -3
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/auth.d.ts +10 -2
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/banner.d.ts.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cmd/auth/api.d.ts +4 -4
  11. package/dist/cmd/auth/api.d.ts.map +1 -1
  12. package/dist/cmd/auth/index.d.ts.map +1 -1
  13. package/dist/cmd/auth/login.d.ts.map +1 -1
  14. package/dist/cmd/auth/signup.d.ts.map +1 -1
  15. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  16. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  17. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  18. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  20. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  21. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  22. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  23. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  24. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  25. package/dist/cmd/auth/whoami.d.ts +2 -0
  26. package/dist/cmd/auth/whoami.d.ts.map +1 -0
  27. package/dist/cmd/bundle/ast.d.ts +14 -3
  28. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  29. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  30. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  31. package/dist/cmd/bundle/bundler.d.ts +6 -1
  32. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  33. package/dist/cmd/bundle/file.d.ts.map +1 -1
  34. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  36. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  37. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  38. package/dist/cmd/bundle/index.d.ts +1 -1
  39. package/dist/cmd/bundle/index.d.ts.map +1 -1
  40. package/dist/cmd/bundle/plugin.d.ts +2 -0
  41. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  42. package/dist/cmd/cloud/deploy.d.ts.map +1 -0
  43. package/dist/cmd/cloud/domain.d.ts +17 -0
  44. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  45. package/dist/cmd/cloud/index.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  47. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  50. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  51. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  52. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  53. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  56. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  57. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  58. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  59. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  60. package/dist/cmd/cloud/ssh.d.ts +2 -0
  61. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  62. package/dist/cmd/dev/api.d.ts +18 -0
  63. package/dist/cmd/dev/api.d.ts.map +1 -0
  64. package/dist/cmd/dev/download.d.ts +11 -0
  65. package/dist/cmd/dev/download.d.ts.map +1 -0
  66. package/dist/cmd/dev/index.d.ts.map +1 -1
  67. package/dist/cmd/dev/templates.d.ts +3 -0
  68. package/dist/cmd/dev/templates.d.ts.map +1 -0
  69. package/dist/cmd/env/delete.d.ts +2 -0
  70. package/dist/cmd/env/delete.d.ts.map +1 -0
  71. package/dist/cmd/env/get.d.ts +2 -0
  72. package/dist/cmd/env/get.d.ts.map +1 -0
  73. package/dist/cmd/env/import.d.ts +2 -0
  74. package/dist/cmd/env/import.d.ts.map +1 -0
  75. package/dist/cmd/env/index.d.ts +2 -0
  76. package/dist/cmd/env/index.d.ts.map +1 -0
  77. package/dist/cmd/env/list.d.ts.map +1 -0
  78. package/dist/cmd/env/pull.d.ts +2 -0
  79. package/dist/cmd/env/pull.d.ts.map +1 -0
  80. package/dist/cmd/env/push.d.ts +2 -0
  81. package/dist/cmd/env/push.d.ts.map +1 -0
  82. package/dist/cmd/env/set.d.ts +2 -0
  83. package/dist/cmd/env/set.d.ts.map +1 -0
  84. package/dist/cmd/profile/show.d.ts.map +1 -1
  85. package/dist/cmd/project/create.d.ts.map +1 -1
  86. package/dist/cmd/project/delete.d.ts.map +1 -1
  87. package/dist/cmd/project/download.d.ts +1 -1
  88. package/dist/cmd/project/download.d.ts.map +1 -1
  89. package/dist/cmd/project/list.d.ts.map +1 -1
  90. package/dist/cmd/project/show.d.ts.map +1 -1
  91. package/dist/cmd/project/template-flow.d.ts +5 -1
  92. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  93. package/dist/cmd/secret/delete.d.ts +2 -0
  94. package/dist/cmd/secret/delete.d.ts.map +1 -0
  95. package/dist/cmd/secret/get.d.ts +2 -0
  96. package/dist/cmd/secret/get.d.ts.map +1 -0
  97. package/dist/cmd/secret/import.d.ts +2 -0
  98. package/dist/cmd/secret/import.d.ts.map +1 -0
  99. package/dist/cmd/secret/index.d.ts +2 -0
  100. package/dist/cmd/secret/index.d.ts.map +1 -0
  101. package/dist/cmd/secret/list.d.ts +2 -0
  102. package/dist/cmd/secret/list.d.ts.map +1 -0
  103. package/dist/cmd/secret/pull.d.ts +2 -0
  104. package/dist/cmd/secret/pull.d.ts.map +1 -0
  105. package/dist/cmd/secret/push.d.ts +2 -0
  106. package/dist/cmd/secret/push.d.ts.map +1 -0
  107. package/dist/cmd/secret/set.d.ts +2 -0
  108. package/dist/cmd/secret/set.d.ts.map +1 -0
  109. package/dist/cmd/version/index.d.ts.map +1 -1
  110. package/dist/config.d.ts +11 -3
  111. package/dist/config.d.ts.map +1 -1
  112. package/dist/crypto/box.d.ts +65 -0
  113. package/dist/crypto/box.d.ts.map +1 -0
  114. package/dist/crypto/box.test.d.ts +2 -0
  115. package/dist/crypto/box.test.d.ts.map +1 -0
  116. package/dist/download.d.ts.map +1 -1
  117. package/dist/env-util.d.ts +67 -0
  118. package/dist/env-util.d.ts.map +1 -0
  119. package/dist/env-util.test.d.ts +2 -0
  120. package/dist/env-util.test.d.ts.map +1 -0
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/schema-parser.d.ts.map +1 -1
  124. package/dist/steps.d.ts +4 -1
  125. package/dist/steps.d.ts.map +1 -1
  126. package/dist/terminal.d.ts.map +1 -1
  127. package/dist/tui.d.ts +32 -2
  128. package/dist/tui.d.ts.map +1 -1
  129. package/dist/types.d.ts +250 -127
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/utils/detectSubagent.d.ts +15 -0
  132. package/dist/utils/detectSubagent.d.ts.map +1 -0
  133. package/dist/utils/zip.d.ts +7 -0
  134. package/dist/utils/zip.d.ts.map +1 -0
  135. package/package.json +11 -3
  136. package/src/api-errors.md +2 -2
  137. package/src/api.ts +12 -7
  138. package/src/auth.ts +116 -7
  139. package/src/banner.ts +13 -6
  140. package/src/cli.ts +709 -36
  141. package/src/cmd/auth/api.ts +10 -16
  142. package/src/cmd/auth/index.ts +3 -1
  143. package/src/cmd/auth/login.ts +24 -8
  144. package/src/cmd/auth/signup.ts +15 -11
  145. package/src/cmd/auth/ssh/add.ts +263 -0
  146. package/src/cmd/auth/ssh/api.ts +94 -0
  147. package/src/cmd/auth/ssh/delete.ts +102 -0
  148. package/src/cmd/auth/ssh/index.ts +10 -0
  149. package/src/cmd/auth/ssh/list.ts +74 -0
  150. package/src/cmd/auth/whoami.ts +69 -0
  151. package/src/cmd/bundle/ast.test.ts +565 -0
  152. package/src/cmd/bundle/ast.ts +457 -44
  153. package/src/cmd/bundle/bundler.ts +255 -57
  154. package/src/cmd/bundle/file.ts +6 -12
  155. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  156. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  157. package/src/cmd/bundle/index.ts +11 -11
  158. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  159. package/src/cmd/bundle/plugin.ts +373 -53
  160. package/src/cmd/cloud/deploy.ts +336 -0
  161. package/src/cmd/cloud/domain.ts +92 -0
  162. package/src/cmd/cloud/index.ts +11 -0
  163. package/src/cmd/cloud/resource/add.ts +56 -0
  164. package/src/cmd/cloud/resource/delete.ts +120 -0
  165. package/src/cmd/cloud/resource/index.ts +11 -0
  166. package/src/cmd/cloud/resource/list.ts +69 -0
  167. package/src/cmd/cloud/scp/download.ts +59 -0
  168. package/src/cmd/cloud/scp/index.ts +9 -0
  169. package/src/cmd/cloud/scp/upload.ts +62 -0
  170. package/src/cmd/cloud/ssh.ts +68 -0
  171. package/src/cmd/dev/api.ts +46 -0
  172. package/src/cmd/dev/download.ts +111 -0
  173. package/src/cmd/dev/index.ts +362 -34
  174. package/src/cmd/dev/templates.ts +84 -0
  175. package/src/cmd/env/delete.ts +47 -0
  176. package/src/cmd/env/get.ts +53 -0
  177. package/src/cmd/env/import.ts +102 -0
  178. package/src/cmd/env/index.ts +22 -0
  179. package/src/cmd/env/list.ts +56 -0
  180. package/src/cmd/env/pull.ts +80 -0
  181. package/src/cmd/env/push.ts +37 -0
  182. package/src/cmd/env/set.ts +71 -0
  183. package/src/cmd/index.ts +2 -2
  184. package/src/cmd/profile/show.ts +15 -6
  185. package/src/cmd/project/create.ts +7 -2
  186. package/src/cmd/project/delete.ts +75 -18
  187. package/src/cmd/project/download.ts +3 -3
  188. package/src/cmd/project/list.ts +8 -8
  189. package/src/cmd/project/show.ts +3 -7
  190. package/src/cmd/project/template-flow.ts +186 -48
  191. package/src/cmd/secret/delete.ts +40 -0
  192. package/src/cmd/secret/get.ts +54 -0
  193. package/src/cmd/secret/import.ts +64 -0
  194. package/src/cmd/secret/index.ts +22 -0
  195. package/src/cmd/secret/list.ts +56 -0
  196. package/src/cmd/secret/pull.ts +78 -0
  197. package/src/cmd/secret/push.ts +37 -0
  198. package/src/cmd/secret/set.ts +45 -0
  199. package/src/cmd/version/index.ts +2 -1
  200. package/src/config.ts +257 -27
  201. package/src/crypto/box.test.ts +431 -0
  202. package/src/crypto/box.ts +477 -0
  203. package/src/download.ts +1 -0
  204. package/src/env-util.test.ts +194 -0
  205. package/src/env-util.ts +290 -0
  206. package/src/index.ts +5 -1
  207. package/src/schema-parser.ts +2 -3
  208. package/src/steps.ts +144 -10
  209. package/src/terminal.ts +24 -23
  210. package/src/tui.ts +208 -68
  211. package/src/types.ts +292 -202
  212. package/src/utils/detectSubagent.ts +31 -0
  213. package/src/utils/zip.ts +38 -0
  214. package/dist/cmd/example/create-user.d.ts +0 -2
  215. package/dist/cmd/example/create-user.d.ts.map +0 -1
  216. package/dist/cmd/example/create.d.ts +0 -2
  217. package/dist/cmd/example/create.d.ts.map +0 -1
  218. package/dist/cmd/example/deploy.d.ts.map +0 -1
  219. package/dist/cmd/example/index.d.ts.map +0 -1
  220. package/dist/cmd/example/list.d.ts.map +0 -1
  221. package/dist/cmd/example/optional-auth.d.ts +0 -3
  222. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  223. package/dist/cmd/example/run-command.d.ts +0 -2
  224. package/dist/cmd/example/run-command.d.ts.map +0 -1
  225. package/dist/cmd/example/sound.d.ts +0 -3
  226. package/dist/cmd/example/sound.d.ts.map +0 -1
  227. package/dist/cmd/example/spinner.d.ts +0 -2
  228. package/dist/cmd/example/spinner.d.ts.map +0 -1
  229. package/dist/cmd/example/steps.d.ts +0 -2
  230. package/dist/cmd/example/steps.d.ts.map +0 -1
  231. package/dist/cmd/example/version.d.ts +0 -2
  232. package/dist/cmd/example/version.d.ts.map +0 -1
  233. package/dist/logger.d.ts +0 -24
  234. package/dist/logger.d.ts.map +0 -1
  235. package/src/cmd/example/create-user.ts +0 -38
  236. package/src/cmd/example/create.ts +0 -31
  237. package/src/cmd/example/deploy.ts +0 -36
  238. package/src/cmd/example/index.ts +0 -29
  239. package/src/cmd/example/list.ts +0 -32
  240. package/src/cmd/example/optional-auth.ts +0 -38
  241. package/src/cmd/example/run-command.ts +0 -45
  242. package/src/cmd/example/sound.ts +0 -14
  243. package/src/cmd/example/spinner.ts +0 -44
  244. package/src/cmd/example/steps.ts +0 -66
  245. package/src/cmd/example/version.ts +0 -13
  246. package/src/logger.ts +0 -235
  247. /package/dist/cmd/{example → cloud}/deploy.d.ts +0 -0
  248. /package/dist/cmd/{example → cloud}/index.d.ts +0 -0
  249. /package/dist/cmd/{example → env}/list.d.ts +0 -0
package/src/cli.ts CHANGED
@@ -1,8 +1,116 @@
1
1
  import { Command } from 'commander';
2
- import type { CommandDefinition, SubcommandDefinition, CommandContext } from './types';
2
+ import type {
3
+ CommandDefinition,
4
+ SubcommandDefinition,
5
+ CommandContext,
6
+ ProjectConfig,
7
+ Config,
8
+ Requires,
9
+ Optional,
10
+ Logger,
11
+ AuthData,
12
+ } from './types';
3
13
  import { showBanner } from './banner';
4
- import { requireAuth, optionalAuth } from './auth';
14
+ import { requireAuth, optionalAuth, requireOrg, optionalOrg as selectOptionalOrg } from './auth';
15
+ import { listRegions, type RegionList } from '@agentuity/server';
16
+ import enquirer from 'enquirer';
17
+ import * as tui from './tui';
5
18
  import { parseArgsSchema, parseOptionsSchema, buildValidationInput } from './schema-parser';
19
+ import { defaultProfileName, loadProjectConfig } from './config';
20
+ import { APIClient, getAPIBaseURL, type APIClient as APIClientType } from './api';
21
+
22
+ function createAPIClient(baseCtx: CommandContext, config: Config | null): APIClient {
23
+ try {
24
+ const apiUrl = getAPIBaseURL(config);
25
+ const apiClient = new APIClient(apiUrl, baseCtx.logger, config);
26
+
27
+ if (!apiClient) {
28
+ throw new Error('APIClient constructor returned null/undefined');
29
+ }
30
+
31
+ if (typeof apiClient.request !== 'function') {
32
+ throw new Error('APIClient instance is missing request method');
33
+ }
34
+
35
+ return apiClient;
36
+ } catch (error) {
37
+ baseCtx.logger.error('Failed to create API client:', error);
38
+ throw new Error(
39
+ `API client initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`
40
+ );
41
+ }
42
+ }
43
+
44
+ type Normalized = {
45
+ requiresAuth: boolean;
46
+ optionalAuth: false | string;
47
+ requiresProject: boolean;
48
+ optionalProject: boolean;
49
+ requiresAPIClient: boolean;
50
+ requiresOrg: boolean;
51
+ optionalOrg: boolean;
52
+ requiresRegions: boolean;
53
+ requiresRegion: boolean;
54
+ optionalRegion: boolean;
55
+ };
56
+
57
+ function normalizeReqs(def: CommandDefinition | SubcommandDefinition): Normalized {
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ const d: any = def as any;
60
+ const requires = d.requires as Requires | undefined;
61
+ const optional = d.optional as Optional | undefined;
62
+
63
+ const requiresAuth = requires?.auth === true;
64
+ const optionalAuthValue = optional?.auth;
65
+ const optionalAuth: false | string =
66
+ optionalAuthValue === true ? 'Continue without authentication' : optionalAuthValue || false;
67
+
68
+ const requiresProject = requires?.project === true;
69
+ const optionalProject = optional?.project === true;
70
+
71
+ const requiresOrg = requires?.org === true;
72
+ const optionalOrg = optional?.org === true;
73
+ const requiresRegions = requires?.regions === true;
74
+ const requiresRegion = requires?.region === true;
75
+ const optionalRegion = optional?.region === true;
76
+
77
+ // Implicitly require apiClient if org or region is required or optional
78
+ const requiresAPIClient =
79
+ requires?.apiClient === true ||
80
+ requiresOrg ||
81
+ optionalOrg ||
82
+ requiresRegion ||
83
+ optionalRegion ||
84
+ requiresRegions;
85
+
86
+ return {
87
+ requiresAuth,
88
+ optionalAuth,
89
+ requiresProject,
90
+ optionalProject,
91
+ requiresAPIClient,
92
+ requiresOrg,
93
+ optionalOrg,
94
+ requiresRegions,
95
+ requiresRegion,
96
+ optionalRegion,
97
+ };
98
+ }
99
+
100
+ function handleProjectConfigError(error: unknown, requiresProject: boolean, logger: Logger): never {
101
+ if (
102
+ requiresProject &&
103
+ error &&
104
+ typeof error === 'object' &&
105
+ 'name' in error &&
106
+ error.name === 'ProjectConfigNotFoundExpection'
107
+ ) {
108
+ logger.fatal(
109
+ 'invalid project folder. use --dir to specify a different directory or change to a project folder'
110
+ );
111
+ }
112
+ throw error;
113
+ }
6
114
 
7
115
  export async function createCLI(version: string): Promise<Command> {
8
116
  const program = new Command();
@@ -11,13 +119,20 @@ export async function createCLI(version: string): Promise<Command> {
11
119
  .name('agentuity')
12
120
  .description('Agentuity CLI')
13
121
  .version(version, '-V, --version', 'Display version')
14
- .helpOption('-h, --help', 'Display help');
122
+ .helpOption('-h, --help', 'Display help')
123
+ .allowUnknownOption(false)
124
+ .allowExcessArguments(false);
15
125
 
16
126
  program
17
- .option('--config <path>', 'Config file path', '~/.config/agentuity/production.yaml')
127
+ .option('--config <path>', 'Config file path')
18
128
  .option('--log-level <level>', 'Log level', process.env.AGENTUITY_LOG_LEVEL ?? 'info')
19
129
  .option('--log-timestamp', 'Show timestamps in log output', false)
20
- .option('--no-log-prefix', 'Hide log level prefixes', false)
130
+ .option('--no-log-prefix', 'Hide log level prefixes', true)
131
+ .option(
132
+ '--org-id <id>',
133
+ 'Use a specific organization when performing operations',
134
+ process.env.AGENTUITY_CLOUD_ORG_ID
135
+ )
21
136
  .option('--color-scheme <scheme>', 'Color scheme: light or dark');
22
137
 
23
138
  const skipVersionCheckOption = program.createOption(
@@ -32,9 +147,121 @@ export async function createCLI(version: string): Promise<Command> {
32
147
  program.help();
33
148
  });
34
149
 
150
+ // Handle unknown commands
151
+ program.on('command:*', (operands: string[]) => {
152
+ const unknownCommand = operands[0];
153
+ console.error(`error: unknown command '${unknownCommand}'`);
154
+ console.error();
155
+ const availableCommands = program.commands.map((cmd) => cmd.name());
156
+ if (availableCommands.length > 0) {
157
+ console.error('Available commands:');
158
+ availableCommands.forEach((name) => {
159
+ console.error(` ${name}`);
160
+ });
161
+ }
162
+ console.error();
163
+ console.error(`Run 'agentuity --help' for usage information.`);
164
+ process.exit(1);
165
+ });
166
+
167
+ // Custom error handling for argument/command parsing errors
168
+ program.configureOutput({
169
+ outputError: (str, write) => {
170
+ // Intercept commander.js error messages
171
+ if (str.includes('too many arguments') || str.includes('unknown command')) {
172
+ // Extract potential command name from error context
173
+ const match = str.match(/got (\d+)/);
174
+ if (match) {
175
+ write(`error: unknown command or subcommand\n`);
176
+ write(`\nRun 'agentuity --help' for available commands.\n`);
177
+ } else {
178
+ write(str);
179
+ }
180
+ } else {
181
+ write(str);
182
+ }
183
+ },
184
+ });
185
+
35
186
  return program;
36
187
  }
37
188
 
189
+ async function getRegion(regions: RegionList): Promise<string> {
190
+ if (regions.length === 1) {
191
+ return regions[0].region;
192
+ } else {
193
+ const response = await enquirer.prompt<{ region: string }>({
194
+ type: 'select',
195
+ name: 'region',
196
+ message: 'Select a cloud region:',
197
+ choices: regions.map((r) => ({
198
+ name: r.region,
199
+ message: `${r.description.padEnd(15, ' ')} ${tui.muted(r.region)}`,
200
+ })),
201
+ });
202
+ return response.region;
203
+ }
204
+ }
205
+
206
+ interface ResolveRegionOptions {
207
+ options: Record<string, unknown>;
208
+ apiClient: APIClientType;
209
+ logger: Logger;
210
+ required: boolean;
211
+ }
212
+
213
+ async function resolveRegion(opts: ResolveRegionOptions): Promise<string | undefined> {
214
+ const { options, apiClient, logger, required } = opts;
215
+
216
+ // Fetch regions
217
+ const regions = await listRegions(apiClient);
218
+
219
+ // No regions available
220
+ if (regions.length === 0) {
221
+ if (required) {
222
+ logger.fatal('No cloud regions available');
223
+ }
224
+ return undefined;
225
+ }
226
+
227
+ // Check if region was provided via flag
228
+ let region = options.region as string | undefined;
229
+
230
+ // Validate --region flag if provided
231
+ if (region) {
232
+ const found = regions.find((r) => r.region === region);
233
+ if (!found) {
234
+ logger.fatal(
235
+ `Invalid region '${region}'. Use one of: ${regions.map((r) => r.region).join(', ')}`
236
+ );
237
+ }
238
+ return region;
239
+ }
240
+
241
+ // Auto-select if only one region available
242
+ if (regions.length === 1) {
243
+ region = regions[0].region;
244
+ if (!process.stdin.isTTY) {
245
+ logger.trace('auto-selected region (non-TTY): %s', region);
246
+ }
247
+ return region;
248
+ }
249
+
250
+ // No flag provided - handle TTY vs non-TTY
251
+ if (required && !process.stdin.isTTY) {
252
+ logger.fatal('--region flag is required in non-interactive mode');
253
+ }
254
+
255
+ if (process.stdin.isTTY) {
256
+ // Interactive mode - prompt user
257
+ region = await getRegion(regions);
258
+ return region;
259
+ }
260
+
261
+ // Non-interactive, optional region - return undefined
262
+ return undefined;
263
+ }
264
+
38
265
  async function registerSubcommand(
39
266
  parent: Command,
40
267
  subcommand: SubcommandDefinition,
@@ -47,7 +274,37 @@ async function registerSubcommand(
47
274
  cmd.aliases(subcommand.aliases);
48
275
  }
49
276
 
50
- // Auto-generate arguments and options from schemas
277
+ // Check if this subcommand has its own subcommands (nested subcommands)
278
+ const subDef = subcommand as unknown as { subcommands?: SubcommandDefinition[] };
279
+ if (subDef.subcommands && subDef.subcommands.length > 0) {
280
+ // Register nested subcommands recursively
281
+ for (const nestedSub of subDef.subcommands) {
282
+ await registerSubcommand(cmd, nestedSub, baseCtx);
283
+ }
284
+ return;
285
+ }
286
+
287
+ const {
288
+ requiresProject,
289
+ optionalProject,
290
+ requiresOrg,
291
+ optionalOrg,
292
+ requiresRegion,
293
+ optionalRegion,
294
+ } = normalizeReqs(subcommand);
295
+
296
+ if (requiresProject || optionalProject) {
297
+ cmd.option('--dir <path>', 'project directory (default: current directory)');
298
+ }
299
+
300
+ if (requiresOrg || optionalOrg) {
301
+ cmd.option('--org-id <id>', 'organization ID');
302
+ }
303
+
304
+ if (requiresRegion || optionalRegion) {
305
+ cmd.option('--region <region>', 'cloud region');
306
+ }
307
+
51
308
  if (subcommand.schema?.args) {
52
309
  const parsed = parseArgsSchema(subcommand.schema.args);
53
310
  for (const argMeta of parsed.metadata) {
@@ -67,11 +324,14 @@ async function registerSubcommand(
67
324
  const flag = opt.name.replace(/([A-Z])/g, '-$1').toLowerCase();
68
325
  const desc = opt.description || '';
69
326
  if (opt.type === 'boolean') {
70
- // Support negatable boolean options (--no-flag) when they have a default
71
327
  if (opt.hasDefault) {
328
+ const defaultValue =
329
+ typeof opt.defaultValue === 'function' ? opt.defaultValue() : opt.defaultValue;
72
330
  cmd.option(`--no-${flag}`, desc);
331
+ cmd.option(`--${flag}`, desc, defaultValue);
332
+ } else {
333
+ cmd.option(`--${flag}`, desc);
73
334
  }
74
- cmd.option(`--${flag}`, desc);
75
335
  } else if (opt.type === 'number') {
76
336
  cmd.option(`--${flag} <${opt.name}>`, desc, parseFloat);
77
337
  } else {
@@ -85,76 +345,322 @@ async function registerSubcommand(
85
345
  const options = cmdObj.opts();
86
346
  const args = rawArgs.slice(0, -1);
87
347
 
88
- if (subcommand.requiresAuth) {
89
- const auth = await requireAuth(baseCtx as CommandContext<false>);
348
+ if (subcommand.banner) {
349
+ showBanner();
350
+ }
351
+
352
+ const normalized = normalizeReqs(subcommand);
353
+
354
+ let project: ProjectConfig | undefined;
355
+ let projectDir: string | undefined;
356
+ const dirNeeded = normalized.requiresProject || normalized.optionalProject;
357
+
358
+ if (dirNeeded) {
359
+ const dir = (options.dir as string | undefined) ?? process.cwd();
360
+ projectDir = dir;
361
+ try {
362
+ project = await loadProjectConfig(dir, baseCtx.config);
363
+ } catch (error) {
364
+ if (normalized.requiresProject) {
365
+ if (
366
+ error &&
367
+ typeof error === 'object' &&
368
+ 'name' in error &&
369
+ error.name === 'ProjectConfigNotFoundExpection'
370
+ ) {
371
+ baseCtx.logger.fatal(
372
+ 'invalid project folder. use --dir to specify a different directory or change to a project folder'
373
+ );
374
+ }
375
+ throw error;
376
+ }
377
+ // For optional projects, silently continue without project config
378
+ }
379
+ }
380
+
381
+ if (normalized.requiresAuth) {
382
+ // Create apiClient before requireAuth since login command needs it
383
+ if (normalized.requiresAPIClient) {
384
+ (baseCtx as Record<string, unknown>).apiClient = createAPIClient(
385
+ baseCtx,
386
+ baseCtx.config ?? null
387
+ );
388
+ }
389
+
390
+ const auth = await requireAuth(baseCtx as CommandContext<undefined>);
90
391
 
91
392
  if (subcommand.schema) {
92
393
  try {
93
394
  const input = buildValidationInput(subcommand.schema, args, options);
94
395
  const ctx: Record<string, unknown> = {
95
396
  ...baseCtx,
397
+ config: {
398
+ ...(baseCtx.config ?? {}),
399
+ auth: {
400
+ api_key: auth.apiKey,
401
+ user_id: auth.userId,
402
+ expires: auth.expires.getTime(),
403
+ },
404
+ },
96
405
  auth,
97
406
  };
407
+ if (project || projectDir) {
408
+ if (project) {
409
+ ctx.project = project;
410
+ }
411
+ ctx.projectDir = projectDir;
412
+ }
98
413
  if (subcommand.schema.args) {
99
414
  ctx.args = subcommand.schema.args.parse(input.args);
100
415
  }
101
416
  if (subcommand.schema.options) {
102
417
  ctx.opts = subcommand.schema.options.parse(input.options);
103
418
  }
104
- await subcommand.handler(ctx as CommandContext);
419
+ if (normalized.requiresAPIClient) {
420
+ // Recreate apiClient with auth credentials
421
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
422
+ }
423
+ if (normalized.requiresOrg) {
424
+ ctx.orgId = await requireOrg(
425
+ ctx as CommandContext & { apiClient: APIClientType }
426
+ );
427
+ }
428
+ if (normalized.optionalOrg && ctx.auth) {
429
+ ctx.orgId = await requireOrg(
430
+ ctx as CommandContext & { apiClient: APIClientType }
431
+ );
432
+ }
433
+ if ((normalized.requiresRegion || normalized.optionalRegion) && ctx.apiClient) {
434
+ const apiClient: APIClientType = ctx.apiClient as APIClientType;
435
+ const region = await tui.spinner({
436
+ message: 'Fetching cloud regions',
437
+ clearOnSuccess: true,
438
+ callback: async () => {
439
+ return resolveRegion({
440
+ options: options as Record<string, unknown>,
441
+ apiClient,
442
+ logger: baseCtx.logger,
443
+ required: !!normalized.requiresRegion,
444
+ });
445
+ },
446
+ });
447
+ if (region) {
448
+ ctx.region = region;
449
+ }
450
+ }
451
+ if (subcommand.handler) {
452
+ await subcommand.handler(ctx as CommandContext);
453
+ }
105
454
  } catch (error) {
106
455
  if (error && typeof error === 'object' && 'issues' in error) {
107
456
  baseCtx.logger.error('Validation error:');
108
457
  const issues = (error as { issues: Array<{ path: string[]; message: string }> })
109
458
  .issues;
110
459
  for (const issue of issues) {
111
- baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
460
+ baseCtx.logger.error(
461
+ ` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
462
+ );
112
463
  }
113
464
  process.exit(1);
114
465
  }
115
- throw error;
466
+ handleProjectConfigError(error, normalized.requiresProject, baseCtx.logger);
116
467
  }
117
468
  } else {
118
- const ctx: CommandContext<true> = {
469
+ const ctx: Record<string, unknown> = {
119
470
  ...baseCtx,
471
+ config: baseCtx.config
472
+ ? {
473
+ ...baseCtx.config,
474
+ name: baseCtx.config.name ?? defaultProfileName,
475
+ auth: {
476
+ api_key: auth.apiKey,
477
+ user_id: auth.userId,
478
+ expires: auth.expires.getTime(),
479
+ },
480
+ }
481
+ : null,
120
482
  auth,
121
483
  };
122
- await subcommand.handler(ctx);
484
+ if (project || projectDir) {
485
+ if (project) {
486
+ ctx.project = project;
487
+ }
488
+ ctx.projectDir = projectDir;
489
+ }
490
+ if (normalized.requiresAPIClient) {
491
+ // Recreate apiClient with auth credentials
492
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
493
+ }
494
+ if (normalized.requiresOrg) {
495
+ ctx.orgId = await requireOrg(ctx as CommandContext & { apiClient: APIClientType });
496
+ }
497
+ if (normalized.optionalOrg && ctx.auth) {
498
+ ctx.orgId = await requireOrg(ctx as CommandContext & { apiClient: APIClientType });
499
+ }
500
+ if ((normalized.requiresRegion || normalized.optionalRegion) && ctx.apiClient) {
501
+ const apiClient: APIClientType = ctx.apiClient as APIClientType;
502
+ const region = await tui.spinner('Fetching cloud regions', async () => {
503
+ return resolveRegion({
504
+ options: options as Record<string, unknown>,
505
+ apiClient,
506
+ logger: baseCtx.logger,
507
+ required: !!normalized.requiresRegion,
508
+ });
509
+ });
510
+ if (region) {
511
+ ctx.region = region;
512
+ }
513
+ }
514
+ if (subcommand.handler) {
515
+ await subcommand.handler(ctx as CommandContext);
516
+ }
123
517
  }
124
- } else if (subcommand.optionalAuth) {
518
+ } else if (normalized.optionalAuth) {
125
519
  const continueText =
126
- typeof subcommand.optionalAuth === 'string' ? subcommand.optionalAuth : undefined;
127
- const auth = await optionalAuth(baseCtx as CommandContext<false>, continueText);
520
+ typeof normalized.optionalAuth === 'string' ? normalized.optionalAuth : undefined;
521
+
522
+ // Create apiClient before optionalAuth since login command needs it
523
+ if (normalized.requiresAPIClient) {
524
+ (baseCtx as Record<string, unknown>).apiClient = createAPIClient(
525
+ baseCtx,
526
+ baseCtx.config ?? null
527
+ );
528
+ }
529
+
530
+ const auth = await optionalAuth(baseCtx as CommandContext<undefined>, continueText);
128
531
 
129
532
  if (subcommand.schema) {
130
533
  try {
131
534
  const input = buildValidationInput(subcommand.schema, args, options);
132
535
  const ctx: Record<string, unknown> = {
133
536
  ...baseCtx,
537
+ config: auth
538
+ ? {
539
+ ...(baseCtx.config ?? {}),
540
+ auth: {
541
+ api_key: auth.apiKey,
542
+ user_id: auth.userId,
543
+ expires: auth.expires.getTime(),
544
+ },
545
+ }
546
+ : baseCtx.config,
134
547
  auth,
135
548
  };
549
+ if (project || projectDir) {
550
+ if (project) {
551
+ ctx.project = project;
552
+ }
553
+ ctx.projectDir = projectDir;
554
+ }
136
555
  if (subcommand.schema.args) {
137
556
  ctx.args = subcommand.schema.args.parse(input.args);
138
557
  }
139
558
  if (subcommand.schema.options) {
140
559
  ctx.opts = subcommand.schema.options.parse(input.options);
141
560
  }
142
- await subcommand.handler(ctx as CommandContext);
561
+ if (normalized.requiresAPIClient) {
562
+ // Recreate apiClient with auth credentials
563
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
564
+ }
565
+ baseCtx.logger.trace(
566
+ 'optionalAuth path: org=%s, region=%s, hasApiClient=%s, hasAuth=%s',
567
+ normalized.optionalOrg,
568
+ normalized.optionalRegion,
569
+ !!ctx.apiClient,
570
+ !!auth
571
+ );
572
+ if (normalized.requiresOrg && ctx.apiClient) {
573
+ ctx.orgId = await requireOrg(
574
+ ctx as CommandContext & { apiClient: APIClientType }
575
+ );
576
+ }
577
+ if (normalized.optionalOrg && ctx.apiClient && auth) {
578
+ ctx.orgId = await selectOptionalOrg(
579
+ ctx as CommandContext & { apiClient?: APIClientType; auth?: AuthData }
580
+ );
581
+ baseCtx.logger.trace('selected orgId: %s', ctx.orgId);
582
+ }
583
+ if (
584
+ (normalized.requiresRegion || normalized.optionalRegion) &&
585
+ ctx.apiClient &&
586
+ auth
587
+ ) {
588
+ const apiClient: APIClientType = ctx.apiClient as APIClientType;
589
+ const region = await resolveRegion({
590
+ options: options as Record<string, unknown>,
591
+ apiClient,
592
+ logger: baseCtx.logger,
593
+ required: !!normalized.requiresRegion,
594
+ });
595
+ if (region) {
596
+ ctx.region = region;
597
+ }
598
+ }
599
+ if (subcommand.handler) {
600
+ await subcommand.handler(ctx as CommandContext);
601
+ }
143
602
  } catch (error) {
144
603
  if (error && typeof error === 'object' && 'issues' in error) {
145
604
  baseCtx.logger.error('Validation error:');
146
605
  const issues = (error as { issues: Array<{ path: string[]; message: string }> })
147
606
  .issues;
148
607
  for (const issue of issues) {
149
- baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
608
+ baseCtx.logger.error(
609
+ ` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
610
+ );
150
611
  }
151
612
  process.exit(1);
152
613
  }
153
- throw error;
614
+ handleProjectConfigError(error, normalized.requiresProject, baseCtx.logger);
154
615
  }
155
616
  } else {
156
- const ctx = { ...baseCtx, auth };
157
- await subcommand.handler(ctx as CommandContext);
617
+ const ctx: Record<string, unknown> = {
618
+ ...baseCtx,
619
+ config: auth
620
+ ? {
621
+ ...(baseCtx.config ?? {}),
622
+ auth: {
623
+ api_key: auth.apiKey,
624
+ user_id: auth.userId,
625
+ expires: auth.expires.getTime(),
626
+ },
627
+ }
628
+ : baseCtx.config,
629
+ auth,
630
+ };
631
+ if (project || projectDir) {
632
+ if (project) {
633
+ ctx.project = project;
634
+ }
635
+ ctx.projectDir = projectDir;
636
+ }
637
+ if (normalized.requiresAPIClient) {
638
+ // Recreate apiClient with auth credentials if auth was provided
639
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
640
+ }
641
+ if (normalized.requiresOrg && ctx.apiClient) {
642
+ ctx.orgId = await requireOrg(ctx as CommandContext & { apiClient: APIClientType });
643
+ }
644
+ if (normalized.optionalOrg && ctx.apiClient) {
645
+ ctx.orgId = await selectOptionalOrg(
646
+ ctx as CommandContext & { apiClient?: APIClientType; auth?: AuthData }
647
+ );
648
+ }
649
+ if ((normalized.requiresRegion || normalized.optionalRegion) && ctx.apiClient) {
650
+ const apiClient: APIClientType = ctx.apiClient as APIClientType;
651
+ const region = await resolveRegion({
652
+ options: options as Record<string, unknown>,
653
+ apiClient,
654
+ logger: baseCtx.logger,
655
+ required: !!normalized.requiresRegion,
656
+ });
657
+ if (region) {
658
+ ctx.region = region;
659
+ }
660
+ }
661
+ if (subcommand.handler) {
662
+ await subcommand.handler(ctx as CommandContext);
663
+ }
158
664
  }
159
665
  } else {
160
666
  if (subcommand.schema) {
@@ -163,27 +669,82 @@ async function registerSubcommand(
163
669
  const ctx: Record<string, unknown> = {
164
670
  ...baseCtx,
165
671
  };
672
+ if (project || projectDir) {
673
+ if (project) {
674
+ ctx.project = project;
675
+ }
676
+ ctx.projectDir = projectDir;
677
+ }
166
678
  if (subcommand.schema.args) {
167
679
  ctx.args = subcommand.schema.args.parse(input.args);
168
680
  }
169
681
  if (subcommand.schema.options) {
170
682
  ctx.opts = subcommand.schema.options.parse(input.options);
171
683
  }
172
- await subcommand.handler(ctx as CommandContext);
684
+ if (normalized.requiresAPIClient && !ctx.apiClient) {
685
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
686
+ }
687
+ if (normalized.requiresOrg && ctx.apiClient) {
688
+ ctx.orgId = await requireOrg(
689
+ ctx as CommandContext & { apiClient: APIClientType }
690
+ );
691
+ }
692
+ if (normalized.optionalOrg && ctx.apiClient && ctx.auth) {
693
+ ctx.orgId = await requireOrg(
694
+ ctx as CommandContext & { apiClient: APIClientType }
695
+ );
696
+ }
697
+ if (subcommand.handler) {
698
+ await subcommand.handler(ctx as CommandContext);
699
+ }
173
700
  } catch (error) {
174
701
  if (error && typeof error === 'object' && 'issues' in error) {
175
702
  baseCtx.logger.error('Validation error:');
176
703
  const issues = (error as { issues: Array<{ path: string[]; message: string }> })
177
704
  .issues;
178
705
  for (const issue of issues) {
179
- baseCtx.logger.error(` ${issue.path.join('.')}: ${issue.message}`);
706
+ baseCtx.logger.error(
707
+ ` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
708
+ );
180
709
  }
181
710
  process.exit(1);
182
711
  }
183
- throw error;
712
+ handleProjectConfigError(error, normalized.requiresProject, baseCtx.logger);
184
713
  }
185
714
  } else {
186
- await subcommand.handler(baseCtx as CommandContext<false>);
715
+ const ctx: Record<string, unknown> = {
716
+ ...baseCtx,
717
+ };
718
+ if (project || projectDir) {
719
+ if (project) {
720
+ ctx.project = project;
721
+ }
722
+ ctx.projectDir = projectDir;
723
+ }
724
+ if (normalized.requiresAPIClient && !ctx.apiClient) {
725
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
726
+ }
727
+ if (normalized.requiresOrg && ctx.apiClient) {
728
+ ctx.orgId = await requireOrg(ctx as CommandContext & { apiClient: APIClientType });
729
+ }
730
+ if (normalized.optionalOrg && ctx.apiClient && ctx.auth) {
731
+ ctx.orgId = await requireOrg(ctx as CommandContext & { apiClient: APIClientType });
732
+ }
733
+ if ((normalized.requiresRegion || normalized.optionalRegion) && ctx.apiClient) {
734
+ const apiClient: APIClientType = ctx.apiClient as APIClientType;
735
+ const region = await resolveRegion({
736
+ options: options as Record<string, unknown>,
737
+ apiClient,
738
+ logger: baseCtx.logger,
739
+ required: !!normalized.requiresRegion,
740
+ });
741
+ if (region) {
742
+ ctx.region = region;
743
+ }
744
+ }
745
+ if (subcommand.handler) {
746
+ await subcommand.handler(ctx as CommandContext);
747
+ }
187
748
  }
188
749
  }
189
750
  });
@@ -206,18 +767,130 @@ export async function registerCommands(
206
767
 
207
768
  if (cmdDef.handler) {
208
769
  cmd.action(async () => {
209
- if (cmdDef.requiresAuth) {
210
- const auth = await requireAuth(baseCtx as CommandContext<false>);
211
- const ctx: CommandContext<true> = { ...baseCtx, auth };
212
- await cmdDef.handler!(ctx);
213
- } else if (cmdDef.optionalAuth) {
770
+ if (cmdDef.banner) {
771
+ showBanner();
772
+ }
773
+
774
+ const normalized = normalizeReqs(cmdDef);
775
+ if (normalized.requiresAuth) {
776
+ // Create apiClient before requireAuth since login command needs it
777
+ if (normalized.requiresAPIClient) {
778
+ (baseCtx as Record<string, unknown>).apiClient = createAPIClient(
779
+ baseCtx,
780
+ baseCtx.config ?? null
781
+ );
782
+ }
783
+
784
+ const auth = await requireAuth(baseCtx as CommandContext<undefined>);
785
+ const ctx: Record<string, unknown> = {
786
+ ...baseCtx,
787
+ config: baseCtx.config
788
+ ? {
789
+ ...baseCtx.config,
790
+ name: baseCtx.config.name ?? defaultProfileName,
791
+ auth: {
792
+ api_key: auth.apiKey,
793
+ user_id: auth.userId,
794
+ expires: auth.expires.getTime(),
795
+ },
796
+ }
797
+ : null,
798
+ auth,
799
+ };
800
+ if (normalized.requiresAPIClient) {
801
+ // Recreate apiClient with auth credentials
802
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
803
+ }
804
+ if ((normalized.requiresRegion || normalized.optionalRegion) && ctx.apiClient) {
805
+ const apiClient: APIClientType = ctx.apiClient as APIClientType;
806
+ const region = await resolveRegion({
807
+ options: baseCtx.options as unknown as Record<string, unknown>,
808
+ apiClient,
809
+ logger: baseCtx.logger,
810
+ required: !!normalized.requiresRegion,
811
+ });
812
+ if (region) {
813
+ ctx.region = region;
814
+ }
815
+ }
816
+ await cmdDef.handler!(ctx as CommandContext);
817
+ } else if (normalized.optionalAuth) {
214
818
  const continueText =
215
- typeof cmdDef.optionalAuth === 'string' ? cmdDef.optionalAuth : undefined;
216
- const auth = await optionalAuth(baseCtx as CommandContext<false>, continueText);
217
- const ctx = { ...baseCtx, auth };
819
+ typeof normalized.optionalAuth === 'string'
820
+ ? normalized.optionalAuth
821
+ : undefined;
822
+
823
+ // Create apiClient before optionalAuth since login command needs it
824
+ if (normalized.requiresAPIClient) {
825
+ (baseCtx as Record<string, unknown>).apiClient = createAPIClient(
826
+ baseCtx,
827
+ baseCtx.config ?? null
828
+ );
829
+ }
830
+
831
+ const auth = await optionalAuth(
832
+ baseCtx as CommandContext<undefined>,
833
+ continueText
834
+ );
835
+ const ctx: Record<string, unknown> = {
836
+ ...baseCtx,
837
+ config: auth
838
+ ? baseCtx.config
839
+ ? {
840
+ ...baseCtx.config,
841
+ auth: {
842
+ api_key: auth.apiKey,
843
+ user_id: auth.userId,
844
+ expires: auth.expires.getTime(),
845
+ },
846
+ }
847
+ : {
848
+ auth: {
849
+ api_key: auth.apiKey,
850
+ user_id: auth.userId,
851
+ expires: auth.expires.getTime(),
852
+ },
853
+ }
854
+ : baseCtx.config,
855
+ auth,
856
+ };
857
+ if (normalized.requiresAPIClient) {
858
+ // Recreate apiClient with auth credentials if auth was provided
859
+ ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
860
+ }
861
+ if ((normalized.requiresRegion || normalized.optionalRegion) && ctx.apiClient) {
862
+ const apiClient: APIClientType = ctx.apiClient as APIClientType;
863
+ const region = await resolveRegion({
864
+ options: baseCtx.options as unknown as Record<string, unknown>,
865
+ apiClient,
866
+ logger: baseCtx.logger,
867
+ required: !!normalized.requiresRegion,
868
+ });
869
+ if (region) {
870
+ ctx.region = region;
871
+ }
872
+ }
218
873
  await cmdDef.handler!(ctx as CommandContext);
219
874
  } else {
220
- await cmdDef.handler!(baseCtx as CommandContext<false>);
875
+ const ctx: Record<string, unknown> = {
876
+ ...baseCtx,
877
+ };
878
+ if (normalized.requiresAPIClient && !(ctx as CommandContext).apiClient) {
879
+ ctx.apiClient = createAPIClient(baseCtx, baseCtx.config);
880
+ }
881
+ if ((normalized.requiresRegion || normalized.optionalRegion) && ctx.apiClient) {
882
+ const apiClient = ctx.apiClient as APIClientType;
883
+ const region = await resolveRegion({
884
+ options: baseCtx.options as unknown as Record<string, unknown>,
885
+ apiClient,
886
+ logger: baseCtx.logger,
887
+ required: !!normalized.requiresRegion,
888
+ });
889
+ if (region) {
890
+ ctx.region = region;
891
+ }
892
+ }
893
+ await cmdDef.handler!(ctx as CommandContext);
221
894
  }
222
895
  });
223
896
  } else {