项目文件夹

文件
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

690 行
21 KiB
TypeScript

export type ModelCost = 'low' | 'medium' | 'high' | 'very_high';
export type ModelCapability = 'standard' | 'advanced' | 'best_quality';
export interface ModelMetadata {
cost?: ModelCost;
capability?: ModelCapability;
}
export interface AgentModelOption {
id: string;
label: string;
/** Whether the current account/tier can use this model. */
enabled?: boolean;
/** Whether this is the default model for the current account/tier. */
default?: boolean;
/** USD price per 1M input tokens when reported by the provider/catalog. */
inputPriceUsdPerMillion?: number;
/** USD price per 1M output tokens when reported by the provider/catalog. */
outputPriceUsdPerMillion?: number;
/** Provider/catalog-owned model picker metadata. */
metadata?: ModelMetadata;
}
/**
* A typed "what should the UI do to fix this" intent attached to an
* {@link AgentDiagnostic}. The UI renders a button per intent and owns the
* concrete handler (open a URL, re-run detection, write an env override,
* launch the OAuth terminal flow). Keeping the intent typed — rather than a
* pre-baked button label + URL — means the Settings card, the unavailable
* grid, and (PR-B) the `od agent healthcheck` CLI / health-check panel all
* render the same fix affordances from one source of truth instead of each
* re-deriving copy and wiring.
*/
export type AgentFixIntent =
/** Open the agent's configuration / auth docs (`AgentInfo.docsUrl`). */
| { kind: 'openDocs' }
/** Open the agent's install / download page (`AgentInfo.installUrl`). */
| { kind: 'openInstall' }
/** Re-run agent detection (the Settings "Rescan" affordance). */
| { kind: 'rescan' }
/**
* Prompt the user to point Open Design at an explicit binary by writing
* `envKey` (e.g. `CURSOR_AGENT_BIN`) into `agentCliEnv`. Used when the CLI
* is installed somewhere PATH detection can't reach.
*/
| { kind: 'setEnv'; envKey: string }
/** Clear a previously-set binary override so detection falls back to PATH. */
| { kind: 'clearEnv'; envKey: string }
/**
* Launch the agent's interactive sign-in in a system terminal (today only
* Antigravity's `agy`, via POST /api/agents/:id/oauth-launch).
*/
| { kind: 'launchOAuth'; agentId: string };
/**
* Why a CLI agent is unavailable or only partially usable, in a shape the UI
* can render as "one-line reason + fix button(s)" instead of a silent grey
* card. Emitted by daemon detection (PATH / executable resolution + the auth
* probe) and reused by the connection-test / health-check surfaces so a
* failure is always actionable.
*/
export type AgentDiagnosticReason =
/** The binary (and any fallback names) was not found on PATH. */
| 'not-on-path'
/** A file matched but is not executable (missing +x / wrong PATHEXT). */
| 'not-executable'
/** A wrapper/shim was found but its target is gone (exit 126/127). */
| 'shim-broken'
/** A user-set `*_BIN` override points at a missing/invalid file. */
| 'configured-bin-invalid'
/** Installed and invocable, but the CLI is not authenticated. */
| 'auth-missing'
/** Installed, but auth status could not be verified. */
| 'auth-unknown';
export type AgentDiagnosticSeverity = 'error' | 'warning' | 'info';
export interface AgentDiagnostic {
reason: AgentDiagnosticReason;
severity: AgentDiagnosticSeverity;
/** Short, human-readable, single-sentence explanation. */
message: string;
/** Optional longer context (e.g. the probe's stderr tail). */
detail?: string;
/**
* Directories PATH detection searched, surfaced verbatim for the
* `not-on-path` case so the user can see where we looked before being
* asked to set an explicit binary path. Sourced from the daemon resolver,
* never recomputed in the client.
*/
searchedDirs?: string[];
/** Ordered fix affordances the UI should offer for this diagnostic. */
fixActions?: AgentFixIntent[];
}
export interface AgentInfo {
id: string;
name: string;
bin: string;
available: boolean;
authStatus?: 'ok' | 'missing' | 'unknown';
authMessage?: string;
path?: string;
version?: string | null;
/**
* Actionable reasons this agent is unavailable or only partially usable,
* each carrying typed fix intents. Empty / omitted means "healthy"
* (available and, where probed, authenticated).
*/
diagnostics?: AgentDiagnostic[];
models?: AgentModelOption[];
/** Whether models came from the installed CLI or Open Design's static fallback. */
modelsSource?: 'live' | 'fallback';
reasoningOptions?: AgentModelOption[];
/** HTTPS URL to install or download the CLI (vendor docs, GitHub README, npm). */
installUrl?: string;
/** Optional HTTPS URL for configuration / auth / usage docs. */
docsUrl?: string;
/**
* How the daemon forwards the user's `.od/mcp-config.json` external MCP
* servers to this runtime at spawn time. Mirrors the field on
* `RuntimeAgentDef` in the daemon. Undefined means the runtime has no
* native MCP transport wired yet, in which case the settings UI surfaces
* a "configure MCP in the agent's own config file" hint instead of
* silently dropping the servers (issue #2142).
*/
externalMcpInjection?:
| 'claude-mcp-json'
| 'acp-merge'
| 'opencode-env-content';
/**
* When `false`, the Settings model picker hides the "Custom (fill below)"
* option and the free-text input. Use this for agents whose CLI doesn't
* accept a model id (e.g. Antigravity `agy` has no `--model` flag yet —
* upstream issue #35) or rejects free-form ids (AMR validates against the
* live Vela catalog). Undefined === allow, matching the historical UX.
*/
supportsCustomModel?: boolean;
}
export interface AgentsResponse {
agents: AgentInfo[];
}
export type AmrModelsSource = 'preset' | 'remote';
export interface AmrModelsResponse {
source: AmrModelsSource;
models: AgentModelOption[];
refreshing: boolean;
stale?: boolean;
remoteError?: string;
}
export type SkillSource = 'built-in' | 'user';
export interface SkillSummary {
id: string;
name: string;
displayName?: Record<string, string>;
description: string;
descriptionI18n?: Record<string, string>;
triggers: string[];
mode:
| 'prototype'
| 'deck'
| 'template'
| 'design-system'
| 'image'
| 'video'
| 'audio';
surface?: 'web' | 'image' | 'video' | 'audio';
platform?: 'desktop' | 'mobile' | null;
scenario?: string | null;
// Optional human-readable category (e.g. "image-generation", "video",
// "design-systems"). Surfaced as a filter pill in Settings → Skills so a
// large pre-loaded catalogue stays scannable. Free-form lowercase slug;
// not part of system-prompt composition.
category?: string | null;
// Origin of the skill: 'built-in' lives under the repo's `skills/`
// directory and cannot be deleted from the UI; 'user' lives under
// `<runtimeData>/user-skills/` and is fully owned by the user (delete
// / re-import allowed). New `import` endpoint always tags `user`.
source?: SkillSource;
previewType: string;
designSystemRequired: boolean;
defaultFor: string[];
upstream: string | null;
featured?: number | null;
fidelity?: 'wireframe' | 'high-fidelity' | null;
speakerNotes?: boolean | null;
animations?: boolean | null;
craftRequires?: string[];
hasBody: boolean;
examplePrompt: string;
examplePromptI18n?: Record<string, string>;
// True when this skill exists only to group derived `<parent>:<child>`
// example cards. The Examples gallery hides such cards because their
// preview would duplicate one of the derived cards and add no extra
// information, but the entry stays in the listing so `findSkillById`
// resolves the parent for system-prompt composition and "Use this
// prompt" fast-create on a derived card still composes the parent's
// SKILL.md body.
aggregatesExamples: boolean;
}
// Body shape for POST /api/skills/import. The daemon turns this into a
// SKILL.md under `<runtimeData>/user-skills/<slug>/` and surfaces the
// freshly-listed summary in the response.
export interface SkillImportRequest {
name: string;
description?: string;
body: string;
triggers?: string[];
}
export interface SkillImportResponse {
skill: SkillSummary;
}
// Body for PUT /api/skills/:id — update an existing skill's SKILL.md.
// The route param resolves to the canonical skill id; the daemon refuses
// updates whose body `name` differs from that id (rename = delete +
// re-import).
export interface SkillUpdateRequest {
name?: string;
description?: string;
body: string;
triggers?: string[];
}
export interface SkillUpdateResponse {
skill: SkillSummary;
}
// Returned by GET /api/skills/:id/files — the on-disk file tree under
// the skill's directory, capped to a small number of entries to keep
// the payload bounded. Used by the Settings → Skills detail panel.
export interface SkillFileEntry {
path: string;
kind: 'file' | 'directory';
size: number | null;
}
export interface SkillFilesResponse {
files: SkillFileEntry[];
}
export interface SkillDetail extends SkillSummary {
body: string;
}
export interface SkillsResponse {
skills: SkillSummary[];
}
export interface SkillResponse {
skill: SkillDetail;
}
// Design templates share the SkillSummary/Detail shape (same SKILL.md
// frontmatter, same preview behavior) but live under a separate registry
// root so the EntryView Templates surface and the Settings → Skills surface
// stay decoupled. See specs/current/skills-and-design-templates.md.
export type DesignTemplateSummary = SkillSummary;
export type DesignTemplateDetail = SkillDetail;
export interface DesignTemplatesResponse {
designTemplates: DesignTemplateSummary[];
}
export interface DesignTemplateResponse {
designTemplate: DesignTemplateDetail;
}
export interface DesignSystemSummary {
id: string;
title: string;
category: string;
summary: string;
swatches?: string[];
surface?: 'web' | 'image' | 'video' | 'audio';
source?: 'built-in' | 'installed' | 'user';
status?: 'draft' | 'published';
isEditable?: boolean;
createdAt?: string;
updatedAt?: string;
provenance?: DesignSystemProvenance;
projectId?: string;
}
export interface DesignSystemDetail extends DesignSystemSummary {
body: string;
packageInfo?: DesignSystemPackageInfo;
}
export interface DesignSystemPackageInfo {
manifest?: {
schemaVersion: string;
id: string;
name: string;
category: string;
source?: {
type?: string;
url?: string;
path?: string;
branch?: string;
commit?: string;
importedAt?: string;
// shadcn registry imports (source.type === 'shadcn').
reference?: string;
registryUrl?: string;
item?: string;
homepage?: string;
};
files?: {
design?: string;
tokens?: string;
designTokens?: string;
tailwind?: string;
components?: string;
};
usage?: string;
componentsManifest?: string;
importMode?: string;
craft?: {
applies?: string[];
suggested?: string[];
exemptions?: string[];
};
fonts?: Array<{ family?: string; weight?: string | number; style?: string; file?: string }>;
preview?: {
dir?: string;
pages?: Array<{ path?: string; role?: string; title?: string }>;
};
sourceFiles?: {
scanned?: string;
evidence?: string;
tokens?: string;
report?: string;
snippets?: string;
};
assetsDir?: string;
};
/** Package-relative files the daemon confirmed exist and can be served via /static. */
availableFiles?: string[];
sourceEvidence?: {
scannedFileCount?: number;
tokenCount?: number;
snippetCount?: number;
confidence?: Record<string, string | number>;
evidenceExcerpt?: string;
tokenContract?: {
contract?: string;
grade?: DesignSystemTokenContractGrade;
score?: number;
recommendRebuild?: boolean;
sourceBackedA1?: number;
requiredA1?: number;
fallbackTokens?: number;
selfCheckOk?: boolean;
};
};
}
export interface DesignSystemsResponse {
designSystems: DesignSystemSummary[];
}
export interface DesignSystemResponse {
designSystem: DesignSystemDetail;
}
export interface DesignSystemProvenance {
companyBlurb?: string;
sourceUrls?: string[];
githubUrls?: string[];
localCodeFiles?: string[];
figFiles?: string[];
assetFiles?: string[];
notes?: string;
sourceNotes?: string;
}
export type DesignSystemFileKind =
| 'folder'
| 'page'
| 'stylesheet'
| 'document'
| 'image'
| 'data'
| 'asset';
export interface DesignSystemFileSummary {
path: string;
name: string;
kind: DesignSystemFileKind;
size?: number;
updatedAt?: string;
}
export interface DesignSystemFileDetail extends DesignSystemFileSummary {
content: string;
}
export interface DesignSystemFilesResponse {
files: DesignSystemFileSummary[];
}
export interface DesignSystemFileResponse {
file: DesignSystemFileDetail;
}
export interface DesignSystemWorkspaceResponse {
project: import('./projects.js').Project;
files: import('./files.js').ProjectFile[];
}
export type DesignSystemRevisionStatus = 'pending' | 'accepted' | 'rejected';
export interface DesignSystemRevision {
id: string;
designSystemId: string;
status: DesignSystemRevisionStatus;
feedback: string;
baseBody: string;
proposedBody: string;
createdAt: string;
updatedAt: string;
sectionTitle?: string;
jobId?: string;
fileChanges?: DesignSystemRevisionFileChange[];
}
export interface DesignSystemRevisionFileChange {
path: string;
baseContent: string;
proposedContent: string;
}
export interface DesignSystemRevisionsResponse {
revisions: DesignSystemRevision[];
}
export interface DesignSystemRevisionResponse {
revision: DesignSystemRevision;
}
export type DesignSystemGenerationJobStatus =
| 'queued'
| 'running'
| 'succeeded'
| 'failed';
export type DesignSystemGenerationStepStatus =
| 'pending'
| 'running'
| 'succeeded'
| 'failed';
export interface DesignSystemGenerationStep {
id: string;
title: string;
status: DesignSystemGenerationStepStatus;
message?: string;
startedAt?: string;
completedAt?: string;
}
export interface DesignSystemGenerationJob {
id: string;
kind?: 'generation' | 'revision' | 'token-contract-rebuild';
status: DesignSystemGenerationJobStatus;
progress: number;
steps: DesignSystemGenerationStep[];
createdAt: string;
updatedAt: string;
completedAt?: string;
designSystemId?: string;
revisionId?: string;
error?: string;
message?: string;
}
export interface DesignSystemGenerationJobResponse {
job: DesignSystemGenerationJob;
}
export type DesignSystemPackageAuditSeverity = 'error' | 'warning';
export interface DesignSystemPackageAuditIssue {
severity: DesignSystemPackageAuditSeverity;
code: string;
message: string;
path?: string;
}
export interface DesignSystemPackageAudit {
ok: boolean;
projectPath: string;
filesInspected: number;
errors: DesignSystemPackageAuditIssue[];
warnings: DesignSystemPackageAuditIssue[];
}
export interface DesignSystemPackageAuditResponse {
audit: DesignSystemPackageAudit;
}
export interface DesignSystemRevisionJobRequest {
feedback: string;
sectionTitle?: string;
body?: string;
}
export type DesignSystemTokenContractGrade =
| 'excellent'
| 'usable'
| 'needs-review'
| 'needs-rebuild';
export interface DesignSystemTokenContractRebuildDecision {
designSystemId: string;
available: boolean;
recommended: boolean;
forced: boolean;
reason: string;
triggers: string[];
reportPath?: string;
grade?: DesignSystemTokenContractGrade;
score?: number;
sourceBackedA1?: number;
requiredA1?: number;
fallbackTokens?: number;
selfCheckOk?: boolean;
weakTokens?: Array<{
name: string;
layer?: string;
confidence: string;
reason: string;
sources: string[];
}>;
}
export interface DesignSystemTokenContractRebuildJobRequest {
force?: boolean;
}
export interface DesignSystemTokenContractRebuildJobResponse {
decision: DesignSystemTokenContractRebuildDecision;
job?: DesignSystemGenerationJob;
}
export interface ImportLocalDesignSystemRequest {
/** Absolute local project directory selected by the user. */
baseDir: string;
/** Optional display name override for the generated design-system project. */
name?: string;
/** Import structure mode. Defaults to hybrid for real project imports. */
importMode?: 'normalized' | 'hybrid' | 'verbatim';
/** Craft sections that should actively apply when this system is used. */
craftApplies?: string[];
}
export interface ImportLocalDesignSystemResponse {
designSystem: DesignSystemSummary;
tokenContractRebuild?: DesignSystemTokenContractRebuildJobResponse;
}
export interface ImportGitHubDesignSystemRequest {
/** Public GitHub repository URL, e.g. https://github.com/owner/repo. */
githubUrl: string;
/** Optional branch to clone. Defaults to the repository default branch. */
branch?: string;
/** Optional display name override for the generated design-system project. */
name?: string;
/** Import structure mode. Defaults to hybrid for real project imports. */
importMode?: 'normalized' | 'hybrid' | 'verbatim';
/** Craft sections that should actively apply when this system is used. */
craftApplies?: string[];
}
export interface ImportGitHubDesignSystemResponse {
designSystem: DesignSystemSummary;
tokenContractRebuild?: DesignSystemTokenContractRebuildJobResponse;
}
export interface ImportShadcnDesignSystemRequest {
/**
* shadcn registry item reference. Accepts either the shadcn CLI
* shorthand `<owner>/<repo>/<item>` (optionally suffixed with
* `#<branch|tag|sha>`), which is resolved against the repository's
* root `registry.json` on GitHub, or a direct `https://…/<item>.json`
* URL pointing at a registry-item document. `http://` is accepted only
* for loopback hosts (localhost / 127.0.0.1) so a self-hosted local
* registry can be imported.
*/
reference: string;
/** Optional display name override for the generated design-system project. */
name?: string;
/** Import structure mode. Defaults to hybrid for real project imports. */
importMode?: 'normalized' | 'hybrid' | 'verbatim';
/** Craft sections that should actively apply when this system is used. */
craftApplies?: string[];
}
export interface ImportShadcnDesignSystemResponse {
designSystem: DesignSystemSummary;
tokenContractRebuild?: DesignSystemTokenContractRebuildJobResponse;
}
export interface HealthResponse {
ok: true;
service?: 'daemon';
version?: string;
}
// A pet packaged by the upstream Codex `hatch-pet` skill. Each pet is a
// folder under `${CODEX_HOME:-$HOME/.codex}/pets/<id>/` that contains a
// `pet.json` manifest and a `spritesheet.<png|webp>` atlas. The daemon
// surfaces these so the web pet settings can offer one-click adoption
// of recently-hatched pets without asking the user to re-upload the
// file by hand.
export interface CodexPetSummary {
id: string;
displayName: string;
description: string;
// URL on the daemon that serves the raw spritesheet bytes.
spritesheetUrl: string;
// File extension reported by the on-disk spritesheet (png / webp /
// gif). Useful only as a hint to the client renderer.
spritesheetExt: string;
// Unix milliseconds for the spritesheet file's mtime — lets the
// client sort "most recently hatched" without re-listing.
hatchedAt: number;
// True when the pet ships in the repo under `assets/community-pets/`
// rather than the user's `~/.codex/pets/`. Surfaced so the UI can
// tag the card with a small "Bundled" pill and avoid prompting the
// user to re-sync something that is already on disk.
bundled?: boolean;
}
export interface CodexPetsResponse {
pets: CodexPetSummary[];
// Absolute path of the directory we scanned. Surfaced so the UI can
// tell the user where their pets live (and where to look if a pet
// they expect is missing).
rootDir: string;
}
// Body for `POST /api/codex-pets/sync` — triggers the daemon-side port
// of `scripts/sync-community-pets.ts`. Both fields are optional so the
// default call (`syncCommunityPets({})`) downloads every catalog and
// skips pets that already exist on disk.
export interface SyncCommunityPetsRequest {
// Which catalog(s) to download. Defaults to 'all'.
source?: 'all' | 'petshare' | 'hatchery';
// Re-download pets that already have a folder on disk.
force?: boolean;
}
// Daemon response after a community sync. Matches the script's stdout
// summary so the web UI can show the same "wrote/skipped/failed" line.
export interface SyncCommunityPetsResponse {
wrote: number;
skipped: number;
failed: number;
total: number;
rootDir: string;
// Up to ~10 surfaced error messages (the daemon log keeps the rest).
errors: string[];
}
export type InstallInput =
| { source: 'github'; url: string }
| { source: 'local'; path: string };
export interface InstallSkillResponse {
skill: SkillSummary;
}
export interface InstallDesignSystemResponse {
designSystem: DesignSystemSummary;
}
export interface UninstallResponse {
ok: true;
}