@bifos/nhncloud-cli 0.19.0 → 0.21.0

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/dist/index.js CHANGED
@@ -333,6 +333,69 @@ async function warnLegacyDeployTargets() {
333
333
  )
334
334
  );
335
335
  }
336
+ async function readJsonState(filePath) {
337
+ let raw;
338
+ try {
339
+ raw = await (0, import_promises.readFile)(filePath, "utf-8");
340
+ } catch (err) {
341
+ const code = err.code;
342
+ if (code === "ENOENT") return { state: "missing" };
343
+ return { state: "unreadable", reason: `\uD30C\uC77C\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${code ?? "UNKNOWN"})` };
344
+ }
345
+ try {
346
+ return { state: "ok", parsed: JSON.parse(raw) };
347
+ } catch {
348
+ return { state: "invalid", reason: "JSON \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4" };
349
+ }
350
+ }
351
+ async function inspectPermissions(filePath) {
352
+ try {
353
+ const { mode } = await (0, import_promises.stat)(filePath);
354
+ if (process.platform === "win32") return "unknown";
355
+ return (mode & 63) === 0 ? "ok" : "too-open";
356
+ } catch {
357
+ return void 0;
358
+ }
359
+ }
360
+ function summarizeProfile(name, value) {
361
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
362
+ return { name, environment: "invalid", blocks: [] };
363
+ }
364
+ const profile = value;
365
+ const env = profile["environment"];
366
+ const environment = env === void 0 ? "real" : env === "gov" ? "gov" : "invalid";
367
+ const blocks = Object.keys(profile).filter((key) => key !== "environment").sort();
368
+ return { name, environment, blocks };
369
+ }
370
+ async function inspectCredentialsFile() {
371
+ const result = await readJsonState(CREDENTIALS_PATH);
372
+ const permissions = result.state === "missing" ? void 0 : await inspectPermissions(CREDENTIALS_PATH);
373
+ const base = { path: CREDENTIALS_PATH, ...permissions ? { permissions } : {}, profiles: [] };
374
+ if (result.state !== "ok") {
375
+ return { ...base, state: result.state, ...result.reason ? { reason: result.reason } : {} };
376
+ }
377
+ if (!isCredentials(result.parsed)) {
378
+ return { ...base, state: "invalid", reason: "version: 1 \uACFC profiles \uD544\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4" };
379
+ }
380
+ const profiles = Object.entries(result.parsed.profiles).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([name, value]) => summarizeProfile(name, value));
381
+ return { ...base, state: "ok", profiles };
382
+ }
383
+ async function inspectConfigFile() {
384
+ const result = await readJsonState(CONFIG_PATH);
385
+ const base = { path: CONFIG_PATH, defaultProfile: null };
386
+ if (result.state !== "ok") {
387
+ return { ...base, state: result.state, ...result.reason ? { reason: result.reason } : {} };
388
+ }
389
+ if (!isConfig(result.parsed)) {
390
+ return { ...base, state: "invalid", reason: "version: 1 \uC774 \uD544\uC694\uD569\uB2C8\uB2E4" };
391
+ }
392
+ const defaultProfile = result.parsed.defaultProfile;
393
+ return {
394
+ ...base,
395
+ state: "ok",
396
+ defaultProfile: typeof defaultProfile === "string" && defaultProfile !== "" ? defaultProfile : null
397
+ };
398
+ }
336
399
 
337
400
  // src/utils/terminal.ts
338
401
  function sanitizeForTerminal(value) {
@@ -357,6 +420,71 @@ function getRequestTimeoutMs() {
357
420
  return DEFAULT_TIMEOUT_MS;
358
421
  }
359
422
 
423
+ // src/api/proxy.ts
424
+ function nonEmpty(value) {
425
+ return value === void 0 || value === "" ? void 0 : value;
426
+ }
427
+ function readEnvPair(env, lower, upper) {
428
+ const lowerValue = nonEmpty(env[lower]);
429
+ if (lowerValue !== void 0) {
430
+ return { source: lower, value: lowerValue };
431
+ }
432
+ const upperValue = nonEmpty(env[upper]);
433
+ if (upperValue !== void 0) {
434
+ return { source: upper, value: upperValue };
435
+ }
436
+ return void 0;
437
+ }
438
+ function validateProxyUrl({ source, value }) {
439
+ let protocol;
440
+ try {
441
+ protocol = new URL(value).protocol;
442
+ } catch {
443
+ protocol = void 0;
444
+ }
445
+ if (protocol !== "http:" && protocol !== "https:") {
446
+ throw new NhnCloudCliError(
447
+ `${source} \uAC12\uC774 \uD504\uB85D\uC2DC URL \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. http:// \uB610\uB294 https:// \uB85C \uC2DC\uC791\uD558\uB294 URL \uC744 \uC9C0\uC815\uD558\uC138\uC694.`,
448
+ EXIT_PARAM_ERROR
449
+ );
450
+ }
451
+ return value;
452
+ }
453
+ function resolveProxySettings(option, env) {
454
+ const noProxy = readEnvPair(env, "no_proxy", "NO_PROXY")?.value;
455
+ const optionValue = nonEmpty(option);
456
+ let settings;
457
+ if (optionValue !== void 0) {
458
+ const proxy = validateProxyUrl({ source: "--proxy", value: optionValue });
459
+ settings = { httpProxy: proxy, httpsProxy: proxy };
460
+ } else {
461
+ const https = readEnvPair(env, "https_proxy", "HTTPS_PROXY");
462
+ const http = readEnvPair(env, "http_proxy", "HTTP_PROXY");
463
+ if (https === void 0 && http === void 0) {
464
+ return void 0;
465
+ }
466
+ settings = {};
467
+ if (https !== void 0) {
468
+ settings.httpsProxy = validateProxyUrl(https);
469
+ }
470
+ if (http !== void 0) {
471
+ settings.httpProxy = validateProxyUrl(http);
472
+ }
473
+ }
474
+ if (noProxy !== void 0) {
475
+ settings.noProxy = noProxy;
476
+ }
477
+ return settings;
478
+ }
479
+ async function installProxy(settings) {
480
+ const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici");
481
+ const options = {};
482
+ if (settings.httpProxy !== void 0) options.httpProxy = settings.httpProxy;
483
+ if (settings.httpsProxy !== void 0) options.httpsProxy = settings.httpsProxy;
484
+ if (settings.noProxy !== void 0) options.noProxy = settings.noProxy;
485
+ setGlobalDispatcher(new EnvHttpProxyAgent(options));
486
+ }
487
+
360
488
  // src/commands/parse-options.ts
361
489
  var POSITIVE_INTEGER_PATTERN = /^[1-9]\d*$/;
362
490
  var NON_NEGATIVE_INTEGER_PATTERN = /^(0|[1-9]\d*)$/;
@@ -400,14 +528,97 @@ function parseNonNegativeIntegerOption(value, flag) {
400
528
 
401
529
  // src/commands/commander-errors.ts
402
530
  var import_commander = require("commander");
403
- function throwWithNormalizedExitCode(error) {
531
+
532
+ // src/commands/unknown-option-hint.ts
533
+ function normalizeName(name) {
534
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
535
+ }
536
+ function parseUnknownOptionName(message) {
537
+ return /unknown option '--([^'=]+)/.exec(message)?.[1];
538
+ }
539
+ function findReferencedArgument(optionName, commandPath2, argumentNames) {
540
+ const option = normalizeName(optionName);
541
+ const normalized = argumentNames.map(normalizeName);
542
+ const exact = normalized.indexOf(option);
543
+ if (exact !== -1) {
544
+ return argumentNames[exact];
545
+ }
546
+ const prefixed = argumentNames.filter((_, i) => normalized[i].startsWith(`${option}-`));
547
+ const candidates = new Set(prefixed.length === 1 ? prefixed : []);
548
+ const segments = commandPath2.slice(1).map(normalizeName);
549
+ argumentNames.forEach((name, i) => {
550
+ const otherArguments = normalized.filter((_, j) => j !== i);
551
+ const matched = segments.some(
552
+ (segment) => !otherArguments.includes(segment) && option === `${segment}-${normalized[i]}`
553
+ );
554
+ if (matched) {
555
+ candidates.add(name);
556
+ }
557
+ });
558
+ return candidates.size === 1 ? [...candidates][0] : void 0;
559
+ }
560
+ function formatArgument(argument) {
561
+ const name = argument.variadic ? `${argument.name}...` : argument.name;
562
+ return argument.required ? `<${name}>` : `[${name}]`;
563
+ }
564
+ function buildUnknownOptionHint(input) {
565
+ if (input.message.includes("(Did you mean")) {
566
+ return "";
567
+ }
568
+ const optionName = parseUnknownOptionName(input.message);
569
+ if (optionName === void 0) {
570
+ return "";
571
+ }
572
+ const referenced = findReferencedArgument(
573
+ optionName,
574
+ input.commandPath,
575
+ input.arguments.map((argument2) => argument2.name)
576
+ );
577
+ const argument = input.arguments.find((candidate) => candidate.name === referenced);
578
+ if (argument === void 0) {
579
+ return "";
580
+ }
581
+ return `\uC548\uB0B4: --${optionName} \uB294 \uC635\uC158\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uC704\uCE58 \uC778\uC218 ${formatArgument(argument)} \uB85C \uC804\uB2EC\uD558\uC138\uC694.
582
+ \uC0AC\uC6A9\uBC95: ${input.commandPath.join(" ")} ${input.usage}
583
+ `;
584
+ }
585
+
586
+ // src/commands/commander-errors.ts
587
+ function writeUnknownOptionHint(command, error) {
588
+ const commandPath2 = [];
589
+ for (let current2 = command; current2 !== null; current2 = current2.parent) {
590
+ commandPath2.unshift(current2.name());
591
+ }
592
+ const hint = buildUnknownOptionHint({
593
+ message: error.message,
594
+ commandPath: commandPath2,
595
+ usage: command.usage(),
596
+ arguments: command.registeredArguments.map((argument) => ({
597
+ name: argument.name(),
598
+ required: argument.required,
599
+ variadic: argument.variadic
600
+ }))
601
+ });
602
+ if (hint === "") {
603
+ return;
604
+ }
605
+ const writeErr = command.configureOutput().writeErr;
606
+ if (writeErr) {
607
+ writeErr(hint);
608
+ } else {
609
+ process.stderr.write(hint);
610
+ }
611
+ }
612
+ function handleCommanderError(command, error) {
404
613
  if (error.code === "commander.missingMandatoryOptionValue") {
405
614
  error.exitCode = EXIT_PARAM_ERROR;
615
+ } else if (error.code === "commander.unknownOption") {
616
+ writeUnknownOptionHint(command, error);
406
617
  }
407
618
  throw error;
408
619
  }
409
620
  function configureCommanderExitCodes(root) {
410
- root.exitOverride(throwWithNormalizedExitCode);
621
+ root.exitOverride((error) => handleCommanderError(root, error));
411
622
  for (const command of root.commands) {
412
623
  configureCommanderExitCodes(command);
413
624
  }
@@ -422,7 +633,7 @@ function configureGlobalOptionsHelp(root) {
422
633
  }
423
634
 
424
635
  // src/version.ts
425
- var CLI_VERSION = true ? "0.19.0" : "0.0.0-dev";
636
+ var CLI_VERSION = true ? "0.21.0" : "0.0.0-dev";
426
637
 
427
638
  // src/commands/configure.ts
428
639
  var import_commander2 = require("commander");
@@ -555,6 +766,13 @@ function toNhnCloudCliError(err) {
555
766
  );
556
767
  }
557
768
  if (err instanceof Error) {
769
+ const cause = err.cause;
770
+ if (cause instanceof Error && cause.message !== "" && cause.message !== err.message) {
771
+ return new NhnCloudCliError(
772
+ `${err.message} (${sanitizeForTerminal(cause.message)})`,
773
+ EXIT_API_ERROR
774
+ );
775
+ }
558
776
  return new NhnCloudCliError(err.message, EXIT_API_ERROR);
559
777
  }
560
778
  return new NhnCloudCliError(String(err), EXIT_API_ERROR);
@@ -2523,14 +2741,14 @@ function compareByCodePoint(left, right) {
2523
2741
  return leftPoints.length - rightPoints.length;
2524
2742
  }
2525
2743
  function ensureRegularFile(filePath) {
2526
- let stat3;
2744
+ let stat4;
2527
2745
  try {
2528
- stat3 = (0, import_node_fs2.lstatSync)(filePath);
2746
+ stat4 = (0, import_node_fs2.lstatSync)(filePath);
2529
2747
  } catch (error) {
2530
2748
  const reason = error instanceof Error ? error.message : String(error);
2531
2749
  throw new NhnCloudCliError(`\uC2A4\uD0AC \uD30C\uC77C\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${filePath} (${reason})`, EXIT_PARAM_ERROR);
2532
2750
  }
2533
- if (stat3.isSymbolicLink() || !stat3.isFile()) {
2751
+ if (stat4.isSymbolicLink() || !stat4.isFile()) {
2534
2752
  throw new NhnCloudCliError(`\uC2A4\uD0AC \uCF58\uD150\uCE20\uB294 \uC815\uADDC \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: ${filePath}`, EXIT_PARAM_ERROR);
2535
2753
  }
2536
2754
  }
@@ -2547,21 +2765,21 @@ function collectReferenceFiles(referencesRoot, currentDir, files) {
2547
2765
  }
2548
2766
  for (const entry of entries) {
2549
2767
  const entryPath = import_node_path4.default.join(currentDir, entry.name);
2550
- let stat3;
2768
+ let stat4;
2551
2769
  try {
2552
- stat3 = (0, import_node_fs2.lstatSync)(entryPath);
2770
+ stat4 = (0, import_node_fs2.lstatSync)(entryPath);
2553
2771
  } catch (error) {
2554
2772
  const reason = error instanceof Error ? error.message : String(error);
2555
2773
  throw new NhnCloudCliError(`\uC2A4\uD0AC \uCF58\uD150\uCE20\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${entryPath} (${reason})`, EXIT_PARAM_ERROR);
2556
2774
  }
2557
- if (stat3.isSymbolicLink()) {
2775
+ if (stat4.isSymbolicLink()) {
2558
2776
  throw new NhnCloudCliError(`\uC2A4\uD0AC \uCF58\uD150\uCE20\uC5D0 \uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${entryPath}`, EXIT_PARAM_ERROR);
2559
2777
  }
2560
- if (stat3.isDirectory()) {
2778
+ if (stat4.isDirectory()) {
2561
2779
  collectReferenceFiles(referencesRoot, entryPath, files);
2562
2780
  continue;
2563
2781
  }
2564
- if (!stat3.isFile()) {
2782
+ if (!stat4.isFile()) {
2565
2783
  throw new NhnCloudCliError(`\uC2A4\uD0AC \uCF58\uD150\uCE20\uB294 \uC815\uADDC \uD30C\uC77C \uB610\uB294 \uB514\uB809\uD130\uB9AC\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${entryPath}`, EXIT_PARAM_ERROR);
2566
2784
  }
2567
2785
  files.push(`references/${import_node_path4.default.relative(referencesRoot, entryPath).split(import_node_path4.default.sep).join("/")}`);
@@ -2609,12 +2827,24 @@ function calculateSkillContentDigest(skillRoot) {
2609
2827
  var PACKAGE_NAME3 = "@bifos/nhncloud-cli";
2610
2828
  var SKILL_NAME2 = "nhncloud-cli";
2611
2829
  var DIGEST_HEX_PATTERN = /^[0-9a-f]{64}$/;
2612
- var defaultOperations = { rename: import_promises3.rename };
2830
+ var SKILL_AGENTS = ["claude", "codex"];
2831
+ var SKILL_AGENT_NAMES = { claude: "Claude Code", codex: "Codex" };
2832
+ var STATUS_PRIORITY = [
2833
+ "corrupt",
2834
+ "modified",
2835
+ "unmanaged",
2836
+ "broken",
2837
+ "outdated",
2838
+ "missing",
2839
+ "current"
2840
+ ];
2841
+ var PROTECTED_STATUSES = ["unmanaged", "modified", "corrupt"];
2842
+ var defaultOperations = { rename: import_promises3.rename, rm: import_promises3.rm };
2613
2843
  function sourcePath(context) {
2614
2844
  return import_node_path5.default.join(context.packageRoot, "skills", SKILL_NAME2);
2615
2845
  }
2616
- function destinationPath(context) {
2617
- return import_node_path5.default.join(context.homeDir, ".claude", "skills", SKILL_NAME2);
2846
+ function destinationPath(context, agent) {
2847
+ return agent === "claude" ? import_node_path5.default.join(context.homeDir, ".claude", "skills", SKILL_NAME2) : import_node_path5.default.join(context.homeDir, ".agents", "skills", SKILL_NAME2);
2618
2848
  }
2619
2849
  function resolveLinkTarget(linkPath, rawTarget) {
2620
2850
  return import_node_path5.default.isAbsolute(rawTarget) ? import_node_path5.default.normalize(rawTarget) : import_node_path5.default.resolve(import_node_path5.default.dirname(linkPath), rawTarget);
@@ -2725,16 +2955,74 @@ async function inspectRepository(targetPath, expected) {
2725
2955
  }
2726
2956
  return { status: "valid", installedVersion: manifest.packageVersion };
2727
2957
  }
2728
- function statusBase(context) {
2958
+ function statusBase(context, agent) {
2729
2959
  return {
2730
2960
  schemaVersion: 1,
2731
- destination: destinationPath(context),
2961
+ destination: destinationPath(context, agent),
2732
2962
  source: sourcePath(context),
2733
2963
  currentVersion: context.currentVersion
2734
2964
  };
2735
2965
  }
2736
- async function inspectSkill(context) {
2737
- const base = statusBase(context);
2966
+ async function managedRepositoryStatus(context, base, linkTarget, repositoryLocation, isExpectedPath) {
2967
+ const repositoryName = parseRepositoryName(repositoryLocation);
2968
+ if (!repositoryName) {
2969
+ return { ...base, status: "corrupt", linkTarget, managed: true };
2970
+ }
2971
+ const repository = await inspectRepository(repositoryLocation, repositoryName);
2972
+ if (repository.status === "modified" || repository.status === "corrupt") {
2973
+ return {
2974
+ ...base,
2975
+ status: repository.status,
2976
+ installedVersion: repository.installedVersion ?? repositoryName.version,
2977
+ linkTarget,
2978
+ managed: true
2979
+ };
2980
+ }
2981
+ if (repository.status === "missing") {
2982
+ return {
2983
+ ...base,
2984
+ status: "broken",
2985
+ installedVersion: repositoryName.version,
2986
+ linkTarget,
2987
+ managed: true
2988
+ };
2989
+ }
2990
+ const currentDigest = calculateSkillContentDigest(base.source);
2991
+ const isCurrent = repositoryName.version === context.currentVersion && repositoryName.digest === currentDigest && await isExpectedPath(repositoryPath(context, currentDigest));
2992
+ return {
2993
+ ...base,
2994
+ status: isCurrent ? "current" : "outdated",
2995
+ installedVersion: repository.installedVersion,
2996
+ linkTarget,
2997
+ managed: true
2998
+ };
2999
+ }
3000
+ async function optionalRealpath(targetPath) {
3001
+ try {
3002
+ return await (0, import_promises3.realpath)(targetPath);
3003
+ } catch (error) {
3004
+ if (isNodeError(error, "ENOENT")) {
3005
+ return void 0;
3006
+ }
3007
+ throw managerError(`\uC2A4\uD0AC \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${targetPath}`, error);
3008
+ }
3009
+ }
3010
+ async function inspectIndirectManagedLink(context, base, linkTarget) {
3011
+ const realRoot = await optionalRealpath(repositoryRoot(context));
3012
+ if (!realRoot) {
3013
+ return void 0;
3014
+ }
3015
+ const realTarget = await optionalRealpath(linkTarget);
3016
+ if (!realTarget || import_node_path5.default.dirname(realTarget) !== realRoot) {
3017
+ return void 0;
3018
+ }
3019
+ return managedRepositoryStatus(context, base, linkTarget, realTarget, async (expectedPath) => {
3020
+ const realExpected = await optionalRealpath(expectedPath);
3021
+ return realExpected !== void 0 && realExpected === realTarget;
3022
+ });
3023
+ }
3024
+ async function inspectAgentSkill(context, agent) {
3025
+ const base = statusBase(context, agent);
2738
3026
  const destinationStat = await optionalLstat(base.destination);
2739
3027
  if (!destinationStat) {
2740
3028
  return { ...base, status: "missing", managed: false };
@@ -2767,38 +3055,17 @@ async function inspectSkill(context) {
2767
3055
  };
2768
3056
  }
2769
3057
  if (isManagedRepositoryLocation(context, linkTarget)) {
2770
- if (!repositoryName) {
2771
- return { ...base, status: "corrupt", linkTarget, managed: true };
2772
- }
2773
- const repository = await inspectRepository(linkTarget, repositoryName);
2774
- if (repository.status === "modified" || repository.status === "corrupt") {
2775
- return {
2776
- ...base,
2777
- status: repository.status,
2778
- installedVersion: repository.installedVersion ?? repositoryName.version,
2779
- linkTarget,
2780
- managed: true
2781
- };
2782
- }
2783
- if (repository.status === "missing") {
2784
- return {
2785
- ...base,
2786
- status: "broken",
2787
- installedVersion: repositoryName.version,
2788
- linkTarget,
2789
- managed: true
2790
- };
2791
- }
2792
- const currentDigest = calculateSkillContentDigest(base.source);
2793
- const expectedPath = repositoryPath(context, currentDigest);
2794
- const isCurrent = repositoryName.version === context.currentVersion && repositoryName.digest === currentDigest && import_node_path5.default.resolve(linkTarget) === import_node_path5.default.resolve(expectedPath);
2795
- return {
2796
- ...base,
2797
- status: isCurrent ? "current" : "outdated",
2798
- installedVersion: repository.installedVersion,
3058
+ return managedRepositoryStatus(
3059
+ context,
3060
+ base,
2799
3061
  linkTarget,
2800
- managed: true
2801
- };
3062
+ linkTarget,
3063
+ async (expectedPath) => import_node_path5.default.resolve(linkTarget) === import_node_path5.default.resolve(expectedPath)
3064
+ );
3065
+ }
3066
+ const indirect = await inspectIndirectManagedLink(context, base, linkTarget);
3067
+ if (indirect) {
3068
+ return indirect;
2802
3069
  }
2803
3070
  const legacyPackage = targetStat.isDirectory() ? await readLegacyPackageMetadata(linkTarget) : void 0;
2804
3071
  if (legacyPackage) {
@@ -2812,6 +3079,14 @@ async function inspectSkill(context) {
2812
3079
  }
2813
3080
  return { ...base, status: "unmanaged", linkTarget, managed: false };
2814
3081
  }
3082
+ function aggregateStatus(statuses) {
3083
+ return STATUS_PRIORITY.find((token) => statuses.some((status) => status.status === token)) ?? "current";
3084
+ }
3085
+ async function inspectSkill(context) {
3086
+ const claude = await inspectAgentSkill(context, "claude");
3087
+ const codex = await inspectAgentSkill(context, "codex");
3088
+ return { ...claude, status: aggregateStatus([claude, codex]), agents: { claude, codex } };
3089
+ }
2815
3090
  function utcBackupSuffix() {
2816
3091
  return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${(0, import_node_crypto3.randomUUID)()}`;
2817
3092
  }
@@ -2908,35 +3183,123 @@ async function prepareRepository(context, force, operations) {
2908
3183
  await (0, import_promises3.rm)(staging, { recursive: true, force: true });
2909
3184
  }
2910
3185
  }
2911
- async function switchActiveLink(context, repository, previousStatus, force, operations) {
2912
- const destination = destinationPath(context);
2913
- const parent = import_node_path5.default.dirname(destination);
2914
- await (0, import_promises3.mkdir)(parent, { recursive: true });
2915
- const temporaryLink = import_node_path5.default.join(parent, `.${SKILL_NAME2}.link-${(0, import_node_crypto3.randomUUID)()}`);
2916
- const backupPaths = [];
2917
- await (0, import_promises3.symlink)(repository, temporaryLink);
2918
- try {
2919
- if (previousStatus.status === "unmanaged") {
2920
- if (!force) {
2921
- throw managerError(`\uAD00\uB9AC\uB418\uC9C0 \uC54A\uC740 \uC2A4\uD0AC \uD56D\uBAA9\uC774 \uC788\uC2B5\uB2C8\uB2E4. --force\uB85C \uBC31\uC5C5 \uD6C4 \uAD50\uCCB4\uD558\uC138\uC694: ${destination}`);
2922
- }
2923
- const backup = await backupPath(destination, operations);
2924
- backupPaths.push(backup);
3186
+ function temporaryLinkPath(destination) {
3187
+ return import_node_path5.default.join(import_node_path5.default.dirname(destination), `.${SKILL_NAME2}.link-${(0, import_node_crypto3.randomUUID)()}`);
3188
+ }
3189
+ async function planLinkSwitches(context, previous) {
3190
+ const switches = [];
3191
+ for (const agent of SKILL_AGENTS) {
3192
+ const agentStatus = previous.agents[agent];
3193
+ if (agentStatus.status === "current") {
3194
+ continue;
3195
+ }
3196
+ const destination = destinationPath(context, agent);
3197
+ const parent = import_node_path5.default.dirname(destination);
3198
+ await (0, import_promises3.mkdir)(parent, { recursive: true });
3199
+ const resolvedDestination = import_node_path5.default.join(await (0, import_promises3.realpath)(parent), import_node_path5.default.basename(destination));
3200
+ const sameTarget = switches.find((entry) => entry.resolvedDestination === resolvedDestination);
3201
+ if (sameTarget) {
3202
+ sameTarget.agents.push(agent);
3203
+ continue;
3204
+ }
3205
+ let previousRawTarget;
3206
+ if (agentStatus.status !== "missing" && agentStatus.status !== "unmanaged") {
2925
3207
  try {
2926
- await operations.rename(temporaryLink, destination);
3208
+ previousRawTarget = await (0, import_promises3.readlink)(destination);
2927
3209
  } catch (error) {
2928
- return await restoreBackup(backup, destination, operations, error);
3210
+ throw managerError(`\uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${destination}`, error);
2929
3211
  }
2930
- return backupPaths;
2931
3212
  }
3213
+ switches.push({
3214
+ agents: [agent],
3215
+ destination,
3216
+ resolvedDestination,
3217
+ previous: agentStatus,
3218
+ previousRawTarget,
3219
+ temporaryLink: temporaryLinkPath(destination),
3220
+ activated: false
3221
+ });
3222
+ }
3223
+ return switches;
3224
+ }
3225
+ async function rollbackLinkSwitches(switches, operations, temporaryLinks) {
3226
+ const failed = [];
3227
+ const preservedBackups = [];
3228
+ const removeEntry = operations.rm ?? defaultOperations.rm;
3229
+ let firstError;
3230
+ for (const entry of [...switches].reverse()) {
3231
+ try {
3232
+ if (entry.activated && entry.backup) {
3233
+ await removeEntry(entry.destination, { force: true });
3234
+ await operations.rename(entry.backup, entry.destination);
3235
+ } else if (entry.activated && entry.previousRawTarget !== void 0) {
3236
+ const restoreLink = temporaryLinkPath(entry.destination);
3237
+ temporaryLinks.push(restoreLink);
3238
+ await (0, import_promises3.symlink)(entry.previousRawTarget, restoreLink);
3239
+ await operations.rename(restoreLink, entry.destination);
3240
+ } else if (entry.activated && entry.previous.status === "missing") {
3241
+ await removeEntry(entry.destination, { force: true });
3242
+ } else if (!entry.activated && entry.backup) {
3243
+ await operations.rename(entry.backup, entry.destination);
3244
+ }
3245
+ } catch (error) {
3246
+ failed.push(entry.destination);
3247
+ if (entry.backup) {
3248
+ preservedBackups.push(entry.backup);
3249
+ }
3250
+ firstError ??= error;
3251
+ }
3252
+ }
3253
+ return { failed, preservedBackups, firstError };
3254
+ }
3255
+ async function switchActiveLinks(context, repository, previous, operations) {
3256
+ const switches = await planLinkSwitches(context, previous);
3257
+ const temporaryLinks = switches.map((entry) => entry.temporaryLink);
3258
+ const backupPaths = [];
3259
+ try {
3260
+ let failure;
3261
+ let failedAfterVerification = false;
2932
3262
  try {
2933
- await operations.rename(temporaryLink, destination);
3263
+ for (const entry of switches) {
3264
+ await (0, import_promises3.symlink)(repository, entry.temporaryLink);
3265
+ }
3266
+ for (const entry of switches) {
3267
+ if (entry.previous.status === "unmanaged") {
3268
+ entry.backup = await backupPath(entry.destination, operations);
3269
+ backupPaths.push(entry.backup);
3270
+ }
3271
+ await operations.rename(entry.temporaryLink, entry.destination);
3272
+ entry.activated = true;
3273
+ }
3274
+ const status = await inspectSkill(context);
3275
+ if (status.status === "current") {
3276
+ return { backupPaths, status };
3277
+ }
3278
+ failure = managerError(`\uC2A4\uD0AC \uC124\uCE58 \uD6C4 \uC0C1\uD0DC\uAC00 current\uAC00 \uC544\uB2D9\uB2C8\uB2E4: ${status.status}`);
3279
+ failedAfterVerification = true;
2934
3280
  } catch (error) {
2935
- throw managerError(`\uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC804\uD658\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${destination}`, error);
3281
+ failure = error;
3282
+ }
3283
+ const rollback = await rollbackLinkSwitches(switches, operations, temporaryLinks);
3284
+ if (rollback.failed.length > 0) {
3285
+ throw managerError(
3286
+ `\uC2A4\uD0AC \uC804\uD658\uC5D0 \uC2E4\uD328\uD588\uACE0 \uC77C\uBD80 \uACBD\uB85C\uB97C \uB418\uB3CC\uB9AC\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${rollback.failed.join(", ")}; \uBCF4\uC874\uD55C \uBC31\uC5C5: ${rollback.preservedBackups.join(", ") || "\uC5C6\uC74C"}; \uC804\uD658 \uC624\uB958: ${toReason(failure)}`,
3287
+ rollback.firstError
3288
+ );
3289
+ }
3290
+ if (failedAfterVerification) {
3291
+ throw failure;
2936
3292
  }
2937
- return backupPaths;
3293
+ const changed = switches.filter((entry) => entry.activated || entry.backup !== void 0);
3294
+ if (changed.length === 0) {
3295
+ throw managerError("\uC2A4\uD0AC \uC804\uD658\uC744 \uC2DC\uC791\uD558\uC9C0 \uBABB\uD574 \uC5B4\uB5A4 \uACBD\uB85C\uB3C4 \uBC14\uAFB8\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4", failure);
3296
+ }
3297
+ throw managerError(
3298
+ `\uC2A4\uD0AC \uC804\uD658\uC5D0 \uC2E4\uD328\uD574 \uBC14\uAFBC \uACBD\uB85C\uB97C \uC774\uC804 \uC0C1\uD0DC\uB85C \uB418\uB3CC\uB838\uC2B5\uB2C8\uB2E4: ${changed.map((entry) => entry.destination).join(", ")}`,
3299
+ failure
3300
+ );
2938
3301
  } finally {
2939
- await (0, import_promises3.rm)(temporaryLink, { force: true });
3302
+ await Promise.all(temporaryLinks.map((link) => (0, import_promises3.rm)(link, { force: true })));
2940
3303
  }
2941
3304
  }
2942
3305
  function installAction(status) {
@@ -2971,23 +3334,26 @@ async function installSkillInternal(context, options = {}, operations = defaultO
2971
3334
  backupPaths: []
2972
3335
  };
2973
3336
  }
2974
- if ((previousStatus.status === "unmanaged" || previousStatus.status === "modified" || previousStatus.status === "corrupt") && !force) {
2975
- throw managerError(`\uC2A4\uD0AC \uC0C1\uD0DC\uAC00 ${previousStatus.status}\uC785\uB2C8\uB2E4. --force\uB85C \uBC31\uC5C5 \uD6C4 \uAD50\uCCB4\uD558\uC138\uC694: ${previousStatus.destination}`);
3337
+ if (!force) {
3338
+ for (const agent of SKILL_AGENTS) {
3339
+ const agentStatus = previousStatus.agents[agent];
3340
+ if (PROTECTED_STATUSES.includes(agentStatus.status)) {
3341
+ throw managerError(
3342
+ `${SKILL_AGENT_NAMES[agent]} \uC2A4\uD0AC \uC0C1\uD0DC\uAC00 ${agentStatus.status}\uC785\uB2C8\uB2E4. --force\uB85C \uBC31\uC5C5 \uD6C4 \uAD50\uCCB4\uD558\uC138\uC694: ${agentStatus.destination}`
3343
+ );
3344
+ }
3345
+ }
2976
3346
  }
2977
3347
  const prepared = await prepareRepository(context, force, operations);
2978
- const linkBackups = await switchActiveLink(context, prepared.repository, previousStatus, force, operations);
2979
- const status = await inspectSkill(context);
2980
- if (status.status !== "current") {
2981
- throw managerError(`\uC2A4\uD0AC \uC124\uCE58 \uD6C4 \uC0C1\uD0DC\uAC00 current\uAC00 \uC544\uB2D9\uB2C8\uB2E4: ${status.status}`);
2982
- }
3348
+ const switched = await switchActiveLinks(context, prepared.repository, previousStatus, operations);
2983
3349
  return {
2984
3350
  schemaVersion: 1,
2985
3351
  action: installAction(previousStatus.status),
2986
3352
  changed: true,
2987
3353
  previousStatus,
2988
- status,
3354
+ status: switched.status,
2989
3355
  repositoryPath: prepared.repository,
2990
- backupPaths: [...prepared.backupPaths, ...linkBackups]
3356
+ backupPaths: [...prepared.backupPaths, ...switched.backupPaths]
2991
3357
  };
2992
3358
  }
2993
3359
  async function installSkill(context, options = {}, operations = defaultOperations) {
@@ -3000,73 +3366,185 @@ async function installSkill(context, options = {}, operations = defaultOperation
3000
3366
  throw managerError("\uC2A4\uD0AC\uC744 \uC124\uCE58\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", error);
3001
3367
  }
3002
3368
  }
3369
+ async function tryRestoreCandidate(candidate, destination, operations) {
3370
+ try {
3371
+ if (await optionalLstat(destination)) {
3372
+ return { restored: false, preserved: candidate };
3373
+ }
3374
+ await operations.rename(candidate, destination);
3375
+ return { restored: true };
3376
+ } catch (error) {
3377
+ return { restored: false, error, preserved: candidate };
3378
+ }
3379
+ }
3003
3380
  async function restoreUninstallCandidate(candidate, destination, operations, originalError) {
3004
- if (await optionalLstat(destination)) {
3381
+ const result = await tryRestoreCandidate(candidate, destination, operations);
3382
+ if (result.restored) {
3383
+ throw managerError(`\uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uD558\uC9C0 \uC54A\uACE0 \uC6D0\uB798 \uC704\uCE58\uB85C \uBCF5\uC6D0\uD588\uC2B5\uB2C8\uB2E4: ${destination}`, originalError);
3384
+ }
3385
+ if (result.error === void 0) {
3005
3386
  throw managerError(
3006
3387
  `\uD65C\uC131 \uC2A4\uD0AC \uACBD\uB85C\uAC00 \uB3D9\uC2DC\uC5D0 \uBCC0\uACBD\uB418\uC5B4 \uC81C\uAC70\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC774\uB3D9\uB41C \uD56D\uBAA9\uC744 \uBCF4\uC874\uD588\uC2B5\uB2C8\uB2E4: ${candidate}`,
3007
3388
  originalError
3008
3389
  );
3009
3390
  }
3391
+ throw managerError(
3392
+ `\uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uD558\uC9C0 \uBABB\uD588\uACE0 \uC6D0\uB798 \uC704\uCE58\uB85C \uBCF5\uC6D0\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC774\uB3D9\uB41C \uD56D\uBAA9: ${candidate}; \uC81C\uAC70 \uC624\uB958: ${toReason(originalError)}`,
3393
+ result.error
3394
+ );
3395
+ }
3396
+ async function tryRestoreRemovedLink(destination, rawTarget, operations) {
3397
+ const restoreLink = temporaryLinkPath(destination);
3010
3398
  try {
3011
- await operations.rename(candidate, destination);
3012
- } catch (restoreError) {
3013
- throw managerError(
3014
- `\uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uD558\uC9C0 \uBABB\uD588\uACE0 \uC6D0\uB798 \uC704\uCE58\uB85C \uBCF5\uC6D0\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC774\uB3D9\uB41C \uD56D\uBAA9: ${candidate}; \uC81C\uAC70 \uC624\uB958: ${toReason(originalError)}`,
3015
- restoreError
3016
- );
3399
+ if (await optionalLstat(destination)) {
3400
+ return { restored: false };
3401
+ }
3402
+ await (0, import_promises3.symlink)(rawTarget, restoreLink);
3403
+ await operations.rename(restoreLink, destination);
3404
+ return { restored: true };
3405
+ } catch (error) {
3406
+ return { restored: false, error };
3407
+ } finally {
3408
+ await (0, import_promises3.rm)(restoreLink, { force: true });
3017
3409
  }
3018
- throw managerError(`\uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uD558\uC9C0 \uC54A\uACE0 \uC6D0\uB798 \uC704\uCE58\uB85C \uBCF5\uC6D0\uD588\uC2B5\uB2C8\uB2E4: ${destination}`, originalError);
3019
3410
  }
3020
- async function uninstallSkill(context, operations = defaultOperations) {
3021
- const destination = destinationPath(context);
3022
- const status = await inspectSkill(context);
3023
- if (status.status === "missing") {
3024
- return "absent";
3411
+ async function planUninstallTargets(context) {
3412
+ const statuses = [];
3413
+ for (const agent of SKILL_AGENTS) {
3414
+ const status = await inspectAgentSkill(context, agent);
3415
+ if (status.status !== "missing" && (!status.managed || !status.linkTarget)) {
3416
+ throw managerError(`\uAD00\uB9AC\uB418\uC9C0 \uC54A\uC740 \uC2A4\uD0AC \uD56D\uBAA9\uC774\uBBC0\uB85C \uC5B4\uB290 \uACBD\uB85C\uB3C4 \uC81C\uAC70\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: ${status.destination}`);
3417
+ }
3418
+ statuses.push({ agent, status });
3025
3419
  }
3026
- if (!status.managed || !status.linkTarget) {
3027
- throw managerError(`\uAD00\uB9AC\uB418\uC9C0 \uC54A\uC740 \uC2A4\uD0AC \uD56D\uBAA9\uC774\uBBC0\uB85C \uC81C\uAC70\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: ${destination}`);
3420
+ const targets = [];
3421
+ for (const { agent, status } of statuses) {
3422
+ if (status.status === "missing" || !status.linkTarget) {
3423
+ continue;
3424
+ }
3425
+ const realParent = await optionalRealpath(import_node_path5.default.dirname(status.destination));
3426
+ if (!realParent) {
3427
+ continue;
3428
+ }
3429
+ const resolvedDestination = import_node_path5.default.join(realParent, import_node_path5.default.basename(status.destination));
3430
+ const sameTarget = targets.find((entry) => entry.resolvedDestination === resolvedDestination);
3431
+ if (sameTarget) {
3432
+ sameTarget.agents.push(agent);
3433
+ continue;
3434
+ }
3435
+ targets.push({
3436
+ agents: [agent],
3437
+ destination: status.destination,
3438
+ resolvedDestination,
3439
+ linkTarget: status.linkTarget
3440
+ });
3028
3441
  }
3442
+ return targets;
3443
+ }
3444
+ function uninstallRollbackError(results, originalError, handledLabel) {
3445
+ const failed = results.filter(({ result }) => !result.restored);
3446
+ if (failed.length === 0) {
3447
+ return managerError(
3448
+ `\uC2A4\uD0AC \uC81C\uAC70\uC5D0 \uC2E4\uD328\uD574 ${handledLabel}\uB97C \uC774\uC804 \uC0C1\uD0DC\uB85C \uB418\uB3CC\uB838\uC2B5\uB2C8\uB2E4: ${results.map(({ destination }) => destination).join(", ")}`,
3449
+ originalError
3450
+ );
3451
+ }
3452
+ const failedText = failed.map(
3453
+ ({ destination, result }) => result.preserved ? `${destination} (\uC774\uB3D9\uB41C \uD56D\uBAA9: ${result.preserved})` : destination
3454
+ ).join(", ");
3455
+ return managerError(
3456
+ `\uC2A4\uD0AC \uC81C\uAC70\uC5D0 \uC2E4\uD328\uD588\uACE0 \uC77C\uBD80 \uACBD\uB85C\uB97C \uB418\uB3CC\uB9AC\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${failedText}; \uC81C\uAC70 \uC624\uB958: ${toReason(originalError)}`,
3457
+ failed.find(({ result }) => result.error !== void 0)?.result.error
3458
+ );
3459
+ }
3460
+ async function rollbackMovedCandidates(moved, operations, originalError) {
3461
+ const results = [];
3462
+ for (const target of [...moved].reverse()) {
3463
+ results.push({
3464
+ destination: target.destination,
3465
+ result: await tryRestoreCandidate(target.candidate, target.destination, operations)
3466
+ });
3467
+ }
3468
+ throw uninstallRollbackError(results, originalError, "\uC62E\uAE34 \uACBD\uB85C");
3469
+ }
3470
+ async function moveUninstallCandidate(target, moved, operations) {
3471
+ const { destination } = target;
3029
3472
  const candidate = import_node_path5.default.join(import_node_path5.default.dirname(destination), `.${SKILL_NAME2}.uninstall-${(0, import_node_crypto3.randomUUID)()}`);
3030
3473
  try {
3031
3474
  await operations.rename(destination, candidate);
3032
3475
  } catch (error) {
3033
3476
  if (isNodeError(error, "ENOENT")) {
3034
- return "absent";
3477
+ return void 0;
3478
+ }
3479
+ const moveError = managerError(`\uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uC6A9 \uC784\uC2DC \uACBD\uB85C\uB85C \uC774\uB3D9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${destination}`, error);
3480
+ if (moved.length === 0) {
3481
+ throw moveError;
3035
3482
  }
3036
- throw managerError(`\uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uC6A9 \uC784\uC2DC \uACBD\uB85C\uB85C \uC774\uB3D9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${destination}`, error);
3483
+ return await rollbackMovedCandidates(moved, operations, moveError);
3037
3484
  }
3038
- let candidateTarget;
3485
+ let rawTarget;
3486
+ let verificationError;
3039
3487
  try {
3040
3488
  const candidateStat = await (0, import_promises3.lstat)(candidate);
3041
3489
  if (!candidateStat.isSymbolicLink()) {
3042
- return await restoreUninstallCandidate(
3043
- candidate,
3044
- destination,
3045
- operations,
3046
- new Error("\uAC80\uC0AC \uD6C4 \uD65C\uC131 \uACBD\uB85C\uAC00 \uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uAC00 \uC544\uB2CC \uD56D\uBAA9\uC73C\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.")
3047
- );
3490
+ verificationError = new Error("\uAC80\uC0AC \uD6C4 \uD65C\uC131 \uACBD\uB85C\uAC00 \uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uAC00 \uC544\uB2CC \uD56D\uBAA9\uC73C\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
3491
+ } else {
3492
+ rawTarget = await (0, import_promises3.readlink)(candidate);
3493
+ if (resolveLinkTarget(destination, rawTarget) !== target.linkTarget) {
3494
+ verificationError = new Error("\uAC80\uC0AC \uD6C4 \uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C \uB300\uC0C1\uC774 \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
3495
+ }
3048
3496
  }
3049
- candidateTarget = resolveLinkTarget(destination, await (0, import_promises3.readlink)(candidate));
3050
3497
  } catch (error) {
3051
- if (error instanceof NhnCloudCliError) {
3052
- throw error;
3498
+ verificationError = error;
3499
+ }
3500
+ if (verificationError !== void 0 || rawTarget === void 0) {
3501
+ if (moved.length === 0) {
3502
+ return await restoreUninstallCandidate(candidate, destination, operations, verificationError);
3053
3503
  }
3054
- return await restoreUninstallCandidate(candidate, destination, operations, error);
3504
+ return await rollbackMovedCandidates([...moved, { destination, candidate }], operations, verificationError);
3055
3505
  }
3056
- if (candidateTarget !== status.linkTarget) {
3057
- return await restoreUninstallCandidate(
3058
- candidate,
3059
- destination,
3060
- operations,
3061
- new Error("\uAC80\uC0AC \uD6C4 \uD65C\uC131 \uC2A4\uD0AC \uB9C1\uD06C \uB300\uC0C1\uC774 \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.")
3062
- );
3506
+ return { ...target, candidate, rawTarget };
3507
+ }
3508
+ async function removeUninstallCandidates(moved, operations) {
3509
+ const removeEntry = operations.rm ?? defaultOperations.rm;
3510
+ for (const [index, target] of moved.entries()) {
3511
+ try {
3512
+ await removeEntry(target.candidate, { force: true });
3513
+ } catch (originalError) {
3514
+ if (index === 0) {
3515
+ if (moved.length === 1) {
3516
+ return await restoreUninstallCandidate(target.candidate, target.destination, operations, originalError);
3517
+ }
3518
+ return await rollbackMovedCandidates(moved, operations, originalError);
3519
+ }
3520
+ const results = [];
3521
+ for (const [restoreIndex, entry] of moved.entries()) {
3522
+ const result = restoreIndex < index ? await tryRestoreRemovedLink(entry.destination, entry.rawTarget, operations) : await tryRestoreCandidate(entry.candidate, entry.destination, operations);
3523
+ results.push({ destination: entry.destination, result });
3524
+ }
3525
+ throw uninstallRollbackError(results, originalError, "\uC9C0\uC6B4 \uACBD\uB85C");
3526
+ }
3063
3527
  }
3064
- try {
3065
- await (0, import_promises3.rm)(candidate);
3066
- } catch (error) {
3067
- return await restoreUninstallCandidate(candidate, destination, operations, error);
3528
+ }
3529
+ async function uninstallSkill(context, operations = defaultOperations) {
3530
+ const targets = await planUninstallTargets(context);
3531
+ const moved = [];
3532
+ for (const target of targets) {
3533
+ const movedTarget = await moveUninstallCandidate(target, moved, operations);
3534
+ if (movedTarget) {
3535
+ moved.push(movedTarget);
3536
+ }
3068
3537
  }
3069
- return "removed";
3538
+ await removeUninstallCandidates(moved, operations);
3539
+ const agentResult = (agent) => ({
3540
+ action: moved.some((target) => target.agents.includes(agent)) ? "removed" : "absent",
3541
+ destination: destinationPath(context, agent)
3542
+ });
3543
+ const agents = { claude: agentResult("claude"), codex: agentResult("codex") };
3544
+ return {
3545
+ action: SKILL_AGENTS.some((agent) => agents[agent].action === "removed") ? "removed" : "absent",
3546
+ agents
3547
+ };
3070
3548
  }
3071
3549
 
3072
3550
  // src/formatters/table.ts
@@ -3142,9 +3620,16 @@ function outputSkillStatus(opts, status) {
3142
3620
  rows: [
3143
3621
  ["\uC0C1\uD0DC", status.status],
3144
3622
  ["\uD604\uC7AC \uBC84\uC804", status.currentVersion],
3145
- ["\uC124\uCE58 \uBC84\uC804", terminalText(status.installedVersion)],
3146
- ["\uC124\uCE58 \uACBD\uB85C", terminalText(status.destination)],
3147
- ["\uB9C1\uD06C \uB300\uC0C1", terminalText(status.linkTarget)],
3623
+ ...SKILL_AGENTS.flatMap((agent) => {
3624
+ const name = SKILL_AGENT_NAMES[agent];
3625
+ const agentStatus = status.agents[agent];
3626
+ return [
3627
+ [`${name} \uC0C1\uD0DC`, agentStatus.status],
3628
+ [`${name} \uC124\uCE58 \uBC84\uC804`, terminalText(agentStatus.installedVersion)],
3629
+ [`${name} \uC124\uCE58 \uACBD\uB85C`, terminalText(agentStatus.destination)],
3630
+ [`${name} \uB9C1\uD06C \uB300\uC0C1`, terminalText(agentStatus.linkTarget)]
3631
+ ];
3632
+ }),
3148
3633
  ["\uBCF5\uAD6C \uBA85\uB839", skillRecoveryCommand(status.status) ?? "\uC870\uCE58 \uC5C6\uC74C"]
3149
3634
  ],
3150
3635
  raw: status,
@@ -3159,7 +3644,10 @@ function outputSkillInstallResult(opts, result) {
3159
3644
  ["\uBCC0\uACBD \uC5EC\uBD80", result.changed ? "\uBCC0\uACBD\uB428" : "\uBCC0\uACBD \uC5C6\uC74C"],
3160
3645
  ["\uC774\uC804 \uC0C1\uD0DC", result.previousStatus.status],
3161
3646
  ["\uD604\uC7AC \uC0C1\uD0DC", result.status.status],
3162
- ["\uC124\uCE58 \uACBD\uB85C", terminalText(result.status.destination)],
3647
+ ...SKILL_AGENTS.map((agent) => [
3648
+ `${SKILL_AGENT_NAMES[agent]} \uC124\uCE58 \uACBD\uB85C`,
3649
+ terminalText(result.status.agents[agent].destination)
3650
+ ]),
3163
3651
  ["\uAD00\uB9AC \uC800\uC7A5\uC18C", terminalText(result.repositoryPath)],
3164
3652
  [
3165
3653
  "\uBC31\uC5C5 \uACBD\uB85C",
@@ -3175,7 +3663,14 @@ function outputSkillUninstallResult(opts, result) {
3175
3663
  headers: ["\uD56D\uBAA9", "\uAC12"],
3176
3664
  rows: [
3177
3665
  ["\uC791\uC5C5", result.action === "removed" ? "\uD65C\uC131 \uB9C1\uD06C \uC81C\uAC70 \uC644\uB8CC" : "\uD65C\uC131 \uB9C1\uD06C \uC5C6\uC74C"],
3178
- ["\uC124\uCE58 \uACBD\uB85C", terminalText(result.destination)],
3666
+ ...SKILL_AGENTS.flatMap((agent) => {
3667
+ const name = SKILL_AGENT_NAMES[agent];
3668
+ const agentResult = result.agents[agent];
3669
+ return [
3670
+ [`${name} \uC124\uCE58 \uACBD\uB85C`, terminalText(agentResult.destination)],
3671
+ [`${name} \uACB0\uACFC`, agentResult.action === "removed" ? "\uD65C\uC131 \uB9C1\uD06C \uC81C\uAC70" : "\uD65C\uC131 \uB9C1\uD06C \uC5C6\uC74C"]
3672
+ ];
3673
+ }),
3179
3674
  ["\uAD00\uB9AC \uC800\uC7A5\uC18C", "\uBCF4\uC874\uB428"]
3180
3675
  ],
3181
3676
  raw: result,
@@ -3198,7 +3693,7 @@ async function showStatus(cmd, dependencies) {
3198
3693
  }
3199
3694
  function createInstallCommand(name, dependencies) {
3200
3695
  return new import_commander3.Command(name).description(
3201
- name === "install" ? "Claude Code \uC2A4\uD0AC\uC744 \uAD00\uB9AC \uC800\uC7A5\uC18C\uC5D0 \uC124\uCE58\uD55C\uB2E4" : "Claude Code \uC2A4\uD0AC\uC744 \uD604\uC7AC CLI \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uD55C\uB2E4"
3696
+ name === "install" ? "Claude Code\xB7Codex \uC2A4\uD0AC\uC744 \uAD00\uB9AC \uC800\uC7A5\uC18C\uC5D0 \uC124\uCE58\uD55C\uB2E4" : "Claude Code\xB7Codex \uC2A4\uD0AC\uC744 \uD604\uC7AC CLI \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uD55C\uB2E4"
3202
3697
  ).option("--force", "\uC0AC\uC6A9\uC790 \uD56D\uBAA9\uC774\uB098 \uC218\uC815\xB7\uC190\uC0C1\uB41C \uAD00\uB9AC \uC800\uC7A5\uC18C\uB97C \uBC31\uC5C5 \uD6C4 \uAD50\uCCB4\uD55C\uB2E4").action(async (_localOpts, cmd) => {
3203
3698
  const opts = cmd.optsWithGlobals();
3204
3699
  const context = dependencies.createContext();
@@ -3207,96 +3702,280 @@ function createInstallCommand(name, dependencies) {
3207
3702
  });
3208
3703
  }
3209
3704
  function createSkillsCommand(dependencies = defaultDependencies) {
3210
- const statusCommand = new import_commander3.Command("status").description("Claude Code \uC2A4\uD0AC\uC758 \uAD00\uB9AC \uC0C1\uD0DC\uB97C \uC870\uD68C\uD55C\uB2E4").action(async (_opts, cmd) => showStatus(cmd, dependencies));
3211
- const uninstallCommand = new import_commander3.Command("uninstall").description("\uD65C\uC131 Claude Code \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uD55C\uB2E4").action(async (_opts, cmd) => {
3705
+ const statusCommand = new import_commander3.Command("status").description("Claude Code\xB7Codex \uC2A4\uD0AC\uC758 \uAD00\uB9AC \uC0C1\uD0DC\uB97C \uC870\uD68C\uD55C\uB2E4").action(async (_opts, cmd) => showStatus(cmd, dependencies));
3706
+ const uninstallCommand = new import_commander3.Command("uninstall").description("\uD65C\uC131 Claude Code\xB7Codex \uC2A4\uD0AC \uB9C1\uD06C\uB97C \uC81C\uAC70\uD55C\uB2E4").action(async (_opts, cmd) => {
3212
3707
  const opts = cmd.optsWithGlobals();
3213
3708
  const context = dependencies.createContext();
3214
- const previousStatus = await dependencies.inspect(context);
3215
- const action = await dependencies.uninstall(context);
3709
+ const uninstalled = await dependencies.uninstall(context);
3216
3710
  const result = {
3217
3711
  schemaVersion: 1,
3218
- action,
3219
- changed: action === "removed",
3712
+ action: uninstalled.action,
3713
+ changed: uninstalled.action === "removed",
3220
3714
  status: "missing",
3221
- destination: previousStatus.destination,
3222
- repositoryPreserved: true
3715
+ destination: uninstalled.agents.claude.destination,
3716
+ repositoryPreserved: true,
3717
+ agents: uninstalled.agents
3223
3718
  };
3224
3719
  outputSkillUninstallResult(opts, result);
3225
3720
  });
3226
- return new import_commander3.Command("skills").description(`Claude Code \uC2A4\uD0AC(${SKILL_NAME3}) \uC124\uCE58 \uAD00\uB9AC`).addCommand(statusCommand).addCommand(createInstallCommand("install", dependencies)).addCommand(createInstallCommand("update", dependencies)).addCommand(uninstallCommand).action(async (_opts, cmd) => showStatus(cmd, dependencies));
3721
+ return new import_commander3.Command("skills").description(`Claude Code\xB7Codex \uC2A4\uD0AC(${SKILL_NAME3}) \uC124\uCE58 \uAD00\uB9AC`).addCommand(statusCommand).addCommand(createInstallCommand("install", dependencies)).addCommand(createInstallCommand("update", dependencies)).addCommand(uninstallCommand).action(async (_opts, cmd) => showStatus(cmd, dependencies));
3227
3722
  }
3228
3723
  var skillsCommand = createSkillsCommand();
3229
3724
 
3230
3725
  // src/commands/doctor.ts
3231
3726
  var import_commander4 = require("commander");
3232
3727
  var import_chalk3 = __toESM(require("chalk"));
3233
- var import_promises4 = require("fs/promises");
3234
- var import_node_os4 = require("os");
3235
- var import_node_path6 = __toESM(require("path"));
3236
- var SKILL_NAME4 = "nhncloud-cli";
3237
- var CREDENTIALS_PATH2 = import_node_path6.default.join((0, import_node_os4.homedir)(), ".nhncloud", "credentials.json");
3238
- var CONFIG_PATH2 = import_node_path6.default.join((0, import_node_os4.homedir)(), ".nhncloud", "config.json");
3239
- async function readProfiles() {
3240
- let raw;
3728
+ var SKILL_ERROR_REASON = "\uACF5\uAC1C \uC2A4\uD0AC \uC0C1\uD0DC\uB97C \uD310\uC815\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4";
3729
+ var defaultDependencies2 = {
3730
+ inspectCredentials: inspectCredentialsFile,
3731
+ inspectConfig: inspectConfigFile,
3732
+ resolveProfile: resolveProfileName,
3733
+ createSkillContext: createSkillManagerContext,
3734
+ inspectSkill: inspectAgentSkill,
3735
+ connection: {
3736
+ getUserAccessKey,
3737
+ getIaasCredential,
3738
+ getOptionalServiceCredential,
3739
+ verifyUserAccessKey,
3740
+ verifyIaas,
3741
+ verifyLogncrash,
3742
+ verifyNcr,
3743
+ verifyNcs
3744
+ }
3745
+ };
3746
+ async function resolveTargetProfile(cliProfile, dependencies) {
3241
3747
  try {
3242
- raw = await (0, import_promises4.readFile)(CREDENTIALS_PATH2, "utf-8");
3243
- } catch {
3244
- return { profiles: [], parseError: false, missing: true };
3748
+ return await dependencies.resolveProfile(cliProfile);
3749
+ } catch (err) {
3750
+ if (err instanceof NhnCloudCliError) return null;
3751
+ throw err;
3245
3752
  }
3753
+ }
3754
+ async function inspectAgentSkillForDoctor(dependencies, agent) {
3246
3755
  try {
3247
- const parsed = JSON.parse(raw);
3248
- return { profiles: Object.keys(parsed.profiles ?? {}), parseError: false, missing: false };
3249
- } catch {
3250
- return { profiles: [], parseError: true, missing: false };
3756
+ const status = await dependencies.inspectSkill(dependencies.createSkillContext(), agent);
3757
+ return { ...status, recoveryCommand: skillRecoveryCommand(status.status) ?? null };
3758
+ } catch (err) {
3759
+ const code = typeof err === "object" && err !== null && "code" in err && typeof err.code === "string" ? err.code : void 0;
3760
+ return { status: "error", reason: code ? `${SKILL_ERROR_REASON}: ${code}` : SKILL_ERROR_REASON };
3251
3761
  }
3252
3762
  }
3253
- async function readDefaultProfile() {
3763
+ function connectionHasFailure(connection) {
3764
+ return connection.checked && Object.values(connection.targets).some((target) => target.status === "failed");
3765
+ }
3766
+ var CONNECTION_TARGETS = ["userAccessKey", "iaas", "logncrash", "ncr", "ncs"];
3767
+ function skipAll(reason) {
3768
+ return Object.fromEntries(
3769
+ CONNECTION_TARGETS.map((target) => [target, { status: "skipped", reason }])
3770
+ );
3771
+ }
3772
+ async function runVerify(verify) {
3254
3773
  try {
3255
- const raw = await (0, import_promises4.readFile)(CONFIG_PATH2, "utf-8");
3256
- const parsed = JSON.parse(raw);
3257
- return parsed.defaultProfile ?? null;
3258
- } catch {
3259
- return null;
3774
+ return await verify() ? { status: "ok" } : { status: "failed", reason: "auth" };
3775
+ } catch (err) {
3776
+ const exitCode = err instanceof NhnCloudCliError ? err.exitCode : EXIT_API_ERROR;
3777
+ return { status: "failed", reason: "error", exitCode };
3260
3778
  }
3261
3779
  }
3262
- var doctorCommand = new import_commander4.Command("doctor").description("\uC790\uACA9\uC99D\uBA85\xB7\uC2A4\uD0AC \uC124\uCE58 \uC0C1\uD0DC\uB97C \uC9C4\uB2E8\uD55C\uB2E4(\uC624\uD504\uB77C\uC778 \u2014 \uC5F0\uACB0 \uD14C\uC2A4\uD2B8\uB294 configure \uB85C)").action(async () => {
3263
- console.log(import_chalk3.default.bold("\n\u{1F50D} nhncloud-cli \uC9C4\uB2E8\n"));
3264
- const creds = await readProfiles();
3265
- const defaultProfile = await readDefaultProfile();
3266
- console.log(import_chalk3.default.bold("\uC790\uACA9\uC99D\uBA85"));
3267
- if (creds.missing) {
3268
- console.log(` ${import_chalk3.default.red("\u274C \uBBF8\uC124\uC815")} \u2014 ${CREDENTIALS_PATH2} \uC5C6\uC74C. nhncloud configure \uB85C \uC124\uC815\uD558\uC138\uC694.`);
3269
- } else if (creds.parseError) {
3270
- console.log(` ${import_chalk3.default.red("\u274C \uD30C\uC2F1 \uC624\uB958")} \u2014 ${CREDENTIALS_PATH2} \uAC00 \uC62C\uBC14\uB978 JSON \uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694.`);
3271
- } else if (creds.profiles.length === 0) {
3272
- console.log(` ${import_chalk3.default.yellow("\u26A0 profile \uC5C6\uC74C")} \u2014 nhncloud configure \uB85C \uC124\uC815\uD558\uC138\uC694.`);
3273
- } else {
3274
- console.log(` ${import_chalk3.default.green("\u2713 \uC124\uC815\uB428")} \u2014 profile: ${creds.profiles.join(", ")}`);
3275
- console.log(` \uAE30\uBCF8 profile: ${defaultProfile ? import_chalk3.default.green(defaultProfile) : import_chalk3.default.gray("\uBBF8\uC9C0\uC815 (default \uC0AC\uC6A9)")}`);
3780
+ async function readRequired(read) {
3781
+ try {
3782
+ return { value: await read() };
3783
+ } catch (err) {
3784
+ const reason = err instanceof NhnCloudCliError ? "not-configured" : "profile-unavailable";
3785
+ return { skipped: { status: "skipped", reason } };
3276
3786
  }
3277
- console.log(import_chalk3.default.bold("\nClaude Code \uC2A4\uD0AC"));
3278
- const skillStatus = await inspectSkill(createSkillManagerContext());
3279
- if (skillStatus.status === "current") {
3280
- console.log(
3281
- ` ${SKILL_NAME4}: ${import_chalk3.default.green(`\u2713 current (${skillStatus.installedVersion ?? skillStatus.currentVersion})`)}`
3282
- );
3283
- } else if (skillStatus.status === "missing") {
3284
- console.log(
3285
- ` ${SKILL_NAME4}: ${import_chalk3.default.gray(`\uBBF8\uC124\uCE58 \u2014 ${skillRecoveryCommand(skillStatus.status)}`)}`
3787
+ }
3788
+ async function readAppkey(service, profileName, deps) {
3789
+ try {
3790
+ const credential = await deps.getOptionalServiceCredential(service, profileName);
3791
+ if (!credential?.appkey) return { skipped: { status: "skipped", reason: "not-configured" } };
3792
+ return { appkey: credential.appkey };
3793
+ } catch {
3794
+ return { skipped: { status: "skipped", reason: "profile-unavailable" } };
3795
+ }
3796
+ }
3797
+ async function checkConnection(profileName, credentials, profileSummary, deps) {
3798
+ if (credentials.state !== "ok" || profileName === null || profileSummary === void 0 || profileSummary.environment === "invalid") {
3799
+ return skipAll("profile-unavailable");
3800
+ }
3801
+ if (profileSummary.environment === "gov") return skipAll("gov-unsupported");
3802
+ const uakRead = await readRequired(() => deps.getUserAccessKey(profileName));
3803
+ const uak = "value" in uakRead ? uakRead.value : void 0;
3804
+ const userAccessKey = "value" in uakRead ? await runVerify(() => deps.verifyUserAccessKey(uakRead.value)) : uakRead.skipped;
3805
+ const iaasRead = await readRequired(() => deps.getIaasCredential(profileName));
3806
+ const iaasResult = "value" in iaasRead ? await runVerify(() => deps.verifyIaas(iaasRead.value)) : iaasRead.skipped;
3807
+ const checkAppkeyService = async (service, verify, usesOAuth) => {
3808
+ const read = await readAppkey(service, profileName, deps);
3809
+ if ("skipped" in read) return read.skipped;
3810
+ if (uak === void 0) return { status: "skipped", reason: "uak-missing" };
3811
+ if (usesOAuth && userAccessKey.status !== "ok") return { status: "skipped", reason: "uak-failed" };
3812
+ return runVerify(() => verify(uak, read.appkey));
3813
+ };
3814
+ const logncrash = await checkAppkeyService("logncrash", deps.verifyLogncrash, true);
3815
+ const ncr = await checkAppkeyService("ncr", deps.verifyNcr, false);
3816
+ const ncs = await checkAppkeyService("ncs", deps.verifyNcs, true);
3817
+ return { userAccessKey, iaas: iaasResult, logncrash, ncr, ncs };
3818
+ }
3819
+ async function buildDoctorReport(options, dependencies) {
3820
+ const credentials = await dependencies.inspectCredentials();
3821
+ const config = await dependencies.inspectConfig();
3822
+ const profileName = await resolveTargetProfile(options.profile, dependencies);
3823
+ const profileSummary = credentials.state === "ok" && profileName !== null ? credentials.profiles.find((summary) => summary.name === profileName) : void 0;
3824
+ const connection = options.checkConnection ? {
3825
+ checked: true,
3826
+ profile: profileName,
3827
+ targets: await checkConnection(profileName, credentials, profileSummary, dependencies.connection)
3828
+ } : { checked: false };
3829
+ const claude = await inspectAgentSkillForDoctor(dependencies, "claude");
3830
+ const codex = await inspectAgentSkillForDoctor(dependencies, "codex");
3831
+ const ready = profileSummary !== void 0 && profileSummary.environment !== "invalid" && profileSummary.blocks.length > 0 && !connectionHasFailure(connection);
3832
+ return {
3833
+ schemaVersion: 1,
3834
+ ready,
3835
+ credentials,
3836
+ config,
3837
+ profile: { name: profileName, exists: profileSummary !== void 0 },
3838
+ connection,
3839
+ skills: { agents: { claude, codex } }
3840
+ };
3841
+ }
3842
+ function line(text = "") {
3843
+ process.stdout.write(`${text}
3844
+ `);
3845
+ }
3846
+ function printCredentialsSection(credentials) {
3847
+ const filePath = sanitizeForTerminal(credentials.path);
3848
+ const reason = sanitizeForTerminal(credentials.reason ?? "");
3849
+ line(import_chalk3.default.bold("\uC790\uACA9\uC99D\uBA85"));
3850
+ switch (credentials.state) {
3851
+ case "missing":
3852
+ line(` ${import_chalk3.default.red("\u274C \uBBF8\uC124\uC815")} \u2014 ${filePath} \uC5C6\uC74C. nhncloud configure \uB85C \uC124\uC815\uD558\uC138\uC694.`);
3853
+ break;
3854
+ case "invalid":
3855
+ line(` ${import_chalk3.default.red("\u274C \uD615\uC2DD \uC624\uB958")} \u2014 ${filePath}: ${reason}`);
3856
+ break;
3857
+ case "unreadable":
3858
+ line(` ${import_chalk3.default.red("\u274C \uC77D\uAE30 \uC2E4\uD328")} \u2014 ${filePath}: ${reason}`);
3859
+ break;
3860
+ case "ok":
3861
+ if (credentials.profiles.length === 0) {
3862
+ line(` ${import_chalk3.default.yellow("\u26A0 profile \uC5C6\uC74C")} \u2014 nhncloud configure \uB85C \uC124\uC815\uD558\uC138\uC694.`);
3863
+ break;
3864
+ }
3865
+ line(` ${import_chalk3.default.green("\u2713 \uC124\uC815\uB428")} \u2014 profile:`);
3866
+ for (const summary of credentials.profiles) {
3867
+ const name = sanitizeForTerminal(summary.name);
3868
+ const blocks = summary.blocks.map(sanitizeForTerminal).join(", ") || "-";
3869
+ if (summary.environment === "invalid") {
3870
+ line(` ${name} ${import_chalk3.default.yellow("\u26A0 environment \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4")} [${blocks}]`);
3871
+ } else {
3872
+ line(` ${name}${summary.environment === "gov" ? " (gov)" : ""} [${blocks}]`);
3873
+ }
3874
+ }
3875
+ break;
3876
+ }
3877
+ if (credentials.permissions === "too-open") {
3878
+ line(` ${import_chalk3.default.yellow("\u26A0 \uD30C\uC77C \uAD8C\uD55C\uC774 \uB113\uC2B5\uB2C8\uB2E4")} \u2014 chmod 600 ${filePath}`);
3879
+ }
3880
+ }
3881
+ function printConfigSection(config) {
3882
+ line(import_chalk3.default.bold("\n\uC124\uC815 \uD30C\uC77C"));
3883
+ if (config.state === "invalid" || config.state === "unreadable") {
3884
+ line(
3885
+ ` ${import_chalk3.default.red(config.state === "invalid" ? "\u274C \uD615\uC2DD \uC624\uB958" : "\u274C \uC77D\uAE30 \uC2E4\uD328")} \u2014 ${sanitizeForTerminal(config.path)}: ${sanitizeForTerminal(config.reason ?? "")}`
3286
3886
  );
3887
+ }
3888
+ line(
3889
+ ` \uAE30\uBCF8 profile: ${config.defaultProfile ? import_chalk3.default.green(sanitizeForTerminal(config.defaultProfile)) : import_chalk3.default.gray("\uBBF8\uC9C0\uC815 (default \uC0AC\uC6A9)")}`
3890
+ );
3891
+ }
3892
+ function printProfileSection(profile) {
3893
+ line(import_chalk3.default.bold("\n\uB300\uC0C1 profile"));
3894
+ if (profile.name === null) {
3895
+ line(` ${import_chalk3.default.red("\u274C \uD574\uC11D \uC2E4\uD328")} \u2014 config.json \uC744 \uBA3C\uC800 \uACE0\uCE58\uC138\uC694.`);
3896
+ } else if (profile.exists) {
3897
+ line(` ${import_chalk3.default.green(`\u2713 ${sanitizeForTerminal(profile.name)}`)}`);
3287
3898
  } else {
3288
- console.log(
3289
- ` ${SKILL_NAME4}: ${import_chalk3.default.yellow(`\u26A0 ${skillStatus.status}`)} \u2014 ${skillRecoveryCommand(skillStatus.status)}`
3899
+ line(
3900
+ ` ${import_chalk3.default.yellow(`\u26A0 ${sanitizeForTerminal(profile.name)} \uC5C6\uC74C`)} \u2014 nhncloud configure \uB85C \uC124\uC815\uD558\uC138\uC694.`
3290
3901
  );
3291
3902
  }
3292
- console.log();
3293
- if (!creds.missing && !creds.parseError && creds.profiles.length > 0) {
3294
- console.log(import_chalk3.default.green("\u2713 \uAE30\uBCF8 \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
3903
+ }
3904
+ function printSkillLine(agent, skill) {
3905
+ const name = SKILL_AGENT_NAMES[agent];
3906
+ if (skill.status === "error") {
3907
+ line(` ${name}: ${import_chalk3.default.yellow("\u26A0 error")} \u2014 ${sanitizeForTerminal(skill.reason)}`);
3908
+ } else if (skill.status === "current") {
3909
+ const version = sanitizeForTerminal(skill.installedVersion ?? skill.currentVersion);
3910
+ line(` ${name}: ${import_chalk3.default.green(`\u2713 current (${version})`)}`);
3911
+ } else if (skill.status === "missing") {
3912
+ line(` ${name}: ${import_chalk3.default.gray(`\uBBF8\uC124\uCE58 \u2014 ${skill.recoveryCommand}`)}`);
3295
3913
  } else {
3296
- console.log(import_chalk3.default.yellow("\u26A0 \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: nhncloud configure"));
3914
+ line(` ${name}: ${import_chalk3.default.yellow(`\u26A0 ${skill.status}`)} \u2014 ${skill.recoveryCommand}`);
3297
3915
  }
3298
- console.log();
3299
- });
3916
+ }
3917
+ function printSkillSection(agents) {
3918
+ line(import_chalk3.default.bold("\n\uACF5\uAC1C \uC2A4\uD0AC"));
3919
+ for (const agent of SKILL_AGENTS) printSkillLine(agent, agents[agent]);
3920
+ }
3921
+ var SKIP_REASONS = {
3922
+ "not-configured": "\uC124\uC815 \uC5C6\uC74C",
3923
+ "uak-missing": "\uACF5\uD1B5 userAccessKey \uC5C6\uC74C",
3924
+ "uak-failed": "userAccessKey \uD655\uC778\uC774 \uC131\uACF5\uD558\uC9C0 \uC54A\uC544 \uAC74\uB108\uB700",
3925
+ "gov-unsupported": "\uACF5\uACF5\uB9DD profile \uC740 \uC5F0\uACB0 \uD655\uC778\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC74C",
3926
+ "profile-unavailable": "\uB300\uC0C1 profile \uC744 \uC77D\uC744 \uC218 \uC5C6\uC74C"
3927
+ };
3928
+ function printConnectionSection(connection) {
3929
+ if (!connection.checked) return;
3930
+ line(import_chalk3.default.bold("\n\uC5F0\uACB0 \uD655\uC778"));
3931
+ for (const target of CONNECTION_TARGETS) {
3932
+ const result = connection.targets[target];
3933
+ const label = ` ${target}:`;
3934
+ if (result.status === "ok") {
3935
+ const region = target === "ncr" || target === "ncs" ? " (kr1)" : "";
3936
+ line(`${label} ${import_chalk3.default.green(`\u2713 \uC131\uACF5${region}`)}`);
3937
+ } else if (result.status === "failed" && result.reason === "auth") {
3938
+ line(`${label} ${import_chalk3.default.red("\u274C \uC778\uC99D \uC2E4\uD328")} \u2014 \uD0A4\uB098 appkey \uB97C \uD655\uC778\uD558\uC138\uC694.`);
3939
+ } else if (result.status === "failed") {
3940
+ line(`${label} ${import_chalk3.default.red(`\u274C \uC624\uB958 (\uC885\uB8CC \uCF54\uB4DC ${result.exitCode ?? EXIT_API_ERROR})`)}`);
3941
+ } else {
3942
+ const reason = result.reason ? SKIP_REASONS[result.reason] ?? result.reason : "";
3943
+ line(`${label} ${import_chalk3.default.gray(`\uAC74\uB108\uB700 \u2014 ${reason}`)}`);
3944
+ }
3945
+ }
3946
+ }
3947
+ function printText(report) {
3948
+ line(import_chalk3.default.bold("\n\u{1F50D} nhncloud-cli \uC9C4\uB2E8\n"));
3949
+ printCredentialsSection(report.credentials);
3950
+ printConfigSection(report.config);
3951
+ printProfileSection(report.profile);
3952
+ printConnectionSection(report.connection);
3953
+ printSkillSection(report.skills.agents);
3954
+ line();
3955
+ if (report.ready) {
3956
+ line(import_chalk3.default.green("\u2713 \uAE30\uBCF8 \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
3957
+ } else {
3958
+ line(import_chalk3.default.yellow("\u26A0 \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: nhncloud configure"));
3959
+ }
3960
+ line();
3961
+ }
3962
+ function createDoctorCommand(dependencies = defaultDependencies2) {
3963
+ return new import_commander4.Command("doctor").description("\uC790\uACA9\uC99D\uBA85\xB7\uC124\uC815\xB7\uC2A4\uD0AC \uC0C1\uD0DC\uB97C \uC9C4\uB2E8\uD55C\uB2E4(\uAE30\uBCF8 \uC624\uD504\uB77C\uC778, --check-connection \uC73C\uB85C \uC5F0\uACB0 \uD655\uC778)").option("--profile <name>", "\uC9C4\uB2E8\uD560 profile \uC774\uB984").option("--check-connection", "\uB300\uC0C1 profile \uC758 \uC790\uACA9\uC99D\uBA85\uC73C\uB85C \uC2E4\uC81C \uC5F0\uACB0\uC744 \uD655\uC778\uD55C\uB2E4 (\uC678\uBD80 API \uD638\uCD9C)").action(async (_opts, cmd) => {
3964
+ const opts = cmd.optsWithGlobals();
3965
+ const report = await buildDoctorReport(
3966
+ { profile: opts.profile, checkConnection: opts.checkConnection },
3967
+ dependencies
3968
+ );
3969
+ if (opts.json) {
3970
+ printJson(report);
3971
+ } else if (opts.quiet) {
3972
+ process.stdout.write(report.ready ? "ready\n" : "not-ready\n");
3973
+ } else {
3974
+ printText(report);
3975
+ }
3976
+ });
3977
+ }
3978
+ var doctorCommand = createDoctorCommand();
3300
3979
 
3301
3980
  // src/commands/logncrash/search.ts
3302
3981
  var import_commander5 = require("commander");
@@ -3545,19 +4224,19 @@ var VALID_LEVELS = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
3545
4224
  function resolveBody(opts) {
3546
4225
  if (opts.body !== void 0) return opts.body;
3547
4226
  if (opts.file !== void 0) {
3548
- let stat3;
4227
+ let stat4;
3549
4228
  try {
3550
- stat3 = (0, import_node_fs3.statSync)(opts.file);
4229
+ stat4 = (0, import_node_fs3.statSync)(opts.file);
3551
4230
  } catch (e) {
3552
4231
  const reason = e.code ?? (e instanceof Error ? e.message : String(e));
3553
4232
  throw new NhnCloudCliError(`\uB85C\uADF8 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${opts.file} (${reason})`, EXIT_PARAM_ERROR);
3554
4233
  }
3555
- if (!stat3.isFile()) {
4234
+ if (!stat4.isFile()) {
3556
4235
  throw new NhnCloudCliError(`\uB85C\uADF8 \uD30C\uC77C\uC774 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${opts.file}`, EXIT_PARAM_ERROR);
3557
4236
  }
3558
- if (stat3.size > MAX_LOG_BYTES) {
4237
+ if (stat4.size > MAX_LOG_BYTES) {
3559
4238
  throw new NhnCloudCliError(
3560
- `\uB85C\uADF8 \uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4: ${stat3.size} \uBC14\uC774\uD2B8 (\uD55C\uB3C4 ${MAX_LOG_BYTES} \uBC14\uC774\uD2B8).`,
4239
+ `\uB85C\uADF8 \uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4: ${stat4.size} \uBC14\uC774\uD2B8 (\uD55C\uB3C4 ${MAX_LOG_BYTES} \uBC14\uC774\uD2B8).`,
3561
4240
  EXIT_PARAM_ERROR
3562
4241
  );
3563
4242
  }
@@ -3628,22 +4307,22 @@ credentials.json \uC5D0 "appkey": "<appkey>" \uB97C \uCD94\uAC00\uD558\uC138\uC6
3628
4307
 
3629
4308
  // src/commands/logncrash/export.ts
3630
4309
  var import_commander7 = require("commander");
3631
- var import_promises5 = require("fs/promises");
4310
+ var import_promises4 = require("fs/promises");
3632
4311
  var import_node_fs4 = require("fs");
3633
4312
  var import_node_crypto4 = require("crypto");
3634
4313
  var MAX_TOTAL = 1e5;
3635
- var defaultExportFileOps = { appendFile: import_promises5.appendFile, rename: import_promises5.rename };
3636
- function assertWritable(path5, force) {
4314
+ var defaultExportFileOps = { appendFile: import_promises4.appendFile, rename: import_promises4.rename };
4315
+ function assertWritable(path4, force) {
3637
4316
  if (force) return;
3638
4317
  try {
3639
- (0, import_node_fs4.statSync)(path5);
4318
+ (0, import_node_fs4.statSync)(path4);
3640
4319
  } catch (e) {
3641
4320
  if (e.code === "ENOENT") return;
3642
4321
  const reason = e.code ?? (e instanceof Error ? e.message : String(e));
3643
- throw new NhnCloudCliError(`--output \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path5} (${reason})`, EXIT_PARAM_ERROR);
4322
+ throw new NhnCloudCliError(`--output \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path4} (${reason})`, EXIT_PARAM_ERROR);
3644
4323
  }
3645
4324
  throw new NhnCloudCliError(
3646
- `--output \uB300\uC0C1\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path5}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC4F0\uC138\uC694.`,
4325
+ `--output \uB300\uC0C1\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path4}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC4F0\uC138\uC694.`,
3647
4326
  EXIT_PARAM_ERROR
3648
4327
  );
3649
4328
  }
@@ -3754,7 +4433,7 @@ function createExportCommand(finalizeOps) {
3754
4433
  } catch (err) {
3755
4434
  if (failureOrigin !== "search" || !(err instanceof LogncrashServerError)) throw err;
3756
4435
  await endAndClose(stream);
3757
- await (0, import_promises5.truncate)(tmp, checkpoint.bytesWritten);
4436
+ await (0, import_promises4.truncate)(tmp, checkpoint.bytesWritten);
3758
4437
  stream = (0, import_node_fs4.createWriteStream)(tmp, { encoding: "utf-8", flags: "a" });
3759
4438
  bytePosition = checkpoint.bytesWritten;
3760
4439
  count = checkpoint.count;
@@ -3781,14 +4460,14 @@ function createExportCommand(finalizeOps) {
3781
4460
  stream.end();
3782
4461
  });
3783
4462
  if (count === 0) {
3784
- await (0, import_promises5.rm)(tmp, { force: true }).catch(() => {
4463
+ await (0, import_promises4.rm)(tmp, { force: true }).catch(() => {
3785
4464
  });
3786
4465
  throw failure;
3787
4466
  }
3788
4467
  const partial = `${opts.output}.partial`;
3789
4468
  try {
3790
- if (format === "json") await (0, import_promises5.appendFile)(tmp, "]\n");
3791
- await (0, import_promises5.rename)(tmp, partial);
4469
+ if (format === "json") await (0, import_promises4.appendFile)(tmp, "]\n");
4470
+ await (0, import_promises4.rename)(tmp, partial);
3792
4471
  } catch {
3793
4472
  process.stderr.write(
3794
4473
  `\uACBD\uACE0: \uBD80\uBD84 \uACB0\uACFC\uB97C ${partial} \uC5D0 \uB0A8\uAE30\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC784\uC2DC \uD30C\uC77C: ${tmp}
@@ -3830,7 +4509,7 @@ function createExportCommand(finalizeOps) {
3830
4509
  throw new NhnCloudCliError(message, EXIT_PARAM_ERROR);
3831
4510
  }
3832
4511
  stopSpinner(true, `${count}\uAC74 \uCD94\uCD9C \uC644\uB8CC`);
3833
- await (0, import_promises5.rm)(`${opts.output}.partial`, { force: true }).catch(() => {
4512
+ await (0, import_promises4.rm)(`${opts.output}.partial`, { force: true }).catch(() => {
3834
4513
  });
3835
4514
  if (count >= MAX_TOTAL && (total > MAX_TOTAL || hasUnqueriedWindows)) {
3836
4515
  const totalDescription = hasUnqueriedWindows ? `\uC870\uD68C\uD55C \uCC3D\uC5D0\uC11C ${total}\uAC74\uC744 \uD655\uC778\uD588\uACE0 \uB0A8\uC740 \uCC3D\uC744 \uC870\uD68C\uD558\uC9C0 \uC54A\uC740 \uCC44` : `\uC804\uCCB4 ${total}\uAC74 \uC911`;
@@ -4448,7 +5127,7 @@ var binariesCommand = new import_commander15.Command("binaries").description("\u
4448
5127
  // src/commands/deploy/upload.ts
4449
5128
  var import_commander16 = require("commander");
4450
5129
  var import_node_fs5 = require("fs");
4451
- var import_node_path7 = require("path");
5130
+ var import_node_path6 = require("path");
4452
5131
  var MAX_UPLOAD_BYTES = 512 * 1024 * 1024;
4453
5132
  function parsePositiveInt2(value, flag) {
4454
5133
  if (value === void 0) return void 0;
@@ -4468,9 +5147,9 @@ var uploadCommand = new import_commander16.Command("upload").description("\uB85C
4468
5147
  }
4469
5148
  const artifactId = requireCoordinate(opts.artifactId, "--artifact-id");
4470
5149
  const filePath = opts.file;
4471
- let stat3;
5150
+ let stat4;
4472
5151
  try {
4473
- stat3 = (0, import_node_fs5.statSync)(filePath);
5152
+ stat4 = (0, import_node_fs5.statSync)(filePath);
4474
5153
  } catch (e) {
4475
5154
  const reason = e.code ?? (e instanceof Error ? e.message : String(e));
4476
5155
  throw new NhnCloudCliError(
@@ -4478,17 +5157,17 @@ var uploadCommand = new import_commander16.Command("upload").description("\uB85C
4478
5157
  EXIT_PARAM_ERROR
4479
5158
  );
4480
5159
  }
4481
- if (!stat3.isFile()) {
5160
+ if (!stat4.isFile()) {
4482
5161
  throw new NhnCloudCliError(`--file \uC774 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${filePath}`, EXIT_PARAM_ERROR);
4483
5162
  }
4484
- if (stat3.size > MAX_UPLOAD_BYTES) {
5163
+ if (stat4.size > MAX_UPLOAD_BYTES) {
4485
5164
  throw new NhnCloudCliError(
4486
- `--file \uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat3.size} \uBC14\uC774\uD2B8). \uC5C5\uB85C\uB4DC \uD55C\uB3C4 ${MAX_UPLOAD_BYTES} \uBC14\uC774\uD2B8.`,
5165
+ `--file \uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat4.size} \uBC14\uC774\uD2B8). \uC5C5\uB85C\uB4DC \uD55C\uB3C4 ${MAX_UPLOAD_BYTES} \uBC14\uC774\uD2B8.`,
4487
5166
  EXIT_PARAM_ERROR
4488
5167
  );
4489
5168
  }
4490
5169
  const fileBuffer = (0, import_node_fs5.readFileSync)(filePath);
4491
- const fileName = (0, import_node_path7.basename)(filePath);
5170
+ const fileName = (0, import_node_path6.basename)(filePath);
4492
5171
  const { client, profileName } = await createDeployClient(opts.profile);
4493
5172
  const appKey = await resolveDeployAppKey(profileName);
4494
5173
  startSpinner("\uBC14\uC774\uB108\uB9AC \uC5C5\uB85C\uB4DC \uC911...");
@@ -4534,20 +5213,20 @@ function parsePositiveInt3(value, flag) {
4534
5213
  }
4535
5214
  return Number(value);
4536
5215
  }
4537
- function assertWritable2(path5, force) {
5216
+ function assertWritable2(path4, force) {
4538
5217
  if (force) return;
4539
5218
  try {
4540
- (0, import_node_fs6.statSync)(path5);
5219
+ (0, import_node_fs6.statSync)(path4);
4541
5220
  } catch (e) {
4542
5221
  if (e.code === "ENOENT") return;
4543
5222
  const reason = e.code ?? (e instanceof Error ? e.message : String(e));
4544
5223
  throw new NhnCloudCliError(
4545
- `-o \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path5} (${reason})`,
5224
+ `-o \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path4} (${reason})`,
4546
5225
  EXIT_PARAM_ERROR
4547
5226
  );
4548
5227
  }
4549
5228
  throw new NhnCloudCliError(
4550
- `-o \uB300\uC0C1\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path5}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC4F0\uC138\uC694.`,
5229
+ `-o \uB300\uC0C1\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path4}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC4F0\uC138\uC694.`,
4551
5230
  EXIT_PARAM_ERROR
4552
5231
  );
4553
5232
  }
@@ -4890,8 +5569,8 @@ var InstanceClient = class {
4890
5569
  return this.serverAction(id, { revertResize: null });
4891
5570
  }
4892
5571
  async listFlavors(params = {}) {
4893
- const path5 = params.detail ? "/flavors/detail" : "/flavors";
4894
- const url = `${this.computeEndpoint}${path5}`;
5572
+ const path4 = params.detail ? "/flavors/detail" : "/flavors";
5573
+ const url = `${this.computeEndpoint}${path4}`;
4895
5574
  const searchParams = {};
4896
5575
  if (params.minDisk !== void 0) searchParams["minDisk"] = params.minDisk;
4897
5576
  if (params.minRam !== void 0) searchParams["minRam"] = params.minRam;
@@ -6571,9 +7250,9 @@ var createCommand5 = new import_commander33.Command("create").description("\uC77
6571
7250
  }
6572
7251
  let userDataBase64;
6573
7252
  if (opts.userData !== void 0) {
6574
- let stat3;
7253
+ let stat4;
6575
7254
  try {
6576
- stat3 = (0, import_node_fs7.statSync)(opts.userData);
7255
+ stat4 = (0, import_node_fs7.statSync)(opts.userData);
6577
7256
  } catch (e) {
6578
7257
  const reason = e.code ?? (e instanceof Error ? e.message : String(e));
6579
7258
  throw new NhnCloudCliError(
@@ -6581,15 +7260,15 @@ var createCommand5 = new import_commander33.Command("create").description("\uC77
6581
7260
  EXIT_PARAM_ERROR
6582
7261
  );
6583
7262
  }
6584
- if (!stat3.isFile()) {
7263
+ if (!stat4.isFile()) {
6585
7264
  throw new NhnCloudCliError(
6586
7265
  `--user-data \uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${opts.userData}`,
6587
7266
  EXIT_PARAM_ERROR
6588
7267
  );
6589
7268
  }
6590
- if (stat3.size > 49149) {
7269
+ if (stat4.size > 49149) {
6591
7270
  throw new NhnCloudCliError(
6592
- `--user-data \uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat3.size} \uBC14\uC774\uD2B8). base64 \uC778\uCF54\uB529 \uD6C4 65535 \uBC14\uC774\uD2B8 \uD55C\uB3C4\uB77C raw 49149 \uBC14\uC774\uD2B8\uAE4C\uC9C0\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.`,
7271
+ `--user-data \uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat4.size} \uBC14\uC774\uD2B8). base64 \uC778\uCF54\uB529 \uD6C4 65535 \uBC14\uC774\uD2B8 \uD55C\uB3C4\uB77C raw 49149 \uBC14\uC774\uD2B8\uAE4C\uC9C0\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.`,
6593
7272
  EXIT_PARAM_ERROR
6594
7273
  );
6595
7274
  }
@@ -6874,9 +7553,9 @@ var import_node_fs8 = require("fs");
6874
7553
  var import_node_crypto5 = require("crypto");
6875
7554
  var import_chalk12 = __toESM(require("chalk"));
6876
7555
  function resolvePublicKey(value) {
6877
- let stat3;
7556
+ let stat4;
6878
7557
  try {
6879
- stat3 = (0, import_node_fs8.statSync)(value);
7558
+ stat4 = (0, import_node_fs8.statSync)(value);
6880
7559
  } catch (e) {
6881
7560
  const err = e;
6882
7561
  if (err.code && err.code !== "ENOENT") {
@@ -6887,7 +7566,7 @@ function resolvePublicKey(value) {
6887
7566
  }
6888
7567
  return value;
6889
7568
  }
6890
- if (!stat3.isFile()) {
7569
+ if (!stat4.isFile()) {
6891
7570
  throw new NhnCloudCliError(`--public-key \uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${value}`, EXIT_PARAM_ERROR);
6892
7571
  }
6893
7572
  return (0, import_node_fs8.readFileSync)(value, "utf-8").trim();
@@ -7194,12 +7873,12 @@ var HarborClient = class {
7194
7873
  * ky.get() 이 Response 를 반환하므로 .json() 과 .headers.get("link") 를 함께 사용한다
7195
7874
  * (체이닝하면 헤더를 못 본다 — 기존 NCR client 의 .json<T>() 체이닝 패턴과 다름).
7196
7875
  */
7197
- async getAllPages(path5) {
7876
+ async getAllPages(path4) {
7198
7877
  const acc = [];
7199
7878
  let page = 1;
7200
7879
  try {
7201
7880
  for (; ; ) {
7202
- const url = `https://${this.host}${path5}?page=${page}&page_size=${PAGE_SIZE}`;
7881
+ const url = `https://${this.host}${path4}?page=${page}&page_size=${PAGE_SIZE}`;
7203
7882
  const res = await import_ky13.default.get(url, {
7204
7883
  headers: this.basicAuthHeaders(),
7205
7884
  retry: 0,
@@ -7439,15 +8118,15 @@ function commandPath(command) {
7439
8118
  return names.join(" ");
7440
8119
  }
7441
8120
  function collectCommand(command) {
7442
- const path5 = commandPath(command);
7443
- if (!path5 || path5 === "help") return null;
8121
+ const path4 = commandPath(command);
8122
+ if (!path4 || path4 === "help") return null;
7444
8123
  return {
7445
- path: path5,
8124
+ path: path4,
7446
8125
  description: command.description() || command.summary() || "",
7447
8126
  arguments: command.registeredArguments.map((arg) => arg.name()),
7448
8127
  options: command.options.filter((option) => !option.hidden).map((option) => option.flags),
7449
8128
  subcommands: command.commands.map((subcommand) => subcommand.name()).filter((name) => name !== "help"),
7450
- ...path5 === "commands" ? { metadata: true } : {}
8129
+ ...path4 === "commands" ? { metadata: true } : {}
7451
8130
  };
7452
8131
  }
7453
8132
  function collectCommandCatalog(program2) {
@@ -7486,7 +8165,7 @@ function createCommandsCommand(program2) {
7486
8165
  var import_commander48 = require("commander");
7487
8166
 
7488
8167
  // src/commands/nks/helpers.ts
7489
- var import_promises6 = require("fs/promises");
8168
+ var import_promises5 = require("fs/promises");
7490
8169
 
7491
8170
  // src/services/nks/client.ts
7492
8171
  var import_ky14 = __toESM(require("ky"));
@@ -7936,15 +8615,15 @@ var NksClient = class {
7936
8615
  throw toNhnCloudCliError(err);
7937
8616
  }
7938
8617
  }
7939
- async getFlatNamedResource(path5, errorMessage) {
7940
- const raw = await this.getJson(path5);
8618
+ async getFlatNamedResource(path4, errorMessage) {
8619
+ const raw = await this.getJson(path4);
7941
8620
  if (!isNksNamedResource(raw)) {
7942
8621
  throw new NhnCloudCliError(errorMessage, EXIT_API_ERROR);
7943
8622
  }
7944
8623
  return raw;
7945
8624
  }
7946
- async getJson(path5, searchParams) {
7947
- const url = `${this.nksEndpoint}${path5}`;
8625
+ async getJson(path4, searchParams) {
8626
+ const url = `${this.nksEndpoint}${path4}`;
7948
8627
  try {
7949
8628
  return await import_ky14.default.get(url, {
7950
8629
  headers: this.authHeaders(),
@@ -7956,15 +8635,15 @@ var NksClient = class {
7956
8635
  throw toNhnCloudCliError(err);
7957
8636
  }
7958
8637
  }
7959
- async requestUuid(method, path5, payload, errorMessage) {
7960
- const raw = await this.requestJson(method, path5, payload);
8638
+ async requestUuid(method, path4, payload, errorMessage) {
8639
+ const raw = await this.requestJson(method, path4, payload);
7961
8640
  if (!isNksUuidResponse(raw)) {
7962
8641
  throw new NhnCloudCliError(errorMessage, EXIT_API_ERROR);
7963
8642
  }
7964
8643
  return raw;
7965
8644
  }
7966
- async requestNoBody(method, path5, payload) {
7967
- const url = `${this.nksEndpoint}${path5}`;
8645
+ async requestNoBody(method, path4, payload) {
8646
+ const url = `${this.nksEndpoint}${path4}`;
7968
8647
  try {
7969
8648
  if (method === "delete") {
7970
8649
  await import_ky14.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS });
@@ -7975,8 +8654,8 @@ var NksClient = class {
7975
8654
  throw toNhnCloudCliError(err);
7976
8655
  }
7977
8656
  }
7978
- async requestJson(method, path5, payload) {
7979
- const url = `${this.nksEndpoint}${path5}`;
8657
+ async requestJson(method, path4, payload) {
8658
+ const url = `${this.nksEndpoint}${path4}`;
7980
8659
  try {
7981
8660
  const options = { headers: this.authHeaders(), json: payload, retry: 0, timeout: DEFAULT_TIMEOUT_MS };
7982
8661
  return method === "post" ? await import_ky14.default.post(url, options).json() : await import_ky14.default.patch(url, options).json();
@@ -8015,13 +8694,13 @@ async function resolveClusterUuid(client, cluster) {
8015
8694
  }
8016
8695
  return uuid;
8017
8696
  }
8018
- async function readJsonFile(path5) {
8697
+ async function readJsonFile(path4) {
8019
8698
  let raw;
8020
8699
  try {
8021
- raw = await (0, import_promises6.readFile)(path5, "utf-8");
8700
+ raw = await (0, import_promises5.readFile)(path4, "utf-8");
8022
8701
  } catch (err) {
8023
8702
  const message = err instanceof Error ? err.message : String(err);
8024
- throw new NhnCloudCliError(`JSON \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path5} (${message})`, EXIT_PARAM_ERROR);
8703
+ throw new NhnCloudCliError(`JSON \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path4} (${message})`, EXIT_PARAM_ERROR);
8025
8704
  }
8026
8705
  try {
8027
8706
  const parsed = JSON.parse(raw);
@@ -8031,15 +8710,15 @@ async function readJsonFile(path5) {
8031
8710
  return parsed;
8032
8711
  } catch (err) {
8033
8712
  const message = err instanceof Error ? err.message : String(err);
8034
- throw new NhnCloudCliError(`JSON \uD30C\uC77C \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${path5} (${message})`, EXIT_PARAM_ERROR);
8713
+ throw new NhnCloudCliError(`JSON \uD30C\uC77C \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${path4} (${message})`, EXIT_PARAM_ERROR);
8035
8714
  }
8036
8715
  }
8037
- async function readTextFile(path5) {
8716
+ async function readTextFile(path4) {
8038
8717
  try {
8039
- return await (0, import_promises6.readFile)(path5, "utf-8");
8718
+ return await (0, import_promises5.readFile)(path4, "utf-8");
8040
8719
  } catch (err) {
8041
8720
  const message = err instanceof Error ? err.message : String(err);
8042
- throw new NhnCloudCliError(`\uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path5} (${message})`, EXIT_PARAM_ERROR);
8721
+ throw new NhnCloudCliError(`\uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path4} (${message})`, EXIT_PARAM_ERROR);
8043
8722
  }
8044
8723
  }
8045
8724
  function parsePositiveInteger(value, optionName) {
@@ -8080,7 +8759,7 @@ var supportsCommand = new import_commander48.Command("supports").description("NK
8080
8759
  });
8081
8760
 
8082
8761
  // src/commands/nks/cluster.ts
8083
- var import_promises7 = require("fs/promises");
8762
+ var import_promises6 = require("fs/promises");
8084
8763
  var import_commander49 = require("commander");
8085
8764
  var import_chalk14 = __toESM(require("chalk"));
8086
8765
  var listCommand8 = new import_commander49.Command("list").description("NKS \uD074\uB7EC\uC2A4\uD130 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
@@ -8171,18 +8850,18 @@ async function confirmDangerousAction(message, yes) {
8171
8850
  }
8172
8851
  return ok;
8173
8852
  }
8174
- async function writeKubeconfigFile(path5, contents, force) {
8853
+ async function writeKubeconfigFile(path4, contents, force) {
8175
8854
  try {
8176
- await (0, import_promises7.writeFile)(path5, contents, {
8855
+ await (0, import_promises6.writeFile)(path4, contents, {
8177
8856
  encoding: "utf-8",
8178
8857
  mode: 384,
8179
8858
  flag: force ? "w" : "wx"
8180
8859
  });
8181
- await (0, import_promises7.chmod)(path5, 384);
8860
+ await (0, import_promises6.chmod)(path4, 384);
8182
8861
  } catch (err) {
8183
8862
  if (err instanceof Error && "code" in err && err.code === "EEXIST") {
8184
8863
  throw new NhnCloudCliError(
8185
- `\uD30C\uC77C\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path5}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC9C0\uC815\uD558\uC138\uC694.`,
8864
+ `\uD30C\uC77C\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path4}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC9C0\uC815\uD558\uC138\uC694.`,
8186
8865
  EXIT_PARAM_ERROR
8187
8866
  );
8188
8867
  }
@@ -9001,9 +9680,9 @@ async function resolveNcsClient(opts) {
9001
9680
  return { client: new NcsClient(accessToken, region, appKey), profileName };
9002
9681
  }
9003
9682
  function readJsonPayload(filePath) {
9004
- let stat3;
9683
+ let stat4;
9005
9684
  try {
9006
- stat3 = (0, import_node_fs9.statSync)(filePath);
9685
+ stat4 = (0, import_node_fs9.statSync)(filePath);
9007
9686
  } catch (err) {
9008
9687
  const reason = err.code ?? (err instanceof Error ? err.message : String(err));
9009
9688
  throw new NhnCloudCliError(
@@ -9011,12 +9690,12 @@ function readJsonPayload(filePath) {
9011
9690
  EXIT_PARAM_ERROR
9012
9691
  );
9013
9692
  }
9014
- if (!stat3.isFile()) {
9693
+ if (!stat4.isFile()) {
9015
9694
  throw new NhnCloudCliError(`--file \uC774 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${filePath}`, EXIT_PARAM_ERROR);
9016
9695
  }
9017
- if (stat3.size > MAX_JSON_INPUT_BYTES) {
9696
+ if (stat4.size > MAX_JSON_INPUT_BYTES) {
9018
9697
  throw new NhnCloudCliError(
9019
- `--file \uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat3.size} \uBC14\uC774\uD2B8). JSON spec \uD55C\uB3C4 ${MAX_JSON_INPUT_BYTES} \uBC14\uC774\uD2B8.`,
9698
+ `--file \uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat4.size} \uBC14\uC774\uD2B8). JSON spec \uD55C\uB3C4 ${MAX_JSON_INPUT_BYTES} \uBC14\uC774\uD2B8.`,
9020
9699
  EXIT_PARAM_ERROR
9021
9700
  );
9022
9701
  }
@@ -9747,7 +10426,7 @@ var malwareCommand = new import_commander54.Command("malware").description("NCS
9747
10426
  var import_commander55 = require("commander");
9748
10427
 
9749
10428
  // src/commands/apigateway/helpers.ts
9750
- var import_promises8 = require("fs/promises");
10429
+ var import_promises7 = require("fs/promises");
9751
10430
 
9752
10431
  // src/services/apigateway/client.ts
9753
10432
  var import_ky15 = __toESM(require("ky"));
@@ -10358,21 +11037,21 @@ function requireYes2(yes, operation) {
10358
11037
  }
10359
11038
  return true;
10360
11039
  }
10361
- async function readPluginConfigFile(path5) {
11040
+ async function readPluginConfigFile(path4) {
10362
11041
  let fileStat;
10363
11042
  try {
10364
- fileStat = await (0, import_promises8.stat)(path5);
11043
+ fileStat = await (0, import_promises7.stat)(path4);
10365
11044
  } catch (error) {
10366
11045
  const reason = fileErrorReason(error);
10367
11046
  throw new NhnCloudCliError(
10368
- `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${JSON.stringify(path5)} (${JSON.stringify(reason)}).`,
11047
+ `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${JSON.stringify(path4)} (${JSON.stringify(reason)}).`,
10369
11048
  EXIT_PARAM_ERROR
10370
11049
  );
10371
11050
  }
10372
11051
  if (!fileStat.isFile()) {
10373
11052
  const reason = fileStat.isDirectory() ? "EISDIR" : "EINVAL";
10374
11053
  throw new NhnCloudCliError(
10375
- `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uACBD\uB85C\uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${JSON.stringify(path5)} (${reason}).`,
11054
+ `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uACBD\uB85C\uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${JSON.stringify(path4)} (${reason}).`,
10376
11055
  EXIT_PARAM_ERROR
10377
11056
  );
10378
11057
  }
@@ -10384,11 +11063,11 @@ async function readPluginConfigFile(path5) {
10384
11063
  }
10385
11064
  let raw;
10386
11065
  try {
10387
- raw = await (0, import_promises8.readFile)(path5, "utf-8");
11066
+ raw = await (0, import_promises7.readFile)(path4, "utf-8");
10388
11067
  } catch (error) {
10389
11068
  const reason = fileErrorReason(error);
10390
11069
  throw new NhnCloudCliError(
10391
- `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${JSON.stringify(path5)} (${JSON.stringify(reason)}).`,
11070
+ `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${JSON.stringify(path4)} (${JSON.stringify(reason)}).`,
10392
11071
  EXIT_PARAM_ERROR
10393
11072
  );
10394
11073
  }
@@ -10399,7 +11078,7 @@ async function readPluginConfigFile(path5) {
10399
11078
  error instanceof Error ? error.message : String(error)
10400
11079
  );
10401
11080
  throw new NhnCloudCliError(
10402
- `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 JSON \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${JSON.stringify(path5)} (${reason}).`,
11081
+ `\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 JSON \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${JSON.stringify(path4)} (${reason}).`,
10403
11082
  EXIT_PARAM_ERROR
10404
11083
  );
10405
11084
  }
@@ -10843,7 +11522,7 @@ var setMethodPluginCommand = addApiGatewayOptions(
10843
11522
  var resourceCommand = new import_commander56.Command("resource").description("API Gateway resource \uC870\uD68C \uBC0F \uBCC0\uACBD \uBA85\uB839").addCommand(listCommand13).addCommand(parametersCommand).addCommand(responsesCommand).addCommand(setPathPluginCommand).addCommand(setMethodPluginCommand);
10844
11523
 
10845
11524
  // src/commands/apigateway/stage.ts
10846
- var import_promises9 = require("fs/promises");
11525
+ var import_promises8 = require("fs/promises");
10847
11526
  var import_commander58 = require("commander");
10848
11527
 
10849
11528
  // src/commands/apigateway/deploy.ts
@@ -11050,9 +11729,9 @@ function fileErrorReason2(error) {
11050
11729
  }
11051
11730
  return error instanceof Error ? error.message : String(error);
11052
11731
  }
11053
- async function writeStageSwaggerFile(path5, swagger, force) {
11732
+ async function writeStageSwaggerFile(path4, swagger, force) {
11054
11733
  try {
11055
- await (0, import_promises9.writeFile)(path5, JSON.stringify(swagger, null, 2) + "\n", {
11734
+ await (0, import_promises8.writeFile)(path4, JSON.stringify(swagger, null, 2) + "\n", {
11056
11735
  encoding: "utf-8",
11057
11736
  flag: force ? "w" : "wx"
11058
11737
  });
@@ -11060,7 +11739,7 @@ async function writeStageSwaggerFile(path5, swagger, force) {
11060
11739
  const reason = fileErrorReason2(error);
11061
11740
  const guidance = reason === "EEXIST" ? " \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC9C0\uC815\uD558\uC138\uC694." : "";
11062
11741
  throw new NhnCloudCliError(
11063
- `Swagger \uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${JSON.stringify(path5)} (${JSON.stringify(reason)}).${guidance}`,
11742
+ `Swagger \uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${JSON.stringify(path4)} (${JSON.stringify(reason)}).${guidance}`,
11064
11743
  EXIT_PARAM_ERROR
11065
11744
  );
11066
11745
  }
@@ -11394,10 +12073,10 @@ var SkmClient = class {
11394
12073
  * 요청을 보내고 봉투 형태인지만 확인해 반환한다. 성공 판정은 호출부가 한다.
11395
12074
  * 쓰기 요청도 중복 실행을 막으려고 재시도하지 않는다.
11396
12075
  */
11397
- async send(method, path5, options) {
12076
+ async send(method, path4, options) {
11398
12077
  let response;
11399
12078
  try {
11400
- response = await import_ky16.default[method](`${this.baseUrl}${path5}`, {
12079
+ response = await import_ky16.default[method](`${this.baseUrl}${path4}`, {
11401
12080
  headers: this.headers,
11402
12081
  ...options,
11403
12082
  retry: 0,
@@ -11415,8 +12094,8 @@ var SkmClient = class {
11415
12094
  * 요청을 보내고 봉투를 벗긴 뒤 body 또는 body[field] 를 가드로 검사해 반환한다.
11416
12095
  * field 가 null 이면 body 전체를 검사한다.
11417
12096
  */
11418
- async request(method, path5, field, guard, options = {}) {
11419
- const body = unwrap(await this.send(method, path5, options));
12097
+ async request(method, path4, field, guard, options = {}) {
12098
+ const body = unwrap(await this.send(method, path4, options));
11420
12099
  const value = field === null ? body : isRecord4(body) ? body[field] : void 0;
11421
12100
  if (!guard(value)) {
11422
12101
  throw new NhnCloudCliError(`Secure Key Manager \uC751\uB2F5 \uD615\uC2DD \uC624\uB958: ${field ?? "body"}`, EXIT_API_ERROR);
@@ -11427,8 +12106,8 @@ var SkmClient = class {
11427
12106
  * body 가 null 이거나 없는 응답(키 저장소 수정·삭제)은 검사할 필드가 없어 헤더만 본다 (ADR-040).
11428
12107
  * isSuccessful 이 false 면 unwrapHeader 가 NhnEnvelopeError 를 던진다.
11429
12108
  */
11430
- async requestHeaderOnly(method, path5, options = {}) {
11431
- unwrapHeader(await this.send(method, path5, options));
12109
+ async requestHeaderOnly(method, path4, options = {}) {
12110
+ unwrapHeader(await this.send(method, path4, options));
11432
12111
  }
11433
12112
  keyPath(prefix, keyId, action) {
11434
12113
  return `/${prefix}/${encodeURIComponent(keyId)}/${action}`;
@@ -11476,12 +12155,12 @@ var SkmClient = class {
11476
12155
  return this.request("get", `/keystores/${keyStoreId}/keys/${encodeURIComponent(keyId)}`, null, isSkmKey);
11477
12156
  }
11478
12157
  listAuths(keyStoreId, type) {
11479
- const { path: path5, field } = AUTH_PATHS[type];
11480
- return this.request("get", `/keystores/${keyStoreId}/${path5}`, field, arrayOf(isString));
12158
+ const { path: path4, field } = AUTH_PATHS[type];
12159
+ return this.request("get", `/keystores/${keyStoreId}/${path4}`, field, arrayOf(isString));
11481
12160
  }
11482
12161
  getAuth(keyStoreId, type, value) {
11483
- const { path: path5, field } = AUTH_PATHS[type];
11484
- return this.request("get", `/keystores/${keyStoreId}/${path5}`, field, arrayOf(isSkmAuthDetail), {
12162
+ const { path: path4, field } = AUTH_PATHS[type];
12163
+ return this.request("get", `/keystores/${keyStoreId}/${path4}`, field, arrayOf(isSkmAuthDetail), {
11485
12164
  searchParams: { value }
11486
12165
  });
11487
12166
  }
@@ -11845,17 +12524,17 @@ function assertWithinLimit(size, spec) {
11845
12524
  }
11846
12525
  }
11847
12526
  function readInputFile(file, spec) {
11848
- let stat3;
12527
+ let stat4;
11849
12528
  try {
11850
- stat3 = (0, import_node_fs10.statSync)(file);
12529
+ stat4 = (0, import_node_fs10.statSync)(file);
11851
12530
  } catch (e) {
11852
12531
  const reason = e.code ?? (e instanceof Error ? e.message : String(e));
11853
12532
  throw new NhnCloudCliError(`${spec.label} \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${file} (${reason})`, EXIT_PARAM_ERROR);
11854
12533
  }
11855
- if (!stat3.isFile()) {
12534
+ if (!stat4.isFile()) {
11856
12535
  throw new NhnCloudCliError(`${spec.label} \uD30C\uC77C\uC774 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${file}`, EXIT_PARAM_ERROR);
11857
12536
  }
11858
- assertWithinLimit(stat3.size, spec);
12537
+ assertWithinLimit(stat4.size, spec);
11859
12538
  return (0, import_node_fs10.readFileSync)(file);
11860
12539
  }
11861
12540
  function readStdin(stdin, spec) {
@@ -11868,7 +12547,7 @@ function readStdin(stdin, spec) {
11868
12547
  }
11869
12548
  function readSkmInput(source, spec, stdin = processStdin) {
11870
12549
  const fileFlag = spec.fileFlag ?? "--file";
11871
- if (source.text !== void 0 && source.file !== void 0) {
12550
+ if (spec.textFlag !== void 0 && source.text !== void 0 && source.file !== void 0) {
11872
12551
  throw new NhnCloudCliError(`${spec.textFlag}\uC640 ${fileFlag}\uC740 \uD568\uAED8 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`, EXIT_PARAM_ERROR);
11873
12552
  }
11874
12553
  let input;
@@ -11878,6 +12557,11 @@ function readSkmInput(source, spec, stdin = processStdin) {
11878
12557
  input = readInputFile(source.file, spec);
11879
12558
  } else if (!stdin.isTTY) {
11880
12559
  input = readStdin(stdin, spec);
12560
+ } else if (spec.textFlag === void 0) {
12561
+ throw new NhnCloudCliError(
12562
+ `${spec.label}\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ${fileFlag} <\uACBD\uB85C>\uB098 \uD45C\uC900 \uC785\uB825(\uD30C\uC774\uD504)\uC73C\uB85C \uC804\uB2EC\uD558\uC138\uC694.`,
12563
+ EXIT_PARAM_ERROR
12564
+ );
11881
12565
  } else {
11882
12566
  throw new NhnCloudCliError(
11883
12567
  `${spec.label}\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ${spec.textFlag} <\uAC12>, ${fileFlag} <\uACBD\uB85C>, \uD45C\uC900 \uC785\uB825(\uD30C\uC774\uD504) \uC911 \uD558\uB098\uB85C \uC804\uB2EC\uD558\uC138\uC694.`,
@@ -12333,18 +13017,153 @@ var keyCommand = new import_commander63.Command("key").description("SKM \uD0A4 \
12333
13017
 
12334
13018
  // src/commands/skm/secret.ts
12335
13019
  var import_commander64 = require("commander");
13020
+
13021
+ // src/commands/skm/secret-json.ts
13022
+ var import_node_crypto6 = require("crypto");
13023
+ var import_node_util = require("util");
13024
+ var NUMBER_LITERAL = /-?\d+(\.\d+)?([eE][+-]?\d+)?/y;
13025
+ var MAX_SIGNIFICANT_DIGITS = 15;
13026
+ function tryParseSecretObject(text) {
13027
+ let parsed;
13028
+ try {
13029
+ parsed = JSON.parse(text);
13030
+ } catch {
13031
+ return null;
13032
+ }
13033
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
13034
+ return parsed;
13035
+ }
13036
+ function parseSecretObject(text, usage) {
13037
+ const object = tryParseSecretObject(text);
13038
+ if (object === null) {
13039
+ throw new NhnCloudCliError(`\uAE30\uBC00 \uB370\uC774\uD130 \uAC12\uC774 JSON \uAC1D\uCCB4\uAC00 \uC544\uB2C8\uC5B4\uC11C ${usage}\uB97C \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`, EXIT_PARAM_ERROR);
13040
+ }
13041
+ return object;
13042
+ }
13043
+ function isLossyLiteral(literal) {
13044
+ const value = Number(literal);
13045
+ if (!Number.isFinite(value) || Object.is(value, -0)) return true;
13046
+ if (/^-?\d+$/.test(literal)) return !Number.isSafeInteger(value);
13047
+ const mantissa = literal.replace(/^-/, "").replace(/[eE].*$/, "").replace(".", "");
13048
+ return mantissa.replace(/^0+/, "").replace(/0+$/, "").length > MAX_SIGNIFICANT_DIGITS;
13049
+ }
13050
+ function hasLossyNumber(text) {
13051
+ let i = 0;
13052
+ while (i < text.length) {
13053
+ const ch = text[i];
13054
+ if (ch === '"') {
13055
+ i += 1;
13056
+ while (i < text.length && text[i] !== '"') i += text[i] === "\\" ? 2 : 1;
13057
+ i += 1;
13058
+ continue;
13059
+ }
13060
+ if (ch === "-" || ch >= "0" && ch <= "9") {
13061
+ NUMBER_LITERAL.lastIndex = i;
13062
+ const match = NUMBER_LITERAL.exec(text);
13063
+ if (match === null) {
13064
+ i += 1;
13065
+ continue;
13066
+ }
13067
+ if (isLossyLiteral(match[0])) return true;
13068
+ i += match[0].length;
13069
+ continue;
13070
+ }
13071
+ i += 1;
13072
+ }
13073
+ return false;
13074
+ }
13075
+ function assertNoLossyNumber(text, usage) {
13076
+ if (hasLossyNumber(text)) {
13077
+ throw new NhnCloudCliError(
13078
+ `\uAE30\uBC00 \uB370\uC774\uD130\uC5D0 \uB2E4\uC2DC \uC4F0\uBA74 \uAC12\uC774 \uBC14\uB00C\uB294 \uC22B\uC790\uAC00 \uC788\uC5B4 ${usage}\uB97C \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC804\uCCB4 \uAC12\uC740 --field \uC5C6\uC774 secret get\uC73C\uB85C \uBC1B\uACE0, \uBC14\uAFC0 \uB54C\uB294 secret update\uB85C \uC804\uCCB4 \uAC12\uC744 \uBCF4\uB0B4\uC138\uC694.`,
13079
+ EXIT_PARAM_ERROR
13080
+ );
13081
+ }
13082
+ }
13083
+ function formatFieldValue(value) {
13084
+ return typeof value === "string" ? value : JSON.stringify(value);
13085
+ }
13086
+ function parseFieldName(value, label) {
13087
+ if (value === "") {
13088
+ throw new NhnCloudCliError(`${label}\uB294 \uBE44\uC5B4 \uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`, EXIT_PARAM_ERROR);
13089
+ }
13090
+ return value;
13091
+ }
13092
+ var DEFAULT_INDENT = " ";
13093
+ function serializeSecretObject(object, originalText) {
13094
+ if (!originalText.trimEnd().includes("\n")) return JSON.stringify(object);
13095
+ const indent = /\n([ \t]+)\S/.exec(originalText)?.[1] ?? DEFAULT_INDENT;
13096
+ const serialized = JSON.stringify(object, null, indent);
13097
+ return originalText.endsWith("\n") ? serialized + "\n" : serialized;
13098
+ }
13099
+ function sha256Hex(text) {
13100
+ return (0, import_node_crypto6.createHash)("sha256").update(text, "utf8").digest("hex");
13101
+ }
13102
+ function planSecretPatch(before, field, change) {
13103
+ const exists = Object.hasOwn(before, field);
13104
+ if (change.kind === "delete") {
13105
+ if (!exists) return { action: "unchanged", after: before };
13106
+ return { action: "deleted", after: Object.fromEntries(Object.entries(before).filter(([key]) => key !== field)) };
13107
+ }
13108
+ if (!exists) {
13109
+ return { action: "added", after: Object.fromEntries([...Object.entries(before), [field, change.value]]) };
13110
+ }
13111
+ if (before[field] === change.value) return { action: "unchanged", after: before };
13112
+ if (!change.replace) {
13113
+ throw new NhnCloudCliError(
13114
+ `\uD544\uB4DC ${JSON.stringify(field)}\uAC00 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. \uAC12\uC744 \uBC14\uAFB8\uB824\uBA74 --replace\uB97C \uBD99\uC774\uC138\uC694.`,
13115
+ EXIT_PARAM_ERROR
13116
+ );
13117
+ }
13118
+ return {
13119
+ action: "replaced",
13120
+ after: Object.fromEntries(
13121
+ Object.entries(before).map(([key, value]) => [key, key === field ? change.value : value])
13122
+ )
13123
+ };
13124
+ }
13125
+ function entriesWithout(object, field) {
13126
+ return Object.entries(object).filter(([key]) => key !== field);
13127
+ }
13128
+ function verifySecretPatch(before, fetched, field, change) {
13129
+ if (fetched === null) return { fieldVerified: false, othersVerified: false };
13130
+ let fieldVerified;
13131
+ if (change.kind === "delete") {
13132
+ fieldVerified = !Object.hasOwn(fetched, field);
13133
+ } else {
13134
+ const value = Object.hasOwn(fetched, field) ? fetched[field] : void 0;
13135
+ fieldVerified = typeof value === "string" && sha256Hex(value) === sha256Hex(change.value);
13136
+ }
13137
+ const expected = entriesWithout(before, field);
13138
+ const actual = new Map(entriesWithout(fetched, field));
13139
+ const othersVerified = expected.length === actual.size && expected.every(([key, value]) => actual.has(key) && (0, import_node_util.isDeepStrictEqual)(actual.get(key), value));
13140
+ return { fieldVerified, othersVerified };
13141
+ }
13142
+
13143
+ // src/commands/skm/secret.ts
12336
13144
  var getCommand13 = withSkmOptions(
12337
- new import_commander64.Command("get").description("\uAE30\uBC00 \uB370\uC774\uD130\uB97C \uC870\uD68C\uD55C\uB2E4 (\uC6D0\uBB38\uC744 stdout\uC5D0 \uCD9C\uB825)").argument("<key-id>", "\uD0A4 ID")
13145
+ new import_commander64.Command("get").description("\uAE30\uBC00 \uB370\uC774\uD130\uB97C \uC870\uD68C\uD55C\uB2E4 (\uC6D0\uBB38\uC744 stdout\uC5D0 \uCD9C\uB825, --field\uB85C JSON \uAC1D\uCCB4\uC758 \uD544\uB4DC \uD558\uB098\uB9CC)").argument("<key-id>", "\uD0A4 ID").option("--field <name>", "JSON \uAC1D\uCCB4 \uAC12\uC5D0\uC11C \uC774 \uD544\uB4DC\uC758 \uAC12\uB9CC \uCD9C\uB825\uD55C\uB2E4")
12338
13146
  ).action(async (keyId, _opts, command) => {
12339
13147
  const opts = command.optsWithGlobals();
12340
13148
  const parsedKeyId = parseRequiredArgument(keyId, "key-id");
12341
13149
  const macAddress = parseMacAddressOption(opts.macAddress);
13150
+ const field = opts.field === void 0 ? void 0 : parseFieldName(opts.field, "--field");
12342
13151
  const { client } = await resolveSkmClient({ profile: opts.profile, macAddress });
12343
13152
  const secret = await withSkmSpinner(
12344
13153
  `SKM \uAE30\uBC00 \uB370\uC774\uD130 ${formatCell(parsedKeyId)} \uC870\uD68C \uC911...`,
12345
13154
  () => client.getSecret(parsedKeyId)
12346
13155
  );
12347
- printSkmValue(opts, secret, { secret });
13156
+ if (field === void 0) {
13157
+ printSkmValue(opts, secret, { secret });
13158
+ return;
13159
+ }
13160
+ const object = parseSecretObject(secret, "--field");
13161
+ if (!Object.hasOwn(object, field)) {
13162
+ throw new NhnCloudCliError(`\uAE30\uBC00 \uB370\uC774\uD130\uC5D0 \uD544\uB4DC ${JSON.stringify(field)}\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`, EXIT_PARAM_ERROR);
13163
+ }
13164
+ const value = object[field];
13165
+ if (typeof value !== "string") assertNoLossyNumber(secret, "--field");
13166
+ printSkmValue(opts, formatFieldValue(value), { keyId: parsedKeyId, field, value });
12348
13167
  });
12349
13168
  var updateCommand5 = withSkmOptions(
12350
13169
  new import_commander64.Command("update").description("\uAE30\uBC00 \uB370\uC774\uD130 \uAC12\uC744 \uBC14\uAFBC\uB2E4 (\uAC12\uC740 --value, --file, \uD45C\uC900 \uC785\uB825 \uC911 \uD558\uB098)").argument("<key-id>", "\uD0A4 ID").option("--value <value>", "\uC0C8 \uAE30\uBC00 \uB370\uC774\uD130 \uAC12").option("--file <path>", "\uC0C8 \uAE30\uBC00 \uB370\uC774\uD130 \uAC12\uC744 \uC77D\uC744 \uD30C\uC77C")
@@ -12365,7 +13184,94 @@ var updateCommand5 = withSkmOptions(
12365
13184
  const { secretValue: _omitted, ...result } = updated;
12366
13185
  outputSkmWriteResult(opts, result, [formatCell(updated.keyId)]);
12367
13186
  });
12368
- var secretCommand = new import_commander64.Command("secret").description("SKM \uAE30\uBC00 \uB370\uC774\uD130 \uC870\uD68C\uC640 \uC218\uC815").addCommand(getCommand13).addCommand(updateCommand5);
13187
+ var patchCommand2 = withSkmOptions(
13188
+ new import_commander64.Command("patch").description("JSON \uAC1D\uCCB4 \uAE30\uBC00 \uB370\uC774\uD130\uC758 \uD544\uB4DC \uD558\uB098\uB97C \uB354\uD558\uAC70\uB098 \uBC14\uAFB8\uAC70\uB098 \uC9C0\uC6B4\uB2E4 (\uAE30\uBCF8\uC740 \uBBF8\uB9AC\uBCF4\uAE30, --yes\uB85C \uBC18\uC601)").argument("<key-id>", "\uD0A4 ID").argument("<field>", "\uD544\uB4DC \uC774\uB984 (\uCD5C\uC0C1\uC704 \uD0A4)").option("--file <path>", "\uD544\uB4DC\uC5D0 \uB123\uC744 \uAC12\uC744 \uC77D\uC744 \uD30C\uC77C (\uC5C6\uC73C\uBA74 \uD45C\uC900 \uC785\uB825)").option("--replace", "\uC774\uBBF8 \uC788\uB294 \uD544\uB4DC\uC758 \uAC12\uC744 \uBC14\uAFBC\uB2E4").option("--delete", "\uD544\uB4DC\uB97C \uC9C0\uC6B4\uB2E4").option("--yes", "\uBBF8\uB9AC\uBCF4\uAE30 \uB300\uC2E0 \uBC18\uC601\uD55C\uB2E4")
13189
+ ).action(async (keyId, field, _opts, command) => {
13190
+ const opts = command.optsWithGlobals();
13191
+ const parsedKeyId = parseRequiredArgument(keyId, "key-id");
13192
+ const parsedField = parseFieldName(field, "<field>");
13193
+ const macAddress = parseMacAddressOption(opts.macAddress);
13194
+ if (opts.delete && opts.replace) {
13195
+ throw new NhnCloudCliError("--delete\uC640 --replace\uB294 \uD568\uAED8 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
13196
+ }
13197
+ if (opts.delete && opts.file !== void 0) {
13198
+ throw new NhnCloudCliError("--delete\uC640 --file\uC740 \uD568\uAED8 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
13199
+ }
13200
+ let change;
13201
+ if (opts.delete) {
13202
+ change = { kind: "delete" };
13203
+ } else {
13204
+ const label = "\uD544\uB4DC\uC5D0 \uB123\uC744 \uB370\uC774\uD130";
13205
+ const input = readSkmInput({ file: opts.file }, { label, maxBytes: MAX_JSON_INPUT_BYTES });
13206
+ const value = decodeUtf8Input(input, label, "\uBC14\uC774\uB108\uB9AC\uB294 base64\uB85C \uC778\uCF54\uB529\uD574 \uC804\uB2EC\uD558\uC138\uC694.");
13207
+ if (value.endsWith("\n")) {
13208
+ process.stderr.write(
13209
+ "\uACBD\uACE0: \uD544\uB4DC\uC5D0 \uB123\uC744 \uB370\uC774\uD130\uAC00 \uC904\uBC14\uAFC8\uC73C\uB85C \uB05D\uB0A9\uB2C8\uB2E4. \uC904\uBC14\uAFC8\uB3C4 \uAC12\uC5D0 \uB4E4\uC5B4\uAC11\uB2C8\uB2E4. \uBE7C\uB824\uBA74 printf '%s'\uB85C \uB118\uAE30\uC138\uC694.\n"
13210
+ );
13211
+ }
13212
+ change = { kind: "set", value, replace: opts.replace === true };
13213
+ }
13214
+ const { client } = await resolveSkmClient({ profile: opts.profile, macAddress });
13215
+ const id = formatCell(parsedKeyId);
13216
+ const current2 = await withSkmSpinner(`SKM \uAE30\uBC00 \uB370\uC774\uD130 ${id} \uC870\uD68C \uC911...`, () => client.getSecret(parsedKeyId));
13217
+ const before = parseSecretObject(current2, "secret patch");
13218
+ assertNoLossyNumber(current2, "secret patch");
13219
+ const { action, after } = planSecretPatch(before, parsedField, change);
13220
+ const report = (applied, verified2) => {
13221
+ const result = {
13222
+ keyId: parsedKeyId,
13223
+ field: parsedField,
13224
+ action,
13225
+ applied,
13226
+ fieldVerified: verified2?.fieldVerified ?? null,
13227
+ othersVerified: verified2?.othersVerified ?? null
13228
+ };
13229
+ outputSkmWriteResult(opts, result, [action]);
13230
+ };
13231
+ if (!opts.yes || action === "unchanged") {
13232
+ report(false, null);
13233
+ if (!opts.yes && action !== "unchanged") {
13234
+ process.stderr.write("\uBBF8\uB9AC\uBCF4\uAE30\uC785\uB2C8\uB2E4. \uBC18\uC601\uD558\uB824\uBA74 --yes\uB97C \uBD99\uC774\uC138\uC694.\n");
13235
+ }
13236
+ return;
13237
+ }
13238
+ try {
13239
+ await withSkmSpinner(
13240
+ `SKM \uAE30\uBC00 \uB370\uC774\uD130 ${id} \uC218\uC815 \uC911...`,
13241
+ () => client.updateSecret(parsedKeyId, serializeSecretObject(after, current2))
13242
+ );
13243
+ } catch (err) {
13244
+ const e = toNhnCloudCliError(err);
13245
+ throw new NhnCloudCliError(
13246
+ `\uAE30\uBC00 \uB370\uC774\uD130 \uC218\uC815 \uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${e.message} \uBC18\uC601\uB410\uB294\uC9C0\uB294 \uAC19\uC740 \uBA85\uB839\uC744 --yes \uC5C6\uC774 \uB2E4\uC2DC \uC2E4\uD589\uD574 action\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.`,
13247
+ e.exitCode
13248
+ );
13249
+ }
13250
+ let fetchedText;
13251
+ try {
13252
+ fetchedText = await withSkmSpinner(`SKM \uAE30\uBC00 \uB370\uC774\uD130 ${id} \uB2E4\uC2DC \uC870\uD68C \uC911...`, () => client.getSecret(parsedKeyId));
13253
+ } catch (err) {
13254
+ const e = toNhnCloudCliError(err);
13255
+ report(true, null);
13256
+ throw new NhnCloudCliError(
13257
+ `\uAE30\uBC00 \uB370\uC774\uD130\uB294 \uBC14\uAFE8\uC9C0\uB9CC \uB2E4\uC2DC \uC870\uD68C\uD558\uC9C0 \uBABB\uD574 \uAC80\uC99D\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${e.message} skm secret get\uC73C\uB85C \uD604\uC7AC \uAC12\uC744 \uD655\uC778\uD558\uC138\uC694.`,
13258
+ EXIT_API_ERROR
13259
+ );
13260
+ }
13261
+ const verified = verifySecretPatch(before, tryParseSecretObject(fetchedText), parsedField, change);
13262
+ report(true, verified);
13263
+ const mismatched = [
13264
+ ...verified.fieldVerified ? [] : [`\uD544\uB4DC ${JSON.stringify(parsedField)}`],
13265
+ ...verified.othersVerified ? [] : ["\uB2E4\uB978 \uD544\uB4DC"]
13266
+ ];
13267
+ if (mismatched.length > 0) {
13268
+ throw new NhnCloudCliError(
13269
+ `\uBC18\uC601 \uB4A4 \uB2E4\uC2DC \uC870\uD68C\uD55C \uAE30\uBC00 \uB370\uC774\uD130\uAC00 \uAE30\uB300\uC640 \uB2E4\uB985\uB2C8\uB2E4 (${mismatched.join(", ")}). \uB2E4\uB978 \uC4F0\uAE30\uAC00 \uB07C\uC5B4\uB4E4\uC5C8\uAC70\uB098 \uC2B9\uC778 \uAE30\uB2A5 \uB54C\uBB38\uC5D0 \uBCC0\uACBD\uC774 \uB300\uAE30 \uC911\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4. skm secret get\uC73C\uB85C \uD604\uC7AC \uAC12\uC744 \uD655\uC778\uD558\uC138\uC694.`,
13270
+ EXIT_API_ERROR
13271
+ );
13272
+ }
13273
+ });
13274
+ var secretCommand = new import_commander64.Command("secret").description("SKM \uAE30\uBC00 \uB370\uC774\uD130 \uC870\uD68C\uC640 \uC218\uC815").addCommand(getCommand13).addCommand(updateCommand5).addCommand(patchCommand2);
12369
13275
 
12370
13276
  // src/commands/skm/symmetric-key.ts
12371
13277
  var import_commander65 = require("commander");
@@ -12747,8 +13653,8 @@ var LoadBalancerClient = class {
12747
13653
  }
12748
13654
  return raw;
12749
13655
  }
12750
- async getJson(path5, searchParams) {
12751
- const url = `${this.endpoint}${path5}`;
13656
+ async getJson(path4, searchParams) {
13657
+ const url = `${this.endpoint}${path4}`;
12752
13658
  try {
12753
13659
  return await import_ky17.default.get(url, {
12754
13660
  headers: this.authHeaders(),
@@ -12760,8 +13666,8 @@ var LoadBalancerClient = class {
12760
13666
  throw toNhnCloudCliError(error);
12761
13667
  }
12762
13668
  }
12763
- async postJson(path5, json) {
12764
- const url = `${this.endpoint}${path5}`;
13669
+ async postJson(path4, json) {
13670
+ const url = `${this.endpoint}${path4}`;
12765
13671
  try {
12766
13672
  return await import_ky17.default.post(url, {
12767
13673
  headers: this.authHeaders(),
@@ -12773,8 +13679,8 @@ var LoadBalancerClient = class {
12773
13679
  throw toNhnCloudCliError(error);
12774
13680
  }
12775
13681
  }
12776
- async putJson(path5, json) {
12777
- const url = `${this.endpoint}${path5}`;
13682
+ async putJson(path4, json) {
13683
+ const url = `${this.endpoint}${path4}`;
12778
13684
  try {
12779
13685
  return await import_ky17.default.put(url, {
12780
13686
  headers: this.authHeaders(),
@@ -12786,8 +13692,8 @@ var LoadBalancerClient = class {
12786
13692
  throw toNhnCloudCliError(error);
12787
13693
  }
12788
13694
  }
12789
- async deleteNoContent(path5) {
12790
- const url = `${this.endpoint}${path5}`;
13695
+ async deleteNoContent(path4) {
13696
+ const url = `${this.endpoint}${path4}`;
12791
13697
  try {
12792
13698
  await import_ky17.default.delete(url, {
12793
13699
  headers: this.authHeaders(),
@@ -13479,8 +14385,8 @@ Agent workflow:
13479
14385
  3. nhncloud ncs workload get <id> --json
13480
14386
  `;
13481
14387
  var program = new import_commander72.Command();
13482
- program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version(CLI_VERSION).option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "\uCD5C\uC18C \uCD9C\uB825 (\uC790\uB3D9\uD654\uC6A9)").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654").option("--request-timeout <sec>", "HTTP \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, \uAE30\uBCF8 30, \uBC94\uC704 1~3600). NHNCLOUD_REQUEST_TIMEOUT \uB85C\uB3C4 \uC9C0\uC815").addHelpText("after", rootAgentHints);
13483
- program.hook("preAction", () => {
14388
+ program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version(CLI_VERSION).option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "\uCD5C\uC18C \uCD9C\uB825 (\uC790\uB3D9\uD654\uC6A9)").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654").option("--request-timeout <sec>", "HTTP \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, \uAE30\uBCF8 30, \uBC94\uC704 1~3600). NHNCLOUD_REQUEST_TIMEOUT \uB85C\uB3C4 \uC9C0\uC815").option("--proxy <url>", "HTTP \uC694\uCCAD\uC5D0 \uC4F8 \uD504\uB85D\uC2DC URL. HTTPS_PROXY\xB7HTTP_PROXY \uBCF4\uB2E4 \uC6B0\uC120\uD558\uACE0 NO_PROXY \uB294 \uADF8\uB300\uB85C \uB530\uB978\uB2E4").addHelpText("after", rootAgentHints);
14389
+ program.hook("preAction", async () => {
13484
14390
  const opts = program.opts();
13485
14391
  const rawTimeout = opts.requestTimeout ?? process.env["NHNCLOUD_REQUEST_TIMEOUT"];
13486
14392
  const timeoutValue = rawTimeout === "" ? void 0 : rawTimeout;
@@ -13489,6 +14395,7 @@ program.hook("preAction", () => {
13489
14395
  opts.requestTimeout !== void 0 ? "--request-timeout" : "NHNCLOUD_REQUEST_TIMEOUT",
13490
14396
  { min: 1, max: 3600 }
13491
14397
  );
14398
+ const proxySettings = resolveProxySettings(opts.proxy, process.env);
13492
14399
  if (!opts.color || process.env["NO_COLOR"]) {
13493
14400
  import_chalk18.default.level = 0;
13494
14401
  }
@@ -13498,6 +14405,9 @@ program.hook("preAction", () => {
13498
14405
  if (timeoutSec !== void 0) {
13499
14406
  setRequestTimeoutMs(timeoutSec * 1e3);
13500
14407
  }
14408
+ if (proxySettings) {
14409
+ await installProxy(proxySettings);
14410
+ }
13501
14411
  });
13502
14412
  program.addCommand(configureCommand);
13503
14413
  var logncrashCommand = new import_commander72.Command("logncrash").description("Log & Crash \uAD00\uB828 \uBA85\uB839").addHelpText("after", logncrashAgentWorkflow);