finny 0.0.2 → 0.1.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/LICENSE +48 -0
- package/README.md +42 -0
- package/bin/opencode +120 -17
- package/package.json +18 -17
- package/postinstall.mjs +150 -87
package/LICENSE
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
FINNY PRO — PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Finny. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software, including its compiled binaries (the "Software"), is licensed, not
|
|
6
|
+
sold. By installing or using the Software you agree to this License and to the
|
|
7
|
+
Finny End User License Agreement at https://finnyai.tech/legal/eula.
|
|
8
|
+
|
|
9
|
+
1. LICENSE GRANT. Subject to a valid, active Finny license and your compliance
|
|
10
|
+
with these terms, Finny grants you a limited, non-exclusive, non-transferable,
|
|
11
|
+
non-sublicensable, revocable license to install and use the Software for your
|
|
12
|
+
own internal use, on the number of devices permitted by your subscription.
|
|
13
|
+
|
|
14
|
+
2. RESTRICTIONS. You may NOT, and may not permit or enable any third party to:
|
|
15
|
+
(a) reverse engineer, decompile, disassemble, deobfuscate, decrypt, unpack,
|
|
16
|
+
extract, or otherwise attempt to derive, reconstruct, or inspect the
|
|
17
|
+
source code, algorithms, models, prompts, or internal structure of the
|
|
18
|
+
Software or any compiled binary;
|
|
19
|
+
(b) circumvent, disable, bypass, or tamper with any license verification or
|
|
20
|
+
technical protection measure;
|
|
21
|
+
(c) copy, distribute, sublicense, sell, rent, lease, lend, publish, mirror,
|
|
22
|
+
host, deploy, or otherwise make the Software (in whole or in part, in
|
|
23
|
+
original or modified form) available to any third party or to the public;
|
|
24
|
+
(d) create derivative works based on the Software; or
|
|
25
|
+
(e) share, publish, resell, or transfer your license key.
|
|
26
|
+
|
|
27
|
+
3. OWNERSHIP. The Software and all intellectual property rights in it are owned
|
|
28
|
+
by Finny and protected by copyright, trade-secret, and other laws. No rights
|
|
29
|
+
are granted except those expressly stated in this License.
|
|
30
|
+
|
|
31
|
+
4. TERMINATION. This License and your rights under it terminate automatically
|
|
32
|
+
upon any breach. Finny may suspend or revoke your license at any time for
|
|
33
|
+
breach or suspected circumvention or reverse engineering. On termination you
|
|
34
|
+
must cease all use of the Software and destroy all copies.
|
|
35
|
+
|
|
36
|
+
5. NO WARRANTY; LIMITATION OF LIABILITY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT
|
|
37
|
+
WARRANTY OF ANY KIND. FINNY DOES NOT PROVIDE FINANCIAL, INVESTMENT, OR TRADING
|
|
38
|
+
ADVICE; SIMULATED OR BACKTESTED RESULTS DO NOT GUARANTEE FUTURE PERFORMANCE. TO
|
|
39
|
+
THE MAXIMUM EXTENT PERMITTED BY LAW, FINNY SHALL NOT BE LIABLE FOR ANY TRADING
|
|
40
|
+
LOSSES OR ANY INDIRECT, INCIDENTAL, CONSEQUENTIAL, SPECIAL, OR PUNITIVE DAMAGES
|
|
41
|
+
ARISING FROM THE SOFTWARE OR THIS LICENSE.
|
|
42
|
+
|
|
43
|
+
6. GOVERNING LAW. This License is governed by the laws of the Province of Ontario
|
|
44
|
+
and the federal laws of Canada applicable therein, without regard to conflict-
|
|
45
|
+
of-laws principles.
|
|
46
|
+
|
|
47
|
+
Full terms: https://finnyai.tech/legal/eula
|
|
48
|
+
Contact: jaimin@finnyai.tech
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @finny-ai/finny-pro
|
|
2
|
+
|
|
3
|
+
Finny Pro — the AI trading assistant CLI for licensed users. Ships as a signed,
|
|
4
|
+
compiled binary; requires a valid Finny Pro license.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g @finny-ai/finny-pro
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
finny-pro
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
You'll be asked to activate with your Finny Pro license key on first run. Get a
|
|
19
|
+
license at https://finnyai.tech/pro.
|
|
20
|
+
|
|
21
|
+
## License — Proprietary. No reverse engineering or redistribution.
|
|
22
|
+
|
|
23
|
+
This is **proprietary software, licensed not sold.** It is distributed only as a
|
|
24
|
+
compiled binary. By installing or using it you agree to the
|
|
25
|
+
[End User License Agreement](https://finnyai.tech/legal/eula). In particular, you
|
|
26
|
+
**may not**:
|
|
27
|
+
|
|
28
|
+
- **reverse engineer, decompile, deobfuscate, unpack, or otherwise inspect or
|
|
29
|
+
reconstruct** the binary or its source, algorithms, or internal logic;
|
|
30
|
+
- **circumvent or tamper with** license verification or any technical protection;
|
|
31
|
+
- **copy, redistribute, publish, mirror, host, deploy, or resell** the package,
|
|
32
|
+
in whole or in part, in original or modified form; or
|
|
33
|
+
- **share or transfer your license key.**
|
|
34
|
+
|
|
35
|
+
Violations terminate your license and may be pursued under copyright, trade-secret,
|
|
36
|
+
and contract law. The full `LICENSE` file is bundled with this package, and the
|
|
37
|
+
complete terms are at the [End User License Agreement](https://finnyai.tech/legal/eula).
|
|
38
|
+
|
|
39
|
+
Not financial advice. Trading involves risk; simulated results do not guarantee
|
|
40
|
+
future performance.
|
|
41
|
+
|
|
42
|
+
© 2026 Finny. All rights reserved. · jaimin@finnyai.tech
|
package/bin/opencode
CHANGED
|
@@ -5,31 +5,59 @@ const fs = require("fs")
|
|
|
5
5
|
const path = require("path")
|
|
6
6
|
const os = require("os")
|
|
7
7
|
|
|
8
|
+
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"]
|
|
9
|
+
|
|
8
10
|
function run(target) {
|
|
9
|
-
const
|
|
11
|
+
const child = childProcess.spawn(target, process.argv.slice(2), {
|
|
10
12
|
stdio: "inherit",
|
|
11
13
|
})
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
child.on("error", (error) => {
|
|
16
|
+
console.error(error.message)
|
|
14
17
|
process.exit(1)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const forwarders = {}
|
|
21
|
+
for (const signal of forwardedSignals) {
|
|
22
|
+
forwarders[signal] = () => {
|
|
23
|
+
try {
|
|
24
|
+
child.kill(signal)
|
|
25
|
+
} catch {
|
|
26
|
+
// The child may have already exited.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
process.on(signal, forwarders[signal])
|
|
15
30
|
}
|
|
16
|
-
const code = typeof result.status === "number" ? result.status : 0
|
|
17
|
-
process.exit(code)
|
|
18
|
-
}
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
child.on("exit", (code, signal) => {
|
|
33
|
+
for (const forwardedSignal of forwardedSignals) {
|
|
34
|
+
process.removeListener(forwardedSignal, forwarders[forwardedSignal])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (signal) {
|
|
38
|
+
process.kill(process.pid, signal)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.exit(typeof code === "number" ? code : 0)
|
|
43
|
+
})
|
|
23
44
|
}
|
|
24
45
|
|
|
46
|
+
const envPath = process.env.FINNY_BIN_PATH ?? process.env.OPENCODE_BIN_PATH
|
|
47
|
+
|
|
25
48
|
const scriptPath = fs.realpathSync(__filename)
|
|
26
49
|
const scriptDir = path.dirname(scriptPath)
|
|
27
50
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
// Derive the platform-package base from this wrapper's own name so the launcher
|
|
52
|
+
// resolves the published scoped packages (e.g. @finny-ai/finny-internal-darwin-arm64),
|
|
53
|
+
// not the legacy unscoped "opencode-*" names.
|
|
54
|
+
const wrapperName = (() => {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(fs.readFileSync(path.join(scriptDir, "..", "package.json"), "utf8")).name
|
|
57
|
+
} catch {
|
|
58
|
+
return "opencode"
|
|
59
|
+
}
|
|
60
|
+
})()
|
|
33
61
|
|
|
34
62
|
const platformMap = {
|
|
35
63
|
darwin: "darwin",
|
|
@@ -50,9 +78,13 @@ let arch = archMap[os.arch()]
|
|
|
50
78
|
if (!arch) {
|
|
51
79
|
arch = os.arch()
|
|
52
80
|
}
|
|
53
|
-
const base = "
|
|
81
|
+
const base = wrapperName + "-" + platform + "-" + arch
|
|
54
82
|
const binary = platform === "windows" ? "opencode.exe" : "opencode"
|
|
55
83
|
|
|
84
|
+
// Fast path: the binary postinstall.mjs copies next to this launcher. Keep this
|
|
85
|
+
// filename in sync with postinstall.mjs's targetBinary.
|
|
86
|
+
const cached = path.join(scriptDir, platform === "windows" ? ".opencode.exe" : ".opencode")
|
|
87
|
+
|
|
56
88
|
function supportsAvx2() {
|
|
57
89
|
if (arch !== "x64") return false
|
|
58
90
|
|
|
@@ -166,14 +198,85 @@ function findBinary(startDir) {
|
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
200
|
|
|
169
|
-
const resolved = findBinary(scriptDir)
|
|
201
|
+
const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir))
|
|
170
202
|
if (!resolved) {
|
|
171
203
|
console.error(
|
|
172
|
-
"It seems that your package manager failed to install the right version of the
|
|
204
|
+
"It seems that your package manager failed to install the right version of the Finny CLI for your platform. You can try manually installing " +
|
|
173
205
|
names.map((n) => `\"${n}\"`).join(" or ") +
|
|
174
206
|
" package",
|
|
175
207
|
)
|
|
176
208
|
process.exit(1)
|
|
177
209
|
}
|
|
178
210
|
|
|
211
|
+
// ------------------------------------------------------------------
|
|
212
|
+
// Windows Terminal compatibility: the TUI requires a modern terminal
|
|
213
|
+
// emulator on Windows. Detect and auto-relaunch, or show a clear error.
|
|
214
|
+
// ------------------------------------------------------------------
|
|
215
|
+
const nonInteractiveFlags = new Set(["--version", "-v", "--help", "-h", "--headless", "version", "help"])
|
|
216
|
+
const isNonInteractive = process.argv.slice(2).some((arg) => nonInteractiveFlags.has(arg))
|
|
217
|
+
|
|
218
|
+
if (os.platform() === "win32" && !isNonInteractive && !process.env.WT_SESSION && process.env.TERM_PROGRAM !== "vscode") {
|
|
219
|
+
const wtCandidates = [
|
|
220
|
+
path.join(process.env.LOCALAPPDATA || "", "Microsoft", "WindowsApps", "wt.exe"),
|
|
221
|
+
path.join(process.env.USERPROFILE || "", "AppData", "Local", "Microsoft", "WindowsApps", "wt.exe"),
|
|
222
|
+
"wt.exe",
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
let wtExe = null
|
|
226
|
+
for (const candidate of wtCandidates) {
|
|
227
|
+
try {
|
|
228
|
+
if (fs.existsSync(candidate)) {
|
|
229
|
+
wtExe = candidate
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
} catch {}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!wtExe) {
|
|
236
|
+
try {
|
|
237
|
+
const result = childProcess.spawnSync("where", ["wt.exe"], {
|
|
238
|
+
encoding: "utf8",
|
|
239
|
+
windowsHide: true,
|
|
240
|
+
})
|
|
241
|
+
if (result.status === 0 && result.stdout) {
|
|
242
|
+
const first = result.stdout.trim().split(/\r?\n/)[0]
|
|
243
|
+
if (first) wtExe = first
|
|
244
|
+
}
|
|
245
|
+
} catch {}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (wtExe) {
|
|
249
|
+
const argsForWt = [
|
|
250
|
+
"powershell",
|
|
251
|
+
"-NoExit",
|
|
252
|
+
"-Command",
|
|
253
|
+
"& " + JSON.stringify(resolved) + " " + process.argv.slice(2).map((a) => JSON.stringify(a)).join(" "),
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
const child = childProcess.spawn(wtExe, argsForWt, {
|
|
257
|
+
detached: true,
|
|
258
|
+
windowsHide: false,
|
|
259
|
+
stdio: "ignore",
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
child.on("error", (err) => {
|
|
263
|
+
console.error("Failed to launch Windows Terminal:", err.message)
|
|
264
|
+
process.exit(1)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
child.unref()
|
|
268
|
+
process.exit(0)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.error(
|
|
272
|
+
"Finny requires a modern terminal emulator.\n" +
|
|
273
|
+
"The legacy Windows Console Host does not support the interactive TUI.\n\n" +
|
|
274
|
+
"Please run this from Windows Terminal (https://aka.ms/terminal) or VS Code.\n" +
|
|
275
|
+
"You can also use the web interface at http://127.0.0.1:4096 once the daemon starts.\n\n" +
|
|
276
|
+
"To install Windows Terminal:\n" +
|
|
277
|
+
" winget install Microsoft.WindowsTerminal"
|
|
278
|
+
)
|
|
279
|
+
process.exit(1)
|
|
280
|
+
}
|
|
281
|
+
|
|
179
282
|
run(resolved)
|
package/package.json
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "finny",
|
|
3
|
+
"version": "0.1.1",
|
|
3
4
|
"bin": {
|
|
4
|
-
"finny": "./bin/opencode"
|
|
5
|
+
"finny": "./bin/opencode",
|
|
6
|
+
"finny-pro": "./bin/opencode"
|
|
5
7
|
},
|
|
6
8
|
"scripts": {
|
|
7
|
-
"postinstall": "
|
|
9
|
+
"postinstall": "node ./postinstall.mjs"
|
|
8
10
|
},
|
|
9
|
-
"version": "0.0.2",
|
|
10
|
-
"license": "MIT",
|
|
11
11
|
"optionalDependencies": {
|
|
12
|
-
"finny-darwin-arm64": "0.
|
|
13
|
-
"finny-darwin-x64": "0.
|
|
14
|
-
"finny-darwin-x64-baseline": "0.
|
|
15
|
-
"finny-linux-arm64": "0.
|
|
16
|
-
"finny-linux-arm64-musl": "0.
|
|
17
|
-
"finny-linux-x64": "0.
|
|
18
|
-
"finny-linux-x64-baseline": "0.
|
|
19
|
-
"finny-linux-x64-baseline-musl": "0.
|
|
20
|
-
"finny-linux-x64-musl": "0.
|
|
21
|
-
"finny-windows-arm64": "0.
|
|
22
|
-
"finny-windows-x64": "0.
|
|
23
|
-
"finny-windows-x64-baseline": "0.
|
|
24
|
-
}
|
|
12
|
+
"finny-darwin-arm64": "0.1.1",
|
|
13
|
+
"finny-darwin-x64": "0.1.1",
|
|
14
|
+
"finny-darwin-x64-baseline": "0.1.1",
|
|
15
|
+
"finny-linux-arm64": "0.1.1",
|
|
16
|
+
"finny-linux-arm64-musl": "0.1.1",
|
|
17
|
+
"finny-linux-x64": "0.1.1",
|
|
18
|
+
"finny-linux-x64-baseline": "0.1.1",
|
|
19
|
+
"finny-linux-x64-baseline-musl": "0.1.1",
|
|
20
|
+
"finny-linux-x64-musl": "0.1.1",
|
|
21
|
+
"finny-windows-arm64": "0.1.1",
|
|
22
|
+
"finny-windows-x64": "0.1.1",
|
|
23
|
+
"finny-windows-x64-baseline": "0.1.1"
|
|
24
|
+
},
|
|
25
|
+
"license": "SEE LICENSE IN LICENSE"
|
|
25
26
|
}
|
package/postinstall.mjs
CHANGED
|
@@ -1,131 +1,194 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import childProcess from "child_process"
|
|
3
4
|
import fs from "fs"
|
|
4
|
-
import path from "path"
|
|
5
5
|
import os from "os"
|
|
6
|
-
import
|
|
6
|
+
import path from "path"
|
|
7
7
|
import { createRequire } from "module"
|
|
8
|
+
import { fileURLToPath } from "url"
|
|
8
9
|
|
|
9
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
10
11
|
const require = createRequire(import.meta.url)
|
|
12
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
const platform = platformMap[os.platform()] ?? os.platform()
|
|
26
|
+
const arch = archMap[os.arch()] ?? os.arch()
|
|
27
|
+
// Platform packages are published as `${wrapperName}-<platform>-<arch>[...]`,
|
|
28
|
+
// e.g. @finny-ai/finny-internal-darwin-arm64. Derive the base from this
|
|
29
|
+
// package's own name so it stays correct across renames/scopes.
|
|
30
|
+
const base = `${packageJson.name}-${platform}-${arch}`
|
|
31
|
+
const sourceBinary = platform === "windows" ? "opencode.exe" : "opencode"
|
|
32
|
+
// Cache the platform binary next to the launcher. The filename must match the
|
|
33
|
+
// launcher's `cached` path in bin/opencode so `finny` actually executes it.
|
|
34
|
+
const targetBinary = path.join(__dirname, "bin", platform === "windows" ? ".opencode.exe" : ".opencode")
|
|
35
|
+
|
|
36
|
+
function supportsAvx2() {
|
|
37
|
+
if (arch !== "x64") return false
|
|
38
|
+
|
|
39
|
+
if (platform === "linux") {
|
|
40
|
+
try {
|
|
41
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
42
|
+
} catch {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (platform === "darwin") {
|
|
48
|
+
try {
|
|
49
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
timeout: 1500,
|
|
52
|
+
})
|
|
53
|
+
if (result.status !== 0) return false
|
|
54
|
+
return (result.stdout || "").trim() === "1"
|
|
55
|
+
} catch {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
28
58
|
}
|
|
29
59
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
if (platform === "windows") {
|
|
61
|
+
const command =
|
|
62
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
63
|
+
|
|
64
|
+
for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
65
|
+
try {
|
|
66
|
+
const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
timeout: 3000,
|
|
69
|
+
windowsHide: true,
|
|
70
|
+
})
|
|
71
|
+
if (result.status !== 0) continue
|
|
72
|
+
const output = (result.stdout || "").trim().toLowerCase()
|
|
73
|
+
if (output === "true" || output === "1") return true
|
|
74
|
+
if (output === "false" || output === "0") return false
|
|
75
|
+
} catch {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
}
|
|
45
79
|
}
|
|
46
80
|
|
|
47
|
-
return
|
|
81
|
+
return false
|
|
48
82
|
}
|
|
49
83
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
const packageName = `finny-${platform}-${arch}`
|
|
53
|
-
const binaryName = platform === "windows" ? "opencode.exe" : "opencode"
|
|
84
|
+
function isMusl() {
|
|
85
|
+
if (platform !== "linux") return false
|
|
54
86
|
|
|
55
87
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!fs.existsSync(binaryPath)) {
|
|
62
|
-
throw new Error(`Binary not found at ${binaryPath}`)
|
|
63
|
-
}
|
|
88
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
89
|
+
} catch {
|
|
90
|
+
// Ignore filesystem probes that are blocked by the host.
|
|
91
|
+
}
|
|
64
92
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
try {
|
|
94
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
95
|
+
return `${result.stdout || ""}${result.stderr || ""}`.toLowerCase().includes("musl")
|
|
96
|
+
} catch {
|
|
97
|
+
return false
|
|
68
98
|
}
|
|
69
99
|
}
|
|
70
100
|
|
|
71
|
-
function
|
|
72
|
-
const
|
|
73
|
-
const targetPath = path.join(binDir, binaryName)
|
|
101
|
+
function packageNames() {
|
|
102
|
+
const baseline = arch === "x64" && !supportsAvx2()
|
|
74
103
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
if (platform === "linux") {
|
|
105
|
+
if (isMusl()) {
|
|
106
|
+
if (arch === "x64")
|
|
107
|
+
return baseline
|
|
108
|
+
? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
109
|
+
: [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
110
|
+
return [`${base}-musl`, base]
|
|
111
|
+
}
|
|
79
112
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
113
|
+
if (arch === "x64")
|
|
114
|
+
return baseline
|
|
115
|
+
? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
116
|
+
: [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
117
|
+
return [base, `${base}-musl`]
|
|
83
118
|
}
|
|
84
119
|
|
|
85
|
-
return {
|
|
120
|
+
if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]
|
|
121
|
+
return [base]
|
|
86
122
|
}
|
|
87
123
|
|
|
88
|
-
function
|
|
89
|
-
const
|
|
124
|
+
function resolveBinary(name) {
|
|
125
|
+
const packageJsonPath = require.resolve(`${name}/package.json`)
|
|
126
|
+
const binaryPath = path.join(path.dirname(packageJsonPath), "bin", sourceBinary)
|
|
127
|
+
if (!fs.existsSync(binaryPath)) throw new Error(`Binary not found at ${binaryPath}`)
|
|
128
|
+
return binaryPath
|
|
129
|
+
}
|
|
90
130
|
|
|
91
|
-
|
|
92
|
-
|
|
131
|
+
function installPackage(name) {
|
|
132
|
+
const version = packageJson.optionalDependencies?.[name]
|
|
133
|
+
if (!version) return
|
|
93
134
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
135
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-install-"))
|
|
136
|
+
try {
|
|
137
|
+
const result = childProcess.spawnSync(
|
|
138
|
+
"npm",
|
|
139
|
+
["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${version}`],
|
|
140
|
+
{ stdio: "inherit", windowsHide: true },
|
|
141
|
+
)
|
|
142
|
+
if (result.status !== 0) return
|
|
143
|
+
const packageDir = path.join(temp, "node_modules", name)
|
|
144
|
+
copyBinary(path.join(packageDir, "bin", sourceBinary), targetBinary)
|
|
145
|
+
return true
|
|
146
|
+
} finally {
|
|
147
|
+
fs.rmSync(temp, { recursive: true, force: true })
|
|
97
148
|
}
|
|
98
149
|
}
|
|
99
150
|
|
|
100
|
-
|
|
151
|
+
function copyBinary(source, target) {
|
|
152
|
+
if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`)
|
|
153
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
154
|
+
if (fs.existsSync(target)) fs.unlinkSync(target)
|
|
101
155
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
156
|
+
fs.linkSync(source, target)
|
|
157
|
+
} catch {
|
|
158
|
+
fs.copyFileSync(source, target)
|
|
159
|
+
}
|
|
160
|
+
fs.chmodSync(target, 0o755)
|
|
161
|
+
}
|
|
108
162
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
163
|
+
function verifyBinary() {
|
|
164
|
+
const result = childProcess.spawnSync(targetBinary, ["--version"], {
|
|
165
|
+
encoding: "utf8",
|
|
166
|
+
stdio: "ignore",
|
|
167
|
+
windowsHide: true,
|
|
168
|
+
})
|
|
169
|
+
return result.status === 0
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function main() {
|
|
173
|
+
for (const name of packageNames()) {
|
|
114
174
|
try {
|
|
115
|
-
|
|
175
|
+
copyBinary(resolveBinary(name), targetBinary)
|
|
176
|
+
if (verifyBinary()) return
|
|
116
177
|
} catch {
|
|
117
|
-
|
|
178
|
+
if (installPackage(name) && verifyBinary()) return
|
|
118
179
|
}
|
|
119
|
-
fs.chmodSync(target, 0o755)
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.error("Failed to setup opencode binary:", error.message)
|
|
122
|
-
process.exit(1)
|
|
123
180
|
}
|
|
181
|
+
|
|
182
|
+
throw new Error(
|
|
183
|
+
`It seems your package manager failed to install the right opencode CLI package. Try manually installing ${packageNames()
|
|
184
|
+
.map((name) => JSON.stringify(name))
|
|
185
|
+
.join(" or ")}.`,
|
|
186
|
+
)
|
|
124
187
|
}
|
|
125
188
|
|
|
126
189
|
try {
|
|
127
190
|
main()
|
|
128
191
|
} catch (error) {
|
|
129
|
-
console.error(
|
|
130
|
-
process.exit(
|
|
192
|
+
console.error(error.message)
|
|
193
|
+
process.exit(1)
|
|
131
194
|
}
|