Files
anti-slop/cli/index.mjs
T

127 lines
4.0 KiB
JavaScript

#!/usr/bin/env node
import path from 'node:path'
import pc from 'picocolors'
import { intro, outro, select, multiselect, confirm, isCancel, cancel, log, spinner } from '@clack/prompts'
import { banner } from './lib/banner.mjs'
import {
CORE,
AGENTS,
skillSourceDir,
resolveTargets,
detectAgents,
detectConflicts,
installSkills,
updatePointers,
} from './lib/install.mjs'
const EXTRA_SKILLS = [
{ value: 'antislop-ui', label: 'antislop-ui', hint: 'UI and visual design rules' },
{ value: 'antislop-copywriting', label: 'antislop-copywriting', hint: 'copywriting and text rules' },
{ value: 'antislop-human', label: 'antislop-human', hint: 'accessibility, with the contrast checker' },
{ value: 'antislop-layoutmobile', label: 'antislop-layoutmobile', hint: 'mobile layout rules' },
{ value: 'antislop-code', label: 'antislop-code', hint: 'code comment rules' },
]
function stop(message) {
cancel(message)
process.exit(0)
}
async function main() {
if (process.argv.includes('--version') || process.argv.includes('-v')) {
console.log('antislop 3.1.3')
return
}
console.log(banner())
intro('Install antislop into your agent setup')
if (!skillSourceDir()) {
stop('Could not find the antislop skills. Reinstall the antislop package.')
}
log.step('Choose your skills')
console.log(` ${pc.green('●')} ${pc.bold(CORE)} ${pc.dim('core rules, always on')}`)
const extra = await multiselect({
message: 'Extra skills to install',
options: EXTRA_SKILLS,
required: false,
})
if (isCancel(extra)) stop('Install cancelled.')
const skills = [CORE, ...(extra ?? [])]
log.step('Choose where antislop goes')
const location = await select({
message: 'Install location',
options: [
{ value: 'project', label: 'This project', hint: 'only this folder' },
{ value: 'global', label: 'Everywhere', hint: 'all your projects' },
],
})
if (isCancel(location)) stop('Install cancelled.')
log.step('Choose which agents get antislop')
const detected = detectAgents(location)
const chosen = await multiselect({
message: 'Which agent(s) should antislop install into?',
options: AGENTS.map((a) => ({
value: a.id,
label: a.label,
hint: detected.includes(a.id) ? `found here (${a.dir})` : `will create ${a.dir}`,
})),
required: 'Pick at least one agent.',
initialValues: detected,
})
if (isCancel(chosen)) stop('Install cancelled.')
const targets = resolveTargets(location, chosen)
await log.message(
'Installing into:\n' + targets.map((t) => ' ' + t.path).join('\n'),
{ symbol: pc.cyan('│') }
)
const conflicts = detectConflicts({ skills, targets })
let overwrite = false
if (conflicts.length > 0) {
const answer = await select({
message: `${conflicts.length} skill folder(s) already exist. What should I do?`,
options: [
{ value: 'overwrite', label: 'Overwrite them', hint: 'replace existing files' },
{ value: 'keep', label: 'Keep what is there', hint: 'skip existing folders' },
],
})
if (isCancel(answer)) stop('Install cancelled.')
overwrite = answer === 'overwrite'
}
const proceed = await confirm({
message: `Install ${skills.length} skill(s) now?`,
initialValue: true,
})
if (isCancel(proceed) || proceed === false) stop('Install cancelled.')
const spin = spinner()
spin.start('Installing skills...')
const written = installSkills({ skills, targets, overwrite })
const pointers = location === 'project' ? updatePointers({ targets, skills }) : []
spin.stop('Done.')
if (pointers.length > 0) {
log.step('Pointer written to ' + pointers.map((p) => path.basename(p)).join(', '))
}
const agentCount = new Set(written.map((w) => w.agent.id)).size
if (written.length > 0) {
outro(`Installed ${written.length} skill(s) into ${agentCount} agent folder(s).`)
} else {
outro('Nothing new to install. Existing folders were kept.')
}
console.log(pc.dim('antislop is ready. The next agent session loads it.'))
}
main().catch((err) => {
console.error(err)
process.exit(1)
})