Files
anti-slop/cli/lib/install.mjs
T

140 lines
4.5 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import os from 'node:os'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export const CORE = 'antislop'
export const AGENTS = [
{ id: 'claude', label: 'Claude Code', dir: '.claude/skills' },
{ id: 'antigravity', label: 'Antigravity', dir: '.agents/skills' },
{ id: 'codex', label: 'Codex', dir: '.codex/skills' },
]
export function skillSourceDir() {
const bundled = path.join(__dirname, '..', 'skills')
if (fs.existsSync(bundled)) return bundled
const repo = path.join(__dirname, '..', '..', 'skills')
if (fs.existsSync(repo)) return repo
return null
}
function resolveBase(location) {
return location === 'global' ? os.homedir() : process.cwd()
}
export function resolveTargets(location) {
const base = resolveBase(location)
return AGENTS.map((agent) => ({ agent, path: path.join(base, agent.dir) }))
.filter((t) => {
if (t.agent.id === 'claude') return true
const parent = path.join(base, t.agent.dir.split('/')[0])
return fs.existsSync(parent)
})
.map((t) => ({ ...t, exists: fs.existsSync(t.path) }))
}
export function detectConflicts({ skills, targets }) {
const conflicts = []
for (const t of targets) {
for (const skill of skills) {
if (fs.existsSync(path.join(t.path, skill))) {
conflicts.push({ skill, agent: t.agent, path: path.join(t.path, skill) })
}
}
}
return conflicts
}
function copyDir(src, dest) {
fs.rmSync(dest, { recursive: true, force: true })
fs.mkdirSync(dest, { recursive: true })
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const s = path.join(src, entry.name)
const d = path.join(dest, entry.name)
if (entry.isDirectory()) copyDir(s, d)
else fs.copyFileSync(s, d)
}
}
export function installSkills({ skills, targets, overwrite = false }) {
const source = skillSourceDir()
const written = []
for (const t of targets) {
for (const skill of skills) {
const src = path.join(source, skill)
if (!fs.existsSync(src)) continue
const dest = path.join(t.path, skill)
if (fs.existsSync(dest) && !overwrite) continue
copyDir(src, dest)
written.push({ skill, agent: t.agent, path: dest })
}
}
return written
}
const POINTER_START = '<!-- antislop:start -->'
const POINTER_END = '<!-- antislop:end -->'
// The entry file each agent reads at session start.
const ENTRY_FILE = { claude: 'CLAUDE.md', codex: 'AGENTS.md', antigravity: 'AGENTS.md' }
const SKILL_LINES = {
[CORE]: 'Core filter, always on: `antislop`',
'antislop-ui': 'UI / visual: `antislop-ui`',
'antislop-copywriting': 'Copy & text: `antislop-copywriting`',
'antislop-human': 'People: `antislop-human`',
'antislop-layoutmobile': 'Mobile / responsive: `antislop-layoutmobile`',
'antislop-code': 'Code comments: `antislop-code`',
}
// Names the skills instead of importing the core. The skills sit in the agent's
// own folder, so the agent already finds them; an `@` import would also pull all
// 46 KB of the core into every session, including sessions that touch no UI.
function pointerBlock(skills) {
return [
POINTER_START,
'## antislop',
'For UI, copy, people, mobile layout, or code comments work, load the antislop skill for the task:',
...skills.filter((s) => SKILL_LINES[s]).map((s) => `- ${SKILL_LINES[s]}`),
'Before starting, ask the user when antislop applies: during the work, or after it is done.',
POINTER_END,
]
}
function writeBlock(entry, block) {
const existing = fs.existsSync(entry) ? fs.readFileSync(entry, 'utf8') : ''
const lines = existing.split(/\r?\n/)
const start = lines.findIndex((l) => l.trim() === POINTER_START)
const end = lines.findIndex((l) => l.trim() === POINTER_END)
const replacing = start !== -1 && end !== -1 && start < end
const head = replacing ? lines.slice(0, start) : lines
const tail = replacing ? lines.slice(end + 1) : []
const body = [...head, '', ...block, '', ...tail]
.join('\n')
.replace(/\n{3,}/g, '\n\n')
.replace(/^\n+/, '')
.trimEnd()
fs.writeFileSync(entry, body + '\n')
}
export function updatePointers({ targets, skills }) {
const entries = new Set()
for (const t of targets) {
if (fs.existsSync(path.join(t.path, CORE))) entries.add(ENTRY_FILE[t.agent.id])
}
const block = pointerBlock(skills)
const written = []
for (const name of entries) {
const entry = path.join(process.cwd(), name)
writeBlock(entry, block)
written.push(entry)
}
return written
}