@jevitate/cli 0.1.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ai-cli.d.ts +31 -0
  3. package/dist/ai-cli.js +238 -0
  4. package/dist/ai-cli.js.map +1 -0
  5. package/dist/bin.d.ts +2 -0
  6. package/dist/bin.js +50663 -0
  7. package/dist/bin.js.map +1 -0
  8. package/dist/data-dir.d.ts +9 -0
  9. package/dist/data-dir.js +13 -0
  10. package/dist/data-dir.js.map +1 -0
  11. package/dist/envelope.d.ts +11 -0
  12. package/dist/envelope.js +7 -0
  13. package/dist/envelope.js.map +1 -0
  14. package/dist/explore-api.d.ts +187 -0
  15. package/dist/explore-api.js +302 -0
  16. package/dist/explore-api.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +3 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/init-keys.d.ts +14 -0
  21. package/dist/init-keys.js +18 -0
  22. package/dist/init-keys.js.map +1 -0
  23. package/dist/init-mcp.d.ts +87 -0
  24. package/dist/init-mcp.js +198 -0
  25. package/dist/init-mcp.js.map +1 -0
  26. package/dist/init-skills.d.ts +80 -0
  27. package/dist/init-skills.js +256 -0
  28. package/dist/init-skills.js.map +1 -0
  29. package/dist/journey-api.d.ts +37 -0
  30. package/dist/journey-api.js +55 -0
  31. package/dist/journey-api.js.map +1 -0
  32. package/dist/load-api.d.ts +48 -0
  33. package/dist/load-api.js +84 -0
  34. package/dist/load-api.js.map +1 -0
  35. package/dist/mcp-api.d.ts +116 -0
  36. package/dist/mcp-api.js +359 -0
  37. package/dist/mcp-api.js.map +1 -0
  38. package/dist/mission-api.d.ts +56 -0
  39. package/dist/mission-api.js +65 -0
  40. package/dist/mission-api.js.map +1 -0
  41. package/dist/program.d.ts +77 -0
  42. package/dist/program.js +1938 -0
  43. package/dist/program.js.map +1 -0
  44. package/dist/record-api.d.ts +74 -0
  45. package/dist/record-api.js +93 -0
  46. package/dist/record-api.js.map +1 -0
  47. package/dist/regression-api.d.ts +17 -0
  48. package/dist/regression-api.js +16 -0
  49. package/dist/regression-api.js.map +1 -0
  50. package/dist/self-heal-adapter.d.ts +31 -0
  51. package/dist/self-heal-adapter.js +70 -0
  52. package/dist/self-heal-adapter.js.map +1 -0
  53. package/dist/source-api.d.ts +147 -0
  54. package/dist/source-api.js +208 -0
  55. package/dist/source-api.js.map +1 -0
  56. package/dist/source-run-api.d.ts +57 -0
  57. package/dist/source-run-api.js +91 -0
  58. package/dist/source-run-api.js.map +1 -0
  59. package/dist/ui-api.d.ts +35 -0
  60. package/dist/ui-api.js +369 -0
  61. package/dist/ui-api.js.map +1 -0
  62. package/dist/ux-api.d.ts +75 -0
  63. package/dist/ux-api.js +231 -0
  64. package/dist/ux-api.js.map +1 -0
  65. package/dist/version.d.ts +1 -0
  66. package/dist/version.js +37 -0
  67. package/dist/version.js.map +1 -0
  68. package/package.json +85 -0
  69. package/skills/jevitate-explore/SKILL.md +108 -0
  70. package/skills/jevitate-load-test/SKILL.md +42 -0
  71. package/skills/jevitate-mission-scope/SKILL.md +107 -0
  72. package/skills/jevitate-record/SKILL.md +78 -0
  73. package/skills/jevitate-run-journey/SKILL.md +72 -0
  74. package/skills/jevitate-sources/SKILL.md +88 -0
  75. package/skills/jevitate-ux-review/SKILL.md +68 -0
  76. package/ui/app.js +538 -0
  77. package/ui/index.html +546 -0
@@ -0,0 +1,208 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { promisify } from "node:util";
4
+ import { FsJourneyStore, JourneyRegistry } from "@jevitate/journey";
5
+ import { GitSourceManager, RemoteSource, UnknownSourceError, collectNavigateOrigins, loadManifest, publishJourney, readLock, surfaceForAck, writeLock, } from "@jevitate/sources";
6
+ import { UnknownJourneyError } from "./journey-api.js";
7
+ const execFileAsync = promisify(execFile);
8
+ /**
9
+ * A publish was requested for a Journey that exists locally but has not been
10
+ * promoted. Publishing shares a Journey with the world — only reviewed,
11
+ * promoted Journeys may leave this machine (spec §7). Kept distinct from
12
+ * `UnknownJourneyError` so the CLI can emit a precise error code without
13
+ * string-matching.
14
+ */
15
+ export class NotPromotedError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "NotPromotedError";
19
+ }
20
+ }
21
+ /**
22
+ * A publish request resolved to zero declared origins — the Journey has no
23
+ * absolute `navigate` origin and none were supplied with `--declare-origin`.
24
+ * A `SharedJourneyFile` MUST declare at least one origin, so this fails
25
+ * closed BEFORE `validateForPublish` (whose Zod error would be opaque).
26
+ */
27
+ export class NoDeclaredOriginsError extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "NoDeclaredOriginsError";
31
+ }
32
+ }
33
+ function mgrOf(deps) {
34
+ return new GitSourceManager(deps.sourcesDir, deps.git);
35
+ }
36
+ function nowIso(deps) {
37
+ return (deps.now ?? (() => new Date().toISOString()))();
38
+ }
39
+ /**
40
+ * Resolves a source `name` to its recorded `SourceEntry`, or throws
41
+ * `UnknownSourceError` (fail-closed, FMECA #5) — a source the lock does not
42
+ * know about is never silently treated as empty/absent. Reused by every
43
+ * command that operates on an existing source (pull/update/remove/trust/
44
+ * publish).
45
+ */
46
+ async function requireEntry(lockPath, name) {
47
+ const lock = await readLock(lockPath);
48
+ const entry = lock.sources.find((s) => s.name === name);
49
+ if (!entry) {
50
+ throw new UnknownSourceError(`source '${name}' is not registered (run 'jevitate source add' first)`);
51
+ }
52
+ return entry;
53
+ }
54
+ /**
55
+ * Clones a remote Journey source, pins it at its current HEAD, and records the
56
+ * pin in the lock. Adding a source NEVER trusts its Journeys (trust is a
57
+ * separate, explicit `source trust` act) — it only registers the source.
58
+ * The source's Terms of Use are surfaced for the human; a `TouAck` is
59
+ * recorded ONLY when `acceptTou` is set, so the run-gate stays fail-closed
60
+ * until the user has explicitly accepted.
61
+ */
62
+ export async function addSource(deps, req) {
63
+ const mgr = mgrOf(deps);
64
+ await mkdir(deps.sourcesDir, { recursive: true });
65
+ const pinnedCommit = await mgr.add(req.name, req.gitUrl);
66
+ const manifest = await loadManifest(mgr.resolveDir(req.name));
67
+ const touSurface = surfaceForAck(manifest, req.gitUrl);
68
+ let touAccepted = false;
69
+ if (req.acceptTou) {
70
+ await deps.ack.put({
71
+ sourceName: req.name,
72
+ gitUrl: req.gitUrl,
73
+ origins: manifest.sites.map((s) => s.origin),
74
+ ackedBy: req.ackedBy ?? "local",
75
+ ackedAtIso: nowIso(deps),
76
+ });
77
+ touAccepted = true;
78
+ }
79
+ const lock = await readLock(deps.lockPath);
80
+ const sources = lock.sources.filter((s) => s.name !== req.name);
81
+ sources.push({ name: req.name, gitUrl: req.gitUrl, pinnedCommit });
82
+ await writeLock(deps.lockPath, { version: 1, sources });
83
+ return { name: req.name, gitUrl: req.gitUrl, pinnedCommit, touSurface, touAccepted };
84
+ }
85
+ /** Lists every registered source (from the lock), annotated with which of its
86
+ * Journeys the local user has explicitly trusted. */
87
+ export async function listSources(deps) {
88
+ const lock = await readLock(deps.lockPath);
89
+ const trustRecords = await deps.trust.list();
90
+ return lock.sources.map((s) => ({
91
+ ...s,
92
+ trustedJourneys: trustRecords
93
+ .filter((t) => t.sourceId === s.name)
94
+ .map((t) => t.journeyId)
95
+ .sort(),
96
+ }));
97
+ }
98
+ /** Fetches new refs for a registered source without moving its pin. */
99
+ export async function pullSource(deps, name) {
100
+ const entry = await requireEntry(deps.lockPath, name);
101
+ await mgrOf(deps).pull(name);
102
+ return { name, pulled: true, pinnedCommit: entry.pinnedCommit, pinAdvanced: false };
103
+ }
104
+ /** The ONLY way a pin advances: fetch + fast-forward, then record the new pin
105
+ * in the lock. Explicit and per-source — never automatic. */
106
+ export async function updateSource(deps, name) {
107
+ await requireEntry(deps.lockPath, name);
108
+ const pinnedCommit = await mgrOf(deps).update(name);
109
+ const lock = await readLock(deps.lockPath);
110
+ const sources = lock.sources.map((s) => (s.name === name ? { ...s, pinnedCommit } : s));
111
+ await writeLock(deps.lockPath, { version: 1, sources });
112
+ return { name, pinnedCommit };
113
+ }
114
+ /** Removes a source's clone and its lock entry. Local `TrustRecord`s are left
115
+ * intact (they are hash-bound and harmless; re-adding the same content
116
+ * re-uses them, a content change invalidates them). */
117
+ export async function removeSource(deps, name) {
118
+ await requireEntry(deps.lockPath, name);
119
+ await mgrOf(deps).remove(name);
120
+ const lock = await readLock(deps.lockPath);
121
+ const sources = lock.sources.filter((s) => s.name !== name);
122
+ await writeLock(deps.lockPath, { version: 1, sources });
123
+ return { name, removed: true };
124
+ }
125
+ /**
126
+ * Records an explicit, human trust decision for one Journey in a source,
127
+ * BOUND to the Journey's CURRENT content hash at the recorded pin (spec §5,
128
+ * FMECA #2 TOCTOU). Trust is the single explicit user act that lets a source's
129
+ * risky Journey run: it never happens implicitly on `add`/`pull`/`update`, and
130
+ * a later content change invalidates it (the run-gate recomputes the hash).
131
+ */
132
+ export async function trustJourney(deps, req) {
133
+ const entry = await requireEntry(deps.lockPath, req.sourceName);
134
+ const mgr = mgrOf(deps);
135
+ // Pin the clone to the exact recorded commit before reading, so the hash we
136
+ // bind trust to is the content AT the pin, not a drifted working tree.
137
+ await mgr.checkout(req.sourceName, entry.pinnedCommit);
138
+ const remote = new RemoteSource(req.sourceName, mgr.resolveDir(req.sourceName), entry.pinnedCommit);
139
+ const sourced = await remote.get(req.journeyId);
140
+ if (!sourced) {
141
+ throw new UnknownJourneyError(`source '${req.sourceName}' has no journey '${req.journeyId}'`);
142
+ }
143
+ const record = {
144
+ sourceId: req.sourceName,
145
+ journeyId: req.journeyId,
146
+ contentHash: sourced.meta.contentHash,
147
+ approvedBy: req.approvedBy,
148
+ approvedAtIso: nowIso(deps),
149
+ };
150
+ await deps.trust.put(record);
151
+ return record;
152
+ }
153
+ /**
154
+ * Publishes a promoted local Journey to a registered distributed source via
155
+ * `@jevitate/sources`' `publishJourney`. Preserves every publish-side guard:
156
+ * - the target source must be REGISTERED (`UnknownSourceError` otherwise);
157
+ * - the Journey must exist and be PROMOTED (`NotPromotedError`);
158
+ * - `publishJourney`/`validateForPublish` then hard-block a materialized
159
+ * (non-redacted) secret value (`EmbeddedSecretError`) and any origin the
160
+ * steps touch but `declaredOrigins` does not cover (`UndeclaredOriginError`).
161
+ * The write always lands on a NEW `publish/<id>` branch — never a default
162
+ * branch — and degrades gracefully to instructions when `gh` is absent.
163
+ */
164
+ export async function publishJourneyToSource(deps, req) {
165
+ await requireEntry(deps.lockPath, req.toSource);
166
+ const registry = new JourneyRegistry(new FsJourneyStore(req.journeysDir));
167
+ const journey = await registry.get(req.id);
168
+ if (!journey) {
169
+ throw new UnknownJourneyError(`unknown journey '${req.id}'`);
170
+ }
171
+ if (!journey.metadata.promoted) {
172
+ throw new NotPromotedError(`journey '${req.id}' is not promoted; promote it before publishing`);
173
+ }
174
+ const declaredOrigins = req.declareOrigins && req.declareOrigins.length > 0
175
+ ? req.declareOrigins
176
+ : collectNavigateOrigins(journey.recording);
177
+ if (declaredOrigins.length === 0) {
178
+ throw new NoDeclaredOriginsError(`journey '${req.id}' declares no origins and navigates to no absolute origin; pass --declare-origin <origin>`);
179
+ }
180
+ return publishJourney(mgrOf(deps), deps.gh, {
181
+ journey,
182
+ declaredOrigins,
183
+ toSource: req.toSource,
184
+ asId: req.asId,
185
+ });
186
+ }
187
+ /**
188
+ * Real `gh` CLI port for `publishJourney`. Degrades gracefully: `available()`
189
+ * is false when `gh` isn't installed/authenticated, so publish falls back to
190
+ * printed branch+push instructions instead of failing (spec §14.3). Shells via
191
+ * `execFile` with an ARGS ARRAY only — never a shell string.
192
+ */
193
+ export const realGhPort = {
194
+ async available() {
195
+ try {
196
+ await execFileAsync("gh", ["auth", "status"]);
197
+ return true;
198
+ }
199
+ catch {
200
+ return false;
201
+ }
202
+ },
203
+ async createPr(cwd, branch, title) {
204
+ const { stdout } = await execFileAsync("gh", ["pr", "create", "--head", branch, "--title", title, "--body", title], { cwd });
205
+ return stdout.trim();
206
+ },
207
+ };
208
+ //# sourceMappingURL=source-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-api.js","sourceRoot":"","sources":["../src/source-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { type RunPolicy } from "@jevitate/domain";
2
+ import { type JourneyRunResult } from "@jevitate/runtime";
3
+ import { type SharedJourneyFile } from "@jevitate/sources";
4
+ import type { SourceApiDeps } from "./source-api.js";
5
+ /**
6
+ * The runner seam for a source-resolved Journey. Takes the ALREADY-GATED
7
+ * `SharedJourneyFile` (`resolveForRun`'s output) — never a raw address or a
8
+ * store `dir` — so the security gate always runs BEFORE anything reaches this
9
+ * function. Injected as a fake in tests so unit tests never launch a real
10
+ * browser; production wires `realResolvedJourneyRunner`.
11
+ */
12
+ export type RunResolvedJourney = (file: SharedJourneyFile, params: Record<string, string>, policy: RunPolicy) => Promise<JourneyRunResult>;
13
+ /**
14
+ * The real, Playwright-backed runner for a gated source Journey. Mirrors
15
+ * `journey-api.ts`'s `runJourneyProgrammatically` exactly:
16
+ * - validates params UP FRONT (`deriveParamSchema`/`validateParams`) before
17
+ * any browser launch, so bad params never pay the cost/risk of a browser;
18
+ * - opens a real `PlaywrightBrowserPort` scoped to the Journey's authorized
19
+ * origins (the gate has already proven `declaredOrigins ⊆ manifest`);
20
+ * - ALWAYS closes the session and removes the temp profile dir in `finally`
21
+ * (matches the just-fixed profile-dir-leak pattern).
22
+ * A `SharedJourneyFile` IS a `Journey` (+ `declaredOrigins`), so it runs
23
+ * through the standard `JourneyRunner` unchanged.
24
+ */
25
+ export declare const realResolvedJourneyRunner: RunResolvedJourney;
26
+ /**
27
+ * Injected seams for `jevitate source run`. Reuses the distributed-sources
28
+ * `SourceApiDeps` (sources clone dir + lock + local trust/ack stores + the
29
+ * injectable `git` port) and adds a runner seam. Every field is injectable so
30
+ * unit tests never touch the network, the real home dir, `git`, or a real
31
+ * browser.
32
+ */
33
+ export interface SourceRunApiDeps extends SourceApiDeps {
34
+ /** Runner seam; defaults to `realResolvedJourneyRunner` in production. */
35
+ runJourney?: RunResolvedJourney;
36
+ }
37
+ export interface RunSourceJourneyRequest {
38
+ sourceName: string;
39
+ journeyId: string;
40
+ params: Record<string, string>;
41
+ /** Defaults to `safeRunPolicy()` (Slice 1 fail-closed secret mode). */
42
+ policy?: RunPolicy;
43
+ }
44
+ /**
45
+ * Runs a Journey that lives in a trusted remote source — the ONE programmatic
46
+ * surface for `jevitate source run`. It NEVER bypasses the gate: it resolves
47
+ * the Journey through `@jevitate/sources`' real `resolveForRun`, which runs the
48
+ * full §9.8 "reviewed, pinned, in-origin, or refuse" ladder IN ORDER and THROWS
49
+ * a typed error on any failure (unknown source/id, hash mismatch/TOCTOU,
50
+ * untrusted-risky, undeclared origin, unacknowledged ToU, embedded secret).
51
+ * Only a `SharedJourneyFile` that passes EVERY gate reaches the runner seam.
52
+ *
53
+ * The clone is checked out to the EXACT recorded pin before the gate reads it,
54
+ * so the content the gate hashes is the content at the pin — not a drifted
55
+ * working tree (matches `trustJourney`).
56
+ */
57
+ export declare function runSourceJourney(deps: SourceRunApiDeps, req: RunSourceJourneyRequest): Promise<JourneyRunResult>;
@@ -0,0 +1,91 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { deriveParamSchema, validateParams } from "@jevitate/journey";
5
+ import { safeRunPolicy } from "@jevitate/domain";
6
+ import { PlaywrightBrowserPort } from "@jevitate/playwright";
7
+ import { CastActor, BrowseTheWeb } from "@jevitate/screenplay";
8
+ import { RecordingInterpreter } from "@jevitate/interpreter";
9
+ import { JourneyRunner } from "@jevitate/runtime";
10
+ import { FederatedJourneyRegistry, GitSourceManager, RemoteSource, UnknownSourceError, loadManifest, readLock, resolveForRun, } from "@jevitate/sources";
11
+ /**
12
+ * The real, Playwright-backed runner for a gated source Journey. Mirrors
13
+ * `journey-api.ts`'s `runJourneyProgrammatically` exactly:
14
+ * - validates params UP FRONT (`deriveParamSchema`/`validateParams`) before
15
+ * any browser launch, so bad params never pay the cost/risk of a browser;
16
+ * - opens a real `PlaywrightBrowserPort` scoped to the Journey's authorized
17
+ * origins (the gate has already proven `declaredOrigins ⊆ manifest`);
18
+ * - ALWAYS closes the session and removes the temp profile dir in `finally`
19
+ * (matches the just-fixed profile-dir-leak pattern).
20
+ * A `SharedJourneyFile` IS a `Journey` (+ `declaredOrigins`), so it runs
21
+ * through the standard `JourneyRunner` unchanged.
22
+ */
23
+ export const realResolvedJourneyRunner = async (file, params, policy) => {
24
+ validateParams(deriveParamSchema(file.recording), params);
25
+ // The browser allowlist is the Journey's authorized origin set — the base
26
+ // site plus every gate-approved declared origin.
27
+ const allowedOrigins = [...new Set([file.recording.site, ...file.declaredOrigins])];
28
+ const profileDir = await mkdtemp(join(tmpdir(), "jevitate-source-run-"));
29
+ const port = new PlaywrightBrowserPort();
30
+ const session = await port.open({
31
+ profileDir,
32
+ headless: true,
33
+ allowedOrigins,
34
+ baseUrl: file.recording.site,
35
+ });
36
+ try {
37
+ const actor = CastActor.named("source-runner").whoCan(new BrowseTheWeb(session, allowedOrigins));
38
+ const runner = new JourneyRunner(actor, new RecordingInterpreter());
39
+ return await runner.run({ journey: file, params, policy });
40
+ }
41
+ finally {
42
+ await session.close();
43
+ await rm(profileDir, { recursive: true, force: true });
44
+ }
45
+ };
46
+ /** Resolves a registered source's recorded pin, or throws `UnknownSourceError`
47
+ * (fail-closed) — a source the lock does not know about is never treated as
48
+ * empty/absent. Mirrors `source-api.ts`'s private `requireEntry`. */
49
+ async function requireEntry(lockPath, name) {
50
+ const lock = await readLock(lockPath);
51
+ const entry = lock.sources.find((s) => s.name === name);
52
+ if (!entry) {
53
+ throw new UnknownSourceError(`source '${name}' is not registered (run 'jevitate source add' first)`);
54
+ }
55
+ return entry;
56
+ }
57
+ /**
58
+ * Runs a Journey that lives in a trusted remote source — the ONE programmatic
59
+ * surface for `jevitate source run`. It NEVER bypasses the gate: it resolves
60
+ * the Journey through `@jevitate/sources`' real `resolveForRun`, which runs the
61
+ * full §9.8 "reviewed, pinned, in-origin, or refuse" ladder IN ORDER and THROWS
62
+ * a typed error on any failure (unknown source/id, hash mismatch/TOCTOU,
63
+ * untrusted-risky, undeclared origin, unacknowledged ToU, embedded secret).
64
+ * Only a `SharedJourneyFile` that passes EVERY gate reaches the runner seam.
65
+ *
66
+ * The clone is checked out to the EXACT recorded pin before the gate reads it,
67
+ * so the content the gate hashes is the content at the pin — not a drifted
68
+ * working tree (matches `trustJourney`).
69
+ */
70
+ export async function runSourceJourney(deps, req) {
71
+ // Unknown/unregistered source -> UnknownSourceError, before any run.
72
+ const entry = await requireEntry(deps.lockPath, req.sourceName);
73
+ const mgr = new GitSourceManager(deps.sourcesDir, deps.git);
74
+ // Pin the clone to the recorded commit before the gate reads it (fail-closed:
75
+ // `checkout` propagates if the pinned commit is absent — never HEAD).
76
+ await mgr.checkout(req.sourceName, entry.pinnedCommit);
77
+ const remote = new RemoteSource(req.sourceName, mgr.resolveDir(req.sourceName), entry.pinnedCommit);
78
+ const fed = new FederatedJourneyRegistry([remote], deps.trust);
79
+ const gateDeps = {
80
+ fed,
81
+ trust: deps.trust,
82
+ manifestFor: (source) => loadManifest(mgr.resolveDir(source)),
83
+ ackFor: (source) => deps.ack.get(source),
84
+ };
85
+ // The full trust boundary: throws on any refusal, returns the validated,
86
+ // ready-to-run file only when every gate passes.
87
+ const file = await resolveForRun(gateDeps, `${req.sourceName}/${req.journeyId}`);
88
+ const run = deps.runJourney ?? realResolvedJourneyRunner;
89
+ return run(file, req.params, req.policy ?? safeRunPolicy());
90
+ }
91
+ //# sourceMappingURL=source-run-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-run-api.js","sourceRoot":"","sources":["../src/source-run-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The `jevitate ui` local HTTP server — the human-facing side of the HITL
3
+ * inbox. SM4 (loopback is NOT a trust boundary) is load-bearing here:
4
+ * - bind 127.0.0.1 ONLY, on every path including the port-retry;
5
+ * - every request is Host-checked (S-A), then every `/api/*` request is
6
+ * capability-token-gated (S-B), before any routing happens;
7
+ * - `GET /` sets a strict CSP (`default-src 'self'`) so the SPA MUST load
8
+ * its JS from a same-origin `<script src="/app.js">` — no inline script;
9
+ * - never logs `req.url` (it carries `?t=<token>`), request bodies, or
10
+ * query strings — only `method <pathname> status`.
11
+ */
12
+ export interface StartUiServerDeps {
13
+ /** Inbox store directory (`~/.jevitate/inbox` in production). Screenshots
14
+ * are read from `<inboxDir>/screenshots/<id>.png`. */
15
+ inboxDir: string;
16
+ /** Accepted but ignored for the actual bind (SM4: 127.0.0.1 ONLY on every
17
+ * path); kept in the signature for forward compatibility. */
18
+ host?: string;
19
+ /** Explicit port. When set, `EADDRINUSE` REJECTS the promise (no drift).
20
+ * When unset, defaults to 4180 and retries the next port on conflict. */
21
+ port?: number;
22
+ /** Open the URL in the default browser once bound. Defaults to `true`.
23
+ * Tests should always pass `false`. Fire-and-forget: a failed opener
24
+ * (e.g. no `xdg-open` under WSL) never rejects `startUiServer`. */
25
+ open?: boolean;
26
+ /** Structured log sink: `method <pathname> status` lines only. */
27
+ logger?: (line: string) => void;
28
+ }
29
+ export interface UiServerHandle {
30
+ url: string;
31
+ token: string;
32
+ port: number;
33
+ close: () => Promise<void>;
34
+ }
35
+ export declare function startUiServer(deps: StartUiServerDeps): Promise<UiServerHandle>;