saeeol 1.4.9 → 1.5.0
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/package.json +6 -51
- package/postinstall.mjs +189 -0
- package/cli.js +0 -65
- package/desktop.js +0 -167
- package/postinstall.js +0 -162
- package/web/assets/index-B3kbSgZV.js +0 -48353
- package/web/assets/index-VbqLvtTL.css +0 -277
- package/web/index.html +0 -13
package/package.json
CHANGED
|
@@ -1,56 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saeeol",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "AI agent engine for SAEEOL — CLI + Desktop in one package",
|
|
5
|
-
"
|
|
5
|
+
"bin": { "saeeol": "./bin/saeeol", "fabulist": "./bin/saeeol" },
|
|
6
|
+
"scripts": { "postinstall": "node ./postinstall.mjs" },
|
|
6
7
|
"license": "Apache-2.0",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
"bin": {
|
|
12
|
-
"saeeol": "cli.js",
|
|
13
|
-
"fabulist": "desktop.js"
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"cli.js",
|
|
17
|
-
"desktop.js",
|
|
18
|
-
"postinstall.js",
|
|
19
|
-
"web/**/*"
|
|
20
|
-
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"postinstall": "node postinstall.js"
|
|
23
|
-
},
|
|
24
|
-
"optionalDependencies": {
|
|
25
|
-
"saeeol-windows-x64": "1.4.8",
|
|
26
|
-
"saeeol-windows-x64-baseline": "1.4.5",
|
|
27
|
-
"saeeol-darwin-x64": "1.4.5",
|
|
28
|
-
"saeeol-darwin-x64-baseline": "1.4.5",
|
|
29
|
-
"saeeol-darwin-arm64": "1.4.5",
|
|
30
|
-
"saeeol-linux-x64": "1.4.5",
|
|
31
|
-
"saeeol-linux-x64-baseline": "1.4.5",
|
|
32
|
-
"saeeol-linux-arm64": "1.4.5",
|
|
33
|
-
"saeeol-linux-arm64-musl": "1.4.5",
|
|
34
|
-
"saeeol-linux-x64-musl": "1.4.5",
|
|
35
|
-
"saeeol-linux-x64-musl-baseline": "1.4.5",
|
|
36
|
-
"saeeol-win32-arm64": "1.4.5",
|
|
37
|
-
"electron": "^35.2.1"
|
|
38
|
-
},
|
|
39
|
-
"publishConfig": {
|
|
40
|
-
"access": "public",
|
|
41
|
-
"registry": "https://registry.npmjs.org"
|
|
42
|
-
},
|
|
43
|
-
"engines": {
|
|
44
|
-
"node": ">=18"
|
|
45
|
-
},
|
|
46
|
-
"keywords": [
|
|
47
|
-
"ai",
|
|
48
|
-
"agent",
|
|
49
|
-
"cli",
|
|
50
|
-
"coding",
|
|
51
|
-
"assistant",
|
|
52
|
-
"saeeol",
|
|
53
|
-
"fabulist",
|
|
54
|
-
"desktop"
|
|
55
|
-
]
|
|
8
|
+
"optionalDependencies": { "saeeol-windows-x64": "1.5.0" },
|
|
9
|
+
"repository": { "type": "git", "url": "https://github.com/byfabulist/fabulist" },
|
|
10
|
+
"keywords": ["ai", "agent", "cli", "coding", "assistant", "saeeol", "fabulist", "desktop"]
|
|
56
11
|
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import os from "os"
|
|
6
|
+
import childProcess from "child_process"
|
|
7
|
+
import { fileURLToPath } from "url"
|
|
8
|
+
import { createRequire } from "module"
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
12
|
+
|
|
13
|
+
// saeeolcode_change start - variant detection matching bin/saeeolcode logic
|
|
14
|
+
const platformMap = {
|
|
15
|
+
darwin: "darwin",
|
|
16
|
+
linux: "linux",
|
|
17
|
+
win32: "windows",
|
|
18
|
+
}
|
|
19
|
+
const archMap = {
|
|
20
|
+
x64: "x64",
|
|
21
|
+
arm64: "arm64",
|
|
22
|
+
arm: "arm",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function detectPlatformAndArch() {
|
|
26
|
+
const platform = platformMap[os.platform()] || os.platform()
|
|
27
|
+
const arch = archMap[os.arch()] || os.arch()
|
|
28
|
+
return { platform, arch }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function supportsAvx2() {
|
|
32
|
+
const { platform, arch } = detectPlatformAndArch()
|
|
33
|
+
if (arch !== "x64") return false
|
|
34
|
+
|
|
35
|
+
if (platform === "linux") {
|
|
36
|
+
try {
|
|
37
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
38
|
+
} catch {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (platform === "darwin") {
|
|
44
|
+
try {
|
|
45
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
timeout: 1500,
|
|
48
|
+
})
|
|
49
|
+
if (result.status !== 0) return false
|
|
50
|
+
return (result.stdout || "").trim() === "1"
|
|
51
|
+
} catch {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isMusl() {
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
62
|
+
} catch {
|
|
63
|
+
// ignore
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
68
|
+
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
|
69
|
+
if (text.includes("musl")) return true
|
|
70
|
+
} catch {
|
|
71
|
+
// ignore
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getPackageNames() {
|
|
78
|
+
const { platform, arch } = detectPlatformAndArch()
|
|
79
|
+
const base = `@saeeolcode/cli-${platform}-${arch}`
|
|
80
|
+
const avx2 = supportsAvx2()
|
|
81
|
+
const baseline = arch === "x64" && !avx2
|
|
82
|
+
|
|
83
|
+
if (platform === "linux") {
|
|
84
|
+
const musl = isMusl()
|
|
85
|
+
if (musl) {
|
|
86
|
+
if (arch === "x64") {
|
|
87
|
+
if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
88
|
+
return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
89
|
+
}
|
|
90
|
+
return [`${base}-musl`, base]
|
|
91
|
+
}
|
|
92
|
+
if (arch === "x64") {
|
|
93
|
+
if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
94
|
+
return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
95
|
+
}
|
|
96
|
+
return [base, `${base}-musl`]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (arch === "x64") {
|
|
100
|
+
if (baseline) return [`${base}-baseline`, base]
|
|
101
|
+
return [base, `${base}-baseline`]
|
|
102
|
+
}
|
|
103
|
+
return [base]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findBinary() {
|
|
107
|
+
const { platform } = detectPlatformAndArch()
|
|
108
|
+
const binaryName = platform === "windows" ? "saeeolcode.exe" : "saeeolcode"
|
|
109
|
+
const names = getPackageNames()
|
|
110
|
+
|
|
111
|
+
for (const packageName of names) {
|
|
112
|
+
try {
|
|
113
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
|
114
|
+
const packageDir = path.dirname(packageJsonPath)
|
|
115
|
+
const binaryPath = path.join(packageDir, "bin", binaryName)
|
|
116
|
+
|
|
117
|
+
if (fs.existsSync(binaryPath)) {
|
|
118
|
+
return { binaryPath, binaryName }
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// package not installed, try next variant
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new Error(`Could not find any binary package. Tried: ${names.map((n) => `"${n}"`).join(", ")}`)
|
|
126
|
+
}
|
|
127
|
+
// saeeolcode_change end
|
|
128
|
+
|
|
129
|
+
function registerWindowsTerminalFragment() {
|
|
130
|
+
const localAppData = process.env.LOCALAPPDATA
|
|
131
|
+
if (!localAppData) return
|
|
132
|
+
|
|
133
|
+
const fragmentDir = path.join(
|
|
134
|
+
localAppData,
|
|
135
|
+
"Microsoft",
|
|
136
|
+
"Windows Terminal",
|
|
137
|
+
"Fragments",
|
|
138
|
+
"saeeolcode",
|
|
139
|
+
)
|
|
140
|
+
const iconPath = path.join(__dirname, "..", "assets", "saeeol.ico")
|
|
141
|
+
if (!fs.existsSync(iconPath)) return
|
|
142
|
+
|
|
143
|
+
const profile = {
|
|
144
|
+
profiles: [
|
|
145
|
+
{
|
|
146
|
+
name: "SAEEOL Code",
|
|
147
|
+
commandline: "powershell.exe -NoExit -Command saeeol",
|
|
148
|
+
icon: iconPath,
|
|
149
|
+
startingDirectory: "%USERPROFILE%",
|
|
150
|
+
tabTitle: "SAEEOL Code",
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fs.mkdirSync(fragmentDir, { recursive: true })
|
|
156
|
+
fs.writeFileSync(
|
|
157
|
+
path.join(fragmentDir, "profile.json"),
|
|
158
|
+
JSON.stringify(profile, null, 2),
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function main() {
|
|
163
|
+
if (os.platform() === "win32") {
|
|
164
|
+
console.log("Windows detected: binary setup not needed (using packaged .exe)")
|
|
165
|
+
try {
|
|
166
|
+
registerWindowsTerminalFragment()
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error("Failed to register Windows Terminal fragment:", err.message)
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const { binaryPath } = findBinary()
|
|
174
|
+
const target = path.join(__dirname, "bin", ".saeeolcode") // saeeolcode_change
|
|
175
|
+
if (fs.existsSync(target)) fs.unlinkSync(target)
|
|
176
|
+
try {
|
|
177
|
+
fs.linkSync(binaryPath, target)
|
|
178
|
+
} catch {
|
|
179
|
+
fs.copyFileSync(binaryPath, target)
|
|
180
|
+
}
|
|
181
|
+
fs.chmodSync(target, 0o755)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
void main()
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error("Failed to setup saeeolcode binary:", error.message)
|
|
188
|
+
process.exit(1)
|
|
189
|
+
}
|
package/cli.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// saeeol bin wrapper — delegates to the platform-specific binary
|
|
4
|
-
const { existsSync, chmodSync, realpathSync } = require("fs")
|
|
5
|
-
const { join, resolve, dirname } = require("path")
|
|
6
|
-
const { spawn } = require("child_process")
|
|
7
|
-
|
|
8
|
-
function getBinaryName() {
|
|
9
|
-
const archMap = { x64: "x64", arm64: "arm64" }
|
|
10
|
-
const platformMap = { win32: "windows", darwin: "darwin", linux: "linux" }
|
|
11
|
-
const p = platformMap[process.platform] || process.platform
|
|
12
|
-
const a = archMap[process.arch] || process.arch
|
|
13
|
-
return `saeeol-${p}-${a}`
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function findBinary() {
|
|
17
|
-
const binaryName = getBinaryName()
|
|
18
|
-
const binaryRel = process.platform === "win32" ? "bin/saeeol.exe" : "bin/saeeol"
|
|
19
|
-
|
|
20
|
-
// 1. Look in node_modules of this package
|
|
21
|
-
const scriptDir = dirname(realpathSync(__filename))
|
|
22
|
-
const localModules = join(scriptDir, "node_modules", binaryName, binaryRel)
|
|
23
|
-
if (existsSync(localModules)) return localModules
|
|
24
|
-
|
|
25
|
-
// 2. Walk up to find node_modules
|
|
26
|
-
let current = scriptDir
|
|
27
|
-
for (let i = 0; i < 10; i++) {
|
|
28
|
-
const candidate = join(current, "node_modules", binaryName, binaryRel)
|
|
29
|
-
if (existsSync(candidate)) return candidate
|
|
30
|
-
const parent = dirname(current)
|
|
31
|
-
if (parent === current) break
|
|
32
|
-
current = parent
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 3. Download directory (postinstall fallback)
|
|
36
|
-
const downloadPath = join(scriptDir, "download", process.platform === "win32" ? `${binaryName}.exe` : "saeeol")
|
|
37
|
-
if (existsSync(downloadPath)) return downloadPath
|
|
38
|
-
|
|
39
|
-
return null
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const binaryPath = findBinary()
|
|
43
|
-
|
|
44
|
-
if (!binaryPath) {
|
|
45
|
-
console.error(`saeeol binary not found for your platform (${getBinaryName()})`)
|
|
46
|
-
console.error(`Run: npm install saeeol`)
|
|
47
|
-
console.error(`Or: https://github.com/byfabulist/fabulist/releases`)
|
|
48
|
-
process.exit(1)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (process.platform !== "win32") {
|
|
52
|
-
try { chmodSync(binaryPath, 0o755) } catch {}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
56
|
-
stdio: "inherit",
|
|
57
|
-
env: { ...process.env },
|
|
58
|
-
windowsHide: true,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
child.on("exit", (code) => process.exit(code || 0))
|
|
62
|
-
child.on("error", (err) => {
|
|
63
|
-
console.error(`Failed to start saeeol: ${err.message}`)
|
|
64
|
-
process.exit(1)
|
|
65
|
-
})
|
package/desktop.js
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
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")
|
|
7
|
-
const { spawn } = require("child_process")
|
|
8
|
-
const http = require("http")
|
|
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
|
-
|
|
26
|
-
const MIME = {
|
|
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",
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getBinaryName() {
|
|
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}`
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function findBinary() {
|
|
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)
|
|
43
|
-
if (existsSync(local)) return local
|
|
44
|
-
// walk up
|
|
45
|
-
let dir = SCRIPT_DIR
|
|
46
|
-
for (let i = 0; i < 10; i++) {
|
|
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
|
|
52
|
-
}
|
|
53
|
-
// download fallback
|
|
54
|
-
const dl = join(SCRIPT_DIR, "download", process.platform === "win32" ? `${name}.exe` : "saeeol")
|
|
55
|
-
if (existsSync(dl)) return dl
|
|
56
|
-
return null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function findElectron() {
|
|
60
|
-
const exe = process.platform === "win32" ? "electron.exe" : "electron"
|
|
61
|
-
const p = join(SCRIPT_DIR, "node_modules", "electron", "dist", exe)
|
|
62
|
-
return existsSync(p) ? p : null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function serveStatic(webDir, port) {
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
const server = http.createServer((req, res) => {
|
|
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)
|
|
76
|
-
})
|
|
77
|
-
server.listen(port, "127.0.0.1", () => resolve(server))
|
|
78
|
-
server.on("error", reject)
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function main() {
|
|
83
|
-
const binaryPath = findBinary()
|
|
84
|
-
if (!binaryPath) {
|
|
85
|
-
console.error(`saeeol binary not found (${getBinaryName()})`)
|
|
86
|
-
console.error("Run: npm install saeeol")
|
|
87
|
-
process.exit(1)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const electronPath = findElectron()
|
|
91
|
-
const webDir = join(SCRIPT_DIR, "web")
|
|
92
|
-
const hasWebUI = existsSync(join(webDir, "index.html"))
|
|
93
|
-
|
|
94
|
-
// Fallback to terminal mode if no electron or no web UI
|
|
95
|
-
if (!electronPath || !hasWebUI) {
|
|
96
|
-
console.log(!electronPath ? "Electron not found." : "Web UI not found.")
|
|
97
|
-
console.log("Starting in terminal mode.\n")
|
|
98
|
-
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
99
|
-
stdio: "inherit", env: { ...process.env }, windowsHide: false,
|
|
100
|
-
})
|
|
101
|
-
child.on("exit", (code) => process.exit(code || 0))
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const webPort = 31000 + Math.floor(Math.random() * 5000)
|
|
106
|
-
const apiPort = 32000 + Math.floor(Math.random() * 5000)
|
|
107
|
-
|
|
108
|
-
// 1. Start saeeol API server
|
|
109
|
-
console.log("Starting saeeol API server...")
|
|
110
|
-
const api = spawn(binaryPath, ["web", "--port", String(apiPort), "--hostname", "127.0.0.1"], {
|
|
111
|
-
stdio: "pipe", env: { ...process.env }, windowsHide: true,
|
|
112
|
-
})
|
|
113
|
-
api.stdout.on("data", (d) => { const m = d.toString().trim(); if (m) console.log(`[api] ${m}`) })
|
|
114
|
-
api.stderr.on("data", (d) => { const m = d.toString().trim(); if (m) console.error(`[api] ${m}`) })
|
|
115
|
-
|
|
116
|
-
// 2. Serve React UI
|
|
117
|
-
const staticServer = await serveStatic(webDir, webPort)
|
|
118
|
-
console.log(`UI: http://127.0.0.1:${webPort}`)
|
|
119
|
-
|
|
120
|
-
// 3. Wait for API
|
|
121
|
-
await new Promise((r) => setTimeout(r, 3000))
|
|
122
|
-
|
|
123
|
-
// 4. Write Electron app inline
|
|
124
|
-
console.log("Opening FABULIST desktop...")
|
|
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"), `
|
|
129
|
-
const { app, BrowserWindow } = require("electron")
|
|
130
|
-
let mainWindow = null
|
|
131
|
-
function createWindow() {
|
|
132
|
-
mainWindow = new BrowserWindow({
|
|
133
|
-
width: 1400, height: 900, minWidth: 800, minHeight: 600,
|
|
134
|
-
title: "\uD30C\uBD88\uB9AC\uC2A4\uD2B8 \u2014 FABULIST",
|
|
135
|
-
autoHideMenuBar: true,
|
|
136
|
-
show: false,
|
|
137
|
-
webPreferences: { nodeIntegration: false, contextIsolation: true },
|
|
138
|
-
})
|
|
139
|
-
mainWindow.once("ready-to-show", () => mainWindow.show())
|
|
140
|
-
mainWindow.loadURL("http://127.0.0.1:${webPort}")
|
|
141
|
-
mainWindow.on("closed", () => { mainWindow = null })
|
|
142
|
-
}
|
|
143
|
-
app.on("ready", createWindow)
|
|
144
|
-
app.on("window-all-closed", () => { /* keep running */ })
|
|
145
|
-
app.on("activate", () => { if (!mainWindow) createWindow() })
|
|
146
|
-
`)
|
|
147
|
-
|
|
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,
|
|
156
|
-
windowsHide: false,
|
|
157
|
-
})
|
|
158
|
-
electron.unref()
|
|
159
|
-
|
|
160
|
-
console.log("FABULIST desktop opened! Close the window to exit.")
|
|
161
|
-
console.log("Press Ctrl+C to stop the server.")
|
|
162
|
-
|
|
163
|
-
const cleanup = () => { api.kill(); staticServer.close(); process.exit(0) }
|
|
164
|
-
process.on("SIGINT", cleanup)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
main().catch((err) => { console.error(err.message); process.exit(1) })
|
package/postinstall.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// saeeol postinstall — downloads platform binary from GitHub Releases
|
|
4
|
-
const https = require("https")
|
|
5
|
-
const http = require("http")
|
|
6
|
-
const fs = require("fs")
|
|
7
|
-
const path = require("path")
|
|
8
|
-
const zlib = require("zlib")
|
|
9
|
-
const { execSync } = require("child_process")
|
|
10
|
-
|
|
11
|
-
const REPO = "byfabulist/fabulist"
|
|
12
|
-
const VERSION = require("./package.json").version
|
|
13
|
-
|
|
14
|
-
function getPlatformInfo() {
|
|
15
|
-
const archMap = { x64: "x64", arm64: "arm64" }
|
|
16
|
-
const platformMap = { win32: "windows", darwin: "darwin", linux: "linux" }
|
|
17
|
-
const p = platformMap[process.platform] || process.platform
|
|
18
|
-
const a = archMap[process.arch] || process.arch
|
|
19
|
-
const ext = process.platform === "win32" ? ".zip" : ".tar.gz"
|
|
20
|
-
const archiveName = `saeeol-${p}-${a}${ext}`
|
|
21
|
-
const binaryName = process.platform === "win32" ? "saeeol.exe" : "saeeol"
|
|
22
|
-
return { archiveName, binaryName, platform: p, arch: a }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function fetch(url, redirects = 5) {
|
|
26
|
-
return new Promise((resolve, reject) => {
|
|
27
|
-
const mod = url.startsWith("https") ? https : http
|
|
28
|
-
mod.get(url, { timeout: 60000 }, (res) => {
|
|
29
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
30
|
-
if (redirects <= 0) return reject(new Error("Too many redirects"))
|
|
31
|
-
return fetch(res.headers.location, redirects - 1).then(resolve, reject)
|
|
32
|
-
}
|
|
33
|
-
if (res.statusCode !== 200) {
|
|
34
|
-
return reject(new Error(`HTTP ${res.statusCode} for ${url}`))
|
|
35
|
-
}
|
|
36
|
-
const chunks = []
|
|
37
|
-
res.on("data", (chunk) => chunks.push(chunk))
|
|
38
|
-
res.on("end", () => resolve(Buffer.concat(chunks)))
|
|
39
|
-
res.on("error", reject)
|
|
40
|
-
}).on("error", reject)
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function extractTarGz(buffer, destDir, binaryName) {
|
|
45
|
-
// Use tar on non-windows, or platform tar on windows (git bash)
|
|
46
|
-
const tmpFile = path.join(destDir, "download.tar.gz")
|
|
47
|
-
fs.writeFileSync(tmpFile, buffer)
|
|
48
|
-
try {
|
|
49
|
-
if (process.platform === "win32") {
|
|
50
|
-
// Try PowerShell Expand-Archive for .tar.gz or just use tar if available
|
|
51
|
-
try {
|
|
52
|
-
execSync(`tar -xzf "${tmpFile}" -C "${destDir}"`, { stdio: "pipe", windowsHide: true })
|
|
53
|
-
} catch {
|
|
54
|
-
// Fallback: use zlib + tar parsing manually not practical, try powershell
|
|
55
|
-
throw new Error("tar command not available on Windows. Install Git Bash or add tar to PATH.")
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
execSync(`tar -xzf "${tmpFile}" -C "${destDir}"`, { stdio: "pipe" })
|
|
59
|
-
}
|
|
60
|
-
} finally {
|
|
61
|
-
try { fs.unlinkSync(tmpFile) } catch {}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function extractZip(buffer, destDir, binaryName) {
|
|
66
|
-
if (process.platform === "win32") {
|
|
67
|
-
const tmpFile = path.join(destDir, "download.zip")
|
|
68
|
-
fs.writeFileSync(tmpFile, buffer)
|
|
69
|
-
try {
|
|
70
|
-
execSync(`powershell -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${destDir}' -Force"`, {
|
|
71
|
-
stdio: "pipe",
|
|
72
|
-
windowsHide: true,
|
|
73
|
-
})
|
|
74
|
-
} finally {
|
|
75
|
-
try { fs.unlinkSync(tmpFile) } catch {}
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
// On non-windows, try unzip
|
|
79
|
-
const tmpFile = path.join(destDir, "download.zip")
|
|
80
|
-
fs.writeFileSync(tmpFile, buffer)
|
|
81
|
-
try {
|
|
82
|
-
execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: "pipe" })
|
|
83
|
-
} finally {
|
|
84
|
-
try { fs.unlinkSync(tmpFile) } catch {}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function main() {
|
|
90
|
-
const { archiveName, binaryName, platform: p, arch: a } = getPlatformInfo()
|
|
91
|
-
const binDir = path.join(__dirname, "download")
|
|
92
|
-
|
|
93
|
-
// Check if binary already exists
|
|
94
|
-
const binaryPath = path.join(binDir, binaryName)
|
|
95
|
-
if (fs.existsSync(binaryPath)) {
|
|
96
|
-
console.log(`saeeol binary already exists at ${binaryPath}`)
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
fs.mkdirSync(binDir, { recursive: true })
|
|
101
|
-
|
|
102
|
-
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`
|
|
103
|
-
console.log(`Downloading saeeol v${VERSION} for ${p}-${a}...`)
|
|
104
|
-
console.log(` ${url}`)
|
|
105
|
-
|
|
106
|
-
let buffer
|
|
107
|
-
try {
|
|
108
|
-
buffer = await fetch(url)
|
|
109
|
-
} catch (err) {
|
|
110
|
-
// Fallback: try without version tag (latest)
|
|
111
|
-
const fallbackUrl = `https://github.com/${REPO}/releases/latest/download/${archiveName}`
|
|
112
|
-
console.log(`Version-specific download failed: ${err.message}`)
|
|
113
|
-
console.log(`Trying latest: ${fallbackUrl}`)
|
|
114
|
-
try {
|
|
115
|
-
buffer = await fetch(fallbackUrl)
|
|
116
|
-
} catch (err2) {
|
|
117
|
-
console.error(`Failed to download saeeol binary: ${err2.message}`)
|
|
118
|
-
console.error(`Please download manually from: https://github.com/${REPO}/releases`)
|
|
119
|
-
process.exit(0) // Don't fail npm install
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
console.log(`Downloaded ${(buffer.length / 1024 / 1024).toFixed(1)} MB`)
|
|
124
|
-
|
|
125
|
-
// Extract
|
|
126
|
-
if (archiveName.endsWith(".tar.gz")) {
|
|
127
|
-
await extractTarGz(buffer, binDir, binaryName)
|
|
128
|
-
} else {
|
|
129
|
-
await extractZip(buffer, binDir, binaryName)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Verify
|
|
133
|
-
if (!fs.existsSync(binaryPath)) {
|
|
134
|
-
// The archive might extract to a subdirectory, search for the binary
|
|
135
|
-
const files = fs.readdirSync(binDir, { recursive: true })
|
|
136
|
-
const found = files.find((f) => {
|
|
137
|
-
const basename = path.basename(f)
|
|
138
|
-
return basename === "saeeol" || basename === "saeeol.exe"
|
|
139
|
-
})
|
|
140
|
-
if (found) {
|
|
141
|
-
const fullPath = path.join(binDir, found)
|
|
142
|
-
fs.copyFileSync(fullPath, binaryPath)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (fs.existsSync(binaryPath)) {
|
|
147
|
-
if (process.platform !== "win32") {
|
|
148
|
-
fs.chmodSync(binaryPath, 0o755)
|
|
149
|
-
}
|
|
150
|
-
console.log(`saeeol v${VERSION} installed successfully!`)
|
|
151
|
-
console.log(` Binary: ${binaryPath}`)
|
|
152
|
-
} else {
|
|
153
|
-
console.warn(`Warning: binary not found after extraction at ${binaryPath}`)
|
|
154
|
-
console.warn(`You may need to install manually from https://github.com/${REPO}/releases`)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
main().catch((err) => {
|
|
159
|
-
console.error(`saeeol postinstall failed: ${err.message}`)
|
|
160
|
-
console.error(`Install manually: https://github.com/${REPO}/releases`)
|
|
161
|
-
process.exit(0) // Don't fail npm install
|
|
162
|
-
})
|