@llm4ts/shell 2.27.0 → 2.28.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/kits/soap-ace/README.md +68 -0
- package/kits/soap-ace/fixtures/demo-bank-soap/RUNBOOK.md +15 -0
- package/kits/soap-ace/flows/lib/soap/Auth.js +133 -11
- package/kits/soap-ace/flows/lib/soap/Call.js +17 -5
- package/kits/soap-ace/flows/lib/soap/Discover.js +31 -3
- package/kits/soap-ace/flows/lib/soap/Envelope.js +29 -4
- package/kits/soap-ace/flows/lib/soap/Explore.js +1 -0
- package/kits/soap-ace/flows/lib/soap/Setup.js +202 -0
- package/kits/soap-ace/flows/lib/soap/Sts.js +216 -0
- package/kits/soap-ace/flows/lib/soap/Stub.js +8 -1
- package/kits/soap-ace/flows/lib/soap/Transport.js +245 -75
- package/kits/soap-ace/flows/lib/soap/Xml.js +4 -2
- package/kits/soap-ace/flows/soap-discover.js +1 -1
- package/kits/soap-ace/flows/soap-explore.js +20 -3
- package/kits/soap-ace/flows/soap-sample.js +143 -12
- package/package.json +4 -4
|
@@ -11,14 +11,20 @@
|
|
|
11
11
|
// llm4ts run soap-sample --repo . "call cercaMovimenti/happy-path"
|
|
12
12
|
// llm4ts run soap-sample --repo . "call cercaMovimenti" every request of the operation
|
|
13
13
|
// llm4ts run soap-sample --repo . -- --allow-mutating revocaBonifico "call revocaBonifico/gia-eseguito"
|
|
14
|
+
// llm4ts run soap-sample --repo . "check" the selected profile as decisions, no network
|
|
15
|
+
// llm4ts run soap-sample --repo . "trust call" capture and pin the chain the endpoint presents
|
|
16
|
+
// llm4ts run soap-sample --repo . "sts init" write the STS request template
|
|
17
|
+
// llm4ts run soap-sample --repo . -- --env uat "call all" with auth.uat.json (or LLM4TS_SOAP_ENV)
|
|
14
18
|
//
|
|
15
19
|
// Requests live in .llm4ts/soap/<service>/samples/<operation>/<name>.request.yaml
|
|
16
20
|
// (or .request.xml). A call validates the request (an invalid one is never
|
|
17
21
|
// sent), checks the confirmed class in operations.md and the environment's
|
|
18
22
|
// mutating policy, sends it with auth.json's call side (Basic/Bearer, mTLS,
|
|
19
|
-
// WS-Security), and records a masked, auth-stripped exchange
|
|
20
|
-
// Flags: --allow-mutating <operation> (exactly one), --keep-raw
|
|
21
|
-
// response under the gitignored raw/)
|
|
23
|
+
// WS-Security, an STS token), and records a masked, auth-stripped exchange
|
|
24
|
+
// beside it. Flags: --allow-mutating <operation> (exactly one), --keep-raw
|
|
25
|
+
// (unmasked response under the gitignored raw/), --env <name> (the
|
|
26
|
+
// environment's auth.<name>.json), --yes (pin a chain without asking).
|
|
27
|
+
// LLM4TS_SOAP_SERVICE picks the service
|
|
22
28
|
// when several were discovered; LLM4TS_SOAP_STUB=<dir> answers calls from
|
|
23
29
|
// <dir>/<operation>.xml instead of the network (rehearsals); LLM4TS_REASONER
|
|
24
30
|
// picks the seat that proposes scenarios (default claude).
|
|
@@ -32,29 +38,40 @@ import { nodeSecretSource } from "./lib/soap/Auth.js";
|
|
|
32
38
|
import { callOperation } from "./lib/soap/Call.js";
|
|
33
39
|
import { operationByName } from "./lib/soap/Catalog.js";
|
|
34
40
|
import { effectiveClass } from "./lib/soap/Classification.js";
|
|
35
|
-
import { loadAuthProfile, loadCatalog, loadOperationsFile, readIfPresent } from "./lib/soap/Discover.js";
|
|
41
|
+
import { loadAuthProfile, loadCatalog, loadOperationsFile, profilePath, readIfPresent } from "./lib/soap/Discover.js";
|
|
36
42
|
import { isSampleName, listRequests, loadMaskingKey, loadMaskingOverrides, readExchanges, renderRequestFile, samplePaths, seedsForService } from "./lib/soap/Samples.js";
|
|
37
43
|
import { proposeScenarios } from "./lib/soap/Scenarios.js";
|
|
44
|
+
import { checkReport, initStsTemplate, trustSide } from "./lib/soap/Setup.js";
|
|
45
|
+
import { makeTokenSource } from "./lib/soap/Sts.js";
|
|
38
46
|
import { importSoapUiProject } from "./lib/soap/SoapUiImport.js";
|
|
39
47
|
import { makeDirectoryStubTransport } from "./lib/soap/Stub.js";
|
|
40
48
|
import { makeNodeSoapTransport } from "./lib/soap/Transport.js";
|
|
41
49
|
const usage = [
|
|
42
|
-
'usage: soap-sample [--allow-mutating <operation>] [--keep-raw] "<command>"',
|
|
50
|
+
'usage: soap-sample [--allow-mutating <operation>] [--keep-raw] [--env <name>] [--yes] "<command>"',
|
|
43
51
|
" list | init <operation> [name] | propose <operation> | import <soapui.xml>",
|
|
44
|
-
" call <operation>[/<name>] | call all"
|
|
52
|
+
" call <operation>[/<name>] | call all",
|
|
53
|
+
" check | trust call|fetch|sts | sts init"
|
|
45
54
|
].join("\n");
|
|
46
|
-
const parseSampleFlags = (argv) => Effect.gen(function* () {
|
|
55
|
+
export const parseSampleFlags = (argv) => Effect.gen(function* () {
|
|
47
56
|
let allowMutating;
|
|
48
57
|
let keepRaw = false;
|
|
58
|
+
let env;
|
|
59
|
+
let yes = false;
|
|
49
60
|
const rest = [];
|
|
61
|
+
const valued = (name, index) => {
|
|
62
|
+
const argument = argv[index] ?? "";
|
|
63
|
+
return argument.includes("=") ? argument.slice(name.length + 1) : argv[index + 1];
|
|
64
|
+
};
|
|
50
65
|
for (let index = 0; index < argv.length; index++) {
|
|
51
66
|
const argument = argv[index] ?? "";
|
|
52
67
|
if (argument === "--keep-raw")
|
|
53
68
|
keepRaw = true;
|
|
69
|
+
else if (argument === "--yes")
|
|
70
|
+
yes = true;
|
|
54
71
|
else if (argument === "--allow-mutating" || argument.startsWith("--allow-mutating=")) {
|
|
55
|
-
const value =
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
const value = valued("--allow-mutating", index);
|
|
73
|
+
if (!argument.includes("="))
|
|
74
|
+
index++;
|
|
58
75
|
if (value === undefined || value.trim() === "") {
|
|
59
76
|
return yield* ScriptUsage.make({
|
|
60
77
|
message: `--allow-mutating needs an operation name\n${usage}`
|
|
@@ -62,10 +79,19 @@ const parseSampleFlags = (argv) => Effect.gen(function* () {
|
|
|
62
79
|
}
|
|
63
80
|
allowMutating = value.trim();
|
|
64
81
|
}
|
|
82
|
+
else if (argument === "--env" || argument.startsWith("--env=")) {
|
|
83
|
+
const value = valued("--env", index);
|
|
84
|
+
if (!argument.includes("="))
|
|
85
|
+
index++;
|
|
86
|
+
if (value === undefined || value.trim() === "" || value.startsWith("--")) {
|
|
87
|
+
return yield* ScriptUsage.make({ message: `--env needs an environment name\n${usage}` });
|
|
88
|
+
}
|
|
89
|
+
env = value.trim();
|
|
90
|
+
}
|
|
65
91
|
else
|
|
66
92
|
rest.push(argument);
|
|
67
93
|
}
|
|
68
|
-
return { allowMutating, keepRaw, rest };
|
|
94
|
+
return { allowMutating, keepRaw, env, yes, rest };
|
|
69
95
|
});
|
|
70
96
|
/** The service directory: LLM4TS_SOAP_SERVICE, or the only one discovered. */
|
|
71
97
|
const pickService = (workspace, requested) => Effect.gen(function* () {
|
|
@@ -91,6 +117,7 @@ const program = Effect.gen(function* () {
|
|
|
91
117
|
const input = yield* resolveFlowInput("list", flags.rest);
|
|
92
118
|
const [command = "list", ...args] = input.prompt.trim().split(/\s+/);
|
|
93
119
|
const environment = process.env;
|
|
120
|
+
const env = flags.env ?? (environment.LLM4TS_SOAP_ENV?.trim() || undefined);
|
|
94
121
|
const reasoner = command === "propose"
|
|
95
122
|
? coderFor((environment.LLM4TS_REASONER ?? "claude").trim() || "claude")
|
|
96
123
|
: undefined;
|
|
@@ -197,9 +224,107 @@ const program = Effect.gen(function* () {
|
|
|
197
224
|
yield* say(`masked ${report.masking.entries.reduce((sum, entry) => sum + entry.count, 0)} values`);
|
|
198
225
|
return;
|
|
199
226
|
}
|
|
227
|
+
case "check": {
|
|
228
|
+
const profile = yield* loadAuthProfile(workspace, service, env).pipe(Effect.catch((error) => error._tag === "AuthProfileError"
|
|
229
|
+
? say(error.message).pipe(Effect.as(undefined))
|
|
230
|
+
: Effect.fail(error)));
|
|
231
|
+
for (const line of yield* checkReport({
|
|
232
|
+
workspace,
|
|
233
|
+
service,
|
|
234
|
+
env,
|
|
235
|
+
profile,
|
|
236
|
+
environment
|
|
237
|
+
})) {
|
|
238
|
+
yield* say(line);
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
case "trust": {
|
|
243
|
+
const side = args[0];
|
|
244
|
+
if (side !== "call" && side !== "fetch" && side !== "sts") {
|
|
245
|
+
return yield* FlowAborted.make({
|
|
246
|
+
message: `trust needs a side: call, fetch or sts\n${usage}`
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const profile = yield* loadAuthProfile(workspace, service, env);
|
|
250
|
+
if (profile === undefined) {
|
|
251
|
+
return yield* FlowAborted.make({
|
|
252
|
+
message: `no profile at ${profilePath(service, env)}; write it first`
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const stub = environment.LLM4TS_SOAP_STUB?.trim();
|
|
256
|
+
if (stub !== undefined && stub !== "") {
|
|
257
|
+
return yield* FlowAborted.make({
|
|
258
|
+
message: "trust reads a real server; unset LLM4TS_SOAP_STUB"
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const fallback = catalog.endpoints[0]?.address;
|
|
262
|
+
const outcome = yield* trustSide({
|
|
263
|
+
workspace,
|
|
264
|
+
service,
|
|
265
|
+
env,
|
|
266
|
+
profile,
|
|
267
|
+
side,
|
|
268
|
+
...(fallback === undefined ? {} : { fallbackEndpoint: fallback }),
|
|
269
|
+
secrets: nodeSecretSource(environment),
|
|
270
|
+
transport: makeNodeSoapTransport(),
|
|
271
|
+
confirm: (lines) => Effect.gen(function* () {
|
|
272
|
+
for (const line of lines)
|
|
273
|
+
yield* say(line);
|
|
274
|
+
if (flags.yes)
|
|
275
|
+
return true;
|
|
276
|
+
if (process.stdin.isTTY !== true) {
|
|
277
|
+
yield* say("not a terminal: pass --yes to pin this chain");
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
return yield* terminalInteraction.ask("trust this chain? [y/N] ").pipe(Effect.map((answer) => /^(y|yes|s|si|sì)$/i.test(answer.trim())), Effect.orElseSucceed(() => false));
|
|
281
|
+
})
|
|
282
|
+
});
|
|
283
|
+
switch (outcome._tag) {
|
|
284
|
+
case "AlreadyTrusted":
|
|
285
|
+
for (const line of outcome.lines)
|
|
286
|
+
yield* say(line);
|
|
287
|
+
yield* say("already trusted by the system roots; nothing to pin");
|
|
288
|
+
return;
|
|
289
|
+
case "Declined":
|
|
290
|
+
yield* say("not pinned");
|
|
291
|
+
return;
|
|
292
|
+
case "Pinned":
|
|
293
|
+
yield* say(`pinned ${outcome.pem}${outcome.replaced ? " (replaced)" : ""}; ${outcome.profile} now sets ${side}.tls.ca to it`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
case "sts": {
|
|
299
|
+
if (args[0] !== "init") {
|
|
300
|
+
return yield* FlowAborted.make({ message: `sts takes "init"\n${usage}` });
|
|
301
|
+
}
|
|
302
|
+
const profile = yield* loadAuthProfile(workspace, service, env);
|
|
303
|
+
if (profile === undefined) {
|
|
304
|
+
return yield* FlowAborted.make({
|
|
305
|
+
message: `no profile at ${profilePath(service, env)}; write it with an "sts" section first`
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const result = yield* initStsTemplate(workspace, service, profile);
|
|
309
|
+
yield* say(result.written
|
|
310
|
+
? `wrote ${result.path}; edit it if the STS differs from WS-Trust 1.3, then: soap-sample "call …"`
|
|
311
|
+
: `${result.path} exists; edit it, or delete it and run sts init again`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
200
314
|
case "call": {
|
|
201
315
|
const target = args[0] ?? "";
|
|
202
|
-
const profile = yield* loadAuthProfile(workspace, service);
|
|
316
|
+
const profile = yield* loadAuthProfile(workspace, service, env);
|
|
317
|
+
if (profile !== undefined)
|
|
318
|
+
yield* say(`profile: ${profilePath(service, env)}`);
|
|
319
|
+
const tokenSource = profile?.sts === undefined
|
|
320
|
+
? undefined
|
|
321
|
+
: yield* makeTokenSource({
|
|
322
|
+
workspace,
|
|
323
|
+
service,
|
|
324
|
+
config: profile.sts,
|
|
325
|
+
secrets: nodeSecretSource(environment),
|
|
326
|
+
transport: makeNodeSoapTransport()
|
|
327
|
+
});
|
|
203
328
|
const stub = environment.LLM4TS_SOAP_STUB?.trim();
|
|
204
329
|
const transport = stub === undefined || stub === ""
|
|
205
330
|
? makeNodeSoapTransport()
|
|
@@ -236,6 +361,7 @@ const program = Effect.gen(function* () {
|
|
|
236
361
|
operations,
|
|
237
362
|
secrets: nodeSecretSource(environment),
|
|
238
363
|
transport,
|
|
364
|
+
...(tokenSource === undefined ? {} : { token: tokenSource }),
|
|
239
365
|
allowMutating: flags.allowMutating,
|
|
240
366
|
confirm,
|
|
241
367
|
keepRaw: flags.keepRaw
|
|
@@ -251,6 +377,11 @@ const program = Effect.gen(function* () {
|
|
|
251
377
|
: `HTTP ${exchange.response?.status ?? "?"}`;
|
|
252
378
|
yield* say(`${operation}/${name}: ${status}, ${exchange.response?.elapsedMs ?? 0} ms, ${exchange.responseIssues.length} schema findings, ${exchange.masking.entries.reduce((sum, entry) => sum + entry.count, 0)} values masked → ${outcome.success.path}`);
|
|
253
379
|
}
|
|
380
|
+
if (tokenSource !== undefined) {
|
|
381
|
+
const fetches = yield* tokenSource.fetches;
|
|
382
|
+
if (fetches > 0)
|
|
383
|
+
yield* say(`sts: ${fetches} token call(s) this run`);
|
|
384
|
+
}
|
|
254
385
|
if (failures > 0)
|
|
255
386
|
return yield* FlowAborted.make({
|
|
256
387
|
message: `${failures} of ${targets.length} calls did not complete`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.1",
|
|
4
4
|
"description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@effect/platform-node": "4.0.0",
|
|
54
54
|
"@effect/platform-node-shared": "4.0.0",
|
|
55
|
-
"@llm4ts/core": "2.
|
|
56
|
-
"@llm4ts/flow": "2.
|
|
57
|
-
"@llm4ts/runner": "2.
|
|
55
|
+
"@llm4ts/core": "2.28.1",
|
|
56
|
+
"@llm4ts/flow": "2.28.1",
|
|
57
|
+
"@llm4ts/runner": "2.28.1"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"effect": "4.0.0"
|