infynon 0.2.6 → 0.2.8

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.
Files changed (4) hide show
  1. package/README.md +56 -8
  2. package/package.json +30 -11
  3. package/postinstall.js +302 -16
  4. package/run.js +245 -35
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 when published from a public GitHub Actions source repository. Private-source release runs publish without provenance because npm rejects private-repo provenance bundles; use the GitHub Release manifest and SHA-256 checksums as the release-integrity signal for those builds.
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
2
  "name": "infynon",
3
- "version": "0.2.6",
4
- "description": "Thin npm installer for the proprietary INFYNON CLI binary releases.",
3
+ "version": "0.2.8",
4
+ "description": "Security-first CLI for AI-assisted development: safe package installs, dependency scanning, API flow testing, and agent task orchestration.",
5
5
  "bin": {
6
6
  "infynon": "run.js",
7
7
  "infynon-pkg": "run-pkg.js"
8
8
  },
9
- "scripts": {
10
- "postinstall": "node postinstall.js",
11
- "preuninstall": "node preuninstall.js"
12
- },
13
9
  "files": [
10
+ "README.md",
14
11
  "LICENSE",
15
12
  "run.js",
16
13
  "run-pkg.js",
17
14
  "postinstall.js",
18
15
  "preuninstall.js"
19
16
  ],
17
+ "optionalDependencies": {
18
+ "@infynon/cli-win32-x64": "0.2.8",
19
+ "@infynon/cli-linux-x64": "0.2.8",
20
+ "@infynon/cli-linux-arm64": "0.2.8",
21
+ "@infynon/cli-darwin-x64": "0.2.8",
22
+ "@infynon/cli-darwin-arm64": "0.2.8"
23
+ },
20
24
  "engines": {
21
- "node": "\u003e=14.14"
25
+ "node": "\u003e=18"
22
26
  },
27
+ "packageManager": "npm@11.8.0",
23
28
  "author": "d4rkNinja",
24
29
  "license": "SEE LICENSE IN LICENSE",
25
30
  "repository": {
26
31
  "type": "git",
27
- "url": "git+https://github.com/d4rkNinja/infynon-cli.git"
32
+ "url": "git+https://github.com/d4rkNinja/infynon-cli.git",
33
+ "directory": "npm"
28
34
  },
29
35
  "bugs": {
30
36
  "url": "https://github.com/d4rkNinja/infynon-cli/issues"
31
37
  },
32
- "homepage": "https://github.com/d4rkNinja/infynon-cli#readme",
38
+ "homepage": "https://cli.infynon.com",
33
39
  "publishConfig": {
34
- "access": "public"
35
- }
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
@@ -13,13 +13,49 @@ const BIN_DIR = path.join(__dirname, "bin");
13
13
  const BIN_PATH = path.join(BIN_DIR, process.platform === "win32" ? "infynon.exe" : "infynon");
14
14
  const TEMP_BIN_PATH = BIN_PATH + ".download-" + process.pid;
15
15
  const TEMP_CHECKSUMS_PATH = BIN_PATH + ".checksums-" + process.pid + ".txt";
16
+ const TEMP_MANIFEST_PATH = BIN_PATH + ".manifest-" + process.pid + ".json";
16
17
 
17
18
  function getTarget() {
18
- if (process.platform === "win32" && process.arch === "x64") return { target: "x86_64-pc-windows-msvc", ext: ".exe" };
19
- if (process.platform === "linux" && process.arch === "x64") return { target: "x86_64-unknown-linux-musl", ext: "" };
20
- if (process.platform === "linux" && process.arch === "arm64") return { target: "aarch64-unknown-linux-musl", ext: "" };
21
- if (process.platform === "darwin" && process.arch === "x64") return { target: "x86_64-apple-darwin", ext: "" };
22
- 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
+ }
23
59
  return null;
24
60
  }
25
61
 
@@ -82,6 +118,14 @@ function removeQuietly(filePath) {
82
118
  } catch (_) {}
83
119
  }
84
120
 
121
+ function isFile(filePath) {
122
+ try {
123
+ return fs.statSync(filePath).isFile();
124
+ } catch (_) {
125
+ return false;
126
+ }
127
+ }
128
+
85
129
  function checksumForAsset(checksumsText, assetName) {
86
130
  const lines = checksumsText.split(/\r?\n/);
87
131
  for (const line of lines) {
@@ -106,19 +150,190 @@ function verifyChecksum(checksumsPath, filePath, assetName) {
106
150
  }
107
151
  }
108
152
 
109
- function verifyBinary() {
110
- const result = spawnSync(BIN_PATH, ["--version"], {
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"], {
111
327
  encoding: "utf8",
112
328
  windowsHide: true,
113
329
  });
114
330
  if (result.error) {
115
- throw new Error("Downloaded binary is not executable: " + result.error.message);
331
+ throw new Error(label + " is not executable: " + result.error.message);
116
332
  }
117
333
  if (result.status !== 0) {
118
334
  const detail = (result.stderr || result.stdout || "").trim();
119
335
  throw new Error(
120
- "Downloaded binary failed verification" +
121
- (detail ? ": " + detail : " with exit code " + result.status)
336
+ label + " failed verification" + (detail ? ": " + detail : " with exit code " + result.status)
122
337
  );
123
338
  }
124
339
  const versionFields = String((result.stdout || "") + " " + (result.stderr || ""))
@@ -128,24 +343,85 @@ function verifyBinary() {
128
343
  return field.replace(/^v/, "");
129
344
  });
130
345
  if (versionFields.indexOf(VERSION) === -1) {
131
- throw new Error("Downloaded binary did not report version " + VERSION);
346
+ throw new Error(label + " did not report version " + VERSION);
132
347
  }
133
348
  }
134
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 };
389
+ }
390
+
135
391
  async function main() {
136
392
  const info = getTarget();
137
393
 
138
394
  if (!info) {
139
395
  console.warn(
140
- "[infynon] Unsupported platform: " + process.platform + " " + process.arch + ".\n" +
141
- " 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"
142
404
  );
143
405
  process.exit(0);
144
406
  }
145
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
+
146
421
  const tag = "v" + VERSION;
147
422
  const assetName = "infynon-" + info.target + info.ext;
148
423
  const url = "https://github.com/" + REPO + "/releases/download/" + tag + "/" + assetName;
424
+ const manifestUrl = "https://github.com/" + REPO + "/releases/download/" + tag + "/release-manifest.json";
149
425
  const checksumsUrl = "https://github.com/" + REPO + "/releases/download/" + tag + "/checksums.txt";
150
426
 
151
427
  if (!fs.existsSync(BIN_DIR)) {
@@ -157,19 +433,29 @@ async function main() {
157
433
  try {
158
434
  removeQuietly(TEMP_BIN_PATH);
159
435
  removeQuietly(TEMP_CHECKSUMS_PATH);
436
+ removeQuietly(TEMP_MANIFEST_PATH);
437
+
438
+ const manifestVerification = await fetchManifestVerification(manifestUrl, assetName);
160
439
  await downloadFile(url, TEMP_BIN_PATH);
161
- await downloadFile(checksumsUrl, TEMP_CHECKSUMS_PATH);
162
- verifyChecksum(TEMP_CHECKSUMS_PATH, TEMP_BIN_PATH, assetName);
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
+
163
447
  removeQuietly(BIN_PATH);
164
448
  fs.renameSync(TEMP_BIN_PATH, BIN_PATH);
165
449
  } catch (err) {
166
450
  removeQuietly(TEMP_BIN_PATH);
167
451
  removeQuietly(TEMP_CHECKSUMS_PATH);
452
+ removeQuietly(TEMP_MANIFEST_PATH);
168
453
  console.error("[infynon] Download failed: " + err.message);
169
454
  console.error("[infynon] Manual install: https://github.com/" + REPO + "/releases/tag/" + tag);
170
455
  process.exit(1);
171
456
  } finally {
172
457
  removeQuietly(TEMP_CHECKSUMS_PATH);
458
+ removeQuietly(TEMP_MANIFEST_PATH);
173
459
  }
174
460
 
175
461
  if (process.platform !== "win32") {
@@ -177,7 +463,7 @@ async function main() {
177
463
  }
178
464
 
179
465
  try {
180
- verifyBinary();
466
+ verifyBinary(BIN_PATH, "Downloaded binary");
181
467
  } catch (err) {
182
468
  removeQuietly(BIN_PATH);
183
469
  console.error("[infynon] Binary verification failed: " + err.message);
package/run.js CHANGED
@@ -1,55 +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");
13
17
 
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"
19
- );
20
- process.exit(1);
21
- }
22
-
23
- function runBinary(args) {
24
- if (process.platform === "win32") {
25
- const estimatedLength = BIN_PATH.length + args.reduce(function (total, arg) {
26
- return total + String(arg).length + 3;
27
- }, 0);
28
- if (estimatedLength > 30000) {
29
- return {
30
- error: new Error("command line is too long for Windows process creation; reduce arguments or use file-based package-manager input"),
31
- };
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
+ }
113
+
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);
32
158
  }
33
159
  }
34
- return spawnSync(BIN_PATH, args, {
35
- stdio: "inherit",
36
- windowsHide: false,
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"
179
+ );
180
+ err.code = "COMMAND_TOO_LONG";
181
+ throw err;
182
+ }
183
+
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
+ }),
37
193
  });
38
194
  }
39
195
 
40
- 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
+ }
41
209
 
42
- if (result.error) {
43
- console.error("[infynon] Failed to run binary:", result.error.message);
44
- if (process.platform === "win32" && result.error.code === "UNKNOWN") {
45
- console.error("[infynon] The installed Windows binary could not be executed.");
46
- console.error("[infynon] Reinstall to download and verify a fresh release asset: npm install -g infynon");
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
+ }
228
+
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.");
47
233
  }
48
- 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);
49
251
  }
50
252
 
51
- if (result.signal) {
52
- 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
+ }
53
263
  }
54
264
 
55
- process.exit(result.status !== null ? result.status : 1);
265
+ main();