@readystack/einvoice-mandate-lint 1.0.0 → 1.0.2
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 +20 -1
- package/cli.js +51 -2
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -25,7 +25,26 @@ Node 18+. The same 35 rules as the VS Code extension, from a terminal or CI.
|
|
|
25
25
|
|
|
26
26
|
An EN 16931 / Peppol integration consultant reviews an invoice mapping at about $170 an hour.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
## Use from an AI agent (MCP)
|
|
29
|
+
|
|
30
|
+
Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{ "mcpServers": { "einvoice-mandate-lint": { "command": "npx", "args": ["-y", "@readystack/einvoice-mandate-lint", "--mcp"] } } }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Tools: `check_text` and `check_file` (free) · `check_dir` (licence; the full sweep is free for 7 days). The agent gets every finding with the line number.
|
|
37
|
+
|
|
38
|
+
## Use in CI
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- name: E-Invoice Mandate Lint - EU 2026
|
|
42
|
+
run: npx -y @readystack/einvoice-mandate-lint --dir . --ci
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
(container: `docker run --rm -v "$PWD:/work" getreadystack/einvoice-mandate-lint --dir /work --ci`)
|
|
46
|
+
|
|
47
|
+
Try the full run free for 7 days — no key needed. Then one licence, 7-day refund, no questions. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
|
|
29
48
|
|
|
30
49
|
[Get a licence](https://buy.polar.sh/polar_cl_V1RBWcrS00NsYLx6EMXeji1xvbix6LF1yO9qR4DduBz)
|
|
31
50
|
|
package/cli.js
CHANGED
|
@@ -38,19 +38,64 @@ function render(rows, fmt) {
|
|
|
38
38
|
let o = ''; for (const r of rows) { o += r.file + '\n'; for (const h of r.hits) o += ' ' + String(h.line).padStart(5) + ' ' + h.sev.padEnd(5) + ' ' + h.msg + (h.fix ? '\n -> ' + h.fix : '') + '\n'; if (!r.hits.length) o += ' (no findings)\n'; }
|
|
39
39
|
return o;
|
|
40
40
|
}
|
|
41
|
+
function trialState() {
|
|
42
|
+
if (process.env.READYSTACK_NO_TRIAL) return { active: false };
|
|
43
|
+
try {
|
|
44
|
+
const p = path.join(path.dirname(lic.storePath()), S.bin + '.trial.json');
|
|
45
|
+
let t = null; try { t = JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) {}
|
|
46
|
+
if (!t || !t.until) { t = { until: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString().slice(0, 10) }; fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, JSON.stringify(t)); }
|
|
47
|
+
return { active: new Date(t.until + 'T23:59:59Z').getTime() > Date.now(), until: t.until };
|
|
48
|
+
} catch (e) { return { active: false }; }
|
|
49
|
+
}
|
|
50
|
+
function mcpServe() {
|
|
51
|
+
// s144 — MCP server over stdio (newline-delimited JSON-RPC · no dependencies). Free: check_text · check_file. Licence (7-day trial): check_dir.
|
|
52
|
+
const rl = require('readline').createInterface({ input: process.stdin });
|
|
53
|
+
const send = (o) => process.stdout.write(JSON.stringify(o) + '\n');
|
|
54
|
+
const tools = [
|
|
55
|
+
{ name: 'check_text', description: S.name + ' - run all ' + RULE_N + ' checks on a text (free)', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'file contents' }, path: { type: 'string', description: 'optional file name for context' } }, required: ['text'] } },
|
|
56
|
+
{ name: 'check_file', description: S.name + ' - run all checks on one file by path (free)', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } },
|
|
57
|
+
{ name: 'check_dir', description: S.name + ' - sweep a folder and return every finding (licence; the full run is free for 7 days)', inputSchema: { type: 'object', properties: { dir: { type: 'string' }, ext: { type: 'string', description: 'optional extension filter, e.g. .html' } }, required: ['dir'] } }
|
|
58
|
+
];
|
|
59
|
+
const result = (id, rows) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: render(rows, 'text') }], structuredContent: { tool: S.name, rules: RULE_N, files: rows } } });
|
|
60
|
+
const fail = (id, text) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text }], isError: true } });
|
|
61
|
+
rl.on('line', async (line) => {
|
|
62
|
+
let m; try { m = JSON.parse(line); } catch (e) { return; }
|
|
63
|
+
const id = m.id, method = m.method;
|
|
64
|
+
if (method === 'initialize') return send({ jsonrpc: '2.0', id, result: { protocolVersion: '2025-06-18', capabilities: { tools: {} }, serverInfo: { name: '@readystack/' + S.bin, version: '1.0.0' } } });
|
|
65
|
+
if (method === 'notifications/initialized' || method === 'ping') { if (id !== undefined) send({ jsonrpc: '2.0', id, result: {} }); return; }
|
|
66
|
+
if (method === 'tools/list') return send({ jsonrpc: '2.0', id, result: { tools } });
|
|
67
|
+
if (method === 'tools/call') {
|
|
68
|
+
const name = (m.params || {}).name, args = (m.params || {}).arguments || {};
|
|
69
|
+
try {
|
|
70
|
+
if (name === 'check_text') return result(id, [{ file: args.path || '(text)', hits: scan(String(args.text || ''), args.path || '') }]);
|
|
71
|
+
if (name === 'check_file') return result(id, [{ file: args.path, hits: scan(fs.readFileSync(args.path, 'utf8'), args.path) }]);
|
|
72
|
+
if (name === 'check_dir') {
|
|
73
|
+
const r = await lic.ensure();
|
|
74
|
+
if (!r.ok && !trialState().active) return fail(id, S.need_key + ' Get a licence ($' + S.price + ', once, 7-day refund): ' + lic.BUY_URL);
|
|
75
|
+
const files = walk(args.dir, args.ext ? [args.ext] : (S.exts || []), []);
|
|
76
|
+
return result(id, files.map((f) => { let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { return { file: f, hits: [], error: String(e.message) }; } return { file: f, hits: scan(t, f) }; }));
|
|
77
|
+
}
|
|
78
|
+
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'unknown tool ' + name } });
|
|
79
|
+
} catch (e) { return fail(id, String(e && e.message || e)); }
|
|
80
|
+
}
|
|
81
|
+
if (id !== undefined) send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'method not found: ' + method } });
|
|
82
|
+
});
|
|
83
|
+
}
|
|
41
84
|
function help() {
|
|
42
85
|
return [S.name + ' - ' + S.subtitle, '', 'Usage: ' + S.bin + ' <file> [more files] check the files you name (free, every rule)',
|
|
43
86
|
' ' + S.bin + ' --dir <folder> [--ext .html] scan a whole folder (licence)',
|
|
44
87
|
' ' + S.bin + ' ... --report csv|json|html [--out file] export a report (licence)',
|
|
45
88
|
' ' + S.bin + ' ... --ci exit 1 when an error-level finding exists (licence)',
|
|
46
89
|
' ' + S.bin + ' --license <key> store your licence key (or set READYSTACK_LICENSE)',
|
|
47
|
-
' ' + S.bin + ' --rules list the ' + RULE_N + ' rules',
|
|
90
|
+
' ' + S.bin + ' --rules list the ' + RULE_N + ' rules',
|
|
91
|
+
' ' + S.bin + ' --mcp run as an MCP server (stdio) for Claude Code / Cursor / Windsurf - free checks, folder sweep needs a licence', '',
|
|
48
92
|
'Free: ' + S.free, 'Licence ($' + S.price + ', once, 7-day refund): ' + S.paid, 'Get a licence: ' + lic.BUY_URL, ''].join('\n');
|
|
49
93
|
}
|
|
50
94
|
(async function main() {
|
|
51
95
|
try { const _feed = await lic.pullFeed(); if (_feed && Array.isArray(_feed.rules)) { if (Array.isArray(ENGINE.RULES)) for (const r of _feed.rules) ENGINE.RULES.push(r); } } catch (e) {} // ★s134 구독 피드 병합 (키 있는 손님만)
|
|
52
96
|
const a = process.argv.slice(2);
|
|
53
97
|
const get = (k) => { const i = a.indexOf(k); return i >= 0 ? a[i + 1] : null; };
|
|
98
|
+
if (a.includes('--mcp')) { mcpServe(); return; }
|
|
54
99
|
if (!a.length || a.includes('--help') || a.includes('-h')) { process.stdout.write(help()); return; }
|
|
55
100
|
if (a.includes('--rules')) { process.stdout.write((RULE_LIST.length ? RULE_LIST.map((r, i) => String(i + 1).padStart(3) + ' [' + (r.sev || 'warn') + '] ' + (r.message || r.msg || r.id || '')) : Array.from({ length: RULE_N }, (_, i) => String(i + 1).padStart(3) + ' [engine] check ' + (i + 1))).join('\n') + '\n'); return; }
|
|
56
101
|
if (a.includes('--license')) { const r = await lic.ensure(get('--license')); process.stdout.write(r.ok ? 'Licence stored: ' + lic.storePath() + '\n' : 'Licence not accepted (' + r.why + '). Get one: ' + lic.BUY_URL + '\n'); process.exit(r.ok ? 0 : 2); }
|
|
@@ -59,7 +104,11 @@ function help() {
|
|
|
59
104
|
const paid = !!(dir || fmt || ci);
|
|
60
105
|
if (paid) {
|
|
61
106
|
const r = await lic.ensure();
|
|
62
|
-
if (!r.ok) {
|
|
107
|
+
if (!r.ok) {
|
|
108
|
+
const t = trialState(); // s144 reverse trial: the full run is free for 7 days from the first paid use, then the key
|
|
109
|
+
if (t.active) process.stderr.write('Trial: the full run is free until ' + t.until + ' — after that $' + S.price + ' once (7-day refund). Get a licence: ' + lic.BUY_URL + '\n');
|
|
110
|
+
else { process.stderr.write(S.need_key + '\n set READYSTACK_LICENSE=<key> or ' + S.bin + ' --license <key>\n Get a licence ($' + S.price + ', once): ' + lic.BUY_URL + '\n'); process.exit(2); }
|
|
111
|
+
}
|
|
63
112
|
}
|
|
64
113
|
const files = dir ? walk(dir, exts, []) : a.filter((x, i) => !x.startsWith('--') && !['--dir', '--report', '--out', '--ext', '--license'].includes(a[i - 1]));
|
|
65
114
|
if (!files.length) { process.stderr.write('No files. ' + S.bin + ' --help\n'); process.exit(2); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@readystack/einvoice-mandate-lint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Lints UBL and CII invoice XML against EN 16931 and the national e-invoicing mandates that are live in 2026",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"publishConfig": {
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=18"
|
|
26
26
|
},
|
|
27
|
+
"mcpName": "io.github.jmshinhwa/einvoice-mandate-lint",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/jmshinhwa/readystack-themes.git",
|
|
31
|
+
"directory": "einvoice-mandate-lint"
|
|
32
|
+
},
|
|
27
33
|
"files": [
|
|
28
34
|
"cli.js",
|
|
29
35
|
"license.js",
|