@piagent/platform 1.43.9 → 1.44.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 +32 -0
- package/docs/command-reference-vietnamese.md +8 -2
- package/docs/company-dev-commands.md +2 -0
- package/docs/distribution-standard.md +6 -6
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +2 -0
- package/docs/release-install-policy.md +4 -2
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/assets/search-en.json +1 -1
- package/docs-site/assets/search-vi.json +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/ecosystem.html +1 -1
- package/docs-site/en/index.html +9 -9
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/update.html +3 -3
- package/docs-site/en/whats-new.html +55 -31
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +9 -9
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/releases.json +77 -0
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/update.html +3 -3
- package/docs-site/whats-new.html +55 -31
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +4 -1
- package/packages/piagent-core/extensions/piagent-guard.ts +3 -0
- package/packages/piagent-core/managed/pi-host.mjs +60 -1
- package/packages/piagent-core/managed/resource-loader.mjs +3 -2
- package/packages/piagent-core/managed/session.mjs +2 -1
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/resources/plugin-skills.mjs +33 -4
- package/packages/piagent-core/runtime/terminal/terminal-style.d.mts +7 -0
- package/packages/piagent-core/runtime/terminal/terminal-style.mjs +99 -0
- package/packages/piagent-core/themes/piagent-contrast.json +78 -0
- package/packages/piagent-core/themes/piagent-focus.json +78 -0
- package/packages/piagent-core/themes/piagent-ocean.json +78 -0
- package/packages/piagent-core/themes/piagent-paper.json +78 -0
- package/packages/piagent-webui/gateway/pi-host.ts +13 -1
- package/scripts/company-terminal.mjs +6 -2
- package/scripts/install-global.sh +13 -1
- package/scripts/pi-host.mjs +109 -0
- package/scripts/piagent-cli.mjs +17 -6
- package/scripts/piagent-dashboard.mjs +2 -2
- package/scripts/runtime-advisory-exceptions.json +40 -0
- package/scripts/setup.sh +19 -1
- package/scripts/skill-sources.mjs +188 -0
- package/scripts/uninstall-global.sh +18 -1
- package/scripts/update-global.mjs +13 -5
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Skill repositories a member adds to Piagent, such as mattpocock/skills.
|
|
3
|
+
//
|
|
4
|
+
// A source is a public GitHub repository of Agent Skills (folders with a
|
|
5
|
+
// SKILL.md). `add` keeps it at one commit under ~/.piagent/skill-sources, so
|
|
6
|
+
// what the agent reads changes only on `update`; the index lists it
|
|
7
|
+
// (~/.piagent/skill-sources.json), and plugin-skills.mjs offers its skills to
|
|
8
|
+
// personal Pi, the company Terminal and the dashboard, as it does a Claude
|
|
9
|
+
// plugin's. A repository with a plugin manifest (.claude-plugin/plugin.json)
|
|
10
|
+
// loads only the skills the manifest publishes. Skills are instructions: a
|
|
11
|
+
// script they bundle runs only through the session's own tools and approvals.
|
|
12
|
+
//
|
|
13
|
+
// piagent skills add <owner/repo> [--ref <branch|tag|commit>]
|
|
14
|
+
// piagent skills list [--json]
|
|
15
|
+
// piagent skills update [<owner/repo>]
|
|
16
|
+
// piagent skills remove <owner/repo>
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { skillSourcePlugin, skillSourcesIndex, skillSourcesRoot } from '../packages/piagent-core/runtime/resources/plugin-skills.mjs';
|
|
23
|
+
|
|
24
|
+
// Short names for sources the team recommends.
|
|
25
|
+
export const KNOWN_SOURCES = { mattpocock: 'mattpocock/skills', 'matt-pocock': 'mattpocock/skills' };
|
|
26
|
+
|
|
27
|
+
function usage() {
|
|
28
|
+
return `Usage:
|
|
29
|
+
piagent skills add <owner/repo> [--ref <branch|tag|commit>]
|
|
30
|
+
Add a GitHub skill repository, kept at one commit
|
|
31
|
+
(for example: piagent skills add mattpocock/skills)
|
|
32
|
+
piagent skills list [--json] Skill sources and the skills each one adds
|
|
33
|
+
piagent skills update [<owner/repo>]
|
|
34
|
+
Move a source (or all) to the newest commit of its ref
|
|
35
|
+
piagent skills remove <owner/repo>
|
|
36
|
+
Stop reading a source and delete its copy
|
|
37
|
+
|
|
38
|
+
Skills from a source appear in Pi as /skill:<name>, in the company Terminal and
|
|
39
|
+
in the dashboard, after the next /reload or new conversation.
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// owner/repo from what the member typed: owner/repo, a github.com URL, or a known name.
|
|
44
|
+
export function parseRepository(value) {
|
|
45
|
+
const typed = KNOWN_SOURCES[String(value ?? '').trim().toLowerCase()] ?? String(value ?? '').trim();
|
|
46
|
+
const match = /^(?:https:\/\/github\.com\/|git:github\.com\/|github\.com\/)?([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))\/([A-Za-z0-9._-]{1,100}?)(?:\.git)?\/?$/.exec(typed);
|
|
47
|
+
if (!match || match[2] === '.' || match[2] === '..') return null;
|
|
48
|
+
return `${match[1]}/${match[2]}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const validRef = (ref) => typeof ref === 'string' && /^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/.test(ref) && !ref.includes('..');
|
|
52
|
+
|
|
53
|
+
export function readIndex(home = os.homedir()) {
|
|
54
|
+
try {
|
|
55
|
+
const value = JSON.parse(fs.readFileSync(skillSourcesIndex(home), 'utf8'));
|
|
56
|
+
return { schema_version: 1, sources: Array.isArray(value?.sources) ? value.sources : [] };
|
|
57
|
+
} catch { return { schema_version: 1, sources: [] }; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeIndex(index, home) {
|
|
61
|
+
const file = skillSourcesIndex(home);
|
|
62
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
63
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
64
|
+
fs.writeFileSync(temporary, `${JSON.stringify(index, null, 2)}\n`, { mode: 0o600 });
|
|
65
|
+
fs.renameSync(temporary, file);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function git(args, options = {}) {
|
|
69
|
+
const result = spawnSync('git', args, { encoding: 'utf8', timeout: 120_000, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }, ...options });
|
|
70
|
+
if (result.error) throw new Error(`git could not start: ${result.error.message}`);
|
|
71
|
+
if (result.status !== 0) throw new Error((result.stderr || result.stdout || `git ${args[0]} failed`).trim().split('\n').at(-1));
|
|
72
|
+
return result.stdout.trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The repository at `ref` (default branch when none), copied without its
|
|
76
|
+
// history into <root>/github.com/<owner>/<repo>/<commit>. Returns the entry.
|
|
77
|
+
export function fetchSource(repo, { ref, home = os.homedir(), runGit = git } = {}) {
|
|
78
|
+
const url = `https://github.com/${repo}.git`, root = skillSourcesRoot(home);
|
|
79
|
+
const parent = path.join(root, 'github.com', ...repo.split('/'));
|
|
80
|
+
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
81
|
+
const staging = fs.mkdtempSync(path.join(parent, '.incoming-'));
|
|
82
|
+
try {
|
|
83
|
+
const checkout = path.join(staging, 'checkout');
|
|
84
|
+
if (ref && /^[0-9a-f]{40}$/i.test(ref)) {
|
|
85
|
+
fs.mkdirSync(checkout);
|
|
86
|
+
runGit(['init', '-q', checkout]);
|
|
87
|
+
runGit(['-C', checkout, 'fetch', '-q', '--depth', '1', url, ref]);
|
|
88
|
+
runGit(['-C', checkout, 'checkout', '-q', 'FETCH_HEAD']);
|
|
89
|
+
} else {
|
|
90
|
+
runGit(['clone', '-q', '--depth', '1', ...(ref ? ['--branch', ref] : []), url, checkout]);
|
|
91
|
+
}
|
|
92
|
+
const commit = runGit(['-C', checkout, 'rev-parse', 'HEAD']);
|
|
93
|
+
if (!/^[0-9a-f]{40}$/.test(commit)) throw new Error('could not read the commit');
|
|
94
|
+
fs.rmSync(path.join(checkout, '.git'), { recursive: true, force: true });
|
|
95
|
+
const target = path.join(parent, commit);
|
|
96
|
+
if (!fs.existsSync(target)) fs.renameSync(checkout, target);
|
|
97
|
+
return { repo, ref: ref ?? null, commit, path: path.relative(root, target), added_at: new Date().toISOString() };
|
|
98
|
+
} finally { fs.rmSync(staging, { recursive: true, force: true }); }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The skills a source adds, as the agent will see them.
|
|
102
|
+
export function sourceSkills(entry, home = os.homedir()) {
|
|
103
|
+
const found = skillSourcePlugin(path.join(skillSourcesRoot(home), entry.path), entry.repo);
|
|
104
|
+
if (!found) return [];
|
|
105
|
+
const names = [];
|
|
106
|
+
const walk = (dir, depth) => {
|
|
107
|
+
if (depth > 4 || names.length >= 400) return;
|
|
108
|
+
if (fs.existsSync(path.join(dir, 'SKILL.md'))) { names.push(path.basename(dir)); return; }
|
|
109
|
+
let entries = [];
|
|
110
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
111
|
+
for (const child of entries) if (child.isDirectory() && !child.name.startsWith('.') && child.name !== 'node_modules') walk(path.join(dir, child.name), depth + 1);
|
|
112
|
+
};
|
|
113
|
+
for (const dir of found.skills) walk(dir, 0);
|
|
114
|
+
return [...new Set(names)].sort();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function removeCopy(entry, home) {
|
|
118
|
+
const root = skillSourcesRoot(home), dir = path.resolve(root, entry.path);
|
|
119
|
+
if (dir.startsWith(root + path.sep)) fs.rmSync(dir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function add(repo, { ref, home }) {
|
|
123
|
+
const index = readIndex(home), existing = index.sources.find((item) => item.repo === repo);
|
|
124
|
+
const entry = fetchSource(repo, { ref: ref ?? existing?.ref ?? undefined, home });
|
|
125
|
+
const skills = sourceSkills(entry, home);
|
|
126
|
+
if (!skills.length) {
|
|
127
|
+
removeCopy(entry, home);
|
|
128
|
+
throw new Error(`${repo} has no skill (no SKILL.md under the folders it publishes)`);
|
|
129
|
+
}
|
|
130
|
+
if (existing && existing.path !== entry.path) removeCopy(existing, home);
|
|
131
|
+
index.sources = [...index.sources.filter((item) => item.repo !== repo), { ...entry, name: repo }];
|
|
132
|
+
writeIndex(index, home);
|
|
133
|
+
return { entry, skills, replaced: existing?.commit ?? null };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function main(argv, home = os.homedir()) {
|
|
137
|
+
const [command, ...rest] = argv;
|
|
138
|
+
const flag = (name) => { const at = rest.indexOf(name); return at >= 0 ? rest[at + 1] : undefined; };
|
|
139
|
+
const positional = rest.filter((value, at) => !value.startsWith('--') && !(at > 0 && rest[at - 1] === '--ref'));
|
|
140
|
+
if ([undefined, 'help', '--help', '-h'].includes(command)) { process.stdout.write(usage()); return 0; }
|
|
141
|
+
try {
|
|
142
|
+
if (command === 'list') {
|
|
143
|
+
const sources = readIndex(home).sources.map((entry) => ({ ...entry, skills: sourceSkills(entry, home) }));
|
|
144
|
+
if (rest.includes('--json')) { process.stdout.write(`${JSON.stringify({ sources }, null, 2)}\n`); return 0; }
|
|
145
|
+
if (!sources.length) { process.stdout.write('No skill source yet. Add one: piagent skills add mattpocock/skills\n'); return 0; }
|
|
146
|
+
for (const entry of sources) process.stdout.write(`${entry.repo} @ ${entry.commit.slice(0, 12)}${entry.ref ? ` (${entry.ref})` : ''}: ${entry.skills.length} skill(s)\n ${entry.skills.join(', ')}\n`);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
if (command === 'add') {
|
|
150
|
+
const repo = parseRepository(positional[0]), ref = flag('--ref');
|
|
151
|
+
if (!repo) throw new Error('name a GitHub repository as owner/repo, for example mattpocock/skills');
|
|
152
|
+
if (ref !== undefined && !validRef(ref)) throw new Error('--ref needs a branch, tag or commit');
|
|
153
|
+
const { entry, skills, replaced } = add(repo, { ref, home });
|
|
154
|
+
process.stdout.write(`${replaced && replaced !== entry.commit ? 'Updated' : 'Added'} ${repo} @ ${entry.commit.slice(0, 12)}: ${skills.length} skill(s)\n ${skills.join(', ')}\n`);
|
|
155
|
+
process.stdout.write('Use them as /skill:<name> after /reload in Pi, or in a new company conversation.\n');
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
if (command === 'update') {
|
|
159
|
+
const only = positional[0] ? parseRepository(positional[0]) : null;
|
|
160
|
+
if (positional[0] && !only) throw new Error('name a GitHub repository as owner/repo');
|
|
161
|
+
const targets = readIndex(home).sources.filter((entry) => !only || entry.repo === only);
|
|
162
|
+
if (!targets.length) throw new Error(only ? `${only} is not a skill source` : 'no skill source to update');
|
|
163
|
+
for (const entry of targets) {
|
|
164
|
+
const { entry: next, skills } = add(entry.repo, { ref: entry.ref ?? undefined, home });
|
|
165
|
+
process.stdout.write(`${entry.repo}: ${entry.commit.slice(0, 12)} -> ${next.commit.slice(0, 12)} (${skills.length} skill(s))\n`);
|
|
166
|
+
}
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
if (command === 'remove') {
|
|
170
|
+
const repo = parseRepository(positional[0]);
|
|
171
|
+
if (!repo) throw new Error('name a GitHub repository as owner/repo');
|
|
172
|
+
const index = readIndex(home), entry = index.sources.find((item) => item.repo === repo);
|
|
173
|
+
if (!entry) throw new Error(`${repo} is not a skill source`);
|
|
174
|
+
removeCopy(entry, home);
|
|
175
|
+
index.sources = index.sources.filter((item) => item.repo !== repo);
|
|
176
|
+
writeIndex(index, home);
|
|
177
|
+
process.stdout.write(`Removed ${repo}.\n`);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
process.stderr.write(`FAIL: ${error.message}\n`);
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
process.stderr.write(usage());
|
|
185
|
+
return 2;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) process.exitCode = main(process.argv.slice(2), os.homedir());
|
|
@@ -212,7 +212,24 @@ fi
|
|
|
212
212
|
|
|
213
213
|
if [[ "$WITH_HOST" == true ]]; then
|
|
214
214
|
echo
|
|
215
|
-
|
|
215
|
+
# Piagent's private host (kept beside a member's own, newer pi) is Piagent's to remove.
|
|
216
|
+
PRIVATE_HOSTS="${HOME}/.piagent/pi-host"
|
|
217
|
+
if [[ -d "$PRIVATE_HOSTS" ]]; then
|
|
218
|
+
echo "Removing Piagent's private Pi host:"
|
|
219
|
+
run_cmd rm -rf "$PRIVATE_HOSTS"
|
|
220
|
+
fi
|
|
221
|
+
# A pi newer than the pinned release is the member's own: it stays.
|
|
222
|
+
own_pi_version=""
|
|
223
|
+
if command -v pi >/dev/null 2>&1 && [[ "$(pi --version 2>/dev/null || true)" =~ ([0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?) ]]; then
|
|
224
|
+
own_pi_version="${BASH_REMATCH[1]}"
|
|
225
|
+
fi
|
|
226
|
+
own_pi_plan=""
|
|
227
|
+
if [[ -n "$own_pi_version" ]]; then
|
|
228
|
+
own_pi_plan="$(node --input-type=module -e 'const { hostPlan } = await import(process.argv[1]); process.stdout.write(hostPlan(process.argv[2]));' "$ROOT/packages/piagent-core/managed/pi-host.mjs" "$own_pi_version" 2>/dev/null || true)"
|
|
229
|
+
fi
|
|
230
|
+
if [[ "$own_pi_plan" == "private" ]]; then
|
|
231
|
+
echo "Pi $own_pi_version is your own (newer than the release Piagent pins): left in place."
|
|
232
|
+
elif command -v npm >/dev/null 2>&1; then
|
|
216
233
|
echo "Uninstalling Pi host:"
|
|
217
234
|
HOST_PREFIX="$(npm_prefix_override)"
|
|
218
235
|
if [[ -n "$HOST_PREFIX" ]]; then
|
|
@@ -22,6 +22,7 @@ import path from "node:path";
|
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
import { inspect as inspectPersonalPi } from "./personal-pi-config.mjs";
|
|
25
|
+
import { hostPlan, installPrivateHost, privateHostRoot } from "../packages/piagent-core/managed/pi-host.mjs";
|
|
25
26
|
|
|
26
27
|
const HELPER_PACKAGE = "@piagent/platform";
|
|
27
28
|
const HOST_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
@@ -426,11 +427,15 @@ function main() {
|
|
|
426
427
|
|
|
427
428
|
// npm exec is temporary even when its version already matches the target.
|
|
428
429
|
const helperNeedsChange = options.force || currentHelper !== targetHelper || isNpxCacheRoot(platformRoot);
|
|
429
|
-
|
|
430
|
+
// A newer pi is the member's own: kept, with the target host installed privately.
|
|
431
|
+
const ownHost = hostPlan(currentHost, targetHost) === "private";
|
|
432
|
+
const hostNeedsChange = options.host && (ownHost ? !privateHostRoot(targetHost) : options.force || currentHost !== targetHost);
|
|
430
433
|
|
|
431
434
|
console.log("Pi Agent Platform update");
|
|
432
435
|
console.log(` helper: ${currentHelper} -> ${targetHelper}${helperNeedsChange ? "" : " (already current)"}`);
|
|
433
|
-
console.log(
|
|
436
|
+
console.log(ownHost
|
|
437
|
+
? ` host: your pi ${currentHost} stays; Piagent's private Pi -> ${targetHost}${hostNeedsChange ? "" : " (already current)"}`
|
|
438
|
+
: ` host: ${currentHost ?? "not installed"} -> ${targetHost}${hostNeedsChange ? "" : " (already current)"}`);
|
|
434
439
|
// The Pi package can lag the helper (installed from an older tag), and
|
|
435
440
|
// personal Pi can hold conflicting Piagent or old company entries.
|
|
436
441
|
const personal = inspectPersonalPi(process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"));
|
|
@@ -473,7 +478,7 @@ function main() {
|
|
|
473
478
|
// here rather than trusted from before the move.
|
|
474
479
|
let effectiveHost = currentHost;
|
|
475
480
|
let effectiveHostNeedsChange = hostNeedsChange;
|
|
476
|
-
if (npmTarget.bin) {
|
|
481
|
+
if (npmTarget.bin && !ownHost) {
|
|
477
482
|
const activeHost = installedHostVersion();
|
|
478
483
|
if (activeHost !== currentHost) {
|
|
479
484
|
effectiveHost = activeHost;
|
|
@@ -484,14 +489,17 @@ function main() {
|
|
|
484
489
|
}
|
|
485
490
|
|
|
486
491
|
// Refuse an impossible package installation before replacing either global.
|
|
487
|
-
if (options.package && !options.host && effectiveHost !== targetHost) {
|
|
492
|
+
if (options.package && !options.host && effectiveHost !== targetHost && !(ownHost && privateHostRoot(targetHost))) {
|
|
488
493
|
fail(`--no-host cannot install this Pi package: active Pi is ${effectiveHost ?? "missing"}, but v${targetHelper} requires ${targetHost}. Rerun without --no-host.`);
|
|
489
494
|
}
|
|
490
495
|
|
|
491
496
|
// Host first: piagent-install refuses to run against a host that does not match
|
|
492
497
|
// the version its package.json pins, so a helper installed ahead of the host
|
|
493
498
|
// would fail on its very next step.
|
|
494
|
-
if (effectiveHostNeedsChange) {
|
|
499
|
+
if (effectiveHostNeedsChange && ownHost) {
|
|
500
|
+
try { installPrivateHost({ version: targetHost, dryRun: options.dryRun, log: (line) => console.log(line.startsWith("DRY RUN") ? line : `run: ${line}`) }); }
|
|
501
|
+
catch { fail(`could not install the private ${HOST_PACKAGE}@${targetHost}`); }
|
|
502
|
+
} else if (effectiveHostNeedsChange) {
|
|
495
503
|
const args = npmInstallArgs(`${HOST_PACKAGE}@${targetHost}`, npmTarget);
|
|
496
504
|
describeStep(options.dryRun, "npm", args);
|
|
497
505
|
if (!options.dryRun && !run("npm", args).ok) fail(`could not install ${HOST_PACKAGE}@${targetHost}`);
|