@averyyy/pi-client 0.80.3-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 +13 -0
- package/README.md +50 -0
- package/bin/pi-client.js +35 -0
- package/bin/update.js +88 -0
- package/bin/web.js +151 -0
- package/package.json +49 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- Initial `pi-client` package as a lightweight wrapper that exposes only the `pi-client` bin without `pi`.
|
|
6
|
+
- Global install wrapper that launches the local forked coding-agent entrypoint while sharing the original `~/.pi/agent` configuration.
|
|
7
|
+
- `pi-client update` command for updating the fork checkout and reinstalling both `pi-client` and `pi-server`.
|
|
8
|
+
- npm global package updates during `pi-client update`.
|
|
9
|
+
- `pi-client web` command that starts the client backend in Tau mirror mode on port `1838` by default.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- 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.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @averyyy/pi-client
|
|
2
|
+
|
|
3
|
+
Client CLI for connecting Pi to a `pi-server` instance.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g --ignore-scripts --legacy-peer-deps @averyyy/pi-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`--legacy-peer-deps` avoids npm peer override warnings when upstream Pi is already installed globally.
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
Connect to the hosted server:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
PI_SERVER_URL=https://pi.yreva.asia pi-client
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Send one prompt and exit:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
PI_SERVER_URL=https://pi.yreva.asia pi-client -p "Say exactly: ok"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Start the browser UI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pi install npm:tau-mirror
|
|
31
|
+
PI_SERVER_URL=https://pi.yreva.asia pi-client web
|
|
32
|
+
```
|
|
33
|
+
|
|
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.
|
|
35
|
+
|
|
36
|
+
## Server Auth
|
|
37
|
+
|
|
38
|
+
If your server uses an auth token, set it on the client:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
PI_SERVER_AUTH_TOKEN=your-token PI_SERVER_URL=http://127.0.0.1:4217 pi-client
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Related Package
|
|
45
|
+
|
|
46
|
+
Install the server separately:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm i -g --ignore-scripts @averyyy/pi-server
|
|
50
|
+
```
|
package/bin/pi-client.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const modulePromise =
|
|
8
|
+
args[0] === "update"
|
|
9
|
+
? import("./update.js").then(async ({ runPiClientUpdate }) => {
|
|
10
|
+
process.exitCode = await runPiClientUpdate(args.slice(1));
|
|
11
|
+
})
|
|
12
|
+
: args[0] === "web"
|
|
13
|
+
? import("./web.js").then(async ({ runPiClientWeb }) => {
|
|
14
|
+
process.exitCode = await runPiClientWeb(args.slice(1));
|
|
15
|
+
})
|
|
16
|
+
: runPiClientCli(args);
|
|
17
|
+
|
|
18
|
+
Promise.resolve(modulePromise).catch((e) => {
|
|
19
|
+
console.error(e);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function runPiClientCli(args) {
|
|
24
|
+
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);
|
|
35
|
+
}
|
package/bin/update.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
function defaultPackageRoot() {
|
|
7
|
+
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function defaultRepoRoot() {
|
|
11
|
+
return resolve(defaultPackageRoot(), "..", "..");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readPackageMetadata(packageRoot) {
|
|
15
|
+
return JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf-8"));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runStep(runner, command, args, cwd, stdio = "inherit") {
|
|
19
|
+
return runner(command, args, { cwd, stdio, encoding: "utf-8" });
|
|
20
|
+
}
|
|
21
|
+
|
|
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");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runNpmGlobalUpdate(runner, repoRoot, stdout, stderr) {
|
|
28
|
+
stdout.write("Updating npm packages: @averyyy/pi-client@latest @averyyy/pi-server@latest\n");
|
|
29
|
+
const result = runStep(
|
|
30
|
+
runner,
|
|
31
|
+
"npm",
|
|
32
|
+
["install", "-g", "--ignore-scripts", "--legacy-peer-deps", "@averyyy/pi-client@latest", "@averyyy/pi-server@latest"],
|
|
33
|
+
repoRoot,
|
|
34
|
+
);
|
|
35
|
+
if (result.status !== 0) {
|
|
36
|
+
stderr.write(
|
|
37
|
+
"pi-client update failed: npm install -g --ignore-scripts --legacy-peer-deps @averyyy/pi-client@latest @averyyy/pi-server@latest\n",
|
|
38
|
+
);
|
|
39
|
+
return result.status ?? 1;
|
|
40
|
+
}
|
|
41
|
+
stdout.write("pi-client update complete\n");
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function runPiClientUpdate(_args = [], options = {}) {
|
|
46
|
+
const packageRoot = options.packageRoot ?? defaultPackageRoot();
|
|
47
|
+
const repoRoot = options.repoRoot ?? defaultRepoRoot();
|
|
48
|
+
const runner = options.runner ?? spawnSync;
|
|
49
|
+
const stdout = options.stdout ?? process.stdout;
|
|
50
|
+
const stderr = options.stderr ?? process.stderr;
|
|
51
|
+
const pkg = readPackageMetadata(packageRoot);
|
|
52
|
+
const baseVersion = pkg.piClient?.basePiVersion ?? "unknown";
|
|
53
|
+
const baseCommit = pkg.piClient?.basePiCommit ?? "unknown";
|
|
54
|
+
|
|
55
|
+
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;
|
|
88
|
+
}
|
package/bin/web.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const defaultPort = "1838";
|
|
9
|
+
const defaultPiServerUrl = "http://127.0.0.1:4217";
|
|
10
|
+
const tauCodexPackage = "@averyyy/pi-tau-codex";
|
|
11
|
+
const tauCodexInstallTarget = "npm:@averyyy/pi-tau-codex";
|
|
12
|
+
const tauCodexGitTarget = "git:github.com/Averyyy/pi-tau-codex";
|
|
13
|
+
|
|
14
|
+
export async function runPiClientWeb(args = process.argv.slice(2)) {
|
|
15
|
+
const parsed = parseArgs(args);
|
|
16
|
+
if (parsed.help) {
|
|
17
|
+
printHelp();
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!hasTauCodexExtensionInstalled(process.env)) {
|
|
22
|
+
printInstallRequired();
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
process.title = "pi-client web";
|
|
27
|
+
const entry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
|
|
28
|
+
const port = parsed.port;
|
|
29
|
+
const host = process.env.TAU_HOST ?? "127.0.0.1";
|
|
30
|
+
const child = spawn(process.execPath, [join(dirname(entry), "cli.js"), ...parsed.clientArgs], {
|
|
31
|
+
env: piClientWebEnv(process.env, { port, host }),
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log(`pi-client web uses Tau at http://${host}:${port}`);
|
|
36
|
+
|
|
37
|
+
return await new Promise((resolve, reject) => {
|
|
38
|
+
child.once("error", reject);
|
|
39
|
+
child.once("exit", (code, signal) => {
|
|
40
|
+
resolve(code ?? (signal === "SIGINT" ? 130 : 1));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function piClientWebEnv(env = process.env, options) {
|
|
46
|
+
return {
|
|
47
|
+
...env,
|
|
48
|
+
PI_CODING_AGENT: "true",
|
|
49
|
+
PI_SERVER_MODE: "true",
|
|
50
|
+
PI_SERVER_URL: env.PI_SERVER_URL ?? defaultPiServerUrl,
|
|
51
|
+
TAU_HOST: options.host,
|
|
52
|
+
TAU_MIRROR_PORT: options.port,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hasTauCodexExtensionInstalled(env = process.env) {
|
|
57
|
+
if (env.TAU_STATIC_DIR) return true;
|
|
58
|
+
const settingsPath = env.PI_CODING_AGENT_SETTINGS_PATH ?? join(
|
|
59
|
+
env.PI_CODING_AGENT_DIR ?? join(env.HOME ?? homedir(), ".pi", "agent"),
|
|
60
|
+
"settings.json",
|
|
61
|
+
);
|
|
62
|
+
if (!existsSync(settingsPath)) return false;
|
|
63
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
64
|
+
const packages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
65
|
+
return packages.some(packageSpecMatchesTauCodex);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function packageSpecMatchesTauCodex(spec) {
|
|
69
|
+
if (typeof spec === "string") return specMatchesTauCodex(spec);
|
|
70
|
+
if (!spec || typeof spec !== "object") return false;
|
|
71
|
+
return ["source", "package", "name", "spec"].some((key) => specMatchesTauCodex(spec[key]));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function specMatchesTauCodex(spec) {
|
|
75
|
+
return typeof spec === "string" && (
|
|
76
|
+
spec === tauCodexPackage ||
|
|
77
|
+
spec === tauCodexInstallTarget ||
|
|
78
|
+
spec.startsWith(`${tauCodexPackage}@`) ||
|
|
79
|
+
spec.startsWith(`${tauCodexInstallTarget}@`) ||
|
|
80
|
+
spec === tauCodexGitTarget ||
|
|
81
|
+
spec.includes("github.com/Averyyy/pi-tau-codex")
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseArgs(args) {
|
|
86
|
+
let port = process.env.TAU_MIRROR_PORT ?? defaultPort;
|
|
87
|
+
const clientArgs = [];
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
90
|
+
const arg = args[i];
|
|
91
|
+
if (arg === "--help" || arg === "-h") return { help: true, port, clientArgs };
|
|
92
|
+
if (arg === "--") {
|
|
93
|
+
clientArgs.push(...args.slice(i + 1));
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
if (arg === "--port" || arg === "-p") {
|
|
97
|
+
port = requireValue(args, (i += 1), arg);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (arg.startsWith("--port=")) {
|
|
101
|
+
port = arg.slice("--port=".length);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Unknown pi-client web argument: ${arg}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
validatePort(port);
|
|
108
|
+
return { help: false, port, clientArgs };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function requireValue(args, index, name) {
|
|
112
|
+
const value = args[index];
|
|
113
|
+
if (value === undefined || value.startsWith("-")) throw new Error(`${name} requires a value`);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function validatePort(port) {
|
|
118
|
+
const parsed = Number(port);
|
|
119
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
120
|
+
throw new Error(`--port must be an integer from 1 to 65535: ${port}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function printHelp() {
|
|
125
|
+
console.log(`Usage: pi-client web [--port <port>] [-- <pi-client args...>]
|
|
126
|
+
|
|
127
|
+
Start pi-client in Tau mirror mode.
|
|
128
|
+
|
|
129
|
+
Options:
|
|
130
|
+
-p, --port <port> Tau port to listen on (default: ${defaultPort})
|
|
131
|
+
-h, --help Show this help
|
|
132
|
+
|
|
133
|
+
Environment:
|
|
134
|
+
PI_SERVER_URL pi-server URL used by pi-client sessions
|
|
135
|
+
PI_SERVER_AUTH_TOKEN pi-server auth token
|
|
136
|
+
TAU_HOST Tau bind host (default: 127.0.0.1)
|
|
137
|
+
TAU_MIRROR_PORT Tau port (default: ${defaultPort})
|
|
138
|
+
|
|
139
|
+
Tau must be installed in the shared Pi agent settings:
|
|
140
|
+
pi-client install npm:@averyyy/pi-tau-codex
|
|
141
|
+
# or: pi install npm:@averyyy/pi-tau-codex
|
|
142
|
+
# dev fallback: pi-client install git:github.com/Averyyy/pi-tau-codex
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function printInstallRequired() {
|
|
147
|
+
console.error(`请安装 ${tauCodexPackage}:`);
|
|
148
|
+
console.error(" pi-client install npm:@averyyy/pi-tau-codex");
|
|
149
|
+
console.error(" # or: pi install npm:@averyyy/pi-tau-codex");
|
|
150
|
+
console.error(" # dev fallback: pi-client install git:github.com/Averyyy/pi-tau-codex");
|
|
151
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@averyyy/pi-client",
|
|
3
|
+
"version": "0.80.3-piclient.10",
|
|
4
|
+
"description": "Lightweight CLI wrapper that connects to a pi-server instance",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"piClient": {
|
|
7
|
+
"basePiVersion": "0.80.3",
|
|
8
|
+
"basePiCommit": "85b7c247"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"pi-client": "bin/pi-client.js"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"README.md",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"clean": "shx echo ok",
|
|
23
|
+
"build": "shx echo ok",
|
|
24
|
+
"test": "vitest --run",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@earendil-works/pi-coding-agent": "npm:@averyyy/pi-coding-agent@0.80.3-piclient.10"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"shx": "0.4.0",
|
|
32
|
+
"vitest": "4.1.9"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"pi-client",
|
|
36
|
+
"llm",
|
|
37
|
+
"agent"
|
|
38
|
+
],
|
|
39
|
+
"author": "Pi Contributors",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/Averyyy/pi-client",
|
|
44
|
+
"directory": "packages/pi-client"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.19.0"
|
|
48
|
+
}
|
|
49
|
+
}
|