mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 01:20:45 +08:00
Auto-install the Netx Ops agent preset on plugin activate.
Copy presets into ~/.dsh/.agent-presets (not a Windows junction — DSH discovery skips those) so dsh plugin add alone surfaces Custom → Netx Ops with skills. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4854101afd
commit
5179eb74b1
9 changed files with 174 additions and 82 deletions
35
README.md
35
README.md
|
|
@ -1,38 +1,33 @@
|
|||
# netxops — Netx Ops for DeepSeek Harness
|
||||
|
||||
Public **DeepSeek Harness** package: host bridge + **Plugins settings card** + ops agent preset.
|
||||
Public **DeepSeek Harness** package: host bridge + Plugins card + **agent preset (skills included)**.
|
||||
|
||||
- GitHub: https://github.com/hansjone/netxops
|
||||
- Topic: [`dsh-plugin`](https://github.com/topics/dsh-plugin)
|
||||
- Package: `dsh-netxops`
|
||||
- Brand: **Netx Ops**
|
||||
|
||||
## What you get
|
||||
|
||||
| Piece | Where | Purpose |
|
||||
|-------|--------|---------|
|
||||
| Host plugin | `src/index.ts` + `cordis.patch.yml` | settings `netxops` + credential `NETX_API_TOKEN` → `mcp__netx__*` |
|
||||
| Plugins card | `src/client/` → `lib/client.js` | Settings → Plugins → fill URL / token in UI |
|
||||
| Agent preset | `presets/netxops/` | Persona + UME / managed-NE skills |
|
||||
|
||||
**No OS env required** for day-to-day use. See [docs/INSTALL.md](docs/INSTALL.md).
|
||||
|
||||
**Not in v1:** topology canvas MCP, Excel report plugin.
|
||||
|
||||
## Quick start
|
||||
## Install (one command)
|
||||
|
||||
```powershell
|
||||
dsh plugin --profile web add github:hansjone/netxops
|
||||
dsh web
|
||||
# Settings → Plugins → Netx Ops → token + API URL → Save
|
||||
```
|
||||
|
||||
Agent preset (skills): clone once and run `scripts/link-preset.ps1` (or `.sh`).
|
||||
Then:
|
||||
|
||||
## Related
|
||||
1. Settings → **Plugins** → **Netx Ops** → API URL / token
|
||||
2. Settings → **Agent presets** → Custom → **Netx Ops** (installed automatically on first host activate)
|
||||
3. New session → choose **Netx Ops**
|
||||
|
||||
- Data plane: [hansjone/netx](https://github.com/hansjone/netx)
|
||||
- Porting: [docs/PORTING.md](docs/PORTING.md)
|
||||
Also need: running **netx API**, and `pip install` of `netx_mcp` (Python execution plane). Details: [docs/INSTALL.md](docs/INSTALL.md).
|
||||
|
||||
## What is coupled
|
||||
|
||||
| In the plugin | Outside (data / runtime) |
|
||||
|---------------|---------------------------|
|
||||
| MCP spawn + settings + credentials | netx HTTP API |
|
||||
| Persona + UME / managed-NE skills | `python -m netx_mcp` on PATH |
|
||||
| Agent preset auto-install to `~/.dsh/.agent-presets` | |
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@
|
|||
pythonCommand: python
|
||||
tokenCredentialRef: NETX_API_TOKEN
|
||||
toolCallTimeoutMs: 120000
|
||||
failOnStartupError: false
|
||||
installAgentPreset: true
|
||||
|
|
|
|||
|
|
@ -1,52 +1,52 @@
|
|||
# Install Netx Ops on DeepSeek Harness
|
||||
|
||||
## Prerequisites
|
||||
## What `dsh plugin add` gives you
|
||||
|
||||
1. **DeepSeek Harness** (`dsh`) installed.
|
||||
2. **netx API** reachable (default `http://127.0.0.1:8890`).
|
||||
3. **Python 3.11+** with `netx_mcp` importable:
|
||||
One install wires **all of**:
|
||||
|
||||
| Piece | How you use it |
|
||||
|-------|----------------|
|
||||
| Host bridge | spawns `mcp__netx__*` via `python -m netx_mcp` |
|
||||
| Plugins card | Settings → Plugins → **Netx Ops** (URL / lang / python) |
|
||||
| Agent preset + skills | Settings → Agent presets → **Custom → Netx Ops** (copied into `~/.dsh/.agent-presets` on first boot) |
|
||||
|
||||
You do **not** run `link-preset.ps1` for normal use. That script is only a manual fallback.
|
||||
|
||||
## Still required outside the npm package
|
||||
|
||||
1. **netx API** reachable (default `http://127.0.0.1:8890`) — the data plane.
|
||||
2. **Python `netx_mcp`** on the same machine as `dsh`:
|
||||
|
||||
```powershell
|
||||
pip install "git+https://github.com/hansjone/netx.git#subdirectory=packages/netx-mcp"
|
||||
python -c "import netx_mcp; print('ok')"
|
||||
```
|
||||
|
||||
No OS env vars needed for URL / token.
|
||||
v1 keeps the Python MCP as the execution plane (shared with OpenClaw / `python -m netx_mcp`). The DSH plugin owns orchestration, settings, persona, and skills — not a second MCP install in the agent preset.
|
||||
|
||||
## Install (GitHub — recommended)
|
||||
## Install
|
||||
|
||||
```powershell
|
||||
dsh plugin --profile web add github:hansjone/netxops
|
||||
dsh web
|
||||
# or from DeepSeekHarness source:
|
||||
# pnpm dsh plugin --profile web add github:hansjone/netxops
|
||||
dsh web # or: pnpm dsh web
|
||||
```
|
||||
|
||||
Then: **Settings → Plugins → Netx Ops** → paste API token + URL → Save.
|
||||
|
||||
That is the whole host install. You do **not** run any `link-dsh-peers` script for GitHub installs.
|
||||
|
||||
### Agent preset (persona + skills)
|
||||
|
||||
One-time (or after pull):
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/hansjone/netxops.git
|
||||
cd netxops
|
||||
powershell -File .\scripts\link-preset.ps1
|
||||
```
|
||||
|
||||
Unix: `./scripts/link-preset.sh`
|
||||
|
||||
New session → preset **Netx Ops**.
|
||||
1. **Settings → Plugins → Netx Ops** → API URL (+ token if the field is enabled).
|
||||
Token fallback: `scripts/set-netx-token.ps1` / `.sh`.
|
||||
2. Restart or open Settings → **Agent presets** → Custom → **Netx Ops** should appear after the host plugin has activated once.
|
||||
3. **New session → preset Netx Ops** → ask e.g. Critical Top / single-host alarms.
|
||||
|
||||
## Verify
|
||||
|
||||
1. Settings → Plugins shows **Netx Ops**; token badge “Configured” after save.
|
||||
2. Tools include `mcp__netx__queryUmeAlarms`.
|
||||
3. Critical Top / single-host alarm queries work.
|
||||
1. Plugins card **Netx Ops** visible.
|
||||
2. Agent presets → Custom → **Netx Ops**.
|
||||
3. Tools include `mcp__netx__queryUmeAlarms`.
|
||||
|
||||
See [TOOL_MAP.md](TOOL_MAP.md).
|
||||
|
||||
## Path / local checkout (developers only)
|
||||
## Path / local checkout (developers)
|
||||
|
||||
```powershell
|
||||
dsh plugin --profile web add D:\path\to\netxops
|
||||
|
|
|
|||
44
lib/index.js
44
lib/index.js
|
|
@ -1,4 +1,8 @@
|
|||
// src/index.ts
|
||||
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import z from "@deepseek-ai/schemastery";
|
||||
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
||||
import * as DshSettings from "@deepseek-ai/dsh-settings";
|
||||
|
|
@ -6,6 +10,7 @@ import * as McpClient from "@deepseek-ai/dsh-mcp-client";
|
|||
var name = "netxops";
|
||||
var inject = ["tools"];
|
||||
var NETXOPS_SETTINGS_NAMESPACE = "netxops";
|
||||
var NETXOPS_PRESET_ID = "netxops";
|
||||
var DEFAULT_TOKEN_REF = "NETX_API_TOKEN";
|
||||
var Config2 = z.object({
|
||||
apiUrl: z.string().default("http://127.0.0.1:8890"),
|
||||
|
|
@ -13,8 +18,40 @@ var Config2 = z.object({
|
|||
pythonCommand: z.string().default("python"),
|
||||
tokenCredentialRef: z.string().role("credential-ref").default(DEFAULT_TOKEN_REF),
|
||||
toolCallTimeoutMs: z.number().step(1).min(1000).default(120000),
|
||||
failOnStartupError: z.boolean().default(false)
|
||||
failOnStartupError: z.boolean().default(false),
|
||||
installAgentPreset: z.boolean().default(true)
|
||||
});
|
||||
function packageRoot() {
|
||||
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
}
|
||||
function resolveDshHome() {
|
||||
const fromEnv = process.env.DSH_HOME?.trim();
|
||||
if (fromEnv !== undefined && fromEnv.length > 0)
|
||||
return fromEnv;
|
||||
return join(homedir(), ".dsh");
|
||||
}
|
||||
function ensureAgentPresetInstalled(logger) {
|
||||
const src = join(packageRoot(), "presets", NETXOPS_PRESET_ID);
|
||||
const composition = join(src, "agent.cordis.yml");
|
||||
if (!existsSync(composition)) {
|
||||
logger.warn("netxops: bundled preset missing at %s — skip user-preset install", src);
|
||||
return;
|
||||
}
|
||||
const destParent = join(resolveDshHome(), ".agent-presets");
|
||||
const dest = join(destParent, NETXOPS_PRESET_ID);
|
||||
try {
|
||||
mkdirSync(destParent, { recursive: true });
|
||||
if (existsSync(dest)) {
|
||||
rmSync(dest, { recursive: true, force: true });
|
||||
}
|
||||
cpSync(src, dest, { recursive: true });
|
||||
writeFileSync(join(dest, ".dsh-netxops-managed"), `${new Date().toISOString()}
|
||||
`, "utf8");
|
||||
logger.info("netxops: agent preset installed at %s", dest);
|
||||
} catch (error) {
|
||||
logger.error("netxops: failed to install agent preset: %s", error);
|
||||
}
|
||||
}
|
||||
async function resolveToken(ctx, refName) {
|
||||
const credentials = ctx.get("credentials");
|
||||
if (credentials === undefined)
|
||||
|
|
@ -37,6 +74,9 @@ function apply(ctx, config = Config2({})) {
|
|||
let mcpFiber;
|
||||
let remounting = Promise.resolve();
|
||||
let generation = 0;
|
||||
if (config.installAgentPreset) {
|
||||
ensureAgentPresetInstalled(ctx.logger);
|
||||
}
|
||||
const remount = () => {
|
||||
remounting = remounting.then(async () => {
|
||||
const gen = ++generation;
|
||||
|
|
@ -102,8 +142,10 @@ function apply(ctx, config = Config2({})) {
|
|||
export {
|
||||
name,
|
||||
inject,
|
||||
ensureAgentPresetInstalled,
|
||||
apply,
|
||||
NETXOPS_SETTINGS_NAMESPACE,
|
||||
NETXOPS_PRESET_ID,
|
||||
DEFAULT_TOKEN_REF,
|
||||
Config2 as Config
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dsh-netxops",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.11",
|
||||
"description": "DeepSeek Harness Netx Ops: host bridge + Plugins settings card + agent preset",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
- id: tool-skill
|
||||
name: '@deepseek-ai/dsh-tool-skill'
|
||||
|
||||
# netx MCP is mounted on the HOST by package `dsh-netxops` (cordis.patch.yml):
|
||||
# Settings → Plugins / credentials NETX_API_TOKEN — not OS env, not this preset.
|
||||
# Tools appear as mcp__netx__* for sessions that can see the host tool registry.
|
||||
# netx MCP is mounted on the HOST by package `dsh-netxops` (cordis.patch.yml).
|
||||
# The same package copies this preset into `$DSH_HOME/.agent-presets/netxops`
|
||||
# on activate — no manual link-preset script for normal installs.
|
||||
# Tools appear as mcp__netx__* on the host tool registry.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
# Link presets/netxops into the DSH user agent-presets root (Windows junction).
|
||||
# Link / install presets/netxops into the DSH user agent-presets root.
|
||||
# Prefer a real directory copy: DSH discovery skips Windows junctions
|
||||
# (Dirent.isDirectory() is false for reparse points).
|
||||
$ErrorActionPreference = "Stop"
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$Source = Join-Path $RepoRoot "presets\netxops"
|
||||
|
|
@ -6,18 +8,14 @@ $DshHome = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $env:USERPROFIL
|
|||
$TargetParent = Join-Path $DshHome ".agent-presets"
|
||||
$Target = Join-Path $TargetParent "netxops"
|
||||
|
||||
if (-not (Test-Path $Source)) {
|
||||
if (-not (Test-Path (Join-Path $Source "agent.cordis.yml"))) {
|
||||
throw "Missing preset dir: $Source"
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $TargetParent | Out-Null
|
||||
if (Test-Path $Target) {
|
||||
$item = Get-Item $Target -Force
|
||||
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
||||
cmd /c "rmdir `"$Target`""
|
||||
} else {
|
||||
throw "Target exists and is not a junction: $Target — remove or rename it first."
|
||||
}
|
||||
Remove-Item -Recurse -Force $Target
|
||||
}
|
||||
cmd /c "mklink /J `"$Target`" `"$Source`""
|
||||
Write-Host "Linked $Target -> $Source"
|
||||
Write-Host "Restart dsh / open a new session and select preset 'netxops' (Netx Ops)."
|
||||
Copy-Item -Recurse -Force $Source $Target
|
||||
Set-Content -Path (Join-Path $Target ".dsh-netxops-managed") -Value ((Get-Date).ToString("o"))
|
||||
Write-Host "Installed $Target (copy from $Source)"
|
||||
Write-Host "Open Settings → Agent presets → Custom → Netx Ops (or new session)."
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
# Install presets/netxops into $DSH_HOME/.agent-presets (copy, not symlink).
|
||||
set -euo pipefail
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SOURCE="$REPO_ROOT/presets/netxops"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SRC="$ROOT/presets/netxops"
|
||||
DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
|
||||
TARGET_PARENT="$DSH_HOME/.agent-presets"
|
||||
TARGET="$TARGET_PARENT/netxops"
|
||||
DEST_PARENT="$DSH_HOME/.agent-presets"
|
||||
DEST="$DEST_PARENT/netxops"
|
||||
|
||||
if [[ ! -d "$SOURCE" ]]; then
|
||||
echo "Missing preset dir: $SOURCE" >&2
|
||||
if [[ ! -f "$SRC/agent.cordis.yml" ]]; then
|
||||
echo "Missing preset dir: $SRC" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$TARGET_PARENT"
|
||||
if [[ -e "$TARGET" || -L "$TARGET" ]]; then
|
||||
rm -rf "$TARGET"
|
||||
fi
|
||||
ln -s "$SOURCE" "$TARGET"
|
||||
echo "Linked $TARGET -> $SOURCE"
|
||||
echo "Restart dsh / open a new session and select preset 'netxops' (Netx Ops)."
|
||||
mkdir -p "$DEST_PARENT"
|
||||
rm -rf "$DEST"
|
||||
cp -R "$SRC" "$DEST"
|
||||
date -u +%Y-%m-%dT%H:%M:%SZ > "$DEST/.dsh-netxops-managed"
|
||||
echo "Installed $DEST"
|
||||
echo "Open Settings → Agent presets → Custom → Netx Ops"
|
||||
|
|
|
|||
64
src/index.ts
64
src/index.ts
|
|
@ -2,13 +2,17 @@
|
|||
* Host-plane Netx Ops bridge: settings (apiUrl / lang / python) + credentials
|
||||
* (NETX_API_TOKEN) drive a dynamically mounted `@deepseek-ai/dsh-mcp-client`.
|
||||
*
|
||||
* Configure in DSH Settings → Plugins → Netx Ops (settings card when client
|
||||
* half is installed). Token is stored as credential `NETX_API_TOKEN` (same
|
||||
* store as model API keys). Helper: `scripts/set-netx-token.ps1`.
|
||||
* On activate, the agent preset + skills are copied into
|
||||
* `$DSH_HOME/.agent-presets/netxops` so `dsh plugin add` alone is enough
|
||||
* (Windows junctions are invisible to DSH's `Dirent.isDirectory()` scan).
|
||||
*
|
||||
* @module dsh-netxops
|
||||
*/
|
||||
|
||||
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { Context, Fiber } from '@deepseek-ai/cordis'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
||||
|
|
@ -26,6 +30,9 @@ export const inject = ['tools']
|
|||
/** Settings / composition namespace (Plugins page join key). */
|
||||
export const NETXOPS_SETTINGS_NAMESPACE = 'netxops'
|
||||
|
||||
/** Agent preset id under `$DSH_HOME/.agent-presets/`. */
|
||||
export const NETXOPS_PRESET_ID = 'netxops'
|
||||
|
||||
/** Default credential reference for the netx API bearer token. */
|
||||
export const DEFAULT_TOKEN_REF = 'NETX_API_TOKEN'
|
||||
|
||||
|
|
@ -43,6 +50,11 @@ export interface Config {
|
|||
toolCallTimeoutMs: number
|
||||
/** Fail activation when MCP cannot connect / sync tools. */
|
||||
failOnStartupError: boolean
|
||||
/**
|
||||
* Copy bundled agent preset + skills into `$DSH_HOME/.agent-presets/netxops`
|
||||
* on every activate (required for Settings → Agent presets).
|
||||
*/
|
||||
installAgentPreset: boolean
|
||||
}
|
||||
|
||||
export const Config: z<Config> = z.object({
|
||||
|
|
@ -52,8 +64,49 @@ export const Config: z<Config> = z.object({
|
|||
tokenCredentialRef: z.string().role('credential-ref').default(DEFAULT_TOKEN_REF),
|
||||
toolCallTimeoutMs: z.number().step(1).min(1000).default(120_000),
|
||||
failOnStartupError: z.boolean().default(false),
|
||||
installAgentPreset: z.boolean().default(true),
|
||||
})
|
||||
|
||||
/** Package root (parent of `lib/` or `src/` depending on launch). */
|
||||
function packageRoot(): string {
|
||||
return join(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
}
|
||||
|
||||
/** Harness home used by agent-presets' user root. */
|
||||
function resolveDshHome(): string {
|
||||
const fromEnv = process.env.DSH_HOME?.trim()
|
||||
if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv
|
||||
return join(homedir(), '.dsh')
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the bundled Netx Ops preset as a real directory under the user
|
||||
* preset root. DSH discovery skips Windows junctions (`Dirent.isDirectory()`
|
||||
* is false for reparse points), so a copy is required — not `mklink /J`.
|
||||
* @param logger - cordis logger for non-fatal install failures.
|
||||
*/
|
||||
export function ensureAgentPresetInstalled(logger: Context['logger']): void {
|
||||
const src = join(packageRoot(), 'presets', NETXOPS_PRESET_ID)
|
||||
const composition = join(src, 'agent.cordis.yml')
|
||||
if (!existsSync(composition)) {
|
||||
logger.warn('netxops: bundled preset missing at %s — skip user-preset install', src)
|
||||
return
|
||||
}
|
||||
const destParent = join(resolveDshHome(), '.agent-presets')
|
||||
const dest = join(destParent, NETXOPS_PRESET_ID)
|
||||
try {
|
||||
mkdirSync(destParent, { recursive: true })
|
||||
if (existsSync(dest)) {
|
||||
rmSync(dest, { recursive: true, force: true })
|
||||
}
|
||||
cpSync(src, dest, { recursive: true })
|
||||
writeFileSync(join(dest, '.dsh-netxops-managed'), `${new Date().toISOString()}\n`, 'utf8')
|
||||
logger.info('netxops: agent preset installed at %s', dest)
|
||||
} catch (error) {
|
||||
logger.error('netxops: failed to install agent preset: %s', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve bearer token from the credentials seam (or empty when unset).
|
||||
*/
|
||||
|
|
@ -105,6 +158,10 @@ export function apply(ctx: Context, config: Config = Config({})): void {
|
|||
let remounting: Promise<void> = Promise.resolve()
|
||||
let generation = 0
|
||||
|
||||
if (config.installAgentPreset) {
|
||||
ensureAgentPresetInstalled(ctx.logger)
|
||||
}
|
||||
|
||||
const remount = (): void => {
|
||||
remounting = remounting.then(async () => {
|
||||
const gen = ++generation
|
||||
|
|
@ -148,7 +205,6 @@ export function apply(ctx: Context, config: Config = Config({})): void {
|
|||
})
|
||||
}
|
||||
|
||||
// Composition defaults first (works even when settings provider is absent).
|
||||
remount()
|
||||
|
||||
installNetxopsSettings(ctx, config, {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue