mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-20 02:43:21 +08:00
feat(skills): pull group skills from netx /mcp resources, drop bundled copy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
277a61894d
commit
fa82f530e9
31 changed files with 2074 additions and 2027 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// src/netx/group-tools-plugin.ts
|
||||
import { readFileSync as readFileSync6 } from "node:fs";
|
||||
import { dirname as dirname6, join as join10 } from "node:path";
|
||||
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
||||
import { dirname as dirname6, join as join11 } from "node:path";
|
||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||
|
||||
// src/netx/config-values.ts
|
||||
var volatileWrite = Symbol.for("cosmokit.volatile.write");
|
||||
|
|
@ -123,7 +123,6 @@ function disabledPublicNames(catalog, enabledGroups, disabledTools, readOnlyOnly
|
|||
// src/netx/mcp-remote.ts
|
||||
var PROTOCOL_VERSION = "2025-03-26";
|
||||
var CLIENT_INFO = { name: "dsh-netxops", version: "0.1.71" };
|
||||
|
||||
class McpRemoteError extends Error {
|
||||
status;
|
||||
constructor(message, status) {
|
||||
|
|
@ -266,6 +265,50 @@ function mcpContentText(content, fallback = "") {
|
|||
`) || fallback;
|
||||
}
|
||||
|
||||
// src/netx/skills-cache.ts
|
||||
import { createHash } from "node:crypto";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
// src/netx/http.ts
|
||||
function normalizeApiUrl(raw) {
|
||||
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
||||
if (!trimmed)
|
||||
return { ok: false, error: "empty" };
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(trimmed);
|
||||
} catch {
|
||||
return { ok: false, error: "invalid_url" };
|
||||
}
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||
return { ok: false, error: "unsupported_scheme" };
|
||||
}
|
||||
if (parsed.username || parsed.password) {
|
||||
return { ok: false, error: "credentials_in_url" };
|
||||
}
|
||||
const path = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, "");
|
||||
const search = parsed.search && parsed.search !== "?" ? parsed.search : "";
|
||||
return { ok: true, url: `${parsed.origin}${path}${search}` };
|
||||
}
|
||||
|
||||
// src/netx/skills-cache.ts
|
||||
function dshHome() {
|
||||
const fromEnv = process.env.DSH_HOME?.trim();
|
||||
return fromEnv || join(homedir(), ".dsh");
|
||||
}
|
||||
function normalizeSkillsApiUrl(apiUrl) {
|
||||
const normalized = normalizeApiUrl(apiUrl);
|
||||
return normalized.ok ? normalized.url : apiUrl.trim().replace(/\/$/, "");
|
||||
}
|
||||
function skillsCacheKey(apiUrl) {
|
||||
const base = normalizeSkillsApiUrl(apiUrl);
|
||||
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
||||
}
|
||||
function skillsCacheDir(apiUrl) {
|
||||
return join(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
||||
}
|
||||
|
||||
// src/netx/mcp-store.ts
|
||||
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
||||
var EMPTY = Object.freeze({
|
||||
|
|
@ -297,34 +340,51 @@ function watchMcpCatalog(listener) {
|
|||
}
|
||||
|
||||
// src/netx/group-skills.ts
|
||||
import { existsSync } from "node:fs";
|
||||
import { readdir, readFile, stat } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
function opsSkillsRoot() {
|
||||
const envRoot = process.env.NETX_SKILLS_ROOT?.trim();
|
||||
if (envRoot && existsSync(envRoot))
|
||||
return envRoot;
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const checkout = [join(here, "..", ".git"), join(here, "..", "..", ".git")].some(existsSync);
|
||||
const siblingCandidates = [
|
||||
join(here, "..", "..", "..", "netx", "skills"),
|
||||
join(here, "..", "..", "netx", "skills")
|
||||
];
|
||||
for (const candidate of checkout ? siblingCandidates : []) {
|
||||
if (existsSync(candidate))
|
||||
return candidate;
|
||||
import { join as join2 } from "node:path";
|
||||
|
||||
// src/netx/runtime.ts
|
||||
var STORE_KEY2 = Symbol.for("dsh-netxops.connection-store");
|
||||
function store2() {
|
||||
const root = globalThis;
|
||||
let current = root[STORE_KEY2];
|
||||
if (current === undefined) {
|
||||
current = { connection: undefined, listeners: new Set };
|
||||
root[STORE_KEY2] = current;
|
||||
}
|
||||
const packaged = [
|
||||
join(here, "..", "presets", "netxops", "skills"),
|
||||
join(here, "..", "..", "presets", "netxops", "skills")
|
||||
];
|
||||
for (const candidate of packaged) {
|
||||
if (existsSync(candidate))
|
||||
return candidate;
|
||||
}
|
||||
return packaged[0];
|
||||
return current;
|
||||
}
|
||||
function getNetxConnection() {
|
||||
return store2().connection;
|
||||
}
|
||||
function provisionalNetxConnection() {
|
||||
const envUrl = process.env.NETX_API_URL?.trim() ?? "";
|
||||
const normalized = envUrl ? normalizeApiUrl(envUrl) : normalizeApiUrl("http://127.0.0.1:8890");
|
||||
return {
|
||||
toolsEnabled: false,
|
||||
apiUrl: normalized.ok ? normalized.url : "http://127.0.0.1:8890",
|
||||
token: process.env.NETX_API_TOKEN?.trim() ?? "",
|
||||
lang: typeof process.env.NETX_LANG === "string" && process.env.NETX_LANG.trim() ? process.env.NETX_LANG.trim() : "zh",
|
||||
toolCallTimeoutMs: 120000,
|
||||
groups: DEFAULT_CAPABILITY_GROUPS,
|
||||
disabledTools: [],
|
||||
readOnlyOnly: false,
|
||||
groupKbInPreset: true,
|
||||
groupKbPublic: false
|
||||
};
|
||||
}
|
||||
function requireNetxConnection() {
|
||||
return getNetxConnection() ?? provisionalNetxConnection();
|
||||
}
|
||||
function watchNetxConnection(listener) {
|
||||
const state = store2();
|
||||
state.listeners.add(listener);
|
||||
return () => {
|
||||
state.listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
// src/netx/group-skills.ts
|
||||
function parseFrontmatter(raw) {
|
||||
const text = raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
|
||||
`).replace(/\r/g, `
|
||||
|
|
@ -371,7 +431,7 @@ function stripQuotes(value) {
|
|||
return value;
|
||||
}
|
||||
async function loadSkillBundle(dir) {
|
||||
const skillPath = join(dir, "SKILL.md");
|
||||
const skillPath = join2(dir, "SKILL.md");
|
||||
let raw;
|
||||
try {
|
||||
raw = await readFile(skillPath, "utf8");
|
||||
|
|
@ -394,7 +454,7 @@ async function loadSkillBundle(dir) {
|
|||
};
|
||||
}
|
||||
async function loadGroupSkills(skillsRoot, groupId) {
|
||||
const groupDir = join(skillsRoot, SKILL_DIR_BY_GROUP[groupId]);
|
||||
const groupDir = join2(skillsRoot, SKILL_DIR_BY_GROUP[groupId]);
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir(groupDir);
|
||||
|
|
@ -404,7 +464,7 @@ async function loadGroupSkills(skillsRoot, groupId) {
|
|||
const skills = [];
|
||||
const skipped = [];
|
||||
for (const entry of entries) {
|
||||
const full = join(groupDir, entry);
|
||||
const full = join2(groupDir, entry);
|
||||
let isDir = false;
|
||||
try {
|
||||
isDir = (await stat(full)).isDirectory();
|
||||
|
|
@ -413,7 +473,7 @@ async function loadGroupSkills(skillsRoot, groupId) {
|
|||
}
|
||||
if (!isDir)
|
||||
continue;
|
||||
const skillPath = join(full, "SKILL.md");
|
||||
const skillPath = join2(full, "SKILL.md");
|
||||
let raw;
|
||||
try {
|
||||
raw = await readFile(skillPath, "utf8");
|
||||
|
|
@ -460,6 +520,15 @@ The following tools are disabled: ${list}. Do not call them.
|
|||
以下工具已禁用:${list},不要调用。
|
||||
`;
|
||||
}
|
||||
function resolveSkillsRoot(options) {
|
||||
if (typeof options.skillsRoot === "string" && options.skillsRoot.trim()) {
|
||||
return options.skillsRoot.trim();
|
||||
}
|
||||
const connection = getNetxConnection();
|
||||
if (!connection?.apiUrl?.trim())
|
||||
return "";
|
||||
return skillsCacheDir(connection.apiUrl);
|
||||
}
|
||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||
const signal = options.signal;
|
||||
|
|
@ -467,10 +536,16 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
|||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const root = opsSkillsRoot();
|
||||
const root = resolveSkillsRoot(options);
|
||||
const disposers = [];
|
||||
const enabled = new Set(groupIds);
|
||||
const log = ctx.logger;
|
||||
if (!root) {
|
||||
if (enabled.size > 0) {
|
||||
log?.warn?.("%s: no MCP skills cache (missing apiUrl)", providerLabel);
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
try {
|
||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||
if (!enabled.has(groupId))
|
||||
|
|
@ -482,7 +557,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
|||
log?.warn?.("%s: skip skill dir %s (%s)", providerLabel, miss.directory, miss.reason);
|
||||
}
|
||||
if (skills.length === 0) {
|
||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||
}
|
||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
||||
for (const skill of skills) {
|
||||
|
|
@ -519,11 +594,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
|
|||
|
||||
// src/netx/kb-context-skill.ts
|
||||
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
||||
import { join as join4 } from "node:path";
|
||||
import { join as join5 } from "node:path";
|
||||
|
||||
// src/netx/kb-manifest.ts
|
||||
import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { basename, dirname as dirname2, join as join2, resolve } from "node:path";
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
||||
var EMPTY_CONTENT = {
|
||||
hasRegions: false,
|
||||
hasTheory: false,
|
||||
|
|
@ -549,7 +624,7 @@ function resolveKbPaths(snapshot) {
|
|||
const paths = { skills: "_skills", ...snapshot.paths };
|
||||
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
||||
const absolute = resolve(snapshot.realRoot, rel);
|
||||
return { key, relative: rel, absolute, exists: existsSync2(absolute) };
|
||||
return { key, relative: rel, absolute, exists: existsSync(absolute) };
|
||||
});
|
||||
}
|
||||
function renderKbPathTable(snapshot) {
|
||||
|
|
@ -565,8 +640,8 @@ function renderKbPathTable(snapshot) {
|
|||
}
|
||||
|
||||
// src/netx/kb-local-path.ts
|
||||
import { existsSync as existsSync3, realpathSync, statSync as statSync2 } from "node:fs";
|
||||
import { join as join3, relative, resolve as resolve2, sep } from "node:path";
|
||||
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
||||
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||
"memories",
|
||||
"drafts",
|
||||
|
|
@ -674,7 +749,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
|||
}
|
||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||
try {
|
||||
if (existsSync3(checked.absolutePath)) {
|
||||
if (existsSync2(checked.absolutePath)) {
|
||||
const realFile = realpathSync(checked.absolutePath);
|
||||
const realLocal = realpathSync(localRoot);
|
||||
const realRel = relative(realLocal, realFile);
|
||||
|
|
@ -702,16 +777,16 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
|||
return checked;
|
||||
}
|
||||
function memoryDir(bucket) {
|
||||
return join3("memories", MEMORY_BUCKET_DIR[bucket]);
|
||||
return join4("memories", MEMORY_BUCKET_DIR[bucket]);
|
||||
}
|
||||
function suggestionDir(kind) {
|
||||
return join3("suggestions", kind);
|
||||
return join4("suggestions", kind);
|
||||
}
|
||||
function draftDir(domain) {
|
||||
const d = domain?.trim();
|
||||
if (!d)
|
||||
return "drafts";
|
||||
return join3("drafts", sanitizeSlug(d, "misc"));
|
||||
return join4("drafts", sanitizeSlug(d, "misc"));
|
||||
}
|
||||
function refDir(area, device) {
|
||||
if (!REF_AREAS.includes(area)) {
|
||||
|
|
@ -722,9 +797,9 @@ function refDir(area, device) {
|
|||
if (!host) {
|
||||
throw new Error("refs/devices requires device (host_name)");
|
||||
}
|
||||
return join3("refs", "devices", host);
|
||||
return join4("refs", "devices", host);
|
||||
}
|
||||
return join3("refs", area);
|
||||
return join4("refs", area);
|
||||
}
|
||||
|
||||
// src/netx/kb-context-skill.ts
|
||||
|
|
@ -735,7 +810,7 @@ function readKbIdentity(snapshot) {
|
|||
const localRoot = resolveKbLocalRoot(snapshot);
|
||||
if (!localRoot)
|
||||
return [];
|
||||
const dir = join4(localRoot, "identity");
|
||||
const dir = join5(localRoot, "identity");
|
||||
let names;
|
||||
try {
|
||||
names = readdirSync2(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
||||
|
|
@ -747,7 +822,7 @@ function readKbIdentity(snapshot) {
|
|||
for (const name of names) {
|
||||
if (budget <= 0)
|
||||
break;
|
||||
const full = join4(dir, name);
|
||||
const full = join5(dir, name);
|
||||
let text;
|
||||
try {
|
||||
if (!statSync3(full).isFile())
|
||||
|
|
@ -875,7 +950,7 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|||
|
||||
// src/netx/kb-local-ops.ts
|
||||
import {
|
||||
existsSync as existsSync4,
|
||||
existsSync as existsSync3,
|
||||
mkdirSync,
|
||||
readdirSync as readdirSync3,
|
||||
readFileSync as readFileSync3,
|
||||
|
|
@ -883,7 +958,7 @@ import {
|
|||
statSync as statSync4,
|
||||
writeFileSync
|
||||
} from "node:fs";
|
||||
import { dirname as dirname3, join as join5 } from "node:path";
|
||||
import { dirname as dirname3, join as join6 } from "node:path";
|
||||
function requireLocalRoot(snapshot) {
|
||||
const root = resolveKbLocalRoot(snapshot);
|
||||
if (!root) {
|
||||
|
|
@ -929,11 +1004,11 @@ function createMemory(snapshot, args) {
|
|||
const localRoot = requireLocalRoot(snapshot);
|
||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||
}
|
||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
||||
const existed = existsSync4(target.absolutePath);
|
||||
const existed = existsSync3(target.absolutePath);
|
||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
||||
`) ? args.body : `${args.body}
|
||||
`, "utf8");
|
||||
|
|
@ -948,11 +1023,11 @@ function createDraft(snapshot, args) {
|
|||
const localRoot = requireLocalRoot(snapshot);
|
||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||
}
|
||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
||||
const existed = existsSync4(target.absolutePath);
|
||||
const existed = existsSync3(target.absolutePath);
|
||||
writeFileSync(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||
return {
|
||||
ok: true,
|
||||
|
|
@ -965,11 +1040,11 @@ function createSuggestion(snapshot, args) {
|
|||
const localRoot = requireLocalRoot(snapshot);
|
||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||
}
|
||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
||||
const existed = existsSync4(target.absolutePath);
|
||||
const existed = existsSync3(target.absolutePath);
|
||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
||||
`) ? args.body : `${args.body}
|
||||
`, "utf8");
|
||||
|
|
@ -984,11 +1059,11 @@ function createRef(snapshot, args) {
|
|||
const localRoot = requireLocalRoot(snapshot);
|
||||
const name = refFileName(args.slug);
|
||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||
}
|
||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
||||
const existed = existsSync4(target.absolutePath);
|
||||
const existed = existsSync3(target.absolutePath);
|
||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
||||
`) ? args.body : `${args.body}
|
||||
`, "utf8");
|
||||
|
|
@ -1002,7 +1077,7 @@ function createRef(snapshot, args) {
|
|||
function updateLocalFile(snapshot, args) {
|
||||
const localRoot = requireLocalRoot(snapshot);
|
||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||
if (!existsSync4(target.absolutePath)) {
|
||||
if (!existsSync3(target.absolutePath)) {
|
||||
throw new Error(`file not found: ${target.relativePath}`);
|
||||
}
|
||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||
|
|
@ -1019,7 +1094,7 @@ function updateLocalFile(snapshot, args) {
|
|||
function deleteLocalFile(snapshot, args) {
|
||||
const localRoot = requireLocalRoot(snapshot);
|
||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||
if (!existsSync4(target.absolutePath)) {
|
||||
if (!existsSync3(target.absolutePath)) {
|
||||
throw new Error(`file not found: ${target.relativePath}`);
|
||||
}
|
||||
rmSync(target.absolutePath, { force: false });
|
||||
|
|
@ -1040,7 +1115,7 @@ function walkFiles(dir, baseLocal, out) {
|
|||
for (const name of entries) {
|
||||
if (name === "." || name === "..")
|
||||
continue;
|
||||
const full = join5(dir, name);
|
||||
const full = join6(dir, name);
|
||||
let st;
|
||||
try {
|
||||
st = statSync4(full);
|
||||
|
|
@ -1065,7 +1140,7 @@ function listLocalFiles(snapshot, args = {}) {
|
|||
const roots = args.root && args.root !== "all" ? [args.root] : [...KB_LOCAL_WRITABLE_ROOTS];
|
||||
const entries = [];
|
||||
for (const r of roots) {
|
||||
walkFiles(join5(localRoot, r), localRoot, entries);
|
||||
walkFiles(join6(localRoot, r), localRoot, entries);
|
||||
}
|
||||
entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
||||
const limit = Math.min(Math.max(args.limit ?? 50, 1), 200);
|
||||
|
|
@ -1078,7 +1153,7 @@ function listLocalFiles(snapshot, args = {}) {
|
|||
|
||||
// src/netx/kb-read-ops.ts
|
||||
import { readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
||||
import { extname, join as join6, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
||||
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
||||
var READ_DEFAULT_LINES = 400;
|
||||
|
|
@ -1129,7 +1204,7 @@ function readKbFile(snapshot, args) {
|
|||
return { name: entry.name, type: "dir" };
|
||||
let size;
|
||||
try {
|
||||
size = statSync5(join6(target.absolutePath, entry.name)).size;
|
||||
size = statSync5(join7(target.absolutePath, entry.name)).size;
|
||||
} catch {}
|
||||
return { name: entry.name, type: "file", size };
|
||||
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
||||
|
|
@ -1205,7 +1280,7 @@ function walkTextFiles(dir, out) {
|
|||
for (const entry of entries) {
|
||||
if (SKIP_DIRS.has(entry.name))
|
||||
continue;
|
||||
const full = join6(dir, entry.name);
|
||||
const full = join7(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walkTextFiles(full, out);
|
||||
continue;
|
||||
|
|
@ -1312,18 +1387,18 @@ function searchKb(snapshot, args) {
|
|||
}
|
||||
|
||||
// src/netx/kb-runtime.ts
|
||||
var STORE_KEY2 = Symbol.for("dsh-netxops.kb-store");
|
||||
function store2() {
|
||||
var STORE_KEY3 = Symbol.for("dsh-netxops.kb-store");
|
||||
function store3() {
|
||||
const root = globalThis;
|
||||
let current = root[STORE_KEY2];
|
||||
let current = root[STORE_KEY3];
|
||||
if (current === undefined) {
|
||||
current = { snapshot: unconfiguredKbSnapshot(), listeners: new Set };
|
||||
root[STORE_KEY2] = current;
|
||||
root[STORE_KEY3] = current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
function getKbContext() {
|
||||
const snap = store2().snapshot;
|
||||
const snap = store3().snapshot;
|
||||
return {
|
||||
...snap,
|
||||
content: { ...snap.content },
|
||||
|
|
@ -1331,7 +1406,7 @@ function getKbContext() {
|
|||
};
|
||||
}
|
||||
function watchKbContext(listener) {
|
||||
const state = store2();
|
||||
const state = store3();
|
||||
state.listeners.add(listener);
|
||||
return () => {
|
||||
state.listeners.delete(listener);
|
||||
|
|
@ -1358,15 +1433,15 @@ function toLosslessJson(value) {
|
|||
|
||||
// src/netx/tool-mount-status.ts
|
||||
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname as dirname4, join as join7 } from "node:path";
|
||||
import { homedir as homedir2 } from "node:os";
|
||||
import { dirname as dirname4, join as join8 } from "node:path";
|
||||
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
||||
function store3() {
|
||||
function store4() {
|
||||
const root = globalThis;
|
||||
return root[KEY] ??= { mounts: new Map, contexts: new WeakMap, sequence: 0 };
|
||||
}
|
||||
function getToolMountId(ctx, source) {
|
||||
const state = store3();
|
||||
const state = store4();
|
||||
let id = state.contexts.get(ctx);
|
||||
if (id === undefined) {
|
||||
id = ++state.sequence;
|
||||
|
|
@ -1375,7 +1450,7 @@ function getToolMountId(ctx, source) {
|
|||
return `${source}:${id}`;
|
||||
}
|
||||
function getToolMountStatuses() {
|
||||
return [...store3().mounts.values()].map((row) => ({ ...row, groups: [...row.groups], names: [...row.names] }));
|
||||
return [...store4().mounts.values()].map((row) => ({ ...row, groups: [...row.groups], names: [...row.names] }));
|
||||
}
|
||||
function getToolMountStatus() {
|
||||
const rows = getToolMountStatuses();
|
||||
|
|
@ -1383,18 +1458,18 @@ function getToolMountStatus() {
|
|||
}
|
||||
function persist() {
|
||||
try {
|
||||
const path = join7(process.env.DSH_HOME?.trim() || join7(homedir(), ".dsh"), "netxops-mount-status.json");
|
||||
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
||||
mkdirSync2(dirname4(path), { recursive: true });
|
||||
writeFileSync2(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
||||
`, "utf8");
|
||||
} catch {}
|
||||
}
|
||||
function publishToolMountStatus(next) {
|
||||
store3().mounts.set(next.id ?? next.source ?? next.plane, { ...next, groups: [...next.groups], names: [...next.names] });
|
||||
store4().mounts.set(next.id ?? next.source ?? next.plane, { ...next, groups: [...next.groups], names: [...next.names] });
|
||||
persist();
|
||||
}
|
||||
function removeToolMountStatus(id) {
|
||||
store3().mounts.delete(id);
|
||||
store4().mounts.delete(id);
|
||||
persist();
|
||||
}
|
||||
|
||||
|
|
@ -1591,13 +1666,13 @@ function registerKbLocalTools(ctx, snapshot = getKbContext(), options = {}) {
|
|||
|
||||
// src/netx/kb-pack-skills.ts
|
||||
import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
||||
import { join as join8 } from "node:path";
|
||||
import { join as join9 } from "node:path";
|
||||
function kbPackSkillsEnabled(snapshot) {
|
||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||
}
|
||||
function resolveKbSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||
return join8(snapshot.realRoot, rel);
|
||||
return join9(snapshot.realRoot, rel);
|
||||
}
|
||||
function kbPackSkillFooter(snapshot) {
|
||||
return [
|
||||
|
|
@ -1619,7 +1694,7 @@ function resolveKbLocalSkillsRoot(snapshot) {
|
|||
const rel = snapshot.paths.localSkills?.trim();
|
||||
if (!rel)
|
||||
return null;
|
||||
return join8(snapshot.realRoot, rel);
|
||||
return join9(snapshot.realRoot, rel);
|
||||
}
|
||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||
let entries;
|
||||
|
|
@ -1638,7 +1713,7 @@ async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options)
|
|||
for (const entry of entries) {
|
||||
if (options.signal?.aborted)
|
||||
break;
|
||||
const full = join8(root, entry);
|
||||
const full = join9(root, entry);
|
||||
let isDir = false;
|
||||
try {
|
||||
isDir = (await stat2(full)).isDirectory();
|
||||
|
|
@ -1735,73 +1810,10 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
};
|
||||
}
|
||||
|
||||
// src/netx/http.ts
|
||||
function normalizeApiUrl(raw) {
|
||||
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
||||
if (!trimmed)
|
||||
return { ok: false, error: "empty" };
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(trimmed);
|
||||
} catch {
|
||||
return { ok: false, error: "invalid_url" };
|
||||
}
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||
return { ok: false, error: "unsupported_scheme" };
|
||||
}
|
||||
if (parsed.username || parsed.password) {
|
||||
return { ok: false, error: "credentials_in_url" };
|
||||
}
|
||||
const path = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, "");
|
||||
const search = parsed.search && parsed.search !== "?" ? parsed.search : "";
|
||||
return { ok: true, url: `${parsed.origin}${path}${search}` };
|
||||
}
|
||||
|
||||
// src/netx/runtime.ts
|
||||
var STORE_KEY3 = Symbol.for("dsh-netxops.connection-store");
|
||||
function store4() {
|
||||
const root = globalThis;
|
||||
let current = root[STORE_KEY3];
|
||||
if (current === undefined) {
|
||||
current = { connection: undefined, listeners: new Set };
|
||||
root[STORE_KEY3] = current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
function getNetxConnection() {
|
||||
return store4().connection;
|
||||
}
|
||||
function provisionalNetxConnection() {
|
||||
const envUrl = process.env.NETX_API_URL?.trim() ?? "";
|
||||
const normalized = envUrl ? normalizeApiUrl(envUrl) : normalizeApiUrl("http://127.0.0.1:8890");
|
||||
return {
|
||||
toolsEnabled: false,
|
||||
apiUrl: normalized.ok ? normalized.url : "http://127.0.0.1:8890",
|
||||
token: process.env.NETX_API_TOKEN?.trim() ?? "",
|
||||
lang: typeof process.env.NETX_LANG === "string" && process.env.NETX_LANG.trim() ? process.env.NETX_LANG.trim() : "zh",
|
||||
toolCallTimeoutMs: 120000,
|
||||
groups: DEFAULT_CAPABILITY_GROUPS,
|
||||
disabledTools: [],
|
||||
readOnlyOnly: false,
|
||||
groupKbInPreset: true,
|
||||
groupKbPublic: false
|
||||
};
|
||||
}
|
||||
function requireNetxConnection() {
|
||||
return getNetxConnection() ?? provisionalNetxConnection();
|
||||
}
|
||||
function watchNetxConnection(listener) {
|
||||
const state = store4();
|
||||
state.listeners.add(listener);
|
||||
return () => {
|
||||
state.listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
// src/netx/tools.ts
|
||||
import { readFileSync as readFileSync5 } from "node:fs";
|
||||
import { dirname as dirname5, join as join9 } from "node:path";
|
||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||
import { dirname as dirname5, join as join10 } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||
|
||||
// src/netx/mcp-schema.ts
|
||||
|
|
@ -1903,8 +1915,8 @@ function parametersFromInputSchema(schema) {
|
|||
// src/netx/tools.ts
|
||||
function packageVersion() {
|
||||
try {
|
||||
const here = dirname5(fileURLToPath2(import.meta.url));
|
||||
const pkg = JSON.parse(readFileSync5(join9(here, "..", "package.json"), "utf8"));
|
||||
const here = dirname5(fileURLToPath(import.meta.url));
|
||||
const pkg = JSON.parse(readFileSync5(join10(here, "..", "package.json"), "utf8"));
|
||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||
} catch {
|
||||
return "0";
|
||||
|
|
@ -2035,8 +2047,8 @@ function registerNetxTools(ctx, connection, options) {
|
|||
// src/netx/group-tools-plugin.ts
|
||||
function packageVersion2() {
|
||||
try {
|
||||
const here = dirname6(fileURLToPath3(import.meta.url));
|
||||
const pkg = JSON.parse(readFileSync6(join10(here, "..", "package.json"), "utf8"));
|
||||
const here = dirname6(fileURLToPath2(import.meta.url));
|
||||
const pkg = JSON.parse(readFileSync6(join11(here, "..", "package.json"), "utf8"));
|
||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||
} catch {
|
||||
return "0";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue