friend-assistant 0.1.6 → 0.1.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
@@ -89,6 +89,7 @@ Every command starts with `friend`. If you run Friend from the source code, writ
89
89
  | `friend daemon stop` | Stops the background Friend and the speech engine with it. | Shutting it down. |
90
90
  | `friend daemon restart` | Stops it and starts it again. | After changing a setting such as `friend lan on` or a different model. |
91
91
  | `friend daemon status` | Says whether the background Friend is running, for how long, and at which address. Exit code 0 when running, 1 when not. | Checking, or in a script. |
92
+ | `friend upgrade` | Updates Friend in one go: checks npm for the newest version, stops the background Friend, installs it, and starts it again. Add `--force` to reinstall the same version. If Friend runs in a terminal instead, it asks you to press Ctrl+C there first. | A new version is out. |
92
93
  | `friend lan` (or `friend lan status`) | Shows what Friend listens on and the addresses to open. | Checking, or finding the address for your phone. |
93
94
  | `friend lan on` | Lets other devices on your network open Friend. Prints the addresses and the things to know (plain http, no microphone on other devices, firewall prompt). | Using Friend from a phone or another PC. |
94
95
  | `friend lan on --https` | The same, with a secure `https://` address (Friend makes its own certificate), so a phone may use the microphone and camera. | Talking to Friend from a phone. |
package/dist/BUILD.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "friend-assistant",
3
- "version": "0.1.6",
4
- "builtAt": "2026-10-09T11:59:13.273Z"
3
+ "version": "0.1.7",
4
+ "builtAt": "2026-10-09T12:08:30.150Z"
5
5
  }
package/dist/friend.mjs CHANGED
@@ -6884,13 +6884,13 @@ function realish(abs) {
6884
6884
  tail2.unshift(cur.slice(parent.length).replace(/^[\\/]+/, ""));
6885
6885
  cur = parent;
6886
6886
  }
6887
- let real;
6887
+ let real2;
6888
6888
  try {
6889
- real = realpathSync.native(cur);
6889
+ real2 = realpathSync.native(cur);
6890
6890
  } catch {
6891
6891
  return UNRESOLVABLE + abs;
6892
6892
  }
6893
- return tail2.length ? resolve3(real, ...tail2) : real;
6893
+ return tail2.length ? resolve3(real2, ...tail2) : real2;
6894
6894
  }
6895
6895
  function within(root, target) {
6896
6896
  if (target.startsWith(UNRESOLVABLE)) return false;
@@ -8951,6 +8951,77 @@ async function daemonCommand(args, io2, deps = realDeps) {
8951
8951
  }
8952
8952
  }
8953
8953
 
8954
+ // ../agent-cli/src/commands/upgrade.ts
8955
+ import crossSpawn5 from "cross-spawn";
8956
+ var PACKAGE = "friend-assistant";
8957
+ var real = {
8958
+ current: () => true ? "0.1.7" : "development",
8959
+ latest: () => new Promise((resolve10) => {
8960
+ const child = crossSpawn5("npm", ["view", PACKAGE, "version", "--prefer-online"], { stdio: ["ignore", "pipe", "ignore"] });
8961
+ let out = "";
8962
+ child.stdout?.on("data", (d) => out += d.toString());
8963
+ child.on("error", () => resolve10(null));
8964
+ child.on("close", (code) => resolve10(code === 0 && /^\d+\.\d+\.\d+/.test(out.trim()) ? out.trim().split(/\s+/)[0] : null));
8965
+ }),
8966
+ install: (version) => new Promise((resolve10) => {
8967
+ const child = crossSpawn5("npm", ["install", "-g", `${PACKAGE}@${version}`, "--prefer-online"], { stdio: "inherit" });
8968
+ child.on("error", () => resolve10(1));
8969
+ child.on("close", (code) => resolve10(code ?? 1));
8970
+ }),
8971
+ startAgain: () => new Promise((resolve10) => {
8972
+ const child = crossSpawn5("friend", ["daemon", "start"], { stdio: "inherit" });
8973
+ child.on("error", () => resolve10(1));
8974
+ child.on("close", (code) => resolve10(code ?? 1));
8975
+ }),
8976
+ daemon: realDeps
8977
+ };
8978
+ var newer = (a, b) => {
8979
+ const n = (v) => v.split(/[.-]/).slice(0, 3).map((x2) => Number(x2) || 0);
8980
+ const [x, y] = [n(a), n(b)];
8981
+ for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) > (y[i] ?? 0);
8982
+ return false;
8983
+ };
8984
+ async function upgradeCommand(args, io2, deps = real) {
8985
+ const force = args.includes("--force");
8986
+ if (args.some((a) => a !== "--force")) {
8987
+ io2.err("Usage: friend upgrade [--force]");
8988
+ return 1;
8989
+ }
8990
+ const current = deps.current();
8991
+ io2.out(`Installed: ${current}`);
8992
+ const latest = await deps.latest();
8993
+ if (!latest) {
8994
+ io2.err("Could not ask npm for the newest version. Check your internet connection and try again.");
8995
+ return 1;
8996
+ }
8997
+ io2.out(`Newest: ${latest}`);
8998
+ if (!force && !newer(latest, current)) {
8999
+ io2.out("Friend is already up to date.");
9000
+ return 0;
9001
+ }
9002
+ const cfg = loadConfig(io2.dataDir);
9003
+ const wasRunning = running(io2.dataDir, deps.daemon) !== null;
9004
+ if (wasRunning) {
9005
+ io2.out("Stopping the background Friend\u2026");
9006
+ const code2 = await daemonCommand(["stop"], io2, deps.daemon);
9007
+ if (code2 !== 0) return code2;
9008
+ } else if (await deps.daemon.portOpen(cfg.port, 500)) {
9009
+ io2.err(`Friend is running in a terminal (port ${cfg.port} is in use). Press Ctrl+C there to stop it, then run: friend upgrade`);
9010
+ return 1;
9011
+ }
9012
+ io2.out(`Installing ${PACKAGE}@${latest}\u2026`);
9013
+ const code = await deps.install(latest);
9014
+ if (code !== 0) {
9015
+ io2.err("The install failed. If it says EBUSY or EPERM, close whatever is using Friend (a terminal running it, a voice process) and run: friend upgrade");
9016
+ if (wasRunning) await deps.startAgain();
9017
+ return 1;
9018
+ }
9019
+ io2.out(`Upgraded ${current} \u2192 ${latest}.`);
9020
+ if (wasRunning) return deps.startAgain();
9021
+ io2.out("Start it with: friend start (or in the background: friend daemon start)");
9022
+ return 0;
9023
+ }
9024
+
8954
9025
  // ../agent-cli/src/commands/claude.ts
8955
9026
  async function check2(run2, command) {
8956
9027
  const v = await run2(command, ["--version"]);
@@ -9088,6 +9159,7 @@ var USAGE4 = `Usage: friend <command>
9088
9159
  setup create the owner account (password, PIN, assistant name)
9089
9160
  start run the gateway and a local device node
9090
9161
  daemon start|stop|restart|status run Friend in the background (no terminal needed), stop it, or see if it is running
9162
+ upgrade [--force] stop the background Friend, install the newest version, start it again
9091
9163
  pair print a one-time code to connect another PC
9092
9164
  node --url <ws-url> --code <code> [--roots a,b] [--name pc]
9093
9165
  connect this PC to a Friend gateway
@@ -9114,6 +9186,8 @@ async function run(argv, io2) {
9114
9186
  return await startCommand(io2);
9115
9187
  case "daemon":
9116
9188
  return await daemonCommand(rest, io2);
9189
+ case "upgrade":
9190
+ return await upgradeCommand(rest, io2);
9117
9191
  case "pair":
9118
9192
  return await pairCommand(io2);
9119
9193
  case "node":
@@ -9134,7 +9208,7 @@ async function run(argv, io2) {
9134
9208
  case "version":
9135
9209
  case "--version":
9136
9210
  case "-v":
9137
- io2.out(true ? "0.1.6" : "development");
9211
+ io2.out(true ? "0.1.7" : "development");
9138
9212
  return 0;
9139
9213
  case "lan":
9140
9214
  return await lanCommand(rest, io2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "friend-assistant",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "A self-hosted personal AI assistant for your own PC: a full-screen stage, voice, tools with your approval, and your Claude subscription or a local model.",
5
5
  "keywords": ["ai", "assistant", "jarvis", "voice", "claude", "ollama", "mcp", "self-hosted"],
6
6
  "license": "UNLICENSED",