skillex 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +31 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +51 -15
- package/dist/markdown.d.ts +7 -0
- package/dist/markdown.js +193 -0
- package/dist/ui.d.ts +10 -5
- package/dist/ui.js +7 -4
- package/dist/web-ui.d.ts +31 -0
- package/dist/web-ui.js +461 -0
- package/dist-ui/assets/CatalogPage-B_qic36n.js +1 -0
- package/dist-ui/assets/SkillDetailPage-BJ3onKk4.js +1 -0
- package/dist-ui/assets/index-DN-z--cR.js +25 -0
- package/dist-ui/assets/index-UBECch6X.css +1 -0
- package/dist-ui/index.html +16 -0
- package/package.json +15 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.1] - 2026-04-08
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Web UI: translate all remaining Portuguese strings to English (`No sources.`, `Sync history`, `Error`/`Done` toasts, `Visible`, `Catalog`, `Skill detail`, `Version`, `Installed`/`Yes`/`No`, `Compatibility`, `Instructions`, `Instructions unavailable`)
|
|
14
|
+
- Web UI dark theme: lift background and surface colors from near-black to a more comfortable dark grey; raise `--text-muted` and `--text-dim` contrast for better readability
|
|
15
|
+
|
|
16
|
+
## [0.3.0] - 2026-04-08
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `skillex ui` now launches a local HTTP server serving the interactive Vue/Vite SPA instead of a terminal TUI
|
|
20
|
+
- New Vue 3 + Vite single-page application under `ui/` with catalog browser, skill detail pages, and router-based navigation
|
|
21
|
+
- `src/web-ui.ts`: self-contained HTTP server with `/api/skills` and `/api/catalog` JSON endpoints and graceful shutdown
|
|
22
|
+
- `src/markdown.ts`: shared Markdown-to-HTML renderer used by the web API
|
|
23
|
+
- Terminal browser detection: `skillex ui` opens the local server in the platform default browser or a detected terminal viewer
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- `src/cli.ts`: `ui` command now delegates to the local web-UI server
|
|
27
|
+
- `src/ui.ts`: updated to detect and delegate to terminal browser when available
|
|
28
|
+
- Build: `npm run build` now runs `build:ui` (Vite) followed by `tsc`
|
|
29
|
+
|
|
10
30
|
## [0.2.5] - 2026-04-08
|
|
11
31
|
|
|
12
32
|
### Added
|
package/README.md
CHANGED
|
@@ -71,6 +71,23 @@ npm install -D skillex
|
|
|
71
71
|
npx skillex <command>
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### Local Web UI development
|
|
75
|
+
|
|
76
|
+
When working on the repository itself, the browser UI is a standalone Vue 3 + Vite frontend under [`ui/`](/Users/lgili/Documents/01%20-%20Codes/01%20-%20Github/Skill/ui). The CLI serves the built assets from [`dist-ui/`](/Users/lgili/Documents/01%20-%20Codes/01%20-%20Github/Skill/dist-ui) and keeps all install/remove/update/sync logic in the local TypeScript backend.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install
|
|
80
|
+
npm run build:ui
|
|
81
|
+
npm run build
|
|
82
|
+
node ./bin/skillex.js ui
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For frontend-only iteration:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run dev:ui
|
|
89
|
+
```
|
|
90
|
+
|
|
74
91
|
---
|
|
75
92
|
|
|
76
93
|
## Commands
|
|
@@ -259,12 +276,25 @@ skillex run git-master:cleanup --yes # skip confirmation
|
|
|
259
276
|
|
|
260
277
|
---
|
|
261
278
|
|
|
279
|
+
### Default terminal browser
|
|
280
|
+
|
|
281
|
+
Running `skillex` with no subcommand now opens the interactive terminal browser by default.
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
skillex
|
|
285
|
+
skillex browse
|
|
286
|
+
skillex tui
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
262
291
|
### `ui`
|
|
263
292
|
|
|
264
|
-
Open
|
|
293
|
+
Open the local Web UI in your browser.
|
|
265
294
|
|
|
266
295
|
```bash
|
|
267
296
|
skillex ui
|
|
297
|
+
skillex ui --global
|
|
268
298
|
```
|
|
269
299
|
|
|
270
300
|
---
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { listAdapters } from "./adapters.js";
|
|
3
|
-
import { computeCatalogCacheKey,
|
|
3
|
+
import { computeCatalogCacheKey, readCatalogCache, searchCatalogSkills, } from "./catalog.js";
|
|
4
4
|
import { DEFAULT_INSTALL_SCOPE, getScopedStatePaths } from "./config.js";
|
|
5
5
|
import { addProjectSource, getInstalledSkills, initProject, installSkills, listProjectSources, loadProjectCatalogs, removeProjectSource, removeSkills, resolveProjectSource, syncInstalledSkills, updateInstalledSkills, } from "./install.js";
|
|
6
6
|
import * as output from "./output.js";
|
|
7
7
|
import { setVerbose } from "./output.js";
|
|
8
8
|
import { parseSkillCommandReference, runSkillScript } from "./runner.js";
|
|
9
9
|
import { runInteractiveUi } from "./ui.js";
|
|
10
|
+
import { startWebUiServer } from "./web-ui.js";
|
|
10
11
|
import { CliError } from "./types.js";
|
|
11
12
|
import { VALID_CONFIG_KEYS, readUserConfig, writeUserConfig } from "./user-config.js";
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -124,7 +125,9 @@ Options:
|
|
|
124
125
|
|
|
125
126
|
Example:
|
|
126
127
|
skillex run git-master:cleanup --yes`,
|
|
127
|
-
|
|
128
|
+
browse: `Usage: skillex browse [options]
|
|
129
|
+
skillex tui [options]
|
|
130
|
+
skillex [options]
|
|
128
131
|
|
|
129
132
|
Open the interactive terminal browser to browse and install skills.
|
|
130
133
|
|
|
@@ -133,7 +136,21 @@ Options:
|
|
|
133
136
|
--no-cache Bypass local catalog cache
|
|
134
137
|
|
|
135
138
|
Example:
|
|
136
|
-
skillex
|
|
139
|
+
skillex
|
|
140
|
+
skillex browse`,
|
|
141
|
+
ui: `Usage: skillex ui [options]
|
|
142
|
+
|
|
143
|
+
Open the local Web UI in your browser.
|
|
144
|
+
|
|
145
|
+
Options:
|
|
146
|
+
--repo <owner/repo> GitHub repository
|
|
147
|
+
--scope <scope> local or global (default: local)
|
|
148
|
+
--global Shortcut for --scope global
|
|
149
|
+
--no-cache Bypass local catalog cache
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
skillex ui
|
|
153
|
+
skillex ui --global`,
|
|
137
154
|
status: `Usage: skillex status [options]
|
|
138
155
|
|
|
139
156
|
Show the installation status of the selected scope.
|
|
@@ -206,9 +223,14 @@ export async function main(argv) {
|
|
|
206
223
|
if (userConfig.githubToken && !process.env.GITHUB_TOKEN) {
|
|
207
224
|
process.env.GITHUB_TOKEN = userConfig.githubToken;
|
|
208
225
|
}
|
|
226
|
+
const resolvedCommand = resolveCommandRoute(command);
|
|
227
|
+
if (flags.help === true && !command) {
|
|
228
|
+
printHelp();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
209
231
|
// Per-command --help
|
|
210
|
-
if (flags.help === true &&
|
|
211
|
-
const helpText = COMMAND_HELP[
|
|
232
|
+
if (flags.help === true && resolvedCommand && resolvedCommand !== "help") {
|
|
233
|
+
const helpText = COMMAND_HELP[resolvedCommand];
|
|
212
234
|
if (helpText) {
|
|
213
235
|
output.info(helpText);
|
|
214
236
|
}
|
|
@@ -217,13 +239,13 @@ export async function main(argv) {
|
|
|
217
239
|
}
|
|
218
240
|
return;
|
|
219
241
|
}
|
|
220
|
-
// Resolve command aliases
|
|
221
|
-
const resolvedCommand = resolveAlias(command);
|
|
222
242
|
switch (resolvedCommand) {
|
|
223
243
|
case "help":
|
|
224
|
-
case undefined:
|
|
225
244
|
printHelp();
|
|
226
245
|
return;
|
|
246
|
+
case "browse":
|
|
247
|
+
await handleBrowse(flags, userConfig);
|
|
248
|
+
return;
|
|
227
249
|
case "init":
|
|
228
250
|
await handleInit(flags, userConfig);
|
|
229
251
|
return;
|
|
@@ -249,7 +271,7 @@ export async function main(argv) {
|
|
|
249
271
|
await handleRun(positionals, flags, userConfig);
|
|
250
272
|
return;
|
|
251
273
|
case "ui":
|
|
252
|
-
await
|
|
274
|
+
await handleWebUi(flags, userConfig);
|
|
253
275
|
return;
|
|
254
276
|
case "status":
|
|
255
277
|
await handleStatus(flags, userConfig);
|
|
@@ -439,12 +461,11 @@ async function handleRun(positionals, flags, userConfig) {
|
|
|
439
461
|
process.exitCode = exitCode;
|
|
440
462
|
}
|
|
441
463
|
}
|
|
442
|
-
async function
|
|
464
|
+
async function handleBrowse(flags, userConfig) {
|
|
443
465
|
const options = commonOptions(flags, userConfig);
|
|
444
466
|
const state = await getInstalledSkills(options);
|
|
445
|
-
const source = await resolveProjectSource(options);
|
|
446
467
|
output.statusLine("Fetching catalog...");
|
|
447
|
-
const catalog = await
|
|
468
|
+
const catalog = await loadProjectCatalogs({ ...options, ...cacheOptions(options) });
|
|
448
469
|
output.clearStatus();
|
|
449
470
|
if (catalog.skills.length === 0) {
|
|
450
471
|
output.info("No skills available in the catalog.");
|
|
@@ -477,6 +498,15 @@ async function handleUi(flags, userConfig) {
|
|
|
477
498
|
}
|
|
478
499
|
printAutoSyncResult(installResult?.autoSync ?? removeResult?.autoSync ?? null);
|
|
479
500
|
}
|
|
501
|
+
async function handleWebUi(flags, userConfig) {
|
|
502
|
+
const options = commonOptions(flags, userConfig);
|
|
503
|
+
const session = await startWebUiServer(options);
|
|
504
|
+
output.success(`Skillex Web UI running at ${session.url}`);
|
|
505
|
+
if (!session.opened) {
|
|
506
|
+
output.warn("Could not open the browser automatically. Open the URL above manually.");
|
|
507
|
+
}
|
|
508
|
+
output.info("Press Ctrl+C to stop the local server.");
|
|
509
|
+
}
|
|
480
510
|
async function handleStatus(flags, userConfig) {
|
|
481
511
|
const options = commonOptions(flags, userConfig);
|
|
482
512
|
const state = await getInstalledSkills(options);
|
|
@@ -737,13 +767,17 @@ async function handleConfig(positionals, flags) {
|
|
|
737
767
|
// ---------------------------------------------------------------------------
|
|
738
768
|
// Helpers
|
|
739
769
|
// ---------------------------------------------------------------------------
|
|
740
|
-
function
|
|
770
|
+
export function resolveCommandRoute(command) {
|
|
741
771
|
const ALIASES = {
|
|
742
772
|
ls: "list",
|
|
743
773
|
rm: "remove",
|
|
744
774
|
uninstall: "remove",
|
|
775
|
+
tui: "browse",
|
|
745
776
|
};
|
|
746
|
-
|
|
777
|
+
if (command === undefined) {
|
|
778
|
+
return "browse";
|
|
779
|
+
}
|
|
780
|
+
return ALIASES[command] ?? command;
|
|
747
781
|
}
|
|
748
782
|
function commonOptions(flags, userConfig = {}) {
|
|
749
783
|
const options = {
|
|
@@ -861,6 +895,7 @@ function printHelp() {
|
|
|
861
895
|
output.info(`skillex — AI agent skill manager
|
|
862
896
|
|
|
863
897
|
Commands:
|
|
898
|
+
skillex open the terminal browser
|
|
864
899
|
skillex init [--repo owner/repo] [--ref main]
|
|
865
900
|
skillex list [--json]
|
|
866
901
|
skillex search [query] [--compatibility claude] [--tag git]
|
|
@@ -868,10 +903,11 @@ Commands:
|
|
|
868
903
|
skillex install --all
|
|
869
904
|
skillex update [skill-id...]
|
|
870
905
|
skillex remove <skill-id...> aliases: rm, uninstall
|
|
906
|
+
skillex browse aliases: tui
|
|
871
907
|
skillex source <add|remove|list> [...]
|
|
872
908
|
skillex sync [--adapter id] [--dry-run] [--mode copy]
|
|
873
909
|
skillex run <skill-id:command> [--yes] [--timeout 30]
|
|
874
|
-
skillex ui
|
|
910
|
+
skillex ui open local Web UI
|
|
875
911
|
skillex status [--json]
|
|
876
912
|
skillex doctor [--json]
|
|
877
913
|
skillex config set <key> <value>
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
function escapeHtml(value) {
|
|
2
|
+
return value
|
|
3
|
+
.replaceAll("&", "&")
|
|
4
|
+
.replaceAll("<", "<")
|
|
5
|
+
.replaceAll(">", ">")
|
|
6
|
+
.replaceAll('"', """)
|
|
7
|
+
.replaceAll("'", "'");
|
|
8
|
+
}
|
|
9
|
+
function renderInlineMarkdown(value) {
|
|
10
|
+
let html = escapeHtml(value);
|
|
11
|
+
html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, (_match, label, url) => {
|
|
12
|
+
const safeLabel = escapeHtml(label);
|
|
13
|
+
const safeUrl = escapeHtml(url);
|
|
14
|
+
return `<a href="${safeUrl}" target="_blank" rel="noreferrer noopener">${safeLabel}</a>`;
|
|
15
|
+
});
|
|
16
|
+
html = html.replace(/`([^`]+)`/g, (_match, code) => `<code>${escapeHtml(code)}</code>`);
|
|
17
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
18
|
+
html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
19
|
+
return html;
|
|
20
|
+
}
|
|
21
|
+
function flushParagraph(paragraph, html) {
|
|
22
|
+
if (paragraph.length === 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
html.push(`<p>${paragraph.map((line) => renderInlineMarkdown(line)).join("<br>")}</p>`);
|
|
26
|
+
paragraph.length = 0;
|
|
27
|
+
}
|
|
28
|
+
function closeList(currentList, html) {
|
|
29
|
+
if (currentList.type) {
|
|
30
|
+
html.push(`</${currentList.type}>`);
|
|
31
|
+
currentList.type = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isTableSeparator(line) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return trimmed
|
|
40
|
+
.slice(1, -1)
|
|
41
|
+
.split("|")
|
|
42
|
+
.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
|
|
43
|
+
}
|
|
44
|
+
function splitTableRow(line) {
|
|
45
|
+
return line
|
|
46
|
+
.trim()
|
|
47
|
+
.slice(1, -1)
|
|
48
|
+
.split("|")
|
|
49
|
+
.map((cell) => renderInlineMarkdown(cell.trim()));
|
|
50
|
+
}
|
|
51
|
+
function consumeTable(lines, startIndex) {
|
|
52
|
+
const headerLine = lines[startIndex]?.trim();
|
|
53
|
+
const separatorLine = lines[startIndex + 1]?.trim();
|
|
54
|
+
if (!headerLine || !separatorLine || !headerLine.startsWith("|") || !headerLine.endsWith("|") || !isTableSeparator(separatorLine)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const headers = splitTableRow(headerLine);
|
|
58
|
+
const rows = [];
|
|
59
|
+
let index = startIndex + 2;
|
|
60
|
+
while (index < lines.length) {
|
|
61
|
+
const candidate = lines[index]?.trim();
|
|
62
|
+
if (!candidate || !candidate.startsWith("|") || !candidate.endsWith("|")) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
rows.push(splitTableRow(candidate));
|
|
66
|
+
index += 1;
|
|
67
|
+
}
|
|
68
|
+
const html = [
|
|
69
|
+
"<div class=\"markdown-table-wrap\">",
|
|
70
|
+
"<table>",
|
|
71
|
+
"<thead>",
|
|
72
|
+
"<tr>",
|
|
73
|
+
...headers.map((header) => `<th>${header}</th>`),
|
|
74
|
+
"</tr>",
|
|
75
|
+
"</thead>",
|
|
76
|
+
"<tbody>",
|
|
77
|
+
...rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`),
|
|
78
|
+
"</tbody>",
|
|
79
|
+
"</table>",
|
|
80
|
+
"</div>",
|
|
81
|
+
].join("");
|
|
82
|
+
return {
|
|
83
|
+
html,
|
|
84
|
+
nextIndex: index - 1,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Renders a safe HTML representation for common Markdown constructs used by skills.
|
|
89
|
+
*
|
|
90
|
+
* @param markdown - Raw markdown source.
|
|
91
|
+
* @returns Sanitized HTML string.
|
|
92
|
+
*/
|
|
93
|
+
export function renderMarkdownToHtml(markdown) {
|
|
94
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
95
|
+
const html = [];
|
|
96
|
+
const paragraph = [];
|
|
97
|
+
const currentList = { type: null };
|
|
98
|
+
let inCodeFence = false;
|
|
99
|
+
let codeFenceLanguage = "";
|
|
100
|
+
const codeFenceLines = [];
|
|
101
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
102
|
+
const line = lines[index] ?? "";
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
if (trimmed.startsWith("```")) {
|
|
105
|
+
flushParagraph(paragraph, html);
|
|
106
|
+
closeList(currentList, html);
|
|
107
|
+
if (inCodeFence) {
|
|
108
|
+
const languageClass = codeFenceLanguage ? ` class="language-${escapeHtml(codeFenceLanguage)}"` : "";
|
|
109
|
+
html.push(`<pre><code${languageClass}>${escapeHtml(codeFenceLines.join("\n"))}</code></pre>`);
|
|
110
|
+
codeFenceLines.length = 0;
|
|
111
|
+
codeFenceLanguage = "";
|
|
112
|
+
inCodeFence = false;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
inCodeFence = true;
|
|
116
|
+
codeFenceLanguage = trimmed.slice(3).trim();
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (inCodeFence) {
|
|
121
|
+
codeFenceLines.push(line);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const table = consumeTable(lines, index);
|
|
125
|
+
if (table) {
|
|
126
|
+
flushParagraph(paragraph, html);
|
|
127
|
+
closeList(currentList, html);
|
|
128
|
+
html.push(table.html);
|
|
129
|
+
index = table.nextIndex;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!trimmed) {
|
|
133
|
+
flushParagraph(paragraph, html);
|
|
134
|
+
closeList(currentList, html);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
138
|
+
if (headingMatch) {
|
|
139
|
+
flushParagraph(paragraph, html);
|
|
140
|
+
closeList(currentList, html);
|
|
141
|
+
const level = headingMatch[1]?.length ?? 1;
|
|
142
|
+
html.push(`<h${level}>${renderInlineMarkdown(headingMatch[2] ?? "")}</h${level}>`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const unorderedMatch = trimmed.match(/^[-*+]\s+(.+)$/);
|
|
146
|
+
if (unorderedMatch) {
|
|
147
|
+
flushParagraph(paragraph, html);
|
|
148
|
+
if (currentList.type !== "ul") {
|
|
149
|
+
closeList(currentList, html);
|
|
150
|
+
currentList.type = "ul";
|
|
151
|
+
html.push("<ul>");
|
|
152
|
+
}
|
|
153
|
+
html.push(`<li>${renderInlineMarkdown(unorderedMatch[1] ?? "")}</li>`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const orderedMatch = trimmed.match(/^\d+\.\s+(.+)$/);
|
|
157
|
+
if (orderedMatch) {
|
|
158
|
+
flushParagraph(paragraph, html);
|
|
159
|
+
if (currentList.type !== "ol") {
|
|
160
|
+
closeList(currentList, html);
|
|
161
|
+
currentList.type = "ol";
|
|
162
|
+
html.push("<ol>");
|
|
163
|
+
}
|
|
164
|
+
html.push(`<li>${renderInlineMarkdown(orderedMatch[1] ?? "")}</li>`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const quoteMatch = trimmed.match(/^>\s?(.*)$/);
|
|
168
|
+
if (quoteMatch) {
|
|
169
|
+
flushParagraph(paragraph, html);
|
|
170
|
+
closeList(currentList, html);
|
|
171
|
+
const quoteLines = [renderInlineMarkdown(quoteMatch[1] ?? "")];
|
|
172
|
+
while (index + 1 < lines.length) {
|
|
173
|
+
const next = lines[index + 1]?.trim() ?? "";
|
|
174
|
+
const nextQuote = next.match(/^>\s?(.*)$/);
|
|
175
|
+
if (!nextQuote) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
quoteLines.push(renderInlineMarkdown(nextQuote[1] ?? ""));
|
|
179
|
+
index += 1;
|
|
180
|
+
}
|
|
181
|
+
html.push(`<blockquote><p>${quoteLines.join("<br>")}</p></blockquote>`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
paragraph.push(trimmed);
|
|
185
|
+
}
|
|
186
|
+
if (inCodeFence) {
|
|
187
|
+
const languageClass = codeFenceLanguage ? ` class="language-${escapeHtml(codeFenceLanguage)}"` : "";
|
|
188
|
+
html.push(`<pre><code${languageClass}>${escapeHtml(codeFenceLines.join("\n"))}</code></pre>`);
|
|
189
|
+
}
|
|
190
|
+
flushParagraph(paragraph, html);
|
|
191
|
+
closeList(currentList, html);
|
|
192
|
+
return html.join("\n");
|
|
193
|
+
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -17,21 +17,26 @@ interface UiPrompts {
|
|
|
17
17
|
}) => Promise<string[]>) | undefined;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Filters catalog skills for the interactive
|
|
20
|
+
* Filters catalog skills for the interactive terminal browser using a case-insensitive text query.
|
|
21
21
|
*
|
|
22
22
|
* @param skills - Catalog skills.
|
|
23
23
|
* @param query - Search text.
|
|
24
24
|
* @returns Filtered skills in their original order.
|
|
25
25
|
*/
|
|
26
|
-
export declare function filterCatalogForUi(skills:
|
|
26
|
+
export declare function filterCatalogForUi<T extends SkillManifest>(skills: T[], query: string): T[];
|
|
27
27
|
/**
|
|
28
|
-
* Runs the interactive terminal flow used by `skillex
|
|
28
|
+
* Runs the interactive terminal browser flow used by `skillex`, `skillex browse`, and `skillex tui`.
|
|
29
29
|
*
|
|
30
30
|
* @param options - UI state and optional prompt overrides.
|
|
31
31
|
* @returns Selected, installable, and removable skill ids.
|
|
32
32
|
*/
|
|
33
|
-
export declare function runInteractiveUi
|
|
34
|
-
|
|
33
|
+
export declare function runInteractiveUi<T extends SkillManifest & {
|
|
34
|
+
source?: {
|
|
35
|
+
repo: string;
|
|
36
|
+
label?: string | undefined;
|
|
37
|
+
};
|
|
38
|
+
}>(options: {
|
|
39
|
+
skills: T[];
|
|
35
40
|
installedIds: string[];
|
|
36
41
|
prompts?: UiPrompts | undefined;
|
|
37
42
|
}): Promise<{
|
package/dist/ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filters catalog skills for the interactive
|
|
2
|
+
* Filters catalog skills for the interactive terminal browser using a case-insensitive text query.
|
|
3
3
|
*
|
|
4
4
|
* @param skills - Catalog skills.
|
|
5
5
|
* @param query - Search text.
|
|
@@ -16,7 +16,7 @@ export function filterCatalogForUi(skills, query) {
|
|
|
16
16
|
.includes(normalized));
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Runs the interactive terminal flow used by `skillex
|
|
19
|
+
* Runs the interactive terminal browser flow used by `skillex`, `skillex browse`, and `skillex tui`.
|
|
20
20
|
*
|
|
21
21
|
* @param options - UI state and optional prompt overrides.
|
|
22
22
|
* @returns Selected, installable, and removable skill ids.
|
|
@@ -39,9 +39,12 @@ export async function runInteractiveUi(options) {
|
|
|
39
39
|
choices: filteredSkills.map((skill) => {
|
|
40
40
|
const tags = (skill.tags ?? []).slice(0, 4).join(", ");
|
|
41
41
|
const detail = tags || (skill.description ?? "").slice(0, 55);
|
|
42
|
+
const source = skill.source && (skill.source.label || skill.source.repo)
|
|
43
|
+
? ` · ${skill.source.label || skill.source.repo}`
|
|
44
|
+
: "";
|
|
42
45
|
const label = detail
|
|
43
|
-
? `${skill.name} (${skill.id}) · ${detail}`
|
|
44
|
-
: `${skill.name} (${skill.id})`;
|
|
46
|
+
? `${skill.name} (${skill.id}) · ${detail}${source}`
|
|
47
|
+
: `${skill.name} (${skill.id})${source}`;
|
|
45
48
|
return {
|
|
46
49
|
name: label,
|
|
47
50
|
value: skill.id,
|
package/dist/web-ui.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import type { CatalogLoader, ProjectOptions, SkillDownloader, SourcedSkillManifest } from "./types.js";
|
|
3
|
+
type BrowserOpener = (url: string) => Promise<void>;
|
|
4
|
+
type SkillBodyLoader = (skill: SourcedSkillManifest, context: {
|
|
5
|
+
options: ProjectOptions;
|
|
6
|
+
}) => Promise<string>;
|
|
7
|
+
export interface WebUiServerOptions extends ProjectOptions {
|
|
8
|
+
host?: string | undefined;
|
|
9
|
+
port?: number | undefined;
|
|
10
|
+
autoOpen?: boolean | undefined;
|
|
11
|
+
openBrowser?: BrowserOpener | undefined;
|
|
12
|
+
catalogLoader?: CatalogLoader | undefined;
|
|
13
|
+
downloader?: SkillDownloader | undefined;
|
|
14
|
+
skillBodyLoader?: SkillBodyLoader | undefined;
|
|
15
|
+
}
|
|
16
|
+
export interface WebUiSession {
|
|
17
|
+
url: string;
|
|
18
|
+
opened: boolean;
|
|
19
|
+
close: () => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Starts a loopback-only local Web UI server for Skillex and optionally opens the browser.
|
|
23
|
+
*/
|
|
24
|
+
export declare function startWebUiServer(options?: WebUiServerOptions): Promise<WebUiSession>;
|
|
25
|
+
/**
|
|
26
|
+
* Creates the HTTP request handler used by the local Web UI server.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createWebUiHandler(options: WebUiServerOptions & {
|
|
29
|
+
token: string;
|
|
30
|
+
}): (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
|
31
|
+
export {};
|