claude-ai-office 0.29.1 โ†’ 0.30.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.
package/README.en.md CHANGED
@@ -159,7 +159,7 @@ With two or more floors, ๐Ÿข **Building view** (`O`) appears in the header. Flo
159
159
 
160
160
  A resident app that shows the web UI in a window. It starts the Node server itself (or reuses one that is already running) and lives in the menu bar / system tray; left- or right-click the icon for the menu: Open, Hide, Always on top, Launch at login, Reinstall hooks, Check for updates, Apply update and Quit. When a session is waiting for you or an update is ready, a **red dot** appears on the menu bar icon and on the Dock (Windows: taskbar) icon, like a chat app; there is no count, and the tooltip says what it is. **Launch at login** is turned on at first run; when started at login the app stays in the menu bar without a window. You can turn it off from the menu. Closing the window hides it; Quit stops the server too. Node is bundled: on first launch the app copies the server files to `~/.ai-office/app` and the Node binary to `~/.ai-office/node`, then registers the hooks, so moving or deleting the app does not break them.
161
161
 
162
- **Updates**: the app checks for a new version 10 seconds after launch and every 6 hours and only **downloads** it in the background (signature verified; a failed check is retried after 5 minutes), then shows the red dot and enables "Apply vX update (restart)". Applying stops the server, installs and restarts; nothing is installed without asking and the running app is never replaced underneath you. "Check for updates" in the menu shows whether you are up to date or why a check failed. Releases are built by GitHub Actions for macOS (Apple Silicon) and Windows (x64) and signed for the updater.
162
+ **Updates**: the app checks for a new version 10 seconds after launch and every 6 hours and only **downloads** it in the background (signature verified; a failed check is retried after 5 minutes), then shows the red dot and enables "Apply vX update (restart)". Applying stops the server, installs and restarts; nothing is installed without asking and the running app is never replaced underneath you. Clicking **"Check for updates"** opens a small update window: it checks, and if there is a new version shows a `Downloadingโ€ฆ 12.3MB / 42.8MB` progress bar (cancelable), then installing and restart; otherwise it says you are up to date or why the check failed. Applying a downloaded update shows "Installingโ€ฆ" in the same window. Releases are built by GitHub Actions for macOS (Apple Silicon) and Windows (x64) and signed for the updater.
163
163
 
164
164
  > **Download**: [macOS AI-Office-mac-arm64.dmg](https://github.com/cozyuj/AI-office/releases/latest/download/AI-Office-mac-arm64.dmg) ยท [Windows AI-Office-windows-x64-setup.exe](https://github.com/cozyuj/AI-office/releases/latest/download/AI-Office-windows-x64-setup.exe) (always the latest; older versions in [Releases](https://github.com/cozyuj/AI-office/releases)). The macOS app is not notarized: **right-click โ†’ Open** the first time; on macOS 15+ use System Settings โ†’ Privacy & Security โ†’ **Open Anyway**, and if it still says "damaged", run `xattr -dr com.apple.quarantine "/Applications/AI Office.app"` once. To build it yourself:
165
165
 
package/README.md CHANGED
@@ -161,7 +161,7 @@ node bin/ai-office.js # ๋˜๋Š” npm run install-hooks && npm start
161
161
 
162
162
  ์›น ๋ฒ„์ „์„ ๊ทธ๋Œ€๋กœ ์ฐฝ์— ๋‹ด์€ ์ƒ์ฃผ ์•ฑ์ž…๋‹ˆ๋‹ค(๋งฅ ๋ฉ”๋‰ด๋ฐ” ยท Windows ํŠธ๋ ˆ์ด). ์•ฑ์ด Node ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ๋„์šฐ๊ณ (์ด๋ฏธ ๋–  ์žˆ์œผ๋ฉด ์žฌ์‚ฌ์šฉ) ์•„์ด์ฝ˜์„ ์™ผ์ชฝ์ด๋‚˜ ์˜ค๋ฅธ์ชฝ ํด๋ฆญํ•˜๋ฉด ๋ฉ”๋‰ด๊ฐ€ ๋œน๋‹ˆ๋‹ค. ๋ฉ”๋‰ด๋Š” ์—ด๊ธฐ ยท ์ˆจ๊ธฐ๊ธฐ ยท ํ•ญ์ƒ ์œ„ ยท ๋กœ๊ทธ์ธ ์‹œ ์ž๋™ ์‹คํ–‰ ยท ํ›… ๋‹ค์‹œ ์„ค์น˜ ยท ์—…๋ฐ์ดํŠธ ํ™•์ธ ยท ์—…๋ฐ์ดํŠธ ์ ์šฉ ยท ์ข…๋ฃŒ์ž…๋‹ˆ๋‹ค. ๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์„ธ์…˜์ด ์žˆ๊ฑฐ๋‚˜ ๋ฐ›์•„ ๋‘” ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ์นด์นด์˜คํ†ก์ฒ˜๋Ÿผ ๋ฉ”๋‰ด๋ฐ” ์•„์ด์ฝ˜๊ณผ Dock(Windows ๋Š” ์ž‘์—… ํ‘œ์‹œ์ค„) ์•„์ด์ฝ˜์— **๋นจ๊ฐ„ ์ **์ด ๋œน๋‹ˆ๋‹ค. ์ˆซ์ž๋Š” ์—†๊ณ , ๋ฌด์—‡์ธ์ง€๋Š” ์•„์ด์ฝ˜์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ฆฌ๋ฉด ํˆดํŒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. **๋กœ๊ทธ์ธ ์‹œ ์ž๋™ ์‹คํ–‰**์€ ์ฒ˜์Œ ์‹คํ–‰ ๋•Œ ์ผœ์ง€๋ฉฐ, ์ž๋™์œผ๋กœ ์ผœ์งˆ ๋• ์ฐฝ ์—†์ด ๋ฉ”๋‰ด๋ฐ”์—๋งŒ ๋œน๋‹ˆ๋‹ค. ๋ฉ”๋‰ด์—์„œ ๋Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐฝ์„ ๋‹ซ์•„๋„ ๋ฉ”๋‰ด๋ฐ”์— ๋‚จ๊ณ , ์ข…๋ฃŒํ•˜๋ฉด ์„œ๋ฒ„๋„ ํ•จ๊ป˜ ๋‚ด๋ ค๊ฐ‘๋‹ˆ๋‹ค(์•ฑ์ด ๊ฐ•์ œ ์ข…๋ฃŒ๋ผ๋„ ์„œ๋ฒ„๊ฐ€ ์Šค์Šค๋กœ ๋๋‚ฉ๋‹ˆ๋‹ค). ์•ฑ์—๋Š” Node ๊ฐ€ ๋‚ด์žฅ๋˜์–ด ์žˆ๊ณ , ์ฒซ ์‹คํ–‰ ๋•Œ ์„œ๋ฒ„ ํŒŒ์ผ์„ `~/.ai-office/app` ์—, Node ๋ฅผ `~/.ai-office/node` ์— ๋ณต์‚ฌํ•˜๊ณ  ๊ทธ ๊ฒฝ๋กœ๋กœ ํ›…์„ ๋“ฑ๋กํ•˜๋ฏ€๋กœ ์•ฑ์„ ์˜ฎ๊ธฐ๊ฑฐ๋‚˜ ์ง€์›Œ๋„ ํ›…์ด ๊นจ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
163
163
 
164
- **์ž๋™ ์—…๋ฐ์ดํŠธ**: ์•ฑ์ด ์‹œ์ž‘ 10์ดˆ ๋’ค์™€ 6์‹œ๊ฐ„๋งˆ๋‹ค ์ƒˆ ๋ฒ„์ „์„ ํ™•์ธํ•ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ **๋ฐ›์•„ ๋‘๊ธฐ๋งŒ** ํ•˜๊ณ (์„œ๋ช… ๊ฒ€์ฆ๊นŒ์ง€, ํ™•์ธ์— ์‹คํŒจํ•˜๋ฉด 5๋ถ„ ๋’ค ๋‹ค์‹œ), ๋นจ๊ฐ„ ์ ์„ ๋„์šฐ๋ฉฐ ๋ฉ”๋‰ด์˜ "vX ์—…๋ฐ์ดํŠธ ์ ์šฉ (๋‹ค์‹œ ์‹œ์ž‘)" ์ด ์ผœ์ง‘๋‹ˆ๋‹ค. ๋ˆ„๋ฅด๋ฉด ์„œ๋ฒ„๋ฅผ ๋‚ด๋ฆฌ๊ณ  ์„ค์น˜ํ•œ ๋’ค ๋‹ค์‹œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋ฌป์ง€ ์•Š๊ณ  ์„ค์น˜ํ•˜๊ฑฐ๋‚˜ ์“ฐ๋˜ ์ค‘์— ์•ฑ ํŒŒ์ผ์„ ๋ฐ”๊พธ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฉ”๋‰ด์˜ "์—…๋ฐ์ดํŠธ ํ™•์ธ" ์— ์ตœ์‹  ์—ฌ๋ถ€๋‚˜ ์‹คํŒจ ์ด์œ ๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค. ๋ฆด๋ฆฌ์Šค๋Š” ํƒœ๊ทธ๋ฅผ ์˜ฌ๋ฆฌ๋ฉด GitHub Actions ๊ฐ€ ๋งฅ(Apple Silicon)ยทWindows(x64) ๋กœ ๋นŒ๋“œํ•˜๊ณ  ์„œ๋ช…ํ•ด ์˜ฌ๋ฆฝ๋‹ˆ๋‹ค.
164
+ **์ž๋™ ์—…๋ฐ์ดํŠธ**: ์•ฑ์ด ์‹œ์ž‘ 10์ดˆ ๋’ค์™€ 6์‹œ๊ฐ„๋งˆ๋‹ค ์ƒˆ ๋ฒ„์ „์„ ํ™•์ธํ•ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ **๋ฐ›์•„ ๋‘๊ธฐ๋งŒ** ํ•˜๊ณ (์„œ๋ช… ๊ฒ€์ฆ๊นŒ์ง€, ํ™•์ธ์— ์‹คํŒจํ•˜๋ฉด 5๋ถ„ ๋’ค ๋‹ค์‹œ), ๋นจ๊ฐ„ ์ ์„ ๋„์šฐ๋ฉฐ ๋ฉ”๋‰ด์˜ "vX ์—…๋ฐ์ดํŠธ ์ ์šฉ (๋‹ค์‹œ ์‹œ์ž‘)" ์ด ์ผœ์ง‘๋‹ˆ๋‹ค. ๋ˆ„๋ฅด๋ฉด ์„œ๋ฒ„๋ฅผ ๋‚ด๋ฆฌ๊ณ  ์„ค์น˜ํ•œ ๋’ค ๋‹ค์‹œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋ฌป์ง€ ์•Š๊ณ  ์„ค์น˜ํ•˜๊ฑฐ๋‚˜ ์“ฐ๋˜ ์ค‘์— ์•ฑ ํŒŒ์ผ์„ ๋ฐ”๊พธ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฉ”๋‰ด์˜ **"์—…๋ฐ์ดํŠธ ํ™•์ธ"** ์„ ๋ˆ„๋ฅด๋ฉด ์ž‘์€ ์—…๋ฐ์ดํŠธ ์ฐฝ์ด ๋– ์„œ ํ™•์ธ โ†’ ์ƒˆ ๋ฒ„์ „์ด๋ฉด `๋‹ค์šด๋กœ๋“œ ์ค‘โ€ฆ 12.3MB / 42.8MB` ์ง„ํ–‰ ๋ง‰๋Œ€(์ทจ์†Œ ๊ฐ€๋Šฅ) โ†’ ์„ค์น˜ ์ค‘ โ†’ ๋‹ค์‹œ ์‹œ์ž‘๊นŒ์ง€ ๋ณด์—ฌ ์ฃผ๊ณ , ์ตœ์‹ ์ด๊ฑฐ๋‚˜ ์‹คํŒจํ•˜๋ฉด ๊ทธ ์ด์œ ๋ฅผ ๋ณด์—ฌ ์ค๋‹ˆ๋‹ค. ๋ฐ›์•„ ๋‘” ์—…๋ฐ์ดํŠธ๋ฅผ ์ ์šฉํ•  ๋•Œ๋„ ๊ฐ™์€ ์ฐฝ์— "์„ค์น˜ ์ค‘" ์ด ๋œน๋‹ˆ๋‹ค. ๋ฆด๋ฆฌ์Šค๋Š” ํƒœ๊ทธ๋ฅผ ์˜ฌ๋ฆฌ๋ฉด GitHub Actions ๊ฐ€ ๋งฅ(Apple Silicon)ยทWindows(x64) ๋กœ ๋นŒ๋“œํ•˜๊ณ  ์„œ๋ช…ํ•ด ์˜ฌ๋ฆฝ๋‹ˆ๋‹ค.
165
165
 
166
166
  > **๋‹ค์šด๋กœ๋“œ**: โฌ‡๏ธ **[๋งฅ AI-Office-mac-arm64.dmg](https://github.com/cozyuj/AI-office/releases/latest/download/AI-Office-mac-arm64.dmg)** ยท **[Windows AI-Office-windows-x64-setup.exe](https://github.com/cozyuj/AI-office/releases/latest/download/AI-Office-windows-x64-setup.exe)** (ํ•ญ์ƒ ์ตœ์‹ ) ยท ์ด์ „ ๋ฒ„์ „์€ [Releases](https://github.com/cozyuj/AI-office/releases). DMG ๋ฅผ ์—ด์–ด `AI Office.app` ์„ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ํด๋”์— ๋„ฃ์œผ์„ธ์š”. ์ฝ”๋“œ ์„œ๋ช…์ด ์—†์–ด ์ฒ˜์Œ์—” "ํ™•์ธ๋˜์ง€ ์•Š์€ ๊ฐœ๋ฐœ์ž" ๊ฒฝ๊ณ ๊ฐ€ ๋œจ๋‹ˆ ์•ฑ์„ **์šฐํด๋ฆญ โ†’ ์—ด๊ธฐ** ๋กœ ํ•œ ๋ฒˆ ํ—ˆ์šฉํ•˜๊ณ , macOS 15 ์ด์ƒ์—์„œ ์•ˆ ์—ด๋ฆฌ๋ฉด ์‹œ์Šคํ…œ ์„ค์ • โ†’ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ๋ฐ ๋ณด์•ˆ โ†’ **๊ทธ๋ž˜๋„ ์—ด๊ธฐ**, ๊ทธ๋ž˜๋„ "์†์ƒ๋จ" ์ด๋ผ๊ณ  ๋‚˜์˜ค๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ `xattr -dr com.apple.quarantine "/Applications/AI Office.app"` ๋ฅผ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜์„ธ์š”. Intel ๋งฅยทLinux ๋Š” ์œ„์˜ `npx claude-ai-office` ํ•œ ์ค„๋กœ ์›น ๋ฒ„์ „์„ ์“ฐ์„ธ์š”. ์ง์ ‘ ๋นŒ๋“œํ•˜๋ ค๋ฉด:
167
167
 
@@ -1,72 +1,88 @@
1
- # AI Office ๊ตฌ์กฐ ์„ค๊ณ„ (์›น ๋ฒ„์ „)
1
+ # AI Office ๊ตฌ์กฐ ์„ค๊ณ„
2
2
 
3
- Claude Code ์„ธ์…˜์ด ์ง€๊ธˆ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ ์‚ฌ๋ฌด์‹ค ํ™”๋ฉด์œผ๋กœ ์‹ค์‹œ๊ฐ„ ๋ณด์—ฌ์ฃผ๋Š” ๋กœ์ปฌ ์›น ์•ฑ.
3
+ Claude Code ์„ธ์…˜์ด ์ง€๊ธˆ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ ์‚ฌ๋ฌด์‹ค ํ™”๋ฉด์œผ๋กœ ์‹ค์‹œ๊ฐ„ ๋ณด์—ฌ์ฃผ๋Š” ๋กœ์ปฌ ์•ฑ (์›น + macOS/Windows ๋ฐ์Šคํฌํ†ฑ).
4
4
  ํŒŒ์ผ์„ ์ฝ์œผ๋ฉด ์ž๋ฃŒ์‹ค, ๋ช…๋ น์„ ๋Œ๋ฆฌ๋ฉด ์„œ๋ฒ„์‹ค, ๋ง‰ํžˆ๋ฉด ๊ทธ ์ž๋ฆฌ์—์„œ ๊ณ ๋ฏผ, ์„œ๋ธŒ์—์ด์ „ํŠธ๋Š” ์—˜๋ฆฌ๋ฒ ์ดํ„ฐ๋กœ ์ถœ๊ทผ.
5
5
 
6
+ > ์ด ๋ฌธ์„œ๋Š” v0.29 ๊ธฐ์ค€์ด๋‹ค. ๊ธฐ๋Šฅ ์†Œ๊ฐœยท์Šคํฌ๋ฆฐ์ƒท์€ `docs/AI-Office-์†Œ๊ฐœ.html`.
7
+
6
8
  ## 1. ์ „์ฒด ํ๋ฆ„
7
9
 
8
10
  ```
9
- Claude Code ์„ธ์…˜ (์—ฌ๋Ÿฌ ํ„ฐ๋ฏธ๋„, ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ)
10
- โ”‚ settings.json hooks (PreToolUse, PostToolUse, SubagentStart, Stop, ...)
11
+ Claude Code ์„ธ์…˜ (์—ฌ๋Ÿฌ ํ„ฐ๋ฏธ๋„, ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ, ์›ŒํฌํŠธ๋ฆฌ)
12
+ โ”‚ ~/.claude/settings.json hooks (11๊ฐœ ์ด๋ฒคํŠธ)
11
13
  โ–ผ
12
- hooks/emit.sh stdin JSON โ†’ curl POST (1์ดˆ ํƒ€์ž„์•„์›ƒ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ, ํ•ญ์ƒ exit 0)
14
+ hooks/emit.js stdin JSON โ†’ POST 127.0.0.1:<port>/hook (Node ๋งŒ ์‚ฌ์šฉ, 1์ดˆ ํƒ€์ž„์•„์›ƒ, ํ•ญ์ƒ exit 0)
13
15
  โ”‚
14
16
  โ–ผ POST /hook
15
- server.js (Node 18+, ์™ธ๋ถ€ ์˜์กด์„ฑ 0)
16
- โ”œโ”€ normalize ํ›… payload โ†’ ๋‚ด๋ถ€ Event (๋„๊ตฌ๋ช…/๋Œ€์ƒ ํŒŒ์ผ/์„œ๋ธŒ์—์ด์ „ํŠธ id ์ถ”์ถœ, ๋Œ€์šฉ๋Ÿ‰ ํ•„๋“œ ์ œ๊ฑฐ)
17
- โ”œโ”€ reduce Event โ†’ Floor/Session/Agent ์ƒํƒœ ๊ฐฑ์‹  (์ƒํƒœ ๋จธ์‹ )
18
- โ”œโ”€ log ์ตœ๊ทผ 500๊ฑด ring buffer
19
- โ””โ”€ broadcast SSE /events ๋กœ {snapshot | update} ์ „์†ก
17
+ server.js (Node 18+, ์™ธ๋ถ€ ์˜์กด์„ฑ 0, 127.0.0.1 ์—๋งŒ listen)
18
+ โ”œโ”€ ์ ‘๊ทผ ๊ฒ€์‚ฌ Host ๋ฃจํ”„๋ฐฑ ยท Origin ํ—ˆ์šฉ ๋ชฉ๋ก (9์ ˆ)
19
+ โ”œโ”€ journal payload ๋ฅผ ~/.ai-office/events.jsonl ์— append โ†’ ๊ธฐ๋™ ์‹œ ์ตœ๊ทผ 2์‹œ๊ฐ„ ์žฌ์ƒ
20
+ โ”œโ”€ normalize payload โ†’ ์ž‘์€ Event (๋„๊ตฌโ†’๋ฐฉยท๋งํ’์„ , ํฐ ํ•„๋“œ ์ž๋ฅด๊ธฐ) lib/normalize.js
21
+ โ”œโ”€ state Event โ†’ ์ธต/์„ธ์…˜/์บ๋ฆญํ„ฐ ์ƒํƒœ ๋จธ์‹ , ํ™œ๋™ ๋กœ๊ทธ 500๊ฑด, sweep lib/state.js
22
+ โ””โ”€ broadcast SSE /events ๋กœ snapshot ยท update ยท remove ยท floor ยท demo
20
23
  โ”‚
21
24
  โ–ผ GET /events (SSE), GET /api/state
22
25
  public/ (๋ธŒ๋ผ์šฐ์ €, ๋ฒˆ๋“ค๋Ÿฌ ์—†์Œ, ES module)
23
- โ”œโ”€ store ์„œ๋ฒ„ ์ƒํƒœ ๋ฏธ๋Ÿฌ + ๊ตฌ๋…
24
- โ”œโ”€ scene canvas ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ ์‚ฌ๋ฌด์‹ค: ๋งต, ๊ฐ€๊ตฌ, ์บ๋ฆญํ„ฐ ์ด๋™(A*), ๋งํ’์„ , ์นด๋ฉ”๋ผ
25
- โ””โ”€ panels ์ขŒ: ์ธต(ํ”„๋กœ์ ํŠธ)ยท์„ธ์…˜ ๋ชฉ๋ก / ์šฐ: ์‚ฌ๋ฌด์‹ค ์‚ฌ๋žŒ๋“คยทํ™œ๋™ ๋กœ๊ทธยท๋ฒ”๋ก€
26
+ โ”œโ”€ store ์„œ๋ฒ„ ์ƒํƒœ ๋ฏธ๋Ÿฌ + ์„ ํƒยท์ ‘๊ธฐ ์ƒํƒœ + ๊ตฌ๋…
27
+ โ”œโ”€ scene canvas ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ ์‚ฌ๋ฌด์‹ค: ์ธต ๋ ˆ์ด์•„์›ƒ, ๊ฐ€๊ตฌ, ์บ๋ฆญํ„ฐ ์ด๋™(A*), ๋งํ’์„ , ์นด๋ฉ”๋ผ, ์‹œ๊ฐ„๋Œ€ ์กฐ๋ช…
28
+ โ”œโ”€ panels ์ขŒ: ์ธต๋ณ„ ์•ˆ๋‚ด(์ธตยท์„ธ์…˜ ๋ชฉ๋ก) / ์šฐ: ์‚ฌ๋ฌด์‹ค ์‚ฌ๋žŒ๋“คยทํ™œ๋™ ๋กœ๊ทธยท๋ฒ”๋ก€ / ์ „์ฒด ๋ณด๊ธฐ(๊ฑด๋ฌผ ๋‹จ๋ฉด)
29
+ โ””โ”€ i18n ํ•œ๊ตญ์–ด ์›๋ฌธ + ์˜์–ด ๋ฒˆ์—ญ (DOM ์ž๋™, ์บ”๋ฒ„์Šค๋Š” tr())
26
30
  ```
27
31
 
28
32
  - ํ›…์€ Claude ์‹คํ–‰์„ ์ ˆ๋Œ€ ๋ง‰์ง€ ์•Š๋Š”๋‹ค. ์„œ๋ฒ„๊ฐ€ ๊บผ์ ธ ์žˆ์–ด๋„ 1์ดˆ ์•ˆ์— ์กฐ์šฉํžˆ ํฌ๊ธฐํ•œ๋‹ค.
29
33
  - ์„œ๋ฒ„๋Š” "์ง€๊ธˆ ์ƒํƒœ"์˜ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›์ด๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋Š” ์ƒํƒœ๋ฅผ ๋ฐ›์•„ ์• ๋‹ˆ๋ฉ”์ด์…˜๋งŒ ๋‹ด๋‹นํ•œ๋‹ค.
30
- - ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋ช‡ ๊ฐœ ์—ด์–ด๋„, ์ƒˆ๋กœ ์—ด์–ด๋„ ๊ฐ™์€ ํ™”๋ฉด์ด๋‹ค (์ ‘์† ์‹œ snapshot ์ˆ˜์‹ ).
34
+ - ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋ช‡ ๊ฐœ ์—ด์–ด๋„, ์ƒˆ๋กœ ์—ด์–ด๋„ ๊ฐ™์€ ํ™”๋ฉด์ด๋‹ค (์ ‘์† ์‹œ snapshot ์ˆ˜์‹ ). ์„œ๋ฒ„๋ฅผ ๋‹ค์‹œ ์ผœ๋„ ์ €๋„ ์žฌ์ƒ์œผ๋กœ ๊ฐ™์€ ํ™”๋ฉด์ด ๋Œ์•„์˜จ๋‹ค.
35
+ - ๋ฐ์Šคํฌํ†ฑ ์•ฑ์€ ๊ฐ™์€ ์„œ๋ฒ„๋ฅผ ์‚ฌ์ด๋“œ์นด Node ๋กœ ๋„์šฐ๊ณ  ์ฐฝ์— ๊ฐ™์€ ํ™”๋ฉด์„ ์—ฐ๋‹ค (10์ ˆ).
31
36
 
32
37
  ## 2. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ
33
38
 
34
39
  ```ts
35
- Floor { // ํ”„๋กœ์ ํŠธ = ์ธต. key = cwd ์ ˆ๋Œ€๊ฒฝ๋กœ
36
- id: string // cwd
37
- name: string // basename(cwd) ์˜ˆ: "nccx"
38
- no: number // ์ถœํ˜„ ์ˆœ์„œ๋กœ 1F, 2F, ... ๋ถ€์—ฌ
39
- sessions: Map<sessionId, Session>
40
- lastActivity: number // epoch ms
40
+ Floor { // ํ”„๋กœ์ ํŠธ = ์ธต. id = git ์ €์žฅ์†Œ ๋ฃจํŠธ (lib/project.js ๊ฐ€ .git ์„ ์œ„๋กœ ์ฐพ๋Š”๋‹ค. ์—†์œผ๋ฉด cwd)
41
+ id: string // ์›ŒํฌํŠธ๋ฆฌ๋„ ์›๋ž˜ ์ €์žฅ์†Œ์˜ ์ธต์œผ๋กœ ๋ชจ์ธ๋‹ค
42
+ name: string // basename(id). ์ด๋ฆ„์ด ๊ฒน์น˜๋ฉด ์ƒ์œ„ ํด๋”๋ฅผ ๋ถ™์—ฌ ๊ตฌ๋ถ„
43
+ no: number // ์ถœํ˜„ ์ˆœ์„œ๋กœ 1F, 2F, ...
44
+ style: string // ์ธํ…Œ๋ฆฌ์–ด ํ”„๋ฆฌ์…‹ id (์ง์ ‘ ์„ ํƒ > ์ธต id ํ•ด์‹œ). ~/.ai-office/floor-styles.json
45
+ sessions: Set<sessionId>
46
+ lastActivity: number
41
47
  }
42
48
 
43
49
  Session {
44
50
  id: string // hook.session_id
45
- floorId: string
46
- title: string // ์ฒซ UserPromptSubmit ์˜ prompt ์•ž 40์ž (session_title ์žˆ์œผ๋ฉด ๊ทธ๊ฒƒ)
51
+ floorId: string // ์ฒ˜์Œ ์ •ํ•ด์ง„ ์ธต ์œ ์ง€ (cwd ๊ฐ€ ํ•˜์œ„ ํด๋”๋กœ ๋ฐ”๋€Œ์–ด๋„)
52
+ title: string // ํ›… session_title > transcript ์˜ ๋งˆ์ง€๋ง‰ custom-title > ์ฒซ ์š”์ฒญ ์•ž๋ถ€๋ถ„
53
+ firstPrompt, lastPrompt // ์ฒซ ์š”์ฒญ(transcript ์•ž 1MB ์—์„œ ๋ณต์›), ์ตœ๊ทผ ์š”์ฒญ
47
54
  status: 'thinking' | 'working' | 'waiting' | 'idle' | 'ended'
55
+ cwd, branch, worktree // ํ—ค๋”ยทํˆดํŒ ํ‘œ์‹œ์šฉ
56
+ transcriptPath // ์ œ๋ชฉ ๊ฐฑ์‹ ยท์ƒ์กด ํ™•์ธ(stat)์šฉ
48
57
  startedAt, lastActivity: number
49
- agents: Map<agentId, Agent> // 'main' + ์„œ๋ธŒ์—์ด์ „ํŠธ๋“ค
50
- pendingSubagents: string[] // Agent ํˆด PreToolUse ์˜ description ํ (SubagentStart ์™€ ์ง์ง€์Œ)
58
+ agents: { [agentId]: Agent } // 'main' + ์„œ๋ธŒ์—์ด์ „ํŠธ๋“ค
59
+ pendingSubagents: string[] // Agent ํˆด PreToolUse ์˜ description ํ (SubagentStart ์™€ ์ง์ง€์Œ)
51
60
  }
52
61
 
53
62
  Agent { // ํ™”๋ฉด์˜ ์บ๋ฆญํ„ฐ ํ•œ ๋ช…
54
63
  id: 'main' | agent_id
55
- sessionId: string
56
64
  kind: 'main' | 'sub'
57
- name: string // "Claude" | agent_type ("Explore") | Agent ํˆด description
65
+ name: string // "Claude" | Agent ํˆด description | agent_type. ํ™”๋ฉด ์ด๋ฆ„์€ ํด๋ผ์ด์–ธํŠธ names.js ๊ฐ€ ๋ณ„๋ช…์œผ๋กœ ๋ฐ”๊พผ๋‹ค
58
66
  room: RoomId // ์ง€๊ธˆ ์žˆ์–ด์•ผ ํ•  ๋ฐฉ
59
- activity: ActivityId // 'think' | 'read' | 'search' | 'run' | 'edit' | 'web' | 'delegate' | 'ask' | 'wait' | 'rest' | 'compact' | 'leave'
60
- bubble: string // ๋งํ’์„  ํ…์ŠคํŠธ ์˜ˆ: "server.js ์ฝ๋Š” ์ค‘"
61
- tool?: { name, useId, target }
67
+ activity: ActivityId // 'arrive' | 'think' | 'read' | 'search' | 'run' | 'edit' | 'web' | 'delegate' | 'plan' | 'ask' | 'rest' | 'compact' | 'work' | 'leave'
68
+ icon, bubble: string // ๋งํ’์„  ์˜ˆ: "๐Ÿ“– server.js ์ฝ๋Š” ์ค‘"
69
+ tool?: { name, useId, target, detail }
62
70
  since: number // ํ˜„์žฌ activity ์‹œ์ž‘ ์‹œ๊ฐ (๊ฒฝ๊ณผ ์‹œ๊ฐ„ ํ‘œ์‹œ์šฉ)
63
71
  }
64
72
 
65
73
  LogEntry { t, floorId, sessionId, agentId, agentName, icon, text }
66
74
 
67
- RoomId = 'library' | 'dev' | 'research' | 'server' | 'meeting' | 'lounge' | 'cafe' | 'terrace' | 'mydesk' | 'lobby' | 'elevator' | 'corridor'
75
+ RoomId = 'library' | 'dev' | 'research' | 'server' | 'meeting' | 'lounge' | 'cafe' | 'terrace' | 'mydesk' | 'lobby' | 'elevator'
76
+ // cafe/terrace ๋Š” ์„œ๋ฒ„์˜ "ํœด์‹ ๋ฐฉ" id. ์ธต ๋ ˆ์ด์•„์›ƒ์ด ์‹ค์ œ ํŽธ์˜์‹œ์„ค(ํ—ฌ์Šค์žฅยท๊ตฌ๋‚ด์‹๋‹น โ€ฆ)๋กœ ๋ณ„์นญํ•œ๋‹ค (6์ ˆ)
68
77
  ```
69
78
 
79
+ ์„ธ์…˜ ์ƒ๋ช…์ฃผ๊ธฐ (sweep ์€ 1์ดˆ๋งˆ๋‹ค, transcript stat ์€ ์„ธ์…˜๋‹น 1๋ถ„์— ํ•œ ๋ฒˆ)
80
+ - 30๋ถ„ ๋ฌดํ™œ๋™ โ†’ ํœด์‹ ๋ฐฉ์œผ๋กœ "์ž๋ฆฌ ๋น„์›€"
81
+ - 10๋ถ„ ๋ฌดํ™œ๋™๋ถ€ํ„ฐ transcript ๋ฅผ ํ™•์ธํ•ด 60๋ถ„ ๋™์•ˆ ๊ฐฑ์‹ ์ด ์—†์œผ๋ฉด ํ‡ด๊ทผ ์ฒ˜๋ฆฌ (ํ„ฐ๋ฏธ๋„์„ ๊ทธ๋ƒฅ ๋‹ซ์€ ๊ฒฝ์šฐ)
82
+ - 2์‹œ๊ฐ„ ๋ฌดํ™œ๋™ โ†’ ํ‡ด๊ทผ. SessionEnd ํ›„ 10์ดˆ ๋’ค ์ œ๊ฑฐ
83
+ - ์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฉด์—์„œ ์น˜์šด ์„ธ์…˜(DELETE)์€ ์ €๋„์— `SessionEnd(reason: dismissed)` ๋กœ ๋‚จ๊ฒจ ์žฌ์‹œ์ž‘ํ•ด๋„ ๋˜์‚ด์•„๋‚˜์ง€ ์•Š๋Š”๋‹ค
84
+ - ์ œ๋ชฉ: ํ›…์˜ session_title ์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ๋”ฐ๋ฅด๊ณ (๋ถ„๋ฆฌ๋œ ์„ธ์…˜), transcript ๋ 64KB ์˜ custom-title ์„ mtime ์ด ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ๋งŒ 1๋ถ„์— ํ•œ ๋ฒˆ ์ฝ๋Š”๋‹ค. ๋ฐ”๋€Œ๋ฉด ๋กœ๊ทธ "๐Ÿท๏ธ ์ œ๋ชฉ: โ€ฆ"
85
+
70
86
  ## 3. ํ›… ์ด๋ฒคํŠธ โ†’ ์ƒํƒœ ์ „์ด
71
87
 
72
88
  Claude Code 2.1.x ํ›… payload ๊ธฐ์ค€. ์„œ๋ธŒ์—์ด์ „ํŠธ๊ฐ€ ๋‚ธ ๋„๊ตฌ ํ˜ธ์ถœ์€ `agent_id` ๊ฐ€ ์‹ค๋ ค ์˜ค๋ฏ€๋กœ ๊ทธ ๊ฐ’์œผ๋กœ ์บ๋ฆญํ„ฐ๋ฅผ ์ฐพ๋Š”๋‹ค. ์—†์œผ๋ฉด `main`.
@@ -74,7 +90,7 @@ Claude Code 2.1.x ํ›… payload ๊ธฐ์ค€. ์„œ๋ธŒ์—์ด์ „ํŠธ๊ฐ€ ๋‚ธ ๋„๊ตฌ ํ˜ธ์ถœ์€
74
90
  | ํ›… ์ด๋ฒคํŠธ | ๋Œ€์ƒ | ๋ฐฉ | activity | ๋งํ’์„  | ๋น„๊ณ  |
75
91
  |---|---|---|---|---|---|
76
92
  | SessionStart | main ์ƒ์„ฑ | elevator โ†’ mydesk | wait | "์ถœ๊ทผ" | source=resume/compact ๋Š” ๊ธฐ์กด ์„ธ์…˜ ์œ ์ง€ |
77
- | UserPromptSubmit | main | ํ˜„์žฌ ์œ„์น˜ | think | "๊ณ ๋ฏผ ์ค‘โ€ฆ" | title ๋ฏธ์„ค์ •์ด๋ฉด prompt ๋กœ ์„ค์ •. status=thinking |
93
+ | UserPromptSubmit | main | ํ˜„์žฌ ์œ„์น˜ | think | "๊ณ ๋ฏผ ์ค‘โ€ฆ" | firstPrompt(์—†์„ ๋•Œ)ยทlastPrompt ๊ฐฑ์‹ . status=thinking |
78
94
  | PreToolUse | agent_id ๋˜๋Š” main | ๋„๊ตฌ๋ณ„ ๋ฐฉ (4์ ˆ) | ๋„๊ตฌ๋ณ„ | ๋„๊ตฌ๋ณ„ ๋ฌธ๊ตฌ | status=working |
79
95
  | PostToolUse | ๋™์ผ | ๊ทธ ์ž๋ฆฌ | think | "๊ณ ๋ฏผ ์ค‘โ€ฆ" | tool ํ•ด์ œ. duration_ms ๋กœ๊ทธ |
80
96
  | PostToolUseFailure | ๋™์ผ | ๊ทธ ์ž๋ฆฌ | think | "โŒ ์‹คํŒจ, ๋‹ค์‹œ ์ƒ๊ฐ ์ค‘" | error ์•ž 80์ž ๋กœ๊ทธ |
@@ -112,94 +128,153 @@ Claude Code 2.1.x ํ›… payload ๊ธฐ์ค€. ์„œ๋ธŒ์—์ด์ „ํŠธ๊ฐ€ ๋‚ธ ๋„๊ตฌ ํ˜ธ์ถœ์€
112
128
  | AskUserQuestion | mydesk | ask | `์งˆ๋ฌธ ์žˆ์–ด์š”` |
113
129
  | ๊ทธ ์™ธ | dev | work | `{tool} ์‚ฌ์šฉ ์ค‘` |
114
130
 
131
+
115
132
  ## 5. ์„œ๋ฒ„ API
116
133
 
117
134
  | ๋ฉ”์„œ๋“œ | ๊ฒฝ๋กœ | ์„ค๋ช… |
118
135
  |---|---|---|
119
- | POST | `/hook` | ํ›… payload ์ˆ˜์‹ . body 2MB ์ œํ•œ. ํ•ญ์ƒ 204 |
120
- | GET | `/events` | SSE. ์ ‘์† ์ฆ‰์‹œ `snapshot` 1ํšŒ, ์ดํ›„ `update` (๊ฐฑ์‹ ๋œ Session ์ „์ฒด + ์ƒˆ LogEntry) |
121
- | GET | `/api/state` | ํ˜„์žฌ ์ „์ฒด ์ƒํƒœ JSON (๋””๋ฒ„๊ทธ์šฉ) |
136
+ | POST | `/hook` | ํ›… payload ์ˆ˜์‹ . body 2MB ์ œํ•œ. ๋จผ์ € 204 ๋ฅผ ๋Œ๋ ค์ฃผ๊ณ  ์ฒ˜๋ฆฌ. ๋ฐ๋ชจ๊ฐ€ ๊บผ์ ธ ์žˆ์œผ๋ฉด `/demo/` ์ด๋ฒคํŠธ๋Š” ๋ฌด์‹œ |
137
+ | GET | `/events` | SSE. ์ ‘์† ์ฆ‰์‹œ `snapshot` 1ํšŒ, 25์ดˆ๋งˆ๋‹ค ping |
138
+ | GET | `/api/state` | ํ˜„์žฌ ์ „์ฒด ์ƒํƒœ JSON (snapshot ๊ณผ ๊ฐ™์€ ๋ชจ์–‘) |
139
+ | POST | `/api/floors/style` | `{ floorId, style }` ์ธต ์ธํ…Œ๋ฆฌ์–ด ํ”„๋ฆฌ์…‹ ์ง€์ • (null ์ด๋ฉด ์ž๋™) |
140
+ | DELETE | `/api/sessions/:id` | ๋๋‚œ/์ž๋ฆฌ ๋น„์šด ์„ธ์…˜์„ ๋ชฉ๋ก์—์„œ ์น˜์šด๋‹ค |
141
+ | POST | `/api/demo/start` ยท `/api/demo/stop` | ๋ฐ๋ชจ ์„ธ์…˜ ์‹œ์ž‘ / ์ค‘์ง€(๋ฐ๋ชจ ์„ธ์…˜ ์ „๋ถ€ ์ œ๊ฑฐ) |
122
142
  | GET | `/` | `public/` ์ •์  ํŒŒ์ผ |
123
143
 
124
144
  SSE ๋ฉ”์‹œ์ง€
125
145
  ```
126
- event: snapshot data: { me: "jinyu", floors: Floor[], log: LogEntry[] }
127
- event: update data: { session: Session, floor: {id,name,no,lastActivity}, log?: LogEntry }
146
+ event: snapshot data: { me, floors, sessions, log, serverPort, hookPort, version, demo, journal }
147
+ event: update data: { session, floor, log? }
128
148
  event: remove data: { sessionId, floorId? }
149
+ event: floor data: Floor // ์ด๋ฆ„ ๊ตฌ๋ถ„ยทํ”„๋ฆฌ์…‹ ๋ณ€๊ฒฝ
150
+ event: demo data: { on }
129
151
  ```
130
152
 
131
- ํฌํŠธ ๊ธฐ๋ณธ 4777, ํ™˜๊ฒฝ๋ณ€์ˆ˜ `AI_OFFICE_PORT` ๋กœ ๋ณ€๊ฒฝ. ์„œ๋ฒ„/ํ›… ์–‘์ชฝ์ด ๊ฐ™์€ ๋ณ€์ˆ˜๋ฅผ ๋ณธ๋‹ค.
153
+ - ํฌํŠธ ๊ธฐ๋ณธ 4777, `AI_OFFICE_PORT` ๋กœ ๋ณ€๊ฒฝ. ํ›… ๋ช…๋ น์— ํฌํŠธ๊ฐ€ ๋ฐ•ํ˜€ ์žˆ์–ด(`lib/hookport.js` ๋กœ ์ฝ์Œ) ์„œ๋ฒ„ ํฌํŠธ์™€ ๋‹ค๋ฅด๋ฉด ํ™”๋ฉด์— ๊ฒฝ๊ณ ํ•œ๋‹ค.
154
+ - ํ™ˆ ํด๋” `~/.ai-office` (`AI_OFFICE_HOME`). `--no-journal` ๋กœ ์ €๋„ ๋„๊ธฐ.
155
+ - `AI_OFFICE_WATCH_PARENT=1` ์ด๋ฉด ๋ถ€๋ชจ ํ”„๋กœ์„ธ์Šค(์•ฑยทCLI)๊ฐ€ ๊ฐ•์ œ ์ข…๋ฃŒ๋  ๋•Œ ์„œ๋ฒ„๋„ ์Šค์Šค๋กœ ๋๋‚œ๋‹ค.
156
+
157
+ ### ์ €๋„ (lib/journal.js)
158
+
159
+ - ํ›… payload ๋ฅผ ํฐ ํ•„๋“œ๋ฅผ ์ž๋ฅธ ์ฑ„ ํ•œ ์ค„์”ฉ append (๋ฐ๋ชจ ์ด๋ฒคํŠธ๋Š” ๋‚จ๊ธฐ์ง€ ์•Š์Œ).
160
+ - ๊ธฐ๋™ ์‹œ ์ตœ๊ทผ 2์‹œ๊ฐ„(SESSION_TTL)๋งŒ ์‹œ๊ฐ„ ์ˆœ์„œ๋Œ€๋กœ `state.apply` ์— ๋‹ค์‹œ ํ˜๋ ค ์ƒํƒœ๋ฅผ ๋ณต์›ํ•œ๋‹ค.
161
+ - ๋งค์‹œ๊ฐ„ + 20MB ์ดˆ๊ณผ ์‹œ ์˜ค๋ž˜๋œ ์ค„์„ ๋ฒ„๋ฆฌ๊ณ  ๋‹ค์‹œ ์“ด๋‹ค(์••์ถ•). `ai-office compact` ๋กœ ์ฆ‰์‹œ ์••์ถ•, `ai-office doctor` ๊ฐ€ ํฌ๊ธฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.
132
162
 
133
163
  ## 6. ํ”„๋ก ํŠธ์—”๋“œ ๋ชจ๋“ˆ
134
164
 
135
165
  ```
136
166
  public/
137
- index.html 3๋‹จ ๋ ˆ์ด์•„์›ƒ (์ขŒ 260px / ์ค‘์•™ canvas / ์šฐ 300px), ์ƒ๋‹จ ํ—ค๋”(์ธต ์ด๋ฆ„ยท์„ธ์…˜ ์ œ๋ชฉยทLIVE)
138
- style.css ๋ผ์ดํŠธ ํ†ค, ํŒจ๋„ ์นด๋“œ ์Šคํƒ€์ผ
167
+ index.html 3๋‹จ ๋ ˆ์ด์•„์›ƒ (์ขŒ ์ธต๋ณ„ ์•ˆ๋‚ด / ์ค‘์•™ canvas / ์šฐ ์‚ฌ๋žŒ๋“คยท๋กœ๊ทธ), ์ƒ๋‹จ ํ—ค๋”(์ธตยท์„ธ์…˜ ์ œ๋ชฉยท๋ธŒ๋žœ์น˜ยทLIVE)
168
+ style.css ๋ผ์ดํŠธ/๋‹คํฌ ํ…Œ๋งˆ ํ† ํฐ, ํŒจ๋„ ์นด๋“œ
169
+ fonts/ ์„œ๋ธŒ์…‹ ์›นํฐํŠธ (scripts/subset-fonts.sh)
139
170
  js/
140
- main.js SSE ์—ฐ๊ฒฐ, store ๊ฐฑ์‹ , requestAnimationFrame ๋ฃจํ”„
141
- store.js floors/sessions/log ๋ฏธ๋Ÿฌ, ์„ ํƒ๋œ floor/session, subscribe()
142
- panels.js ์ขŒยท์šฐ ํŒจ๋„ DOM ๋ Œ๋” (์ธต ๋ชฉ๋ก, ์„ธ์…˜ ๋ชฉ๋ก, ์‚ฌ๋žŒ๋“ค, ํ™œ๋™ ๋กœ๊ทธ, ๋ฒ”๋ก€)
171
+ main.js SSE ์—ฐ๊ฒฐยท์žฌ์ ‘์†, store ๊ฐฑ์‹ , ์•Œ๋ฆผ(์†Œ๋ฆฌยท๋ธŒ๋ผ์šฐ์ € ์•Œ๋ฆผ), ์„ค์ •, ํ‚ค๋ณด๋“œ(1~9 ์ธต ์ด๋™)
172
+ store.js floors/sessions/log ๋ฏธ๋Ÿฌ, ์„ ํƒ๋œ ์ธต/์„ธ์…˜, ์ธต ์ ‘๊ธฐ(auto/all/none), subscribe()
173
+ panels.js ์ขŒยท์šฐ ํŒจ๋„ DOM (์ธต ๋ชฉ๋ก + ๋ชจ๋‘ ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ, ์„ธ์…˜ ๋ชฉ๋ก, ์‚ฌ๋žŒ๋“ค, ํ™œ๋™ ๋กœ๊ทธ, ๋ฒ”๋ก€)
174
+ overview.js ์ „์ฒด ๋ณด๊ธฐ: ์ธต์„ ๊ฑด๋ฌผ์ฒ˜๋Ÿผ ์Œ“์•„ ๋ฏธ๋‹ˆ ์‚ฌ๋ฌด์‹ค + ์บ๋ฆญํ„ฐ ์ 
175
+ i18n.js ์–ธ์–ด: ํ•œ๊ตญ์–ด ์›๋ฌธ, ์˜์–ด๋Š” ์‚ฌ์ „ โ†’ ํŒจํ„ด ๊ทœ์น™ โ†’ ๊ตฌ์ ˆ ์น˜ํ™˜. ์‚ฌ์šฉ์ž ์š”์ฒญยท์ œ๋ชฉ์€ ๋ฒˆ์—ญํ•˜์ง€ ์•Š๋Š”๋‹ค
176
+ names.js ์„ธ์…˜๋ณ„ ์บ๋ฆญํ„ฐ ๋ณ„๋ช… (localStorage)
177
+ customize.js, avatarStore.js ์บ๋ฆญํ„ฐ ์™ธํ˜• ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ (๋ชธ ์ข…๋ฅ˜ยทํŒŒ์ธ ยท์ƒ‰ยท์•…์„ธ์‚ฌ๋ฆฌ)
178
+ floorStylePicker.js ์ธต ๐ŸŽจ โ†’ ์ธํ…Œ๋ฆฌ์–ด ํ”„๋ฆฌ์…‹ ์„ ํƒ (์„œ๋ฒ„์— ์ €์žฅ)
179
+ tooltip.js, texttip.js ์บ๋ฆญํ„ฐ ํˆดํŒ, ์ž˜๋ฆฐ ๊ธ€์ž ํˆดํŒ
143
180
  scene/
144
- map.js 28x22 ํƒ€์ผ ๊ทธ๋ฆฌ๋“œ, ๋ฐฉ ์˜์—ญ, ๊ฐ€๊ตฌ footprint, ๋ฒฝ, ์„œ ์žˆ๋Š” ์ž๋ฆฌ(slot), ํ†ตํ–‰ ๊ฐ€๋Šฅ ๋งต
145
- pathfind.js A* (4๋ฐฉํ–ฅ), ํƒ€์ผ ๋‹จ์œ„
181
+ layouts.js ์ธต ๋ ˆ์ด์•„์›ƒ ์ƒ์„ฑ๊ธฐ: 4์—ด ร— ๋  ๊ฒฉ์ž์— ๋ฐฉ ๋ฐฐ์น˜, ๋ฐฉ ํฌ๊ธฐ์— ๋งž๋Š” ๊ฐ€๊ตฌยท์„œ๋Š” ์ž๋ฆฌยทํœด์‹ ๋ช…์†Œ
182
+ floorStyle.js ์ธํ…Œ๋ฆฌ์–ด ํ”„๋ฆฌ์…‹(์ƒ‰) โ†” ๋ ˆ์ด์•„์›ƒ ์—ฐ๊ฒฐ
183
+ map.js ํƒ€์ผ ๊ฒฉ์ž(x 1..35, y 1..21), ๋ฒฝยท๋ฌธ, ํ†ตํ–‰ ๋งต, ํˆฌ์˜. setLayout() ์œผ๋กœ ์ธต๋งˆ๋‹ค ๊ต์ฒด
184
+ pathfind.js A* (4๋ฐฉํ–ฅ), ๋ฒฝ edge ์™€ solid ๊ฐ€๊ตฌ ํšŒํ”ผ
146
185
  actors.js Agent โ†’ Actor(์œ„์น˜, ๊ฒฝ๋กœ, ๊ฑท๊ธฐ ์• ๋‹ˆ, ๋งํ’์„ , ์Šฌ๋กฏ ์ ์œ /ํ•ด์ œ, ํœด์‹ ๋ฐฉ ์•ˆ ๋ฐฐํšŒ)
147
- spots.js ํœด์‹ ๋ฐฉ(ํœด๊ฒŒ์‹คยท์นดํŽ˜ยทํ…Œ๋ผ์Šค) ์•ˆ ๋ช…์†Œ ๋ชฉ๋ก: ํƒ€์ผ, ๋งํ’์„ , ๋จธ๋ฌด๋Š” ์‹œ๊ฐ„
148
- render.js canvas 2D ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ: ๋ฐ”๋‹ฅ โ†’ ๊นŠ์ด ์ •๋ ฌ๋œ ๊ฐ€๊ตฌ/๋ฒฝ/์บ๋ฆญํ„ฐ โ†’ ๋งํ’์„ /์ด๋ฆ„ํ‘œ โ†’ ์„ ํƒ ํ•˜์ด๋ผ์ดํŠธ
149
- camera.js pan(๋“œ๋ž˜๊ทธ), zoom(ํœ /๋ฒ„ํŠผ), ์ดˆ๊ธฐ fit, ์„ ํƒ ์„ธ์…˜ ๋”ฐ๋ผ๊ฐ€๊ธฐ
150
- sprites.js ๊ฐ€๊ตฌ/์บ๋ฆญํ„ฐ๋ฅผ ์ฝ”๋“œ๋กœ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜๋“ค (์™ธ๋ถ€ ์ด๋ฏธ์ง€ ์—†์Œ, ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ)
151
- daylight.js ์‹œ๊ฐ„๋Œ€ ์กฐ๋ช…: ์‹ค์ œ ์‹œ๊ฐ โ†’ ์•„์นจ/๋‚ฎ/์ €๋…/๋ฐค, ์ฐฝ๋ฌธยทํ…Œ๋ผ์Šคยท์‹ค๋‚ด ํ‹ดํŠธ ํŒ”๋ ˆํŠธ (์„ค์ • '์‹œ๊ฐ„๋Œ€ ์กฐ๋ช…', aiOffice.daylight)
186
+ spots.js ํœด์‹ ๋ฐฉ ๋ช…์†Œ (๋ ˆ์ด์•„์›ƒ์ด ์ฑ„์šฐ๋Š” live ๊ฐ์ฒด)
187
+ avatar.js ์น˜๋น„ ์บ๋ฆญํ„ฐ ๊ทธ๋ฆฌ๊ธฐ (์‚ฌ๋žŒยท๋กœ๋ด‡ยท๊ณ ์–‘์ดยท๊ณฐ)
188
+ render.js ๋ฐ”๋‹ฅ โ†’ 3D AABB ๊นŠ์ด ์ •๋ ฌ๋œ ๊ฐ€๊ตฌ/๋ฒฝ/์บ๋ฆญํ„ฐ โ†’ ๋ฐฉ ์ด๋ฆ„ํ‘œยท๋งํ’์„ 
189
+ sprites.js ๊ฐ€๊ตฌยท๋ฒฝ์„ ์ฝ”๋“œ๋กœ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜๋“ค (์™ธ๋ถ€ ์ด๋ฏธ์ง€ ์—†์Œ)
190
+ camera.js ํŒฌยท์คŒยท๋งต ๋งž์ถคยทํด๋ฆญ ํŒ์ •, ์„ ํƒ ์„ธ์…˜ ๋”ฐ๋ผ๊ฐ€๊ธฐ
191
+ daylight.js ์‹ค์ œ ์‹œ๊ฐ โ†’ ์•„์นจ/๋‚ฎ/์ €๋…/๋ฐค ์ฐฝ๋ฌธยท์‹ค๋‚ด ํ‹ดํŠธ
152
192
  ```
153
193
 
154
- ๋งต ๋ ˆ์ด์•„์›ƒ (ํƒ€์ผ ์ขŒํ‘œ, ๊ทธ๋ฆผ ์ฐธ๊ณ )
194
+ ์ธต ๋ ˆ์ด์•„์›ƒ๊ณผ ์ธํ…Œ๋ฆฌ์–ด ํ”„๋ฆฌ์…‹
155
195
 
156
- ```
157
- x 1-8 x 10-17 x 19-26 x 28-35
158
- y 1-7 ์ž๋ฃŒ์‹ค(์ฑ…์žฅ) ๊ฐœ๋ฐœ์‹ค(์ฑ…์ƒ6) ์ •๋ณด๊ฒ€์ƒ‰์‹ค(๋Œ€ํ˜• ํ™”๋ฉด, ์ง€๊ตฌ๋ณธ) ์นดํŽ˜(๋ถ์ชฝ ์œ ๋ฆฌ์ฐฝ, ์นด์šดํ„ฐยท์ปคํ”ผ๋จธ์‹ ยทํ…Œ์ด๋ธ”)
159
- y 8-9 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๋ณต๋„ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
160
- y 10-15 ํšŒ์˜์‹ค(ํ…Œ์ด๋ธ”) ํœด๊ฒŒ์‹ค(์†ŒํŒŒ) ์„œ๋ฒ„์‹ค(๋ž™ 2์—ด) ํ…Œ๋ผ์Šค(๋ฐํฌ ๋ฐ”๋‹ฅ, ๋‚œ๊ฐ„, ํŒŒ๋ผ์†”ยท๋ฒค์น˜ยท๋‚˜๋ฌด)
161
- y 16-17 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๋ณต๋„ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ (ํ…Œ๋ผ์Šค๋Š” y 10-21 ๊นŒ์ง€, ๋ถ์ชฝ ๋ฒฝ ๋ฌธ์œผ๋กœ ์ถœ์ž…)
162
- y 18-21 ๋‚ด ์ž๋ฆฌ(์‚ฌ์šฉ์ž) ๋กœ๋น„(์•ˆ๋‚ด, ํ™”๋ถ„) ์—˜๋ฆฌ๋ฒ ์ดํ„ฐ(๋ฌธ 2๊ฐœ)
163
- ```
164
-
165
- - x 27 ์€ ์„ธ๋กœ ๋ณต๋„. ์นดํŽ˜๋Š” ๋‚จ์ชฝ ๋ฒฝ ๋ฌธ, ํ…Œ๋ผ์Šค๋Š” ๋ถ์ชฝ ๋ฒฝ ๋ฌธ์œผ๋กœ ๋“œ๋‚˜๋“ ๋‹ค. ์นดํŽ˜ยทํ…Œ๋ผ์Šค๋Š” ๋„๊ตฌ ๋งคํ•‘ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋ผ ํœด๊ฒŒ์‹ค๊ณผ ํ•จ๊ป˜ ํœด์‹ ๋ฐฉ(์„ธ์…˜๋งˆ๋‹ค ํ•˜๋‚˜)์ด๋ฉฐ, ์‰ฌ๋Š” ์บ๋ฆญํ„ฐ๊ฐ€ ๋ฐฉ ์•ˆ ๋ช…์†Œ๋ฅผ ์˜ฎ๊ฒจ ๋‹ค๋‹Œ๋‹ค. "์ƒ๋™๊ฐ" ์ž์œจ ํ–‰๋™(๋Œ€๊ธฐ ์ค‘ ์‚ฐ์ฑ…)์˜ ๋ชฉ์ ์ง€์ด๊ธฐ๋„ ํ•˜๋‹ค.
166
- - ํ…Œ๋ผ์Šค ๋‚œ๊ฐ„์€ ๊ฒฉ์ž ๋ฐ–(x 36, y 22)์— ๋†“์ธ ๋‚ฎ์€ ๋ฒฝ์ด๋ผ ํ†ตํ–‰์—๋Š” ์˜ํ–ฅ์ด ์—†๋‹ค.
196
+ | ํ”„๋ฆฌ์…‹ | ๋ ˆ์ด์•„์›ƒ | ํœด์‹ ๋ฐฉ cafe / terrace ๊ฐ€ ๋˜๋Š” ๊ณณ |
197
+ |---|---|---|
198
+ | ๋”ฐ๋œปํ•œ ์šฐ๋“œ ยท ํŒŒ์Šคํ…” | classic | ์นดํŽ˜ / ํ…Œ๋ผ์Šค |
199
+ | ์ฟจ ๊ทธ๋ ˆ์ด | studio | ์ŠคํŠœ๋””์˜ค / ์„ธ๋ฏธ๋‚˜์‹ค |
200
+ | ๋ฏผํŠธ | fitness | ํ—ฌ์Šค์žฅ / ์˜ฅ์ƒ์ •์› |
201
+ | ๋ผ๋ฒค๋” | play | ๋ถ ๋ผ์šด์ง€ / ๊ฒŒ์ž„๋ฃธ |
202
+ | ์ƒŒ๋“œ | canteen | ๊ตฌ๋‚ด์‹๋‹น / ์ˆ˜๋ฉด์‹ค |
167
203
 
168
- - ์บ๋ฆญํ„ฐ๋Š” ๋ฐฉ๋งˆ๋‹ค ์ •ํ•ด์ง„ "์„œ๋Š” ์ž๋ฆฌ(slot)" ์ค‘ ๋นˆ ๊ณณ์„ ์ฐจ์ง€ํ•œ๋‹ค. ๊ฐ™์€ ๋ฐฉ์— ์—ฌ๋Ÿฟ์ด๋ฉด ์˜† ์ž๋ฆฌ๋กœ.
169
- - ์ด๋™์€ A* ๊ฒฝ๋กœ๋ฅผ ๋”ฐ๋ผ ์ดˆ๋‹น 4ํƒ€์ผ. ๊ฑท๋Š” ๋™์•ˆ ์ƒํ•˜ ๋ฐ”์šด์Šค, ๋ฐœ๋ฐ‘ ๊ทธ๋ฆผ์ž.
170
- - ์„œ๋ธŒ์—์ด์ „ํŠธ๋Š” ์—˜๋ฆฌ๋ฒ ์ดํ„ฐ ํƒ€์ผ์—์„œ ์ƒ์„ฑ๋˜์–ด ํšŒ์˜์‹ค๋กœ ๊ฑธ์–ด์˜ค๊ณ , ๋๋‚˜๋ฉด ์—˜๋ฆฌ๋ฒ ์ดํ„ฐ๋กœ ๊ฑธ์–ด๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค.
171
- - ๋งํ’์„ : `{์•„์ด์ฝ˜} {bubble} ยท {๊ฒฝ๊ณผ์‹œ๊ฐ„}`. ์„ ํƒ๋œ ์„ธ์…˜์˜ ์บ๋ฆญํ„ฐ๋Š” ํ…Œ๋‘๋ฆฌ ๊ฐ•์กฐ, ๋‚˜๋จธ์ง€๋Š” ๋ฐ˜ํˆฌ๋ช….
172
- - ์‚ฌ์šฉ์ž ์บ๋ฆญํ„ฐ("{me} (๋‚˜)")๋Š” ๋‚ด ์ž๋ฆฌ์— ํ•ญ์ƒ ์•‰์•„ ์žˆ๋‹ค. Claude ๊ฐ€ `ask` ์ƒํƒœ๋ฉด ๊ทธ ์˜†์— ์™€์„œ ์„ ๋‹ค.
173
- - ํ•œ ์ธต = ํ•œ ํ”„๋กœ์ ํŠธ. ๊ทธ ํ”„๋กœ์ ํŠธ์˜ ๋ชจ๋“  ์„ธ์…˜ ์บ๋ฆญํ„ฐ๊ฐ€ ๊ฐ™์€ ์ธต์— ๋ณด์ธ๋‹ค. ์ขŒ์ธก์—์„œ ์ธต์„ ๋ฐ”๊พธ๋ฉด ๋งต์ด ํ†ต์งธ๋กœ ๋ฐ”๋€๋‹ค.
204
+ - ์—…๋ฌด ๋ฐฉ 9๊ฐœ(libraryยทdevยทresearchยทmeetingยทloungeยทserverยทmydeskยทlobbyยทelevator)๋Š” ๋ชจ๋“  ๋ ˆ์ด์•„์›ƒ์— ์žˆ๋‹ค. ์œ„์น˜๋งŒ ๋‹ค๋ฅด๋‹ค.
205
+ - ์„œ๋ฒ„(`lib/floorStyle.js`)๊ฐ€ ๊ฐ™์€ ํ‘œ๋ฅผ CommonJS ๋กœ ๋“ค๊ณ  ์žˆ์–ด ํœด์‹ ๋งํ’์„ ์„ ๊ทธ ์ธต ํŽธ์˜์‹œ์„ค์— ๋งž์ถ˜๋‹ค (`test/floorStyleShared.test.js` ๊ฐ€ ๋‘ ํ‘œ ์ผ์น˜๋ฅผ ๊ณ ์ •).
206
+ - ์บ๋ฆญํ„ฐ๋Š” ๋ฐฉ๋งˆ๋‹ค ์ •ํ•ด์ง„ "์„œ๋Š” ์ž๋ฆฌ(slot)" ์ค‘ ๋นˆ ๊ณณ์„ ์ฐจ์ง€ํ•œ๋‹ค. ์ด๋™์€ A* ๊ฒฝ๋กœ, ์ดˆ๋‹น 4ํƒ€์ผ.
207
+ - ํœด์‹ ์ค‘์ธ ์บ๋ฆญํ„ฐ๋Š” ๊ทธ ๋ฐฉ ๋ช…์†Œ๋ฅผ 10~30์ดˆ๋งˆ๋‹ค ์˜ฎ๊ฒจ ๋‹ค๋‹Œ๋‹ค. ์ƒˆ ์ด๋ฒคํŠธ๊ฐ€ ์˜ค๋ฉด ์ฆ‰์‹œ ํ•ด๋‹น ๋ฐฉ์œผ๋กœ ๊ฐ„๋‹ค.
208
+ - ์„œ๋ธŒ์—์ด์ „ํŠธ๋Š” ์—˜๋ฆฌ๋ฒ ์ดํ„ฐ์—์„œ ์ƒ์„ฑ๋˜์–ด ํšŒ์˜์‹ค๋กœ ๊ฑธ์–ด์˜ค๊ณ , ๋๋‚˜๋ฉด ์—˜๋ฆฌ๋ฒ ์ดํ„ฐ๋กœ ๊ฑธ์–ด๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค.
209
+ - ์‚ฌ์šฉ์ž ์บ๋ฆญํ„ฐ("{me} (๋‚˜)")๋Š” ๋‚ด ์ž๋ฆฌ์— ์•‰์•„ ์žˆ๋‹ค. Claude ๊ฐ€ `ask` ์ƒํƒœ๋ฉด ๊ทธ ์˜†์— ์™€์„œ ์„ ๋‹ค.
174
210
 
175
- ## 7. ์„ค์น˜ยท์šด์˜ ์Šคํฌ๋ฆฝํŠธ
211
+ ## 7. CLI ยท ์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ
176
212
 
177
213
  | ํŒŒ์ผ | ์—ญํ•  |
178
214
  |---|---|
179
- | `hooks/emit.sh` | ํ›… ๋ช…๋ น. stdin โ†’ POST. ์‹คํŒจ ๋ฌด์‹œ |
180
- | `scripts/install-hooks.js` | `~/.claude/settings.json` ๋ฐฑ์—… ํ›„ 11๊ฐœ ์ด๋ฒคํŠธ์— emit.sh ํ•ญ๋ชฉ์„ ๋ณ‘ํ•ฉ. ์ด๋ฏธ ์žˆ์œผ๋ฉด ๊ฑด๋„ˆ๋œ€. `--remove` ๋กœ ์ œ๊ฑฐ |
181
- | `scripts/demo.js` | ๊ฐ€์งœ ์„ธ์…˜ 2๊ฐœ(ํ”„๋กœ์ ํŠธ 2๊ฐœ)๊ฐ€ ์ฝ๊ธฐโ†’๊ฒ€์ƒ‰โ†’์‹คํ–‰โ†’์ˆ˜์ •โ†’์„œ๋ธŒ์—์ด์ „ํŠธโ†’์งˆ๋ฌธโ†’ํœด์‹ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ฐ˜๋ณต POST. UI ๊ฐœ๋ฐœยท์‹œ์—ฐ์šฉ |
182
- | `npm start` | ์„œ๋ฒ„ ๊ธฐ๋™. ์ฝ˜์†”์— ์ ‘์† URL ์ถœ๋ ฅ |
215
+ | `bin/ai-office.js` | `ai-office` (์„ค์น˜ + ํ›… ๋“ฑ๋ก + ์„œ๋ฒ„ + ๋ธŒ๋ผ์šฐ์ €), `install [--force]`, `install-hooks`, `uninstall-hooks`, `start`, `demo`, `doctor`, `compact` |
216
+ | `hooks/emit.js` | ํ›… ๋ช…๋ น. stdin โ†’ POST. ์‹คํŒจ ๋ฌด์‹œ (`emit.sh` ๋Š” ์˜ˆ์ „ curl ๋ฒ„์ „. ๋“ฑ๋ก๋ผ ์žˆ์œผ๋ฉด install-hooks ๊ฐ€ emit.js ๋กœ ๋ฐ”๊พผ๋‹ค) |
217
+ | `scripts/install-hooks.js` | `settings.json` ๋ฐฑ์—… ํ›„ 11๊ฐœ ์ด๋ฒคํŠธ์— emit.js ํ•ญ๋ชฉ ๋ณ‘ํ•ฉ, ์ด๋ฏธ ์žˆ์œผ๋ฉด ๊ฑด๋„ˆ๋œ€, `--remove` ๋กœ ์ œ๊ฑฐ |
218
+ | `lib/nodepath.js` | ํ›… ๋ช…๋ น์— ๋„ฃ์„ Node ๊ฒฝ๋กœ: `~/.ai-office/node` > Homebrew Cellar ๋ฒ„์ „ ํด๋” โ†’ ๋ฒ„์ „ ์—†๋Š” opt ๋งํฌ > ํ˜„์žฌ Node. Node ๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œํ•ด๋„ ํ›…์ด ์•ˆ ๊นจ์ง€๊ฒŒ |
219
+ | `scripts/demo.js` | ๊ฐ€์งœ ์„ธ์…˜๋“ค์ด ์ฝ๊ธฐโ†’๊ฒ€์ƒ‰โ†’์‹คํ–‰โ†’์ˆ˜์ •โ†’์„œ๋ธŒ์—์ด์ „ํŠธโ†’์งˆ๋ฌธโ†’ํœด์‹ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ฐ˜๋ณต POST |
220
+ | `scripts/bump-version.js` | ๋ฒ„์ „์„ package.json ยท app/package.json ยท tauri.conf.json ยท Cargo.toml ์— ๋™์‹œ ๋ฐ˜์˜ (`npm run bump:patch|minor`) |
221
+ | `scripts/fetch-node.js` | ๋ฐ์Šคํฌํ†ฑ ์•ฑ ์‚ฌ์ด๋“œ์นด์šฉ Node ๋ฐ”์ด๋„ˆ๋ฆฌ ๋‹ค์šด๋กœ๋“œ (์ปค๋ฐ‹ํ•˜์ง€ ์•Š์Œ) |
222
+ | `scripts/update-homebrew.js` | ๋ฆด๋ฆฌ์Šค ๋’ค Homebrew tap ์˜ caskยทformula ๋ฒ„์ „๊ณผ sha256 ๊ฐฑ์‹  |
223
+ | `scripts/make-tray-icon.js` | ๋ฉ”๋‰ด๋ฐ” ํ…œํ”Œ๋ฆฟ ์•„์ด์ฝ˜(๋นจ๊ฐ„ ์  ๋ณ€ํ˜• ํฌํ•จ) ์ƒ์„ฑ |
224
+
225
+ - CLI ๋Š” ์•ฑ ํŒŒ์ผ์„ `~/.ai-office/app` ์— ๋ณต์‚ฌํ•ด ๋‘๊ณ  ๊ฑฐ๊ธฐ์„œ ์‹คํ–‰ํ•œ๋‹ค(npx ์บ์‹œ๊ฐ€ ์ง€์›Œ์ ธ๋„ ํ›…์ด ์‚ด์•„ ์žˆ๊ฒŒ). ๋ฐ์Šคํฌํ†ฑ ์•ฑ์ด ๊น”์•„ ๋‘” ๊ฒƒ(`.app-version`)์€ `--force` ์—†์ด๋Š” ๋ฎ์–ด์“ฐ์ง€ ์•Š๋Š”๋‹ค.
226
+
183
227
 
184
228
  ## 8. ์„ค๊ณ„์ƒ ๊ฒฐ์ •
185
229
 
186
- - **์˜์กด์„ฑ 0**: ์„ค์น˜ ๋งˆ์ฐฐ์„ ์—†์• ๊ณ  ๋‚˜์ค‘์— Electron/Tauri ๋กœ ๊ฐ์Œ€ ๋•Œ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ธฐ๊ธฐ ์œ„ํ•ด Node ๋‚ด์žฅ ๋ชจ๋“ˆ๊ณผ ๋ฐ”๋‹๋ผ JS ๋งŒ ์“ด๋‹ค.
230
+ - **์˜์กด์„ฑ 0**: ์„ค์น˜ ๋งˆ์ฐฐ์„ ์—†์• ๊ณ  ๋ฐ์Šคํฌํ†ฑ ์•ฑ(์‚ฌ์ด๋“œ์นด Node)์œผ๋กœ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ธฐ๊ธฐ ์œ„ํ•ด Node ๋‚ด์žฅ ๋ชจ๋“ˆ๊ณผ ๋ฐ”๋‹๋ผ JS ๋งŒ ์“ด๋‹ค.
187
231
  - **SSE, WebSocket ์•„๋‹˜**: ์„œ๋ฒ„โ†’๋ธŒ๋ผ์šฐ์ € ๋‹จ๋ฐฉํ–ฅ์ด๋ฉด ์ถฉ๋ถ„ํ•˜๊ณ  ์žฌ์ ‘์†์ด ๋ธŒ๋ผ์šฐ์ €์— ๋‚ด์žฅ๋ผ ์žˆ๋‹ค.
188
232
  - **์ƒํƒœ๋Š” ์„œ๋ฒ„์—์„œ ๊ณ„์‚ฐ**: ํ›… ์ด๋ฒคํŠธ ํ•ด์„ ๊ทœ์น™์ด ํ•œ ๊ณณ์— ์žˆ์–ด์•ผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฝ๊ณ , ๋ธŒ๋ผ์šฐ์ €๋Š” "์–ด๋””๋กœ ๊ฐ€์„œ ๋ฌด์Šจ ๋งํ’์„ " ๋งŒ ๋ฐ›๋Š”๋‹ค.
189
- - **๊ทธ๋ฆผ ์ž์‚ฐ ์—†์Œ**: ๊ฐ€๊ตฌยท์บ๋ฆญํ„ฐ๋ฅผ canvas ์ฝ”๋“œ๋กœ ๊ทธ๋ฆฐ๋‹ค. ์ฒ˜์Œ์—” ๋‹จ์ˆœํ•œ ์ƒ์ž ์กฐํ•ฉ์œผ๋กœ ์‹œ์ž‘ํ•˜๊ณ , ๋‚˜์ค‘์— ์Šคํ”„๋ผ์ดํŠธ ์‹œํŠธ๋กœ ๊ต์ฒด ๊ฐ€๋Šฅํ•˜๋„๋ก sprites.js ์— ๊ฒฉ๋ฆฌํ•œ๋‹ค.
190
- - **๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ**: ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ์ƒํƒœ๋Š” ์‚ฌ๋ผ์ง„๋‹ค. ๋‹ค์Œ ํ›…๋ถ€ํ„ฐ ๋‹ค์‹œ ์ฑ„์›Œ์ง„๋‹ค. ํ•„์š”ํ•ด์ง€๋ฉด `~/.ai-office/events.jsonl` ๋กœ append ํ›„ ์žฌ์ƒํ•˜๋Š” ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
233
+ - **๊ทธ๋ฆผ ์ž์‚ฐ ์—†์Œ**: ๊ฐ€๊ตฌยท์บ๋ฆญํ„ฐ๋ฅผ canvas ์ฝ”๋“œ๋กœ ๊ทธ๋ฆฐ๋‹ค (sprites.js ยท avatar.js).
234
+ - **์žฌ์ƒ ๊ฐ€๋Šฅํ•œ ์ €๋„**: ์ƒํƒœ๋ฅผ ์ง์ ‘ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ์›๋ณธ ์ด๋ฒคํŠธ๋ฅผ ๋‚จ๊ฒจ ์žฌ์ƒํ•œ๋‹ค. ์ƒํƒœ ๋จธ์‹ ์ด ๋ฐ”๋€Œ์–ด๋„ ์ €๋„์€ ๊ทธ๋Œ€๋กœ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
235
+ - **๋ทฐ์–ด ์„ค์ •์€ ๋ธŒ๋ผ์šฐ์ €์—**: ๋ณ„๋ช…ยท์™ธํ˜•ยท์–ธ์–ดยทํ…Œ๋งˆ๋Š” localStorage. ์ธต ์ธํ…Œ๋ฆฌ์–ด์ฒ˜๋Ÿผ ๋ชจ๋‘๊ฐ€ ๊ฐ™์ด ๋ด์•ผ ํ•˜๋Š” ๊ฒƒ๋งŒ ์„œ๋ฒ„์— ๋‘”๋‹ค.
236
+ - **๋ฒ”์œ„**: Claude Code ํ›…๋งŒ ๋ฐ›๋Š”๋‹ค. claude.ai ์ฑ„ํŒ…ยทํด๋ผ์šฐ๋“œ ์„ธ์…˜์€ ํ›…์ด ์—†์–ด ๋‹ค๋ฃจ์ง€ ์•Š๋Š”๋‹ค.
237
+
238
+ ## 9. ๋ณด์•ˆ (BE-21)
239
+
240
+ ์„œ๋ฒ„๋Š” `127.0.0.1` ์—๋งŒ ์—ด๋ฆฌ์ง€๋งŒ, ๋ธŒ๋ผ์šฐ์ €์— ์—ด๋ฆฐ ๋‹ค๋ฅธ ์›น์‚ฌ์ดํŠธ๋„ ๊ทธ ์ฃผ์†Œ๋กœ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค. ์„ธ์…˜ ์ œ๋ชฉยท์š”์ฒญยท๊ฒฝ๋กœ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ๊ฐ€์งœ ์ด๋ฒคํŠธ๋ฅผ ๋„ฃ์ง€ ๋ชปํ•˜๊ฒŒ:
241
+
242
+ - `Host` ์ด๋ฆ„์ด `127.0.0.1` / `localhost` / `[::1]` ์ด ์•„๋‹ˆ๋ฉด 403 โ†’ DNS ๋ฆฌ๋ฐ”์ธ๋”ฉ ์ฐจ๋‹จ
243
+ - `Origin` ์ด ์žˆ์œผ๋ฉด ์ด ์„œ๋ฒ„ ํ™”๋ฉด(๊ฐ™์€ ํฌํŠธ), ๋ฐ์Šคํฌํ†ฑ ์•ฑ ์ฐฝ(`tauri://localhost`, `http(s)://tauri.localhost`)๋งŒ ํ—ˆ์šฉ, ๋‚˜๋จธ์ง€(`null` ํฌํ•จ) 403
244
+ - `Access-Control-Allow-Origin` ์€ ํ—ˆ์šฉ๋œ Origin ๋งŒ ๊ทธ๋Œ€๋กœ ๋Œ๋ ค์ค€๋‹ค (`*` ์“ฐ์ง€ ์•Š์Œ)
245
+ - ํ›…(emit.js)ยทCLIยทcurl ์€ Origin ์„ ๋ณด๋‚ด์ง€ ์•Š์œผ๋ฏ€๋กœ ์˜ํ–ฅ ์—†์Œ
246
+
247
+ ## 10. ๋ฐ์Šคํฌํ†ฑ ์•ฑ (app/, Tauri 2)
191
248
 
192
- ## 9. ํ‹ฐ์ผ“ ๋ถ„ํ• 
249
+ ```
250
+ AI Office.app / AI Office.exe (app/src-tauri/src/lib.rs)
251
+ โ”œโ”€ ๋ฆฌ์†Œ์Šค(server.jsยทlibยทpublicยทhooksยทscripts) โ†’ ~/.ai-office/app ์— ๋ณต์‚ฌ (.app-version ์ด ๋‹ค๋ฅผ ๋•Œ๋งŒ)
252
+ โ”œโ”€ ์‚ฌ์ด๋“œ์นด Node โ†’ ~/.ai-office/node ์— ๋ณต์‚ฌ โ†’ ํ›… ๋ช…๋ น๋„ ์ด Node ๋ฅผ ์“ด๋‹ค
253
+ โ”œโ”€ ํ›… ๋“ฑ๋ก (install-hooks.js --app-dir โ€ฆ)
254
+ โ”œโ”€ ์„œ๋ฒ„ ์ž์‹ ํ”„๋กœ์„ธ์Šค (AI_OFFICE_WATCH_PARENT=1, Windows ๋Š” CREATE_NO_WINDOW). ํฌํŠธ๊ฐ€ ์ด๋ฏธ ์—ด๋ ค ์žˆ์œผ๋ฉด ๊ทธ ์„œ๋ฒ„๋ฅผ ์“ด๋‹ค
255
+ โ”œโ”€ ์ฐฝ: app/dist/index.html ๋กœ๋”๊ฐ€ /api/state ๊ฐ€ ๋œฐ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค http://127.0.0.1:<port>/ ๋กœ ์ด๋™
256
+ โ”œโ”€ ํŠธ๋ ˆ์ด(๋ฉ”๋‰ด๋ฐ”): ์™ผ์ชฝยท์˜ค๋ฅธ์ชฝ ํด๋ฆญ ๋ชจ๋‘ ๋ฉ”๋‰ด. ๊ธฐ๋‹ค๋ฆฌ๋Š” ์„ธ์…˜์ด๋‚˜ ๋ฐ›์•„ ๋‘” ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ๋นจ๊ฐ„ ์ 
257
+ โ”‚ (macOS ๋Š” status item ์˜ effectiveAppearance ๋กœ ๊ธ€์ž ์ƒ‰ ๊ฒฐ์ •), Dock ๋ฐฐ์ง€ / Windows ์˜ค๋ฒ„๋ ˆ์ด ์•„์ด์ฝ˜๋„ ๊ฐ™์€ ๊ทœ์น™
258
+ โ”œโ”€ ์—…๋ฐ์ดํ„ฐ (tauri-plugin-updater): ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋‚ด๋ ค๋ฐ›๊ธฐ๋งŒ โ†’ ๋ฉ”๋‰ด "์—…๋ฐ์ดํŠธ ์ ์šฉ" ๋•Œ ์„œ๋ฒ„ ์ข…๋ฃŒ โ†’ ์„ค์น˜ โ†’ ์žฌ์‹œ์ž‘.
259
+ โ”‚ ์„ฑ๊ณต ์ „์—” 5๋ถ„๋งˆ๋‹ค, ์„ฑ๊ณต ๋’ค์—” 6์‹œ๊ฐ„๋งˆ๋‹ค ํ™•์ธ
260
+ โ”‚ ๋ฉ”๋‰ด "์—…๋ฐ์ดํŠธ ํ™•์ธ"ยท"์—…๋ฐ์ดํŠธ ์ ์šฉ" ์€ ์ง„ํ–‰ ์ฐฝ(app/dist/update.html)์„ ๋„์šด๋‹ค: ํ™•์ธ โ†’ ๋‹ค์šด๋กœ๋“œ ๋ง‰๋Œ€(์ทจ์†Œ) โ†’ ์„ค์น˜ ์ค‘ โ†’ ์žฌ์‹œ์ž‘.
261
+ โ”‚ ์ฐฝ์€ IPC ๊ถŒํ•œ ์—†์ด Rust ๊ฐ€ eval ๋กœ ์ƒํƒœ๋ฅผ ๋„ฃ๊ณ , ๋ฒ„ํŠผ์€ ๊ฐ€์งœ ์ฃผ์†Œ ์ด๋™ โ†’ on_navigation ์ด ๋ฐ›๋Š”๋‹ค
262
+ โ””โ”€ ๋กœ๊ทธ์ธ ์‹œ ์ž๋™ ์‹คํ–‰ (tauri-plugin-autostart, LaunchAgent / Run ํ‚ค, --hidden). ์ฒซ ์‹คํ–‰์— ํ•œ ๋ฒˆ ์ผœ๊ณ  ๋ฉ”๋‰ด ์ฒดํฌ๋ฐ•์Šค๋กœ ๋”
263
+ ```
264
+
265
+ ## 11. ๋ฐฐํฌ ยท CI
266
+
267
+ | ๊ฒฝ๋กœ | ๋‚ด์šฉ |
268
+ |---|---|
269
+ | GitHub Release | ํƒœ๊ทธ `v*` โ†’ `.github/workflows/release.yml`: macOS arm64 dmg + `.app.tar.gz(.sig)`, Windows NSIS(.sig), `latest.json`(์—…๋ฐ์ดํ„ฐ), ๊ณ ์ • ์ด๋ฆ„ ์‚ฌ๋ณธ(`AI-Office-mac-arm64.dmg`, `AI-Office-windows-x64-setup.exe`), Windows ์„ค์น˜ ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ |
270
+ | npm | `claude-ai-office` (bin `ai-office`). 2FA ๋•Œ๋ฌธ์— ์‚ฌ๋žŒ์ด `npm publish` |
271
+ | Homebrew | `cozyuj/homebrew-tap`: `Casks/ai-office.rb`(dmg, ๊ฒฉ๋ฆฌ ์†์„ฑ ์ œ๊ฑฐ), `Formula/claude-ai-office.rb`(npm tarball). release ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด `homebrew.yml` ์ด ๋‘˜์„ ๊ฐฑ์‹ ํ•ด ํ‘ธ์‹œ(formula ๋Š” npm ์— ๊ทธ ๋ฒ„์ „์ด ์žˆ์„ ๋•Œ). npm publish ๋’ค์—” `gh workflow run homebrew.yml -f version=x.y.z` |
193
272
 
194
- BE
195
- 1. `BE-01` ํ›… ์ˆ˜์‹  ์„œ๋ฒ„ + emit.sh + install-hooks (POST /hook, SSE, ์ •์  ์„œ๋น™)
196
- 2. `BE-02` ์ด๋ฒคํŠธ ์ •๊ทœํ™” + ์ƒํƒœ ๋จธ์‹  (3ยท4์ ˆ ํ‘œ ๊ตฌํ˜„, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” `node --test`)
197
- 3. `BE-03` ๋ฐ๋ชจ ์‹œ๋‚˜๋ฆฌ์˜ค ์Šคํฌ๋ฆฝํŠธ
273
+ - `test.yml`: `npm test` (node --test) ๋ฅผ ubuntu ยท windows ยท macos ์—์„œ
274
+ - `app-check.yml`: `app/**` ๊ฐ€ ๋ฐ”๋€Œ๋ฉด macOS ยท Windows ์—์„œ `cargo check --locked`
275
+ - ์•„์ง ์„œ๋ช…ยท๊ณต์ฆ ์ „์ด๋ผ macOS ๋Š” ์ฒ˜์Œ ์—ด ๋•Œ "ํ™•์ธ๋˜์ง€ ์•Š์€ ๊ฐœ๋ฐœ์ž" ๊ฒฝ๊ณ ๊ฐ€ ๋œฌ๋‹ค (Homebrew cask ๋Š” ๊ฒฉ๋ฆฌ ์†์„ฑ์„ ์ง€์›Œ ์šฐํšŒ).
198
276
 
199
- FE
200
- 4. `FE-01` 3๋‹จ ๋ ˆ์ด์•„์›ƒ + ์ขŒยท์šฐ ํŒจ๋„ (์ธต/์„ธ์…˜ ๋ชฉ๋ก, ์‚ฌ๋žŒ๋“ค, ํ™œ๋™ ๋กœ๊ทธ, ๋ฒ”๋ก€), SSE ์—ฐ๊ฒฐยทstore
201
- 5. `FE-02` ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ ๋งต + ๊ฐ€๊ตฌ + ์นด๋ฉ”๋ผ (์ •์  ์‚ฌ๋ฌด์‹ค์ด ๊ทธ๋ ค์ง€๋Š” ๋‹จ๊ณ„)
202
- 6. `FE-03` ์บ๋ฆญํ„ฐ ์ด๋™(A*)ยท๋งํ’์„ ยท์Šฌ๋กฏยท์—˜๋ฆฌ๋ฒ ์ดํ„ฐ ์ถœํ‡ด๊ทผ
203
- 7. `FE-04` ์„ ํƒ/ํ•˜์ด๋ผ์ดํŠธ/๋”ฐ๋ผ๊ฐ€๊ธฐ, ๊ฒฝ๊ณผ ์‹œ๊ฐ„, ๋งˆ๋ฌด๋ฆฌ ๋‹ค๋“ฌ๊ธฐ
277
+ ## 12. ์ž‘์—… ๊ทœ์น™
204
278
 
205
- ์ˆœ์„œ: BE-01 โ†’ BE-02 โ†’ BE-03 โ†’ FE-01 โ†’ FE-02 โ†’ FE-03 โ†’ FE-04. ๋ธŒ๋žœ์น˜๋Š” `feature/BE-01`, `fix/FE-02`, `test/BE-02` ์ฒ˜๋Ÿผ `<type>/<ํ‹ฐ์ผ“ID>`. FE ๋Š” BE-03 ๋ฐ๋ชจ๋กœ ์‹ค์ œ ํ›… ์—†์ด๋„ ๊ฐœ๋ฐœยท๊ฒ€์ฆํ•œ๋‹ค.
279
+ - ์ž‘์—…๋งˆ๋‹ค GitHub ์ด์Šˆ `[FE-nn]` / `[BE-nn]` / `[APP-nn]` โ†’ main ์—์„œ `feature|fix|test|docs/<ID>` ๋ธŒ๋žœ์น˜ โ†’ PR โ†’ CI(test, app-check) ํ†ต๊ณผ ํ›„ ๋จธ์ง€ โ†’ ์ด์Šˆ์— ๊ฒฐ๊ณผยทํ…Œ์ŠคํŠธยท์บก์ฒ˜ ๋Œ“๊ธ€.
280
+ - ๋ณ€๊ฒฝ๋งˆ๋‹ค ๋ฒ„์ „์„ ์˜ฌ๋ฆฐ๋‹ค: ๋ฒ„๊ทธยท๋ฌธ์„œ `npm run bump:patch`, ๊ธฐ๋Šฅ `npm run bump:minor`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-ai-office",
3
- "version": "0.29.1",
3
+ "version": "0.30.1",
4
4
  "description": "Claude Code ์„ธ์…˜ ํ™œ๋™์„ ์•„์ด์†Œ๋ฉ”ํŠธ๋ฆญ ์‚ฌ๋ฌด์‹ค๋กœ ์‹ค์‹œ๊ฐ„ ์‹œ๊ฐํ™”",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // ๋ฒ„์ „์„ ๋„ค ํŒŒ์ผ์— ๋™์‹œ์— ๋ฐ˜์˜ํ•œ๋‹ค: package.json, app/package.json, app/src-tauri/tauri.conf.json, app/src-tauri/Cargo.toml
4
+ // (+ Cargo.lock ์˜ ์ด ํฌ๋ ˆ์ดํŠธ ํ•ญ๋ชฉ. ์•ˆ ๋งž์œผ๋ฉด CI ์˜ cargo check --locked ๊ฐ€ ์‹คํŒจํ•œ๋‹ค)
4
5
  // node scripts/bump-version.js patch|minor|major|1.2.3 (๋ฒ„๊ทธ ์ˆ˜์ • = patch, ๊ธฐ๋Šฅ = minor, ํ˜ธํ™˜ ๊นจ์ง = major)
5
6
  const fs = require('fs');
6
7
  const path = require('path');
@@ -11,6 +12,7 @@ const FILES = {
11
12
  appPkg: path.join(ROOT, 'app', 'package.json'),
12
13
  tauri: path.join(ROOT, 'app', 'src-tauri', 'tauri.conf.json'),
13
14
  cargo: path.join(ROOT, 'app', 'src-tauri', 'Cargo.toml'),
15
+ lock: path.join(ROOT, 'app', 'src-tauri', 'Cargo.lock'),
14
16
  };
15
17
 
16
18
  function next(current, how) {
@@ -40,9 +42,16 @@ function bump(how) {
40
42
  changed.push(FILES.tauri);
41
43
  }
42
44
  if (fs.existsSync(FILES.cargo)) {
43
- const t = fs.readFileSync(FILES.cargo, 'utf8').replace(/^version = "[^"]+"/m, `version = "${to}"`);
44
- fs.writeFileSync(FILES.cargo, t);
45
+ const toml = fs.readFileSync(FILES.cargo, 'utf8');
46
+ fs.writeFileSync(FILES.cargo, toml.replace(/^version = "[^"]+"/m, `version = "${to}"`));
45
47
  changed.push(FILES.cargo);
48
+ const name = (/^name = "([^"]+)"/m.exec(toml) || [])[1];
49
+ if (name && fs.existsSync(FILES.lock)) {
50
+ const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
51
+ const lock = fs.readFileSync(FILES.lock, 'utf8');
52
+ const t = lock.replace(new RegExp(`(\\[\\[package\\]\\]\\r?\\nname = "${esc}"\\r?\\nversion = )"[^"]+"`), `$1"${to}"`);
53
+ if (t !== lock) { fs.writeFileSync(FILES.lock, t); changed.push(FILES.lock); }
54
+ }
46
55
  }
47
56
  return { from: pkg.version, to, changed };
48
57
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- // ๋ฆด๋ฆฌ์Šค ๋’ค Homebrew tap(cozyuj/homebrew-tap)์˜ caskยทformula ๋ฒ„์ „๊ณผ sha256 ์„ ๊ฐฑ์‹ ํ•œ๋‹ค.
3
+ // ๋ฆด๋ฆฌ์Šค ๋’ค Homebrew tap(cozyuj/homebrew-tap)์˜ caskยทformula ๋ฒ„์ „๊ณผ sha256 ์„ ๊ฐฑ์‹ ํ•œ๋‹ค. CI ์—์„  .github/workflows/homebrew.yml ์ด ๋Œ๋ฆฐ๋‹ค.
4
4
  // node scripts/update-homebrew.js [--tap ../homebrew-tap] [--version 0.25.0]
5
5
  // cask: GitHub Release ์˜ AI.Office_<v>_aarch64.dmg, formula: npm ์˜ claude-ai-office-<v>.tgz (npm ์— ๊ทธ ๋ฒ„์ „์ด ์žˆ์„ ๋•Œ๋งŒ)
6
6
  const fs = require('fs');
@@ -10,7 +10,8 @@ const crypto = require('crypto');
10
10
  const arg = (name, def) => { const i = process.argv.indexOf(`--${name}`); return i >= 0 ? process.argv[i + 1] : def; };
11
11
  const ROOT = path.join(__dirname, '..');
12
12
  const TAP = path.resolve(arg('tap', path.join(ROOT, '..', 'homebrew-tap')));
13
- const VERSION = arg('version', require(path.join(ROOT, 'package.json')).version);
13
+ const VERSION = arg('version', require(path.join(ROOT, 'package.json')).version).replace(/^v/, '');
14
+ if (!/^\d+\.\d+\.\d+$/.test(VERSION)) { console.error(`๋ฒ„์ „ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค: '${VERSION}' โ€” ์ˆซ์ž x.y.z ๋กœ ๋„ฃ์œผ์„ธ์š” (์˜ˆ: 0.29.5)`); process.exit(1); }
14
15
 
15
16
  async function sha256Of(url) {
16
17
  const res = await fetch(url, { redirect: 'follow' });
package/server.js CHANGED
@@ -96,7 +96,7 @@ async function handleFloorStyle(req, res) {
96
96
  const id = typeof body.id === 'string' ? body.id : '';
97
97
  if (!id) { res.writeHead(400).end(); return; }
98
98
  const floor = state.setFloorStyle(id, typeof body.style === 'string' ? body.style : null);
99
- res.writeHead(200, { 'Content-Type': MIME['.json'], 'Access-Control-Allow-Origin': '*' });
99
+ res.writeHead(200, { 'Content-Type': MIME['.json'], ...cors(req) });
100
100
  res.end(JSON.stringify({ floor }));
101
101
  }
102
102
 
@@ -125,7 +125,7 @@ async function handleHook(req, res) {
125
125
 
126
126
  function handleEvents(req, res) {
127
127
  res.writeHead(200, {
128
- 'Access-Control-Allow-Origin': '*',
128
+ ...cors(req),
129
129
  'Content-Type': 'text/event-stream',
130
130
  'Cache-Control': 'no-cache',
131
131
  Connection: 'keep-alive',
@@ -150,13 +150,42 @@ function serveStatic(req, res) {
150
150
  });
151
151
  }
152
152
 
153
+ // ---------- ์ ‘๊ทผ ๊ฒ€์‚ฌ (BE-21) ----------
154
+ // ๋กœ์ปฌ ์„œ๋ฒ„๋ผ๋„ ๋ธŒ๋ผ์šฐ์ €์— ์—ด๋ฆฐ ๋‹ค๋ฅธ ์›น์‚ฌ์ดํŠธ๊ฐ€ fetchยทEventSource ๋กœ ์„ธ์…˜ ๊ธฐ๋ก(์ œ๋ชฉยท์š”์ฒญยท๊ฒฝ๋กœ)์„ ์ฝ๊ฑฐ๋‚˜ ๊ฐ€์งœ ์ด๋ฒคํŠธ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
155
+ // - Host ๊ฐ€ ๋ฃจํ”„๋ฐฑ ์ด๋ฆ„์ด ์•„๋‹ˆ๋ฉด ๊ฑฐ์ ˆ (DNS ๋ฆฌ๋ฐ”์ธ๋”ฉ ์ฐจ๋‹จ)
156
+ // - Origin ์ด ์žˆ์œผ๋ฉด AI Office ์ž์‹ ๊ณผ ๋ฐ์Šคํฌํ†ฑ ์•ฑ ์ฐฝ๋งŒ ํ—ˆ์šฉ. ํ›…(emit.js)ยทCLIยทcurl ์€ Origin ์ด ์—†์–ด ๊ทธ๋Œ€๋กœ ํ†ต๊ณผ
157
+ const LOOPBACK = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
158
+ function hostAllowed(req) {
159
+ const host = String(req.headers.host || '').toLowerCase();
160
+ const name = host.startsWith('[') ? host.slice(0, host.indexOf(']') + 1) : host.replace(/:\d+$/, '');
161
+ return LOOPBACK.has(name);
162
+ }
163
+ function originAllowed(origin) {
164
+ if (!origin) return true;
165
+ let u;
166
+ try { u = new URL(origin); } catch { return false; }
167
+ const host = u.hostname.toLowerCase();
168
+ if (u.protocol === 'tauri:' && host === 'localhost') return true; // macOS ์•ฑ ์ฐฝ
169
+ if ((u.protocol === 'http:' || u.protocol === 'https:') && host === 'tauri.localhost') return true; // Windows ์•ฑ ์ฐฝ
170
+ return u.protocol === 'http:' && LOOPBACK.has(host) && Number(u.port || 80) === Number(PORT); // ์ด ์„œ๋ฒ„๊ฐ€ ๋„์šด ์›น ํ™”๋ฉด
171
+ }
172
+ /** ํ—ˆ์šฉ๋œ Origin ์ด๋ฉด ๊ทธ ๊ฐ’์„ ๋Œ๋ ค์ฃผ๋Š” CORS ํ—ค๋” (* ๋Š” ์“ฐ์ง€ ์•Š๋Š”๋‹ค) */
173
+ function cors(req) {
174
+ const origin = req.headers.origin;
175
+ return origin && originAllowed(origin) ? { 'Access-Control-Allow-Origin': origin, Vary: 'Origin' } : {};
176
+ }
177
+
153
178
  const server = http.createServer((req, res) => {
179
+ if (!hostAllowed(req) || !originAllowed(req.headers.origin)) {
180
+ res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
181
+ return res.end('forbidden: AI Office ๋Š” ์ด ์ปดํ“จํ„ฐ์˜ AI Office ํ™”๋ฉด์—์„œ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค\n');
182
+ }
154
183
  const { pathname } = new URL(req.url, 'http://x');
155
184
  if (req.method === 'POST' && pathname === '/hook') return handleHook(req, res);
156
185
  if (req.method === 'GET' && pathname === '/events') return handleEvents(req, res);
157
186
  if (req.method === 'POST' && pathname === '/api/floors/style') return handleFloorStyle(req, res);
158
- if (req.method === 'POST' && pathname === '/api/demo/start') { startDemo(); res.writeHead(200, { 'Content-Type': MIME['.json'], 'Access-Control-Allow-Origin': '*' }); return res.end(JSON.stringify({ demo: true })); }
159
- if (req.method === 'POST' && pathname === '/api/demo/stop') { const removed = stopDemo(); res.writeHead(200, { 'Content-Type': MIME['.json'], 'Access-Control-Allow-Origin': '*' }); return res.end(JSON.stringify({ demo: false, removed })); }
187
+ if (req.method === 'POST' && pathname === '/api/demo/start') { startDemo(); res.writeHead(200, { 'Content-Type': MIME['.json'], ...cors(req) }); return res.end(JSON.stringify({ demo: true })); }
188
+ if (req.method === 'POST' && pathname === '/api/demo/stop') { const removed = stopDemo(); res.writeHead(200, { 'Content-Type': MIME['.json'], ...cors(req) }); return res.end(JSON.stringify({ demo: false, removed })); }
160
189
  // ๋๋‚œ/์ž๋ฆฌ ๋น„์šด ์„ธ์…˜์„ ๋ชฉ๋ก์—์„œ ์น˜์šด๋‹ค (TTL ์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ ). state.remove โ†’ SSE 'remove' ๋กœ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์— ์ „ํŒŒ
161
190
  const del = /^\/api\/sessions\/([^/]+)$/.exec(pathname);
162
191
  if (del && req.method === 'DELETE') {
@@ -168,12 +197,12 @@ const server = http.createServer((req, res) => {
168
197
  const s0 = { session_id: id, hook_event_name: 'SessionEnd', reason: 'dismissed' };
169
198
  if (!String(id).startsWith('demo')) journal.append(Date.now(), s0);
170
199
  }
171
- res.writeHead(found ? 200 : 404, { 'Content-Type': MIME['.json'], 'Access-Control-Allow-Origin': '*' });
200
+ res.writeHead(found ? 200 : 404, { 'Content-Type': MIME['.json'], ...cors(req) });
172
201
  return res.end(JSON.stringify({ removed: found, sessionId: id }));
173
202
  }
174
203
  if (req.method === 'GET' && pathname === '/api/state') {
175
204
  // ๋ฐ์Šคํฌํƒ‘ ์•ฑ(tauri://) ๋กœ๋”๊ฐ€ ์„œ๋ฒ„ ์ค€๋น„ ์—ฌ๋ถ€๋ฅผ ๋ฌป๋Š”๋‹ค โ€” ๋กœ์ปฌ ์ „์šฉ ์„œ๋ฒ„๋ผ ์ „์ฒด ํ—ˆ์šฉ
176
- res.writeHead(200, { 'Content-Type': MIME['.json'], 'Access-Control-Allow-Origin': '*' });
205
+ res.writeHead(200, { 'Content-Type': MIME['.json'], ...cors(req) });
177
206
  return res.end(JSON.stringify({ ...withPorts(state.snapshot()), installDir: __dirname }));
178
207
  }
179
208
  if (req.method === 'GET') return serveStatic(req, res);