@agent-surface/cli 0.15.0 → 0.16.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/README.md +6 -182
- package/dist/bin.js +181 -207
- package/dist/bin.js.map +1 -1
- package/dist/chunk-IA7FUP4R.js +125 -0
- package/dist/chunk-IA7FUP4R.js.map +1 -0
- package/dist/chunk-X6RGGBQ4.js +139 -0
- package/dist/chunk-X6RGGBQ4.js.map +1 -0
- package/dist/index.d.ts +31 -173
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/ink-E6DATMUQ.js +51 -0
- package/dist/ink-E6DATMUQ.js.map +1 -0
- package/package.json +4 -20
- package/dist/check-VWRYNYLN.js +0 -259
- package/dist/check-VWRYNYLN.js.map +0 -1
- package/dist/chunk-A2G4QLX5.js +0 -29
- package/dist/chunk-A2G4QLX5.js.map +0 -1
- package/dist/chunk-A5UCBF7D.js +0 -246
- package/dist/chunk-A5UCBF7D.js.map +0 -1
- package/dist/chunk-GYYWHZPM.js +0 -532
- package/dist/chunk-GYYWHZPM.js.map +0 -1
- package/dist/chunk-NFK3XWWH.js +0 -1475
- package/dist/chunk-NFK3XWWH.js.map +0 -1
- package/dist/chunk-Y2LSPEVK.js +0 -61
- package/dist/chunk-Y2LSPEVK.js.map +0 -1
- package/dist/collect.js +0 -52
- package/dist/collect.js.map +0 -1
- package/dist/config-rHD2wEws.d.ts +0 -60
- package/dist/init-BVZR6CRS.js +0 -164
- package/dist/init-BVZR6CRS.js.map +0 -1
- package/dist/ink-ZCQ26EY4.js +0 -345
- package/dist/ink-ZCQ26EY4.js.map +0 -1
- package/dist/inspect-3YFPCYQJ.js +0 -226
- package/dist/inspect-3YFPCYQJ.js.map +0 -1
- package/dist/snapshot-5QKLZKZI.js +0 -130
- package/dist/snapshot-5QKLZKZI.js.map +0 -1
- package/dist/vitest.d.ts +0 -30
- package/dist/vitest.js +0 -9
- package/dist/vitest.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,187 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@agent-surface/cli`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
It is a developer tool: nothing it prints ever reaches a model.
|
|
6
|
-
|
|
7
|
-
Docs: https://agent-surface-docs.vercel.app/20-cli
|
|
8
|
-
|
|
9
|
-
## Install
|
|
3
|
+
Compiles, snapshots and diffs the production capability contract.
|
|
10
4
|
|
|
11
5
|
```bash
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Configure
|
|
16
|
-
|
|
17
|
-
A presentation surface only exists once components mount — it is a projection of *what is mounted × route × host context × consumer × policy × live `when()`*. So there is nothing static to read, and the CLI mounts your app. It does not re-implement it: the config points at the composition root you already have.
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// agent-surface.config.tsx
|
|
21
|
-
import { defineSurface } from "@agent-surface/cli";
|
|
22
|
-
import { agentManifest, createApp } from "./src/agent/setup.js"; // already exists
|
|
23
|
-
import { App } from "./src/app/App.js"; // already exists
|
|
24
|
-
|
|
25
|
-
export default defineSurface({
|
|
26
|
-
manifest: agentManifest,
|
|
27
|
-
mount: ({ user }) => {
|
|
28
|
-
const app = createApp({ environment: "test", user });
|
|
29
|
-
return { registry: app.registry, ui: <App app={app} />, app };
|
|
30
|
-
},
|
|
31
|
-
scenarios: {
|
|
32
|
-
admin: { user: { id: "u_admin", permissions: ["devices:write"] } },
|
|
33
|
-
anonymous: { user: null },
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Loading goes through vite-node on your own `vite.config.*`, so your aliases, plugins and TSX resolve exactly as they do in dev.
|
|
39
|
-
|
|
40
|
-
## Use
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
agent-surface init # read the codebase, then scaffold a config
|
|
44
|
-
agent-surface inspect [scenario] # what an agent can reach, and what it cannot
|
|
45
|
-
agent-surface snapshot [scenario] # write/refresh the committed baseline
|
|
46
|
-
agent-surface check [scenario] # fail drift, gaps, rejections, stale scenarios
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Every command covers all scenarios in the config unless you name one. `inspect` prints each in turn:
|
|
50
|
-
|
|
51
|
-
```text
|
|
52
|
-
SURFACE INSPECT
|
|
53
|
-
Config agent-surface.config.tsx
|
|
54
|
-
Depth full — the source is read and every scenario is mounted
|
|
55
|
-
Scope whole surface — no component-type prefix filter
|
|
56
|
-
Scenarios 1 of 2 — admin
|
|
57
|
-
|
|
58
|
-
SURFACE SUMMARY
|
|
59
|
-
Reach 11/11 authored capabilities reached
|
|
60
|
-
Callable 9/11 mounted capabilities are callable in at least one scenario · 2 never callable
|
|
61
|
-
Risk 1 destructive · 1 confirmation-gated · 1 with bound input
|
|
62
|
-
Domain 1 capability reached against the authoritative oRPC manifest
|
|
63
|
-
Catalog every call site read
|
|
64
|
-
Scenarios 1 mounted
|
|
65
|
-
Verdict every authored capability is reached by a scenario
|
|
66
|
-
|
|
67
|
-
STATIC CATALOG
|
|
68
|
-
STATUS COMPLETE — every capability identity resolved
|
|
69
|
-
Capabilities 11 authored (upper bound) · 10 resolved call sites
|
|
70
|
-
Program 21 files analyzed · 40 agent-surface implementation files excluded
|
|
71
|
-
Metadata 5 call sites partially read · identity remains resolved
|
|
72
|
-
Domain 1 manifest capability
|
|
73
|
-
|
|
74
|
-
scenario admin route /devices
|
|
75
|
-
9 callable, 2 visible-disabled, 0 hidden · 1 destructive, 1 confirmation-gated
|
|
76
|
-
|
|
77
|
-
CAPABILITY KIND EFFECT STATE FLAGS
|
|
78
|
-
app.navigation.goTo action navigation callable reversible
|
|
79
|
-
devices.drawer.open action local-state callable reversible
|
|
80
|
-
devices.drawer.close action local-state disabled reversible
|
|
81
|
-
⤷ The drawer is not open
|
|
82
|
-
devices.table.sort action local-state callable idempotent · reversible
|
|
83
|
-
devices.disable procedure destructive disabled confirmation:required · deviceIds bound+locked
|
|
84
|
-
⤷ Select at least one device first
|
|
6
|
+
agent-surface inspect
|
|
7
|
+
agent-surface snapshot
|
|
8
|
+
agent-surface check --base origin/main --format github
|
|
85
9
|
```
|
|
86
10
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`--detail` shows full capability, origin, and diagnostic detail; `--explain` and `--schemas` imply it.
|
|
90
|
-
|
|
91
|
-
### Depth
|
|
92
|
-
|
|
93
|
-
A presentation surface has two sources of truth, and `--depth` says how much of each to compute:
|
|
94
|
-
|
|
95
|
-
| | reads the TypeScript program | mounts the scenarios | answers |
|
|
96
|
-
|---|---|---|---|
|
|
97
|
-
| `--depth static` | ✔ | — | what this codebase authors |
|
|
98
|
-
| `--depth runtime` | — | ✔ | what a mounted scenario surfaces |
|
|
99
|
-
| `--depth full` *(default)* | ✔ | ✔ | both, **and the difference between them** |
|
|
100
|
-
|
|
101
|
-
`full` is the default because a tool that has to be asked for the complete answer mostly gives the incomplete one. `static` needs no scenarios and survives an app that will not mount; `runtime` skips the program boot on a repository wide enough to feel it.
|
|
102
|
-
|
|
103
|
-
### Why is my capability missing?
|
|
104
|
-
|
|
105
|
-
`snapshot()` bakes policy outcomes: a `hide` removes the capability *and* the reason, because the existence of a hidden capability is itself information. Correct at the agent boundary, useless when you are the developer. `--explain` answers it:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
agent-surface inspect anonymous --explain
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
```text
|
|
112
|
-
scenario anonymous route /devices
|
|
113
|
-
0 callable, 0 visible-disabled, 11 hidden
|
|
114
|
-
|
|
115
|
-
hidden by policy (absent from the snapshot) (11)
|
|
116
|
-
- set [devices.filters@default]
|
|
117
|
-
Update one or both filters; omitted fields are unchanged.
|
|
118
|
-
policy authenticated (registry, discovery/authorize): hide
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
The hidden capabilities themselves print without the flag — signed out, that scenario shows eleven rows marked `hidden` rather than a bare `0 callable`. `--explain` adds the attribution: every policy in the chain, in the order it runs, with its own vote, the layer it came from, and whether its `onDiscovery` threw. Availability is reported apart from the policy votes — *authority hides, state discloses*, and the two failures must never look alike.
|
|
122
|
-
|
|
123
|
-
### What did we author that no scenario reaches?
|
|
124
|
-
|
|
125
|
-
A route nobody visits never registers, so it is in no snapshot and drifts against no baseline — invisible to a mount by construction. The static half reads the TypeScript program instead, and the difference is a first-class finding in **every** command:
|
|
126
|
-
|
|
127
|
-
```text
|
|
128
|
-
UNREACHED — authored, and no scenario mounts it (1)
|
|
129
|
-
CAPABILITY ORIGIN
|
|
130
|
-
view:cov.unmounted.toCsv Unmounted.tsx:26
|
|
131
|
-
→ add a scenario that mounts them, delete the dead component, or record the decision in
|
|
132
|
-
.agent-surface/coverage-allow.json
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
`check` says the same thing as a report — a verdict, the health matrix behind it, one row per scenario, then the findings and the commands that clear them:
|
|
136
|
-
|
|
137
|
-
```text
|
|
138
|
-
SURFACE CHECK FAIL
|
|
139
|
-
Config agent-surface.config.tsx
|
|
140
|
-
Depth full — the source is read and every scenario is mounted
|
|
141
|
-
Scope whole surface — no component-type prefix filter
|
|
142
|
-
|
|
143
|
-
Coverage FAIL 2/3 authored capabilities reached · 1 unreached
|
|
144
|
-
Catalog PASS all static sites resolved
|
|
145
|
-
Domain PASS 1 manifest capability reached
|
|
146
|
-
Baselines FAIL 1/2 scenario baselines current
|
|
147
|
-
Runtime PASS 2 scenarios mounted
|
|
148
|
-
|
|
149
|
-
SCENARIOS (2)
|
|
150
|
-
SCENARIO ROUTE CALLABLE DISABLED HIDDEN REJECTED BASELINE
|
|
151
|
-
admin /devices 9 2 0 — drift (1)
|
|
152
|
-
anonymous /devices 0 0 11 — current
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
`inspect` reports it and `snapshot` reports it; **`check` fails on it**. It also fails on an unread call site, because the catalog is `unreached`'s denominator and holes in it make that count a floor rather than an answer — pass `--allow-unresolved` to accept that knowingly, which still prints the gap. Adoption ratchets through a committed `.agent-surface/coverage-allow.json`, whose stale entries fail the command.
|
|
156
|
-
|
|
157
|
-
If any scenario fails to mount there is **no verdict at all**: that scenario reached nothing, so everything it would have surfaced would read as unreached. The failed scenarios are named instead, and the static half still prints.
|
|
158
|
-
|
|
159
|
-
### Exit codes
|
|
160
|
-
|
|
161
|
-
**0** clean · **1** a finding · **2** the command could not run.
|
|
162
|
-
|
|
163
|
-
`inspect` never exits `1` — it reports, and `check` gates. CI has to be able to tell "the surface changed" from "the tool never loaded the app", because the second one passing silently is how a gate rots.
|
|
164
|
-
|
|
165
|
-
> Upgrading from 0.10? `capabilities` is now `inspect --depth static`, and `coverage` is folded into `inspect` and `check`. Both were removed rather than aliased; naming either still prints where its answer went.
|
|
166
|
-
|
|
167
|
-
### The scenarios are not a fixture
|
|
168
|
-
|
|
169
|
-
The same definitions drive your test suite, so "admin on /devices" exists once rather than twice:
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
import config from "../agent-surface.config.js";
|
|
173
|
-
import { mountScenario } from "@agent-surface/cli/vitest";
|
|
174
|
-
|
|
175
|
-
const { surface, app } = await mountScenario(config, "admin");
|
|
176
|
-
expect(surface).toExpose("view:devices.filters.set");
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Notes
|
|
180
|
-
|
|
181
|
-
Requires `@testing-library/react` and `react-dom` (peers) — anything that mounts does so in jsdom, so anything needing a real browser is out of reach by construction. `--depth static` and `init` mount nothing at all, and install no DOM.
|
|
182
|
-
|
|
183
|
-
Output falls back to plain text when piped, or under `--plain`, `CI` and `NO_COLOR`. stdout carries the command's output and nothing else; diagnostics go to stderr.
|
|
184
|
-
|
|
185
|
-
Full specification: [docs/20](https://github.com/Wiseair-srl/agent-surface/blob/main/docs/20-cli.md).
|
|
186
|
-
|
|
187
|
-
MIT © Wiseair S.r.l.
|
|
11
|
+
No scenarios, app mounts, static heuristics or allowlists. See [CLI docs](../../docs/20-cli.md).
|
package/dist/bin.js
CHANGED
|
@@ -1,99 +1,167 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
DEFAULT_SNAPSHOT,
|
|
4
|
+
readBaseManifest,
|
|
5
|
+
readManifest,
|
|
6
|
+
renderReport,
|
|
7
|
+
writeManifest
|
|
8
|
+
} from "./chunk-X6RGGBQ4.js";
|
|
9
|
+
import {
|
|
10
|
+
diffContracts
|
|
11
|
+
} from "./chunk-IA7FUP4R.js";
|
|
11
12
|
|
|
12
13
|
// src/bin.ts
|
|
13
14
|
import { realpathSync } from "fs";
|
|
14
15
|
import { fileURLToPath } from "url";
|
|
15
16
|
import { parseArgs } from "util";
|
|
16
17
|
|
|
17
|
-
// src/
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
// src/commands.ts
|
|
19
|
+
import { resolve } from "path";
|
|
20
|
+
import {
|
|
21
|
+
compileCapabilityContract,
|
|
22
|
+
computeManifestHash
|
|
23
|
+
} from "@agent-surface/compiler";
|
|
24
|
+
function mergeManifests(manifests) {
|
|
25
|
+
const entries = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const manifest of manifests) {
|
|
27
|
+
for (const entry of manifest.capabilities) {
|
|
28
|
+
const key = `${entry.declarationId}\0${entry.capabilityId}`;
|
|
29
|
+
const existing = entries.get(key);
|
|
30
|
+
if (!existing) entries.set(key, { ...entry, targets: [...entry.targets] });
|
|
31
|
+
else {
|
|
32
|
+
if (existing.contractHash !== entry.contractHash) {
|
|
33
|
+
throw new Error(`contract ${entry.declarationId} / ${entry.capabilityId} differs across targets`);
|
|
34
|
+
}
|
|
35
|
+
existing.targets = [.../* @__PURE__ */ new Set([...existing.targets, ...entry.targets])].sort();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
28
38
|
}
|
|
39
|
+
const payload = {
|
|
40
|
+
formatVersion: 3,
|
|
41
|
+
compilerVersion: manifests[0]?.compilerVersion ?? "unknown",
|
|
42
|
+
targets: [...new Set(manifests.flatMap((manifest) => manifest.targets))].sort(),
|
|
43
|
+
capabilities: [...entries.values()].sort(
|
|
44
|
+
(a, b) => `${a.declarationId}\0${a.capabilityId}`.localeCompare(`${b.declarationId}\0${b.capabilityId}`)
|
|
45
|
+
),
|
|
46
|
+
externalContracts: manifests.flatMap((manifest) => manifest.externalContracts).filter((entry, index, all) => all.findIndex((candidate) => candidate.digest === entry.digest) === index).sort((a, b) => a.source.localeCompare(b.source)),
|
|
47
|
+
completeness: { status: "proven" }
|
|
48
|
+
};
|
|
49
|
+
return { ...payload, hash: computeManifestHash(payload) };
|
|
29
50
|
}
|
|
30
|
-
function
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
async function compile(options) {
|
|
52
|
+
const targets = options.targets.length > 0 ? options.targets : ["web-production"];
|
|
53
|
+
const manifests = await Promise.all(
|
|
54
|
+
targets.map(
|
|
55
|
+
(target) => compileCapabilityContract({
|
|
56
|
+
root: options.root,
|
|
57
|
+
...options.configFile ? { configFile: options.configFile } : {},
|
|
58
|
+
target
|
|
59
|
+
})
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
return mergeManifests(manifests);
|
|
63
|
+
}
|
|
64
|
+
function shouldFail(policy, changes) {
|
|
65
|
+
if (policy === "none") return false;
|
|
66
|
+
if (policy === "all") return changes.length > 0;
|
|
67
|
+
return changes.some((change) => change.classification === policy);
|
|
68
|
+
}
|
|
69
|
+
async function present(report, options) {
|
|
70
|
+
const interactive = options.format === "human" && !options.plain && process.stdout.isTTY === true && !process.env["CI"] && !process.env["NO_COLOR"];
|
|
71
|
+
if (interactive) {
|
|
72
|
+
const { renderInk } = await import("./ink-E6DATMUQ.js");
|
|
73
|
+
await renderInk(report);
|
|
37
74
|
return;
|
|
38
75
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
76
|
+
process.stdout.write(renderReport(report, options.format));
|
|
77
|
+
}
|
|
78
|
+
async function runInspect(options) {
|
|
79
|
+
const manifest = await compile(options);
|
|
80
|
+
const snapshotPath = resolve(options.root, options.snapshot ?? DEFAULT_SNAPSHOT);
|
|
81
|
+
const snapshot = readManifest(snapshotPath);
|
|
82
|
+
const integrityChanges = diffContracts(snapshot, manifest);
|
|
83
|
+
const report = {
|
|
84
|
+
command: "inspect",
|
|
85
|
+
status: "view",
|
|
86
|
+
manifest,
|
|
87
|
+
snapshotPath,
|
|
88
|
+
integrity: {
|
|
89
|
+
status: !snapshot ? "missing" : snapshot.hash === manifest.hash ? "current" : "stale",
|
|
90
|
+
changes: integrityChanges
|
|
91
|
+
},
|
|
92
|
+
...options.base ? {
|
|
93
|
+
pullRequest: {
|
|
94
|
+
base: options.base,
|
|
95
|
+
changes: diffContracts(readBaseManifest(options.base, snapshotPath, options.root), manifest)
|
|
96
|
+
}
|
|
97
|
+
} : {}
|
|
98
|
+
};
|
|
99
|
+
await present(report, options);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
async function runSnapshot(options) {
|
|
103
|
+
const manifest = await compile(options);
|
|
104
|
+
const snapshotPath = resolve(options.root, options.snapshot ?? DEFAULT_SNAPSHOT);
|
|
105
|
+
writeManifest(snapshotPath, manifest);
|
|
106
|
+
await present({ command: "snapshot", status: "written", manifest, snapshotPath }, options);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
async function runCheck(options) {
|
|
110
|
+
const manifest = await compile(options);
|
|
111
|
+
const snapshotPath = resolve(options.root, options.snapshot ?? DEFAULT_SNAPSHOT);
|
|
112
|
+
const snapshot = readManifest(snapshotPath);
|
|
113
|
+
const integrityChanges = diffContracts(snapshot, manifest);
|
|
114
|
+
const integrityCurrent = snapshot?.hash === manifest.hash;
|
|
115
|
+
const pr = options.base ? {
|
|
116
|
+
base: options.base,
|
|
117
|
+
changes: diffContracts(readBaseManifest(options.base, snapshotPath, options.root), snapshot ?? manifest)
|
|
118
|
+
} : void 0;
|
|
119
|
+
const failed = !integrityCurrent || (pr ? shouldFail(options.policy, pr.changes) : false);
|
|
120
|
+
const report = {
|
|
121
|
+
command: "check",
|
|
122
|
+
status: failed ? "fail" : "pass",
|
|
123
|
+
manifest,
|
|
124
|
+
snapshotPath,
|
|
125
|
+
integrity: {
|
|
126
|
+
status: !snapshot ? "missing" : integrityCurrent ? "current" : "stale",
|
|
127
|
+
changes: integrityChanges
|
|
128
|
+
},
|
|
129
|
+
...pr ? { pullRequest: pr } : {}
|
|
130
|
+
};
|
|
131
|
+
await present(report, options);
|
|
132
|
+
return failed ? 1 : 0;
|
|
57
133
|
}
|
|
58
134
|
|
|
59
135
|
// src/bin.ts
|
|
60
|
-
var USAGE = `agent-surface \u2014
|
|
136
|
+
var USAGE = `agent-surface \u2014 compiled production capability contract
|
|
61
137
|
|
|
62
138
|
Usage
|
|
63
|
-
agent-surface
|
|
64
|
-
agent-surface
|
|
65
|
-
agent-surface
|
|
66
|
-
agent-surface check [scenario] fail on drift, or on a capability no scenario reaches
|
|
67
|
-
|
|
68
|
-
Every command covers all scenarios in the config unless you name one.
|
|
69
|
-
|
|
70
|
-
Depth
|
|
71
|
-
--depth full read the source AND mount the scenarios, and report the gap (default)
|
|
72
|
-
--depth static read the source only \u2014 no Vite, no jsdom, no mount, no scenarios needed
|
|
73
|
-
--depth runtime mount only \u2014 skip the TypeScript program on a repo wide enough to feel it
|
|
139
|
+
agent-surface inspect
|
|
140
|
+
agent-surface snapshot
|
|
141
|
+
agent-surface check --base origin/main --format github
|
|
74
142
|
|
|
75
143
|
Options
|
|
76
|
-
--
|
|
77
|
-
--
|
|
78
|
-
--
|
|
79
|
-
--
|
|
80
|
-
--
|
|
81
|
-
--
|
|
82
|
-
--
|
|
83
|
-
--
|
|
84
|
-
--
|
|
85
|
-
--
|
|
86
|
-
|
|
87
|
-
-h, --help show this
|
|
88
|
-
-v, --version print the version
|
|
144
|
+
--root <path> application root (default: cwd)
|
|
145
|
+
--config <path> Vite config, relative to root
|
|
146
|
+
--snapshot <path> committed contract (default: .agent-surface/contract.json)
|
|
147
|
+
--target <name> production build target; repeatable
|
|
148
|
+
--base <git-ref> render committed PR drift against Git base
|
|
149
|
+
--policy <mode> all | widening | narrowing | neutral | none (default: all)
|
|
150
|
+
--format <format> human | json | github | markdown (default: human)
|
|
151
|
+
--json alias for --format json
|
|
152
|
+
--plain disable Ink terminal rendering
|
|
153
|
+
-h, --help
|
|
154
|
+
-v, --version
|
|
89
155
|
|
|
90
|
-
Exit
|
|
156
|
+
Exit: 0 clean/viewed, 1 deterministic drift/policy finding, 2 completeness failure.
|
|
91
157
|
`;
|
|
92
|
-
var COMMANDS = ["
|
|
93
|
-
var
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
158
|
+
var COMMANDS = /* @__PURE__ */ new Set(["inspect", "snapshot", "check"]);
|
|
159
|
+
var FORMATS = /* @__PURE__ */ new Set(["human", "json", "github", "markdown"]);
|
|
160
|
+
var POLICIES = /* @__PURE__ */ new Set(["all", "widening", "narrowing", "neutral", "none"]);
|
|
161
|
+
function write(text, error = false) {
|
|
162
|
+
(error ? process.stderr : process.stdout).write(text.endsWith("\n") ? text : `${text}
|
|
163
|
+
`);
|
|
164
|
+
}
|
|
97
165
|
async function main(argv = process.argv.slice(2)) {
|
|
98
166
|
let parsed;
|
|
99
167
|
try {
|
|
@@ -101,16 +169,13 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
101
169
|
args: argv,
|
|
102
170
|
allowPositionals: true,
|
|
103
171
|
options: {
|
|
172
|
+
root: { type: "string", default: process.cwd() },
|
|
104
173
|
config: { type: "string" },
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
schemas: { type: "boolean", default: false },
|
|
111
|
-
tsconfig: { type: "string" },
|
|
112
|
-
"allow-unresolved": { type: "boolean", default: false },
|
|
113
|
-
yes: { type: "boolean", default: false },
|
|
174
|
+
snapshot: { type: "string" },
|
|
175
|
+
target: { type: "string", multiple: true },
|
|
176
|
+
base: { type: "string" },
|
|
177
|
+
policy: { type: "string", default: "all" },
|
|
178
|
+
format: { type: "string", default: "human" },
|
|
114
179
|
json: { type: "boolean", default: false },
|
|
115
180
|
plain: { type: "boolean", default: false },
|
|
116
181
|
help: { type: "boolean", short: "h", default: false },
|
|
@@ -118,8 +183,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
118
183
|
}
|
|
119
184
|
});
|
|
120
185
|
} catch (error) {
|
|
121
|
-
|
|
122
|
-
|
|
186
|
+
write(error instanceof Error ? error.message : String(error), true);
|
|
187
|
+
write(USAGE, true);
|
|
123
188
|
return 2;
|
|
124
189
|
}
|
|
125
190
|
const { values, positionals } = parsed;
|
|
@@ -131,87 +196,47 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
131
196
|
write(await readVersion());
|
|
132
197
|
return 0;
|
|
133
198
|
}
|
|
134
|
-
const [command,
|
|
135
|
-
if (!command) {
|
|
136
|
-
write(
|
|
199
|
+
const [command, ...extra] = positionals;
|
|
200
|
+
if (!command || !COMMANDS.has(command) || extra.length > 0) {
|
|
201
|
+
write(command === "init" ? "init removed: configure the Vite compiler plugin" : `invalid command ${command ?? ""}`, true);
|
|
202
|
+
write(USAGE, true);
|
|
137
203
|
return 2;
|
|
138
204
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
moved ? `"${command}" was removed in 0.11 \u2014 its answer is now \`${moved}\`. The static catalog and the live projection are one command, so the gap between them is reported rather than left for whoever remembers to look.` : `unknown command "${command}"`
|
|
143
|
-
);
|
|
144
|
-
if (!moved) writeError(USAGE);
|
|
205
|
+
const format = values.json ? "json" : values.format;
|
|
206
|
+
if (!FORMATS.has(format)) {
|
|
207
|
+
write(`--format must be human, json, github, or markdown`, true);
|
|
145
208
|
return 2;
|
|
146
209
|
}
|
|
147
|
-
if (!
|
|
148
|
-
|
|
210
|
+
if (!POLICIES.has(values.policy)) {
|
|
211
|
+
write(`--policy must be all, widening, narrowing, neutral, or none`, true);
|
|
149
212
|
return 2;
|
|
150
213
|
}
|
|
151
|
-
const
|
|
214
|
+
const options = {
|
|
215
|
+
root: values.root,
|
|
216
|
+
...values.config ? { configFile: values.config } : {},
|
|
217
|
+
...values.snapshot ? { snapshot: values.snapshot } : {},
|
|
218
|
+
targets: values.target ?? [],
|
|
219
|
+
...values.base ? { base: values.base } : {},
|
|
220
|
+
format,
|
|
221
|
+
policy: values.policy,
|
|
222
|
+
...values.plain ? { plain: true } : {}
|
|
223
|
+
};
|
|
152
224
|
try {
|
|
153
|
-
if (command === "
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
cwd: process.cwd(),
|
|
157
|
-
...values.tsconfig ? { tsconfig: values.tsconfig } : {},
|
|
158
|
-
...values.yes ? { yes: true } : {},
|
|
159
|
-
...values.plain ? { plain: true } : {}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
const configPath = values.config ?? findConfig();
|
|
163
|
-
if (!configPath) {
|
|
164
|
-
writeError(
|
|
165
|
-
"no agent-surface.config.* found (searched upward from the working directory).\nRun `agent-surface init`, or see https://agent-surface-docs.vercel.app/20-cli"
|
|
166
|
-
);
|
|
167
|
-
return 2;
|
|
168
|
-
}
|
|
169
|
-
if (depth !== "static") installDom();
|
|
170
|
-
const shared = {
|
|
171
|
-
configPath,
|
|
172
|
-
depth,
|
|
173
|
-
...scenario ? { scenario } : {},
|
|
174
|
-
...values.scope ? { scope: values.scope } : {},
|
|
175
|
-
...values.tsconfig ? { tsconfig: values.tsconfig } : {},
|
|
176
|
-
...values.json ? { json: true } : {},
|
|
177
|
-
...values.plain ? { plain: true } : {},
|
|
178
|
-
...values["baseline-dir"] ? { baselineDir: values["baseline-dir"] } : {}
|
|
179
|
-
};
|
|
180
|
-
if (command === "inspect") {
|
|
181
|
-
const { runInspect } = await import("./inspect-3YFPCYQJ.js");
|
|
182
|
-
return await runInspect({
|
|
183
|
-
...shared,
|
|
184
|
-
...values.detail ? { detail: true } : {},
|
|
185
|
-
...values.explain ? { explain: true } : {},
|
|
186
|
-
...values.schemas ? { schemas: true } : {}
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
if (command === "snapshot") {
|
|
190
|
-
const { runSnapshot } = await import("./snapshot-5QKLZKZI.js");
|
|
191
|
-
return await runSnapshot(shared);
|
|
192
|
-
}
|
|
193
|
-
const { runCheck } = await import("./check-VWRYNYLN.js");
|
|
194
|
-
return await runCheck({
|
|
195
|
-
...shared,
|
|
196
|
-
...values.detail ? { detail: true } : {},
|
|
197
|
-
...values["allow-unresolved"] ? { allowUnresolved: true } : {}
|
|
198
|
-
});
|
|
225
|
+
if (command === "inspect") return await runInspect(options);
|
|
226
|
+
if (command === "snapshot") return await runSnapshot(options);
|
|
227
|
+
return await runCheck(options);
|
|
199
228
|
} catch (error) {
|
|
200
|
-
|
|
229
|
+
write(error instanceof Error ? error.message : String(error), true);
|
|
201
230
|
if (error instanceof Error && error.stack && process.env["AGENT_SURFACE_DEBUG"]) {
|
|
202
|
-
|
|
231
|
+
write(error.stack, true);
|
|
203
232
|
}
|
|
204
233
|
return 2;
|
|
205
234
|
}
|
|
206
235
|
}
|
|
207
236
|
async function readVersion() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return JSON.parse(readFileSync(path, "utf8")).version;
|
|
212
|
-
} catch {
|
|
213
|
-
return "unknown";
|
|
214
|
-
}
|
|
237
|
+
const { readFileSync } = await import("fs");
|
|
238
|
+
const path = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
239
|
+
return JSON.parse(readFileSync(path, "utf8")).version;
|
|
215
240
|
}
|
|
216
241
|
function invokedAsBinary() {
|
|
217
242
|
const entry = process.argv[1];
|
|
@@ -222,61 +247,10 @@ function invokedAsBinary() {
|
|
|
222
247
|
return false;
|
|
223
248
|
}
|
|
224
249
|
}
|
|
225
|
-
var GRACE_MS = 1e3;
|
|
226
|
-
var OWN_STDIO = /* @__PURE__ */ new Set(["TTYWrap", "PipeWrap", "FileWrap"]);
|
|
227
|
-
function heldHandles() {
|
|
228
|
-
const active = process.getActiveResourcesInfo?.() ?? [];
|
|
229
|
-
return active.filter((resource) => !OWN_STDIO.has(resource));
|
|
230
|
-
}
|
|
231
|
-
async function flushOutput() {
|
|
232
|
-
const drained = Promise.all(
|
|
233
|
-
[process.stdout, process.stderr].map(
|
|
234
|
-
(stream) => new Promise((resolve) => {
|
|
235
|
-
if (stream.writableLength === 0) resolve();
|
|
236
|
-
else stream.write("", () => resolve());
|
|
237
|
-
})
|
|
238
|
-
)
|
|
239
|
-
);
|
|
240
|
-
const deadline = new Promise((resolve) => {
|
|
241
|
-
setTimeout(resolve, 2e3).unref();
|
|
242
|
-
});
|
|
243
|
-
await Promise.race([drained, deadline]);
|
|
244
|
-
}
|
|
245
|
-
function exitWhenWedged(code) {
|
|
246
|
-
process.exitCode = code;
|
|
247
|
-
setTimeout(() => {
|
|
248
|
-
const held = heldHandles();
|
|
249
|
-
if (held.length > 0) {
|
|
250
|
-
const kinds = [...new Set(held)].sort().join(", ");
|
|
251
|
-
writeError(
|
|
252
|
-
`
|
|
253
|
-
${renderReportPlain([
|
|
254
|
-
{
|
|
255
|
-
title: "PROCESS CLEANUP WARN",
|
|
256
|
-
rows: [
|
|
257
|
-
{ label: "Open handles", text: `${held.length} (${kinds})` },
|
|
258
|
-
{ label: "Impact", text: "report complete; exit code unchanged; process exit forced" },
|
|
259
|
-
{
|
|
260
|
-
label: "Likely cause",
|
|
261
|
-
text: "polling, websocket, or a cache timer created during mount"
|
|
262
|
-
},
|
|
263
|
-
{ label: "Exit", text: String(code) }
|
|
264
|
-
]
|
|
265
|
-
}
|
|
266
|
-
])}`
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
void flushOutput().then(() => process.exit(code));
|
|
270
|
-
}, GRACE_MS).unref();
|
|
271
|
-
}
|
|
272
250
|
if (invokedAsBinary()) {
|
|
273
|
-
main().then(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
writeError(error instanceof Error ? error.message : String(error));
|
|
277
|
-
exitWhenWedged(1);
|
|
278
|
-
}
|
|
279
|
-
);
|
|
251
|
+
void main().then((code) => {
|
|
252
|
+
process.exitCode = code;
|
|
253
|
+
});
|
|
280
254
|
}
|
|
281
255
|
export {
|
|
282
256
|
main
|