@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.
- package/AGENTS.md +1 -1
- package/README.md +1 -1
- package/bin/cli.ts +7 -5
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts +10 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/banner.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cmd/auth/api.d.ts +4 -4
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/signup.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/add.d.ts +2 -0
- package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/api.d.ts +16 -0
- package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/delete.d.ts +2 -0
- package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/index.d.ts +3 -0
- package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/list.d.ts +2 -0
- package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
- package/dist/cmd/auth/whoami.d.ts +2 -0
- package/dist/cmd/auth/whoami.d.ts.map +1 -0
- package/dist/cmd/bundle/ast.d.ts +14 -3
- package/dist/cmd/bundle/ast.d.ts.map +1 -1
- package/dist/cmd/bundle/ast.test.d.ts +2 -0
- package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
- package/dist/cmd/bundle/bundler.d.ts +6 -1
- package/dist/cmd/bundle/bundler.d.ts.map +1 -1
- package/dist/cmd/bundle/file.d.ts.map +1 -1
- package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
- package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
- package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
- package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
- package/dist/cmd/bundle/index.d.ts +1 -1
- package/dist/cmd/bundle/index.d.ts.map +1 -1
- package/dist/cmd/bundle/plugin.d.ts +2 -0
- package/dist/cmd/bundle/plugin.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -0
- package/dist/cmd/cloud/domain.d.ts +17 -0
- package/dist/cmd/cloud/domain.d.ts.map +1 -0
- package/dist/cmd/cloud/index.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/add.d.ts +2 -0
- package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/delete.d.ts +2 -0
- package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/index.d.ts +3 -0
- package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/list.d.ts +2 -0
- package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/download.d.ts +2 -0
- package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/index.d.ts +3 -0
- package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/upload.d.ts +2 -0
- package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
- package/dist/cmd/cloud/ssh.d.ts +2 -0
- package/dist/cmd/cloud/ssh.d.ts.map +1 -0
- package/dist/cmd/dev/api.d.ts +18 -0
- package/dist/cmd/dev/api.d.ts.map +1 -0
- package/dist/cmd/dev/download.d.ts +11 -0
- package/dist/cmd/dev/download.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/templates.d.ts +3 -0
- package/dist/cmd/dev/templates.d.ts.map +1 -0
- package/dist/cmd/env/delete.d.ts +2 -0
- package/dist/cmd/env/delete.d.ts.map +1 -0
- package/dist/cmd/env/get.d.ts +2 -0
- package/dist/cmd/env/get.d.ts.map +1 -0
- package/dist/cmd/env/import.d.ts +2 -0
- package/dist/cmd/env/import.d.ts.map +1 -0
- package/dist/cmd/env/index.d.ts +2 -0
- package/dist/cmd/env/index.d.ts.map +1 -0
- package/dist/cmd/env/list.d.ts.map +1 -0
- package/dist/cmd/env/pull.d.ts +2 -0
- package/dist/cmd/env/pull.d.ts.map +1 -0
- package/dist/cmd/env/push.d.ts +2 -0
- package/dist/cmd/env/push.d.ts.map +1 -0
- package/dist/cmd/env/set.d.ts +2 -0
- package/dist/cmd/env/set.d.ts.map +1 -0
- package/dist/cmd/profile/show.d.ts.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/delete.d.ts.map +1 -1
- package/dist/cmd/project/download.d.ts +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/list.d.ts.map +1 -1
- package/dist/cmd/project/show.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +5 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/secret/delete.d.ts +2 -0
- package/dist/cmd/secret/delete.d.ts.map +1 -0
- package/dist/cmd/secret/get.d.ts +2 -0
- package/dist/cmd/secret/get.d.ts.map +1 -0
- package/dist/cmd/secret/import.d.ts +2 -0
- package/dist/cmd/secret/import.d.ts.map +1 -0
- package/dist/cmd/secret/index.d.ts +2 -0
- package/dist/cmd/secret/index.d.ts.map +1 -0
- package/dist/cmd/secret/list.d.ts +2 -0
- package/dist/cmd/secret/list.d.ts.map +1 -0
- package/dist/cmd/secret/pull.d.ts +2 -0
- package/dist/cmd/secret/pull.d.ts.map +1 -0
- package/dist/cmd/secret/push.d.ts +2 -0
- package/dist/cmd/secret/push.d.ts.map +1 -0
- package/dist/cmd/secret/set.d.ts +2 -0
- package/dist/cmd/secret/set.d.ts.map +1 -0
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/config.d.ts +11 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/crypto/box.d.ts +65 -0
- package/dist/crypto/box.d.ts.map +1 -0
- package/dist/crypto/box.test.d.ts +2 -0
- package/dist/crypto/box.test.d.ts.map +1 -0
- package/dist/download.d.ts.map +1 -1
- package/dist/env-util.d.ts +67 -0
- package/dist/env-util.d.ts.map +1 -0
- package/dist/env-util.test.d.ts +2 -0
- package/dist/env-util.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/schema-parser.d.ts.map +1 -1
- package/dist/steps.d.ts +4 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/terminal.d.ts.map +1 -1
- package/dist/tui.d.ts +32 -2
- package/dist/tui.d.ts.map +1 -1
- package/dist/types.d.ts +250 -127
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/detectSubagent.d.ts +15 -0
- package/dist/utils/detectSubagent.d.ts.map +1 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/package.json +11 -3
- package/src/api-errors.md +2 -2
- package/src/api.ts +12 -7
- package/src/auth.ts +116 -7
- package/src/banner.ts +13 -6
- package/src/cli.ts +709 -36
- package/src/cmd/auth/api.ts +10 -16
- package/src/cmd/auth/index.ts +3 -1
- package/src/cmd/auth/login.ts +24 -8
- package/src/cmd/auth/signup.ts +15 -11
- package/src/cmd/auth/ssh/add.ts +263 -0
- package/src/cmd/auth/ssh/api.ts +94 -0
- package/src/cmd/auth/ssh/delete.ts +102 -0
- package/src/cmd/auth/ssh/index.ts +10 -0
- package/src/cmd/auth/ssh/list.ts +74 -0
- package/src/cmd/auth/whoami.ts +69 -0
- package/src/cmd/bundle/ast.test.ts +565 -0
- package/src/cmd/bundle/ast.ts +457 -44
- package/src/cmd/bundle/bundler.ts +255 -57
- package/src/cmd/bundle/file.ts +6 -12
- package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
- package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
- package/src/cmd/bundle/index.ts +11 -11
- package/src/cmd/bundle/patch/aisdk.ts +1 -1
- package/src/cmd/bundle/plugin.ts +373 -53
- package/src/cmd/cloud/deploy.ts +336 -0
- package/src/cmd/cloud/domain.ts +92 -0
- package/src/cmd/cloud/index.ts +11 -0
- package/src/cmd/cloud/resource/add.ts +56 -0
- package/src/cmd/cloud/resource/delete.ts +120 -0
- package/src/cmd/cloud/resource/index.ts +11 -0
- package/src/cmd/cloud/resource/list.ts +69 -0
- package/src/cmd/cloud/scp/download.ts +59 -0
- package/src/cmd/cloud/scp/index.ts +9 -0
- package/src/cmd/cloud/scp/upload.ts +62 -0
- package/src/cmd/cloud/ssh.ts +68 -0
- package/src/cmd/dev/api.ts +46 -0
- package/src/cmd/dev/download.ts +111 -0
- package/src/cmd/dev/index.ts +362 -34
- package/src/cmd/dev/templates.ts +84 -0
- package/src/cmd/env/delete.ts +47 -0
- package/src/cmd/env/get.ts +53 -0
- package/src/cmd/env/import.ts +102 -0
- package/src/cmd/env/index.ts +22 -0
- package/src/cmd/env/list.ts +56 -0
- package/src/cmd/env/pull.ts +80 -0
- package/src/cmd/env/push.ts +37 -0
- package/src/cmd/env/set.ts +71 -0
- package/src/cmd/index.ts +2 -2
- package/src/cmd/profile/show.ts +15 -6
- package/src/cmd/project/create.ts +7 -2
- package/src/cmd/project/delete.ts +75 -18
- package/src/cmd/project/download.ts +3 -3
- package/src/cmd/project/list.ts +8 -8
- package/src/cmd/project/show.ts +3 -7
- package/src/cmd/project/template-flow.ts +186 -48
- package/src/cmd/secret/delete.ts +40 -0
- package/src/cmd/secret/get.ts +54 -0
- package/src/cmd/secret/import.ts +64 -0
- package/src/cmd/secret/index.ts +22 -0
- package/src/cmd/secret/list.ts +56 -0
- package/src/cmd/secret/pull.ts +78 -0
- package/src/cmd/secret/push.ts +37 -0
- package/src/cmd/secret/set.ts +45 -0
- package/src/cmd/version/index.ts +2 -1
- package/src/config.ts +257 -27
- package/src/crypto/box.test.ts +431 -0
- package/src/crypto/box.ts +477 -0
- package/src/download.ts +1 -0
- package/src/env-util.test.ts +194 -0
- package/src/env-util.ts +290 -0
- package/src/index.ts +5 -1
- package/src/schema-parser.ts +2 -3
- package/src/steps.ts +144 -10
- package/src/terminal.ts +24 -23
- package/src/tui.ts +208 -68
- package/src/types.ts +292 -202
- package/src/utils/detectSubagent.ts +31 -0
- package/src/utils/zip.ts +38 -0
- package/dist/cmd/example/create-user.d.ts +0 -2
- package/dist/cmd/example/create-user.d.ts.map +0 -1
- package/dist/cmd/example/create.d.ts +0 -2
- package/dist/cmd/example/create.d.ts.map +0 -1
- package/dist/cmd/example/deploy.d.ts.map +0 -1
- package/dist/cmd/example/index.d.ts.map +0 -1
- package/dist/cmd/example/list.d.ts.map +0 -1
- package/dist/cmd/example/optional-auth.d.ts +0 -3
- package/dist/cmd/example/optional-auth.d.ts.map +0 -1
- package/dist/cmd/example/run-command.d.ts +0 -2
- package/dist/cmd/example/run-command.d.ts.map +0 -1
- package/dist/cmd/example/sound.d.ts +0 -3
- package/dist/cmd/example/sound.d.ts.map +0 -1
- package/dist/cmd/example/spinner.d.ts +0 -2
- package/dist/cmd/example/spinner.d.ts.map +0 -1
- package/dist/cmd/example/steps.d.ts +0 -2
- package/dist/cmd/example/steps.d.ts.map +0 -1
- package/dist/cmd/example/version.d.ts +0 -2
- package/dist/cmd/example/version.d.ts.map +0 -1
- package/dist/logger.d.ts +0 -24
- package/dist/logger.d.ts.map +0 -1
- package/src/cmd/example/create-user.ts +0 -38
- package/src/cmd/example/create.ts +0 -31
- package/src/cmd/example/deploy.ts +0 -36
- package/src/cmd/example/index.ts +0 -29
- package/src/cmd/example/list.ts +0 -32
- package/src/cmd/example/optional-auth.ts +0 -38
- package/src/cmd/example/run-command.ts +0 -45
- package/src/cmd/example/sound.ts +0 -14
- package/src/cmd/example/spinner.ts +0 -44
- package/src/cmd/example/steps.ts +0 -66
- package/src/cmd/example/version.ts +0 -13
- package/src/logger.ts +0 -235
- /package/dist/cmd/{example → cloud}/deploy.d.ts +0 -0
- /package/dist/cmd/{example → cloud}/index.d.ts +0 -0
- /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 {
|
|
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'
|
|
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',
|
|
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
|
-
//
|
|
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.
|
|
89
|
-
|
|
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
|
-
|
|
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(
|
|
460
|
+
baseCtx.logger.error(
|
|
461
|
+
` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
|
|
462
|
+
);
|
|
112
463
|
}
|
|
113
464
|
process.exit(1);
|
|
114
465
|
}
|
|
115
|
-
|
|
466
|
+
handleProjectConfigError(error, normalized.requiresProject, baseCtx.logger);
|
|
116
467
|
}
|
|
117
468
|
} else {
|
|
118
|
-
const ctx:
|
|
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
|
-
|
|
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 (
|
|
518
|
+
} else if (normalized.optionalAuth) {
|
|
125
519
|
const continueText =
|
|
126
|
-
typeof
|
|
127
|
-
|
|
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
|
-
|
|
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(
|
|
608
|
+
baseCtx.logger.error(
|
|
609
|
+
` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
|
|
610
|
+
);
|
|
150
611
|
}
|
|
151
612
|
process.exit(1);
|
|
152
613
|
}
|
|
153
|
-
|
|
614
|
+
handleProjectConfigError(error, normalized.requiresProject, baseCtx.logger);
|
|
154
615
|
}
|
|
155
616
|
} else {
|
|
156
|
-
const ctx = {
|
|
157
|
-
|
|
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
|
-
|
|
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(
|
|
706
|
+
baseCtx.logger.error(
|
|
707
|
+
` ${issue.path?.length ? issue.path.join('.') + ': ' : ''}${issue.message}`
|
|
708
|
+
);
|
|
180
709
|
}
|
|
181
710
|
process.exit(1);
|
|
182
711
|
}
|
|
183
|
-
|
|
712
|
+
handleProjectConfigError(error, normalized.requiresProject, baseCtx.logger);
|
|
184
713
|
}
|
|
185
714
|
} else {
|
|
186
|
-
|
|
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.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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 {
|