@hyperdreamer/pi-webui 1.10.7 → 1.11.0-beta.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/README.md +17 -2
- package/dist/cli.js +265 -32
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-BIb2Wx8W.js → CodeViewer-CAf_j27u.js} +1 -1
- package/dist/client/assets/{UnifiedDiffViewer-DpWiGlQo.js → UnifiedDiffViewer-wBjEJWa1.js} +1 -1
- package/dist/client/assets/{index-DX583DEv.js → index-5EtDb9nj.js} +1197 -476
- package/dist/client/index.html +1 -1
- package/dist/config.js +52 -11
- package/dist/config.js.map +1 -1
- package/dist/pi-webui-plugins/workspace-memory/pi-webui-plugin.js +3 -2
- package/dist/pi-webui-plugins/workspace-tasks/pi-webui-plugin.js +1 -0
- package/dist/plugin-api.d.ts +30 -0
- package/dist/server/app.js +6 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +40 -5
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/realtime/sessionStatusCoalescer.js +137 -0
- package/dist/server/realtime/sessionStatusCoalescer.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +2 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond.js +57 -5
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/modelPolicyCapability.js +88 -0
- package/dist/server/sessions/modelPolicyCapability.js.map +1 -0
- package/dist/server/sessions/modelPolicyTool.js +31 -0
- package/dist/server/sessions/modelPolicyTool.js.map +1 -0
- package/dist/server/sessions/modelTierRegistry.js +14 -0
- package/dist/server/sessions/modelTierRegistry.js.map +1 -1
- package/dist/server/sessions/modelTierSettingsRoutes.js +36 -0
- package/dist/server/sessions/modelTierSettingsRoutes.js.map +1 -0
- package/dist/server/sessions/modelTierSettingsService.js +100 -0
- package/dist/server/sessions/modelTierSettingsService.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +1643 -433
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionDefaultsRoutes.js +39 -0
- package/dist/server/sessions/sessionDefaultsRoutes.js.map +1 -1
- package/dist/server/sessions/sessionDefaultsService.js +36 -6
- package/dist/server/sessions/sessionDefaultsService.js.map +1 -1
- package/dist/server/sessions/sessionMetadataStore.js +107 -13
- package/dist/server/sessions/sessionMetadataStore.js.map +1 -1
- package/dist/server/sessions/sessionModelPolicy.js +118 -0
- package/dist/server/sessions/sessionModelPolicy.js.map +1 -0
- package/dist/server/sessions/sessionReorder.js +117 -0
- package/dist/server/sessions/sessionReorder.js.map +1 -0
- package/dist/server/sessions/sessionRouteFastifyOptions.js +8 -0
- package/dist/server/sessions/sessionRouteFastifyOptions.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +208 -3
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/starterModelPolicyPreferenceStore.js +164 -0
- package/dist/server/sessions/starterModelPolicyPreferenceStore.js.map +1 -0
- package/dist/server/sessions/utilityModelExtension.js +199 -0
- package/dist/server/sessions/utilityModelExtension.js.map +1 -0
- package/dist/server/sessions/utilityModelResolver.js +77 -0
- package/dist/server/sessions/utilityModelResolver.js.map +1 -0
- package/dist/server/sessions/utilityModelSettingsRoutes.js +62 -0
- package/dist/server/sessions/utilityModelSettingsRoutes.js.map +1 -0
- package/dist/server/sessions/utilityModelSettingsService.js +101 -0
- package/dist/server/sessions/utilityModelSettingsService.js.map +1 -0
- package/dist/server/skills/optionalSkillInstall.js +69 -0
- package/dist/server/skills/optionalSkillInstall.js.map +1 -0
- package/dist/server/skills/optionalSkillInstaller.js +148 -0
- package/dist/server/skills/optionalSkillInstaller.js.map +1 -0
- package/dist/shared/apiTypes.d.ts +129 -3
- package/dist/shared/apiTypes.js +10 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +15 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +7 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/docs/assets/pi-webui-desktop.png +0 -0
- package/docs/config.md +61 -3
- package/docs/plugins.md +93 -16
- package/optional-skills/deterministic-subagent-driven-development/SKILL.md +224 -0
- package/optional-skills/deterministic-subagent-driven-development/pi-webui-skill.json +28 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/final-reviewer.md +132 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/implementer.md +101 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/re-reviewer.md +60 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/task-reviewer.md +80 -0
- package/optional-skills/deterministic-subagent-driven-development/references/capability-contract.md +174 -0
- package/optional-skills/deterministic-subagent-driven-development/references/plan-contract.md +268 -0
- package/optional-skills/deterministic-subagent-driven-development/references/state-machine.md +177 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/manifest.mjs +258 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/plan-policy.mjs +350 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/prompt-renderer.mjs +290 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-machine.mjs +1264 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-store.mjs +532 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state +3 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state.mjs +349 -0
- package/optional-skills/deterministic-writing-plans/SKILL.md +232 -0
- package/optional-skills/deterministic-writing-plans/references/grammar.md +84 -0
- package/optional-skills/deterministic-writing-plans/templates/plan-skeleton.md +143 -0
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Run agents where your code, tools, credentials, and build caches live. Supervise
|
|
|
11
11
|
|
|
12
12
|

|
|
13
13
|
|
|
14
|
-

|
|
15
15
|
|
|
16
16
|
## Why PI WEBUI?
|
|
17
17
|
|
|
@@ -36,7 +36,7 @@ Requirements:
|
|
|
36
36
|
|
|
37
37
|
- Node.js 22.19.0 or newer
|
|
38
38
|
- npm
|
|
39
|
-
- Pi Coding Agent `>=0.
|
|
39
|
+
- Pi Coding Agent `>=0.83.0 <0.84`, configured for your user
|
|
40
40
|
- git and the development tools your agents need
|
|
41
41
|
|
|
42
42
|
Install PI WEBUI from npm or clone from GitHub:
|
|
@@ -117,6 +117,21 @@ Project-local PI WEBUI config lives at:
|
|
|
117
117
|
|
|
118
118
|
Common configuration includes host/port, path access, uploads, PI WEBUI plugin enablement, shortcuts, and session daemon options. In Settings, machine-affecting config targets the selected machine; gateway host/port/allowed-hosts, remote machine registration, tokens, and keyboard shortcuts stay local.
|
|
119
119
|
|
|
120
|
+
## Extra skills
|
|
121
|
+
|
|
122
|
+
PI WEBUI ships two opt-in agent skills for deterministic plan authoring and
|
|
123
|
+
subagent-driven execution. Nothing installs or activates them by default:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pi-webui install-extra --dry-run # show what would change
|
|
127
|
+
pi-webui install-extra # install, after confirming
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
They install as `writing-plans` and `subagent-driven-development`, **replacing**
|
|
131
|
+
any existing skills with those names. The command explains the risk, asks for
|
|
132
|
+
confirmation, and backs up both skills and the skill lock file first. See
|
|
133
|
+
[docs/optional-skills.md](https://github.com/hyperdreamer/pi-webui/blob/main/docs/optional-skills.md).
|
|
134
|
+
|
|
120
135
|
## Development
|
|
121
136
|
|
|
122
137
|
`start.sh` (a thin wrapper around `npm run dev`) starts the complete development stack. To run its processes separately:
|
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import { homedir, userInfo } from "node:os";
|
|
6
6
|
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { DEFAULT_PORT, defaultPiWebUiConfigPath, defaultPiWebUiDataDir, effectivePiWebUiConfig, examplePiWebUiConfig } from "./config.js";
|
|
9
|
-
import { packageVersion, printPiWebUiVersionReport } from "./piWebUiVersionReport.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { DEFAULT_PORT, defaultPiWebUiConfigPath, defaultPiWebUiDataDir, effectivePiWebUiConfig, examplePiWebUiConfig, } from "./config.js";
|
|
10
|
+
import { packageVersion, printPiWebUiVersionReport, } from "./piWebUiVersionReport.js";
|
|
11
|
+
import { installOptionalSkills, } from "./server/skills/optionalSkillInstaller.js";
|
|
12
|
+
import { checkNodePtyDarwinSpawnHelper, formatNodePtyDarwinSpawnHelperCheck, } from "./server/diagnostics/nodePtySpawnHelper.js";
|
|
13
|
+
import { checkNodePtyNativeModule, formatNodePtyNativeModuleCheck, } from "./server/diagnostics/nodePtyNativeModule.js";
|
|
12
14
|
import { installNativeServiceCandidate, nativeServiceInstallFailureNeedsPathAdvice, } from "./nativeServices/serviceInstall.js";
|
|
13
15
|
import { minimumSupportedNodeVersion, nativeServiceManagerRefs, productionNativeServiceIds, } from "./nativeServices/servicePlan.js";
|
|
14
16
|
import { formatNativeServiceDoctorResult, inferInstalledNativeServiceMode, inspectInstalledDevelopmentServiceInput, inspectInstalledProductionServiceContext, runNativeServiceDoctor, } from "./nativeServices/serviceDoctor.js";
|
|
15
17
|
import { createNativeServiceAuthoritativeProbe, nativeServicePrerequisiteShellCheck, } from "./nativeServices/serviceProbe.js";
|
|
16
|
-
import { renderLaunchdPlist, renderSystemdUnit } from "./nativeServices/serviceRendering.js";
|
|
18
|
+
import { renderLaunchdPlist, renderSystemdUnit, } from "./nativeServices/serviceRendering.js";
|
|
17
19
|
const PI_WEBUI_PACKAGE_NAME = "@hyperdreamer/pi-webui";
|
|
18
20
|
const systemdServiceDir = join(homedir(), ".config", "systemd", "user");
|
|
19
21
|
const launchdServiceDir = join(homedir(), "Library", "LaunchAgents");
|
|
@@ -87,18 +89,31 @@ function capture(command, args) {
|
|
|
87
89
|
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
88
90
|
const errorMessage = result.error instanceof Error ? result.error.message : "";
|
|
89
91
|
const stderr = outputText(result.stderr);
|
|
90
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
status: result.status ?? 1,
|
|
94
|
+
stdout: outputText(result.stdout),
|
|
95
|
+
stderr: stderr === "" ? errorMessage : stderr,
|
|
96
|
+
};
|
|
91
97
|
}
|
|
92
98
|
function runQuiet(command, args) {
|
|
93
99
|
return capture(command, args).status;
|
|
94
100
|
}
|
|
95
101
|
function hasCommand(command) {
|
|
96
|
-
return capture("/usr/bin/env", [
|
|
102
|
+
return (capture("/usr/bin/env", [
|
|
103
|
+
"sh",
|
|
104
|
+
"-c",
|
|
105
|
+
`command -v ${shellSingleQuote(command)}`,
|
|
106
|
+
]).status === 0);
|
|
97
107
|
}
|
|
98
108
|
function isLingerEnabled() {
|
|
99
109
|
if (!hasCommand("loginctl"))
|
|
100
110
|
return undefined;
|
|
101
|
-
const result = capture("loginctl", [
|
|
111
|
+
const result = capture("loginctl", [
|
|
112
|
+
"show-user",
|
|
113
|
+
userInfo().username,
|
|
114
|
+
"-p",
|
|
115
|
+
"Linger",
|
|
116
|
+
]);
|
|
102
117
|
if (result.status !== 0)
|
|
103
118
|
return undefined;
|
|
104
119
|
const value = result.stdout.trim();
|
|
@@ -109,7 +124,11 @@ function isLingerEnabled() {
|
|
|
109
124
|
return undefined;
|
|
110
125
|
}
|
|
111
126
|
function parseInstallOptions(args) {
|
|
112
|
-
const options = {
|
|
127
|
+
const options = {
|
|
128
|
+
host: "127.0.0.1",
|
|
129
|
+
port: String(DEFAULT_PORT),
|
|
130
|
+
mode: "production",
|
|
131
|
+
};
|
|
113
132
|
for (let i = 0; i < args.length; i += 1) {
|
|
114
133
|
const arg = args[i];
|
|
115
134
|
if (arg === undefined)
|
|
@@ -196,7 +215,9 @@ function serviceShellCommand(command, cwd) {
|
|
|
196
215
|
return ["/usr/bin/env", detectServiceShell().executable, "-lc", fullCommand];
|
|
197
216
|
}
|
|
198
217
|
function serviceShellQuote(value) {
|
|
199
|
-
return detectServiceShell().name === "fish"
|
|
218
|
+
return detectServiceShell().name === "fish"
|
|
219
|
+
? fishSingleQuote(value)
|
|
220
|
+
: shellSingleQuote(value);
|
|
200
221
|
}
|
|
201
222
|
function describeServiceShell() {
|
|
202
223
|
const shell = detectServiceShell();
|
|
@@ -205,7 +226,9 @@ function describeServiceShell() {
|
|
|
205
226
|
? "could not detect a supported login shell; using bash"
|
|
206
227
|
: `detected ${shell.detectedExecutable}; using bash because PI WEBUI currently supports bash, zsh, and fish`;
|
|
207
228
|
}
|
|
208
|
-
return shell.detectedExecutable === null
|
|
229
|
+
return shell.detectedExecutable === null
|
|
230
|
+
? shell.name
|
|
231
|
+
: `${shell.name} (${shell.detectedExecutable})`;
|
|
209
232
|
}
|
|
210
233
|
function configEnvironment(options, configPath) {
|
|
211
234
|
return options.config === undefined ? {} : { PI_WEBUI_CONFIG: configPath };
|
|
@@ -252,7 +275,9 @@ function launchdLogPath(ref) {
|
|
|
252
275
|
return join(logDir, ref.logName);
|
|
253
276
|
}
|
|
254
277
|
function installConfigPath(options) {
|
|
255
|
-
return options.config === undefined
|
|
278
|
+
return options.config === undefined
|
|
279
|
+
? defaultPiWebUiConfigPath()
|
|
280
|
+
: resolve(options.config);
|
|
256
281
|
}
|
|
257
282
|
async function writeInitialConfig(options, configPath) {
|
|
258
283
|
await mkdir(dirname(configPath), { recursive: true });
|
|
@@ -267,13 +292,17 @@ function launchdPlistPath(ref) {
|
|
|
267
292
|
return join(launchdServiceDir, ref.launchdPlistName);
|
|
268
293
|
}
|
|
269
294
|
function serviceFilePath(backend, ref) {
|
|
270
|
-
return backend.kind === "systemd"
|
|
295
|
+
return backend.kind === "systemd"
|
|
296
|
+
? systemdServicePath(ref)
|
|
297
|
+
: launchdPlistPath(ref);
|
|
271
298
|
}
|
|
272
299
|
function serviceFileExists(backend, ref) {
|
|
273
300
|
return existsSync(serviceFilePath(backend, ref));
|
|
274
301
|
}
|
|
275
302
|
function installedServiceIds(backend) {
|
|
276
|
-
return new Set(allServiceRefs()
|
|
303
|
+
return new Set(allServiceRefs()
|
|
304
|
+
.filter((ref) => serviceFileExists(backend, ref))
|
|
305
|
+
.map((ref) => ref.id));
|
|
277
306
|
}
|
|
278
307
|
function installedServiceRefs(backend) {
|
|
279
308
|
const installed = startOrder(allServiceRefs().filter((ref) => serviceFileExists(backend, ref)));
|
|
@@ -302,13 +331,15 @@ function launchdServiceTarget(ref) {
|
|
|
302
331
|
return `${launchdDomain()}/${ref.launchdLabel}`;
|
|
303
332
|
}
|
|
304
333
|
function launchdIsLoaded(ref) {
|
|
305
|
-
return capture("launchctl", ["print", launchdServiceTarget(ref)]).status === 0;
|
|
334
|
+
return (capture("launchctl", ["print", launchdServiceTarget(ref)]).status === 0);
|
|
306
335
|
}
|
|
307
336
|
function launchdBootout(ref) {
|
|
308
337
|
runQuiet("launchctl", ["bootout", launchdServiceTarget(ref)]);
|
|
309
338
|
}
|
|
310
339
|
function launchdBootstrap(ref) {
|
|
311
|
-
run("launchctl", ["bootstrap", launchdDomain(), launchdPlistPath(ref)], {
|
|
340
|
+
run("launchctl", ["bootstrap", launchdDomain(), launchdPlistPath(ref)], {
|
|
341
|
+
check: true,
|
|
342
|
+
});
|
|
312
343
|
run("launchctl", ["enable", launchdServiceTarget(ref)], { check: true });
|
|
313
344
|
}
|
|
314
345
|
function launchdStart(ref) {
|
|
@@ -406,14 +437,24 @@ function makeServiceRuntimeStatus(ref, health, detail, target, filePath, pid) {
|
|
|
406
437
|
}
|
|
407
438
|
function firstOutputLine(...values) {
|
|
408
439
|
for (const value of values) {
|
|
409
|
-
const line = value
|
|
440
|
+
const line = value
|
|
441
|
+
.trim()
|
|
442
|
+
.split("\n")
|
|
443
|
+
.find((candidate) => candidate.trim() !== "");
|
|
410
444
|
if (line !== undefined)
|
|
411
445
|
return line.trim();
|
|
412
446
|
}
|
|
413
447
|
return undefined;
|
|
414
448
|
}
|
|
415
449
|
function systemdMainPid(ref) {
|
|
416
|
-
const result = capture("systemctl", [
|
|
450
|
+
const result = capture("systemctl", [
|
|
451
|
+
"--user",
|
|
452
|
+
"--no-pager",
|
|
453
|
+
"show",
|
|
454
|
+
ref.systemdName,
|
|
455
|
+
"--property=MainPID",
|
|
456
|
+
"--value",
|
|
457
|
+
]);
|
|
417
458
|
if (result.status !== 0)
|
|
418
459
|
return undefined;
|
|
419
460
|
const value = result.stdout.trim();
|
|
@@ -424,7 +465,12 @@ function systemdRuntimeStatus(backend, ref) {
|
|
|
424
465
|
const filePath = serviceFilePath(backend, ref);
|
|
425
466
|
if (!serviceFileExists(backend, ref))
|
|
426
467
|
return makeServiceRuntimeStatus(ref, "not-installed", "not installed", target, filePath);
|
|
427
|
-
const result = capture("systemctl", [
|
|
468
|
+
const result = capture("systemctl", [
|
|
469
|
+
"--user",
|
|
470
|
+
"--no-pager",
|
|
471
|
+
"is-active",
|
|
472
|
+
target,
|
|
473
|
+
]);
|
|
428
474
|
const state = firstOutputLine(result.stdout, result.stderr) ?? "unknown";
|
|
429
475
|
if (result.status === 0 && state === "active")
|
|
430
476
|
return makeServiceRuntimeStatus(ref, "running", "running", target, filePath, systemdMainPid(ref));
|
|
@@ -440,7 +486,9 @@ export function launchdRuntimeDetails(output) {
|
|
|
440
486
|
const lastExitCode = parseLaunchdField(output, "last exit code");
|
|
441
487
|
const detail = state === "running"
|
|
442
488
|
? "running"
|
|
443
|
-
: lastExitCode === undefined
|
|
489
|
+
: lastExitCode === undefined
|
|
490
|
+
? state
|
|
491
|
+
: `${state} (last exit code ${lastExitCode})`;
|
|
444
492
|
return { state, detail, pid };
|
|
445
493
|
}
|
|
446
494
|
function launchdRuntimeStatus(backend, ref) {
|
|
@@ -453,11 +501,17 @@ function launchdRuntimeStatus(backend, ref) {
|
|
|
453
501
|
return makeServiceRuntimeStatus(ref, "stopped", firstOutputLine(result.stderr, result.stdout) ?? "not loaded", target, filePath);
|
|
454
502
|
}
|
|
455
503
|
const details = launchdRuntimeDetails(result.stdout);
|
|
456
|
-
const health = details.state === "running"
|
|
504
|
+
const health = details.state === "running"
|
|
505
|
+
? "running"
|
|
506
|
+
: details.state === "unknown"
|
|
507
|
+
? "unknown"
|
|
508
|
+
: "stopped";
|
|
457
509
|
return makeServiceRuntimeStatus(ref, health, details.detail, target, filePath, details.pid);
|
|
458
510
|
}
|
|
459
511
|
function runtimeStatus(backend, ref) {
|
|
460
|
-
return backend.kind === "systemd"
|
|
512
|
+
return backend.kind === "systemd"
|
|
513
|
+
? systemdRuntimeStatus(backend, ref)
|
|
514
|
+
: launchdRuntimeStatus(backend, ref);
|
|
461
515
|
}
|
|
462
516
|
function printServiceStatus(status) {
|
|
463
517
|
const icon = status.health === "running" ? "✓" : "✗";
|
|
@@ -611,7 +665,11 @@ async function uninstall() {
|
|
|
611
665
|
console.log(`PI WEBUI ${backend.label} removed. Production and development service files were removed; config and data were left in place.`);
|
|
612
666
|
}
|
|
613
667
|
function systemdServiceAction(action, refs) {
|
|
614
|
-
const orderedRefs = action === "stop"
|
|
668
|
+
const orderedRefs = action === "stop"
|
|
669
|
+
? stopOrder(refs)
|
|
670
|
+
: action === "restart"
|
|
671
|
+
? restartOrder(refs)
|
|
672
|
+
: startOrder(refs);
|
|
615
673
|
run("systemctl", ["--user", action, ...orderedRefs.map((ref) => ref.systemdName)], { check: true });
|
|
616
674
|
}
|
|
617
675
|
function launchdServiceAction(action, refs) {
|
|
@@ -649,7 +707,11 @@ function logs() {
|
|
|
649
707
|
const backend = requireServiceBackend("pi-webui logs");
|
|
650
708
|
const refs = installedServiceRefs(backend);
|
|
651
709
|
if (backend.kind === "systemd") {
|
|
652
|
-
run("journalctl", [
|
|
710
|
+
run("journalctl", [
|
|
711
|
+
"--user",
|
|
712
|
+
...refs.flatMap((ref) => ["-u", ref.systemdName]),
|
|
713
|
+
"-f",
|
|
714
|
+
]);
|
|
653
715
|
return;
|
|
654
716
|
}
|
|
655
717
|
run("tail", ["-F", ...refs.map((ref) => launchdLogPath(ref))]);
|
|
@@ -684,9 +746,18 @@ function generalDoctorChecks() {
|
|
|
684
746
|
const shell = serviceShellLabel();
|
|
685
747
|
const agentCommand = agentCommandForChecks();
|
|
686
748
|
return [
|
|
687
|
-
[
|
|
688
|
-
|
|
689
|
-
|
|
749
|
+
[
|
|
750
|
+
`Caller login ${shell} can find node >= ${minimumSupportedNodeVersion}`,
|
|
751
|
+
serviceShellCommand(nodeVersionCheck()),
|
|
752
|
+
],
|
|
753
|
+
[
|
|
754
|
+
`Caller login ${shell} can find npm`,
|
|
755
|
+
serviceShellCommand(commandWithVersionCheck("npm")),
|
|
756
|
+
],
|
|
757
|
+
[
|
|
758
|
+
`Caller login ${shell} can find ${agentCommand}`,
|
|
759
|
+
serviceShellCommand(commandWithVersionCheck(agentCommand)),
|
|
760
|
+
],
|
|
690
761
|
];
|
|
691
762
|
}
|
|
692
763
|
function runChecks(checks) {
|
|
@@ -715,7 +786,12 @@ function printCheckOutput(output) {
|
|
|
715
786
|
}
|
|
716
787
|
function optionalDoctorChecks() {
|
|
717
788
|
const shell = serviceShellLabel();
|
|
718
|
-
return [
|
|
789
|
+
return [
|
|
790
|
+
[
|
|
791
|
+
`Caller login ${shell} can find optional ripgrep (rg)`,
|
|
792
|
+
serviceShellCommand(commandCheck("rg")),
|
|
793
|
+
],
|
|
794
|
+
];
|
|
719
795
|
}
|
|
720
796
|
function printOptionalDoctorChecks() {
|
|
721
797
|
let missingOptionalTool = false;
|
|
@@ -859,7 +935,9 @@ async function doctor() {
|
|
|
859
935
|
printOptionalDoctorChecks();
|
|
860
936
|
console.log("\nNative terminal runtime readiness:");
|
|
861
937
|
const nodePtyNativeModuleOk = printNodePtyNativeModuleCheck();
|
|
862
|
-
const nodePtySpawnHelperOk = nodePtyNativeModuleOk
|
|
938
|
+
const nodePtySpawnHelperOk = nodePtyNativeModuleOk
|
|
939
|
+
? printNodePtyDarwinSpawnHelperCheck()
|
|
940
|
+
: true;
|
|
863
941
|
let nativeServiceReport = null;
|
|
864
942
|
if (backend !== undefined) {
|
|
865
943
|
console.log("\nNative service plan checks (service-manager context):");
|
|
@@ -889,7 +967,8 @@ async function doctor() {
|
|
|
889
967
|
const pathFailure = !generalReadinessOk || nativeServiceReport?.pathAdviceRecommended === true;
|
|
890
968
|
if (pathFailure) {
|
|
891
969
|
console.log("\nIf a command works in your terminal but fails in the service-manager check, compare the caller and manager contexts above.");
|
|
892
|
-
const adviceShell = nativeServiceReport?.pathAdviceRecommended === true &&
|
|
970
|
+
const adviceShell = nativeServiceReport?.pathAdviceRecommended === true &&
|
|
971
|
+
nativeServiceReport.adviceShell !== null
|
|
893
972
|
? nativeServiceReport.adviceShell
|
|
894
973
|
: detectServiceShell();
|
|
895
974
|
printPathSetupAdvice(adviceShell);
|
|
@@ -915,12 +994,161 @@ function printNodePtyDarwinSpawnHelperCheck() {
|
|
|
915
994
|
function isRecord(value) {
|
|
916
995
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
917
996
|
}
|
|
997
|
+
function optionalSkillsSourceRoot() {
|
|
998
|
+
return join(packageRootPath(), "optional-skills");
|
|
999
|
+
}
|
|
1000
|
+
function globalSkillsRootPath() {
|
|
1001
|
+
return join(homedir(), ".pi", "agent", "skills");
|
|
1002
|
+
}
|
|
1003
|
+
function skillLockFilePath() {
|
|
1004
|
+
const xdgStateHome = process.env["XDG_STATE_HOME"];
|
|
1005
|
+
return xdgStateHome !== undefined && xdgStateHome !== ""
|
|
1006
|
+
? join(xdgStateHome, "skills", ".skill-lock.json")
|
|
1007
|
+
: join(homedir(), ".agents", ".skill-lock.json");
|
|
1008
|
+
}
|
|
1009
|
+
function createInstallerIo() {
|
|
1010
|
+
return {
|
|
1011
|
+
exists: (path) => existsSync(path),
|
|
1012
|
+
listFiles: (dir) => listFilesRecursively(dir),
|
|
1013
|
+
readFile: (path) => readFileSync(path, "utf8"),
|
|
1014
|
+
writeFile: (path, content) => {
|
|
1015
|
+
writeFileSync(path, content);
|
|
1016
|
+
},
|
|
1017
|
+
copyFile: (from, to) => {
|
|
1018
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
1019
|
+
copyFileSync(from, to);
|
|
1020
|
+
},
|
|
1021
|
+
makeDir: (path) => {
|
|
1022
|
+
mkdirSync(path, { recursive: true });
|
|
1023
|
+
},
|
|
1024
|
+
movePath: (from, to) => {
|
|
1025
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
1026
|
+
renameSync(from, to);
|
|
1027
|
+
},
|
|
1028
|
+
removePath: (path) => {
|
|
1029
|
+
rmSync(path, { recursive: true, force: true });
|
|
1030
|
+
},
|
|
1031
|
+
regenerateManifest: (skillDir) => {
|
|
1032
|
+
const manifest = join(skillDir, "pi-webui-skill.json");
|
|
1033
|
+
if (!existsSync(manifest))
|
|
1034
|
+
return;
|
|
1035
|
+
const result = spawnSync(process.execPath, [
|
|
1036
|
+
join(skillDir, "scripts", "sdd-state.mjs"),
|
|
1037
|
+
"manifest-create",
|
|
1038
|
+
"--source-root",
|
|
1039
|
+
skillDir,
|
|
1040
|
+
"--package-json",
|
|
1041
|
+
join(packageRootPath(), "package.json"),
|
|
1042
|
+
"--output",
|
|
1043
|
+
manifest,
|
|
1044
|
+
], { encoding: "utf8", stdio: "pipe" });
|
|
1045
|
+
if (result.status !== 0) {
|
|
1046
|
+
throw new Error(`failed to regenerate the runtime manifest for ${skillDir}: ${result.stderr.trim()}`);
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function listFilesRecursively(dir, prefix = "") {
|
|
1052
|
+
const found = [];
|
|
1053
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1054
|
+
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
1055
|
+
if (entry.isDirectory())
|
|
1056
|
+
found.push(...listFilesRecursively(join(dir, entry.name), relative));
|
|
1057
|
+
else
|
|
1058
|
+
found.push(relative);
|
|
1059
|
+
}
|
|
1060
|
+
return found;
|
|
1061
|
+
}
|
|
1062
|
+
async function confirmOptionalSkillInstall() {
|
|
1063
|
+
if (!process.stdin.isTTY) {
|
|
1064
|
+
throw new Error("pi-webui install-extra needs an interactive terminal to confirm. Re-run it directly, or pass --yes to skip the prompt.");
|
|
1065
|
+
}
|
|
1066
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1067
|
+
try {
|
|
1068
|
+
const answer = await rl.question("Proceed? [y/N] ");
|
|
1069
|
+
return /^y(es)?$/iu.test(answer.trim());
|
|
1070
|
+
}
|
|
1071
|
+
finally {
|
|
1072
|
+
rl.close();
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
function printOptionalSkillRisk(skillsRoot, lockPath) {
|
|
1076
|
+
console.log(`PI WEBUI extra skills install
|
|
1077
|
+
|
|
1078
|
+
This installs two opt-in agent skills into:
|
|
1079
|
+
${skillsRoot}
|
|
1080
|
+
|
|
1081
|
+
They install under the names 'writing-plans' and
|
|
1082
|
+
'subagent-driven-development', because other skills such as
|
|
1083
|
+
'brainstorming' route to those names. Any existing skill with either name
|
|
1084
|
+
WILL BE REPLACED.
|
|
1085
|
+
|
|
1086
|
+
What this changes:
|
|
1087
|
+
- replaces ${join(skillsRoot, "writing-plans")}
|
|
1088
|
+
- replaces ${join(skillsRoot, "subagent-driven-development")}
|
|
1089
|
+
- removes those two entries from ${lockPath}
|
|
1090
|
+
|
|
1091
|
+
That directory is not version controlled, so a timestamped backup of both
|
|
1092
|
+
skills and the lock file is taken first, and its path is printed at the end.
|
|
1093
|
+
Helper scripts the controller inherits are carried over from the replaced
|
|
1094
|
+
skill.
|
|
1095
|
+
`);
|
|
1096
|
+
}
|
|
1097
|
+
async function installExtra(args) {
|
|
1098
|
+
const dryRun = args.includes("--dry-run");
|
|
1099
|
+
const assumeYes = args.includes("--yes") || args.includes("-y");
|
|
1100
|
+
for (const arg of args) {
|
|
1101
|
+
if (arg !== "--dry-run" && arg !== "--yes" && arg !== "-y") {
|
|
1102
|
+
throw new Error(`Unknown install-extra option: ${arg}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const sourceRoot = optionalSkillsSourceRoot();
|
|
1106
|
+
if (!existsSync(sourceRoot)) {
|
|
1107
|
+
throw new Error(`This build does not ship optional skills (${sourceRoot} is missing). Install from a repository checkout instead.`);
|
|
1108
|
+
}
|
|
1109
|
+
const skillsRoot = globalSkillsRootPath();
|
|
1110
|
+
const lockPath = skillLockFilePath();
|
|
1111
|
+
printOptionalSkillRisk(skillsRoot, lockPath);
|
|
1112
|
+
if (dryRun)
|
|
1113
|
+
console.log("Dry run: nothing will be written.\n");
|
|
1114
|
+
else if (!assumeYes && !(await confirmOptionalSkillInstall())) {
|
|
1115
|
+
console.log("Aborted. Nothing was changed.");
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const backupRoot = join(homedir(), ".pi", "agent", `skills-backup-${timestampSlug()}`);
|
|
1119
|
+
const report = installOptionalSkills(createInstallerIo(), {
|
|
1120
|
+
sourceRoot,
|
|
1121
|
+
skillsRoot,
|
|
1122
|
+
backupRoot,
|
|
1123
|
+
lockPath,
|
|
1124
|
+
dryRun,
|
|
1125
|
+
});
|
|
1126
|
+
console.log(dryRun ? "\nWould install:" : "\nInstalled:");
|
|
1127
|
+
for (const name of report.installed)
|
|
1128
|
+
console.log(` ${name}`);
|
|
1129
|
+
if (report.lockEntriesRemoved.length > 0) {
|
|
1130
|
+
console.log(dryRun ? "Would remove lock entries:" : "Removed lock entries:");
|
|
1131
|
+
for (const name of report.lockEntriesRemoved)
|
|
1132
|
+
console.log(` ${name}`);
|
|
1133
|
+
}
|
|
1134
|
+
if (dryRun)
|
|
1135
|
+
return;
|
|
1136
|
+
console.log(`\nBackup: ${report.backupRoot}`);
|
|
1137
|
+
console.log(`Restore with:\n rm -rf ${report.installed
|
|
1138
|
+
.map((n) => join(skillsRoot, n))
|
|
1139
|
+
.join(" ")}\n cp -a ${report.backupRoot}/* ${skillsRoot}/`);
|
|
1140
|
+
console.log("\nType /reload in each idle PI WEBUI session to pick up the new skills.");
|
|
1141
|
+
}
|
|
1142
|
+
function timestampSlug() {
|
|
1143
|
+
return new Date().toISOString().replaceAll(/[:.]/gu, "-").slice(0, 19);
|
|
1144
|
+
}
|
|
918
1145
|
function help() {
|
|
919
1146
|
console.log(`PI WEBUI
|
|
920
1147
|
|
|
921
1148
|
Usage:
|
|
922
1149
|
pi-webui install [--dev] [--host 127.0.0.1] [--port ${String(DEFAULT_PORT)}] [--config ~/.config/pi-webui/config.json]
|
|
923
1150
|
pi-webui uninstall
|
|
1151
|
+
pi-webui install-extra [--dry-run] [--yes]
|
|
924
1152
|
pi-webui start|stop|restart|status|logs
|
|
925
1153
|
pi-webui doctor
|
|
926
1154
|
pi-webui version
|
|
@@ -939,8 +1167,13 @@ async function main() {
|
|
|
939
1167
|
await install(args);
|
|
940
1168
|
else if (command === "uninstall")
|
|
941
1169
|
await uninstall();
|
|
942
|
-
else if (command === "start" ||
|
|
1170
|
+
else if (command === "start" ||
|
|
1171
|
+
command === "stop" ||
|
|
1172
|
+
command === "restart" ||
|
|
1173
|
+
command === "status")
|
|
943
1174
|
serviceAction(command);
|
|
1175
|
+
else if (command === "install-extra")
|
|
1176
|
+
await installExtra(args);
|
|
944
1177
|
else if (command === "logs")
|
|
945
1178
|
logs();
|
|
946
1179
|
else if (command === "doctor")
|