Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions apps/web/content/docs/(unrag)/ai-assisted-coding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@ AI coding assistants often hallucinate method signatures and configuration optio

## Installing the Unrag skill

Installing the skill is straightforward. You use the `add-skill` CLI, a tool from Vercel Labs that installs Agent Skills into your project:
There are two ways to install the Unrag skill:

### Option 1: Using the Unrag CLI (recommended)

```bash
bunx unrag add skills
```

This launches an interactive prompt where you can select which AI coding assistants to install the skill for. You can install for multiple agents at once—Claude Code, Cursor, Windsurf, Antigravity, or Codex.

### Option 2: Using add-skill

You can also use the `add-skill` CLI from Vercel Labs:

```bash
bunx add-skill betterstacks/unrag
```

This command downloads the skill and places it in your project's `.claude/skills/` directory (or the equivalent location for your AI assistant). From that point forward, your AI assistant has access to the complete Unrag reference when working in your codebase.
This installs the skill specifically for Claude Code into your project's `.claude/skills/` directory.

<Callout type="info">
The skill is designed with Claude Code in mind, but the format works with other AI assistants that support similar knowledge structures. The underlying content is just markdown files with structured information—any assistant that can read and reason over project files can benefit.
Expand Down Expand Up @@ -119,7 +131,7 @@ The Unrag skill is versioned to track Unrag releases:

| Component | Version |
|-----------|---------|
| Skill Version | 1.0.0 |
| Skill Version | 0.3.2 |
| Unrag CLI Version | 0.3.2 |
| Config Version | 2 |

Expand Down
2 changes: 1 addition & 1 deletion apps/web/content/docs/(unrag)/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
"examples",
"reference"
]
}
}
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions packages/unrag/cli/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ const addPackageJsonScripts = async (args: {
}

type ParsedAddArgs = {
kind?: 'connector' | 'extractor' | 'battery'
kind?: 'connector' | 'extractor' | 'battery' | 'skills'
name?: string
yes?: boolean
noInstall?: boolean
Expand Down Expand Up @@ -515,6 +515,10 @@ const parseAddArgs = (args: string[]): ParsedAddArgs => {
out.kind = 'battery'
continue
}
if (a === 'skills') {
out.kind = 'skills'
continue
}
out.kind = 'connector'
out.name = a
continue
Expand All @@ -534,14 +538,21 @@ const parseAddArgs = (args: string[]): ParsedAddArgs => {
}

export async function addCommand(args: string[]) {
// Handle skills subcommand early (doesn't require project root)
const parsed = parseAddArgs(args)
if (parsed.kind === 'skills') {
const {skillsCommand} = await import('./skills')
await skillsCommand(args)
return
}

const root = await tryFindProjectRoot(process.cwd())
if (!root) {
throw new Error(
'Could not find a project root (no package.json found).'
)
}

const parsed = parseAddArgs(args)
const kind = parsed.kind ?? 'connector'
const name = parsed.name
const noInstall =
Expand Down
213 changes: 213 additions & 0 deletions packages/unrag/cli/commands/skills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import {copyFile, mkdir, readdir} from 'node:fs/promises'
import {homedir} from 'node:os'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import {cancel, isCancel, log, multiselect, outro} from '@clack/prompts'
import {exists, findUp} from '../lib/fs'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

// ANSI color codes
const GRAY = '\x1b[37m'
const DARK = '\x1b[90m'
const RESET = '\x1b[0m'
const B = GRAY
const D = DARK
const R = RESET

const BANNER = `
${B} ██${D}╗ ${B}██${D}╗${B}███${D}╗ ${B}██${D}╗${B}██████${D}╗ ${B}█████${D}╗ ${B}██████${D}╗${R}
${B} ██${D}║ ${B}██${D}║${B}████${D}╗ ${B}██${D}║${B}██${D}╔══${B}██${D}╗${B}██${D}╔══${B}██${D}╗${B}██${D}╔════╝${R}
${B} ██${D}║ ${B}██${D}║${B}██${D}╔${B}██${D}╗ ${B}██${D}║${B}██████${D}╔╝${B}███████${D}║${B}██${D}║ ${B}███${D}╗${R}
${B} ██${D}║ ${B}██${D}║${B}██${D}║╚${B}██${D}╗${B}██${D}║${B}██${D}╔══${B}██${D}╗${B}██${D}╔══${B}██${D}║${B}██${D}║ ${B}██${D}║${R}
${D} ╚${B}██████${D}╔╝${B}██${D}║ ╚${B}████${D}║${B}██${D}║ ${B}██${D}║${B}██${D}║ ${B}██${D}║╚${B}██████${D}╔╝${R}
${D} ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝${R}
${D} skills${R}
`

type AgentId = 'claude-code' | 'cursor' | 'windsurf' | 'antigravity' | 'codex'

interface AgentConfig {
id: AgentId
name: string
description: string
getInstallPath: (projectRoot: string) => string
isGlobal: boolean
}

const AGENTS: AgentConfig[] = [
{
id: 'claude-code',
name: 'Claude Code',
description: 'Anthropic Claude Code (.claude/skills/)',
getInstallPath: (projectRoot) =>
path.join(projectRoot, '.claude', 'skills', 'unrag'),
isGlobal: false
},
{
id: 'cursor',
name: 'Cursor',
description: 'Cursor IDE (.cursor/rules/)',
getInstallPath: (projectRoot) =>
path.join(projectRoot, '.cursor', 'rules', 'unrag'),
isGlobal: false
},
{
id: 'windsurf',
name: 'Windsurf',
description: 'Windsurf AI (.windsurf/rules/)',
getInstallPath: (projectRoot) =>
path.join(projectRoot, '.windsurf', 'rules', 'unrag'),
isGlobal: false
},
{
id: 'antigravity',
name: 'Antigravity',
description: 'Google Antigravity (.gemini/skills/)',
getInstallPath: (projectRoot) =>
path.join(projectRoot, '.gemini', 'skills', 'unrag'),
isGlobal: false
},
{
id: 'codex',
name: 'Codex',
description: 'OpenAI Codex CLI (~/.codex/prompts/)',
getInstallPath: () =>
path.join(homedir(), '.codex', 'prompts', 'unrag'),
isGlobal: true
}
]

async function copyDirectory(src: string, dest: string): Promise<string[]> {
const copiedFiles: string[] = []

await mkdir(dest, {recursive: true})

const entries = await readdir(src, {withFileTypes: true})

for (const entry of entries) {
const srcPath = path.join(src, entry.name)
const destPath = path.join(dest, entry.name)

if (entry.isDirectory()) {
const nested = await copyDirectory(srcPath, destPath)
copiedFiles.push(...nested)
} else {
await copyFile(srcPath, destPath)
copiedFiles.push(destPath)
}
}

return copiedFiles
}

export async function skillsCommand(args: string[]) {
const nonInteractive = args.includes('--yes') || args.includes('-y')

// Print the banner
console.log(BANNER)

// Find CLI package root to locate bundled skills
const cliPackageRoot = await findUp(__dirname, 'package.json')
if (!cliPackageRoot) {
outro('Could not locate CLI package root.')
process.exitCode = 1
return
}

// Skills are bundled in the package
const skillsSource = path.join(cliPackageRoot, 'skills', 'unrag')
if (!(await exists(skillsSource))) {
outro('Skills bundle not found in CLI package.')
process.exitCode = 1
return
}

// Find project root for project-local installations
const projectRoot = process.cwd()

let selectedAgents: AgentConfig[] = []

if (nonInteractive) {
// Default to Claude Code in non-interactive mode
const claudeCode = AGENTS.find((a) => a.id === 'claude-code')
if (claudeCode) {
selectedAgents = [claudeCode]
}
} else {
const choices = await multiselect({
message:
'Which IDE/agents would you like to install the Unrag skill for?',
options: AGENTS.map((agent) => ({
value: agent.id,
label: agent.name,
hint: agent.description
})),
required: true
})

if (isCancel(choices)) {
cancel('Cancelled.')
return
}

selectedAgents = (choices as AgentId[])
.map((id) => AGENTS.find((a) => a.id === id))
.filter((a): a is AgentConfig => a !== undefined)
}

if (selectedAgents.length === 0) {
outro('No agents selected.')
return
}

const installed: string[] = []
const skipped: string[] = []

for (const agent of selectedAgents) {
const installPath = agent.getInstallPath(projectRoot)

// Check if already installed
if (await exists(installPath)) {
skipped.push(agent.name)
continue
}

// Copy the skills
log.step(`Installing Unrag skills for ${agent.name}...`)

try {
await copyDirectory(skillsSource, installPath)
installed.push(agent.name)

if (agent.isGlobal) {
log.success(`Installed to ${installPath}`)
} else {
log.success(
`Installed to ${path.relative(projectRoot, installPath)}/`
)
}
} catch (err) {
log.error(
`Failed to install for ${agent.name}: ${err instanceof Error ? err.message : String(err)}`
)
}
}

// Summary
const summary: string[] = []

if (installed.length > 0) {
summary.push(`✓ Installed for: ${installed.join(', ')}`)
}
if (skipped.length > 0) {
summary.push(`⊘ Already installed: ${skipped.join(', ')}`)
}

if (summary.length > 0) {
outro(
`${summary.join('\n')}\n\nYour AI assistants now have access to Unrag documentation and patterns.`
)
}
}
1 change: 1 addition & 0 deletions packages/unrag/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function renderHelp() {
' add <connector> Install a connector (notion, google-drive)',
' add extractor <n> Install an extractor (pdf-llm, image-ocr, etc.)',
' add battery <name> Install a battery module (reranker, eval, debug)',
' add skills Install Unrag agent skills for your IDE/agent',
' upgrade Upgrade vendored sources (git-style merge)',
' doctor Validate installation and configuration',
' doctor setup Generate project-specific doctor config and scripts',
Expand Down
2 changes: 1 addition & 1 deletion packages/unrag/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"unrag:doctor:db": "unrag doctor --config .unrag/doctor.json --db",
"unrag:doctor:ci": "unrag doctor --config .unrag/doctor.json --db --strict --json"
},
"files": ["dist/**", "registry/**", "README.md"],
"files": ["dist/**", "registry/**", "skills/**", "README.md"],
"publishConfig": {
"access": "public"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: unrag
description: Covers RAG installation, ContextEngine API, embedding providers, store adapters, extractors, connectors, batteries, and CLI commands for the unrag TypeScript library.
version: 1.0.0
version: 0.3.2
---

# Unrag Agent Skill
Expand Down