@invokable/conformance 0.1.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/LICENSE +21 -0
- package/README.md +57 -0
- package/bin/invokable-test.mjs +4 -0
- package/dist/checks.d.ts +25 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +231 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +80 -0
- package/dist/cli.js.map +1 -0
- package/dist/exec.d.ts +27 -0
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +39 -0
- package/dist/exec.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +13 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +74 -0
- package/dist/report.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Invokable
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @invokable/conformance
|
|
2
|
+
|
|
3
|
+
Checks that a CLI honours the [invokable](https://github.com/beinvokable/invokable)
|
|
4
|
+
agent contract.
|
|
5
|
+
|
|
6
|
+
```console
|
|
7
|
+
$ npx invokable-test node bin/mytool.mjs
|
|
8
|
+
|
|
9
|
+
✓ `--version --json` returns a valid ok envelope
|
|
10
|
+
✓ `--help --json` returns a command manifest
|
|
11
|
+
✓ An unknown command exits 2 with an error envelope
|
|
12
|
+
✓ A bare invocation does not exit 0
|
|
13
|
+
✓ `doctor --json` reports auth and config state
|
|
14
|
+
✓ Every exit code is reserved or in 30-99
|
|
15
|
+
✓ `--json` puts exactly one JSON document on stdout
|
|
16
|
+
✓ No credential-shaped strings on stdout
|
|
17
|
+
✓ Errors carry a `remediation`
|
|
18
|
+
|
|
19
|
+
9 passed
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Exit 0 when everything passes, 1 otherwise. It works on **any** CLI, not just one
|
|
23
|
+
built with `@invokable/core`: the contract is the product, and the library is one
|
|
24
|
+
way of satisfying it.
|
|
25
|
+
|
|
26
|
+
## What it will not do
|
|
27
|
+
|
|
28
|
+
**It never runs a command from your manifest.** Only `--help`, `--version`,
|
|
29
|
+
`doctor` and deliberately invalid invocations are executed. A runner that
|
|
30
|
+
executed every listed command could deploy something or spend the user's money,
|
|
31
|
+
and tools where that matters are exactly the ones this suite exists for.
|
|
32
|
+
|
|
33
|
+
To include a command you know is safe:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
invokable-test node bin/mytool.mjs --safe-command "projects list"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**It never reproduces a credential.** The secret-leak check reports the shape and
|
|
40
|
+
the prefix — `a token-shaped string: prefix "mtl_", 36 characters` — never the
|
|
41
|
+
material, because a report gets pasted into issues and CI logs.
|
|
42
|
+
|
|
43
|
+
## Options
|
|
44
|
+
|
|
45
|
+
| Option | Effect |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `--json` | Emit the report as JSON on stdout instead of text on stderr. |
|
|
48
|
+
| `--safe-command <cmd>` | Also run this command. Repeatable. |
|
|
49
|
+
| `--timeout <ms>` | Per-invocation timeout. Default 30000. |
|
|
50
|
+
|
|
51
|
+
## In CI
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
- run: npx invokable-test node bin/mytool.mjs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A failing check prints why an agent depends on it and a command to reproduce it.
|
package/dist/checks.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ExecOptions } from './exec.js';
|
|
2
|
+
export type CheckStatus = 'pass' | 'fail' | 'warn' | 'skip';
|
|
3
|
+
export interface CheckResult {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
status: CheckStatus;
|
|
7
|
+
/** What went wrong, or what was observed. */
|
|
8
|
+
detail?: string;
|
|
9
|
+
/** Why an agent depends on this. Shown for failures. */
|
|
10
|
+
rationale: string;
|
|
11
|
+
invocations: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface CheckContext extends ExecOptions {
|
|
14
|
+
/** Commands the runner may execute beyond the always-safe set. */
|
|
15
|
+
extraSafeCommands: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Every check runs the tool for real. Only commands that cannot change anything
|
|
19
|
+
* are executed: `--help`, `--version`, `doctor`, and deliberately invalid
|
|
20
|
+
* invocations. A conformance runner that executed arbitrary commands from the
|
|
21
|
+
* manifest could deploy something or spend the user's money — the tools this
|
|
22
|
+
* suite exists to validate are exactly the ones where that matters.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runChecks(ctx: CheckContext): Promise<CheckResult[]>;
|
|
25
|
+
//# sourceMappingURL=checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,WAAW,EAAmB,MAAM,WAAW,CAAC;AAEtE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,kEAAkE;IAClE,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAkFD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAyLzE"}
|
package/dist/checks.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { EXIT, EXIT_NAME, CUSTOM_EXIT_RANGE } from '@invokable/core';
|
|
2
|
+
import { invoke } from './exec.js';
|
|
3
|
+
const VALID_EXIT_CODES = new Set(Object.values(EXIT));
|
|
4
|
+
function isValidExitCode(code) {
|
|
5
|
+
if (code === null)
|
|
6
|
+
return false;
|
|
7
|
+
if (VALID_EXIT_CODES.has(code))
|
|
8
|
+
return true;
|
|
9
|
+
return code >= CUSTOM_EXIT_RANGE.min && code <= CUSTOM_EXIT_RANGE.max;
|
|
10
|
+
}
|
|
11
|
+
/** Exactly one JSON document, nothing else. */
|
|
12
|
+
function parseSingleDocument(stdout) {
|
|
13
|
+
const trimmed = stdout.trim();
|
|
14
|
+
if (trimmed === '')
|
|
15
|
+
return { ok: false, reason: 'stdout was empty' };
|
|
16
|
+
try {
|
|
17
|
+
return { ok: true, value: JSON.parse(trimmed) };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
const lines = trimmed.split('\n').filter((l) => l.trim() !== '');
|
|
21
|
+
if (lines.length > 1) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
reason: `stdout had ${lines.length} lines and is not one JSON document. ` +
|
|
25
|
+
`First line: ${JSON.stringify(lines[0].slice(0, 120))}`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return { ok: false, reason: `stdout is not valid JSON: ${JSON.stringify(trimmed.slice(0, 200))}` };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isRecord(v) {
|
|
32
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Strings that look like credentials, which must never reach stdout.
|
|
36
|
+
*
|
|
37
|
+
* The finding describes the shape and never quotes the material. A report that
|
|
38
|
+
* reprinted the secret would copy it into CI logs and pasted transcripts — the
|
|
39
|
+
* exact spread this check exists to prevent.
|
|
40
|
+
*/
|
|
41
|
+
function looksLikeSecret(text) {
|
|
42
|
+
const patterns = [
|
|
43
|
+
[
|
|
44
|
+
/\b([a-z]{2,10})_[A-Za-z0-9]{24,}\b/,
|
|
45
|
+
(m) => `a token-shaped string: prefix "${m.split('_')[0]}_", ${m.length} characters`,
|
|
46
|
+
],
|
|
47
|
+
[/\bBearer\s+[A-Za-z0-9._-]{16,}/i, (m) => `a Bearer credential, ${m.length} characters`],
|
|
48
|
+
[/-----BEGIN [A-Z ]*PRIVATE KEY-----/, () => 'a PEM private key block'],
|
|
49
|
+
];
|
|
50
|
+
for (const [re, describe] of patterns) {
|
|
51
|
+
const m = re.exec(text);
|
|
52
|
+
if (m)
|
|
53
|
+
return describe(m[0]);
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function result(id, title, rationale, status, invocations, detail) {
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
title,
|
|
61
|
+
status,
|
|
62
|
+
rationale,
|
|
63
|
+
invocations: invocations.map((i) => i.commandLine),
|
|
64
|
+
...(detail !== undefined ? { detail } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Every check runs the tool for real. Only commands that cannot change anything
|
|
69
|
+
* are executed: `--help`, `--version`, `doctor`, and deliberately invalid
|
|
70
|
+
* invocations. A conformance runner that executed arbitrary commands from the
|
|
71
|
+
* manifest could deploy something or spend the user's money — the tools this
|
|
72
|
+
* suite exists to validate are exactly the ones where that matters.
|
|
73
|
+
*/
|
|
74
|
+
export async function runChecks(ctx) {
|
|
75
|
+
const results = [];
|
|
76
|
+
const runs = [];
|
|
77
|
+
const run = async (args) => {
|
|
78
|
+
const inv = await invoke(ctx, args);
|
|
79
|
+
runs.push(inv);
|
|
80
|
+
return inv;
|
|
81
|
+
};
|
|
82
|
+
// ---- 1. --version --------------------------------------------------------
|
|
83
|
+
const version = await run(['--version', '--json']);
|
|
84
|
+
{
|
|
85
|
+
const parsed = parseSingleDocument(version.stdout);
|
|
86
|
+
if (version.timedOut) {
|
|
87
|
+
results.push(result('version', '`--version --json` responds', RATIONALE.responds, 'fail', [version], 'The process timed out.'));
|
|
88
|
+
}
|
|
89
|
+
else if (!parsed.ok) {
|
|
90
|
+
results.push(result('version', '`--version --json` emits one JSON document', RATIONALE.singleDoc, 'fail', [version], parsed.reason));
|
|
91
|
+
}
|
|
92
|
+
else if (!isRecord(parsed.value) || parsed.value['status'] !== 'ok') {
|
|
93
|
+
results.push(result('version', '`--version --json` returns status ok', RATIONALE.envelope, 'fail', [version], `status was ${JSON.stringify(parsed.value?.['status'])}`));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
results.push(result('version', '`--version --json` returns a valid ok envelope', RATIONALE.envelope, 'pass', [version]));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ---- 2. --help --json is a machine-readable manifest ---------------------
|
|
100
|
+
const help = await run(['--help', '--json']);
|
|
101
|
+
{
|
|
102
|
+
const parsed = parseSingleDocument(help.stdout);
|
|
103
|
+
const data = isRecord(parsed.value) ? parsed.value['data'] : undefined;
|
|
104
|
+
const commands = isRecord(data) ? data['commands'] : undefined;
|
|
105
|
+
if (!parsed.ok) {
|
|
106
|
+
results.push(result('help', '`--help --json` emits one JSON document', RATIONALE.singleDoc, 'fail', [help], parsed.reason));
|
|
107
|
+
}
|
|
108
|
+
else if (!Array.isArray(commands) || commands.length === 0) {
|
|
109
|
+
results.push(result('help', '`--help --json` lists commands', RATIONALE.manifest, 'fail', [help], 'data.commands was missing or empty. An agent cannot discover the surface.'));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
results.push(result('help', '`--help --json` returns a command manifest', RATIONALE.manifest, 'pass', [help], `${commands.length} commands`));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ---- 3. An unknown command is a usage error, not a crash ----------------
|
|
116
|
+
const unknown = await run(['definitely-not-a-real-command-xyz', '--json']);
|
|
117
|
+
{
|
|
118
|
+
const parsed = parseSingleDocument(unknown.stdout);
|
|
119
|
+
const env = isRecord(parsed.value) ? parsed.value : undefined;
|
|
120
|
+
const problems = [];
|
|
121
|
+
if (unknown.exitCode !== EXIT.usage) {
|
|
122
|
+
problems.push(`exit was ${unknown.exitCode}, expected ${EXIT.usage} (usage)`);
|
|
123
|
+
}
|
|
124
|
+
if (!parsed.ok)
|
|
125
|
+
problems.push(parsed.reason ?? 'stdout was not one JSON document');
|
|
126
|
+
else {
|
|
127
|
+
if (env?.['status'] !== 'error')
|
|
128
|
+
problems.push(`status was ${JSON.stringify(env?.['status'])}, expected "error"`);
|
|
129
|
+
if (typeof env?.['code'] !== 'string')
|
|
130
|
+
problems.push('error envelope has no string `code`');
|
|
131
|
+
if (typeof env?.['message'] !== 'string')
|
|
132
|
+
problems.push('error envelope has no string `message`');
|
|
133
|
+
if (typeof env?.['retryable'] !== 'boolean')
|
|
134
|
+
problems.push('error envelope has no boolean `retryable`');
|
|
135
|
+
}
|
|
136
|
+
results.push(problems.length
|
|
137
|
+
? result('unknown-command', 'An unknown command exits 2 with an error envelope', RATIONALE.usage, 'fail', [unknown], problems.join('; '))
|
|
138
|
+
: result('unknown-command', 'An unknown command exits 2 with an error envelope', RATIONALE.usage, 'pass', [unknown]));
|
|
139
|
+
}
|
|
140
|
+
// ---- 4. A bare invocation does not look like success ---------------------
|
|
141
|
+
const bare = await run(['--json']);
|
|
142
|
+
{
|
|
143
|
+
results.push(bare.exitCode === EXIT.ok
|
|
144
|
+
? result('bare', 'A bare invocation does not exit 0', RATIONALE.bare, 'fail', [bare], 'Exit 0 with no command tells an agent the work succeeded when nothing ran.')
|
|
145
|
+
: result('bare', 'A bare invocation does not exit 0', RATIONALE.bare, 'pass', [bare], `exit ${bare.exitCode}`));
|
|
146
|
+
}
|
|
147
|
+
// ---- 5. doctor -----------------------------------------------------------
|
|
148
|
+
const doctor = await run(['doctor', '--json']);
|
|
149
|
+
{
|
|
150
|
+
const parsed = parseSingleDocument(doctor.stdout);
|
|
151
|
+
const data = isRecord(parsed.value) ? parsed.value['data'] : undefined;
|
|
152
|
+
if (doctor.timedOut) {
|
|
153
|
+
results.push(result('doctor', '`doctor --json` responds', RATIONALE.doctor, 'fail', [doctor], 'The process timed out.'));
|
|
154
|
+
}
|
|
155
|
+
else if (!parsed.ok) {
|
|
156
|
+
results.push(result('doctor', '`doctor --json` emits one JSON document', RATIONALE.singleDoc, 'fail', [doctor], parsed.reason));
|
|
157
|
+
}
|
|
158
|
+
else if (!isRecord(data) || !isRecord(data['auth']) || !isRecord(data['config'])) {
|
|
159
|
+
results.push(result('doctor', '`doctor --json` reports auth and config', RATIONALE.doctor, 'fail', [doctor], 'Expected data.auth and data.config. An agent uses these to tell "not logged in" from "service down".'));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
results.push(result('doctor', '`doctor --json` reports auth and config state', RATIONALE.doctor, 'pass', [doctor]));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ---- 6. Optional user-nominated safe commands ----------------------------
|
|
166
|
+
for (const command of ctx.extraSafeCommands) {
|
|
167
|
+
const args = command.split(/\s+/).filter(Boolean);
|
|
168
|
+
const inv = await run([...args, '--json']);
|
|
169
|
+
const parsed = parseSingleDocument(inv.stdout);
|
|
170
|
+
results.push(parsed.ok
|
|
171
|
+
? result(`safe:${command}`, `\`${command} --json\` emits one JSON document`, RATIONALE.singleDoc, 'pass', [inv])
|
|
172
|
+
: result(`safe:${command}`, `\`${command} --json\` emits one JSON document`, RATIONALE.singleDoc, 'fail', [inv], parsed.reason));
|
|
173
|
+
}
|
|
174
|
+
// ---- 7. Cross-cutting: exit codes ---------------------------------------
|
|
175
|
+
{
|
|
176
|
+
const bad = runs.filter((r) => !isValidExitCode(r.exitCode));
|
|
177
|
+
results.push(bad.length
|
|
178
|
+
? result('exit-codes', 'Every exit code is reserved or in 30-99', RATIONALE.exitCodes, 'fail', bad, bad.map((r) => `\`${r.commandLine}\` exited ${r.exitCode ?? 'by signal/timeout'}`).join('; '))
|
|
179
|
+
: result('exit-codes', 'Every exit code is reserved or in 30-99', RATIONALE.exitCodes, 'pass', [], [...new Set(runs.map((r) => r.exitCode))]
|
|
180
|
+
.sort((a, b) => (a ?? 0) - (b ?? 0))
|
|
181
|
+
.map((c) => `${c}${c !== null && EXIT_NAME[c] ? ` (${EXIT_NAME[c]})` : ''}`)
|
|
182
|
+
.join(', ')));
|
|
183
|
+
}
|
|
184
|
+
// ---- 8. Cross-cutting: --json stdout purity ------------------------------
|
|
185
|
+
{
|
|
186
|
+
const impure = runs.filter((r) => {
|
|
187
|
+
if (!r.argv.includes('--json') || r.timedOut)
|
|
188
|
+
return false;
|
|
189
|
+
return !parseSingleDocument(r.stdout).ok;
|
|
190
|
+
});
|
|
191
|
+
results.push(impure.length
|
|
192
|
+
? result('json-purity', '`--json` puts exactly one JSON document on stdout', RATIONALE.singleDoc, 'fail', impure, impure.map((r) => `\`${r.commandLine}\`: ${parseSingleDocument(r.stdout).reason}`).join('; '))
|
|
193
|
+
: result('json-purity', '`--json` puts exactly one JSON document on stdout', RATIONALE.singleDoc, 'pass', []));
|
|
194
|
+
}
|
|
195
|
+
// ---- 9. Cross-cutting: no credentials on stdout -------------------------
|
|
196
|
+
{
|
|
197
|
+
const leaks = runs
|
|
198
|
+
.map((r) => ({ run: r, found: looksLikeSecret(r.stdout) }))
|
|
199
|
+
.filter((x) => x.found !== null);
|
|
200
|
+
results.push(leaks.length
|
|
201
|
+
? result('no-secrets', 'No credential-shaped strings on stdout', RATIONALE.secrets, 'fail', leaks.map((l) => l.run), leaks.map((l) => `\`${l.run.commandLine}\` printed ${l.found}`).join('; '))
|
|
202
|
+
: result('no-secrets', 'No credential-shaped strings on stdout', RATIONALE.secrets, 'pass', []));
|
|
203
|
+
}
|
|
204
|
+
// ---- 10. Cross-cutting: errors carry remediation -------------------------
|
|
205
|
+
{
|
|
206
|
+
const errors = runs
|
|
207
|
+
.filter((r) => r.argv.includes('--json'))
|
|
208
|
+
.map((r) => parseSingleDocument(r.stdout).value)
|
|
209
|
+
.filter((v) => isRecord(v) && v['status'] === 'error');
|
|
210
|
+
const missing = errors.filter((e) => typeof e['remediation'] !== 'string');
|
|
211
|
+
results.push(errors.length === 0
|
|
212
|
+
? result('remediation', 'Errors carry a `remediation`', RATIONALE.remediation, 'skip', [], 'No error envelopes were produced.')
|
|
213
|
+
: missing.length
|
|
214
|
+
? result('remediation', 'Errors carry a `remediation`', RATIONALE.remediation, 'warn', [], `${missing.length} of ${errors.length} error envelopes had no remediation.`)
|
|
215
|
+
: result('remediation', 'Errors carry a `remediation`', RATIONALE.remediation, 'pass', []));
|
|
216
|
+
}
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
const RATIONALE = {
|
|
220
|
+
responds: 'An agent that gets no response cannot tell a hang from a failure.',
|
|
221
|
+
singleDoc: 'The agent parses stdout as one JSON document. A stray log line makes the parse fail, and the agent reports a broken tool rather than the real result.',
|
|
222
|
+
envelope: 'The agent reads `status` before anything else.',
|
|
223
|
+
manifest: '`--help --json` is how an agent discovers commands and options instead of guessing them.',
|
|
224
|
+
usage: 'A mistyped command must be distinguishable from a real failure, or the agent retries something that can never work.',
|
|
225
|
+
bare: 'Exit 0 with no command run tells the agent the task is done.',
|
|
226
|
+
doctor: '`doctor` is how an agent tells "not logged in" from "service unreachable" — the two failures that look identical from outside.',
|
|
227
|
+
exitCodes: 'The agent decides what to do next from the exit code alone. An unreserved code means it has no rule to apply.',
|
|
228
|
+
secrets: 'stdout is captured, logged and often echoed back into a transcript.',
|
|
229
|
+
remediation: 'Without it the agent has to invent the next command.',
|
|
230
|
+
};
|
|
231
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface MainOptions {
|
|
2
|
+
argv: readonly string[];
|
|
3
|
+
stdout?: (s: string) => void;
|
|
4
|
+
stderr?: (s: string) => void;
|
|
5
|
+
}
|
|
6
|
+
/** Returns the process exit code. */
|
|
7
|
+
export declare function conformanceMain(options: MainOptions): Promise<number>;
|
|
8
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAsBD,qCAAqC;AACrC,wBAAsB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D3E"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { runChecks } from './checks.js';
|
|
2
|
+
import { renderReport, summarise } from './report.js';
|
|
3
|
+
const USAGE = `Usage: invokable-test <command> [args...] [options]
|
|
4
|
+
|
|
5
|
+
Checks that a CLI honours the invokable agent contract.
|
|
6
|
+
|
|
7
|
+
invokable-test ./bin/mytool.mjs
|
|
8
|
+
invokable-test node ./bin/mytool.mjs
|
|
9
|
+
invokable-test npx mytool
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--json Emit the report as JSON on stdout.
|
|
13
|
+
--safe-command <cmd> Additionally run this command (repeatable).
|
|
14
|
+
Only pass commands that change nothing.
|
|
15
|
+
--timeout <ms> Per-invocation timeout. Default 30000.
|
|
16
|
+
-h, --help Show this help.
|
|
17
|
+
|
|
18
|
+
Only commands that cannot change anything are run: --help, --version, doctor,
|
|
19
|
+
and deliberately invalid invocations. Commands from the manifest are never
|
|
20
|
+
executed on their own, because one of them may spend the user's money.
|
|
21
|
+
`;
|
|
22
|
+
/** Returns the process exit code. */
|
|
23
|
+
export async function conformanceMain(options) {
|
|
24
|
+
const stdout = options.stdout ?? ((s) => process.stdout.write(s));
|
|
25
|
+
const stderr = options.stderr ?? ((s) => process.stderr.write(s));
|
|
26
|
+
const argv = [...options.argv];
|
|
27
|
+
let json = false;
|
|
28
|
+
let timeoutMs = 30_000;
|
|
29
|
+
const safeCommands = [];
|
|
30
|
+
const target = [];
|
|
31
|
+
for (let i = 0; i < argv.length; i++) {
|
|
32
|
+
const token = argv[i];
|
|
33
|
+
if (token === '--json') {
|
|
34
|
+
json = true;
|
|
35
|
+
}
|
|
36
|
+
else if (token === '--help' || token === '-h') {
|
|
37
|
+
stderr(USAGE);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
else if (token === '--safe-command') {
|
|
41
|
+
const value = argv[++i];
|
|
42
|
+
if (value === undefined) {
|
|
43
|
+
stderr('error: --safe-command needs a value\n');
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
safeCommands.push(value);
|
|
47
|
+
}
|
|
48
|
+
else if (token === '--timeout') {
|
|
49
|
+
const value = Number(argv[++i]);
|
|
50
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
51
|
+
stderr('error: --timeout must be a positive number of milliseconds\n');
|
|
52
|
+
return 2;
|
|
53
|
+
}
|
|
54
|
+
timeoutMs = value;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
target.push(token);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const command = target[0];
|
|
61
|
+
if (command === undefined) {
|
|
62
|
+
stderr(USAGE);
|
|
63
|
+
return 2;
|
|
64
|
+
}
|
|
65
|
+
const checks = await runChecks({
|
|
66
|
+
command,
|
|
67
|
+
baseArgs: target.slice(1),
|
|
68
|
+
timeoutMs,
|
|
69
|
+
extraSafeCommands: safeCommands,
|
|
70
|
+
});
|
|
71
|
+
const report = summarise(target.join(' '), checks);
|
|
72
|
+
if (json) {
|
|
73
|
+
stdout(JSON.stringify(report) + '\n');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
stderr(renderReport(report));
|
|
77
|
+
}
|
|
78
|
+
return report.ok ? 0 : 1;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/exec.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface Invocation {
|
|
2
|
+
/** Args passed after the base command. */
|
|
3
|
+
argv: readonly string[];
|
|
4
|
+
/** The full command line, for reproducing the run. */
|
|
5
|
+
commandLine: string;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
/** Null when the process was killed by a signal or timed out. */
|
|
9
|
+
exitCode: number | null;
|
|
10
|
+
timedOut: boolean;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ExecOptions {
|
|
14
|
+
command: string;
|
|
15
|
+
baseArgs: readonly string[];
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
cwd?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Runs the tool under test as a real subprocess.
|
|
22
|
+
*
|
|
23
|
+
* Everything is measured on actual file descriptors: stdout purity is only
|
|
24
|
+
* meaningful at the process boundary, which is where the agent reads it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function invoke(opts: ExecOptions, args: readonly string[]): Promise<Invocation>;
|
|
27
|
+
//# sourceMappingURL=exec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAoCtF"}
|
package/dist/exec.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the tool under test as a real subprocess.
|
|
4
|
+
*
|
|
5
|
+
* Everything is measured on actual file descriptors: stdout purity is only
|
|
6
|
+
* meaningful at the process boundary, which is where the agent reads it.
|
|
7
|
+
*/
|
|
8
|
+
export function invoke(opts, args) {
|
|
9
|
+
const argv = [...opts.baseArgs, ...args];
|
|
10
|
+
const started = Date.now();
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
execFile(opts.command, argv, {
|
|
13
|
+
timeout: opts.timeoutMs ?? 30_000,
|
|
14
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
15
|
+
...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
|
|
16
|
+
env: {
|
|
17
|
+
...process.env,
|
|
18
|
+
...opts.env,
|
|
19
|
+
// Keep the tool from picking up ambient credentials or an interactive
|
|
20
|
+
// path: a conformance run must be reproducible on any machine.
|
|
21
|
+
NO_COLOR: '1',
|
|
22
|
+
CI: '1',
|
|
23
|
+
},
|
|
24
|
+
}, (error, stdout, stderr) => {
|
|
25
|
+
const err = error;
|
|
26
|
+
const timedOut = Boolean(err?.killed) || err?.code === 'ETIMEDOUT';
|
|
27
|
+
resolve({
|
|
28
|
+
argv,
|
|
29
|
+
commandLine: [opts.command, ...argv].join(' '),
|
|
30
|
+
stdout: String(stdout),
|
|
31
|
+
stderr: String(stderr),
|
|
32
|
+
exitCode: timedOut ? null : typeof err?.code === 'number' ? err.code : 0,
|
|
33
|
+
timedOut,
|
|
34
|
+
durationMs: Date.now() - started,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=exec.js.map
|
package/dist/exec.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAuB9C;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,IAAiB,EAAE,IAAuB;IAC/D,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CACN,IAAI,CAAC,OAAO,EACZ,IAAI,EACJ;YACE,OAAO,EAAE,IAAI,CAAC,SAAS,IAAI,MAAM;YACjC,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;YAC3B,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,GAAG,EAAE;gBACH,GAAG,OAAO,CAAC,GAAG;gBACd,GAAG,IAAI,CAAC,GAAG;gBACX,sEAAsE;gBACtE,+DAA+D;gBAC/D,QAAQ,EAAE,GAAG;gBACb,EAAE,EAAE,GAAG;aACR;SACF,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,MAAM,GAAG,GAAG,KAAsE,CAAC;YACnF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,GAAG,EAAE,IAAI,KAAK,WAAW,CAAC;YACnE,OAAO,CAAC;gBACN,IAAI;gBACJ,WAAW,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;gBACtB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;gBACtB,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACxE,QAAQ;gBACR,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;aACjC,CAAC,CAAC;QACL,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { runChecks } from './checks.js';
|
|
2
|
+
export type { CheckResult, CheckStatus, CheckContext } from './checks.js';
|
|
3
|
+
export { summarise, renderReport } from './report.js';
|
|
4
|
+
export type { Report } from './report.js';
|
|
5
|
+
export { invoke } from './exec.js';
|
|
6
|
+
export type { Invocation, ExecOptions } from './exec.js';
|
|
7
|
+
export { conformanceMain } from './cli.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE1E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGnC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CheckResult } from './checks.js';
|
|
2
|
+
export interface Report {
|
|
3
|
+
command: string;
|
|
4
|
+
passed: number;
|
|
5
|
+
failed: number;
|
|
6
|
+
warnings: number;
|
|
7
|
+
skipped: number;
|
|
8
|
+
ok: boolean;
|
|
9
|
+
checks: CheckResult[];
|
|
10
|
+
}
|
|
11
|
+
export declare function summarise(command: string, checks: CheckResult[]): Report;
|
|
12
|
+
export declare function renderReport(report: Report): string;
|
|
13
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;AAE5D,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAYxE;AASD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgCnD"}
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export function summarise(command, checks) {
|
|
2
|
+
const count = (s) => checks.filter((c) => c.status === s).length;
|
|
3
|
+
const failed = count('fail');
|
|
4
|
+
return {
|
|
5
|
+
command,
|
|
6
|
+
passed: count('pass'),
|
|
7
|
+
failed,
|
|
8
|
+
warnings: count('warn'),
|
|
9
|
+
skipped: count('skip'),
|
|
10
|
+
ok: failed === 0,
|
|
11
|
+
checks,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const GLYPH = {
|
|
15
|
+
pass: '✓',
|
|
16
|
+
fail: '✗',
|
|
17
|
+
warn: '!',
|
|
18
|
+
skip: '–',
|
|
19
|
+
};
|
|
20
|
+
export function renderReport(report) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push('');
|
|
23
|
+
lines.push(`invokable conformance — ${report.command}`);
|
|
24
|
+
lines.push('');
|
|
25
|
+
for (const check of report.checks) {
|
|
26
|
+
lines.push(` ${GLYPH[check.status]} ${check.title}`);
|
|
27
|
+
if (check.detail && check.status !== 'pass') {
|
|
28
|
+
for (const line of wrap(check.detail, 70))
|
|
29
|
+
lines.push(` ${line}`);
|
|
30
|
+
}
|
|
31
|
+
else if (check.detail) {
|
|
32
|
+
lines.push(` ${check.detail}`);
|
|
33
|
+
}
|
|
34
|
+
// The rationale is shown only on failure: it is the part that tells someone
|
|
35
|
+
// why the contract cares, which is what they need when deciding to fix it.
|
|
36
|
+
if (check.status === 'fail' || check.status === 'warn') {
|
|
37
|
+
for (const line of wrap(`Why: ${check.rationale}`, 70))
|
|
38
|
+
lines.push(` ${line}`);
|
|
39
|
+
if (check.invocations.length) {
|
|
40
|
+
lines.push(` Reproduce: ${check.invocations[0]}`);
|
|
41
|
+
}
|
|
42
|
+
lines.push('');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
lines.push('');
|
|
46
|
+
const parts = [`${report.passed} passed`];
|
|
47
|
+
if (report.failed)
|
|
48
|
+
parts.push(`${report.failed} failed`);
|
|
49
|
+
if (report.warnings)
|
|
50
|
+
parts.push(`${report.warnings} warning${report.warnings === 1 ? '' : 's'}`);
|
|
51
|
+
if (report.skipped)
|
|
52
|
+
parts.push(`${report.skipped} skipped`);
|
|
53
|
+
lines.push(` ${parts.join(', ')}`);
|
|
54
|
+
lines.push('');
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
function wrap(text, width) {
|
|
58
|
+
const out = [];
|
|
59
|
+
let line = '';
|
|
60
|
+
for (const word of text.split(/\s+/)) {
|
|
61
|
+
if (line === '')
|
|
62
|
+
line = word;
|
|
63
|
+
else if ((line + ' ' + word).length <= width)
|
|
64
|
+
line += ' ' + word;
|
|
65
|
+
else {
|
|
66
|
+
out.push(line);
|
|
67
|
+
line = word;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (line)
|
|
71
|
+
out.push(line);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@invokable/conformance",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Checks that a CLI honours the invokable agent contract.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"invokable-test": "./bin/invokable-test.mjs"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"bin",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@invokable/core": "^0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/beinvokable/invokable.git",
|
|
32
|
+
"directory": "packages/conformance"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -b",
|
|
39
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
40
|
+
}
|
|
41
|
+
}
|