@aid-framework/cli 0.1.60 → 0.1.62
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/.tsbuildinfo +1 -1
- package/dist/commands/dev.d.ts +53 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +235 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/guard.d.ts +55 -0
- package/dist/commands/guard.d.ts.map +1 -0
- package/dist/commands/guard.js +673 -0
- package/dist/commands/guard.js.map +1 -0
- package/dist/diagnostics.d.ts +12 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +18 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +35 -0
- package/dist/main.js.map +1 -1
- package/dist/python.d.ts +10 -1
- package/dist/python.d.ts.map +1 -1
- package/dist/python.js +12 -3
- package/dist/python.js.map +1 -1
- package/dist/targets.d.ts +40 -0
- package/dist/targets.d.ts.map +1 -1
- package/dist/targets.js +25 -1
- package/dist/targets.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid dev` -- compile the spec, reconcile the tree, and run the application.
|
|
3
|
+
*
|
|
4
|
+
* **Why generation is part of it.** The command's whole promise is "the spec you just
|
|
5
|
+
* edited is the thing serving requests", and a `dev` that started a server against a tree
|
|
6
|
+
* compiled from an older spec would answer from code the developer is no longer looking
|
|
7
|
+
* at. So it runs the same generator the `gen` command does, and if generation fails it
|
|
8
|
+
* does not start the server: a tree that could not be reconciled is not a tree to serve.
|
|
9
|
+
*
|
|
10
|
+
* **Why it starts the target's own server instead of one of its own.** The Python target
|
|
11
|
+
* emits routers; the application owns `app/main.py` and decides what to mount and how to
|
|
12
|
+
* run it (the generator says so in its own comments). A dev server implemented here would
|
|
13
|
+
* be a second, competing way to run the same app, and it would drift. So this command
|
|
14
|
+
* spawns the module the application already declares, which is also what a person types by
|
|
15
|
+
* hand: `python -m uvicorn app.main:app`. `-m uvicorn` rather than the `uvicorn` binary,
|
|
16
|
+
* because the probe that proved the interpreter can import the runtime also proves it can
|
|
17
|
+
* import uvicorn; a `uvicorn` script on `PATH` proves neither.
|
|
18
|
+
*
|
|
19
|
+
* **Why a target may refuse.** `ts-vercel` is a deployment target: it emits route handlers
|
|
20
|
+
* that Next mounts, and ships no server of its own to start. Inventing one here would be
|
|
21
|
+
* exactly the competing dev server the previous paragraph rules out. The registry declares
|
|
22
|
+
* that per target, so this command refuses with a reason rather than guessing.
|
|
23
|
+
*
|
|
24
|
+
* **Why the start banner is printed by the command.** Every other command returns text and
|
|
25
|
+
* lets the dispatcher print it. This one hands the terminal to a child for an unbounded
|
|
26
|
+
* time, so a banner returned after the server exits would arrive after the log it was
|
|
27
|
+
* meant to introduce. The banner and the generation summary are therefore written as they
|
|
28
|
+
* happen, and the returned text is only the farewell.
|
|
29
|
+
*
|
|
30
|
+
* **Why there is no `--json`.** A JSON document is a verdict about a finished run. `dev`
|
|
31
|
+
* produces a running process and a stream of server output, so the honest answers are "a
|
|
32
|
+
* document that lies about when the run ended" or "a document interrupted by logs". It
|
|
33
|
+
* refuses `--json` as a usage error instead of emitting something that cannot be parsed.
|
|
34
|
+
*
|
|
35
|
+
* **What is not watched.** uvicorn's `--reload` reloads on changes to the code it imported,
|
|
36
|
+
* and that is left on by default. The *spec* is not watched: regenerating it needs the
|
|
37
|
+
* generator to run again, and doing that from a synchronous command would mean the
|
|
38
|
+
* dispatcher grew an event loop for one command. Restart `aid dev` after editing the spec;
|
|
39
|
+
* the generation step makes that restart cheap and the tree correct either way.
|
|
40
|
+
*/
|
|
41
|
+
import type { CliContext } from '../context.js';
|
|
42
|
+
import type { CommandOutcome } from '../output.js';
|
|
43
|
+
export interface DevOptions {
|
|
44
|
+
readonly spec: string | undefined;
|
|
45
|
+
readonly target: string | undefined;
|
|
46
|
+
readonly port: string | undefined;
|
|
47
|
+
/** Skip regeneration and serve the tree as it is. */
|
|
48
|
+
readonly noGen: boolean;
|
|
49
|
+
/** Turn off uvicorn's own reloader. */
|
|
50
|
+
readonly noReload: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function dev(context: CliContext, options: DevOptions): CommandOutcome;
|
|
53
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAOnD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAaD,wBAAgB,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,cAAc,CA6L5E"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid dev` -- compile the spec, reconcile the tree, and run the application.
|
|
3
|
+
*
|
|
4
|
+
* **Why generation is part of it.** The command's whole promise is "the spec you just
|
|
5
|
+
* edited is the thing serving requests", and a `dev` that started a server against a tree
|
|
6
|
+
* compiled from an older spec would answer from code the developer is no longer looking
|
|
7
|
+
* at. So it runs the same generator the `gen` command does, and if generation fails it
|
|
8
|
+
* does not start the server: a tree that could not be reconciled is not a tree to serve.
|
|
9
|
+
*
|
|
10
|
+
* **Why it starts the target's own server instead of one of its own.** The Python target
|
|
11
|
+
* emits routers; the application owns `app/main.py` and decides what to mount and how to
|
|
12
|
+
* run it (the generator says so in its own comments). A dev server implemented here would
|
|
13
|
+
* be a second, competing way to run the same app, and it would drift. So this command
|
|
14
|
+
* spawns the module the application already declares, which is also what a person types by
|
|
15
|
+
* hand: `python -m uvicorn app.main:app`. `-m uvicorn` rather than the `uvicorn` binary,
|
|
16
|
+
* because the probe that proved the interpreter can import the runtime also proves it can
|
|
17
|
+
* import uvicorn; a `uvicorn` script on `PATH` proves neither.
|
|
18
|
+
*
|
|
19
|
+
* **Why a target may refuse.** `ts-vercel` is a deployment target: it emits route handlers
|
|
20
|
+
* that Next mounts, and ships no server of its own to start. Inventing one here would be
|
|
21
|
+
* exactly the competing dev server the previous paragraph rules out. The registry declares
|
|
22
|
+
* that per target, so this command refuses with a reason rather than guessing.
|
|
23
|
+
*
|
|
24
|
+
* **Why the start banner is printed by the command.** Every other command returns text and
|
|
25
|
+
* lets the dispatcher print it. This one hands the terminal to a child for an unbounded
|
|
26
|
+
* time, so a banner returned after the server exits would arrive after the log it was
|
|
27
|
+
* meant to introduce. The banner and the generation summary are therefore written as they
|
|
28
|
+
* happen, and the returned text is only the farewell.
|
|
29
|
+
*
|
|
30
|
+
* **Why there is no `--json`.** A JSON document is a verdict about a finished run. `dev`
|
|
31
|
+
* produces a running process and a stream of server output, so the honest answers are "a
|
|
32
|
+
* document that lies about when the run ended" or "a document interrupted by logs". It
|
|
33
|
+
* refuses `--json` as a usage error instead of emitting something that cannot be parsed.
|
|
34
|
+
*
|
|
35
|
+
* **What is not watched.** uvicorn's `--reload` reloads on changes to the code it imported,
|
|
36
|
+
* and that is left on by default. The *spec* is not watched: regenerating it needs the
|
|
37
|
+
* generator to run again, and doing that from a synchronous command would mean the
|
|
38
|
+
* dispatcher grew an event loop for one command. Restart `aid dev` after editing the spec;
|
|
39
|
+
* the generation step makes that restart cheap and the tree correct either way.
|
|
40
|
+
*/
|
|
41
|
+
import { existsSync } from 'node:fs';
|
|
42
|
+
import { sortDiagnostics } from '@aid-framework/spec';
|
|
43
|
+
import { CLI_DIAGNOSTIC_CODES, cliError } from '../diagnostics.js';
|
|
44
|
+
import { EXIT_FAILURE, EXIT_OK, EXIT_USAGE } from '../exit.js';
|
|
45
|
+
import { outcome } from '../output.js';
|
|
46
|
+
import { absolutePath } from '../paths.js';
|
|
47
|
+
import { DEV_PROBE, discoverInterpreter, INTERPRETERS, interpreterCommand } from '../python.js';
|
|
48
|
+
import { resolveTarget, unknownTargetDiagnostic } from '../targets.js';
|
|
49
|
+
import { generate } from './gen.js';
|
|
50
|
+
/** uvicorn's own default, so `aid dev` and `uvicorn app.main:app` agree. */
|
|
51
|
+
const DEFAULT_PORT = 8000;
|
|
52
|
+
/** The port, or `undefined` when the flag could not have meant one. */
|
|
53
|
+
function parsePort(raw) {
|
|
54
|
+
if (raw === undefined)
|
|
55
|
+
return DEFAULT_PORT;
|
|
56
|
+
if (!/^\d+$/.test(raw))
|
|
57
|
+
return undefined;
|
|
58
|
+
const port = Number(raw);
|
|
59
|
+
return port >= 1 && port <= 65_535 ? port : undefined;
|
|
60
|
+
}
|
|
61
|
+
export function dev(context, options) {
|
|
62
|
+
const command = 'dev';
|
|
63
|
+
const target = resolveTarget(options.target);
|
|
64
|
+
if (target === undefined) {
|
|
65
|
+
return outcome({
|
|
66
|
+
command,
|
|
67
|
+
exitCode: EXIT_USAGE,
|
|
68
|
+
diagnostics: [unknownTargetDiagnostic(options.target ?? '')],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Before the project is touched: a port that is not a port is a mistake in the
|
|
72
|
+
// invocation, and reporting a spec problem instead would bury the actual mistake.
|
|
73
|
+
const port = parsePort(options.port);
|
|
74
|
+
if (port === undefined) {
|
|
75
|
+
return outcome({
|
|
76
|
+
command,
|
|
77
|
+
exitCode: EXIT_USAGE,
|
|
78
|
+
diagnostics: [
|
|
79
|
+
cliError({
|
|
80
|
+
code: CLI_DIAGNOSTIC_CODES.devPortInvalid,
|
|
81
|
+
message: `\`--port\` needs an integer between 1 and 65535, got "${options.port}".`,
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const specPath = options.spec ?? target.specPath;
|
|
87
|
+
const fields = { target: target.name, spec: specPath, port };
|
|
88
|
+
const surface = target.dev;
|
|
89
|
+
if (surface.kind === 'none') {
|
|
90
|
+
return outcome({
|
|
91
|
+
command,
|
|
92
|
+
// Exit 2, not 1: no server was started and no project was judged, so nothing here
|
|
93
|
+
// is a verdict about the tree.
|
|
94
|
+
exitCode: EXIT_USAGE,
|
|
95
|
+
fields,
|
|
96
|
+
diagnostics: [
|
|
97
|
+
cliError({
|
|
98
|
+
code: CLI_DIAGNOSTIC_CODES.runnerUnsupported,
|
|
99
|
+
message: `\`aid dev\` cannot start ${target.name}: ${surface.reason}.`,
|
|
100
|
+
hint: 'Run the application with the server its platform provides; `aid gen` still compiles and reconciles the tree.',
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const diagnostics = [];
|
|
106
|
+
if (!options.noGen) {
|
|
107
|
+
const generated = generate(context, {
|
|
108
|
+
spec: options.spec,
|
|
109
|
+
target: options.target,
|
|
110
|
+
dryRun: false,
|
|
111
|
+
force: false,
|
|
112
|
+
check: false,
|
|
113
|
+
});
|
|
114
|
+
if (generated.exitCode !== EXIT_OK) {
|
|
115
|
+
// The generator's own summary and diagnostics are the answer: it is the same
|
|
116
|
+
// failure `aid gen` would have reported, and renaming it here would hide that.
|
|
117
|
+
return outcome({
|
|
118
|
+
command,
|
|
119
|
+
exitCode: generated.exitCode,
|
|
120
|
+
fields,
|
|
121
|
+
diagnostics: generated.diagnostics,
|
|
122
|
+
text: generated.text,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (generated.text !== '')
|
|
126
|
+
context.stdout(`${generated.text}\n`);
|
|
127
|
+
diagnostics.push(...generated.diagnostics);
|
|
128
|
+
}
|
|
129
|
+
// The entry point is the application's own file, not a generated one, so its absence
|
|
130
|
+
// means the project was never set up rather than never regenerated. Named explicitly
|
|
131
|
+
// because "app/main.py is missing" is the one thing the reader needs to know.
|
|
132
|
+
const entry = absolutePath(context.cwd, surface.entry);
|
|
133
|
+
if (!existsSync(entry)) {
|
|
134
|
+
return outcome({
|
|
135
|
+
command,
|
|
136
|
+
exitCode: EXIT_FAILURE,
|
|
137
|
+
fields,
|
|
138
|
+
diagnostics: [
|
|
139
|
+
cliError({
|
|
140
|
+
code: CLI_DIAGNOSTIC_CODES.devEntryMissing,
|
|
141
|
+
message: `No ASGI entry point at ${surface.entry}.`,
|
|
142
|
+
path: [surface.entry],
|
|
143
|
+
hint: 'Run `aid init` for a starter entry point, or run `aid dev` from the directory that contains the generated app.',
|
|
144
|
+
}),
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const interpreter = discoverInterpreter(context, DEV_PROBE);
|
|
149
|
+
if (interpreter === undefined) {
|
|
150
|
+
return outcome({
|
|
151
|
+
command,
|
|
152
|
+
// Exit 2: the project was not judged, because nothing could start it.
|
|
153
|
+
exitCode: EXIT_USAGE,
|
|
154
|
+
fields,
|
|
155
|
+
diagnostics: [
|
|
156
|
+
cliError({
|
|
157
|
+
code: CLI_DIAGNOSTIC_CODES.devUvicornMissing,
|
|
158
|
+
message: `No interpreter can import both aid_runtime and uvicorn (tried ${INTERPRETERS.join(', ')}).`,
|
|
159
|
+
hint: 'Install the runtime and uvicorn into the interpreter you run with, or run `python -m uvicorn` yourself.',
|
|
160
|
+
}),
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const argv = [
|
|
165
|
+
...interpreterCommand(interpreter),
|
|
166
|
+
'-m',
|
|
167
|
+
'uvicorn',
|
|
168
|
+
surface.module,
|
|
169
|
+
'--host',
|
|
170
|
+
'127.0.0.1',
|
|
171
|
+
'--port',
|
|
172
|
+
String(port),
|
|
173
|
+
...(options.noReload ? [] : ['--reload']),
|
|
174
|
+
];
|
|
175
|
+
// Bound to loopback on purpose: a dev server is for the machine it runs on, and a
|
|
176
|
+
// `0.0.0.0` default quietly exposes a half-built app to the network.
|
|
177
|
+
context.stdout([
|
|
178
|
+
`${target.name}: serving ${surface.module} on http://127.0.0.1:${port}${options.noReload ? '' : ' (reload on)'}`,
|
|
179
|
+
` ${argv.join(' ')}`,
|
|
180
|
+
'Press Ctrl-C to stop.',
|
|
181
|
+
'',
|
|
182
|
+
].join('\n'));
|
|
183
|
+
const run = context.spawn(argv[0] ?? interpreter, argv.slice(1), {
|
|
184
|
+
cwd: context.cwd,
|
|
185
|
+
stdio: 'inherit',
|
|
186
|
+
env: context.env,
|
|
187
|
+
});
|
|
188
|
+
if (run.error !== undefined) {
|
|
189
|
+
return outcome({
|
|
190
|
+
command,
|
|
191
|
+
exitCode: EXIT_USAGE,
|
|
192
|
+
fields: { ...fields, interpreter },
|
|
193
|
+
diagnostics: [
|
|
194
|
+
cliError({
|
|
195
|
+
code: CLI_DIAGNOSTIC_CODES.runnerFailed,
|
|
196
|
+
message: `Could not start ${interpreter} to run "${surface.module}": ${run.error.message}`,
|
|
197
|
+
}),
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// A null status means the child was ended by a signal, which for a foreground server is
|
|
202
|
+
// how Ctrl-C arrives. Reporting that as a failure would make stopping the dev server
|
|
203
|
+
// look like the server broke.
|
|
204
|
+
if (run.status === null) {
|
|
205
|
+
return outcome({
|
|
206
|
+
command,
|
|
207
|
+
exitCode: EXIT_OK,
|
|
208
|
+
fields: { ...fields, interpreter, stopped: true },
|
|
209
|
+
diagnostics: sortDiagnostics(diagnostics),
|
|
210
|
+
text: 'server stopped.',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (run.status !== 0) {
|
|
214
|
+
return outcome({
|
|
215
|
+
command,
|
|
216
|
+
exitCode: EXIT_FAILURE,
|
|
217
|
+
fields: { ...fields, interpreter, status: run.status },
|
|
218
|
+
diagnostics: sortDiagnostics([
|
|
219
|
+
...diagnostics,
|
|
220
|
+
cliError({
|
|
221
|
+
code: CLI_DIAGNOSTIC_CODES.runnerFailed,
|
|
222
|
+
message: `The server exited with status ${run.status}.`,
|
|
223
|
+
}),
|
|
224
|
+
]),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return outcome({
|
|
228
|
+
command,
|
|
229
|
+
exitCode: EXIT_OK,
|
|
230
|
+
fields: { ...fields, interpreter },
|
|
231
|
+
diagnostics: sortDiagnostics(diagnostics),
|
|
232
|
+
text: 'server stopped.',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid guard` -- measure an app's guardrails against a red-team corpus.
|
|
3
|
+
*
|
|
4
|
+
* **What is measured, and why it is measured here.** The runtime's `GuardrailEngine` is
|
|
5
|
+
* the thing that decides whether text is a jailbreak, a secret, or an injection, and the
|
|
6
|
+
* generated `generated/guardrails.py` is the table of declarations the routes enforce.
|
|
7
|
+
* This command joins the two and reports, per fixture, whether the table the app actually
|
|
8
|
+
* shipped caught it. Detection is not reimplemented: a second implementation here would
|
|
9
|
+
* drift from the one the routes run, and the number it produced would describe the CLI
|
|
10
|
+
* rather than the application.
|
|
11
|
+
*
|
|
12
|
+
* **Why the corpus is an input.** The framework's own corpus lives in
|
|
13
|
+
* `runtimes/python/tests/redteam/fixtures.json` and is not shipped with any package.
|
|
14
|
+
* Copying it into the CLI would create a second copy that drifts from the one
|
|
15
|
+
* `test_redteam.py` gates on, and shipping it with the runtime would change a landed
|
|
16
|
+
* layer's published contents for one command's convenience. So the format is the
|
|
17
|
+
* contract: `--red-team` names a file, and without it the command looks for
|
|
18
|
+
* `redteam.json` beside the spec and then at the project root.
|
|
19
|
+
*
|
|
20
|
+
* **Why it evaluates the declared table, not one synthesized definition per fixture.**
|
|
21
|
+
* `test_redteam.py` builds a `GuardrailDefinition` for each fixture and runs it alone,
|
|
22
|
+
* because it is testing the detectors. This command is answering a different question --
|
|
23
|
+
* "would *this app* have caught this?" -- so it hands the engine every declaration that
|
|
24
|
+
* covers the fixture's stage, exactly as a route does, and a fixture counts as caught
|
|
25
|
+
* when any of them fires. A declaration that names the wrong policy still counts as a
|
|
26
|
+
* catch if its other policy fires: the app stopped the payload, which is what matters.
|
|
27
|
+
*
|
|
28
|
+
* **Why `covered` is a separate fact from `caught`.** "No declaration at this stage names
|
|
29
|
+
* this policy" is a spec gap the developer can close by editing the YAML; "declared but
|
|
30
|
+
* the detector missed" is a detector gap they cannot. Folding both into one recall number
|
|
31
|
+
* would send a reader to the wrong file, so the undeclared pairs are listed on their own
|
|
32
|
+
* line and in the `--json` document.
|
|
33
|
+
*
|
|
34
|
+
* **Why nothing is regenerated.** A gate measures the tree it is given, and `aid eval`
|
|
35
|
+
* already sets that precedent: this command reads `generated/guardrails.py`, and a tree
|
|
36
|
+
* that predates the spec is reported as a missing or stale module rather than silently
|
|
37
|
+
* replaced. Run `aid gen` first, as CI does.
|
|
38
|
+
*
|
|
39
|
+
* **When it does not run, and what it exits with.** Exit 2 is "this command could not
|
|
40
|
+
* judge the project": an unknown target, a target with no driver, a corpus that is
|
|
41
|
+
* missing or malformed, or no interpreter that can import the runtime. Exit 1 is a
|
|
42
|
+
* verdict about the project: it declares no guardrails, its tree has no guardrail module,
|
|
43
|
+
* the run failed, or the measured recall / false-positive rate misses what the corpus
|
|
44
|
+
* asks for.
|
|
45
|
+
*/
|
|
46
|
+
import type { CliContext } from '../context.js';
|
|
47
|
+
import type { CommandOutcome } from '../output.js';
|
|
48
|
+
export interface GuardOptions {
|
|
49
|
+
readonly spec: string | undefined;
|
|
50
|
+
readonly target: string | undefined;
|
|
51
|
+
/** The corpus to measure against; discovered beside the spec when omitted. */
|
|
52
|
+
readonly redTeam: string | undefined;
|
|
53
|
+
}
|
|
54
|
+
export declare function guard(context: CliContext, options: GuardOptions): CommandOutcome;
|
|
55
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/commands/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAOnD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAwZD,wBAAgB,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,GAAG,cAAc,CAkYhF"}
|