saeeol 1.4.7 → 1.4.9
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 +92 -111
- package/package.json +2 -2
package/desktop.js
CHANGED
|
@@ -1,85 +1,81 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// fabulist —
|
|
4
|
-
|
|
5
|
-
const {
|
|
3
|
+
// fabulist — saeeol server + Electron desktop with React UI
|
|
4
|
+
"use strict"
|
|
5
|
+
const { existsSync, realpathSync, createReadStream, statSync, mkdirSync, writeFileSync } = require("fs")
|
|
6
|
+
const { join, dirname, extname } = require("path")
|
|
6
7
|
const { spawn } = require("child_process")
|
|
7
8
|
const http = require("http")
|
|
8
9
|
|
|
10
|
+
// Resolve script directory (works with symlinks and global installs)
|
|
11
|
+
function getScriptDir() {
|
|
12
|
+
// 1. process.argv[1] (how npm bin scripts invoke us)
|
|
13
|
+
if (process.argv[1]) {
|
|
14
|
+
try { const r = realpathSync(process.argv[1]); if (r) return dirname(r) } catch {}
|
|
15
|
+
}
|
|
16
|
+
// 2. __filename (CJS context)
|
|
17
|
+
try { if (typeof __filename === "string") return dirname(realpathSync(__filename)) } catch {}
|
|
18
|
+
// 3. require.main
|
|
19
|
+
try { if (require.main && require.main.filename) return dirname(realpathSync(require.main.filename)) } catch {}
|
|
20
|
+
// 4. find saeeol package in global node_modules
|
|
21
|
+
try { const p = require.resolve("saeeol/cli.js"); return dirname(p) } catch {}
|
|
22
|
+
return process.cwd()
|
|
23
|
+
}
|
|
24
|
+
const SCRIPT_DIR = getScriptDir()
|
|
25
|
+
|
|
9
26
|
const MIME = {
|
|
10
|
-
".html": "text/html",
|
|
11
|
-
".
|
|
12
|
-
".
|
|
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",
|
|
27
|
+
".html": "text/html", ".js": "text/javascript", ".css": "text/css",
|
|
28
|
+
".json": "application/json", ".png": "image/png", ".ico": "image/x-icon",
|
|
29
|
+
".svg": "image/svg+xml", ".woff2": "font/woff2", ".wasm": "application/wasm",
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
function getBinaryName() {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const a = archMap[process.arch] || process.arch
|
|
26
|
-
return `saeeol-${p}-${a}`
|
|
33
|
+
const a = { x64: "x64", arm64: "arm64" }
|
|
34
|
+
const p = { win32: "windows", darwin: "darwin", linux: "linux" }
|
|
35
|
+
return `saeeol-${p[process.platform] || process.platform}-${a[process.arch] || process.arch}`
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
function findBinary() {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const local = join(scriptDir, "node_modules", binaryName, binaryRel)
|
|
39
|
+
const name = getBinaryName()
|
|
40
|
+
const rel = process.platform === "win32" ? "bin/saeeol.exe" : "bin/saeeol"
|
|
41
|
+
// node_modules of this package
|
|
42
|
+
const local = join(SCRIPT_DIR, "node_modules", name, rel)
|
|
35
43
|
if (existsSync(local)) return local
|
|
36
|
-
|
|
37
|
-
let
|
|
44
|
+
// walk up
|
|
45
|
+
let dir = SCRIPT_DIR
|
|
38
46
|
for (let i = 0; i < 10; i++) {
|
|
39
|
-
const
|
|
40
|
-
if (existsSync(
|
|
41
|
-
const parent = dirname(
|
|
42
|
-
if (parent ===
|
|
43
|
-
|
|
47
|
+
const c = join(dir, "node_modules", name, rel)
|
|
48
|
+
if (existsSync(c)) return c
|
|
49
|
+
const parent = dirname(dir)
|
|
50
|
+
if (parent === dir) break
|
|
51
|
+
dir = parent
|
|
44
52
|
}
|
|
45
|
-
|
|
46
|
-
const dl = join(
|
|
53
|
+
// download fallback
|
|
54
|
+
const dl = join(SCRIPT_DIR, "download", process.platform === "win32" ? `${name}.exe` : "saeeol")
|
|
47
55
|
if (existsSync(dl)) return dl
|
|
48
|
-
|
|
49
56
|
return null
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
function findElectron() {
|
|
53
|
-
const scriptDir = dirname(realpathSync(__filename))
|
|
54
60
|
const exe = process.platform === "win32" ? "electron.exe" : "electron"
|
|
55
|
-
const p = join(
|
|
56
|
-
|
|
57
|
-
return null
|
|
61
|
+
const p = join(SCRIPT_DIR, "node_modules", "electron", "dist", exe)
|
|
62
|
+
return existsSync(p) ? p : null
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
function serveStatic(webDir, port) {
|
|
61
|
-
return new Promise((resolve) => {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
62
67
|
const server = http.createServer((req, res) => {
|
|
63
|
-
let
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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)
|
|
68
|
+
let path = req.url.split("?")[0]
|
|
69
|
+
if (path === "/") path = "/index.html"
|
|
70
|
+
const fp = join(webDir, path)
|
|
71
|
+
if (!existsSync(fp)) { res.writeHead(404); res.end("not found"); return }
|
|
72
|
+
const ct = MIME[extname(fp)] || "application/octet-stream"
|
|
73
|
+
const s = statSync(fp)
|
|
74
|
+
res.writeHead(200, { "Content-Type": ct, "Content-Length": s.size })
|
|
75
|
+
createReadStream(fp).pipe(res)
|
|
82
76
|
})
|
|
77
|
+
server.listen(port, "127.0.0.1", () => resolve(server))
|
|
78
|
+
server.on("error", reject)
|
|
83
79
|
})
|
|
84
80
|
}
|
|
85
81
|
|
|
@@ -87,100 +83,85 @@ async function main() {
|
|
|
87
83
|
const binaryPath = findBinary()
|
|
88
84
|
if (!binaryPath) {
|
|
89
85
|
console.error(`saeeol binary not found (${getBinaryName()})`)
|
|
90
|
-
console.error(
|
|
86
|
+
console.error("Run: npm install saeeol")
|
|
91
87
|
process.exit(1)
|
|
92
88
|
}
|
|
93
89
|
|
|
94
90
|
const electronPath = findElectron()
|
|
95
|
-
const webDir = join(
|
|
91
|
+
const webDir = join(SCRIPT_DIR, "web")
|
|
96
92
|
const hasWebUI = existsSync(join(webDir, "index.html"))
|
|
97
93
|
|
|
94
|
+
// Fallback to terminal mode if no electron or no web UI
|
|
98
95
|
if (!electronPath || !hasWebUI) {
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
}
|
|
96
|
+
console.log(!electronPath ? "Electron not found." : "Web UI not found.")
|
|
97
|
+
console.log("Starting in terminal mode.\n")
|
|
105
98
|
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
106
|
-
stdio: "inherit",
|
|
107
|
-
env: { ...process.env },
|
|
108
|
-
windowsHide: false,
|
|
99
|
+
stdio: "inherit", env: { ...process.env }, windowsHide: false,
|
|
109
100
|
})
|
|
110
101
|
child.on("exit", (code) => process.exit(code || 0))
|
|
111
102
|
return
|
|
112
103
|
}
|
|
113
104
|
|
|
114
|
-
// Find ports
|
|
115
105
|
const webPort = 31000 + Math.floor(Math.random() * 5000)
|
|
116
106
|
const apiPort = 32000 + Math.floor(Math.random() * 5000)
|
|
117
107
|
|
|
118
108
|
// 1. Start saeeol API server
|
|
119
109
|
console.log("Starting saeeol API server...")
|
|
120
110
|
const api = spawn(binaryPath, ["web", "--port", String(apiPort), "--hostname", "127.0.0.1"], {
|
|
121
|
-
stdio: "pipe",
|
|
122
|
-
env: { ...process.env },
|
|
123
|
-
windowsHide: true,
|
|
111
|
+
stdio: "pipe", env: { ...process.env }, windowsHide: true,
|
|
124
112
|
})
|
|
125
113
|
api.stdout.on("data", (d) => { const m = d.toString().trim(); if (m) console.log(`[api] ${m}`) })
|
|
126
114
|
api.stderr.on("data", (d) => { const m = d.toString().trim(); if (m) console.error(`[api] ${m}`) })
|
|
127
115
|
|
|
128
|
-
// 2.
|
|
116
|
+
// 2. Serve React UI
|
|
129
117
|
const staticServer = await serveStatic(webDir, webPort)
|
|
130
|
-
console.log(`UI
|
|
118
|
+
console.log(`UI: http://127.0.0.1:${webPort}`)
|
|
131
119
|
|
|
132
|
-
// 3. Wait for API
|
|
120
|
+
// 3. Wait for API
|
|
133
121
|
await new Promise((r) => setTimeout(r, 3000))
|
|
134
122
|
|
|
135
|
-
// 4.
|
|
123
|
+
// 4. Write Electron app inline
|
|
136
124
|
console.log("Opening FABULIST desktop...")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
writeFileSync(join(electronAppDir, "package.json"), '{"name":"fabulist-desktop"}')
|
|
142
|
-
writeFileSync(join(electronAppDir, "main.js"), `
|
|
125
|
+
const appDir = join(SCRIPT_DIR, ".desktop-cache")
|
|
126
|
+
mkdirSync(appDir, { recursive: true })
|
|
127
|
+
writeFileSync(join(appDir, "package.json"), '{"name":"fabulist-desktop"}')
|
|
128
|
+
writeFileSync(join(appDir, "main.js"), `
|
|
143
129
|
const { app, BrowserWindow } = require("electron")
|
|
144
|
-
|
|
145
|
-
|
|
130
|
+
let mainWindow = null
|
|
146
131
|
function createWindow() {
|
|
147
|
-
|
|
148
|
-
width: 1400,
|
|
149
|
-
|
|
150
|
-
minWidth: 800,
|
|
151
|
-
minHeight: 600,
|
|
152
|
-
title: "파불리스트 — FABULIST",
|
|
132
|
+
mainWindow = new BrowserWindow({
|
|
133
|
+
width: 1400, height: 900, minWidth: 800, minHeight: 600,
|
|
134
|
+
title: "\uD30C\uBD88\uB9AC\uC2A4\uD2B8 \u2014 FABULIST",
|
|
153
135
|
autoHideMenuBar: true,
|
|
136
|
+
show: false,
|
|
154
137
|
webPreferences: { nodeIntegration: false, contextIsolation: true },
|
|
155
138
|
})
|
|
156
|
-
|
|
139
|
+
mainWindow.once("ready-to-show", () => mainWindow.show())
|
|
140
|
+
mainWindow.loadURL("http://127.0.0.1:${webPort}")
|
|
141
|
+
mainWindow.on("closed", () => { mainWindow = null })
|
|
157
142
|
}
|
|
158
|
-
|
|
159
|
-
app.
|
|
160
|
-
app.on("
|
|
143
|
+
app.on("ready", createWindow)
|
|
144
|
+
app.on("window-all-closed", () => { /* keep running */ })
|
|
145
|
+
app.on("activate", () => { if (!mainWindow) createWindow() })
|
|
161
146
|
`)
|
|
162
147
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
148
|
+
// Remove ELECTRON_RUN_AS_NODE if set (prevents Electron from running as Node)
|
|
149
|
+
const env = { ...process.env }
|
|
150
|
+
delete env.ELECTRON_RUN_AS_NODE
|
|
151
|
+
|
|
152
|
+
const electron = spawn(electronPath, [join(appDir, "main.js")], {
|
|
153
|
+
stdio: "ignore",
|
|
154
|
+
env,
|
|
155
|
+
detached: true,
|
|
166
156
|
windowsHide: false,
|
|
167
157
|
})
|
|
158
|
+
electron.unref()
|
|
168
159
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
staticServer.close()
|
|
172
|
-
process.exit(code || 0)
|
|
173
|
-
})
|
|
160
|
+
console.log("FABULIST desktop opened! Close the window to exit.")
|
|
161
|
+
console.log("Press Ctrl+C to stop the server.")
|
|
174
162
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
electron.kill()
|
|
178
|
-
staticServer.close()
|
|
179
|
-
process.exit(0)
|
|
180
|
-
})
|
|
163
|
+
const cleanup = () => { api.kill(); staticServer.close(); process.exit(0) }
|
|
164
|
+
process.on("SIGINT", cleanup)
|
|
181
165
|
}
|
|
182
166
|
|
|
183
|
-
main().catch((err) => {
|
|
184
|
-
console.error(err.message)
|
|
185
|
-
process.exit(1)
|
|
186
|
-
})
|
|
167
|
+
main().catch((err) => { console.error(err.message); process.exit(1) })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saeeol",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.9",
|
|
4
4
|
"description": "AI agent engine for SAEEOL — CLI + Desktop in one package",
|
|
5
5
|
"author": "byfabulist",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"postinstall": "node postinstall.js"
|
|
23
23
|
},
|
|
24
24
|
"optionalDependencies": {
|
|
25
|
-
"saeeol-windows-x64": "1.4.
|
|
25
|
+
"saeeol-windows-x64": "1.4.8",
|
|
26
26
|
"saeeol-windows-x64-baseline": "1.4.5",
|
|
27
27
|
"saeeol-darwin-x64": "1.4.5",
|
|
28
28
|
"saeeol-darwin-x64-baseline": "1.4.5",
|