@triflux/remote 10.1.0 → 10.25.1

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 (155) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +61 -0
  3. package/hub/server.mjs +2165 -708
  4. package/hub/store-adapter.mjs +59 -26
  5. package/hub/store.mjs +356 -153
  6. package/hub/team/agent-map.json +26 -7
  7. package/hub/team/ansi.mjs +245 -146
  8. package/hub/team/backend.mjs +84 -17
  9. package/hub/team/build-worker-prompt.mjs +42 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +219 -0
  12. package/hub/team/claude-native-bridge.mjs +784 -0
  13. package/hub/team/claude-session-projection.mjs +65 -0
  14. package/hub/team/cli/commands/attach.mjs +57 -37
  15. package/hub/team/cli/commands/control.mjs +29 -8
  16. package/hub/team/cli/commands/debug.mjs +95 -74
  17. package/hub/team/cli/commands/focus.mjs +80 -53
  18. package/hub/team/cli/commands/interrupt.mjs +18 -6
  19. package/hub/team/cli/commands/kill.mjs +16 -5
  20. package/hub/team/cli/commands/list.mjs +31 -24
  21. package/hub/team/cli/commands/send.mjs +19 -6
  22. package/hub/team/cli/commands/start/index.mjs +154 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +89 -12
  24. package/hub/team/cli/commands/start/start-headless.mjs +125 -37
  25. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  26. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  27. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  28. package/hub/team/cli/commands/status.mjs +43 -14
  29. package/hub/team/cli/commands/stop.mjs +11 -4
  30. package/hub/team/cli/commands/task.mjs +8 -3
  31. package/hub/team/cli/commands/tasks.mjs +13 -13
  32. package/hub/team/cli/index.mjs +2 -2
  33. package/hub/team/cli/manifest.mjs +38 -8
  34. package/hub/team/cli/render.mjs +52 -30
  35. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  36. package/hub/team/cli/services/hub-client.mjs +44 -16
  37. package/hub/team/cli/services/member-selector.mjs +37 -30
  38. package/hub/team/cli/services/native-control.mjs +144 -116
  39. package/hub/team/cli/services/runtime-mode.mjs +55 -1
  40. package/hub/team/cli/services/state-store.mjs +66 -9
  41. package/hub/team/cli/services/task-model.mjs +40 -30
  42. package/hub/team/codex-review.mjs +521 -0
  43. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  44. package/hub/team/conductor-registry.mjs +121 -0
  45. package/hub/team/conductor.mjs +1379 -671
  46. package/hub/team/consensus-meta.mjs +176 -0
  47. package/hub/team/dashboard-anchor.mjs +4 -5
  48. package/hub/team/dashboard-layout.mjs +3 -1
  49. package/hub/team/dashboard-open.mjs +20 -118
  50. package/hub/team/dashboard.mjs +76 -28
  51. package/hub/team/event-log.mjs +18 -10
  52. package/hub/team/execution-mode.mjs +241 -0
  53. package/hub/team/extract-completion-payload.mjs +110 -0
  54. package/hub/team/git-preflight.mjs +286 -0
  55. package/hub/team/handoff.mjs +49 -18
  56. package/hub/team/headless-bridge-session.mjs +17 -0
  57. package/hub/team/headless.mjs +2065 -1156
  58. package/hub/team/health-probe.mjs +232 -69
  59. package/hub/team/launcher-template.mjs +23 -13
  60. package/hub/team/lead-control.mjs +91 -4
  61. package/hub/team/mcp-selector.mjs +145 -0
  62. package/hub/team/native-supervisor.mjs +90 -26
  63. package/hub/team/native.mjs +74 -35
  64. package/hub/team/nativeProxy.mjs +184 -113
  65. package/hub/team/notify.mjs +147 -78
  66. package/hub/team/orchestrator.mjs +173 -161
  67. package/hub/team/pane.mjs +43 -15
  68. package/hub/team/process-cleanup.mjs +69 -25
  69. package/hub/team/psmux.mjs +1020 -279
  70. package/hub/team/recovery-store.mjs +149 -0
  71. package/hub/team/remote-probe.mjs +68 -52
  72. package/hub/team/remote-session.mjs +131 -59
  73. package/hub/team/remote-watcher.mjs +61 -33
  74. package/hub/team/retry-state-machine.mjs +267 -0
  75. package/hub/team/routing.mjs +51 -25
  76. package/hub/team/runtime-strategy.mjs +200 -0
  77. package/hub/team/schemas/worker-completion.json +39 -0
  78. package/hub/team/sentinel-capture.mjs +127 -0
  79. package/hub/team/session-sync.mjs +153 -6
  80. package/hub/team/session.mjs +605 -611
  81. package/hub/team/shared.mjs +13 -13
  82. package/hub/team/staleState.mjs +72 -30
  83. package/hub/team/swarm-cli.mjs +390 -0
  84. package/hub/team/swarm-hypervisor.mjs +1522 -193
  85. package/hub/team/swarm-intent.mjs +216 -0
  86. package/hub/team/swarm-locks.mjs +140 -25
  87. package/hub/team/swarm-planner.mjs +121 -21
  88. package/hub/team/swarm-preflight.mjs +317 -0
  89. package/hub/team/swarm-reconciler.mjs +128 -23
  90. package/hub/team/synapse-cli.mjs +458 -0
  91. package/hub/team/synapse-http.mjs +60 -0
  92. package/hub/team/synapse-registry.mjs +329 -0
  93. package/hub/team/terminal-opener.mjs +216 -0
  94. package/hub/team/tui-core.mjs +288 -0
  95. package/hub/team/tui-lite.mjs +234 -146
  96. package/hub/team/tui-remote-adapter.mjs +18 -34
  97. package/hub/team/tui-synapse.mjs +223 -0
  98. package/hub/team/tui-viewer.mjs +104 -44
  99. package/hub/team/tui-widgets.mjs +262 -0
  100. package/hub/team/tui.mjs +927 -437
  101. package/hub/team/worker-completion-validator.mjs +84 -0
  102. package/hub/team/worker-sandbox.mjs +93 -0
  103. package/hub/team/worker-signal.mjs +263 -0
  104. package/hub/team/worker-signal.types.d.ts +52 -0
  105. package/hub/team/worktree-lifecycle.mjs +517 -63
  106. package/hub/team/wt-manager.mjs +297 -26
  107. package/hub/tools.mjs +474 -252
  108. package/hub/tray.mjs +411 -368
  109. package/hub/workers/claude-worker.mjs +162 -118
  110. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  111. package/hub/workers/codex-mcp.mjs +724 -507
  112. package/hub/workers/delegator-mcp.mjs +662 -335
  113. package/hub/workers/factory.mjs +100 -21
  114. package/hub/workers/gemini-worker.mjs +179 -86
  115. package/hub/workers/interface.mjs +23 -2
  116. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  117. package/hub/workers/worker-utils.mjs +22 -16
  118. package/package.json +4 -3
  119. package/scripts/lib/agent-json.mjs +27 -0
  120. package/scripts/lib/async.mjs +174 -0
  121. package/scripts/lib/claudemd-scanner.mjs +223 -0
  122. package/scripts/lib/cli-agy.mjs +62 -0
  123. package/scripts/lib/cli-claude.mjs +78 -0
  124. package/scripts/lib/cli-codex.mjs +199 -0
  125. package/scripts/lib/cli-gemini.mjs +67 -0
  126. package/scripts/lib/codex-recovery.sh +50 -0
  127. package/scripts/lib/context.mjs +67 -0
  128. package/scripts/lib/cross-review-utils.mjs +59 -0
  129. package/scripts/lib/doctor-env-checks.mjs +121 -0
  130. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  131. package/scripts/lib/env-probe.mjs +269 -0
  132. package/scripts/lib/env.mjs +14 -0
  133. package/scripts/lib/gemini-profiles.mjs +119 -0
  134. package/scripts/lib/handoff.mjs +187 -0
  135. package/scripts/lib/hook-utils.mjs +16 -0
  136. package/scripts/lib/hub.mjs +48 -0
  137. package/scripts/lib/keyword-rules.mjs +195 -0
  138. package/scripts/lib/logger.mjs +105 -0
  139. package/scripts/lib/mcp-filter.mjs +883 -0
  140. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  141. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  142. package/scripts/lib/mcp-health.mjs +773 -0
  143. package/scripts/lib/mcp-manifest.mjs +89 -0
  144. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  145. package/scripts/lib/pid.mjs +63 -0
  146. package/scripts/lib/process-utils.mjs +26 -0
  147. package/scripts/lib/psmux-info.mjs +179 -0
  148. package/scripts/lib/quota.mjs +47 -0
  149. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  150. package/scripts/lib/skill-state.mjs +220 -0
  151. package/scripts/lib/skill-template.mjs +375 -0
  152. package/scripts/lib/team.mjs +75 -0
  153. package/scripts/lib/timeout.mjs +66 -0
  154. package/scripts/lib/tmp.mjs +26 -0
  155. package/scripts/lib/toml.mjs +113 -0
@@ -1,11 +1,30 @@
1
1
  {
2
- "executor": "codex", "build-fixer": "codex", "debugger": "codex", "deep-executor": "codex",
3
- "architect": "codex", "planner": "codex", "critic": "codex", "analyst": "codex",
4
- "code-reviewer": "codex", "security-reviewer": "codex", "quality-reviewer": "codex",
5
- "scientist": "codex", "scientist-deep": "codex", "document-specialist": "codex",
2
+ "executor": "codex",
3
+ "build-fixer": "codex",
4
+ "cleanup": "codex",
5
+ "deslop": "codex",
6
+ "debugger": "codex",
7
+ "deep-executor": "codex",
8
+ "architect": "codex",
9
+ "planner": "codex",
10
+ "critic": "codex",
11
+ "analyst": "codex",
12
+ "code-reviewer": "codex",
13
+ "security-reviewer": "codex",
14
+ "quality-reviewer": "codex",
15
+ "scientist": "codex",
16
+ "scientist-deep": "codex",
17
+ "document-specialist": "codex",
6
18
  "spark": "codex",
7
- "designer": "gemini", "writer": "gemini",
19
+ "designer": "gemini",
20
+ "writer": "gemini",
8
21
  "explore": "claude",
9
- "verifier": "codex", "test-engineer": "codex", "qa-tester": "codex",
10
- "codex": "codex", "gemini": "gemini", "claude": "claude"
22
+ "verifier": "codex",
23
+ "test-engineer": "codex",
24
+ "qa-tester": "codex",
25
+ "codex": "codex",
26
+ "gemini": "gemini",
27
+ "antigravity": "antigravity",
28
+ "agy": "antigravity",
29
+ "claude": "claude"
11
30
  }
package/hub/team/ansi.mjs CHANGED
@@ -7,54 +7,60 @@
7
7
  export const ESC = "\x1b";
8
8
 
9
9
  // ── 화면 ──
10
- export const altScreenOn = `${ESC}[?1049h`;
10
+ export const altScreenOn = `${ESC}[?1049h`;
11
11
  export const altScreenOff = `${ESC}[?1049l`;
12
- export const clearScreen = `${ESC}[2J`;
13
- export const cursorHome = `${ESC}[H`;
14
- export const cursorHide = `${ESC}[?25l`;
15
- export const cursorShow = `${ESC}[?25h`;
12
+ export const clearScreen = `${ESC}[2J`;
13
+ export const cursorHome = `${ESC}[H`;
14
+ export const cursorHide = `${ESC}[?25l`;
15
+ export const cursorShow = `${ESC}[?25h`;
16
16
 
17
17
  // ── 커서 이동 ──
18
- export function moveTo(row, col) { return `${ESC}[${row};${col}H`; }
19
- export function moveUp(n = 1) { return `${ESC}[${n}A`; }
20
- export function moveDown(n = 1) { return `${ESC}[${n}B`; }
18
+ export function moveTo(row, col) {
19
+ return `${ESC}[${row};${col}H`;
20
+ }
21
+ export function moveUp(n = 1) {
22
+ return `${ESC}[${n}A`;
23
+ }
24
+ export function moveDown(n = 1) {
25
+ return `${ESC}[${n}B`;
26
+ }
21
27
 
22
28
  // ── 줄 제어 ──
23
- export const clearLine = `${ESC}[2K`;
24
- export const clearToEnd = `${ESC}[K`;
25
- export const eraseBelow = `${ESC}[J`;
29
+ export const clearLine = `${ESC}[2K`;
30
+ export const clearToEnd = `${ESC}[K`;
31
+ export const eraseBelow = `${ESC}[J`;
26
32
 
27
33
  // ── 색상 (triflux 디자인 시스템) ──
28
34
  export const RESET = `${ESC}[0m`;
29
- export const BOLD = `${ESC}[1m`;
30
- export const DIM = `${ESC}[2m`;
35
+ export const BOLD = `${ESC}[1m`;
36
+ export const DIM = `${ESC}[2m`;
31
37
 
32
38
  export const FG = {
33
- white: `${ESC}[97m`,
34
- black: `${ESC}[30m`,
35
- red: `${ESC}[31m`,
36
- green: `${ESC}[32m`,
37
- yellow: `${ESC}[33m`,
38
- blue: `${ESC}[34m`,
39
- magenta: `${ESC}[35m`,
40
- cyan: `${ESC}[36m`,
41
- gray: `${ESC}[90m`,
39
+ white: `${ESC}[97m`,
40
+ black: `${ESC}[30m`,
41
+ red: `${ESC}[31m`,
42
+ green: `${ESC}[32m`,
43
+ yellow: `${ESC}[33m`,
44
+ blue: `${ESC}[34m`,
45
+ magenta: `${ESC}[35m`,
46
+ cyan: `${ESC}[36m`,
47
+ gray: `${ESC}[90m`,
42
48
  // triflux 브랜드
43
- codex: `${ESC}[38;2;16;163;127m`, // #10a37f codex green
44
- gemini: `${ESC}[38;5;39m`, // blue
45
- claude: `${ESC}[38;2;232;112;64m`, // orange
46
- triflux: `${ESC}[38;5;214m`, // amber
47
- accent: `${ESC}[38;5;75m`, // light blue (Catppuccin blue)
48
- muted: `${ESC}[38;5;245m`, // gray
49
+ codex: `${ESC}[38;2;16;163;127m`, // #10a37f codex green
50
+ gemini: `${ESC}[38;5;39m`, // blue
51
+ claude: `${ESC}[38;2;232;112;64m`, // orange
52
+ triflux: `${ESC}[38;5;214m`, // amber
53
+ accent: `${ESC}[38;5;75m`, // light blue (Catppuccin blue)
54
+ muted: `${ESC}[38;5;245m`, // gray
49
55
  };
50
56
 
51
57
  export const BG = {
52
- black: `${ESC}[40m`,
53
- red: `${ESC}[41m`,
54
- green: `${ESC}[42m`,
55
- yellow: `${ESC}[43m`,
56
- blue: `${ESC}[44m`,
57
- header: `${ESC}[48;5;236m`, // dark gray
58
+ black: `${ESC}[40m`,
59
+ red: `${ESC}[41m`,
60
+ green: `${ESC}[42m`,
61
+ yellow: `${ESC}[43m`,
62
+ blue: `${ESC}[44m`,
63
+ header: `${ESC}[48;5;236m`, // dark gray
58
64
  };
59
65
 
60
66
  // ── 색상 헬퍼 ──
@@ -63,8 +69,12 @@ export function color(text, fg, bg) {
63
69
  return prefix ? `${prefix}${text}${RESET}` : text;
64
70
  }
65
71
 
66
- export function bold(text) { return `${BOLD}${text}${RESET}`; }
67
- export function dim(text) { return `${DIM}${text}${RESET}`; }
72
+ export function bold(text) {
73
+ return `${BOLD}${text}${RESET}`;
74
+ }
75
+ export function dim(text) {
76
+ return `${DIM}${text}${RESET}`;
77
+ }
68
78
 
69
79
  export function lerpRgb(a, b, t) {
70
80
  return {
@@ -83,9 +93,10 @@ function brightenRgb(rgb, amount = 0.3) {
83
93
  }
84
94
 
85
95
  function parseRgbSeq(seq) {
86
- const match = typeof seq === "string"
87
- ? seq.match(/\x1b\[(?:38|48);2;(\d+);(\d+);(\d+)m/)
88
- : null;
96
+ const match =
97
+ typeof seq === "string"
98
+ ? seq.match(/\x1b\[(?:38|48);2;(\d+);(\d+);(\d+)m/)
99
+ : null;
89
100
  if (!match) return null;
90
101
  return {
91
102
  r: Number.parseInt(match[1], 10),
@@ -100,7 +111,16 @@ function reapplyBackground(text, bgSeq) {
100
111
  }
101
112
 
102
113
  // ── 박스 그리기 (유니코드 테두리) ──
103
- const BOX = { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", ml: "├", mr: "┤" };
114
+ const BOX = {
115
+ tl: "┌",
116
+ tr: "┐",
117
+ bl: "└",
118
+ br: "┘",
119
+ h: "─",
120
+ v: "│",
121
+ ml: "├",
122
+ mr: "┤",
123
+ };
104
124
 
105
125
  function borderHighlightCell(width, totalRows, highlightPos) {
106
126
  if (!Number.isFinite(highlightPos)) return null;
@@ -118,7 +138,14 @@ function borderHighlightCell(width, totalRows, highlightPos) {
118
138
  return { row: totalRows - 1 - pos, col: 0 };
119
139
  }
120
140
 
121
- function renderBorderChar(glyph, row, col, highlightCell, borderSeq, highlightSeq) {
141
+ function renderBorderChar(
142
+ glyph,
143
+ row,
144
+ col,
145
+ highlightCell,
146
+ borderSeq,
147
+ highlightSeq,
148
+ ) {
122
149
  if (highlightCell && highlightCell.row === row && highlightCell.col === col) {
123
150
  return `${highlightSeq}${glyph}${RESET}`;
124
151
  }
@@ -128,28 +155,75 @@ function renderBorderChar(glyph, row, col, highlightCell, borderSeq, highlightSe
128
155
  export function box(lines, width, borderColor = "", options = {}) {
129
156
  const isFn = typeof borderColor === "function";
130
157
  const totalRows = lines.length + 2;
158
+ const highlightCell = borderHighlightCell(
159
+ width,
160
+ totalRows,
161
+ options.highlightPos,
162
+ );
163
+
164
+ // Fast path: static border, no highlight — batch border chars (~13x less output)
165
+ if (!highlightCell && !isFn) {
166
+ const bc = borderColor || "";
167
+ const rst = bc ? RESET : "";
168
+ const hLine = BOX.h.repeat(Math.max(0, width - 2));
169
+ const top = `${bc}${BOX.tl}${hLine}${BOX.tr}${rst}`;
170
+ const bot = `${bc}${BOX.bl}${hLine}${BOX.br}${rst}`;
171
+ const mid = `${bc}${BOX.ml}${hLine}${BOX.mr}${rst}`;
172
+ const body = lines.map((l, i) => {
173
+ const content =
174
+ options.titleFlashBg && i === 0
175
+ ? reapplyBackground(padRight(l, width - 4), options.titleFlashBg)
176
+ : padRight(l, width - 4);
177
+ return `${bc}${BOX.v}${rst} ${content} ${bc}${BOX.v}${rst}`;
178
+ });
179
+ return { top, body, bot, mid };
180
+ }
181
+
182
+ // Slow path: per-character rendering for highlight animation
131
183
  const bc = isFn ? (row) => borderColor(row, totalRows) : () => borderColor;
132
- const rst = (isFn || borderColor) ? RESET : "";
133
- const highlightCell = borderHighlightCell(width, totalRows, options.highlightPos);
134
- const highlightSeq = options.highlightColor
135
- || (() => {
136
- const parsed = parseRgbSeq(typeof borderColor === "string" ? borderColor : "");
184
+ const rst = isFn || borderColor ? RESET : "";
185
+ const highlightSeq =
186
+ options.highlightColor ||
187
+ (() => {
188
+ const parsed = parseRgbSeq(
189
+ typeof borderColor === "string" ? borderColor : "",
190
+ );
137
191
  return parsed ? rgbSeq(brightenRgb(parsed, 0.45)) : `${BOLD}${FG.white}`;
138
192
  })();
139
- const topChars = [BOX.tl, ...Array.from({ length: width - 2 }, () => BOX.h), BOX.tr];
140
- const botChars = [BOX.bl, ...Array.from({ length: width - 2 }, () => BOX.h), BOX.br];
193
+ const topChars = [
194
+ BOX.tl,
195
+ ...Array.from({ length: width - 2 }, () => BOX.h),
196
+ BOX.tr,
197
+ ];
198
+ const botChars = [
199
+ BOX.bl,
200
+ ...Array.from({ length: width - 2 }, () => BOX.h),
201
+ BOX.br,
202
+ ];
141
203
  const top = topChars
142
- .map((glyph, col) => renderBorderChar(glyph, 0, col, highlightCell, bc(0), highlightSeq))
204
+ .map((glyph, col) =>
205
+ renderBorderChar(glyph, 0, col, highlightCell, bc(0), highlightSeq),
206
+ )
143
207
  .join("");
144
208
  const bot = botChars
145
- .map((glyph, col) => renderBorderChar(glyph, totalRows - 1, col, highlightCell, bc(totalRows - 1), highlightSeq))
209
+ .map((glyph, col) =>
210
+ renderBorderChar(
211
+ glyph,
212
+ totalRows - 1,
213
+ col,
214
+ highlightCell,
215
+ bc(totalRows - 1),
216
+ highlightSeq,
217
+ ),
218
+ )
146
219
  .join("");
147
- const mid = `${bc(Math.floor(totalRows / 2))}${BOX.ml}${BOX.h.repeat(width - 2)}${BOX.mr}${rst}`;
220
+ const mid = `${bc(Math.floor(totalRows / 2))}${BOX.ml}${BOX.h.repeat(Math.max(0, width - 2))}${BOX.mr}${rst}`;
148
221
  const body = lines.map((l, i) => {
149
222
  const row = i + 1;
150
- const content = options.titleFlashBg && i === 0
151
- ? reapplyBackground(padRight(l, width - 4), options.titleFlashBg)
152
- : padRight(l, width - 4);
223
+ const content =
224
+ options.titleFlashBg && i === 0
225
+ ? reapplyBackground(padRight(l, width - 4), options.titleFlashBg)
226
+ : padRight(l, width - 4);
153
227
  return `${renderBorderChar(BOX.v, row, 0, highlightCell, bc(row), highlightSeq)} ${content} ${renderBorderChar(BOX.v, row, width - 1, highlightCell, bc(row), highlightSeq)}`;
154
228
  });
155
229
  return { top, body, bot, mid };
@@ -160,50 +234,53 @@ export function box(lines, width, borderColor = "", options = {}) {
160
234
  function charWidth(cp) {
161
235
  // combining / zero-width 범위
162
236
  if (
163
- cp === 0 || cp === 0xAD ||
164
- (cp >= 0x0300 && cp <= 0x036F) || // Combining Diacritical Marks
165
- (cp >= 0x0610 && cp <= 0x061A) ||
166
- (cp >= 0x064B && cp <= 0x065F) ||
167
- (cp >= 0x1AB0 && cp <= 0x1AFF) ||
168
- (cp >= 0x1DC0 && cp <= 0x1DFF) ||
169
- (cp >= 0x20D0 && cp <= 0x20FF) || // Combining Diacritical Marks for Symbols
170
- (cp >= 0xFE20 && cp <= 0xFE2F) // Combining Half Marks
171
- ) return 0;
237
+ cp === 0 ||
238
+ cp === 0xad ||
239
+ (cp >= 0x0300 && cp <= 0x036f) || // Combining Diacritical Marks
240
+ (cp >= 0x0610 && cp <= 0x061a) ||
241
+ (cp >= 0x064b && cp <= 0x065f) ||
242
+ (cp >= 0x1ab0 && cp <= 0x1aff) ||
243
+ (cp >= 0x1dc0 && cp <= 0x1dff) ||
244
+ (cp >= 0x20d0 && cp <= 0x20ff) || // Combining Diacritical Marks for Symbols
245
+ (cp >= 0xfe20 && cp <= 0xfe2f) // Combining Half Marks
246
+ )
247
+ return 0;
172
248
 
173
249
  // Wide: CJK Unified Ideographs, Hangul, Fullwidth, emoji 주요 블록
174
250
  if (
175
- (cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
176
- (cp >= 0x2E80 && cp <= 0x2EFF) || // CJK Radicals Supplement
177
- (cp >= 0x2F00 && cp <= 0x2FFF) ||
178
- (cp >= 0x3000 && cp <= 0x303F) || // CJK Symbols and Punctuation
179
- (cp >= 0x3040 && cp <= 0x309F) || // Hiragana
180
- (cp >= 0x30A0 && cp <= 0x30FF) || // Katakana
181
- (cp >= 0x3100 && cp <= 0x312F) ||
182
- (cp >= 0x3130 && cp <= 0x318F) || // Hangul Compatibility Jamo
183
- (cp >= 0x3190 && cp <= 0x319F) ||
184
- (cp >= 0x31C0 && cp <= 0x31EF) ||
185
- (cp >= 0x3200 && cp <= 0x32FF) ||
186
- (cp >= 0x3300 && cp <= 0x33FF) ||
187
- (cp >= 0x3400 && cp <= 0x4DBF) ||
188
- (cp >= 0x4E00 && cp <= 0x9FFF) || // CJK Unified Ideographs
189
- (cp >= 0xA000 && cp <= 0xA48F) ||
190
- (cp >= 0xA490 && cp <= 0xA4CF) ||
191
- (cp >= 0xA960 && cp <= 0xA97F) ||
192
- (cp >= 0xAC00 && cp <= 0xD7AF) || // Hangul Syllables
193
- (cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compatibility Ideographs
194
- (cp >= 0xFE10 && cp <= 0xFE1F) ||
195
- (cp >= 0xFE30 && cp <= 0xFE4F) ||
196
- (cp >= 0xFF00 && cp <= 0xFF60) || // Fullwidth Forms
197
- (cp >= 0xFFE0 && cp <= 0xFFE6) ||
198
- (cp >= 0x1B000 && cp <= 0x1B0FF) ||
199
- (cp >= 0x1F004 && cp <= 0x1F0CF) ||
200
- (cp >= 0x1F200 && cp <= 0x1F2FF) ||
201
- (cp >= 0x1F300 && cp <= 0x1F64F) || // Misc Symbols, Emoticons
202
- (cp >= 0x1F680 && cp <= 0x1F6FF) || // Transport & Map
203
- (cp >= 0x1F900 && cp <= 0x1FAFF) || // Supplemental Symbols
204
- (cp >= 0x20000 && cp <= 0x2FFFD) ||
205
- (cp >= 0x30000 && cp <= 0x3FFFD)
206
- ) return 2;
251
+ (cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
252
+ (cp >= 0x2e80 && cp <= 0x2eff) || // CJK Radicals Supplement
253
+ (cp >= 0x2f00 && cp <= 0x2fff) ||
254
+ (cp >= 0x3000 && cp <= 0x303f) || // CJK Symbols and Punctuation
255
+ (cp >= 0x3040 && cp <= 0x309f) || // Hiragana
256
+ (cp >= 0x30a0 && cp <= 0x30ff) || // Katakana
257
+ (cp >= 0x3100 && cp <= 0x312f) ||
258
+ (cp >= 0x3130 && cp <= 0x318f) || // Hangul Compatibility Jamo
259
+ (cp >= 0x3190 && cp <= 0x319f) ||
260
+ (cp >= 0x31c0 && cp <= 0x31ef) ||
261
+ (cp >= 0x3200 && cp <= 0x32ff) ||
262
+ (cp >= 0x3300 && cp <= 0x33ff) ||
263
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
264
+ (cp >= 0x4e00 && cp <= 0x9fff) || // CJK Unified Ideographs
265
+ (cp >= 0xa000 && cp <= 0xa48f) ||
266
+ (cp >= 0xa490 && cp <= 0xa4cf) ||
267
+ (cp >= 0xa960 && cp <= 0xa97f) ||
268
+ (cp >= 0xac00 && cp <= 0xd7af) || // Hangul Syllables
269
+ (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs
270
+ (cp >= 0xfe10 && cp <= 0xfe1f) ||
271
+ (cp >= 0xfe30 && cp <= 0xfe4f) ||
272
+ (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms
273
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
274
+ (cp >= 0x1b000 && cp <= 0x1b0ff) ||
275
+ (cp >= 0x1f004 && cp <= 0x1f0cf) ||
276
+ (cp >= 0x1f200 && cp <= 0x1f2ff) ||
277
+ (cp >= 0x1f300 && cp <= 0x1f64f) || // Misc Symbols, Emoticons
278
+ (cp >= 0x1f680 && cp <= 0x1f6ff) || // Transport & Map
279
+ (cp >= 0x1f900 && cp <= 0x1faff) || // Supplemental Symbols
280
+ (cp >= 0x20000 && cp <= 0x2fffd) ||
281
+ (cp >= 0x30000 && cp <= 0x3fffd)
282
+ )
283
+ return 2;
207
284
 
208
285
  return 1;
209
286
  }
@@ -230,62 +307,78 @@ export function padRight(str, len) {
230
307
  return str + " ".repeat(pad);
231
308
  }
232
309
 
233
- // wcwidth-aware truncate: wide char 경계에서 자름
234
- export function truncate(str, maxLen) {
235
- const plain = stripAnsi(str);
236
- const w = wcswidth(plain);
237
- if (w <= maxLen) return str;
238
-
239
- let acc = 0;
310
+ // ANSI-preserving slice: ANSI escape를 보존하면서 visible width만큼 자름
311
+ function ansiSlice(str, maxWidth) {
312
+ let result = "";
313
+ let visWidth = 0;
314
+ let hasAnsi = false;
240
315
  let i = 0;
241
- for (const char of plain) {
242
- const cw = charWidth(char.codePointAt(0));
243
- if (acc + cw > maxLen - 1) break;
244
- acc += cw;
245
- i += char.length;
316
+
317
+ while (i < str.length) {
318
+ if (str.charCodeAt(i) === 0x1b) {
319
+ const rest = str.slice(i);
320
+ const m = rest.match(/^(\x1b(?:\[[0-9;]*[a-zA-Z]|\].*?(?:\x07|\x1b\\)))/);
321
+ if (m) {
322
+ result += m[1];
323
+ hasAnsi = true;
324
+ i += m[1].length;
325
+ continue;
326
+ }
327
+ }
328
+
329
+ const cp = str.codePointAt(i);
330
+ const charLen = cp > 0xffff ? 2 : 1;
331
+ const cw = charWidth(cp);
332
+
333
+ if (visWidth + cw > maxWidth) break;
334
+
335
+ result += str.slice(i, i + charLen);
336
+ visWidth += cw;
337
+ i += charLen;
246
338
  }
247
- return plain.slice(0, i) + "…";
339
+
340
+ return { result, visWidth, hasAnsi };
341
+ }
342
+
343
+ // wcwidth-aware truncate: wide char 경계에서 자름 (ANSI 보존)
344
+ export function truncate(str, maxLen) {
345
+ if (wcswidth(str) <= maxLen) return str;
346
+
347
+ const { result, hasAnsi } = ansiSlice(str, maxLen - 1);
348
+ return hasAnsi ? result + RESET + "…" : result + "…";
248
349
  }
249
350
 
250
- // wcwidth-aware clip: 정확히 width 셀에 맞게 자르고 패딩 (wide char 경계 보정)
351
+ // wcwidth-aware clip: 정확히 width 셀에 맞게 자르고 패딩 (ANSI 보존, wide char 경계 보정)
251
352
  export function clip(str, width) {
252
- const plain = stripAnsi(str);
253
- let acc = 0;
254
- let i = 0;
255
- for (const char of plain) {
256
- const cw = charWidth(char.codePointAt(0));
257
- if (acc + cw > width) {
258
- // wide char이 경계를 넘으면 공백으로 채움
259
- const result = plain.slice(0, i) + " ".repeat(width - acc);
260
- return result;
261
- }
262
- acc += cw;
263
- i += char.length;
264
- }
265
- return str + " ".repeat(width - acc);
353
+ const vis = wcswidth(str);
354
+ if (vis <= width) return str + " ".repeat(width - vis);
355
+
356
+ const { result, visWidth, hasAnsi } = ansiSlice(str, width);
357
+ const suffix = hasAnsi ? RESET : "";
358
+ return result + suffix + " ".repeat(Math.max(0, width - visWidth));
266
359
  }
267
360
 
268
361
  // ── Catppuccin Mocha 색상 상수 ──
269
362
  export const MOCHA = {
270
- ok: `${ESC}[38;5;114m`, // #a6e3a1 green
271
- partial: `${ESC}[38;5;216m`, // #fab387 peach
272
- fail: `${ESC}[38;5;210m`, // #f38ba8 red
273
- thinking: `${ESC}[38;5;183m`, // #cba6f7 mauve
274
- executing: `${ESC}[38;5;117m`, // #74c7ec sky
275
- border: `${ESC}[38;5;238m`, // #45475a surface1
276
- text: `${ESC}[38;2;205;214;244m`, // #cdd6f4 catppuccin text
277
- subtext: `${ESC}[38;2;166;173;200m`, // #a6adc8 subtext0
278
- overlay: `${ESC}[38;2;108;112;134m`, // #6c7086 overlay0
279
- blue: `${ESC}[38;2;137;180;250m`, // #89b4fa blue
280
- yellow: `${ESC}[38;2;249;226;175m`, // #f9e2af yellow
281
- red: `${ESC}[38;2;243;139;168m`, // #f38ba8 red (truecolor)
282
- surface0: `${ESC}[38;2;49;50;68m`, // #313244 surface0
363
+ ok: `${ESC}[38;5;114m`, // #a6e3a1 green
364
+ partial: `${ESC}[38;5;216m`, // #fab387 peach
365
+ fail: `${ESC}[38;5;210m`, // #f38ba8 red
366
+ thinking: `${ESC}[38;5;183m`, // #cba6f7 mauve
367
+ executing: `${ESC}[38;5;117m`, // #74c7ec sky
368
+ border: `${ESC}[38;5;238m`, // #45475a surface1
369
+ text: `${ESC}[38;2;205;214;244m`, // #cdd6f4 catppuccin text
370
+ subtext: `${ESC}[38;2;166;173;200m`, // #a6adc8 subtext0
371
+ overlay: `${ESC}[38;2;108;112;134m`, // #6c7086 overlay0
372
+ blue: `${ESC}[38;2;137;180;250m`, // #89b4fa blue
373
+ yellow: `${ESC}[38;2;249;226;175m`, // #f9e2af yellow
374
+ red: `${ESC}[38;2;243;139;168m`, // #f38ba8 red (truecolor)
375
+ surface0: `${ESC}[38;2;49;50;68m`, // #313244 surface0
283
376
  };
284
377
 
285
378
  const MOCHA_RGB = {
286
- ok: { r: 166, g: 227, b: 161 },
287
- partial: { r: 250, g: 179, b: 135 },
288
- fail: { r: 243, g: 139, b: 168 },
379
+ ok: { r: 166, g: 227, b: 161 },
380
+ partial: { r: 250, g: 179, b: 135 },
381
+ fail: { r: 243, g: 139, b: 168 },
289
382
  };
290
383
 
291
384
  // ── badge 헬퍼 ──
@@ -319,7 +412,12 @@ export function progressBar(percent, width = 20, time) {
319
412
  const ratio = Math.max(0, Math.min(100, percent)) / 100;
320
413
  const filled = Math.round(ratio * width);
321
414
  const empty = width - filled;
322
- const fillRgb = percent >= 100 ? MOCHA_RGB.ok : percent >= 50 ? MOCHA_RGB.partial : MOCHA_RGB.fail;
415
+ const fillRgb =
416
+ percent >= 100
417
+ ? MOCHA_RGB.ok
418
+ : percent >= 50
419
+ ? MOCHA_RGB.partial
420
+ : MOCHA_RGB.fail;
323
421
  const fillColor = rgbSeq(fillRgb);
324
422
  let fillText = "█".repeat(filled);
325
423
 
@@ -330,13 +428,14 @@ export function progressBar(percent, width = 20, time) {
330
428
  );
331
429
  const shineColor = rgbSeq(brightenRgb(fillRgb, 0.3));
332
430
  fillText = Array.from({ length: filled }, (_, idx) =>
333
- idx === shinePos ? `${shineColor}█${RESET}` : `${fillColor}█${RESET}`
431
+ idx === shinePos ? `${shineColor}█${RESET}` : `${fillColor}█${RESET}`,
334
432
  ).join("");
335
433
  } else if (filled > 0) {
336
434
  fillText = `${fillColor}${fillText}${RESET}`;
337
435
  }
338
436
 
339
- const emptyText = empty > 0 ? `${MOCHA.border}${"░".repeat(empty)}${RESET}` : "";
437
+ const emptyText =
438
+ empty > 0 ? `${MOCHA.border}${"░".repeat(empty)}${RESET}` : "";
340
439
  return `${fillText}${emptyText}`;
341
440
  }
342
441
 
@@ -360,14 +459,14 @@ export function animatedProgressBar(percent, width = 20, tick = 0) {
360
459
 
361
460
  // ── 상태 아이콘 ──
362
461
  export const STATUS_ICON = {
363
- running: `${MOCHA.partial}⏳${RESET}`,
462
+ running: `${MOCHA.partial}⏳${RESET}`,
364
463
  completed: `${MOCHA.ok}✓${RESET}`,
365
- failed: `${MOCHA.fail}✗${RESET}`,
366
- pending: `${FG.gray}⏸${RESET}`,
464
+ failed: `${MOCHA.fail}✗${RESET}`,
465
+ pending: `${FG.gray}⏸${RESET}`,
367
466
  };
368
467
 
369
468
  export const CLI_ICON = {
370
- codex: `${FG.codex}⚪${RESET}`,
469
+ codex: `${FG.codex}⚪${RESET}`,
371
470
  gemini: `${FG.gemini}🔵${RESET}`,
372
471
  claude: `${FG.claude}🟠${RESET}`,
373
472
  };