@acid-cat/cli 0.9.0 → 0.10.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 +21 -8
- package/conformance/fixture.acidcat.json +167 -1
- package/dist/catalog.js +45 -15
- package/dist/conform/check.d.ts +13 -1
- package/dist/conform/check.js +73 -19
- package/dist/conform/cli.js +132 -10
- package/dist/conform/http.js +208 -4
- package/dist/conform/index.d.ts +20 -0
- package/dist/conform/index.js +11 -5
- package/dist/conform/mcp-stdio.d.ts +2 -0
- package/dist/conform/mcp-stdio.js +69 -4
- package/dist/conform/mcp.d.ts +64 -8
- package/dist/conform/mcp.js +469 -9
- package/dist/conform/poll.d.ts +13 -0
- package/dist/conform/poll.js +288 -0
- package/dist/conform/properties.js +2 -3
- package/dist/conform/serve.js +5 -3
- package/dist/conform/sse.d.ts +15 -0
- package/dist/conform/sse.js +56 -0
- package/dist/conform/webhook.d.ts +28 -0
- package/dist/conform/webhook.js +474 -0
- package/dist/conform/webmcp.js +7 -3
- package/dist/index.d.ts +1 -1
- package/dist/json/catalog.schema.json.js +27 -4
- package/dist/json/fixture.acidcat.json.js +118 -3
- package/dist/json/package.json.js +2 -3
- package/dist/json.js +1 -2
- package/dist/main.js +53 -7
- package/package.json +9 -9
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// conformance/fixture.acidcat.json
|
|
2
2
|
var fixture_acidcat_default = {
|
|
3
3
|
acidcat: "0.1",
|
|
4
4
|
title: "AcidCat conformance fixture",
|
|
5
|
-
description: "One capability for each behavior that acid-cat conform tests.",
|
|
5
|
+
description: "One capability or event for each behavior that acid-cat conform tests.",
|
|
6
6
|
targets: {
|
|
7
7
|
"api": { "bindings": ["http"] },
|
|
8
8
|
"app": { "bindings": ["typescript", "webmcp"] },
|
|
@@ -167,6 +167,28 @@ var fixture_acidcat_default = {
|
|
|
167
167
|
"type": "object"
|
|
168
168
|
}
|
|
169
169
|
},
|
|
170
|
+
"fixture.signal": {
|
|
171
|
+
"description": "Give an occurrence of fixture.watch to each subscription that watches the key. Return the number of these subscriptions.",
|
|
172
|
+
"audience": [
|
|
173
|
+
"app",
|
|
174
|
+
"mcp",
|
|
175
|
+
"cli",
|
|
176
|
+
"api"
|
|
177
|
+
],
|
|
178
|
+
"input": {
|
|
179
|
+
"properties": {
|
|
180
|
+
"key": { "type": "string" },
|
|
181
|
+
"value": { "type": "string" }
|
|
182
|
+
},
|
|
183
|
+
"required": ["key", "value"],
|
|
184
|
+
"type": "object"
|
|
185
|
+
},
|
|
186
|
+
"output": {
|
|
187
|
+
"properties": { "delivered": { "type": "integer" } },
|
|
188
|
+
"required": ["delivered"],
|
|
189
|
+
"type": "object"
|
|
190
|
+
}
|
|
191
|
+
},
|
|
170
192
|
"fixture.switch.set": {
|
|
171
193
|
"description": "Turn the switch on or off. The switch controls the availability of fixture.toggle.",
|
|
172
194
|
"audience": [
|
|
@@ -193,8 +215,101 @@ var fixture_acidcat_default = {
|
|
|
193
215
|
"properties": {},
|
|
194
216
|
"type": "object"
|
|
195
217
|
}
|
|
218
|
+
},
|
|
219
|
+
"fixture.watching": {
|
|
220
|
+
"description": "Return the keys of the subscriptions to fixture.watch that are active now.",
|
|
221
|
+
"audience": [
|
|
222
|
+
"app",
|
|
223
|
+
"mcp",
|
|
224
|
+
"cli",
|
|
225
|
+
"api"
|
|
226
|
+
],
|
|
227
|
+
"output": {
|
|
228
|
+
"properties": { "keys": {
|
|
229
|
+
"items": { "type": "string" },
|
|
230
|
+
"type": "array"
|
|
231
|
+
} },
|
|
232
|
+
"required": ["keys"],
|
|
233
|
+
"type": "object"
|
|
234
|
+
},
|
|
235
|
+
"readOnly": true
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
events: {
|
|
239
|
+
"fixture.broken": {
|
|
240
|
+
"description": "Give the occurrence n = 1, then fail with a defect.",
|
|
241
|
+
"audience": [
|
|
242
|
+
"app",
|
|
243
|
+
"mcp",
|
|
244
|
+
"cli",
|
|
245
|
+
"api"
|
|
246
|
+
],
|
|
247
|
+
"payload": {
|
|
248
|
+
"properties": { "n": { "type": "integer" } },
|
|
249
|
+
"required": ["n"],
|
|
250
|
+
"type": "object"
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
"fixture.secret": {
|
|
254
|
+
"description": "Only the app target can follow this event. It never gives an occurrence.",
|
|
255
|
+
"audience": ["app"]
|
|
256
|
+
},
|
|
257
|
+
"fixture.ticks": {
|
|
258
|
+
"description": "Give the occurrences n = 1 to count, with the eventId and the cursor String(n), then end. With a cursor c, start at c + 1. With a gap g, the occurrence n = g has truncated: true, as if the source skipped occurrences before it. The key changes nothing: it gives the subscriptions of each test their own input.",
|
|
259
|
+
"audience": [
|
|
260
|
+
"app",
|
|
261
|
+
"mcp",
|
|
262
|
+
"cli",
|
|
263
|
+
"api"
|
|
264
|
+
],
|
|
265
|
+
"input": {
|
|
266
|
+
"properties": {
|
|
267
|
+
"count": {
|
|
268
|
+
"maximum": 100,
|
|
269
|
+
"minimum": 0,
|
|
270
|
+
"type": "integer"
|
|
271
|
+
},
|
|
272
|
+
"gap": {
|
|
273
|
+
"maximum": 100,
|
|
274
|
+
"minimum": 1,
|
|
275
|
+
"type": "integer"
|
|
276
|
+
},
|
|
277
|
+
"key": { "type": "string" }
|
|
278
|
+
},
|
|
279
|
+
"required": ["count"],
|
|
280
|
+
"type": "object"
|
|
281
|
+
},
|
|
282
|
+
"payload": {
|
|
283
|
+
"properties": { "n": { "type": "integer" } },
|
|
284
|
+
"required": ["n"],
|
|
285
|
+
"type": "object"
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
"fixture.watch": {
|
|
289
|
+
"description": "Give an occurrence each time that fixture.signal gets the key. Never end. With a marker, write a file at that path when the subscription starts.",
|
|
290
|
+
"audience": [
|
|
291
|
+
"app",
|
|
292
|
+
"mcp",
|
|
293
|
+
"cli",
|
|
294
|
+
"api"
|
|
295
|
+
],
|
|
296
|
+
"input": {
|
|
297
|
+
"properties": {
|
|
298
|
+
"key": { "type": "string" },
|
|
299
|
+
"marker": { "type": "string" }
|
|
300
|
+
},
|
|
301
|
+
"required": ["key"],
|
|
302
|
+
"type": "object"
|
|
303
|
+
},
|
|
304
|
+
"payload": {
|
|
305
|
+
"properties": {
|
|
306
|
+
"key": { "type": "string" },
|
|
307
|
+
"value": { "type": "string" }
|
|
308
|
+
},
|
|
309
|
+
"required": ["key", "value"],
|
|
310
|
+
"type": "object"
|
|
311
|
+
}
|
|
196
312
|
}
|
|
197
313
|
}
|
|
198
314
|
};
|
|
199
|
-
//#endregion
|
|
200
315
|
export { fixture_acidcat_default as default };
|
package/dist/json.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/json.ts
|
|
2
2
|
/** True if the value is a JSON object: not `null`, and not an array. */
|
|
3
3
|
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
4
|
/** True if the value is an array of strings. */
|
|
@@ -17,5 +17,4 @@ async function readJson(response) {
|
|
|
17
17
|
}
|
|
18
18
|
/** True if the value is an array, and `isItem` is true for each of its items. */
|
|
19
19
|
const isArrayOf = (value, isItem) => Array.isArray(value) && value.every(isItem);
|
|
20
|
-
//#endregion
|
|
21
20
|
export { isArrayOf, isObject, isRecordOf, isStringArray, memberOf, parseJson, readJson };
|
package/dist/main.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { version } from "./json/package.json.js";
|
|
3
3
|
import { check, isCatalog, viewOf } from "./catalog.js";
|
|
4
4
|
import { DEADLINE } from "./conform/check.js";
|
|
5
5
|
import { conform } from "./conform/index.js";
|
|
6
|
-
import {
|
|
6
|
+
import { parseJson } from "./json.js";
|
|
7
7
|
import { canonicalText } from "@acid-cat/catalog";
|
|
8
8
|
import { Console, Effect, FileSystem, Option } from "effect";
|
|
9
9
|
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
10
10
|
import { Argument, Command, Flag } from "effect/cli";
|
|
11
|
-
|
|
11
|
+
// src/main.ts
|
|
12
12
|
/**
|
|
13
13
|
* `acid-cat`: check, format and view AcidCat catalogs.
|
|
14
14
|
*
|
|
@@ -61,7 +61,7 @@ const viewCommand = Command.make("view", {
|
|
|
61
61
|
yield* Console.error(`${file}: error: the file is not a catalog; run \`acid-cat check ${file}\``);
|
|
62
62
|
return yield* fail;
|
|
63
63
|
}
|
|
64
|
-
if (!(
|
|
64
|
+
if (!Object.hasOwn(catalog.targets, target)) {
|
|
65
65
|
yield* Console.error(`${file}: error: there is no target ${target}`);
|
|
66
66
|
return yield* fail;
|
|
67
67
|
}
|
|
@@ -70,21 +70,67 @@ const viewCommand = Command.make("view", {
|
|
|
70
70
|
const url = (name, description) => Flag.String(name).pipe(Flag.withDescription(description), Flag.optional);
|
|
71
71
|
const conformCommand = Command.make("conform", {
|
|
72
72
|
mcp: url("mcp", "The URL of the MCP server"),
|
|
73
|
+
mcpHandshake: Flag.Boolean("mcp-handshake").pipe(Flag.withDescription("Check the initialize handshake of MCP revision 2025-11-25 through --mcp too"), Flag.withDefault(false)),
|
|
73
74
|
mcpStdio: url("mcp-stdio", "The command that starts the MCP server through standard input and output, for example \"node fixture-mcp-stdio.ts\""),
|
|
74
75
|
http: url("http", "The base URL of the HTTP API, before /capabilities"),
|
|
75
76
|
cli: url("cli", "The command that starts the command line, for example \"node fixture-cli.ts\""),
|
|
76
77
|
webmcp: url("webmcp", "The URL of a web page that serves the app target. It runs in Chromium, through Playwright"),
|
|
78
|
+
pollBuffer: Flag.Int("poll-buffer").pipe(Flag.withDescription("The number of occurrences that a poll lease of the deployment keeps, from 1 to 97. With it, conform tests a cursor older than the buffer"), Flag.optional),
|
|
79
|
+
pollLeaseMs: Flag.Int("poll-lease-ms").pipe(Flag.withDescription("The lease time of the deployment, in milliseconds, shorter than the deadline. With it, conform tests that a lease with no poll ends"), Flag.optional),
|
|
80
|
+
webhookPrincipal: url("webhook-principal", "Test webhook delivery through --mcp. The deployment gives this principal to a request with the header Authorization: Bearer <principal>"),
|
|
81
|
+
webhookCert: url("webhook-cert", "The PEM file of the certificate of the HTTPS receiver of the webhook tests. It must be valid for 127.0.0.1, and the deployment must trust it"),
|
|
82
|
+
webhookKey: url("webhook-key", "The PEM file of the private key of --webhook-cert"),
|
|
83
|
+
webhookOtherPrincipal: url("webhook-other-principal", "A second principal that the deployment gives to a request with Authorization: Bearer <principal>, for the check of the limit"),
|
|
84
|
+
webhookMaxTtlMs: Flag.Int("webhook-max-ttl-ms").pipe(Flag.withDescription("The maximum lifetime of a webhook subscription in the deployment. The default is 3600000"), Flag.optional),
|
|
85
|
+
webhookMinTtlMs: Flag.Int("webhook-min-ttl-ms").pipe(Flag.withDescription("The minimum lifetime of a webhook subscription in the deployment, shorter than the deadline. With it, conform tests that a subscription with no renewal ends"), Flag.optional),
|
|
86
|
+
webhookMaxSubscriptions: Flag.Int("webhook-max-subscriptions").pipe(Flag.withDescription("The maximum number of webhook subscriptions of one principal in the deployment. With it, conform tests the limit"), Flag.optional),
|
|
77
87
|
maximumBytes: Flag.Int("max-bytes").pipe(Flag.withDescription("The body limit of the remote bindings"), Flag.withDefault(1048576)),
|
|
78
88
|
deadline: Flag.Int("deadline").pipe(Flag.withDescription("The time, in milliseconds, that each check has to settle. A check that does not settle in this time fails"), Flag.withDefault(DEADLINE))
|
|
79
89
|
}, Effect.fn(function* (options) {
|
|
80
90
|
const value = (option) => Option.isSome(option) ? option.value : void 0;
|
|
81
91
|
const words = (option) => value(option)?.split(/\s+/).filter((word) => word !== "");
|
|
92
|
+
const optionalInt = (option) => Option.isSome(option) ? option.value : void 0;
|
|
93
|
+
const fs = yield* FileSystem.FileSystem;
|
|
94
|
+
const given = [
|
|
95
|
+
options.webhookPrincipal,
|
|
96
|
+
options.webhookCert,
|
|
97
|
+
options.webhookKey
|
|
98
|
+
].filter(Option.isSome).length;
|
|
99
|
+
if (given !== 0 && given !== 3) {
|
|
100
|
+
yield* Console.error("Give --webhook-principal, --webhook-cert and --webhook-key together.");
|
|
101
|
+
return yield* fail;
|
|
102
|
+
}
|
|
103
|
+
const settings = [
|
|
104
|
+
Option.isSome(options.webhookOtherPrincipal),
|
|
105
|
+
Option.isSome(options.webhookMaxTtlMs),
|
|
106
|
+
Option.isSome(options.webhookMinTtlMs),
|
|
107
|
+
Option.isSome(options.webhookMaxSubscriptions)
|
|
108
|
+
];
|
|
109
|
+
if (given === 0 && settings.includes(true)) {
|
|
110
|
+
yield* Console.error("The other --webhook-* options need --webhook-principal, --webhook-cert and --webhook-key.");
|
|
111
|
+
return yield* fail;
|
|
112
|
+
}
|
|
113
|
+
const webhook = Option.isSome(options.webhookPrincipal) && Option.isSome(options.webhookCert) && Option.isSome(options.webhookKey) ? {
|
|
114
|
+
principal: options.webhookPrincipal.value,
|
|
115
|
+
cert: yield* fs.readFileString(options.webhookCert.value),
|
|
116
|
+
key: yield* fs.readFileString(options.webhookKey.value),
|
|
117
|
+
otherPrincipal: value(options.webhookOtherPrincipal),
|
|
118
|
+
maxTtlMs: optionalInt(options.webhookMaxTtlMs),
|
|
119
|
+
minTtlMs: optionalInt(options.webhookMinTtlMs),
|
|
120
|
+
maxSubscriptions: optionalInt(options.webhookMaxSubscriptions)
|
|
121
|
+
} : void 0;
|
|
82
122
|
const results = yield* Effect.promise(() => conform({
|
|
83
123
|
mcp: value(options.mcp),
|
|
124
|
+
mcpHandshake: options.mcpHandshake,
|
|
84
125
|
mcpStdio: words(options.mcpStdio),
|
|
85
126
|
http: value(options.http),
|
|
86
127
|
cli: words(options.cli),
|
|
87
128
|
webmcp: value(options.webmcp),
|
|
129
|
+
poll: {
|
|
130
|
+
bufferLimit: optionalInt(options.pollBuffer),
|
|
131
|
+
leaseMs: optionalInt(options.pollLeaseMs)
|
|
132
|
+
},
|
|
133
|
+
webhook,
|
|
88
134
|
maximumBytes: options.maximumBytes,
|
|
89
135
|
deadline: options.deadline
|
|
90
136
|
}));
|
|
@@ -92,9 +138,10 @@ const conformCommand = Command.make("conform", {
|
|
|
92
138
|
yield* Console.error("Give at least one binding: --mcp, --mcp-stdio, --http, --cli or --webmcp.");
|
|
93
139
|
return yield* fail;
|
|
94
140
|
}
|
|
95
|
-
for (const result of results) yield* Console.log(`${result.passed ? "✓" : "✗"} ${result.binding}: ${result.name} (${result.rule})${result.detail ? `\n ${result.detail}` : ""}`);
|
|
141
|
+
for (const result of results) yield* Console.log(`${result.warning === true ? "!" : result.passed ? "✓" : "✗"} ${result.binding}: ${result.name} (${result.rule})${result.detail ? `\n ${result.detail}` : ""}`);
|
|
96
142
|
const failed = results.filter((result) => !result.passed).length;
|
|
97
|
-
|
|
143
|
+
const warnings = results.filter((result) => result.warning === true).length;
|
|
144
|
+
yield* Console.log(`\n${results.length - failed} passed, ${failed} failed${warnings === 0 ? "" : `, ${warnings} with a warning`}.`);
|
|
98
145
|
if (failed > 0) return yield* fail;
|
|
99
146
|
})).pipe(Command.withDescription("Test a running implementation of the conformance fixture through its bindings."));
|
|
100
147
|
Command.make("acid-cat").pipe(Command.withDescription("Check, format and view AcidCat catalogs, and test implementations."), Command.withSubcommands([
|
|
@@ -103,5 +150,4 @@ Command.make("acid-cat").pipe(Command.withDescription("Check, format and view Ac
|
|
|
103
150
|
viewCommand,
|
|
104
151
|
conformCommand
|
|
105
152
|
])).pipe(Command.run({ version }), Effect.provide(NodeServices.layer), NodeRuntime.runMain);
|
|
106
|
-
//#endregion
|
|
107
153
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acid-cat/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "The AcidCat command line: check, format and view catalogs, and test implementations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acidcat",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@acid-cat/catalog": "0.
|
|
41
|
-
"@acid-cat/spec-ts": "0.
|
|
42
|
-
"@effect/platform-node": "4.
|
|
43
|
-
"@effect/platform-node-shared": "4.
|
|
44
|
-
"@modelcontextprotocol/client": "2.
|
|
45
|
-
"effect": "4.
|
|
40
|
+
"@acid-cat/catalog": "0.10.1",
|
|
41
|
+
"@acid-cat/spec-ts": "0.10.1",
|
|
42
|
+
"@effect/platform-node": "^4.1.3",
|
|
43
|
+
"@effect/platform-node-shared": "^4.1.3",
|
|
44
|
+
"@modelcontextprotocol/client": "2.4.0",
|
|
45
|
+
"effect": "^4.1.3",
|
|
46
46
|
"fast-check": "4.10.2"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@types/node": "26.6.
|
|
58
|
-
"playwright": "1.
|
|
57
|
+
"@types/node": "26.6.6",
|
|
58
|
+
"playwright": "1.65.0",
|
|
59
59
|
"typescript": "7.0.2"
|
|
60
60
|
}
|
|
61
61
|
}
|