@piagent/platform 1.0.2 → 1.1.1
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 +52 -23
- package/README.md +78 -366
- package/docs/command-reference-vietnamese.md +43 -4
- package/docs/distribution-standard.md +9 -9
- package/docs/herdr-workflow.md +22 -1
- package/docs/mcp-and-tools.md +27 -3
- package/docs/operator-manual-vietnamese.md +12 -12
- package/docs/publishing-for-teams.md +1 -0
- package/docs/quickstart-vietnamese.md +32 -232
- package/docs/release-install-policy.md +21 -10
- package/docs/security-threat-model.md +1 -1
- package/docs/task-lifecycle-vietnamese.md +139 -0
- package/docs/team-onboarding.md +8 -7
- package/docs-site/index.html +36 -88
- package/package.json +4 -3
- package/packages/piagent-core/README.md +2 -1
- package/packages/piagent-core/extensions/document-intake.ts +464 -0
- package/packages/piagent-core/extensions/guard-types.ts +1 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +120 -0
- package/packages/piagent-core/package.json +2 -2
- package/schemas/project-profile.schema.json +8 -0
- package/scripts/check-published-site.mjs +242 -0
- package/scripts/configure-mcp.sh +34 -1
- package/scripts/init-project.sh +19 -0
- package/scripts/install-global.sh +26 -8
- package/scripts/setup.sh +5 -0
- package/scripts/team-doctor.sh +1 -1
- package/scripts/uninstall-global.sh +4 -1
- package/scripts/verify-local.sh +113 -69
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Checks that the published documentation site serves the released version from
|
|
4
|
+
// every address it resolves to.
|
|
5
|
+
//
|
|
6
|
+
// Opening the site in a browser is not this check. A domain with several A
|
|
7
|
+
// records answers from whichever one the resolver picked that second, so one
|
|
8
|
+
// address can be dead for a week while every manual look happens to land on a
|
|
9
|
+
// healthy one. This resolves the name, then asks each address directly with the
|
|
10
|
+
// right Host header and SNI, and reports them separately. A partial outage has
|
|
11
|
+
// to read as a failure, not as an average.
|
|
12
|
+
//
|
|
13
|
+
// A redirect from a pinned address back to the same host is reported rather
|
|
14
|
+
// than followed: that is the shape of a loop, and following it would only
|
|
15
|
+
// re-resolve the name and hide which address caused it.
|
|
16
|
+
|
|
17
|
+
import dns from "node:dns/promises";
|
|
18
|
+
import https from "node:https";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
|
+
|
|
23
|
+
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
24
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
25
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
26
|
+
|
|
27
|
+
function fail(message) {
|
|
28
|
+
process.stderr.write(`FAIL: ${message}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseArguments(values) {
|
|
33
|
+
const options = { hosts: [], expect: null, allowUnverified: false };
|
|
34
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
35
|
+
const option = values[index];
|
|
36
|
+
if (option === "-h" || option === "--help") {
|
|
37
|
+
process.stdout.write("Usage: node scripts/check-published-site.mjs [--host <name>]... [--expect <text>] [--allow-unverified]\n");
|
|
38
|
+
process.stdout.write("Verify every resolved address of the docs site serves the released version.\n");
|
|
39
|
+
process.stdout.write("Defaults to piagent.io.vn and www.piagent.io.vn, expecting the root package version.\n");
|
|
40
|
+
process.stdout.write("--allow-unverified downgrades addresses this machine cannot route to from a failure to a note.\n");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
if (option === "--allow-unverified") {
|
|
44
|
+
options.allowUnverified = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const value = values[index + 1];
|
|
48
|
+
if (option === "--host") {
|
|
49
|
+
if (!value || value.startsWith("--")) fail("--host requires a value");
|
|
50
|
+
if (!/^[a-z0-9.-]{1,253}$/i.test(value)) fail(`--host value ${JSON.stringify(value)} is not a hostname`);
|
|
51
|
+
options.hosts.push(value);
|
|
52
|
+
index += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (option === "--expect") {
|
|
56
|
+
if (!value || value.startsWith("--")) fail("--expect requires a value");
|
|
57
|
+
if (options.expect !== null) fail("duplicate option --expect");
|
|
58
|
+
options.expect = value;
|
|
59
|
+
index += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
fail(`unknown option ${option}`);
|
|
63
|
+
}
|
|
64
|
+
if (options.hosts.length === 0) options.hosts = ["piagent.io.vn", "www.piagent.io.vn"];
|
|
65
|
+
if (options.expect === null) {
|
|
66
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(repositoryRoot, "package.json"), "utf8"));
|
|
67
|
+
options.expect = manifest.version;
|
|
68
|
+
}
|
|
69
|
+
return options;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function resolveAddresses(host) {
|
|
73
|
+
const addresses = [];
|
|
74
|
+
for (const [family, resolver] of [[4, dns.resolve4], [6, dns.resolve6]]) {
|
|
75
|
+
try {
|
|
76
|
+
for (const address of await resolver(host)) addresses.push({ address, family });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error?.code !== "ENODATA" && error?.code !== "ENOTFOUND") throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return addresses;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// `port` and `tlsOptions` exist so the body-cap path can be driven against a
|
|
85
|
+
// local server in a test. Nothing on the command line reaches them, and the
|
|
86
|
+
// defaults are what every real run uses: port 443 and Node's own trust store.
|
|
87
|
+
export function request(host, { address, family, port = 443 }, tlsOptions = {}) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
// Every path out of here has to settle exactly once. Capping the body
|
|
90
|
+
// destroys the response, and a destroyed response emits neither `end` nor
|
|
91
|
+
// `error` — it goes straight to `aborted` and `close`. Without a `close`
|
|
92
|
+
// handler the promise stays pending forever, and the request timeout cannot
|
|
93
|
+
// rescue it because the socket is already gone. That is a hung release gate,
|
|
94
|
+
// not a failed one.
|
|
95
|
+
let settled = false;
|
|
96
|
+
const settle = (value) => {
|
|
97
|
+
if (settled) return;
|
|
98
|
+
settled = true;
|
|
99
|
+
resolve(value);
|
|
100
|
+
};
|
|
101
|
+
const call = https.request(
|
|
102
|
+
{
|
|
103
|
+
...tlsOptions,
|
|
104
|
+
host: address,
|
|
105
|
+
family,
|
|
106
|
+
servername: host,
|
|
107
|
+
port,
|
|
108
|
+
path: "/",
|
|
109
|
+
method: "GET",
|
|
110
|
+
headers: { Host: host, "User-Agent": "piagent-site-check" },
|
|
111
|
+
timeout: REQUEST_TIMEOUT_MS
|
|
112
|
+
},
|
|
113
|
+
(response) => {
|
|
114
|
+
let body = "";
|
|
115
|
+
let bytes = 0;
|
|
116
|
+
let truncated = false;
|
|
117
|
+
response.setEncoding("utf8");
|
|
118
|
+
response.on("data", (chunk) => {
|
|
119
|
+
bytes += Buffer.byteLength(chunk, "utf8");
|
|
120
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
121
|
+
truncated = true;
|
|
122
|
+
response.destroy();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
body += chunk;
|
|
126
|
+
});
|
|
127
|
+
response.on("end", () =>
|
|
128
|
+
settle({ status: response.statusCode, location: response.headers.location, body, truncated })
|
|
129
|
+
);
|
|
130
|
+
response.on("error", (error) => settle({ error: error.message, code: error.code }));
|
|
131
|
+
response.on("close", () =>
|
|
132
|
+
settle({ status: response.statusCode, location: response.headers.location, body, truncated })
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
call.on("timeout", () => {
|
|
137
|
+
call.destroy();
|
|
138
|
+
settle({ error: `no response within ${REQUEST_TIMEOUT_MS}ms` });
|
|
139
|
+
});
|
|
140
|
+
call.on("error", (error) => settle({ error: error.message, code: error.code }));
|
|
141
|
+
call.end();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// A redirect is only acceptable when it leaves this host for another name we are
|
|
146
|
+
// also checking. Pointing back at itself is the loop; pointing off the domain
|
|
147
|
+
// means the released page is not what this address serves.
|
|
148
|
+
export function judge(host, hosts, expect, result, family) {
|
|
149
|
+
// A machine with no route to the address family cannot say anything about the
|
|
150
|
+
// address. Calling that a site failure would be a false positive, and a gate
|
|
151
|
+
// that cries wolf gets ignored, so it is reported as unverified instead.
|
|
152
|
+
if (result.code === "ENETUNREACH" || result.code === "EHOSTUNREACH") {
|
|
153
|
+
return { unverified: `no route from this machine to IPv${family}: ${result.error}` };
|
|
154
|
+
}
|
|
155
|
+
if (result.error) return `unreachable: ${result.error}`;
|
|
156
|
+
if (result.status >= 300 && result.status < 400) {
|
|
157
|
+
let target;
|
|
158
|
+
try {
|
|
159
|
+
target = new URL(result.location ?? "", `https://${host}/`);
|
|
160
|
+
} catch {
|
|
161
|
+
return `HTTP ${result.status} with an unreadable Location header`;
|
|
162
|
+
}
|
|
163
|
+
if (target.hostname === host) return `HTTP ${result.status} redirecting to itself at ${target.href}`;
|
|
164
|
+
if (!hosts.includes(target.hostname)) return `HTTP ${result.status} redirecting off the site to ${target.href}`;
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
if (result.status !== 200) return `HTTP ${result.status}`;
|
|
168
|
+
if (result.body.includes(expect)) return null;
|
|
169
|
+
// Not finding the version in a body that was cut short is not the same claim
|
|
170
|
+
// as not finding it in the page.
|
|
171
|
+
if (result.truncated) {
|
|
172
|
+
return `HTTP 200 but the response exceeded ${MAX_BODY_BYTES} bytes and the part read does not mention ${expect}`;
|
|
173
|
+
}
|
|
174
|
+
return `HTTP 200 but the page does not mention ${expect}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function main() {
|
|
178
|
+
const options = parseArguments(process.argv.slice(2));
|
|
179
|
+
const failures = [];
|
|
180
|
+
const unverified = [];
|
|
181
|
+
let checked = 0;
|
|
182
|
+
|
|
183
|
+
for (const host of options.hosts) {
|
|
184
|
+
let addresses;
|
|
185
|
+
try {
|
|
186
|
+
addresses = await resolveAddresses(host);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
fail(`${host} did not resolve: ${error instanceof Error ? error.message : String(error)}`);
|
|
189
|
+
}
|
|
190
|
+
if (addresses.length === 0) fail(`${host} resolved to no addresses`);
|
|
191
|
+
for (const entry of addresses) {
|
|
192
|
+
const verdict = judge(host, options.hosts, options.expect, await request(host, entry), entry.family);
|
|
193
|
+
checked += 1;
|
|
194
|
+
if (verdict?.unverified) {
|
|
195
|
+
unverified.push(`${host} at ${entry.address}: ${verdict.unverified}`);
|
|
196
|
+
process.stdout.write(` ???? ${host} ${entry.address} — ${verdict.unverified}\n`);
|
|
197
|
+
} else if (verdict) {
|
|
198
|
+
failures.push(`${host} at ${entry.address}: ${verdict}`);
|
|
199
|
+
process.stdout.write(` FAIL ${host} ${entry.address} — ${verdict}\n`);
|
|
200
|
+
} else {
|
|
201
|
+
process.stdout.write(` ok ${host} ${entry.address}\n`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const summary = summarize({ checked, failures, unverified, expect: options.expect, allowUnverified: options.allowUnverified });
|
|
207
|
+
process.stdout.write(summary.out);
|
|
208
|
+
process.stderr.write(summary.err);
|
|
209
|
+
process.exit(summary.code);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// An address nobody checked is not an address that passed. Reporting it as
|
|
213
|
+
// unverified and then exiting 0 makes the gate say "verified every address"
|
|
214
|
+
// about a run that did not, so an unchecked address ends the run unless the
|
|
215
|
+
// operator accepts the gap deliberately.
|
|
216
|
+
export function summarize({ checked, failures, unverified, expect, allowUnverified }) {
|
|
217
|
+
let out = "";
|
|
218
|
+
for (const entry of unverified) out += `UNVERIFIED: ${entry}\n`;
|
|
219
|
+
|
|
220
|
+
if (failures.length > 0) {
|
|
221
|
+
let err = `FAIL: ${failures.length} of ${checked} addresses do not serve ${expect}\n`;
|
|
222
|
+
for (const failure of failures) err += ` ${failure}\n`;
|
|
223
|
+
err += "Each address is a real path users take. Fix the DNS records or the deployment behind them.\n";
|
|
224
|
+
return { code: 1, out, err };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (unverified.length > 0 && !allowUnverified) {
|
|
228
|
+
let err = `FAIL: ${unverified.length} of ${checked} addresses could not be checked from this machine\n`;
|
|
229
|
+
for (const entry of unverified) err += ` ${entry}\n`;
|
|
230
|
+
err += "Run the check from a machine with a route to them, or pass --allow-unverified to accept the gap.\n";
|
|
231
|
+
return { code: 1, out, err };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const verified = checked - unverified.length;
|
|
235
|
+
const note = unverified.length > 0 ? ` (${unverified.length} accepted as not reachable from this machine)` : "";
|
|
236
|
+
out += `PASS: ${verified} of ${checked} addresses serve ${expect}${note}\n`;
|
|
237
|
+
return { code: 0, out, err: "" };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
241
|
+
await main();
|
|
242
|
+
}
|
package/scripts/configure-mcp.sh
CHANGED
|
@@ -211,6 +211,22 @@ const servers = {
|
|
|
211
211
|
}
|
|
212
212
|
};
|
|
213
213
|
|
|
214
|
+
// Writing a server definition does not make it usable. What each one still
|
|
215
|
+
// needs from the operator is stated here so it can be printed at the moment the
|
|
216
|
+
// definition is written, rather than living only in a description string that
|
|
217
|
+
// shows up under --list.
|
|
218
|
+
const prerequisites = {
|
|
219
|
+
context7: ["optional: export CONTEXT7_API_KEY for a higher documentation quota"],
|
|
220
|
+
"chrome-devtools": ["a local Chrome installation"],
|
|
221
|
+
github: [
|
|
222
|
+
"Docker running",
|
|
223
|
+
"export GITHUB_PERSONAL_ACCESS_TOKEN with the scopes you intend to use"
|
|
224
|
+
],
|
|
225
|
+
playwright: ["browsers installed on first use (npx playwright install)"],
|
|
226
|
+
figma: ["Figma OAuth, completed from the Pi MCP panel on first use"],
|
|
227
|
+
"figma-desktop": ["Figma desktop running with Dev Mode MCP enabled"]
|
|
228
|
+
};
|
|
229
|
+
|
|
214
230
|
const presets = {
|
|
215
231
|
minimal: [],
|
|
216
232
|
docs: ["context7"],
|
|
@@ -227,7 +243,10 @@ const presets = {
|
|
|
227
243
|
if (listOnly) {
|
|
228
244
|
console.log(JSON.stringify({
|
|
229
245
|
presets,
|
|
230
|
-
servers: Object.fromEntries(Object.entries(servers).map(([name, value]) => [
|
|
246
|
+
servers: Object.fromEntries(Object.entries(servers).map(([name, value]) => [
|
|
247
|
+
name,
|
|
248
|
+
{ description: value.description, requires: prerequisites[name] ?? [] }
|
|
249
|
+
]))
|
|
231
250
|
}, null, 2));
|
|
232
251
|
process.exit(0);
|
|
233
252
|
}
|
|
@@ -290,6 +309,19 @@ const report = {
|
|
|
290
309
|
serverCount: Object.keys(current.mcpServers).length
|
|
291
310
|
};
|
|
292
311
|
|
|
312
|
+
// stdout stays machine-readable; the operator note goes to stderr.
|
|
313
|
+
function reportPrerequisites() {
|
|
314
|
+
const lines = [];
|
|
315
|
+
for (const name of presets[presetName]) {
|
|
316
|
+
for (const item of prerequisites[name] ?? []) lines.push(` ${name}: ${item}`);
|
|
317
|
+
}
|
|
318
|
+
if (lines.length === 0) return;
|
|
319
|
+
console.error("");
|
|
320
|
+
console.error("Server definitions are written. Each still needs this before it can connect:");
|
|
321
|
+
for (const line of lines) console.error(line);
|
|
322
|
+
console.error("Servers connect lazily, so nothing above is checked until the first call.");
|
|
323
|
+
}
|
|
324
|
+
|
|
293
325
|
if (dryRun) {
|
|
294
326
|
console.log(JSON.stringify(report, null, 2));
|
|
295
327
|
console.log(output);
|
|
@@ -297,5 +329,6 @@ if (dryRun) {
|
|
|
297
329
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
298
330
|
fs.writeFileSync(configPath, output);
|
|
299
331
|
console.log(JSON.stringify(report, null, 2));
|
|
332
|
+
reportPrerequisites();
|
|
300
333
|
}
|
|
301
334
|
NODE
|
package/scripts/init-project.sh
CHANGED
|
@@ -238,6 +238,20 @@ NODE
|
|
|
238
238
|
fi
|
|
239
239
|
fi
|
|
240
240
|
|
|
241
|
+
# Running from an installed package rather than a checkout. The name and
|
|
242
|
+
# version on disk are exactly what a teammate resolves from the registry, so
|
|
243
|
+
# they are a portable source; the install path is not, and project settings
|
|
244
|
+
# are meant to be committed.
|
|
245
|
+
if [[ "$PLATFORM_ROOT" == */node_modules/* ]]; then
|
|
246
|
+
local installed_name installed_version
|
|
247
|
+
installed_name="$(node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1], "utf8")).name ?? "");' "$PLATFORM_ROOT/package.json")"
|
|
248
|
+
installed_version="$(node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1], "utf8")).version ?? "");' "$PLATFORM_ROOT/package.json")"
|
|
249
|
+
if [[ -n "$installed_name" && "$installed_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
|
|
250
|
+
printf 'npm:%s@%s\n' "$installed_name" "$installed_version"
|
|
251
|
+
return
|
|
252
|
+
fi
|
|
253
|
+
fi
|
|
254
|
+
|
|
241
255
|
echo "WARN: No exact package source provided." >&2
|
|
242
256
|
echo "WARN: .pi/settings.json will use local platform path; do not commit it for team use." >&2
|
|
243
257
|
printf '%s\n' "$PLATFORM_ROOT"
|
|
@@ -365,5 +379,10 @@ echo " cd \"$PROJECT_PATH\""
|
|
|
365
379
|
echo " pi"
|
|
366
380
|
echo " /login # first time only"
|
|
367
381
|
echo " /onboard-project # first project-read after model selection"
|
|
382
|
+
echo " /mcp # MCP servers come from the global install, not from this project"
|
|
368
383
|
echo " /context-index # inspect compact project context index"
|
|
369
384
|
echo " /memory-policy # inspect project memory policy when needed"
|
|
385
|
+
echo
|
|
386
|
+
echo "Note: .mcp.json and .pi/mcp.json are written empty on purpose. They exist so this"
|
|
387
|
+
echo "project can add its own servers later; the shared baseline lives in the global"
|
|
388
|
+
echo "config written by piagent-setup. An empty file here does not mean MCP is missing."
|
|
@@ -4,7 +4,7 @@ set -euo pipefail
|
|
|
4
4
|
usage() {
|
|
5
5
|
cat <<'USAGE'
|
|
6
6
|
Usage:
|
|
7
|
-
scripts/install-global.sh [--stable|--dev|--local|--channel <stable|dev|local>] [--version <tag>] [--resolve-tag] [--package-source <source>] [--dry-run] [--with-mcp] [--mcp-preset <preset>] [--with-subagents] [--subagents-preset <preset>] [--with-web-access] [--with-herdr] [--model-scope <preset>]
|
|
7
|
+
scripts/install-global.sh [--stable|--dev|--local|--channel <stable|dev|local>] [--version <tag>] [--resolve-tag] [--package-source <source>] [--dry-run] [--with-mcp|--no-mcp] [--mcp-preset <preset>] [--with-subagents] [--subagents-preset <preset>] [--with-web-access] [--with-herdr] [--model-scope <preset>]
|
|
8
8
|
|
|
9
9
|
Purpose:
|
|
10
10
|
Install the piagent Pi package into the current user's global Pi settings.
|
|
@@ -15,7 +15,7 @@ Package source examples:
|
|
|
15
15
|
# Installs the stable tag after resolving it to a commit SHA.
|
|
16
16
|
|
|
17
17
|
# Pin a specific release:
|
|
18
|
-
scripts/install-global.sh --version v1.
|
|
18
|
+
scripts/install-global.sh --version v1.1.1 --resolve-tag
|
|
19
19
|
|
|
20
20
|
# Preview the planned install/update without changing user config:
|
|
21
21
|
scripts/install-global.sh --stable --dry-run
|
|
@@ -35,7 +35,10 @@ Notes:
|
|
|
35
35
|
- OAuth is intentionally not automated. Run `pi` then `/login`.
|
|
36
36
|
- Model scope is configured with Pi's native `enabledModels` so users choose via `/model`, Ctrl+L, `/scoped-models`, and Ctrl+P.
|
|
37
37
|
- Herdr integration is optional and modifies user-level Herdr/Pi config.
|
|
38
|
-
- MCP
|
|
38
|
+
- MCP is installed by default with the core preset: Context7 docs, Chrome DevTools, GitHub.
|
|
39
|
+
Skip it with --no-mcp. Writing the config does not start or authenticate anything:
|
|
40
|
+
the GitHub server additionally needs Docker running and GITHUB_PERSONAL_ACCESS_TOKEN
|
|
41
|
+
exported, and Chrome DevTools needs a local Chrome.
|
|
39
42
|
- Subagents preset defaults to safe: compact tool description, bounded concurrency/depth.
|
|
40
43
|
- Web access is optional. Install it only when you want the builtin `researcher` subagent to browse/fetch web sources inside Pi.
|
|
41
44
|
USAGE
|
|
@@ -50,8 +53,9 @@ DEFAULT_REPO_REMOTE_URL="https://github.com/Vt-mmm/piagent.git"
|
|
|
50
53
|
PACKAGE_SOURCE="${PIAGENT_PACKAGE_SOURCE:-}"
|
|
51
54
|
PACKAGE_VERSION="${PIAGENT_PACKAGE_VERSION:-}"
|
|
52
55
|
RELEASE_CHANNEL="${PIAGENT_RELEASE_CHANNEL:-}"
|
|
53
|
-
WITH_MCP=
|
|
56
|
+
WITH_MCP=true
|
|
54
57
|
MCP_PRESET="core"
|
|
58
|
+
MCP_PRESET_EXPLICIT=false
|
|
55
59
|
WITH_SUBAGENTS=false
|
|
56
60
|
SUBAGENTS_PRESET="safe"
|
|
57
61
|
SUBAGENTS_MODEL_SCOPE="none"
|
|
@@ -123,7 +127,7 @@ detect_runtime_surface() {
|
|
|
123
127
|
printf 'native-windows/%s (not release-gated; terminal helpers require Bash semantics)' "$arch_name"
|
|
124
128
|
;;
|
|
125
129
|
*)
|
|
126
|
-
printf '%s/%s (outside v1.
|
|
130
|
+
printf '%s/%s (outside v1.1.1 release matrix)' "$os_name" "$arch_name"
|
|
127
131
|
;;
|
|
128
132
|
esac
|
|
129
133
|
}
|
|
@@ -131,7 +135,7 @@ detect_runtime_surface() {
|
|
|
131
135
|
warn_runtime_surface_if_needed() {
|
|
132
136
|
local surface="$1"
|
|
133
137
|
case "$surface" in
|
|
134
|
-
*"supported target"*|*"experimental"*|*"not release-gated"*|*"outside v1.
|
|
138
|
+
*"supported target"*|*"experimental"*|*"not release-gated"*|*"outside v1.1.1 release matrix"*)
|
|
135
139
|
echo "WARN: runtime surface is $surface." >&2
|
|
136
140
|
echo "WARN: For team rollout, run piagent-doctor plus the project smoke/verify suite on this machine before relying on it." >&2
|
|
137
141
|
;;
|
|
@@ -275,9 +279,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
275
279
|
WITH_MCP=true
|
|
276
280
|
shift
|
|
277
281
|
;;
|
|
282
|
+
--no-mcp)
|
|
283
|
+
WITH_MCP=false
|
|
284
|
+
shift
|
|
285
|
+
;;
|
|
278
286
|
--mcp-preset)
|
|
279
287
|
require_value "$1" "${2:-}"
|
|
280
288
|
MCP_PRESET="$2"
|
|
289
|
+
MCP_PRESET_EXPLICIT=true
|
|
281
290
|
shift 2
|
|
282
291
|
;;
|
|
283
292
|
--with-subagents)
|
|
@@ -328,6 +337,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
328
337
|
esac
|
|
329
338
|
done
|
|
330
339
|
|
|
340
|
+
# A preset chosen alongside --no-mcp used to be accepted and then quietly
|
|
341
|
+
# dropped, which reads as "the preset was applied".
|
|
342
|
+
if [[ "$WITH_MCP" == false && "$MCP_PRESET_EXPLICIT" == true ]]; then
|
|
343
|
+
echo "FAIL: --mcp-preset has no effect with --no-mcp. Drop one of them." >&2
|
|
344
|
+
exit 2
|
|
345
|
+
fi
|
|
346
|
+
|
|
331
347
|
require_node_version
|
|
332
348
|
CURRENT_RUNTIME_SURFACE="$(detect_runtime_surface)"
|
|
333
349
|
warn_runtime_surface_if_needed "$CURRENT_RUNTIME_SURFACE"
|
|
@@ -438,7 +454,7 @@ if ! command -v pi >/dev/null 2>&1; then
|
|
|
438
454
|
else
|
|
439
455
|
echo "FAIL: pi is not on PATH." >&2
|
|
440
456
|
echo "Install Pi first, then rerun this script." >&2
|
|
441
|
-
echo "Expected command on npm-based installs: npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.
|
|
457
|
+
echo "Expected command on npm-based installs: npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0" >&2
|
|
442
458
|
exit 1
|
|
443
459
|
fi
|
|
444
460
|
else
|
|
@@ -527,6 +543,8 @@ echo " pi"
|
|
|
527
543
|
echo " /login"
|
|
528
544
|
echo " /model # or Ctrl+L: select provider/model from Pi selector"
|
|
529
545
|
echo " /scoped-models # optional: edit Ctrl+P model cycle scope"
|
|
530
|
-
|
|
546
|
+
if [[ "$WITH_MCP" == true ]]; then
|
|
547
|
+
echo " /mcp # inspect MCP servers; authenticate Figma/GitHub only when needed"
|
|
548
|
+
fi
|
|
531
549
|
echo " /subagents-doctor"
|
|
532
550
|
echo " /task Implement <task> # parent may auto-delegate scout/planner/reviewer"
|
package/scripts/setup.sh
CHANGED
|
@@ -378,8 +378,13 @@ echo
|
|
|
378
378
|
if [[ "$DO_GLOBAL" == true ]]; then
|
|
379
379
|
ensure_pi_cli
|
|
380
380
|
install_args=("$PLATFORM_ROOT/scripts/install-global.sh" "--package-source" "$PACKAGE_SOURCE")
|
|
381
|
+
# The opt-out has to be passed explicitly. The installer defaults MCP on, so
|
|
382
|
+
# saying nothing here would install it against the operator's choice; the
|
|
383
|
+
# flags below can stay one-sided only because the installer defaults them off.
|
|
381
384
|
if [[ "$WITH_MCP" == true ]]; then
|
|
382
385
|
install_args+=("--with-mcp" "--mcp-preset" "$MCP_PRESET")
|
|
386
|
+
else
|
|
387
|
+
install_args+=("--no-mcp")
|
|
383
388
|
fi
|
|
384
389
|
if [[ "$WITH_SUBAGENTS" == true ]]; then
|
|
385
390
|
install_args+=("--with-subagents" "--subagents-preset" "$SUBAGENTS_PRESET" "--subagents-model-scope" "$SUBAGENTS_MODEL_SCOPE")
|
package/scripts/team-doctor.sh
CHANGED
|
@@ -137,7 +137,7 @@ function runtimeMatrixStatus() {
|
|
|
137
137
|
surface: `${platform}/${arch}`,
|
|
138
138
|
status: "outside-release-matrix",
|
|
139
139
|
teamRolloutReady: false,
|
|
140
|
-
note: "This runtime is outside the documented v1.
|
|
140
|
+
note: "This runtime is outside the documented v1.1.1 support matrix."
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -155,8 +155,11 @@ else
|
|
|
155
155
|
fi
|
|
156
156
|
# The MCP baseline and subagent config are shared files that the operator
|
|
157
157
|
# may have edited after install, so they are reported rather than reset.
|
|
158
|
+
# Install writes the shared global scope; the Pi-global file is the override
|
|
159
|
+
# scope and is listed too, because an operator who used it will look there.
|
|
158
160
|
echo "Left in place, review by hand if no longer wanted:"
|
|
159
|
-
echo " $
|
|
161
|
+
echo " ${XDG_CONFIG_HOME:-"${HOME}/.config"}/mcp/mcp.json # written by install"
|
|
162
|
+
echo " $PI_AGENT_DIR/mcp.json # Pi-global override scope, only if you created it"
|
|
160
163
|
echo " $PI_AGENT_DIR/extensions/subagent/config.json"
|
|
161
164
|
fi
|
|
162
165
|
fi
|