leerness 1.9.179 β†’ 1.9.181

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/CHANGELOG.md CHANGED
@@ -1,5 +1,144 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.181 β€” 2026-05-21
4
+
5
+ **πŸšͺ REPL μ§„μž… 흐름 정리 β€” μ‚¬μš©μž λͺ…μ‹œ 4μ’… + 직접 ꡬ동 μ‹€ 호좜 검증.**
6
+
7
+ 자율 λͺ¨λ“œ 111 λΌμš΄λ“œ. μ‚¬μš©μž 직접 ꡬ동 ν…ŒμŠ€νŠΈ κ²°κ³Ό 보고 (μŠ€ν¬λ¦°μƒ· 첨뢀):
8
+ 1. *"1.9.149 Hermes/OpenClaw μŠ€νƒ€μΌ λ“±μ˜ λ¬Έκ΅¬λŠ” 제거"* β†’ μ„€μΉ˜ μ™„λ£Œ λ©”μ‹œμ§€ λ‹¨μˆœν™”
9
+ 2. *"이 λ‹¨κ³„μ—μ„œ Ollama μ œμ™Έν•œ λͺ¨λΈμ„ μ„ νƒν–ˆλŠ”λ° REPL μ§„μž… μ‹œ Ollama μš°μ„  호좜"* β†’ installβ†’REPL provider μžλ™ 선택
10
+ 3. *"ν”„λ‘œλ°”μ΄λ” μ „ν™˜ 선택 단계 없이 λ°”λ‘œ μ±„νŒ… λͺ¨λ“œλ‘œ μ§„μž…"* β†’ μ§„μž… prompt 제거
11
+ 4. *"REPL을 직접 κ΅¬λ™ν•΄μ„œ λͺ…λ Ή μž…λ ₯해보고 개발/μ›Ή/PC/μΆ”λ‘ /질문-λ‹΅λ³€ λ™μž‘ ν…ŒμŠ€νŠΈ"* β†’ agents multi --execute μ‹€ 호좜 검증
12
+
13
+ ### Fix #1 β€” 문ꡬ λ‹¨μˆœν™”
14
+ ```diff
15
+ - log('πŸš€ μ„€μΉ˜ μ™„λ£Œ β€” REPL agent λͺ¨λ“œλ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€ (1.9.149 Hermes/OpenClaw μŠ€νƒ€μΌ)...');
16
+ + log('πŸš€ μ„€μΉ˜ μ™„λ£Œ β€” REPL agent λͺ¨λ“œλ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€...');
17
+ ```
18
+
19
+ ### Fix #2 β€” installβ†’REPL provider ν•˜λ“œμ½”λ”© 제거
20
+ ```diff
21
+ - await _agentRepl(root, { provider: 'ollama', role: 'actor' }); // 1.9.151 β€” 무쑰건 ollama
22
+ + await _agentRepl(root, { role: 'actor' }); // provider λ―Έμ§€μ • β†’ auto-select λ™μž‘ (1.9.181 fix)
23
+ ```
24
+
25
+ ### Fix #3 β€” λΉ„-Ollama μš°μ„  μžλ™ 선택 (Ollama μš°μ„  호좜 X)
26
+ ```js
27
+ const ready = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }))
28
+ .filter(x => x.status.status === 'ready');
29
+ const nonOllama = ready.filter(x => x.def.id !== 'ollama');
30
+ if (nonOllama.length >= 1) {
31
+ // λΉ„-Ollama ν™œμ„± β†’ 첫 번째 μžλ™ (μ‚¬μš©μž λͺ…μ‹œ: Ollama μš°μ„  호좜 X)
32
+ initialProvider = nonOllama[0].def.id;
33
+ _autoPickNote = nonOllama.length === 1
34
+ ? `${initialProvider} μžλ™ 선택 (ν™œμ„± CLI 1개)`
35
+ : `${initialProvider} μžλ™ 선택 (ν™œμ„± CLI ${nonOllama.length}개 Β· Tab으둜 μ „ν™˜)`;
36
+ }
37
+ ```
38
+
39
+ ### Fix #4 β€” provider μ „ν™˜ prompt 단계 제거 (μžλ™ μ „ν™˜)
40
+ 이전:
41
+ ```
42
+ ⚠ Ollama 미가동 λ˜λŠ” λͺ¨λΈ μ—†μŒ
43
+ πŸ’‘ ν™œμ„± μ™ΈλΆ€ CLI 4개 발견 β€” provider μ „ν™˜ κ°€λŠ₯:
44
+ 1) claude (v2.1.145)
45
+ 2) codex (vcodex-cli 0.132.0)
46
+ ...
47
+ provider μ „ν™˜ (번호 / Enter=ollama 계속): _
48
+ ```
49
+ μ§€κΈˆ:
50
+ ```
51
+ β–Έ Provider: claude μžλ™ 선택 (ν™œμ„± CLI 4개 Β· Tab으둜 μ „ν™˜)
52
+ [μ±„νŒ… λͺ¨λ“œ μ¦‰μ‹œ μ§„μž…]
53
+ ```
54
+
55
+ ### 직접 ꡬ동 μ‹€ 호좜 검증 (μ‚¬μš©μž λͺ…μ‹œ 4번째 μš”μ²­)
56
+ ```bash
57
+ $ leerness agents multi "1+1=? 숫자만 λ‹΅ν•΄μ£Όμ„Έμš”." --only claude,gemini --execute --timeout 30
58
+ βœ“ claude Β· 4810ms Β· 1 토큰
59
+ βœ— gemini Β· 1266ms Β· exit=null
60
+ best: claude Β· score=0.600
61
+ --- 처음 600자 ---
62
+ 2
63
+ ```
64
+ **claude μΆ”λ‘  응닡 정상**. geminiλŠ” 별도 ν™˜κ²½ 이슈 (--yolo κΆŒν•œ λ˜λŠ” quota β€” 1.9.182μ—μ„œ μΆ”κ°€ 디버그 후보).
65
+
66
+ ### Verified
67
+ - stress-v126: **18/18** (μ‚¬μš©μž λͺ…μ‹œ 4 + μžλ™ 선택 λ™μž‘ 4 + 직접 ꡬ동 2 + VERSION+λˆ„μ  νšŒκ·€ 8)
68
+ - e2e 217/217 baseline μœ μ§€
69
+ - claude μ‹€ 호좜 4810ms Β· 응닡 "2" β€” REPL agent의 핡심 λŠ₯λ ₯ (μ‹€ λͺ¨λΈ 호좜 + μΆ”λ‘  + 응닡 μˆ˜μ‹ ) λ™μž‘ 확인
70
+ - VERSION = 1.9.181 Β· autonomous-rounds = 111 Β· main μžλ™ push 42 λΌμš΄λ“œ 연속
71
+
72
+ ---
73
+
74
+ ## 1.9.180 β€” 2026-05-21
75
+
76
+ **πŸ”§ REPL Tab cycle 핡심 fix + μ±„νŒ… μ˜μ—­ separator β€” μ‚¬μš©μž λͺ…μ‹œ (직접 ꡬ동 ν…ŒμŠ€νŠΈ κ²°κ³Ό).**
77
+
78
+ 자율 λͺ¨λ“œ 110 λΌμš΄λ“œ. μ‚¬μš©μž λͺ…μ‹œ: *"REPL agent λͺ¨λ“œλ₯Ό λ„€κ°€ 직접 κ΅¬λ™ν•΄μ„œ ν…ŒμŠ€νŠΈν•΄μ€˜ / REPL agent λͺ¨λ“œλŠ” κ³ μ •λœ 헀더와 μ±„νŒ…ν˜•μ‹μ΄μ–΄μ•Όν•΄ / 그리고 λͺ¨λΈμ΄λ‚˜ ν”„λ‘œλ°”μ΄λ” μ „ν™˜μ΄ μ›ν™œν•˜μ§€μ•Šμ€κ±°κ°™μ•„"*.
79
+
80
+ ### 핡심 fix β€” Tab cycle μ‹€ λ™μž‘ 보μž₯
81
+
82
+ #### 1. readline `completer` no-op
83
+ ```js
84
+ // 1.9.180: completer no-op β€” readline의 자체 Tab completion이 keypress λ¦¬μŠ€λ„ˆλ₯Ό κ°€λ‘œμ±„λŠ” 문제 차단
85
+ const rl = readline.createInterface({
86
+ input: process.stdin,
87
+ output: process.stdout,
88
+ completer: (line) => [[], line]
89
+ });
90
+ ```
91
+ 이전: μ‚¬μš©μžκ°€ Tab을 λˆ„λ₯΄λ©΄ readline κΈ°λ³Έ completerκ°€ 빈 κ²°κ³Όλ₯Ό ν‘œμ‹œν•˜λ©° promptλ₯Ό 재좜λ ₯ν•΄ keypress λ¦¬μŠ€λ„ˆκ°€ λ™μž‘ν•˜μ§€ μ•Šμ„ λ•Œκ°€ μžˆμ—ˆμŒ.
92
+ μ§€κΈˆ: `completer: () => [[], line]` λͺ…μ‹œλ‘œ readline의 Tab κ°€λ‘œμ±„κΈ° 차단 β†’ keypress λ¦¬μŠ€λ„ˆκ°€ 항상 λ°œλ™.
93
+
94
+ #### 2. Shift+Tab β†’ `cycleModel(false)` λ§€ν•‘ μˆ˜μ • (CRITICAL)
95
+ ```js
96
+ process.stdin.on('keypress', (str, key) => {
97
+ if (!key) return;
98
+ if (key.name === 'tab') {
99
+ // 1.9.180 fix: Shift+Tab β†’ cycleModel (이전 cycleProvider 잘λͺ»)
100
+ if (key.shift === true) {
101
+ cycleModel(false); // Shift+Tab β†’ ν˜„μž¬ provider의 λͺ¨λΈ cycle
102
+ } else {
103
+ cycleProvider(false); // Tab β†’ λ‹€μŒ provider
104
+ }
105
+ }
106
+ });
107
+ ```
108
+ 이전 (1.9.170): `cycleProvider(key.shift)` β€” Shift+Tab은 provider reverse μ˜€κ³  model cycle ν‚€κ°€ μ—†μ—ˆμŒ.
109
+ μ§€κΈˆ: μ‚¬μš©μž μ˜λ„λŒ€λ‘œ `Tab=provider`, `Shift+Tab=model`.
110
+
111
+ ### μ‹œκ° ν”Όλ“œλ°± κ°•ν™” (μ‚¬μš©μž λͺ…μ‹œ: "μ›ν™œν•˜μ§€ μ•ŠμŒ")
112
+ ```
113
+ ⇄ provider [3/5]: claude βœ“ ready
114
+ β”” 7개 λͺ¨λΈ catalog Β· Shift+Tab으둜 model cycle
115
+
116
+ ⇄ model [2/7]: claude-opus-4
117
+ β”” μ΅œμ‹  thinking λͺ¨λΈ
118
+ ```
119
+ - bold green provider Β· bold magenta model
120
+ - `[idx/total]` μœ„μΉ˜/총수 ν‘œμ‹œ
121
+ - ready/⚠ status ν™œμ„± μ—¬λΆ€ ν‘œμ‹œ
122
+ - catalog λͺ¨λΈ 수 λ…ΈμΆœ
123
+
124
+ ### μ±„νŒ… μ˜μ—­ separator (μ‚¬μš©μž λͺ…μ‹œ: "κ³ μ •λœ 헀더와 μ±„νŒ…ν˜•μ‹")
125
+ ```
126
+ [... ν™˜μ˜ ν™”λ©΄ (헀더 + Tips + What's new + Slash + ν‚€λ³΄λ“œ + μƒνƒœλ°”) ...]
127
+
128
+ ───────────────────────────── μ±„νŒ… μ‹œμž‘ ─────────────────────────────
129
+ λ©”μ‹œμ§€ μž…λ ₯ ν›„ Enter Β· :help 으둜 λͺ…λ Ή λͺ©λ‘ Β· Ctrl+C 둜 μ’…λ£Œ
130
+
131
+ agent[ollama/actor/β–Ά]> _
132
+ ```
133
+ ν™˜μ˜ ν™”λ©΄ (κ³ μ • 헀더) κ³Ό μž…λ ₯ μ˜μ—­ (μ±„νŒ…) 의 μ‹œκ°μ  ꡬ뢄을 λͺ…ν™•ν•˜κ²Œ.
134
+
135
+ ### Verified
136
+ - e2e 217/217 baseline μœ μ§€
137
+ - stress-v125: **17/17** (Tab cycle fix 3 + μ‹œκ° ν”Όλ“œλ°± 4 + μ±„νŒ… μ˜μ—­ 3 + λˆ„μ  νšŒκ·€ 7)
138
+ - VERSION = 1.9.180 Β· autonomous-rounds = 110 Β· main μžλ™ push 41 λΌμš΄λ“œ 연속
139
+
140
+ ---
141
+
3
142
  ## 1.9.179 β€” 2026-05-21
4
143
 
5
144
  **🎨 REPL ν™˜μ˜ ν™”λ©΄ μž¬λ””μžμΈ β€” Hermes/Claude/Codex/Gemini CLI μŠ€νƒ€μΌ (μ‚¬μš©μž λͺ…μ‹œ).**
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **AI μ½”λ”© μ—μ΄μ „νŠΈμ˜ κ±°μ§“ μ™„λ£ŒΒ·μ€‘λ³΅Β·λ§κ°Β·μΆ©λŒμ„ λ§‰μ•„μ£ΌλŠ” κ²€μˆ˜Β·κΈ°μ–΅Β·ν˜‘μ—… CLI ν•˜λ„€μŠ€.**
4
4
 
5
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.179-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v124-19%2F19-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-109-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-40_rounds-success)]() [![repl-design](https://img.shields.io/badge/REPL_ν™˜μ˜-Hermes%2FClaude%2FCodex_μŠ€νƒ€μΌ-success)]() [![npm-auto](https://img.shields.io/badge/npm_auto--publish-NPM__TOKEN_톡합-success)]() [![auto-review](https://img.shields.io/badge/task_add-μžλ™_review_trigger-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
5
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.181-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v126-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-111-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-42_rounds-success)]() [![repl-flow](https://img.shields.io/badge/REPL_μ§„μž…-provider_μžλ™μ„ νƒ-success)]() [![repl-tested](https://img.shields.io/badge/μ‹€ν˜ΈμΆœ-claude_검증-success)]() [![npm-auto](https://img.shields.io/badge/npm_auto--publish-NPM__TOKEN_톡합-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,9 +12,9 @@
12
12
  β•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘ β•‘
13
13
  β•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•‘
14
14
  β•‘ β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β• β•‘
15
- β•‘ v1.9.179 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.181 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ 🎨 REPL ν™˜μ˜ ν™”λ©΄ μž¬λ””μžμΈ (Hermes/Claude/Codex μŠ€νƒ€μΌ) β•‘
17
+ β•‘ πŸšͺ REPL μ§„μž… 흐름 정리 (provider μžλ™ 선택 Β· μ‹€ν˜ΈμΆœ 검증) β•‘
18
18
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
19
19
  ```
20
20
 
package/bin/harness.js CHANGED
@@ -7,7 +7,7 @@ const cp = require('child_process');
7
7
  const os = require('os'); // 1.9.178: _publishToNpm μ—μ„œ os.tmpdir() μ‚¬μš© (μ „μ—­ import)
8
8
  const readline = require('readline');
9
9
 
10
- const VERSION = '1.9.179';
10
+ const VERSION = '1.9.181';
11
11
  const MARK = '<!-- leerness:managed -->';
12
12
  const README_START = '<!-- leerness:project-readme:start -->';
13
13
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -934,12 +934,13 @@ async function install(root, opts = {}) {
934
934
  // resolveInstallOptions (1.9.146) κ°€ 이미 λͺ¨λ“  prompt λͺ¨μ€ μœ„μΉ˜μ— ν†΅ν•©λœ 4μ§€μ„ λ‹€ prompt 있음.
935
935
  // 별도 setupAgents λͺ…령은 μ‚¬μš©μžκ°€ λͺ…μ‹œμ μœΌλ‘œ `leerness setup-agents` 호좜 μ‹œμ—λ§Œ.
936
936
  // 1.9.151: μ„€μΉ˜ μ™„λ£Œ 직후 β€” startRepl 선택 μ‹œ REPL agent λͺ¨λ“œ μ¦‰μ‹œ μ§„μž… (μ‚¬μš©μž λͺ…μ‹œ μš”μ²­)
937
+ // 1.9.181: 문ꡬ λ‹¨μˆœν™” + provider ν•˜λ“œμ½”λ”© 제거 (μ‚¬μš©μž λͺ…μ‹œ β€” install μ„ νƒν•œ CLIλ₯Ό REPL이 μžλ™ 선택)
937
938
  if (resolved.startRepl && !opts.migration && process.stdin.isTTY && process.env.LEERNESS_NO_PROMPT !== '1') {
938
939
  log('');
939
- log('πŸš€ μ„€μΉ˜ μ™„λ£Œ β€” REPL agent λͺ¨λ“œλ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€ (1.9.149 Hermes/OpenClaw μŠ€νƒ€μΌ)...');
940
+ log('πŸš€ μ„€μΉ˜ μ™„λ£Œ β€” REPL agent λͺ¨λ“œλ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€...');
940
941
  log('');
941
942
  try {
942
- await _agentRepl(root, { provider: 'ollama', role: 'actor' });
943
+ await _agentRepl(root, { role: 'actor' }); // provider λ―Έμ§€μ • β†’ _agentRepl 의 auto-select λ™μž‘ (1.9.181 fix)
943
944
  } catch (e) { warn('REPL μ§„μž… μ‹€νŒ¨: ' + e.message); }
944
945
  }
945
946
  }
@@ -11053,30 +11054,41 @@ async function _agentRepl(root, opts) {
11053
11054
  // 1.9.153: .env μžλ™ λ‘œλ“œ (REPL μ§„μž… 직전) β€” install 직후 LEERNESS_ENABLE_* μ¦‰μ‹œ 반영
11054
11055
  try { _loadEnvFile(root); } catch {}
11055
11056
  const readline = require('readline');
11056
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
11057
+ // 1.9.180: completer no-op β€” readline 의 Tab completion κ°€λ‘œμ±„κΈ°λ₯Ό 차단해 keypress λ¦¬μŠ€λ„ˆκ°€ Tab cycle을 처리.
11058
+ // (μ‚¬μš©μž λͺ…μ‹œ: "λͺ¨λΈ/ν”„λ‘œλ°”μ΄λ” μ „ν™˜μ΄ μ›ν™œν•˜μ§€ μ•Šλ‹€" β€” readline Tab completion이 cycle ν‚€λ₯Ό κ°€λ‘œμ±„λŠ” 경우 fix)
11059
+ const rl = readline.createInterface({
11060
+ input: process.stdin,
11061
+ output: process.stdout,
11062
+ completer: (line) => [[], line] // 빈 completion + 원본 line λ°˜ν™˜ β†’ Tab은 keypress λ¦¬μŠ€λ„ˆλ‘œλ§Œ 처리
11063
+ });
11057
11064
  const isTty = process.stdout.isTTY;
11058
11065
  const C = isTty ? {
11059
11066
  cy: s => `\x1b[36m${s}\x1b[0m`, dim: s => `\x1b[2m${s}\x1b[0m`,
11060
11067
  bold: s => `\x1b[1m${s}\x1b[0m`, green: s => `\x1b[32m${s}\x1b[0m`,
11061
11068
  yel: s => `\x1b[33m${s}\x1b[0m`, mag: s => `\x1b[35m${s}\x1b[0m`
11062
11069
  } : { cy:s=>s, dim:s=>s, bold:s=>s, green:s=>s, yel:s=>s, mag:s=>s };
11063
- // 1.9.153: provider μžλ™ 선택 β€” opts.provider λͺ…μ‹œ μ•ˆ 됨 + ν™œμ„± CLI κ°€ 있으면 μ‚¬μš©μžμ—κ²Œ 선택지 ν‘œμ‹œ
11070
+ // 1.9.181 fix: provider μžλ™ 선택 β€” prompt 단계 μ œκ±°ν•˜κ³  μ¦‰μ‹œ μ±„νŒ… λͺ¨λ“œ μ§„μž… (μ‚¬μš©μž λͺ…μ‹œ).
11071
+ // μ •μ±…: λΉ„-Ollama ν™œμ„± CLI μš°μ„  β†’ Ollama β†’ fallback. 볡수 ν™œμ„± μ‹œ prompt 없이 첫 λΉ„-Ollama μžλ™ 선택.
11072
+ // μ‚¬μš©μž μ˜λ„: "ν”„λ‘œλ°”μ΄λ” μ „ν™˜ 선택 단계없이 λ°”λ‘œ μ±„νŒ… λͺ¨λ“œλ‘œ μ§„μž…ν•΄λ„ 될거같아" + "Ollamaλ₯Ό μš°μ„  호좜 X".
11073
+ // Tab 으둜 μ–Έμ œλ“  cycle κ°€λŠ₯ν•˜λ―€λ‘œ μžλ™ 선택해도 μ‚¬μš©μžκ°€ μ¦‰μ‹œ μ „ν™˜ κ°€λŠ₯.
11064
11074
  let initialProvider = opts.provider;
11075
+ let _autoPickNote = '';
11065
11076
  if (!initialProvider) {
11066
11077
  const ready = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }))
11067
11078
  .filter(x => x.status.status === 'ready');
11068
- if (ready.length === 1) {
11069
- initialProvider = ready[0].def.id; // 단일 ν™œμ„± β†’ μžλ™ 선택
11070
- } else if (ready.length > 1 && isTty) {
11071
- // 볡수 ν™œμ„± β†’ μ‚¬μš©μžμ—κ²Œ 선택지 (Ollama μš°μ„ μ΄ μ•„λ‹Œ, ν™œμ„±λœ CLI 쀑 선택)
11072
- console.log('');
11073
- console.log(` μ‚¬μš© κ°€λŠ₯ν•œ CLI μ—μ΄μ „νŠΈ ${ready.length}개:`);
11074
- ready.forEach((x, i) => console.log(` ${i + 1}) ${x.def.id}${x.status.version ? ' (v' + x.status.version + ')' : ''}`));
11075
- const choice = await new Promise(res => rl.question(`\n provider 선택 (Enter=1): `, res));
11076
- const idx = parseInt(choice, 10) - 1;
11077
- initialProvider = (idx >= 0 && idx < ready.length) ? ready[idx].def.id : ready[0].def.id;
11079
+ const nonOllama = ready.filter(x => x.def.id !== 'ollama');
11080
+ if (nonOllama.length >= 1) {
11081
+ // λΉ„-Ollama ν™œμ„± β†’ 첫 번째 μžλ™ (μ‚¬μš©μž λͺ…μ‹œ: Ollama μš°μ„  호좜 X)
11082
+ initialProvider = nonOllama[0].def.id;
11083
+ _autoPickNote = nonOllama.length === 1
11084
+ ? `${initialProvider} μžλ™ 선택 (ν™œμ„± CLI 1개)`
11085
+ : `${initialProvider} μžλ™ 선택 (ν™œμ„± CLI ${nonOllama.length}개 Β· Tab으둜 μ „ν™˜)`;
11086
+ } else if (ready.length === 1) {
11087
+ initialProvider = ready[0].def.id; // ollama 단독
11088
+ _autoPickNote = `${initialProvider} μžλ™ 선택`;
11078
11089
  } else {
11079
- initialProvider = 'ollama'; // ν™œμ„± 0개 β†’ fallback (μ‚¬μš© μ‹œ friendly κ²½κ³ )
11090
+ initialProvider = 'ollama'; // ν™œμ„± 0개 β†’ fallback
11091
+ _autoPickNote = 'fallback ollama (ν™œμ„± CLI μ—†μŒ β€” .env μ—μ„œ LEERNESS_ENABLE_* ν™œμ„±ν™” ꢌμž₯)';
11080
11092
  }
11081
11093
  }
11082
11094
  // μ„Έμ…˜ state
@@ -11110,47 +11122,27 @@ async function _agentRepl(root, opts) {
11110
11122
  log('');
11111
11123
  log(C.dim(` β–Έ Welcome back Β· ${wsName} (${rel(process.cwd(), absRoot(root))})`));
11112
11124
  log(C.dim(` β–Έ Session: ${state.sessionId}`));
11125
+ if (_autoPickNote) log(C.dim(` β–Έ Provider: ${_autoPickNote}`));
11113
11126
  log('');
11114
- // Ollama λͺ¨λΈ μžλ™ 감지 β€” model이 λͺ…μ‹œλ˜μ§€ μ•Šμ•˜μœΌλ©΄ μ‚¬μš©μžμ—κ²Œ 선택지 제곡
11127
+ // 1.9.181 fix: provider μ§„μž… μ‹œμ  prompt 단계 제거 β€” μžλ™ μ „ν™˜λ§Œ μˆ˜ν–‰ (μ‚¬μš©μž λͺ…μ‹œ β€” λ°”λ‘œ μ±„νŒ… λͺ¨λ“œ μ§„μž…).
11115
11128
  if (state.provider === 'ollama' && !state.model) {
11116
- log(C.dim(' Ollama λͺ¨λΈ λͺ©λ‘ 쑰회 쀑...'));
11129
+ // Ollama μ‚¬μš© κ°€λŠ₯ β†’ 첫 λͺ¨λΈ μžλ™ 선택 (μ‚¬μš©μž prompt μ—†μŒ)
11117
11130
  const r = await _ollamaListModels();
11118
11131
  if (r.ok && r.models.length) {
11119
- log(C.green(` μ‚¬μš© κ°€λŠ₯ λͺ¨λΈ ${r.models.length}개:`));
11120
- r.models.slice(0, 8).forEach((m, i) => log(` ${i + 1}) ${m}`));
11121
- const choice = await new Promise(res => rl.question(C.cy('\n λͺ¨λΈ 번호 선택 (Enter=1): '), res));
11122
- const idx = parseInt(choice, 10) - 1;
11123
- state.model = (idx >= 0 && idx < r.models.length) ? r.models[idx] : r.models[0];
11124
- log(C.green(` βœ“ λͺ¨λΈ 선택: ${state.model}`));
11132
+ state.model = process.env.LEERNESS_OLLAMA_MODEL || r.models[0];
11133
+ log(C.dim(` β–Έ Model: ${state.model} (Ollama ${r.models.length}개 catalog Β· Shift+Tab으둜 λ³€κ²½)`));
11125
11134
  } else {
11126
- log(C.yel(` ⚠ Ollama 미가동 λ˜λŠ” λͺ¨λΈ μ—†μŒ`));
11127
- // 1.9.164: Ollama μ‹€νŒ¨ μ‹œ λ‹€λ₯Έ ν™œμ„± CLI μ¦‰μ‹œ μ œμ•ˆ (UX κ°œμ„  β€” μ‚¬μš©μž λͺ…μ‹œ μš”μ²­)
11128
- try {
11129
- const readyCli = EXTERNAL_AGENTS.filter(a => a.id !== 'ollama')
11130
- .map(a => ({ def: a, status: _checkAgent(a) }))
11131
- .filter(x => x.status.status === 'ready');
11132
- if (readyCli.length) {
11133
- log('');
11134
- log(C.cy(` πŸ’‘ ν™œμ„± μ™ΈλΆ€ CLI ${readyCli.length}개 발견 β€” provider μ „ν™˜ κ°€λŠ₯:`));
11135
- readyCli.forEach((x, i) => log(` ${i + 1}) ${x.def.id} (v${x.status.version || '?'})`));
11136
- const choice = await new Promise(res => rl.question(C.cy('\n provider μ „ν™˜ (번호 / Enter=ollama 계속): '), res));
11137
- const idx = parseInt(choice, 10) - 1;
11138
- if (idx >= 0 && idx < readyCli.length) {
11139
- state.provider = readyCli[idx].def.id;
11140
- state.model = null; // μƒˆ provider κΈ°λ³Έ λͺ¨λΈ μ‚¬μš©
11141
- log(C.green(` βœ“ provider μ „ν™˜: ${state.provider} (λ©”μ‹œμ§€ μž…λ ₯ μ¦‰μ‹œ μ‚¬μš©)`));
11142
- } else {
11143
- state.model = process.env.LEERNESS_OLLAMA_MODEL || 'llama3';
11144
- log(C.dim(` ollama fallback: ${state.model} β€” μΆ”ν›„ :provider <이름> 으둜 μ „ν™˜ κ°€λŠ₯`));
11145
- }
11146
- } else {
11147
- log(C.dim(` ollama serve + ollama pull <model> / λ˜λŠ” .env μ—μ„œ LEERNESS_ENABLE_CLAUDE=1 λ“± ν™œμ„±ν™”`));
11148
- state.model = process.env.LEERNESS_OLLAMA_MODEL || 'llama3';
11149
- log(C.dim(` fallback: ${state.model} (μ‹€ 호좜 μ‹€νŒ¨ μ‹œ :provider 메뉴 λ˜λŠ” :quit)`));
11150
- }
11151
- } catch {
11135
+ // Ollama 미가동 β†’ λΉ„-Ollama ν™œμ„± CLI κ°€ 있으면 μžλ™ μ „ν™˜, μ—†μœΌλ©΄ fallback ollama (warn)
11136
+ const readyCli = EXTERNAL_AGENTS.filter(a => a.id !== 'ollama')
11137
+ .map(a => ({ def: a, status: _checkAgent(a) }))
11138
+ .filter(x => x.status.status === 'ready');
11139
+ if (readyCli.length) {
11140
+ state.provider = readyCli[0].def.id;
11141
+ state.model = null;
11142
+ log(C.green(` β–Έ Ollama 미가동 β†’ ${state.provider} μžλ™ μ „ν™˜ (Tab으둜 λ‹€λ₯Έ provider μ „ν™˜ κ°€λŠ₯)`));
11143
+ } else {
11152
11144
  state.model = process.env.LEERNESS_OLLAMA_MODEL || 'llama3';
11153
- log(C.dim(` fallback: ${state.model}`));
11145
+ log(C.yel(` ⚠ Ollama 미가동 + ν™œμ„± CLI μ—†μŒ β€” fallback ${state.model} (μ‹€ 호좜 μ‹€νŒ¨ μ‹œ :quit λ˜λŠ” :provider)`));
11154
11146
  }
11155
11147
  }
11156
11148
  }
@@ -11187,6 +11179,11 @@ async function _agentRepl(root, opts) {
11187
11179
  + C.green(`role=${state.role}`) + ' Β· '
11188
11180
  + C.yel(`perms=${permMode}`) + ' Β· '
11189
11181
  + (state.streamMode ? C.green('β–Ά stream=on') : C.dim('β–‘ stream=off')));
11182
+ // 1.9.180: μ±„νŒ… μ˜μ—­ μ§„μž… separator (μ‚¬μš©μž λͺ…μ‹œ β€” "κ³ μ • 헀더 + μ±„νŒ… ν˜•μ‹")
11183
+ // ν™˜μ˜ ν™”λ©΄ 끝에 λͺ…ν™•ν•œ ꡬ뢄선 + μ•ˆλ‚΄ β†’ μ±„νŒ… μ‹œμž‘ λͺ…ν™•.
11184
+ log('');
11185
+ log(C.dim(' ───────────────────────────── μ±„νŒ… μ‹œμž‘ ─────────────────────────────'));
11186
+ log(C.dim(' λ©”μ‹œμ§€ μž…λ ₯ ν›„ Enter Β· :help 으둜 λͺ…λ Ή λͺ©λ‘ Β· Ctrl+C 둜 μ’…λ£Œ'));
11190
11187
  // 1.9.155: REPL μ§„μž… μ‹œ handoff μ»¨ν…μŠ€νŠΈ μžλ™ λ…ΈμΆœ (UX κ°œμ„  β€” μ‚¬μš©μžκ°€ 맀번 :handoff μ•ˆ 해도 μ»¨ν…μŠ€νŠΈ 인지)
11191
11188
  try {
11192
11189
  const hf = cp.spawnSync(process.execPath, [__filename, 'handoff', root, '--compact', '--no-drift-check', '--no-headline'], {
@@ -11224,20 +11221,34 @@ async function _agentRepl(root, opts) {
11224
11221
  } catch {}
11225
11222
  return _PROVIDER_CYCLE_ORDER.slice();
11226
11223
  };
11224
+ // 1.9.180: cycleProvider/cycleModel β€” μ‹œκ° ν”Όλ“œλ°± κ°•ν™” (μ‚¬μš©μž λͺ…μ‹œ "μ›ν™œν•˜μ§€ μ•Šλ‹€").
11225
+ // 이전: ν•œ 쀄 ⇄ λ©”μ‹œμ§€λ§Œ. 1.9.180: provider μœ„μΉ˜ ν‘œμ‹œ [1/5] + bold highlight + ν™œμ„± μ—¬λΆ€ ν‘œμ‹œ.
11227
11226
  const cycleProvider = (reverse) => {
11228
11227
  const list = getProviders();
11229
11228
  let idx = list.indexOf(state.provider);
11230
11229
  if (idx < 0) idx = 0;
11231
11230
  idx = reverse ? (idx - 1 + list.length) % list.length : (idx + 1) % list.length;
11232
11231
  state.provider = list[idx];
11233
- state.model = null; // μƒˆ provider κΈ°λ³Έ λͺ¨λΈ
11232
+ state.model = null;
11234
11233
  const cat = _PROVIDER_MODEL_CATALOG[state.provider];
11235
- const hint = cat?.length ? ` (${cat.length}개 λͺ¨λΈ catalog β€” Shift+Tab으둜 cycle)` : '';
11236
- // ν˜„μž¬ μž…λ ₯ 라인 보쑴: cursorλ₯Ό 라인 μ‹œμž‘μœΌλ‘œ 이동 β†’ 클리어 β†’ status + prompt 재좜λ ₯
11234
+ // ν™œμ„± μ—¬λΆ€ 사전 확인 (1.9.180 UX β€” μ‚¬μš©μžκ°€ μ¦‰μ‹œ 인지)
11235
+ let activeMark = '';
11236
+ try {
11237
+ if (state.provider !== 'ollama') {
11238
+ const agent = EXTERNAL_AGENTS.find(a => a.id === state.provider);
11239
+ if (agent) {
11240
+ const st = _checkAgent(agent);
11241
+ activeMark = st.status === 'ready' ? C.green(' βœ“ ready') : C.yel(` ⚠ ${st.status}`);
11242
+ }
11243
+ }
11244
+ } catch {}
11237
11245
  process.stdout.write('\r\x1b[K');
11238
- process.stdout.write(C.green(` ⇄ provider: ${state.provider}${hint}\n`));
11246
+ process.stdout.write(C.bold(C.green(` ⇄ provider [${idx + 1}/${list.length}]: ${state.provider}`)) + activeMark + '\n');
11247
+ if (cat?.length) {
11248
+ process.stdout.write(C.dim(` β”” ${cat.length}개 λͺ¨λΈ catalog Β· Shift+Tab으둜 model cycle\n`));
11249
+ }
11239
11250
  rl.setPrompt(prompt());
11240
- rl.prompt(true); // preserve cursor
11251
+ rl.prompt(true);
11241
11252
  };
11242
11253
  const cycleModel = (reverse) => {
11243
11254
  const cat = _PROVIDER_MODEL_CATALOG[state.provider] || [];
@@ -11252,17 +11263,25 @@ async function _agentRepl(root, opts) {
11252
11263
  idx = reverse ? (idx - 1 + cat.length) % cat.length : (idx + 1) % cat.length;
11253
11264
  state.model = cat[idx].id;
11254
11265
  process.stdout.write('\r\x1b[K');
11255
- process.stdout.write(C.green(` ⇄ model: ${state.model} ${C.dim('β€” ' + (cat[idx].note || ''))}\n`));
11266
+ process.stdout.write(C.bold(C.mag(` ⇄ model [${idx + 1}/${cat.length}]: ${state.model}`)) + '\n');
11267
+ if (cat[idx].note) {
11268
+ process.stdout.write(C.dim(` β”” ${cat[idx].note}\n`));
11269
+ }
11256
11270
  rl.setPrompt(prompt());
11257
11271
  rl.prompt(true);
11258
11272
  };
11259
11273
  process.stdin.on('keypress', (str, key) => {
11260
11274
  if (!key) return;
11261
11275
  if (key.name === 'tab') {
11262
- cycleProvider(key.shift === true);
11276
+ // 1.9.180 fix: Shift+Tab β†’ cycleModel (μ‚¬μš©μž λͺ…μ‹œ μ˜λ„).
11277
+ // 이전 (1.9.170): cycleProvider(key.shift) β€” Shift+Tab μ‹œ provider reverse cycle만 λ˜μ–΄ model μ „ν™˜μ΄ λΆˆκ°€λŠ₯ν–ˆμŒ.
11278
+ // μ‚¬μš©μž λͺ…μ‹œ: "Tab=provider, Shift+Tab=model 선택과 μ „ν™˜μ΄ κ°„νŽΈν•˜κ²Œ".
11279
+ if (key.shift === true) {
11280
+ cycleModel(false); // Shift+Tab β†’ ν˜„μž¬ provider의 λ‹€μŒ model
11281
+ } else {
11282
+ cycleProvider(false); // Tab β†’ λ‹€μŒ provider
11283
+ }
11263
11284
  }
11264
- // Shift+\ λ˜λŠ” λ‹€λ₯Έ λͺ¨λΈ cycle alias β€” 일뢀 ν„°λ―Έλ„μ—μ„œ Shift+Tab 처리 어렀움 λŒ€λΉ„
11265
- // (Shift+Tab은 key.name='tab' + key.shift=true 둜 μœ„μ—μ„œ 처리됨)
11266
11285
  });
11267
11286
  } catch (e) {
11268
11287
  log(C.dim(` (Tab cycle λΉ„ν™œμ„±: ${e.message})`));
@@ -11625,7 +11644,7 @@ async function _agentRepl(root, opts) {
11625
11644
  async function agentCmd(root, taskArg) {
11626
11645
  root = absRoot(root || process.cwd());
11627
11646
  const task = (taskArg || arg('--task', '') || '').trim();
11628
- // 1.9.149: REPL μ§„μž… β€” 인자 μ—†κ±°λ‚˜ --interactive λͺ…μ‹œ (Hermes/OpenClaw μŠ€νƒ€μΌ)
11647
+ // 1.9.149+1.9.181: REPL μ§„μž… β€” 인자 μ—†κ±°λ‚˜ --interactive λͺ…μ‹œ (provider μžλ™ 선택)
11629
11648
  if (!task || has('--interactive') || has('--repl')) {
11630
11649
  if (process.stdin.isTTY && !has('--no-repl') && process.env.LEERNESS_NO_PROMPT !== '1') {
11631
11650
  const t0 = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.179",
3
+ "version": "1.9.181",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",