@averyyy/pi-client 0.80.6-piclient.4 → 0.80.6-piclient.6

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/CHANGELOG.md CHANGED
@@ -15,3 +15,4 @@
15
15
  ### Changed
16
16
 
17
17
  - Rebased the client on upstream Pi `0.80.6`, including GPT-5.6 model metadata and `max` thinking support.
18
+ - Changed `pi-client update` to update global packages without reinstalling the active checkout or stopping sessions; updated sessions require `/reload` to restart on the new runtime with their existing history.
package/README.md CHANGED
@@ -41,6 +41,10 @@ If your server uses an auth token, set it on the client:
41
41
  PI_SERVER_AUTH_TOKEN=your-token PI_SERVER_URL=http://127.0.0.1:4217 pi-client
42
42
  ```
43
43
 
44
+ ## Update
45
+
46
+ `pi-client update` installs the latest client and server packages without stopping active client sessions. Existing sessions block new prompts until you run `/reload`; `/reload` restarts that session on the new runtime and resumes its persisted history.
47
+
44
48
  ## Related Package
45
49
 
46
50
  Install the server separately:
package/bin/pi-client.js CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
3
5
  import { dirname, join } from "node:path";
4
6
  import { fileURLToPath } from "node:url";
5
7
 
@@ -21,15 +23,66 @@ Promise.resolve(modulePromise).catch((e) => {
21
23
  });
22
24
 
23
25
  function runPiClientCli(args) {
26
+ const reloadDir = mkdtempSync(join(tmpdir(), "pi-client-reload-"));
27
+ const reloadStatePath = join(reloadDir, "state.json");
28
+ let cliPath = getLocalCliPath();
29
+ let childArgs = args;
30
+
31
+ try {
32
+ for (;;) {
33
+ const result = spawnSync(process.execPath, [cliPath, ...childArgs], {
34
+ env: {
35
+ ...process.env,
36
+ PI_CODING_AGENT: "true",
37
+ PI_SERVER_MODE: "true",
38
+ PI_CLIENT_RELOAD_STATE_PATH: reloadStatePath,
39
+ },
40
+ stdio: "inherit",
41
+ });
42
+ if (result.error) throw result.error;
43
+ if (result.status !== 75) {
44
+ process.exitCode = result.status ?? (result.signal === "SIGINT" ? 130 : 1);
45
+ return;
46
+ }
47
+
48
+ const state = readReloadState(reloadStatePath);
49
+ if (!state) {
50
+ console.error("pi-client reload failed: missing session state from the previous runtime.");
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+ rmSync(state.updateMarkerPath, { force: true });
55
+ childArgs = state.sessionDir ? ["--session-dir", state.sessionDir, "--session", state.sessionId] : ["--session", state.sessionId];
56
+ cliPath = getUpdatedGlobalCliPath() ?? getLocalCliPath();
57
+ }
58
+ } finally {
59
+ rmSync(reloadDir, { recursive: true, force: true });
60
+ }
61
+ }
62
+
63
+ function getLocalCliPath() {
24
64
  const entry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
25
- const result = spawnSync(process.execPath, [join(dirname(entry), "cli.js"), ...args], {
26
- env: {
27
- ...process.env,
28
- PI_CODING_AGENT: "true",
29
- PI_SERVER_MODE: "true",
30
- },
31
- stdio: "inherit",
32
- });
33
- if (result.error) throw result.error;
34
- process.exitCode = result.status ?? (result.signal === "SIGINT" ? 130 : 1);
65
+ return join(dirname(entry), "cli.js");
66
+ }
67
+
68
+ function getUpdatedGlobalCliPath() {
69
+ const npmCli = join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
70
+ const result = existsSync(npmCli)
71
+ ? spawnSync(process.execPath, [npmCli, "root", "-g"], { encoding: "utf-8" })
72
+ : spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
73
+ if (result.status !== 0) return undefined;
74
+ const root = result.stdout.trim();
75
+ const cliPath = join(root, "@averyyy", "pi-client", "node_modules", "@earendil-works", "pi-coding-agent", "dist", "cli.js");
76
+ return existsSync(cliPath) ? cliPath : undefined;
77
+ }
78
+
79
+ function readReloadState(path) {
80
+ try {
81
+ const value = JSON.parse(readFileSync(path, "utf-8"));
82
+ if (typeof value.sessionId !== "string" || typeof value.updateMarkerPath !== "string") return undefined;
83
+ if (value.sessionDir !== undefined && typeof value.sessionDir !== "string") return undefined;
84
+ return value;
85
+ } catch {
86
+ return undefined;
87
+ }
35
88
  }
package/bin/update.js CHANGED
@@ -1,16 +1,13 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { readFileSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
 
6
7
  function defaultPackageRoot() {
7
8
  return dirname(dirname(fileURLToPath(import.meta.url)));
8
9
  }
9
10
 
10
- function defaultRepoRoot() {
11
- return resolve(defaultPackageRoot(), "..", "..");
12
- }
13
-
14
11
  function readPackageMetadata(packageRoot) {
15
12
  return JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf-8"));
16
13
  }
@@ -19,70 +16,53 @@ function runStep(runner, command, args, cwd, stdio = "inherit") {
19
16
  return runner(command, args, { cwd, stdio, encoding: "utf-8" });
20
17
  }
21
18
 
22
- function isMissingGitCheckout(result) {
23
- const text = `${String(result.stdout ?? "")}\n${String(result.stderr ?? "")}`;
24
- return result.status !== 0 && text.includes("not a git repository");
19
+ function defaultUpdateMarkerPath() {
20
+ return join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "pi-client-update.json");
21
+ }
22
+
23
+ function writeUpdateMarker(path) {
24
+ mkdirSync(dirname(path), { recursive: true });
25
+ writeFileSync(path, `${JSON.stringify({ version: "latest", updatedAt: new Date().toISOString() })}\n`);
25
26
  }
26
27
 
27
- async function runNpmGlobalUpdate(runner, repoRoot, stdout, stderr) {
28
+ function runNpmGlobalUpdate(runner, cwd, stdout, stderr, updateMarkerPath) {
28
29
  stdout.write("Updating npm packages: @averyyy/pi-client@latest @averyyy/pi-server@latest\n");
29
30
  const result = runStep(
30
31
  runner,
31
32
  "npm",
32
- ["install", "-g", "--ignore-scripts", "--legacy-peer-deps", "@averyyy/pi-client@latest", "@averyyy/pi-server@latest"],
33
- repoRoot,
33
+ [
34
+ "install",
35
+ "-g",
36
+ "--ignore-scripts",
37
+ "--legacy-peer-deps",
38
+ "--force",
39
+ "@averyyy/pi-client@latest",
40
+ "@averyyy/pi-server@latest",
41
+ ],
42
+ cwd,
34
43
  );
35
44
  if (result.status !== 0) {
36
45
  stderr.write(
37
- "pi-client update failed: npm install -g --ignore-scripts --legacy-peer-deps @averyyy/pi-client@latest @averyyy/pi-server@latest\n",
46
+ "pi-client update failed: npm install -g --ignore-scripts --legacy-peer-deps --force @averyyy/pi-client@latest @averyyy/pi-server@latest\n",
38
47
  );
39
48
  return result.status ?? 1;
40
49
  }
50
+ writeUpdateMarker(updateMarkerPath);
51
+ stdout.write("Updated package files without stopping active pi-client sessions. Run /reload in each session to switch runtimes.\n");
41
52
  stdout.write("pi-client update complete\n");
42
53
  return 0;
43
54
  }
44
55
 
45
56
  export async function runPiClientUpdate(_args = [], options = {}) {
46
57
  const packageRoot = options.packageRoot ?? defaultPackageRoot();
47
- const repoRoot = options.repoRoot ?? defaultRepoRoot();
48
58
  const runner = options.runner ?? spawnSync;
49
59
  const stdout = options.stdout ?? process.stdout;
50
60
  const stderr = options.stderr ?? process.stderr;
61
+ const updateMarkerPath = options.updateMarkerPath ?? defaultUpdateMarkerPath();
51
62
  const pkg = readPackageMetadata(packageRoot);
52
63
  const baseVersion = pkg.piClient?.basePiVersion ?? "unknown";
53
64
  const baseCommit = pkg.piClient?.basePiCommit ?? "unknown";
54
65
 
55
66
  stdout.write(`pi-client ${pkg.version} (based on pi ${baseVersion}, upstream ${baseCommit})\n`);
56
- stdout.write(`Updating checkout: ${repoRoot}\n`);
57
-
58
- const status = runStep(runner, "git", ["status", "--porcelain"], repoRoot, "pipe");
59
- if (status.status !== 0) {
60
- if (isMissingGitCheckout(status)) {
61
- return runNpmGlobalUpdate(runner, repoRoot, stdout, stderr);
62
- }
63
- stderr.write("pi-client update failed: unable to inspect git status\n");
64
- return status.status ?? 1;
65
- }
66
- if (String(status.stdout ?? "").trim().length > 0) {
67
- stderr.write("pi-client update failed: working tree has uncommitted changes\n");
68
- return 1;
69
- }
70
-
71
- const steps = [
72
- ["git", ["pull", "--ff-only"]],
73
- ["npm", ["install", "--ignore-scripts"]],
74
- ["npm", ["run", "install:pi-client"]],
75
- ["npm", ["run", "install:pi-server"]],
76
- ];
77
-
78
- for (const [command, args] of steps) {
79
- const result = runStep(runner, command, args, repoRoot);
80
- if (result.status !== 0) {
81
- stderr.write(`pi-client update failed: ${command} ${args.join(" ")}\n`);
82
- return result.status ?? 1;
83
- }
84
- }
85
-
86
- stdout.write("pi-client update complete\n");
87
- return 0;
67
+ return runNpmGlobalUpdate(runner, process.cwd(), stdout, stderr, updateMarkerPath);
88
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@averyyy/pi-client",
3
- "version": "0.80.6-piclient.4",
3
+ "version": "0.80.6-piclient.6",
4
4
  "description": "Lightweight CLI wrapper that connects to a pi-server instance",
5
5
  "type": "module",
6
6
  "piClient": {
@@ -25,7 +25,7 @@
25
25
  "prepublishOnly": "npm run build"
26
26
  },
27
27
  "dependencies": {
28
- "@earendil-works/pi-coding-agent": "npm:@averyyy/pi-coding-agent@0.80.6-piclient.4"
28
+ "@earendil-works/pi-coding-agent": "npm:@averyyy/pi-coding-agent@0.80.6-piclient.6"
29
29
  },
30
30
  "devDependencies": {
31
31
  "shx": "0.4.0",