Let plugins describe header actions, search, tabs, selects, and empty states through mount context shell.set(), and render them with the host UI.
565 lines
18 KiB
TypeScript
565 lines
18 KiB
TypeScript
export declare const PLUGIN_API_VERSION: "2";
|
|
export declare const THEME_API_VERSION: "1";
|
|
export declare const PLUGIN_PERMISSIONS: readonly ["notes:read", "notes:write", "notes:delete", "metadata:read", "metadata:write", "resources:read", "resources:write", "templates:read", "templates:write", "network", "network:public", "ai:generate", "storage", "secrets", "schedules", "editor:read", "editor:write", "ui:commands", "ui:navigation", "ui:notices", "ui:panels", "ui:embeds"];
|
|
export type PluginPermission = (typeof PLUGIN_PERMISSIONS)[number];
|
|
export type ExtensionPlatform = "web" | "desktop" | "android" | "ios";
|
|
export interface PluginManifest {
|
|
type: "plugin";
|
|
id: string;
|
|
name: string;
|
|
version: string;
|
|
apiVersion: typeof PLUGIN_API_VERSION;
|
|
/** Plugins must delegate ordinary persistent configuration to EdgeEver. */
|
|
settingsUi: "host";
|
|
description?: string;
|
|
author?: string;
|
|
entry: string;
|
|
platforms?: ExtensionPlatform[];
|
|
permissions: PluginPermission[];
|
|
networkHosts?: string[];
|
|
settings?: PluginSettingsSchema;
|
|
}
|
|
export interface PluginSettingListItem {
|
|
title: string;
|
|
description?: string;
|
|
}
|
|
export interface PluginSettingList {
|
|
title?: string;
|
|
actionLabel?: string;
|
|
items: PluginSettingListItem[];
|
|
}
|
|
/**
|
|
* Declarative setting metadata. EdgeEver owns the layout, controls, validation,
|
|
* state feedback, and responsive behavior; plugins cannot supply presentation code or styles.
|
|
*/
|
|
interface PluginSettingBase {
|
|
key: string;
|
|
label: string;
|
|
description?: string;
|
|
required?: boolean;
|
|
/** Host-rendered read-only items, opened from a small entry next to the field. */
|
|
list?: PluginSettingList;
|
|
}
|
|
export type PluginSettingField = (PluginSettingBase & {
|
|
type: "text";
|
|
default?: string;
|
|
placeholder?: string;
|
|
}) | (PluginSettingBase & {
|
|
type: "secret";
|
|
placeholder?: string;
|
|
}) | (PluginSettingBase & {
|
|
type: "number";
|
|
default?: number;
|
|
min?: number;
|
|
max?: number;
|
|
step?: number;
|
|
}) | (PluginSettingBase & {
|
|
type: "boolean";
|
|
default?: boolean;
|
|
}) | (PluginSettingBase & {
|
|
type: "select";
|
|
default?: string;
|
|
options: Array<{
|
|
value: string;
|
|
label: string;
|
|
}>;
|
|
});
|
|
export interface PluginSettingsSchema {
|
|
/** Host-rendered fields in display order. Arbitrary UI markup and styling are intentionally unsupported. */
|
|
fields: PluginSettingField[];
|
|
}
|
|
export type PluginSettingValue = string | number | boolean;
|
|
export declare const PLUGIN_API_ERROR_CODES: readonly ["NOTE_CONFLICT", "RESOURCE_CONFLICT", "INVALID_MARKDOWN_EDIT"];
|
|
export type PluginApiErrorCode = (typeof PLUGIN_API_ERROR_CODES)[number];
|
|
export interface PluginApiError extends Error {
|
|
code: PluginApiErrorCode;
|
|
}
|
|
export declare const THEME_TOKEN_NAMES: readonly ["color.background", "color.surface", "color.surfaceMuted", "color.text", "color.textMuted", "color.border", "color.accent", "color.accentForeground", "color.success", "color.warning", "color.danger", "font.body", "font.mono", "font.size", "lineHeight.body", "radius.medium", "density.scale", "editor.contentWidth"];
|
|
export type ThemeTokenName = (typeof THEME_TOKEN_NAMES)[number];
|
|
export type ThemeTokens = Partial<Record<ThemeTokenName, string>>;
|
|
export interface ThemeManifest {
|
|
type: "theme";
|
|
id: string;
|
|
name: string;
|
|
version: string;
|
|
themeApiVersion: typeof THEME_API_VERSION;
|
|
description?: string;
|
|
author?: string;
|
|
modes: Array<"light" | "dark">;
|
|
light: ThemeTokens;
|
|
dark?: ThemeTokens;
|
|
}
|
|
export type ExtensionManifest = PluginManifest | ThemeManifest;
|
|
export declare const MARKETPLACE_REGISTRY_VERSION: "1";
|
|
export interface MarketplaceEntry {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
author: string;
|
|
publisher?: "edgeever";
|
|
category: string;
|
|
repositoryUrl: string;
|
|
distribution: {
|
|
type: "github";
|
|
repositoryUrl: string;
|
|
} | {
|
|
type: "manifest";
|
|
manifestUrl: string;
|
|
};
|
|
verification: {
|
|
version: string;
|
|
checksums?: {
|
|
manifestJson?: string;
|
|
mainJs?: string;
|
|
stylesCss?: string;
|
|
};
|
|
};
|
|
}
|
|
export interface MarketplaceRegistry {
|
|
registryVersion: typeof MARKETPLACE_REGISTRY_VERSION;
|
|
updatedAt: string;
|
|
entries: MarketplaceEntry[];
|
|
}
|
|
export interface PluginNoteSummary {
|
|
id: string;
|
|
notebookId: string;
|
|
title: string | null;
|
|
excerpt: string;
|
|
tags: string[];
|
|
isPinned: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
export interface PluginNote extends PluginNoteSummary {
|
|
revision: number;
|
|
contentMarkdown: string;
|
|
contentText: string;
|
|
contentHash: string;
|
|
}
|
|
/**
|
|
* A replacement range in a note's Markdown source. Offsets use JavaScript
|
|
* UTF-16 string indices and ranges are half-open: [from, to).
|
|
*/
|
|
export interface PluginMarkdownEdit {
|
|
from: number;
|
|
to: number;
|
|
insert: string;
|
|
}
|
|
export interface PluginMarkdownEditInput {
|
|
expectedRevision: number;
|
|
expectedContentHash: string;
|
|
edits: PluginMarkdownEdit[];
|
|
}
|
|
export interface PluginNoteQuery {
|
|
notebookId?: string;
|
|
text?: string;
|
|
tags?: string[];
|
|
sort?: "updated-desc" | "created-desc" | "title-asc";
|
|
limit?: number;
|
|
offset?: number;
|
|
}
|
|
export interface PluginNoteCreateInput {
|
|
notebookId: string;
|
|
title?: string;
|
|
contentMarkdown?: string;
|
|
tags?: string[];
|
|
}
|
|
export interface PluginNoteUpdateInput {
|
|
title?: string;
|
|
contentMarkdown?: string;
|
|
tags?: string[];
|
|
}
|
|
export interface PluginNoteQueryResult {
|
|
notes: PluginNoteSummary[];
|
|
totalCount: number;
|
|
nextOffset: number | null;
|
|
}
|
|
export interface PluginNoteContentQueryResult {
|
|
notes: PluginNote[];
|
|
totalCount: number;
|
|
nextOffset: number | null;
|
|
}
|
|
export interface PluginNotebook {
|
|
id: string;
|
|
parentId: string | null;
|
|
name: string;
|
|
memoCount: number;
|
|
}
|
|
export interface PluginNoteRevision {
|
|
id: string;
|
|
noteId: string;
|
|
revision: number;
|
|
title: string | null;
|
|
tags: string[];
|
|
contentMarkdown: string;
|
|
contentText: string;
|
|
createdAt: string;
|
|
}
|
|
export interface PluginResource {
|
|
id: string;
|
|
noteId: string;
|
|
kind: "image" | "attachment";
|
|
mimeType: string | null;
|
|
filename: string | null;
|
|
byteSize: number;
|
|
contentHash: string | null;
|
|
width: number | null;
|
|
height: number | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
url: string;
|
|
}
|
|
export interface PluginTag {
|
|
name: string;
|
|
noteCount: number;
|
|
}
|
|
export interface PluginTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
title: string | null;
|
|
contentMarkdown: string;
|
|
tags: string[];
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
export type PluginEventMap = {
|
|
"settings.changed": {
|
|
key: string;
|
|
};
|
|
"note.created": {
|
|
note: PluginNote;
|
|
};
|
|
"note.updated": {
|
|
note: PluginNote;
|
|
};
|
|
"note.deleted": {
|
|
noteId: string;
|
|
};
|
|
"tag.changed": {
|
|
previousName?: string;
|
|
name?: string;
|
|
deleted?: boolean;
|
|
};
|
|
"template.created": {
|
|
template: PluginTemplate;
|
|
};
|
|
"template.updated": {
|
|
template: PluginTemplate;
|
|
};
|
|
"template.deleted": {
|
|
templateId: string;
|
|
};
|
|
"resource.created": {
|
|
resource: PluginResource;
|
|
};
|
|
"resource.updated": {
|
|
resource: PluginResource;
|
|
};
|
|
"resource.deleted": {
|
|
resourceId: string;
|
|
};
|
|
"workspace.sync-queue-changed": Record<string, never>;
|
|
"workspace.synced": {
|
|
bootstrapped: boolean;
|
|
changed: number;
|
|
};
|
|
};
|
|
export interface PluginCommand {
|
|
id: string;
|
|
title: string;
|
|
run: () => void | Promise<void>;
|
|
}
|
|
export type PluginScheduleMissedRunPolicy = "run-once" | "skip";
|
|
export interface PluginScheduleInput {
|
|
/** Stable plugin-local identifier. Reusing it updates the same persistent schedule. */
|
|
key: string;
|
|
name: string;
|
|
commandId: string;
|
|
cronExpression: string;
|
|
timezone?: string;
|
|
missedRunPolicy?: PluginScheduleMissedRunPolicy;
|
|
isEnabled?: boolean;
|
|
}
|
|
export interface PluginSchedule extends Required<Omit<PluginScheduleInput, "timezone" | "missedRunPolicy" | "isEnabled">> {
|
|
timezone: string;
|
|
missedRunPolicy: PluginScheduleMissedRunPolicy;
|
|
isEnabled: boolean;
|
|
runsOnThisDevice: boolean;
|
|
lastRun: {
|
|
status: "running" | "succeeded" | "failed";
|
|
scheduledFor: string;
|
|
startedAt: string;
|
|
finishedAt: string | null;
|
|
errorMessage: string | null;
|
|
} | null;
|
|
}
|
|
export interface PluginEditorSelection {
|
|
noteId: string;
|
|
from: number;
|
|
to: number;
|
|
empty: boolean;
|
|
text: string;
|
|
contentMarkdown: string;
|
|
}
|
|
export interface PluginEditorDocument {
|
|
noteId: string;
|
|
contentMarkdown: string;
|
|
hasUnsavedChanges: boolean;
|
|
}
|
|
export interface PluginOpenNoteOptions {
|
|
/** Opens in-note search and reveals the first exact text match. */
|
|
search?: string;
|
|
}
|
|
export type PluginJsonValue = null | boolean | number | string | PluginJsonValue[] | {
|
|
[key: string]: PluginJsonValue;
|
|
};
|
|
export type PluginPanelPresentation = "dialog" | "fullscreen";
|
|
export type PluginPanelPurpose = "workflow" | "dashboard" | "preview" | "onboarding";
|
|
export interface PluginPanelOpenOptions {
|
|
state?: PluginJsonValue;
|
|
}
|
|
export type PluginPanelActionVariant = "default" | "primary" | "ghost";
|
|
export interface PluginPanelAction {
|
|
id: string;
|
|
label: string;
|
|
variant?: PluginPanelActionVariant;
|
|
disabled?: boolean;
|
|
}
|
|
export interface PluginPanelSelectOption {
|
|
value: string;
|
|
label: string;
|
|
}
|
|
export type PluginPanelToolbarItem = {
|
|
type: "search";
|
|
key: string;
|
|
placeholder?: string;
|
|
value?: string;
|
|
} | {
|
|
type: "tabs";
|
|
key: string;
|
|
value?: string;
|
|
options: PluginPanelSelectOption[];
|
|
} | {
|
|
type: "select";
|
|
key: string;
|
|
label?: string;
|
|
value?: string;
|
|
options: PluginPanelSelectOption[];
|
|
} | {
|
|
type: "button";
|
|
key: string;
|
|
label: string;
|
|
variant?: PluginPanelActionVariant;
|
|
disabled?: boolean;
|
|
};
|
|
export interface PluginPanelEmptyState {
|
|
title: string;
|
|
description?: string;
|
|
action?: PluginPanelAction;
|
|
}
|
|
/**
|
|
* Host-rendered panel chrome. Plugins describe intent; EdgeEver owns layout and controls.
|
|
* Callbacks stay in-memory and are not serialized with panel open state.
|
|
*/
|
|
export interface PluginPanelChrome {
|
|
header?: {
|
|
title?: string;
|
|
/** Pass `null` to hide the host's default panel description. */
|
|
description?: string | null;
|
|
actions?: PluginPanelAction[];
|
|
};
|
|
toolbar?: PluginPanelToolbarItem[];
|
|
empty?: PluginPanelEmptyState | null;
|
|
onAction?: (id: string) => void;
|
|
onChange?: (key: string, value: string) => void;
|
|
}
|
|
export interface PluginPanelShell {
|
|
set(chrome: PluginPanelChrome): void;
|
|
}
|
|
export interface PluginPanelMountContext {
|
|
state: PluginJsonValue | null;
|
|
requestClose(): Promise<void>;
|
|
/** Host-rendered header, toolbar, and empty state. `set` is a no-op when the host has no chrome adapter. */
|
|
shell: PluginPanelShell;
|
|
}
|
|
export type PluginPanelCloseDecision = boolean | {
|
|
title: string;
|
|
message: string;
|
|
confirmLabel?: string;
|
|
};
|
|
export interface PluginEmbedInput {
|
|
type: string;
|
|
resourceId: string;
|
|
previewResourceId?: string;
|
|
title?: string;
|
|
data?: PluginJsonValue;
|
|
}
|
|
export interface PluginEmbedInstance extends PluginEmbedInput {
|
|
id: string;
|
|
pluginId: string;
|
|
previewResourceId: string;
|
|
title: string;
|
|
data: PluginJsonValue;
|
|
}
|
|
export interface PluginEmbedRenderer {
|
|
type: string;
|
|
mount(container: HTMLElement, embed: PluginEmbedInstance): void | (() => void) | Promise<void | (() => void)>;
|
|
}
|
|
export interface PluginPanel {
|
|
id: string;
|
|
title: string;
|
|
/** Business purpose of this panel. Custom settings pages are intentionally unsupported. */
|
|
purpose: PluginPanelPurpose;
|
|
presentation?: PluginPanelPresentation;
|
|
mount(container: HTMLElement, context: PluginPanelMountContext): void | (() => void) | Promise<void | (() => void)>;
|
|
beforeClose?(): PluginPanelCloseDecision | Promise<PluginPanelCloseDecision>;
|
|
}
|
|
export interface PluginContext {
|
|
pluginId: string;
|
|
ai: {
|
|
status(): Promise<{
|
|
configured: boolean;
|
|
modelName?: string;
|
|
}>;
|
|
generate(input: {
|
|
system: string;
|
|
prompt: string;
|
|
maxOutputTokens?: number;
|
|
signal?: AbortSignal;
|
|
}): Promise<{
|
|
text: string;
|
|
}>;
|
|
};
|
|
notes: {
|
|
query(input?: PluginNoteQuery): Promise<PluginNoteQueryResult>;
|
|
queryContent(input?: PluginNoteQuery): Promise<PluginNoteContentQueryResult>;
|
|
get(noteId: string): Promise<PluginNote>;
|
|
editMarkdown(noteId: string, input: PluginMarkdownEditInput): Promise<PluginNote>;
|
|
create(input: PluginNoteCreateInput): Promise<PluginNote>;
|
|
update(noteId: string, input: PluginNoteUpdateInput): Promise<PluginNote>;
|
|
delete(noteId: string, options?: {
|
|
permanent?: boolean;
|
|
}): Promise<void>;
|
|
move(noteIds: string[], notebookId: string): Promise<number>;
|
|
pin(noteIds: string[], isPinned: boolean): Promise<number>;
|
|
restore(noteId: string): Promise<PluginNote>;
|
|
revisions: {
|
|
list(noteId: string): Promise<PluginNoteRevision[]>;
|
|
restore(noteId: string, revisionId: string): Promise<PluginNote>;
|
|
};
|
|
};
|
|
notebooks: {
|
|
list(): Promise<PluginNotebook[]>;
|
|
create(input: {
|
|
name: string;
|
|
parentId?: string | null;
|
|
}): Promise<PluginNotebook>;
|
|
update(notebookId: string, input: {
|
|
name?: string;
|
|
parentId?: string | null;
|
|
sortOrder?: number;
|
|
}): Promise<PluginNotebook>;
|
|
delete(notebookId: string): Promise<void>;
|
|
};
|
|
tags: {
|
|
list(): Promise<PluginTag[]>;
|
|
rename(name: string, nextName: string): Promise<number>;
|
|
delete(name: string): Promise<number>;
|
|
};
|
|
templates: {
|
|
list(): Promise<PluginTemplate[]>;
|
|
create(input: {
|
|
name: string;
|
|
description?: string | null;
|
|
noteId?: string;
|
|
title?: string | null;
|
|
contentMarkdown?: string;
|
|
tags?: string[];
|
|
}): Promise<PluginTemplate>;
|
|
update(templateId: string, input: {
|
|
name?: string;
|
|
description?: string | null;
|
|
title?: string | null;
|
|
contentMarkdown?: string;
|
|
tags?: string[];
|
|
}): Promise<PluginTemplate>;
|
|
delete(templateId: string): Promise<void>;
|
|
use(templateId: string, notebookId: string): Promise<PluginNote>;
|
|
};
|
|
commands: {
|
|
register(command: PluginCommand): () => void;
|
|
};
|
|
schedules: {
|
|
/** Creates or updates one persistent schedule owned by this plugin. Desktop only. */
|
|
upsert(input: PluginScheduleInput): Promise<PluginSchedule>;
|
|
list(): Promise<PluginSchedule[]>;
|
|
remove(key: string): Promise<void>;
|
|
};
|
|
events: {
|
|
on<K extends keyof PluginEventMap>(event: K, listener: (payload: PluginEventMap[K]) => void): () => void;
|
|
};
|
|
storage: {
|
|
get<T>(key: string): Promise<T | null>;
|
|
set<T>(key: string, value: T): Promise<void>;
|
|
remove(key: string): Promise<void>;
|
|
};
|
|
secrets: {
|
|
get(key: string): Promise<string | null>;
|
|
set(key: string, value: string): Promise<void>;
|
|
remove(key: string): Promise<void>;
|
|
};
|
|
editor: {
|
|
getSelection(): Promise<PluginEditorSelection | null>;
|
|
getDocument(): Promise<PluginEditorDocument | null>;
|
|
editMarkdown(edits: PluginMarkdownEdit[]): Promise<PluginEditorDocument>;
|
|
insertEmbed(input: PluginEmbedInput): Promise<PluginEmbedInstance>;
|
|
embeds: {
|
|
register(renderer: PluginEmbedRenderer): () => void;
|
|
};
|
|
replaceSelection(contentMarkdown: string): Promise<void>;
|
|
insertAtCursor(contentMarkdown: string): Promise<void>;
|
|
};
|
|
resources: {
|
|
list(noteId?: string): Promise<PluginResource[]>;
|
|
read(resourceId: string): Promise<Blob>;
|
|
upload(noteId: string, file: File): Promise<PluginResource>;
|
|
update(resourceId: string, input: {
|
|
file: File;
|
|
expectedContentHash: string;
|
|
}): Promise<PluginResource>;
|
|
rename(resourceId: string, filename: string): Promise<PluginResource>;
|
|
delete(resourceId: string): Promise<void>;
|
|
};
|
|
settings: {
|
|
get(key: string): Promise<PluginSettingValue | null>;
|
|
set(key: string, value: PluginSettingValue): Promise<void>;
|
|
remove(key: string): Promise<void>;
|
|
};
|
|
network: {
|
|
/** direct preserves browser fetch. public uses authenticated, bounded HTTPS GET/HEAD transport. */
|
|
fetch(input: string, init?: RequestInit & {
|
|
transport?: "direct" | "public";
|
|
}): Promise<Response>;
|
|
};
|
|
ui: {
|
|
showNotice(message: string): void;
|
|
openNote(noteId: string, options?: PluginOpenNoteOptions): Promise<void>;
|
|
panels: {
|
|
register(panel: PluginPanel): () => void;
|
|
open(panelId: string, options?: PluginPanelOpenOptions): Promise<void>;
|
|
};
|
|
};
|
|
}
|
|
export interface EdgeEverPlugin {
|
|
activate(context: PluginContext): void | (() => void) | Promise<void | (() => void)>;
|
|
deactivate?(): void | Promise<void>;
|
|
}
|
|
export declare const definePlugin: <T extends EdgeEverPlugin>(plugin: T) => T;
|
|
export declare const defineTheme: <T extends ThemeManifest>(theme: T) => T;
|
|
/** Strips unknown fields and clamps sizes so host chrome rendering stays bounded. */
|
|
export declare const normalizePluginPanelChrome: (value: PluginPanelChrome | null | undefined) => PluginPanelChrome;
|
|
export declare const parseExtensionManifest: (value: unknown) => ExtensionManifest;
|
|
export declare const parseMarketplaceRegistry: (value: unknown) => MarketplaceRegistry;
|
|
export {};
|