@mulsok/traders-client 0.1.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.
Files changed (119) hide show
  1. package/README.md +103 -0
  2. package/bin/cli.js +160 -0
  3. package/bin/postinstall.js +57 -0
  4. package/bin/preuninstall.js +36 -0
  5. package/dist/server/broker/kiwoom/cache.js +86 -0
  6. package/dist/server/broker/kiwoom/cache.js.map +1 -0
  7. package/dist/server/broker/kiwoom/client.js +256 -0
  8. package/dist/server/broker/kiwoom/client.js.map +1 -0
  9. package/dist/server/broker/kiwoom/endpoints/_helpers.js +61 -0
  10. package/dist/server/broker/kiwoom/endpoints/_helpers.js.map +1 -0
  11. package/dist/server/broker/kiwoom/endpoints/account.js +448 -0
  12. package/dist/server/broker/kiwoom/endpoints/account.js.map +1 -0
  13. package/dist/server/broker/kiwoom/endpoints/detail.js +118 -0
  14. package/dist/server/broker/kiwoom/endpoints/detail.js.map +1 -0
  15. package/dist/server/broker/kiwoom/endpoints/investor.js +139 -0
  16. package/dist/server/broker/kiwoom/endpoints/investor.js.map +1 -0
  17. package/dist/server/broker/kiwoom/endpoints/order.js +134 -0
  18. package/dist/server/broker/kiwoom/endpoints/order.js.map +1 -0
  19. package/dist/server/broker/kiwoom/endpoints/quote.js +165 -0
  20. package/dist/server/broker/kiwoom/endpoints/quote.js.map +1 -0
  21. package/dist/server/broker/kiwoom/endpoints/ranking.js +180 -0
  22. package/dist/server/broker/kiwoom/endpoints/ranking.js.map +1 -0
  23. package/dist/server/broker/kiwoom/endpoints/sector.js +135 -0
  24. package/dist/server/broker/kiwoom/endpoints/sector.js.map +1 -0
  25. package/dist/server/broker/kiwoom/endpoints/theme.js +104 -0
  26. package/dist/server/broker/kiwoom/endpoints/theme.js.map +1 -0
  27. package/dist/server/broker/kiwoom/endpoints/universe.js +119 -0
  28. package/dist/server/broker/kiwoom/endpoints/universe.js.map +1 -0
  29. package/dist/server/broker/kiwoom/index.js +59 -0
  30. package/dist/server/broker/kiwoom/index.js.map +1 -0
  31. package/dist/server/broker/kiwoom/order-tracker.js +353 -0
  32. package/dist/server/broker/kiwoom/order-tracker.js.map +1 -0
  33. package/dist/server/broker/kiwoom/price-feed.js +119 -0
  34. package/dist/server/broker/kiwoom/price-feed.js.map +1 -0
  35. package/dist/server/broker/kiwoom/rate-limiter.js +97 -0
  36. package/dist/server/broker/kiwoom/rate-limiter.js.map +1 -0
  37. package/dist/server/broker/kiwoom/types.js +13 -0
  38. package/dist/server/broker/kiwoom/types.js.map +1 -0
  39. package/dist/server/broker/kiwoom/ws/client.js +370 -0
  40. package/dist/server/broker/kiwoom/ws/client.js.map +1 -0
  41. package/dist/server/broker/kiwoom/ws/endpoints/condition.js +146 -0
  42. package/dist/server/broker/kiwoom/ws/endpoints/condition.js.map +1 -0
  43. package/dist/server/broker/kiwoom/ws/realtime-bus.js +42 -0
  44. package/dist/server/broker/kiwoom/ws/realtime-bus.js.map +1 -0
  45. package/dist/server/broker/kiwoom/ws/types.js +19 -0
  46. package/dist/server/broker/kiwoom/ws/types.js.map +1 -0
  47. package/dist/server/broker/news.js +34 -0
  48. package/dist/server/broker/news.js.map +1 -0
  49. package/dist/server/bundle.js +43 -0
  50. package/dist/server/bundle.js.map +1 -0
  51. package/dist/server/calendar/krx-holidays.js +162 -0
  52. package/dist/server/calendar/krx-holidays.js.map +1 -0
  53. package/dist/server/config.js +263 -0
  54. package/dist/server/config.js.map +1 -0
  55. package/dist/server/db/sqlite.js +252 -0
  56. package/dist/server/db/sqlite.js.map +1 -0
  57. package/dist/server/diary/writer.js +266 -0
  58. package/dist/server/diary/writer.js.map +1 -0
  59. package/dist/server/index.js +316 -0
  60. package/dist/server/index.js.map +1 -0
  61. package/dist/server/jobs/universe-sync.js +132 -0
  62. package/dist/server/jobs/universe-sync.js.map +1 -0
  63. package/dist/server/jobs/watchdog.js +87 -0
  64. package/dist/server/jobs/watchdog.js.map +1 -0
  65. package/dist/server/journal/pnl-stats.js +108 -0
  66. package/dist/server/journal/pnl-stats.js.map +1 -0
  67. package/dist/server/journal/telemetry.js +174 -0
  68. package/dist/server/journal/telemetry.js.map +1 -0
  69. package/dist/server/journal/trade-journal.js +239 -0
  70. package/dist/server/journal/trade-journal.js.map +1 -0
  71. package/dist/server/llm/anthropic.js +98 -0
  72. package/dist/server/llm/anthropic.js.map +1 -0
  73. package/dist/server/llm/claude-cli.js +204 -0
  74. package/dist/server/llm/claude-cli.js.map +1 -0
  75. package/dist/server/llm/context-builder.js +229 -0
  76. package/dist/server/llm/context-builder.js.map +1 -0
  77. package/dist/server/llm/gemini.js +86 -0
  78. package/dist/server/llm/gemini.js.map +1 -0
  79. package/dist/server/llm/index.js +36 -0
  80. package/dist/server/llm/index.js.map +1 -0
  81. package/dist/server/llm/openai.js +87 -0
  82. package/dist/server/llm/openai.js.map +1 -0
  83. package/dist/server/personas/executor.js +318 -0
  84. package/dist/server/personas/executor.js.map +1 -0
  85. package/dist/server/personas/loader.js +165 -0
  86. package/dist/server/personas/loader.js.map +1 -0
  87. package/dist/server/personas/persona-agent.js +386 -0
  88. package/dist/server/personas/persona-agent.js.map +1 -0
  89. package/dist/server/personas/persona-state.js +170 -0
  90. package/dist/server/personas/persona-state.js.map +1 -0
  91. package/dist/server/personas/runner.js +162 -0
  92. package/dist/server/personas/runner.js.map +1 -0
  93. package/dist/server/personas/schema.js +123 -0
  94. package/dist/server/personas/schema.js.map +1 -0
  95. package/dist/server/personas/wake-plan.js +313 -0
  96. package/dist/server/personas/wake-plan.js.map +1 -0
  97. package/dist/server/readiness.js +414 -0
  98. package/dist/server/readiness.js.map +1 -0
  99. package/dist/server/routes.js +1216 -0
  100. package/dist/server/routes.js.map +1 -0
  101. package/dist/server/safety.js +153 -0
  102. package/dist/server/safety.js.map +1 -0
  103. package/dist/server/screener/engine.js +856 -0
  104. package/dist/server/screener/engine.js.map +1 -0
  105. package/dist/server/server-sync.js +427 -0
  106. package/dist/server/server-sync.js.map +1 -0
  107. package/dist/server/signing.js +39 -0
  108. package/dist/server/signing.js.map +1 -0
  109. package/dist/server/watchers/condition-watcher.js +519 -0
  110. package/dist/server/watchers/condition-watcher.js.map +1 -0
  111. package/dist/server/watchers/types.js +16 -0
  112. package/dist/server/watchers/types.js.map +1 -0
  113. package/dist/web/assets/index-62SMpbaf.js +79 -0
  114. package/dist/web/assets/index-BPLQR0wt.css +1 -0
  115. package/dist/web/index.html +14 -0
  116. package/package.json +93 -0
  117. package/scripts/com.mulsok.traders.client.plist.template +58 -0
  118. package/scripts/install-daemon.sh +156 -0
  119. package/scripts/uninstall-daemon.sh +62 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Bundle 클라이언트 · ADR-009.
3
+ * mulsok-traders 웹 서버의 Bundle API 호출 + 해시 검증 + Layer 조립.
4
+ */
5
+ import { hashBody } from "./signing.js";
6
+ export async function fetchCommonBundle(serverBaseUrl) {
7
+ const url = `${serverBaseUrl.replace(/\/$/, "")}/api/bundle/common?version=latest`;
8
+ const res = await fetch(url, { headers: { accept: "application/json" } });
9
+ if (!res.ok) {
10
+ throw new Error(`Bundle common fetch 실패 · HTTP ${res.status} · ${await res.text()}`);
11
+ }
12
+ const bundle = (await res.json());
13
+ return { bundle, hash_ok: hashBody(bundle.body) === bundle.hash };
14
+ }
15
+ export async function fetchPersonaBundle(serverBaseUrl, slug, authBearer) {
16
+ const url = `${serverBaseUrl.replace(/\/$/, "")}/api/bundle/persona/${encodeURIComponent(slug)}?version=latest`;
17
+ const headers = { accept: "application/json" };
18
+ if (authBearer)
19
+ headers.authorization = `Bearer ${authBearer}`;
20
+ const res = await fetch(url, { headers });
21
+ if (!res.ok) {
22
+ throw new Error(`Bundle persona/${slug} fetch 실패 · HTTP ${res.status} · ${await res.text()}`);
23
+ }
24
+ const bundle = (await res.json());
25
+ return { bundle, hash_ok: hashBody(bundle.body) === bundle.hash };
26
+ }
27
+ /**
28
+ * Full prompt 조립 (ADR-009 canonical 순서):
29
+ * L3 header → L2 body → L3 patches → L1 body (마지막 = 성역)
30
+ */
31
+ export function assembleFullPrompt(opts) {
32
+ const parts = [];
33
+ if (opts.layer3Header?.trim()) {
34
+ parts.push("# === LAYER 3 · LOCAL JOURNAL (header) ===", opts.layer3Header.trim(), "");
35
+ }
36
+ parts.push("# === LAYER 2 · PERSONA BODY ===", opts.layer2.trim(), "");
37
+ if (opts.layer3Patches?.trim()) {
38
+ parts.push("# === LAYER 3 · PATCHES (accumulated) ===", opts.layer3Patches.trim(), "");
39
+ }
40
+ parts.push("# === LAYER 1 · COMMON (SACRED · overrides all) ===", opts.layer1.trim());
41
+ return parts.join("\n");
42
+ }
43
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src-server/bundle.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAkBxC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,aAAqB;IAErB,MAAM,GAAG,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,mCAAmC,CAAC;IACnF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;IAC1E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAe,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,aAAqB,EACrB,IAAY,EACZ,UAAmB;IAEnB,MAAM,GAAG,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,uBAAuB,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,CAAC;IAChH,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACvE,IAAI,UAAU;QAAE,OAAO,CAAC,aAAa,GAAG,UAAU,UAAU,EAAE,CAAC;IAC/D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,oBAAoB,GAAG,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAe,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAKlC;IACC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,4CAA4C,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,kCAAkC,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACvE,IAAI,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,2CAA2C,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,qDAAqD,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACtF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * KRX 휴장일 캘린더 (한국거래소).
3
+ *
4
+ * 데이터 소스 (우선순위):
5
+ * 1. ~/.mulsok-traders/krx-holidays.json (사용자 override · 정확한 발표 반영)
6
+ * 2. 정적 fallback 리스트 (이 파일 · 2026·2027 추정치 · 발표 변경 가능)
7
+ *
8
+ * 사용:
9
+ * - normalizeKstDateMarker("다음거래일T...") · 휴장일 자동 skip
10
+ * - isKrxHoliday(date), nextTradingDay(date), isTradingDay(date)
11
+ *
12
+ * ⚠️ 정적 리스트는 추정이며 한국거래소 공식 발표와 어긋날 수 있음.
13
+ * 실 거래 운영 시 사용자가 ~/.mulsok-traders/krx-holidays.json 으로 갱신 권장.
14
+ * 공식 발표: https://open.krx.co.kr (시장 운영 공지)
15
+ */
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import os from "node:os";
19
+ const USER_OVERRIDE_PATH = path.join(os.homedir(), ".mulsok-traders", "krx-holidays.json");
20
+ /**
21
+ * 정적 KRX 휴장일 (YYYY-MM-DD KST · 2026·2027 추정).
22
+ *
23
+ * 포함:
24
+ * - 일요일·토요일 자동 skip (별도 계산 · 이 리스트에 없음)
25
+ * - 신정 · 삼일절 · 어린이날 · 부처님오신날 · 현충일 · 광복절 · 개천절 · 한글날 · 성탄절
26
+ * - 설날·추석 연휴 (음력 기준)
27
+ * - 12월 31일 (KRX 연말 폐장)
28
+ * - 대체공휴일 (토/일과 겹치는 경우)
29
+ *
30
+ * 갱신 책임: 사용자 (또는 매년 12월 KRX 공식 발표 후 SPEC 갱신).
31
+ */
32
+ const STATIC_KRX_HOLIDAYS = new Set([
33
+ // ── 2026 ──
34
+ "2026-01-01", // 신정
35
+ "2026-02-16", // 설날 연휴 (월)
36
+ "2026-02-17", // 설날 (화 · 음력 1/1)
37
+ "2026-02-18", // 설날 연휴 (수)
38
+ "2026-03-02", // 삼일절 대체 (3/1 일요일)
39
+ "2026-05-05", // 어린이날
40
+ "2026-05-25", // 부처님오신날 (음력 4/8)
41
+ "2026-08-17", // 광복절 대체 (8/15 토요일)
42
+ "2026-09-24", // 추석 연휴 (목)
43
+ "2026-09-25", // 추석 (금 · 음력 8/15)
44
+ "2026-09-28", // 추석 대체 (9/26 토요일)
45
+ "2026-10-05", // 개천절 대체 (10/3 토요일)
46
+ "2026-10-09", // 한글날
47
+ "2026-12-25", // 성탄절
48
+ "2026-12-31", // 연말 폐장
49
+ // ── 2027 ──
50
+ "2027-01-01", // 신정
51
+ "2027-02-08", // 설날 연휴
52
+ "2027-02-09", // 설날 (음력 1/1)
53
+ "2027-02-10", // 설날 연휴
54
+ "2027-03-01", // 삼일절
55
+ "2027-05-05", // 어린이날
56
+ "2027-05-13", // 부처님오신날 (음력 4/8)
57
+ "2027-08-16", // 광복절 대체 (8/15 일요일)
58
+ "2027-09-14", // 추석 연휴
59
+ "2027-09-15", // 추석 (음력 8/15)
60
+ "2027-09-16", // 추석 연휴
61
+ "2027-10-04", // 개천절 대체 (10/3 일요일)
62
+ "2027-10-11", // 한글날 대체 (10/9 토요일)
63
+ "2027-12-31", // 연말 폐장
64
+ ]);
65
+ let userOverrideCache = null;
66
+ /** 사용자 override 로드 (변경 감지 + 캐시) */
67
+ function loadUserOverride() {
68
+ try {
69
+ if (!fs.existsSync(USER_OVERRIDE_PATH))
70
+ return null;
71
+ const stat = fs.statSync(USER_OVERRIDE_PATH);
72
+ const mtime = stat.mtimeMs;
73
+ if (userOverrideCache && userOverrideCache.mtime === mtime) {
74
+ return userOverrideCache.set;
75
+ }
76
+ const raw = fs.readFileSync(USER_OVERRIDE_PATH, "utf-8");
77
+ const parsed = JSON.parse(raw);
78
+ if (!Array.isArray(parsed)) {
79
+ console.warn(`[krx-holidays] ${USER_OVERRIDE_PATH} 형식 오류 (배열 아님) · static fallback 사용`);
80
+ return null;
81
+ }
82
+ const set = new Set();
83
+ for (const item of parsed) {
84
+ if (typeof item === "string" && /^\d{4}-\d{2}-\d{2}$/.test(item))
85
+ set.add(item);
86
+ }
87
+ userOverrideCache = { mtime, set };
88
+ return set;
89
+ }
90
+ catch (e) {
91
+ console.warn(`[krx-holidays] override 로드 실패 · ${e}`);
92
+ return null;
93
+ }
94
+ }
95
+ /** YYYY-MM-DD (KST) 문자열 → KRX 휴장일 여부 (공휴일만 · 토/일 별도) */
96
+ export function isKrxHolidayKey(yyyyMmDd) {
97
+ const override = loadUserOverride();
98
+ if (override)
99
+ return override.has(yyyyMmDd);
100
+ return STATIC_KRX_HOLIDAYS.has(yyyyMmDd);
101
+ }
102
+ /** Date (UTC) → KST 기준 YYYY-MM-DD */
103
+ function toKstYmd(d) {
104
+ const kst = new Date(d.getTime() + 9 * 3600_000);
105
+ return kst.toISOString().slice(0, 10);
106
+ }
107
+ /** Date → KST 기준 요일 (0=일, 6=토) */
108
+ function kstDay(d) {
109
+ const kst = new Date(d.getTime() + 9 * 3600_000);
110
+ return kst.getUTCDay();
111
+ }
112
+ /** Date → KRX 거래일 여부 (평일 + 휴장일 아님) */
113
+ export function isTradingDay(d) {
114
+ const day = kstDay(d);
115
+ if (day === 0 || day === 6)
116
+ return false; // 일·토
117
+ return !isKrxHolidayKey(toKstYmd(d));
118
+ }
119
+ /** Date → KRX 휴장일 (휴장일 또는 주말) */
120
+ export function isKrxHoliday(d) {
121
+ return !isTradingDay(d);
122
+ }
123
+ /**
124
+ * 주어진 시각 이후 (또는 같은 날) 다음 거래일.
125
+ * - input 이 거래일 · 09:00 이전 → input 그대로 (같은 날 장 시작 가능)
126
+ * - input 이 거래일 · 09:00 이후 → 다음 거래일
127
+ * - input 이 휴장일 → 다음 거래일
128
+ *
129
+ * KST 기준. 시각 부분은 input 그대로 유지 (날짜만 변경).
130
+ *
131
+ * @param d 기준 시점 (UTC Date)
132
+ * @param afterMarketHours true 면 input 이 거래일이라도 다음 거래일 반환 (장 마감 후 호출 시)
133
+ */
134
+ export function nextTradingDay(d, afterMarketHours = false) {
135
+ let cursor = new Date(d.getTime());
136
+ // input 이 거래일이고 afterMarketHours=true 면 +1일
137
+ if (afterMarketHours && isTradingDay(cursor)) {
138
+ cursor = new Date(cursor.getTime() + 86400_000);
139
+ }
140
+ // 거래일 찾을 때까지 +1일
141
+ let safety = 0;
142
+ while (!isTradingDay(cursor)) {
143
+ cursor = new Date(cursor.getTime() + 86400_000);
144
+ safety++;
145
+ if (safety > 30) {
146
+ // 30일 연속 휴장은 비정상 — fallback 으로 다음 평일 반환
147
+ console.warn(`[krx-holidays] nextTradingDay safety break · ${cursor.toISOString()}`);
148
+ break;
149
+ }
150
+ }
151
+ return cursor;
152
+ }
153
+ /** 통계용 — 디버깅 시 정적 리스트 size + override 활성 여부 */
154
+ export function holidayStats() {
155
+ const override = loadUserOverride();
156
+ return {
157
+ staticCount: STATIC_KRX_HOLIDAYS.size,
158
+ overrideActive: override !== null,
159
+ overrideCount: override?.size ?? 0,
160
+ };
161
+ }
162
+ //# sourceMappingURL=krx-holidays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"krx-holidays.js","sourceRoot":"","sources":["../../../src-server/calendar/krx-holidays.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Client Config · 로컬 JSON 파일 저장.
3
+ * MVP · OS Keychain 은 향후 Tauri 포장 시 교체 (현재는 ~/.mulsok-traders/config.json).
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import os from "node:os";
8
+ import { z } from "zod";
9
+ const CONFIG_DIR = path.join(os.homedir(), ".mulsok-traders");
10
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
11
+ /**
12
+ * Server Base URL · 내장 상수.
13
+ * 프로덕션: mulsok-traders.vercel.app (기본)
14
+ * 개발: `MULSOK_SERVER_URL=http://localhost:3000 npm run dev`
15
+ */
16
+ export const SERVER_BASE_URL = process.env.MULSOK_SERVER_URL ?? "https://mulsok-traders.vercel.app";
17
+ export const LlmProviderKind = z.enum([
18
+ "none",
19
+ "gemini",
20
+ "openai",
21
+ "anthropic",
22
+ "claude-cli",
23
+ ]);
24
+ export const ClientConfigSchema = z.object({
25
+ version: z.literal(1).default(1),
26
+ llm: z.object({
27
+ provider: LlmProviderKind.default("none"),
28
+ apiKey: z.string().optional(), // gemini / openai / anthropic 용
29
+ model: z.string().optional(), // 예: gemini-2.5-flash, gpt-4o-mini, claude-sonnet-4-5, sonnet
30
+ claudeCli: z.object({
31
+ binaryPath: z.string().optional(), // 기본: `claude` (PATH 에서 찾음)
32
+ model: z.string().default("sonnet"), // sonnet / opus / haiku
33
+ }).optional(),
34
+ }).default({ provider: "none" }),
35
+ broker: z.object({
36
+ kind: z.enum(["none", "kiwoom", "kis"]).default("none"),
37
+ appKey: z.string().optional(), // 키움 AppKey · KIS APP_KEY
38
+ appSecret: z.string().optional(), // 키움 SecretKey · KIS APP_SECRET
39
+ isDemo: z.boolean().default(true), // 모의투자 · 실전 토글 · 기본 안전
40
+ // 계좌번호: 주문 실행 단계에서 키움 API 가 자동 감지 예정 (사용자 입력 불필요)
41
+ }).default({ kind: "none", isDemo: true }),
42
+ auth: z.object({
43
+ deviceToken: z.string().optional(), // mtd_... · 웹 대시보드에서 발급
44
+ }).default({}),
45
+ // 구독한 페르소나 슬러그 목록 (SaaS 구독 모델 · ADR-001).
46
+ // server SSoT (`/api/client/ping` 응답) 의 캐시 (사용자 임의 추가 X).
47
+ subscriptions: z.array(z.string()).default([]),
48
+ /**
49
+ * 페르소나별 배정 자본 (KRW · taeeun-life allocatedCapital 컨셉)
50
+ * 페르소나는 자기 받은 자본 안에서만 운용. 초과 주문 차단 (SPEC-26).
51
+ * 향후 server `/api/me/instances.allocated_capital_krw` 동기화 예정.
52
+ * 사용자 설정 탭에서 manual 입력 가능.
53
+ */
54
+ allocations: z.record(z.string(), z.number().nonnegative()).default({}),
55
+ /**
56
+ * 응급 정지 상태 (SPEC-26 Emergency Stop).
57
+ * true 시 모든 주문·LLM 호출 차단. 사용자가 UI 토글로 해제.
58
+ */
59
+ halted: z.boolean().default(false),
60
+ });
61
+ export const DEFAULT_CONFIG = ClientConfigSchema.parse({});
62
+ function ensureDir() {
63
+ if (!fs.existsSync(CONFIG_DIR)) {
64
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
65
+ }
66
+ }
67
+ /** 로컬 config 로드 · file → env override → default 우선순위 */
68
+ export function loadConfig() {
69
+ let fromFile = DEFAULT_CONFIG;
70
+ try {
71
+ if (fs.existsSync(CONFIG_FILE)) {
72
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
73
+ const parsed = ClientConfigSchema.safeParse(JSON.parse(raw));
74
+ if (parsed.success) {
75
+ fromFile = parsed.data;
76
+ }
77
+ else {
78
+ console.warn("[config] invalid format · default 사용:", parsed.error.issues);
79
+ }
80
+ }
81
+ }
82
+ catch (e) {
83
+ console.warn("[config] load 실패 · default:", e);
84
+ }
85
+ // env override — webui 안 쓰는 사용자 (CI/CD · 자동 배포 · 비밀 관리자) 시나리오 지원
86
+ return mergeFromEnv(fromFile);
87
+ }
88
+ /**
89
+ * 환경 변수로 config override.
90
+ *
91
+ * 우선순위: env > file > default
92
+ * 지원 키:
93
+ * MULSOK_BROKER_KIND · APPKEY · APPSECRET · DEMO
94
+ * MULSOK_LLM_PROVIDER · APIKEY · MODEL
95
+ * MULSOK_DEVICE_TOKEN
96
+ * MULSOK_HALTED
97
+ * MULSOK_ALLOCATION_<SLUG>=<KRW> (예: MULSOK_ALLOCATION_NEOUL=1000000)
98
+ *
99
+ * 빈 문자열 / undefined / 잘못된 enum 은 무시 (file 값 보존).
100
+ */
101
+ export function mergeFromEnv(cfg) {
102
+ const env = process.env;
103
+ const broker = { ...cfg.broker };
104
+ const brokerKind = env.MULSOK_BROKER_KIND;
105
+ if (brokerKind === "none" || brokerKind === "kiwoom" || brokerKind === "kis") {
106
+ broker.kind = brokerKind;
107
+ }
108
+ if (env.MULSOK_BROKER_APPKEY)
109
+ broker.appKey = env.MULSOK_BROKER_APPKEY;
110
+ if (env.MULSOK_BROKER_APPSECRET)
111
+ broker.appSecret = env.MULSOK_BROKER_APPSECRET;
112
+ if (env.MULSOK_BROKER_DEMO != null && env.MULSOK_BROKER_DEMO !== "") {
113
+ broker.isDemo = env.MULSOK_BROKER_DEMO === "true";
114
+ }
115
+ const llm = { ...cfg.llm };
116
+ const provider = env.MULSOK_LLM_PROVIDER;
117
+ if (provider && LlmProviderKind.options.includes(provider)) {
118
+ llm.provider = provider;
119
+ }
120
+ if (env.MULSOK_LLM_APIKEY)
121
+ llm.apiKey = env.MULSOK_LLM_APIKEY;
122
+ if (env.MULSOK_LLM_MODEL)
123
+ llm.model = env.MULSOK_LLM_MODEL;
124
+ const auth = { ...cfg.auth };
125
+ if (env.MULSOK_DEVICE_TOKEN)
126
+ auth.deviceToken = env.MULSOK_DEVICE_TOKEN;
127
+ let halted = cfg.halted;
128
+ if (env.MULSOK_HALTED != null && env.MULSOK_HALTED !== "") {
129
+ halted = env.MULSOK_HALTED === "true";
130
+ }
131
+ // allocations: MULSOK_ALLOCATION_<SLUG>=<KRW>
132
+ const allocations = { ...cfg.allocations };
133
+ for (const [k, v] of Object.entries(env)) {
134
+ if (!k.startsWith("MULSOK_ALLOCATION_") || !v)
135
+ continue;
136
+ const slug = k.replace("MULSOK_ALLOCATION_", "").toLowerCase();
137
+ const n = Number(v);
138
+ if (Number.isFinite(n) && n >= 0)
139
+ allocations[slug] = n;
140
+ }
141
+ return {
142
+ ...cfg,
143
+ broker,
144
+ llm,
145
+ auth,
146
+ halted,
147
+ allocations,
148
+ };
149
+ }
150
+ /** config 저장 · 0600 권한 */
151
+ export function saveConfig(cfg) {
152
+ ensureDir();
153
+ const validated = ClientConfigSchema.parse(cfg);
154
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(validated, null, 2), { mode: 0o600 });
155
+ return validated;
156
+ }
157
+ /** API key 마스킹 · GET /api/config 응답 시 사용 */
158
+ export function maskedConfig(cfg) {
159
+ const mask = (s) => {
160
+ if (!s)
161
+ return undefined;
162
+ if (s.length <= 10)
163
+ return "***";
164
+ return s.slice(0, 6) + "..." + s.slice(-4);
165
+ };
166
+ return {
167
+ ...cfg,
168
+ llm: {
169
+ ...cfg.llm,
170
+ apiKey: mask(cfg.llm.apiKey),
171
+ },
172
+ broker: {
173
+ ...cfg.broker,
174
+ appKey: mask(cfg.broker.appKey),
175
+ appSecret: mask(cfg.broker.appSecret),
176
+ },
177
+ auth: {
178
+ ...cfg.auth,
179
+ deviceToken: mask(cfg.auth.deviceToken),
180
+ },
181
+ };
182
+ }
183
+ /**
184
+ * maskedConfig 가 만든 마스킹 형태 (`***` 또는 `xxxxxx...yyyy`) 인지 감지.
185
+ * UI 가 GET 응답을 그대로 POST 로 다시 보내는 실수를 백엔드에서 차단하기 위함.
186
+ * 정상 비밀값(키움 AppKey, deviceToken 등)은 `...` 를 포함하지 않거나, 길이/형태가 다름.
187
+ */
188
+ export function isMaskedSecret(s) {
189
+ if (typeof s !== "string")
190
+ return false;
191
+ if (s === "***")
192
+ return true;
193
+ // maskedConfig: head(6) + "..." + tail(4) = 정확히 13자
194
+ return s.length === 13 && s.slice(6, 9) === "...";
195
+ }
196
+ /**
197
+ * POST /api/config partial merge.
198
+ *
199
+ * 규칙:
200
+ * - 비밀값(llm.apiKey · auth.deviceToken · broker.appKey/appSecret): 빈문자열·미포함·**마스킹 sentinel** → 기존 유지
201
+ * - 공개값(provider · model · broker.kind · isDemo): 명시 시 교체
202
+ * - patch 에 없는 최상위 섹션(subscriptions/allocations/halted/llm/broker/auth): **기존 유지** (default 로 리셋 X)
203
+ *
204
+ * 결함 회귀 차단: 한 섹션 저장이 다른 섹션을 덮어쓰지 않도록 모든 최상위 필드를 명시적 보존.
205
+ */
206
+ export function mergeConfigPatch(existing, patch) {
207
+ const preserveSecret = (incoming, current) => {
208
+ if (typeof incoming === "string" && incoming.length > 0 && !isMaskedSecret(incoming))
209
+ return incoming;
210
+ return current;
211
+ };
212
+ // patch 에 섹션이 명시 안 되면 기존 그대로 — default 로 리셋 X
213
+ const has = (k) => Object.prototype.hasOwnProperty.call(patch, k);
214
+ const mergedLlm = (() => {
215
+ if (!has("llm"))
216
+ return existing.llm;
217
+ const p = (patch.llm ?? {});
218
+ return {
219
+ provider: p.provider ?? existing.llm.provider,
220
+ apiKey: preserveSecret(p.apiKey, existing.llm.apiKey),
221
+ model: p.model === undefined ? existing.llm.model : p.model,
222
+ claudeCli: p.claudeCli === undefined
223
+ ? existing.llm.claudeCli
224
+ : p.claudeCli,
225
+ };
226
+ })();
227
+ const mergedBroker = (() => {
228
+ if (!has("broker"))
229
+ return existing.broker;
230
+ const p = (patch.broker ?? {});
231
+ return {
232
+ kind: p.kind ?? existing.broker.kind,
233
+ appKey: preserveSecret(p.appKey, existing.broker.appKey),
234
+ appSecret: preserveSecret(p.appSecret, existing.broker.appSecret),
235
+ isDemo: typeof p.isDemo === "boolean" ? p.isDemo : existing.broker.isDemo,
236
+ };
237
+ })();
238
+ const mergedAuth = (() => {
239
+ if (!has("auth"))
240
+ return existing.auth;
241
+ const p = (patch.auth ?? {});
242
+ return {
243
+ deviceToken: preserveSecret(p.deviceToken, existing.auth?.deviceToken),
244
+ };
245
+ })();
246
+ const merged = {
247
+ version: 1,
248
+ llm: mergedLlm,
249
+ broker: mergedBroker,
250
+ auth: mergedAuth,
251
+ // 누락 시 기존 유지 (default 리셋 차단)
252
+ subscriptions: has("subscriptions")
253
+ ? patch.subscriptions
254
+ : existing.subscriptions,
255
+ allocations: has("allocations")
256
+ ? patch.allocations
257
+ : existing.allocations,
258
+ halted: typeof patch.halted === "boolean" ? patch.halted : existing.halted,
259
+ };
260
+ return ClientConfigSchema.parse(merged);
261
+ }
262
+ export const CONFIG_PATHS = { dir: CONFIG_DIR, file: CONFIG_FILE };
263
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src-server/config.ts"],"names":[],"mappings":"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"}