fix(tui): route /skills subcommands through skills.manage instead of curses slash.exec

/skills install, inspect, search, browse, list now call the typed skills.manage RPC
and render results via panel/page. Previously they fell through to slash.exec which
invokes v1's curses code path — that hangs or crashes inside the Ink worker per the
§2 parity-audit finding.

Also drop Enter-as-install from the Skills Hub action stage since the Hub lists
locally installed skills; primary action is inspect-and-close. x still triggers a
manual reinstall for power users.
This commit is contained in:
Brooklyn Nicholson 2026-04-18 09:46:36 -05:00
parent 949b8f5521
commit 5e148ca3d0
3 changed files with 198 additions and 22 deletions

View file

@ -26,17 +26,55 @@ describe('createSlashHandler', () => {
expect(ctx.gateway.gw.request).not.toHaveBeenCalled()
})
it('falls through /skills with args to slash.exec without opening overlay', () => {
it('routes /skills install <name> to skills.manage without opening overlay', () => {
const ctx = buildCtx()
expect(createSlashHandler(ctx)('/skills install foo')).toBe(true)
expect(getOverlayState().skillsHub).toBe(false)
expect(ctx.gateway.rpc).toHaveBeenCalledWith('slash.exec', {
command: 'skills install foo',
session_id: null
expect(ctx.gateway.rpc).toHaveBeenCalledWith('skills.manage', {
action: 'install',
query: 'foo'
})
})
it('routes /skills inspect <name> to skills.manage', () => {
const ctx = buildCtx()
createSlashHandler(ctx)('/skills inspect my-skill')
expect(ctx.gateway.rpc).toHaveBeenCalledWith('skills.manage', {
action: 'inspect',
query: 'my-skill'
})
})
it('routes /skills search <query> to skills.manage', () => {
const ctx = buildCtx()
createSlashHandler(ctx)('/skills search vibe')
expect(ctx.gateway.rpc).toHaveBeenCalledWith('skills.manage', {
action: 'search',
query: 'vibe'
})
})
it('routes /skills browse [page] to skills.manage with a numeric page', () => {
const ctx = buildCtx()
createSlashHandler(ctx)('/skills browse 3')
expect(ctx.gateway.rpc).toHaveBeenCalledWith('skills.manage', {
action: 'browse',
page: 3
})
})
it('shows usage for an unknown /skills subcommand', () => {
const ctx = buildCtx()
createSlashHandler(ctx)('/skills zzz')
expect(ctx.gateway.rpc).not.toHaveBeenCalled()
expect(ctx.transcript.sys).toHaveBeenCalledWith(expect.stringContaining('usage: /skills'))
})
it('cycles details mode and persists it', async () => {
const ctx = buildCtx()