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 +1 -1
- package/README.md +1 -1
- package/docs/ARCHITECTURE.md +161 -86
- package/package.json +1 -1
- package/scripts/bump-version.js +11 -2
- package/scripts/update-homebrew.js +3 -2
- package/server.js +35 -6
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"
|
|
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 ์
๋ฐ์ดํธ ์ ์ฉ (๋ค์ ์์)" ์ด ์ผ์ง๋๋ค. ๋๋ฅด๋ฉด ์๋ฒ๋ฅผ ๋ด๋ฆฌ๊ณ ์ค์นํ ๋ค ๋ค์ ์์ํฉ๋๋ค. ๋ฌป์ง ์๊ณ ์ค์นํ๊ฑฐ๋ ์ฐ๋ ์ค์ ์ฑ ํ์ผ์ ๋ฐ๊พธ์ง ์์ต๋๋ค. ๋ฉ๋ด์ "์
๋ฐ์ดํธ ํ์ธ"
|
|
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
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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 (
|
|
11
|
+
Claude Code ์ธ์
(์ฌ๋ฌ ํฐ๋ฏธ๋, ์ฌ๋ฌ ํ๋ก์ ํธ, ์ํฌํธ๋ฆฌ)
|
|
12
|
+
โ ~/.claude/settings.json hooks (11๊ฐ ์ด๋ฒคํธ)
|
|
11
13
|
โผ
|
|
12
|
-
hooks/emit.
|
|
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
|
-
โโ
|
|
17
|
-
โโ
|
|
18
|
-
โโ
|
|
19
|
-
|
|
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 ์์ด์๋ฉํธ๋ฆญ ์ฌ๋ฌด์ค:
|
|
25
|
-
|
|
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 { // ํ๋ก์ ํธ = ์ธต.
|
|
36
|
-
id: string //
|
|
37
|
-
name: string // basename(
|
|
38
|
-
no: number // ์ถํ ์์๋ก 1F, 2F, ...
|
|
39
|
-
|
|
40
|
-
|
|
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 //
|
|
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:
|
|
50
|
-
pendingSubagents: string[]
|
|
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" |
|
|
65
|
+
name: string // "Claude" | Agent ํด description | agent_type. ํ๋ฉด ์ด๋ฆ์ ํด๋ผ์ด์ธํธ names.js ๊ฐ ๋ณ๋ช
์ผ๋ก ๋ฐ๊พผ๋ค
|
|
58
66
|
room: RoomId // ์ง๊ธ ์์ด์ผ ํ ๋ฐฉ
|
|
59
|
-
activity: ActivityId // 'think' | 'read' | 'search' | 'run' | 'edit' | 'web' | 'delegate' | '
|
|
60
|
-
bubble: string
|
|
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'
|
|
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 | "๊ณ ๋ฏผ ์คโฆ" |
|
|
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 ์ ํ.
|
|
120
|
-
| GET | `/events` | SSE. ์ ์ ์ฆ์ `snapshot` 1ํ,
|
|
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
|
|
127
|
-
event: update data: { session
|
|
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,
|
|
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๋จ ๋ ์ด์์ (์ข
|
|
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
|
|
141
|
-
store.js floors/sessions/log ๋ฏธ๋ฌ, ์ ํ๋
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
sprites.js
|
|
151
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
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
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `scripts/
|
|
182
|
-
| `
|
|
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**: ์ค์น ๋ง์ฐฐ์ ์์ ๊ณ
|
|
230
|
+
- **์์กด์ฑ 0**: ์ค์น ๋ง์ฐฐ์ ์์ ๊ณ ๋ฐ์คํฌํฑ ์ฑ(์ฌ์ด๋์นด Node)์ผ๋ก ๊ทธ๋๋ก ์ฎ๊ธฐ๊ธฐ ์ํด Node ๋ด์ฅ ๋ชจ๋๊ณผ ๋ฐ๋๋ผ JS ๋ง ์ด๋ค.
|
|
187
231
|
- **SSE, WebSocket ์๋**: ์๋ฒโ๋ธ๋ผ์ฐ์ ๋จ๋ฐฉํฅ์ด๋ฉด ์ถฉ๋ถํ๊ณ ์ฌ์ ์์ด ๋ธ๋ผ์ฐ์ ์ ๋ด์ฅ๋ผ ์๋ค.
|
|
188
232
|
- **์ํ๋ ์๋ฒ์์ ๊ณ์ฐ**: ํ
์ด๋ฒคํธ ํด์ ๊ท์น์ด ํ ๊ณณ์ ์์ด์ผ ํ
์คํธํ๊ธฐ ์ฝ๊ณ , ๋ธ๋ผ์ฐ์ ๋ "์ด๋๋ก ๊ฐ์ ๋ฌด์จ ๋งํ์ " ๋ง ๋ฐ๋๋ค.
|
|
189
|
-
- **๊ทธ๋ฆผ ์์ฐ ์์**: ๊ฐ๊ตฌยท์บ๋ฆญํฐ๋ฅผ canvas ์ฝ๋๋ก
|
|
190
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/scripts/bump-version.js
CHANGED
|
@@ -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
|
|
44
|
-
fs.writeFileSync(FILES.cargo,
|
|
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'],
|
|
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
|
-
|
|
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'],
|
|
159
|
-
if (req.method === 'POST' && pathname === '/api/demo/stop') { const removed = stopDemo(); res.writeHead(200, { 'Content-Type': MIME['.json'],
|
|
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'],
|
|
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'],
|
|
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);
|