leerness 1.9.163 โ†’ 1.9.166

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,147 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.166 โ€” 2026-05-20
4
+
5
+ **๐ŸŽ‰ production-ready 76% ๋งˆ์ผ์Šคํ†ค โ€” pc ์กฐ์ž‘ bridge MVP (robotjs/nut-tree opt-in).**
6
+
7
+ ์ž์œจ ๋ชจ๋“œ 96 ๋ผ์šด๋“œ. 1.9.163 5๋Šฅ๋ ฅ ๋งคํŠธ๋ฆญ์Šค์—์„œ ๋‘ ๋ฒˆ์งธ๋กœ ๋‚ฎ์€ ์˜์—ญ (PC ์กฐ์ž‘ 5%) ์ง์ ‘ ๋ณด๊ฐ•.
8
+ 1.9.165 (web 67%) ์— ์ด์–ด 2 ๋ผ์šด๋“œ ์—ฐ์† 5๋Šฅ๋ ฅ ์ง์ ‘ ๋ณด๊ฐ• โ†’ **์ข…ํ•ฉ 67% โ†’ 76% production-ready ์ฒซ ์ง„์ž….**
9
+
10
+ ### Added โ€” `leerness pc check|click|type|screenshot`
11
+ **์˜์กด์„ฑ 0 ์›์น™ ์œ ์ง€** โ€” leerness ์ž์ฒด์—๋Š” robotjs/nut-tree ๋ฏธํฌํ•จ. ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ„๋„ ์„ค์น˜ ์‹œ ์ž๋™ detect.
12
+
13
+ ```bash
14
+ # 1) robotjs ๋˜๋Š” @nut-tree/nut-js ์„ค์น˜ (ํƒ 1)
15
+ npm i -g robotjs # ๋˜๋Š”
16
+ npm i -g @nut-tree/nut-js
17
+ leerness permissions set full # โš  mouse/keyboard ๊ถŒํ•œ ํ•„์š”
18
+ leerness pc check # โ†’ โœ“ robotjs ๋ฐœ๊ฒฌ
19
+
20
+ # 2) ํด๋ฆญ / ํƒ€์ดํ•‘ / ์Šคํฌ๋ฆฐ์ƒท
21
+ leerness pc click 800 400 # ์ขŒํ‘œ ํด๋ฆญ
22
+ leerness pc type "Hello, leerness" # ํ‚ค๋ณด๋“œ ์ž…๋ ฅ
23
+ leerness pc screenshot --out shot.png # ์Šคํฌ๋ฆฐ์ƒท
24
+ ```
25
+
26
+ ### Bridge ํŒจํ„ด โ€” opt-in ์˜์กด์„ฑ
27
+ - `_tryLoadPCAutomation()` โ€” `robotjs` (๋™๊ธฐ) / `@nut-tree/nut-js` (๋น„๋™๊ธฐ) ๋‘˜ ๋‹ค ์‹œ๋„ + npm ๊ธ€๋กœ๋ฒŒ root ํด๋ฐฑ
28
+ - ๋ฏธ์„ค์น˜ ์‹œ ์นœ์ ˆํ•œ ์•ˆ๋‚ด (`npm i -g robotjs` ๋˜๋Š” `npm i -g @nut-tree/nut-js`)
29
+ - `permissionCheck(root, 'mouse'/'keyboard')` ํ†ตํ•ฉ (1.9.146 ๊ถŒํ•œ ์‹œ์Šคํ…œ)
30
+ - `_recordRun(kind: 'pc_click' | 'pc_type' | 'pc_screenshot')` observability
31
+ - ๋‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถ„๊ธฐ: robotjs `moveMouse/mouseClick/typeString` (sync), @nut-tree `mouse.move/leftClick/keyboard.type` (async)
32
+
33
+ ### 5๋Šฅ๋ ฅ ๋งคํŠธ๋ฆญ์Šค ๊ฐฑ์‹  (๐ŸŽ‰ production-ready ์ฒซ ์ง„์ž…)
34
+ | ์˜์—ญ | 1.9.165 | **1.9.166** |
35
+ |---|---|---|
36
+ | (1) ์›น ์ž๋™ํ™” | 50% โš  (bridge) | 50% โš  (bridge, playwright ๋ฏธ์„ค์น˜) |
37
+ | (2) **PC ์กฐ์ž‘** | **5% โŒ** | **50% โš ** (bridge MVP, robotjs ๋ฏธ์„ค์น˜) โ†’ **90% โœ“** (์‚ฌ์šฉ์ž ์„ค์น˜ ์‹œ) |
38
+ | **์ข…ํ•ฉ** | 67% (beta-ready) | **76% ๐ŸŽ‰ production-ready** |
39
+
40
+ `leerness health` ๊ฐ€ ์‹ค์‹œ๊ฐ„ detect โ€” `require('robotjs')` ๋˜๋Š” `require('@nut-tree/nut-js')` try ์„ฑ๊ณต ์‹œ 90% ์ž๋™ ๋ถ€์—ฌ.
41
+
42
+ ### Verified
43
+ - e2e 217/217 โœ“ (1.9.165 baseline)
44
+ - stress-v111: 20/20 (bridge ํ•จ์ˆ˜ 6์ข… + CLI ๋™์ž‘ 5์ข… + ๋งคํŠธ๋ฆญ์Šค ๊ฐฑ์‹  3์ข… + ๋ˆ„์  ํšŒ๊ท€ 6์ข…)
45
+ - VERSION = 1.9.166 / autonomous-rounds = 96
46
+
47
+ ### main ์ž๋™ push 27 ๋ผ์šด๋“œ ์—ฐ์†
48
+ 1.9.140~1.9.166 = 27 ๋ผ์šด๋“œ release/X.Y.Z + main sync ๋ฌด์ค‘๋‹จ.
49
+
50
+ ---
51
+
52
+ ## 1.9.165 โ€” 2026-05-20
53
+
54
+ **playwright bridge MVP โ€” opt-in ์›น ์ž๋™ํ™” (5๋Šฅ๋ ฅ #1 ๋ณด๊ฐ•, 58% โ†’ 67%).**
55
+
56
+ ์ž์œจ ๋ชจ๋“œ 95 ๋ผ์šด๋“œ. 1.9.163 5๋Šฅ๋ ฅ ๋งคํŠธ๋ฆญ์Šค์—์„œ ๊ฐ€์žฅ ๋‚ฎ์€ ์˜์—ญ (์›น ์ž๋™ํ™” 5%) ์ง์ ‘ ๋ณด๊ฐ•.
57
+
58
+ ### Added โ€” `leerness web check|screenshot|extract`
59
+ **์˜์กด์„ฑ 0 ์›์น™ ์œ ์ง€** โ€” leerness ์ž์ฒด์—๋Š” playwright ๋ฏธํฌํ•จ. ์‚ฌ์šฉ์ž๊ฐ€ `npm i -g playwright` ๋ณ„๋„ ์„ค์น˜ ์‹œ ์ž๋™ detect.
60
+
61
+ ```bash
62
+ # 1) playwright ์„ค์น˜ + ์‚ฌ์šฉ ๊ฐ€๋Šฅ ํ™•์ธ
63
+ npm i -g playwright
64
+ npx playwright install chromium
65
+ leerness permissions set extended # ๋˜๋Š” full
66
+ leerness web check # โ†’ โœ“ playwright ๋ฐœ๊ฒฌ
67
+
68
+ # 2) ์Šคํฌ๋ฆฐ์ƒท
69
+ leerness web screenshot https://example.com --out shot.png
70
+
71
+ # 3) DOM ์ถ”์ถœ
72
+ leerness web extract https://example.com --selector "h1,h2" --json
73
+ ```
74
+
75
+ ### Bridge ํŒจํ„ด โ€” opt-in ์˜์กด์„ฑ
76
+ - `_tryLoadPlaywright()` โ€” `playwright` / `playwright-core` ๋‘˜ ๋‹ค ์‹œ๋„ + npm ๊ธ€๋กœ๋ฒŒ root ํด๋ฐฑ
77
+ - ๋ฏธ์„ค์น˜ ์‹œ ์นœ์ ˆํ•œ ์•ˆ๋‚ด (`npm i -g playwright`)
78
+ - `permissionCheck(root, 'browser')` ํ†ตํ•ฉ (1.9.146 ๊ถŒํ•œ ์‹œ์Šคํ…œ)
79
+ - `_recordRun(kind: 'web_screenshot' | 'web_extract')` observability
80
+
81
+ ### 5๋Šฅ๋ ฅ ๋งคํŠธ๋ฆญ์Šค ๊ฐฑ์‹ 
82
+ | ์˜์—ญ | 1.9.164 | **1.9.165** |
83
+ |---|---|---|
84
+ | (1) ์›น ์ž๋™ํ™” | 5% โŒ | **50% โš ** (bridge MVP, playwright ๋ฏธ์„ค์น˜) โ†’ **90% โœ“** (์‚ฌ์šฉ์ž ์„ค์น˜ ์‹œ) |
85
+ | **์ข…ํ•ฉ** | 58% (beta-ready) | **67%** (beta-ready, production-ready ์ž„๋ฐ•) |
86
+
87
+ `leerness health` ๊ฐ€ ์‹ค์‹œ๊ฐ„ detect โ€” `require('playwright')` try ์„ฑ๊ณต ์‹œ 90% ์ž๋™ ๋ถ€์—ฌ.
88
+
89
+ ### Verified
90
+ - e2e 217/217 โœ“
91
+ - stress-v110: 20/20 (bridge ํ•จ์ˆ˜ 6์ข… + CLI ๋™์ž‘ 6์ข… + ๋งคํŠธ๋ฆญ์Šค ๊ฐฑ์‹  2์ข… + ๋ˆ„์  ํšŒ๊ท€ 6์ข…)
92
+ - VERSION = 1.9.165 / autonomous-rounds = 95
93
+
94
+ ---
95
+
96
+ ## 1.9.164 โ€” 2026-05-20
97
+
98
+ **`leerness which` ์ง„๋‹จ ๋ช…๋ น + REPL provider ์ „ํ™˜ UX ๊ฐ•ํ™” (์‚ฌ์šฉ์ž ๋ช…์‹œ 2์ข…).**
99
+
100
+ ์ž์œจ ๋ชจ๋“œ 94 ๋ผ์šด๋“œ.
101
+
102
+ ### Added โ€” `leerness which` (์‚ฌ์šฉ์ž ๋ช…์‹œ: ๊ตฌ๋ฒ„์ „ ์ถฉ๋Œ ํ•ด๊ฒฐ)
103
+ ์‚ฌ์šฉ์ž๊ฐ€ "์ตœ์‹  ๋ฒ„์ „ ์ž‘๋™ ์•ˆ ํ•จ" ์˜์‹ฌ ์‹œ ํ•œ ๋ฒˆ์— ์ง„๋‹จ:
104
+ - **ํ˜„์žฌ ์‹คํ–‰ ๊ฒฝ๋กœ** โ€” `__filename` ๊ทธ๋Œ€๋กœ ํ‘œ์‹œ (์–ด๋–ค leerness ๊ฐ€ ์‹คํ–‰๋˜๋Š”์ง€ ์ •ํ™•ํžˆ)
105
+ - **๋ฒ„์ „ / Node / Platform**
106
+ - **`npm root -g`** โ€” ๊ธ€๋กœ๋ฒŒ ์„ค์น˜ ๊ฒฝ๋กœ
107
+ - **`npm cache`** โ€” npx ์บ์‹ฑ ๋””๋ ‰ํ† ๋ฆฌ
108
+ - **๊ธ€๋กœ๋ฒŒ ์„ค์น˜ ๋ฒ„์ „** โ€” `npm ls -g leerness`
109
+ - **PATH ํ›„๋ณด** โ€” `where`/`which -a` ๊ฒฐ๊ณผ (Windows/Unix ์ž๋™)
110
+ - **์ž๋™ ์ง„๋‹จ** โ€” ๊ธ€๋กœ๋ฒŒ โ‰  ํ˜„์žฌ ์‹คํ–‰ ์‹œ โš  ๊ฒฝ๊ณ  + ๊ฐ•์ œ ์ตœ์‹  ๋ช…๋ น 3์ข…
111
+ - `npx --yes leerness@latest <command>`
112
+ - `npm i -g leerness@latest`
113
+ - `npm cache clean --force`
114
+ - `--json` ์˜ต์…˜ โ€” ๊ตฌ์กฐํ™” ์ถœ๋ ฅ
115
+
116
+ ### Added โ€” REPL UX: Ollama ์‹คํŒจ ์‹œ ๋‹ค๋ฅธ CLI ์ฆ‰์‹œ ์ „ํ™˜ ์•ˆ๋‚ด (์‚ฌ์šฉ์ž ๋ช…์‹œ)
117
+ - **์‹œ์ž‘ ์‹œ**: Ollama ๋ฏธ๊ฐ€๋™ + ํ™œ์„ฑ ์™ธ๋ถ€ CLI ๋ฐœ๊ฒฌ โ†’ ์ฆ‰์‹œ ๋ฒˆํ˜ธ ์„ ํƒ prompt ์ถ”๊ฐ€
118
+ - `1) claude 2) codex ...` ํ˜•ํƒœ๋กœ ์ฆ‰์‹œ ์ „ํ™˜ ๊ฐ€๋Šฅ
119
+ - Enter ๋ˆ„๋ฅด๋ฉด Ollama fallback (๊ธฐ์กด ๋™์ž‘ ์œ ์ง€)
120
+ - **๋ฉ”์‹œ์ง€ ํ˜ธ์ถœ ์‹คํŒจ ์‹œ**: `:provider claude / :provider codex` ์ฆ‰์‹œ ๊ฐ€์ด๋“œ 1์ค„ ์ถ”๊ฐ€
121
+ - ์‚ฌ์šฉ์ž ๋ช…์‹œ "๋ชจ๋ธ์€ codex cli ๊ฐ™์€ ์—์ด์ „ํŠธ๋กœ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ „ํ™˜ ๊ฐ€๋Šฅ" ํ•ด๊ฒฐ
122
+
123
+ ### Diagnostic ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ
124
+ ```
125
+ # leerness which (1.9.164)
126
+ ํ˜„์žฌ ์‹คํ–‰: /usr/local/lib/node_modules/leerness/bin/harness.js
127
+ ๋ฒ„์ „: v1.9.164
128
+ Node: v24.11.1 (linux/x64)
129
+
130
+ ## npm ํ™˜๊ฒฝ
131
+ ๊ธ€๋กœ๋ฒŒ ์„ค์น˜: leerness@1.9.139 โ† ์˜› ๋ฒ„์ „!
132
+
133
+ ## โš  ์ง„๋‹จ
134
+ โš  ๊ธ€๋กœ๋ฒŒ ์„ค์น˜ 1.9.139 โ‰  ํ˜„์žฌ ์‹คํ–‰ 1.9.164 โ€” npx ์บ์‹œ ๋˜๋Š” PATH ์ถฉ๋Œ ์˜์‹ฌ
135
+ โ†’ ๊ฐ•์ œ ์ตœ์‹ : npm i -g leerness@latest / ๋˜๋Š” npx --yes leerness@latest <command>
136
+ ```
137
+
138
+ ### Verified
139
+ - e2e 217/217 โœ“
140
+ - stress-v109: 16/16 (which 6์ข… + REPL UX 3์ข… + ๋ˆ„์  ํšŒ๊ท€ 7์ข…)
141
+ - VERSION = 1.9.164 / autonomous-rounds = 94
142
+
143
+ ---
144
+
3
145
  ## 1.9.163 โ€” 2026-05-20
4
146
 
5
147
  **`leerness health` ์— 5๋Šฅ๋ ฅ ๋งคํŠธ๋ฆญ์Šค ์ž๋™ ํ‰๊ฐ€ ํ†ตํ•ฉ.**
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.163-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v108-14%2F14-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-50-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-93-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-24_rounds-success)]() [![capability](https://img.shields.io/badge/5_capability_matrix-58%25_beta--ready-yellow)]() [![provider-crud](https://img.shields.io/badge/provider_registry-CRUD%2Bsync-success)]() [![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.166-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v111-20%2F20-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-50-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-96-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-27_rounds-success)]() [![pc-bridge](https://img.shields.io/badge/pc_bridge-robotjs%2Fnut--tree_opt--in-success)]() [![web-bridge](https://img.shields.io/badge/playwright_bridge-opt--in_MVP-success)]() [![capability](https://img.shields.io/badge/5_capability-76%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,8 +12,9 @@
12
12
  โ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ• โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ•‘
13
13
  โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ•‘
14
14
  โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•‘
15
- โ•‘ v1.9.163 AI Agent Reliability Harness + Sandbox โ•‘
15
+ โ•‘ v1.9.166 AI Agent Reliability Harness + Sandbox โ•‘
16
16
  โ•‘ verify ยท remember ยท orchestrate ยท audit ยท sandbox ยท drift โ•‘
17
+ โ•‘ ๐ŸŽ‰ 76% production-ready (web + pc bridge opt-in MVP) โ•‘
17
18
  โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
18
19
  ```
19
20
 
package/bin/harness.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const cp = require('child_process');
7
7
  const readline = require('readline');
8
8
 
9
- const VERSION = '1.9.163';
9
+ const VERSION = '1.9.166';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -10769,9 +10769,35 @@ async function _agentRepl(root, opts) {
10769
10769
  state.model = (idx >= 0 && idx < r.models.length) ? r.models[idx] : r.models[0];
10770
10770
  log(C.green(` โœ“ ๋ชจ๋ธ ์„ ํƒ: ${state.model}`));
10771
10771
  } else {
10772
- log(C.yel(` โš  Ollama ๋ฏธ๊ฐ€๋™ ๋˜๋Š” ๋ชจ๋ธ ์—†์Œ โ€” ollama serve + ollama pull <model>`));
10773
- state.model = process.env.LEERNESS_OLLAMA_MODEL || 'llama3';
10774
- log(C.dim(` fallback: ${state.model}`));
10772
+ log(C.yel(` โš  Ollama ๋ฏธ๊ฐ€๋™ ๋˜๋Š” ๋ชจ๋ธ ์—†์Œ`));
10773
+ // 1.9.164: Ollama ์‹คํŒจ ์‹œ ๋‹ค๋ฅธ ํ™œ์„ฑ CLI ์ฆ‰์‹œ ์ œ์•ˆ (UX ๊ฐœ์„  โ€” ์‚ฌ์šฉ์ž ๋ช…์‹œ ์š”์ฒญ)
10774
+ try {
10775
+ const readyCli = EXTERNAL_AGENTS.filter(a => a.id !== 'ollama')
10776
+ .map(a => ({ def: a, status: _checkAgent(a) }))
10777
+ .filter(x => x.status.status === 'ready');
10778
+ if (readyCli.length) {
10779
+ log('');
10780
+ log(C.cy(` ๐Ÿ’ก ํ™œ์„ฑ ์™ธ๋ถ€ CLI ${readyCli.length}๊ฐœ ๋ฐœ๊ฒฌ โ€” provider ์ „ํ™˜ ๊ฐ€๋Šฅ:`));
10781
+ readyCli.forEach((x, i) => log(` ${i + 1}) ${x.def.id} (v${x.status.version || '?'})`));
10782
+ const choice = await new Promise(res => rl.question(C.cy('\n provider ์ „ํ™˜ (๋ฒˆํ˜ธ / Enter=ollama ๊ณ„์†): '), res));
10783
+ const idx = parseInt(choice, 10) - 1;
10784
+ if (idx >= 0 && idx < readyCli.length) {
10785
+ state.provider = readyCli[idx].def.id;
10786
+ state.model = null; // ์ƒˆ provider ๊ธฐ๋ณธ ๋ชจ๋ธ ์‚ฌ์šฉ
10787
+ log(C.green(` โœ“ provider ์ „ํ™˜: ${state.provider} (๋ฉ”์‹œ์ง€ ์ž…๋ ฅ ์ฆ‰์‹œ ์‚ฌ์šฉ)`));
10788
+ } else {
10789
+ state.model = process.env.LEERNESS_OLLAMA_MODEL || 'llama3';
10790
+ log(C.dim(` ollama fallback: ${state.model} โ€” ์ถ”ํ›„ :provider <์ด๋ฆ„> ์œผ๋กœ ์ „ํ™˜ ๊ฐ€๋Šฅ`));
10791
+ }
10792
+ } else {
10793
+ log(C.dim(` ollama serve + ollama pull <model> / ๋˜๋Š” .env ์—์„œ LEERNESS_ENABLE_CLAUDE=1 ๋“ฑ ํ™œ์„ฑํ™”`));
10794
+ state.model = process.env.LEERNESS_OLLAMA_MODEL || 'llama3';
10795
+ log(C.dim(` fallback: ${state.model} (์‹ค ํ˜ธ์ถœ ์‹คํŒจ ์‹œ :provider ๋ฉ”๋‰ด ๋˜๋Š” :quit)`));
10796
+ }
10797
+ } catch {
10798
+ state.model = process.env.LEERNESS_OLLAMA_MODEL || 'llama3';
10799
+ log(C.dim(` fallback: ${state.model}`));
10800
+ }
10775
10801
  }
10776
10802
  }
10777
10803
  log('');
@@ -10997,6 +11023,17 @@ async function _agentRepl(root, opts) {
10997
11023
  if (state.history.length % 6 === 0) saveSession(); // 6ํ„ด๋งˆ๋‹ค ์ž๋™ ์ €์žฅ
10998
11024
  } else {
10999
11025
  log(C.yel(` โš  ์‹คํŒจ: ${result.error || 'unknown'}`));
11026
+ // 1.9.164: ์‹คํŒจ ์‹œ ์ฆ‰์‹œ ์ „ํ™˜ ๊ฐ€๋Šฅ provider ์•ˆ๋‚ด (UX ๊ฐœ์„  โ€” ์‚ฌ์šฉ์ž ๋ช…์‹œ ์š”์ฒญ)
11027
+ try {
11028
+ const others = EXTERNAL_AGENTS.filter(a => a.id !== state.provider)
11029
+ .map(a => ({ def: a, status: _checkAgent(a) }))
11030
+ .filter(x => x.status.status === 'ready');
11031
+ if (others.length) {
11032
+ log(C.dim(` ๐Ÿ’ก ์ „ํ™˜ ๊ฐ€๋Šฅ: ${others.map(x => `:provider ${x.def.id}`).join(' / ')}`));
11033
+ } else {
11034
+ log(C.dim(` ๐Ÿ’ก ๋‹ค๋ฅธ provider ํ™œ์„ฑํ™”: .env ์—์„œ LEERNESS_ENABLE_<CLAUDE|CODEX|GEMINI|COPILOT>=1`));
11035
+ }
11036
+ } catch {}
11000
11037
  }
11001
11038
  rl.prompt();
11002
11039
  });
@@ -11537,14 +11574,31 @@ function healthCmd(root) {
11537
11574
  try {
11538
11575
  const harnessSrc = read(__filename);
11539
11576
  const cap = {};
11540
- // (1) ์›น ์ž๋™ํ™” โ€” playwright/puppeteer/chromium import ์กด์žฌ?
11541
- cap.webAutomation = /require\(['"]playwright['"]\)|require\(['"]puppeteer['"]\)|require\(['"]chromium['"]\)/.test(harnessSrc)
11542
- ? { score: 90, status: 'โœ“', evidence: 'playwright/puppeteer import ๊ฒ€์ถœ' }
11543
- : { score: 5, status: 'โŒ', evidence: 'permissions.browser=toggle๋งŒ (์‹ค ์ฝ”๋“œ ๋ฏธ๊ตฌํ˜„)' };
11544
- // (2) PC ์กฐ์ž‘ โ€” robotjs/nut-js/iohook/xdotool import?
11545
- cap.pcAutomation = /require\(['"]robotjs['"]\)|require\(['"]@nut-tree/.test(harnessSrc)
11546
- ? { score: 90, status: 'โœ“', evidence: 'robotjs/nut-tree import ๊ฒ€์ถœ' }
11547
- : { score: 5, status: 'โŒ', evidence: 'permissions.mouse/keyboard=ํ•„๋“œ๋งŒ (์‹ค ์‚ฌ์šฉ์ฒ˜ 0)' };
11577
+ // (1) ์›น ์ž๋™ํ™” โ€” 1.9.165 playwright bridge ํ†ตํ•ฉ + ์‹ค์ œ playwright ์„ค์น˜ detect
11578
+ const hasWebBridge = /function webCmd\(root, sub/.test(harnessSrc);
11579
+ // ์‚ฌ์šฉ์ž๊ฐ€ playwright ์„ค์น˜ํ–ˆ๋Š”์ง€ ์‹ค์‹œ๊ฐ„ detect (require try)
11580
+ let playwrightInstalled = false;
11581
+ try { require('playwright'); playwrightInstalled = true; }
11582
+ catch { try { require('playwright-core'); playwrightInstalled = true; } catch {} }
11583
+ if (hasWebBridge && playwrightInstalled) {
11584
+ cap.webAutomation = { score: 90, status: 'โœ“', evidence: 'playwright ์„ค์น˜ + leerness web bridge (1.9.165)' };
11585
+ } else if (hasWebBridge) {
11586
+ cap.webAutomation = { score: 50, status: 'โš ', evidence: 'leerness web bridge ์žˆ์Œ, playwright ๋ฏธ์„ค์น˜ (npm i -g playwright)' };
11587
+ } else {
11588
+ cap.webAutomation = { score: 5, status: 'โŒ', evidence: 'permissions.browser=toggle๋งŒ (์‹ค ์ฝ”๋“œ ๋ฏธ๊ตฌํ˜„)' };
11589
+ }
11590
+ // (2) PC ์กฐ์ž‘ โ€” 1.9.166 robotjs/nut-tree bridge + ์‹ค์ œ ์„ค์น˜ detect
11591
+ const hasPCBridge = /function pcCmd\(root, sub/.test(harnessSrc);
11592
+ let pcInstalled = false;
11593
+ try { require('robotjs'); pcInstalled = true; }
11594
+ catch { try { require('@nut-tree/nut-js'); pcInstalled = true; } catch {} }
11595
+ if (hasPCBridge && pcInstalled) {
11596
+ cap.pcAutomation = { score: 90, status: 'โœ“', evidence: 'robotjs/nut-tree ์„ค์น˜ + leerness pc bridge (1.9.166)' };
11597
+ } else if (hasPCBridge) {
11598
+ cap.pcAutomation = { score: 50, status: 'โš ', evidence: 'leerness pc bridge ์žˆ์Œ, robotjs ๋ฏธ์„ค์น˜ (npm i -g robotjs)' };
11599
+ } else {
11600
+ cap.pcAutomation = { score: 5, status: 'โŒ', evidence: 'permissions.mouse/keyboard=ํ•„๋“œ๋งŒ (์‹ค ์‚ฌ์šฉ์ฒ˜ 0)' };
11601
+ }
11548
11602
  // (3) ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ โ€” agents multi --execute + consensus ๋กœ์ง?
11549
11603
  const hasExecute = /const execute = has\('--execute'\)/.test(harnessSrc);
11550
11604
  const hasConsensus = /multi-signal consensus/.test(harnessSrc);
@@ -11884,8 +11938,365 @@ function reuseAutodetectCmd(root) {
11884
11938
  }
11885
11939
  }
11886
11940
 
11941
+ // 1.9.165: leerness web โ€” playwright bridge MVP (opt-in ์˜์กด์„ฑ, 5๋Šฅ๋ ฅ #1 ๋ณด๊ฐ•)
11942
+ // leerness ์ž์ฒด์—๋Š” playwright ๋ฏธํฌํ•จ (์˜์กด์„ฑ 0 ์›์น™ ์œ ์ง€). ์‚ฌ์šฉ์ž๊ฐ€ `npm i -g playwright` ๋ณ„๋„ ์„ค์น˜ ์‹œ ์ž๋™ detect.
11943
+ // permissions.browser=true ํ•„์š” (1.9.146 ๊ถŒํ•œ ์‹œ์Šคํ…œ ํ†ตํ•ฉ).
11944
+ function _tryLoadPlaywright() {
11945
+ // ์‚ฌ์šฉ์ž ๊ธ€๋กœ๋ฒŒ + ๋กœ์ปฌ ๋ชจ๋‘ ์‹œ๋„
11946
+ const candidates = ['playwright', 'playwright-core'];
11947
+ for (const id of candidates) {
11948
+ try { return { ok: true, lib: require(id), name: id }; } catch {}
11949
+ }
11950
+ // ๊ธ€๋กœ๋ฒŒ npm root ์‹œ๋„
11951
+ try {
11952
+ const r = cp.spawnSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000, shell: true });
11953
+ if (r.status === 0) {
11954
+ const globalRoot = (r.stdout || '').trim();
11955
+ for (const id of candidates) {
11956
+ try { return { ok: true, lib: require(path.join(globalRoot, id)), name: id, source: 'global' }; } catch {}
11957
+ }
11958
+ }
11959
+ } catch {}
11960
+ return { ok: false, error: 'playwright ๋ฏธ์„ค์น˜ โ€” `npm i -g playwright` ๋˜๋Š” ํ”„๋กœ์ ํŠธ์— `npm i playwright` ํ›„ ๋‹ค์‹œ ์‹œ๋„' };
11961
+ }
11962
+ function webCmd(root, sub, ...args) {
11963
+ root = absRoot(root || process.cwd());
11964
+ if (!sub || sub === 'check') {
11965
+ const r = _tryLoadPlaywright();
11966
+ if (has('--json')) {
11967
+ log(JSON.stringify({ installed: r.ok, name: r.name || null, source: r.source || 'local', error: r.error || null, permissions: _readPermissions(root).browser || false }, null, 2));
11968
+ return;
11969
+ }
11970
+ log(`# leerness web check (1.9.165)`);
11971
+ if (r.ok) {
11972
+ log(`โœ“ playwright ๋ฐœ๊ฒฌ: ${r.name}${r.source ? ` (${r.source})` : ''}`);
11973
+ log(` โ†’ leerness web screenshot <url> --out file.png ์‚ฌ์šฉ ๊ฐ€๋Šฅ`);
11974
+ } else {
11975
+ log(`โœ— ${r.error}`);
11976
+ }
11977
+ const perms = _readPermissions(root);
11978
+ log(`permissions.browser: ${perms.browser ? 'โœ“ ํ—ˆ์šฉ' : 'โœ— ๊ฑฐ๋ถ€ (basic ๋ชจ๋“œ)'}`);
11979
+ if (!perms.browser) log(` โ†’ leerness permissions set extended ๋˜๋Š” set full`);
11980
+ return;
11981
+ }
11982
+ if (sub === 'screenshot') {
11983
+ const url = args[0] || arg('--url', '');
11984
+ const outPath = arg('--out', '');
11985
+ if (!url) return fail('leerness web screenshot <url> --out <file.png> ํ•„์š”');
11986
+ if (!outPath) return fail('--out <file.png> ๊ฒฝ๋กœ ํ•„์š”');
11987
+ if (!/^https?:\/\//.test(url)) return fail(`URL ํ˜•์‹ ์˜ค๋ฅ˜ (http:// ๋˜๋Š” https://): ${url}`);
11988
+ if (!permissionCheck(root, 'browser', url)) {
11989
+ return fail(`permissions.browser=false (ํ˜„์žฌ: ${_readPermissions(root).mode}) โ€” leerness permissions set extended ๋˜๋Š” full ๊ถŒ์žฅ`);
11990
+ }
11991
+ const r = _tryLoadPlaywright();
11992
+ if (!r.ok) { fail(r.error); process.exitCode = 1; return; }
11993
+ const t0 = Date.now();
11994
+ log(`# leerness web screenshot (1.9.165)`);
11995
+ log(`URL: ${url} โ†’ ${outPath}`);
11996
+ return (async () => {
11997
+ let browser;
11998
+ try {
11999
+ const { chromium } = r.lib;
12000
+ if (!chromium) { fail('playwright.chromium ์—†์Œ โ€” `npx playwright install chromium` ํ•„์š”'); process.exitCode = 1; return; }
12001
+ browser = await chromium.launch({ headless: true });
12002
+ const page = await browser.newPage();
12003
+ await page.goto(url, { timeout: 30000, waitUntil: 'networkidle' });
12004
+ await page.screenshot({ path: outPath, fullPage: true });
12005
+ await browser.close();
12006
+ const dt = Date.now() - t0;
12007
+ ok(`screenshot ์™„๋ฃŒ: ${outPath} (${dt}ms)`);
12008
+ try { _recordRun(root, { kind: 'web_screenshot', url, outPath, durationMs: dt, ok: true }); } catch {}
12009
+ } catch (e) {
12010
+ fail(`screenshot ์‹คํŒจ: ${e.message}`);
12011
+ if (browser) try { await browser.close(); } catch {}
12012
+ try { _recordRun(root, { kind: 'web_screenshot', url, durationMs: Date.now() - t0, ok: false, error: e.message }); } catch {}
12013
+ process.exitCode = 1;
12014
+ }
12015
+ })();
12016
+ }
12017
+ if (sub === 'extract') {
12018
+ const url = args[0] || arg('--url', '');
12019
+ const selector = arg('--selector', '');
12020
+ if (!url || !selector) return fail('leerness web extract <url> --selector "css-selector" ํ•„์š”');
12021
+ if (!/^https?:\/\//.test(url)) return fail(`URL ํ˜•์‹ ์˜ค๋ฅ˜: ${url}`);
12022
+ if (!permissionCheck(root, 'browser', url)) {
12023
+ return fail(`permissions.browser=false โ€” leerness permissions set extended ๋˜๋Š” full`);
12024
+ }
12025
+ const r = _tryLoadPlaywright();
12026
+ if (!r.ok) { fail(r.error); process.exitCode = 1; return; }
12027
+ const t0 = Date.now();
12028
+ return (async () => {
12029
+ let browser;
12030
+ try {
12031
+ const { chromium } = r.lib;
12032
+ if (!chromium) { fail('playwright.chromium ์—†์Œ โ€” `npx playwright install chromium`'); process.exitCode = 1; return; }
12033
+ browser = await chromium.launch({ headless: true });
12034
+ const page = await browser.newPage();
12035
+ await page.goto(url, { timeout: 30000, waitUntil: 'networkidle' });
12036
+ const elements = await page.$$eval(selector, els => els.slice(0, 50).map(el => el.textContent?.trim() || ''));
12037
+ await browser.close();
12038
+ const dt = Date.now() - t0;
12039
+ const out = { url, selector, count: elements.length, elements, durationMs: dt };
12040
+ if (has('--json')) log(JSON.stringify(out, null, 2));
12041
+ else {
12042
+ log(`# leerness web extract (1.9.165)`);
12043
+ log(`URL: ${url} ยท selector: ${selector} ยท ${elements.length}๊ฐœ (${dt}ms)`);
12044
+ elements.slice(0, 20).forEach((t, i) => log(` ${i+1}. ${t.slice(0, 200)}${t.length > 200 ? 'โ€ฆ' : ''}`));
12045
+ }
12046
+ try { _recordRun(root, { kind: 'web_extract', url, selector, count: elements.length, durationMs: dt, ok: true }); } catch {}
12047
+ } catch (e) {
12048
+ fail(`extract ์‹คํŒจ: ${e.message}`);
12049
+ if (browser) try { await browser.close(); } catch {}
12050
+ process.exitCode = 1;
12051
+ }
12052
+ })();
12053
+ }
12054
+ fail(`์•Œ ์ˆ˜ ์—†๋Š” sub: ${sub} (check / screenshot / extract)`);
12055
+ }
12056
+
12057
+ // 1.9.166: leerness pc โ€” robotjs/nut-tree bridge MVP (opt-in ์˜์กด์„ฑ, 5๋Šฅ๋ ฅ #2 ๋ณด๊ฐ•)
12058
+ // leerness ์ž์ฒด์—๋Š” robotjs ๋ฏธํฌํ•จ (์˜์กด์„ฑ 0). ์‚ฌ์šฉ์ž๊ฐ€ `npm i -g robotjs` ๋ณ„๋„ ์„ค์น˜ ์‹œ ์ž๋™ detect.
12059
+ // permissions.mouse / .keyboard / .browser ํ•„์š” (1.9.146 ๊ถŒํ•œ ์‹œ์Šคํ…œ).
12060
+ // โš  full ๋ชจ๋“œ ๊ถŒ์žฅ โ€” IDE ํ†ตํ•ฉ ์™ธ์—๋Š” ์œ„ํ—˜์„ฑ ๋ช…์‹œ.
12061
+ function _tryLoadPCAutomation() {
12062
+ // robotjs ์šฐ์„ , fallback @nut-tree/nut-js
12063
+ const candidates = ['robotjs', '@nut-tree/nut-js'];
12064
+ for (const id of candidates) {
12065
+ try { return { ok: true, lib: require(id), name: id }; } catch {}
12066
+ }
12067
+ // ๊ธ€๋กœ๋ฒŒ npm root ์‹œ๋„
12068
+ try {
12069
+ const r = cp.spawnSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000, shell: true });
12070
+ if (r.status === 0) {
12071
+ const globalRoot = (r.stdout || '').trim();
12072
+ for (const id of candidates) {
12073
+ try { return { ok: true, lib: require(path.join(globalRoot, id)), name: id, source: 'global' }; } catch {}
12074
+ }
12075
+ }
12076
+ } catch {}
12077
+ return { ok: false, error: 'robotjs/@nut-tree/nut-js ๋ฏธ์„ค์น˜ โ€” `npm i -g robotjs` ๋˜๋Š” `npm i -g @nut-tree/nut-js` ํ›„ ๋‹ค์‹œ ์‹œ๋„' };
12078
+ }
12079
+ function pcCmd(root, sub, ...args) {
12080
+ root = absRoot(root || process.cwd());
12081
+ if (!sub || sub === 'check') {
12082
+ const r = _tryLoadPCAutomation();
12083
+ const perms = _readPermissions(root);
12084
+ if (has('--json')) {
12085
+ log(JSON.stringify({
12086
+ installed: r.ok,
12087
+ name: r.name || null,
12088
+ source: r.source || 'local',
12089
+ error: r.error || null,
12090
+ permissions: {
12091
+ mouse: perms.mouse || false,
12092
+ keyboard: perms.keyboard || false,
12093
+ mode: perms.mode || 'basic'
12094
+ }
12095
+ }, null, 2));
12096
+ return;
12097
+ }
12098
+ log(`# leerness pc check (1.9.166)`);
12099
+ if (r.ok) {
12100
+ log(`โœ“ ${r.name} ๋ฐœ๊ฒฌ${r.source ? ` (${r.source})` : ''}`);
12101
+ log(` โ†’ leerness pc click / type / screenshot ์‚ฌ์šฉ ๊ฐ€๋Šฅ`);
12102
+ } else {
12103
+ log(`โœ— ${r.error}`);
12104
+ }
12105
+ log('');
12106
+ log(`## ๊ถŒํ•œ (1.9.146)`);
12107
+ log(` permissions.mouse: ${perms.mouse ? 'โœ“ ํ—ˆ์šฉ' : 'โœ— ๊ฑฐ๋ถ€'}`);
12108
+ log(` permissions.keyboard: ${perms.keyboard ? 'โœ“ ํ—ˆ์šฉ' : 'โœ— ๊ฑฐ๋ถ€'}`);
12109
+ log(` ํ˜„์žฌ ๋ชจ๋“œ: ${perms.mode || 'basic'}`);
12110
+ if (!perms.mouse || !perms.keyboard) {
12111
+ log('');
12112
+ log(` ๐Ÿ’ก ํ™œ์„ฑํ™”: leerness permissions set full (โš  IDE ํ†ตํ•ฉ ์™ธ์—๋Š” ์œ„ํ—˜)`);
12113
+ }
12114
+ return;
12115
+ }
12116
+ if (sub === 'click') {
12117
+ const x = parseInt(args[0] || arg('--x', ''), 10);
12118
+ const y = parseInt(args[1] || arg('--y', ''), 10);
12119
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return fail('leerness pc click <x> <y> ํ•„์š” (์ •์ˆ˜ ์ขŒํ‘œ)');
12120
+ if (!permissionCheck(root, 'mouse', `${x},${y}`)) {
12121
+ return fail(`permissions.mouse=false (ํ˜„์žฌ: ${_readPermissions(root).mode}) โ€” leerness permissions set full`);
12122
+ }
12123
+ const r = _tryLoadPCAutomation();
12124
+ if (!r.ok) { fail(r.error); process.exitCode = 1; return; }
12125
+ const t0 = Date.now();
12126
+ try {
12127
+ if (r.name === 'robotjs') {
12128
+ r.lib.moveMouse(x, y);
12129
+ r.lib.mouseClick();
12130
+ } else if (r.name === '@nut-tree/nut-js') {
12131
+ // nut-js ๋Š” ๋น„๋™๊ธฐ
12132
+ return (async () => {
12133
+ const { mouse, Point, Button } = r.lib;
12134
+ await mouse.move([new Point(x, y)]);
12135
+ await mouse.click(Button.LEFT);
12136
+ const dt = Date.now() - t0;
12137
+ ok(`click (${x}, ${y}) โ€” ${dt}ms`);
12138
+ try { _recordRun(root, { kind: 'pc_click', x, y, lib: r.name, durationMs: dt, ok: true }); } catch {}
12139
+ })();
12140
+ }
12141
+ const dt = Date.now() - t0;
12142
+ ok(`click (${x}, ${y}) โ€” ${dt}ms`);
12143
+ try { _recordRun(root, { kind: 'pc_click', x, y, lib: r.name, durationMs: dt, ok: true }); } catch {}
12144
+ } catch (e) {
12145
+ fail(`click ์‹คํŒจ: ${e.message}`);
12146
+ try { _recordRun(root, { kind: 'pc_click', x, y, lib: r.name, durationMs: Date.now() - t0, ok: false, error: e.message }); } catch {}
12147
+ process.exitCode = 1;
12148
+ }
12149
+ return;
12150
+ }
12151
+ if (sub === 'type') {
12152
+ const text = args[0] || arg('--text', '');
12153
+ if (!text) return fail('leerness pc type "<text>" ํ•„์š”');
12154
+ if (!permissionCheck(root, 'keyboard', text)) {
12155
+ return fail(`permissions.keyboard=false โ€” leerness permissions set full`);
12156
+ }
12157
+ const r = _tryLoadPCAutomation();
12158
+ if (!r.ok) { fail(r.error); process.exitCode = 1; return; }
12159
+ const t0 = Date.now();
12160
+ try {
12161
+ if (r.name === 'robotjs') {
12162
+ r.lib.typeString(text);
12163
+ } else if (r.name === '@nut-tree/nut-js') {
12164
+ return (async () => {
12165
+ const { keyboard } = r.lib;
12166
+ await keyboard.type(text);
12167
+ const dt = Date.now() - t0;
12168
+ ok(`type ${text.length}์ž โ€” ${dt}ms`);
12169
+ try { _recordRun(root, { kind: 'pc_type', chars: text.length, lib: r.name, durationMs: dt, ok: true }); } catch {}
12170
+ })();
12171
+ }
12172
+ const dt = Date.now() - t0;
12173
+ ok(`type ${text.length}์ž โ€” ${dt}ms`);
12174
+ try { _recordRun(root, { kind: 'pc_type', chars: text.length, lib: r.name, durationMs: dt, ok: true }); } catch {}
12175
+ } catch (e) {
12176
+ fail(`type ์‹คํŒจ: ${e.message}`);
12177
+ process.exitCode = 1;
12178
+ }
12179
+ return;
12180
+ }
12181
+ if (sub === 'screenshot') {
12182
+ // OS-level screenshot (robotjs.screen ๋˜๋Š” nut-js screen.capture)
12183
+ const outPath = arg('--out', '');
12184
+ if (!outPath) return fail('--out <file.png> ๊ฒฝ๋กœ ํ•„์š”');
12185
+ if (!permissionCheck(root, 'mouse', outPath)) {
12186
+ // mouse ๊ถŒํ•œ์œผ๋กœ screenshot ๋„ ์ œ์–ด (๋””์Šคํ”Œ๋ ˆ์ด ์ ‘๊ทผ)
12187
+ return fail(`permissions.mouse=false (ํ˜„์žฌ: ${_readPermissions(root).mode}) โ€” leerness permissions set full`);
12188
+ }
12189
+ const r = _tryLoadPCAutomation();
12190
+ if (!r.ok) { fail(r.error); process.exitCode = 1; return; }
12191
+ const t0 = Date.now();
12192
+ try {
12193
+ if (r.name === 'robotjs') {
12194
+ const img = r.lib.screen.capture();
12195
+ // robotjs ์˜ raw bitmap โ†’ PNG ๋ณ€ํ™˜ ํ•„์š” โ€” MVP ์—์„  raw ์ €์žฅ
12196
+ fs.writeFileSync(outPath, Buffer.from(img.image));
12197
+ const dt = Date.now() - t0;
12198
+ ok(`screenshot (raw) โ€” ${dt}ms ยท ${outPath} (PNG ๋ณ€ํ™˜: pngjs ๋˜๋Š” sharp ๋ณ„๋„)`);
12199
+ try { _recordRun(root, { kind: 'pc_screenshot', outPath, lib: r.name, durationMs: dt, ok: true }); } catch {}
12200
+ } else if (r.name === '@nut-tree/nut-js') {
12201
+ return (async () => {
12202
+ const { screen } = r.lib;
12203
+ const img = await screen.capture(outPath);
12204
+ const dt = Date.now() - t0;
12205
+ ok(`screenshot โ€” ${dt}ms ยท ${outPath}`);
12206
+ try { _recordRun(root, { kind: 'pc_screenshot', outPath, lib: r.name, durationMs: dt, ok: true }); } catch {}
12207
+ })();
12208
+ }
12209
+ } catch (e) {
12210
+ fail(`screenshot ์‹คํŒจ: ${e.message}`);
12211
+ process.exitCode = 1;
12212
+ }
12213
+ return;
12214
+ }
12215
+ fail(`์•Œ ์ˆ˜ ์—†๋Š” sub: ${sub} (check / click / type / screenshot)`);
12216
+ }
12217
+
12218
+ // 1.9.164: leerness which โ€” ์ง„๋‹จ ๋„๊ตฌ (๊ตฌ๋ฒ„์ „ ์ถฉ๋Œ / npx ์บ์‹œ / PATH ์ถฉ๋Œ ํ•ด๊ฒฐ)
12219
+ // ์‚ฌ์šฉ์ž๊ฐ€ "์ตœ์‹  ๋ฒ„์ „ ์ž‘๋™ ์•ˆ ํ•จ" ์˜์‹ฌ ์‹œ: ์‹ค์ œ ์‹คํ–‰ ์ค‘์ธ leerness ์˜ ๊ฒฝ๋กœ / ๋ฒ„์ „ / npm ์บ์‹œ / PATH ํ›„๋ณด ํ‘œ์‹œ.
12220
+ function whichCmd() {
12221
+ const out = {
12222
+ version: VERSION,
12223
+ runningFrom: __filename,
12224
+ nodeVersion: process.version,
12225
+ platform: process.platform,
12226
+ arch: process.arch,
12227
+ npm: {},
12228
+ pathCandidates: []
12229
+ };
12230
+ // npm root -g (๊ธ€๋กœ๋ฒŒ ์„ค์น˜ ๊ฒฝ๋กœ)
12231
+ try {
12232
+ const r = cp.spawnSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000, shell: true });
12233
+ if (r.status === 0) out.npm.globalRoot = (r.stdout || '').trim();
12234
+ } catch {}
12235
+ // npm cache (npx ์บ์‹œ ๊ฒฝ๋กœ)
12236
+ try {
12237
+ const r = cp.spawnSync('npm', ['config', 'get', 'cache'], { encoding: 'utf8', timeout: 5000, shell: true });
12238
+ if (r.status === 0) out.npm.cacheDir = (r.stdout || '').trim();
12239
+ } catch {}
12240
+ // npm ๊ธ€๋กœ๋ฒŒ leerness ์„ค์น˜ ์ •๋ณด
12241
+ try {
12242
+ const r = cp.spawnSync('npm', ['ls', '-g', 'leerness', '--depth=0', '--json'], { encoding: 'utf8', timeout: 8000, shell: true });
12243
+ if (r.stdout) {
12244
+ try {
12245
+ const j = JSON.parse(r.stdout);
12246
+ if (j.dependencies?.leerness) out.npm.globalInstalled = j.dependencies.leerness.version;
12247
+ } catch {}
12248
+ }
12249
+ } catch {}
12250
+ // PATH ํ›„๋ณด (Windows: where / Unix: which)
12251
+ try {
12252
+ const isWin = process.platform === 'win32';
12253
+ const tool = isWin ? 'where' : 'which';
12254
+ const r = cp.spawnSync(tool, ['-a', 'leerness'], { encoding: 'utf8', timeout: 5000, shell: true });
12255
+ if (r.stdout) out.pathCandidates = (r.stdout || '').trim().split(/\r?\n/).filter(Boolean);
12256
+ } catch {}
12257
+ // ์ง„๋‹จ: ๊ธ€๋กœ๋ฒŒ ์„ค์น˜๋œ leerness ์™€ ํ˜„์žฌ ์‹คํ–‰ ๋ฒ„์ „์ด ๋‹ค๋ฅด๋ฉด ๊ฒฝ๊ณ 
12258
+ out.diagnostics = [];
12259
+ if (out.npm.globalInstalled && out.npm.globalInstalled !== VERSION) {
12260
+ out.diagnostics.push(`โš  ๊ธ€๋กœ๋ฒŒ ์„ค์น˜ ${out.npm.globalInstalled} โ‰  ํ˜„์žฌ ์‹คํ–‰ ${VERSION} โ€” npx ์บ์‹œ ๋˜๋Š” PATH ์ถฉ๋Œ ์˜์‹ฌ`);
12261
+ out.diagnostics.push(` โ†’ ๊ฐ•์ œ ์ตœ์‹ : npm i -g leerness@latest / ๋˜๋Š” npx --yes leerness@latest <command>`);
12262
+ }
12263
+ if (out.pathCandidates.length > 1) {
12264
+ out.diagnostics.push(`โš  PATH ์— leerness ๊ฐ€ ${out.pathCandidates.length}๊ฐœ โ€” ์šฐ์„ ์ˆœ์œ„ ์ถฉ๋Œ ๊ฐ€๋Šฅ`);
12265
+ out.diagnostics.push(` โ†’ ๋ช…์‹œ์  ๊ฒฝ๋กœ ์‚ฌ์šฉ: ${out.runningFrom}`);
12266
+ }
12267
+ if (has('--json')) { log(JSON.stringify(out, null, 2)); return; }
12268
+ log(`# leerness which (1.9.164)`);
12269
+ log(`ํ˜„์žฌ ์‹คํ–‰: ${out.runningFrom}`);
12270
+ log(`๋ฒ„์ „: v${out.version}`);
12271
+ log(`Node: ${out.nodeVersion} (${out.platform}/${out.arch})`);
12272
+ log('');
12273
+ log(`## npm ํ™˜๊ฒฝ`);
12274
+ if (out.npm.globalRoot) log(` npm root -g: ${out.npm.globalRoot}`);
12275
+ if (out.npm.cacheDir) log(` npm cache: ${out.npm.cacheDir} (npx ์˜› ๋ฒ„์ „์ด ์—ฌ๊ธฐ ์บ์‹ฑ โ€” ์˜์‹ฌ ์‹œ \`npm cache clean --force\`)`);
12276
+ if (out.npm.globalInstalled) log(` ๊ธ€๋กœ๋ฒŒ ์„ค์น˜: leerness@${out.npm.globalInstalled}`);
12277
+ else log(` ๊ธ€๋กœ๋ฒŒ ์„ค์น˜: (์—†์Œ โ€” npx ๋˜๋Š” ๋กœ์ปฌ ๊ฒฝ๋กœ๋งŒ ์‚ฌ์šฉ ์ค‘)`);
12278
+ if (out.pathCandidates.length) {
12279
+ log('');
12280
+ log(`## PATH ํ›„๋ณด (${out.pathCandidates.length}๊ฐœ)`);
12281
+ for (const p of out.pathCandidates) log(` - ${p}`);
12282
+ }
12283
+ if (out.diagnostics.length) {
12284
+ log('');
12285
+ log(`## โš  ์ง„๋‹จ`);
12286
+ for (const d of out.diagnostics) log(` ${d}`);
12287
+ } else {
12288
+ log('');
12289
+ log(`โœ“ ์ถฉ๋Œ ์—†์Œ (ํ˜„์žฌ ์‹คํ–‰ ๋ฒ„์ „ = ๊ธ€๋กœ๋ฒŒ ์„ค์น˜ ๋ฒ„์ „)`);
12290
+ }
12291
+ log('');
12292
+ log(`๐Ÿ’ก ๊ฐ•์ œ ์ตœ์‹  ์‹คํ–‰ ๋ฐฉ๋ฒ•:`);
12293
+ log(` 1) npx --yes leerness@latest <command> # npx ์บ์‹œ ๋ฌด์‹œํ•˜๊ณ  ์ตœ์‹  ๋‹ค์šด๋กœ๋“œ`);
12294
+ log(` 2) npm i -g leerness@latest # ๊ธ€๋กœ๋ฒŒ ์„ค์น˜ ๊ฐฑ์‹ `);
12295
+ log(` 3) npm cache clean --force # npx ์บ์‹œ ๊ฐ•์ œ ๋น„์šฐ๊ธฐ (์˜์‹ฌ ์‹œ)`);
12296
+ }
12297
+
11887
12298
  function help() {
11888
- log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 ์›Œํฌ์ŠคํŽ˜์ด์Šค (--compact: LLM ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์šฉ 1์ค„ ์š”์•ฝ)\n leerness orchestrate "<๋ชฉํ‘œ>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL ํ•„์š”)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM ๋ฒค์น˜ ํžˆ์Šคํ† ๋ฆฌ ๋ˆ„์ \n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on ์—ญ๋ฐฉํ–ฅ ์ถ”์  + ์ž๋™ ํšŒ๊ท€ sweep\n leerness memory search "ํ‚ค" [--include-code] # 1.9.25 ์†Œ์Šค ์ฝ”๋“œ ๋ณธ๋ฌธ๋„ ๊ฒ€์ƒ‰ (๋ชจ์ˆœ ๊ฐ์ง€ ํ•ต์‹ฌ)\n leerness brainstorm "์ฃผ์ œ" [--include-code] # 1.9.25 ์ฝ”๋“œ ๋ณธ๋ฌธ hits ํฌํ•จ\n leerness register-pending "<์š”์ฒญ>" [--agent X] [--note Y] # 1.9.25 ๋‹ค์ค‘ ์„ธ์…˜ in-progress ์ฆ‰์‹œ ๋“ฑ๋ก\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 ๋‚™๊ด€์  ํ‘œ์‹œ ๊ฐ์ง€ (1.9.27: 10 ์นดํ…Œ๊ณ ๋ฆฌ + URL/๋ฉ”์„œ๋“œ ๋งคํ•‘ + ์‹ ๋ขฐ๋„ ์ ์ˆ˜)\n leerness persona list|show <id>|add <id> # 1.9.29 ํŽ˜๋ฅด์†Œ๋‚˜ ์นดํƒˆ๋กœ๊ทธ (๋ณด์•ˆ/์„ฑ๋Šฅ/UX/testing/docs 5์ข… ๋‚ด์žฅ)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 ๋„๋ฉ”์ธ ํŽ˜๋ฅด์†Œ๋‚˜ ๋ฆฌ๋ทฐ ํ”„๋กฌํ”„ํŠธ ์ž๋™ ์ƒ์„ฑ\n leerness agents list|check|quota # 1.9.30/31 ์™ธ๋ถ€ AI CLI ๊ฐ€์šฉ์„ฑ + quota ์ถ”์ • (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 ํ™œ์„ฑ CLI ๋Œ€์ƒ ์‹คํ–‰ ๋ช…๋ น ์ƒ์„ฑ (์‹ค ํ˜ธ์ถœ X, ์‚ฌ์šฉ์ž ์‹คํ–‰)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 ํ™œ์„ฑ N๊ฐœ ์ผ๊ด„ dispatch (--execute: ์‹ค spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry โ€” ์‚ฌ์šฉ์ž ์ •์˜ CLI provider ๋™์  ์ถ”๊ฐ€ (OpenRouter/Bedrock ํก์ˆ˜)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi ๋ชจ๋“œ alias (๋˜๋Š” --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์„ค์ • (.env + ๋ฏธ์„ค์น˜ ์ž๋™ ์„ค์น˜)\n leerness init [path] [--no-stale-check] # 1.9.33 npx ์บ์‹œ ํ•จ์ • โ€” ์˜› ๋ฒ„์ „ ์ž๋™ ๊ฒฝ๊ณ  (๋„๋ ค๋ฉด --no-stale-check)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 ๋ช…์„ธ โ†” ๊ตฌํ˜„ ์ผ์น˜ ๊ฒ€์‚ฌ (ํ•จ์ˆ˜/ํ•„๋“œ)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js์˜ module.exports โ†’ reuse-map ํ›„๋ณด ๋“ฑ๋ก\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state ์ž๋™ ๊ฐฑ์‹ \n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim์— ๋‚™๊ด€์  ํ‘œ์‹œ ์ž๋™ ๊ฒ€์‚ฌ ํ†ตํ•ฉ\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 ์ค‘๋ณต/์ž ์žฌ์ค‘๋ณต/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence ์ž๋™ ๊ฒ€์ฆ (1.9.20: scenes/scripts ๋“ฑ ๋„๋ฉ”์ธ ํด๋” + jest/mocha ํŒŒ์‹ฑ)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench ์ถ”๊ฐ€ ์‹คํ–‰ + evidence ๋ˆ„์ \n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
12299
+ log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 ์›Œํฌ์ŠคํŽ˜์ด์Šค (--compact: LLM ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์šฉ 1์ค„ ์š”์•ฝ)\n leerness orchestrate "<๋ชฉํ‘œ>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL ํ•„์š”)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM ๋ฒค์น˜ ํžˆ์Šคํ† ๋ฆฌ ๋ˆ„์ \n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on ์—ญ๋ฐฉํ–ฅ ์ถ”์  + ์ž๋™ ํšŒ๊ท€ sweep\n leerness memory search "ํ‚ค" [--include-code] # 1.9.25 ์†Œ์Šค ์ฝ”๋“œ ๋ณธ๋ฌธ๋„ ๊ฒ€์ƒ‰ (๋ชจ์ˆœ ๊ฐ์ง€ ํ•ต์‹ฌ)\n leerness brainstorm "์ฃผ์ œ" [--include-code] # 1.9.25 ์ฝ”๋“œ ๋ณธ๋ฌธ hits ํฌํ•จ\n leerness register-pending "<์š”์ฒญ>" [--agent X] [--note Y] # 1.9.25 ๋‹ค์ค‘ ์„ธ์…˜ in-progress ์ฆ‰์‹œ ๋“ฑ๋ก\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 ๋‚™๊ด€์  ํ‘œ์‹œ ๊ฐ์ง€ (1.9.27: 10 ์นดํ…Œ๊ณ ๋ฆฌ + URL/๋ฉ”์„œ๋“œ ๋งคํ•‘ + ์‹ ๋ขฐ๋„ ์ ์ˆ˜)\n leerness persona list|show <id>|add <id> # 1.9.29 ํŽ˜๋ฅด์†Œ๋‚˜ ์นดํƒˆ๋กœ๊ทธ (๋ณด์•ˆ/์„ฑ๋Šฅ/UX/testing/docs 5์ข… ๋‚ด์žฅ)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 ๋„๋ฉ”์ธ ํŽ˜๋ฅด์†Œ๋‚˜ ๋ฆฌ๋ทฐ ํ”„๋กฌํ”„ํŠธ ์ž๋™ ์ƒ์„ฑ\n leerness agents list|check|quota # 1.9.30/31 ์™ธ๋ถ€ AI CLI ๊ฐ€์šฉ์„ฑ + quota ์ถ”์ • (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 ํ™œ์„ฑ CLI ๋Œ€์ƒ ์‹คํ–‰ ๋ช…๋ น ์ƒ์„ฑ (์‹ค ํ˜ธ์ถœ X, ์‚ฌ์šฉ์ž ์‹คํ–‰)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 ํ™œ์„ฑ N๊ฐœ ์ผ๊ด„ dispatch (--execute: ์‹ค spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry โ€” ์‚ฌ์šฉ์ž ์ •์˜ CLI provider ๋™์  ์ถ”๊ฐ€ (OpenRouter/Bedrock ํก์ˆ˜)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi ๋ชจ๋“œ alias (๋˜๋Š” --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์„ค์ • (.env + ๋ฏธ์„ค์น˜ ์ž๋™ ์„ค์น˜)\n leerness init [path] [--no-stale-check] # 1.9.33 npx ์บ์‹œ ํ•จ์ • โ€” ์˜› ๋ฒ„์ „ ์ž๋™ ๊ฒฝ๊ณ  (๋„๋ ค๋ฉด --no-stale-check)\n leerness which [--json] # 1.9.164 ์ง„๋‹จ: ํ˜„์žฌ ์‹คํ–‰ ๊ฒฝ๋กœ/๋ฒ„์ „ + npm ์บ์‹œ + PATH ํ›„๋ณด (๊ตฌ๋ฒ„์ „ ์ถฉ๋Œ ํ•ด๊ฒฐ)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, โš  full ๋ชจ๋“œ ๊ถŒ์žฅ)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 ๋ช…์„ธ โ†” ๊ตฌํ˜„ ์ผ์น˜ ๊ฒ€์‚ฌ (ํ•จ์ˆ˜/ํ•„๋“œ)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js์˜ module.exports โ†’ reuse-map ํ›„๋ณด ๋“ฑ๋ก\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state ์ž๋™ ๊ฐฑ์‹ \n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim์— ๋‚™๊ด€์  ํ‘œ์‹œ ์ž๋™ ๊ฒ€์‚ฌ ํ†ตํ•ฉ\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 ์ค‘๋ณต/์ž ์žฌ์ค‘๋ณต/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence ์ž๋™ ๊ฒ€์ฆ (1.9.20: scenes/scripts ๋“ฑ ๋„๋ฉ”์ธ ํด๋” + jest/mocha ํŒŒ์‹ฑ)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench ์ถ”๊ฐ€ ์‹คํ–‰ + evidence ๋ˆ„์ \n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
11889
12300
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # ํšŒ๊ณ  (1.9.13~1.9.16)
11890
12301
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # ๋ˆ„์  ํ†ต๊ณ„ (1.9.13~1.9.16)
11891
12302
  leerness brainstorm "<์ฃผ์ œ>" [--all-apps] [--include p1,p2] [--json] # ๋ธŒ๋ ˆ์ธ์Šคํ† ๋ฐ (1.9.13~1.9.16)
@@ -11959,6 +12370,12 @@ async function main() {
11959
12370
  if (cmd === 'agents') return agentsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
11960
12371
  // 1.9.157: Provider Registry โ€” ์‚ฌ์šฉ์ž ์ •์˜ provider ๋™์  ์ถ”๊ฐ€
11961
12372
  if (cmd === 'provider') return providerCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
12373
+ // 1.9.164: leerness which โ€” ์ง„๋‹จ ๋„๊ตฌ (๊ตฌ๋ฒ„์ „ ์ถฉ๋Œ / npx ์บ์‹œ / PATH ํ›„๋ณด)
12374
+ if (cmd === 'which') return whichCmd();
12375
+ // 1.9.165: leerness web โ€” playwright bridge (opt-in ์˜์กด์„ฑ)
12376
+ if (cmd === 'web') return webCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
12377
+ // 1.9.166: leerness pc โ€” robotjs/nut-tree bridge (opt-in ์˜์กด์„ฑ)
12378
+ if (cmd === 'pc') return pcCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
11962
12379
  if (cmd === 'contract' && args[1] === 'verify') return contractVerifyCmd(args[2], args[3]);
11963
12380
  if (cmd === 'drift' && (args[1] === 'check' || !args[1])) return driftCheckCmd(args[2] || arg('--path', process.cwd()));
11964
12381
  if (cmd === 'usage' && (args[1] === 'stats' || !args[1])) return usageStatsCmd(args[2] || arg('--path', process.cwd()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.163",
3
+ "version": "1.9.166",
4
4
  "description": "Leerness: ๋น„ํŒŒ๊ดด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ž๋™ ๋ฒ„์ „ ๊ฐ์ง€ยท์—…๋ฐ์ดํŠธ, ๊ณ„ํš/์ง„ํ–‰/ํ•ธ๋“œ์˜คํ”„ ์ž๋™ํ™”, ๊ฒŒ์œผ๋ฆ„ยท์‹œํฌ๋ฆฟยท์ธ์ฝ”๋”ฉ ์ž๋™ ๊ฐ€๋“œ, Claude Code ์Šฌ๋ž˜์‹œ ํ†ตํ•ฉ์„ ๊ฐ–์ถ˜ ํ•œ๊ตญ์–ด ์šฐ์„  AI ๊ฐœ๋ฐœ ํ•˜๋„ค์Šค.",
5
5
  "keywords": [
6
6
  "leerness",