saeeol 1.4.5 → 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 CHANGED
@@ -1,9 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // saeeol desktop — launches saeeol web server + Electron window
4
- const { existsSync, realpathSync } = require("fs")
5
- const { join, dirname } = require("path")
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
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
+ }
7
20
 
8
21
  function getBinaryName() {
9
22
  const archMap = { x64: "x64", arm64: "arm64" }
@@ -18,11 +31,9 @@ function findBinary() {
18
31
  const binaryRel = process.platform === "win32" ? "bin/saeeol.exe" : "bin/saeeol"
19
32
  const scriptDir = dirname(realpathSync(__filename))
20
33
 
21
- // 1. node_modules of this package
22
34
  const local = join(scriptDir, "node_modules", binaryName, binaryRel)
23
35
  if (existsSync(local)) return local
24
36
 
25
- // 2. Walk up
26
37
  let current = scriptDir
27
38
  for (let i = 0; i < 10; i++) {
28
39
  const candidate = join(current, "node_modules", binaryName, binaryRel)
@@ -32,7 +43,6 @@ function findBinary() {
32
43
  current = parent
33
44
  }
34
45
 
35
- // 3. Download dir
36
46
  const dl = join(scriptDir, "download", process.platform === "win32" ? `${binaryName}.exe` : "saeeol")
37
47
  if (existsSync(dl)) return dl
38
48
 
@@ -41,11 +51,38 @@ function findBinary() {
41
51
 
42
52
  function findElectron() {
43
53
  const scriptDir = dirname(realpathSync(__filename))
44
- const electronPath = join(scriptDir, "node_modules", "electron", "dist", process.platform === "win32" ? "electron.exe" : "electron")
45
- if (existsSync(electronPath)) return electronPath
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
46
57
  return null
47
58
  }
48
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
+
49
86
  async function main() {
50
87
  const binaryPath = findBinary()
51
88
  if (!binaryPath) {
@@ -55,11 +92,16 @@ async function main() {
55
92
  }
56
93
 
57
94
  const electronPath = findElectron()
58
-
59
- // If no electron, fall back to CLI mode
60
- if (!electronPath) {
61
- console.log("Electron not found. Install with: npm install -g saeeol")
62
- console.log("Starting in terminal mode...\n")
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
+ }
63
105
  const child = spawn(binaryPath, process.argv.slice(2), {
64
106
  stdio: "inherit",
65
107
  env: { ...process.env },
@@ -69,32 +111,37 @@ async function main() {
69
111
  return
70
112
  }
71
113
 
72
- // Find free port
73
- const port = 30000 + Math.floor(Math.random() * 10000)
74
-
75
- console.log(`Starting saeeol server on port ${port}...`)
114
+ // Find ports
115
+ const webPort = 31000 + Math.floor(Math.random() * 5000)
116
+ const apiPort = 32000 + Math.floor(Math.random() * 5000)
76
117
 
77
- // Start saeeol web in background
78
- const server = spawn(binaryPath, ["web", "--port", String(port), "--hostname", "127.0.0.1"], {
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"], {
79
121
  stdio: "pipe",
80
122
  env: { ...process.env },
81
123
  windowsHide: true,
82
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}`) })
83
127
 
84
- server.stdout.on("data", (data) => {
85
- const msg = data.toString().trim()
86
- if (msg) console.log(`[server] ${msg}`)
87
- })
88
- server.stderr.on("data", (data) => {
89
- const msg = data.toString().trim()
90
- if (msg) console.error(`[server] ${msg}`)
91
- })
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}`)
92
131
 
93
- // Create a minimal Electron app that loads the saeeol web URL
94
- const electronApp = join(__dirname, "desktop-app.js")
95
- const desktopAppCode = `
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"), `
96
143
  const { app, BrowserWindow } = require("electron")
97
- const url = "http://127.0.0.1:${port}"
144
+ const path = require("path")
98
145
 
99
146
  function createWindow() {
100
147
  const win = new BrowserWindow({
@@ -106,34 +153,31 @@ function createWindow() {
106
153
  autoHideMenuBar: true,
107
154
  webPreferences: { nodeIntegration: false, contextIsolation: true },
108
155
  })
109
- win.loadURL(url)
156
+ win.loadURL("http://127.0.0.1:${webPort}")
110
157
  }
111
158
 
112
159
  app.whenReady().then(createWindow)
113
- app.on("window-all-closed", () => {
114
- app.quit()
115
- process.kill(${server.pid})
116
- process.exit(0)
117
- })
118
- `
119
- require("fs").writeFileSync(electronApp, desktopAppCode)
160
+ app.on("window-all-closed", () => app.quit())
161
+ `)
120
162
 
121
- // Wait a bit for server to start
122
- await new Promise((r) => setTimeout(r, 3000))
123
-
124
- console.log("Opening FABULIST desktop...")
125
-
126
- const electron = spawn(electronPath, [electronApp], {
163
+ const electron = spawn(electronPath, [join(electronAppDir, "main.js")], {
127
164
  stdio: "inherit",
128
165
  env: { ...process.env },
129
166
  windowsHide: false,
130
167
  })
131
168
 
132
169
  electron.on("exit", (code) => {
133
- server.kill()
134
- try { require("fs").unlinkSync(electronApp) } catch {}
170
+ api.kill()
171
+ staticServer.close()
135
172
  process.exit(code || 0)
136
173
  })
174
+
175
+ process.on("SIGINT", () => {
176
+ api.kill()
177
+ electron.kill()
178
+ staticServer.close()
179
+ process.exit(0)
180
+ })
137
181
  }
138
182
 
139
183
  main().catch((err) => {
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "saeeol",
3
- "version": "1.4.5",
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",
@@ -14,7 +15,8 @@
14
15
  "files": [
15
16
  "cli.js",
16
17
  "desktop.js",
17
- "postinstall.js"
18
+ "postinstall.js",
19
+ "web/**/*"
18
20
  ],
19
21
  "scripts": {
20
22
  "postinstall": "node postinstall.js"