mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-05 00:13:20 +00:00
Epic P3. Migrates hotpath console.error/warn to the structured logger
(diagnostics) or process.stderr.write (user-facing), and adds a redaction
safety gate so the migration cannot leak credentials.
Redaction gate (MR1):
- log-redaction: scrub known credential token shapes (sk-ant, sk-, xoxb,
ghp, glpat, AIza, JWT bodies, api_key=, Bearer/Basic/Token scheme) in
string values, Error.message, AND the log message string (defense-in-depth).
- logger: message now passes through maskSecretTokens.
tool-sanitization-proxy: deleted the private file-logging subsystem
(initLogFile/writeLog/log/warn, logFilePath, debugMode); 13 call sites now
route through the existing createLogger('cliproxy:tool-sanitization-proxy').
Sweep (~120 diagnostic -> structured createLogger; ~540 user-facing -> stderr):
- diagnostics converted across proxy, web-server/routes, glmt pipeline, quota
fetchers, executors, delegation, session-bridge, https-tunnel-proxy.
- user-facing CLI output (flows, arg-parser usage, installers, prompts, adapter
launch errors, error display) moved to process.stderr.write (preserves stderr).
- src/utils/error-manager.ts reclassified CLI-UX-exempt (user-facing display).
Metric: hotpath console.error 928 -> 267 (71%); createLogger files 35 -> 64.
Residual 267 is user-facing CLI output (not diagnostics); documented in
docs/hardening-debt-burndown.md. Redaction gate makes further conversion safe.
Tests: hotpath-redaction-regression (12 token shapes); updated delegation-handler,
arg-parser, model-warnings spies (console.error -> process.stderr.write).
validate + validate:ci-parity green.
642 lines
20 KiB
TypeScript
642 lines
20 KiB
TypeScript
/**
|
|
* CLIProxy Executor Arg Parser
|
|
*
|
|
* Extracted from index.ts (Concern A):
|
|
* - readOptionValue
|
|
* - hasGitLabTokenLoginFlag / getGitLabTokenLoginFlagName
|
|
* - CCS_FLAGS constant + filterCcsFlags()
|
|
* - parseExecutorFlags() — flag extraction block (lines ~411-639 in original)
|
|
* - validateFlagCombinations() — cross-flag guard block (lines ~531-585)
|
|
*
|
|
* IMPORTANT: process.exit semantics are kept identical to original index.ts,
|
|
* with explicit parseFailed/validation return state for callers that must not
|
|
* depend on ambient process.exitCode.
|
|
* All console.error messages are byte-identical.
|
|
*/
|
|
|
|
import { fail } from '../../utils/ui/indicators';
|
|
import {
|
|
isKiroAuthMethod,
|
|
isKiroIDCFlow,
|
|
type KiroAuthMethod,
|
|
type KiroIDCFlow,
|
|
normalizeKiroAuthMethod,
|
|
normalizeKiroIDCFlow,
|
|
} from '../auth/auth-types';
|
|
import type { UnifiedConfig } from '../../config/unified-config-types';
|
|
import { PROXY_CLI_FLAGS } from '../proxy/proxy-config-resolver';
|
|
import { parseThinkingOverride } from './thinking-arg-parser';
|
|
|
|
// Inlined from antigravity-responsibility.ts to avoid pulling in unified-config-loader
|
|
// (which requires js-yaml at runtime). Keep in sync if ANTIGRAVITY_ACCEPT_RISK_FLAGS changes.
|
|
const ANTIGRAVITY_ACCEPT_RISK_FLAGS_LOCAL = [
|
|
'--accept-agr-risk',
|
|
'--accept-antigravity-risk',
|
|
] as const;
|
|
|
|
function hasAntigravityRiskAcceptanceFlag(args: string[]): boolean {
|
|
return args.some((arg) =>
|
|
(ANTIGRAVITY_ACCEPT_RISK_FLAGS_LOCAL as readonly string[]).includes(arg)
|
|
);
|
|
}
|
|
|
|
// ── Simple Helpers ────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Parse a flag value from args supporting both `--flag value` and `--flag=value` forms.
|
|
* Returns the shape used throughout the executor:
|
|
* { present, value?, missingValue }
|
|
*/
|
|
export function readOptionValue(
|
|
args: string[],
|
|
flag: string
|
|
): { present: boolean; value?: string; missingValue: boolean } {
|
|
const inlinePrefix = `${flag}=`;
|
|
const inlineArg = args.find((arg) => arg.startsWith(inlinePrefix));
|
|
if (inlineArg !== undefined) {
|
|
const value = inlineArg.slice(inlinePrefix.length).trim();
|
|
return {
|
|
present: true,
|
|
value: value.length > 0 ? value : undefined,
|
|
missingValue: value.length === 0,
|
|
};
|
|
}
|
|
|
|
const index = args.indexOf(flag);
|
|
if (index === -1) {
|
|
return { present: false, missingValue: false };
|
|
}
|
|
|
|
const next = args[index + 1];
|
|
if (!next || next.startsWith('-')) {
|
|
return { present: true, missingValue: true };
|
|
}
|
|
|
|
return { present: true, value: next.trim(), missingValue: false };
|
|
}
|
|
|
|
/** Returns true if args contain a GitLab token-login flag. */
|
|
export function hasGitLabTokenLoginFlag(args: string[]): boolean {
|
|
return args.includes('--gitlab-token-login') || args.includes('--token-login');
|
|
}
|
|
|
|
/** Returns the specific flag name present in args (used for error messages). */
|
|
export function getGitLabTokenLoginFlagName(
|
|
args: string[]
|
|
): '--gitlab-token-login' | '--token-login' {
|
|
return args.includes('--gitlab-token-login') ? '--gitlab-token-login' : '--token-login';
|
|
}
|
|
|
|
// ── CCS Flags Filter ──────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* CCS-specific flags that must not be forwarded to the underlying Claude CLI.
|
|
* The list is kept here as the single source of truth (previously inlined in index.ts).
|
|
*/
|
|
export const CCS_FLAGS: readonly string[] = [
|
|
'--auth',
|
|
'--paste-callback',
|
|
'--port-forward',
|
|
'--headless',
|
|
'--logout',
|
|
'--config',
|
|
'--add',
|
|
'--accounts',
|
|
'--use',
|
|
'--nickname',
|
|
'--kiro-auth-method',
|
|
'--kiro-idc-start-url',
|
|
'--kiro-idc-region',
|
|
'--kiro-idc-flow',
|
|
'--thinking',
|
|
'--effort',
|
|
'--1m',
|
|
'--no-1m',
|
|
'--incognito',
|
|
'--no-incognito',
|
|
'--import',
|
|
'--accept-agr-risk',
|
|
'--accept-antigravity-risk',
|
|
'--settings',
|
|
...PROXY_CLI_FLAGS,
|
|
] as const;
|
|
|
|
/**
|
|
* Filter all CCS-specific flags (and their value arguments) from args
|
|
* before forwarding to the Claude CLI.
|
|
* Mirrors the filter logic from index.ts lines ~1328-1349.
|
|
*/
|
|
export function filterCcsFlags(args: string[]): string[] {
|
|
return args.filter((arg, idx) => {
|
|
if (CCS_FLAGS.includes(arg)) return false;
|
|
if (arg.startsWith('--kiro-auth-method=')) return false;
|
|
if (arg.startsWith('--kiro-idc-start-url=')) return false;
|
|
if (arg.startsWith('--kiro-idc-region=')) return false;
|
|
if (arg.startsWith('--kiro-idc-flow=')) return false;
|
|
if (arg.startsWith('--thinking=')) return false;
|
|
if (arg.startsWith('--effort=')) return false;
|
|
if (arg.startsWith('--1m=') || arg.startsWith('--no-1m=')) return false;
|
|
if (
|
|
args[idx - 1] === '--use' ||
|
|
args[idx - 1] === '--nickname' ||
|
|
args[idx - 1] === '--kiro-auth-method' ||
|
|
args[idx - 1] === '--kiro-idc-start-url' ||
|
|
args[idx - 1] === '--kiro-idc-region' ||
|
|
args[idx - 1] === '--kiro-idc-flow' ||
|
|
args[idx - 1] === '--thinking' ||
|
|
args[idx - 1] === '--effort'
|
|
)
|
|
return false;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// ── ParsedExecutorFlags ───────────────────────────────────────────────────────
|
|
|
|
/** Result of parsing CCS executor flags from args. */
|
|
export interface ParsedExecutorFlags {
|
|
parseFailed?: boolean;
|
|
forceAuth: boolean;
|
|
pasteCallback: boolean;
|
|
portForward: boolean;
|
|
forceHeadless: boolean;
|
|
forceLogout: boolean;
|
|
forceConfig: boolean;
|
|
addAccount: boolean;
|
|
showAccounts: boolean;
|
|
forceImport: boolean;
|
|
gitlabTokenLogin: boolean;
|
|
acceptAgyRisk: boolean;
|
|
incognitoFlag: boolean;
|
|
noIncognitoFlag: boolean;
|
|
noIncognito: boolean;
|
|
useAccount: string | undefined;
|
|
setNickname: string | undefined;
|
|
kiroAuthMethod: KiroAuthMethod | undefined;
|
|
kiroIDCStartUrl: string | undefined;
|
|
kiroIDCRegion: string | undefined;
|
|
kiroIDCFlow: KiroIDCFlow | undefined;
|
|
gitlabBaseUrl: string | undefined;
|
|
extendedContextOverride: boolean | undefined;
|
|
thinkingParse: ReturnType<typeof parseThinkingOverride>;
|
|
}
|
|
|
|
/**
|
|
* Parse all CCS executor flags from args.
|
|
*
|
|
* Exits with code 1 (process.exitCode = 1 + parseFailed return) on invalid flag values.
|
|
* Exits with process.exit(1) on conflicting flag combinations — identical to
|
|
* the original index.ts behavior.
|
|
*
|
|
* @param args args AFTER proxy flags have been stripped (argsWithoutProxy)
|
|
* @param context provider context needed for kiro/incognito defaults
|
|
*/
|
|
export function parseExecutorFlags(
|
|
args: string[],
|
|
context: {
|
|
provider: string;
|
|
compositeProviders: string[];
|
|
unifiedConfig: UnifiedConfig;
|
|
}
|
|
): ParsedExecutorFlags {
|
|
const { provider, unifiedConfig } = context;
|
|
|
|
const forceAuth = args.includes('--auth');
|
|
const pasteCallback = args.includes('--paste-callback');
|
|
const portForward = args.includes('--port-forward');
|
|
const forceHeadless = args.includes('--headless');
|
|
|
|
if (pasteCallback && portForward) {
|
|
process.stderr.write(String(fail('Cannot use --paste-callback with --port-forward')) + '\n');
|
|
process.stderr.write(' --paste-callback: Manually paste OAuth redirect URL\n');
|
|
process.stderr.write(' --port-forward: Use SSH port forwarding for callback\n');
|
|
process.exit(1);
|
|
}
|
|
|
|
const forceLogout = args.includes('--logout');
|
|
const forceConfig = args.includes('--config');
|
|
const addAccount = args.includes('--add');
|
|
const showAccounts = args.includes('--accounts');
|
|
const forceImport = args.includes('--import');
|
|
const gitlabTokenLogin = hasGitLabTokenLoginFlag(args);
|
|
const acceptAgyRisk = hasAntigravityRiskAcceptanceFlag(args);
|
|
|
|
const incognitoFlag = args.includes('--incognito');
|
|
const noIncognitoFlag = args.includes('--no-incognito');
|
|
const kiroNoIncognitoConfig =
|
|
provider === 'kiro' ? (unifiedConfig.cliproxy?.kiro_no_incognito ?? true) : false;
|
|
const noIncognito = incognitoFlag ? false : noIncognitoFlag || kiroNoIncognitoConfig;
|
|
|
|
// Parse --use flag
|
|
let useAccount: string | undefined;
|
|
const useIdx = args.indexOf('--use');
|
|
if (useIdx !== -1 && args[useIdx + 1] && !args[useIdx + 1].startsWith('-')) {
|
|
useAccount = args[useIdx + 1];
|
|
}
|
|
|
|
// Parse --nickname flag
|
|
let setNickname: string | undefined;
|
|
const nicknameIdx = args.indexOf('--nickname');
|
|
if (nicknameIdx !== -1 && args[nicknameIdx + 1] && !args[nicknameIdx + 1].startsWith('-')) {
|
|
setNickname = args[nicknameIdx + 1];
|
|
}
|
|
|
|
// Parse --kiro-auth-method flag
|
|
let kiroAuthMethod: KiroAuthMethod | undefined;
|
|
const kiroMethodValue = readOptionValue(args, '--kiro-auth-method');
|
|
if (kiroMethodValue.present) {
|
|
const rawMethod = kiroMethodValue.value;
|
|
if (kiroMethodValue.missingValue || !rawMethod) {
|
|
process.stderr.write(String(fail('--kiro-auth-method requires a value')) + '\n');
|
|
process.stderr.write(' Supported values: aws, aws-authcode, google, github, idc\n');
|
|
process.exitCode = 1;
|
|
// Caller must check parseFailed and bail — matching original return behavior
|
|
return buildPartialFlags({
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod: undefined,
|
|
kiroIDCStartUrl: undefined,
|
|
kiroIDCRegion: undefined,
|
|
kiroIDCFlow: undefined,
|
|
gitlabBaseUrl: undefined,
|
|
extendedContextOverride: undefined,
|
|
thinkingParse: parseThinkingOverride(args),
|
|
parseFailed: true,
|
|
});
|
|
}
|
|
const normalized = rawMethod.trim().toLowerCase();
|
|
if (!isKiroAuthMethod(normalized)) {
|
|
process.stderr.write(String(fail(`Invalid --kiro-auth-method value: ${rawMethod}`)) + '\n');
|
|
process.stderr.write(' Supported values: aws, aws-authcode, google, github, idc\n');
|
|
process.exitCode = 1;
|
|
return buildPartialFlags({
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod: undefined,
|
|
kiroIDCStartUrl: undefined,
|
|
kiroIDCRegion: undefined,
|
|
kiroIDCFlow: undefined,
|
|
gitlabBaseUrl: undefined,
|
|
extendedContextOverride: undefined,
|
|
thinkingParse: parseThinkingOverride(args),
|
|
parseFailed: true,
|
|
});
|
|
}
|
|
kiroAuthMethod = normalizeKiroAuthMethod(normalized);
|
|
}
|
|
|
|
let kiroIDCStartUrl: string | undefined;
|
|
const kiroIDCStartUrlValue = readOptionValue(args, '--kiro-idc-start-url');
|
|
if (kiroIDCStartUrlValue.present && kiroIDCStartUrlValue.value) {
|
|
kiroIDCStartUrl = kiroIDCStartUrlValue.value;
|
|
} else if (kiroIDCStartUrlValue.present) {
|
|
process.stderr.write(String(fail('--kiro-idc-start-url requires a value')) + '\n');
|
|
process.exitCode = 1;
|
|
return buildPartialFlags({
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod,
|
|
kiroIDCStartUrl: undefined,
|
|
kiroIDCRegion: undefined,
|
|
kiroIDCFlow: undefined,
|
|
gitlabBaseUrl: undefined,
|
|
extendedContextOverride: undefined,
|
|
thinkingParse: parseThinkingOverride(args),
|
|
parseFailed: true,
|
|
});
|
|
}
|
|
|
|
let kiroIDCRegion: string | undefined;
|
|
const kiroIDCRegionValue = readOptionValue(args, '--kiro-idc-region');
|
|
if (kiroIDCRegionValue.present && kiroIDCRegionValue.value) {
|
|
kiroIDCRegion = kiroIDCRegionValue.value;
|
|
} else if (kiroIDCRegionValue.present) {
|
|
process.stderr.write(String(fail('--kiro-idc-region requires a value')) + '\n');
|
|
process.exitCode = 1;
|
|
return buildPartialFlags({
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod,
|
|
kiroIDCStartUrl,
|
|
kiroIDCRegion: undefined,
|
|
kiroIDCFlow: undefined,
|
|
gitlabBaseUrl: undefined,
|
|
extendedContextOverride: undefined,
|
|
thinkingParse: parseThinkingOverride(args),
|
|
parseFailed: true,
|
|
});
|
|
}
|
|
|
|
let kiroIDCFlow: KiroIDCFlow | undefined;
|
|
const kiroIDCFlowValue = readOptionValue(args, '--kiro-idc-flow');
|
|
if (kiroIDCFlowValue.present) {
|
|
const rawFlow = kiroIDCFlowValue.value;
|
|
if (kiroIDCFlowValue.missingValue || !rawFlow) {
|
|
process.stderr.write(String(fail('--kiro-idc-flow requires a value')) + '\n');
|
|
process.stderr.write(' Supported values: authcode, device\n');
|
|
process.exitCode = 1;
|
|
return buildPartialFlags({
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod,
|
|
kiroIDCStartUrl,
|
|
kiroIDCRegion,
|
|
kiroIDCFlow: undefined,
|
|
gitlabBaseUrl: undefined,
|
|
extendedContextOverride: undefined,
|
|
thinkingParse: parseThinkingOverride(args),
|
|
parseFailed: true,
|
|
});
|
|
}
|
|
const normalized = rawFlow.trim().toLowerCase();
|
|
if (!isKiroIDCFlow(normalized)) {
|
|
process.stderr.write(String(fail(`Invalid --kiro-idc-flow value: ${rawFlow}`)) + '\n');
|
|
process.stderr.write(' Supported values: authcode, device\n');
|
|
process.exitCode = 1;
|
|
return buildPartialFlags({
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod,
|
|
kiroIDCStartUrl,
|
|
kiroIDCRegion,
|
|
kiroIDCFlow: undefined,
|
|
gitlabBaseUrl: undefined,
|
|
extendedContextOverride: undefined,
|
|
thinkingParse: parseThinkingOverride(args),
|
|
parseFailed: true,
|
|
});
|
|
}
|
|
kiroIDCFlow = normalizeKiroIDCFlow(normalized);
|
|
}
|
|
|
|
let gitlabBaseUrl: string | undefined;
|
|
const gitlabBaseUrlValue = readOptionValue(args, '--gitlab-url');
|
|
if (gitlabBaseUrlValue.present && gitlabBaseUrlValue.value) {
|
|
gitlabBaseUrl = gitlabBaseUrlValue.value.trim();
|
|
} else if (gitlabBaseUrlValue.present) {
|
|
process.stderr.write(String(fail('--gitlab-url requires a value')) + '\n');
|
|
process.exitCode = 1;
|
|
return buildPartialFlags({
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod,
|
|
kiroIDCStartUrl,
|
|
kiroIDCRegion,
|
|
kiroIDCFlow,
|
|
gitlabBaseUrl: undefined,
|
|
extendedContextOverride: undefined,
|
|
thinkingParse: parseThinkingOverride(args),
|
|
parseFailed: true,
|
|
});
|
|
}
|
|
|
|
// Parse --thinking / --effort flags (aliases; first occurrence wins)
|
|
const thinkingParse = parseThinkingOverride(args);
|
|
if (thinkingParse.error) {
|
|
const { flag } = thinkingParse.error;
|
|
process.stderr.write(String(fail(`${flag} requires a value`)) + '\n');
|
|
|
|
if (provider === 'codex') {
|
|
process.stderr.write(' Codex examples: --effort xhigh, --effort high, --effort medium\n');
|
|
process.stderr.write(' Alias: --thinking xhigh (same behavior)\n');
|
|
} else {
|
|
process.stderr.write(' Examples: --thinking low, --thinking 8192, --thinking off\n');
|
|
process.stderr.write(' Levels: minimal, low, medium, high, xhigh, max, auto\n');
|
|
}
|
|
|
|
process.exit(1);
|
|
}
|
|
|
|
// Parse --1m / --no-1m flags for extended context (1M token window)
|
|
let extendedContextOverride: boolean | undefined;
|
|
const has1mFlag = args.includes('--1m') || args.some((arg) => arg.startsWith('--1m='));
|
|
const hasNo1mFlag = args.includes('--no-1m') || args.some((arg) => arg.startsWith('--no-1m='));
|
|
|
|
if (has1mFlag && hasNo1mFlag) {
|
|
process.stderr.write(String(fail('Cannot use both --1m and --no-1m flags')) + '\n');
|
|
process.exit(1);
|
|
} else if (has1mFlag) {
|
|
extendedContextOverride = true;
|
|
} else if (hasNo1mFlag) {
|
|
extendedContextOverride = false;
|
|
}
|
|
|
|
// Auto-set kiroAuthMethod = 'idc' if IDC sub-flags present without explicit method
|
|
if (!kiroAuthMethod && (kiroIDCStartUrl || kiroIDCRegion || kiroIDCFlow)) {
|
|
kiroAuthMethod = 'idc';
|
|
}
|
|
|
|
return {
|
|
forceAuth,
|
|
pasteCallback,
|
|
portForward,
|
|
forceHeadless,
|
|
forceLogout,
|
|
forceConfig,
|
|
addAccount,
|
|
showAccounts,
|
|
forceImport,
|
|
gitlabTokenLogin,
|
|
acceptAgyRisk,
|
|
incognitoFlag,
|
|
noIncognitoFlag,
|
|
noIncognito,
|
|
useAccount,
|
|
setNickname,
|
|
kiroAuthMethod,
|
|
kiroIDCStartUrl,
|
|
kiroIDCRegion,
|
|
kiroIDCFlow,
|
|
gitlabBaseUrl,
|
|
extendedContextOverride,
|
|
thinkingParse,
|
|
parseFailed: false,
|
|
};
|
|
}
|
|
|
|
/** Internal helper — builds a ParsedExecutorFlags from raw fields (avoids repeating the full struct). */
|
|
function buildPartialFlags(fields: ParsedExecutorFlags): ParsedExecutorFlags {
|
|
return fields;
|
|
}
|
|
|
|
// ── Cross-Flag Validation ─────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Validate flag combinations that are mutually exclusive or provider-scoped.
|
|
* Sets process.exitCode=1 and returns false on any violation.
|
|
* Call AFTER parseExecutorFlags() and only if parseFailed is false.
|
|
*
|
|
* @param parsed Result of parseExecutorFlags()
|
|
* @param context Provider context (provider string + compositeProviders list)
|
|
* @param args Raw argsWithoutProxy (needed for getGitLabTokenLoginFlagName)
|
|
*/
|
|
export function validateFlagCombinations(
|
|
parsed: ParsedExecutorFlags,
|
|
context: { provider: string; compositeProviders: string[] },
|
|
args: string[]
|
|
): boolean {
|
|
const { provider, compositeProviders } = context;
|
|
const {
|
|
kiroAuthMethod,
|
|
kiroIDCStartUrl,
|
|
kiroIDCRegion,
|
|
kiroIDCFlow,
|
|
gitlabTokenLogin,
|
|
gitlabBaseUrl,
|
|
} = parsed;
|
|
|
|
if (kiroAuthMethod && provider !== 'kiro' && !compositeProviders.includes('kiro')) {
|
|
process.stderr.write(String(fail('--kiro-auth-method is only valid for ccs kiro')) + '\n');
|
|
process.exitCode = 1;
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
(kiroIDCStartUrl || kiroIDCRegion || kiroIDCFlow) &&
|
|
provider !== 'kiro' &&
|
|
!compositeProviders.includes('kiro')
|
|
) {
|
|
process.stderr.write(
|
|
String(
|
|
fail(
|
|
'--kiro-idc-start-url, --kiro-idc-region, and --kiro-idc-flow are only valid for ccs kiro'
|
|
)
|
|
) + '\n'
|
|
);
|
|
process.exitCode = 1;
|
|
return false;
|
|
}
|
|
|
|
if (kiroAuthMethod === 'idc' && !kiroIDCStartUrl) {
|
|
process.stderr.write(String(fail('Kiro IDC login requires --kiro-idc-start-url')) + '\n');
|
|
process.stderr.write(
|
|
' Example: ccs kiro --auth --kiro-auth-method idc --kiro-idc-start-url https://d-xxx.awsapps.com/start\n'
|
|
);
|
|
process.exitCode = 1;
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
kiroAuthMethod &&
|
|
kiroAuthMethod !== 'idc' &&
|
|
(kiroIDCStartUrl || kiroIDCRegion || kiroIDCFlow)
|
|
) {
|
|
process.stderr.write(
|
|
String(
|
|
fail(
|
|
'--kiro-idc-start-url, --kiro-idc-region, and --kiro-idc-flow require --kiro-auth-method idc'
|
|
)
|
|
) + '\n'
|
|
);
|
|
process.exitCode = 1;
|
|
return false;
|
|
}
|
|
|
|
if ((gitlabTokenLogin || gitlabBaseUrl) && provider !== 'gitlab') {
|
|
const flagName = gitlabTokenLogin ? getGitLabTokenLoginFlagName(args) : '--gitlab-url';
|
|
process.stderr.write(String(fail(`${flagName} is only valid for ccs gitlab`)) + '\n');
|
|
process.exitCode = 1;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|