mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-24 00:55:35 +00:00
feat(cli): add Pi agent profile
This commit is contained in:
parent
48376069db
commit
c888be7212
8 changed files with 168 additions and 5 deletions
|
|
@ -6,6 +6,8 @@ All notable CLI behavior changes are documented in this file.
|
|||
|
||||
### Added
|
||||
|
||||
- Add the `pi` agent profile, displayed as Pi, with `--agent pi`, project-level
|
||||
`<project>/.pi/skills/`, and user-level `~/.pi/agent/skills/` support.
|
||||
- Add the user-level `astudio` agent profile, displayed as AStudio, with automatic detection of
|
||||
`~/.acode/skills` on Linux, macOS, and Windows.
|
||||
- Add repeatable `sync pull --skill <slug>` selection for non-interactive and JSON workflows, with
|
||||
|
|
|
|||
|
|
@ -172,6 +172,9 @@ skillhub install pdf-parser --agent codex
|
|||
# Install to AStudio's fixed user-level directory
|
||||
skillhub install pdf-parser --agent astudio
|
||||
|
||||
# Install to Pi's user-level directory (use --scope project for the project directory)
|
||||
skillhub install pdf-parser --agent pi
|
||||
|
||||
# Install to multiple Agents
|
||||
skillhub install pdf-parser --agent codex --agent claude-code
|
||||
|
||||
|
|
@ -219,6 +222,7 @@ Most Agents have both project-level and user-level skills directories. Use `--sc
|
|||
| `openclaw` | `<project>/.openclaw/skills/` | `~/.openclaw/skills/` |
|
||||
| `opencode` | `<project>/.opencode/skills/` | `~/.opencode/skills/` |
|
||||
| `kilo` | `<project>/.kilo/skills/` | `~/.kilo/skills/` |
|
||||
| `pi` (Pi) | `<project>/.pi/skills/` | `~/.pi/agent/skills/` |
|
||||
| _fallback_ | `<project>/.agents/skills/` | `~/.agents/skills/` |
|
||||
|
||||
For a custom path or an unsupported Agent directory, use `--dir` to specify the installation path. In interactive user scope, the `generic` target is offered alongside detected Agent targets. AStudio appears in that selector when `~/.acode/skills/` exists. When `--scope user|project` finds no matching agent directory, the CLI falls back to the `_fallback_` row above.
|
||||
|
|
|
|||
|
|
@ -14,19 +14,20 @@ import { traeProfile } from './profiles/trae'
|
|||
import { traeCnProfile } from './profiles/trae-cn'
|
||||
import { opencodeProfile } from './profiles/opencode'
|
||||
import { kiloProfile } from './profiles/kilo'
|
||||
import { piProfile } from './profiles/pi'
|
||||
|
||||
export {
|
||||
aStudioProfile, claudeCodeProfile, codexProfile, cursorProfile, githubCopilotProfile,
|
||||
geminiCliProfile, openhandsProfile, windsurfProfile, openclawProfile,
|
||||
kiroCliProfile, rooProfile, traeProfile, traeCnProfile,
|
||||
opencodeProfile, kiloProfile
|
||||
opencodeProfile, kiloProfile, piProfile
|
||||
}
|
||||
|
||||
export const allProfiles: AgentProfile[] = [
|
||||
aStudioProfile, claudeCodeProfile, codexProfile, cursorProfile, githubCopilotProfile,
|
||||
geminiCliProfile, openhandsProfile, windsurfProfile, openclawProfile,
|
||||
kiroCliProfile, rooProfile, traeProfile, traeCnProfile,
|
||||
opencodeProfile, kiloProfile
|
||||
opencodeProfile, kiloProfile, piProfile
|
||||
]
|
||||
|
||||
export const profileMap = new Map(allProfiles.map(p => [p.id, p]))
|
||||
|
|
|
|||
2
cli/src/agents/profiles/pi.ts
Normal file
2
cli/src/agents/profiles/pi.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import { makeProfile } from './make-profile'
|
||||
export const piProfile = makeProfile('pi', 'Pi', '.pi/skills', '.pi/agent/skills')
|
||||
|
|
@ -667,6 +667,72 @@ describe('install command — server errors', () => {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('install command — multi-agent & auto-detect', () => {
|
||||
test('--agent pi defaults to the user root and persists the Pi agent id', async () => {
|
||||
const env = await createTempHome()
|
||||
registry = await startFakeRegistry({
|
||||
token: 'sk_ok',
|
||||
user: { handle: 'u', displayName: 'U' },
|
||||
skills: [{ namespace: 'global', slug: 'pdf-parser', version: '1.0.0', zipBytes: makeSkillZip() }]
|
||||
})
|
||||
await runCli(['login', '--registry', registry.url, '--token', 'sk_ok'], { HOME: env.home, USERPROFILE: env.home })
|
||||
|
||||
const result = await runCli(
|
||||
[
|
||||
'install', 'pdf-parser',
|
||||
'--agent', 'pi',
|
||||
'--registry', registry.url,
|
||||
'--token', 'sk_ok',
|
||||
'--json'
|
||||
],
|
||||
{ HOME: env.home, USERPROFILE: env.home },
|
||||
{ cwd: env.cwd }
|
||||
)
|
||||
|
||||
expect(result.exitCode).toBe(0)
|
||||
const installDir = join(env.home, '.pi', 'agent', 'skills', 'pdf-parser')
|
||||
const parsed = JSON.parse(result.stdout) as { installed: Array<{ agent: string; dir: string }> }
|
||||
expect(parsed.installed).toEqual([{ agent: 'pi', dir: installDir }])
|
||||
|
||||
const metadataPath = join(installDir, '.skillhub', 'metadata.json')
|
||||
expect(JSON.parse(await readFile(metadataPath, 'utf-8')).agent).toBe('pi')
|
||||
|
||||
const inventory = JSON.parse(await readFile(join(env.home, '.skillhub', 'inventory.json'), 'utf-8')) as {
|
||||
items: Array<{ targets: Array<{ agent: string; installDir: string }> }>
|
||||
}
|
||||
expect(inventory.items[0]?.targets[0]).toMatchObject({ agent: 'pi', installDir })
|
||||
})
|
||||
|
||||
test('auto-detects an existing Pi project skills directory end to end', async () => {
|
||||
const env = await createTempHome()
|
||||
registry = await startFakeRegistry({
|
||||
token: 'sk_ok',
|
||||
user: { handle: 'u', displayName: 'U' },
|
||||
skills: [{ namespace: 'global', slug: 'pdf-parser', version: '1.0.0', zipBytes: makeSkillZip() }]
|
||||
})
|
||||
await runCli(['login', '--registry', registry.url, '--token', 'sk_ok'], { HOME: env.home, USERPROFILE: env.home })
|
||||
await mkdir(join(env.cwd, '.pi', 'skills'), { recursive: true })
|
||||
|
||||
const result = await runCli(
|
||||
['install', 'pdf-parser', '--registry', registry.url, '--token', 'sk_ok', '--json'],
|
||||
{ HOME: env.home, USERPROFILE: env.home },
|
||||
{ cwd: env.cwd }
|
||||
)
|
||||
|
||||
expect(result.exitCode).toBe(0)
|
||||
const parsed = JSON.parse(result.stdout) as { installed: Array<{ agent: string; dir: string }> }
|
||||
expect(parsed.installed).toHaveLength(1)
|
||||
expect(parsed.installed[0]?.agent).toBe('pi')
|
||||
expect(parsed.installed[0]?.dir).toMatch(/[/\\]\.pi[/\\]skills[/\\]pdf-parser/)
|
||||
expect(await Bun.file(join(
|
||||
env.cwd,
|
||||
'.pi',
|
||||
'skills',
|
||||
'pdf-parser',
|
||||
'.skillhub',
|
||||
'metadata.json'
|
||||
)).exists()).toBe(true)
|
||||
})
|
||||
|
||||
test('--agent astudio installs and persists the stable lowercase agent id', async () => {
|
||||
const env = await createTempHome()
|
||||
registry = await startFakeRegistry({
|
||||
|
|
@ -993,6 +1059,38 @@ describe('install command — multi-agent & auto-detect', () => {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('install command — --scope', () => {
|
||||
test('--scope project --agent pi installs to <cwd>/.pi/skills', async () => {
|
||||
const env = await createTempHome()
|
||||
registry = await startFakeRegistry({
|
||||
token: 'sk_ok',
|
||||
user: { handle: 'u1', displayName: 'User One' },
|
||||
skills: [{ namespace: 'global', slug: 'foo', version: '1.0.0', zipBytes: makeSkillZip() }]
|
||||
})
|
||||
|
||||
await runCli(
|
||||
['login', '--registry', registry.url, '--token', 'sk_ok'],
|
||||
{ HOME: env.home, USERPROFILE: env.home }
|
||||
)
|
||||
|
||||
const result = await runCli(
|
||||
['install', 'foo', '--scope', 'project', '--agent', 'pi',
|
||||
'--registry', registry.url, '--token', 'sk_ok', '--json'],
|
||||
{ HOME: env.home, USERPROFILE: env.home },
|
||||
{ cwd: env.cwd }
|
||||
)
|
||||
|
||||
expect(result.exitCode).toBe(0)
|
||||
const installDir = join(env.cwd, '.pi', 'skills', 'foo')
|
||||
const parsed = JSON.parse(result.stdout) as { installed: Array<{ agent: string; dir: string }> }
|
||||
expect(parsed.installed).toHaveLength(1)
|
||||
expect(parsed.installed[0]?.agent).toBe('pi')
|
||||
expect(parsed.installed[0]?.dir).toMatch(/[/\\]\.pi[/\\]skills[/\\]foo/)
|
||||
expect(JSON.parse(await readFile(
|
||||
join(installDir, '.skillhub', 'metadata.json'),
|
||||
'utf-8'
|
||||
)).agent).toBe('pi')
|
||||
})
|
||||
|
||||
test('--scope project --agent codex installs to <cwd>/.codex/skills', async () => {
|
||||
const env = await createTempHome()
|
||||
registry = await startFakeRegistry({
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { describe, expect, test } from 'bun:test'
|
|||
import { allProfiles, profileMap } from '../../../src/agents/detector'
|
||||
|
||||
describe('agent profiles', () => {
|
||||
test('has 15 tier 1 profiles', () => {
|
||||
expect(allProfiles).toHaveLength(15)
|
||||
test('has 16 tier 1 profiles', () => {
|
||||
expect(allProfiles).toHaveLength(16)
|
||||
})
|
||||
|
||||
test('all profiles have unique ids', () => {
|
||||
|
|
@ -15,12 +15,13 @@ describe('agent profiles', () => {
|
|||
})
|
||||
|
||||
test('profileMap contains all profiles', () => {
|
||||
expect(profileMap.size).toBe(15)
|
||||
expect(profileMap.size).toBe(16)
|
||||
expect(profileMap.has('astudio')).toBe(true)
|
||||
expect(profileMap.has('claude-code')).toBe(true)
|
||||
expect(profileMap.has('codex')).toBe(true)
|
||||
expect(profileMap.has('cursor')).toBe(true)
|
||||
expect(profileMap.has('kilo')).toBe(true)
|
||||
expect(profileMap.has('pi')).toBe(true)
|
||||
})
|
||||
|
||||
test('claude-code profile returns correct roots', () => {
|
||||
|
|
@ -40,6 +41,53 @@ describe('agent profiles', () => {
|
|||
expect(profile.projectRoots('/repo')).toEqual(['/repo/.cursor/skills'])
|
||||
})
|
||||
|
||||
test('Pi exposes and detects its project and user skills directories', async () => {
|
||||
const base = await mkdtemp(join(tmpdir(), 'skillhub-pi-profile-'))
|
||||
const cwd = join(base, 'repo')
|
||||
const home = join(base, 'home')
|
||||
const projectRoot = `${cwd}/.pi/skills`
|
||||
const userRoot = `${home}/.pi/agent/skills`
|
||||
const profile = profileMap.get('pi')!
|
||||
|
||||
try {
|
||||
await mkdir(cwd, { recursive: true })
|
||||
await mkdir(home, { recursive: true })
|
||||
|
||||
expect(profile.displayName).toBe('Pi')
|
||||
expect(profile.projectRoots(cwd)).toEqual([projectRoot])
|
||||
expect(profile.userRoots(home)).toEqual([userRoot])
|
||||
expect(await profile.detectInstalled(cwd, home)).toEqual([])
|
||||
|
||||
await mkdir(join(cwd, '.pi'), { recursive: true })
|
||||
await mkdir(join(home, '.pi', 'agent'), { recursive: true })
|
||||
await writeFile(projectRoot, 'not a directory')
|
||||
await writeFile(userRoot, 'not a directory')
|
||||
expect(await profile.detectInstalled(cwd, home)).toEqual([])
|
||||
|
||||
await rm(projectRoot)
|
||||
await rm(userRoot)
|
||||
await mkdir(projectRoot, { recursive: true })
|
||||
await mkdir(userRoot, { recursive: true })
|
||||
|
||||
expect(await profile.detectInstalled(cwd, home)).toEqual([
|
||||
{
|
||||
agent: 'pi',
|
||||
rootDir: projectRoot,
|
||||
scope: 'project',
|
||||
source: 'detected'
|
||||
},
|
||||
{
|
||||
agent: 'pi',
|
||||
rootDir: userRoot,
|
||||
scope: 'user',
|
||||
source: 'detected'
|
||||
}
|
||||
])
|
||||
} finally {
|
||||
await rm(base, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('AStudio exposes only its fixed user-level directory on Linux, macOS, and Windows', () => {
|
||||
const profile = profileMap.get('astudio')!
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,9 @@ skillhub install pdf-parser --agent codex
|
|||
# Install to AStudio's fixed user-level directory
|
||||
skillhub install pdf-parser --agent astudio
|
||||
|
||||
# Install to Pi's user-level directory (use --scope project for the project directory)
|
||||
skillhub install pdf-parser --agent pi
|
||||
|
||||
# Install to multiple Agents
|
||||
skillhub install pdf-parser --agent codex --agent claude-code
|
||||
|
||||
|
|
@ -204,6 +207,7 @@ Most Agents have both project-level and user-level skills directories. Use `--sc
|
|||
| `openclaw` | `<project>/.openclaw/skills/` | `~/.openclaw/skills/` |
|
||||
| `opencode` | `<project>/.opencode/skills/` | `~/.opencode/skills/` |
|
||||
| `kilo` | `<project>/.kilo/skills/` | `~/.kilo/skills/` |
|
||||
| `pi` (Pi) | `<project>/.pi/skills/` | `~/.pi/agent/skills/` |
|
||||
| _fallback_ | `<project>/.agents/skills/` | `~/.agents/skills/` |
|
||||
|
||||
For a custom path or an unsupported Agent directory, use `--dir` to specify the installation path. In interactive user scope, the `generic` target is offered alongside detected Agent targets. AStudio appears in that selector when `~/.acode/skills/` exists. When `--scope user|project` finds no matching agent directory, the CLI falls back to the `_fallback_` row above.
|
||||
|
|
|
|||
|
|
@ -153,6 +153,9 @@ skillhub install pdf-parser --agent codex
|
|||
# 安装到 AStudio 的固定用户级目录
|
||||
skillhub install pdf-parser --agent astudio
|
||||
|
||||
# 安装到 Pi 的用户级目录(添加 --scope project 可安装到项目级目录)
|
||||
skillhub install pdf-parser --agent pi
|
||||
|
||||
# 安装到多个 Agent
|
||||
skillhub install pdf-parser --agent codex --agent claude-code
|
||||
|
||||
|
|
@ -200,6 +203,7 @@ CLI 按以下逻辑确定安装位置:
|
|||
| `openclaw` | `<project>/.openclaw/skills/` | `~/.openclaw/skills/` |
|
||||
| `opencode` | `<project>/.opencode/skills/` | `~/.opencode/skills/` |
|
||||
| `kilo` | `<project>/.kilo/skills/` | `~/.kilo/skills/` |
|
||||
| `pi`(Pi) | `<project>/.pi/skills/` | `~/.pi/agent/skills/` |
|
||||
| _fallback_ | `<project>/.agents/skills/` | `~/.agents/skills/` |
|
||||
|
||||
对于自定义路径或不在列表中的 Agent 目录,使用 `--dir` 显式指定安装路径。交互式 user scope 下会与已探测 Agent 目标一同提供 `generic` 目标;当 `~/.acode/skills/` 存在时,选择器会显示 AStudio。当 `--scope user|project` 找不到匹配的 agent 目录时,CLI 会回退到上表的 `_fallback_` 行。
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue