skillex 0.2.4 → 0.3.0
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 +29 -0
- package/README.md +45 -23
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +67 -25
- package/dist/install.js +148 -35
- package/dist/markdown.d.ts +7 -0
- package/dist/markdown.js +193 -0
- package/dist/sync.js +90 -4
- package/dist/types.d.ts +16 -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-BVGkKQWg.js +1 -0
- package/dist-ui/assets/SkillDetailPage-CbIWg9lz.js +1 -0
- package/dist-ui/assets/index-DXrW27jF.js +25 -0
- package/dist-ui/assets/index-K8hfZs7_.css +1 -0
- package/dist-ui/index.html +16 -0
- package/package.json +15 -5
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 {};
|
package/dist/web-ui.js
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { readFile, stat } from "node:fs/promises";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { listAdapters, resolveAdapterState } from "./adapters.js";
|
|
8
|
+
import { buildRawGitHubUrl } from "./catalog.js";
|
|
9
|
+
import { getScopedStatePaths } from "./config.js";
|
|
10
|
+
import { readJson, readText } from "./fs.js";
|
|
11
|
+
import { fetchText } from "./http.js";
|
|
12
|
+
import { addProjectSource, getInstalledSkills, installSkills, listProjectSources, loadProjectCatalogs, removeProjectSource, removeSkills, syncInstalledSkills, updateInstalledSkills, } from "./install.js";
|
|
13
|
+
import { renderMarkdownToHtml } from "./markdown.js";
|
|
14
|
+
import { CliError } from "./types.js";
|
|
15
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const UI_BOOTSTRAP_MARKER = "\"__SKILLEX_BOOTSTRAP__\"";
|
|
17
|
+
function toProjectOptions(options) {
|
|
18
|
+
const projectOptions = {
|
|
19
|
+
cwd: options.cwd || process.cwd(),
|
|
20
|
+
scope: options.scope,
|
|
21
|
+
};
|
|
22
|
+
if (options.repo)
|
|
23
|
+
projectOptions.repo = options.repo;
|
|
24
|
+
if (options.ref)
|
|
25
|
+
projectOptions.ref = options.ref;
|
|
26
|
+
if (options.catalogPath)
|
|
27
|
+
projectOptions.catalogPath = options.catalogPath;
|
|
28
|
+
if (options.skillsDir)
|
|
29
|
+
projectOptions.skillsDir = options.skillsDir;
|
|
30
|
+
if (options.catalogUrl !== undefined)
|
|
31
|
+
projectOptions.catalogUrl = options.catalogUrl;
|
|
32
|
+
if (options.cacheDir)
|
|
33
|
+
projectOptions.cacheDir = options.cacheDir;
|
|
34
|
+
if (options.noCache !== undefined)
|
|
35
|
+
projectOptions.noCache = options.noCache;
|
|
36
|
+
if (options.agentSkillsDir)
|
|
37
|
+
projectOptions.agentSkillsDir = options.agentSkillsDir;
|
|
38
|
+
if (options.adapter)
|
|
39
|
+
projectOptions.adapter = options.adapter;
|
|
40
|
+
if (options.autoSync !== undefined)
|
|
41
|
+
projectOptions.autoSync = options.autoSync;
|
|
42
|
+
if (options.dryRun !== undefined)
|
|
43
|
+
projectOptions.dryRun = options.dryRun;
|
|
44
|
+
if (options.mode)
|
|
45
|
+
projectOptions.mode = options.mode;
|
|
46
|
+
if (options.trust !== undefined)
|
|
47
|
+
projectOptions.trust = options.trust;
|
|
48
|
+
if (options.yes !== undefined)
|
|
49
|
+
projectOptions.yes = options.yes;
|
|
50
|
+
if (options.timeout !== undefined)
|
|
51
|
+
projectOptions.timeout = options.timeout;
|
|
52
|
+
if (options.now)
|
|
53
|
+
projectOptions.now = options.now;
|
|
54
|
+
if (options.verbose !== undefined)
|
|
55
|
+
projectOptions.verbose = options.verbose;
|
|
56
|
+
if (options.onProgress)
|
|
57
|
+
projectOptions.onProgress = options.onProgress;
|
|
58
|
+
return projectOptions;
|
|
59
|
+
}
|
|
60
|
+
function readRequestedScope(value) {
|
|
61
|
+
return value === "global" ? "global" : "local";
|
|
62
|
+
}
|
|
63
|
+
function withRequestScope(options, scopeValue) {
|
|
64
|
+
return {
|
|
65
|
+
...options,
|
|
66
|
+
scope: readRequestedScope(scopeValue),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Starts a loopback-only local Web UI server for Skillex and optionally opens the browser.
|
|
71
|
+
*/
|
|
72
|
+
export async function startWebUiServer(options = {}) {
|
|
73
|
+
const host = options.host || "127.0.0.1";
|
|
74
|
+
const port = options.port || 0;
|
|
75
|
+
const token = randomBytes(18).toString("hex");
|
|
76
|
+
const server = createServer(createWebUiHandler({ ...options, token }));
|
|
77
|
+
await new Promise((resolve, reject) => {
|
|
78
|
+
server.once("error", reject);
|
|
79
|
+
server.listen(port, host, () => resolve());
|
|
80
|
+
});
|
|
81
|
+
const address = server.address();
|
|
82
|
+
if (!address || typeof address === "string") {
|
|
83
|
+
throw new CliError("Failed to determine local Web UI address.", "WEB_UI_ADDRESS_ERROR");
|
|
84
|
+
}
|
|
85
|
+
const url = `http://${host}:${address.port}/?token=${encodeURIComponent(token)}`;
|
|
86
|
+
let opened = false;
|
|
87
|
+
if (options.autoOpen !== false) {
|
|
88
|
+
try {
|
|
89
|
+
await (options.openBrowser || openBrowser)(url);
|
|
90
|
+
opened = true;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
opened = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
url,
|
|
98
|
+
opened,
|
|
99
|
+
close: async () => {
|
|
100
|
+
await closeServer(server);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Creates the HTTP request handler used by the local Web UI server.
|
|
106
|
+
*/
|
|
107
|
+
export function createWebUiHandler(options) {
|
|
108
|
+
return async (request, response) => {
|
|
109
|
+
await handleRequest(request, response, options).catch((error) => {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
sendJson(response, 500, {
|
|
112
|
+
error: {
|
|
113
|
+
message,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function handleRequest(request, response, context) {
|
|
120
|
+
const method = request.method || "GET";
|
|
121
|
+
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
122
|
+
const pathname = url.pathname;
|
|
123
|
+
const asset = method === "GET" ? await loadStaticAsset(pathname).catch(() => null) : null;
|
|
124
|
+
if (asset) {
|
|
125
|
+
sendBuffer(response, 200, asset.body, asset.contentType);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (method === "GET" && (pathname === "/" || pathname.startsWith("/skills/"))) {
|
|
129
|
+
if (url.searchParams.get("token") !== context.token) {
|
|
130
|
+
sendHtml(response, 403, "<h1>Forbidden</h1><p>Missing or invalid local session token.</p>");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
sendHtml(response, 200, await renderAppShell({
|
|
134
|
+
token: context.token,
|
|
135
|
+
initialScope: readRequestedScope(url.searchParams.get("scope")),
|
|
136
|
+
initialSkillId: pathname.startsWith("/skills/") ? decodeURIComponent(pathname.slice("/skills/".length)) : null,
|
|
137
|
+
}));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!pathname.startsWith("/api/")) {
|
|
141
|
+
sendJson(response, 404, { error: { message: "Not found" } });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (request.headers["x-skillex-token"] !== context.token) {
|
|
145
|
+
sendJson(response, 401, { error: { message: "Missing or invalid Skillex session token." } });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (method === "GET" && pathname === "/api/state") {
|
|
149
|
+
sendJson(response, 200, await buildDashboardState(withRequestScope(context, url.searchParams.get("scope"))));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (method === "GET" && pathname === "/api/catalog") {
|
|
153
|
+
sendJson(response, 200, await buildCatalogResponse(withRequestScope(context, url.searchParams.get("scope"))));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (method === "GET" && pathname.startsWith("/api/catalog/")) {
|
|
157
|
+
const skillId = decodeURIComponent(pathname.slice("/api/catalog/".length));
|
|
158
|
+
sendJson(response, 200, await buildSkillDetail(skillId, withRequestScope(context, url.searchParams.get("scope"))));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (method === "GET" && pathname === "/api/sources") {
|
|
162
|
+
sendJson(response, 200, await listProjectSources(withRequestScope(context, url.searchParams.get("scope"))));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (method === "POST" && pathname === "/api/install") {
|
|
166
|
+
const body = await readRequestBody(request);
|
|
167
|
+
const installOptions = withRequestScope(toProjectOptions(context), body.scope);
|
|
168
|
+
if (body.repo)
|
|
169
|
+
installOptions.repo = body.repo;
|
|
170
|
+
if (body.ref)
|
|
171
|
+
installOptions.ref = body.ref;
|
|
172
|
+
const result = await installSkills(body.skillIds || [], {
|
|
173
|
+
...installOptions,
|
|
174
|
+
...(body.all ? { installAll: true } : {}),
|
|
175
|
+
...(context.catalogLoader ? { catalogLoader: context.catalogLoader } : {}),
|
|
176
|
+
...(context.downloader ? { downloader: context.downloader } : {}),
|
|
177
|
+
});
|
|
178
|
+
sendJson(response, 200, result);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (method === "POST" && pathname === "/api/remove") {
|
|
182
|
+
const body = await readRequestBody(request);
|
|
183
|
+
sendJson(response, 200, await removeSkills(body.skillIds || [], withRequestScope(toProjectOptions(context), body.scope)));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (method === "POST" && pathname === "/api/update") {
|
|
187
|
+
const body = await readRequestBody(request);
|
|
188
|
+
sendJson(response, 200, await updateInstalledSkills(body.skillIds || [], {
|
|
189
|
+
...withRequestScope(toProjectOptions(context), body.scope),
|
|
190
|
+
...(context.catalogLoader ? { catalogLoader: context.catalogLoader } : {}),
|
|
191
|
+
...(context.downloader ? { downloader: context.downloader } : {}),
|
|
192
|
+
}));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (method === "POST" && pathname === "/api/sync") {
|
|
196
|
+
const body = await readRequestBody(request);
|
|
197
|
+
const syncOptions = withRequestScope(toProjectOptions(context), body.scope);
|
|
198
|
+
if (body.adapter)
|
|
199
|
+
syncOptions.adapter = body.adapter;
|
|
200
|
+
if (body.dryRun !== undefined)
|
|
201
|
+
syncOptions.dryRun = body.dryRun;
|
|
202
|
+
if (body.mode)
|
|
203
|
+
syncOptions.mode = body.mode;
|
|
204
|
+
sendJson(response, 200, await syncInstalledSkills(syncOptions));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (method === "POST" && pathname === "/api/sources") {
|
|
208
|
+
const body = await readRequestBody(request);
|
|
209
|
+
if (!body.repo) {
|
|
210
|
+
throw new CliError("Provide owner/repo to add a source.", "WEB_UI_SOURCE_REQUIRES_REPO");
|
|
211
|
+
}
|
|
212
|
+
sendJson(response, 200, await addProjectSource({
|
|
213
|
+
repo: body.repo,
|
|
214
|
+
...(body.ref ? { ref: body.ref } : {}),
|
|
215
|
+
...(body.label ? { label: body.label } : {}),
|
|
216
|
+
}, withRequestScope(toProjectOptions(context), body.scope)));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (method === "DELETE" && pathname.startsWith("/api/sources/")) {
|
|
220
|
+
const repo = decodeURIComponent(pathname.slice("/api/sources/".length));
|
|
221
|
+
sendJson(response, 200, await removeProjectSource(repo, withRequestScope(toProjectOptions(context), url.searchParams.get("scope"))));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
sendJson(response, 404, { error: { message: "Route not found" } });
|
|
225
|
+
}
|
|
226
|
+
async function buildDashboardState(options) {
|
|
227
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
228
|
+
const scope = options.scope || "local";
|
|
229
|
+
const statePaths = getScopedStatePaths(cwd, {
|
|
230
|
+
scope,
|
|
231
|
+
baseDir: options.agentSkillsDir,
|
|
232
|
+
});
|
|
233
|
+
const lockfile = await getInstalledSkills({ ...options, cwd });
|
|
234
|
+
const sources = await listProjectSources({ ...options, cwd });
|
|
235
|
+
const adapters = lockfile?.adapters
|
|
236
|
+
? lockfile.adapters
|
|
237
|
+
: await resolveAdapterState({
|
|
238
|
+
cwd,
|
|
239
|
+
...(options.adapter ? { adapter: options.adapter } : {}),
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
scope,
|
|
243
|
+
cwd,
|
|
244
|
+
stateDir: statePaths.stateDir,
|
|
245
|
+
initialized: Boolean(lockfile),
|
|
246
|
+
sources,
|
|
247
|
+
installed: Object.entries(lockfile?.installed || {}).map(([id, metadata]) => ({
|
|
248
|
+
id,
|
|
249
|
+
...metadata,
|
|
250
|
+
})),
|
|
251
|
+
adapters: {
|
|
252
|
+
active: adapters.active,
|
|
253
|
+
detected: adapters.detected,
|
|
254
|
+
supported: listAdapters(),
|
|
255
|
+
},
|
|
256
|
+
settings: {
|
|
257
|
+
autoSync: lockfile?.settings.autoSync ?? true,
|
|
258
|
+
},
|
|
259
|
+
sync: lockfile?.sync || null,
|
|
260
|
+
syncHistory: lockfile?.syncHistory || {},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function buildCatalogResponse(options) {
|
|
264
|
+
const catalog = await loadProjectCatalogs(options, options.catalogLoader);
|
|
265
|
+
const state = await getInstalledSkills(options);
|
|
266
|
+
const installedIds = new Set(Object.keys(state?.installed || {}));
|
|
267
|
+
return {
|
|
268
|
+
formatVersion: catalog.formatVersion,
|
|
269
|
+
sources: catalog.sources,
|
|
270
|
+
skills: catalog.skills.map((skill) => ({
|
|
271
|
+
...skill,
|
|
272
|
+
installed: installedIds.has(skill.id),
|
|
273
|
+
})),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async function buildSkillDetail(skillId, options) {
|
|
277
|
+
const catalog = await buildCatalogResponse(options);
|
|
278
|
+
const skill = catalog.skills.find((entry) => entry.id === skillId);
|
|
279
|
+
if (!skill) {
|
|
280
|
+
throw new CliError(`Skill "${skillId}" not found in the configured catalog sources.`, "WEB_UI_SKILL_NOT_FOUND");
|
|
281
|
+
}
|
|
282
|
+
let instructionsMarkdown = "";
|
|
283
|
+
let instructionsError = null;
|
|
284
|
+
try {
|
|
285
|
+
instructionsMarkdown = await loadSkillInstructions(skill, options);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
instructionsError = error instanceof Error ? error.message : String(error);
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
skill,
|
|
292
|
+
instructionsMarkdown,
|
|
293
|
+
instructionsHtml: instructionsMarkdown
|
|
294
|
+
? renderMarkdownToHtml(instructionsMarkdown)
|
|
295
|
+
: "<p>Instructions are currently unavailable for this skill.</p>",
|
|
296
|
+
instructionsError,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async function loadSkillInstructions(skill, options) {
|
|
300
|
+
if (options.skillBodyLoader) {
|
|
301
|
+
return options.skillBodyLoader(skill, { options });
|
|
302
|
+
}
|
|
303
|
+
const localContent = await readInstalledSkillInstructions(skill.id, options);
|
|
304
|
+
if (localContent) {
|
|
305
|
+
return localContent;
|
|
306
|
+
}
|
|
307
|
+
const remotePath = skill.path ? path.posix.join(skill.path, skill.entry) : skill.entry;
|
|
308
|
+
return fetchText(buildRawGitHubUrl(skill.source.repo, skill.source.ref, remotePath), {
|
|
309
|
+
headers: { Accept: "text/plain" },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async function readInstalledSkillInstructions(skillId, options) {
|
|
313
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
314
|
+
const state = await getInstalledSkills({ ...options, cwd });
|
|
315
|
+
const metadata = state?.installed?.[skillId];
|
|
316
|
+
if (!metadata) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const skillDir = path.isAbsolute(metadata.path) ? metadata.path : path.resolve(cwd, metadata.path);
|
|
320
|
+
const manifest = (await readJson(path.join(skillDir, "skill.json"), {})) || {};
|
|
321
|
+
const entry = manifest.entry || "SKILL.md";
|
|
322
|
+
return readText(path.join(skillDir, entry), null);
|
|
323
|
+
}
|
|
324
|
+
async function readRequestBody(request) {
|
|
325
|
+
const chunks = [];
|
|
326
|
+
for await (const chunk of request) {
|
|
327
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
328
|
+
}
|
|
329
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
330
|
+
if (!raw) {
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
return JSON.parse(raw);
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
throw new CliError("Invalid JSON body in Web UI request.", "WEB_UI_INVALID_JSON");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function renderAppShell(bootstrap) {
|
|
341
|
+
const uiDistDir = await resolveUiDistDir();
|
|
342
|
+
const html = await readFile(path.join(uiDistDir, "index.html"), "utf8").catch(() => {
|
|
343
|
+
throw new CliError("Web UI assets are missing. Build the frontend with `npm run build:ui` before launching `skillex ui` from the repository checkout.", "WEB_UI_ASSETS_MISSING");
|
|
344
|
+
});
|
|
345
|
+
const serializedBootstrap = JSON.stringify(bootstrap).replace(/</g, "\\u003c");
|
|
346
|
+
if (!html.includes(UI_BOOTSTRAP_MARKER)) {
|
|
347
|
+
throw new CliError("The built Web UI index is missing the bootstrap marker.", "WEB_UI_BOOTSTRAP_MARKER_MISSING");
|
|
348
|
+
}
|
|
349
|
+
return html.replace(UI_BOOTSTRAP_MARKER, serializedBootstrap);
|
|
350
|
+
}
|
|
351
|
+
async function loadStaticAsset(pathname) {
|
|
352
|
+
const uiDistDir = await resolveUiDistDir();
|
|
353
|
+
const relativePath = pathname.replace(/^\/+/, "");
|
|
354
|
+
if (!relativePath || relativePath === "index.html" || relativePath.startsWith("api/")) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const filePath = path.resolve(uiDistDir, relativePath);
|
|
358
|
+
if (!isPathInside(filePath, uiDistDir)) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const body = await readFile(filePath).catch(() => null);
|
|
362
|
+
if (!body) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
body,
|
|
367
|
+
contentType: inferContentType(filePath),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async function resolveUiDistDir() {
|
|
371
|
+
const candidates = [
|
|
372
|
+
path.resolve(MODULE_DIR, "..", "dist-ui"),
|
|
373
|
+
path.resolve(MODULE_DIR, "..", "..", "dist-ui"),
|
|
374
|
+
path.resolve(process.cwd(), "dist-ui"),
|
|
375
|
+
];
|
|
376
|
+
for (const candidate of candidates) {
|
|
377
|
+
const info = await stat(candidate).catch(() => null);
|
|
378
|
+
if (info?.isDirectory()) {
|
|
379
|
+
return candidate;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return candidates[0];
|
|
383
|
+
}
|
|
384
|
+
function isPathInside(candidate, root) {
|
|
385
|
+
const normalizedRoot = `${path.resolve(root)}${path.sep}`;
|
|
386
|
+
const normalizedCandidate = path.resolve(candidate);
|
|
387
|
+
return normalizedCandidate === path.resolve(root) || normalizedCandidate.startsWith(normalizedRoot);
|
|
388
|
+
}
|
|
389
|
+
function inferContentType(filePath) {
|
|
390
|
+
switch (path.extname(filePath)) {
|
|
391
|
+
case ".css":
|
|
392
|
+
return "text/css; charset=utf-8";
|
|
393
|
+
case ".js":
|
|
394
|
+
return "text/javascript; charset=utf-8";
|
|
395
|
+
case ".json":
|
|
396
|
+
return "application/json; charset=utf-8";
|
|
397
|
+
case ".svg":
|
|
398
|
+
return "image/svg+xml";
|
|
399
|
+
case ".html":
|
|
400
|
+
return "text/html; charset=utf-8";
|
|
401
|
+
case ".ico":
|
|
402
|
+
return "image/x-icon";
|
|
403
|
+
case ".png":
|
|
404
|
+
return "image/png";
|
|
405
|
+
default:
|
|
406
|
+
return "application/octet-stream";
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function openBrowser(url) {
|
|
410
|
+
if (process.platform === "darwin") {
|
|
411
|
+
await spawnDetached("open", [url]);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (process.platform === "win32") {
|
|
415
|
+
await spawnDetached("cmd", ["/c", "start", "", url]);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
await spawnDetached("xdg-open", [url]);
|
|
419
|
+
}
|
|
420
|
+
async function spawnDetached(command, args) {
|
|
421
|
+
await new Promise((resolve, reject) => {
|
|
422
|
+
const child = spawn(command, args, {
|
|
423
|
+
detached: true,
|
|
424
|
+
stdio: "ignore",
|
|
425
|
+
});
|
|
426
|
+
child.once("error", reject);
|
|
427
|
+
child.once("spawn", () => {
|
|
428
|
+
child.unref();
|
|
429
|
+
resolve();
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
async function closeServer(server) {
|
|
434
|
+
await new Promise((resolve, reject) => {
|
|
435
|
+
server.close((error) => {
|
|
436
|
+
if (error) {
|
|
437
|
+
reject(error);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
resolve();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function sendJson(response, statusCode, payload) {
|
|
445
|
+
response.statusCode = statusCode;
|
|
446
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
447
|
+
response.end(`${JSON.stringify(payload, null, 2)}\n`);
|
|
448
|
+
}
|
|
449
|
+
function sendHtml(response, statusCode, html) {
|
|
450
|
+
sendText(response, statusCode, html, "text/html; charset=utf-8");
|
|
451
|
+
}
|
|
452
|
+
function sendText(response, statusCode, body, contentType) {
|
|
453
|
+
response.statusCode = statusCode;
|
|
454
|
+
response.setHeader("content-type", contentType);
|
|
455
|
+
response.end(body);
|
|
456
|
+
}
|
|
457
|
+
function sendBuffer(response, statusCode, body, contentType) {
|
|
458
|
+
response.statusCode = statusCode;
|
|
459
|
+
response.setHeader("content-type", contentType);
|
|
460
|
+
response.end(body);
|
|
461
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as C,o as i,c as o,n as p,a as e,b as k,t as n,e as y,F as m,r as h,w as $,u as x,f as v,g as S,h as B,i as M}from"./index-DXrW27jF.js";const j={class:"skill-card-body"},L={class:"skill-card-icon-row"},T={key:0,class:"skill-installed-dot"},O={class:"skill-card-meta"},I={class:"downloads-count"},z={class:"skill-card-head"},A={class:"version-badge"},R={class:"skill-description"},V={key:0,class:"tag-block"},F={key:1,class:"tag-block tag-block-compat"},E={class:"skill-card-footer"},N={class:"skill-author"},D=["src","alt"],G=C({__name:"SkillCard",props:{skill:{},onOpen:{type:Function},onInstall:{type:Function},onRemove:{type:Function}},setup(l){const d={workflow:"icon-bg-workflow",testing:"icon-bg-testing",devops:"icon-bg-devops",tools:"icon-bg-tools",security:"icon-bg-security"},g={git:"tag-git",github:"tag-git",commit:"tag-git",testing:"tag-test",jest:"tag-test",tdd:"tag-test",test:"tag-test",docker:"tag-docker",devops:"tag-docker",yaml:"tag-docker",security:"tag-security",owasp:"tag-security",audit:"tag-security",react:"tag-react",hooks:"tag-react",python:"tag-python",docs:"tag-python"};function w(a){const t=a.tags.map(s=>s.toLowerCase());return t.some(s=>["git","github","commit","workflow"].includes(s))?"icon-bg-workflow":t.some(s=>["testing","jest","tdd","test","vitest"].includes(s))?"icon-bg-testing":t.some(s=>["docker","devops","ci","cd"].includes(s))?"icon-bg-devops":t.some(s=>["security","owasp","audit"].includes(s))?"icon-bg-security":t.some(s=>["tools","python","scraper","web"].includes(s))?"icon-bg-tools":d[a.id]??"icon-bg-default"}function f(a){return g[a.toLowerCase()]??"tag-default"}function b(a){const t=a.id.toLowerCase();return t.includes("git")||t.includes("commit")?"🌿":t.includes("test")||t.includes("jest")?"🧪":t.includes("docker")?"🐳":t.includes("security")||t.includes("guard")?"🛡️":t.includes("react")?"⚛️":t.includes("python")?"🐍":t.includes("typescript")||t.includes("ts")?"🔷":t.includes("cpp")||t.includes("c++")?"⚙️":t.includes("latex")?"📄":t.includes("create")?"✨":t.includes("code")||t.includes("review")?"🔍":t.includes("error")?"🐛":"📦"}return(a,t)=>(i(),o("article",{class:p(["skill-card",{"is-installed":l.skill.installed}]),onClick:t[3]||(t[3]=s=>l.onOpen(l.skill.id))},[e("div",j,[e("div",L,[e("div",{class:p(["skill-icon-wrap",w(l.skill)])},[k(n(b(l.skill))+" ",1),l.skill.installed?(i(),o("div",T,[...t[4]||(t[4]=[e("svg",{fill:"currentColor",viewBox:"0 0 20 20"},[e("path",{"fill-rule":"evenodd",d:"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z","clip-rule":"evenodd"})],-1)])])):y("",!0)],2),e("div",O,[t[6]||(t[6]=e("span",{class:"verified-badge"},[e("svg",{width:"10",height:"10",fill:"currentColor",viewBox:"0 0 20 20"},[e("path",{"fill-rule":"evenodd",d:"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z","clip-rule":"evenodd"})]),k(" Oficial ")],-1)),e("span",I,[t[5]||(t[5]=e("svg",{fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"})],-1)),k(" "+n(l.skill.source.repo),1)])])]),e("div",z,[e("h3",null,n(l.skill.name),1),e("span",A,"v"+n(l.skill.version),1)]),e("p",R,n(l.skill.description),1),l.skill.tags.length>0?(i(),o("div",V,[(i(!0),o(m,null,h(l.skill.tags,s=>(i(),o("span",{key:`${l.skill.id}-${s}`,class:p(["skill-tag",f(s)])},n(s),3))),128))])):y("",!0),l.skill.compatibility.length>0?(i(),o("div",F,[(i(!0),o(m,null,h(l.skill.compatibility,s=>(i(),o("span",{key:`${l.skill.id}-${s}`,class:"chip chip-accent",style:{height:"20px","font-size":"10px",padding:"0 8px"}},n(s),1))),128))])):y("",!0)]),e("div",E,[e("div",N,[e("img",{src:`https://api.dicebear.com/7.x/avataaars/svg?seed=${l.skill.author||l.skill.source.repo}`,alt:l.skill.author||l.skill.source.repo},null,8,D),k(" "+n(l.skill.author||l.skill.source.repo),1)]),e("div",{class:"skill-card-actions",onClick:t[2]||(t[2]=$(()=>{},["stop"]))},[l.skill.installed?(i(),o(m,{key:0},[t[8]||(t[8]=e("span",{class:"installed-label"},"Instalado",-1)),e("button",{class:"remove-btn",type:"button",title:"Remover",onClick:t[0]||(t[0]=s=>l.onRemove(l.skill.id))},[...t[7]||(t[7]=[e("svg",{fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})],-1)])])],64)):(i(),o("button",{key:1,class:"button button-primary",style:{"min-height":"28px",padding:"0 12px","font-size":"0.75rem"},type:"button",onClick:t[1]||(t[1]=s=>l.onInstall(l.skill))},[...t[9]||(t[9]=[e("svg",{width:"11",height:"11",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2.5",d:"M12 4v16m8-8H4"})],-1),k(" Instalar ",-1)])]))])])],2))}}),H={class:"page-column"},P={class:"panel"},W={class:"catalog-overview"},Y={class:"overview-card"},q={class:"overview-card"},J={class:"overview-card"},K={class:"category-pills"},Q=["onClick"],U={key:0,class:"category-pill-count"},X={key:0,class:"empty-state"},Z={width:"40",height:"40",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",style:{color:"var(--text-dim)","margin-bottom":"4px"}},_={key:1,class:"catalog-grid"},st=C({__name:"CatalogPage",setup(l){const d=x(),g=B("all"),w=[{id:"all",name:"Todas",icon:"✦"},{id:"workflow",name:"Workflow",icon:"🌿"},{id:"testing",name:"Testing",icon:"🧪"},{id:"security",name:"Security",icon:"🛡️"},{id:"devops",name:"DevOps",icon:"🐳"},{id:"tools",name:"Tools",icon:"🔧"}];function f(t,s){const r=[t,...s].map(u=>u.toLowerCase()).join(" ");return/git|commit|workflow|branch/.test(r)?"workflow":/test|jest|tdd|vitest|spec/.test(r)?"testing":/security|owasp|audit|guard/.test(r)?"security":/docker|devops|ci|cd|helm/.test(r)?"devops":/python|scraper|tool|docs|web/.test(r)?"tools":"other"}function b(t){var s,r;return t==="all"?((s=d.state.catalog)==null?void 0:s.skills.length)??0:(((r=d.state.catalog)==null?void 0:r.skills)??[]).filter(u=>f(u.id,u.tags)===t).length}const a=S(()=>{const t=d.filteredSkills.value;return g.value==="all"?t:t.filter(s=>f(s.id,s.tags)===g.value)});return(t,s)=>{var r,u;return i(),o("section",H,[e("div",P,[s[3]||(s[3]=e("div",{class:"panel-head"},[e("div",{class:"panel-head-title"},[e("p",{class:"eyebrow"},"Discovery"),e("h2",null,"Marketplace"),e("p",null,"Habilidades validadas para o seu agente de IA.")])],-1)),e("div",W,[e("article",Y,[s[0]||(s[0]=e("span",null,"Visíveis",-1)),e("strong",null,n(a.value.length),1)]),e("article",q,[s[1]||(s[1]=e("span",null,"Sources",-1)),e("strong",null,n(((r=v(d).state.catalog)==null?void 0:r.sources.length)??0),1)]),e("article",J,[s[2]||(s[2]=e("span",null,"Instaladas",-1)),e("strong",null,n(((u=v(d).state.dashboard)==null?void 0:u.installed.length)??0),1)])]),e("div",K,[(i(),o(m,null,h(w,c=>e("button",{key:c.id,class:p(["category-pill",{active:g.value===c.id}]),type:"button",onClick:tt=>g.value=c.id},[e("span",null,n(c.icon),1),k(" "+n(c.name)+" ",1),c.id!=="all"?(i(),o("span",U,n(b(c.id)),1)):y("",!0)],10,Q)),64))])]),a.value.length===0?(i(),o("div",X,[(i(),o("svg",Z,[...s[4]||(s[4]=[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"1.5",d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"},null,-1)])])),s[5]||(s[5]=e("strong",null,"Nenhuma skill encontrada",-1)),s[6]||(s[6]=e("p",null,"Tente outro filtro ou adicione uma source na sidebar.",-1))])):(i(),o("div",_,[(i(!0),o(m,null,h(a.value,c=>(i(),M(G,{key:c.id,skill:c,"on-open":v(d).navigateToSkill,"on-install":v(d).installSkill,"on-remove":v(d).removeSkill},null,8,["skill","on-open","on-install","on-remove"]))),128))]))])}}});export{st as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as b,j as g,u as h,k as f,l as w,c as e,a as t,f as r,b as d,t as i,e as u,F as v,m as x,r as m,g as y,o}from"./index-DXrW27jF.js";const C={class:"page-column"},S={class:"panel"},_={class:"detail-topbar"},I={key:0,class:"detail-hero"},M={class:"detail-description"},B={class:"detail-actions"},H={key:0,class:"empty-state panel"},L={class:"panel"},V={class:"detail-metadata-grid"},j={class:"metadata-card"},z={style:{"font-family":"monospace","font-size":"0.82rem"}},D={class:"metadata-card"},N={class:"metadata-card"},$={style:{"font-family":"monospace"}},T={class:"metadata-card"},E={class:"detail-chip-sections"},A={class:"chip-row"},F={key:0,style:{"font-size":"0.8rem",color:"var(--text-dim)"}},R={class:"chip-row"},K={key:0,style:{"font-size":"0.8rem",color:"var(--text-dim)"}},P={class:"panel"},U={key:0,class:"error-card",style:{margin:"0 20px 20px"}},q=["innerHTML"],Q=b({__name:"SkillDetailPage",setup(G){const k=g(),a=h(),p=y(()=>typeof k.params.skillId=="string"?k.params.skillId:""),s=y(()=>a.state.detail);async function c(){p.value&&await a.loadSkillDetail(p.value)}return f(p,()=>void c()),w(()=>void c()),(J,l)=>(o(),e("section",C,[t("div",S,[t("div",_,[t("button",{class:"button button-ghost",type:"button",onClick:l[0]||(l[0]=n=>r(a).navigateHome())},[...l[5]||(l[5]=[t("svg",{width:"13",height:"13",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 19l-7-7 7-7"})],-1),d(" Catálogo ",-1)])]),l[6]||(l[6]=t("p",{class:"section-label"},"Detalhe da skill",-1))]),s.value?(o(),e("div",I,[t("div",null,[l[7]||(l[7]=t("p",{class:"eyebrow"},"Skill",-1)),t("h2",null,i(s.value.skill.name),1),t("p",M,i(s.value.skill.description),1)]),t("div",B,[t("button",{class:"button button-secondary",type:"button",onClick:l[1]||(l[1]=n=>r(a).updateSkill(s.value.skill.id))},[...l[8]||(l[8]=[t("svg",{width:"13",height:"13",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})],-1),d(" Update ",-1)])]),t("button",{class:"button button-ghost",type:"button",onClick:l[2]||(l[2]=n=>r(a).syncNow())},"Sync"),s.value.skill.installed?(o(),e("button",{key:1,class:"button button-danger",type:"button",onClick:l[4]||(l[4]=n=>r(a).removeSkill(s.value.skill.id))},[...l[10]||(l[10]=[t("svg",{width:"13",height:"13",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})],-1),d(" Remover ",-1)])])):(o(),e("button",{key:0,class:"button button-primary",type:"button",onClick:l[3]||(l[3]=n=>r(a).installSkill(s.value.skill))},[...l[9]||(l[9]=[t("svg",{width:"13",height:"13",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2.5",d:"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"})],-1),d(" Instalar ",-1)])]))])])):u("",!0)]),r(a).state.detailLoading?(o(),e("div",H,[...l[11]||(l[11]=[t("div",{style:{width:"28px",height:"28px","border-radius":"50%",border:"2px solid rgba(16,185,129,0.15)","border-top-color":"var(--accent)",animation:"spin 700ms linear infinite"}},null,-1),t("strong",null,"Carregando detalhes...",-1)])])):s.value?(o(),e(v,{key:1},[t("div",L,[t("div",V,[t("article",j,[l[12]||(l[12]=t("span",null,"Skill ID",-1)),t("strong",z,i(s.value.skill.id),1)]),t("article",D,[l[13]||(l[13]=t("span",null,"Source",-1)),t("strong",null,i(s.value.skill.source.label||s.value.skill.source.repo),1)]),t("article",N,[l[14]||(l[14]=t("span",null,"Versão",-1)),t("strong",$,"v"+i(s.value.skill.version),1)]),t("article",T,[l[15]||(l[15]=t("span",null,"Instalada",-1)),t("strong",{style:x(s.value.skill.installed?"color:var(--accent)":"color:var(--text-muted)")},i(s.value.skill.installed?"✓ Sim":"Não"),5)])]),t("div",E,[t("div",null,[l[16]||(l[16]=t("p",{class:"section-label",style:{margin:"0 0 8px",display:"block"}},"Tags",-1)),t("div",A,[(o(!0),e(v,null,m(s.value.skill.tags,n=>(o(),e("span",{key:n,class:"chip"},i(n),1))),128)),s.value.skill.tags.length===0?(o(),e("span",F,"—")):u("",!0)])]),t("div",null,[l[17]||(l[17]=t("p",{class:"section-label",style:{margin:"0 0 8px",display:"block"}},"Compatibilidade",-1)),t("div",R,[(o(!0),e(v,null,m(s.value.skill.compatibility,n=>(o(),e("span",{key:n,class:"chip chip-accent"},i(n),1))),128)),s.value.skill.compatibility.length===0?(o(),e("span",K,"—")):u("",!0)])])])]),t("div",P,[l[19]||(l[19]=t("div",{class:"panel-head"},[t("div",{class:"panel-head-title"},[t("p",{class:"eyebrow"},"Instruções"),t("h2",null,"SKILL.md"),t("p",null,"Conteúdo renderizado pelo backend.")])],-1)),s.value.instructionsError?(o(),e("div",U,[l[18]||(l[18]=t("strong",null,"Instruções indisponíveis",-1)),t("p",null,i(s.value.instructionsError),1)])):(o(),e("article",{key:1,class:"markdown-body",innerHTML:s.value.instructionsHtml},null,8,q))])],64)):u("",!0)]))}});export{Q as default};
|