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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ bun run review:hourly

# inspect run state for one issue
bun run src/index.ts status --project <PROJECT_ID> --issue ENG-123

# skills management
bun run src/index.ts skills list [--project <PROJECT_ID>]
bun run src/index.ts skills add --title "<TITLE>" --description "<DESCRIPTION>" --content "<CONTENT>" [--project <PROJECT_ID>]
bun run src/index.ts skills update <NAME> [--title "<TITLE>"] [--description "<DESCRIPTION>"] [--content "<CONTENT>"] [--project <PROJECT_ID>]
bun run src/index.ts skills remove <NAME> [--project <PROJECT_ID>]
```

After linking/installing the package bin, you can also use `adhd-ai ...` directly.
Expand Down
21 changes: 21 additions & 0 deletions skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ skills: {
}
```

## Manage skills via CLI

Use the CLI to list, add, update, and remove skill folders under the configured
`skills.root` for the selected project:

```bash
bun run src/index.ts skills list [--project <PROJECT_ID>]
bun run src/index.ts skills add --title "<TITLE>" --description "<DESCRIPTION>" --content "<CONTENT>" [--project <PROJECT_ID>]
bun run src/index.ts skills update <NAME> [--title "<TITLE>"] [--description "<DESCRIPTION>"] [--content "<CONTENT>"] [--project <PROJECT_ID>]
bun run src/index.ts skills remove <NAME> [--project <PROJECT_ID>]
```

Generated `SKILL.md` template:

```md
name: <skill title>
description: <skill description>

<skill content>
```

## Using skills from another repository

You can source skills from another repo in three common ways:
Expand Down
116 changes: 116 additions & 0 deletions src/args.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
import type { RunOptions } from "./core/types";

export type SkillsCommand =
| { action: "list"; projectId?: string }
| {
action: "add";
projectId?: string;
title: string;
description: string;
content: string;
}
| {
action: "update";
projectId?: string;
name: string;
title?: string;
description?: string;
content?: string;
}
| {
action: "remove";
projectId?: string;
name: string;
};

export type CliCommand =
| { kind: "run"; options: RunOptions }
| { kind: "cron"; jobId?: string }
| { kind: "status"; issueKey: string; projectId: string }
| { kind: "projects" }
| { kind: "skills"; command: SkillsCommand }
| { kind: "setup"; check: boolean }
| { kind: "help" };

Expand Down Expand Up @@ -76,9 +100,89 @@ export function parseArgs(argv: string[]): CliCommand {
return { kind: "projects" };
}

if (command === "skills") {
return {
kind: "skills",
command: parseSkillsCommand(rest.slice(1)),
};
}

throw new Error(`Unknown command: ${command}`);
}

function parseSkillsCommand(args: string[]): SkillsCommand {
const action = args[0];
if (!action) {
throw new Error(
"skills command requires an action: list | add | update | remove",
);
}

if (action === "list") {
return {
action: "list",
projectId: readFlagValue(args.slice(1), "--project"),
};
}

if (action === "add") {
const actionArgs = args.slice(1);
return {
action: "add",
projectId: readFlagValue(actionArgs, "--project"),
title: readRequiredFlagValue(actionArgs, "--title", "skills add"),
description: readRequiredFlagValue(
actionArgs,
"--description",
"skills add",
),
content: readRequiredFlagValue(actionArgs, "--content", "skills add"),
};
}

if (action === "update") {
const name = args[1];
if (!name) {
throw new Error("skills update requires <NAME>");
}
const actionArgs = args.slice(2);
const title = readFlagValue(actionArgs, "--title");
const description = readFlagValue(actionArgs, "--description");
const content = readFlagValue(actionArgs, "--content");
if (
title === undefined &&
description === undefined &&
content === undefined
) {
throw new Error(
"skills update requires at least one of --title, --description, or --content",
);
}
return {
action: "update",
name,
projectId: readFlagValue(actionArgs, "--project"),
title,
description,
content,
};
}

if (action === "remove") {
const name = args[1];
if (!name) {
throw new Error("skills remove requires <NAME>");
}
return {
action: "remove",
name,
projectId: readFlagValue(args.slice(2), "--project"),
};
}

throw new Error(`Unknown skills action: ${action}`);
}

function readFlagValue(args: string[], flag: string): string | undefined {
const index = args.indexOf(flag);
if (index < 0) {
Expand All @@ -87,6 +191,18 @@ function readFlagValue(args: string[], flag: string): string | undefined {
return args[index + 1];
}

function readRequiredFlagValue(
args: string[],
flag: string,
commandLabel: string,
): string {
const value = readFlagValue(args, flag);
if (!value) {
throw new Error(`${commandLabel} requires ${flag} <VALUE>`);
}
return value;
}

function readOptionalPositiveInt(
args: string[],
flag: string,
Expand Down
73 changes: 72 additions & 1 deletion src/commands/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { runSetupCheck, runSetupWizard } from "../core/setup";
import { loadRunState, normalizeIssueKey } from "../core/state";
import { runWorkflow } from "../core/workflow";
import { runCronScheduler } from "../services/cron";
import { formatWorkflowStageDisplay } from "../utils/status";
import {
addSkill,
listSkills,
removeSkill,
updateSkill,
} from "../skills/manage";

type SetupCommand = Extract<CliCommand, { kind: "setup" }>;
type RunnableCommand = Exclude<CliCommand, { kind: "help" } | SetupCommand>;
Expand Down Expand Up @@ -49,6 +54,68 @@ export async function handleCommand(
return;
}

if (command.kind === "skills") {
const selectedProject = command.command.projectId
? getProjectById(config, command.command.projectId)
: config.projects[0];
if (command.command.projectId && !selectedProject) {
throw new Error(`Project '${command.command.projectId}' not found`);
}
const project = selectedProject;
if (!project) {
throw new Error("No project is configured");
}

if (command.command.action === "list") {
const skills = await listSkills(project.skills.root);
if (skills.length === 0) {
process.stdout.write(`No skills found in ${project.skills.root}\n`);
return;
}
for (const skill of skills) {
process.stdout.write(
`${[skill.name, skill.title, skill.description || "-"].join("\t")}\n`,
);
}
return;
}

if (command.command.action === "add") {
const created = await addSkill(project.skills.root, {
title: command.command.title,
description: command.command.description,
content: command.command.content,
});
process.stdout.write(`Added skill ${created.name} at ${created.path}\n`);
return;
}

if (command.command.action === "update") {
const updated = await updateSkill(
project.skills.root,
command.command.name,
{
title: command.command.title,
description: command.command.description,
content: command.command.content,
},
);
process.stdout.write(
`Updated skill ${updated.name} at ${updated.path}\n`,
);
return;
}

const removed = await removeSkill(
project.skills.root,
command.command.name,
);
process.stdout.write(
`Removed skill ${removed.name} from ${removed.path}\n`,
);
return;
}

const project = getProjectById(config, command.projectId);
if (!project) {
throw new Error(`Project '${command.projectId}' not found`);
Expand Down Expand Up @@ -79,6 +146,10 @@ export function printHelp(): void {
" adhd-ai cron [--job <JOB_ID>]",
" adhd-ai status --project <PROJECT_ID> --issue <LINEAR_KEY>",
" adhd-ai projects",
" adhd-ai skills list [--project <PROJECT_ID>]",
" adhd-ai skills add --title <TITLE> --description <TEXT> --content <TEXT> [--project <PROJECT_ID>]",
" adhd-ai skills update <NAME> [--title <TITLE>] [--description <TEXT>] [--content <TEXT>] [--project <PROJECT_ID>]",
" adhd-ai skills remove <NAME> [--project <PROJECT_ID>]",
" adhd-ai setup [--check]",
" adhd-ai help",
"",
Expand Down
Loading
Loading