@terra-symposium/docs 0.2.0 → 0.4.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/README.md +7 -1
- package/bin/terra-docs.mjs +78 -23
- package/dist/lib/components/LocalDocsWorkspace.d.ts +3 -0
- package/dist/lib/components/LocalDocsWorkspace.d.ts.map +1 -1
- package/dist/lib/components/LocalDocsWorkspace.js +23 -23
- package/dist/lib/components/LocalDocsWorkspace.js.map +1 -1
- package/dist/lib/components/SiteLayout.d.ts +3 -1
- package/dist/lib/components/SiteLayout.d.ts.map +1 -1
- package/dist/lib/components/SiteLayout.js +2 -2
- package/dist/lib/components/SiteLayout.js.map +1 -1
- package/dist/lib/components/StaticWorkspace.d.ts +4 -1
- package/dist/lib/components/StaticWorkspace.d.ts.map +1 -1
- package/dist/lib/components/StaticWorkspace.js +2 -2
- package/dist/lib/components/StaticWorkspace.js.map +1 -1
- package/dist/lib/components/StrictLocalWorkspaceHarness.d.ts +5 -0
- package/dist/lib/components/StrictLocalWorkspaceHarness.d.ts.map +1 -1
- package/dist/lib/components/StrictLocalWorkspaceHarness.js +14 -1
- package/dist/lib/components/StrictLocalWorkspaceHarness.js.map +1 -1
- package/dist/lib/components/blocks.d.ts +1 -1
- package/dist/lib/components/blocks.d.ts.map +1 -1
- package/dist/lib/components/blocks.js +59 -36
- package/dist/lib/components/blocks.js.map +1 -1
- package/dist/lib/components/navigation.js +1 -1
- package/dist/lib/components/navigation.js.map +1 -1
- package/dist/lib/components/quiz-demo-fixture.d.ts +23 -0
- package/dist/lib/components/quiz-demo-fixture.d.ts.map +1 -0
- package/dist/lib/components/quiz-demo-fixture.js +22 -0
- package/dist/lib/components/quiz-demo-fixture.js.map +1 -0
- package/dist/lib/components/quiz.d.ts +51 -0
- package/dist/lib/components/quiz.d.ts.map +1 -0
- package/dist/lib/components/quiz.js +183 -0
- package/dist/lib/components/quiz.js.map +1 -0
- package/dist/lib/components/terra/Workspace.d.ts.map +1 -1
- package/dist/lib/components/terra/Workspace.js +13 -12
- package/dist/lib/components/terra/Workspace.js.map +1 -1
- package/dist/lib/components/terra/editor/ContentEditor.d.ts.map +1 -1
- package/dist/lib/components/terra/editor/ContentEditor.js +35 -35
- package/dist/lib/components/terra/editor/ContentEditor.js.map +1 -1
- package/dist/lib/components/terra/editor/_blocks.js +6 -6
- package/dist/lib/components/terra/editor/_blocks.js.map +1 -1
- package/dist/lib/components/terra/editor/docs/BlockContext.js +12 -12
- package/dist/lib/components/terra/editor/docs/BlockContext.js.map +1 -1
- package/dist/lib/components/terra/editor/docs/DocsAttachmentPicker.js +6 -6
- package/dist/lib/components/terra/editor/docs/DocsAttachmentPicker.js.map +1 -1
- package/dist/lib/components/terra/editor/docs/TerraToolbar.d.ts.map +1 -1
- package/dist/lib/components/terra/editor/docs/TerraToolbar.js +2 -1
- package/dist/lib/components/terra/editor/docs/TerraToolbar.js.map +1 -1
- package/dist/lib/components/terra/editor/docs/_CodeBlock.js +10 -10
- package/dist/lib/components/terra/editor/docs/_CodeBlock.js.map +1 -1
- package/dist/lib/components/terra/editor/terra-content.styles.d.ts +1 -1
- package/dist/lib/components/terra/editor/terra-content.styles.d.ts.map +1 -1
- package/dist/lib/components/terra/editor/terra-content.styles.js +1 -1
- package/dist/lib/components/terra/editor/terra-content.styles.js.map +1 -1
- package/dist/lib/components/terra/flow/Nodes.js +7 -7
- package/dist/lib/components/terra/flow/Nodes.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_attachments.d.ts.map +1 -1
- package/dist/lib/components/terra/pages/docs/_attachments.js +11 -11
- package/dist/lib/components/terra/pages/docs/_attachments.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_blocks.js +2 -2
- package/dist/lib/components/terra/pages/docs/_blocks.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_editable.d.ts.map +1 -1
- package/dist/lib/components/terra/pages/docs/_editable.js +2 -2
- package/dist/lib/components/terra/pages/docs/_editable.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_icons.d.ts +1 -0
- package/dist/lib/components/terra/pages/docs/_icons.d.ts.map +1 -1
- package/dist/lib/components/terra/pages/docs/_icons.js +1 -0
- package/dist/lib/components/terra/pages/docs/_icons.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_main.d.ts.map +1 -1
- package/dist/lib/components/terra/pages/docs/_main.js +48 -16
- package/dist/lib/components/terra/pages/docs/_main.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_sidebar.js +2 -2
- package/dist/lib/components/terra/pages/docs/_sidebar.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_toc.js +1 -1
- package/dist/lib/components/terra/pages/docs/_toc.js.map +1 -1
- package/dist/lib/components/terra/pages/docs/_toolbar.d.ts.map +1 -1
- package/dist/lib/components/terra/pages/docs/_toolbar.js +12 -9
- package/dist/lib/components/terra/pages/docs/_toolbar.js.map +1 -1
- package/dist/lib/components/workspace-demo-fixture.d.ts.map +1 -1
- package/dist/lib/components/workspace-demo-fixture.js +51 -53
- package/dist/lib/components/workspace-demo-fixture.js.map +1 -1
- package/dist/lib/lib/host-runtime.js +3 -3
- package/dist/lib/lib/host-runtime.js.map +1 -1
- package/dist/lib/lib/host.d.ts +4 -1
- package/dist/lib/lib/host.d.ts.map +1 -1
- package/dist/lib/lib/index.d.ts +3 -0
- package/dist/lib/lib/index.d.ts.map +1 -1
- package/dist/lib/lib/index.js +2 -0
- package/dist/lib/lib/index.js.map +1 -1
- package/dist/lib/lib/markdown-model.d.ts.map +1 -1
- package/dist/lib/lib/markdown-model.js +106 -107
- package/dist/lib/lib/markdown-model.js.map +1 -1
- package/dist/lib/lib/markdown.js +4 -4
- package/dist/lib/lib/markdown.js.map +1 -1
- package/dist/lib/lib/schema.d.ts +2 -2
- package/dist/lib/lib/terra/custom-components.d.ts +1 -1
- package/dist/lib/lib/terra/custom-components.d.ts.map +1 -1
- package/dist/lib/lib/terra/custom-components.js +2 -2
- package/dist/lib/lib/terra/custom-components.js.map +1 -1
- package/dist/lib/lib/terra/docs/editor-dom.d.ts.map +1 -1
- package/dist/lib/lib/terra/docs/editor-dom.js +2 -2
- package/dist/lib/lib/terra/docs/editor-dom.js.map +1 -1
- package/dist/lib/lib/terra/docs/workspace.d.ts +14 -12
- package/dist/lib/lib/terra/docs/workspace.d.ts.map +1 -1
- package/dist/lib/lib/terra/docs/workspace.js +5 -5
- package/dist/lib/lib/terra/docs/workspace.js.map +1 -1
- package/dist/lib/lib/terra/runtime/action-proxy.d.ts +1 -1
- package/dist/lib/lib/terra/runtime/action-proxy.d.ts.map +1 -1
- package/dist/lib/lib/terra/runtime/action-proxy.js +2 -2
- package/dist/lib/lib/terra/runtime/action-proxy.js.map +1 -1
- package/dist/lib/lib/terra/runtime/content/markdown.d.ts +0 -7
- package/dist/lib/lib/terra/runtime/content/markdown.d.ts.map +1 -1
- package/dist/lib/lib/terra/runtime/content/markdown.js +0 -7
- package/dist/lib/lib/terra/runtime/content/markdown.js.map +1 -1
- package/dist/lib/lib/terra/runtime/nodes.js +3 -3
- package/dist/lib/lib/terra/runtime/nodes.js.map +1 -1
- package/dist/lib/lib/terra/runtime/runtime.d.ts +1 -2
- package/dist/lib/lib/terra/runtime/runtime.d.ts.map +1 -1
- package/dist/lib/lib/terra/runtime/runtime.js +3 -8
- package/dist/lib/lib/terra/runtime/runtime.js.map +1 -1
- package/dist/lib/lib/terra/runtime/shared.d.ts.map +1 -1
- package/dist/lib/lib/terra/runtime/shared.js +6 -6
- package/dist/lib/lib/terra/runtime/shared.js.map +1 -1
- package/dist/lib/lib/terra/runtime/types.d.ts +6 -0
- package/dist/lib/lib/terra/runtime/types.d.ts.map +1 -1
- package/dist/lib/lib/terra/runtime/types.js +1 -0
- package/dist/lib/lib/terra/runtime/types.js.map +1 -1
- package/dist/lib/style.css +2 -2
- package/package.json +8 -12
- package/site/astro.config.mjs +27 -22
- package/site/docs-content-plugin.mjs +4 -1
- package/site/src/components/DocsSiteSearch.tsx +51 -33
- package/site/src/components/QuizDemo.tsx +62 -0
- package/site/src/lib/catalog.ts +12 -4
- package/site/src/pages/[...slug].astro +3 -9
- package/site/src/pages/quiz-demo.astro +50 -0
- package/src/components/navigation.tsx +2 -2
- package/src/components/quiz-demo-fixture.ts +23 -0
- package/bin/generate-skills.mjs +0 -188
- package/site/src/components/SkillsExplorer.tsx +0 -236
package/bin/generate-skills.mjs
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/*//////////////////////////////////////////////////
|
|
4
|
-
IMPORTS
|
|
5
|
-
//////////////////////////////////////////////////*/
|
|
6
|
-
|
|
7
|
-
// API
|
|
8
|
-
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
9
|
-
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
10
|
-
import { parse } from 'yaml';
|
|
11
|
-
|
|
12
|
-
/*//////////////////////////////////////////////////
|
|
13
|
-
CONFIG
|
|
14
|
-
//////////////////////////////////////////////////*/
|
|
15
|
-
|
|
16
|
-
const ignoredDirs = new Set([
|
|
17
|
-
'.astro',
|
|
18
|
-
'.cache',
|
|
19
|
-
'.git',
|
|
20
|
-
'.next',
|
|
21
|
-
'.nuxt',
|
|
22
|
-
'.output',
|
|
23
|
-
'.svelte-kit',
|
|
24
|
-
'.turbo',
|
|
25
|
-
'.vite',
|
|
26
|
-
'.vercel',
|
|
27
|
-
'.wrangler',
|
|
28
|
-
'build',
|
|
29
|
-
'coverage',
|
|
30
|
-
'dist',
|
|
31
|
-
'docs-dist',
|
|
32
|
-
'node_modules',
|
|
33
|
-
'target',
|
|
34
|
-
'vendor',
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
/*//////////////////////////////////////////////////
|
|
38
|
-
HELPERS
|
|
39
|
-
//////////////////////////////////////////////////*/
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Finds skill instruction files beneath a directory, skipping generated and dependency folders.
|
|
43
|
-
*
|
|
44
|
-
* @param dir Directory whose descendants should be searched.
|
|
45
|
-
* @param files Accumulator shared by recursive calls.
|
|
46
|
-
*
|
|
47
|
-
* @return Paths to the discovered `SKILL.md` files.
|
|
48
|
-
*/
|
|
49
|
-
async function findSkillFiles(dir, files = []) {
|
|
50
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
51
|
-
for (const entry of entries) {
|
|
52
|
-
if (entry.isDirectory() && ignoredDirs.has(entry.name)) continue;
|
|
53
|
-
const path = join(dir, entry.name);
|
|
54
|
-
if (entry.isDirectory()) await findSkillFiles(path, files);
|
|
55
|
-
else if (entry.isFile() && entry.name === 'SKILL.md') files.push(path);
|
|
56
|
-
}
|
|
57
|
-
return files;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Separates a skill document's Markdown body from its optional YAML frontmatter.
|
|
62
|
-
*
|
|
63
|
-
* @param source Complete skill document contents.
|
|
64
|
-
*
|
|
65
|
-
* @return Parsed metadata and body, plus a validation issue when YAML parsing fails.
|
|
66
|
-
*/
|
|
67
|
-
function splitFrontmatter(source) {
|
|
68
|
-
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?/.exec(source);
|
|
69
|
-
if (!match) return { body: source.trim(), metadata: {}, metadataIssue: undefined };
|
|
70
|
-
try {
|
|
71
|
-
const metadata = parse(match[1]);
|
|
72
|
-
return {
|
|
73
|
-
body: source.slice(match[0].length).trim(),
|
|
74
|
-
metadata:
|
|
75
|
-
metadata && typeof metadata === 'object' && !Array.isArray(metadata) ? metadata : {},
|
|
76
|
-
metadataIssue: undefined,
|
|
77
|
-
};
|
|
78
|
-
} catch (err) {
|
|
79
|
-
return {
|
|
80
|
-
body: source.slice(match[0].length).trim(),
|
|
81
|
-
metadata: {},
|
|
82
|
-
metadataIssue: err instanceof Error ? err.message : String(err),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Resolves a skill's display scope and directory from its repository-relative path.
|
|
89
|
-
*
|
|
90
|
-
* @param root Repository root used to make the skill path relative.
|
|
91
|
-
* @param path Absolute path of the skill instruction file.
|
|
92
|
-
*
|
|
93
|
-
* @return Directory and scope labels for the generated index entry.
|
|
94
|
-
*/
|
|
95
|
-
function skillLocation(root, path) {
|
|
96
|
-
const segments = relative(root, path).split(sep);
|
|
97
|
-
const skillRootIndex = segments.findIndex(
|
|
98
|
-
(segment, i) => (segment === '.agents' || segment === '.codex') && segments[i + 1] === 'skills',
|
|
99
|
-
);
|
|
100
|
-
if (skillRootIndex >= 0) {
|
|
101
|
-
const scope = segments.slice(0, skillRootIndex).join('/') || 'repository';
|
|
102
|
-
const dir = segments.slice(skillRootIndex + 2, -1).join('/') || 'root';
|
|
103
|
-
return { directory: dir, scope };
|
|
104
|
-
}
|
|
105
|
-
return { directory: segments.slice(0, -1).join('/') || 'root', scope: 'repository' };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Derives a fallback description from the first prose paragraph in a skill document.
|
|
110
|
-
*
|
|
111
|
-
* @param body Markdown body with frontmatter removed.
|
|
112
|
-
*
|
|
113
|
-
* @return The first non-heading paragraph, or an empty string when none exists.
|
|
114
|
-
*/
|
|
115
|
-
function fallbackDescription(body) {
|
|
116
|
-
return (
|
|
117
|
-
body
|
|
118
|
-
.replace(/^\s*#.+\n/, '')
|
|
119
|
-
.split(/\n\s*\n/)
|
|
120
|
-
.map((paragraph) => paragraph.replace(/\s+/g, ' ').trim())
|
|
121
|
-
.find((paragraph) => paragraph && !paragraph.startsWith('#')) ?? ''
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Converts a directory or skill name into a title-style label.
|
|
127
|
-
*
|
|
128
|
-
* @param value Slug containing words separated by hyphens or underscores.
|
|
129
|
-
*
|
|
130
|
-
* @return A human-readable label with each word capitalized.
|
|
131
|
-
*/
|
|
132
|
-
function humanize(value) {
|
|
133
|
-
return value.replace(/[-_]+/g, ' ').replace(/\b\p{L}/gu, (letter) => letter.toUpperCase());
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Builds a sorted index of repository skills and writes it as JSON.
|
|
138
|
-
*
|
|
139
|
-
* @param repoRoot Repository root whose `.agents` and `.codex` skill trees are scanned.
|
|
140
|
-
* @param outputPath Destination file for the generated skill index.
|
|
141
|
-
*
|
|
142
|
-
* @return The indexed skills, including their parsed metadata and Markdown bodies.
|
|
143
|
-
*/
|
|
144
|
-
export async function generateSkillsIndex(repoRoot, outputPath) {
|
|
145
|
-
const root = resolve(repoRoot);
|
|
146
|
-
const files = await findSkillFiles(root);
|
|
147
|
-
const skills = await Promise.all(
|
|
148
|
-
files.map(async (path) => {
|
|
149
|
-
const source = await readFile(path, 'utf8');
|
|
150
|
-
const { body, metadata, metadataIssue } = splitFrontmatter(source);
|
|
151
|
-
const location = skillLocation(root, path);
|
|
152
|
-
const dirName = location.directory.split('/').at(-1) ?? 'skill';
|
|
153
|
-
const name = typeof metadata.name === 'string' ? metadata.name.trim() : dirName;
|
|
154
|
-
const title = /^#\s+(.+?)\s*#*\s*$/m.exec(body)?.[1]?.trim() ?? humanize(name);
|
|
155
|
-
const description =
|
|
156
|
-
typeof metadata.description === 'string'
|
|
157
|
-
? metadata.description.trim()
|
|
158
|
-
: fallbackDescription(body);
|
|
159
|
-
return {
|
|
160
|
-
body,
|
|
161
|
-
description,
|
|
162
|
-
directory: location.directory,
|
|
163
|
-
metadata,
|
|
164
|
-
...(metadataIssue ? { metadataIssue } : {}),
|
|
165
|
-
name,
|
|
166
|
-
scope: location.scope,
|
|
167
|
-
sourcePath: relative(root, path).split(sep).join('/'),
|
|
168
|
-
title,
|
|
169
|
-
};
|
|
170
|
-
}),
|
|
171
|
-
);
|
|
172
|
-
skills.sort((first, second) =>
|
|
173
|
-
`${first.scope}/${first.directory}/${first.name}`.localeCompare(
|
|
174
|
-
`${second.scope}/${second.directory}/${second.name}`,
|
|
175
|
-
),
|
|
176
|
-
);
|
|
177
|
-
await mkdir(dirname(outputPath), { recursive: true });
|
|
178
|
-
await writeFile(outputPath, `${JSON.stringify(skills, null, 2)}\n`);
|
|
179
|
-
return skills;
|
|
180
|
-
}
|
|
181
|
-
if (process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname)) {
|
|
182
|
-
const root = resolve(process.argv[2] ?? process.cwd());
|
|
183
|
-
const outputPath = resolve(
|
|
184
|
-
process.argv[3] ?? join(root, 'dist/cache/skills.generated.json'),
|
|
185
|
-
);
|
|
186
|
-
const skills = await generateSkillsIndex(root, outputPath);
|
|
187
|
-
console.log(`Indexed ${skills.length} skills from ${root}`);
|
|
188
|
-
}
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* site/src/components/SkillsExplorer.tsx
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/*//////////////////////////////////////////////////
|
|
6
|
-
IMPORTS
|
|
7
|
-
//////////////////////////////////////////////////*/
|
|
8
|
-
|
|
9
|
-
// API
|
|
10
|
-
import { useMemo, useState } from 'react';
|
|
11
|
-
|
|
12
|
-
/*//////////////////////////////////////////////////
|
|
13
|
-
CONFIG
|
|
14
|
-
//////////////////////////////////////////////////*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Skill content and metadata prepared for display in the repository skills explorer.
|
|
18
|
-
*/
|
|
19
|
-
export type SkillRecord = {
|
|
20
|
-
body: string;
|
|
21
|
-
description: string;
|
|
22
|
-
directory: string;
|
|
23
|
-
metadata: Record<string, unknown>;
|
|
24
|
-
metadataIssue?: string;
|
|
25
|
-
name: string;
|
|
26
|
-
scope: string;
|
|
27
|
-
sourcePath: string;
|
|
28
|
-
title: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/*//////////////////////////////////////////////////
|
|
32
|
-
HELPERS
|
|
33
|
-
//////////////////////////////////////////////////*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Converts scalar metadata to readable text and structured metadata to JSON.
|
|
37
|
-
*
|
|
38
|
-
* @param value Metadata value parsed from a skill's YAML frontmatter.
|
|
39
|
-
*
|
|
40
|
-
* @return Text suitable for searching and displaying in the explorer.
|
|
41
|
-
*/
|
|
42
|
-
function metadataValue(value: unknown) {
|
|
43
|
-
if (typeof value === 'string') return value;
|
|
44
|
-
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
45
|
-
return JSON.stringify(value);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Formats a count with a simple English plural suffix.
|
|
50
|
-
*
|
|
51
|
-
* @param count Number of matching items.
|
|
52
|
-
* @param singular Singular label for one item.
|
|
53
|
-
*
|
|
54
|
-
* @return The count followed by the singular or plural label.
|
|
55
|
-
*/
|
|
56
|
-
function plural(count: number, singular: string) {
|
|
57
|
-
return `${count} ${singular}${count === 1 ? '' : 's'}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/*//////////////////////////////////////////////////
|
|
61
|
-
COMPONENTS
|
|
62
|
-
//////////////////////////////////////////////////*/
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Lets readers filter, select, and inspect the repository's indexed skill files.
|
|
66
|
-
*
|
|
67
|
-
* @param props Skill records built from the repository's `SKILL.md` files.
|
|
68
|
-
*
|
|
69
|
-
* @return A filterable list and the selected skill's description and content.
|
|
70
|
-
*/
|
|
71
|
-
export function SkillsExplorer({ skills }: { skills: SkillRecord[] }) {
|
|
72
|
-
const [query, setQuery] = useState('');
|
|
73
|
-
const [aPath, setActivePath] = useState(skills[0]?.sourcePath ?? '');
|
|
74
|
-
const filteredSkills = useMemo(() => {
|
|
75
|
-
const normQuery = query.trim().toLocaleLowerCase();
|
|
76
|
-
if (!normQuery) return skills;
|
|
77
|
-
return skills.filter((skill) =>
|
|
78
|
-
[
|
|
79
|
-
skill.name,
|
|
80
|
-
skill.title,
|
|
81
|
-
skill.description,
|
|
82
|
-
skill.directory,
|
|
83
|
-
skill.scope,
|
|
84
|
-
skill.sourcePath,
|
|
85
|
-
JSON.stringify(skill.metadata),
|
|
86
|
-
skill.body,
|
|
87
|
-
]
|
|
88
|
-
.join('\n')
|
|
89
|
-
.toLocaleLowerCase()
|
|
90
|
-
.includes(normQuery),
|
|
91
|
-
);
|
|
92
|
-
}, [query, skills]);
|
|
93
|
-
const selectedSkill =
|
|
94
|
-
filteredSkills.find((skill) => skill.sourcePath === aPath) ?? filteredSkills[0] ?? null;
|
|
95
|
-
const groups = useMemo(() => {
|
|
96
|
-
const byScope = new Map<string, SkillRecord[]>();
|
|
97
|
-
for (const skill of filteredSkills) {
|
|
98
|
-
const entries = byScope.get(skill.scope) ?? [];
|
|
99
|
-
entries.push(skill);
|
|
100
|
-
byScope.set(skill.scope, entries);
|
|
101
|
-
}
|
|
102
|
-
return [...byScope.entries()];
|
|
103
|
-
}, [filteredSkills]);
|
|
104
|
-
const metadataEntries = selectedSkill
|
|
105
|
-
? Object.entries(selectedSkill.metadata).filter(
|
|
106
|
-
([key]) => !['name', 'description'].includes(key),
|
|
107
|
-
)
|
|
108
|
-
: [];
|
|
109
|
-
return (
|
|
110
|
-
<section aria-label="Repository skills" className="my-8">
|
|
111
|
-
<header className="mb-4 flex flex-wrap items-end justify-between gap-6">
|
|
112
|
-
<div>
|
|
113
|
-
<p className="mb-1 text-xs font-bold uppercase tracking-wider text-gray-10">
|
|
114
|
-
Repository inventory
|
|
115
|
-
</p>
|
|
116
|
-
<h2 className="m-0">Skills</h2>
|
|
117
|
-
<p className="mb-0 mt-1 text-gray-10">
|
|
118
|
-
Browse the workflows and guidance files included in this repository.
|
|
119
|
-
</p>
|
|
120
|
-
</div>
|
|
121
|
-
<div aria-live="polite" className="grid justify-items-end text-sm text-gray-10">
|
|
122
|
-
<strong className="text-2xl leading-none text-primary-11">{skills.length}</strong>
|
|
123
|
-
<span>{plural(skills.length, 'skill')} indexed</span>
|
|
124
|
-
</div>
|
|
125
|
-
</header>
|
|
126
|
-
{skills.length ? (
|
|
127
|
-
<div className="grid min-w-0 items-start gap-4 md:grid-cols-[minmax(16rem,0.8fr)_minmax(0,1.6fr)]">
|
|
128
|
-
<nav
|
|
129
|
-
aria-label="Available skills"
|
|
130
|
-
className="max-h-[78vh] min-w-0 overflow-auto rounded-xl border border-gray-6 bg-gray-1 p-3"
|
|
131
|
-
>
|
|
132
|
-
<div>
|
|
133
|
-
<label className="text-sm font-medium text-gray-11" htmlFor="skills-explorer-filter">
|
|
134
|
-
Filter skills
|
|
135
|
-
</label>
|
|
136
|
-
<input
|
|
137
|
-
className="mt-1 w-full rounded-md border border-gray-6 bg-gray-1 px-3 py-2 text-sm text-gray-12 shadow-sm outline-none placeholder:text-gray-9 focus-visible:ring-2 focus-visible:ring-primary-8"
|
|
138
|
-
id="skills-explorer-filter"
|
|
139
|
-
onChange={(event) => setQuery(event.currentTarget.value)}
|
|
140
|
-
placeholder="Name, topic, or text"
|
|
141
|
-
type="search"
|
|
142
|
-
value={query}
|
|
143
|
-
/>
|
|
144
|
-
</div>
|
|
145
|
-
<p aria-live="polite" className="mb-4 mt-2 text-xs text-gray-10">
|
|
146
|
-
Showing {filteredSkills.length} of {skills.length}
|
|
147
|
-
</p>
|
|
148
|
-
{groups.length ? (
|
|
149
|
-
groups.map(([scope, entries]) => (
|
|
150
|
-
<section aria-label={scope} className="mt-4 first:mt-0" key={scope}>
|
|
151
|
-
<h3 className="mb-2 break-words text-xs font-bold uppercase text-gray-10">
|
|
152
|
-
{scope}
|
|
153
|
-
</h3>
|
|
154
|
-
<ul className="m-0 grid list-none gap-1 p-0">
|
|
155
|
-
{entries.map((skill) => (
|
|
156
|
-
<li key={skill.sourcePath}>
|
|
157
|
-
<button
|
|
158
|
-
aria-pressed={selectedSkill?.sourcePath === skill.sourcePath}
|
|
159
|
-
className={`grid h-auto w-full min-w-0 justify-start rounded-md border px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-8 ${
|
|
160
|
-
selectedSkill?.sourcePath === skill.sourcePath
|
|
161
|
-
? 'border-primary-7 bg-primary-3 text-primary-12'
|
|
162
|
-
: 'border-transparent bg-transparent text-gray-11 hover:border-gray-6 hover:bg-gray-2 hover:text-gray-12'
|
|
163
|
-
}`}
|
|
164
|
-
onClick={() => setActivePath(skill.sourcePath)}
|
|
165
|
-
type="button"
|
|
166
|
-
>
|
|
167
|
-
<span className="grid min-w-0 gap-1">
|
|
168
|
-
<span className="font-semibold leading-snug">{skill.title}</span>
|
|
169
|
-
<span className="line-clamp-2 text-xs font-normal text-gray-10">
|
|
170
|
-
{skill.description || skill.name}
|
|
171
|
-
</span>
|
|
172
|
-
<span className="truncate text-[0.7rem] font-normal text-gray-9">
|
|
173
|
-
{skill.directory}
|
|
174
|
-
</span>
|
|
175
|
-
</span>
|
|
176
|
-
</button>
|
|
177
|
-
</li>
|
|
178
|
-
))}
|
|
179
|
-
</ul>
|
|
180
|
-
</section>
|
|
181
|
-
))
|
|
182
|
-
) : (
|
|
183
|
-
<p className="text-sm text-gray-10">No skills match “{query}”.</p>
|
|
184
|
-
)}
|
|
185
|
-
</nav>
|
|
186
|
-
{selectedSkill ? (
|
|
187
|
-
<article
|
|
188
|
-
aria-live="polite"
|
|
189
|
-
className="min-w-0 overflow-hidden rounded-xl border border-gray-6 bg-gray-1"
|
|
190
|
-
>
|
|
191
|
-
<header className="border-b border-gray-6 p-4">
|
|
192
|
-
<p className="mb-1 text-xs font-bold uppercase tracking-wider text-primary-11">
|
|
193
|
-
{selectedSkill.scope}
|
|
194
|
-
</p>
|
|
195
|
-
<h3 className="mb-2 mt-0">{selectedSkill.title}</h3>
|
|
196
|
-
<p className="mb-2 text-gray-10">{selectedSkill.description}</p>
|
|
197
|
-
<p className="mb-0 break-all font-mono text-xs text-gray-9">
|
|
198
|
-
{selectedSkill.sourcePath}
|
|
199
|
-
</p>
|
|
200
|
-
</header>
|
|
201
|
-
{metadataEntries.length ? (
|
|
202
|
-
<dl className="m-0 grid gap-3 border-b border-gray-6 p-4 sm:grid-cols-2">
|
|
203
|
-
{metadataEntries.map(([key, value]) => (
|
|
204
|
-
<div className="min-w-0" key={key}>
|
|
205
|
-
<dt className="text-xs font-semibold uppercase text-gray-10">
|
|
206
|
-
{key.replace(/[-_]+/g, ' ')}
|
|
207
|
-
</dt>
|
|
208
|
-
<dd className="mb-0 mt-1 break-words text-sm">{metadataValue(value)}</dd>
|
|
209
|
-
</div>
|
|
210
|
-
))}
|
|
211
|
-
</dl>
|
|
212
|
-
) : null}
|
|
213
|
-
{selectedSkill.metadataIssue ? (
|
|
214
|
-
<p className="m-4 rounded-md border border-gray-6 bg-gray-2 p-3 text-sm">
|
|
215
|
-
Frontmatter could not be parsed: {selectedSkill.metadataIssue}
|
|
216
|
-
</p>
|
|
217
|
-
) : null}
|
|
218
|
-
<details className="p-4" open>
|
|
219
|
-
<summary className="cursor-pointer font-semibold">Full skill instructions</summary>
|
|
220
|
-
<pre className="mt-3 max-h-[60vh] overflow-auto whitespace-pre-wrap break-words rounded-lg bg-gray-2 p-4 text-sm">
|
|
221
|
-
<code>{selectedSkill.body}</code>
|
|
222
|
-
</pre>
|
|
223
|
-
</details>
|
|
224
|
-
</article>
|
|
225
|
-
) : null}
|
|
226
|
-
</div>
|
|
227
|
-
) : (
|
|
228
|
-
<p className="rounded-lg border border-gray-6 bg-gray-1 p-4 text-sm text-gray-10">
|
|
229
|
-
No SKILL.md files were found in this repository. Add skills under a .agents/skills or
|
|
230
|
-
.codex/skills folder, then rebuild the Docs site.
|
|
231
|
-
</p>
|
|
232
|
-
)}
|
|
233
|
-
</section>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
export default SkillsExplorer;
|