From b4f817bb51ef863c7502a920039fd542a0e3fa35 Mon Sep 17 00:00:00 2001 From: 0libote <79215282+0libote@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:53:50 +0100 Subject: [PATCH 1/4] feat(setup): add a toggle to hide bundled skills --- docker/setup/app.js | 1 + docker/setup/client/skills.js | 16 ++++++++-- docker/setup/console.css | 4 +++ docs/context-management.md | 6 ++++ scripts/skills-ui-audit.js | 22 ++++++++++++- tests/skills-ui.test.mjs | 60 +++++++++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 tests/skills-ui.test.mjs diff --git a/docker/setup/app.js b/docker/setup/app.js index ea8dcfd..499ed25 100644 --- a/docker/setup/app.js +++ b/docker/setup/app.js @@ -3406,6 +3406,7 @@ 'storage.versions': () => storageUI.reviewVersions(), 'storage.details': () => storageUI.toggle(), 'skills.refresh': () => skillsUI.refresh(), + 'skills.filter': () => skillsUI.toggleBundled(), 'skills.view': (a) => skillsUI.view(a.id), 'skills.add': () => skillsUI.add(), 'skills.import': () => skillsUI.install(), diff --git a/docker/setup/client/skills.js b/docker/setup/client/skills.js index 9f9bbfe..b98db53 100644 --- a/docker/setup/client/skills.js +++ b/docker/setup/client/skills.js @@ -11,6 +11,10 @@ const T3SkillsUI = (() => { : { text: 'Unverified', tone: '' }; const create = ({ api, getStatus, render, readMarkdown, downloadMarkdown }) => { let provider = null, inventory = null, loading = false, error = null, generation = 0, viewer = null, changing = null; + const filterKey = 't3-console-hide-bundled-skills'; + let hideBundled = false; + try { hideBundled = localStorage.getItem(filterKey) === 'true'; } catch { /* this session only */ } + const visibleSkills = () => (inventory?.skills || []).filter((skill) => !hideBundled || skill.scope !== 'system'); const importer = T3SkillImport.create({ api, providers, refresh: () => load(true) }); const chooseDefault = () => { const installed = (getStatus()?.harnesses || []).filter((row) => row.installed).map((row) => row.id); @@ -30,7 +34,7 @@ const T3SkillsUI = (() => { }; const providerName = (id) => providers.find(([key]) => key === id)?.[1] || 'this agent'; const coverageSummary = () => !inventory || loading ? '' - : M.plural(inventory.skills?.length || 0, 'skill'); + : M.plural(visibleSkills().length, 'skill') + (hideBundled && inventory.skills?.some((skill) => skill.scope === 'system') ? ' · ' + (inventory.skills.length - visibleSkills().length) + ' hidden' : ''); // What a skill's availability means for this agent, said once under the list. const coverageText = () => !inventory ? 'Refresh after changing skills outside the console.' : inventory.coverage === 'policy' ? 'These settings are ' + providerName(provider) + '’s own, for every project. A project’s settings can still turn a skill off. A change applies to new conversations.' @@ -63,14 +67,15 @@ const T3SkillsUI = (() => { }; const content = () => html`
${coverageSummary()}
${actions()}
+
Hide skills included with the agent from this list.
${loading ? html`

Reading skills…

` : ''} ${error ? html`` : ''} ${!loading && !error && inventory ? html` ${inventory.nativeError ? html`

${icon('triangle-alert')}${providerName(provider)} couldn’t be asked which skills it loads: ${inventory.nativeError}

` : ''} ${(inventory.warnings || []).map((warning) => html`

${icon('triangle-alert')}${warning}

`)} - ${inventory.skills?.length ? html`
${inventory.skills.map(row)}
` : ''} - ${!inventory.skills?.length ? html`
${inventory.warnings?.length ? 'No skills could be shown' : 'No skills yet'}${inventory.warnings?.length ? 'The messages above say why.' : 'Nothing in ' + providerName(provider) + '’s global skill folders. Add one here, or install one from a source.'}
` : ''} + ${visibleSkills().length ? html`
${visibleSkills().map(row)}
` : ''} + ${!visibleSkills().length ? html`
${inventory.skills?.length ? 'All skills are hidden' : inventory.warnings?.length ? 'No skills could be shown' : 'No skills yet'}${inventory.skills?.length ? 'Turn off Hide bundled skills to show them.' : inventory.warnings?.length ? 'The messages above say why.' : 'Nothing in ' + providerName(provider) + '’s global skill folders. Add one here, or install one from a source.'}
` : ''} ${inventory.roots?.length ? html`
Looked in ${M.plural(inventory.roots.length, 'folder')}${inventory.roots.map((root) => html`${root.path}`)}
` : ''} ` : ''}
@@ -192,6 +197,11 @@ const T3SkillsUI = (() => { return { content, actions, setState, install: () => importer.open(provider || chooseDefault()), view: (id, editing = false) => open(id, editing), add: () => { if (inventory?.canCreate) return open(null, true, true); }, activate: () => { if (!inventory && !loading && !error) load(); }, refresh: () => load(true), + toggleBundled: () => { + hideBundled = !hideBundled; + try { localStorage.setItem(filterKey, String(hideBundled)); } catch { /* this session only */ } + render(); + }, select: (id) => { if (!providers.some(([key]) => key === id) || id === provider) return; viewer?.layer.close('provider'); diff --git a/docker/setup/console.css b/docker/setup/console.css index 9579950..8f39f2b 100644 --- a/docker/setup/console.css +++ b/docker/setup/console.css @@ -649,6 +649,10 @@ html.tc-locked { overflow: hidden; } .tc-skill-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); padding: var(--space-4); } .tc-skill-toolbar .tc-label { margin: 0; } .tc-skill-toolbar select { width: auto; min-width: 140px; max-width: 100%; } +.tc-skill-filter { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); padding: 0 var(--space-4) var(--space-4); font-size: 12px; } +.tc-skill-filter label { cursor: pointer; } +.tc-skill-filter .tc-switch { flex: none; } +.tc-skill-filter .tc-hint { margin: 0; } .tc-skill-count { white-space: nowrap; } .tc-skill-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); } @media (max-width: 639.98px) { diff --git a/docs/context-management.md b/docs/context-management.md index cbd6835..63b628b 100644 --- a/docs/context-management.md +++ b/docs/context-management.md @@ -102,6 +102,12 @@ and runtime metadata, as described below. Cursor, Grok and Muse still show **Unverified** availability. Plugin inventories and configured extra locations are outside the basic known-folder inventory. +**Hide bundled skills** hides entries identified as system skills, such as those +shipped with Codex. It only filters this list; the agent can still use them. The +choice is remembered in this browser, and the count shows how many are hidden. +User, administrator and plugin installations remain visible unless the provider +identifies them as system skills. + Inventory loads when the Skills tab opens or its agent changes. **Refresh skills** rescans after external changes. Concurrent requests share a lookup and results are cached for 15 seconds; regular `/status` polling does not scan skills. diff --git a/scripts/skills-ui-audit.js b/scripts/skills-ui-audit.js index fc7b1c2..0be611d 100644 --- a/scripts/skills-ui-audit.js +++ b/scripts/skills-ui-audit.js @@ -59,6 +59,7 @@ const checkSelect = async (page, select, theme) => { let gateRead = false, releaseRead, oldReadStarted, readError = false, inventoryError = false; let saveError = true, gateSave = false, releaseSave, saveStarted; let stateError = true, gateState = false, releaseState, stateStarted, firstEnabled = true; + let bundledFixture = false; let policies = false, importFailure = true, gateImport = false, releaseImport, importStarted; let gateSource = false, releaseSource, sourceStarted; let gatePlan = false, releasePlan, planStarted, importPlanning = false; @@ -77,7 +78,7 @@ const checkSelect = async (page, select, theme) => { canEdit: true, canRemove: true, revision: 'fixture-revision', removalRevision: 'fixture-removal', removalKind: 'directory', removalPath: '/fixture/local/review' }); const inventoryFor = (provider) => ({ ok: true, provider, canCreate: true, creationTargets: [{ id: 'provider', name: 'Provider folder' }, ...(provider === 'codex' || provider === 'opencode' ? [{ id: 'shared', name: 'Shared .agents folder' }] : [])], - skills: [skill(provider, firstId, 'review'), skill(provider, secondId, 'manual-review')], + skills: [skill(provider, firstId, 'review'), skill(provider, secondId, 'manual-review'), ...(bundledFixture ? [{ ...skill(provider, 'c'.repeat(32), 'bundled-example'), scope: 'system', source: 'System', canToggle: false, canSetState: false }] : [])], coverage: provider === 'codex' ? 'native' : policies ? 'policy' : 'files', warnings: [], coverageText: provider + ' global skill inventory' }); await page.route('**/context/skills**', async (route) => { const url = new URL(route.request().url()), provider = url.searchParams.get('provider'); @@ -168,6 +169,25 @@ const checkSelect = async (page, select, theme) => { assert.equal(await page.locator('#skill-provider').inputValue(), 'opencode'); await page.locator('#skill-provider').selectOption('codex'); await page.locator('.tc-skills[data-provider="codex"]').waitFor(); + // Hiding bundled skills changes only the list, never the provider's settings. + bundledFixture = true; + await page.getByRole('button', { name: 'Refresh skills', exact: true }).click(); + await page.getByRole('button', { name: 'View bundled-example', exact: true }).waitFor(); + await capture('bundled-visible'); + const beforeFilterMutations = mutations.length; + await page.getByRole('switch', { name: 'Hide bundled skills', exact: true }).click(); + assert.equal(await page.locator('.tc-skill-row').count(), 2); + assert.equal(await page.locator('.tc-skill-count').innerText(), '2 skills · 1 hidden'); + assert.equal(await page.getByRole('button', { name: 'View bundled-example', exact: true }).count(), 0); + assert.equal(mutations.length, beforeFilterMutations); + assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false); + await capture('bundled-hidden'); + await page.getByRole('switch', { name: 'Hide bundled skills', exact: true }).click(); + assert.equal(await page.locator('.tc-skill-row').count(), 3); + bundledFixture = false; + await page.getByRole('button', { name: 'Refresh skills', exact: true }).click(); + await page.getByRole('button', { name: 'View review', exact: true }).waitFor(); + assert.equal(await page.getByRole('button', { name: 'View bundled-example', exact: true }).count(), 0); // Codex's own on/off for each skill is a switch in its row. const codexStates = () => page.locator('.tc-skill-row [role="switch"]').evaluateAll((all) => all.map((el) => el.getAttribute('aria-checked'))); const reviewSwitch = page.getByRole('switch', { name: 'review for Codex', exact: true }); diff --git a/tests/skills-ui.test.mjs b/tests/skills-ui.test.mjs new file mode 100644 index 0000000..a475ede --- /dev/null +++ b/tests/skills-ui.test.mjs @@ -0,0 +1,60 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import vm from 'node:vm'; +import test from 'node:test'; + +const source = await readFile(new URL('../docker/setup/client/skills.js', import.meta.url), 'utf8'); +const skills = [ + { id: 'user', name: 'My skill', scope: 'user' }, + { id: 'system', name: 'Bundled skill', scope: 'system' }, + { id: 'admin', name: 'Organization skill', scope: 'admin' }, + { id: 'plugin', name: 'Plugin skill', scope: 'user', source: 'Plugin' }, +]; +async function fixture(rows = skills, storage = new Map()) { + const requests = []; + const context = vm.createContext({ + Kit: { html: (parts, ...values) => parts.reduce((text, part, i) => text + part + (Array.isArray(values[i]) ? values[i].join('') : values[i] ?? ''), ''), raw: (value) => value, icon: () => '' }, + T3Model: { plural: (count, name) => `${count} ${name}${count === 1 ? '' : 's'}` }, + T3SkillImport: { create: () => ({}) }, + localStorage: { getItem: (key) => storage.get(key), setItem: (key, value) => storage.set(key, value) }, + }); + vm.runInContext(source + '\nglobalThis.create = T3SkillsUI.create;', context); + const ui = context.create({ render() {}, getStatus: () => ({ harnesses: [{ installed: true, id: 'codex' }] }), api: async (url) => { + requests.push(url); + return { ok: true, data: { provider: 'codex', skills: rows } }; + } }); + await ui.refresh(); + return { ui, requests, storage }; +} + +test('bundled filter hides only system skills and never changes provider state', async () => { + const { ui, requests, storage } = await fixture(); + assert.match(ui.content(), /View Bundled skill/); + ui.toggleBundled(); + const content = ui.content(); + assert.doesNotMatch(content, /View Bundled skill/); + for (const name of ['My skill', 'Organization skill', 'Plugin skill']) assert.ok(content.includes('View ' + name)); + assert.match(content, /3 skills · 1 hidden/); + assert.equal(requests.length, 1, 'filtering needs no API call'); + const restored = await fixture(skills, storage); + assert.doesNotMatch(restored.ui.content(), /View Bundled skill/); + restored.ui.toggleBundled(); + assert.match(restored.ui.content(), /View Bundled skill/); + assert.match(restored.ui.content(), /4 skills/); +}); + +test('filtered empty state explains how to reveal bundled skills', async () => { + const { ui } = await fixture([skills[1]]); + ui.toggleBundled(); + assert.match(ui.content(), /0 skills · 1 hidden/); + assert.match(ui.content(), /All skills are hidden/); + assert.match(ui.content(), /Turn off Hide bundled skills to show them/); + assert.doesNotMatch(ui.content(), /No skills yet/); +}); + +test('blocked browser storage still allows filtering for the session', async () => { + const storage = { get() { throw Error('blocked'); }, set() { throw Error('blocked'); } }; + const { ui } = await fixture(skills, storage); + ui.toggleBundled(); + assert.doesNotMatch(ui.content(), /View Bundled skill/); +}); From abb3240f3646068a3674e5d143f94447d5107d48 Mon Sep 17 00:00:00 2001 From: 0libote <79215282+0libote@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:43:11 +0100 Subject: [PATCH 2/4] feat(setup): track repository skills and check for updates --- docker/setup/app.js | 1 + docker/setup/client/skill-import.js | 7 +- docker/setup/client/skills.js | 27 ++++++-- docker/setup/server.mjs | 9 +-- docker/setup/skills.mjs | 39 ++++++++++- docker/setup/skills/imports.mjs | 12 +++- docker/setup/skills/origins.mjs | 29 ++++++++ docker/setup/skills/sources.mjs | 4 +- docs/context-management.md | 21 ++++-- scripts/skills-ui-audit.js | 30 +++++++- tests/skill-sources.test.mjs | 1 + tests/skill-updates.test.mjs | 104 ++++++++++++++++++++++++++++ tests/skills-api.test.mjs | 3 +- 13 files changed, 260 insertions(+), 27 deletions(-) create mode 100644 docker/setup/skills/origins.mjs create mode 100644 tests/skill-updates.test.mjs diff --git a/docker/setup/app.js b/docker/setup/app.js index 499ed25..96e7293 100644 --- a/docker/setup/app.js +++ b/docker/setup/app.js @@ -3405,6 +3405,7 @@ 'storage.preferences': () => storageUI.preferences(), 'storage.versions': () => storageUI.reviewVersions(), 'storage.details': () => storageUI.toggle(), + 'skills.check-updates': () => skillsUI.checkUpdates(), 'skills.refresh': () => skillsUI.refresh(), 'skills.filter': () => skillsUI.toggleBundled(), 'skills.view': (a) => skillsUI.view(a.id), diff --git a/docker/setup/client/skill-import.js b/docker/setup/client/skill-import.js index 3d50cb1..a6cc840 100644 --- a/docker/setup/client/skill-import.js +++ b/docker/setup/client/skill-import.js @@ -4,7 +4,7 @@ const T3SkillImport = (() => { const { html, raw, icon } = Kit; const create = ({ api, providers, refresh }) => { let active = null; - const open = (provider) => { + const open = (provider, initial = null) => { if (active) return; const view = { source: '', catalogSearch: '', preview: null, plan: null, result: null, skills: new Set(), providers: new Set([provider]), busy: null, error: null, replace: false, layer: null }; active = view; @@ -53,7 +53,7 @@ const T3SkillImport = (() => { const input = view.layer.panel.querySelector('#skill-source'); if (input && input.value !== source) input.value = source; const result = await request('source', { source }, previous); - if (result) { view.preview = result; view.skills = new Set(result.skills.length === 1 ? result.skills.map((skill) => skill.name) : []); } + if (result) { view.preview = result; view.skills = new Set(initial?.name ? result.skills.filter((skill) => skill.name === initial.name).map((skill) => skill.name) : result.skills.length === 1 ? result.skills.map((skill) => skill.name) : []); } render(true); }; view.layer = Kit.open({ kind: 'center', panel: { class: 'tc-sheet tc-context-sheet tc-skill-sheet tc-skill-import ' + (Kit.isPhone() ? 'tc-sheet--bottom' : 'tc-sheet--inset'), role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'skill-import-title' }, focus: '#skill-source', backdrop: 'ignore', onEscape: close, @@ -73,7 +73,7 @@ const T3SkillImport = (() => { ${view.plan && !view.result ? html`

${view.source}

${view.plan.destinations.map((entry) => html`
${providers.find(([id]) => id === entry.provider)?.[1]} · ${entry.name}

${entry.action === 'blocked' ? entry.error : entry.action === 'replace' ? 'Replace existing local skill folder and supporting files' : 'Add skill folder'}

${entry.directory ? html`${entry.directory}` : ''}${entry.also?.length ? html`

Same folder also used by ${entry.also.map((id) => providers.find(([key]) => key === id)?.[1]).join(', ')}

` : ''}
`)}
${view.plan.destinations.some((entry) => entry.action === 'replace') ? html`` : ''}

Installs the exact previewed contents. The source is not fetched again. Start a new conversation to load changes; provider policies may still restrict use.

` : ''} - ${view.result ? html`
${view.result.results.map((entry) => html`
${entry.name} · ${providers.find(([id]) => id === entry.provider)?.[1]}

${entry.status === 'failed' ? entry.error : 'Installed'}${entry.warning ? ' · ' + entry.warning : ''}

${entry.directory}
`)}

Start a new conversation to load installed skills. Availability is checked in the provider inventory where supported. To update, preview the source again and review its replacement destinations.

` : ''} + ${view.result ? html`
${view.result.results.map((entry) => html`
${entry.name} · ${providers.find(([id]) => id === entry.provider)?.[1]}

${entry.status === 'failed' ? entry.error : 'Installed'}${entry.warning ? ' · ' + entry.warning : ''}

${entry.directory}
`)}

Start a new conversation to load installed skills. Availability is checked in the provider inventory where supported. Repository installs remember their source. Use Check for updates in the skill list, then review the source before replacing an installation.

` : ''} ${view.busy ? html`

${view.busy === 'install' ? 'Installing…' : view.busy === 'plan' ? 'Checking destinations…' : 'Reading source… You can close this dialog while it finishes.'}

` : ''}
${view.plan && !view.result ? html`` : !view.result ? html`${view.preview?.skills?.length ? html`` : ''}` : ''}
`, }); @@ -113,6 +113,7 @@ const T3SkillImport = (() => { render(true); if (view.result) await refresh(); } }); + if (initial?.source) void preview(initial.source); }; return { open }; }; diff --git a/docker/setup/client/skills.js b/docker/setup/client/skills.js index b98db53..9d01701 100644 --- a/docker/setup/client/skills.js +++ b/docker/setup/client/skills.js @@ -10,7 +10,7 @@ const T3SkillsUI = (() => { : skill.native && skill.state === 'disabled' ? { text: 'Disabled', tone: '' } : { text: 'Unverified', tone: '' }; const create = ({ api, getStatus, render, readMarkdown, downloadMarkdown }) => { - let provider = null, inventory = null, loading = false, error = null, generation = 0, viewer = null, changing = null; + let provider = null, inventory = null, loading = false, error = null, generation = 0, viewer = null, changing = null, checking = false; const filterKey = 't3-console-hide-bundled-skills'; let hideBundled = false; try { hideBundled = localStorage.getItem(filterKey) === 'true'; } catch { /* this session only */ } @@ -59,7 +59,7 @@ const T3SkillsUI = (() => { return html`
${control(skill)}
@@ -81,7 +81,22 @@ const T3SkillsUI = (() => {
${icon('info', 'tc-icon--sm')}${coverageText()}
`; - const actions = () => html`${inventory?.canCreate ? html`` : ''}`; + const actions = () => html`${inventory?.skills?.some((skill) => skill.origin) ? html`` : ''}${inventory?.canCreate ? html`` : ''}`; + const checkUpdates = async () => { + if (loading || changing || checking) return; + const selected = provider, current = generation, rows = (inventory?.skills || []).filter((skill) => skill.origin); + checking = true; render(); + try { + for (const skill of rows) { + if (provider !== selected || generation !== current) break; + const response = await api('/context/skills/check-update', { body: { provider: selected, id: skill.id } }); + if (provider !== selected || generation !== current) break; + if (response.ok) skill.update = response.data.update; + else skill.update = { status: 'error', error: response.error }; + render(); + } + } finally { checking = false; render(); } + }; const setState = async (id, state) => { const row = inventory?.skills?.find((skill) => skill.id === id); if ((!row?.canToggle && !row?.canSetState) || loading || changing) return; @@ -119,6 +134,7 @@ const T3SkillsUI = (() => {
${creating ? html`${inventory?.creationTargets?.length > 1 ? html`

Shared skills can be used by multiple providers. Editing or removing them affects every provider using the same files.

` : ''}

Use lowercase letters, numbers and hyphens. The frontmatter name must match.

` : html`
Location${current.data?.path || row.path}${(current.data?.resolvedPath || row.resolvedPath) && (current.data?.resolvedPath || row.resolvedPath) !== row.path ? html`Linked target${current.data?.resolvedPath || row.resolvedPath}` : ''}

${stateOf(row).text}${row.invocation === 'manual' ? ' · Manual invocation requested by the skill' : row.invocation === 'model-only' ? ' · User invocation disabled in frontmatter' : ''}. ${row.stateAuthority === 'user-policy' ? 'Native user policy checked by the managed provider. ' + row.effectiveStateText + '. Session rules can differ.' : row.native ? 'Listed by Codex in the managed setup environment.' : 'File found on disk; provider availability is unverified.'}

`} + ${!creating && row.origin ? html`
Installed from${row.origin.source}

${row.update?.status === 'available' ? 'An update is available. Review the source before replacing this installation, including any local edits.' : row.update?.status === 'current' ? 'Installed source snapshot is up to date. Local edits are preserved until you explicitly replace this folder.' : row.update?.status === 'error' ? 'Could not check for updates: ' + row.update.error : 'Use Check for updates in the skill list to check this repository.'}

` : ''} ${!creating && row.stateReadOnlyReason ? html`

${row.stateReadOnlyReason}

` : ''} ${row.metadataError ? html`

${row.metadataError}

` : ''} ${current.error ? html`` : ''} @@ -128,7 +144,7 @@ const T3SkillsUI = (() => { ${current.editing ? html`
${editorMeta()}${creating ? 'Creates the skill in the selected location.' : 'Saving updates this file wherever it is used. Supporting files and symlinks stay in place.'} Ctrl/⌘ S to save.
` : (current.data?.readOnlyReason || current.data?.removalError) ? html`

${current.data.readOnlyReason || current.data.removalError}

` : ''} ` : ''}
- ${current.editing ? html`` : html`${current.data?.canRemove ? html`` : ''}${current.data?.canEdit ? html`` : ''}`}
`, + ${current.editing ? html`` : html`${current.data?.canRemove ? html`` : ''}${row.origin && current.data?.canEdit ? html`` : ''}${current.data?.canEdit ? html`` : ''}`}`, }); const syncEditor = () => { const meta = current.layer.panel.querySelector('#skill-editor-meta'); @@ -165,6 +181,7 @@ const T3SkillsUI = (() => { else if (button.hasAttribute('data-skill-copy')) Kit.copy(current.draft, button, 'Skill Markdown copied'); else if (button.hasAttribute('data-skill-download')) download(); else if (button.hasAttribute('data-skill-upload')) current.layer.panel.querySelector('#skill-file').click(); + else if (button.hasAttribute('data-skill-update')) { current.layer.close('update'); importer.open(current.provider, { source: row.origin.source, name: row.origin.name }); } else if (button.hasAttribute('data-skill-edit')) { current.editing = true; current.layer.render(); current.layer.panel.querySelector('#skill-content').focus(); } else if (button.hasAttribute('data-skill-save') || button.hasAttribute('data-skill-remove')) { const removing = button.hasAttribute('data-skill-remove'); @@ -194,7 +211,7 @@ const T3SkillsUI = (() => { else current.error = response.error; current.layer.render(); }; - return { content, actions, setState, install: () => importer.open(provider || chooseDefault()), view: (id, editing = false) => open(id, editing), add: () => { if (inventory?.canCreate) return open(null, true, true); }, + return { content, actions, checkUpdates, setState, install: () => importer.open(provider || chooseDefault()), view: (id, editing = false) => open(id, editing), add: () => { if (inventory?.canCreate) return open(null, true, true); }, activate: () => { if (!inventory && !loading && !error) load(); }, refresh: () => load(true), toggleBundled: () => { diff --git a/docker/setup/server.mjs b/docker/setup/server.mjs index 9a63091..7197364 100644 --- a/docker/setup/server.mjs +++ b/docker/setup/server.mjs @@ -2153,7 +2153,7 @@ const ROUTES = ["/login", "/logout", "/hello", "/status", "/pair", "/revoke", "/ "/providers", "/updates/check", "/packages/install", "/packages/update", "/packages/uninstall", "/packages/cancel", "/packages/registry", "/packages/versions", "/packages/info", - "/context/agents-md", "/context/skills/read", "/context/skills/create", "/context/skills/save", "/context/skills/remove", "/context/skills/state", "/context/skills/source", "/context/skills/plan", "/context/skills/install", "/context/skills/discard", "/context/skills"]; + "/context/agents-md", "/context/skills/read", "/context/skills/create", "/context/skills/save", "/context/skills/remove", "/context/skills/state", "/context/skills/check-update", "/context/skills/source", "/context/skills/plan", "/context/skills/install", "/context/skills/discard", "/context/skills"]; // One background pass for the opt-in auto-updater. Checks are cheap and // always safe; installs run only when enabled, an update is known, and @@ -2650,11 +2650,12 @@ const server = createServer(async (req, res) => { } } - if (req.method === "POST" && ["/context/skills/create", "/context/skills/save", "/context/skills/remove", "/context/skills/state", "/context/skills/source", "/context/skills/plan", "/context/skills/install", "/context/skills/discard"].includes(route)) { + if (req.method === "POST" && ["/context/skills/create", "/context/skills/save", "/context/skills/remove", "/context/skills/state", "/context/skills/check-update", "/context/skills/source", "/context/skills/plan", "/context/skills/install", "/context/skills/discard"].includes(route)) { try { const input = JSON.parse((await readBody(req, 6 * 64 * 1024 + 4096)) || "{}"); if (!input || typeof input !== "object" || Array.isArray(input)) return sendJson(res, 400, { ok: false, error: "Expected a skill operation object." }); - const result = route === "/context/skills/source" ? await skillsService.previewSource(input.source) + const result = route === "/context/skills/check-update" ? await skillsService.checkUpdate(input.provider, input.id) + : route === "/context/skills/source" ? await skillsService.previewSource(input.source) : route === "/context/skills/plan" ? await skillsService.planImport(input.previewId, input.skills, input.providers) : route === "/context/skills/install" ? await skillsService.installImport(input.previewId, input.planId, input.replace) : route === "/context/skills/discard" ? await skillsService.discardImport(input.previewId) @@ -2662,7 +2663,7 @@ const server = createServer(async (req, res) => { : route === "/context/skills/save" ? await skillsService.save(input.provider, input.id, input.revision, input.content) : route === "/context/skills/state" ? await skillsService.setState(input.provider, input.id, input.stateRevision, input.enabled, input.state) : await skillsService.remove(input.provider, input.id, input.revision, input.removalRevision); - if (!["/context/skills/source", "/context/skills/plan", "/context/skills/discard"].includes(route)) recordEvent("context.saved", route === "/context/skills/install" ? "Imported local skills; review individual installation results" : route === "/context/skills/remove" ? "Removed a local skill installation" : route === "/context/skills/state" ? "Changed native skill availability" : "Saved a local skill"); + if (!["/context/skills/check-update", "/context/skills/source", "/context/skills/plan", "/context/skills/discard"].includes(route)) recordEvent("context.saved", route === "/context/skills/install" ? "Imported local skills; review individual installation results" : route === "/context/skills/remove" ? "Removed a local skill installation" : route === "/context/skills/state" ? "Changed native skill availability" : "Saved a local skill"); return sendJson(res, 200, result); } catch (error) { const status = { invalid: 400, missing: 404, ENOENT: 404, conflict: 409, EEXIST: 409, too_large: 413, unsupported: 403, EACCES: 403, EPERM: 403 }[error?.code] || (error instanceof SyntaxError ? 400 : 500); diff --git a/docker/setup/skills.mjs b/docker/setup/skills.mjs index 3f282af..d056aa8 100644 --- a/docker/setup/skills.mjs +++ b/docker/setup/skills.mjs @@ -8,6 +8,8 @@ import { listCodexSkills, writeCodexSkillConfig } from "./skills/codex.mjs"; import { codexConfigPath, configSnapshot, stateRevision, serializeState } from "./skills/state.mjs"; import { createSkillFileManager, skillRevision } from "./skills/files.mjs"; import { createSkillPolicies } from "./skills/policies.mjs"; +import { readSkillOrigin, skillFingerprint } from "./skills/origins.mjs"; +import { resolveSkillSource } from "./skills/sources.mjs"; import { createSkillImporter } from "./skills/imports.mjs"; const PROVIDERS = Object.freeze({ claude: "Claude Code", codex: "Codex", opencode: "OpenCode", cursor: "Cursor", grok: "Grok Build", muse: "Muse Code", antigravity: "Antigravity" }); @@ -145,7 +147,7 @@ export async function scanSkillFiles(provider, options) { export function createSkillsService({ home, env = process.env, io = fs, nativeCodex = listCodexSkills, nativeCodexWrite = writeCodexSkillConfig, inspectClaude, inspectOpenCode, getHarness = async () => null, ttlMs = 15000, limits, resolveSource } = {}) { home = path.resolve(home || env.HOME || "/home/t3"); - const cache = new Map(); + const cache = new Map(), updateStates = new Map(), updateChecks = new Map(); const policies = createSkillPolicies({ home, env, io, getHarness, inspectClaude, inspectOpenCode, invalidate: () => cache.clear() }); const load = async (provider) => { const scanned = await scanSkillFiles(provider, { home, env, io, limits }); @@ -194,6 +196,14 @@ export function createSkillsService({ home, env = process.env, io = fs, nativeCo } catch (error) { nativeError = summaryError(error); } } const policy = await fileManager.policyFor(provider); + for (const row of scanned.skills) { + const origin = await readSkillOrigin(io, row, home); + if (origin && policy.capabilities(row).canEdit) { + row.origin = origin; + const checked = updateStates.get(row.resolvedPath); + if (checked?.fingerprint === origin.fingerprint && checked.source === origin.source) row.update = checked; + } + } return { ok: true, provider, canCreate: policy.canCreate, creationTargets: policy.creationTargets, providers: skillProviders(), skills: scanned.skills.map((row) => { if (row.stateAuthority === "user-policy") return { ...row, ...policy.capabilities(row) }; const reason = provider !== "codex" ? nativeError || "Native policy controls are currently supported for Claude Code and OpenCode; native availability controls for Codex." @@ -268,5 +278,30 @@ export function createSkillsService({ home, env = process.env, io = fs, nativeCo }; const fileManager = createSkillFileManager({ home, env, io, rootsFor: (provider) => skillRoots(provider, { home, env }), metadata: skillMetadata, read, invalidate: () => cache.clear() }); const importer = createSkillImporter({ home, env, io, metadata: skillMetadata, policyFor: fileManager.policyFor, invalidate: () => cache.clear(), resolveSource }); - return { list, read, setState, create: fileManager.create, save: fileManager.save, remove: fileManager.remove, ...importer }; + const checkUpdate = async (provider, id) => { + const row = await read(provider, id); + if (!row.origin || !row.canEdit) throw problem("unsupported", "This skill has no tracked repository source. Re-import it from its repository to enable update checks."); + const key = row.resolvedPath; + if (updateChecks.has(key)) return updateChecks.get(key); + if (updateChecks.size >= 3) throw problem("conflict", "Other repository checks are still running. Try again when they finish."); + const pending = (async () => { + let update; + try { + const snapshot = await (resolveSource || resolveSkillSource)(row.origin.source, { home, env, metadata: skillMetadata }); + const skill = snapshot.skills?.find((entry) => entry.name === row.origin.name); + if (!skill) throw new Error("The installed skill was not found in this source. It may have been renamed or removed."); + update = { ...row.origin, status: skillFingerprint(skill) === row.origin.fingerprint ? "current" : "available", checkedAt: Date.now() }; + } catch (error) { update = { ...row.origin, status: "error", error: summaryError(error), checkedAt: Date.now() }; } + // A replaced or removed installation must not inherit a stale check result. + const current = await readSkillOrigin(io, row, home); + if (!current || current.source !== row.origin.source || current.fingerprint !== row.origin.fingerprint) throw problem("conflict", "The installation changed while checking. Refresh and check again."); + updateStates.delete(key); updateStates.set(key, update); + if (updateStates.size > 1024) updateStates.delete(updateStates.keys().next().value); + cache.clear(); + return { ok: true, id, update }; + })(); + updateChecks.set(key, pending); + try { return await pending; } finally { updateChecks.delete(key); } + }; + return { list, read, checkUpdate, setState, create: fileManager.create, save: fileManager.save, remove: fileManager.remove, ...importer }; } diff --git a/docker/setup/skills/imports.mjs b/docker/setup/skills/imports.mjs index 83ee3f3..a154d2d 100644 --- a/docker/setup/skills/imports.mjs +++ b/docker/setup/skills/imports.mjs @@ -1,7 +1,8 @@ -// Previewed source snapshots become ordinary local skills. No package manifest. +// Previewed source snapshots become local skills with repository provenance. import path from "node:path"; import { randomBytes } from "node:crypto"; import { resolveSkillSource } from "./sources.mjs"; +import { ORIGIN_FILE, skillFingerprint } from "./origins.mjs"; import { removalPlan, serialized } from "./files.mjs"; const fail = (code, message) => Object.assign(new Error(message), { code }); @@ -68,7 +69,7 @@ export function createSkillImporter({ home, env, io, metadata, policyFor, invali return { ok: true, planId, destinations: destinations.map(({ root, rootPath, revision, ...entry }) => entry), canInstall: destinations.every((entry) => entry.action !== "blocked") }; } finally { snapshot.planning = false; } }; - const publish = async (entry, skill) => serialized(path.join(entry.directory, "SKILL.md"), async () => { + const publish = async (entry, skill, snapshot) => serialized(path.join(entry.directory, "SKILL.md"), async () => { for (const provider of entry.also) { const alias = await inspect(provider, skill); if (alias.directory !== entry.directory || alias.action !== entry.action || alias.revision !== entry.revision) throw fail("conflict", "A shared provider folder changed after review."); @@ -80,12 +81,17 @@ export function createSkillImporter({ home, env, io, metadata, policyFor, invali const staging = await io.mkdtemp(path.join(entry.root, ".t3-skill-import-")); let backup = null, reserved = false; try { + if (skill.files.some((file) => file.name === ORIGIN_FILE)) throw fail("conflict", "This source contains reserved skill tracking metadata."); for (const file of skill.files) { const output = path.join(staging, file.name); await io.mkdir(path.dirname(output), { recursive: true, mode: 0o700 }); await io.writeFile(output, file.data, { flag: "wx", mode: file.mode }); await io.chmod(output, file.mode); } + if (snapshot.sourceType === "git") { + // A source cannot supply or overwrite the console's provenance file. + await io.writeFile(path.join(staging, ORIGIN_FILE), JSON.stringify({ version: 1, source: snapshot.source, name: skill.name, fingerprint: skillFingerprint(skill) }) + "\n", { flag: "wx", mode: 0o600 }); + } // Recheck after staging too, so supporting-file edits invalidate replacement. for (const provider of [entry.provider, ...entry.also]) { const checked = await inspect(provider, skill); @@ -135,7 +141,7 @@ export function createSkillImporter({ home, env, io, metadata, policyFor, invali } const results = []; for (const entry of plan.destinations) { - try { results.push(await publish(entry, snapshot.skills.find((skill) => skill.name === entry.name))); } + try { results.push(await publish(entry, snapshot.skills.find((skill) => skill.name === entry.name), snapshot)); } catch (error) { results.push({ ...entry, status: "failed", error: error.message }); } } snapshot.result = { ok: true, results: results.map(({ root, rootPath, revision, ...entry }) => entry) }; diff --git a/docker/setup/skills/origins.mjs b/docker/setup/skills/origins.mjs new file mode 100644 index 0000000..fd1a129 --- /dev/null +++ b/docker/setup/skills/origins.mjs @@ -0,0 +1,29 @@ +// Import provenance travels atomically with the installed folder. Inventory never fetches sources. +import path from "node:path"; +import { constants } from "node:fs"; +import { createHash } from "node:crypto"; +import { parseSkillSource } from "./sources.mjs"; + +export const ORIGIN_FILE = ".t3-skill-source.json"; +export const skillFingerprint = (skill) => { + const files = skill.files.map((file) => [file.name, Boolean(file.mode & 0o100), createHash("sha256").update(file.data).digest("hex")]); + files.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0); + return createHash("sha256").update(JSON.stringify(files)).digest("hex"); +}; +export async function readSkillOrigin(io, row, home) { + if (!row.resolvedPath || row.scope !== "user" || row.source === "Plugin" || path.basename(row.path) !== "SKILL.md") return null; + let handle; + try { + // Refuse a retargeted skill before reading its adjacent metadata. + if (await io.realpath(row.path) !== row.resolvedPath) return null; + handle = await io.open(path.join(path.dirname(row.resolvedPath), ORIGIN_FILE), constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW); + const stat = await handle.stat(); + if (!stat.isFile() || stat.size > 8192) return null; + const buffer = Buffer.alloc(8193), { bytesRead } = await handle.read(buffer, 0, buffer.length, 0); + if (bytesRead > 8192) return null; + const value = JSON.parse(buffer.toString("utf8", 0, bytesRead)); + if (value.version !== 1 || typeof value.source !== "string" || parseSkillSource(value.source, home).type !== "git" || typeof value.name !== "string" || !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value.name) || value.name.length > 64 || !/^[a-f0-9]{64}$/.test(value.fingerprint)) return null; + return { source: value.source, name: value.name, fingerprint: value.fingerprint }; + } catch { return null; } + finally { await handle?.close(); } +} diff --git a/docker/setup/skills/sources.mjs b/docker/setup/skills/sources.mjs index 40781d1..1c08ae4 100644 --- a/docker/setup/skills/sources.mjs +++ b/docker/setup/skills/sources.mjs @@ -255,7 +255,7 @@ export async function resolveSkillSource(value, { home, metadata, env = process. const relative = file.name.slice(prefix.length), parts = relative.split("/"); for (let i = 1; i <= parts.length; i++) treeEntries.add(parts.slice(0, i).join("/")); } - if (treeEntries.size > 1024) throw fail("too_large", "This skill exceeds the 1024-entry management limit. Use a smaller skill folder."); + if (treeEntries.size + (parsed.type === "git" ? 1 : 0) > 1024) throw fail("too_large", "This skill exceeds the 1024-entry management limit. Use a smaller skill folder."); const copied = []; for (const file of children) { const name = file.name.slice(prefix.length); @@ -269,6 +269,6 @@ export async function resolveSkillSource(value, { home, metadata, env = process. if (preferredPaths?.has(entry.name)) foundPreferred = true; } if (!skills.length) throw fail(oversized ? "too_large" : "invalid", oversized ? "No matching skill could be imported. Skill Markdown must fit within 64 KiB; use a smaller skill or manage oversized skills directly." : parsed.filter ? "That skill was not found in this source." : "No valid SKILL.md files found. Choose a skill repository/folder or paste a direct Markdown link."); - return { source: value, skills, warnings: [...new Set(warnings)].slice(0, 30) }; + return { source: value, sourceType: parsed.type, skills, warnings: [...new Set(warnings)].slice(0, 30) }; } finally { await cleanup(); } } diff --git a/docs/context-management.md b/docs/context-management.md index 63b628b..9cd8cf0 100644 --- a/docs/context-management.md +++ b/docs/context-management.md @@ -228,10 +228,23 @@ Native path references: [Cursor skills](https://cursor.com/docs/skills), [Muse skills](https://meta-models.github.io/muse-code-sdk/next/guides/extend/skills/), and [Antigravity skills](https://antigravity.google/docs/skills). -Re-importing the source is the update flow: review a fresh snapshot and explicitly -replace each existing local folder, including removed/renamed supporting files. -The console does not remember origins, schedule updates or maintain a source -manifest. Use **View → Remove** to delete an imported local skill. Files tracked +Repository installs remember their source and a fingerprint of the installed +files in `.t3-skill-source.json` inside the skill folder. **Check for updates** +on the Skills tab checks tracked repository installs on demand. It compares the +skill's complete source contents and executable flags, so unrelated repository +commits do not trigger an update. Ordinary inventory refreshes never fetch sources. +Checks report **Up to date**, **Update available**, or **Update check failed**. +These results last until the setup service restarts; source tracking survives restarts. + +Open a skill and choose **Review update** (or **Review source**) to preview its +repository with that skill selected. Explicitly confirm replacing the existing +folder, including local edits and removed/renamed supporting files. Checking alone +never changes installed files, and local edits do not count as upstream updates. +A ref pinned to a commit continues to check that commit. There are no scheduled +checks or automatic replacements. Earlier installs and imports from local folders +or direct Markdown links need a repository re-import to enable tracking. + +Use **View → Remove** to delete an imported local skill. Files tracked in the upstream skills CLI lock remain read-only here, including during import; manage those packages with their package manager. Unreadable locks, known plugin/system roots (including Grok/Antigravity plugin folders and Codex admin skills), linked diff --git a/scripts/skills-ui-audit.js b/scripts/skills-ui-audit.js index 0be611d..3310179 100644 --- a/scripts/skills-ui-audit.js +++ b/scripts/skills-ui-audit.js @@ -59,7 +59,7 @@ const checkSelect = async (page, select, theme) => { let gateRead = false, releaseRead, oldReadStarted, readError = false, inventoryError = false; let saveError = true, gateSave = false, releaseSave, saveStarted; let stateError = true, gateState = false, releaseState, stateStarted, firstEnabled = true; - let bundledFixture = false; + let bundledFixture = false, trackedFixture = false, updateStatus = 'available'; let policies = false, importFailure = true, gateImport = false, releaseImport, importStarted; let gateSource = false, releaseSource, sourceStarted; let gatePlan = false, releasePlan, planStarted, importPlanning = false; @@ -68,7 +68,8 @@ const checkSelect = async (page, select, theme) => { const importFocus = async (id) => assert.equal(await page.evaluate(() => document.activeElement.id), id); const policyValues = { claude: 'on', opencode: 'allow' }; const mutations = []; - const skill = (provider, id, name) => ({ id, name, description: 'A description that is long enough to wrap comfortably on a phone. '.repeat(4), + const skill = (provider, id, name) => ({ id, name, + ...(trackedFixture && id === firstId ? { origin: { source: 'org/repo@review', name: 'review', fingerprint: 'a'.repeat(64) } } : {}), description: 'A description that is long enough to wrap comfortably on a phone. '.repeat(4), path: '/fixture/a-long-global-configuration-directory/skills/' + name + '/SKILL.md', resolvedPath: '/fixture/shared-source/skills/' + name + '/SKILL.md', source: 'Shared .agents', native: provider === 'codex', state: policies && provider !== 'codex' ? policyValues[provider] : id === firstId && firstEnabled ? 'enabled' : 'disabled', bytes: body.length, @@ -84,7 +85,10 @@ const checkSelect = async (page, select, theme) => { const url = new URL(route.request().url()), provider = url.searchParams.get('provider'); if (route.request().method() === 'POST') { const input = route.request().postDataJSON(); mutations.push({ path: url.pathname, input }); - if (url.pathname.endsWith('/source')) { + if (url.pathname.endsWith('/check-update')) { + assert.equal(input.id, firstId); + await route.fulfill({ json: { ok: true, id: input.id, update: { status: updateStatus, error: updateStatus === 'error' ? 'Repository unavailable' : undefined } } }); + } else if (url.pathname.endsWith('/source')) { if (gateSource) { gateSource = false; sourceStarted(); await new Promise((resolve) => { releaseSource = resolve; }); } if (input.source === 'bad-source') await route.fulfill({ status: 400, json: { ok: false, error: 'No skill found at this source.' } }); else if (input.source === 'catalog') await route.fulfill({ json: { ok: true, skills: [], warnings: [], links: [{ label: 'Frontend skill', source: 'org/repo@review' }, { label: 'Other skill', source: 'org/other' }] } }); @@ -401,6 +405,26 @@ const checkSelect = async (page, select, theme) => { await capture('results'); await page.getByRole('button', { name: 'Done', exact: true }).click(); assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false); + trackedFixture = true; readError = false; + await page.getByRole('button', { name: 'Refresh skills', exact: true }).click(); + const checkUpdates = page.getByRole('button', { name: 'Check for updates', exact: true }); + await checkUpdates.waitFor(); + await checkUpdates.click(); + await page.getByText('Update available', { exact: true }).waitFor(); + await capture('update-available'); + await page.getByRole('button', { name: 'View review', exact: true }).click(); + await page.locator('[data-skill-update]').waitFor(); + await page.locator('[data-skill-update]').click(); + await page.locator('[data-import-skill="review"]').waitFor(); + assert.equal(await page.locator('#skill-source').inputValue(), 'org/repo@review'); + assert.equal(await page.locator('[data-import-skill="review"]').isChecked(), true); + await capture('review-update'); + await page.getByRole('dialog').getByRole('button', { name: 'Close', exact: true }).last().click(); + updateStatus = 'error'; await checkUpdates.click(); + await page.getByText('Update check failed', { exact: true }).waitFor(); + updateStatus = 'current'; await checkUpdates.click(); + await page.getByText('Up to date', { exact: true }).waitFor(); + assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false); assert.deepEqual(errors, []); console.log('PASS Skill inventory, native state, files and source installation:', theme, width + 'x' + height, embedded ? 'embedded-query fallback' : 'standalone'); await page.close(); diff --git a/tests/skill-sources.test.mjs b/tests/skill-sources.test.mjs index b9144c6..f0da585 100644 --- a/tests/skill-sources.test.mjs +++ b/tests/skill-sources.test.mjs @@ -81,6 +81,7 @@ test("Git discovery reads bounded blobs without checkout, shell execution, hooks assert.ok(args.includes("cat-file")); return { stdout: Buffer.from(args.at(-1) === "a".repeat(40) ? body : "check") }; }; const result = await f.resolve("https://github.com/org/repo/tree/main/skills/review", { run }); + assert.equal(result.sourceType, "git"); assert.equal(result.skills[0].files.length, 2); assert.equal(result.warnings.length, 2); assert.equal(calls.filter((args) => args.includes("cat-file")).length, 2); await assert.rejects(fs.stat(path.dirname(temporary)), { code: "ENOENT" }); diff --git a/tests/skill-updates.test.mjs b/tests/skill-updates.test.mjs new file mode 100644 index 0000000..5db9cbe --- /dev/null +++ b/tests/skill-updates.test.mjs @@ -0,0 +1,104 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import * as fs from "node:fs/promises"; +import path from "node:path"; +import os from "node:os"; +import { createSkillsService } from "../docker/setup/skills.mjs"; +import { ORIGIN_FILE } from "../docker/setup/skills/origins.mjs"; + +async function fixture(t) { + const home = await fs.mkdtemp(path.join(os.tmpdir(), "t3-skill-updates-")); + t.after(() => fs.rm(home, { recursive: true, force: true })); + let pause = null; + let guide = "Original guide", executable = false, failure = false, removed = false, calls = 0; + const source = "https://github.com/example/skills/tree/main/skills/review"; + const content = "---\nname: review\ndescription: Review changes\n---\nInstructions\n"; + const resolveSource = async (value) => { + assert.equal(value, source); calls++; + if (pause) await pause; + if (failure) throw Error("Repository unavailable"); + return { source, sourceType: "git", warnings: [], skills: removed ? [] : [{ name: "review", description: "Review changes", content, files: [ + { name: "SKILL.md", data: Buffer.from(content), mode: 0o600 }, + { name: "references/guide.md", data: Buffer.from(guide), mode: executable ? 0o700 : 0o600 }, + ] }] }; + }; + const options = { home, env: {}, resolveSource }; + const service = createSkillsService(options); + const install = async (replace = false) => { + const preview = await service.previewSource(source); + const plan = await service.planImport(preview.previewId, ["review"], ["claude"]); + return service.installImport(preview.previewId, plan.planId, replace); + }; + await install(); + const row = async () => (await service.list("claude", { refresh: true })).skills[0]; + return { home, service, options, source, install, row, pause: (promise) => { pause = promise; }, calls: () => calls, + change: () => { guide = "New upstream guide"; }, chmod: () => { executable = true; }, fail: () => { failure = true; }, remove: () => { removed = true; } }; +} + +test("repository origins survive restarts; checks detect supporting-file updates without writing skills", async (t) => { + const f = await fixture(t), row = await f.row(), calls = f.calls(); + assert.equal(row.origin.source, f.source); + const restarted = createSkillsService(f.options); + assert.equal((await restarted.list("claude")).skills[0].origin.source, f.source); + assert.equal(f.calls(), calls, "inventory never contacts the repository"); + assert.equal((await f.service.checkUpdate("claude", row.id)).update.status, "current"); + const local = path.join(path.dirname(row.path), "references/guide.md"); + await fs.writeFile(local, "Local edits"); + assert.equal((await f.service.checkUpdate("claude", row.id)).update.status, "current", "local changes do not masquerade as upstream updates"); + f.change(); + assert.equal((await f.service.checkUpdate("claude", row.id)).update.status, "available"); + assert.equal((await f.row()).update.status, "available"); + assert.equal(await fs.readFile(local, "utf8"), "Local edits"); + await assert.rejects(f.install(), /Confirm replacement/); + assert.equal((await f.install(true)).results[0].status, "installed"); + assert.equal((await f.row()).update, undefined, "the old check does not attach to the new snapshot"); + assert.equal((await f.service.checkUpdate("claude", row.id)).update.status, "current"); + assert.equal(await fs.readFile(local, "utf8"), "New upstream guide"); +}); + +test("file executability changes count as updates; unavailable and removed sources report errors", async (t) => { + const f = await fixture(t), row = await f.row(); + f.chmod(); assert.equal((await f.service.checkUpdate("claude", row.id)).update.status, "available"); + f.remove(); assert.match((await f.service.checkUpdate("claude", row.id)).update.error, /renamed or removed/); + f.fail(); assert.match((await f.service.checkUpdate("claude", row.id)).update.error, /Repository unavailable/); +}); + +test("untracked, malformed and linked provenance cannot trigger repository checks", async (t) => { + const f = await fixture(t), row = await f.row(); + const file = path.join(path.dirname(row.path), ORIGIN_FILE), calls = f.calls(); + await fs.writeFile(file, JSON.stringify({ version: 1, source: "/etc", name: "review", fingerprint: "a".repeat(64) })); + assert.equal((await f.row()).origin, undefined); + await assert.rejects(f.service.checkUpdate("claude", row.id), /no tracked repository/); + await fs.rm(file); await fs.symlink("/etc/passwd", file); + assert.equal((await f.row()).origin, undefined); + assert.equal(f.calls(), calls); +}); + + +test("concurrent checks share a fetch and discard results for a removed installation", { timeout: 3000 }, async (t) => { + const f = await fixture(t), row = await f.row(), before = f.calls(); + let release; + f.pause(new Promise((resolve) => { release = resolve; })); + const first = f.service.checkUpdate("claude", row.id); + const second = f.service.checkUpdate("claude", row.id); + // Both reads share the inventory; wait until the source fetch starts. + while (f.calls() === before) await new Promise((resolve) => setTimeout(resolve, 5)); + await fs.rm(path.join(path.dirname(row.path), ORIGIN_FILE)); + release(); + const results = await Promise.allSettled([first, second]); + assert.equal(f.calls(), before + 1); + assert.ok(results.every((entry) => entry.status === "rejected")); + assert.equal((await f.row()).update, undefined); +}); + + +test("source-supplied tracking metadata cannot impersonate a repository install", async (t) => { + const f = await fixture(t), row = await f.row(); + const local = createSkillsService({ home: f.home, env: {} }); + const preview = await local.previewSource(path.dirname(row.path)); + const plan = await local.planImport(preview.previewId, ["review"], ["cursor"]); + const result = await local.installImport(preview.previewId, plan.planId); + assert.equal(result.results[0].status, "failed"); + assert.match(result.results[0].error, /reserved skill tracking metadata/); + assert.equal((await local.list("cursor")).skills.filter((skill) => skill.source === "Provider").length, 0); +}); diff --git a/tests/skills-api.test.mjs b/tests/skills-api.test.mjs index 26a1352..39943f0 100644 --- a/tests/skills-api.test.mjs +++ b/tests/skills-api.test.mjs @@ -72,7 +72,7 @@ require('node:readline').createInterface({input:process.stdin}).on('line',line=> assert.equal((await fetch(base + "/context/skills/read?provider=claude&id=" + "a".repeat(32))).status, 401); assert.equal((await get("/context/skills?provider=__proto__")).status, 400); assert.equal((await get("/context/skills")).status, 400); - for (const operation of ["source", "plan", "install", "discard"]) { + for (const operation of ["source", "plan", "install", "discard", "check-update"]) { assert.equal((await fetch(base + "/context/skills/" + operation, { method: "POST" })).status, 401); assert.equal((await post("/context/skills/" + operation, {}, { "sec-fetch-site": "cross-site" })).status, 403); assert.equal((await post("/context/skills/" + operation, null)).status, 400); @@ -95,6 +95,7 @@ require('node:readline').createInterface({input:process.stdin}).on('line',line=> const result = await (await get("/context/skills?provider=claude")).json(); assert.equal(result.coverage, "policy"); assert.equal(result.skills.length, 1); const row = result.skills[0]; assert.equal(row.name, "one"); assert.ok(!("content" in row)); + assert.equal((await post("/context/skills/check-update", { provider: "claude", id: row.id })).status, 403); const readPath = "/context/skills/read?provider=claude&id=" + row.id; assert.equal((await (await get(readPath + "&path=" + encodeURIComponent(other))).json()).content, markdown); assert.equal((await get("/context/skills/read?provider=claude&id=" + encodeURIComponent(other))).status, 400); From f6f4b195df73ec5a6427a9391b096552b11249e2 Mon Sep 17 00:00:00 2001 From: 0libote <79215282+0libote@users.noreply.github.com> Date: Tue, 20 Oct 2026 02:13:53 +0100 Subject: [PATCH 3/4] feat(skills): provider adapters with detection hints and project locations Borrow the registry shape from skills-hub (MIT): each inventory now reports its adapter (global/project skill dirs, install detection). Detection never gates management; undetected tools show a note. --- docker/setup/client/skills.js | 1 + docker/setup/skills.mjs | 4 +- docker/setup/skills/adapters.mjs | 64 ++++++++++++++++++++++++++ docs/context-management.md | 5 +++ docs/experiments/skills-hub.md | 67 +++++++++++++++++++++++++++ tests/skill-adapters.test.mjs | 77 ++++++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 docker/setup/skills/adapters.mjs create mode 100644 docs/experiments/skills-hub.md create mode 100644 tests/skill-adapters.test.mjs diff --git a/docker/setup/client/skills.js b/docker/setup/client/skills.js index 9d01701..1553528 100644 --- a/docker/setup/client/skills.js +++ b/docker/setup/client/skills.js @@ -77,6 +77,7 @@ const T3SkillsUI = (() => { ${visibleSkills().length ? html`
${visibleSkills().map(row)}
` : ''} ${!visibleSkills().length ? html`
${inventory.skills?.length ? 'All skills are hidden' : inventory.warnings?.length ? 'No skills could be shown' : 'No skills yet'}${inventory.skills?.length ? 'Turn off Hide bundled skills to show them.' : inventory.warnings?.length ? 'The messages above say why.' : 'Nothing in ' + providerName(provider) + '’s global skill folders. Add one here, or install one from a source.'}
` : ''} ${inventory.roots?.length ? html`
Looked in ${M.plural(inventory.roots.length, 'folder')}${inventory.roots.map((root) => html`${root.path}`)}
` : ''} + ${inventory.adapter && !inventory.adapter.detected ? html`

${icon('info', 'tc-icon--sm')}${providerName(provider)} isn’t detected here, so these are plain files until it is installed. Adding, editing and importing still work.

` : ''} ` : ''}
${icon('info', 'tc-icon--sm')}${coverageText()}
diff --git a/docker/setup/skills.mjs b/docker/setup/skills.mjs index d056aa8..04e0071 100644 --- a/docker/setup/skills.mjs +++ b/docker/setup/skills.mjs @@ -9,6 +9,7 @@ import { codexConfigPath, configSnapshot, stateRevision, serializeState } from " import { createSkillFileManager, skillRevision } from "./skills/files.mjs"; import { createSkillPolicies } from "./skills/policies.mjs"; import { readSkillOrigin, skillFingerprint } from "./skills/origins.mjs"; +import { adapterDetection } from "./skills/adapters.mjs"; import { resolveSkillSource } from "./skills/sources.mjs"; import { createSkillImporter } from "./skills/imports.mjs"; @@ -196,6 +197,7 @@ export function createSkillsService({ home, env = process.env, io = fs, nativeCo } catch (error) { nativeError = summaryError(error); } } const policy = await fileManager.policyFor(provider); + const adapter = await adapterDetection(provider, { home, env, io }); for (const row of scanned.skills) { const origin = await readSkillOrigin(io, row, home); if (origin && policy.capabilities(row).canEdit) { @@ -214,7 +216,7 @@ export function createSkillsService({ home, env = process.env, io = fs, nativeCo return { ...row, ...policy.capabilities(row), canToggle: !reason, stateReadOnlyReason: reason, stateRevision: !reason ? stateRevision(config, row) : null }; }).sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path)), - roots: scanned.roots, coverage, nativeError, warnings: scanned.warnings, scannedAt: Date.now(), + roots: scanned.roots, coverage, nativeError, warnings: scanned.warnings, scannedAt: Date.now(), adapter, coverageText: coverage === "policy" ? "Known global skill folders with native user policy settings checked by the managed provider. Project/session and managed policies can further restrict availability." : coverage === "native" ? "Managed Codex inventory in the setup environment, with known local folders included. Project skills are excluded." : "Known global skill folders only. Provider availability, plugins and additional configured locations are not verified.", diff --git a/docker/setup/skills/adapters.mjs b/docker/setup/skills/adapters.mjs new file mode 100644 index 0000000..59fba59 --- /dev/null +++ b/docker/setup/skills/adapters.mjs @@ -0,0 +1,64 @@ +// Provider directory adapters: global/project skill locations and install +// detection hints. Table shape informed by skills-hub's tool registry (MIT, +// https://github.com/qufei1993/skills-hub); global locations stay aligned +// with skillRoots() in ../skills.mjs, which remains canonical for scanning. +// Detection is advisory only: an undetected tool stays fully manageable +// through its files, unlike registries that refuse unknown tools. +import path from "node:path"; + +const fail = (code, message) => Object.assign(new Error(message), { code }); + +// projectDir is relative to a project root; null means project scope is +// unverified for that provider. Codex, Cursor, OpenCode and Antigravity +// share the `.agents/skills` project directory, so one project's skills can +// appear in another provider's inventory. +const TABLE = Object.freeze({ + claude: Object.freeze({ label: "Claude Code", projectDir: ".claude/skills" }), + codex: Object.freeze({ label: "Codex", projectDir: ".agents/skills" }), + opencode: Object.freeze({ label: "OpenCode", projectDir: ".agents/skills" }), + cursor: Object.freeze({ label: "Cursor", projectDir: ".agents/skills" }), + grok: Object.freeze({ label: "Grok Build", projectDir: null }), + muse: Object.freeze({ label: "Muse Code", projectDir: null }), + antigravity: Object.freeze({ label: "Antigravity", projectDir: ".agents/skills" }), +}); + +export function adapterProviders() { + return Object.entries(TABLE).map(([id, { label }]) => ({ id, name: label })); +} + +// Resolve the global skills directory and the detection hint under home, +// honoring the same environment overrides as skillRoots(). +export function skillAdapter(provider, { home, env = {} } = {}) { + const entry = TABLE[provider]; + if (!entry) throw fail("invalid", "Unknown skill provider."); + const override = (key, fallback) => path.resolve(home, String(env[key] || "").trim() || fallback); + const config = override("XDG_CONFIG_HOME", ".config"); + const root = { + claude: override("CLAUDE_CONFIG_DIR", ".claude"), + codex: override("CODEX_HOME", ".codex"), + opencode: override("OPENCODE_CONFIG_DIR", path.join(config, "opencode")), + cursor: path.join(home, ".cursor"), + grok: override("GROK_HOME", ".grok"), + muse: path.join(config, "muse"), + antigravity: path.join(home, ".gemini", "config"), + }[provider]; + return { + id: provider, + label: entry.label, + globalDir: path.join(root, "skills"), + projectDir: entry.projectDir, + detectDir: root, + }; +} + +// Stat the detection directory. Filesystem errors mean "not detected", +// never a failure: inventory and management work regardless. +export async function adapterDetection(provider, { home, env = {}, io }) { + const adapter = skillAdapter(provider, { home, env }); + try { + const stat = await io.stat(adapter.detectDir); + return { ...adapter, detected: stat.isDirectory() }; + } catch { + return { ...adapter, detected: false }; + } +} diff --git a/docs/context-management.md b/docs/context-management.md index 9cd8cf0..0a73812 100644 --- a/docs/context-management.md +++ b/docs/context-management.md @@ -111,6 +111,11 @@ identifies them as system skills. Inventory loads when the Skills tab opens or its agent changes. **Refresh skills** rescans after external changes. Concurrent requests share a lookup and results are cached for 15 seconds; regular `/status` polling does not scan skills. +Each inventory also reports its provider adapter: the global skill folder, +the project skill folder where one is verified (Codex, Cursor, OpenCode and +Antigravity share `.agents/skills` inside a project), and whether the tool +itself is detected. Detection never gates management: an undetected tool +shows a note, and adding, editing and importing still work. Bodies load separately through authenticated, discovered-installation IDs. Details refuse a link retargeted after discovery. No skill files, provider settings or persistence mappings change during inventory reads. Native Codex diff --git a/docs/experiments/skills-hub.md b/docs/experiments/skills-hub.md new file mode 100644 index 0000000..92810f2 --- /dev/null +++ b/docs/experiments/skills-hub.md @@ -0,0 +1,67 @@ +# Spike: skills-hub CLI for skills/plugin support + +Branch: `exp/skills-hub-sync` (off `main`). Disposable test only, no repo changes. +Test home: `/tmp/sh-hub-test`, CLI 1.0.1 (`skillshub-cli`), glibc 2.41 satisfies CLI's 2.39 floor. + +## What skills-hub is + +Central library (`~/.skillshub/`) + `deploy` to 48 tool adapters +(`agents list`: global dir, project dir, detection dir, `sync_mode`). +Commands are JSON-stable: `skills install/list/search/show/status/check`, +`skills deploy/undeploy [--agent …] [--project …] [--dry-run]`, +`skills update/check --all`, `skills adopt --dry-run/--yes`, `tag`, `remove` +(recycle bin). Install script is SHA-256 verified, `~/.local/bin`, no node/npm. + +## Experiment: ponytail + +- `skills install https://github.com/DietrichGebert/ponytail` returns + `MULTI_SKILLS` with 6 candidates (`skills/ponytail`, `-audit`, `-debt`, + `-gain`, `-help`, `-review`). One `--subpath` each — same per-skill + friction our preview has. +- Library records git source + revision + content hash; `check` reports + `update_available` without touching targets. Closest existing equivalent + to our `origins.mjs` fingerprint + `check-update` model. +- `deploy --dry-run` previews; real deploy defaults to **symlink** + (`mode: symlink`) from each tool dir to the central copy, not a copy. +- Deploy **refuses unknown tools**: `AGENT_NOT_FOUND … not_installed` + until the detection dir exists (created `.claude`, `.codex`, + `.config/opencode` to proceed). Our console manages files regardless + of detection — UX gap to note. +- Conflict: planting an unmanaged `skills/ponytail/SKILL.md` then + deploying gives `TARGET_CONFLICT … unmanaged_target`; `adopt --dry-run` + is the recovery path. Mirrors our replacement-consent + fingerprint + checks, but centralized. + +## What it can't do (plugin gap remains) + +- No plugin/marketplace/hook handling: no `marketplace add`, no + `.claude-plugin` / `.codex-plugin` hooks, no `opencode.json` plugins + array, no Cursor `hooks.json` merge, no Grok enable list. Deploying + all 6 ponytail skills still yields prompt-only skills, same as our copy. +- No native policy controls: no Claude `skillOverrides`, OpenCode + `permission.skill`/`permissions`, Codex `skills/config/write` + enable/disable. Complementary to, not a replacement for, + `docker/setup/skills/policies.mjs` + `native.mjs`. +- No bundled/system/plugin inventory distinction beyond + managed-vs-unmanaged; our hide-bundled + read-only plugin roots + still add value. + +## Options for this repo + +1. **Adapter table**: borrow the 48-entry global/project/detection paths + (`tool_adapters/mod.rs`) to grow beyond our 7 providers. MIT licensed. + → Landed in part: `docker/setup/skills/adapters.mjs` + `adapter` on the + inventory + `tests/skill-adapters.test.mjs` (the update half came via + `feat/skill-source-updates`, below). +2. **Library + symlink deploy**: single update point + `check/update --all` + beats per-provider copies, but changes persistence (central lib must be + on the state volume), edit/remove semantics (symlink vs copy), and the + no-shell setup-page story (CLI vs native service). + → Landed in copy-model form via `feat/skill-source-updates`: + `.t3-skill-source.json` provenance + on-demand `check-update`, no + central library, no symlinks. +3. **CLI bridge**: shell to `skillshub-cli --json` like `native.mjs` shells + to managed CLIs — bounded, allowlisted subcommands, dry-run first. + Still leaves the ponytail plugin layer (hooks/commands/marketplace) open. + +Not committing to any option here; this file is the spike record. diff --git a/tests/skill-adapters.test.mjs b/tests/skill-adapters.test.mjs new file mode 100644 index 0000000..3c84eb4 --- /dev/null +++ b/tests/skill-adapters.test.mjs @@ -0,0 +1,77 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import * as fs from "node:fs/promises"; +import path from "node:path"; +import os from "node:os"; +import { adapterProviders, skillAdapter, adapterDetection } from "../docker/setup/skills/adapters.mjs"; +import { skillRoots, createSkillsService } from "../docker/setup/skills.mjs"; + +test("adapter registry covers every provider with portable project locations", () => { + const ids = adapterProviders().map((entry) => entry.id).sort(); + assert.deepEqual(ids, ["antigravity", "claude", "codex", "cursor", "grok", "muse", "opencode"]); + for (const { id, name } of adapterProviders()) { + assert.match(name, /^[A-Za-z ]+$/); + const adapter = skillAdapter(id, { home: "/home/fixture", env: {} }); + assert.equal(adapter.id, id); + assert.equal(adapter.label, name); + assert.ok(path.isAbsolute(adapter.globalDir)); + assert.ok(path.isAbsolute(adapter.detectDir)); + assert.equal(adapter.globalDir, path.join(adapter.detectDir, "skills")); + if (adapter.projectDir !== null) { + assert.match(adapter.projectDir, /^(\.[A-Za-z0-9_-]+)(\/[A-Za-z0-9_.-]+)*$/); + assert.ok(!path.isAbsolute(adapter.projectDir)); + } + } + // Codex, Cursor, OpenCode and Antigravity share one project directory. + for (const id of ["codex", "cursor", "opencode", "antigravity"]) { + assert.equal(skillAdapter(id, { home: "/home/fixture", env: {} }).projectDir, ".agents/skills"); + } + // Grok and Muse project scope stays unverified rather than guessed. + assert.equal(skillAdapter("grok", { home: "/home/fixture", env: {} }).projectDir, null); + assert.equal(skillAdapter("muse", { home: "/home/fixture", env: {} }).projectDir, null); + assert.throws(() => skillAdapter("unknown", { home: "/home/fixture", env: {} }), /Unknown skill provider/); +}); + +test("adapter global locations track the scanned primary roots and overrides", () => { + const home = "/home/fixture"; + for (const { id } of adapterProviders()) { + const adapter = skillAdapter(id, { home, env: {} }); + assert.equal(adapter.globalDir, skillRoots(id, { home, env: {} })[0].path); + } + const env = { CLAUDE_CONFIG_DIR: ".claude-alt", CODEX_HOME: ".codex-alt", GROK_HOME: ".grok-alt", XDG_CONFIG_HOME: ".cfg" }; + assert.equal(skillAdapter("claude", { home, env }).globalDir, path.join(home, ".claude-alt/skills")); + assert.equal(skillAdapter("codex", { home, env }).globalDir, path.join(home, ".codex-alt/skills")); + assert.equal(skillAdapter("grok", { home, env }).globalDir, path.join(home, ".grok-alt/skills")); + assert.equal(skillAdapter("opencode", { home, env }).globalDir, path.join(home, ".cfg/opencode/skills")); + assert.equal(skillAdapter("muse", { home, env }).globalDir, path.join(home, ".cfg/muse/skills")); + assert.equal(skillAdapter("cursor", { home, env }).globalDir, path.join(home, ".cursor/skills")); + assert.equal(skillAdapter("antigravity", { home, env }).globalDir, path.join(home, ".gemini/config/skills")); +}); + +test("detection reflects the install directory without failing", async (t) => { + const home = await fs.mkdtemp(path.join(os.tmpdir(), "t3-adapter-test-")); + t.after(() => fs.rm(home, { recursive: true, force: true })); + const missing = await adapterDetection("claude", { home, env: {}, io: fs }); + assert.equal(missing.detected, false); + assert.equal(missing.detectDir, path.join(home, ".claude")); + await fs.mkdir(path.join(home, ".claude"), { recursive: true }); + const present = await adapterDetection("claude", { home, env: {}, io: fs }); + assert.equal(present.detected, true); + // A broken io still reports undetected instead of throwing. + const broken = await adapterDetection("codex", { home, env: {}, io: { stat: async () => { throw new Error("denied"); } } }); + assert.equal(broken.detected, false); +}); + +test("inventory exposes the adapter without gating management", async (t) => { + const home = await fs.mkdtemp(path.join(os.tmpdir(), "t3-adapter-test-")); + t.after(() => fs.rm(home, { recursive: true, force: true })); + const service = createSkillsService({ home, env: {}, getHarness: async () => null }); + const undetected = await service.list("cursor", { refresh: true }); + assert.equal(undetected.adapter.id, "cursor"); + assert.equal(undetected.adapter.detected, false); + assert.equal(undetected.adapter.globalDir, path.join(home, ".cursor/skills")); + assert.equal(undetected.canCreate, true); + await fs.mkdir(path.join(home, ".cursor"), { recursive: true }); + const detected = await service.list("cursor", { refresh: true }); + assert.equal(detected.adapter.detected, true); +}); From d330e2024987fd7d9ac07cebfe510f9492aa9492 Mon Sep 17 00:00:00 2001 From: 0libote <79215282+0libote@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:24:25 +0100 Subject: [PATCH 4/4] docs(skills): phone screenshots of the undetected-tool adapter note --- docs/media/skills/adapter-note-phone-dark.png | Bin 0 -> 46708 bytes docs/media/skills/adapter-note-phone-light.png | Bin 0 -> 52707 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/media/skills/adapter-note-phone-dark.png create mode 100644 docs/media/skills/adapter-note-phone-light.png diff --git a/docs/media/skills/adapter-note-phone-dark.png b/docs/media/skills/adapter-note-phone-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0468bb00e2b7f1cd099090bc3042de29ef748537 GIT binary patch literal 46708 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 literal 0 HcmV?d00001 diff --git a/docs/media/skills/adapter-note-phone-light.png b/docs/media/skills/adapter-note-phone-light.png new file mode 100644 index 0000000000000000000000000000000000000000..0dea403708e7ec5a6e813af118017a3b7d34eb8a GIT binary patch literal 52707 zcmb^ZWmHvP7%vK=ba!`mcc-+JARtJmbV+SMx?4(01O-7#q`SMjySq32&i@(XocGJU zcZ_rI;gfE0@3rQd^Lc*t>_`nY1#}b=6c`v7bR|VOEf|*Jy5M zX9vy3=BEBHGaH+9&fNMG9SOfDG0kK-BL;fCvUNvz+@&ChcM_4$lh z?~XiKAI;_M>{ziG)Vc33wD^T{7tNL%H+s1c*1>ZcH8pvjwr7jBz@yB?5^;}pbYxlL z%!^dW+KlEjLl34i=jMbE!(Uqte6^n_kQg+R2#9lccQ?^TKIvVor37mV@3`91=y6D4 z-_{;T{;{#f`tHI~FDy_;t1ylcEg+Ts`=U(z(h`g3(Ol2i*q75k8hLEbpQfCRtE?{L z$R7|ePda%b`Bb##|Kj7_Ocm?kJ`WCO3Wkc88q|Ihbc&Ptn*n!c)8-{&wPfOVPoHdW*mI`Mmts+e}`oAuLhR#h)s(Us-fI zdV2+hg#Ou#cVvs!R##TuT>5%kYa=gvp$@z&YWV83h@7`U9kn7^C=<`m@?R1Htn?{oShOw7XLIDQ(0$4M` z7O=q(uZnzc9X~d>zZV+l-Ie&!8cXtf?*MwRH~tQ65U}OuCJRSBPp&$+w0>)OciqgE zug=uDh%doHed@A1s2^@A@2;4Objvvy7#P4`!uEw+x2S%>BYB95h>#4f`>JJ&G&`?L zrmGeiHQ)cUZROBAyWQ+nzrUpvfs7j+t}J7H3&*5v&vbKPZt#ElpstRYB5X4C zq2BebC-yLv<0Azb*-(U7|M=Q2i9}SpjfiQ7_M^A2F|oj(T&r?)@;t@Fj`XPCn)xDO z;w_UyLwM4bj|SyNZ{^rSpGe{wJWoxJ7btFS?zna0^YsuEm6dla#}ay$mJEN6CQ9(I zHop&#Zib0I4(h%-}(Qo<%Fqn9QbN{t|KMYmdvP5Ms~!% zt5N;Ql2(`t(^Q>0t_Mj*Tr3r?_OLB5A1UvXn!VKxy&1YF5`p+?j|MxP)Rfj-`Fc@N z5fg6}XR-@%7AmW@;tVa9Z*WCo1K*yU%a_vK8pZwm^tGcSdh zS5{UQ+O`&yg@hmnZq&&|~!UhJKcXe_hrh>Y-5wzJO`AH3vmvVfRRo8KWy{)(N`Zmx&hI`=yIN<+F+6^U>sZQHIOX`* zz;4vEHEUeh47tNOCk*AQqaPpVJ{$jv8QzYJP9V{Nv@wb`Kc~0;DfFpBfJ|yBIE~Y| z>R)gX*6No{D{E`re>RDNPLEE@4Z~a1mvwbQNN?S*()em>YRZiVM}mk&sKXmQPtR5S zul6c0EOER;;JY(#U5d6$Y~m#C*UXE<|JhDHc12dDXZC2^uXi$X5T{QEvQ!TJg4|7{ zzwS(k90DhPfCt8&uS*LHDHySsr+sHwbQ_c2Vgb!Wq_m(T6t%IwUjM@fp8Dpq&G_W! zdpn5^R(h3GG7)#hT#4qd6QVR~?t5dApwy-^tw&K&CO?sM!bmtTH}Kyai^lZ7rdVll z*;E_N`CCyL9-diyYZh_{I*#m@vX713RY4w6A|0w^8}B(d-cXrFyiorrcw5P+{=9#4 zyvQ~0?s1JJ!^6b|y&M$=C8VOVQku>FaOQ2r^}*Ed$y%ouYFUKay>Y*}3T)BOM_nl> z>&`;cv&aJ8U+49|U3N}=MMFaas!aSJ6Lk|c(c7>+gSmM%niX=Dlv12C`;qZ35_ z1&Y_P5{fFyZ=*rF?v_r%h0UXq((+^+)$tY;$qRE@YT2@T)RFrOmE88$*Sj4-=>kl- zH}MqW@U^gu|G|$XAvcPr&<7eXF0-{LGvy~xiz7z0OFtx9#yG7in_bwr+wAS0o%Q|E z%j%k#=vY`tN_|CjArx^#Q}ngP+nL*21^~c5_Oarm|8ofN{|+!def^5}KVWE|^8ZNy znp6`*DcL?eoFDKKh>VPUw79X}sjxku(`5|m!}L$^(*DHH%tqf>$(O9rTl=NDNJ{#n z^Q%AIw{i*!yg`bD_iV6U-sI4`U1Pk@6cUgMn{gU8UazyY_4%sZfv?QVMVhjyv44zP zsd;#c172Eoc6K0`7JJZh+_^fJ&7H;CQ=Y@ulf8xNftdmh4IYO#`3mvJNA@ad9O9-O zm~Wzls{U-{#VE{ye!bTfDG2~52@jo8YHBKqFB&>}Y;3F^L#1hFPiJ_%`_Y_)DEF3AK zY=cb3Ei#uo!vJ~m+RtIhW9iF>k<}ugALI(T{Da(GJeq@EGmj}l%Ot`nfgS3v~dT;#cv~}0l$HzpJg=JuIG1HR%{rfx6 zw=R$7-z_(Kn!_bpTJ$Nuj*E-yj;dd34WL}qDb^lI)w^RL@b(mEdJjCP2d>l zP;hYN`mx_gPfyrs2qA-HIi)iEW=D57ywAdqH_qQApm z-@HE59UfA>)4e^p8_8{517JHZ&$K%VzvNxv=W-))RJ^{?*3iOWD5`*f(~6C$Y2e!6 z^`W3dz_VeEea2}EsX?AhsP>_>f`UR`$L7Wcv?rZAUt>||6nvhApq6Bc0Wk>)*czXw z=AsGxi+%w7%c9M+TBMo(q54y?YAfieV6T~U_V?R9+_0an2CU6$l^M5Gk#sDylla{B#?}Eh0K4Tpe(#6mGt^C0Rdu%P z|2s=&?Ndn0Dl-D zTP^X;ey>nbFT11DKILM{*tS0M;0JX`N+ed9_m0r3JeR&NEf(7D2#tn7PA-1Dj!+!U z@w+)$M&CCjIh4LD&mE`Ih|6v9Cr1@Igv+B(G zbG~Q-`(w1T`uRp#h1oyWL^kbz_VcbSu_wp-`@hS}caQxlWy5bOQf5jGz?rph)Oos| z6*Dl%(1GWb7N+b<2m1`OOd*9;w^*-o0|3!n2|tALPoVXKiwsgliX6vWAH^W%>6@E_ zp0*Og!^5w)>6E-5%@kB{6K9(IsbWne3jWLeVL9ieA=Apl&zDaaba(gnuHrJ>J#plv zg@xf`LIrQv*_N1sk%cV>%2hkw&TIcqEx@Rj^*tM>b`g~dp{VBx&VXULQ8pk4hV`z) z8g4b#cRY~0@AuUa2!T0e2&8d zsFg?#q=})1W5!d|FMCHvO&*5~Cy1b89rwf9_ z2gNt<^YI7OlXt4Q&jy334qQc1zG}r&r3Q?pK5$^0p2q@Q!>sV>-|pQdAp|z5OVGrJ z&tW0mNz(sOE{iCs{ThiXp-WIkM&``v+rfHgIOcN0wVasZTrk&6o~!~?U`UNa7D_8_{GNCO1h6Xqbx%v6CMLb9ZpR7i6=)Vg4A^okh9GpQwC+Rfyf4Cv1 zMT)hkr_(nGxHjhc5Y-wK3!DGNiFSa?yk`i5Tm-pCv)D$Mm{ZPKD1ze>U9=JHZ3d7p zetIHPyP4S84GNktMdC(J5)l!xSp4)8Z`%(>Fon%KueQ@Y?++1n+xd^>RauWZfvy&R zb8{0h$x4PqX#aFyeWrS+F!ye=$4}CtkBg#$P$=N}^~Mu~G|@Jd8v&DoPK+BS8u=VN z3lTEW&so?ZK7S39Vq)J;`Gxxbb>BDREcc7xcdnKRClmLHNi9#EoSuGHPZ`{l0IP~A z9W2!0PV`!Y4Hif=XXrx~$fDj@D0nPal=V}Oe^PI*ztq2J;r4$NQthX{2#pkj$c>fi zmPylssLHR7B2`7~UH!E~lGYNqTU@1I|=G$dWO2HjL6=(B}fqMRD*@GNcRK8i<3{|R>r z2_H19()y|Fuv8bfsH~}}sp=^1aWp5r%)Iu&48y-W>ZjezpAFwSQ+gOwyV0C%=Xt2Y z5F<+(b*WSgc*@QPQ!dX)`0h8Lc>Gte#?J(L+)T$B-FgwF->pYl4^t%tnQ-|a$&@;u za0PbrBgwLQ{=V3KF&i;4LwceN>*Lny?QkVy`HrN&Di&BO*&sb7VF@BU!g+$ z33#q?@$r%nC=wdIMWf$C98e6n>Q#!4M1D3>bWuTeb{SEl)H4N^XK(#~D&un(?jL-J zQMY{U(dyoQwcQh&>6|){!kU>*WUdu|N@y=1mz~WHhtZFKwpZ)8jL>)Gx%yOL-aDgB z>7%9e@(k;OOhIu1kWOz`SBKpFixMhVkcMm+-Tu+MTlyJ+1cit@(Ob&P)ejPFPcj%j zf4vy*1%m#l-c!~B@KI2 z=7~+OA|cx2L!egvYXGzvR*=X4fR(33;?FJ zzv>F5xR2g8E$V(5A~)7=P)ax0iU=fv!z!>*B&~*u{2o4E8n4j!$Bb2XxWX4DgGwe7 z7YYN(lY!U7%og?3(r2-V{w^A?hXtCN%fIKT zWeFY6RZhnAQ3HrvqU!{7v+}d^5pv_D5r7O)eb0`Ibx8Fa(nUQJy&R65;sN*uh&^c= z;q%E7XG`hp#6-<#!g4Ep`G!k%h2;%HL<}+@A}($9Z+@C88i^pIkxkP65mJ)7YPY@# z*c}=g8imB;JxWy?=jvA`&6uCrn3NLm@Szn>-+*rTcS%Y{rem^@Q`M5Ee?*ep zgOrTT&guGa_HR{Hf7)mgwy<2-%nFD8#PwL-CN*`QfWQnhGqX)yPR=yMkJ4jrEUG`% z2%}S%UWH&c5+FlU9Uw@M+>+*NWQ!QDmt_PsS~S#4G$9|a7RL!ZV#&*x%` zSoMZwArElFi!N|_y>CxC`x0UAdV#PW@Zu*S(NKq154pQfVHI@l0eDg)1{_!_X%myN zChwQ$$IIJC4_B^6fTuAzHz$7Dv47y}alXH=emR{F|{ zSi_EnZduZ@aRlyL#@4~X0f?X8kI;-jSYLb=Is-d0_&SfnlR|ZAP%WHh{=5pHy#bmz zATJx6xDW`$PVqAddntX*TwKh3RoPQvx8`imIy857jvLVRglc z@xOl~AKYE-qXPXsUjbA8nnK(M#Usu~_8w^Y#U<)3l!ZVf!xJ%T54vH>8z3!)unjbs zwDU~yo{;1G`!rqP|HOG}*x<4`(jMIFylP59N=*Fp{4lE8GADeKE8-!+!}C|K^2hOF z?G`6@co{hV1eQ`d-*JF;9wFORh=KZmMhlOh^Yv>8ETC*_r!ViRUy6$MI>Xh)0-kwT zSd_EsWwy6Qok}!nH*iA%J!#HeWd#NWWU)ZWk8ESpjdHi6R`TVsF*NjU>Njq8xLCl6 z=gm>~Y&mRJ7Rttj3*JH-po0KLtRasbSdAJ%mpwbl$x(~#5H@gndVAB@4T`-lIqg_! z#d#i&Ybm?gxki(ym1H*Mp9WI;&|{HHOHDf)Y$lynTY8(@{UL*mtWqxuPDk@qmroDz zpN}tCK1^kx#y)zK=KwVjC>?kY72?vbXjq|6#V$5X-rAwN7WoZ`nANlXmdc)|D5_Vzqy z$?>{zr7?ps-e^M3nbrWze=?lq`H$OgRBv!T8ehrcwe|lV>yFl?{*B8Rw;rvUFY{^k z2D%=unXpi5;F$v zYDVFBg+YF$IS0gM#8L&bsh5*w#4&tC%+zKR4Bc?Mqw)OY$jHdF*+h@S8kDCD*wXRY z*_Y=BY;JCDObW5ezXQ8#8dB5&5u%@*+P;u*^&|=_Gg;7-k?{$;EJsNWw8u zs1|qM>-uYX=nplu*dExv+^*7?-`a9Anp-N%{)WB?4lamusXw;8*6u%n;{%}BYY+uM612V!Y;6m-OP z>8+m_;fKLUM)e!Xzi-m|=~`#t=$PU{`}D-$oDa-ACk zdaQTa%=b;re*SYEXG?c#c$lvPrIEU&nx#L(^Y5C7$)c}K6?yALuo(A5s-fz1OGT(A zAP|%c42)}tp(O)V7tr4~tTMDApWEWXx(n75fCrS1zV<;wfRCLI3MzkIV&_R1%sJl} z-am(5mBbM$Nc_DCJ*4<7h<~G>ViJyA>>3Z`5P z{dT>F+f^OlM%#ki=q3ch1Ii?LV ztzA~3b*_9cPAU*Y7bL!?<;HlMo&$`)7bNQ7OQxgt^uPdW8VwpGEW$MZrv~2GVJSly zXc&+=0+&M`Feq_%sC$s*`}&LM@>DuQ`~l%=!tpQR>X0oM`$r^WxfBbcwaN+zqepc*4{i6^k%VnaVvLf5`j3x@v`mMg;?4NnGR?J|m zqqlOOXUdjoAU`#P4>b0%4-X4BxT&>g^TRh#Pm6U*VqkDdgkuG{XC~4BizRsmC&^x(LaCEP87lq?Oa>M;VlkjApD$D?3#b zM=wsRYRX#YSTJgc#4>ouNpPIOry&7xm1>H?ro_H;FN?1h&{yM~5hh=w#q{?xe{A>{ zTpg5TqPp|I{Y>i|T!j7tUGJRb-EbeXOeIKgQyvK5z{+QO#D+7&+MRh^aPjrk&w>v? zd6=$InJoIBTEH|T8i5d-4A1%J;@*oN#SkjlWqeMdyS#AkjfAQ(f;w{ zZKLM8PvFf$mcW;F=8?`EcQT!iLZ=~mv{_x6A4hXE)zc&L+%So0OW%DMkKnGLO^P*r zYa{hqg9aK2#XX^K$^j?TG)ti~^fr)V*!pUXq)jml7f~HfCfJ5`@-;MFh*Jb6S%DI( z9KQ^;xDt3}UvcXrb0NQh0OW&hW#G(?C1)7@J(y(VwN5B+1W%`IR}5(rDTA-;27d>E zGYdai^n(1f!(t6$&g&7RP0F2=uzR`h=pVZmA6Q00Il6FJQxvW%RV9Yj4xDM7>!4Wf zy3jnNdzK?zm|wVPh(ovQsnpN0QbdoOp?tfe#ygcMzyP0$OiB4E^X$B>!7hewzXgDc ztx$fQ<9xs&`T<%ag|O5V7dwo;W*l7K4_R(PccGknnI$!@4Y|O~;EW)~(eVhs$iy=IOAc*OZwE8i!$rqDLM-wwIsK7Wq8qiV&skii|EvacskC30;Id#wPW9yWiEb%- zhkb-8k5X^kp?Z{7R{g;pq$nyOaf_{^A>8;at=}>!B*Q7>p96l7DgC`pBJVD!u7?9U-D1aK`ywnp&V|gdUC?^fz=j(8wmNwP>4VDcg zeEP$c<%&H zZoYXQP($Yz791D;<(c<#(8{ANwyqv7R3E~l5&-0707xsIT44UrFgp;*A<#2yTG~!v z%Kmd)J^-fdO6$vaJ?^wf+<-P_FJ;Qy#>?H8C+N93(DB32NzusWjE#*c5ogLio?h)I z2R!k=bVViuKc!5+s%Ip-v8f3Ide{8tUExh%B5f1_JMeNdsbu3wjky~v2YV)ez5TS( zlvrANG;0LWw~>|Y1cKyBi{BuilV|uuA>Ao=oDX+e$_6z@9PfT@aki@NO_x*w$vc$N z`Ya{s`ue&jmQ=t(F&7XmY%3gsvH8YZjp<_ifV=JR`)fPdqsOP!zkC;fTAOyt^L?5E zYeNfC9e~1+;h{VX4-I8ykr8q}Tc56cbaAPiEDZSAbbhwpdG-o`$%n)8chDRVcL0H< z$E$r@jRgHFD@m{K8_Pt1h`2geNcvojMPSnc?j&H+_MJk!;Xm`ntQ#en@uh1P-}mKN z{6;oXEl2DISi*5+!aSZQxsT3<7j1#Td7^`dD0x2&yj`BVPsy@jXdIe9yw*EHA8t=?&(>+z6TW}{4yY}yOMjB_dxmb! z3ZIMZ!Pz`nUqZq(Qb9+iT%P+ zD_vb*edqeQdv-f6{x zPXt;n6U{tIdAU-ra=-0s1Pq!1ltGs8@NlrPyq4;?frS1g4Nf%F5|wEP$i^Y@NUpN+ zS=p$ph3+2gpHGbrPK~J}mK4M2l(wVB4^l(l8yONyj8~c$-VIcNo%H332`aa*$4ty@ zJ(TWwd*ZiUzNThG_-Y?Gthh8wZaYJ7Z0?pDZzGn0gEKfWfm3B0PdR8r3yMplPRYdO zk2v?SdN+kE_osXGvrVe6FVBs2o~Jo1#`F`lPPyPQ)84=Z`rls>Z)^*NfSZ&C&$v!) z;D0^kuB06COIZxnlRTgTXt%#s26w6Y%!0aotHd?WDj0-_2E8|$K_{K5uh;36dj^gh z2;oIKVQ*$g8VmIE+fIZS`)7!_?VJJ=tvCJ!W0CE!eQ6$?>i6YF(er$8g9f#Z!e(90 zDa@IIPGR5XtE^X>yaT$sx`3lzZQSY~L)C(8)BHbTl21pe|0^NchpRIGaM%1^xcixX z{pg6P$u)<{l@m|pf4JP6^8fo8%Kt|V?f*kU^L$u&0`Srrhs8lxOdzNg8`K6~JOel5 zbh$B?u}G(cV^!FCWa+f^CGfv37G~52l3~lsjhGB{bm?MC^c%fy}#;bJ6>wMsi4z6_^+>K2)+^)nq+i=gq2Vz6(10S2W-oXTe(4W02qqezqRT?vn3b(ytmv~ zIIxtIoV*KSWT51}dgU~j#wlqc)e81JNElRG{Gxjm@%pOhZRXD1;)Dg6FD0r*(>%vyCo z>6hn0`)C+NRr{X`8<&|5WGE^0hpT-QcJNT|FSZXERf*t_J9U9#b$QNB#`T?1&H<=S zSby7tA0YR5TiYJ)eF+k3YN19=Uhr?sGZZp-eg+2xQHpu#l^N=%AcL2=eXiyJcqRAw z`pMDv<|vc`nDB%*JHQt|ItG41->}7BxHGoZuxw94^unHBlNekX)hEC2Rd9I z`cUru_4EKxDYoV+WldRaCAvWrGhuox&o7UbjoRoNI}oIsi}eO z`+kW>rvLUxm}xZw#(|%oKXf65-5|=6C81z{vM`Q_yBKok3S@qV<%X1;9Eul|G+aD9 zKCiQi_FzPQhhm)wb^!qnW@ch#3tQWH(D>xB>a&J&U8-tFM+GmotN6IMz6gdN1C6DF zB17I#MvAp2w zkz(`qAb5j+Hq9pOt01sy3YR$84^ax@FKEJb`KgrL^Z8(E2lVMGD|ReA=}dEY99-N` z2SwybjC0M`_xHwVxE@umr3Qiz+i8?2_$(l85tl7`TQ!8ByYTDn;nTw{$dkXE_mZ_- zf?8i~HyiXfOUOlQ5CTF6@BipEfD<^U?6COx445`ZG=j;cqxATkCqZ7ks(A*rCp`6k zzRP}6jHlAn*5+FcJI5g*>8S+)i*mM4edxAdd8HHJjhh2rH0OdHw%W8{Z%BSDoI$a6 z!vcE$!a&2ES35c^X5sTkM@AeUp{M@$`@Oc4R$z-2`ALAH&ZzciI+U&nqIuw+Gy55o zf?98Gj(r&de3060Sy@#WbP3=J&NZ`H5fKl@^9v&kHZd!$PhelGs|)rG0LniX{97=j z3=pmm+1tnZTp7oTU=W(46n4EB&V)fCW0-aB0kRtZR{;<<`(c74V@hg8zVRLbA%nnO z6awu70f+Lz_MR3$Bd{t~7a6a+`h+=-g@A&IX`N4>zSvP5VEo|JRH_ z+)!>SoV+rCrr_vz-kOUz%~kkYj~a=&?Z_A!LO@=R`yotKIgO(dkTY}hO~B4@c7r49 zfuSKag{9j;w{W)pgl%bkAgzkH&+XB-gQwR!xRGpwMMldGz}1eSa%HqlEdWYJOdm{SWJY~b?ZD$c1@=Vyf{5|A ziAiOc|6*)W^#6xP8yLv{U(HEm+|?|~5i2Pv^)tH?Nn@>Yh`3%|xnEyhp`r1-RpQy5 zPC7qFhE9%~g5VAl&DeN5B`tuFa108rJTdVWdeq3sJm}nfQ-XYa7Q_tsNnaBZfO7W* z9T%6@?`h~?{eQtdsdP}qO+$%DI@q{Ph^b_g;5=etV|fJxWQ4&ddS}+vc_9TwM!Lv9 z)?HB|i_0Jn4XLgv@dQy_h{?-`X$0@>5!n|Sq)Z5JhEWM@jZICZUqw(Mz4G%DfA1FHVRyBOexP^oG<0|pa+f?U%bh*02pzf9lJ@c+w7`ikSMMRzREj zRX}MYNFflwF)jELJW$t5!5@(v6mv!2++h6wcVFCM0Q6BA1+RF z3kw^2`;b=19UF0CEw$3UjG5U45S3$N(ZK_9u>UhAZgW?7M9s~e82_rWveMLa9nkUp z+GKCy0}mb$2VHG>`Z8i-Wi=3S1V7>A7$?bVukhxLr0)%mU{C@^V#D zTv8TM9aLm5JR^Y$WsKRtD*z;kVrAgcKC*5n}QyDRe-O+sL!!?+@~> zO;6Y3`FcU(y`bFD#T#ZOuZTlbQ70YKXTD|2ip)-NoP(VdqJpEZ@0=*}~@)bZ|fB*gKYPA(9R!X)4S%nUT zu5sWj>I-aaXlc(qZ>RnCy*)8sWg1IEC#_M>xOf20<^4merETG->8SJ_b2+u-(xiU@ zt-6UcBLERt>UZu>TI&(49UdO;?h1OHwE=^Tk&e#Bd+K=6bEVmrbA{J&X%5t>SWCh; z(FFRf{tnj%j;c38HWmaradLCDr&wgoxd9Zb1xn3tGe7_8tO0SZEHIWNzr{DtsMOxx zzK7o#hA!^^2f|0`X|u%bn8461&;5&Pf-~a&E6P4JRk3 zmP?>c074_=yk-LaeQ8Myxaz&^iN*ohVOb#Gf-Z|Ds=reS(4AoICqNK(b`jZ)%EMiHj4`&>>Y$xIYelq9DeCYy&zHTA(l|aKsv9=@47UZ!LUk2|feZi=VQo2}S?7v*qxjk1UEqTukFxi} zK!R!ky%+?RO~c>+*1{gR*yv_;2?C$a`Qe}0z*qf>f{qS}=vLr4z5r)lvw1MJ^!of< zC*!5cn)7|x(w9!4N&&4Y3R_?4=51iUxu~dUt=%j^%QT2pftbs{EeN4>uC4}5{zA7= zM`5>nxkdh7$rtLH`QdrY`z3O-~=3Q5pmLUB}0bu6JYA9XU z_ht#i60?QfK$M0Os34cio*BOt_G-af5DPk@k=2TLeDd`sx0$O56q0WBhXugsdC`eH zN6Z_5@F^0brnA#cQCl6nY%K2g&z0Phf~E znbK>k5A@=wJQxj{mfnShgLp|R3{nBtvG1<{T;Sp31IId9j)LSR=yQcN932Y|N`X=Hw_t?pp6nm9p_cq$--DyI4_xdMK=qC* zhUuAESWpmv0-`d0c;<3{Wy=-h<>TcIlo$}PKwbo(T-57qERj~Bp02y6=lN>V!kyaQ z^LWAX{z}zuX`t<|EHzl)b_)jecSIyy>KEG&pVon?W3lkhID^e~N=QJ!YO`l}&x6ST zL~kprT(^~*vQVU&KoSFpjiweBbggZ_z_1cFGrVw_m6Jq^Z0}_I&I|KM0o}Rm+nq1nX|+wypk8zx*C*=1!)5(=J8z##>gAF6{~JbBa?Q~cfEK#CanTRz!6sEOJ#cN2@wc6B_vwj zX3ntjb8&t5@z%^!7#|-8x~-{xm|?KiR&f`PZhU&vd{=LPhYWyzT{UQUyAP)dAt50n zIZuK^^zHrP5NHVG_phnk*ZZrjg+M+ABXMn~`G^Bm-|UmY zNG2)yU3@9PHY{-O9PI78D9Oplx+qPFfQ${YhpSiiXkPs4uUK!3hE{lNd{{=Aq zL4ILV6sVG>=H~E;%KQNlACeia1D@}E`-nqgK=Jvki1WJSYy_ljs~ndIgQQ(eAXwsjL`$q?Jf}l@9`6{|I((yT~}6Aw9z%38JdAj1mrE^?zcF1xK(jCiZRx3|!B zd*HwogpwZB@VJea{PYc{ z-g%v;Nr2(<7dZ%#3`YE+6Q#2SUjA}}2fieugQaEYa|IYER-A|-p5(F%n|GO`1(?R~ z`T$=&`1{1pFe!+`&sN)E3TYrwP%o9-$pN(yB#02mWZ}NN+*wl(5~UXe++A!>{cer$ zq7PH|k47dSYYD)OOhwbXLJUshmY$f*o#~I< z!Jl_W$m^VXdV08ou@P_rP>F$heiMeCNy7iB$ki0qBJRSZr>*;EL`%!7KP*RojF!<; zK&q(&{FcQfZ&n6|J>^iu??;pixLg&}LX&doygEfw<+xMZnLl|Q2!>+v7KHQEN`TsG zuOQQJkaAgrQH*zUt!}%H(oJAYP{k*)r@tHKf zNPc4a<*+CKAa0%O_NP3VZv_6qlKkEmazvaO7OJQh`oe)h@TiV+6^=bIt6`Xw@Tl0- zFT10;sMvOv4C*<2j!T|MR_z!vV!ojBhNF{GLr_a9fS`Z2{9lBfBiuOP3G{$f1Pro` zjSbhm1j5xwp(zlO$`m&um9`01`?gbzwGtf{SN)IAS|9cYHJG1}2`B*rq=RP8>|mzD z)x{;X@173e&mi_HuX9N@Qt3W0wt`Q{nUiJ`0mpv1Jt$aK_GbSWratZLO!3{jO}^#! zte5#?(3QEH0-#@twjRLgn_FCDC1ucu+Yn0UvciF>EC37EK43l{oi-at!2V5z*CN_L zR;71K+I2;$ZK)RoitzC9W3hzH$kL|=awvi>Ws<=4yKPw#-JY(#T#nxH+sy!N9AVKJ zK)m!mFqVTmNMO_g!6$<5KYBY2IX#1!1=Xap-sp*29BZ1}q|2nKW?#X_$}E`RiW|xN zVM8p*ju^}hA=R6y82%r#NJY#cZB=o@nYBkt^(8=CV%6pF^zg7uqGM)G{8Jzn@y|?Y z9vSb|EYQpmMMX(J_#1qZlb44_3>Og*p_)(#(>kHo?MZS);Tv}}?f9R$0QVuI`Nc(e zxF+k6uC})>&VBKuaz0;Y`zGz2^1Dfih@?^`YWD~e-!yq&68OM9APGibd&h|R4c6zu zl@E7xJoYCOCMI5mO_L!!F92YVP283Ro1y5P-1Iy^_0RWCbbpwu{&N2vRzpZjOHO4a z_Vb>f@;oe!Pr6|zB~1#n)<%Jd><9jzG; zs75m6Sd_BF2+(qqc8Fn&V*CXgof1!7{Fi{zKNqG99KymGFKo%!`k2ORnsxAxw6qs2 zbV|l!cao~lkS}MPiPI6YLp0npib>C~9%w;*`hDh%|2tn}@LhpEvI!$Vnet^IXSOih zLYP?z{37T zZ{^3q!2#*P%}rbh|HrL04Ju=aLG1v1xMU`7X3O5+XU4jIJX5pW0yEr#GyFUS8euVY z(5=3?t+~0aI=Gu&=LZgS_N(iOOVJW{Hur~E6`U(3IyyEHkzb0~jPhIPMy(iJyu8&l zBZYaG((yP+S_F=mD_%!cDfLEP?plTrWkVTd9(F}qM61Naz_`l1vY#1_dU6}z1q5hC z^+^jprq~=Gzrcv2pg*aCEz@=!7Znv|`^S+TvEHw%CJV{flWu58PLPU=nvF)7A?|9A zwFpI9(MC?Itq_hEw|N>-#xol^Tr&f`#2hl11ebX-Sx7aJFNWlJ4~FQIrftyfr+51b zR%akQg5$oFUlo-L0t+l|IGDz}^gIVH)DyY;>6G>neJJqof}q%7aLp~J1FY$ z$%_(F z&X0ko@38Rijqq?PyTM}3u||V?eZb4d4y5Y>i#f|o{vrPrCdAa=coj3KXDm9U{~|AR zFMEaTzx|r-;?^pA>DDwYY>s^%_9)X)CNX~~=f)r{^zT17GE1xc=%D{W(R9_vi&Un{ zc5);lqR+JR9*mULYyG?+AlQMj8n>v1W0W$ektIvEE`Q_C%=iZ^LeY@#kUM3!-RJ%Z zwP=FUiL16^WiYR2J(30dp}}Ovc#p%G6adyDJHp3j) z3b>+tDzu=-c!7R0$aT8X+}R4i!A~W5WHXEJlMOZSza|<5M{4g)m4JGI4Thk#|@y3>)(KDTWwHF z0pQ8xPE2ncIY{`E2{>zmQARIf0#2g_r`2bx;SW+Am5f|mdyS_(zCJVHOGkuT(243V21;T{U2!QZ!>Fz{O*V9-F7tKWJ4D2D#kKn5mP;w>U)zZ0s%Y+W3aeUk%;P?B|gTBOkb2Bq7@sD7RmVm>s?Gk`awpH)5wRYgS0mI&NvQRzr z9+QNxtF6ssG-nwM28Cc<9STb6VXp%+4`$+@z^DP3g^_&Za%m504R~+}OPg)!>+1uv z$K}o(+a+xy09M={FSfdD%FlsOzyBX?Zy8o~*me6#mvnbG(jeU}-Q5aEmr8eubO=Za z0)nJS7__9Mlt_0fCDQEa^X~V2*Kw@cw~DXP zAB)JK{V;lGYzV(2NC>imSft!gAX;5t{E?NU!?y41?cJ#yf)fEkYWKSHc>;I$tirbj6#4Sg zn1bP4U+k2ISvEm+Z|NYMQxr)Vap-)@7tqA*6d-q|G z26y~e=iAb`rr#bJ9K`khYOVf%Dxe8Px4+O1Rb`qgu#abkjzPSYb146K+FyQ3cM@B(` z3`m}|C<5ggm}WtBSspR?mc{qw(a`k41wbi9#sV9ZXO~P!H>s-7o~c zN={x{_qFbd{$T!2Igtvo$wpZLvAp$r>q*O}%$Om6Vh3CL8lMhb%+eK{_g;U#Dju-I z_pQ(wZs--fRjyh6C7HB8Kj(jxiaWD7_no0FBY2bD9Z`yT$^FN|QigPJoVt8xp;f?0 zjNRqpe8-S;c`_55sXEmqUhbJN?_ro#?pbB&uZ{0$q-5ut;!)Qv;kgZ|m?RCo4W<=U z!V(fI5a8f_YfNJ2=*a8iDDB_?vQndxa_^2|K5ek&z8$SFmx`H`=}N zH+m3AUZR2+Yan{U0MZx+h$}yfBEES>+>eoDLw!IB(`3T@HIPg)fZcPDOL|JmWqbgB zhg%-kx?55MVbayTt2bq$qN0bX@j8CQ@Mf`_)R!ces%P`phS*HhPAuv6`5kuFz+iL5 zeR)PnS^4+}(v@sdmdkVDtaT3Ss4fNF&h{q-I8eG+AIS5SWStL@@aA}xw*@Dqi*B>vQwy2;rHdaZ@-_j5{`Y6Q0TvpnCwf4^K= z3r`9DA!9~=)?A6LN!Op5Ij;2b(I-l&l`I}nTGN@Ih`txUUFcQmvF#7Za1E(33fU*1|O z*4c7jNb-8TSHp(w>k?%2rMo!iY!Cq z+o7s_NnR|30h^>b^cx!<*1M)8r@|KpgLp)c+P<70=zHNwW!c?v5LClEb-YCTv51+JSSD35TXr zus(rTG&%E`ZPsygO86E}8d_(_na$PJd5GQk_43RhILFQMi}`8evS4&vp#>em*;q zK5UL4Wu&7GGBdVoby<4X6w78|y8Ln3s4PKQK0+$QbIs5{YEOh(*v=!&Y}ee`K+vLI z`Kv(}`P}`VwM_2%!gq5egGGCyObfzU4}z+9!0m$93ML9M5l0GL zJgb8HzYB)*eonpH9*&)Bk3iE>%j7D>D^OF@jC-M4J^C~0;T`pMzBhODEZd)Y^LZ0m zNLsjttY#)gxq0(vBFCT{=8V62pq?x82$*!Q?*GzHLqS#jG9V2ja>Y5&epeTc6$S_)+mjz5e{m zcnh_@(TDM#k0w@5i}e}yc6NWZ_2{4lq?JZ3LSXx?Jl)wako;uo$5KS#gOZZkqW!9M z0bQ;K=b2SZkpwQTuF&~fE6}V?a^7B@yN=yS7sq?DwObeX=%e&W17GR1t8+|t+;CEm z>(C2d9bf1&kzI9+jP6@WVcFTq>%Ngdacg?}w(iZFmg*cn3oX)R*ZIUh_deZkq0`Pu>TUB z=mQSs!n{18Qa8`tu{-)KJ#K;sScLOoRaGv>+dMNgxpHalJLHR9jjpcVScH=LIc`%w zc}34po(Q*xWJKGp^2^Ih7cWCTkag@6d#fm&4Em$hk%0kSE31Otl<3-+y}Tw>_AyWP zl(GIId{t-MsJQ@|-?!}a(fN0i?QBc*W)=!lE5`~Or_R5feADpNNbmvGfJ#NJ_xsPq zW+uKC4o{nv1IZU>jY~m_e}TF;7Kk3wYab}5rf$xEvqJc=O(^Bdc>-9q>d1XX%qk1M^nDX=8jjP~E&-OZucWh(2l6u7?q zo4)RJR&2J#jZP)|>crRA=RMy>uF-3b*(QAkE&6!Q?Jcsc?LMn}c?_SQ@zi{9u|YUJHEo$zg=X$-iu%Nj8lo0fsilEC`!ek2Jl!yukX+#ijpw98>&| zIK&|xH5r3kO<@=`PvkPNh$2&J6-ey^Z0XgjUT}x=EgicG^D!aV7q=nj_ z?FKWdds3D{usPQQiB;pG&amnu+{<$?qW&H}Qo!gz4{SrQw^lres7a|p!i;{(*=!RgY)z$1RMfyfh}-*vi%*RwBTP51#9N$sCE<*WSkGcB>;n8 zUob1RCyim6?G10##o}6xPB_OTgcJ_`SfD=XtmiV)^+%PwMfvEKeYv zNci+sdMtM)lkEXQ0EKA3tRT2slaNraJNoOkPN(1T&Bgjlc902Z_-vnrA)?IOTK$N0 z90MJLl-sWe(c>yQ{bhPI8b1;cx-ZwyisXtZdL*TJii%n=44OQ;qa z<)Sd58<5 z`3m9^kinot^x2spCqfOGfw#>gN*)Y@yj8*59-b{}2;{wy5-s zvNh@KXJH|okD^V_?9^0d|5b{|=xaqzdJ||RiR(Yq)-F*(Ul}9$RS>+dN$!Q-UOYT( zY{s$M2Wij4rcp<*C^+^oFgi}?o|`6z2=epm9%K}XK%rw^XUqJE;FHfbB(7B=59&go z!%s-i_%@^$@)V?qh`s&&&2Q&~*22-%G^gkJ`BGHmtpX70<>FFTdAG;6oZQ|nbEx`t zZKQw}HQnRM6)~sbx2Y*j5oFfq&z~ELRA?843p`Q>t>^Dpaj*6IVczZ4`@a5uaOAaR zlRy#c51|p>ZoKw!7RiuNofam{k{p@7=X73}Z&e5+gu4B?ty(+RM$qQJ1SIu$E7k;G z`(u%CtrcyFh+?8Ka6}%U<>D(uJ)W%Sw|}mUjzju)5YK6&x1#I&ckkO^9yU@53MRv| zMmU#vzXJqnMEb}f-Xjyr>8^KOKt%aTj3euh{HoT*$qC~V?l5VZ86QlgKuKvrLc@7Z z8?BA8qA?d9!INFV)59k$+{rE#ggKsj7ehA)zs0K|pRS$|UgZjpUg?#D&z8qC)zL!u zA|78Kn7^w64d8M7wWR+^VqBbo2wq(YOxn^pjSiuSg`A4bQT~fAzKE$hyO(gPbQFE} z0K3K~*)a~ocPq;}NJk|H_XItGFa#P!xygHBOn7v?v&f~+I<=sN?W&@~A@VLUt+@;K+1a7n zV;06BM#sQVL>%&uR7YOS1VN8#ae;99CFE;g&PHF-GS zG0_3%;9*s3Yb&Zjebb1$UD_s%8FPK&|D1M7K2vG)yi@{^LWheCxSZkmcXh1-r`o-H z55q!?m1>i~US=J$v%Bjw%J?j#L|GwG-f+ALDXMF@mJ!gz6AHDA0J;DDo zeISv3<gwdB4FVFA3!%H1zzHKD!7h@U;VAgjMFM;t#$oLBl+ibTu2G_)7(5!c{@3kh-oYu$`tnq z2{G-{n1-_7A8dao?Nsg?dQQBXeO09Z=IbK^y!Wx-3ISzZEFf{fGt!oomG$!M?6IGI z4uJzD$f>d^8GoT|u5g>r@!r(r)a{OY$gwlbmR!0~A$kb#5!EXF)Z;|6JTW0bF1-Rz zAV;)(Bzfq>6r{W^B3B2NEk6h~+)Gl^TX9*Px=$iU#4uyqg}oqa{3?(0C5 z20x6Rj|--U8EaQ^O&%WracuAA22Wzl{?EUtC@4TWQdPe?dOmx0b_RS4+q+FoJa8B? z1~N;XLL$HQJT@1wPi&FR51A+X}Wmv0yQh#zfp^5!~5XEq##+W%yWiHeMr^!~$v zccAPYHv%g$5ll*tr+gru9Q7X6s#pd=+&sP(eILdb0547o1f%pEu&${xQd7MSmJY$M zZx?#cNOMfrtlnN@C}*-#|7})svWV{vE%@;(tlCIUq1<@7H5%O7imS6XkTD231{92? z`3c~7t7jAR0VI4L2tIWU4JRk>dU(Pj2B%r-HYhzI(gI~x$OVyciOMo?!$`Oslky;d z(f(RhT^(3I1o{Ak$OnWi$FXdJkdyB3m9NfF+C3c1l>1O1x|$%Z|Mlit*@F-wBpth_ zBhsW@d@=IK29Tg#YH;#C*kAY(1H@v_^X5z>w8Z5`hM-DA4NtLrKL{->kJ|!3R|y!* zj=~2OKuT_O`JvE^Jcn{fUWdkN+UPP5aDgaHQGk;b>c3r)^ZvRG1EesH=Lrdw+C?sa zJ2l|u`Iq?}wn`D-k2M{j1Kg9)^>E}-YLR24|($t-SHj+>`+ulwmA(88ZJjO}%XdwNN zRyr7%JyvzL_0_S%?_tvQYhGMLj6?y^$J(a^d@d5t4xUYv%c_K8>Gz>ojE(#jV!|+u zEb& zN+N1Dhi<+EP8sdWF9AA$ypeLN}erY4CbGoeb6eX28~$cNmulKp4bP)CUEmIGz1*2 z)in$CQT-7JzUuq*ly>juQ?k^Uk5QwA8+_=?GWji?1{P$&^_0 zo6&IqLd|@bE8~Lq1=gdVKN7I(k)TQ(u;~uzm-4f0@j~jcxoNWNm5Oq=YQvE!>oi9G zUI_4vW;w5A4^6KS8|OUivLs&GkQ7JSXZs2VkA zvXB22GlgLyTc-Q+ZV>7Q(sE~h$$x7BHvnE^xy!uOS7~(P=m4B?WgIqM%-81%Y{cR9 zuCsz&p&*BaJ1<{)`o4KU3SO4GVr2e^clq`raZxhDio&F`j4(}>J$z7EaFu|_jIbUR zh9uh&jE#~^+M0PrFrIFFoN#A&STp$^bWME-BUIDz!%1#0XwYRzcVsQ3c_W>Pf;8z=r!1(V#`O{=5vaag_wM2SkWG`3WDHs#Aoh4N#2Ria zPvvP1Jn#S5O9x(#AO>jfH#6L2&B1zGUqrCeC&h_D5q3JzWYgU_K^j7dj~~{^Id@MA zvFir{4J6lx+^!^dB{I?)ccM-|2#gi*_6y?$*^NHijya-4@jpKrzn>7piru3}J#EBpG0}l%9|X=_ z6YXjO@3%&;>cuq{oOM||epr$W8kuGRqnJ>%XDfDdb;M``8zlCyyRciFhU^+X7=ko7V&16QZ zaC%ZV(zK*7$G|xad4cfjB)mKCcn|H+IKl>d(Cl9{y!H39eNN)5l=5sTx*t9jxT(;2 zaTv)WU7s1u^~=4*dnT70+doM%jHPwAZE4{PS|=B7$mFjmzQb&>LQgrNh8D#v%(eXJIA-Fm4Nk|L}WrE$P=39=}Z4jRo!~L^)gTt^myHZSMqTc$U>2{%0TEm zwZGs|tDB(zN{tRAYA0fPWR34f#b4rH@v?mlSH|bp9F9O0nbUJ3Po+Y-Dgl!ffybI4 zVnQBXEi0JU)N^{mjK3dsRZt*egqREBIHM{F$)gu^ z=nO)gU5wxT(f_@G|5Gbi{?{t{f4;^NmzA&aqz?xTq33BlGJ;OASt(%_Ob5M?k`qta zyZjx}-dIpjaFs0fFJa51dTX=PPOpt{b$&KS`~=LQgaL?%h{jU@x9T0??=(4u;7;?i zLr^HKl<-o+m{f(@u1^Eh)G!B09u4K39~8Maxmg&WdhJYX16)z^*+DY?zx6Y^VR?D; ztG!8of6bR2{e(L2)RJJ6{C$rMiOOiKW z0^W7BGuPtm;<5(}?%FRPsFkL08dc-aN)d2df=dtP7b`GOj;xUb9Z$dfh5E0>wvoRt zD1=aHAudb#6SNnIRX8P3-mkLwv;g6a6f$ubkNgWtsB^deCrttL_dBYom_0AZqe4iz zuUI+M4*zIFElI;`Jqlb65Z11)kzfPEM3Z}nW3mW#_d@`?q@>_OyaP%#5TuIo^YcYI zATbPrja9jJ2V@s0DJcc+g_b8!{=Ehv2wq@??wbJ^H-Ys%(!Hvx>R;p>n*Ety140y9Of}3^ShZ-{JeG3-*#RaL8TRg&%gf8vMVQn>oO@ug|&mzS8=nu@kI9ObjCi9{%mUVB=mbex z!%IOZeHJ@=cDUFA99L5$ar9GsoW1!^?lqP{Zgb6VlMDTQn-~;x0e|Z8_tzIfR!ti% zuzwu#5E0&XimcdN!r$;`J3cZf&!^*8@wdE}-NMAms;s2M%w&DT$|oucsYnY8>L5{g zLO*^aPc=Qcf)P#EubCF_>A9BU?p6bkQN8@@a8aOuhYs%pn}QB2r4X5gDTJ7qh~drV9UGZ>=KxOW3^J}~GvG@4(6E~{a@9V;1!3+$h<`A@Cb z6cb6Y=rOYC(rp$U;W%@vpp7*)H5KsLQIzgOZ!}kHgG7_5NsQcR+Tb`3)DSX}g$2^O zG64EwIze4oqyQfF+IdK3x4s7@95hnMQn5e!3w+DVt8;HhfUNVkC1HIBo;gg&F)9zr z3RobS>*Uq07G8$R46eu*A^Up`3BOA*>?uiK;*pRf>Mi+jeL2~g{Ql#I$>%b)i~-8~ z)iK$}pi_d336=tQe#~v;ux$^QyDnk2-tNh%UDyth+R`Nz+eVy<$OK4+T|juK6orI@ z1itmax@2ScrF~yT3k>8LX@IFfzh$Vv-c0!&<_^@&(wpl`z{dl!^>F;npadQ&K!?$b zF>vS@^f1$+kyG@x+Al|?gi2o*7u(&tH)QfLv>y+Uh?gkm-My@jGJ{GI!O=)Yv9r6~ z&m{|!uX|-by@lFt#9T-6zr^OjS1%S{K{F!GR!`3^iqA~o$7Zvlg2IZGf&*+PkP5*W zK@AJ_kpsxUdj&dF-K;FKhi>`N9$vp}!6puvgT38dGdSNs$l?p{%d<~gB_Skqwl2i= z<>lv(%!oV}iZZEqcc-j9&O;12!}GC+Y_i=D=6x%85`&va|K0AQU@HdUxP*-37Zs}%J60aF#OUBnMOL21% zSqY@E?-+55iA6)|*8niq)@57U+m9k%m146+SG&zJTp&Y6(S%#oBRxF?tpEpZg^UpI z0vrOuH2$Mn*mh>3mid5SuJpyFW34mL?Eq1QCOdz1eGR_Euj4o&_StmkPO0nHg#JXd z1!KJjuwaaoj0^?#?jOYxzeW<+}r-LJR zFWevn===QsYIY70w;;b-q%Y;LZY zgM({KNRrtNim>4V--Lv?AQPO;n-G!Dda^XkZBnoF${wP91~csD{42PGEb8mx_uUOW zPk|{3+0EOlV;JJx>ORHHKtmLi$jw{#s$bRV_lZmc84;iLU(7xhs95cx`EG&JG~my8gCV^1 zRuEw(%n9JMrp|kTa|eJ`>kkqgM?m-kL*NC31xt3SfyVKVv0Ju83(ozg9&GLPcRUSB zOnt#|3MLm7$TFI^DP4XF`Li?hkEMJ55OJ{5J|@H7y0D;>HiuC{zsW5k3<<43)Vo?C zjsW;R>+2s#rxL=#5QGo##SaZ`Zz3v;{;pNBJdV4ff2*%g*4Y$ti~k>je$I}T!O><@ zm3fQAAQ@CVZ00jMmnUQx@UY(@4mdkdt%ys}&3&houv1#f!i9xr6tz*&HTM-n1r9^5 zUN6yEz-rfyBqpNh9GmE$kF&mdS0WhIbY8pwKdZ5!L0i-z>6vJ&9O9V|3(M?+=(*l% zKe~&PCU{jMsG zM^ofpyz%uV9JkTkHJ0b3Y}RPZFzwoFa7rVpKn%xuL{mGu2!- zh+zkxMg;4FuZ7J;a1{Kh3}Ee>?^lw8(^juvbIfx&P^!m;l;1ffY`U9({g+ypg;Zqj zJ2OGRJGW&lfaV0PKAJW~Y| zN_lc?&yXuJHf|c6?jYHiNN;TXzel<@%TfUwO+l9n)fVD~Y7oZ$Wc&3Zg%M3|d(G;L zjHphnUfHl+`Pp{aZ(asdV`C7SE3MjoZH)t3?jLxplg$B((dTEQq$5(Zx*$fBC>2?# z<1zSN$AhCuO^TL_GG3EAatotPqHOO?Rth{>5#FB;$q4yX9_ z^J)3RL6%W*WIjP6DYX7lR-d&rHz(foLKJjLK%_k$%+z4jr22Cl05vxG)faHVPP|=( z_My9kl3~v2qF;PAhNRAwnoNop!CIM0#6$fG#a4Zk;>6zBnbSwp5rG2P)^fn4J^8K0 z&+ouIvLK>{^oy}5HW&5D{RT2J_GDHTcT0fv0Q$sEM!pHQQ@+>;U*YoIaTLQcrzyW% zA6^%INP<*zK&+1&!Rmv|Lf>j&B8T9kl8#v>;Ta4Ee`b z8Y=W%nRG2;Ux$t`8k$MBh4gz=FT_zu2~KgU6U~gvy~)&WZVw9h zvsP$d(Hp+nnWThVDBTaW;sXH!~2ca>TyXvhE{n zkHU9|LY9@S2nf$~2%E7%PjJ?gqDiLOPi*naMgWY7Aums^bdeo=Om(y>G zD7%>PH_pz)SlNo|Cm!3CbC${8)HX#W1%CY`xK78y%}tX?z%m;tmMi3putF5e$+xe@ zcE|HB_BB_ae(*et19;{7p0$Ts@)pz4@?M<9(d~NtrBfZnAEGzQGU7E}Dw0W!e7sqB z+kVhZ`;i=x8>*T#I%YPSUs!uW!QKWWxW1gN$S=6VWy9Qf_LSQc-5gmxQVNqC?>M5y zqVXj>jDEEULoz|)=8>&6^;S)KsyDC|$M4tNS3rNgKY3j=LzPY<9+d=DOK0$1>Dr|9 zW1>a>jDK%Mzh~D{CJX1=|3od_%^J4cZ|cAz0eA+wENoZXZIt2nB!=Tgqij`G#~8T& zbaU1Y4uiB<;l!cXGd<1RC&)fzGd)~(GF-eLj{vXQ zB+o&FYiYXORY9%!LF~01Zc^Y|WU<)S{C>yL9eqOhK{OXwX(jH9ZoP^06H6oG@$htM zn=VfkcAz(*Hr;Kd#95{_;O;A~KLB|pMxu_2`PDn77C)X>2jdGLqLWx$81oZMzmJVo zY18MWA@(0-q>b;)bOnJ6(rS6NJxmgxvHdQ-m%O*ryy>YIzM!mvF?0e=zPtJTX}$F) zi05Py&G%ow%0l8WZQy+^O>Q!a5#~BWv?7^|`fdK;w2Huyq`_{yWLqUJIlGpYS+;rk zciYP%{MCxD`Qc>hyTGhMl|-ni%RxJ5K3O%!v9@78~Lxze}C^Wy+dZcq0 zhH}(t+ogOP2wxW&(qtq2iy4=9tP4BXB6=%4Uc(a)%Z^>IwTj=qThq&fEtQ}*at#1B}>y92~KjV|-ZNDuiUnh#^ zh8)40)W3GbBylgyEIJH<5Fg5N8_cC)juF#xu0WY~ar>;&;GAvy#oarj@Ay)}{7CGN zmgTKY7g6vC2*Stss$`p?)e{j}i=nfF0nEFiG7K&KN8Gjc z4nsLf$!Q|dsVuw(F=w#~fp`qmHAsC@_a>(A_w^|hgR4aenVFqk=c3ImVt;6!@C*y0 zW}{blGL7^h7V|Ifi_?!rY(*Eig#P&GV!SUNL3af_6>@c_E-Clf1}Sw<7KFmogSbMq*nBZDlFsfYhM%`ks>!d= zksRse;bP5lDX$SXPw3<5+2IOFI&?!Cu{iX-U^|O%Y6ZX z{+-1+0%Urn@kGjyo2c6vY{f{qIxf`B72h!L<%tqyWcr|bp=vi78%niDbVfPuI4H6ngMZA*FC^5_2JCLX)2EZjDWCd&mEq!% zf1P`xqLLLBmcK!(ZjWd0<`_#Dwc_JsVHZmmo6CSk6B~{jMR%WJ7woPkC2D=b+}tE- zim*D>WVv1=ix+qeDwo&UDmn1nY1X?{so3Bv-kbLQM(4hO%w_r6AHJ>trhTN)m?nU4 z1wO=b0+vfi_5Fhlu}dJ4Uyx@$1vwJLlkih{0Kd`qGGSmM_AlMa z$)43n3b3iFSNl*=i*oIko5$gMwLJY@1a>JHupV0de7wN3vDzzB;&6_#84f5R%+*wuCF<6Sui*#&bcev?m6oRes% zzkE*Yq&bJ$YbhfaV2vZc4aQy%gHYz<#ZgjClMYB2*7jSkUXSK9B&=>*8dN zmwpPil$7u`*>8q(Lmcw_8~Ja@39LrTxt{yG*n-3wiO84+?g)x;y48B_ULJ0@_|P}rrTt72OIH7r!rHyEqH#+9}Oo7p2tL8$x{CPgN^?0qdWbuqKZHy?_S#+Q?e;Lc@)3Awq4}V^MsvfzK;_7+ z(PFY37@wWRye7Ajtr_d^aOzac+6kp8VgY*O^Xe$6)8B2Ck?S%Uq$3mBI_j-^6Y1=$ zn?sjq9CSW9Yl~n9IX8WZN|EUoSpnmRv5um4kS)*^{hsM7PRe?xw8FK9DV-9QW>y?6 zPVu(suK|&|Y$|*gCPtr6(6?&LC?U!3QUH$9KCx*Wl!_v^HZVeH);%2L2Ei%rDwKV5 z@p6D!wRJ^734f0<({iG!JN9(C{-NFZ1(UEG)>N(4od^y$2@1g%)IJYlsjWm(-kOl; zcKL@Qa!f}?rV2mSpN7pGUtU8?ALy&VVikeNx_*Q}E{BdQ&yc8iskB7P*fQWhS{qp^ zB9A>$y&u;4yvv5+@S%XsnasS0(wbgjXkJRn^ADBUX)4j;#-E$9FyFd~D)Ws}Pe=?$ zDPUDpc_%V zAHiJY;3;}rMEq7h@Rdh`2(=*26hj5|%S+p$|JDMsWumXHEWtZNfdb1zwED!-P)}rz zL$q567}IKK;$)3y`tMs$y`AGhZNb3E3bc{2>Br9)lzVK9t1aWFp^}a~z-;NFzV{1* zv+Ff4IA4ht7<=rKz1KT5uNJA^XLq+u_X`EvA0={ zUzA&i8;*kcU}x(}6QylC5xo2!S$vHh5l2gbMLs*|{S@K^0{}Ph^m5yiSlqjxZh7%tM+(z-*2`YIAEbQ2*fN z6S8kWKka&1IKe*)SW*G=v(BhN8_z!1_TU`ao6*wO6Dd3Jfxu|&o;oR)#Yd8T%eny7MV+jCi2`aZ%i z@9tGPv#cA#EtDVXr}s4FR0xf^k2kKx?RwVkwrWPurK|R?FxfM&`@$n-bHFOez=UX` z3Ys%qs{?bZ<_ zV-SIZPQPhA3$1T zAd55F#QaYn-!23;c|%x*!-y^?UN4_&ShGMxMb+tr0l2UC^#d;-^Y@Jo=UrO0oocD7 z>SWU~FpN)rD2K6XQ4#&We{y0nb$oUR7PFfwt2V=8r9UvP82533HGkCNr$w6pniG1g z-=u(Hw5P730(9v@uw*&!27-g}!(+c#TntQ1-~sr;r3hSOYsx81b5^Puut7d}fW_1d z3J~mlSY2I(v9h&wp)nF@-&H!rVy&G@{i$DpA3#e)1eBP$w|X$tb*3jz@0*$`g%uFs z8a3*p=jQ$era)!o-mAmjMeR5EjHnf#F~fk(DB{d!ABOnk^c2O%r*-P*TfI$B3?Lu4 zO;*HJS99m;+1afg9K^!^iAy=vrjpVLxbb_?P(DyolS{W>_#86YHJMG!kz`tD8hddoeTs8M{5`Qt9i4r<2?AGY3`pa1xmY_)7|v&zhg?8=|3ILTYVx z))Y9g)B%vQZ*YD|x`rWMl!S+ajUwaZ`5Y|&IBC5UD~(J_QXjM4fOYl*v$GYBI-982 zj~82xnN{wJBX(?y`APsICn;&`T;#KA+6538G;#z(QxFl3{saL%dK(hBwhrJF6Cy1H zL3!((*U~z^=zzVAg(B@XM*zI3?FqzxgcX$2@o$Pwrncd0V8nm0KrK?-t(jt0`9)l&7 zC}NLehZbUJn0Zwk78bT4lho3}z6ddM0G--rKPB#Ly7Ee1x^)nY2Lul4Yj>ygD8>rNXn9mAFyr(X?e;S@&O(EY&-;;)$O`F#L^6Y3rK=o~Nh~Zq%~aonk7S6a z8P9b{Ky3Ty}hpNSX9mh8haWnjqr*#9G!I{+SYzGu_i9rM>CmsOgVkPjhZlU^tN{OL5L^W6 zM#~3)k(&9@&%ZrW_Xi-%0DX@br}qNpKC>rIEZ#li+7lQB0M(ydq8WE(5k51Smpx3T zP#W`YqL_5ojUr~zqhQUx=R&2ZIO0HtT);=$ul_`2sn4C4Wh>>PsH_q@~iK6Iyw#rI@#L(-9`)KP{L~E z_MRTVXadv`8wl8IpoANffVn}yd&3IqLVJ@Q@GpW_wQF2cI9)?zED}R+SzLhqaM~&! zD6#KqVLyrdKcja`g1M>7b~1G;z&~(9subgu;1fNTgy!s{$1>(3AkbkR8R7c#6lR2_ zIg>Cj7%PV9Pk)2maBkEA-gzTHsW9&tr932uzExCA>;POkzhUpJ+#u3-i$+Kdz|d8w zs_eVEz5N7$)NQvJ2;)=C`c!=J2kc{*=p#?gy+y8sY_#5}t;1$^#z5FEqvYYy7f<>Y zYbb&8DPGLSC;Kel!~bhNC^{{^GOa{&Qg$}`AV0X)^`XXS4T9ffA6Nb9505a|8KAYh zE$F?O$n$_F+xW$toKi)+HjB8ess{-skfC^a@qTx_c=h4W%cSUN)F)4b$y^0DBaq>T z1FvL0UlI*^6IgX{9-MWpkuwov3d^Pj1_tUMX*_kL$}NQ9+UifJwN|Z~V@B)W>N!CC zpWu5SAgq*=@;~~0XXWN@HLhcTHaF;cwhi_Xu)$mB4eOd>z>Yo-mp7Q(VV)Q&j=Iam z%S#rm4&K-YnLdgLV890MTRT>0;SB7j#cn#8{b07djNj(~!$6VTen!WK1L=3ME@AyT zJJsO##gD$EqTh1Q)8ICZ+&TSP<<%x+;Z!&~ha0I~!JTHz^dAJx`09_S;)@VI>PTk7oWP_F@VqPE#JR+jNc z({JA4@FQr6N-aKRI&#b;#X*=|6L!xa8o}AJ4)eGpG5yx3J8IQ01|DxtCrVGiz8o8^ zk;jne!J~as|8auY%)%A+tRtPnZibHGVUm{-&p6Yt*LarHKl`r(teyUacez#+;_5G2 zF&`=#d3}8%%(>Q}2Mrzz)UypviCuur*#BjT9X6}~K9$HHVtq#|W`TrC5JSSn|g-yG^f9%``moUeyEesGx6(C9g+au0`1H1R#M+=_-+{W6m zJzLrE0If)<`TOyP2CQSHpXF8yKHXD1QH3l9?32xt4gt1uBKo7{DWKfq-kzfW~#Kb2eP^{;>)^UWd-!gxqZJH8c{l0 zczg*s*@Ec^mV(i8XR+%N&Gqq!+P8B$KDWOB-H0c-_l;OtMoWt{0((|?2hZr@VA-xg zcY(UlAMX>aG=*;$2RAqOkk#ut@uVQj5!rAbKU(3uNkm&w=heQOQ6%)H?&{cn4XvGRQ>9IQF7CrAAo&9Jb)O2I&Q8m@DHFWBQTKk)2e?r1E_;Quz zMtrmfV`22!>!O#OMieh2FyUKZ^gJ;}hhkGvD>?&>5H~7JM&6SCu>1*3?6&rsEi6lZhr14`Y4#_seyfSlh0;y~atQ+*4Ti z7>NiMBTCD1sbgA3Ngqxx_H0rq(%}XPd+mo0B&?z55jnbU-)%>cA|mAdaDmPr;T#$e z%J)1ZK1@+1?i-&Ae}W8Mq=;a9GunHrGxTEl8Kno|V_1K}2yA%mj~pd$6j>t)I8 zgfOxpQ98{qfB18CEgFqTv}0@4FohAC`n@a0z(}RoHX*^Qoot5II|5|;#EdkLx>+=H zq3%>Uk$%#yOr5+&TNGAedXf^j$Lo+X$VB%Ut{bfc^ft80l$Org z7_&3!8R@$|AJU&hmDwuODfUOdcA0CYejLtk$I7(=>n!;GytDk$-`!mT+gu&9V+q0W zt9YhX^tL1iz73_DshC84iw#cl<*7@19=sb@$0L|N&&UcWa3G`h=TA^<>?)W?&S-la z&dz!Lg{Z!M3WDfAH0h3-gqunKP9i@pDAFUJxvy8BvEulSfNhrRC=w*8l=K8xxR#ri# zuHd@&h30q3u+GY_y2+bTK=(SrBiYkVPjpo<>{2^z3@)FCHBT-nH`8!<>FeQwhetGF7*rx{9vsVu^yZb^4=YSm6ZS5H$mcA& zabVH-(IC(JLJf$*b)dT7eH)ypGLt{9Yt}m0Ulz5KSqjnS+X4e7t{_#E(j8~?i2u>v zTSiqChi%?82RL*cy1S7MNol0JLj*xeP(ehRLwAXVAR*EqrF0{TgdiayB_IM)0>a!r z@B6&(hnZ*A%$l$B&7~|nZ1(=|JFe?@G1DtXge+G%e(&hlc!GODe=Z(nE$JHZAVmuQ z(a7*t>4^X@xrS8YAAT-8{4U2nPDN|d48gfXcCGmwE@WcClz!Na0JZaQ(wd5~>ZaDC6cUVY(;g6KwA+^q#Y| z0fO{}s+jLE>#7XLCfy!KQFvjet40)g1pRc%oTy8+OaFyLAj+0p2V|4p6BO|{ny7m! zH*^BIVjm_s3vilS{dCWRO>CDhdRXOLS3#5Qg`wT1p#_8Q8$;%+ZpzB!dnyX3Js+fF z?pw9Li=+fp%xU2cahdNr5KJA87WclvYn=Y;DT6dpB{x^A|C2ZQbW9oh^|&)Oizpkk z%xif<4pO&HGM$bnv9&NVBu!h_frGj_4!70lQ$WonkrlTX(K-!K3w$wnEWy;=xj?1q z>qrWv1C}aB@(sib$?mSlS8lS|-g6hrV9?3^rTj$oH`k+!^FOe*+`HZMfN=b1)9@-2 zellL-kX`FLw~yyU_R#+S#I-S~ERrkjhN4()L2M~Hj-NY4^2IEa?wI1IXTOxnbz>gK zX0no?gQPz!lhRKR9VMuWk(tM~60-F%-P~toRSL`zzHL;*rEA{J9u^4F3cg~=()XZy zDNy1#!2TJT6V=S0#W$J7*iT66iYR?^*Q?{6S}Fq}`e+=WIeu^A3k~h`6!|@|uhVe9 zfG8#OZOdiF2Y37Mp2Qdx;;Fcc+Qf6?7q)@Bj)Xb^{ze8@s6$ z*&TDxiv2?aFVKKNtiNNj(MLKvtHU^7{05C%8{0_Y0GlHYD8pcNsGUphqD})+A&*

n36-hD2vkSx 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�^5XjtX5)-$9PBzOG;T))$!reA*L{^{w&ivYHLs5ad&p% z-fAb1*V~|9rmEd%2n^%JmWbGKMyl5pGLa5p(IR6=ZwHB@7hjKJ;@=WFGeOBh3>kL$ zEr{1R4?tQ$_-MV$3zu%yzF!t*W@^7;kU^Av^TzQ!Gavu12=e*M}tuD9e@>uPJ=f3GssSK=Cb zia?m)9uVK!?g|00O-YR%vavw)f{U9TxJANckypfi#0p>wpz3JO*(p_=U0oNqc#ytP zVH`0ivOA9pP?3Y~T7)ktRn( zm)dmSFRaE-siuiv;y@lv^+RX+EFD#ae5zeU?trJ(R^8f2CqlGFxq;WEcaBCyvS~(1 z5)G4yAGC#>4A0EWgnUZwOy&6C=kl+jkoEqbmKQis&_uGa=5qe8R3s-X7#J9rm!m^| hneYd^e?1X4#?P4J)Hy}JSoj+Z?Q7T7D^(pM{u?2jRsH|~ literal 0 HcmV?d00001