finny 0.0.2 → 0.1.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/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 result = childProcess.spawnSync(target, process.argv.slice(2), {
11
+ const child = childProcess.spawn(target, process.argv.slice(2), {
10
12
  stdio: "inherit",
11
13
  })
12
- if (result.error) {
13
- console.error(result.error.message)
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
- const envPath = process.env.OPENCODE_BIN_PATH
21
- if (envPath) {
22
- run(envPath)
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
- const cached = path.join(scriptDir, ".opencode")
30
- if (fs.existsSync(cached)) {
31
- run(cached)
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 = "finny-" + platform + "-" + arch
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 opencode CLI for your platform. You can try manually installing " +
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.0",
3
4
  "bin": {
4
- "finny": "./bin/opencode"
5
+ "finny": "./bin/opencode",
6
+ "finny-pro": "./bin/opencode"
5
7
  },
6
8
  "scripts": {
7
- "postinstall": "bun ./postinstall.mjs || node ./postinstall.mjs"
9
+ "postinstall": "node ./postinstall.mjs"
8
10
  },
9
- "version": "0.0.2",
10
- "license": "MIT",
11
11
  "optionalDependencies": {
12
- "finny-darwin-arm64": "0.0.2",
13
- "finny-darwin-x64": "0.0.2",
14
- "finny-darwin-x64-baseline": "0.0.2",
15
- "finny-linux-arm64": "0.0.2",
16
- "finny-linux-arm64-musl": "0.0.2",
17
- "finny-linux-x64": "0.0.2",
18
- "finny-linux-x64-baseline": "0.0.2",
19
- "finny-linux-x64-baseline-musl": "0.0.2",
20
- "finny-linux-x64-musl": "0.0.2",
21
- "finny-windows-arm64": "0.0.2",
22
- "finny-windows-x64": "0.0.2",
23
- "finny-windows-x64-baseline": "0.0.2"
24
- }
12
+ "finny-darwin-arm64": "0.1.0",
13
+ "finny-darwin-x64": "0.1.0",
14
+ "finny-darwin-x64-baseline": "0.1.0",
15
+ "finny-linux-arm64": "0.1.0",
16
+ "finny-linux-arm64-musl": "0.1.0",
17
+ "finny-linux-x64": "0.1.0",
18
+ "finny-linux-x64-baseline": "0.1.0",
19
+ "finny-linux-x64-baseline-musl": "0.1.0",
20
+ "finny-linux-x64-musl": "0.1.0",
21
+ "finny-windows-arm64": "0.1.0",
22
+ "finny-windows-x64": "0.1.0",
23
+ "finny-windows-x64-baseline": "0.1.0"
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 { fileURLToPath } from "url"
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
- function detectPlatformAndArch() {
13
- // Map platform names
14
- let platform
15
- switch (os.platform()) {
16
- case "darwin":
17
- platform = "darwin"
18
- break
19
- case "linux":
20
- platform = "linux"
21
- break
22
- case "win32":
23
- platform = "windows"
24
- break
25
- default:
26
- platform = os.platform()
27
- break
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
- // Map architecture names
31
- let arch
32
- switch (os.arch()) {
33
- case "x64":
34
- arch = "x64"
35
- break
36
- case "arm64":
37
- arch = "arm64"
38
- break
39
- case "arm":
40
- arch = "arm"
41
- break
42
- default:
43
- arch = os.arch()
44
- break
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 { platform, arch }
81
+ return false
48
82
  }
49
83
 
50
- function findBinary() {
51
- const { platform, arch } = detectPlatformAndArch()
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
- // Use require.resolve to find the package
57
- const packageJsonPath = require.resolve(`${packageName}/package.json`)
58
- const packageDir = path.dirname(packageJsonPath)
59
- const binaryPath = path.join(packageDir, "bin", binaryName)
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
- return { binaryPath, binaryName }
66
- } catch (error) {
67
- throw new Error(`Could not find package ${packageName}: ${error.message}`)
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 prepareBinDirectory(binaryName) {
72
- const binDir = path.join(__dirname, "bin")
73
- const targetPath = path.join(binDir, binaryName)
101
+ function packageNames() {
102
+ const baseline = arch === "x64" && !supportsAvx2()
74
103
 
75
- // Ensure bin directory exists
76
- if (!fs.existsSync(binDir)) {
77
- fs.mkdirSync(binDir, { recursive: true })
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
- // Remove existing binary/symlink if it exists
81
- if (fs.existsSync(targetPath)) {
82
- fs.unlinkSync(targetPath)
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 { binDir, targetPath }
120
+ if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]
121
+ return [base]
86
122
  }
87
123
 
88
- function symlinkBinary(sourcePath, binaryName) {
89
- const { targetPath } = prepareBinDirectory(binaryName)
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
- fs.symlinkSync(sourcePath, targetPath)
92
- console.log(`opencode binary symlinked: ${targetPath} -> ${sourcePath}`)
131
+ function installPackage(name) {
132
+ const version = packageJson.optionalDependencies?.[name]
133
+ if (!version) return
93
134
 
94
- // Verify the file exists after operation
95
- if (!fs.existsSync(targetPath)) {
96
- throw new Error(`Failed to symlink binary to ${targetPath}`)
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
- async function main() {
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
- if (os.platform() === "win32") {
103
- // On Windows, the .exe is already included in the package and bin field points to it
104
- // No postinstall setup needed
105
- console.log("Windows detected: binary setup not needed (using packaged .exe)")
106
- return
107
- }
156
+ fs.linkSync(source, target)
157
+ } catch {
158
+ fs.copyFileSync(source, target)
159
+ }
160
+ fs.chmodSync(target, 0o755)
161
+ }
108
162
 
109
- // On non-Windows platforms, just verify the binary package exists
110
- // Don't replace the wrapper script - it handles binary execution
111
- const { binaryPath } = findBinary()
112
- const target = path.join(__dirname, "bin", ".opencode")
113
- if (fs.existsSync(target)) fs.unlinkSync(target)
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
- fs.linkSync(binaryPath, target)
175
+ copyBinary(resolveBinary(name), targetBinary)
176
+ if (verifyBinary()) return
116
177
  } catch {
117
- fs.copyFileSync(binaryPath, target)
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("Postinstall script error:", error.message)
130
- process.exit(0)
192
+ console.error(error.message)
193
+ process.exit(1)
131
194
  }