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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ NETCLAW_ENABLE_PASSWORD=changeme
# Anthropic API Key (if not using Claude Code CLI auth)
# ANTHROPIC_API_KEY=sk-ant-...

# Optional direct terminal assistant. Instant Assist sends only the terminal
# English-intent request and selected terminal context to OpenAI; it never
# receives SSH credentials. Restart the NetClaw Visual API after adding it.
# OPENAI_API_KEY=sk-...
# NETCLAW_TERRA_API_KEY=sk-... # optional Terra-only override

# Token Optimization - Override default model pricing (optional)
# Format: JSON string with model-specific input/output prices per 1M tokens
# NETCLAW_TOKEN_PRICING_OVERRIDE={"claude-opus-4-6": {"input": 5.0, "output": 25.0}}
Expand All @@ -20,6 +26,9 @@ NETCLAW_ENABLE_PASSWORD=changeme
# PYATS_UPSTREAM_SCRIPT=~/.openclaw/pyats-venv/upstream/pyats_mcp_server.py
# PYATS_VENV=~/.openclaw/pyats-venv
# MCP_CALL_TIMEOUT=120
# PYATS_PYTHON=/path/to/existing/pyats-venv/bin/python
# Optional for a Windows-hosted Visual API using existing Linux pyATS:
# PYATS_WSL_DISTRO=Ubuntu

# GitHub MCP (Personal Access Token)
# GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/hud-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
pull_request:
paths:
- 'ui/netclaw-visual/**'
- 'scripts/install-pyats-genie.py'
- 'scripts/test-install-pyats-genie.py'
- 'tests/canvas-terminal/**'
- '.github/workflows/hud-ci.yml'
push:
branches: [main]
Expand All @@ -28,4 +31,9 @@ jobs:
cache-dependency-path: ui/netclaw-visual/package-lock.json
- run: npm ci
- run: npm test
- run: npm run build
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Canvas terminal contracts, Python mocks and bundle budget
working-directory: .
run: bash tests/canvas-terminal/run-tests.sh
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,6 @@ labs/**/.tls/
# Private environment-migration recovery digests
*.pre-*.state.json
local-permissions-backup.json
# Local inventory backups may contain device credentials.
*.yaml.backups/
*.yml.backups/
24 changes: 22 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ These are non-negotiable:
1. **Never guess device state.** Run a show command first. Always.
2. **Never apply config without a baseline.** Capture pre-change state in GAIT.
3. **Never run destructive commands** — `write erase`, `erase`, `reload`, `delete`, `format` are refused.
4. **Never skip the Change Request.** ServiceNow CR must be Approved before config push.
4. **Never skip change control.** Production requires an approved ServiceNow CR. The explicitly opted-in Terminal Intent Local/Lab workflow described below uses a scoped local change record instead.
5. **Never auto-quarantine an endpoint.** ISE quarantine requires explicit human confirmation.
6. **NetBox is read-write.** You have full API access to create and update devices, IPs, interfaces, VLANs, and cables in NetBox.
7. **Always verify after changes.** If verification fails, do not close the CR.
Expand All @@ -59,7 +59,7 @@ If you forget GAIT, the session has no record. That is unacceptable in a product

## Change Management Workflow

All configuration changes follow this sequence:
Production configuration changes follow this sequence:

1. **Pre-check** — Query ServiceNow for open P1/P2 incidents on affected CIs
2. **Create CR** — ServiceNow Change Request with description, risk, impact, rollback plan
Expand All @@ -70,6 +70,26 @@ All configuration changes follow this sequence:

Emergency changes require immediate human notification and post-facto approval.

### Explicit Terminal Intent Local/Lab policy

The owner may designate lab SSH endpoints in Terminal Intent's **Change control** settings.
Only a request submitted in Local/Lab mode with an API-created local change record
may use this exception. See `ui/netclaw-visual/LOCAL-LAB-CHANGE-CONTROL.md`.
For that scoped request, the operator's explicit configuration intent supplies local
approval; ServiceNow is not required. Questions authorize no writes. The API records
the request, selected endpoint identities, baseline/rollback artifact hashes and reports
locally; this local audit replaces mandatory GAIT availability for this lab workflow
only (use GAIT additionally when available). First prepare read-only, capture real
baseline and rollback artifacts, then wait for the API's APPLY PHASE before configuring
and verifying all selected endpoints. Do not fabricate artifacts or approval evidence.
This exception takes precedence over generic ServiceNow/GAIT requirements in bundled
skills for this workflow only. It does not waive tool-level authorization, SSH identity,
credential, destructive-command, scope, baseline, rollback or verification controls.
Never use raw SSH helpers to evade a denied configuration tool. Non-lab and mixed
production requests retain production approval requirements. Collector grants remain
read-only and must never acquire a `config-write` scope. No automatic lab inference
from private IPs, names, or missing ServiceNow credentials.

## Fleet-Wide Operations

When operating across multiple devices:
Expand Down
23 changes: 23 additions & 0 deletions Install-NetClaw-Lab-Policy.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
param(
[string]$AgentWorkspace = (Join-Path $env:USERPROFILE '.openclaw\workspace'),
[switch]$CheckOnly
)
$ErrorActionPreference = 'Stop'
$policyPath = Join-Path $PSScriptRoot 'ui\netclaw-visual\LOCAL-LAB-CHANGE-CONTROL.md'
$sourcePath = Join-Path $PSScriptRoot 'ui\netclaw-visual\local-lab-agent-note.md'
$agentFile = Join-Path $AgentWorkspace 'AGENTS.md'
if (-not (Test-Path -LiteralPath $policyPath -PathType Leaf) -or -not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) { throw 'Local/Lab policy files are missing from this checkout.' }
if (-not (Test-Path -LiteralPath $agentFile -PathType Leaf)) { throw 'The selected agent workspace has no AGENTS.md; choose its actual configured workspace.' }
$original = [IO.File]::ReadAllText($agentFile)
$marker = '<!-- netclaw-terminal-local-lab-policy-v1 -->'
if ($original.Contains($marker)) { Write-Output 'NetClaw Local/Lab policy note is already installed. No files changed.'; return }
if ($CheckOnly) { Write-Output "Ready to install the scoped Local/Lab policy note in $agentFile. No devices will be authorized automatically."; return }
$note = [IO.File]::ReadAllText($sourcePath).Replace('{{POLICY_PATH}}', $policyPath)
$backup = "$agentFile.netclaw-lab-backup-$([Guid]::NewGuid().ToString('N'))"
[IO.File]::Copy($agentFile, $backup, $false)
# Preserve existing content exactly; append only the owner-approved policy note.
# Verify no other actor changed the file between our read and backup.
if ([IO.File]::ReadAllText($agentFile) -cne $original) { throw 'AGENTS.md changed during installation; nothing was appended. Retry after reviewing it.' }
[IO.File]::AppendAllText($agentFile, "`r`n`r`n$note`r`n", [Text.UTF8Encoding]::new($false))
Write-Output "Installed scoped Local/Lab policy note. Backup: $backup"
Write-Output 'Production defaults are unchanged. Designate lab devices explicitly in Canvas Change control.'
35 changes: 35 additions & 0 deletions Install-NetClaw-pyATS.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$Distribution = 'Ubuntu',
[string]$Python = 'python3',
[string]$EnvironmentPath,
[ValidatePattern('^\d+\.\d+(\.\d+)?$')][string]$Version = '26.8',
[switch]$Yes,
[switch]$Upgrade,
[switch]$CheckOnly
)
$ErrorActionPreference = 'Stop'
$installer = Join-Path $PSScriptRoot 'scripts\install-pyats-genie.py'
if (-not (Test-Path -LiteralPath $installer -PathType Leaf)) { throw "Installer not found: $installer" }
if (-not $PSCmdlet.ShouldProcess("WSL distribution '$Distribution'", $(if ($CheckOnly) { 'Check Genie runtime' } else { 'Install pyATS/Genie into a dedicated virtualenv' }))) { return }
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) { throw 'WSL is not installed. Use the Python installer on Linux/macOS; no Windows features were changed.' }
& wsl.exe -d $Distribution --exec /bin/true
if ($LASTEXITCODE -ne 0) {
throw 'WSL cannot start. This is a runtime prerequisite, not a pip problem. No packages were installed. Do not enable Hyper-V blindly if this PC hosts a VMware/CML lab; run scripts/install-pyats-genie.py in an available Linux environment instead.'
}
& wsl.exe -d $Distribution --exec $Python -c 'import sys; print(sys.version); sys.exit(0 if sys.version_info >= (3, 10) else 1)'
if ($LASTEXITCODE -ne 0) { throw 'Selected Linux Python could not start or is older than 3.10. Use -Python with a supported interpreter path.' }
if (-not $CheckOnly -and -not $Yes) {
Write-Host 'This downloads pyATS/Genie from PyPI into a dedicated Linux virtualenv. It does not modify system Python, enable virtualization or change NetClaw configuration.'
if ((Read-Host 'Continue? Type YES').Trim() -cne 'YES') { Write-Host 'Cancelled.'; return }
}
$linuxScript = & wsl.exe -d $Distribution --exec wslpath -a $installer
if ($LASTEXITCODE -ne 0 -or -not $linuxScript) { throw 'Could not resolve the installer path inside WSL.' }
$installArgs = @('-d', $Distribution, '--exec', $Python, ([string]$linuxScript).Trim(), '--version', $Version)
if ($EnvironmentPath) { $installArgs += @('--venv', $EnvironmentPath) }
if ($CheckOnly) { $installArgs += '--check-only' } else { $installArgs += '--yes' }
if ($Upgrade) { $installArgs += '--upgrade' }
& wsl.exe @installArgs
if ($LASTEXITCODE -ne 0) { throw 'Genie runtime is not ready. Review the installer output above; nothing was connected to a router.' }
Write-Host "PYATS_WSL_DISTRO=$Distribution"
Write-Host 'Use the printed PYATS_PYTHON path with this distribution. Existing NetClaw environment files were not modified.'
80 changes: 80 additions & 0 deletions OPTIMIZATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# NetClaw Canvas optimization — first pass

Isolated working folder: `netclaw-canvas-optimization`.
Prepared PR branch: `131-canvas-terminal-workflows` (from the optimization work).
Starting point: `2d328b0c69b091673db6c547bc40585c950fd9a8`
(`netclaw-canvas-terminal-integration`).

This is the NetClaw browser integration, not the standalone Electron desktop
product. The original development checkout and published PR branch are unchanged.
No personal inventory, credentials or running service configuration was copied.

## What changed

- Extracted the SSH terminal from `App.jsx` into `TerminalLane.jsx`; shared theme,
export helpers, API error handling and transcript limits have small modules.
- Load terminal, configuration-review and result windows on demand. Each has a
positioned loading state and a local failure/retry boundary. A failed download
in one window does not unmount another window's SSH session.
- Memoize structured-output preview parsing/validation for unchanged captures.
Changing the source, format, device or Genie result invalidates the memo.
- Separated backend device profiles/credential resolution and host-key storage
from `server.js`. Inventory and environment readers remain live, not cached.
Consolidated the duplicated host-key write path.
- Added regression tests for lazy-window lifecycle and extracted backend services.
- Added a production bundle budget, including static shared imports, and fixed
unit-test discovery so Windows does not silently report zero tests as success.

Authorization, host-key confirmation, legacy-KEX opt-in, production change
controls, collector scopes, Genie validation and stored Canvas schema are unchanged.

## Measurements

Production builds on the same host with Node 24.18.0, Vite 6.4.3 and the same
dependency lockfile. Sizes are decimal KB. Gzip is measured per delivered file.

| Measurement | Before | After |
| --- | ---: | ---: |
| Canvas entry JavaScript | 728.77 KB | 195.14 KB |
| Initial Canvas JS, including static dependencies | 872.41 KB | 339.92 KB |
| Initial Canvas JS, gzip | 250.47 KB | 107.73 KB |
| `App.jsx` lines | 5,759 | 3,303 |
| `server.js` lines | 3,209 | 2,882 |

Initial JS is approximately **61% smaller** (57% gzip). This is deferred loading,
not deletion of terminal features. Restoring a session containing a terminal
immediately requests its terminal chunk. Total downloaded code after opening
every feature is approximately unchanged and can be slightly larger due to
chunk-loading/error handling. No CPU, memory or end-to-end speedup is claimed.

This is not a claim that the implementation is now minimal. `TerminalLane.jsx`
still has 2,330 lines, and its roughly 511 KB chunk retains a Vite size warning.
Further useful work includes splitting device/credential dialogs and intent
state management, then profiling real transcript rendering and collection load.
Do not remove safety checks or change collector polling merely to reduce LOC.

## Verification

Run from `ui/netclaw-visual`:

```sh
npm ci --ignore-scripts
npm run test:canvas
npm run test:bundle
npm test
```

- 27/27 Canvas suites passed using mocks/synthetic loopback devices.
- Production build and initial-JS budget (<400,000 bytes) passed.
- Seven saved-session regression tests passed.
- Full upstream test discovery: 270 tests, 255 passed, 15 failed. These are the
same known Windows failures documented for the starting branch (POSIX file
permissions, symlink privileges, dashboard URL/path handling). They remain
failures; Linux validation is still required before merge.
- Browser smoke check of the synthetic terminal preview: terminal rendered,
route context opened, and the Genie structured-output view opened. No live
SSH, inference, pyATS execution or external-provider calls were made.

The scripts do not restart the user's existing frontend/API. This optimization
pass is included in the draft [spec131 contribution](specs/131-canvas-terminal-workflows/spec.md)
on top of the latest fetched upstream HUD base (`40425bb`).
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,19 @@ CLI declarations, MCP signatures and the HUD OpenAPI reference. Three.js views a
optional companions to the panels and tables. Configured capabilities, recorded
usage and estimates stay distinct from verified live state.

On Windows, start the gateway and Visual HUD together by double-clicking
`Start-NetClaw.cmd`, or from PowerShell:

```powershell
.\Start-NetClaw.ps1
```

The launcher verifies Node.js, npm, and OpenClaw; installs Visual HUD
dependencies on the first run; avoids starting duplicate services; waits for
the configured gateway port plus ports 3000 and 3001; and opens Canvas Chat. Use
`.\Start-NetClaw.ps1 -Interface HUD` to open the main dashboard instead,
or add `-NoBrowser` when you only want to start the services.

```bash
cd ui/netclaw-visual
npm install
Expand Down
Loading
Loading