Skip to content

Commit 6efdbb8

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/demo-skill-page-only
# Conflicts: # scripts/record-skill-demo.ts
2 parents 271d1eb + d0a4805 commit 6efdbb8

92 files changed

Lines changed: 7336 additions & 433 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/deploy-cloudflare.yml‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,16 @@ permissions:
1717
actions: read
1818

1919
concurrency:
20-
group: deploy-cloudflare-${{ inputs.target || 'site' }}
20+
# Every Test run that completes starts this workflow, including pull request
21+
# runs and cancelled main runs. Their deploy job skips, but a run in the
22+
# deploy group still replaces the run waiting there. On 2026-10-07 a pull
23+
# request's run cancelled the queued deploy of #526, so main stayed
24+
# undeployed. Only a successful main push can deploy, so every other run
25+
# takes a group of its own.
26+
group: >-
27+
${{ (github.event_name == 'workflow_run' && (github.event.workflow_run.event != 'push' || github.event.workflow_run.conclusion != 'success'))
28+
&& format('deploy-cloudflare-noop-{0}', github.run_id)
29+
|| format('deploy-cloudflare-{0}', inputs.target || 'site') }}
2130
# A deploy in flight must finish. This workflow applies D1 migrations and then
2231
# deploys the signer and public Worker, then runs smoke and rollback. Cancelling
2332
# between those steps leaves migrated data against the previous Worker.
@@ -85,8 +94,10 @@ jobs:
8594
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
8695
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
8796

97+
# Retries a transient Cloudflare API fault, such as code 10013. Any other
98+
# failure stops the job at once.
8899
- name: Deploy Artifact signer
89-
run: pnpm --filter @skilld/artifact-signer-worker deploy:production
100+
run: pnpm production:deploy-signer
90101
env:
91102
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
92103
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

‎COPY.md‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ These exact strings. Do not paraphrase them per page.
4949
| Demo provenance | {Agent logo} {model}, such as Opus 5.5 | guidance only: the recording line on the Skill page panel and the homepage stage. Screen readers and the title get "Recorded with {agent}, {model}". No date and no GitHub link: the Skill page is where visitors inspect a Skill |
5050
| Demo outdated | Recorded on an older version of this Skill. | guidance only: under the Demo panel when the Skill moved past the recorded commit |
5151
| Home section: changes | Keep up with skill changes. | guidance only: the homepage section heading for watching and the digest |
52+
| Home section: why | What skilld does differently. | guidance only: the homepage Why band heading, after trending. No line under it: the three columns from `HOME_WHY_REASONS` follow directly, each a title, `summary`, and picture. Their skills.sh lines stay on `/vs/skills-sh` |
53+
| Why trust line | Built by Harlan Wilton · Compare with skills.sh | guidance only: the line that closes the homepage Why band. The avatar and name link Harlan's GitHub, and the button opens `/vs/skills-sh`. The columns carry every claim, so the line carries none |
54+
| Why reasons | Human first · Preview the output · Independent and open source · Open trending weights · No more skill bloat · Know what it runs · Know what it costs · No telemetry | guidance only: the reason titles. Each has a one-line `summary` for the homepage and a full `line` for the comparison. The homepage shows the first three, which Harlan chose on 2026-10-07. `WHY_REASONS` in `app/utils/why-skilld.ts` holds each title, its line, and the sourced skills.sh line, and both surfaces read it. The Preview line opens with the Demos line above. Open trending weights links the ranking code, so its weights must match `shared/trending-skill-score.ts`. Know what it runs never claims a Skill is safe |
55+
| Comparison H1 | skilld vs skills.sh | guidance only: the `/vs/skills-sh` H1. Its lead says what skills.sh is before what skilld is. Every skills.sh fact carries its source link and the date it was checked |
5256
| Home section: authoring | Write a skill for your project. | guidance only: the homepage section heading for making a Skill |
5357
| Nav: developers | Developers | guidance only: the header menu trigger, and the group label in the mobile menu. The menu holds the four items below |
5458
| Nav: CLI | CLI · Search, run, install, and keep Skills current. | guidance only: the first compact row in the Developers menu: name, line. Links `/cli`. The mobile menu shows the name only. The line is the CLI line, so change that first |
@@ -66,7 +70,7 @@ These exact strings. Do not paraphrase them per page.
6670
| CLI claims line | Open-source CLI · No telemetry · 19 Agent targets | guidance only: the `/cli` claims line. The page counts the `--agent` values of `skilld install --help` |
6771
| CLI install label | Install the CLI | guidance only: the label beside the CLI install chip on `/cli`. A switch above the chip picks `macOS / Linux`, `Windows`, or `npm`, and `macOS / Linux` is preselected |
6872
| CLI install consequence | One native binary. It upgrades itself from signed releases. · Needs Node.js. npm handles upgrades. | guidance only: the line under the CLI install chip on `/cli`. The first is for `macOS / Linux` and `Windows`, the second for `npm`. `crates/skilld-command/src/upgrade.rs` in the CLI decides both. If it changes, change these strings |
69-
| Claim: open source | open-source CLI | guidance only: the CLI. Never call skilld.dev or its site open source |
73+
| Claim: open source | open source | guidance only: the CLI and the site. `skilld-dev/skilld` and `skilld-dev/skilld.dev` are both public under MIT, checked 2026-10-07. If either repository goes private, narrow the claim to the one a reader can clone. "No telemetry" still names the CLI only |
7074
| Claim: telemetry | no telemetry | guidance only: the CLI. Its README states that it sends no telemetry or analytics. Never say it of the site, which keeps anonymous analytics |
7175
| Claim: privacy | analytics without cookies or IPs | guidance only: analytics and privacy copy. Never the bare "privacy-friendly" |
7276
| Claim: comparison | skills.sh alternative | guidance only: always a link to `/vs/skills-sh` |
@@ -147,6 +151,16 @@ Each status shows a title, then a detail line:
147151

148152
Found Skills replace the search results under the heading `Skills for this task`, with the line `A language model picked these from a few searches of the registry.`
149153

154+
### Behavior readings
155+
156+
The Skill behaviors panel shows a behavior reading under each SKILL.md match that needs approval. GLOSSARY.md defines the term and ADR-0016 the mechanism.
157+
Name the model plainly, as a language model. Never call a reading a review, a scan, a safety call, or a false positive.
158+
The strings live in `layers/registry/app/components/_SkillBehaviors.vue`.
159+
160+
- Each match: `SKILL.md:42 · Quoted example. {reason}`. `behaviorVerdictLabel` in `shared/behavior-readings.ts` owns the five labels: `Instruction`, `Quoted example`, `Prohibition`, `Documentation`, `Unclear`.
161+
- Panel note, shown when a match has a reading: `A language model read each match that needs approval in its context. Its reading is no guarantee and changes no approval.`
162+
- The skilld CLI approval message adds `(model reading: quoted example. {reason})` to each match, and ends its list with `A language model on skilld.dev wrote each model reading. A reading is no guarantee and changes no approval.` The CLI owns that string, so change it there first.
163+
150164
## Discord digest
151165

152166
The weekly card uses `Trending skills this week` as its title and links to the trending page.
@@ -225,7 +239,6 @@ near-miss is also banned.
225239
| people (for our audience) | dev, devs | They are developers, and "people" is a vaguer word doing a smaller job. "Developers" in full is fine where the short form reads clipped |
226240
| first person in a CTA | the content, then the destination | "We send this every Monday" spends the words on us. "Trending skills to your inbox every Monday" gives the reader both things that matter |
227241
| privacy-friendly (bare) | analytics without cookies or IPs | A bare privacy claim cannot be checked. The mechanism can |
228-
| open source (for the site or skilld.dev) | open-source CLI | A reader must be able to clone what the claim names, and the claim names the CLI |
229242

230243
**Exception, the humans-versus-generated claim.** When the contrast is authorship by a human
231244
against machine generation, "human" and "person" are the right words and "dev" weakens the

‎DESIGN.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ Rules for every texture:
347347
| Skill | Name, then owner, then the curator note or the curator count |
348348
| Collection | Name, description, curator, then the curator note or the first Skills |
349349
| Curator | Avatar, name, handle with counts, description, then the first Skills |
350+
| Demo | The demo's picture under a band: the lockup, then "What /{name} made" and the recording line. Its own frame, below |
351+
352+
**Demo card.** A demo page shares what the Agent made, so its card is a picture first, and `OgLayout` does not frame it. `OgImage/Demo.takumi.vue` puts the film's poster frame, or the page's first screen, in a window that fills the card below a band. The band has the lockup on the left. On the right, it has "What /{name} made" and the recording line. It has no Braille names field: the picture is the content. Rose inside the picture is depicted content, so the lockup's dot stays the card's one rose element.
350353

351354
### Motifs
352355

‎GLOSSARY.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Shared terms match the skilld CLI glossary (`skilld-dev/skilld/GLOSSARY.md`). Th
3333
| skilld token | `cli_tokens`, `layers/identity` | Author 1—N token | "skilld token" |
3434
| provenance | Skill detail, cards | Skill 1—1 author and source link | "written by", "source" |
3535
| behavior | `skilld-protocol/behaviors`, Skill page | Skill 1—N behavior | "Skill behaviors" |
36+
| behavior reading | `behavior-review` check result, Skill page, skilld CLI | behavior match 1—1 behavior reading | "model reading" |
3637
| Skillgen | `workers/skill-harness`, `/skillgen` | Repository 1—N pull request | "Skillgen" |
3738
| task search | search box, `layers/registry` | sentence 1—N Skill | "Find skills for this task" |
3839

@@ -228,6 +229,18 @@ Existing route paths, table names, and protocol fields keep their identifiers.
228229

229230
**Never:** permission, capability, risk, threat, scan result, "safe", "secure". No match never means the Skill does nothing.
230231

232+
### behavior reading
233+
234+
**Is:** a language model's reading of one match of a behavior marked "Needs approval": what the matched line does where it stands, as one of five verdicts, with a reason of at most 20 words. The `behavior-review` check result carries the readings of one Artifact. See ADR-0016.
235+
236+
**Verdicts:** Instruction, Quoted example, Prohibition, Documentation, Unclear.
237+
238+
**Use for:** the line under a match in the Skill behaviors panel, and the CLI approval message. Always say a language model wrote it, and that it changes no approval.
239+
240+
**Never:** review result, AI review, verdict (as the noun in UI), clearance, scan, audit, "safe", "cleared", "false positive". A reading never removes the approval a behavior needs.
241+
242+
**Collides with:** behavior. A behavior is the pattern match, and it decides the approval. A reading only annotates one match.
243+
231244
### Owner
232245

233246
**Is:** the GitHub organization or user that hosts Skill Repositories. A proxied entity, never a row skilld.dev owns. Lives at `/gh/[owner]`.

‎app/components/HomeWhy.vue‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<script setup lang="ts">
2+
import type { RecentPullRequest } from '#shared/open-source-pull-requests'
3+
import type { TrendingBoardRow } from '#shared/trending-range'
4+
import type { HomeDemoItem } from '~/utils/home-demos'
5+
import { HOME_WHY_REASONS, WHY_REASONS } from '~/utils/why-skilld'
6+
import WhyTrustLine from './why/_WhyTrustLine.vue'
7+
import WhyVisual from './why/_WhyVisual.vue'
8+
9+
/**
10+
* What skilld does differently from skills.sh, as three columns: a title, one
11+
* line, and a small picture of the real interface. The skills.sh side of each
12+
* reason lives on `/vs/skills-sh`, so the homepage stays light.
13+
*
14+
* From 1024px the columns share one subgrid, so the titles, lines and pictures
15+
* each sit on one row, and every column ends at the same height.
16+
*/
17+
const { trendingRow, demos, pulls } = defineProps<{
18+
/** The first trending row ranked by posts, for the people picture. */
19+
trendingRow: TrendingBoardRow | null
20+
/** The approved demos, for the previews picture. */
21+
demos: readonly HomeDemoItem[]
22+
/** Recently merged pull requests, for the open source picture. */
23+
pulls: readonly RecentPullRequest[]
24+
}>()
25+
26+
const reasons = HOME_WHY_REASONS.map(id => WHY_REASONS[id])
27+
</script>
28+
29+
<template>
30+
<section id="why" class="home-wm" aria-labelledby="why-heading">
31+
<span class="home-watermark" aria-hidden="true">Why</span>
32+
<div class="mx-auto max-w-6xl px-4 py-12 sm:px-6 md:py-16">
33+
<h2 id="why-heading" class="home-h2 text-balance">
34+
What skilld <span class="home-ink">does differently</span>.
35+
</h2>
36+
37+
<ul class="home-why mt-8 list-none p-0">
38+
<li v-for="reason in reasons" :key="reason.id" class="home-why__col">
39+
<h3 class="home-why__title">
40+
<UIcon :name="reason.icon" class="size-4 shrink-0 text-muted" aria-hidden="true" />
41+
{{ reason.title }}
42+
</h3>
43+
<p class="home-why__line">
44+
{{ reason.summary }}
45+
</p>
46+
<WhyVisual :id="reason.id" :trending-row="trendingRow" :demos :pulls compact class="home-why__visual" />
47+
</li>
48+
</ul>
49+
50+
<WhyTrustLine class="mt-8" />
51+
</div>
52+
</section>
53+
</template>
54+
55+
<style scoped>
56+
.home-why {
57+
display: grid;
58+
grid-template-columns: minmax(0, 1fr);
59+
gap: 2.5rem;
60+
}
61+
62+
.home-why__col {
63+
display: flex;
64+
min-width: 0;
65+
flex-direction: column;
66+
}
67+
68+
/* One subgrid: each row takes its tallest cell, so the pictures stretch to match. */
69+
@media (min-width: 64rem) {
70+
.home-why {
71+
grid-template-columns: repeat(3, minmax(0, 1fr));
72+
grid-template-rows: auto auto 1fr;
73+
column-gap: 1.5rem;
74+
row-gap: 0;
75+
}
76+
77+
.home-why__col {
78+
display: grid;
79+
grid-row: span 3;
80+
grid-template-rows: subgrid;
81+
}
82+
}
83+
84+
.home-why__title {
85+
display: flex;
86+
align-items: center;
87+
gap: 0.5rem;
88+
font-size: 1rem;
89+
font-weight: 600;
90+
line-height: 1.5rem;
91+
color: var(--ui-text-highlighted);
92+
}
93+
94+
.home-why__line {
95+
margin-top: 0.25rem;
96+
font-size: 0.875rem;
97+
line-height: 1.6;
98+
color: var(--ui-text-muted);
99+
text-wrap: pretty;
100+
}
101+
102+
.home-why__visual {
103+
margin-top: 1rem;
104+
}
105+
</style>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<script setup lang="ts">
2+
import { ogCount, ogText } from '../../utils/og-props'
3+
import { OG_INK, OG_MUTED, OG_RULE, OG_SURFACE } from '../../utils/og-style'
4+
5+
// Declared as the wire types the OG image URL can deliver, not as the types the
6+
// template wants. A size arrives as a number or a string.
7+
const props = defineProps<{
8+
name?: string | number | null
9+
recording?: string | number | null
10+
image?: string | number | null
11+
imageWidth?: string | number | null
12+
imageHeight?: string | number | null
13+
}>()
14+
15+
// A demo card is a window onto what the Agent made, under a band that names the
16+
// demo and how it was recorded. The picture fills the window's width from its
17+
// top edge, so a page shows its header and a film its frame, and the card's
18+
// bottom edge cuts the rest.
19+
const WIDTH = 1200
20+
const GUTTER = 56
21+
const BAND = 104
22+
const WINDOW_WIDTH = WIDTH - GUTTER * 2
23+
// Room for the title right of the lockup, and the average advance of one
24+
// character, in em, of a title that is mostly a Skill name.
25+
const TITLE_ROOM = 880
26+
const TITLE_EM = 0.5
27+
28+
const safeTitle = computed(() => `What /${ogText(props.name)} made`)
29+
// A Skill name runs to 64 characters, so a long one shrinks to keep one line.
30+
const titleSize = computed(() => Math.max(20, Math.min(30, Math.floor(TITLE_ROOM / (safeTitle.value.length * TITLE_EM)))))
31+
const safeRecording = computed(() => ogText(props.recording))
32+
const safeImage = computed(() => ogText(props.image))
33+
const scaledHeight = computed(() => {
34+
const naturalWidth = ogCount(props.imageWidth)
35+
const naturalHeight = ogCount(props.imageHeight)
36+
return naturalWidth && naturalHeight ? Math.round(naturalHeight * WINDOW_WIDTH / naturalWidth) : 0
37+
})
38+
</script>
39+
40+
<template>
41+
<div
42+
data-theme="dark"
43+
class="flex flex-col w-full h-full relative overflow-hidden font-sans"
44+
:style="{ background: 'oklch(0.14 0.008 60)', color: OG_INK }"
45+
>
46+
<div
47+
class="flex items-center justify-between shrink-0"
48+
:style="{ height: `${BAND}px`, paddingLeft: `${GUTTER}px`, paddingRight: `${GUTTER}px` }"
49+
>
50+
<OgBrand :size="30" />
51+
<div class="flex flex-col items-end" :style="{ gap: '6px' }">
52+
<span :style="{ fontSize: `${titleSize}px`, fontWeight: 600, letterSpacing: '-0.03em', lineHeight: 1.1 }">
53+
{{ safeTitle }}
54+
</span>
55+
<span v-if="safeRecording" class="font-mono" :style="{ fontSize: '18px', color: OG_MUTED }">
56+
{{ safeRecording }}
57+
</span>
58+
</div>
59+
</div>
60+
<div
61+
class="flex overflow-hidden"
62+
:style="{
63+
marginLeft: `${GUTTER}px`,
64+
width: `${WINDOW_WIDTH}px`,
65+
flexGrow: 1,
66+
background: OG_SURFACE,
67+
border: `1px solid ${OG_RULE}`,
68+
borderBottom: 'none',
69+
borderTopLeftRadius: '14px',
70+
borderTopRightRadius: '14px',
71+
}"
72+
>
73+
<img
74+
v-if="safeImage && scaledHeight"
75+
:src="safeImage"
76+
alt=""
77+
:width="WINDOW_WIDTH"
78+
:height="scaledHeight"
79+
:style="{ width: `${WINDOW_WIDTH}px`, height: `${scaledHeight}px`, flexShrink: 0 }"
80+
>
81+
</div>
82+
</div>
83+
</template>

0 commit comments

Comments
 (0)