@openforge-app/plugin-sdk 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.
@@ -0,0 +1,350 @@
1
+ import type { Component } from 'svelte';
2
+ import type { AgentSession, FileContent, FileEntry, Project, ProjectAttention, Task, TaskWorkspaceInfo, WritableBoardStatus } from './domain';
3
+ export type SupportedOpenForgeApiVersion = 1;
4
+ export declare const SUPPORTED_OPENFORGE_API_VERSIONS: readonly [1, ...1[]];
5
+ export declare const OPENFORGE_PLUGIN_API_VERSION: SupportedOpenForgeApiVersion;
6
+ export declare const MIN_SUPPORTED_API_VERSION: SupportedOpenForgeApiVersion;
7
+ export declare const MAX_SUPPORTED_API_VERSION: SupportedOpenForgeApiVersion;
8
+ export type JsonPrimitive = string | number | boolean | null;
9
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
10
+ export interface JsonObject {
11
+ [key: string]: JsonValue;
12
+ }
13
+ export type JsonSchema = Record<string, unknown>;
14
+ export type MaybePromise<T> = T | Promise<T>;
15
+ export interface ValidationError {
16
+ path: string;
17
+ message: string;
18
+ }
19
+ export type OpenForgePluginCapability = 'commands' | 'events' | 'views' | 'taskPane' | 'settings' | 'background' | 'backend' | 'storage' | 'context' | 'navigation' | 'tasks' | 'projects' | 'fs' | 'shell' | 'notifications' | 'attention' | 'system.openUrl' | 'config' | 'projectConfig';
20
+ export interface OpenForgePackageMetadata {
21
+ id: string;
22
+ apiVersion: SupportedOpenForgeApiVersion;
23
+ displayName: string;
24
+ description: string;
25
+ icon?: string;
26
+ frontend?: string;
27
+ backend?: string;
28
+ requires?: OpenForgePluginCapability[];
29
+ }
30
+ export interface OpenForgePluginPackageJson {
31
+ name: string;
32
+ version: string;
33
+ peerDependencies?: Record<string, string>;
34
+ openforge: OpenForgePackageMetadata;
35
+ }
36
+ export type PluginState = 'installed' | 'active' | 'error' | 'disabled';
37
+ export interface PluginEntry {
38
+ metadata: OpenForgePackageMetadata;
39
+ state: PluginState;
40
+ error: string | null;
41
+ installPath?: string;
42
+ isBuiltin?: boolean;
43
+ }
44
+ export interface Disposable {
45
+ dispose(): void | Promise<void>;
46
+ }
47
+ export interface SubscriptionSink {
48
+ add(subscription: Disposable | (() => void)): void;
49
+ }
50
+ export interface OpenForgeContextSnapshot {
51
+ pluginId: string;
52
+ projectId: string | null;
53
+ taskId?: string | null;
54
+ }
55
+ export interface OpenForgeNavigationSnapshot {
56
+ activeProjectId: string | null;
57
+ currentView: string;
58
+ selectedTaskId: string | null;
59
+ }
60
+ export interface OpenForgeNavigationRequest {
61
+ viewId?: string;
62
+ projectId?: string | null;
63
+ taskId?: string | null;
64
+ }
65
+ export interface NavigationAPI {
66
+ get(): OpenForgeNavigationSnapshot;
67
+ navigate(request: OpenForgeNavigationRequest): Promise<OpenForgeNavigationSnapshot>;
68
+ }
69
+ export interface OpenForgePluginContext {
70
+ pluginId: string;
71
+ apiVersion: SupportedOpenForgeApiVersion;
72
+ packageMetadata: OpenForgePackageMetadata;
73
+ subscriptions: SubscriptionSink;
74
+ }
75
+ export type FrontendPluginContext = OpenForgePluginContext;
76
+ export type BackendPluginContext = OpenForgePluginContext;
77
+ export interface PluginStorageScope {
78
+ get<T extends JsonValue = JsonValue>(key: string): Promise<T | null>;
79
+ set<T extends JsonValue = JsonValue>(key: string, value: T): Promise<void>;
80
+ delete(key: string): Promise<void>;
81
+ }
82
+ export interface PluginStorage {
83
+ readonly global: PluginStorageScope;
84
+ project(projectId: string): PluginStorageScope;
85
+ task(taskId: string): PluginStorageScope;
86
+ }
87
+ export type CommandShortcutMetadata = string | {
88
+ key: string;
89
+ scope?: 'global' | 'project' | 'task';
90
+ when?: string;
91
+ };
92
+ export interface CommandRegistration<TInput = unknown, TOutput = unknown> {
93
+ id: string;
94
+ title: string;
95
+ icon?: string;
96
+ shortcut?: CommandShortcutMetadata;
97
+ /** Whether this command should appear in user-facing discovery surfaces such as the Command Palette. Defaults to true. */
98
+ discoverable?: boolean;
99
+ input?: JsonSchema;
100
+ output?: JsonSchema;
101
+ handler(input: TInput): MaybePromise<TOutput>;
102
+ }
103
+ export interface CommandDescriptor {
104
+ id: string;
105
+ qualifiedId: string;
106
+ pluginId: string;
107
+ projectId: string | null;
108
+ title: string;
109
+ icon?: string;
110
+ shortcut?: CommandShortcutMetadata;
111
+ discoverable: boolean;
112
+ input?: JsonSchema;
113
+ output?: JsonSchema;
114
+ }
115
+ export interface CommandRegistry {
116
+ register<TInput = unknown, TOutput = unknown>(registration: CommandRegistration<TInput, TOutput>): Disposable;
117
+ invoke<TOutput = unknown>(id: string, payload?: unknown): Promise<TOutput>;
118
+ invokeGlobal<TOutput = unknown>(qualifiedId: string, payload?: unknown): Promise<TOutput>;
119
+ list(): Promise<CommandDescriptor[]>;
120
+ }
121
+ export type EventHandler<TPayload = unknown> = (payload: TPayload) => void;
122
+ export interface EventRegistry {
123
+ on<TPayload = unknown>(event: string, handler: EventHandler<TPayload>): Disposable;
124
+ onGlobal<TPayload = unknown>(qualifiedEvent: string, handler: EventHandler<TPayload>): Disposable;
125
+ emit<TPayload = unknown>(event: string, payload: TPayload): Promise<void>;
126
+ emitGlobal<TPayload = unknown>(qualifiedEvent: string, payload: TPayload): Promise<void>;
127
+ }
128
+ export type PluginComponent<Props extends Record<string, unknown> = Record<string, unknown>> = Component<Props>;
129
+ export type PluginComponentModule<Props extends Record<string, unknown> = Record<string, unknown>> = {
130
+ default: PluginComponent<Props>;
131
+ };
132
+ export type PluginComponentLoader<Props extends Record<string, unknown> = Record<string, unknown>> = () => MaybePromise<PluginComponent<Props> | PluginComponentModule<Props>>;
133
+ export interface PluginViewProps extends Record<string, unknown> {
134
+ api: FrontendOpenForgeAPI;
135
+ context: OpenForgeContextSnapshot;
136
+ }
137
+ export interface PluginTaskPaneProps extends Record<string, unknown> {
138
+ api: FrontendOpenForgeAPI;
139
+ context: OpenForgeContextSnapshot;
140
+ taskId: string;
141
+ projectId: string | null;
142
+ }
143
+ export interface PluginSettingsSectionProps extends Record<string, unknown> {
144
+ api: FrontendOpenForgeAPI;
145
+ context: OpenForgeContextSnapshot;
146
+ }
147
+ export interface PluginViewRegistration {
148
+ id: string;
149
+ title: string;
150
+ icon: string;
151
+ /**
152
+ * Where the host surfaces the view's nav entry. `'rail'` (default) places it on
153
+ * the icon rail; `'sidebar'` places it in the left projects sidebar. Either way
154
+ * the view itself is registered and routable by its key.
155
+ */
156
+ placement: 'rail' | 'sidebar';
157
+ order?: number;
158
+ shortcut?: string;
159
+ component: PluginComponentLoader<PluginViewProps> | PluginComponent<PluginViewProps>;
160
+ }
161
+ export interface PluginTaskPaneTabRegistration {
162
+ id: string;
163
+ title: string;
164
+ icon?: string;
165
+ order?: number;
166
+ component: PluginComponentLoader<PluginTaskPaneProps> | PluginComponent<PluginTaskPaneProps>;
167
+ }
168
+ export interface PluginSettingsSectionRegistration {
169
+ id: string;
170
+ title: string;
171
+ order?: number;
172
+ component: PluginComponentLoader<PluginSettingsSectionProps> | PluginComponent<PluginSettingsSectionProps>;
173
+ }
174
+ export interface FrontendViewRegistry {
175
+ register(registration: PluginViewRegistration): Disposable;
176
+ }
177
+ export interface FrontendTaskPaneRegistry {
178
+ registerTab(registration: PluginTaskPaneTabRegistration): Disposable;
179
+ }
180
+ export interface FrontendSettingsRegistry {
181
+ registerSection(registration: PluginSettingsSectionRegistration): Disposable;
182
+ }
183
+ export type BackendReadyState = 'missing' | 'starting' | 'ready' | 'error';
184
+ export interface FrontendBackendBridge {
185
+ readonly state: BackendReadyState;
186
+ whenReady(): Promise<void>;
187
+ onReady(handler: () => void): Disposable;
188
+ invoke<TOutput = unknown>(method: string, payload?: unknown): Promise<TOutput>;
189
+ }
190
+ export interface BackendMethodRegistration<TInput = unknown, TOutput = unknown> {
191
+ input?: JsonSchema;
192
+ output?: JsonSchema;
193
+ handler(input: TInput): MaybePromise<TOutput>;
194
+ }
195
+ export interface BackendMethodRegistry {
196
+ registerMethod<TInput = unknown, TOutput = unknown>(method: string, registration: BackendMethodRegistration<TInput, TOutput>): Disposable;
197
+ }
198
+ export interface BackgroundServiceRegistration {
199
+ id: string;
200
+ scope: 'global' | 'project' | 'task';
201
+ start(): MaybePromise<void>;
202
+ stop?(): MaybePromise<void>;
203
+ }
204
+ export interface BackgroundServiceRegistry {
205
+ register(registration: BackgroundServiceRegistration): Disposable;
206
+ }
207
+ export interface ProjectScopedFileRequest {
208
+ projectId: string;
209
+ path: string;
210
+ }
211
+ export interface FileSystemAPI {
212
+ readDir(request: {
213
+ projectId: string;
214
+ path?: string | null;
215
+ }): Promise<FileEntry[]>;
216
+ readFile(request: ProjectScopedFileRequest): Promise<FileContent>;
217
+ writeFile(request: ProjectScopedFileRequest & {
218
+ content: string;
219
+ }): Promise<void>;
220
+ searchFiles(request: {
221
+ projectId: string;
222
+ query: string;
223
+ limit?: number;
224
+ }): Promise<string[]>;
225
+ }
226
+ export interface ShellSessionRequest {
227
+ taskId: string;
228
+ terminalIndex: number;
229
+ }
230
+ export interface ShellSpawnRequest extends ShellSessionRequest {
231
+ cwd: string;
232
+ cols: number;
233
+ rows: number;
234
+ }
235
+ export interface ShellWriteRequest extends ShellSessionRequest {
236
+ data: string;
237
+ }
238
+ export interface ShellResizeRequest extends ShellSessionRequest {
239
+ cols: number;
240
+ rows: number;
241
+ }
242
+ export interface ShellAPI {
243
+ spawn(request: ShellSpawnRequest): Promise<number>;
244
+ write(request: ShellWriteRequest): Promise<void>;
245
+ resize(request: ShellResizeRequest): Promise<void>;
246
+ kill(request: ShellSessionRequest): Promise<void>;
247
+ getBuffer(request: ShellSessionRequest): Promise<string | null>;
248
+ }
249
+ export interface CreateTaskRequest {
250
+ initialPrompt: string;
251
+ projectId: string;
252
+ dependsOn?: string[];
253
+ labelNames?: string[];
254
+ }
255
+ export interface StartPromptContribution {
256
+ id: string;
257
+ enabled: boolean;
258
+ /** Prompt text injected before OpenForge's task prompt. The host substitutes {{taskId}} and {{task_id}}. */
259
+ content: string;
260
+ /** Lower values are injected first. Defaults to 0. */
261
+ order?: number;
262
+ }
263
+ export interface ConfigureStartPromptContributionRequest extends StartPromptContribution {
264
+ projectId: string;
265
+ }
266
+ export interface StartTaskImplementationRequest {
267
+ taskId: string;
268
+ }
269
+ export interface ImplementationRun {
270
+ taskId: string;
271
+ sessionId: string;
272
+ workspacePath: string;
273
+ }
274
+ export interface TasksAPI {
275
+ list(request?: {
276
+ projectId?: string | null;
277
+ }): Promise<Task[]>;
278
+ get(taskId: string): Promise<Task>;
279
+ create(request: CreateTaskRequest): Promise<Task>;
280
+ updateSummary(taskId: string, summary: string): Promise<void>;
281
+ updateStatus(taskId: string, status: WritableBoardStatus): Promise<void>;
282
+ listStartPromptContributions(projectId: string): Promise<StartPromptContribution[]>;
283
+ configureStartPromptContribution(request: ConfigureStartPromptContributionRequest): Promise<StartPromptContribution[]>;
284
+ startImplementation(request: StartTaskImplementationRequest): Promise<ImplementationRun>;
285
+ getWorkspace(taskId: string): Promise<TaskWorkspaceInfo | null>;
286
+ getLatestSession(taskId: string): Promise<AgentSession | null>;
287
+ }
288
+ export interface ProjectsAPI {
289
+ list(): Promise<Project[]>;
290
+ get(projectId: string): Promise<Project | null>;
291
+ }
292
+ export type NotificationRequest = {
293
+ title: string;
294
+ body?: string;
295
+ [key: string]: JsonValue | undefined;
296
+ };
297
+ export interface NotificationsAPI {
298
+ notify(request: NotificationRequest): Promise<void>;
299
+ }
300
+ export interface AttentionAPI {
301
+ listProjects(): Promise<ProjectAttention[]>;
302
+ }
303
+ export interface SystemAPI {
304
+ openUrl(url: string): Promise<void>;
305
+ }
306
+ export interface KeyValueConfigAPI {
307
+ get<T extends JsonValue = JsonValue>(key: string, projectId?: string): Promise<T | null>;
308
+ set<T extends JsonValue = JsonValue>(key: string, value: T, projectId?: string): Promise<void>;
309
+ }
310
+ export interface OpenForgeCommonAPI {
311
+ commands: CommandRegistry;
312
+ events: EventRegistry;
313
+ storage: PluginStorage;
314
+ context: {
315
+ getSnapshot(): OpenForgeContextSnapshot;
316
+ };
317
+ tasks: TasksAPI;
318
+ projects: ProjectsAPI;
319
+ fs: FileSystemAPI;
320
+ shell: ShellAPI;
321
+ notifications: NotificationsAPI;
322
+ attention: AttentionAPI;
323
+ system: SystemAPI;
324
+ config: KeyValueConfigAPI;
325
+ projectConfig: KeyValueConfigAPI;
326
+ }
327
+ export interface FrontendOpenForgeAPI extends OpenForgeCommonAPI {
328
+ navigation: NavigationAPI;
329
+ views: FrontendViewRegistry;
330
+ taskPane: FrontendTaskPaneRegistry;
331
+ settings: FrontendSettingsRegistry;
332
+ backend: FrontendBackendBridge;
333
+ }
334
+ export interface BackendOpenForgeAPI extends OpenForgeCommonAPI {
335
+ backend: BackendMethodRegistry;
336
+ background: BackgroundServiceRegistry;
337
+ }
338
+ export interface FrontendPlugin {
339
+ activate(openforge: FrontendOpenForgeAPI, context: FrontendPluginContext): MaybePromise<void>;
340
+ }
341
+ export interface BackendPlugin {
342
+ activate(openforge: BackendOpenForgeAPI, context: BackendPluginContext): MaybePromise<void>;
343
+ }
344
+ export type PluginViewKey = `plugin:${string}:${string}`;
345
+ export declare function makePluginViewKey(pluginId: string, viewId: string): PluginViewKey;
346
+ export declare function isPluginViewKey(value: string): value is PluginViewKey;
347
+ export declare function parsePluginViewKey(key: PluginViewKey): {
348
+ pluginId: string;
349
+ viewId: string;
350
+ };
package/dist/types.js ADDED
@@ -0,0 +1,22 @@
1
+ import packageMetadataSchemaData from './openforgePackageMetadataSchema.json';
2
+ function readSupportedOpenForgeApiVersions() {
3
+ const versions = packageMetadataSchemaData.properties.apiVersion.enum;
4
+ if (!Array.isArray(versions) || versions.length === 0 || !versions.every((version) => typeof version === 'number' && Number.isInteger(version))) {
5
+ throw new Error('openforgePackageMetadataSchema.json properties.apiVersion.enum must contain at least one integer');
6
+ }
7
+ return [...versions];
8
+ }
9
+ export const SUPPORTED_OPENFORGE_API_VERSIONS = Object.freeze(readSupportedOpenForgeApiVersions());
10
+ export const OPENFORGE_PLUGIN_API_VERSION = SUPPORTED_OPENFORGE_API_VERSIONS[0];
11
+ export const MIN_SUPPORTED_API_VERSION = Math.min(...SUPPORTED_OPENFORGE_API_VERSIONS);
12
+ export const MAX_SUPPORTED_API_VERSION = Math.max(...SUPPORTED_OPENFORGE_API_VERSIONS);
13
+ export function makePluginViewKey(pluginId, viewId) {
14
+ return `plugin:${pluginId}:${viewId}`;
15
+ }
16
+ export function isPluginViewKey(value) {
17
+ return value.startsWith('plugin:') && value.match(/^plugin:[^:]+:[^:]+$/) !== null;
18
+ }
19
+ export function parsePluginViewKey(key) {
20
+ const parts = key.split(':');
21
+ return { pluginId: parts[1], viewId: parts[2] };
22
+ }
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { renderMarkdownHtml } from '../markdown'
3
+
4
+ interface Props {
5
+ content: string
6
+ imageBaseUrl?: string | null
7
+ onOpenUrl?: (url: string) => void | Promise<void>
8
+ }
9
+
10
+ let { content, imageBaseUrl = null, onOpenUrl }: Props = $props()
11
+
12
+ let html = $derived(renderMarkdownHtml(content, { imageBaseUrl }))
13
+
14
+ function handleClick(e: MouseEvent) {
15
+ if (!(e.target instanceof Element)) return
16
+
17
+ const anchor = e.target.closest('a')
18
+ if (anchor?.href) {
19
+ e.preventDefault()
20
+ if (onOpenUrl) {
21
+ void onOpenUrl(anchor.href)
22
+ }
23
+ }
24
+ }
25
+ </script>
26
+
27
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
28
+ <div role="presentation" class="markdown-body" onclick={handleClick}>
29
+ {@html html}
30
+ </div>
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import { parseStrictFiniteNumber } from '../numberParsing'
4
+
5
+ interface Props {
6
+ storageKey: string
7
+ defaultWidth: number
8
+ minWidth?: number
9
+ maxWidth?: number
10
+ side?: 'left' | 'right'
11
+ children?: Snippet
12
+ }
13
+
14
+ let {
15
+ storageKey,
16
+ defaultWidth,
17
+ minWidth = 120,
18
+ maxWidth = 600,
19
+ side = 'left',
20
+ children,
21
+ }: Props = $props()
22
+
23
+ let panelEl = $state<HTMLElement | null>(null)
24
+
25
+ function clamp(value: number): number {
26
+ return Math.max(minWidth, Math.min(maxWidth, value))
27
+ }
28
+
29
+ function loadWidth(): number {
30
+ try {
31
+ const stored = localStorage.getItem(`resizable-panel:${storageKey}`)
32
+ if (stored !== null) {
33
+ const parsed = parseStrictFiniteNumber(stored)
34
+ if (parsed !== null) return clamp(parsed)
35
+ }
36
+ } catch { /* localStorage unavailable */ }
37
+ return defaultWidth
38
+ }
39
+
40
+ function saveWidth(w: number) {
41
+ try {
42
+ localStorage.setItem(`resizable-panel:${storageKey}`, String(w))
43
+ } catch { /* localStorage unavailable */ }
44
+ }
45
+
46
+ function clearWidth() {
47
+ try {
48
+ localStorage.removeItem(`resizable-panel:${storageKey}`)
49
+ } catch { /* localStorage unavailable */ }
50
+ }
51
+
52
+ let width = $state(loadWidth())
53
+ let isDragging = $state(false)
54
+
55
+ function onMouseDown(e: MouseEvent) {
56
+ e.preventDefault()
57
+ isDragging = true
58
+
59
+ const startX = e.clientX
60
+ const startWidth = width
61
+ const rect = panelEl?.getBoundingClientRect()
62
+ if (!rect) return
63
+
64
+ function onMouseMove(e: MouseEvent) {
65
+ const delta = side === 'left'
66
+ ? e.clientX - startX
67
+ : startX - e.clientX
68
+ width = clamp(startWidth + delta)
69
+ }
70
+
71
+ function onMouseUp() {
72
+ isDragging = false
73
+ saveWidth(width)
74
+ document.removeEventListener('mousemove', onMouseMove)
75
+ document.removeEventListener('mouseup', onMouseUp)
76
+ }
77
+
78
+ document.addEventListener('mousemove', onMouseMove)
79
+ document.addEventListener('mouseup', onMouseUp)
80
+ }
81
+
82
+ function onDblClick() {
83
+ width = defaultWidth
84
+ clearWidth()
85
+ }
86
+
87
+ function onKeyDown(e: KeyboardEvent) {
88
+ let delta = 0
89
+ if (e.key === 'ArrowRight') {
90
+ delta = side === 'left' ? 10 : -10
91
+ } else if (e.key === 'ArrowLeft') {
92
+ delta = side === 'left' ? -10 : 10
93
+ } else if (e.key === 'Enter' || e.key === ' ') {
94
+ e.preventDefault()
95
+ onDblClick()
96
+ return
97
+ }
98
+
99
+ if (delta !== 0) {
100
+ e.preventDefault()
101
+ width = clamp(width + delta)
102
+ saveWidth(width)
103
+ }
104
+ }
105
+ </script>
106
+
107
+ <div
108
+ data-testid="resizable-panel"
109
+ class="relative flex shrink-0 h-full overflow-hidden"
110
+ style="width: {width}px"
111
+ bind:this={panelEl}
112
+ >
113
+ {#if side === 'right'}
114
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
115
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
116
+ <div
117
+ data-testid="resize-handle"
118
+ class="absolute left-0 top-0 bottom-0 z-10 w-1 hover:bg-primary/30 transition-colors {isDragging ? 'bg-primary/40' : ''} focus-visible:bg-primary/40 focus-visible:outline-none"
119
+ style="cursor: col-resize"
120
+ role="separator"
121
+ aria-orientation="vertical"
122
+ tabindex="0"
123
+ onmousedown={onMouseDown}
124
+ ondblclick={onDblClick}
125
+ onkeydown={onKeyDown}
126
+ ></div>
127
+ {/if}
128
+ <div class="flex-1 overflow-hidden">
129
+ {@render children?.()}
130
+ </div>
131
+ {#if side === 'left'}
132
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
133
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
134
+ <div
135
+ data-testid="resize-handle"
136
+ class="absolute right-0 top-0 bottom-0 z-10 w-1 hover:bg-primary/30 transition-colors {isDragging ? 'bg-primary/40' : ''} focus-visible:bg-primary/40 focus-visible:outline-none"
137
+ style="cursor: col-resize"
138
+ role="separator"
139
+ aria-orientation="vertical"
140
+ tabindex="0"
141
+ onmousedown={onMouseDown}
142
+ ondblclick={onDblClick}
143
+ onkeydown={onKeyDown}
144
+ ></div>
145
+ {/if}
146
+ </div>
package/dist/vite.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export declare const OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS: readonly string[];
2
+ export declare const OPENFORGE_HOST_SHARED_SVELTE_IMPORTS: readonly string[];
3
+ export declare const OPENFORGE_HOST_SHARED_TERMINAL_RUNTIME_IMPORTS: readonly string[];
4
+ export type OpenForgeHostRuntimeSvelteSpecifier = typeof OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS[number];
5
+ export type OpenForgeHostSharedSvelteImport = OpenForgeHostRuntimeSvelteSpecifier;
6
+ export type OpenForgeHostSharedTerminalRuntimeImport = typeof OPENFORGE_HOST_SHARED_TERMINAL_RUNTIME_IMPORTS[number];
7
+ export declare function isOpenForgeHostRuntimeExternal(id: string): boolean;
8
+ export declare const openforgePluginViteExternals: typeof isOpenForgeHostRuntimeExternal;
9
+ export type OpenForgePluginSdkSourceAlias = Readonly<{
10
+ find: string;
11
+ replacement: string;
12
+ }>;
13
+ export type OpenForgePluginSdkSourceAliasRecord = Readonly<Record<string, string>>;
14
+ export declare function createOpenForgePluginSdkSourceAliases(repoRoot: URL | string): OpenForgePluginSdkSourceAlias[];
15
+ export declare function createOpenForgePluginSdkSourceAliasRecord(repoRoot: URL | string): OpenForgePluginSdkSourceAliasRecord;
package/dist/vite.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Svelte browser/runtime entrypoints that OpenForge frontend plugin bundles must
3
+ * share with the host renderer. The Electron renderer import map and packaged
4
+ * plugin://host-runtime assets are derived from the same host-runtime contract.
5
+ */
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+ import { OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS as HOST_RUNTIME_SVELTE_SPECIFIERS } from './svelteHostRuntimeContract.mjs';
8
+ export const OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS = HOST_RUNTIME_SVELTE_SPECIFIERS;
9
+ export const OPENFORGE_HOST_SHARED_SVELTE_IMPORTS = OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS;
10
+ export const OPENFORGE_HOST_SHARED_TERMINAL_RUNTIME_IMPORTS = Object.freeze([
11
+ '@openforge-app/terminal-runtime',
12
+ '@openforge-app/terminal-runtime/terminalRuntime',
13
+ '@openforge-app/terminal-runtime/terminalOptions',
14
+ '@openforge-app/terminal-runtime/theme',
15
+ '@openforge-app/terminal-runtime/shortcuts',
16
+ '@openforge-app/terminal-runtime/shortcutController',
17
+ '@openforge-app/terminal-runtime/TerminalTabsShell',
18
+ ]);
19
+ const OPENFORGE_HOST_RUNTIME_MODULES = new Set([
20
+ ...OPENFORGE_HOST_RUNTIME_SVELTE_SPECIFIERS,
21
+ ...OPENFORGE_HOST_SHARED_TERMINAL_RUNTIME_IMPORTS,
22
+ ]);
23
+ export function isOpenForgeHostRuntimeExternal(id) {
24
+ return OPENFORGE_HOST_RUNTIME_MODULES.has(id);
25
+ }
26
+ export const openforgePluginViteExternals = isOpenForgeHostRuntimeExternal;
27
+ const OPENFORGE_PLUGIN_SDK_SOURCE_ENTRYPOINTS = Object.freeze([
28
+ ['@openforge-app/plugin-sdk/frontend', 'packages/plugin-sdk/src/frontend.ts'],
29
+ ['@openforge-app/plugin-sdk/backend', 'packages/plugin-sdk/src/backend.ts'],
30
+ ['@openforge-app/plugin-sdk/testing', 'packages/plugin-sdk/src/testing.ts'],
31
+ ['@openforge-app/plugin-sdk/vite', 'packages/plugin-sdk/src/vite.ts'],
32
+ ['@openforge-app/plugin-sdk/domain', 'packages/plugin-sdk/src/domain.ts'],
33
+ ['@openforge-app/plugin-sdk/prStatusPresentation', 'packages/plugin-sdk/src/prStatusPresentation.ts'],
34
+ ['@openforge-app/plugin-sdk/markdown', 'packages/plugin-sdk/src/markdown.ts'],
35
+ ['@openforge-app/plugin-sdk/numberParsing', 'packages/plugin-sdk/src/numberParsing.ts'],
36
+ ['@openforge-app/plugin-sdk/projectFileTree', 'packages/plugin-sdk/src/projectFileTree.ts'],
37
+ ['@openforge-app/plugin-sdk/sanitize', 'packages/plugin-sdk/src/sanitize.ts'],
38
+ ['@openforge-app/plugin-sdk/ui/MarkdownContent.svelte', 'packages/plugin-sdk/src/ui/MarkdownContent.svelte'],
39
+ ['@openforge-app/plugin-sdk/ui/ResizablePanel.svelte', 'packages/plugin-sdk/src/ui/ResizablePanel.svelte'],
40
+ ['@openforge-app/plugin-sdk', 'packages/plugin-sdk/src/index.ts'],
41
+ ]);
42
+ function repoRootUrl(repoRoot) {
43
+ if (repoRoot instanceof URL) {
44
+ return new URL(repoRoot.href.endsWith('/') ? repoRoot.href : `${repoRoot.href}/`);
45
+ }
46
+ if (isUrlString(repoRoot)) {
47
+ return new URL(repoRoot.endsWith('/') ? repoRoot : `${repoRoot}/`);
48
+ }
49
+ return pathToFileURL(hasTrailingPathSeparator(repoRoot) ? repoRoot : `${repoRoot}/`);
50
+ }
51
+ function isUrlString(value) {
52
+ return /^[a-z][a-z\d+.-]*:\/\//i.test(value) || /^file:/i.test(value);
53
+ }
54
+ function hasTrailingPathSeparator(value) {
55
+ return value.endsWith('/') || value.endsWith('\\');
56
+ }
57
+ function sourceAliasReplacement(sourceUrl) {
58
+ if (sourceUrl.protocol === 'file:') {
59
+ return fileURLToPath(sourceUrl);
60
+ }
61
+ return sourceUrl.pathname;
62
+ }
63
+ function createOpenForgePluginSdkSourceAliasEntries(repoRoot) {
64
+ const rootUrl = repoRootUrl(repoRoot);
65
+ return OPENFORGE_PLUGIN_SDK_SOURCE_ENTRYPOINTS.map(([find, sourcePath]) => [
66
+ find,
67
+ sourceAliasReplacement(new URL(sourcePath, rootUrl)),
68
+ ]);
69
+ }
70
+ export function createOpenForgePluginSdkSourceAliases(repoRoot) {
71
+ return createOpenForgePluginSdkSourceAliasEntries(repoRoot).map(([find, replacement]) => ({
72
+ find,
73
+ replacement,
74
+ }));
75
+ }
76
+ export function createOpenForgePluginSdkSourceAliasRecord(repoRoot) {
77
+ return Object.fromEntries(createOpenForgePluginSdkSourceAliasEntries(repoRoot));
78
+ }