@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 +7 -0
- package/README.md +15 -2
- package/bin/pi-client.js +69 -11
- package/bin/send.js +49 -0
- package/bin/update.js +26 -46
- package/bin/web.js +20 -3
- package/package.json +4 -4
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
|
|
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.
|
|
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] === "
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (result.
|
|
34
|
-
|
|
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 {
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
"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.
|
|
8
|
-
"basePiCommit": "
|
|
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.
|
|
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",
|