Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion COPY.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ The Agent request uses `Fork this Skill: <Skill page URL>`.
The Skill page's Markdown explains how to copy the source and install the local path.
Keep run as the default. Fork is the opt-in for an editable local copy.
The HTML page links to that Markdown with `Fork this Skill`.
Its description uses `A fork creates an editable local Skill with its original author and licence.`
Its description uses `Edit a local copy. It keeps the author and licence.`
The fork link adds `?action=fork` to request only the fork workflow.
That workflow uses a shallow fetch at one commit and plain installation output.
Check existing files before fetching. Keep licence and provenance checks.
Expand Down
43 changes: 23 additions & 20 deletions layers/registry/app/components/SkillDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1807,7 +1807,7 @@ useHead(computed(() => ({
<div class="mb-2 flex items-baseline justify-between gap-2">
<h2
id="files-heading"
class="section-label"
class="font-mono text-sm text-default"
>
Files
</h2>
Expand Down Expand Up @@ -1840,20 +1840,34 @@ useHead(computed(() => ({
class="hidden lg:block"
aria-labelledby="rail-behaviors-heading"
>
<h2
id="rail-behaviors-heading"
class="section-label mb-2"
>
Skill behaviors
</h2>
<div class="mb-1 flex items-baseline justify-between gap-2">
<h2
id="rail-behaviors-heading"
class="font-mono text-sm text-default"
>
Skill behaviors
</h2>
<a
href="#skill-behaviors"
class="data-label inline-flex min-h-9 items-center gap-1 transition-colors hover:text-default"
>
Where each one appears
<UIcon
name="i-lucide-arrow-down"
class="size-3"
aria-hidden="true"
/>
</a>
</div>
<ul
role="list"
class="flex flex-wrap gap-1.5"
class="space-y-1.5 font-mono text-xs text-muted"
>
<li
v-for="behavior in behaviors"
:key="behavior.id"
class="skill-chip"
class="flex items-center gap-2"
:class="behavior.tier === 'ask' ? 'text-default' : undefined"
>
<UIcon
:name="behaviorIcon(behavior)"
Expand All @@ -1868,17 +1882,6 @@ useHead(computed(() => ({
>, needs approval</span>
</li>
</ul>
<a
href="#skill-behaviors"
class="data-label mt-2 inline-flex min-h-6 items-center gap-1 transition-colors hover:text-default"
>
Where each one appears
<UIcon
name="i-lucide-arrow-down"
class="size-3"
aria-hidden="true"
/>
</a>
</section>
</aside>

Expand Down
183 changes: 86 additions & 97 deletions layers/registry/app/components/_SkillCommandPanel.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<script setup lang="ts">
import type { RunCheckFlag } from '#shared/run-check-flags'
import type { ZipState } from '../utils/skill-zip'
import { SKILL_RUN_PROMPT_LEAD } from '#shared/skill-commands'
import { AGENT_LOGOS } from '~/utils/agent-logos'
import SkillRunFlag from './_SkillRunFlag.vue'
import SkillRunPrompt from './_SkillRunPrompt.vue'

type CommandMode = 'run' | 'install'
type InstallTarget = 'local' | 'claude' | 'chatgpt'
Expand Down Expand Up @@ -59,7 +58,6 @@ const registrySetup: Record<InstallTarget, { to: string, label: string }> = {
claude: { to: '/developers?setup=mcp&app=claude', label: 'Search the registry from Claude' },
chatgpt: { to: '/developers?setup=mcp&app=chatgpt', label: 'Search the registry from ChatGPT' },
}
const agentNames = AGENT_LOGOS.map(agent => agent.label).join(', ')

const mode = defineModel<CommandMode>({ required: true })

Expand All @@ -74,6 +72,7 @@ const copyLabel = computed(() => commandCopied.value
: mode.value === 'run' ? 'Copy Agent prompt' : 'Copy install command')
const copyErrorId = useId()
const installPanelId = useId()
const forkNoteId = useId()

// Stacked shows both commands, so the copy error follows the last click.
function copyFrom(next: CommandMode) {
Expand All @@ -86,62 +85,41 @@ function copyFrom(next: CommandMode) {
<div
v-if="layout === 'stacked'"
data-testid="skill-command-panel"
class="space-y-6"
class="space-y-8"
>
<div class="flex items-center gap-2">
<div
role="img"
class="skill-agent-stack"
:aria-label="`Works with ${agentNames}`"
>
<span
v-for="agent in AGENT_LOGOS"
:key="agent.id"
:title="agent.label"
>
<UIcon
:name="agent.icon"
class="size-3"
aria-hidden="true"
/>
</span>
</div>
<span
class="data-label"
aria-hidden="true"
>Works with your agent</span>
</div>

<div class="space-y-3">
<div class="space-y-2">
<div class="space-y-1">
<h2 class="font-mono text-sm text-default">
Run once off
</h2>
<p class="text-xs leading-relaxed text-muted">
<strong class="font-medium text-default">This session only.</strong> Nothing lands on disk. Nothing to clean up.
Nothing lands on disk. Nothing to clean up.
</p>
</div>
<p class="rounded-lg border border-default bg-muted px-3 py-2 text-sm leading-relaxed text-default">
{{ SKILL_RUN_PROMPT_LEAD }} <code class="install-command install-command--wrap install-command__target inline">{{ runUrl }}</code>
</p>
<UButton
:icon="runCopied ? 'i-lucide-check' : 'i-lucide-copy'"
:label="runCopied ? 'Copied' : 'Copy prompt'"
color="neutral"
variant="outline"
size="sm"
class="font-mono"
:aria-describedby="copyError && mode === 'run' ? copyErrorId : undefined"
@click="copyFrom('run')"
/>
<div class="flex items-center gap-2 rounded-lg border border-default bg-muted py-1 pr-1 pl-3 text-xs">
<SkillRunPrompt
:url="runUrl"
class="block min-w-0 flex-1 py-1"
/>
<UButton
:icon="runCopied ? 'i-lucide-check' : 'i-lucide-copy'"
color="neutral"
variant="ghost"
size="sm"
class="min-h-11 min-w-11 shrink-0"
:aria-label="runCopied ? 'Copied' : 'Copy Agent prompt'"
:aria-describedby="copyError && mode === 'run' ? copyErrorId : undefined"
@click="copyFrom('run')"
/>
</div>
<SkillRunFlag
v-if="runFlag"
:flag="runFlag"
:source-url="sourceUrl"
/>
</div>

<div class="space-y-3 border-t border-default pt-6">
<div class="space-y-2">
<h2 class="font-mono text-sm text-default">
Install as a Skill
</h2>
Expand All @@ -156,7 +134,7 @@ function copyFrom(next: CommandMode) {
type="button"
class="-mb-px min-h-9 border-b font-mono text-xs transition-colors"
:class="installTarget === item.value
? 'border-[var(--ui-text)] text-default'
? 'border-primary text-default'
: 'border-transparent text-muted hover:text-default'"
:aria-pressed="installTarget === item.value"
:aria-controls="installPanelId"
Expand All @@ -167,13 +145,13 @@ function copyFrom(next: CommandMode) {
</div>
<div
:id="installPanelId"
class="space-y-3"
class="space-y-2 pt-1"
>
<template v-if="installTarget === 'local'">
<p class="text-xs leading-relaxed text-muted">
The files land in your project. The lockfile records them.
</p>
<div class="flex items-center gap-2 rounded-lg border border-default py-1 pr-1 pl-3 text-sm">
<div class="flex items-center gap-2 rounded-lg border border-default bg-muted py-1 pr-1 pl-3 text-xs">
<InstallCommand
:command="installCommand"
wrap
Expand Down Expand Up @@ -216,26 +194,47 @@ function copyFrom(next: CommandMode) {
{{ zipState.message }}
</p>
</template>
</div>
</div>

<ul
role="list"
class="font-mono text-xs"
>
<li>
<NuxtLink
:to="`${runUrl}.md?action=fork`"
class="inline-flex min-h-9 items-center gap-2 text-default underline-offset-2 hover:underline"
:aria-describedby="forkNoteId"
>
<UIcon
name="i-lucide-git-fork"
class="size-3.5"
aria-hidden="true"
/>
Fork this Skill
</NuxtLink>
<p
:id="forkNoteId"
class="pb-1 pl-5.5 font-sans leading-relaxed text-muted"
>
Edit a local copy. It keeps the author and licence.
</p>
</li>
<li>
<NuxtLink
:to="registrySetup[installTarget].to"
class="inline-flex min-h-11 items-center text-xs text-muted underline underline-offset-2 hover:text-default"
class="inline-flex min-h-9 items-center gap-2 text-default underline-offset-2 hover:underline"
>
<UIcon
name="i-lucide-search"
class="size-3.5"
aria-hidden="true"
/>
{{ registrySetup[installTarget].label }}
</NuxtLink>
</div>
</div>

<div class="space-y-1 border-t border-default pt-6">
<NuxtLink
:to="`${runUrl}.md?action=fork`"
class="inline-flex min-h-11 items-center font-mono text-sm text-default underline underline-offset-2 hover:text-primary"
>
Fork this Skill
</NuxtLink>
<p class="text-sm leading-relaxed text-muted">
A fork creates an editable local Skill with its original author and licence.
</p>
</div>
</li>
</ul>

<p
v-if="copyError"
Expand Down Expand Up @@ -272,15 +271,17 @@ function copyFrom(next: CommandMode) {
</div>

<div class="flex items-center gap-2 rounded-lg border border-default bg-muted py-1 pr-1 pl-3 text-sm">
<div class="min-w-0 flex-1 py-1">
<p v-if="mode === 'run'" class="section-label mb-1">
Ask your Agent
</p>
<p v-if="mode === 'run'" class="leading-relaxed text-default">
{{ SKILL_RUN_PROMPT_LEAD }} <code class="install-command install-command--wrap install-command__target inline">{{ runUrl }}</code>
</p>
<InstallCommand v-else :command="installCommand" wrap class="block" />
</div>
<SkillRunPrompt
v-if="mode === 'run'"
:url="runUrl"
class="block min-w-0 flex-1 py-1"
/>
<InstallCommand
v-else
:command="installCommand"
wrap
class="block min-w-0 flex-1 py-1"
/>
<UButton
:icon="commandCopied ? 'i-lucide-check' : 'i-lucide-copy'"
color="neutral"
Expand All @@ -297,23 +298,32 @@ function copyFrom(next: CommandMode) {
v-if="mode === 'run'"
class="text-xs leading-relaxed text-muted"
>
<strong class="font-medium text-default">This session only.</strong> Nothing lands on disk.
Nothing lands on disk. Nothing to clean up.
</p>
<SkillRunFlag
v-if="mode === 'run' && runFlag"
:flag="runFlag"
:source-url="sourceUrl"
/>

<div class="space-y-1">
<div class="text-xs">
<NuxtLink
:to="`${runUrl}.md?action=fork`"
class="inline-flex min-h-11 items-center font-mono text-sm text-default underline underline-offset-2 hover:text-primary"
class="inline-flex min-h-11 items-center gap-2 font-mono text-default underline-offset-2 hover:underline"
:aria-describedby="forkNoteId"
>
<UIcon
name="i-lucide-git-fork"
class="size-3.5"
aria-hidden="true"
/>
Fork this Skill
</NuxtLink>
<p class="text-sm leading-relaxed text-muted">
A fork creates an editable local Skill with its original author and licence.
<p
:id="forkNoteId"
class="pl-5.5 leading-relaxed text-muted"
>
Edit a local copy. It keeps the author and licence.
</p>
</div>

Expand All @@ -327,24 +337,3 @@ function copyFrom(next: CommandMode) {
</p>
</div>
</template>

<style scoped>
/* Overlapping monochrome marks, so the row reads as "every agent" without
pulling focus from the commands. */
.skill-agent-stack {
display: flex;
}
.skill-agent-stack > span {
display: grid;
place-items: center;
width: 1.375rem;
height: 1.375rem;
border: 1px solid var(--ui-border);
border-radius: 999px;
background: var(--ui-bg);
color: var(--ui-text-muted);
}
.skill-agent-stack > span + span {
margin-left: -0.375rem;
}
</style>
6 changes: 3 additions & 3 deletions layers/registry/app/components/_SkillRunFlag.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ const headingId = useId()
role="note"
:aria-labelledby="headingId"
data-testid="skill-run-flag"
class="flex gap-2 rounded-lg border border-default bg-muted/30 px-3 py-2 text-xs leading-relaxed"
class="flex gap-2 text-xs leading-relaxed"
>
<UIcon
name="i-lucide-circle-alert"
class="mt-0.5 size-3.5 shrink-0 text-warning"
aria-hidden="true"
/>
<div class="min-w-0 space-y-1">
<div class="min-w-0 space-y-0.5">
<p
:id="headingId"
class="font-mono text-default"
Expand All @@ -43,7 +43,7 @@ const headingId = useId()
:href="sourceUrl"
target="_blank"
rel="noopener"
class="font-mono underline underline-offset-2 transition-colors hover:text-default"
class="font-mono whitespace-nowrap underline underline-offset-2 transition-colors hover:text-default"
>SKILL.md on GitHub</a>
</template>
</p>
Expand Down
Loading
Loading