diff --git a/COPY.md b/COPY.md index e82cb92b..0533b78b 100644 --- a/COPY.md +++ b/COPY.md @@ -46,6 +46,7 @@ These exact strings. Do not paraphrase them per page. | Nav: setup guides | All setup guides | guidance only: the link under the Developers menu cards. Links `/developers` | | Skillgen lead | Keep your package skill current. After each release tag, Skillgen opens a draft pull request that updates the Skill in your repository. You review it and decide what merges. | guidance only: the `/skillgen` line under the H1. Its first two sentences are the page's meta description. A "Make a skill" link above the H1 returns to `/make-skill` | | Skillgen opt-in | Skillgen opens a draft pull request after each release tag. It runs only on the repositories you turn on here. | guidance only: the intro of the Skillgen view on `/me?view=skillgen`. The `/skillgen` step 02 says the same in its own words: Skillgen never runs on a repository you did not turn on | +| Skillgen groups | Ready · Needs a change · Turn on all (n) · n of n on | guidance only: the two groups on `/me?view=skillgen`. Ready rows say "Updates on each new tag." when on and "Can update when you turn it on." when off. Needs a change rows show the refusal sentence from `skillgenRefusal` | | CLI H1 | The skilld CLI | guidance only: the `/cli` H1 and its OG title | | CLI line | Search, run, install, and keep Skills current. | guidance only: the `/cli` line under the H1. It is the CLI's own `--help` line, so change it in the CLI first | | CLI intro | Give your Agent Skills that real maintainers write. A run reads the current source every time, and one command updates the Skills you install. Install the skilld Skill once, and your Agent searches and loads Skills on its own. | guidance only: the `/cli` intro under the line | diff --git a/docs/runbooks/skillgen.md b/docs/runbooks/skillgen.md index 2f0ef2ef..c567c1af 100644 --- a/docs/runbooks/skillgen.md +++ b/docs/runbooks/skillgen.md @@ -11,7 +11,9 @@ Maintainers set it up from [/skillgen](https://skilld.dev/skillgen). Installing the App alone queues nothing. A signed-in maintainer turns Skillgen on for each repository, and `skillgen_repositories` records it. -Turning it on needs admin or maintain access, a public repository, a root `package.json`, and the Skill where the Worker reads it. +Turning it on needs admin or maintain access, a public repository, and a published package with a Skill. +`workers/skill-harness/src/package-skills.ts` holds the package rule. The site and the Worker both import it. +A package sits at the root or under `packages//`, with its Skill in `skills//SKILL.md` beside its `package.json`. Skillgen supports npm packages only. Before the Worker queues a job, it asks `POST /api/internal/skillgen/opt-ins` which repositories in the event opted in. @@ -52,8 +54,11 @@ New tag creation starts another job automatically. ## Source and publication checks -Each job reads the root `package.json` from the exact tag commit. -The tag must equal its package version, with an optional `v` prefix. +Each job lists the packages with a Skill on the default branch. +It reads each one's `package.json` from the exact tag commit, and skips private packages. +The tag must equal a package version, as `1.2.3`, `v1.2.3`, or `name@1.2.3`. +If several packages match one tag, the job splits into one job per package. +Each split job opens its own draft on a branch named after the package directory. If the package publication follows the tag, the job waits for it. The whole job has a thirty minute deadline. diff --git a/layers/identity/app/components/_SkillgenRepositories.vue b/layers/identity/app/components/_SkillgenRepositories.vue index 9319411d..396f994b 100644 --- a/layers/identity/app/components/_SkillgenRepositories.vue +++ b/layers/identity/app/components/_SkillgenRepositories.vue @@ -6,37 +6,69 @@ import type { SkillgenOptInResult, SkillgenRepositoryEntry } from '../../shared/ * that holds a Skill. The skill-harness Worker queues jobs only for * repositories switched on here, so installing the App alone runs nothing. * - * The list calls GitHub, so it loads in the browser after the page renders. + * Each row carries what the Worker would find, so a repository it cannot run + * on shows its reason up front instead of after a click. The list reads + * GitHub, so it loads in the browser after the page renders. */ const INSTALL_URL = 'https://github.com/apps/skilld-skillgen/installations/new' const { data, error, status, refresh } = useFetch<{ items: SkillgenRepositoryEntry[] }>('/api/me/skillgen', { lazy: true, server: false }) -type RowState = { _tag: 'Idle' } | { _tag: 'Saving' } | { _tag: 'Refused', message: string } +type RowState = { _tag: 'Idle' } | { _tag: 'Saving' } | { _tag: 'Failed', message: string } const rowStates = ref>({}) const key = (item: SkillgenRepositoryEntry) => `${item.owner}/${item.repo}` +// A repository already on stays in the ready list even if it stopped qualifying, so it can be turned off. +const ready = computed(() => data.value?.items.filter(item => item.eligibility._tag === 'Eligible' || item.optedIn) ?? []) +const blocked = computed(() => data.value?.items.filter(item => item.eligibility._tag === 'Ineligible' && !item.optedIn) ?? []) +const waiting = computed(() => ready.value.filter(item => !item.optedIn && item.eligibility._tag === 'Eligible')) +const onCount = computed(() => ready.value.filter(item => item.optedIn).length) +const turningOnAll = ref(false) + +/** `useFetch` data is shallow, so a row changes by replacing the list. */ +function update(item: SkillgenRepositoryEntry, patch: Partial): void { + if (data.value) + data.value = { items: data.value.items.map(row => key(row) === key(item) ? { ...row, ...patch } : row) } +} + async function setOptIn(item: SkillgenRepositoryEntry, optedIn: boolean): Promise { rowStates.value[key(item)] = { _tag: 'Saving' } await $fetch('/api/me/skillgen', { method: 'PUT', body: { owner: item.owner, repo: item.repo, optedIn } }) .then((result) => { if (result._tag === 'Refused') { - rowStates.value[key(item)] = { _tag: 'Refused', message: result.message } + // The repository changed since the list loaded, so it moves to the blocked group with the new reason. + update(item, { eligibility: { _tag: 'Ineligible', message: result.message } }) + rowStates.value[key(item)] = { _tag: 'Idle' } return } - item.optedIn = result.optedIn + update(item, { optedIn: result.optedIn }) rowStates.value[key(item)] = { _tag: 'Idle' } }) .catch((cause: unknown) => { // An expected refusal arrives as a value, so this is a network or server failure. console.warn('[skillgen] Saving the switch failed:', cause) - rowStates.value[key(item)] = { _tag: 'Refused', message: 'Could not save. Try again.' } + rowStates.value[key(item)] = { _tag: 'Failed', message: 'Could not save. Try again.' } }) } -function refusedMessage(item: SkillgenRepositoryEntry): string | null { +/** Turns on every ready repository that is off, one at a time, so each keeps its own result. */ +async function turnOnAll(): Promise { + turningOnAll.value = true + for (const item of [...waiting.value]) + await setOptIn(item, true) + turningOnAll.value = false +} + +/** Joins package names as `a`, `a and b`, or `a, b and c`. */ +function listSeparator(index: number, length: number): string { + if (index >= length - 1) + return '' + return index === length - 2 ? ' and ' : ', ' +} + +function failure(item: SkillgenRepositoryEntry): string | null { const state = rowStates.value[key(item)] - return state?._tag === 'Refused' ? state.message : null + return state?._tag === 'Failed' ? state.message : null } const signInAgain = computed(() => error.value?.statusCode === 401) @@ -47,18 +79,16 @@ const signInAgain = computed(() => error.value?.statusCode === 401)

Skillgen repositories

-

- Skillgen opens a draft pull request after each release tag. It runs only on the repositories you turn on here. +

+ Skillgen opens a draft pull request after each release tag. It runs only on the repositories you turn on here. Then + install the App + on the same repositories. How Skillgen works

-

- Then install the App on the same repositories. - Install Skillgen -

-