inillucent 0.1.4

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/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # inillucent
2
+
3
+ An embedded SQL database that speaks SQLite's dialect on its own storage, with
4
+ full-text and vector search built in — the job of PostgreSQL + pgvector + an
5
+ embedding server, in one process and one file.
6
+
7
+ ```sh
8
+ npm install -g inillucent
9
+ # or, without installing anything permanently:
10
+ npx inillucent help
11
+ ```
12
+
13
+ No native build step and no postinstall download: the binaries ship as
14
+ per-platform packages that npm installs only where they run, so `npm ci` works
15
+ offline and behind a proxy.
16
+
17
+ ## Four programs
18
+
19
+ | | |
20
+ |---|---|
21
+ | `inillucent` | the command line: `query`, `exec`, `describe`, `import`, `export`, `search`, and twenty more |
22
+ | `inillucent-shell` | an interactive shell shaped like `sqlite3`, with all 63 of its dot commands |
23
+ | `inillucent-mcp` | the same commands served to an AI agent over MCP |
24
+ | `inillucent-migrate` | builds an inillucent database from a SQLite file |
25
+
26
+ ## From a shell
27
+
28
+ ```sh
29
+ inillucent create app.rdb
30
+ inillucent --db app.rdb exec "CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)"
31
+ inillucent --db app.rdb exec "INSERT INTO notes (body) VALUES (?1)" --params '["hello"]'
32
+ inillucent --db app.rdb query "SELECT * FROM notes"
33
+ inillucent --db app.rdb describe notes
34
+ inillucent help
35
+ ```
36
+
37
+ Exit codes carry meaning: `0` success, `1` failed, `2` a command line nobody
38
+ could act on, and **`3` a construct the engine has not built yet** — so a script
39
+ can branch on "not yet" without matching on a message.
40
+
41
+ ## From Node
42
+
43
+ ```js
44
+ import { query, inillucent } from 'inillucent';
45
+
46
+ const rows = await query('SELECT id, body FROM notes WHERE id > ?1', {
47
+ db: 'app.rdb',
48
+ params: [3],
49
+ });
50
+ // [{ id: 4, body: 'hello' }]
51
+
52
+ const described = await inillucent('describe', { db: 'app.rdb', table: 'notes' });
53
+ console.log(described.ddl, described.indexes, described.row_count_in_table);
54
+ ```
55
+
56
+ Each call is a process, so this is the right tool for the dozen calls a build
57
+ script or a tool wrapper makes and the wrong one for a loop over a million rows.
58
+ For that, write a binding over the C ABI — the header ships in this package's
59
+ platform dependency under `include/`, and
60
+ [`drivers/README.md`](https://github.com/Black-Rainbow-Labs/Inillucent/blob/main/drivers/README.md)
61
+ is written to be followed.
62
+
63
+ ## For an agent
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "inillucent": {
69
+ "command": "npx",
70
+ "args": ["-y", "inillucent-mcp", "--db", "app.rdb"]
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ 27 tools, generated from the same command table the CLI reads, so the two can
77
+ never drift. `--readonly` refuses every statement that changes something, and
78
+ `--root DIR` refuses every path outside a directory.
79
+
80
+ ## Licence
81
+
82
+ MIT. Source: <https://github.com/Black-Rainbow-Labs/Inillucent>
83
+
84
+ ## The API
85
+
86
+ Every method this binding has. The worked example each one appears in is the link; nothing here is
87
+ a summary of a method that does not exist, because
88
+ `cargo test -p inillucent-compat --test documentation` reads this table and fails on a name the
89
+ binding source does not declare.
90
+
91
+ | what | one line |
92
+ |---|---|
93
+ | `inillucent(command, options)` | run one command of the command line and return its parsed JSON. `options.db` names the file, `options.args` the rest. |
94
+ | `query(sql, options)` | run one `SELECT` and return its rows. `options.params` binds `?1`, `?2`; `options.limit` caps the rows kept. |
95
+ | `resolveBinary(program)` | the path to one of the four programs on this platform, from the platform package npm installed. |
96
+ | `platformPackage()` | the name of the platform package this machine needs, which is what an install failure should name. |
97
+ | `PROGRAMS` | the four programs and what each is for, as an object. |
98
+
99
+ Every call goes through the command line rather than through the C ABI: the four programs are what
100
+ the platform package ships, and `--output json` is the same object every other binding sees. That
101
+ is why there is no `Connection` here and no transaction - a command is one process, and a
102
+ transaction that spanned two of them would be a transaction nothing held open.
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // The `inillucent-mcp` shim: resolve the platform binary and become it.
3
+ //
4
+ // `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
5
+ // execve and this has to work on Windows. The child's exit code and its signal
6
+ // are both passed back up: a wrapper that always exited 0 would break every
7
+ // script that branches on the exit code, and inillucent's exit codes carry
8
+ // meaning - 3 is "the engine has not built that".
9
+ import { spawn } from 'node:child_process';
10
+ import { resolveBinary } from '../resolve.mjs';
11
+
12
+ let binary;
13
+ try {
14
+ binary = resolveBinary('inillucent-mcp');
15
+ } catch (why) {
16
+ process.stderr.write(String(why.message ?? why) + '\n');
17
+ process.exit(1);
18
+ }
19
+
20
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
21
+ child.on('error', (why) => {
22
+ process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
23
+ process.exit(1);
24
+ });
25
+ child.on('exit', (code, signal) => {
26
+ if (signal) {
27
+ // Re-raise it on ourselves so a shell sees the same thing it would have
28
+ // seen from the real program, rather than a plain exit.
29
+ process.kill(process.pid, signal);
30
+ return;
31
+ }
32
+ process.exit(code ?? 0);
33
+ });
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // The `inillucent-migrate` shim: resolve the platform binary and become it.
3
+ //
4
+ // `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
5
+ // execve and this has to work on Windows. The child's exit code and its signal
6
+ // are both passed back up: a wrapper that always exited 0 would break every
7
+ // script that branches on the exit code, and inillucent's exit codes carry
8
+ // meaning - 3 is "the engine has not built that".
9
+ import { spawn } from 'node:child_process';
10
+ import { resolveBinary } from '../resolve.mjs';
11
+
12
+ let binary;
13
+ try {
14
+ binary = resolveBinary('inillucent-migrate');
15
+ } catch (why) {
16
+ process.stderr.write(String(why.message ?? why) + '\n');
17
+ process.exit(1);
18
+ }
19
+
20
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
21
+ child.on('error', (why) => {
22
+ process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
23
+ process.exit(1);
24
+ });
25
+ child.on('exit', (code, signal) => {
26
+ if (signal) {
27
+ // Re-raise it on ourselves so a shell sees the same thing it would have
28
+ // seen from the real program, rather than a plain exit.
29
+ process.kill(process.pid, signal);
30
+ return;
31
+ }
32
+ process.exit(code ?? 0);
33
+ });
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // The `inillucent-shell` shim: resolve the platform binary and become it.
3
+ //
4
+ // `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
5
+ // execve and this has to work on Windows. The child's exit code and its signal
6
+ // are both passed back up: a wrapper that always exited 0 would break every
7
+ // script that branches on the exit code, and inillucent's exit codes carry
8
+ // meaning - 3 is "the engine has not built that".
9
+ import { spawn } from 'node:child_process';
10
+ import { resolveBinary } from '../resolve.mjs';
11
+
12
+ let binary;
13
+ try {
14
+ binary = resolveBinary('inillucent-shell');
15
+ } catch (why) {
16
+ process.stderr.write(String(why.message ?? why) + '\n');
17
+ process.exit(1);
18
+ }
19
+
20
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
21
+ child.on('error', (why) => {
22
+ process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
23
+ process.exit(1);
24
+ });
25
+ child.on('exit', (code, signal) => {
26
+ if (signal) {
27
+ // Re-raise it on ourselves so a shell sees the same thing it would have
28
+ // seen from the real program, rather than a plain exit.
29
+ process.kill(process.pid, signal);
30
+ return;
31
+ }
32
+ process.exit(code ?? 0);
33
+ });
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // The `inillucent` shim: resolve the platform binary and become it.
3
+ //
4
+ // `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
5
+ // execve and this has to work on Windows. The child's exit code and its signal
6
+ // are both passed back up: a wrapper that always exited 0 would break every
7
+ // script that branches on the exit code, and inillucent's exit codes carry
8
+ // meaning - 3 is "the engine has not built that".
9
+ import { spawn } from 'node:child_process';
10
+ import { resolveBinary } from '../resolve.mjs';
11
+
12
+ let binary;
13
+ try {
14
+ binary = resolveBinary('inillucent');
15
+ } catch (why) {
16
+ process.stderr.write(String(why.message ?? why) + '\n');
17
+ process.exit(1);
18
+ }
19
+
20
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
21
+ child.on('error', (why) => {
22
+ process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
23
+ process.exit(1);
24
+ });
25
+ child.on('exit', (code, signal) => {
26
+ if (signal) {
27
+ // Re-raise it on ourselves so a shell sees the same thing it would have
28
+ // seen from the real program, rather than a plain exit.
29
+ process.kill(process.pid, signal);
30
+ return;
31
+ }
32
+ process.exit(code ?? 0);
33
+ });
package/index.mjs ADDED
@@ -0,0 +1,197 @@
1
+ // inillucent, from Node.
2
+ //
3
+ // **This is not a database binding.** It is the four programs, plus one
4
+ // convenience for calling the command line and getting a typed result back.
5
+ // A real binding would go through the C ABI in
6
+ // `drivers/inillucent-driver-capi/include/inillucent_driver.h` with koffi or an
7
+ // N-API addon, and `drivers/README.md` says exactly how to write one - it is a
8
+ // worthwhile thing to have and it is not this.
9
+ //
10
+ // What this is for: a script or an agent harness that wants to run a query
11
+ // without shelling out by hand and parsing a table. Each call is a process, so
12
+ // it is the wrong tool for a loop over a million rows and the right one for the
13
+ // dozen calls a build script or a tool wrapper makes.
14
+
15
+ import { execFile } from 'node:child_process';
16
+ import { promisify } from 'node:util';
17
+
18
+ import { resolveBinary, PROGRAMS, platformPackage } from './resolve.mjs';
19
+
20
+ const run = promisify(execFile);
21
+
22
+ /**
23
+ * Encodes one bound value as the JSON text the command line reads.
24
+ *
25
+ * **`JSON.stringify` loses three things a parameter can be (task-1979, D13 and
26
+ * D16).** A NaN and an Infinity both become `null`, so a REAL column silently
27
+ * stored NULL and nothing said so; `-0` becomes `0`, so the sign of negative
28
+ * zero was gone before the value left Node; and a byte string has no JSON form
29
+ * at all, so there was no way to bind a BLOB.
30
+ *
31
+ * What is written instead: a non-finite number throws here, where the caller
32
+ * can see which value it was, rather than turning into a NULL nobody asked for.
33
+ * Negative zero is written `-0.0`, which JSON's own grammar carries and the
34
+ * command line's parser reads back as a negative zero. Bytes are written
35
+ * `{"blob":"<hex>"}`.
36
+ *
37
+ * @param value - one element of `params`
38
+ */
39
+ function encodeParam(value) {
40
+ if (typeof value === 'number') {
41
+ if (!Number.isFinite(value)) {
42
+ throw new TypeError(
43
+ `a parameter cannot be ${value}: SQL has no spelling for NaN or Infinity.`,
44
+ );
45
+ }
46
+ return Object.is(value, -0) ? '-0.0' : JSON.stringify(value);
47
+ }
48
+ if (value instanceof Uint8Array) {
49
+ const hex = Buffer.from(value).toString('hex');
50
+ return JSON.stringify({ blob: hex });
51
+ }
52
+ if (Array.isArray(value)) {
53
+ return `[${value.map(encodeParam).join(',')}]`;
54
+ }
55
+ return JSON.stringify(value === undefined ? null : value);
56
+ }
57
+
58
+ /**
59
+ * Encodes the whole `params` array as JSON text.
60
+ *
61
+ * @param values - the bound values, in order
62
+ */
63
+ function encodeParams(values) {
64
+ return `[${values.map(encodeParam).join(',')}]`;
65
+ }
66
+
67
+ export { resolveBinary, PROGRAMS, platformPackage };
68
+
69
+ /**
70
+ * Runs a program, writing `stdin` to it when there is any.
71
+ *
72
+ * `execFile` cannot write to standard input, so a call that has something to
73
+ * write goes through `spawn` and the two are answered the same way: `{ stdout,
74
+ * stderr }`, or a rejection carrying both plus the exit code, which is the
75
+ * shape the caller below already handles.
76
+ *
77
+ * @param binary - the program to run
78
+ * @param args - its command line
79
+ * @param stdin - what to write to its standard input, or null
80
+ */
81
+ async function spawnWith(binary, args, stdin) {
82
+ if (stdin === null) {
83
+ return run(binary, args, { maxBuffer: 256 * 1024 * 1024 });
84
+ }
85
+ const { spawn } = await import('node:child_process');
86
+ return new Promise((resolve, reject) => {
87
+ const child = spawn(binary, args, { stdio: ['pipe', 'pipe', 'pipe'] });
88
+ let stdout = '';
89
+ let stderr = '';
90
+ child.stdout.on('data', (chunk) => {
91
+ stdout += chunk;
92
+ });
93
+ child.stderr.on('data', (chunk) => {
94
+ stderr += chunk;
95
+ });
96
+ child.on('error', reject);
97
+ child.on('close', (code) => {
98
+ if (code === 0) {
99
+ resolve({ stdout, stderr });
100
+ return;
101
+ }
102
+ const why = new Error(`inillucent exited ${code}`);
103
+ why.stdout = stdout;
104
+ why.stderr = stderr;
105
+ why.code = code;
106
+ reject(why);
107
+ });
108
+ child.stdin.on('error', () => {});
109
+ child.stdin.end(stdin);
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Runs one inillucent command and returns its result object.
115
+ *
116
+ * The command line's `--output json` contract: `{ ok, command, columns, rows,
117
+ * total, more, changes, last_insert_rowid, elapsed_ms, text }` on success, and
118
+ * `{ ok: false, status, message, ... }` on failure. `status` is one of the
119
+ * driver's thirteen names, and `unsupported` is its own - a construct the
120
+ * engine has not built is not a syntax error and should not be handled as one.
121
+ *
122
+ * A failure is returned rather than thrown, because the interesting failures
123
+ * here are answers: "no such table", "this construct is not built yet". Only a
124
+ * broken invocation throws.
125
+ *
126
+ * @param command - the verb, such as "query" or "describe"
127
+ * @param options - the named arguments the verb takes, plus `db`
128
+ */
129
+ export async function inillucent(command, options = {}) {
130
+ const { db, ...rest } = options;
131
+ const args = [command, '--output', 'json'];
132
+ let stdin = null;
133
+ if (db) {
134
+ args.push('--db', db);
135
+ }
136
+ for (const [name, value] of Object.entries(rest)) {
137
+ if (value === undefined || value === null) {
138
+ continue;
139
+ }
140
+ if (value === true) {
141
+ args.push(`--${name}`);
142
+ continue;
143
+ }
144
+ if (value === false) {
145
+ continue;
146
+ }
147
+ // **`params` travels on standard input (task-1979, D17).** A command line
148
+ // has a length ceiling - about 32 KB on Windows - and a parameter past it
149
+ // failed with an operating system error rather than with anything about
150
+ // SQL. `--params-file -` has no such limit, and it is also what lets the
151
+ // encoding above carry bytes and a negative zero.
152
+ if (name === 'params' && Array.isArray(value)) {
153
+ stdin = encodeParams(value);
154
+ args.push('--params-file', '-');
155
+ continue;
156
+ }
157
+ // Any other array is a JSON argument - `vector` is the one - and the
158
+ // command line reads it as JSON, so it is serialised rather than joined.
159
+ args.push(`--${name}`, Array.isArray(value) ? JSON.stringify(value) : String(value));
160
+ }
161
+ const binary = resolveBinary('inillucent');
162
+ try {
163
+ const { stdout } = await spawnWith(binary, args, stdin);
164
+ return JSON.parse(stdout);
165
+ } catch (why) {
166
+ // A non-zero exit still prints the result object on standard output when
167
+ // `--output json` was asked for, so the failure the caller wants is in
168
+ // there. Only a truly broken invocation has nothing to parse.
169
+ if (typeof why.stdout === 'string' && why.stdout.trim().startsWith('{')) {
170
+ return JSON.parse(why.stdout);
171
+ }
172
+ throw new Error(
173
+ `inillucent ${command} could not be run: ${why.stderr || why.message}`,
174
+ );
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Runs a query and returns its rows as objects keyed by column name.
180
+ *
181
+ * The shape most callers actually want. `inillucent()` is there for the ones
182
+ * that need the counts, the timing or the failure class.
183
+ *
184
+ * @param sql - the statement
185
+ * @param options - `db`, `params`, `limit`
186
+ */
187
+ export async function query(sql, options = {}) {
188
+ const result = await inillucent('query', { sql, ...options });
189
+ if (!result.ok) {
190
+ const error = new Error(result.message);
191
+ error.status = result.status;
192
+ error.feature = result.feature;
193
+ throw error;
194
+ }
195
+ const names = result.columns.map((column) => column.name);
196
+ return result.rows.map((row) => Object.fromEntries(names.map((name, at) => [name, row[at]])));
197
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "inillucent",
3
+ "version": "0.1.4",
4
+ "description": "Embedded SQL database with full-text and vector search, an MCP server, and a sqlite3-shaped shell. No native build step: the binaries ship as platform packages.",
5
+ "keywords": [
6
+ "database",
7
+ "sql",
8
+ "sqlite",
9
+ "vector",
10
+ "embedding",
11
+ "search",
12
+ "mcp",
13
+ "cli",
14
+ "rag"
15
+ ],
16
+ "homepage": "https://github.com/Black-Rainbow-Labs/Inillucent#readme",
17
+ "bugs": "https://github.com/Black-Rainbow-Labs/Inillucent/issues",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/Black-Rainbow-Labs/Inillucent.git",
21
+ "directory": "packages/npm/inillucent"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Black Rainbow Labs",
25
+ "type": "module",
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "bin": {
30
+ "inillucent": "bin/inillucent.mjs",
31
+ "inillucent-shell": "bin/inillucent-shell.mjs",
32
+ "inillucent-mcp": "bin/inillucent-mcp.mjs",
33
+ "inillucent-migrate": "bin/inillucent-migrate.mjs"
34
+ },
35
+ "main": "index.mjs",
36
+ "exports": {
37
+ ".": "./index.mjs"
38
+ },
39
+ "files": [
40
+ "bin",
41
+ "index.mjs",
42
+ "resolve.mjs",
43
+ "README.md"
44
+ ],
45
+ "optionalDependencies": {
46
+ "@blackrainbowlabs/cli-win32-x64": "0.1.4",
47
+ "@blackrainbowlabs/cli-darwin-arm64": "0.1.4",
48
+ "@blackrainbowlabs/cli-darwin-x64": "0.1.4",
49
+ "@blackrainbowlabs/cli-linux-x64": "0.1.4",
50
+ "@blackrainbowlabs/cli-linux-arm64": "0.1.4"
51
+ }
52
+ }
package/resolve.mjs ADDED
@@ -0,0 +1,114 @@
1
+ // Finds the binary for the machine this is running on.
2
+ //
3
+ // The arrangement is esbuild's, and it is the right one: the binaries live in
4
+ // per-platform packages listed as `optionalDependencies`, npm installs only the
5
+ // one that matches, and this package's `bin` entries are tiny shims that
6
+ // resolve it and hand over. There is no postinstall script and nothing is
7
+ // downloaded at install time, which means `npm ci` works offline, in a locked
8
+ // CI, and behind a registry proxy - none of which is true of a package that
9
+ // fetches a binary from GitHub when it is installed.
10
+ //
11
+ // The cost is that a platform without a published package cannot install, and
12
+ // the failure has to say so in a sentence somebody can act on rather than as an
13
+ // unresolved import. That is what `resolveBinary` is for.
14
+
15
+ import { createRequire } from 'node:module';
16
+ import { accessSync, constants, existsSync } from 'node:fs';
17
+ import { dirname, join } from 'node:path';
18
+
19
+ const require = createRequire(import.meta.url);
20
+
21
+ /** The platform packages, by the `process.platform`-`process.arch` pair each serves. */
22
+ const PACKAGES = {
23
+ 'win32-x64': '@inillucent/cli-win32-x64',
24
+ 'darwin-arm64': '@inillucent/cli-darwin-arm64',
25
+ 'darwin-x64': '@inillucent/cli-darwin-x64',
26
+ 'linux-x64': '@inillucent/cli-linux-x64',
27
+ 'linux-arm64': '@inillucent/cli-linux-arm64',
28
+ };
29
+
30
+ /** The four programs the release ships, and what each one is for. */
31
+ export const PROGRAMS = {
32
+ inillucent: 'the command line: query, exec, describe, import, export, search',
33
+ 'inillucent-shell': 'the interactive sqlite3-shaped shell',
34
+ 'inillucent-mcp': 'the MCP server, for an agent',
35
+ 'inillucent-migrate': 'builds an inillucent database from a SQLite file',
36
+ };
37
+
38
+ /**
39
+ * Returns the platform package name for this machine, or null if there is none.
40
+ */
41
+ export function platformPackage() {
42
+ return PACKAGES[`${process.platform}-${process.arch}`] ?? null;
43
+ }
44
+
45
+ /**
46
+ * Returns the absolute path of one of the four programs on this machine.
47
+ *
48
+ * @param program - which program, as it is named in PROGRAMS
49
+ */
50
+ export function resolveBinary(program) {
51
+ if (!(program in PROGRAMS)) {
52
+ throw new Error(`inillucent has no program called ${program}`);
53
+ }
54
+ // **`INILLUCENT_BIN` wins, the way it already does for the Go and PHP
55
+ // wrappers (task-1969, 4.5).** It names the `inillucent` binary; the others
56
+ // are looked for beside it, which is where a build and an install both put
57
+ // them. Without this the wrappers stage of `tools/validate` could point the
58
+ // other two languages at a freshly built binary and had no way to point this
59
+ // one, so the only npm test that could run was the one that reads the
60
+ // platform table as text.
61
+ //
62
+ // It throws rather than falling through when the named binary is not there:
63
+ // a caller who says where the binary is and is wrong wants to know, not to
64
+ // have the resolver quietly go looking somewhere else.
65
+ const named = process.env.INILLUCENT_BIN;
66
+ if (named) {
67
+ const suffix = process.platform === 'win32' ? '.exe' : '';
68
+ const beside = program === 'inillucent'
69
+ ? named
70
+ : join(dirname(named), `${program}${suffix}`);
71
+ if (!existsSync(beside)) {
72
+ throw new Error(
73
+ `INILLUCENT_BIN is set and ${beside} is not there.
74
+ ` +
75
+ ` Unset INILLUCENT_BIN to look for an installed copy instead.`,
76
+ );
77
+ }
78
+ return beside;
79
+ }
80
+ const name = platformPackage();
81
+ if (!name) {
82
+ throw new Error(
83
+ `inillucent has no prebuilt binary for ${process.platform}-${process.arch}.\n` +
84
+ ` Build it from source instead: cargo install inillucent-cli\n` +
85
+ ` Or open an issue: https://github.com/Black-Rainbow-Labs/Inillucent/issues`,
86
+ );
87
+ }
88
+ const suffix = process.platform === 'win32' ? '.exe' : '';
89
+ let path;
90
+ try {
91
+ // The platform package's own entry point tells us where its bin directory
92
+ // is, rather than this package guessing at a node_modules layout - which
93
+ // pnpm, yarn's pnp and a hoisted npm tree all arrange differently.
94
+ path = require.resolve(`${name}/bin/${program}${suffix}`);
95
+ } catch {
96
+ throw new Error(
97
+ `inillucent's binary for ${process.platform}-${process.arch} is not installed.\n` +
98
+ ` The package ${name} should have been installed as an optional dependency.\n` +
99
+ ` If your installer was run with --no-optional, install it directly:\n` +
100
+ ` npm install ${name}\n`,
101
+ );
102
+ }
103
+ try {
104
+ accessSync(path, constants.X_OK);
105
+ } catch {
106
+ // An npm tarball does not always preserve the executable bit, and the
107
+ // failure it produces otherwise is EACCES from execve with no explanation.
108
+ throw new Error(
109
+ `${path} is not executable.\n Fix it with: chmod +x ${path}\n` +
110
+ ` Then please report it: https://github.com/Black-Rainbow-Labs/Inillucent/issues`,
111
+ );
112
+ }
113
+ return path;
114
+ }