diff --git a/src/agent/detect.ts b/src/agent/detect.ts index 2b5d1223..1b79bd3a 100644 --- a/src/agent/detect.ts +++ b/src/agent/detect.ts @@ -18,6 +18,15 @@ export function detectInstalledAgents(): AgentType[] { .map(([type]) => type as AgentType) } +/** Detect the active agent from environment variables. */ +export function detectEnvAgent(): AgentType | null { + for (const [type, target] of Object.entries(agents)) { + if (target.detectEnv()) + return type as AgentType + } + return null +} + /** * Detect the target agent (where skills are installed) from env vars and cwd. * This is NOT the generator LLM — it determines the skills directory. @@ -27,10 +36,9 @@ export function detectInstalledAgents(): AgentType[] { * rather than silently picking the first match. */ export function detectTargetAgent(): AgentType | null { - for (const [type, target] of Object.entries(agents)) { - if (target.detectEnv()) - return type as AgentType - } + const envAgent = detectEnvAgent() + if (envAgent) + return envAgent const cwd = process.cwd() const projectMatches: AgentType[] = [] diff --git a/src/agent/index.ts b/src/agent/index.ts index 03a14710..a8ee0ecd 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -17,7 +17,7 @@ export { // Import detection export { detectImportedPackages } from './detect-imports.ts' // Detection -export { detectInstalledAgents, detectProjectAgents, detectTargetAgent, getAgentVersion } from './detect.ts' +export { detectEnvAgent, detectInstalledAgents, detectProjectAgents, detectTargetAgent, getAgentVersion } from './detect.ts' // Installation export { computeSkillDirName, installSkillForAgents, linkSkillToAgents, sanitizeName, unlinkSkillFromAgents } from './install.ts' diff --git a/src/cli/agent-prompt.ts b/src/cli/agent-prompt.ts index 5a2fc45a..139219c1 100644 --- a/src/cli/agent-prompt.ts +++ b/src/cli/agent-prompt.ts @@ -1,16 +1,22 @@ import type { AgentType } from '../agent/index.ts' import { styleText } from 'node:util' import * as p from '@clack/prompts' -import { agents, detectInstalledAgents, detectProjectAgents, detectTargetAgent } from '../agent/index.ts' +import { agents, detectEnvAgent, detectInstalledAgents, detectProjectAgents } from '../agent/index.ts' import { readConfig, updateConfig } from '../core/config.ts' import { isInteractive } from './env.ts' +function detectProjectAgent(): AgentType | null { + const projectMatches = detectProjectAgents() + return projectMatches.length === 1 ? projectMatches[0]! : null +} + export function resolveAgent(agentFlag?: string): AgentType | 'none' | null { if (process.env.SKILLD_NO_AGENT) return null return (agentFlag as AgentType | undefined) - ?? detectTargetAgent() + ?? detectEnvAgent() ?? (readConfig().agent as AgentType | undefined) + ?? detectProjectAgent() ?? null } @@ -35,10 +41,6 @@ export function autoResolveAgent(agentFlag?: string): AgentType | null { if (process.env.SKILLD_NO_AGENT) return null - const projectMatches = detectProjectAgents() - if (projectMatches.length === 1) - return projectMatches[0]! - const installed = detectInstalledAgents() if (installed.length === 1) return installed[0]! diff --git a/test/unit/agent-prompt.test.ts b/test/unit/agent-prompt.test.ts new file mode 100644 index 00000000..2e4150aa --- /dev/null +++ b/test/unit/agent-prompt.test.ts @@ -0,0 +1,49 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { autoResolveAgent } from '../../src/cli/agent-prompt.ts' + +const detection = vi.hoisted(() => ({ + configAgent: undefined as string | undefined, + envAgent: null as string | null, + installedAgents: [] as string[], + projectAgents: [] as string[], +})) + +vi.mock('../../src/agent/index.ts', () => ({ + agents: {}, + detectEnvAgent: () => detection.envAgent, + detectInstalledAgents: () => detection.installedAgents, + detectProjectAgents: () => detection.projectAgents, + detectTargetAgent: () => detection.envAgent + ?? (detection.projectAgents.length === 1 ? detection.projectAgents[0] : null), +})) + +vi.mock('../../src/core/config.ts', () => ({ + readConfig: () => ({ agent: detection.configAgent }), + updateConfig: vi.fn(), +})) + +describe('autoResolveAgent', () => { + const originalNoAgent = process.env.SKILLD_NO_AGENT + + beforeEach(() => { + detection.configAgent = undefined + detection.envAgent = null + detection.installedAgents = [] + detection.projectAgents = [] + delete process.env.SKILLD_NO_AGENT + }) + + afterEach(() => { + if (originalNoAgent === undefined) + delete process.env.SKILLD_NO_AGENT + else + process.env.SKILLD_NO_AGENT = originalNoAgent + }) + + it('prefers the saved config over a single project marker', () => { + detection.configAgent = 'claude-code' + detection.projectAgents = ['cursor'] + + expect(autoResolveAgent()).toBe('claude-code') + }) +})