feral-agent 2026.7.19 → 2026.8.1

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 (2) hide show
  1. package/bin/feral.js +90 -14
  2. package/package.json +6 -6
package/bin/feral.js CHANGED
@@ -15,33 +15,109 @@
15
15
  import { spawnSync } from "node:child_process";
16
16
  import { createRequire } from "node:module";
17
17
  import { join, dirname } from "node:path";
18
- import { existsSync, readFileSync } from "node:fs";
18
+ import { accessSync, constants, existsSync, readFileSync } from "node:fs";
19
19
  import { fileURLToPath } from "node:url";
20
20
 
21
21
  const here = dirname(fileURLToPath(import.meta.url));
22
22
  const require = createRequire(import.meta.url);
23
23
  const ext = process.platform === "win32" ? ".exe" : "";
24
24
 
25
- // 1. Production: the matching per-platform package (an optionalDependency).
26
- const platformPkg = `@bloommedia/feral-agent-${process.platform}-${process.arch}`;
27
- let exe = null;
28
- try {
29
- exe = require.resolve(`${platformPkg}/feral-cli${ext}`);
30
- } catch {
31
- /* package not installed for this platform fall through to the dev path */
25
+ // `feral update` is handled HERE and never reaches the Rust binary. Two reasons,
26
+ // both fatal if we delegated: on Windows npm cannot overwrite a running .exe, and
27
+ // a half-installed machine (platform package missing) has no Rust binary to run —
28
+ // which is exactly when you most want to re-install. This shim is the one file in
29
+ // the install that is never locked.
30
+ if (process.argv[2] === "update") {
31
+ const win = process.platform === "win32";
32
+ // Was a gateway serving before the update? If so it is running the OLD binary,
33
+ // and a Discord/Slack connector keeps running it until something restarts it.
34
+ // Checked BEFORE the install so we never start a daemon the user didn't have.
35
+ const old = exeFor();
36
+ const wasOnline =
37
+ !!old && spawnSync(old, ["gateway", "status"], { stdio: "ignore" }).status === 0;
38
+
39
+ const install = spawnSync(win ? "npm.cmd" : "npm", ["install", "-g", "feral-agent@latest"], {
40
+ stdio: "inherit",
41
+ shell: win, // npm.cmd is a batch file; Node needs a shell to exec it
42
+ });
43
+ if (install.error || install.status !== 0) {
44
+ process.stderr.write(`feral: update failed.\n${updateFailureHint(win)}`);
45
+ process.exit(install.status ?? 1);
46
+ }
47
+ if (!wasOnline) {
48
+ process.stdout.write("feral: updated. Start it with: feral gateway start\n");
49
+ process.exit(0);
50
+ }
51
+ // Re-run THIS file — npm has just replaced it with the new version, so the
52
+ // restart is driven by the new shim and starts the new binary.
53
+ process.stdout.write("feral: updated — restarting the gateway…\n");
54
+ const restart = spawnSync(
55
+ process.execPath,
56
+ [fileURLToPath(import.meta.url), "gateway", "restart"],
57
+ { stdio: "inherit" },
58
+ );
59
+ process.exit(restart.status ?? 1);
32
60
  }
33
61
 
34
- // 2. Dev / manual build: binaries staged in ./vendor next to this shim
35
- // (what `node scripts/package-win.mjs` produces for a local Windows build).
36
- if (!exe || !existsSync(exe)) {
62
+ /**
63
+ * What to print when `npm install -g` fails. NOT "run the command that just
64
+ * failed": the common Unix failure is EACCES on a root-owned global prefix, and
65
+ * repeating it there fails identically forever — which is how a working update
66
+ * path reads as a broken product.
67
+ *
68
+ * The cause is diagnosed, not guessed: ask npm where its global prefix is and
69
+ * test whether we can write to it. Guessing from uid instead would tell every
70
+ * nvm/homebrew user to `sudo`, and `sudo npm -g` on those installs is the one
71
+ * thing that actually breaks them.
72
+ */
73
+ function updateFailureHint(win) {
74
+ const manual = "Install manually with:\n npm install -g feral-agent@latest\n";
75
+ if (win) return manual; // no EACCES-on-prefix equivalent worth guessing at
76
+
77
+ const prefix = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8" });
78
+ if (prefix.error || prefix.status !== 0) return manual;
79
+ const root = prefix.stdout.trim();
80
+ // Global packages land in <prefix>/lib/node_modules; fall back outward for
81
+ // layouts that differ, and give up rather than invent a diagnosis.
82
+ const target = [join(root, "lib", "node_modules"), join(root, "lib"), root].find(existsSync);
83
+ if (!target) return manual;
84
+
85
+ try {
86
+ accessSync(target, constants.W_OK);
87
+ } catch {
88
+ return (
89
+ `No write access to npm's global directory (${target}).\n` +
90
+ "Either install as root:\n" +
91
+ " sudo npm install -g feral-agent@latest\n" +
92
+ "or point npm at a prefix you own — this also makes every future\n" +
93
+ "`feral update` work without sudo:\n" +
94
+ " npm config set prefix ~/.npm-global\n" +
95
+ " export PATH=~/.npm-global/bin:$PATH # add this to your shell rc\n" +
96
+ " npm install -g feral-agent@latest\n"
97
+ );
98
+ }
99
+ return manual;
100
+ }
101
+
102
+ /** Resolve the platform binary, or null when this install has none. */
103
+ function exeFor() {
104
+ const pkg = `@bloommedia/feral-agent-${process.platform}-${process.arch}`;
105
+ try {
106
+ const p = require.resolve(`${pkg}/feral-cli${ext}`);
107
+ if (existsSync(p)) return p;
108
+ } catch {
109
+ /* fall through to the dev path */
110
+ }
37
111
  const local = join(here, "..", "vendor", `feral-cli${ext}`);
38
- if (existsSync(local)) exe = local;
112
+ return existsSync(local) ? local : null;
39
113
  }
40
114
 
41
- if (!exe || !existsSync(exe)) {
115
+ const exe = exeFor();
116
+
117
+ if (!exe) {
42
118
  process.stderr.write(
43
119
  `Feral could not find its runtime for ${process.platform}-${process.arch}.\n` +
44
- `The matching package \`${platformPkg}\` may have failed to install ` +
120
+ `The matching package \`@bloommedia/feral-agent-${process.platform}-${process.arch}\` may have failed to install ` +
45
121
  "(a blocked optional dependency, an unsupported platform, or " +
46
122
  "`--ignore-optional`).\n" +
47
123
  "Reinstall with: npm install -g feral-agent\n" +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feral-agent",
3
- "version": "2026.7.19",
3
+ "version": "2026.8.1",
4
4
  "description": "Feral Agent — proactive, portable AI agent with a native security sandbox",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
@@ -46,10 +46,10 @@
46
46
  "package:win": "node scripts/package-win.mjs"
47
47
  },
48
48
  "optionalDependencies": {
49
- "@bloommedia/feral-agent-win32-x64": "2026.7.19",
50
- "@bloommedia/feral-agent-darwin-arm64": "2026.7.19",
51
- "@bloommedia/feral-agent-darwin-x64": "2026.7.19",
52
- "@bloommedia/feral-agent-linux-x64": "2026.7.19"
49
+ "@bloommedia/feral-agent-win32-x64": "2026.8.1",
50
+ "@bloommedia/feral-agent-darwin-arm64": "2026.8.1",
51
+ "@bloommedia/feral-agent-darwin-x64": "2026.8.1",
52
+ "@bloommedia/feral-agent-linux-x64": "2026.8.1"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@slack/socket-mode": "^2.0.7",
@@ -61,7 +61,7 @@
61
61
  "discord.js": "^14.26.4",
62
62
  "gpt-tokenizer": "^3.4.0",
63
63
  "qrcode-terminal": "^0.12.0",
64
- "typescript": "^5.6.0"
64
+ "typescript": "^7.0.0"
65
65
  },
66
66
  "overrides": {
67
67
  "undici": ">=6.27.0"