@averyyy/pi-client 0.80.3-piclient.9 → 0.80.6-piclient.10

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
@@ -7,7 +7,14 @@
7
7
  - `pi-client update` command for updating the fork checkout and reinstalling both `pi-client` and `pi-server`.
8
8
  - npm global package updates during `pi-client update`.
9
9
  - `pi-client web` command that starts the client backend in Tau mirror mode on port `1838` by default.
10
+ - `pi-client send <path>` command for chunked file and folder uploads to pi-server.
10
11
 
11
12
  ### Fixed
12
13
 
13
14
  - Used `--legacy-peer-deps` for npm-global fork updates and documented installs so existing upstream Pi installs do not trigger peer override warnings for forked prerelease aliases.
15
+
16
+ ### Changed
17
+
18
+ - Updated the upstream Pi base through commit `0e6909f0`, including the latest provider and compaction fixes.
19
+ - Rebased the client on upstream Pi `0.80.6`, including GPT-5.6 model metadata and `max` thinking support.
20
+ - 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
@@ -24,14 +24,23 @@ Send one prompt and exit:
24
24
  PI_SERVER_URL=https://pi.yreva.asia pi-client -p "Say exactly: ok"
25
25
  ```
26
26
 
27
+ Send a file or folder to the server:
28
+
29
+ ```bash
30
+ PI_SERVER_URL=https://pi.yreva.asia pi-client send /path/to/file-or-folder
31
+ ```
32
+
33
+ The server saves it under its configured upload directory, which defaults to `~/.pi/upload_files`.
34
+
27
35
  Start the browser UI:
28
36
 
29
37
  ```bash
30
- pi install npm:tau-mirror
38
+ pi-client install npm:@averyyy/pi-tau-codex
39
+ # or: pi install npm:@averyyy/pi-tau-codex
31
40
  PI_SERVER_URL=https://pi.yreva.asia pi-client web
32
41
  ```
33
42
 
34
- The web command starts `pi-client` in Tau mirror mode. Tau listens on `http://127.0.0.1:1838` by default and uses the same shared `~/.pi/agent` extension install as local `pi`, so installing Tau with either `pi` or `pi-client` works.
43
+ The web command starts `pi-client` in Tau mirror mode. Install the standalone `@averyyy/pi-tau-codex` extension into the shared `~/.pi/agent` settings first. Tau listens on `http://127.0.0.1:1838` by default.
35
44
 
36
45
  ## Server Auth
37
46
 
@@ -41,6 +50,10 @@ If your server uses an auth token, set it on the client:
41
50
  PI_SERVER_AUTH_TOKEN=your-token PI_SERVER_URL=http://127.0.0.1:4217 pi-client
42
51
  ```
43
52
 
53
+ ## Update
54
+
55
+ `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.
56
+
44
57
  ## Related Package
45
58
 
46
59
  Install the server separately:
package/bin/pi-client.js CHANGED
@@ -1,11 +1,18 @@
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";
7
+ import { runPiClientSend } from "./send.js";
5
8
 
6
9
  const args = process.argv.slice(2);
7
10
  const modulePromise =
8
- args[0] === "update"
11
+ args[0] === "send"
12
+ ? runPiClientSend(args.slice(1)).then((code) => {
13
+ process.exitCode = code;
14
+ })
15
+ : args[0] === "update"
9
16
  ? import("./update.js").then(async ({ runPiClientUpdate }) => {
10
17
  process.exitCode = await runPiClientUpdate(args.slice(1));
11
18
  })
@@ -21,15 +28,66 @@ Promise.resolve(modulePromise).catch((e) => {
21
28
  });
22
29
 
23
30
  function runPiClientCli(args) {
31
+ const reloadDir = mkdtempSync(join(tmpdir(), "pi-client-reload-"));
32
+ const reloadStatePath = join(reloadDir, "state.json");
33
+ let cliPath = getLocalCliPath();
34
+ let childArgs = args;
35
+
36
+ try {
37
+ for (;;) {
38
+ const result = spawnSync(process.execPath, [cliPath, ...childArgs], {
39
+ env: {
40
+ ...process.env,
41
+ PI_CODING_AGENT: "true",
42
+ PI_SERVER_MODE: "true",
43
+ PI_CLIENT_RELOAD_STATE_PATH: reloadStatePath,
44
+ },
45
+ stdio: "inherit",
46
+ });
47
+ if (result.error) throw result.error;
48
+ if (result.status !== 75) {
49
+ process.exitCode = result.status ?? (result.signal === "SIGINT" ? 130 : 1);
50
+ return;
51
+ }
52
+
53
+ const state = readReloadState(reloadStatePath);
54
+ if (!state) {
55
+ console.error("pi-client reload failed: missing session state from the previous runtime.");
56
+ process.exitCode = 1;
57
+ return;
58
+ }
59
+ rmSync(state.updateMarkerPath, { force: true });
60
+ childArgs = state.sessionDir ? ["--session-dir", state.sessionDir, "--session", state.sessionId] : ["--session", state.sessionId];
61
+ cliPath = getUpdatedGlobalCliPath() ?? getLocalCliPath();
62
+ }
63
+ } finally {
64
+ rmSync(reloadDir, { recursive: true, force: true });
65
+ }
66
+ }
67
+
68
+ function getLocalCliPath() {
24
69
  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);
70
+ return join(dirname(entry), "cli.js");
71
+ }
72
+
73
+ function getUpdatedGlobalCliPath() {
74
+ const npmCli = join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
75
+ const result = existsSync(npmCli)
76
+ ? spawnSync(process.execPath, [npmCli, "root", "-g"], { encoding: "utf-8" })
77
+ : spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
78
+ if (result.status !== 0) return undefined;
79
+ const root = result.stdout.trim();
80
+ const cliPath = join(root, "@averyyy", "pi-client", "node_modules", "@earendil-works", "pi-coding-agent", "dist", "cli.js");
81
+ return existsSync(cliPath) ? cliPath : undefined;
82
+ }
83
+
84
+ function readReloadState(path) {
85
+ try {
86
+ const value = JSON.parse(readFileSync(path, "utf-8"));
87
+ if (typeof value.sessionId !== "string" || typeof value.updateMarkerPath !== "string") return undefined;
88
+ if (value.sessionDir !== undefined && typeof value.sessionDir !== "string") return undefined;
89
+ return value;
90
+ } catch {
91
+ return undefined;
92
+ }
35
93
  }
package/bin/send.js ADDED
@@ -0,0 +1,49 @@
1
+ import { lstatSync, readdirSync, readFileSync } from "node:fs";
2
+ import { basename, join, relative, resolve, sep } from "node:path";
3
+ import { ChunkRequest } from "@earendil-works/pi-coding-agent/pi-server-request";
4
+
5
+ function addDirectoryEntries(root, directory, entries) {
6
+ const path = relative(root, directory).split(sep).join("/");
7
+ entries.push({ path, type: "directory" });
8
+ for (const child of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
9
+ const childPath = join(directory, child.name);
10
+ if (child.isDirectory()) addDirectoryEntries(root, childPath, entries);
11
+ else if (child.isFile()) {
12
+ entries.push({ path: relative(root, childPath).split(sep).join("/"), type: "file", data: readFileSync(childPath).toString("base64") });
13
+ } else {
14
+ throw new Error(`Unsupported file type: ${childPath}`);
15
+ }
16
+ }
17
+ }
18
+
19
+ export function createUploadBody(sourcePath) {
20
+ const source = resolve(sourcePath);
21
+ const stat = lstatSync(source);
22
+ if (stat.isSymbolicLink()) throw new Error(`Symbolic links are not supported: ${source}`);
23
+ if (stat.isFile()) {
24
+ return { name: basename(source), entries: [{ path: "", type: "file", data: readFileSync(source).toString("base64") }] };
25
+ }
26
+ if (!stat.isDirectory()) throw new Error(`Unsupported file type: ${source}`);
27
+ const entries = [];
28
+ addDirectoryEntries(source, source, entries);
29
+ return { name: basename(source), entries };
30
+ }
31
+
32
+ export async function runPiClientSend(args) {
33
+ if (args.length !== 1) {
34
+ console.error("Usage: pi-client send /path/to/file-or-folder");
35
+ return 1;
36
+ }
37
+ const request = new ChunkRequest({
38
+ serverUrl: process.env.PI_SERVER_URL ?? "http://127.0.0.1:4217",
39
+ authToken: process.env.PI_SERVER_AUTH_TOKEN ?? "",
40
+ });
41
+ const response = await request.postJson("/api/receive", createUploadBody(args[0]));
42
+ const body = await response.json();
43
+ if (!response.ok) {
44
+ console.error(`pi-client send failed (${response.status}): ${body.error ?? response.statusText}`);
45
+ return 1;
46
+ }
47
+ console.log(`Saved to ${body.path}`);
48
+ return 0;
49
+ }
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/bin/web.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from "node:child_process";
2
+ import { spawn, spawnSync } from "node:child_process";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
+ import { createInterface } from "node:readline/promises";
6
7
  import { fileURLToPath } from "node:url";
7
8
 
8
9
  const defaultPort = "1838";
@@ -19,8 +20,7 @@ export async function runPiClientWeb(args = process.argv.slice(2)) {
19
20
  }
20
21
 
21
22
  if (!hasTauCodexExtensionInstalled(process.env)) {
22
- printInstallRequired();
23
- return 1;
23
+ if (!await offerTauCodexInstall()) return 1;
24
24
  }
25
25
 
26
26
  process.title = "pi-client web";
@@ -42,6 +42,23 @@ export async function runPiClientWeb(args = process.argv.slice(2)) {
42
42
  });
43
43
  }
44
44
 
45
+ export async function offerTauCodexInstall(env = process.env, input = process.stdin, output = process.stdout) {
46
+ printInstallRequired();
47
+ if (!input.isTTY || !output.isTTY) return false;
48
+
49
+ const rl = createInterface({ input, output });
50
+ const answer = (await rl.question("现在安装吗? [y/N] ")).trim().toLowerCase();
51
+ rl.close();
52
+ if (answer !== "y" && answer !== "yes") return false;
53
+
54
+ const result = spawnSync(process.execPath, [join(dirname(fileURLToPath(import.meta.url)), "pi-client.js"), "install", tauCodexInstallTarget], {
55
+ env,
56
+ stdio: "inherit",
57
+ });
58
+ if (result.error) throw result.error;
59
+ return result.status === 0 && hasTauCodexExtensionInstalled(env);
60
+ }
61
+
45
62
  export function piClientWebEnv(env = process.env, options) {
46
63
  return {
47
64
  ...env,
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@averyyy/pi-client",
3
- "version": "0.80.3-piclient.9",
3
+ "version": "0.80.6-piclient.10",
4
4
  "description": "Lightweight CLI wrapper that connects to a pi-server instance",
5
5
  "type": "module",
6
6
  "piClient": {
7
- "basePiVersion": "0.80.3",
8
- "basePiCommit": "85b7c247"
7
+ "basePiVersion": "0.80.6",
8
+ "basePiCommit": "0e6909f0"
9
9
  },
10
10
  "bin": {
11
11
  "pi-client": "bin/pi-client.js"
@@ -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.3-piclient.9"
28
+ "@earendil-works/pi-coding-agent": "npm:@averyyy/pi-coding-agent@0.80.6-piclient.10"
29
29
  },
30
30
  "devDependencies": {
31
31
  "shx": "0.4.0",