diff --git a/modules/agent/Bootstrap.php b/modules/agent/Bootstrap.php index 3c63092..c9938ac 100644 --- a/modules/agent/Bootstrap.php +++ b/modules/agent/Bootstrap.php @@ -38,7 +38,7 @@ protected function _initAdminSettings() 'key' => 'agent-skills', 'label' => 'Agent Skills', 'icon' => 'fa-wand-magic-sparkles', - 'href' => '/agent/skills', + 'href' => '/admin/settings/agent/skills', // pretty alias (routes.ini); /agent/skills still works 'resource' => 'Agent_SkillsController', 'order' => 46, ]); diff --git a/modules/agent/configs/routes.ini b/modules/agent/configs/routes.ini new file mode 100644 index 0000000..e823abf --- /dev/null +++ b/modules/agent/configs/routes.ini @@ -0,0 +1,17 @@ +; SPDX-License-Identifier: BSD-3-Clause +; Copyright (c) 2026 WebTigers. Tiger™ and WebTigers™ are trademarks of WebTigers. +; +; Pretty admin URL for the Agent Skills manager (ROUTING.md — folds into the config cascade). The +; canonical /agent/skills path keeps working for free; this is the /admin/settings// +; alias (an interim one-off ahead of the general admin-URL normalization to /admin/settings/[module]/*). +; A static route resolves to a dispatchable controller, so the RouteOverride plugin leaves it alone even +; though it sits under the reserved /admin prefix. +[production] +resources.router.routes.agentSkillsAdmin.type = "Zend_Controller_Router_Route_Static" +resources.router.routes.agentSkillsAdmin.route = "admin/settings/agent/skills" +resources.router.routes.agentSkillsAdmin.defaults.module = "agent" +resources.router.routes.agentSkillsAdmin.defaults.controller = "skills" +resources.router.routes.agentSkillsAdmin.defaults.action = "index" +[staging : production] +[testing : production] +[development : production] diff --git a/modules/agent/services/Skills.php b/modules/agent/services/Skills.php index 767647b..103247c 100644 --- a/modules/agent/services/Skills.php +++ b/modules/agent/services/Skills.php @@ -49,6 +49,82 @@ public function installed(array $params): void $this->_success(['skills' => Tiger_Agent_Skills::installed()], null); } + /** + * DataTables source — the ONE grid: the browse catalog merged with what's installed, so a row's status + + * action controls tell you whether it's installed (like the Modules screen). Installed skills are pinned + * to the top (then active-first, then by name). Search filters name/description/provenance; `refresh` + * re-scans the sources (bypass the per-source cache). Cached scans, merged + paginated in PHP (mixed + * origins, no shared DB order). + * + * @param array $params DataTables request (+ optional `refresh`) + * @return void + */ + public function datatable(array $params): void + { + if (!$this->_isAdmin()) { $this->_error('core.api.error.not_allowed'); return; } + + $dt = $this->_dtParams($params); + $search = strtolower(trim((string) $dt['search'])); + $refresh = !empty($params['refresh']); + + // Installed skills keyed by install key (key == safeKey(source__name), which installKey() mirrors). + $installed = []; + foreach (Tiger_Agent_Skills::installed() as $s) { $installed[$s['key']] = $s; } + + $items = []; + $seen = []; + // The browse catalog (per-source cached; only the sources scan hits the network, and only on refresh). + foreach (Tiger_Skill_Index::all($refresh) as $e) { + $key = Agent_Service_Skills::installKey($e); + $inst = $installed[$key] ?? null; + $items[] = $this->_row($key, (string) $e['source'], $e['name'], $e['description'], $e['sourceLabel'], + $e['repo'], $e['ref'], $e['path'], $e['url'], $inst !== null, $inst !== null && !empty($inst['active'])); + $seen[$key] = true; + } + // Installed but not in any catalog (a pasted-URL install, or a source that's since delisted). + foreach ($installed as $key => $s) { + if (isset($seen[$key])) { continue; } + $items[] = $this->_row($key, '', $s['name'], $s['description'], $s['sourceLabel'], + (string) $s['repo'], '', '', (string) $s['url'], true, !empty($s['active'])); + } + + if ($search !== '') { + $items = array_values(array_filter($items, static function ($r) use ($search) { + return strpos(strtolower($r['name'] . ' ' . $r['description'] . ' ' . $r['sourceLabel']), $search) !== false; + })); + } + + // Pin installed to the top → active-first within installed → then by name. + usort($items, static function ($a, $b) { + if ($a['installed'] !== $b['installed']) { return $a['installed'] ? -1 : 1; } + if ($a['installed'] && $a['active'] !== $b['active']) { return $a['active'] ? -1 : 1; } + return strcasecmp($a['name'], $b['name']); + }); + + $total = count($items); + $len = ($dt['length'] > 0) ? $dt['length'] : 25; + $page = array_slice($items, (int) $dt['start'], $len); + $this->_dtResponse($dt['draw'], $total, $total, $page); + } + + /** One normalized grid row (a catalog entry and/or an installed skill). */ + private function _row($key, $source, $name, $desc, $sourceLabel, $repo, $ref, $path, $url, $installed, $active): array + { + return [ + 'key' => $key, + 'source' => $source, // adapter id — needed so an Install uses the same key + 'name' => (string) $name, + 'description' => (string) $desc, + 'sourceLabel' => (string) $sourceLabel, // provenance, NOT a vouch + 'repo' => (string) $repo, + 'ref' => (string) $ref, + 'path' => (string) $path, + 'url' => (string) $url, + 'installed' => (bool) $installed, + 'active' => (bool) $active, + ]; + } + /** * Install a skill — from a browse entry (`repo`/`ref`/`path`/`name`/`source`) or a pasted `url`. * diff --git a/modules/agent/views/scripts/skills/index.phtml b/modules/agent/views/scripts/skills/index.phtml index f9333f3..1e3fc3a 100644 --- a/modules/agent/views/scripts/skills/index.phtml +++ b/modules/agent/views/scripts/skills/index.phtml @@ -2,154 +2,186 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2026 WebTigers. Tiger™ and WebTigers™ are trademarks of WebTigers. /** - * Agent Skills manager (admin shell). Browse/search the supported skill repos (Agent_Service_Skills over - * /api), install, and manage installed skills (turn on/off, remove, view source). Tiger is NOT a trust - * authority — results show PROVENANCE and you review the SKILL.md before installing (TIGERSKILLS.md). + * Agent Skills manager — ONE DataTables grid (like the Modules screen): the browse catalog merged with + * what's installed, so a row's Status + action controls tell you whether it's installed. Installed skills + * pin to the top. Data loads from Agent_Service_Skills::datatable over /api; install / toggle / remove / + * view-source are /api calls. Tiger is NOT a trust authority — the grid shows PROVENANCE and you review the + * SKILL.md before installing (TIGERSKILLS.md §2, §5). */ ?>

Agent Skills

-

Installable know-how for the AI agent. Tiger browses these repos — it does not vouch for them; review a skill before you install and turn it on.

+

Installable know-how for the AI agent. Tiger browses these repos — it does not vouch for them; review a skill's source before you install and turn it on. Installed skills are pinned to the top.

+
+
+
-
-
-
-
Browse skills
-
-
- - -
-
- -
-
-
-
Add from a GitHub URL
-
-
- - -
-
Any repo, branch, subfolder, or a link straight to a SKILL.md.
-
+
+
+ +
+ +
+
Any repo, branch, subfolder, or a link straight to a SKILL.md — not just the listed sources.
+
-
-
-
Installed
-
-
- -
+
+
+
+ + + + + + + + + + +
SkillDescriptionSourceStatusActions
+
-
-