@promptscore/cli 0.1.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/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import {
|
|
8
|
+
analyze,
|
|
9
|
+
createDefaultRegistry,
|
|
10
|
+
format,
|
|
11
|
+
ProfileLoader
|
|
12
|
+
} from "@promptscore/core";
|
|
13
|
+
var program = new Command();
|
|
14
|
+
program.name("promptscore").description("Static analysis for LLM prompts \u2014 ESLint, but for prompts.").version("0.1.1");
|
|
15
|
+
program.command("analyze").description("Analyze a prompt and print a report").argument("[file]", "path to a prompt file").option("-i, --inline <prompt>", "analyze an inline prompt string").option("-m, --model <model>", "model profile name (e.g. claude, gpt)", "_base").option("-f, --format <format>", "output format: text, json, markdown", "text").option("-r, --rules <rules>", "comma-separated rule ids to include").option("--llm", "include LLM-powered rules (requires API key)", false).option("--no-color", "disable colored output").action(async (file, opts) => {
|
|
16
|
+
try {
|
|
17
|
+
const prompt = await resolvePromptInput(file, opts.inline);
|
|
18
|
+
if (!prompt) {
|
|
19
|
+
process.stderr.write("No prompt provided. Pass a file path or use --inline.\n");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const only = opts.rules ? String(opts.rules).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
23
|
+
const report = await analyze(prompt, {
|
|
24
|
+
model: opts.model,
|
|
25
|
+
only,
|
|
26
|
+
includeLlm: Boolean(opts.llm)
|
|
27
|
+
});
|
|
28
|
+
const fmt = normalizeFormat(opts.format);
|
|
29
|
+
const output = format(report, fmt, { color: opts.color !== false });
|
|
30
|
+
process.stdout.write(output + "\n");
|
|
31
|
+
const hasErrors = report.results.some((r) => !r.passed && r.severity === "error");
|
|
32
|
+
process.exit(hasErrors ? 1 : 0);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
printError(err);
|
|
35
|
+
process.exit(2);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
program.command("rules").description("List all available rules").option("-f, --format <format>", "output format: text, json", "text").action((opts) => {
|
|
39
|
+
const registry = createDefaultRegistry();
|
|
40
|
+
const rules = registry.all();
|
|
41
|
+
if (opts.format === "json") {
|
|
42
|
+
process.stdout.write(JSON.stringify(rules.map(stripRuleCheck), null, 2) + "\n");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const rule of rules) {
|
|
46
|
+
process.stdout.write(`${rule.id} [${rule.category}, ${rule.defaultSeverity}]
|
|
47
|
+
`);
|
|
48
|
+
process.stdout.write(` ${rule.description}
|
|
49
|
+
|
|
50
|
+
`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
program.command("profiles").description("List available profiles").action(async () => {
|
|
54
|
+
try {
|
|
55
|
+
const loader = new ProfileLoader();
|
|
56
|
+
const names = await loader.list();
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
const profile = await loader.load(name);
|
|
59
|
+
process.stdout.write(`${profile.name} ${profile.displayName}
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
printError(err);
|
|
64
|
+
process.exit(2);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
async function resolvePromptInput(file, inline) {
|
|
68
|
+
if (inline) return inline;
|
|
69
|
+
if (file) {
|
|
70
|
+
if (!existsSync(file)) {
|
|
71
|
+
throw new Error(`File not found: ${file}`);
|
|
72
|
+
}
|
|
73
|
+
return readFile(file, "utf8");
|
|
74
|
+
}
|
|
75
|
+
if (!process.stdin.isTTY) {
|
|
76
|
+
return readStdin();
|
|
77
|
+
}
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
async function readStdin() {
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
let data = "";
|
|
83
|
+
process.stdin.setEncoding("utf8");
|
|
84
|
+
process.stdin.on("data", (chunk) => {
|
|
85
|
+
data += chunk;
|
|
86
|
+
});
|
|
87
|
+
process.stdin.on("end", () => resolve(data));
|
|
88
|
+
process.stdin.on("error", reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function normalizeFormat(input) {
|
|
92
|
+
if (input === "json" || input === "markdown" || input === "text") return input;
|
|
93
|
+
return "text";
|
|
94
|
+
}
|
|
95
|
+
function stripRuleCheck(rule) {
|
|
96
|
+
const { check: _check, ...rest } = rule;
|
|
97
|
+
return rest;
|
|
98
|
+
}
|
|
99
|
+
function printError(err) {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
process.stderr.write(`error: ${msg}
|
|
102
|
+
`);
|
|
103
|
+
}
|
|
104
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
105
|
+
printError(err);
|
|
106
|
+
process.exit(2);
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport {\n analyze,\n createDefaultRegistry,\n format,\n ProfileLoader,\n type ReportFormat,\n} from '@promptscore/core';\n\nconst program = new Command();\n\nprogram\n .name('promptscore')\n .description('Static analysis for LLM prompts — ESLint, but for prompts.')\n .version('0.1.1');\n\nprogram\n .command('analyze')\n .description('Analyze a prompt and print a report')\n .argument('[file]', 'path to a prompt file')\n .option('-i, --inline <prompt>', 'analyze an inline prompt string')\n .option('-m, --model <model>', 'model profile name (e.g. claude, gpt)', '_base')\n .option('-f, --format <format>', 'output format: text, json, markdown', 'text')\n .option('-r, --rules <rules>', 'comma-separated rule ids to include')\n .option('--llm', 'include LLM-powered rules (requires API key)', false)\n .option('--no-color', 'disable colored output')\n .action(async (file: string | undefined, opts) => {\n try {\n const prompt = await resolvePromptInput(file, opts.inline);\n if (!prompt) {\n process.stderr.write('No prompt provided. Pass a file path or use --inline.\\n');\n process.exit(1);\n }\n\n const only = opts.rules\n ? String(opts.rules)\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : undefined;\n\n const report = await analyze(prompt, {\n model: opts.model,\n only,\n includeLlm: Boolean(opts.llm),\n });\n\n const fmt = normalizeFormat(opts.format);\n const output = format(report, fmt, { color: opts.color !== false });\n process.stdout.write(output + '\\n');\n\n const hasErrors = report.results.some((r) => !r.passed && r.severity === 'error');\n process.exit(hasErrors ? 1 : 0);\n } catch (err) {\n printError(err);\n process.exit(2);\n }\n });\n\nprogram\n .command('rules')\n .description('List all available rules')\n .option('-f, --format <format>', 'output format: text, json', 'text')\n .action((opts) => {\n const registry = createDefaultRegistry();\n const rules = registry.all();\n if (opts.format === 'json') {\n process.stdout.write(JSON.stringify(rules.map(stripRuleCheck), null, 2) + '\\n');\n return;\n }\n for (const rule of rules) {\n process.stdout.write(`${rule.id} [${rule.category}, ${rule.defaultSeverity}]\\n`);\n process.stdout.write(` ${rule.description}\\n\\n`);\n }\n });\n\nprogram\n .command('profiles')\n .description('List available profiles')\n .action(async () => {\n try {\n const loader = new ProfileLoader();\n const names = await loader.list();\n for (const name of names) {\n const profile = await loader.load(name);\n process.stdout.write(`${profile.name} ${profile.displayName}\\n`);\n }\n } catch (err) {\n printError(err);\n process.exit(2);\n }\n });\n\nasync function resolvePromptInput(\n file: string | undefined,\n inline: string | undefined,\n): Promise<string | undefined> {\n if (inline) return inline;\n if (file) {\n if (!existsSync(file)) {\n throw new Error(`File not found: ${file}`);\n }\n return readFile(file, 'utf8');\n }\n // Try reading from stdin if piped\n if (!process.stdin.isTTY) {\n return readStdin();\n }\n return undefined;\n}\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n process.stdin.on('end', () => resolve(data));\n process.stdin.on('error', reject);\n });\n}\n\nfunction normalizeFormat(input: string): ReportFormat {\n if (input === 'json' || input === 'markdown' || input === 'text') return input;\n return 'text';\n}\n\nfunction stripRuleCheck<T extends { check: unknown }>(rule: T): Omit<T, 'check'> {\n const { check: _check, ...rest } = rule;\n return rest;\n}\n\nfunction printError(err: unknown): void {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`error: ${msg}\\n`);\n}\n\nprogram.parseAsync(process.argv).catch((err) => {\n printError(err);\n process.exit(2);\n});\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promptscore/cli",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI for PromptScore — static analysis for LLM prompts.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Riccardo Merenda",
|
|
7
|
+
"homepage": "https://promptscore.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/riccardomerenda/promptscore.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"llm",
|
|
15
|
+
"prompt",
|
|
16
|
+
"prompt-engineering",
|
|
17
|
+
"cli",
|
|
18
|
+
"linter"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"promptscore": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"lint": "eslint src",
|
|
33
|
+
"clean": "rimraf dist .turbo"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@promptscore/core": "^0.1.1",
|
|
37
|
+
"commander": "^12.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.10.0",
|
|
41
|
+
"tsup": "^8.3.5",
|
|
42
|
+
"typescript": "^5.7.2"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"provenance": true
|
|
47
|
+
}
|
|
48
|
+
}
|