infynon 0.2.5 → 0.2.7

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 CHANGED
@@ -1,19 +1,26 @@
1
1
  # infynon
2
2
 
3
- INFYNON is a security-first CLI for package intelligence, API flow testing, repository memory, and bounded AI task execution.
3
+ [![npm version](https://img.shields.io/npm/v/infynon?style=flat-square&logo=npm)](https://www.npmjs.com/package/infynon)
4
+ [![npm downloads](https://img.shields.io/badge/dynamic/json?style=flat-square&logo=npm&label=npm%20downloads&query=%24.downloads&url=https%3A%2F%2Fapi.npmjs.org%2Fdownloads%2Fpoint%2F2025-05-04%3A2050-12-31%2Finfynon&cacheSeconds=3600)](https://www.npmjs.com/package/infynon)
5
+ [![GitHub release](https://img.shields.io/github/v/release/d4rkNinja/infynon-cli?style=flat-square&logo=github)](https://github.com/d4rkNinja/infynon-cli/releases)
6
+ [![Agent control plane](https://img.shields.io/badge/agent%20control%20plane-Codex%20%7C%20Claude%20%7C%20Gemini-7c3aed?style=flat-square)](https://github.com/d4rkNinja/infynon-cli/blob/main/docs/agent-control-plane.md)
7
+ [![Package security](https://img.shields.io/badge/pkg-secure%20installs-ef4444?style=flat-square)](https://github.com/d4rkNinja/infynon-cli/blob/main/docs/commands.md)
4
8
 
5
- This npm package is the official installer wrapper for the INFYNON native binary. It does not contain the Rust source code. During installation, it downloads the matching prebuilt binary from the official GitHub Releases page.
9
+ INFYNON is a terminal control plane for agentic engineering: multi-agent workspace/task orchestration, package intelligence, API flow testing, and repository memory in one native CLI.
10
+
11
+ This npm package is the official wrapper for the INFYNON native binary. npm installs the matching optional platform package when available; otherwise the wrapper downloads and verifies the matching GitHub Release binary on first launch. The public distribution repo includes installers, npm/go wrappers, docs, and release assets; the core Rust implementation is not included.
6
12
 
7
13
  ## Why Install INFYNON
8
14
 
9
- INFYNON is built for teams that need one terminal tool for four connected workflows:
15
+ INFYNON is built for teams that need one terminal tool for five connected workflows:
10
16
 
11
17
  | Workflow | Command Area | Purpose |
12
18
  |---|---|---|
19
+ | Agent control plane | `infynon workspace`, `infynon task`, `infynon coding` | Coordinate Codex, Claude Code, Gemini CLI, and child agent sessions through durable workspace and task records. |
13
20
  | Package intelligence | `infynon pkg` | Scan dependencies, inspect risk, audit package changes, and support safer install workflows. |
14
21
  | API flow testing | `infynon weave` | Run multi-step API flows with context passed between requests. |
15
22
  | Repository memory | `infynon trace` | Preserve structured handoff notes, branch context, package ownership, and repo memory. |
16
- | Agent task contracts | `infynon task` | Turn vague AI work requests into GCCD task briefs with a goal, context, constraints, and completion criteria. |
23
+ | Agent task contracts | GCCD briefs | Turn vague AI work requests into Goal, Context, Constraints, and Done When. |
17
24
 
18
25
  ## Install
19
26
 
@@ -21,7 +28,7 @@ INFYNON is built for teams that need one terminal tool for four connected workfl
21
28
  npm install -g infynon
22
29
  ```
23
30
 
24
- The installer downloads the binary for the current platform and makes `infynon` available through npm's global binary directory.
31
+ npm installs the wrapper and, when available, the matching optional native package for the current platform. If the optional package is unavailable, first launch downloads and verifies the matching GitHub Release binary.
25
32
 
26
33
  ## Supported Platforms
27
34
 
@@ -33,17 +40,55 @@ The installer downloads the binary for the current platform and makes `infynon`
33
40
 
34
41
  Unsupported platforms can still install the npm wrapper, but the wrapper will not be able to download a native binary until a matching release asset exists.
35
42
 
43
+ ## Provenance and Platform Packages
44
+
45
+ INFYNON npm packages are configured for npm provenance from the release pipeline. Provenance links the published package to the GitHub Actions workflow that produced it.
46
+
47
+ The package can use optional native binary packages:
48
+
49
+ - `@infynon/cli-win32-x64`
50
+ - `@infynon/cli-linux-x64`
51
+ - `@infynon/cli-linux-arm64`
52
+ - `@infynon/cli-darwin-x64`
53
+ - `@infynon/cli-darwin-arm64`
54
+
36
55
  ## Quick Start
37
56
 
38
57
  ```bash
39
58
  infynon --help
59
+ infynon workspace agent-root-show
40
60
  infynon pkg scan
41
61
  infynon pkg audit
42
62
  infynon weave flow run checkout
43
63
  infynon trace tui
44
- infynon task create task_001 --mutate --workspace . --prompt "Ship the settings API patch"
64
+ infynon task create task_001 --mutate --workspace app --agent codex --prompt "Ship the settings API patch"
65
+ ```
66
+
67
+ ## Agent Control Plane
68
+
69
+ Use INFYNON when one lead session needs to coordinate child coding agents.
70
+
71
+ ```bash
72
+ infynon workspace agent-root-set --mutate --path D:/Codeverse/infynon-agent
73
+ infynon workspace create app --mutate --folder-name web --path D:/Codeverse/app --default
74
+
75
+ infynon task create task_ui_review \
76
+ --mutate \
77
+ --workspace app \
78
+ --folder-name web \
79
+ --agent claude \
80
+ --prompt "Review the settings UI change. Do not edit backend files. Done when findings are recorded."
81
+
82
+ infynon coding tui
45
83
  ```
46
84
 
85
+ Good fit:
86
+
87
+ - parent and child agent work
88
+ - Codex, Claude Code, and Gemini CLI sessions launched from the right workspace
89
+ - task retries where context and completion criteria must stay intact
90
+ - reviewable handoffs between agents and humans
91
+
47
92
  ## Package Intelligence
48
93
 
49
94
  Use `infynon pkg` to inspect dependency risk and package state.
@@ -113,7 +158,6 @@ infynon task create task_001 \
113
158
 
114
159
  Good fit:
115
160
 
116
- - parent and child agent work
117
161
  - scoped implementation tasks
118
162
  - reviewable handoffs
119
163
  - retries where completion criteria must stay intact
@@ -155,10 +199,14 @@ https://github.com/d4rkNinja/infynon-cli/releases
155
199
  ## Documentation
156
200
 
157
201
  - Public docs: https://github.com/d4rkNinja/infynon-cli/tree/main/docs
202
+ - Agent control plane: https://github.com/d4rkNinja/infynon-cli/blob/main/docs/agent-control-plane.md
203
+ - AI agent workflow: https://github.com/d4rkNinja/infynon-cli/blob/main/docs/ai-agent-workflow.md
158
204
  - GCCD task contracts: https://github.com/d4rkNinja/infynon-cli/blob/main/docs/gccd.md
205
+ - npm install: https://github.com/d4rkNinja/infynon-cli/blob/main/docs/npm-install.md
206
+ - Windows troubleshooting: https://github.com/d4rkNinja/infynon-cli/blob/main/docs/windows-troubleshooting.md
159
207
  - Releases: https://github.com/d4rkNinja/infynon-cli/releases
160
208
  - Issues: https://github.com/d4rkNinja/infynon-cli/issues
161
209
 
162
210
  ## Source Availability
163
211
 
164
- This npm package distributes the INFYNON binary and installer wrapper only. The Rust source code is proprietary and is not bundled in this package.
212
+ This npm package distributes the INFYNON binary and installer wrapper only. The core Rust implementation is not bundled in this package.
package/package.json CHANGED
@@ -1,36 +1,55 @@
1
1
  {
2
- "name": "infynon",
3
- "version": "0.2.5",
4
- "description": "Thin npm installer for the proprietary INFYNON CLI binary releases.",
5
- "bin": {
6
- "infynon": "run.js",
7
- "infynon-pkg": "run-pkg.js"
8
- },
9
- "scripts": {
10
- "postinstall": "node postinstall.js",
11
- "preuninstall": "node preuninstall.js"
12
- },
13
- "files": [
14
- "LICENSE",
15
- "run.js",
16
- "run-pkg.js",
17
- "postinstall.js",
18
- "preuninstall.js"
19
- ],
20
- "engines": {
21
- "node": "\u003e=14.14"
22
- },
23
- "author": "d4rkNinja",
24
- "license": "SEE LICENSE IN LICENSE",
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/d4rkNinja/infynon-cli.git"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/d4rkNinja/infynon-cli/issues"
31
- },
32
- "homepage": "https://github.com/d4rkNinja/infynon-cli#readme",
33
- "publishConfig": {
34
- "access": "public"
35
- }
2
+ "name": "infynon",
3
+ "version": "0.2.7",
4
+ "description": "Security-first CLI for AI-assisted development: safe package installs, dependency scanning, API flow testing, and agent task orchestration.",
5
+ "bin": {
6
+ "infynon": "run.js",
7
+ "infynon-pkg": "run-pkg.js"
8
+ },
9
+ "files": [
10
+ "README.md",
11
+ "LICENSE",
12
+ "run.js",
13
+ "run-pkg.js",
14
+ "postinstall.js",
15
+ "preuninstall.js"
16
+ ],
17
+ "optionalDependencies": {
18
+ "@infynon/cli-win32-x64": "0.2.7",
19
+ "@infynon/cli-linux-x64": "0.2.7",
20
+ "@infynon/cli-linux-arm64": "0.2.7",
21
+ "@infynon/cli-darwin-x64": "0.2.7",
22
+ "@infynon/cli-darwin-arm64": "0.2.7"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "packageManager": "npm@11.8.0",
28
+ "author": "d4rkNinja",
29
+ "license": "SEE LICENSE IN LICENSE",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/d4rkNinja/infynon-cli.git",
33
+ "directory": "npm"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/d4rkNinja/infynon-cli/issues"
37
+ },
38
+ "homepage": "https://cli.infynon.com",
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": true
42
+ },
43
+ "keywords": [
44
+ "cli",
45
+ "security",
46
+ "supply-chain",
47
+ "dependency-scanning",
48
+ "ai-agents",
49
+ "claude-code",
50
+ "codex",
51
+ "gemini-cli",
52
+ "api-testing",
53
+ "devtools"
54
+ ]
36
55
  }
package/postinstall.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ const crypto = require("crypto");
4
5
  const https = require("https");
5
6
  const fs = require("fs");
6
7
  const path = require("path");
@@ -10,27 +11,75 @@ const REPO = "d4rkNinja/infynon-cli";
10
11
  const VERSION = require("./package.json").version;
11
12
  const BIN_DIR = path.join(__dirname, "bin");
12
13
  const BIN_PATH = path.join(BIN_DIR, process.platform === "win32" ? "infynon.exe" : "infynon");
14
+ const TEMP_BIN_PATH = BIN_PATH + ".download-" + process.pid;
15
+ const TEMP_CHECKSUMS_PATH = BIN_PATH + ".checksums-" + process.pid + ".txt";
16
+ const TEMP_MANIFEST_PATH = BIN_PATH + ".manifest-" + process.pid + ".json";
13
17
 
14
18
  function getTarget() {
15
- if (process.platform === "win32" && process.arch === "x64") return { target: "x86_64-pc-windows-msvc", ext: ".exe" };
16
- if (process.platform === "linux" && process.arch === "x64") return { target: "x86_64-unknown-linux-musl", ext: "" };
17
- if (process.platform === "linux" && process.arch === "arm64") return { target: "aarch64-unknown-linux-musl", ext: "" };
18
- if (process.platform === "darwin" && process.arch === "x64") return { target: "x86_64-apple-darwin", ext: "" };
19
- if (process.platform === "darwin" && process.arch === "arm64") return { target: "aarch64-apple-darwin", ext: "" };
19
+ if (process.platform === "win32" && process.arch === "x64") {
20
+ return {
21
+ target: "x86_64-pc-windows-msvc",
22
+ ext: ".exe",
23
+ packageName: "@infynon/cli-win32-x64",
24
+ binaryName: "infynon.exe",
25
+ };
26
+ }
27
+ if (process.platform === "linux" && process.arch === "x64") {
28
+ return {
29
+ target: "x86_64-unknown-linux-musl",
30
+ ext: "",
31
+ packageName: "@infynon/cli-linux-x64",
32
+ binaryName: "infynon",
33
+ };
34
+ }
35
+ if (process.platform === "linux" && process.arch === "arm64") {
36
+ return {
37
+ target: "aarch64-unknown-linux-musl",
38
+ ext: "",
39
+ packageName: "@infynon/cli-linux-arm64",
40
+ binaryName: "infynon",
41
+ };
42
+ }
43
+ if (process.platform === "darwin" && process.arch === "x64") {
44
+ return {
45
+ target: "x86_64-apple-darwin",
46
+ ext: "",
47
+ packageName: "@infynon/cli-darwin-x64",
48
+ binaryName: "infynon",
49
+ };
50
+ }
51
+ if (process.platform === "darwin" && process.arch === "arm64") {
52
+ return {
53
+ target: "aarch64-apple-darwin",
54
+ ext: "",
55
+ packageName: "@infynon/cli-darwin-arm64",
56
+ binaryName: "infynon",
57
+ };
58
+ }
20
59
  return null;
21
60
  }
22
61
 
23
62
  function downloadFile(url, dest, redirects) {
24
63
  redirects = redirects === undefined ? 0 : redirects;
25
64
  if (redirects > 5) {
26
- throw new Error("Too many redirects while downloading binary");
65
+ return Promise.reject(new Error("Too many redirects while downloading binary"));
27
66
  }
28
67
 
29
68
  return new Promise(function (resolve, reject) {
30
69
  https
31
70
  .get(url, { headers: { "User-Agent": "infynon-npm-installer" } }, function (res) {
32
- if (res.statusCode === 301 || res.statusCode === 302) {
33
- return downloadFile(res.headers.location, dest, redirects + 1)
71
+ if (res.statusCode >= 300 && res.statusCode < 400) {
72
+ res.resume();
73
+ if (!res.headers.location) {
74
+ return reject(new Error("Redirect response did not include a Location header"));
75
+ }
76
+ let nextUrl;
77
+ try {
78
+ nextUrl = new URL(res.headers.location, url).toString();
79
+ } catch (err) {
80
+ return reject(err);
81
+ }
82
+ return downloadFile(nextUrl, dest, redirects + 1)
34
83
  .then(resolve)
35
84
  .catch(reject);
36
85
  }
@@ -48,6 +97,12 @@ function downloadFile(url, dest, redirects) {
48
97
  file.on("finish", function () {
49
98
  file.close(resolve);
50
99
  });
100
+ res.on("error", function (err) {
101
+ file.close(function () {
102
+ fs.unlink(dest, function () {});
103
+ reject(err);
104
+ });
105
+ });
51
106
  res.pipe(file);
52
107
  })
53
108
  .on("error", function (err) {
@@ -57,21 +112,280 @@ function downloadFile(url, dest, redirects) {
57
112
  });
58
113
  }
59
114
 
60
- function verifyBinary() {
61
- const result = spawnSync(BIN_PATH, ["--version"], {
115
+ function removeQuietly(filePath) {
116
+ try {
117
+ fs.rmSync(filePath, { force: true });
118
+ } catch (_) {}
119
+ }
120
+
121
+ function isFile(filePath) {
122
+ try {
123
+ return fs.statSync(filePath).isFile();
124
+ } catch (_) {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ function checksumForAsset(checksumsText, assetName) {
130
+ const lines = checksumsText.split(/\r?\n/);
131
+ for (const line of lines) {
132
+ const match = line.match(/^([a-fA-F0-9]{64})\s+[* ]?(.+)$/);
133
+ if (!match) continue;
134
+ if (path.basename(match[2].trim()) === assetName) {
135
+ return match[1].toLowerCase();
136
+ }
137
+ }
138
+ throw new Error("checksums.txt does not include " + assetName);
139
+ }
140
+
141
+ function sha256File(filePath) {
142
+ return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
143
+ }
144
+
145
+ function verifyChecksum(checksumsPath, filePath, assetName) {
146
+ const expected = checksumForAsset(fs.readFileSync(checksumsPath, "utf8"), assetName);
147
+ const actual = sha256File(filePath);
148
+ if (actual !== expected) {
149
+ throw new Error("SHA-256 mismatch for " + assetName);
150
+ }
151
+ }
152
+
153
+ function unavailable(message) {
154
+ const err = new Error(message);
155
+ err.fallbackAllowed = true;
156
+ return err;
157
+ }
158
+
159
+ function integrityFailure(message) {
160
+ const err = new Error(message);
161
+ err.integrityFailure = true;
162
+ return err;
163
+ }
164
+
165
+ function basenameMatches(value, assetName) {
166
+ return typeof value === "string" && path.basename(value) === assetName;
167
+ }
168
+
169
+ function entryName(entry) {
170
+ if (!entry || typeof entry !== "object") {
171
+ return null;
172
+ }
173
+ return entry.name || entry.filename || entry.file || entry.path || entry.asset || entry.asset_name || null;
174
+ }
175
+
176
+ function objectEntryForAsset(objectValue, assetName) {
177
+ if (!objectValue || typeof objectValue !== "object" || Array.isArray(objectValue)) {
178
+ return null;
179
+ }
180
+
181
+ if (Object.prototype.hasOwnProperty.call(objectValue, assetName)) {
182
+ const value = objectValue[assetName];
183
+ if (value && typeof value === "object") {
184
+ return Object.assign({ name: assetName }, value);
185
+ }
186
+ return { name: assetName, sha256: value };
187
+ }
188
+
189
+ for (const key of Object.keys(objectValue)) {
190
+ const value = objectValue[key];
191
+ if (path.basename(key) === assetName) {
192
+ if (value && typeof value === "object") {
193
+ return Object.assign({ name: key }, value);
194
+ }
195
+ return { name: key, sha256: value };
196
+ }
197
+ if (value && typeof value === "object" && basenameMatches(entryName(value), assetName)) {
198
+ return value;
199
+ }
200
+ }
201
+
202
+ return null;
203
+ }
204
+
205
+ function findManifestEntry(manifest, assetName) {
206
+ if (basenameMatches(entryName(manifest), assetName)) {
207
+ return manifest;
208
+ }
209
+
210
+ const collections = [
211
+ manifest && manifest.assets,
212
+ manifest && manifest.files,
213
+ manifest && manifest.binaries,
214
+ manifest && manifest.artifacts,
215
+ manifest && manifest.release_assets,
216
+ ];
217
+
218
+ for (const collection of collections) {
219
+ if (Array.isArray(collection)) {
220
+ for (const entry of collection) {
221
+ if (basenameMatches(entryName(entry), assetName)) {
222
+ return entry;
223
+ }
224
+ }
225
+ } else {
226
+ const entry = objectEntryForAsset(collection, assetName);
227
+ if (entry) {
228
+ return entry;
229
+ }
230
+ }
231
+ }
232
+
233
+ return objectEntryForAsset(manifest, assetName);
234
+ }
235
+
236
+ function fieldValue(entry, names) {
237
+ for (const name of names) {
238
+ if (Object.prototype.hasOwnProperty.call(entry, name)) {
239
+ return entry[name];
240
+ }
241
+ }
242
+ return null;
243
+ }
244
+
245
+ function normalizeSha256(value) {
246
+ if (typeof value !== "string") {
247
+ return null;
248
+ }
249
+ const normalized = value.trim().toLowerCase().replace(/^sha256[:=\s]+/, "");
250
+ if (/^[a-f0-9]{64}$/.test(normalized)) {
251
+ return normalized;
252
+ }
253
+ return null;
254
+ }
255
+
256
+ function normalizeSize(value) {
257
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
258
+ return value;
259
+ }
260
+ if (typeof value === "string" && /^\d+$/.test(value.trim())) {
261
+ const parsed = Number(value.trim());
262
+ if (Number.isSafeInteger(parsed)) {
263
+ return parsed;
264
+ }
265
+ }
266
+ return null;
267
+ }
268
+
269
+ function manifestVerificationForAsset(manifestPath, assetName) {
270
+ let manifest;
271
+ try {
272
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
273
+ } catch (err) {
274
+ throw unavailable("release-manifest.json is not valid JSON: " + err.message);
275
+ }
276
+
277
+ const entry = findManifestEntry(manifest, assetName);
278
+ if (!entry) {
279
+ throw unavailable("release-manifest.json does not include " + assetName);
280
+ }
281
+
282
+ const sha256 = normalizeSha256(
283
+ fieldValue(entry, ["sha256", "sha_256", "sha256sum", "checksum", "digest"])
284
+ );
285
+ if (!sha256) {
286
+ throw unavailable("release-manifest.json does not include a SHA-256 for " + assetName);
287
+ }
288
+
289
+ const size = normalizeSize(fieldValue(entry, ["size", "size_bytes", "bytes", "length"]));
290
+ if (size === null) {
291
+ throw unavailable("release-manifest.json does not include a size for " + assetName);
292
+ }
293
+
294
+ return { sha256: sha256, size: size };
295
+ }
296
+
297
+ async function fetchManifestVerification(manifestUrl, assetName) {
298
+ try {
299
+ removeQuietly(TEMP_MANIFEST_PATH);
300
+ await downloadFile(manifestUrl, TEMP_MANIFEST_PATH);
301
+ return manifestVerificationForAsset(TEMP_MANIFEST_PATH, assetName);
302
+ } catch (err) {
303
+ if (err && err.integrityFailure) {
304
+ throw err;
305
+ }
306
+ console.warn("[infynon] release-manifest.json verification unavailable: " + err.message);
307
+ console.warn("[infynon] Falling back to checksums.txt.");
308
+ return null;
309
+ } finally {
310
+ removeQuietly(TEMP_MANIFEST_PATH);
311
+ }
312
+ }
313
+
314
+ function verifyManifestAsset(expected, filePath, assetName) {
315
+ const actualSize = fs.statSync(filePath).size;
316
+ if (actualSize !== expected.size) {
317
+ throw integrityFailure("Size mismatch for " + assetName);
318
+ }
319
+ const actualSha256 = sha256File(filePath);
320
+ if (actualSha256 !== expected.sha256) {
321
+ throw integrityFailure("SHA-256 mismatch for " + assetName);
322
+ }
323
+ }
324
+
325
+ function verifyBinary(binaryPath, label) {
326
+ const result = spawnSync(binaryPath, ["--version"], {
62
327
  encoding: "utf8",
63
328
  windowsHide: true,
64
329
  });
65
330
  if (result.error) {
66
- throw new Error("Downloaded binary is not executable: " + result.error.message);
331
+ throw new Error(label + " is not executable: " + result.error.message);
67
332
  }
68
333
  if (result.status !== 0) {
69
334
  const detail = (result.stderr || result.stdout || "").trim();
70
335
  throw new Error(
71
- "Downloaded binary failed verification" +
72
- (detail ? ": " + detail : " with exit code " + result.status)
336
+ label + " failed verification" + (detail ? ": " + detail : " with exit code " + result.status)
73
337
  );
74
338
  }
339
+ const versionFields = String((result.stdout || "") + " " + (result.stderr || ""))
340
+ .trim()
341
+ .split(/\s+/)
342
+ .map(function (field) {
343
+ return field.replace(/^v/, "");
344
+ });
345
+ if (versionFields.indexOf(VERSION) === -1) {
346
+ throw new Error(label + " did not report version " + VERSION);
347
+ }
348
+ }
349
+
350
+ function resolvePlatformPackageBinary(info) {
351
+ let packageJsonPath;
352
+ try {
353
+ packageJsonPath = require.resolve(info.packageName + "/package.json", { paths: [__dirname] });
354
+ } catch (err) {
355
+ if (err && err.code !== "MODULE_NOT_FOUND") {
356
+ console.warn("[infynon] Could not inspect " + info.packageName + ": " + err.message);
357
+ }
358
+ return null;
359
+ }
360
+
361
+ let packageVersion = null;
362
+ try {
363
+ packageVersion = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).version;
364
+ } catch (err) {
365
+ console.warn("[infynon] Could not read " + info.packageName + " metadata: " + err.message);
366
+ return null;
367
+ }
368
+
369
+ if (packageVersion !== VERSION) {
370
+ console.warn(
371
+ "[infynon] Ignoring " +
372
+ info.packageName +
373
+ " " +
374
+ packageVersion +
375
+ "; expected wrapper version " +
376
+ VERSION +
377
+ "."
378
+ );
379
+ return null;
380
+ }
381
+
382
+ const binaryPath = path.join(path.dirname(packageJsonPath), "bin", info.binaryName);
383
+ if (!isFile(binaryPath)) {
384
+ console.warn("[infynon] Ignoring " + info.packageName + "; missing binary at " + binaryPath + ".");
385
+ return null;
386
+ }
387
+
388
+ return { path: binaryPath, packageName: info.packageName };
75
389
  }
76
390
 
77
391
  async function main() {
@@ -79,15 +393,36 @@ async function main() {
79
393
 
80
394
  if (!info) {
81
395
  console.warn(
82
- "[infynon] Unsupported platform: " + process.platform + " " + process.arch + ".\n" +
83
- " Download a release manually: https://github.com/" + REPO + "/releases"
396
+ "[infynon] Unsupported platform: " +
397
+ process.platform +
398
+ " " +
399
+ process.arch +
400
+ ".\n" +
401
+ " Download a release manually: https://github.com/" +
402
+ REPO +
403
+ "/releases"
84
404
  );
85
405
  process.exit(0);
86
406
  }
87
407
 
408
+ const platformBinary = resolvePlatformPackageBinary(info);
409
+ if (platformBinary) {
410
+ try {
411
+ verifyBinary(platformBinary.path, "Installed platform package " + platformBinary.packageName);
412
+ } catch (err) {
413
+ console.error("[infynon] Platform package verification failed: " + err.message);
414
+ console.error("[infynon] Reinstall after the package is corrected: npm install -g infynon");
415
+ process.exit(1);
416
+ }
417
+ console.log("[infynon] Using installed native package " + platformBinary.packageName + ".");
418
+ return;
419
+ }
420
+
88
421
  const tag = "v" + VERSION;
89
422
  const assetName = "infynon-" + info.target + info.ext;
90
423
  const url = "https://github.com/" + REPO + "/releases/download/" + tag + "/" + assetName;
424
+ const manifestUrl = "https://github.com/" + REPO + "/releases/download/" + tag + "/release-manifest.json";
425
+ const checksumsUrl = "https://github.com/" + REPO + "/releases/download/" + tag + "/checksums.txt";
91
426
 
92
427
  if (!fs.existsSync(BIN_DIR)) {
93
428
  fs.mkdirSync(BIN_DIR, { recursive: true });
@@ -96,11 +431,31 @@ async function main() {
96
431
  console.log("[infynon] Downloading " + assetName + " from " + tag + " release...");
97
432
 
98
433
  try {
99
- await downloadFile(url, BIN_PATH);
434
+ removeQuietly(TEMP_BIN_PATH);
435
+ removeQuietly(TEMP_CHECKSUMS_PATH);
436
+ removeQuietly(TEMP_MANIFEST_PATH);
437
+
438
+ const manifestVerification = await fetchManifestVerification(manifestUrl, assetName);
439
+ await downloadFile(url, TEMP_BIN_PATH);
440
+ if (manifestVerification) {
441
+ verifyManifestAsset(manifestVerification, TEMP_BIN_PATH, assetName);
442
+ } else {
443
+ await downloadFile(checksumsUrl, TEMP_CHECKSUMS_PATH);
444
+ verifyChecksum(TEMP_CHECKSUMS_PATH, TEMP_BIN_PATH, assetName);
445
+ }
446
+
447
+ removeQuietly(BIN_PATH);
448
+ fs.renameSync(TEMP_BIN_PATH, BIN_PATH);
100
449
  } catch (err) {
450
+ removeQuietly(TEMP_BIN_PATH);
451
+ removeQuietly(TEMP_CHECKSUMS_PATH);
452
+ removeQuietly(TEMP_MANIFEST_PATH);
101
453
  console.error("[infynon] Download failed: " + err.message);
102
454
  console.error("[infynon] Manual install: https://github.com/" + REPO + "/releases/tag/" + tag);
103
455
  process.exit(1);
456
+ } finally {
457
+ removeQuietly(TEMP_CHECKSUMS_PATH);
458
+ removeQuietly(TEMP_MANIFEST_PATH);
104
459
  }
105
460
 
106
461
  if (process.platform !== "win32") {
@@ -108,9 +463,9 @@ async function main() {
108
463
  }
109
464
 
110
465
  try {
111
- verifyBinary();
466
+ verifyBinary(BIN_PATH, "Downloaded binary");
112
467
  } catch (err) {
113
- fs.unlink(BIN_PATH, function () {});
468
+ removeQuietly(BIN_PATH);
114
469
  console.error("[infynon] Binary verification failed: " + err.message);
115
470
  console.error("[infynon] Reinstall after the release asset is corrected: npm install -g infynon");
116
471
  process.exit(1);
package/preuninstall.js CHANGED
@@ -3,54 +3,9 @@
3
3
 
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
- const os = require("os");
7
6
 
8
- // Mirror the same path logic as src/firewall/config.rs → config_dir()
9
- const INFYNON_DIR = path.join(os.homedir(), ".infynon");
7
+ const BIN_DIR = path.join(__dirname, "bin");
10
8
 
11
- // Files/dirs managed by infynon that live outside the npm package
12
- const MANAGED_PATHS = [
13
- { p: path.join(INFYNON_DIR, "infynon.toml"), label: "firewall config" },
14
- { p: path.join(INFYNON_DIR, "eagle-eye.toml"), label: "eagle eye config" },
15
- { p: path.join(INFYNON_DIR, "access.jsonl"), label: "access log" },
16
- { p: path.join(INFYNON_DIR, "blocked.jsonl"), label: "blocked log" },
17
- { p: path.join(INFYNON_DIR, "sbom.json"), label: "SBOM" },
18
- ];
19
-
20
- console.log("\n[infynon] Cleaning up...\n");
21
-
22
- let removed = 0;
23
-
24
- for (const { p, label } of MANAGED_PATHS) {
25
- if (fs.existsSync(p)) {
26
- try {
27
- fs.rmSync(p, { force: true });
28
- console.log(" removed " + label + " (" + p + ")");
29
- removed++;
30
- } catch (err) {
31
- console.warn(" skipped " + label + " — " + err.message);
32
- }
33
- }
34
- }
35
-
36
- // Remove ~/.infynon/ itself if it is now empty
37
- if (fs.existsSync(INFYNON_DIR)) {
38
- try {
39
- const remaining = fs.readdirSync(INFYNON_DIR);
40
- if (remaining.length === 0) {
41
- fs.rmdirSync(INFYNON_DIR);
42
- console.log(" removed ~/.infynon/ directory");
43
- } else {
44
- console.log(
45
- " kept ~/.infynon/ — " + remaining.length +
46
- " user file(s) remain: " + remaining.join(", ")
47
- );
48
- }
49
- } catch (_) {}
50
- }
51
-
52
- if (removed === 0) {
53
- console.log(" nothing to clean up.\n");
54
- } else {
55
- console.log("\n[infynon] Clean uninstall complete.\n");
9
+ if (fs.existsSync(BIN_DIR)) {
10
+ fs.rmSync(BIN_DIR, { recursive: true, force: true });
56
11
  }
package/run.js CHANGED
@@ -1,45 +1,265 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const path = require("path");
5
- const { spawnSync } = require("child_process");
6
4
  const fs = require("fs");
5
+ const path = require("path");
6
+ const { spawn } = require("child_process");
7
7
 
8
- const BIN_PATH = path.join(
9
- __dirname,
8
+ const PACKAGE_DIR = __dirname;
9
+ const VERSION = require("./package.json").version;
10
+ const RELEASES_URL = "https://github.com/d4rkNinja/infynon-cli/releases";
11
+ const LOCAL_BIN_PATH = path.join(
12
+ PACKAGE_DIR,
10
13
  "bin",
11
14
  process.platform === "win32" ? "infynon.exe" : "infynon"
12
15
  );
16
+ const POSTINSTALL_PATH = path.join(PACKAGE_DIR, "postinstall.js");
17
+
18
+ function getPlatformPackage() {
19
+ if (process.platform === "win32" && process.arch === "x64") {
20
+ return { packageName: "@infynon/cli-win32-x64", binaryName: "infynon.exe" };
21
+ }
22
+ if (process.platform === "linux" && process.arch === "x64") {
23
+ return { packageName: "@infynon/cli-linux-x64", binaryName: "infynon" };
24
+ }
25
+ if (process.platform === "linux" && process.arch === "arm64") {
26
+ return { packageName: "@infynon/cli-linux-arm64", binaryName: "infynon" };
27
+ }
28
+ if (process.platform === "darwin" && process.arch === "x64") {
29
+ return { packageName: "@infynon/cli-darwin-x64", binaryName: "infynon" };
30
+ }
31
+ if (process.platform === "darwin" && process.arch === "arm64") {
32
+ return { packageName: "@infynon/cli-darwin-arm64", binaryName: "infynon" };
33
+ }
34
+ return null;
35
+ }
36
+
37
+ function isFile(filePath) {
38
+ try {
39
+ return fs.statSync(filePath).isFile();
40
+ } catch (_) {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ function resolvePlatformBinary(notes) {
46
+ const info = getPlatformPackage();
47
+ if (!info) {
48
+ notes.push("No optional native package is published for " + process.platform + " " + process.arch + ".");
49
+ return null;
50
+ }
51
+
52
+ let packageJsonPath;
53
+ try {
54
+ packageJsonPath = require.resolve(info.packageName + "/package.json", { paths: [PACKAGE_DIR] });
55
+ } catch (err) {
56
+ if (err && err.code !== "MODULE_NOT_FOUND") {
57
+ notes.push("Could not inspect " + info.packageName + ": " + err.message);
58
+ }
59
+ return null;
60
+ }
61
+
62
+ const packageDir = path.dirname(packageJsonPath);
63
+ let packageVersion = null;
64
+ try {
65
+ packageVersion = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).version;
66
+ } catch (err) {
67
+ notes.push("Could not read " + info.packageName + " metadata: " + err.message);
68
+ return null;
69
+ }
70
+
71
+ if (packageVersion !== VERSION) {
72
+ notes.push(
73
+ info.packageName + " is version " + packageVersion + ", but the wrapper is version " + VERSION + "."
74
+ );
75
+ return null;
76
+ }
77
+
78
+ const binaryPath = path.join(packageDir, "bin", info.binaryName);
79
+ if (!isFile(binaryPath)) {
80
+ notes.push("Found " + info.packageName + ", but its binary is missing at " + binaryPath + ".");
81
+ return null;
82
+ }
83
+
84
+ return { path: binaryPath, source: info.packageName };
85
+ }
86
+
87
+ function spawnInherited(command, args, options) {
88
+ return new Promise(function (resolve, reject) {
89
+ let child;
90
+ try {
91
+ child = spawn(
92
+ command,
93
+ args,
94
+ Object.assign(
95
+ {
96
+ stdio: "inherit",
97
+ windowsHide: false,
98
+ },
99
+ options || {}
100
+ )
101
+ );
102
+ } catch (err) {
103
+ reject(err);
104
+ return;
105
+ }
106
+
107
+ child.on("error", reject);
108
+ child.on("exit", function (code, signal) {
109
+ resolve({ code: code, signal: signal });
110
+ });
111
+ });
112
+ }
13
113
 
14
- if (!fs.existsSync(BIN_PATH)) {
15
- console.error(
16
- "[infynon] Binary not found at: " + BIN_PATH + "\n" +
17
- " Try reinstalling: npm install -g infynon\n" +
18
- " Or download a release manually: https://github.com/d4rkNinja/infynon-cli/releases"
114
+ async function runPostinstallFallback() {
115
+ if (!isFile(POSTINSTALL_PATH)) {
116
+ throw new Error("postinstall.js is missing at " + POSTINSTALL_PATH);
117
+ }
118
+
119
+ console.error("[infynon] Native binary is missing; running npm postinstall fallback once.");
120
+ const result = await spawnInherited(process.execPath, [POSTINSTALL_PATH], {
121
+ env: Object.assign({}, process.env, {
122
+ INFYNON_NPM_POSTINSTALL_FALLBACK: "1",
123
+ }),
124
+ });
125
+
126
+ if (result.signal) {
127
+ throw new Error("postinstall.js was terminated by signal " + result.signal);
128
+ }
129
+ if (result.code !== 0) {
130
+ throw new Error("postinstall.js failed with exit code " + result.code);
131
+ }
132
+ }
133
+
134
+ async function resolveNativeBinary() {
135
+ const notes = [];
136
+ const platformBinary = resolvePlatformBinary(notes);
137
+ if (platformBinary) {
138
+ return platformBinary;
139
+ }
140
+
141
+ if (isFile(LOCAL_BIN_PATH)) {
142
+ return { path: LOCAL_BIN_PATH, source: "npm/bin" };
143
+ }
144
+
145
+ await runPostinstallFallback();
146
+ if (isFile(LOCAL_BIN_PATH)) {
147
+ return { path: LOCAL_BIN_PATH, source: "npm/bin:postinstall" };
148
+ }
149
+
150
+ const message = [
151
+ "Binary not found for " + process.platform + " " + process.arch + ".",
152
+ "Expected local fallback at: " + LOCAL_BIN_PATH,
153
+ ];
154
+ if (notes.length > 0) {
155
+ message.push("Resolution notes:");
156
+ for (const note of notes) {
157
+ message.push(" - " + note);
158
+ }
159
+ }
160
+ message.push("Try reinstalling: npm install -g infynon");
161
+ message.push("Or download a release manually: " + RELEASES_URL);
162
+ const err = new Error(message.join("\n"));
163
+ err.code = "BINARY_NOT_FOUND";
164
+ throw err;
165
+ }
166
+
167
+ function assertWindowsCommandLineFits(binaryPath, args) {
168
+ if (process.platform !== "win32") {
169
+ return;
170
+ }
171
+ const estimatedLength = binaryPath.length + args.reduce(function (total, arg) {
172
+ return total + String(arg).length + 3;
173
+ }, 0);
174
+ if (estimatedLength <= 30000) {
175
+ return;
176
+ }
177
+ const err = new Error(
178
+ "command line is too long for Windows process creation; reduce arguments or use file-based package-manager input"
19
179
  );
20
- process.exit(1);
180
+ err.code = "COMMAND_TOO_LONG";
181
+ throw err;
21
182
  }
22
183
 
23
- function runBinary(args) {
24
- return spawnSync(BIN_PATH, args, {
25
- stdio: "inherit",
26
- windowsHide: false,
184
+ async function runBinary(resolution, args) {
185
+ assertWindowsCommandLineFits(resolution.path, args);
186
+ return spawnInherited(resolution.path, args, {
187
+ env: Object.assign({}, process.env, {
188
+ INFYNON_NPM_WRAPPER: __filename,
189
+ INFYNON_NPM_PACKAGE_DIR: PACKAGE_DIR,
190
+ INFYNON_NPM_BINARY: resolution.path,
191
+ INFYNON_NPM_BINARY_SOURCE: resolution.source,
192
+ }),
27
193
  });
28
194
  }
29
195
 
30
- const result = runBinary(process.argv.slice(2));
196
+ function exitFromChild(result) {
197
+ if (result.signal) {
198
+ try {
199
+ process.kill(process.pid, result.signal);
200
+ } catch (_) {
201
+ const signalExitCodes = { SIGHUP: 129, SIGINT: 130, SIGTERM: 143 };
202
+ process.exit(signalExitCodes[result.signal] || 1);
203
+ }
204
+ setTimeout(function () {
205
+ process.exit(1);
206
+ }, 1000);
207
+ return;
208
+ }
209
+
210
+ process.exit(result.code === null ? 1 : result.code);
211
+ }
212
+
213
+ function printLaunchError(err, resolution) {
214
+ const detected = [
215
+ "platform: " + process.platform + " " + process.arch,
216
+ "wrapper: " + __filename,
217
+ "package dir: " + PACKAGE_DIR,
218
+ "binary: " + (resolution ? resolution.path : "unresolved"),
219
+ "binary source: " + (resolution ? resolution.source : "unresolved"),
220
+ "node: " + process.version,
221
+ ];
222
+
223
+ if (err && err.code === "BINARY_NOT_FOUND") {
224
+ console.error("[infynon] " + err.message);
225
+ console.error("[infynon] Detected:\n - " + detected.join("\n - "));
226
+ return;
227
+ }
31
228
 
32
- if (result.error) {
33
- console.error("[infynon] Failed to run binary:", result.error.message);
34
- if (process.platform === "win32" && result.error.code === "UNKNOWN") {
35
- console.error("[infynon] The installed Windows binary could not be executed.");
36
- console.error("[infynon] Reinstall to download and verify a fresh release asset: npm install -g infynon");
229
+ if (resolution) {
230
+ console.error("[infynon] Failed to launch native binary: " + resolution.path);
231
+ } else {
232
+ console.error("[infynon] Failed to resolve native binary.");
37
233
  }
38
- process.exit(1);
234
+ console.error("[infynon] " + (err && err.message ? err.message : String(err)));
235
+
236
+ if (err && err.code === "COMMAND_TOO_LONG") {
237
+ console.error("[infynon] On Windows, pass large package-manager input through files instead of argv.");
238
+ } else if (err && err.code === "ENOENT") {
239
+ console.error("[infynon] The resolved binary path no longer exists. Try reinstalling: npm install -g infynon");
240
+ } else if (err && (err.code === "EACCES" || err.code === "EPERM")) {
241
+ console.error("[infynon] The resolved binary is not executable. Try reinstalling: npm install -g infynon");
242
+ } else if (process.platform === "win32") {
243
+ console.error("[infynon] If Windows blocked or quarantined the executable, reinstall to restore a verified copy.");
244
+ console.error("[infynon] Reinstall command: npm install -g infynon");
245
+ } else {
246
+ console.error("[infynon] Try reinstalling: npm install -g infynon");
247
+ }
248
+ console.error("[infynon] Detected:\n - " + detected.join("\n - "));
249
+ console.error("[infynon] Diagnostics: infynon doctor npm");
250
+ console.error("[infynon] Manual release downloads: " + RELEASES_URL);
39
251
  }
40
252
 
41
- if (result.signal) {
42
- process.kill(process.pid, result.signal);
253
+ async function main() {
254
+ let resolution = null;
255
+ try {
256
+ resolution = await resolveNativeBinary();
257
+ const result = await runBinary(resolution, process.argv.slice(2));
258
+ exitFromChild(result);
259
+ } catch (err) {
260
+ printLaunchError(err, resolution);
261
+ process.exit(1);
262
+ }
43
263
  }
44
264
 
45
- process.exit(result.status !== null ? result.status : 1);
265
+ main();