release: r017 batch Community Apps search
This commit is contained in:
@@ -76,10 +76,10 @@ Oder manuell:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# .txz von Gitea laden
|
# .txz von Gitea laden
|
||||||
curl -O http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/dist/mua-2026.08.23.r016-x86_64-1.txz
|
curl -O http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/dist/mua-2026.08.24.r017-x86_64-1.txz
|
||||||
|
|
||||||
# Installieren
|
# Installieren
|
||||||
upgradepkg --install-new mua-2026.08.23.r016-x86_64-1.txz
|
upgradepkg --install-new mua-2026.08.24.r017-x86_64-1.txz
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Service starten
|
### 2. Service starten
|
||||||
@@ -94,7 +94,7 @@ Der Service startet automatisch bei jedem Boot (SysVinit).
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://192.168.1.2:3002/health
|
curl http://192.168.1.2:3002/health
|
||||||
# → {"status":"ok","version":"2026.08.23.r016","auth":"required"}
|
# → {"status":"ok","version":"2026.08.24.r017","auth":"required"}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
BIN
Binary file not shown.
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
ini_set('display_errors', 0); // Fehler werden als JSON-RPC-Error zurückgegeben, nicht als HTML
|
ini_set('display_errors', 0); // Fehler werden als JSON-RPC-Error zurückgegeben, nicht als HTML
|
||||||
|
|
||||||
const MUA_VERSION = '2026.08.23.r016';
|
const MUA_VERSION = '2026.08.24.r017';
|
||||||
const MUA_SERVER_NAME = 'mua';
|
const MUA_SERVER_NAME = 'mua';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ const MUA_PORT = 3002;
|
|||||||
const MUA_BIND = '0.0.0.0';
|
const MUA_BIND = '0.0.0.0';
|
||||||
const MUA_SESSION_TTL = 3600; // Sekunden
|
const MUA_SESSION_TTL = 3600; // Sekunden
|
||||||
const MUA_SERVER_NAME = 'mua';
|
const MUA_SERVER_NAME = 'mua';
|
||||||
const MUA_VERSION = '2026.08.23.r016';
|
const MUA_VERSION = '2026.08.24.r017';
|
||||||
|
|
||||||
// ── Session-Management (in-memory) ───────────────────────────────────────
|
// ── Session-Management (in-memory) ───────────────────────────────────────
|
||||||
$sessions = []; // session_id => ['created' => time, 'initialized' => bool]
|
$sessions = []; // session_id => ['created' => time, 'initialized' => bool]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mua",
|
"name": "mua",
|
||||||
"version": "2026.08.23.r016",
|
"version": "2026.08.24.r017",
|
||||||
"description": "Mikes Unraid Agent - MCP over HTTP (Streamable HTTP) for Unraid",
|
"description": "Mikes Unraid Agent - MCP over HTTP (Streamable HTTP) for Unraid",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
+7
-4
@@ -2,13 +2,13 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "mua">
|
<!ENTITY name "mua">
|
||||||
<!ENTITY author "Michael">
|
<!ENTITY author "Michael">
|
||||||
<!ENTITY version "2026.08.23.r016">
|
<!ENTITY version "2026.08.24.r017">
|
||||||
<!ENTITY launch "Settings/mua">
|
<!ENTITY launch "Settings/mua">
|
||||||
<!ENTITY pluginURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/plugin/mua.plg">
|
<!ENTITY pluginURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/plugin/mua.plg">
|
||||||
<!ENTITY pluginLOC "/boot/config/plugins/&name;">
|
<!ENTITY pluginLOC "/boot/config/plugins/&name;">
|
||||||
<!ENTITY emhttpLOC "/usr/local/emhttp/plugins/&name;">
|
<!ENTITY emhttpLOC "/usr/local/emhttp/plugins/&name;">
|
||||||
<!ENTITY txzURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/dist/mua-2026.08.23.r016-x86_64-1.txz">
|
<!ENTITY txzURL "http://192.168.1.2:4000/michael/MUA-Mikes-Unraid-Agent/raw/branch/main/dist/mua-2026.08.24.r017-x86_64-1.txz">
|
||||||
<!ENTITY txzSHA256 "2fd0d75e84fcc3b7591ff4373a844f28ea686cdfc6e9fdbd9fa2da6f99654c24">
|
<!ENTITY txzSHA256 "dd95d28fdcc875ff6d1f8ae317735b724b6d192dd23e7dfd3d866fd07395c7f8">
|
||||||
]>
|
]>
|
||||||
|
|
||||||
<PLUGIN name="&name;"
|
<PLUGIN name="&name;"
|
||||||
@@ -23,6 +23,9 @@
|
|||||||
>
|
>
|
||||||
|
|
||||||
<CHANGES>
|
<CHANGES>
|
||||||
|
### 2026.08.24.r017
|
||||||
|
- Community-Applications-Suche akzeptiert bis zu fünf mit `|` getrennte Namens-, Schreib- oder Zweckvarianten in einem einzigen begrenzten Feed-Durchlauf.
|
||||||
|
- Treffer nennen die tatsächlich passenden Varianten; kleine Agenten müssen Synonyme nicht mehr in einzelnen Werkzeugrunden abfragen.
|
||||||
### 2026.08.23.r016
|
### 2026.08.23.r016
|
||||||
- Docker-Update-Status wird primär über Unraids nativen Containernamen-Schlüssel aufgelöst; Image-, `library/`- und `:latest`-Varianten bleiben als kompatible Fallbacks erhalten.
|
- Docker-Update-Status wird primär über Unraids nativen Containernamen-Schlüssel aufgelöst; Image-, `library/`- und `:latest`-Varianten bleiben als kompatible Fallbacks erhalten.
|
||||||
- Alternative Cacheformate und boolesche oder textuelle Statuswerte werden global normalisiert, statt einzelne Container wie Windows11 und WindowsXP als `unknown` zu verlieren.
|
- Alternative Cacheformate und boolesche oder textuelle Statuswerte werden global normalisiert, statt einzelne Container wie Windows11 und WindowsXP als `unknown` zu verlieren.
|
||||||
@@ -119,7 +122,7 @@ Das .txz enthält:
|
|||||||
install/doinst.sh (läuft nach Installation)
|
install/doinst.sh (läuft nach Installation)
|
||||||
===========================================
|
===========================================
|
||||||
-->
|
-->
|
||||||
<FILE Name="/boot/config/plugins/&name;/mua-2026.08.23.r016-x86_64-1.txz" Run="upgradepkg --install-new" Mode="755" Min="7.0.0">
|
<FILE Name="/boot/config/plugins/&name;/mua-2026.08.24.r017-x86_64-1.txz" Run="upgradepkg --install-new" Mode="755" Min="7.0.0">
|
||||||
<URL>&txzURL;</URL>
|
<URL>&txzURL;</URL>
|
||||||
<SHA256>&txzSHA256;</SHA256>
|
<SHA256>&txzSHA256;</SHA256>
|
||||||
</FILE>
|
</FILE>
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mua",
|
"name": "mua",
|
||||||
"author": "Michael",
|
"author": "Michael",
|
||||||
"version": "2026.08.23.r016",
|
"version": "2026.08.24.r017",
|
||||||
"minver": "7.0.0",
|
"minver": "7.0.0",
|
||||||
"pluginDirectory": "/usr/local/emhttp/plugins/mua",
|
"pluginDirectory": "/usr/local/emhttp/plugins/mua",
|
||||||
"configDirectory": "/boot/config/plugins/mua",
|
"configDirectory": "/boot/config/plugins/mua",
|
||||||
|
|||||||
+20
-5
@@ -14,7 +14,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|||||||
|
|
||||||
// ── Konstanten ──────────────────────────────────────────────────────────
|
// ── Konstanten ──────────────────────────────────────────────────────────
|
||||||
export const MUA_SERVER_NAME = "mua";
|
export const MUA_SERVER_NAME = "mua";
|
||||||
export const MUA_VERSION = "2026.08.23.r016";
|
export const MUA_VERSION = "2026.08.24.r017";
|
||||||
export const MUA_PROTOCOL_VERSION = "2025-03-26";
|
export const MUA_PROTOCOL_VERSION = "2025-03-26";
|
||||||
export const PHP_HELPER = "/usr/local/bin/unraid-docker-mcp-helper.php";
|
export const PHP_HELPER = "/usr/local/bin/unraid-docker-mcp-helper.php";
|
||||||
export const STATUS_HELPER = "/usr/local/bin/unraid-mcp-status-helper.php";
|
export const STATUS_HELPER = "/usr/local/bin/unraid-mcp-status-helper.php";
|
||||||
@@ -826,8 +826,17 @@ function caConfigSummary(app: CaApp): Record<string, unknown>[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function searchCommunityApps(query: string, limit = 10): Promise<string> {
|
export async function searchCommunityApps(query: string, limit = 10): Promise<string> {
|
||||||
const needle = query.trim().toLowerCase();
|
// A pipe-separated query lets an agent check a small set of spelling or
|
||||||
if (needle.length < 2 || needle.length > 100) throw new Error("query must contain 2-100 characters");
|
// product-name variants in one bounded feed pass instead of burning one MCP
|
||||||
|
// round per synonym. Keep the schema itself a plain string; some local
|
||||||
|
// function-call parsers are unreliable with nested array/object schemas.
|
||||||
|
const needles = query.split("|").map((term) => term.trim().toLowerCase()).filter(Boolean);
|
||||||
|
if (needles.length < 1 || needles.length > 5) {
|
||||||
|
throw new Error("query must contain 1-5 terms separated by |");
|
||||||
|
}
|
||||||
|
if (query.length > 300 || needles.some((needle) => needle.length < 2 || needle.length > 100)) {
|
||||||
|
throw new Error("each query term must contain 2-100 characters (300 total)");
|
||||||
|
}
|
||||||
limit = Math.max(1, Math.min(25, Math.floor(limit)));
|
limit = Math.max(1, Math.min(25, Math.floor(limit)));
|
||||||
const feed = await caFeed();
|
const feed = await caFeed();
|
||||||
const scored = feed.apps.map((app) => {
|
const scored = feed.apps.map((app) => {
|
||||||
@@ -835,13 +844,17 @@ export async function searchCommunityApps(query: string, limit = 10): Promise<st
|
|||||||
const repo = caText(app.Repository, 300);
|
const repo = caText(app.Repository, 300);
|
||||||
const extra = `${caText(app.Overview, 1000)} ${caText(app.Repo, 200)}`.toLowerCase();
|
const extra = `${caText(app.Overview, 1000)} ${caText(app.Repo, 200)}`.toLowerCase();
|
||||||
const lower = name.toLowerCase();
|
const lower = name.toLowerCase();
|
||||||
|
const matches = needles.map((needle) => {
|
||||||
let score = 0;
|
let score = 0;
|
||||||
if (lower === needle) score += 100;
|
if (lower === needle) score += 100;
|
||||||
else if (lower.startsWith(needle)) score += 60;
|
else if (lower.startsWith(needle)) score += 60;
|
||||||
else if (lower.includes(needle)) score += 40;
|
else if (lower.includes(needle)) score += 40;
|
||||||
if (repo.toLowerCase().includes(needle)) score += 20;
|
if (repo.toLowerCase().includes(needle)) score += 20;
|
||||||
if (extra.includes(needle)) score += 5;
|
if (extra.includes(needle)) score += 5;
|
||||||
return { app, score, name, repo };
|
return { needle, score };
|
||||||
|
}).filter((match) => match.score > 0);
|
||||||
|
const score = Math.max(0, ...matches.map((match) => match.score));
|
||||||
|
return { app, score, name, repo, matches };
|
||||||
}).filter((entry) => entry.score > 0)
|
}).filter((entry) => entry.score > 0)
|
||||||
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
|
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
|
||||||
.slice(0, limit);
|
.slice(0, limit);
|
||||||
@@ -850,10 +863,12 @@ export async function searchCommunityApps(query: string, limit = 10): Promise<st
|
|||||||
source: "Unraid Community Applications official feed",
|
source: "Unraid Community Applications official feed",
|
||||||
feed_updated: feed.updated ?? null,
|
feed_updated: feed.updated ?? null,
|
||||||
query,
|
query,
|
||||||
|
query_terms: needles,
|
||||||
result_count: scored.length,
|
result_count: scored.length,
|
||||||
results: scored.map(({ app, name, repo }) => ({
|
results: scored.map(({ app, name, repo, matches }) => ({
|
||||||
app_id: caAppId(app),
|
app_id: caAppId(app),
|
||||||
name,
|
name,
|
||||||
|
matched_terms: matches.map((match) => match.needle),
|
||||||
image: repo,
|
image: repo,
|
||||||
network: caText(app.Network, 80),
|
network: caText(app.Network, 80),
|
||||||
privileged: String(app.Privileged ?? "false").toLowerCase() === "true",
|
privileged: String(app.Privileged ?? "false").toLowerCase() === "true",
|
||||||
|
|||||||
+2
-2
@@ -286,11 +286,11 @@ export const TOOLS: ToolDef[] = [
|
|||||||
{
|
{
|
||||||
name: "unraid_ca_search",
|
name: "unraid_ca_search",
|
||||||
description:
|
description:
|
||||||
"Search the official Unraid Community Applications feed. Returns compact app metadata and a stable app_id; does not install anything.",
|
"Search the official Unraid Community Applications feed in one bounded pass. Returns compact app metadata and a stable app_id; does not install anything. Put up to five useful name/spelling/purpose variants in one query separated by | instead of making repeated synonym calls.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
query: str("Application name, image, repository or purpose"),
|
query: str("One to five application names, images, repositories or purposes separated by |"),
|
||||||
limit: int("Maximum results (1-25, default 10)"),
|
limit: int("Maximum results (1-25, default 10)"),
|
||||||
},
|
},
|
||||||
required: ["query"],
|
required: ["query"],
|
||||||
|
|||||||
Reference in New Issue
Block a user