mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(desktop): SDK — useGrabScroll export + dogfood plugin touch-ups
This commit is contained in:
parent
79e7adae2d
commit
5c4d1e1ea2
5 changed files with 614 additions and 0 deletions
121
apps/desktop/src/plugins/example/plugin.tsx
Normal file
121
apps/desktop/src/plugins/example/plugin.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* Example plugin — the authoring + publishing reference. A folder under
|
||||
* `src/plugins/` with a `plugin.tsx` that default-exports a `HermesPlugin` is
|
||||
* all it takes; `discoverBundledPlugins()` finds and registers it (no import,
|
||||
* no registry edit). Delete this folder and everything below is gone.
|
||||
*
|
||||
* The ONLY import surface is `@hermes/plugin-sdk` (lint-enforced) — the
|
||||
* vscode-module model. This one plugin dogfoods the whole authoring kit:
|
||||
* - `render()` contribution — full stateful React in a statusbar slot;
|
||||
* - `ctx.storage` — the count survives reloads (namespaced persistence);
|
||||
* - `host.onEvent('*')` — live gateway stream, counted in the tooltip;
|
||||
* - PALETTE + KEYBINDS contracts — "Example: Reset click counter" in ⌘K
|
||||
* and as a rebindable (default-unbound) action in the keybind panel;
|
||||
* - plugin-local `atom` + `useValue` — module state, leaf subscription;
|
||||
* - `haptic` / `host.notify` / `Tip` / `cn` — the design language.
|
||||
*/
|
||||
|
||||
import {
|
||||
atom,
|
||||
cn,
|
||||
haptic,
|
||||
type HermesPlugin,
|
||||
host,
|
||||
type KeybindContribution,
|
||||
KEYBINDS_AREA,
|
||||
PALETTE_AREA,
|
||||
type PaletteContribution,
|
||||
STATUSBAR_AREAS,
|
||||
Tip,
|
||||
useValue
|
||||
} from '@hermes/plugin-sdk'
|
||||
|
||||
const $clicks = atom(0)
|
||||
const $events = atom(0)
|
||||
|
||||
function ClickCounter() {
|
||||
const count = useValue($clicks)
|
||||
const events = useValue($events)
|
||||
const gateway = useValue(host.state.gateway)
|
||||
|
||||
return (
|
||||
<Tip label={`Example plugin — gateway ${gateway}, ${events} events heard`}>
|
||||
<button
|
||||
className={cn(
|
||||
'inline-flex h-full items-center gap-1 rounded-none px-1.5 text-[0.6875rem] tabular-nums transition-colors',
|
||||
'text-(--ui-text-tertiary) hover:bg-(--chrome-action-hover) hover:text-foreground',
|
||||
count > 0 && 'text-foreground'
|
||||
)}
|
||||
onClick={() => {
|
||||
haptic('tap')
|
||||
// Imperative read in the handler ($atom.get()), reactive read in the
|
||||
// render (useValue) — never a stale closure.
|
||||
const next = $clicks.get() + 1
|
||||
$clicks.set(next)
|
||||
|
||||
if (next % 10 === 0) {
|
||||
host.notify({ kind: 'success', message: `Example plugin: ${next} clicks!` })
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span aria-hidden>◉</span>
|
||||
<span>{count === 0 ? 'click me' : `clicked ${count}×`}</span>
|
||||
</button>
|
||||
</Tip>
|
||||
)
|
||||
}
|
||||
|
||||
const plugin: HermesPlugin = {
|
||||
id: 'example',
|
||||
name: 'Example Plugin',
|
||||
register(ctx) {
|
||||
// Persisted count: hydrate once, write through on every change.
|
||||
$clicks.set(ctx.storage.get('clicks', 0))
|
||||
$clicks.listen(clicks => ctx.storage.set('clicks', clicks))
|
||||
|
||||
// Hear the live gateway stream (deltas, lifecycle, tools — everything).
|
||||
host.onEvent('*', () => $events.set($events.get() + 1))
|
||||
|
||||
const reset = () => {
|
||||
$clicks.set(0)
|
||||
host.notify({ kind: 'info', message: 'Example plugin: counter reset' })
|
||||
}
|
||||
|
||||
// Provenance (source: 'plugin:example') and the namespaced registry ids
|
||||
// (example:counter, …) are stamped by ctx — authors write plain
|
||||
// contributions. The shared `example.reset` ACTION id links the palette
|
||||
// row's hotkey hint to the keybind panel's live binding.
|
||||
ctx.registerMany([
|
||||
{
|
||||
id: 'counter',
|
||||
area: STATUSBAR_AREAS.right,
|
||||
order: 100,
|
||||
render: () => <ClickCounter />
|
||||
},
|
||||
{
|
||||
id: 'reset',
|
||||
area: PALETTE_AREA,
|
||||
data: {
|
||||
id: 'example.reset',
|
||||
action: 'example.reset',
|
||||
label: 'Example: Reset click counter',
|
||||
keywords: ['example', 'plugin', 'counter'],
|
||||
run: reset
|
||||
} satisfies PaletteContribution
|
||||
},
|
||||
{
|
||||
id: 'reset',
|
||||
area: KEYBINDS_AREA,
|
||||
data: {
|
||||
id: 'example.reset',
|
||||
label: 'Example: Reset click counter',
|
||||
defaults: [],
|
||||
run: reset
|
||||
} satisfies KeybindContribution
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export default plugin
|
||||
Loading…
Add table
Add a link
Reference in a new issue