mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-10 22:41:02 +00:00
feat(cli): add AStudio agent profile (#840)
* feat(cli): add AstronStudio agent profile Signed-off-by: dongmucat <1127093059@qq.com> * test(cli): make AstronStudio path assertions portable Signed-off-by: dongmucat <1127093059@qq.com> * docs(cli): document AstronStudio install target Signed-off-by: dongmucat <1127093059@qq.com> * fix(cli): rename AstronStudio profile to AStudio Signed-off-by: dongmucat <1127093059@qq.com> * fix(cli): use stable lowercase AStudio agent id Signed-off-by: dongmucat <1127093059@qq.com> --------- Signed-off-by: dongmucat <1127093059@qq.com>
This commit is contained in:
parent
1e5fe097fe
commit
ccacb530e3
13 changed files with 220 additions and 20 deletions
|
|
@ -6,6 +6,8 @@ All notable CLI behavior changes are documented in this file.
|
|||
|
||||
### Added
|
||||
|
||||
- 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
|
||||
interactive multi-select in a TTY.
|
||||
- Add `skillhub upgrade <coordinate...>` for bounded, explicit upgrades of already-installed Skills,
|
||||
|
|
|
|||
|
|
@ -169,6 +169,9 @@ skillhub install pdf-parser --version 1.2.0
|
|||
# Install to specific Agent
|
||||
skillhub install pdf-parser --agent codex
|
||||
|
||||
# Install to AStudio's fixed user-level directory
|
||||
skillhub install pdf-parser --agent astudio
|
||||
|
||||
# Install to multiple Agents
|
||||
skillhub install pdf-parser --agent codex --agent claude-code
|
||||
|
||||
|
|
@ -197,10 +200,11 @@ The CLI determines the installation location using the following logic:
|
|||
|
||||
### Install Paths
|
||||
|
||||
Each Agent has both project-level and user-level skills directories. Use `--scope user|project` to control which one is used.
|
||||
Most Agents have both project-level and user-level skills directories. Use `--scope user|project` to control which one is used. AStudio uses its fixed user-level directory only.
|
||||
|
||||
| Agent | Project-level Path | User-level Path |
|
||||
|-------|-------------------|-----------------|
|
||||
| `astudio` (AStudio) | Not supported | `~/.acode/skills/` |
|
||||
| `claude-code` | `<project>/.claude/skills/` | `~/.claude/skills/` |
|
||||
| `codex` | `<project>/.codex/skills/` | `~/.codex/skills/` |
|
||||
| `cursor` | `<project>/.cursor/skills/` | `~/.cursor/skills/` |
|
||||
|
|
@ -217,7 +221,7 @@ Each Agent has both project-level and user-level skills directories. Use `--scop
|
|||
| `kilo` | `<project>/.kilo/skills/` | `~/.kilo/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. When `--scope user|project` finds no matching agent directory, the CLI falls back to the `_fallback_` row above.
|
||||
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.
|
||||
|
||||
### File Structure After Installation
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { AgentProfile } from './types'
|
||||
import { aStudioProfile } from './profiles/astudio'
|
||||
import { claudeCodeProfile } from './profiles/claude-code'
|
||||
import { codexProfile } from './profiles/codex'
|
||||
import { cursorProfile } from './profiles/cursor'
|
||||
|
|
@ -15,14 +16,14 @@ import { opencodeProfile } from './profiles/opencode'
|
|||
import { kiloProfile } from './profiles/kilo'
|
||||
|
||||
export {
|
||||
claudeCodeProfile, codexProfile, cursorProfile, githubCopilotProfile,
|
||||
aStudioProfile, claudeCodeProfile, codexProfile, cursorProfile, githubCopilotProfile,
|
||||
geminiCliProfile, openhandsProfile, windsurfProfile, openclawProfile,
|
||||
kiroCliProfile, rooProfile, traeProfile, traeCnProfile,
|
||||
opencodeProfile, kiloProfile
|
||||
}
|
||||
|
||||
export const allProfiles: AgentProfile[] = [
|
||||
claudeCodeProfile, codexProfile, cursorProfile, githubCopilotProfile,
|
||||
aStudioProfile, claudeCodeProfile, codexProfile, cursorProfile, githubCopilotProfile,
|
||||
geminiCliProfile, openhandsProfile, windsurfProfile, openclawProfile,
|
||||
kiroCliProfile, rooProfile, traeProfile, traeCnProfile,
|
||||
opencodeProfile, kiloProfile
|
||||
|
|
|
|||
23
cli/src/agents/profiles/astudio.ts
Normal file
23
cli/src/agents/profiles/astudio.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { directoryExists } from '../../platform/paths'
|
||||
import type { AgentProfile } from '../types'
|
||||
|
||||
function userSkillsRoot(home: string): string {
|
||||
return home.replace(/\\/g, '/').replace(/\/+$/, '') + '/.acode/skills'
|
||||
}
|
||||
|
||||
export const aStudioProfile: AgentProfile = {
|
||||
id: 'astudio',
|
||||
displayName: 'AStudio',
|
||||
projectRoots: () => [],
|
||||
userRoots: home => [userSkillsRoot(home)],
|
||||
async detectInstalled(_cwd, home) {
|
||||
const rootDir = userSkillsRoot(home)
|
||||
if (!await directoryExists(rootDir)) return []
|
||||
return [{
|
||||
agent: this.id,
|
||||
rootDir,
|
||||
scope: 'user',
|
||||
source: 'detected'
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,6 @@
|
|||
import { pathExists } from '../../platform/paths'
|
||||
import { directoryExists } from '../../platform/paths'
|
||||
import type { AgentProfile, AgentCandidate } from '../types'
|
||||
|
||||
async function dirExists(path: string): Promise<boolean> {
|
||||
return pathExists(path)
|
||||
}
|
||||
|
||||
export function makeProfile(id: string, displayName: string, projectSkills: string, userSkills: string): AgentProfile {
|
||||
return {
|
||||
id,
|
||||
|
|
@ -15,7 +11,7 @@ export function makeProfile(id: string, displayName: string, projectSkills: stri
|
|||
const roots = [...this.projectRoots(cwd), ...this.userRoots(home)]
|
||||
const results: AgentCandidate[] = []
|
||||
for (const root of roots) {
|
||||
if (await dirExists(root)) {
|
||||
if (await directoryExists(root)) {
|
||||
results.push({
|
||||
agent: this.id,
|
||||
rootDir: root,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { homedir } from 'node:os'
|
||||
import { CliError } from '../shared/errors'
|
||||
import { EXIT } from '../shared/constants'
|
||||
import { canonicalizeExistingPath, pathExists } from '../platform/paths'
|
||||
import { canonicalizeExistingPath, directoryExists } from '../platform/paths'
|
||||
import type { AgentCandidate } from './types'
|
||||
import { allProfiles, profileMap } from './detector'
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ async function generateScopedCandidates(
|
|||
for (const profile of allProfiles) {
|
||||
const roots = scope === 'user' ? profile.userRoots(home) : profile.projectRoots(cwd)
|
||||
for (const root of roots) {
|
||||
if (await pathExists(root)) {
|
||||
if (await directoryExists(root)) {
|
||||
results.push({ agent: profile.id, rootDir: root, scope, source: 'detected' })
|
||||
}
|
||||
}
|
||||
|
|
@ -145,6 +145,11 @@ async function resolveExplicitAgents(
|
|||
const userRoots = home ? profile.userRoots(home) : []
|
||||
roots = userRoots.length > 0 ? userRoots : profile.projectRoots(cwd)
|
||||
}
|
||||
if (roots.length === 0) {
|
||||
throw new CliError(`agent ${agentId} does not support ${scope} scope`, EXIT.usage, {
|
||||
next: 'choose a supported scope or omit --scope'
|
||||
})
|
||||
}
|
||||
const userRootSet = new Set(home ? profile.userRoots(home) : [])
|
||||
for (const root of roots) {
|
||||
const candidateScope: AgentCandidate['scope'] = scope !== undefined
|
||||
|
|
@ -183,7 +188,7 @@ async function selectTargetsInteractively(candidates: AgentCandidate[]): Promise
|
|||
name: 'selected',
|
||||
message: 'Select install targets',
|
||||
choices: candidates.map(c => ({
|
||||
title: `${c.agent} (${c.rootDir})`,
|
||||
title: `${profileMap.get(c.agent)?.displayName ?? c.agent} (${c.rootDir})`,
|
||||
value: c
|
||||
})),
|
||||
onRender: function (this: { cursor?: number }) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@ export async function pathExists(path: string): Promise<boolean> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function directoryExists(path: string): Promise<boolean> {
|
||||
const { stat } = await import('node:fs/promises')
|
||||
try {
|
||||
return (await stat(path)).isDirectory()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function canonicalizeExistingPath(path: string): Promise<string> {
|
||||
const { realpath } = await import('node:fs/promises')
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -667,6 +667,49 @@ describe('install command — server errors', () => {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('install command — multi-agent & auto-detect', () => {
|
||||
test('--agent astudio installs and persists the stable lowercase 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', 'astudio',
|
||||
'--registry', registry.url,
|
||||
'--token', 'sk_ok',
|
||||
'--json'
|
||||
],
|
||||
{ HOME: env.home, USERPROFILE: env.home }
|
||||
)
|
||||
|
||||
expect(result.exitCode).toBe(0)
|
||||
const parsed = JSON.parse(result.stdout) as { installed: Array<{ agent: string; dir: string }> }
|
||||
expect(parsed.installed).toEqual([{
|
||||
agent: 'astudio',
|
||||
dir: join(env.home, '.acode', 'skills', 'pdf-parser')
|
||||
}])
|
||||
const metadataPath = join(
|
||||
env.home,
|
||||
'.acode',
|
||||
'skills',
|
||||
'pdf-parser',
|
||||
'.skillhub',
|
||||
'metadata.json'
|
||||
)
|
||||
expect(await Bun.file(metadataPath).exists()).toBe(true)
|
||||
expect(JSON.parse(await readFile(metadataPath, 'utf-8')).agent).toBe('astudio')
|
||||
|
||||
const inventory = JSON.parse(await readFile(join(env.home, '.skillhub', 'inventory.json'), 'utf-8')) as {
|
||||
items: Array<{ targets: Array<{ agent: string }> }>
|
||||
}
|
||||
expect(inventory.items[0]?.targets[0]?.agent).toBe('astudio')
|
||||
})
|
||||
|
||||
test('multi --agent installs the same skill into every specified user-level dir', async () => {
|
||||
const env = await createTempHome()
|
||||
registry = await startFakeRegistry({
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { allProfiles, profileMap } from '../../../src/agents/detector'
|
||||
|
||||
describe('agent profiles', () => {
|
||||
test('has 14 tier 1 profiles', () => {
|
||||
expect(allProfiles).toHaveLength(14)
|
||||
test('has 15 tier 1 profiles', () => {
|
||||
expect(allProfiles).toHaveLength(15)
|
||||
})
|
||||
|
||||
test('all profiles have unique ids', () => {
|
||||
|
|
@ -12,7 +15,8 @@ describe('agent profiles', () => {
|
|||
})
|
||||
|
||||
test('profileMap contains all profiles', () => {
|
||||
expect(profileMap.size).toBe(14)
|
||||
expect(profileMap.size).toBe(15)
|
||||
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)
|
||||
|
|
@ -35,4 +39,41 @@ describe('agent profiles', () => {
|
|||
const profile = profileMap.get('cursor')!
|
||||
expect(profile.projectRoots('/repo')).toEqual(['/repo/.cursor/skills'])
|
||||
})
|
||||
|
||||
test('AStudio exposes only its fixed user-level directory on Linux, macOS, and Windows', () => {
|
||||
const profile = profileMap.get('astudio')!
|
||||
|
||||
expect(profile.displayName).toBe('AStudio')
|
||||
expect(profile.projectRoots('/repo')).toEqual([])
|
||||
expect(profile.userRoots('/home/alice')).toEqual(['/home/alice/.acode/skills'])
|
||||
expect(profile.userRoots('/Users/alice')).toEqual(['/Users/alice/.acode/skills'])
|
||||
expect(profile.userRoots('C:\\Users\\alice')).toEqual(['C:/Users/alice/.acode/skills'])
|
||||
})
|
||||
|
||||
test('AStudio is detected only when the user .acode skills directory exists', async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), 'skillhub-astudio-home-'))
|
||||
const profile = profileMap.get('astudio')!
|
||||
const nativeRootDir = join(home, '.acode', 'skills')
|
||||
const profileRootDir = nativeRootDir.replace(/\\/g, '/')
|
||||
|
||||
try {
|
||||
expect(await profile.detectInstalled('/repo', home)).toEqual([])
|
||||
|
||||
await mkdir(join(home, '.acode'), { recursive: true })
|
||||
await writeFile(nativeRootDir, 'not a directory')
|
||||
expect(await profile.detectInstalled('/repo', home)).toEqual([])
|
||||
|
||||
await rm(nativeRootDir)
|
||||
await mkdir(nativeRootDir, { recursive: true })
|
||||
|
||||
expect(await profile.detectInstalled('/repo', home)).toEqual([{
|
||||
agent: 'astudio',
|
||||
rootDir: profileRootDir,
|
||||
scope: 'user',
|
||||
source: 'detected'
|
||||
}])
|
||||
} finally {
|
||||
await rm(home, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
||||
import type { AgentCandidate } from '../../../src/agents/types'
|
||||
|
||||
interface PromptChoice {
|
||||
title: string
|
||||
value: AgentCandidate
|
||||
}
|
||||
|
||||
|
|
@ -13,9 +17,11 @@ interface PromptOptions {
|
|||
|
||||
const defaultSelectedTargets = (options: PromptOptions): AgentCandidate[] => options.format?.([]) ?? []
|
||||
let selectPromptTargets = defaultSelectedTargets
|
||||
let renderedChoices: PromptChoice[] = []
|
||||
|
||||
mock.module('prompts', () => ({
|
||||
default: (options: PromptOptions) => {
|
||||
renderedChoices = options.choices ?? []
|
||||
options.onRender?.call({ cursor: 1 })
|
||||
return { selected: selectPromptTargets(options) }
|
||||
}
|
||||
|
|
@ -23,11 +29,62 @@ mock.module('prompts', () => ({
|
|||
|
||||
afterEach(() => {
|
||||
selectPromptTargets = defaultSelectedTargets
|
||||
renderedChoices = []
|
||||
})
|
||||
|
||||
const { resolveInstallTargets } = await import('../../../src/agents/resolver')
|
||||
|
||||
describe('resolveInstallTargets interactive prompt', () => {
|
||||
test('renders AStudio by display name when its directory was detected', async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), 'skillhub-astudio-resolver-'))
|
||||
const nativeRootDir = join(home, '.acode', 'skills')
|
||||
const profileRootDir = nativeRootDir.replace(/\\/g, '/')
|
||||
|
||||
try {
|
||||
await mkdir(nativeRootDir, { recursive: true })
|
||||
await resolveInstallTargets({
|
||||
cwd: '/repo',
|
||||
home,
|
||||
agents: [],
|
||||
scope: 'user',
|
||||
json: false,
|
||||
interactive: true
|
||||
})
|
||||
|
||||
expect(renderedChoices[0]?.title).toBe(`AStudio (${profileRootDir})`)
|
||||
} finally {
|
||||
await rm(home, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('does not render AStudio when .acode skills is a regular file', async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), 'skillhub-astudio-file-'))
|
||||
const rootDir = join(home, '.acode', 'skills')
|
||||
|
||||
try {
|
||||
await mkdir(join(home, '.acode'), { recursive: true })
|
||||
await writeFile(rootDir, 'not a directory')
|
||||
const targets = await resolveInstallTargets({
|
||||
cwd: '/repo',
|
||||
home,
|
||||
agents: [],
|
||||
scope: 'user',
|
||||
json: false,
|
||||
interactive: true
|
||||
})
|
||||
|
||||
expect(renderedChoices.some(choice => choice.value.agent === 'astudio')).toBe(false)
|
||||
expect(targets).toEqual([{
|
||||
agent: 'generic',
|
||||
rootDir: `${home}/.agents/skills`,
|
||||
scope: 'user',
|
||||
source: 'fallback'
|
||||
}])
|
||||
} finally {
|
||||
await rm(home, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('uses the highlighted target when Enter submits an empty multiselect', async () => {
|
||||
const detected: AgentCandidate[] = [
|
||||
{ agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'detected' },
|
||||
|
|
|
|||
|
|
@ -128,6 +128,17 @@ describe('resolveInstallTargets', () => {
|
|||
expect(targets).toEqual([{ agent: 'codex', rootDir: '/repo/.codex/skills', scope: 'project', source: 'explicit' }])
|
||||
})
|
||||
|
||||
test('rejects project scope for the user-only AStudio profile', async () => {
|
||||
await expect(resolveInstallTargets({
|
||||
cwd: '/repo',
|
||||
home: '/home/u',
|
||||
agents: ['astudio'],
|
||||
scope: 'project',
|
||||
json: false,
|
||||
interactive: false
|
||||
})).rejects.toThrow('agent astudio does not support project scope')
|
||||
})
|
||||
|
||||
test('scope=user + agent codex returns user root with user scope', async () => {
|
||||
const targets = await resolveInstallTargets({
|
||||
cwd: '/repo',
|
||||
|
|
|
|||
|
|
@ -154,6 +154,9 @@ skillhub install pdf-parser --version 1.2.0
|
|||
# Install to specific Agent
|
||||
skillhub install pdf-parser --agent codex
|
||||
|
||||
# Install to AStudio's fixed user-level directory
|
||||
skillhub install pdf-parser --agent astudio
|
||||
|
||||
# Install to multiple Agents
|
||||
skillhub install pdf-parser --agent codex --agent claude-code
|
||||
|
||||
|
|
@ -182,10 +185,11 @@ The CLI determines the installation location using the following logic:
|
|||
|
||||
### Install Paths
|
||||
|
||||
Each Agent has both project-level and user-level skills directories. Use `--scope user|project` to control which one is used.
|
||||
Most Agents have both project-level and user-level skills directories. Use `--scope user|project` to control which one is used. AStudio uses its fixed user-level directory only.
|
||||
|
||||
| Agent | Project-level Path | User-level Path |
|
||||
|-------|-------------------|-----------------|
|
||||
| `astudio` (AStudio) | Not supported | `~/.acode/skills/` |
|
||||
| `claude-code` | `<project>/.claude/skills/` | `~/.claude/skills/` |
|
||||
| `codex` | `<project>/.codex/skills/` | `~/.codex/skills/` |
|
||||
| `cursor` | `<project>/.cursor/skills/` | `~/.cursor/skills/` |
|
||||
|
|
@ -202,7 +206,7 @@ Each Agent has both project-level and user-level skills directories. Use `--scop
|
|||
| `kilo` | `<project>/.kilo/skills/` | `~/.kilo/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. When `--scope user|project` finds no matching agent directory, the CLI falls back to the `_fallback_` row above.
|
||||
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.
|
||||
|
||||
### File Structure After Installation
|
||||
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ skillhub install pdf-parser --version 1.2.0
|
|||
# 安装到指定 Agent
|
||||
skillhub install pdf-parser --agent codex
|
||||
|
||||
# 安装到 AStudio 的固定用户级目录
|
||||
skillhub install pdf-parser --agent astudio
|
||||
|
||||
# 安装到多个 Agent
|
||||
skillhub install pdf-parser --agent codex --agent claude-code
|
||||
|
||||
|
|
@ -178,10 +181,11 @@ CLI 按以下逻辑确定安装位置:
|
|||
|
||||
### 安装路径
|
||||
|
||||
每个 Agent 有项目级和用户级两个 skills 目录。`--scope user|project` 决定使用哪一个。
|
||||
大多数 Agent 都有项目级和用户级两个 skills 目录。`--scope user|project` 决定使用哪一个。AStudio 仅使用固定的用户级目录。
|
||||
|
||||
| Agent | 项目级路径 | 用户级路径 |
|
||||
|-------|-----------|-----------|
|
||||
| `astudio`(AStudio) | 不支持 | `~/.acode/skills/` |
|
||||
| `claude-code` | `<project>/.claude/skills/` | `~/.claude/skills/` |
|
||||
| `codex` | `<project>/.codex/skills/` | `~/.codex/skills/` |
|
||||
| `cursor` | `<project>/.cursor/skills/` | `~/.cursor/skills/` |
|
||||
|
|
@ -198,7 +202,7 @@ CLI 按以下逻辑确定安装位置:
|
|||
| `kilo` | `<project>/.kilo/skills/` | `~/.kilo/skills/` |
|
||||
| _fallback_ | `<project>/.agents/skills/` | `~/.agents/skills/` |
|
||||
|
||||
对于自定义路径或不在列表中的 Agent 目录,使用 `--dir` 显式指定安装路径。交互式 user scope 下会与已探测 Agent 目标一同提供 `generic` 目标;当 `--scope user|project` 找不到匹配的 agent 目录时,CLI 会回退到上表的 `_fallback_` 行。
|
||||
对于自定义路径或不在列表中的 Agent 目录,使用 `--dir` 显式指定安装路径。交互式 user scope 下会与已探测 Agent 目标一同提供 `generic` 目标;当 `~/.acode/skills/` 存在时,选择器会显示 AStudio。当 `--scope user|project` 找不到匹配的 agent 目录时,CLI 会回退到上表的 `_fallback_` 行。
|
||||
|
||||
### 安装后的文件结构
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue