@lousy-agents/cli 2.6.1 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/api/copilot-with-fastify/vitest.integration.config.ts +2 -4
- package/dist/index.js +287 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +85 -0
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## TL;DR
|
|
8
8
|
|
|
9
|
-
A CLI tool that scaffolds projects with the structure AI coding assistants need to be effective. Run `npx @lousy-agents/cli init` to create a new project with testing, linting, and GitHub Copilot configuration. Run `npx @lousy-agents/cli copilot-setup` in existing projects to generate a workflow that gives Copilot your environment context.
|
|
9
|
+
A CLI tool that scaffolds projects with the structure AI coding assistants need to be effective. Run `npx @lousy-agents/cli init` to create a new project with testing, linting, and GitHub Copilot configuration. Run `npx @lousy-agents/cli copilot-setup` in existing projects to generate a workflow that gives Copilot your environment context. Run `npx @lousy-agents/cli lint` to validate agent skill frontmatter.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -59,6 +59,7 @@ AI coding assistants work best when given clear constraints. Without structure,
|
|
|
59
59
|
|
|
60
60
|
- **[`init`](docs/init.md)** - Scaffold new projects with testing, linting, and Copilot configuration
|
|
61
61
|
- **[`new`](docs/new.md)** - Create new resources like custom GitHub Copilot agents
|
|
62
|
+
- **[`lint`](docs/lint.md)** - Validate agent skill frontmatter in SKILL.md files
|
|
62
63
|
- **[`copilot-setup`](docs/copilot-setup.md)** - Generate GitHub Actions workflows for Copilot environment setup
|
|
63
64
|
|
|
64
65
|
### MCP Server
|
|
@@ -97,6 +98,7 @@ For detailed documentation on each command, see:
|
|
|
97
98
|
|
|
98
99
|
- **[`init` command](docs/init.md)** - Scaffold new projects
|
|
99
100
|
- **[`new` command](docs/new.md)** - Create new resources
|
|
101
|
+
- **[`lint` command](docs/lint.md)** - Validate agent skill frontmatter
|
|
100
102
|
- **[`copilot-setup` command](docs/copilot-setup.md)** - Generate Copilot workflows
|
|
101
103
|
- **[MCP Server](docs/mcp-server.md)** - AI assistant integration
|
|
102
104
|
|
|
@@ -120,6 +122,12 @@ npx @lousy-agents/cli new --copilot-agent security
|
|
|
120
122
|
npx @lousy-agents/cli copilot-setup
|
|
121
123
|
```
|
|
122
124
|
|
|
125
|
+
**Lint agent skill frontmatter:**
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx @lousy-agents/cli lint
|
|
129
|
+
```
|
|
130
|
+
|
|
123
131
|
## Roadmap
|
|
124
132
|
|
|
125
133
|
| Feature | Status |
|
|
@@ -130,12 +138,15 @@ npx @lousy-agents/cli copilot-setup
|
|
|
130
138
|
| Scaffolding for GraphQL APIs | Not Started |
|
|
131
139
|
| Copilot setup package manager install steps | ✅ Complete |
|
|
132
140
|
| Copilot agent and skill scaffolding | ✅ Complete |
|
|
141
|
+
| Agent skill frontmatter linting | ✅ Complete |
|
|
133
142
|
| MCP server package | ✅ Complete |
|
|
143
|
+
| Claude Code web environment setup | ✅ Complete |
|
|
134
144
|
|
|
135
145
|
## Documentation
|
|
136
146
|
|
|
137
147
|
- **[`init` Command](docs/init.md)** - Project scaffolding
|
|
138
148
|
- **[`new` Command](docs/new.md)** - Create new resources
|
|
149
|
+
- **[`lint` Command](docs/lint.md)** - Agent skill frontmatter validation
|
|
139
150
|
- **[`copilot-setup` Command](docs/copilot-setup.md)** - Workflow generation
|
|
140
151
|
- **[MCP Server](docs/mcp-server.md)** - AI assistant integration
|
|
141
152
|
|
|
@@ -145,5 +156,6 @@ The repository includes fully working reference implementations demonstrating th
|
|
|
145
156
|
|
|
146
157
|
- **[ui/copilot-with-react](ui/copilot-with-react)** - Next.js + TypeScript webapp with pre-configured testing (Vitest), linting (Biome), GitHub Copilot instructions, and Dev Container configuration.
|
|
147
158
|
- **[api/copilot-with-fastify](api/copilot-with-fastify)** - Fastify + TypeScript REST API with Kysely, PostgreSQL, Testcontainers integration testing, and Dev Container configuration.
|
|
159
|
+
- **[cli/copilot-with-citty](cli/copilot-with-citty)** - Citty + TypeScript CLI with pre-configured testing (Vitest), linting (Biome), GitHub Copilot instructions, and Dev Container configuration.
|
|
148
160
|
|
|
149
|
-
Launch a GitHub Codespace to instantly spin up
|
|
161
|
+
Launch a GitHub Codespace to instantly spin up any of these environments and experiment with spec-driven development.
|
package/dist/index.js
CHANGED
|
@@ -14681,6 +14681,90 @@ async function watchConfig(options) {
|
|
|
14681
14681
|
|
|
14682
14682
|
// EXTERNAL MODULE: ./node_modules/yaml/dist/index.js
|
|
14683
14683
|
var dist = __webpack_require__(1198);
|
|
14684
|
+
;// CONCATENATED MODULE: ./src/gateways/skill-lint-gateway.ts
|
|
14685
|
+
/**
|
|
14686
|
+
* Gateway for skill lint file system operations.
|
|
14687
|
+
* Discovers skill files and parses YAML frontmatter.
|
|
14688
|
+
*/
|
|
14689
|
+
|
|
14690
|
+
|
|
14691
|
+
|
|
14692
|
+
/**
|
|
14693
|
+
* File system implementation of the skill lint gateway.
|
|
14694
|
+
*/ class FileSystemSkillLintGateway {
|
|
14695
|
+
async discoverSkills(targetDir) {
|
|
14696
|
+
const skillsDir = (0,external_node_path_.join)(targetDir, ".github", "skills");
|
|
14697
|
+
if (!await file_system_utils_fileExists(skillsDir)) {
|
|
14698
|
+
return [];
|
|
14699
|
+
}
|
|
14700
|
+
const entries = await (0,promises_.readdir)(skillsDir, {
|
|
14701
|
+
withFileTypes: true
|
|
14702
|
+
});
|
|
14703
|
+
const skills = [];
|
|
14704
|
+
for (const entry of entries){
|
|
14705
|
+
if (!entry.isDirectory()) {
|
|
14706
|
+
continue;
|
|
14707
|
+
}
|
|
14708
|
+
if (entry.name.includes("..") || entry.name.includes("/") || entry.name.includes("\\")) {
|
|
14709
|
+
continue;
|
|
14710
|
+
}
|
|
14711
|
+
const skillFilePath = (0,external_node_path_.join)(skillsDir, entry.name, "SKILL.md");
|
|
14712
|
+
if (await file_system_utils_fileExists(skillFilePath)) {
|
|
14713
|
+
skills.push({
|
|
14714
|
+
filePath: skillFilePath,
|
|
14715
|
+
skillName: entry.name
|
|
14716
|
+
});
|
|
14717
|
+
}
|
|
14718
|
+
}
|
|
14719
|
+
return skills;
|
|
14720
|
+
}
|
|
14721
|
+
async readSkillFileContent(filePath) {
|
|
14722
|
+
return (0,promises_.readFile)(filePath, "utf-8");
|
|
14723
|
+
}
|
|
14724
|
+
parseFrontmatter(content) {
|
|
14725
|
+
const lines = content.split("\n");
|
|
14726
|
+
if (lines[0]?.trim() !== "---") {
|
|
14727
|
+
return null;
|
|
14728
|
+
}
|
|
14729
|
+
let endIndex = -1;
|
|
14730
|
+
for(let i = 1; i < lines.length; i++){
|
|
14731
|
+
if (lines[i]?.trim() === "---") {
|
|
14732
|
+
endIndex = i;
|
|
14733
|
+
break;
|
|
14734
|
+
}
|
|
14735
|
+
}
|
|
14736
|
+
if (endIndex === -1) {
|
|
14737
|
+
return null;
|
|
14738
|
+
}
|
|
14739
|
+
const yamlContent = lines.slice(1, endIndex).join("\n");
|
|
14740
|
+
let data;
|
|
14741
|
+
try {
|
|
14742
|
+
const parsed = (0,dist.parse)(yamlContent);
|
|
14743
|
+
data = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
14744
|
+
} catch {
|
|
14745
|
+
return null;
|
|
14746
|
+
}
|
|
14747
|
+
const fieldLines = new Map();
|
|
14748
|
+
for(let i = 1; i < endIndex; i++){
|
|
14749
|
+
// Match YAML top-level field names: non-whitespace start, any chars except colon, then colon+space
|
|
14750
|
+
const match = lines[i]?.match(/^([^\s:][^:]*?):\s/);
|
|
14751
|
+
if (match?.[1]) {
|
|
14752
|
+
fieldLines.set(match[1], i + 1);
|
|
14753
|
+
}
|
|
14754
|
+
}
|
|
14755
|
+
return {
|
|
14756
|
+
data: data ?? {},
|
|
14757
|
+
fieldLines,
|
|
14758
|
+
frontmatterStartLine: 1
|
|
14759
|
+
};
|
|
14760
|
+
}
|
|
14761
|
+
}
|
|
14762
|
+
/**
|
|
14763
|
+
* Creates and returns the default skill lint gateway.
|
|
14764
|
+
*/ function createSkillLintGateway() {
|
|
14765
|
+
return new FileSystemSkillLintGateway();
|
|
14766
|
+
}
|
|
14767
|
+
|
|
14684
14768
|
;// CONCATENATED MODULE: ./src/gateways/tool-discovery-gateway.ts
|
|
14685
14769
|
/**
|
|
14686
14770
|
* Gateway for discovering CLI tools and commands from GitHub Actions workflows
|
|
@@ -15137,6 +15221,7 @@ var dist = __webpack_require__(1198);
|
|
|
15137
15221
|
|
|
15138
15222
|
|
|
15139
15223
|
|
|
15224
|
+
|
|
15140
15225
|
;// CONCATENATED MODULE: ./src/use-cases/candidate-builder.ts
|
|
15141
15226
|
/**
|
|
15142
15227
|
* Use case for building setup step candidates from environment detection.
|
|
@@ -30935,6 +31020,203 @@ const initCommand = defineCommand({
|
|
|
30935
31020
|
}
|
|
30936
31021
|
});
|
|
30937
31022
|
|
|
31023
|
+
;// CONCATENATED MODULE: ./src/use-cases/lint-skill-frontmatter.ts
|
|
31024
|
+
/**
|
|
31025
|
+
* Use case for linting GitHub Copilot Agent Skill frontmatter.
|
|
31026
|
+
* Validates required and recommended fields, name format, and directory naming.
|
|
31027
|
+
*/
|
|
31028
|
+
/**
|
|
31029
|
+
* Zod schema for validating agent skill frontmatter.
|
|
31030
|
+
* Based on the agentskills.io specification.
|
|
31031
|
+
*/ const AgentSkillFrontmatterSchema = schemas_object({
|
|
31032
|
+
name: schemas_string().min(1, "Name is required").max(64, "Name must be 64 characters or fewer").regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Name must contain only lowercase letters, numbers, and hyphens. It cannot start/end with a hyphen or contain consecutive hyphens."),
|
|
31033
|
+
description: schemas_string().min(1, "Description is required").max(1024, "Description must be 1024 characters or fewer").refine((s)=>s.trim().length > 0, {
|
|
31034
|
+
message: "Description cannot be empty or whitespace-only"
|
|
31035
|
+
}),
|
|
31036
|
+
license: schemas_string().optional(),
|
|
31037
|
+
compatibility: schemas_string().max(500, "Compatibility must be 500 characters or fewer").optional(),
|
|
31038
|
+
metadata: record(schemas_string(), schemas_string()).optional(),
|
|
31039
|
+
"allowed-tools": schemas_string().optional()
|
|
31040
|
+
});
|
|
31041
|
+
/**
|
|
31042
|
+
* Recommended (optional) fields that produce warnings when missing.
|
|
31043
|
+
*/ const RECOMMENDED_FIELDS = [
|
|
31044
|
+
"allowed-tools"
|
|
31045
|
+
];
|
|
31046
|
+
/**
|
|
31047
|
+
* Use case for linting skill frontmatter across a repository.
|
|
31048
|
+
*/ class LintSkillFrontmatterUseCase {
|
|
31049
|
+
gateway;
|
|
31050
|
+
constructor(gateway){
|
|
31051
|
+
this.gateway = gateway;
|
|
31052
|
+
}
|
|
31053
|
+
async execute(input) {
|
|
31054
|
+
if (!input.targetDir) {
|
|
31055
|
+
throw new Error("Target directory is required");
|
|
31056
|
+
}
|
|
31057
|
+
const skills = await this.gateway.discoverSkills(input.targetDir);
|
|
31058
|
+
const results = [];
|
|
31059
|
+
for (const skill of skills){
|
|
31060
|
+
const content = await this.gateway.readSkillFileContent(skill.filePath);
|
|
31061
|
+
const result = this.lintSkill(skill, content);
|
|
31062
|
+
results.push(result);
|
|
31063
|
+
}
|
|
31064
|
+
const totalErrors = results.reduce((sum, r)=>sum + r.diagnostics.filter((d)=>d.severity === "error").length, 0);
|
|
31065
|
+
const totalWarnings = results.reduce((sum, r)=>sum + r.diagnostics.filter((d)=>d.severity === "warning").length, 0);
|
|
31066
|
+
return {
|
|
31067
|
+
results,
|
|
31068
|
+
totalSkills: skills.length,
|
|
31069
|
+
totalErrors,
|
|
31070
|
+
totalWarnings
|
|
31071
|
+
};
|
|
31072
|
+
}
|
|
31073
|
+
lintSkill(skill, content) {
|
|
31074
|
+
let parsed = null;
|
|
31075
|
+
let diagnostics = [];
|
|
31076
|
+
try {
|
|
31077
|
+
parsed = this.gateway.parseFrontmatter(content);
|
|
31078
|
+
} catch (error) {
|
|
31079
|
+
const messagePrefix = "Invalid YAML frontmatter";
|
|
31080
|
+
const errorMessage = error instanceof Error && error.message ? `${messagePrefix}: ${error.message}` : `${messagePrefix}.`;
|
|
31081
|
+
diagnostics.push({
|
|
31082
|
+
line: 1,
|
|
31083
|
+
severity: "error",
|
|
31084
|
+
message: errorMessage
|
|
31085
|
+
});
|
|
31086
|
+
}
|
|
31087
|
+
if (!parsed) {
|
|
31088
|
+
if (diagnostics.length === 0) {
|
|
31089
|
+
const message = hasFrontmatterDelimiters(content) ? "Invalid YAML frontmatter. The content between --- delimiters could not be parsed as valid YAML." : "Missing YAML frontmatter. Skill files must begin with --- delimited YAML frontmatter.";
|
|
31090
|
+
diagnostics.push({
|
|
31091
|
+
line: 1,
|
|
31092
|
+
severity: "error",
|
|
31093
|
+
message
|
|
31094
|
+
});
|
|
31095
|
+
}
|
|
31096
|
+
return {
|
|
31097
|
+
filePath: skill.filePath,
|
|
31098
|
+
skillName: skill.skillName,
|
|
31099
|
+
diagnostics,
|
|
31100
|
+
valid: false
|
|
31101
|
+
};
|
|
31102
|
+
}
|
|
31103
|
+
const frontmatterDiagnostics = this.validateFrontmatter(parsed, skill.skillName);
|
|
31104
|
+
diagnostics = diagnostics.concat(frontmatterDiagnostics);
|
|
31105
|
+
return {
|
|
31106
|
+
filePath: skill.filePath,
|
|
31107
|
+
skillName: skill.skillName,
|
|
31108
|
+
diagnostics,
|
|
31109
|
+
valid: diagnostics.every((d)=>d.severity !== "error")
|
|
31110
|
+
};
|
|
31111
|
+
}
|
|
31112
|
+
validateFrontmatter(parsed, parentDirName) {
|
|
31113
|
+
const diagnostics = [];
|
|
31114
|
+
// Validate against Zod schema
|
|
31115
|
+
const result = AgentSkillFrontmatterSchema.safeParse(parsed.data);
|
|
31116
|
+
if (!result.success) {
|
|
31117
|
+
for (const issue of result.error.issues){
|
|
31118
|
+
const fieldName = issue.path[0]?.toString();
|
|
31119
|
+
const line = fieldName ? parsed.fieldLines.get(fieldName) ?? parsed.frontmatterStartLine : parsed.frontmatterStartLine;
|
|
31120
|
+
diagnostics.push({
|
|
31121
|
+
line,
|
|
31122
|
+
severity: "error",
|
|
31123
|
+
message: issue.message,
|
|
31124
|
+
field: fieldName
|
|
31125
|
+
});
|
|
31126
|
+
}
|
|
31127
|
+
}
|
|
31128
|
+
// Check name matches parent directory
|
|
31129
|
+
if (result.success && result.data.name !== parentDirName) {
|
|
31130
|
+
const nameLine = parsed.fieldLines.get("name") ?? parsed.frontmatterStartLine;
|
|
31131
|
+
diagnostics.push({
|
|
31132
|
+
line: nameLine,
|
|
31133
|
+
severity: "error",
|
|
31134
|
+
message: `Frontmatter name '${result.data.name}' must match parent directory name '${parentDirName}'`,
|
|
31135
|
+
field: "name"
|
|
31136
|
+
});
|
|
31137
|
+
}
|
|
31138
|
+
// Check recommended fields
|
|
31139
|
+
for (const field of RECOMMENDED_FIELDS){
|
|
31140
|
+
if (parsed.data[field] === undefined) {
|
|
31141
|
+
diagnostics.push({
|
|
31142
|
+
line: parsed.frontmatterStartLine,
|
|
31143
|
+
severity: "warning",
|
|
31144
|
+
message: `Recommended field '${field}' is missing`,
|
|
31145
|
+
field
|
|
31146
|
+
});
|
|
31147
|
+
}
|
|
31148
|
+
}
|
|
31149
|
+
return diagnostics;
|
|
31150
|
+
}
|
|
31151
|
+
}
|
|
31152
|
+
/**
|
|
31153
|
+
* Checks whether content has opening and closing --- frontmatter delimiters.
|
|
31154
|
+
*/ function hasFrontmatterDelimiters(content) {
|
|
31155
|
+
const lines = content.split("\n");
|
|
31156
|
+
if (lines[0]?.trim() !== "---") {
|
|
31157
|
+
return false;
|
|
31158
|
+
}
|
|
31159
|
+
for(let i = 1; i < lines.length; i++){
|
|
31160
|
+
if (lines[i]?.trim() === "---") {
|
|
31161
|
+
return true;
|
|
31162
|
+
}
|
|
31163
|
+
}
|
|
31164
|
+
return false;
|
|
31165
|
+
}
|
|
31166
|
+
|
|
31167
|
+
;// CONCATENATED MODULE: ./src/commands/lint.ts
|
|
31168
|
+
/**
|
|
31169
|
+
* CLI command for linting agent skill frontmatter.
|
|
31170
|
+
* Discovers skills, validates frontmatter, and reports diagnostics.
|
|
31171
|
+
*/
|
|
31172
|
+
|
|
31173
|
+
|
|
31174
|
+
|
|
31175
|
+
/**
|
|
31176
|
+
* The `lint` command for validating agent skill files.
|
|
31177
|
+
*/ const lintCommand = defineCommand({
|
|
31178
|
+
meta: {
|
|
31179
|
+
name: "lint",
|
|
31180
|
+
description: "Lint agent skill frontmatter. Validates required and recommended fields in SKILL.md files."
|
|
31181
|
+
},
|
|
31182
|
+
run: async (context)=>{
|
|
31183
|
+
const targetDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
31184
|
+
const gateway = createSkillLintGateway();
|
|
31185
|
+
const useCase = new LintSkillFrontmatterUseCase(gateway);
|
|
31186
|
+
const output = await useCase.execute({
|
|
31187
|
+
targetDir
|
|
31188
|
+
});
|
|
31189
|
+
if (output.totalSkills === 0) {
|
|
31190
|
+
consola.info("No skills found in .github/skills/");
|
|
31191
|
+
return;
|
|
31192
|
+
}
|
|
31193
|
+
consola.info(`Discovered ${output.totalSkills} skill(s)`);
|
|
31194
|
+
for (const result of output.results){
|
|
31195
|
+
if (result.diagnostics.length === 0) {
|
|
31196
|
+
consola.success(`${result.filePath}: OK`);
|
|
31197
|
+
continue;
|
|
31198
|
+
}
|
|
31199
|
+
for (const diagnostic of result.diagnostics){
|
|
31200
|
+
const prefix = `${result.filePath}:${diagnostic.line}`;
|
|
31201
|
+
const fieldInfo = diagnostic.field ? ` [${diagnostic.field}]` : "";
|
|
31202
|
+
if (diagnostic.severity === "error") {
|
|
31203
|
+
consola.error(`${prefix}${fieldInfo}: ${diagnostic.message}`);
|
|
31204
|
+
} else {
|
|
31205
|
+
consola.warn(`${prefix}${fieldInfo}: ${diagnostic.message}`);
|
|
31206
|
+
}
|
|
31207
|
+
}
|
|
31208
|
+
}
|
|
31209
|
+
if (output.totalErrors > 0) {
|
|
31210
|
+
throw new Error(`Skill lint failed: ${output.totalErrors} error(s), ${output.totalWarnings} warning(s)`);
|
|
31211
|
+
}
|
|
31212
|
+
if (output.totalWarnings > 0) {
|
|
31213
|
+
consola.warn(`Skill lint passed with ${output.totalWarnings} warning(s)`);
|
|
31214
|
+
} else {
|
|
31215
|
+
consola.success("All skills passed lint checks");
|
|
31216
|
+
}
|
|
31217
|
+
}
|
|
31218
|
+
});
|
|
31219
|
+
|
|
30938
31220
|
;// CONCATENATED MODULE: ./src/entities/copilot-agent.ts
|
|
30939
31221
|
/**
|
|
30940
31222
|
* Core domain entity for GitHub Copilot custom agent files.
|
|
@@ -31054,7 +31336,9 @@ You are a ${normalizedName} agent specialized in {domain/responsibility}.
|
|
|
31054
31336
|
;// CONCATENATED MODULE: ./src/entities/skill.ts
|
|
31055
31337
|
/**
|
|
31056
31338
|
* Core domain entity for GitHub Copilot Agent Skills.
|
|
31057
|
-
* Handles name normalization
|
|
31339
|
+
* Handles name normalization, SKILL.md content generation, and lint types.
|
|
31340
|
+
*/ /**
|
|
31341
|
+
* Severity levels for skill lint diagnostics.
|
|
31058
31342
|
*/ /**
|
|
31059
31343
|
* Normalizes a skill name to lowercase with hyphens.
|
|
31060
31344
|
* Handles spaces, mixed case, multiple spaces, and leading/trailing spaces.
|
|
@@ -31268,6 +31552,7 @@ const skillArgs = {
|
|
|
31268
31552
|
|
|
31269
31553
|
|
|
31270
31554
|
|
|
31555
|
+
|
|
31271
31556
|
const src_main = defineCommand({
|
|
31272
31557
|
meta: {
|
|
31273
31558
|
name: "lousy-agents",
|
|
@@ -31276,6 +31561,7 @@ const src_main = defineCommand({
|
|
|
31276
31561
|
},
|
|
31277
31562
|
subCommands: {
|
|
31278
31563
|
init: initCommand,
|
|
31564
|
+
lint: lintCommand,
|
|
31279
31565
|
new: newCommand,
|
|
31280
31566
|
"copilot-setup": copilotSetupCommand
|
|
31281
31567
|
}
|