@dug-21/unimatrix 0.5.5

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.
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { execFileSync } = require("child_process");
5
+
6
+ function main() {
7
+ const args = process.argv.slice(2);
8
+
9
+ // Route "init" to JS implementation (ADR-003)
10
+ if (args[0] === "init") {
11
+ const { init } = require("../lib/init.js");
12
+ const projectDirIdx = args.indexOf("--project-dir");
13
+ const projectDir =
14
+ projectDirIdx >= 0 && projectDirIdx + 1 < args.length
15
+ ? args[projectDirIdx + 1]
16
+ : undefined;
17
+ init({ dryRun: args.includes("--dry-run"), projectDir })
18
+ .then(() => {
19
+ process.exitCode = 0;
20
+ })
21
+ .catch((error) => {
22
+ process.stderr.write("unimatrix init failed: " + error.message + "\n");
23
+ process.exitCode = 1;
24
+ });
25
+ return;
26
+ }
27
+
28
+ // All other subcommands: resolve binary and exec
29
+ let binaryPath;
30
+ try {
31
+ binaryPath = require("../lib/resolve-binary.js").resolveBinary();
32
+ } catch (error) {
33
+ process.stderr.write(error.message + "\n");
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+
38
+ // Ensure bundled shared libraries (libonnxruntime) are found at runtime
39
+ const binDir = require("path").dirname(binaryPath);
40
+ const ldPath = process.env.LD_LIBRARY_PATH;
41
+ const env = Object.assign({}, process.env, {
42
+ LD_LIBRARY_PATH: ldPath ? binDir + ":" + ldPath : binDir,
43
+ });
44
+
45
+ try {
46
+ execFileSync(binaryPath, args, { stdio: "inherit", env: env });
47
+ } catch (error) {
48
+ // execFileSync throws on non-zero exit code
49
+ // error.status contains the exit code from the child process
50
+ if (error.status !== null && error.status !== undefined) {
51
+ process.exitCode = error.status;
52
+ } else {
53
+ // Signal death or spawn failure
54
+ process.stderr.write(
55
+ "Failed to execute unimatrix: " + error.message + "\n"
56
+ );
57
+ process.exitCode = 1;
58
+ }
59
+ }
60
+ }
61
+
62
+ main();
package/lib/init.js ADDED
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { execFileSync } = require("child_process");
6
+ const { resolveBinary } = require("./resolve-binary.js");
7
+ const { mergeSettings } = require("./merge-settings.js");
8
+
9
+ const HOOK_EVENTS = [
10
+ "SessionStart",
11
+ "Stop",
12
+ "UserPromptSubmit",
13
+ "PreToolUse",
14
+ "PostToolUse",
15
+ "SubagentStart",
16
+ "SubagentStop",
17
+ ];
18
+
19
+ /**
20
+ * Detect project root by walking up from startDir to find .git directory.
21
+ * Mirrors the Rust detect_project_root algorithm (ADR-003).
22
+ *
23
+ * @param {string} startDir - Directory to start searching from.
24
+ * @returns {string} Absolute path to the project root.
25
+ */
26
+ function detectProjectRoot(startDir) {
27
+ let current = path.resolve(startDir);
28
+ for (;;) {
29
+ if (fs.existsSync(path.join(current, ".git"))) {
30
+ return current;
31
+ }
32
+ const parent = path.dirname(current);
33
+ if (parent === current) {
34
+ throw new Error(
35
+ "Could not find project root (.git directory).\n" +
36
+ "Run this command from within a git repository."
37
+ );
38
+ }
39
+ current = parent;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Write or merge .mcp.json with the unimatrix server entry.
45
+ * Preserves existing servers. Malformed JSON causes an error (ADR-004).
46
+ *
47
+ * @param {string} projectRoot - Absolute path to project root.
48
+ * @param {string} binaryPath - Absolute path to the unimatrix binary.
49
+ * @param {boolean} dryRun - If true, do not write the file.
50
+ * @returns {string[]} Actions taken.
51
+ */
52
+ function writeMcpJson(projectRoot, binaryPath, dryRun) {
53
+ const mcpPath = path.join(projectRoot, ".mcp.json");
54
+ const actions = [];
55
+ let existing = {};
56
+
57
+ if (fs.existsSync(mcpPath)) {
58
+ try {
59
+ existing = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
60
+ } catch (parseError) {
61
+ throw new Error(
62
+ "Malformed .mcp.json at " +
63
+ mcpPath +
64
+ ": " +
65
+ parseError.message +
66
+ "\nFix the JSON syntax and re-run 'npx unimatrix init'."
67
+ );
68
+ }
69
+ actions.push("Updated .mcp.json (preserved existing servers)");
70
+ } else {
71
+ actions.push("Created .mcp.json");
72
+ }
73
+
74
+ if (!existing.mcpServers) {
75
+ existing.mcpServers = {};
76
+ }
77
+
78
+ existing.mcpServers.unimatrix = {
79
+ command: binaryPath,
80
+ args: [],
81
+ env: {},
82
+ };
83
+
84
+ if (!dryRun) {
85
+ fs.writeFileSync(
86
+ mcpPath,
87
+ JSON.stringify(existing, null, 2) + "\n",
88
+ "utf8"
89
+ );
90
+ } else {
91
+ actions[actions.length - 1] = "[dry-run] " + actions[actions.length - 1];
92
+ }
93
+
94
+ return actions;
95
+ }
96
+
97
+ /**
98
+ * Copy bundled skill files from the package's skills/ directory
99
+ * into the project's .claude/skills/ directory. Overwrites existing
100
+ * unimatrix skills, preserves non-unimatrix skills.
101
+ *
102
+ * @param {string} projectRoot - Absolute path to project root.
103
+ * @param {boolean} dryRun - If true, do not copy files.
104
+ * @returns {string[]} Actions taken.
105
+ */
106
+ function copySkills(projectRoot, dryRun) {
107
+ const actions = [];
108
+ const targetDir = path.join(projectRoot, ".claude", "skills");
109
+ const sourceDir = path.join(__dirname, "..", "skills");
110
+
111
+ if (!fs.existsSync(sourceDir)) {
112
+ actions.push("No bundled skills found (skipped)");
113
+ return actions;
114
+ }
115
+
116
+ if (!dryRun) {
117
+ fs.mkdirSync(targetDir, { recursive: true });
118
+ }
119
+
120
+ const skillDirs = fs
121
+ .readdirSync(sourceDir, { withFileTypes: true })
122
+ .filter((d) => d.isDirectory())
123
+ .map((d) => d.name);
124
+
125
+ for (const skillDir of skillDirs) {
126
+ const src = path.join(sourceDir, skillDir);
127
+ const dst = path.join(targetDir, skillDir);
128
+
129
+ if (!dryRun) {
130
+ fs.mkdirSync(dst, { recursive: true });
131
+
132
+ const files = fs.readdirSync(src);
133
+ for (const file of files) {
134
+ if (file.includes("..")) {
135
+ throw new Error(
136
+ "Path traversal detected in skill file: " + file
137
+ );
138
+ }
139
+
140
+ const srcFile = path.join(src, file);
141
+ const dstFile = path.join(dst, file);
142
+
143
+ // Only copy files, not subdirectories
144
+ const stat = fs.statSync(srcFile);
145
+ if (stat.isFile()) {
146
+ fs.copyFileSync(srcFile, dstFile);
147
+ }
148
+ }
149
+
150
+ actions.push("Copied skill: " + skillDir);
151
+ } else {
152
+ actions.push("[dry-run] Would copy skill: " + skillDir);
153
+ }
154
+ }
155
+
156
+ return actions;
157
+ }
158
+
159
+ /**
160
+ * Print summary of all actions taken during init.
161
+ *
162
+ * @param {string[]} actions - List of action descriptions.
163
+ * @param {boolean} dryRun - Whether this was a dry run.
164
+ */
165
+ function printSummary(actions, dryRun) {
166
+ if (dryRun) {
167
+ console.log("\n--- Dry Run Summary ---\n");
168
+ } else {
169
+ console.log("\n--- Unimatrix Init Complete ---\n");
170
+ }
171
+
172
+ for (const action of actions) {
173
+ console.log(" " + action);
174
+ }
175
+
176
+ console.log("");
177
+ if (!dryRun) {
178
+ console.log("Next step: start a Claude Code session and run /unimatrix-init");
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Deterministic, non-interactive, idempotent project wiring.
184
+ * Configures MCP server, hooks, skills, and pre-creates the database.
185
+ * Implemented in JavaScript per ADR-003.
186
+ *
187
+ * @param {object} options
188
+ * @param {boolean} [options.dryRun=false] - Print actions without modifying files.
189
+ * @param {string} [options.projectDir] - Override project root (skip .git walk).
190
+ */
191
+ async function init(options) {
192
+ const dryRun = (options && options.dryRun) || false;
193
+ const actions = [];
194
+
195
+ // Step 1: Resolve project root
196
+ let projectRoot;
197
+ if (options && options.projectDir) {
198
+ projectRoot = path.resolve(options.projectDir);
199
+ } else {
200
+ projectRoot = detectProjectRoot(process.cwd());
201
+ }
202
+ actions.push("Project root: " + projectRoot);
203
+
204
+ // Step 2: Resolve binary path
205
+ const binaryPath = resolveBinary();
206
+ actions.push("Binary: " + binaryPath);
207
+
208
+ // Step 3: Write/merge .mcp.json
209
+ const mcpActions = writeMcpJson(projectRoot, binaryPath, dryRun);
210
+ actions.push(...mcpActions);
211
+
212
+ // Step 4: Merge hooks into .claude/settings.json
213
+ const settingsPath = path.join(projectRoot, ".claude", "settings.json");
214
+ const settingsResult = mergeSettings(settingsPath, binaryPath, { dryRun });
215
+ actions.push(...settingsResult.actions);
216
+
217
+ // Step 5: Copy skill files
218
+ const skillActions = copySkills(projectRoot, dryRun);
219
+ actions.push(...skillActions);
220
+
221
+ // Step 6: Pre-create database (exec Rust binary)
222
+ if (!dryRun) {
223
+ try {
224
+ execFileSync(binaryPath, ["version", "--project-dir", projectRoot], {
225
+ stdio: "pipe",
226
+ });
227
+ actions.push("Database: pre-created at ~/.unimatrix/{hash}/");
228
+ } catch (error) {
229
+ const stderr =
230
+ error.stderr ? error.stderr.toString() : error.message;
231
+ throw new Error("Database creation failed: " + stderr);
232
+ }
233
+ } else {
234
+ actions.push(
235
+ "[dry-run] Would pre-create database via: unimatrix version --project-dir " +
236
+ projectRoot
237
+ );
238
+ }
239
+
240
+ // Step 7: Validate binary
241
+ if (!dryRun) {
242
+ try {
243
+ const versionOutput = execFileSync(binaryPath, ["version"], {
244
+ stdio: "pipe",
245
+ encoding: "utf8",
246
+ }).trim();
247
+ actions.push("Validation: " + versionOutput);
248
+ } catch (error) {
249
+ const stderr =
250
+ error.stderr ? error.stderr.toString() : error.message;
251
+ throw new Error("Binary validation failed: " + stderr);
252
+ }
253
+ } else {
254
+ actions.push("[dry-run] Would validate binary via: unimatrix version");
255
+ }
256
+
257
+ // Step 8: Print summary
258
+ printSummary(actions, dryRun);
259
+ }
260
+
261
+ module.exports = {
262
+ init,
263
+ detectProjectRoot,
264
+ writeMcpJson,
265
+ copySkills,
266
+ printSummary,
267
+ };
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ /**
7
+ * Regex patterns to identify Unimatrix-owned hook entries (ADR-004).
8
+ * Matches both current "unimatrix" and pre-rename "unimatrix-server" commands,
9
+ * whether bare names or absolute paths.
10
+ */
11
+ const UNIMATRIX_PATTERNS = [
12
+ /^unimatrix\s+hook\s/,
13
+ /^unimatrix-server\s+hook\s/,
14
+ /\/unimatrix\s+hook\s/,
15
+ /\/unimatrix-server\s+hook\s/,
16
+ ];
17
+
18
+ const HOOK_EVENTS = [
19
+ "SessionStart",
20
+ "Stop",
21
+ "UserPromptSubmit",
22
+ "PreToolUse",
23
+ "PostToolUse",
24
+ "SubagentStart",
25
+ "SubagentStop",
26
+ ];
27
+
28
+ /** Matcher per event: "" for session-level, "*" for tool/agent-level */
29
+ const EVENT_MATCHERS = {
30
+ SessionStart: "",
31
+ Stop: "",
32
+ UserPromptSubmit: "",
33
+ PreToolUse: "*",
34
+ PostToolUse: "*",
35
+ SubagentStart: "*",
36
+ SubagentStop: "*",
37
+ };
38
+
39
+ /**
40
+ * Returns true if a hook entry is owned by Unimatrix, identified by
41
+ * prefix-matching the command field against known patterns.
42
+ *
43
+ * @param {object} hookEntry - A hook entry object with a `command` field.
44
+ * @returns {boolean}
45
+ */
46
+ function isUnimatrixHook(hookEntry) {
47
+ if (!hookEntry || !hookEntry.command || typeof hookEntry.command !== "string") {
48
+ return false;
49
+ }
50
+ return UNIMATRIX_PATTERNS.some((pattern) => pattern.test(hookEntry.command));
51
+ }
52
+
53
+ /**
54
+ * Merge Unimatrix hook configuration into .claude/settings.json.
55
+ *
56
+ * Implements ADR-004 prefix-match identification. Preserves all non-unimatrix
57
+ * hooks, permissions, and other top-level keys. Idempotent: running twice
58
+ * produces the same result.
59
+ *
60
+ * @param {string} filePath - Path to .claude/settings.json
61
+ * @param {string} binaryPath - Absolute path to the unimatrix binary
62
+ * @param {object} options - { dryRun: boolean }
63
+ * @returns {{ actions: string[], content: object }}
64
+ */
65
+ function mergeSettings(filePath, binaryPath, options) {
66
+ const dryRun = (options && options.dryRun) || false;
67
+ const actions = [];
68
+ let content = {};
69
+
70
+ // Step 1: Read existing file
71
+ if (fs.existsSync(filePath)) {
72
+ const raw = fs.readFileSync(filePath, "utf8").trim();
73
+
74
+ if (raw === "") {
75
+ content = {};
76
+ actions.push("settings.json was empty, initializing");
77
+ } else {
78
+ try {
79
+ content = JSON.parse(raw);
80
+ } catch (parseError) {
81
+ throw new Error(
82
+ "Malformed .claude/settings.json: " +
83
+ parseError.message +
84
+ "\nFix the JSON syntax manually and re-run 'npx unimatrix init'." +
85
+ "\nFile: " +
86
+ filePath
87
+ );
88
+ }
89
+ }
90
+ } else {
91
+ actions.push("Created .claude/settings.json");
92
+ }
93
+
94
+ // Step 2: Ensure hooks key exists
95
+ if (!content.hooks) {
96
+ content.hooks = {};
97
+ }
98
+
99
+ // Validate hooks is an object (not array, not primitive)
100
+ if (typeof content.hooks !== "object" || Array.isArray(content.hooks)) {
101
+ throw new Error(
102
+ ".claude/settings.json 'hooks' key is not an object." +
103
+ '\nExpected: { "hooks": { "EventName": [...] } }' +
104
+ "\nFile: " +
105
+ filePath
106
+ );
107
+ }
108
+
109
+ // Step 3: For each hook event, merge the unimatrix entry
110
+ for (const event of HOOK_EVENTS) {
111
+ const hookCommand = binaryPath + " hook " + event;
112
+ const matcher = EVENT_MATCHERS[event];
113
+
114
+ const newHookEntry = {
115
+ type: "command",
116
+ command: hookCommand,
117
+ };
118
+
119
+ // The settings format is: hooks.EventName = [ { matcher, hooks: [...] } ]
120
+ if (!content.hooks[event]) {
121
+ content.hooks[event] = [];
122
+ }
123
+
124
+ const eventArray = content.hooks[event];
125
+ let merged = false;
126
+
127
+ for (const matcherGroup of eventArray) {
128
+ if (matcherGroup.matcher === matcher) {
129
+ // Found a matcher group for our matcher value
130
+ if (!matcherGroup.hooks) {
131
+ matcherGroup.hooks = [];
132
+ }
133
+
134
+ // Look for existing unimatrix hook to update
135
+ let existingIndex = -1;
136
+ const duplicateIndices = [];
137
+ for (let i = 0; i < matcherGroup.hooks.length; i++) {
138
+ if (isUnimatrixHook(matcherGroup.hooks[i])) {
139
+ if (existingIndex === -1) {
140
+ existingIndex = i;
141
+ } else {
142
+ duplicateIndices.push(i);
143
+ }
144
+ }
145
+ }
146
+
147
+ // Remove duplicates in reverse order (dedup on re-run, per ADR-004)
148
+ for (let j = duplicateIndices.length - 1; j >= 0; j--) {
149
+ matcherGroup.hooks.splice(duplicateIndices[j], 1);
150
+ actions.push("Removed duplicate unimatrix hook for " + event);
151
+ }
152
+
153
+ if (existingIndex >= 0) {
154
+ matcherGroup.hooks[existingIndex] = newHookEntry;
155
+ actions.push("Updated hook: " + event);
156
+ } else {
157
+ matcherGroup.hooks.push(newHookEntry);
158
+ actions.push("Added hook: " + event);
159
+ }
160
+
161
+ merged = true;
162
+ break;
163
+ }
164
+ }
165
+
166
+ if (!merged) {
167
+ // No matcher group found for our matcher value; create one
168
+ eventArray.push({
169
+ matcher: matcher,
170
+ hooks: [newHookEntry],
171
+ });
172
+ actions.push("Added hook: " + event + " (new matcher group)");
173
+ }
174
+ }
175
+
176
+ // Step 4: Write file (or prefix actions with [dry-run])
177
+ if (!dryRun) {
178
+ const dir = path.dirname(filePath);
179
+ fs.mkdirSync(dir, { recursive: true });
180
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + "\n", "utf8");
181
+ }
182
+
183
+ const finalActions = dryRun ? actions.map((a) => "[dry-run] " + a) : actions;
184
+
185
+ return { actions: finalActions, content };
186
+ }
187
+
188
+ module.exports = { mergeSettings, isUnimatrixHook, HOOK_EVENTS, EVENT_MATCHERS, UNIMATRIX_PATTERNS };
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+
7
+ const PLATFORMS = {
8
+ "linux-x64": "@dug-21/unimatrix-linux-x64",
9
+ "linux-arm64": "@dug-21/unimatrix-linux-arm64",
10
+ };
11
+
12
+ /**
13
+ * Resolve the absolute path to the platform-specific Unimatrix binary.
14
+ *
15
+ * Resolution order:
16
+ * 1. UNIMATRIX_BINARY env var (development/testing override)
17
+ * 2. Platform package via require.resolve (optionalDependencies)
18
+ *
19
+ * @returns {string} Absolute real path to the binary
20
+ * @throws {Error} If binary cannot be found or env override points to missing file
21
+ */
22
+ function resolveBinary() {
23
+ // 1. Check environment variable override (development/testing)
24
+ const envPath = process.env.UNIMATRIX_BINARY;
25
+ if (envPath) {
26
+ if (!fs.existsSync(envPath)) {
27
+ throw new Error(
28
+ "UNIMATRIX_BINARY points to non-existent file: " + envPath
29
+ );
30
+ }
31
+ return fs.realpathSync(envPath);
32
+ }
33
+
34
+ // 2. Determine current platform key
35
+ const platformKey = os.platform() + "-" + os.arch();
36
+
37
+ // 3. Look up package name for this platform
38
+ const packageName = PLATFORMS[platformKey];
39
+ if (!packageName) {
40
+ throw new Error(
41
+ "Unsupported platform: " +
42
+ platformKey +
43
+ "\n" +
44
+ "Supported platforms: " +
45
+ Object.keys(PLATFORMS).join(", ") +
46
+ "\n" +
47
+ "Set UNIMATRIX_BINARY environment variable to use a custom binary path."
48
+ );
49
+ }
50
+
51
+ // 4. Resolve binary path via require.resolve
52
+ let binaryPath;
53
+ try {
54
+ binaryPath = require.resolve(packageName + "/bin/unimatrix");
55
+ } catch (_err) {
56
+ throw new Error(
57
+ "Could not find platform binary package '" +
58
+ packageName +
59
+ "'.\n" +
60
+ "Run 'npm install @dug-21/unimatrix' to install.\n" +
61
+ "If using pnpm or yarn, set UNIMATRIX_BINARY to the binary path."
62
+ );
63
+ }
64
+
65
+ // 5. Resolve through symlinks to get the real absolute path (ADR-001)
66
+ return fs.realpathSync(binaryPath);
67
+ }
68
+
69
+ module.exports = { resolveBinary, PLATFORMS };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@dug-21/unimatrix",
3
+ "version": "0.5.5",
4
+ "description": "Unimatrix knowledge engine for multi-agent development",
5
+ "bin": {
6
+ "unimatrix": "bin/unimatrix.js"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node postinstall.js"
10
+ },
11
+ "optionalDependencies": {
12
+ "@dug-21/unimatrix-linux-x64": "0.5.5",
13
+ "@dug-21/unimatrix-linux-arm64": "0.5.5"
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "lib/",
18
+ "skills/",
19
+ "postinstall.js"
20
+ ],
21
+ "license": "MIT OR Apache-2.0",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/anthropics/unimatrix"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }
package/postinstall.js ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ function main() {
5
+ try {
6
+ var resolveBinary;
7
+ try {
8
+ resolveBinary = require("./lib/resolve-binary").resolveBinary;
9
+ } catch (_err) {
10
+ console.warn(
11
+ "[unimatrix] postinstall: could not load resolve-binary module, skipping model download"
12
+ );
13
+ process.exit(0);
14
+ }
15
+
16
+ var binaryPath;
17
+ try {
18
+ binaryPath = resolveBinary();
19
+ } catch (error) {
20
+ console.warn(
21
+ "[unimatrix] postinstall: platform binary not available (" +
22
+ error.message +
23
+ "), skipping model download"
24
+ );
25
+ process.exit(0);
26
+ }
27
+
28
+ var execFileSync = require("child_process").execFileSync;
29
+ var path = require("path");
30
+ var binDir = path.dirname(binaryPath);
31
+ var ldPath = process.env.LD_LIBRARY_PATH;
32
+ var env = Object.assign({}, process.env, {
33
+ LD_LIBRARY_PATH: ldPath ? binDir + ":" + ldPath : binDir,
34
+ });
35
+ try {
36
+ execFileSync(binaryPath, ["model-download"], {
37
+ stdio: ["ignore", "inherit", "inherit"],
38
+ timeout: 300000,
39
+ env: env,
40
+ });
41
+ console.log("[unimatrix] postinstall: ONNX model ready");
42
+ } catch (execError) {
43
+ console.warn(
44
+ "[unimatrix] postinstall: model download failed, will retry on first server start"
45
+ );
46
+ if (execError.stderr) {
47
+ console.warn(
48
+ "[unimatrix] " + execError.stderr.toString().trim()
49
+ );
50
+ }
51
+ }
52
+ } catch (outerError) {
53
+ console.warn(
54
+ "[unimatrix] postinstall: unexpected error: " + outerError.message
55
+ );
56
+ }
57
+
58
+ process.exit(0);
59
+ }
60
+
61
+ main();