AX Commander 비교본 런처 기능 대량 이식
변경 목적: Agent Compare 아래 비교본의 개발 문서와 런처 소스를 기준으로 현재 AX Commander에 빠져 있던 신규 런처 기능을 동일한 흐름으로 옮겨, 비교본 수준의 기능 폭을 현재 제품에 반영했습니다. 핵심 수정사항: 비교본의 신규 런처 핸들러 다수를 src/AxCopilot/Handlers로 이식하고 App.xaml.cs 등록 흐름에 연결했습니다. 빠른 링크, 파일 태그, 알림 센터, 포모도로, 파일 브라우저, 핫키 관리, OCR, 세션/스케줄/매크로, Git/정규식/네트워크/압축/해시/UUID/JWT/QR 등 AX Commander 기능을 추가했습니다. 핵심 수정사항: 신규 기능이 실제 동작하도록 AppSettings 확장, SchedulerService/FileTagService/NotificationCenterService/IconCacheService/UrlTemplateEngine/PomodoroService 추가, 배치 이름변경/세션/스케줄/매크로 편집 창 추가, NotificationService와 Symbols 보강, QR/OCR용 csproj 의존성과 Windows 타겟 프레임워크를 반영했습니다. 문서 반영: README.md와 docs/DEVELOPMENT.md에 비교본 기반 런처 기능 이식 이력과 검증 결과를 업데이트했습니다. 검증 결과: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 실행 기준 경고 0개, 오류 0개를 확인했습니다.
This commit is contained in:
249
src/AxCopilot/Handlers/PasswordGenHandler.cs
Normal file
249
src/AxCopilot/Handlers/PasswordGenHandler.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// L9-1: 비밀번호 생성기 핸들러. "pwd" 프리픽스로 사용합니다.
|
||||
///
|
||||
/// 예: pwd → 기본 16자 비밀번호 5개 생성
|
||||
/// pwd 24 → 24자 비밀번호 5개 생성
|
||||
/// pwd 32 strong → 32자 강력 옵션 (대소문자+숫자+특수)
|
||||
/// pwd 16 alpha → 알파벳+숫자만 (특수문자 제외)
|
||||
/// pwd 20 pin → 숫자만 (PIN 코드)
|
||||
/// pwd passphrase → 단어 조합 기억하기 쉬운 패스프레이즈
|
||||
/// Enter → 클립보드에 복사.
|
||||
/// </summary>
|
||||
public class PasswordGenHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "pwd";
|
||||
|
||||
public PluginMetadata Metadata => new(
|
||||
"PasswordGen",
|
||||
"비밀번호 생성기 — 길이 · 복잡도 · 패스프레이즈",
|
||||
"1.0",
|
||||
"AX");
|
||||
|
||||
private const string LowerChars = "abcdefghijklmnopqrstuvwxyz";
|
||||
private const string UpperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private const string DigitChars = "0123456789";
|
||||
private const string SpecialChars = "!@#$%^&*()-_=+[]{}|;:,.<>?";
|
||||
|
||||
// 기억하기 쉬운 단어 목록 (간결한 영단어)
|
||||
private static readonly string[] WordList =
|
||||
[
|
||||
"apple","bridge","cloud","dawn","eagle","flame","grape","harbor",
|
||||
"ivory","jungle","kite","lemon","maple","night","ocean","pearl",
|
||||
"quartz","river","storm","tiger","ultra","violet","water","xenon",
|
||||
"yellow","zenith","amber","blaze","cedar","delta","ember","frost",
|
||||
"glass","honey","iron","jade","knot","lunar","mango","nova",
|
||||
"orbit","prism","quest","range","solar","track","umbra","valor",
|
||||
];
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
var q = query.Trim().ToLowerInvariant();
|
||||
var items = new List<LauncherItem>();
|
||||
|
||||
// 패스프레이즈 모드
|
||||
if (q.StartsWith("passphrase") || q.StartsWith("phrase"))
|
||||
{
|
||||
var wordCount = 4;
|
||||
var parts2 = q.Split(' ');
|
||||
if (parts2.Length >= 2 && int.TryParse(parts2[1], out var wc))
|
||||
wordCount = Math.Clamp(wc, 2, 8);
|
||||
|
||||
items.Add(new LauncherItem(
|
||||
"패스프레이즈 생성",
|
||||
$"{wordCount}단어 조합 · 기억하기 쉬운 형식",
|
||||
null, null, Symbol: "\uE8D4"));
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var phrase = GeneratePassphrase(wordCount);
|
||||
var strength = EstimateEntropy(phrase);
|
||||
items.Add(new LauncherItem(
|
||||
phrase,
|
||||
$"엔트로피: ~{strength}bit",
|
||||
null,
|
||||
("copy", phrase),
|
||||
Symbol: "\uE8D4"));
|
||||
}
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
// 파라미터 파싱: [길이] [모드]
|
||||
int length = 16;
|
||||
string mode = "strong";
|
||||
|
||||
var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 1 && int.TryParse(parts[0], out var l))
|
||||
{
|
||||
length = Math.Clamp(l, 4, 128);
|
||||
if (parts.Length >= 2) mode = parts[1];
|
||||
}
|
||||
else if (parts.Length >= 1 && parts[0] is "strong" or "alpha" or "pin" or "simple")
|
||||
{
|
||||
mode = parts[0];
|
||||
}
|
||||
|
||||
// 문자 집합 결정
|
||||
var charset = BuildCharset(mode);
|
||||
|
||||
// 강도 표시
|
||||
var modeLabel = mode switch
|
||||
{
|
||||
"alpha" => "알파뉴메릭 (대소문자+숫자)",
|
||||
"pin" => "숫자 PIN",
|
||||
"simple" => "간단 (소문자+숫자)",
|
||||
_ => "강력 (대소문자+숫자+특수)",
|
||||
};
|
||||
|
||||
items.Add(new LauncherItem(
|
||||
$"비밀번호 생성 {length}자",
|
||||
modeLabel,
|
||||
null, null, Symbol: "\uE8D4"));
|
||||
|
||||
// 5개 후보 생성
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var pw = GeneratePassword(charset, length, mode);
|
||||
var strength = GetStrengthLabel(pw);
|
||||
items.Add(new LauncherItem(
|
||||
pw,
|
||||
strength,
|
||||
null,
|
||||
("copy", pw),
|
||||
Symbol: "\uE8D4"));
|
||||
}
|
||||
|
||||
// 옵션 안내
|
||||
items.Add(new LauncherItem(
|
||||
"pwd <길이> alpha",
|
||||
"알파뉴메릭 (특수문자 제외)",
|
||||
null, null, Symbol: "\uE946"));
|
||||
items.Add(new LauncherItem(
|
||||
"pwd <길이> pin",
|
||||
"숫자만 (PIN 코드)",
|
||||
null, null, Symbol: "\uE946"));
|
||||
items.Add(new LauncherItem(
|
||||
"pwd passphrase",
|
||||
"단어 조합 패스프레이즈",
|
||||
null, null, Symbol: "\uE946"));
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is ("copy", string pw))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(
|
||||
() => Clipboard.SetText(pw));
|
||||
NotificationService.Notify("비밀번호", "클립보드에 복사했습니다.");
|
||||
}
|
||||
catch { /* 비핵심 */ }
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// ── 헬퍼 ────────────────────────────────────────────────────────────────
|
||||
|
||||
private static string BuildCharset(string mode) => mode switch
|
||||
{
|
||||
"pin" => DigitChars,
|
||||
"alpha" => LowerChars + UpperChars + DigitChars,
|
||||
"simple" => LowerChars + DigitChars,
|
||||
_ => LowerChars + UpperChars + DigitChars + SpecialChars,
|
||||
};
|
||||
|
||||
private static string GeneratePassword(string charset, int length, string mode)
|
||||
{
|
||||
// strong 모드: 각 카테고리 최소 1개 보장
|
||||
if (mode == "strong" && length >= 4)
|
||||
{
|
||||
var mandatory = new[]
|
||||
{
|
||||
RandomChar(LowerChars),
|
||||
RandomChar(UpperChars),
|
||||
RandomChar(DigitChars),
|
||||
RandomChar(SpecialChars),
|
||||
};
|
||||
|
||||
var remaining = length - mandatory.Length;
|
||||
var bulk = Enumerable.Range(0, remaining)
|
||||
.Select(_ => RandomChar(charset))
|
||||
.ToList();
|
||||
|
||||
var all = mandatory.Concat(bulk).ToArray();
|
||||
Shuffle(all);
|
||||
return new string(all);
|
||||
}
|
||||
|
||||
// alpha/pin/simple
|
||||
return new string(Enumerable.Range(0, length)
|
||||
.Select(_ => RandomChar(charset))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
private static string GeneratePassphrase(int wordCount)
|
||||
{
|
||||
var words = Enumerable.Range(0, wordCount)
|
||||
.Select(_ => WordList[RandomInt(WordList.Length)]);
|
||||
var num = RandomInt(9000) + 1000;
|
||||
var sep = new[] { "-", "_", ".", "!" }[RandomInt(4)];
|
||||
return string.Join(sep, words) + sep + num;
|
||||
}
|
||||
|
||||
private static char RandomChar(string charset) =>
|
||||
charset[RandomInt(charset.Length)];
|
||||
|
||||
private static int RandomInt(int max) =>
|
||||
(int)(RandomNumberGenerator.GetInt32(int.MaxValue) % max);
|
||||
|
||||
private static void Shuffle<T>(T[] arr)
|
||||
{
|
||||
for (int i = arr.Length - 1; i > 0; i--)
|
||||
{
|
||||
var j = RandomInt(i + 1);
|
||||
(arr[i], arr[j]) = (arr[j], arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetStrengthLabel(string pw)
|
||||
{
|
||||
var hasLower = pw.Any(char.IsLower);
|
||||
var hasUpper = pw.Any(char.IsUpper);
|
||||
var hasDigit = pw.Any(char.IsDigit);
|
||||
var hasSpecial = pw.Any(c => SpecialChars.Contains(c));
|
||||
var types = new[] { hasLower, hasUpper, hasDigit, hasSpecial }.Count(b => b);
|
||||
|
||||
var strength = (pw.Length, types) switch
|
||||
{
|
||||
( >= 24, >= 4) => "매우 강함 🔐",
|
||||
( >= 16, >= 3) => "강함 🔒",
|
||||
( >= 12, >= 2) => "보통 🔑",
|
||||
_ => "약함 ⚠",
|
||||
};
|
||||
return $"{strength} · {pw.Length}자";
|
||||
}
|
||||
|
||||
private static int EstimateEntropy(string pw)
|
||||
{
|
||||
// 간략 엔트로피 추정: log2(charset^length)
|
||||
var hasLower = pw.Any(char.IsLower);
|
||||
var hasUpper = pw.Any(char.IsUpper);
|
||||
var hasDigit = pw.Any(char.IsDigit);
|
||||
var hasSpecial = pw.Any(c => !char.IsLetterOrDigit(c));
|
||||
var pool = (hasLower ? 26 : 0) + (hasUpper ? 26 : 0)
|
||||
+ (hasDigit ? 10 : 0) + (hasSpecial ? 32 : 0);
|
||||
if (pool == 0) pool = 36;
|
||||
return (int)(pw.Length * Math.Log2(pool));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user