Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions pmoves/configs/cli_tools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,62 @@ host_clis:
windows: "npm install -g skills"
linux: "npm install -g skills"
check: "npx skills --version"
tailscale:
purpose: "Tailscale — fleet mesh. NOTE: raw 'tailscale status' leaks IPs — use 'make fleet-status' (Known Road)"
required: false
install:
windows: "winget install Tailscale.Tailscale"
linux: "curl -fsSL https://tailscale.com/install.sh | sh"
check: "tailscale version"
opencode:
purpose: "OpenCode — KiloCode runtime (kilo-pmoves selects per-node configs)"
required: false
install:
windows: "winget install sst.opencode # or: curl -fsSL https://opencode.ai/install | bash"
linux: "curl -fsSL https://opencode.ai/install | bash"
check: "opencode --version"
node:
purpose: "Node.js — web toolchains (jest/eslint run via package.json scripts, not global). Windows: nvm4w manages versions"
required: false
install:
windows: "winget install CoreyButler.NVMforWindows # then: nvm install lts"
linux: "curl -fsSL https://fnm.vercel.app/install | bash # or nvm"
check: "node --version"
npm:
purpose: "npm — package management for JS toolchains; global installs discouraged, prefer package.json+npx"
required: false
install:
windows: "ships with node (nvm4w)"
linux: "ships with node"
check: "npm --version"

project_devtools:
# Tools pinned by package.json (NOT host CLIs — always run via the owning
# workspace's npm scripts so every node gets the version from ITS lockfile;
# never install globally, never bare `npx` from the repo root: there is no
# root package.json, and bare npx would fetch unrelated versions)
jest:
entry: "jest.config.js (pmoves/ui)"
run_via: "npm --prefix pmoves/ui test # or: cd pmoves/ui && npm test"
purpose: "JS test runner (pmoves/ui workspace)"
eslint:
entry: "pmoves/ui package.json devDependencies"
run_via: "npm --prefix pmoves/ui run lint"
purpose: "JS/TS lint (pmoves/ui workspace)"

pinokio_ecosystem:
# Fleet Pinokio plane — launchers self-install deps into per-app venvs
# (the pattern the PMOVES launchers mirror via make env-bootstrap-lite)
pinokio:
purpose: "Pinokio app server — fleet launcher plane (D:/pinokio on Windows nodes)"
docs: "D:/pinokio/prototype/PINOKIO.md + .claude/PINOKIO_LAUNCHER_GUIDE.md"
check: "pinokio --version # via Pinokio install"
pterm:
purpose: "Pinokio terminal CLI — clipboard, notifications, dialogs, script testing"
docs: ".claude/skills/pterm/SKILL.md"
gepeto:
purpose: "Pinokio built-in code-assistant skill (Jetson JONS native skill)"
docs: "~/.agents/skills/gepeto"

service_clis:
# CLIs that live inside PMOVES services/submodules
Expand Down
40 changes: 37 additions & 3 deletions pmoves/tools/install_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,30 @@ def warn_if_not_on_path(bin_dir: Path) -> None:
print(f"WARN: {bin_dir} is not on PATH - add it to your shell profile.")


def check_host_clis(strict: bool) -> int:
def _cli_version(check_cmd: str) -> str:
"""Best-effort version capture: run the manifest's check command, keep line 1."""
import shlex
import subprocess

try:
completed = subprocess.run(
shlex.split(check_cmd),
check=False,
capture_output=True,
text=True,
timeout=10,
)
except (OSError, ValueError, subprocess.TimeoutExpired):
return ""
stream = completed.stdout or completed.stderr
for line in stream.splitlines():
line = line.strip()
if line:
return line[:120]
return ""


def check_host_clis(strict: bool, report_versions: bool = False) -> int:
"""Doctor mode: validate host CLIs against configs/cli_tools.yaml."""
import shutil

Expand All @@ -159,7 +182,8 @@ def check_host_clis(strict: bool) -> int:
binary = shutil.which(name)
required = bool(spec.get("required", False))
if binary:
print(f" OK {name}: {binary}")
version = f" {_cli_version(spec.get('check', ''))}" if report_versions and spec.get("check") else ""
Comment thread
POWERFULMOVES marked this conversation as resolved.
print(f" OK {name}: {binary}{version}")
continue
hint = (spec.get("install", {}) or {}).get(platform_key, "")
marker = "REQUIRED" if required else "optional"
Expand Down Expand Up @@ -200,10 +224,20 @@ def main(argv: list[str] | None = None) -> int:
action="store_true",
help="With --check: also fail on missing optional CLIs.",
)
parser.add_argument(
"--no-report-versions",
action="store_true",
help="With --check: skip the per-CLI version capture line.",
)
args = parser.parse_args(argv)

if args.check:
return check_host_clis(strict=args.strict)
# Doctor mode reports versions by default -- the version capture is the
# point of the check output; --no-report-versions opts out for
# deterministic-script consumers.
return check_host_clis(
strict=args.strict, report_versions=not args.no_report_versions
)

bin_dir = Path(args.bin).expanduser() if args.bin else default_bin_dir()
if args.dry_run:
Expand Down
Loading