@lousy-agents/cli 4.0.1 → 5.0.0
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 +38 -0
- package/api/copilot-with-fastify/.vscode/mcp.json +1 -1
- package/api/copilot-with-fastify/package-lock.json +5202 -0
- package/api/copilot-with-fastify/src/app.integration.ts +45 -0
- package/api/copilot-with-fastify/src/app.test.ts +28 -0
- package/api/copilot-with-fastify/src/app.ts +11 -0
- package/api/copilot-with-fastify/src/index.ts +12 -0
- package/api/copilot-with-fastify/src/parse-port.test.ts +100 -0
- package/api/copilot-with-fastify/src/parse-port.ts +20 -0
- package/cli/copilot-with-citty/.vscode/mcp.json +1 -1
- package/dist/index.js +111 -73
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/ui/copilot-with-react/.vscode/mcp.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AddressInfo } from "node:net";
|
|
2
|
+
import type { FastifyInstance } from "fastify";
|
|
3
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
4
|
+
import { buildApp } from "./app.js";
|
|
5
|
+
|
|
6
|
+
describe("API integration", () => {
|
|
7
|
+
let app: FastifyInstance;
|
|
8
|
+
let baseUrl: string;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
app = buildApp();
|
|
12
|
+
await app.listen({ port: 0, host: "127.0.0.1" });
|
|
13
|
+
const address = app.server.address() as AddressInfo;
|
|
14
|
+
baseUrl = `http://127.0.0.1:${address.port}`;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await app.close();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("GET /health", () => {
|
|
22
|
+
it("should respond with 200 and health status", async () => {
|
|
23
|
+
const response = await fetch(`${baseUrl}/health`);
|
|
24
|
+
|
|
25
|
+
expect(response.status).toBe(200);
|
|
26
|
+
expect(await response.json()).toEqual({ status: "ok" });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should return application/json content type", async () => {
|
|
30
|
+
const response = await fetch(`${baseUrl}/health`);
|
|
31
|
+
|
|
32
|
+
expect(response.headers.get("content-type")).toContain(
|
|
33
|
+
"application/json",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("undefined routes", () => {
|
|
39
|
+
it("should return 404 for non-existent routes", async () => {
|
|
40
|
+
const response = await fetch(`${baseUrl}/nonexistent`);
|
|
41
|
+
|
|
42
|
+
expect(response.status).toBe(404);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
import { buildApp } from "./app.js";
|
|
4
|
+
|
|
5
|
+
describe("Health endpoint", () => {
|
|
6
|
+
let app: FastifyInstance;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
app = buildApp();
|
|
10
|
+
await app.ready();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
await app.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("GET /health", () => {
|
|
18
|
+
it("should return 200 with status ok", async () => {
|
|
19
|
+
const response = await app.inject({
|
|
20
|
+
method: "GET",
|
|
21
|
+
url: "/health",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(response.statusCode).toBe(200);
|
|
25
|
+
expect(response.json()).toEqual({ status: "ok" });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { buildApp } from "./app.js";
|
|
2
|
+
import { parsePort } from "./parse-port.js";
|
|
3
|
+
|
|
4
|
+
const app = buildApp();
|
|
5
|
+
const port = parsePort(process.env.PORT);
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
await app.listen({ port, host: "0.0.0.0" });
|
|
9
|
+
} catch (err) {
|
|
10
|
+
app.log.error(err);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { parsePort } from "./parse-port.js";
|
|
3
|
+
|
|
4
|
+
describe("parsePort", () => {
|
|
5
|
+
describe("when value is undefined", () => {
|
|
6
|
+
it("should return default port 3000", () => {
|
|
7
|
+
const result = parsePort(undefined);
|
|
8
|
+
|
|
9
|
+
expect(result).toBe(3000);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("when value is empty string", () => {
|
|
14
|
+
it("should return default port 3000", () => {
|
|
15
|
+
const result = parsePort("");
|
|
16
|
+
|
|
17
|
+
expect(result).toBe(3000);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("when value is a valid unprivileged port", () => {
|
|
22
|
+
it("should return the parsed port", () => {
|
|
23
|
+
const result = parsePort("8080");
|
|
24
|
+
|
|
25
|
+
expect(result).toBe(8080);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("when value is invalid", () => {
|
|
30
|
+
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
warnSpy.mockRestore();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should warn and return default for non-numeric value", () => {
|
|
41
|
+
const result = parsePort("abc");
|
|
42
|
+
|
|
43
|
+
expect(result).toBe(3000);
|
|
44
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
45
|
+
'Invalid PORT "abc", using default 3000',
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should warn and return default for privileged port", () => {
|
|
50
|
+
const result = parsePort("80");
|
|
51
|
+
|
|
52
|
+
expect(result).toBe(3000);
|
|
53
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
54
|
+
'Invalid PORT "80", using default 3000',
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should warn and return default for port above max", () => {
|
|
59
|
+
const result = parsePort("70000");
|
|
60
|
+
|
|
61
|
+
expect(result).toBe(3000);
|
|
62
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
63
|
+
'Invalid PORT "70000", using default 3000',
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should warn and return default for non-integer port", () => {
|
|
68
|
+
const result = parsePort("3000.5");
|
|
69
|
+
|
|
70
|
+
expect(result).toBe(3000);
|
|
71
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
72
|
+
'Invalid PORT "3000.5", using default 3000',
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("when value is not provided or empty", () => {
|
|
78
|
+
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
warnSpy.mockRestore();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should not warn when value is undefined", () => {
|
|
89
|
+
parsePort(undefined);
|
|
90
|
+
|
|
91
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should not warn when value is empty string", () => {
|
|
95
|
+
parsePort("");
|
|
96
|
+
|
|
97
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const defaultPort = 3000;
|
|
2
|
+
const minUnprivilegedPort = 1024;
|
|
3
|
+
const maxPort = 65535;
|
|
4
|
+
|
|
5
|
+
export function parsePort(value: string | undefined): number {
|
|
6
|
+
if (value === undefined || value === "") {
|
|
7
|
+
return defaultPort;
|
|
8
|
+
}
|
|
9
|
+
const parsed = Number(value);
|
|
10
|
+
if (
|
|
11
|
+
!Number.isInteger(parsed) ||
|
|
12
|
+
parsed < minUnprivilegedPort ||
|
|
13
|
+
parsed > maxPort
|
|
14
|
+
) {
|
|
15
|
+
// biome-ignore lint/suspicious/noConsole: user-facing warning for invalid PORT
|
|
16
|
+
console.warn(`Invalid PORT "${value}", using default ${defaultPort}`);
|
|
17
|
+
return defaultPort;
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -35198,7 +35198,38 @@ const CLI_TEMPLATE_DIR = (0,external_node_path_.join)(PROJECT_ROOT, "cli", "copi
|
|
|
35198
35198
|
path: "vitest.setup.ts",
|
|
35199
35199
|
content: readRestApiTemplateFile("vitest.setup.ts")
|
|
35200
35200
|
},
|
|
35201
|
-
...buildCommonNodes(readRestApiTemplateFile)
|
|
35201
|
+
...buildCommonNodes(readRestApiTemplateFile),
|
|
35202
|
+
// Source code
|
|
35203
|
+
{
|
|
35204
|
+
type: "file",
|
|
35205
|
+
path: "src/app.ts",
|
|
35206
|
+
content: readRestApiTemplateFile("src/app.ts")
|
|
35207
|
+
},
|
|
35208
|
+
{
|
|
35209
|
+
type: "file",
|
|
35210
|
+
path: "src/app.test.ts",
|
|
35211
|
+
content: readRestApiTemplateFile("src/app.test.ts")
|
|
35212
|
+
},
|
|
35213
|
+
{
|
|
35214
|
+
type: "file",
|
|
35215
|
+
path: "src/app.integration.ts",
|
|
35216
|
+
content: readRestApiTemplateFile("src/app.integration.ts")
|
|
35217
|
+
},
|
|
35218
|
+
{
|
|
35219
|
+
type: "file",
|
|
35220
|
+
path: "src/parse-port.ts",
|
|
35221
|
+
content: readRestApiTemplateFile("src/parse-port.ts")
|
|
35222
|
+
},
|
|
35223
|
+
{
|
|
35224
|
+
type: "file",
|
|
35225
|
+
path: "src/parse-port.test.ts",
|
|
35226
|
+
content: readRestApiTemplateFile("src/parse-port.test.ts")
|
|
35227
|
+
},
|
|
35228
|
+
{
|
|
35229
|
+
type: "file",
|
|
35230
|
+
path: "src/index.ts",
|
|
35231
|
+
content: readRestApiTemplateFile("src/index.ts")
|
|
35232
|
+
}
|
|
35202
35233
|
]
|
|
35203
35234
|
};
|
|
35204
35235
|
return cachedRestApiStructure;
|
|
@@ -55779,6 +55810,84 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
|
55779
55810
|
}
|
|
55780
55811
|
}
|
|
55781
55812
|
|
|
55813
|
+
;// CONCATENATED MODULE: ../core/src/use-cases/apply-severity-filter.ts
|
|
55814
|
+
/**
|
|
55815
|
+
* Shared severity filtering for lint outputs.
|
|
55816
|
+
* Applies rule severity configuration to diagnostics, filtering out "off" rules,
|
|
55817
|
+
* remapping "warn" → "warning", and recalculating summary counts.
|
|
55818
|
+
*/ /** Maps a lint target to its config key */ const TARGET_TO_CONFIG_KEY = {
|
|
55819
|
+
skill: "skills",
|
|
55820
|
+
agent: "agents",
|
|
55821
|
+
instruction: "instructions"
|
|
55822
|
+
};
|
|
55823
|
+
/**
|
|
55824
|
+
* Maps config severity to diagnostic severity.
|
|
55825
|
+
* "warn" → "warning", "error" → "error", "off" → null (drop).
|
|
55826
|
+
*/ function apply_severity_filter_mapSeverity(configSeverity) {
|
|
55827
|
+
if (configSeverity === "off") {
|
|
55828
|
+
return null;
|
|
55829
|
+
}
|
|
55830
|
+
if (configSeverity === "warn") {
|
|
55831
|
+
return "warning";
|
|
55832
|
+
}
|
|
55833
|
+
return configSeverity;
|
|
55834
|
+
}
|
|
55835
|
+
/**
|
|
55836
|
+
* Filters instruction suggestions based on rule severity configuration.
|
|
55837
|
+
* Drops suggestions whose corresponding rule is "off".
|
|
55838
|
+
* Suggestions without a ruleId pass through unchanged.
|
|
55839
|
+
*/ function filterInstructionSuggestions(suggestions, rules) {
|
|
55840
|
+
return suggestions.filter((suggestion)=>{
|
|
55841
|
+
if (!suggestion.ruleId) {
|
|
55842
|
+
return true;
|
|
55843
|
+
}
|
|
55844
|
+
return rules[suggestion.ruleId] !== "off";
|
|
55845
|
+
});
|
|
55846
|
+
}
|
|
55847
|
+
/**
|
|
55848
|
+
* Applies severity filtering to a LintOutput based on rule configuration.
|
|
55849
|
+
* Drops diagnostics for "off" rules, remaps severity for "warn"/"error" rules.
|
|
55850
|
+
* Diagnostics without a ruleId pass through unchanged.
|
|
55851
|
+
* For instruction targets, also filters qualityResult.suggestions.
|
|
55852
|
+
*/ function applySeverityFilter(output, rulesConfig) {
|
|
55853
|
+
const configKey = TARGET_TO_CONFIG_KEY[output.target];
|
|
55854
|
+
const targetRules = rulesConfig[configKey];
|
|
55855
|
+
const filteredDiagnostics = [];
|
|
55856
|
+
for (const diagnostic of output.diagnostics){
|
|
55857
|
+
const configuredSeverity = diagnostic.ruleId ? targetRules[diagnostic.ruleId] : undefined;
|
|
55858
|
+
if (!configuredSeverity) {
|
|
55859
|
+
filteredDiagnostics.push(diagnostic);
|
|
55860
|
+
continue;
|
|
55861
|
+
}
|
|
55862
|
+
const mappedSeverity = apply_severity_filter_mapSeverity(configuredSeverity);
|
|
55863
|
+
if (mappedSeverity === null) {
|
|
55864
|
+
continue;
|
|
55865
|
+
}
|
|
55866
|
+
filteredDiagnostics.push({
|
|
55867
|
+
...diagnostic,
|
|
55868
|
+
severity: mappedSeverity
|
|
55869
|
+
});
|
|
55870
|
+
}
|
|
55871
|
+
const totalErrors = filteredDiagnostics.filter((d)=>d.severity === "error").length;
|
|
55872
|
+
const totalWarnings = filteredDiagnostics.filter((d)=>d.severity === "warning").length;
|
|
55873
|
+
const totalInfos = filteredDiagnostics.filter((d)=>d.severity === "info").length;
|
|
55874
|
+
const filteredQualityResult = output.qualityResult && configKey === "instructions" ? {
|
|
55875
|
+
...output.qualityResult,
|
|
55876
|
+
suggestions: filterInstructionSuggestions(output.qualityResult.suggestions, targetRules)
|
|
55877
|
+
} : output.qualityResult;
|
|
55878
|
+
return {
|
|
55879
|
+
...output,
|
|
55880
|
+
diagnostics: filteredDiagnostics,
|
|
55881
|
+
qualityResult: filteredQualityResult,
|
|
55882
|
+
summary: {
|
|
55883
|
+
...output.summary,
|
|
55884
|
+
totalErrors,
|
|
55885
|
+
totalWarnings,
|
|
55886
|
+
totalInfos
|
|
55887
|
+
}
|
|
55888
|
+
};
|
|
55889
|
+
}
|
|
55890
|
+
|
|
55782
55891
|
;// CONCATENATED MODULE: ../core/src/use-cases/lint-agent-frontmatter.ts
|
|
55783
55892
|
/**
|
|
55784
55893
|
* Use case for linting GitHub Copilot custom agent frontmatter.
|
|
@@ -56080,6 +56189,7 @@ function hasFrontmatterDelimiters(content) {
|
|
|
56080
56189
|
|
|
56081
56190
|
|
|
56082
56191
|
|
|
56192
|
+
|
|
56083
56193
|
/** Schema for validating target directory */ const TargetDirSchema = schemas_string().min(1, "Target directory is required");
|
|
56084
56194
|
/**
|
|
56085
56195
|
* Validates the target directory.
|
|
@@ -56244,78 +56354,6 @@ function hasFrontmatterDelimiters(content) {
|
|
|
56244
56354
|
consola.warn(suggestion.message);
|
|
56245
56355
|
}
|
|
56246
56356
|
}
|
|
56247
|
-
/** Maps a lint target to its config key */ const TARGET_TO_CONFIG_KEY = {
|
|
56248
|
-
skill: "skills",
|
|
56249
|
-
agent: "agents",
|
|
56250
|
-
instruction: "instructions"
|
|
56251
|
-
};
|
|
56252
|
-
/**
|
|
56253
|
-
* Maps config-facing severity to diagnostic-facing severity.
|
|
56254
|
-
* "warn" → "warning", "error" → "error", "off" → null (drop).
|
|
56255
|
-
*/ function lint_mapSeverity(configSeverity) {
|
|
56256
|
-
if (configSeverity === "off") {
|
|
56257
|
-
return null;
|
|
56258
|
-
}
|
|
56259
|
-
if (configSeverity === "warn") {
|
|
56260
|
-
return "warning";
|
|
56261
|
-
}
|
|
56262
|
-
return configSeverity;
|
|
56263
|
-
}
|
|
56264
|
-
/**
|
|
56265
|
-
* Filters instruction suggestions based on rule severity configuration.
|
|
56266
|
-
* Drops suggestions whose corresponding rule is "off".
|
|
56267
|
-
* Suggestions without a ruleId pass through unchanged.
|
|
56268
|
-
*/ function filterInstructionSuggestions(suggestions, rules) {
|
|
56269
|
-
return suggestions.filter((suggestion)=>{
|
|
56270
|
-
if (!suggestion.ruleId) {
|
|
56271
|
-
return true;
|
|
56272
|
-
}
|
|
56273
|
-
return rules[suggestion.ruleId] !== "off";
|
|
56274
|
-
});
|
|
56275
|
-
}
|
|
56276
|
-
/**
|
|
56277
|
-
* Applies severity filtering to a LintOutput based on rule configuration.
|
|
56278
|
-
* Drops diagnostics for "off" rules, remaps severity for "warn"/"error" rules.
|
|
56279
|
-
* Diagnostics without a ruleId pass through unchanged.
|
|
56280
|
-
* For instruction targets, also filters qualityResult.suggestions.
|
|
56281
|
-
*/ function applySeverityFilter(output, rulesConfig) {
|
|
56282
|
-
const configKey = TARGET_TO_CONFIG_KEY[output.target];
|
|
56283
|
-
const targetRules = rulesConfig[configKey];
|
|
56284
|
-
const filteredDiagnostics = [];
|
|
56285
|
-
for (const diagnostic of output.diagnostics){
|
|
56286
|
-
const configuredSeverity = diagnostic.ruleId ? targetRules[diagnostic.ruleId] : undefined;
|
|
56287
|
-
if (!configuredSeverity) {
|
|
56288
|
-
filteredDiagnostics.push(diagnostic);
|
|
56289
|
-
continue;
|
|
56290
|
-
}
|
|
56291
|
-
const mappedSeverity = lint_mapSeverity(configuredSeverity);
|
|
56292
|
-
if (mappedSeverity === null) {
|
|
56293
|
-
continue;
|
|
56294
|
-
}
|
|
56295
|
-
filteredDiagnostics.push({
|
|
56296
|
-
...diagnostic,
|
|
56297
|
-
severity: mappedSeverity
|
|
56298
|
-
});
|
|
56299
|
-
}
|
|
56300
|
-
const totalErrors = filteredDiagnostics.filter((d)=>d.severity === "error").length;
|
|
56301
|
-
const totalWarnings = filteredDiagnostics.filter((d)=>d.severity === "warning").length;
|
|
56302
|
-
const totalInfos = filteredDiagnostics.filter((d)=>d.severity === "info").length;
|
|
56303
|
-
const filteredQualityResult = output.qualityResult && configKey === "instructions" ? {
|
|
56304
|
-
...output.qualityResult,
|
|
56305
|
-
suggestions: filterInstructionSuggestions(output.qualityResult.suggestions, targetRules)
|
|
56306
|
-
} : output.qualityResult;
|
|
56307
|
-
return {
|
|
56308
|
-
...output,
|
|
56309
|
-
diagnostics: filteredDiagnostics,
|
|
56310
|
-
qualityResult: filteredQualityResult,
|
|
56311
|
-
summary: {
|
|
56312
|
-
...output.summary,
|
|
56313
|
-
totalErrors,
|
|
56314
|
-
totalWarnings,
|
|
56315
|
-
totalInfos
|
|
56316
|
-
}
|
|
56317
|
-
};
|
|
56318
|
-
}
|
|
56319
56357
|
/**
|
|
56320
56358
|
* The `lint` command for validating agent skills, custom agents, and instruction files.
|
|
56321
56359
|
*/ const lintCommand = defineCommand({
|