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
60 changes: 60 additions & 0 deletions docs/plans/onboarding-init-phase-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Feature Plan

## Summary
Add a new `interfacectl init` onboarding command to bootstrap the first surface from either a remote URL or a local app root. The command introduces interactive prompts, optional non-interactive flags, browser-session auth profile capture metadata, and run artifact emission so onboarding contributes to the same run lineage model used by SurfaceOps. This work targets first-run usability without hardcoding any customer surface identifiers.

## Strategy alignment
This work strengthens the enforcement path by turning first extraction into a deterministic `Generation time` flow that produces contract/extraction artifacts and records violation/status signals immediately. It keeps onboarding inside contract-aware tooling and preserves existing `CI/CD time` checks by emitting schema-compatible artifacts consumed by current validation scripts.

Decision filter sentence
"When an agent proposes a change to the user experience, Surfaces determines whether that change is allowed, under what conditions it may proceed, and blocks or corrects it if it violates intent."

## Contract impact
1. Contract definition
No canonical contract schema changes. New onboarding output uses existing generated contract/extraction artifact shapes and existing run artifact schemas (`contract-runs.json`, `contract-lineage.json`).

2. Enforcement timing
- `Generation time`: `interfacectl init` produces the first generated artifacts and onboarding summary.
- `CI/CD time`: Existing `interfacectl validate*` and `validate-run-artifacts` continue to gate outputs without additional schema branching.
- `Runtime (edge)`: no change in this phase.

3. Violation handling
Invalid URL/inputs and unsupported mode combinations are blocked with non-zero exit and explicit errors. Extraction warnings are surfaced as `warn` status in emitted onboarding run entries.

## Surface impact
- `interfacectl`: primary implementation for first-surface onboarding and auth profile lifecycle.
- `surfaceops.ai`: consumes emitted artifacts/run lineage for visibility (no runtime enforcement change in this phase).
- `surfaces.systems`: unchanged contract semantics.

## Non-goals
- Full browser automation or cookie/token persistence.
- Runtime (edge) auth/session enforcement changes.
- Changing canonical contract authoring semantics.

## Acceptance criteria
1. `interfacectl init` exists and supports interactive onboarding prompts.
2. `interfacectl init --non-interactive` works with `--url` and optional `--surface`/`--auth-profile`.
3. `interfacectl init` writes generated contract and extraction report artifacts.
4. Onboarding writes run/lineage artifacts in existing schema with source `bootstrap`.
5. Protected URL path supports browser-session profile capture metadata and reuse checks.
6. `interfacectl auth list`, `interfacectl auth test`, and `interfacectl auth clear` commands are available.
7. No auth tokens/cookies are persisted to run artifacts.
8. Existing CLI tests still pass.

## Risks and drift checks
1. Risk: onboarding becomes observability-only (summary without artifacts).
Mitigation: require successful artifact write and run-record emit before exit 0.

2. Risk: auth handling drifts into insecure token persistence.
Mitigation: store opaque session references only; redact sensitive values in output.

3. Risk: onboarding bypasses contract validation paths.
Mitigation: keep output schema-compatible and rely on existing validation commands/scripts.

## Implementation outline
1. Add auth profile store utility with domain-scoped session metadata and TTL checks.
2. Add run artifact emitter utility reusing current artifact shapes.
3. Add `init` command with prompt/non-interactive input resolution.
4. Add `auth` subcommands (`list`, `test`, `clear`).
5. Add CLI tests for init/auth workflows.
6. Build and run package tests.
11 changes: 11 additions & 0 deletions packages/interfacectl-cli/dist/commands/auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface AuthCommandOptions {
profile?: string;
domain?: string;
all?: boolean;
format?: "text" | "json";
}
export declare function runAuthListCommand(): Promise<number>;
export declare function runAuthListCommandWithOptions(options: AuthCommandOptions): Promise<number>;
export declare function runAuthTestCommand(options: AuthCommandOptions): Promise<number>;
export declare function runAuthClearCommand(options: AuthCommandOptions): Promise<number>;
//# sourceMappingURL=auth.d.ts.map
1 change: 1 addition & 0 deletions packages/interfacectl-cli/dist/commands/auth.d.ts.map

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

92 changes: 92 additions & 0 deletions packages/interfacectl-cli/dist/commands/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { clearAuthProfiles, findAuthProfile, getAuthStorageMode, isProfileExpired, listAuthProfiles, } from "../utils/auth-profiles.js";
export async function runAuthListCommand() {
return runAuthListCommandWithOptions({});
}
export async function runAuthListCommandWithOptions(options) {
const profiles = await listAuthProfiles();
const storageMode = getAuthStorageMode();
if (options.format === "json") {
const payload = {
ok: true,
storageMode,
profiles: profiles.map((profile) => ({
...profile,
status: isProfileExpired(profile) ? "expired" : "active",
})),
};
console.log(JSON.stringify(payload, null, 2));
return 0;
}
if (profiles.length === 0) {
console.log(`No auth profiles found. storage=${storageMode}`);
return 0;
}
for (const profile of profiles) {
const status = isProfileExpired(profile) ? "expired" : "active";
console.log(`${profile.name} (${profile.domain}) mode=${profile.mode} status=${status} expires=${profile.expiresAt}`);
}
return 0;
}
export async function runAuthTestCommand(options) {
if (!options.profile) {
if (options.format === "json") {
console.log(JSON.stringify({ ok: false, error: "Missing --profile for auth test." }, null, 2));
return 1;
}
console.error("Missing --profile for auth test.");
return 1;
}
const profile = await findAuthProfile(options.profile, options.domain);
if (!profile) {
if (options.format === "json") {
console.log(JSON.stringify({ ok: false, error: `Auth profile not found: ${options.profile}` }, null, 2));
return 1;
}
console.error(`Auth profile not found: ${options.profile}`);
return 1;
}
if (isProfileExpired(profile)) {
if (options.format === "json") {
console.log(JSON.stringify({
ok: false,
error: `Auth profile expired: ${profile.name} (${profile.domain})`,
storageMode: getAuthStorageMode(),
profile: { ...profile, status: "expired" },
}, null, 2));
return 1;
}
console.error(`Auth profile expired: ${profile.name} (${profile.domain})`);
return 1;
}
if (options.format === "json") {
console.log(JSON.stringify({
ok: true,
storageMode: getAuthStorageMode(),
profile: { ...profile, status: "active" },
}, null, 2));
return 0;
}
console.log(`Auth profile OK: ${profile.name} (${profile.domain})`);
return 0;
}
export async function runAuthClearCommand(options) {
if (!options.all && !options.profile && !options.domain) {
if (options.format === "json") {
console.log(JSON.stringify({ ok: false, error: "Provide --all, --profile <name>, or --domain <domain>." }, null, 2));
return 1;
}
console.error("Provide --all, --profile <name>, or --domain <domain>.");
return 1;
}
const removed = await clearAuthProfiles({
all: options.all,
name: options.profile,
domain: options.domain,
});
if (options.format === "json") {
console.log(JSON.stringify({ ok: true, removed, storageMode: getAuthStorageMode() }, null, 2));
return 0;
}
console.log(`Removed ${removed} auth profile(s).`);
return 0;
}
14 changes: 14 additions & 0 deletions packages/interfacectl-cli/dist/commands/init.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type ExtractMode = "remote-url" | "local-root";
export interface InitOptions {
url?: string;
surface?: string;
surfaceName?: string;
authProfile?: string;
extractMode?: ExtractMode;
appRoot?: string;
nonInteractive?: boolean;
outDir?: string;
}
export declare function runInitCommand(options: InitOptions): Promise<number>;
export {};
//# sourceMappingURL=init.d.ts.map
1 change: 1 addition & 0 deletions 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