saeeol 1.4.4 → 1.4.6

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/desktop.js ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+
3
+ // fabulist — launches saeeol server + Electron desktop with React UI
4
+ const { existsSync, realpathSync, createReadStream, statSync, readdirSync } = require("fs")
5
+ const { join, dirname, extname, mimeLookup } = require("path")
6
+ const { spawn } = require("child_process")
7
+ const http = require("http")
8
+
9
+ const MIME = {
10
+ ".html": "text/html",
11
+ ".js": "text/javascript",
12
+ ".css": "text/css",
13
+ ".json": "application/json",
14
+ ".png": "image/png",
15
+ ".ico": "image/x-icon",
16
+ ".svg": "image/svg+xml",
17
+ ".woff2": "font/woff2",
18
+ ".wasm": "application/wasm",
19
+ }
20
+
21
+ function getBinaryName() {
22
+ const archMap = { x64: "x64", arm64: "arm64" }
23
+ const platformMap = { win32: "windows", darwin: "darwin", linux: "linux" }
24
+ const p = platformMap[process.platform] || process.platform
25
+ const a = archMap[process.arch] || process.arch
26
+ return `saeeol-${p}-${a}`
27
+ }
28
+
29
+ function findBinary() {
30
+ const binaryName = getBinaryName()
31
+ const binaryRel = process.platform === "win32" ? "bin/saeeol.exe" : "bin/saeeol"
32
+ const scriptDir = dirname(realpathSync(__filename))
33
+
34
+ const local = join(scriptDir, "node_modules", binaryName, binaryRel)
35
+ if (existsSync(local)) return local
36
+
37
+ let current = scriptDir
38
+ for (let i = 0; i < 10; i++) {
39
+ const candidate = join(current, "node_modules", binaryName, binaryRel)
40
+ if (existsSync(candidate)) return candidate
41
+ const parent = dirname(current)
42
+ if (parent === current) break
43
+ current = parent
44
+ }
45
+
46
+ const dl = join(scriptDir, "download", process.platform === "win32" ? `${binaryName}.exe` : "saeeol")
47
+ if (existsSync(dl)) return dl
48
+
49
+ return null
50
+ }
51
+
52
+ function findElectron() {
53
+ const scriptDir = dirname(realpathSync(__filename))
54
+ const exe = process.platform === "win32" ? "electron.exe" : "electron"
55
+ const p = join(scriptDir, "node_modules", "electron", "dist", exe)
56
+ if (existsSync(p)) return p
57
+ return null
58
+ }
59
+
60
+ function serveStatic(webDir, port) {
61
+ return new Promise((resolve) => {
62
+ const server = http.createServer((req, res) => {
63
+ let urlPath = req.url.split("?")[0]
64
+ if (urlPath === "/") urlPath = "/index.html"
65
+
66
+ const filePath = join(webDir, urlPath)
67
+ if (!existsSync(filePath)) {
68
+ res.writeHead(404)
69
+ res.end("not found")
70
+ return
71
+ }
72
+
73
+ const ext = extname(filePath)
74
+ const ct = MIME[ext] || "application/octet-stream"
75
+ const stat = statSync(filePath)
76
+ res.writeHead(200, { "Content-Type": ct, "Content-Length": stat.size })
77
+ createReadStream(filePath).pipe(res)
78
+ })
79
+
80
+ server.listen(port, "127.0.0.1", () => {
81
+ resolve(server)
82
+ })
83
+ })
84
+ }
85
+
86
+ async function main() {
87
+ const binaryPath = findBinary()
88
+ if (!binaryPath) {
89
+ console.error(`saeeol binary not found (${getBinaryName()})`)
90
+ console.error(`Run: npm install saeeol`)
91
+ process.exit(1)
92
+ }
93
+
94
+ const electronPath = findElectron()
95
+ const webDir = join(dirname(realpathSync(__filename)), "web")
96
+ const hasWebUI = existsSync(join(webDir, "index.html"))
97
+
98
+ if (!electronPath || !hasWebUI) {
99
+ // Fallback: terminal mode
100
+ if (!electronPath) {
101
+ console.log("Electron not found, starting in terminal mode.\n")
102
+ } else {
103
+ console.log("Web UI not found, starting in terminal mode.\n")
104
+ }
105
+ const child = spawn(binaryPath, process.argv.slice(2), {
106
+ stdio: "inherit",
107
+ env: { ...process.env },
108
+ windowsHide: false,
109
+ })
110
+ child.on("exit", (code) => process.exit(code || 0))
111
+ return
112
+ }
113
+
114
+ // Find ports
115
+ const webPort = 31000 + Math.floor(Math.random() * 5000)
116
+ const apiPort = 32000 + Math.floor(Math.random() * 5000)
117
+
118
+ // 1. Start saeeol API server
119
+ console.log("Starting saeeol API server...")
120
+ const api = spawn(binaryPath, ["web", "--port", String(apiPort), "--hostname", "127.0.0.1"], {
121
+ stdio: "pipe",
122
+ env: { ...process.env },
123
+ windowsHide: true,
124
+ })
125
+ api.stdout.on("data", (d) => { const m = d.toString().trim(); if (m) console.log(`[api] ${m}`) })
126
+ api.stderr.on("data", (d) => { const m = d.toString().trim(); if (m) console.error(`[api] ${m}`) })
127
+
128
+ // 2. Start static file server for React UI
129
+ const staticServer = await serveStatic(webDir, webPort)
130
+ console.log(`UI server: http://127.0.0.1:${webPort}`)
131
+
132
+ // 3. Wait for API to be ready
133
+ await new Promise((r) => setTimeout(r, 3000))
134
+
135
+ // 4. Launch Electron with inline app
136
+ console.log("Opening FABULIST desktop...")
137
+
138
+ const electronAppDir = join(dirname(realpathSync(__filename__)), "desktop-app")
139
+ const { mkdirSync, writeFileSync } = require("fs")
140
+ mkdirSync(electronAppDir, { recursive: true })
141
+ writeFileSync(join(electronAppDir, "package.json"), '{"name":"fabulist-desktop"}')
142
+ writeFileSync(join(electronAppDir, "main.js"), `
143
+ const { app, BrowserWindow } = require("electron")
144
+ const path = require("path")
145
+
146
+ function createWindow() {
147
+ const win = new BrowserWindow({
148
+ width: 1400,
149
+ height: 900,
150
+ minWidth: 800,
151
+ minHeight: 600,
152
+ title: "파불리스트 — FABULIST",
153
+ autoHideMenuBar: true,
154
+ webPreferences: { nodeIntegration: false, contextIsolation: true },
155
+ })
156
+ win.loadURL("http://127.0.0.1:${webPort}")
157
+ }
158
+
159
+ app.whenReady().then(createWindow)
160
+ app.on("window-all-closed", () => app.quit())
161
+ `)
162
+
163
+ const electron = spawn(electronPath, [join(electronAppDir, "main.js")], {
164
+ stdio: "inherit",
165
+ env: { ...process.env },
166
+ windowsHide: false,
167
+ })
168
+
169
+ electron.on("exit", (code) => {
170
+ api.kill()
171
+ staticServer.close()
172
+ process.exit(code || 0)
173
+ })
174
+
175
+ process.on("SIGINT", () => {
176
+ api.kill()
177
+ electron.kill()
178
+ staticServer.close()
179
+ process.exit(0)
180
+ })
181
+ }
182
+
183
+ main().catch((err) => {
184
+ console.error(err.message)
185
+ process.exit(1)
186
+ })
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "saeeol",
3
- "version": "1.4.4",
4
- "description": "AI agent engine for SAEEOL",
3
+ "version": "1.4.6",
4
+ "description": "AI agent engine for SAEEOL — CLI + Desktop in one package",
5
+ "author": "byfabulist",
5
6
  "license": "Apache-2.0",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "https://github.com/byfabulist/fabulist"
9
10
  },
10
11
  "bin": {
11
- "saeeol": "cli.js"
12
+ "saeeol": "cli.js",
13
+ "fabulist": "desktop.js"
12
14
  },
13
15
  "files": [
14
16
  "cli.js",
15
- "postinstall.js"
17
+ "desktop.js",
18
+ "postinstall.js",
19
+ "web/**/*"
16
20
  ],
17
21
  "scripts": {
18
22
  "postinstall": "node postinstall.js"
@@ -29,7 +33,8 @@
29
33
  "saeeol-linux-arm64-musl": "1.4.3",
30
34
  "saeeol-linux-x64-musl": "1.4.3",
31
35
  "saeeol-linux-x64-musl-baseline": "1.4.3",
32
- "saeeol-win32-arm64": "1.4.3"
36
+ "saeeol-win32-arm64": "1.4.3",
37
+ "electron": "^35.2.1"
33
38
  },
34
39
  "publishConfig": {
35
40
  "access": "public",
@@ -44,6 +49,8 @@
44
49
  "cli",
45
50
  "coding",
46
51
  "assistant",
47
- "saeeol"
52
+ "saeeol",
53
+ "fabulist",
54
+ "desktop"
48
55
  ]
49
56
  }