@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.
@@ -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"}