feat(card): show cached netx skills per capability group

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
oliver 2026-10-16 23:23:40 +08:00
parent fa82f530e9
commit 056f2d4ea3
9 changed files with 285 additions and 96 deletions

View file

@ -2,7 +2,7 @@ import { createRequire } from "node:module";
var __require = /* @__PURE__ */ createRequire(import.meta.url);
// src/index.ts
import { readFileSync as readFileSync9 } from "node:fs";
import { readFileSync as readFileSync10 } from "node:fs";
import { dirname as dirname8, join as join14 } from "node:path";
import { fileURLToPath as fileURLToPath4 } from "node:url";
import z from "@deepseek-ai/schemastery";
@ -1391,7 +1391,9 @@ import {
existsSync as existsSync3,
mkdirSync as mkdirSync2,
readdirSync,
readFileSync as readFileSync3,
rmSync,
statSync as statSync2,
writeFileSync as writeFileSync3
} from "node:fs";
import { homedir as homedir3 } from "node:os";
@ -1449,6 +1451,40 @@ function replaceSkillsCache(apiUrl, files) {
`, "utf8");
return root;
}
function countFiles(dir) {
let n = 0;
for (const entry of readdirSync(dir)) {
const full = join3(dir, entry);
n += statSync2(full).isDirectory() ? countFiles(full) : 1;
}
return n;
}
function listCachedSkills(apiUrl) {
const root = skillsCacheDir(apiUrl);
const skills = [];
let listedAt = null;
if (!apiUrl.trim() || !existsSync3(root))
return { listedAt, skills };
try {
const meta = JSON.parse(readFileSync3(join3(root, ".meta.json"), "utf8"));
if (typeof meta.listedAt === "string")
listedAt = meta.listedAt;
} catch {}
for (const group of CAPABILITY_GROUP_IDS) {
const groupDir = join3(root, SKILL_DIR_BY_GROUP[group]);
if (!existsSync3(groupDir))
continue;
try {
for (const name of readdirSync(groupDir).sort()) {
const dir = join3(groupDir, name);
if (!statSync2(dir).isDirectory() || !existsSync3(join3(dir, "SKILL.md")))
continue;
skills.push({ group, name, files: countFiles(dir) });
}
} catch {}
}
return { listedAt, skills };
}
// src/netx/group-skills.ts
function parseFrontmatter(raw) {
@ -1659,11 +1695,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
}
// src/netx/kb-context-skill.ts
import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync5 } from "node:fs";
import { join as join7 } from "node:path";
// src/netx/kb-manifest.ts
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
import { basename, dirname as dirname3, join as join5, resolve } from "node:path";
var EMPTY_CONTENT = {
hasRegions: false,
@ -1710,7 +1746,7 @@ function findManifest(kbRoot, maxDepth = 3) {
}
let rootStat;
try {
rootStat = statSync2(root);
rootStat = statSync3(root);
} catch {
return { paths: [], error: `kbRoot not found: ${root}` };
}
@ -1720,7 +1756,7 @@ function findManifest(kbRoot, maxDepth = 3) {
const direct = join5(root, "MANIFEST.json");
if (existsSync4(direct)) {
try {
if (statSync2(direct).isFile())
if (statSync3(direct).isFile())
return { paths: [direct] };
} catch {}
}
@ -1731,7 +1767,7 @@ function findManifest(kbRoot, maxDepth = 3) {
const candidate = join5(dir, "MANIFEST.json");
if (existsSync4(candidate)) {
try {
if (statSync2(candidate).isFile())
if (statSync3(candidate).isFile())
found.push(candidate);
} catch {}
}
@ -1749,7 +1785,7 @@ function findManifest(kbRoot, maxDepth = 3) {
const full = join5(dir, name);
let st;
try {
st = statSync2(full);
st = statSync3(full);
} catch {
continue;
}
@ -1769,7 +1805,7 @@ function findManifest(kbRoot, maxDepth = 3) {
const full = join5(root, name);
let st;
try {
st = statSync2(full);
st = statSync3(full);
} catch {
continue;
}
@ -1859,7 +1895,7 @@ function resolveKbRoot(kbRoot, maxDepth = 3) {
return unconfiguredKbSnapshot();
let root = resolve(trimmed);
try {
const st = statSync2(root);
const st = statSync3(root);
if (st.isFile()) {
if (basename(root).toLowerCase() === "manifest.json") {
root = dirname3(root);
@ -1884,7 +1920,7 @@ function resolveKbRoot(kbRoot, maxDepth = 3) {
const manifestPath = located.paths[0];
let raw;
try {
raw = readFileSync3(manifestPath, "utf8");
raw = readFileSync4(manifestPath, "utf8");
} catch (error) {
return errorSnapshot(`cannot read MANIFEST: ${error instanceof Error ? error.message : String(error)}`);
}
@ -1926,7 +1962,7 @@ function renderKbPathTable(snapshot) {
}
// src/netx/kb-local-path.ts
import { existsSync as existsSync5, realpathSync, statSync as statSync3 } from "node:fs";
import { existsSync as existsSync5, realpathSync, statSync as statSync4 } from "node:fs";
import { join as join6, relative, resolve as resolve2, sep } from "node:path";
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
"memories",
@ -2046,7 +2082,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
throw new Error(`path not under writable roots: ${realRel}`);
}
if (!statSync3(realFile).isFile()) {
if (!statSync4(realFile).isFile()) {
throw new Error(`not a file: ${realFile}`);
}
return {
@ -2111,9 +2147,9 @@ function readKbIdentity(snapshot) {
const full = join7(dir, name);
let text;
try {
if (!statSync4(full).isFile())
if (!statSync5(full).isFile())
continue;
text = readFileSync4(full, "utf8").replace(/^\uFEFF/, "").trim();
text = readFileSync5(full, "utf8").replace(/^\uFEFF/, "").trim();
} catch {
continue;
}
@ -2239,9 +2275,9 @@ import {
existsSync as existsSync6,
mkdirSync as mkdirSync3,
readdirSync as readdirSync4,
readFileSync as readFileSync5,
readFileSync as readFileSync6,
rmSync as rmSync2,
statSync as statSync5,
statSync as statSync6,
writeFileSync as writeFileSync4
} from "node:fs";
import { dirname as dirname4, join as join8 } from "node:path";
@ -2404,7 +2440,7 @@ function walkFiles(dir, baseLocal, out) {
const full = join8(dir, name);
let st;
try {
st = statSync5(full);
st = statSync6(full);
} catch {
continue;
}
@ -2438,7 +2474,7 @@ function listLocalFiles(snapshot, args = {}) {
}
// src/netx/kb-read-ops.ts
import { readdirSync as readdirSync5, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
import { readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync as statSync7 } from "node:fs";
import { extname, join as join9, 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"]);
@ -2483,14 +2519,14 @@ function resolveKbReadPath(snapshot, pathArg) {
}
function readKbFile(snapshot, args) {
const target = resolveKbReadPath(snapshot, args.path);
const st = statSync6(target.absolutePath);
const st = statSync7(target.absolutePath);
if (st.isDirectory()) {
const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
if (entry.isDirectory())
return { name: entry.name, type: "dir" };
let size;
try {
size = statSync6(join9(target.absolutePath, entry.name)).size;
size = statSync7(join9(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);
@ -2499,7 +2535,7 @@ function readKbFile(snapshot, args) {
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
throw new Error(`not a text KB file: ${target.relativePath}`);
}
const lines = readFileSync6(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
const lines = readFileSync7(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
`).split(`
`);
const start = Math.max(1, Math.floor(args.offset ?? 1));
@ -2544,7 +2580,7 @@ function cachedText(path, mtimeMs, size) {
const hit = cache.files.get(path);
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
return hit;
const text = readFileSync6(path, "utf8").replace(/^\uFEFF/, "");
const text = readFileSync7(path, "utf8").replace(/^\uFEFF/, "");
if (hit)
cache.chars -= hit.text.length;
if (cache.chars + text.length > CACHE_MAX_CHARS) {
@ -2574,7 +2610,7 @@ function walkTextFiles(dir, out) {
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
continue;
try {
const st = statSync6(full);
const st = statSync7(full);
if (st.size <= SEARCH_MAX_FILE_BYTES)
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
} catch {}
@ -2604,7 +2640,7 @@ function searchKb(snapshot, args) {
const files = [];
const seen = new Set;
for (const dir of dirs) {
const st = statSync6(dir);
const st = statSync7(dir);
const batch = [];
if (st.isFile())
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
@ -3273,7 +3309,7 @@ function replyReminder(setting) {
// src/netx/session-export.ts
import { homedir as homedir5, hostname as osHostname } from "node:os";
// ../../netxops/node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
// node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
import { createRequire as createRequire3 } from "module";
var require2 = createRequire3("/");
var _a;
@ -4540,7 +4576,7 @@ function mountNetxopsWebRoute(ctx, channel, handler) {
}
// src/netx/tools.ts
import { readFileSync as readFileSync7 } from "node:fs";
import { readFileSync as readFileSync8 } from "node:fs";
import { dirname as dirname6, join as join12 } from "node:path";
import { fileURLToPath as fileURLToPath2 } from "node:url";
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
@ -5085,7 +5121,7 @@ async function refreshMcpCatalog(input) {
function packageVersion() {
try {
const here = dirname6(fileURLToPath2(import.meta.url));
const pkg = JSON.parse(readFileSync7(join12(here, "..", "package.json"), "utf8"));
const pkg = JSON.parse(readFileSync8(join12(here, "..", "package.json"), "utf8"));
return typeof pkg.version === "string" ? pkg.version : "0";
} catch {
return "0";
@ -5217,13 +5253,13 @@ function registerNetxTools(ctx, connection, options) {
import { createScope, scopeOf } from "@deepseek-ai/dsh-scope";
// src/netx/group-tools-plugin.ts
import { readFileSync as readFileSync8 } from "node:fs";
import { readFileSync as readFileSync9 } from "node:fs";
import { dirname as dirname7, join as join13 } from "node:path";
import { fileURLToPath as fileURLToPath3 } from "node:url";
function packageVersion2() {
try {
const here = dirname7(fileURLToPath3(import.meta.url));
const pkg = JSON.parse(readFileSync8(join13(here, "..", "package.json"), "utf8"));
const pkg = JSON.parse(readFileSync9(join13(here, "..", "package.json"), "utf8"));
return typeof pkg.version === "string" ? pkg.version : "0";
} catch {
return "0";
@ -5476,6 +5512,24 @@ function installPresetPublication(ctx) {
}
// src/index.ts
function mcpCatalogPayload(catalog) {
const cached = listCachedSkills(getNetxConnection()?.apiUrl ?? "");
return {
phase: catalog.phase,
mcpUrl: catalog.mcpUrl,
error: catalog.error,
listedAt: catalog.listedAt,
fromCache: catalog.fromCache,
tools: catalog.tools.map((tool2) => ({
name: tool2.name,
description: tool2.description,
readOnlyHint: tool2.readOnlyHint,
group: tool2.group ?? null
})),
skills: cached.skills,
skillsListedAt: cached.listedAt
};
}
var name = "netxops";
var inject = ["tools"];
var NETXOPS_RPC_CHANNEL = "/netxops";
@ -5550,7 +5604,7 @@ function resolveApiUrlForConnection(value) {
function packageVersion3() {
try {
const here = dirname8(fileURLToPath4(import.meta.url));
const pkg = JSON.parse(readFileSync9(join14(here, "..", "package.json"), "utf8"));
const pkg = JSON.parse(readFileSync10(join14(here, "..", "package.json"), "utf8"));
return typeof pkg.version === "string" ? pkg.version : "0";
} catch {
return "0";
@ -6147,23 +6201,7 @@ function apply(ctx, config = Config({})) {
};
}
if (endpoint === "tools.catalog") {
const catalog = getMcpCatalogState();
return {
ok: true,
value: {
phase: catalog.phase,
mcpUrl: catalog.mcpUrl,
error: catalog.error,
listedAt: catalog.listedAt,
fromCache: catalog.fromCache,
tools: catalog.tools.map((tool2) => ({
name: tool2.name,
description: tool2.description,
readOnlyHint: tool2.readOnlyHint,
group: tool2.group ?? null
}))
}
};
return { ok: true, value: mcpCatalogPayload(getMcpCatalogState()) };
}
if (endpoint === "tools.refresh") {
const connection = getNetxConnection();
@ -6177,14 +6215,7 @@ function apply(ctx, config = Config({})) {
});
return {
ok: catalog.phase === "connected",
value: {
phase: catalog.phase,
mcpUrl: catalog.mcpUrl,
error: catalog.error,
listedAt: catalog.listedAt,
fromCache: catalog.fromCache,
toolCount: catalog.tools.length
},
value: { ...mcpCatalogPayload(catalog), toolCount: catalog.tools.length },
...catalog.error ? { error: catalog.error } : {}
};
}