Skip to content
Open
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
28 changes: 26 additions & 2 deletions src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
import { readFileSync, mkdtempSync } from 'fs';
import { join } from 'path';
import { runCliOutput, stripLogo, hasLogo } from './test-utils.ts';
import { tmpdir } from 'os';
import { runCli, runCliOutput, stripLogo, hasLogo } from './test-utils.ts';

describe('skills CLI', () => {
describe('--help', () => {
Expand All @@ -14,6 +15,7 @@ describe('skills CLI', () => {
expect(output).toContain('check');
expect(output).toContain('update');
expect(output).toContain('Add Options:');
expect(output).toContain('Check Options:');
expect(output).toContain('-g, --global');
expect(output).toContain('-a, --agent');
expect(output).toContain('-s, --skill');
Expand Down Expand Up @@ -67,6 +69,28 @@ describe('skills CLI', () => {
});
});

describe('check --json', () => {
it('should output valid JSON with no skills', () => {
const tempState = mkdtempSync(join(tmpdir(), 'skills-check-test-'));
const result = runCli(['check', '--json'], undefined, { XDG_STATE_HOME: tempState });
const parsed = JSON.parse(result.stdout.trim());
expect(parsed).toEqual([]);
});

it('should output an array', () => {
const tempState = mkdtempSync(join(tmpdir(), 'skills-check-test-'));
const result = runCli(['check', '--json'], undefined, { XDG_STATE_HOME: tempState });
const parsed = JSON.parse(result.stdout.trim());
expect(Array.isArray(parsed)).toBe(true);
});

it('should not contain ANSI codes', () => {
const tempState = mkdtempSync(join(tmpdir(), 'skills-check-test-'));
const result = runCli(['check', '--json'], undefined, { XDG_STATE_HOME: tempState });
expect(result.stdout).not.toMatch(/\x1b\[/);
});
});

describe('logo display', () => {
it('should not display logo for list command', () => {
const output = runCliOutput(['list']);
Expand Down
35 changes: 32 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ ${BOLD}List Options:${RESET}
-a, --agent <agents> Filter by specific agents
--json Output as JSON (machine-readable, no ANSI codes)

${BOLD}Check Options:${RESET}
--json Output as JSON (machine-readable, no ANSI codes)

${BOLD}Options:${RESET}
--help, -h Show this help message
--version, -v Show version number
Expand All @@ -169,6 +172,7 @@ ${BOLD}Examples:${RESET}
${DIM}$${RESET} skills find ${DIM}# interactive search${RESET}
${DIM}$${RESET} skills find typescript ${DIM}# search by keyword${RESET}
${DIM}$${RESET} skills check
${DIM}$${RESET} skills check --json ${DIM}# JSON output${RESET}
${DIM}$${RESET} skills update
${DIM}$${RESET} skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
${DIM}$${RESET} skills init my-skill
Expand Down Expand Up @@ -397,13 +401,21 @@ function printSkippedSkills(skipped: SkippedSkill[]): void {
}

async function runCheck(args: string[] = []): Promise<void> {
console.log(`${TEXT}Checking for skill updates...${RESET}`);
console.log();
const json = args.includes('--json');

if (!json) {
console.log(`${TEXT}Checking for skill updates...${RESET}`);
console.log();
}

const lock = readSkillLock();
const skillNames = Object.keys(lock.skills);

if (skillNames.length === 0) {
if (json) {
console.log(JSON.stringify([], null, 2));
return;
}
console.log(`${DIM}No skills tracked in lock file.${RESET}`);
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
return;
Expand Down Expand Up @@ -433,12 +445,18 @@ async function runCheck(args: string[] = []): Promise<void> {

const totalSkills = skillNames.length - skipped.length;
if (totalSkills === 0) {
if (json) {
console.log(JSON.stringify([], null, 2));
return;
}
console.log(`${DIM}No GitHub skills to check.${RESET}`);
printSkippedSkills(skipped);
return;
}

console.log(`${DIM}Checking ${totalSkills} skill(s) for updates...${RESET}`);
if (!json) {
console.log(`${DIM}Checking ${totalSkills} skill(s) for updates...${RESET}`);
}

const updates: Array<{ name: string; source: string }> = [];
const errors: Array<{ name: string; source: string; error: string }> = [];
Expand Down Expand Up @@ -467,6 +485,17 @@ async function runCheck(args: string[] = []): Promise<void> {
}
}

if (json) {
console.log(
JSON.stringify(
updates.map((u) => ({ name: u.name, source: u.source })),
null,
2
)
);
return;
}

console.log();

if (updates.length === 0) {
Expand Down