Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

`interfacectl` is a command-line tool for managing interface contracts in the Surfaces ecosystem. It validates, compares, and enforces compliance between defined interface contracts and actual implementation artifacts across multiple surfaces.

In an interactive terminal, running bare `interfacectl` with no arguments opens the first-run onboarding screen. In non-interactive contexts, bare `interfacectl` keeps the existing help output and exit behavior so scripts and CI remain stable.

## Generation-time gating

`interfacectl validate` is the canonical command for contract compliance. Use it to gate changes before merge or deployment. For deterministic, category-based exit codes, use `--exit-codes v2` or set `INTERFACECTL_EXIT_CODES=v2`. The command `enforce --mode fail` runs a structural diff and applies a policy threshold. It is optional and useful when you want to block on diff severity separately from compliance. For the minimal contract format and where contract semantics live in the repo, see [docs/contract-baseline.md](docs/contract-baseline.md).
Expand All @@ -26,7 +28,7 @@ interfacectl init [options]
- extracts UI-system attributes across typography, color, layout, motion, icons, shell/auth primitives, sections, and copy-role signals
- decides whether to adopt an existing design system or synthesize a first draft from repeated norms
- writes four artifacts under `contracts/generated/`
- runs `validate-extracted` plus contract validation and prints a short summary
- validates preview artifacts before write and, in interactive mode, asks for confirmation before writing anything

**Options:**

Expand All @@ -40,6 +42,7 @@ interfacectl init [options]
| `--surface-kind <marketing\|application\|unknown>` | Confirm low-confidence classification in non-interactive flows | inferred |
| `--auth-profile <name>` | Replay a saved browser-session auth profile for protected remote onboarding | none |
| `--non-interactive` | Disable prompts | `false` |
| `--continue-on-gate` | Allow provisional output when remote onboarding resolves to a login or access-denied page | `false` |
| `--out-dir <path>` | Output directory for generated artifacts | `contracts/generated` |
| `--analysis-out <path>` | Explicit output path for `<surface>.analysis.json` | derived from `--out-dir` |
| `--draft-out <path>` | Explicit output path for `<surface>.design-system.draft.json` | derived from `--out-dir` |
Expand All @@ -57,6 +60,7 @@ interfacectl init [options]
- First-run output is warn-first. Findings are surfaced in the summary rather than blocking the onboarding command unless artifact generation or validation infrastructure fails.
- If surface-kind inference is low confidence, interactive mode asks for confirmation. Non-interactive mode must pass `--surface-kind`.
- For protected remote URLs, `--auth-profile` must point at a replay-ready profile. Interactive `init` can capture one; non-interactive mode fails fast if the profile is missing, expired, legacy, or not replayable.
- If a remote URL resolves to a login or access-denied page, interactive mode stops and offers next actions before any artifacts are written. Non-interactive mode must pass `--continue-on-gate` to accept provisional output.
- First-party or dogfood surfaces are not used as baselines for inference or starter recommendations.

---
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ Install the CLI package as a development dependency:
pnpm add -D @surfaces/interfacectl-cli
```

In an interactive terminal, the first step is simply:

```bash
interfacectl
```

## Quick Start

For a first-time web surface experience, start with `init`:
In an interactive shell, `interfacectl` opens the terminal onboarding screen and immediately asks whether you want to inspect a local app root or a live URL.

For explicit command-driven onboarding, start with `init`:

```bash
interfacectl init --app-root apps/my-app --surface my-app
Expand All @@ -59,7 +67,7 @@ The CLI provides two first-run commands, browser-session auth helpers, and the v

### `init`

First-run onboarding for web surfaces. `init` analyzes either a local app root or a URL, drafts a first contract, writes a draft design-system artifact, runs validation, and prints a short onboarding summary grouped as adopted, normalized, flagged, and next steps.
First-run onboarding for web surfaces. `init` analyzes either a local app root or a URL, validates a draft contract and design-system artifact in preview first, and in interactive mode only writes artifacts after confirmation. If a remote URL resolves to a login or access-denied page, interactive mode stops and offers capture-auth / continue-anyway / switch-to-local-root choices before anything is written.

```bash
interfacectl init [options]
Expand All @@ -73,6 +81,7 @@ Outputs:
- `contracts/generated/<surface>.extraction.json`

Default behavior is warn-first. If surface-kind inference is low confidence, interactive mode asks for confirmation and non-interactive mode requires `--surface-kind marketing|application|unknown`.
Non-interactive URL onboarding fails fast on login/access-denied pages unless `--continue-on-gate` is supplied for provisional output.

### `analyze`

Expand All @@ -96,6 +105,7 @@ Notes:

- Profiles are exact-host scoped in v1.
- Remote analysis never silently falls back to anonymous access when `--auth-profile` is explicitly supplied.
- Interactive onboarding writes artifacts only after a preview confirmation step.
- If Chromium is missing locally, install it with `pnpm --filter @surfaces/interfacectl-cli exec playwright install chromium`.

### `validate`
Expand Down
2 changes: 1 addition & 1 deletion packages/interfacectl-cli/dist/commands/analyze.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/interfacectl-cli/dist/commands/analyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ export async function runAnalyzeCommand(options) {
await writeFile(outputPath, stringifyStableArtifact(result.analysis), "utf-8");
console.log(`Wrote analysis: ${outputPath}`);
console.log(`Inferred surface kind: ${result.analysis.classification.inferredKind} (${result.analysis.classification.confidence.toFixed(2)})`);
if (result.analysis.sourceHealth.status !== "ok") {
console.log(`Source access: ${result.analysis.sourceHealth.status} (${result.analysis.sourceHealth.confidence}) at ${result.analysis.sourceHealth.finalUrl ?? options.url}`);
}
if (result.analysis.classification.requiresConfirmation && !options.surfaceKind) {
console.log("Note: classification is low confidence; pass --surface-kind to confirm seeding intent.");
}
Expand Down
8 changes: 4 additions & 4 deletions packages/interfacectl-cli/dist/commands/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,7 @@ export async function runAuthTestCommand(options) {
storageState: inspection.storageState,
});
const ok = new URL(observation.finalUrl).hostname === inspection.profile.domain &&
!observation.loginDetected &&
!observation.accessDeniedDetected;
observation.sourceHealth.status === "ok";
if (options.format === "json") {
console.log(JSON.stringify({
ok,
Expand All @@ -177,16 +176,17 @@ export async function runAuthTestCommand(options) {
finalUrl: observation.finalUrl,
loginDetected: observation.loginDetected,
accessDeniedDetected: observation.accessDeniedDetected,
sourceHealth: observation.sourceHealth,
}, null, 2));
return ok ? 0 : 1;
}
if (!ok) {
console.error(`Auth replay failed for ${inspection.profile.name} (${inspection.profile.domain}).`);
console.error(`Final URL: ${observation.finalUrl}`);
if (observation.loginDetected) {
if (observation.sourceHealth.status === "login") {
console.error("The replayed session still resolved to a login page.");
}
if (observation.accessDeniedDetected) {
if (observation.sourceHealth.status === "access-denied") {
console.error("The replayed session resolved to an access-denied page.");
}
return 1;
Expand Down
15 changes: 4 additions & 11 deletions packages/interfacectl-cli/dist/commands/init.d.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { type AnalysisSourceMode, type WebSurfaceKind } from "../utils/first-run-analysis.js";
type ExtractMode = AnalysisSourceMode;
export interface InitOptions {
url?: string;
surface?: string;
surfaceName?: string;
surfaceKind?: WebSurfaceKind;
authProfile?: string;
extractMode?: ExtractMode;
appRoot?: string;
import { type InteractiveInitOptions } from "../utils/init-interactive.js";
export interface InitOptions extends InteractiveInitOptions {
nonInteractive?: boolean;
verbose?: boolean;
continueOnGate?: boolean;
outDir?: string;
analysisOut?: string;
draftOut?: string;
contractOut?: string;
reportOut?: string;
}
export declare function runInitCommand(options: InitOptions): Promise<number>;
export {};
//# sourceMappingURL=init.d.ts.map
2 changes: 1 addition & 1 deletion packages/interfacectl-cli/dist/commands/init.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading