@flowdrop/flowdrop 1.10.0 → 1.11.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.
@@ -11,18 +11,9 @@
11
11
  import Logo from './Logo.svelte';
12
12
  import SettingsModal from './SettingsModal.svelte';
13
13
  import type { SettingsCategory } from '../types/settings.js';
14
+ import type { NavbarAction } from '../types/navbar.js';
14
15
  import { m } from '../messages/index.js';
15
16
 
16
- interface NavbarAction {
17
- label: string;
18
- href: string;
19
- icon?: string;
20
- variant?: 'primary' | 'secondary' | 'outline';
21
- onclick?: (event: Event) => void;
22
- /** If true, opens link in new tab with proper security attributes */
23
- external?: boolean;
24
- }
25
-
26
17
  interface BreadcrumbItem {
27
18
  label: string;
28
19
  href?: string;
@@ -1,13 +1,5 @@
1
1
  import type { SettingsCategory } from '../types/settings.js';
2
- interface NavbarAction {
3
- label: string;
4
- href: string;
5
- icon?: string;
6
- variant?: 'primary' | 'secondary' | 'outline';
7
- onclick?: (event: Event) => void;
8
- /** If true, opens link in new tab with proper security attributes */
9
- external?: boolean;
10
- }
2
+ import type { NavbarAction } from '../types/navbar.js';
11
3
  interface BreadcrumbItem {
12
4
  label: string;
13
5
  href?: string;
@@ -890,7 +890,7 @@
890
890
  }
891
891
 
892
892
  .playground--standalone {
893
- height: 100vh;
893
+ height: 100%;
894
894
  background: var(--fd-layout-background, var(--fd-muted));
895
895
  }
896
896
 
@@ -0,0 +1,110 @@
1
+ <!--
2
+ PlaygroundApp Component
3
+
4
+ Full-page playground wrapper that pairs the FlowDrop Navbar with
5
+ PlaygroundStudio.
6
+
7
+ When importing this component directly (rather than via mountPlaygroundApp),
8
+ call initializeSettings() before mount — the navbar's settings modal reads
9
+ from the settings store.
10
+ -->
11
+
12
+ <script lang="ts">
13
+ import Navbar from '../Navbar.svelte';
14
+ import PlaygroundStudio from './PlaygroundStudio.svelte';
15
+ import type { Workflow } from '../../types/index.js';
16
+ import type { EndpointConfig } from '../../config/endpoints.js';
17
+ import type { PlaygroundConfig } from '../../types/playground.js';
18
+ import type { SettingsCategory } from '../../types/settings.js';
19
+ import type { NavbarAction } from '../../types/navbar.js';
20
+
21
+ interface Props {
22
+ workflowId: string;
23
+ workflow?: Workflow;
24
+ mode?: 'standalone' | 'embedded';
25
+ endpointConfig?: EndpointConfig;
26
+ config?: PlaygroundConfig;
27
+ showNavbar?: boolean;
28
+ navbarTitle?: string;
29
+ primaryActions?: NavbarAction[];
30
+ showSettings?: boolean;
31
+ settingsCategories?: SettingsCategory[];
32
+ showSettingsSyncButton?: boolean;
33
+ showSettingsResetButton?: boolean;
34
+ initialSessionId?: string;
35
+ initialPipelineOpen?: boolean;
36
+ minChatWidth?: number;
37
+ initialPipelineWidth?: number;
38
+ onClose?: () => void;
39
+ onSessionNavigate?: (sessionId: string) => void;
40
+ }
41
+
42
+ let {
43
+ workflowId,
44
+ workflow,
45
+ mode = 'standalone',
46
+ endpointConfig,
47
+ config = {},
48
+ showNavbar = true,
49
+ navbarTitle,
50
+ primaryActions = [],
51
+ showSettings = true,
52
+ settingsCategories,
53
+ showSettingsSyncButton,
54
+ showSettingsResetButton,
55
+ initialSessionId,
56
+ initialPipelineOpen,
57
+ minChatWidth,
58
+ initialPipelineWidth,
59
+ onClose,
60
+ onSessionNavigate
61
+ }: Props = $props();
62
+
63
+ const displayTitle = $derived(navbarTitle ?? workflow?.name ?? 'Playground');
64
+ </script>
65
+
66
+ <div class="fd-playground-app">
67
+ {#if showNavbar}
68
+ <Navbar
69
+ title={displayTitle}
70
+ {primaryActions}
71
+ showStatus={false}
72
+ {showSettings}
73
+ {settingsCategories}
74
+ {showSettingsSyncButton}
75
+ {showSettingsResetButton}
76
+ />
77
+ {/if}
78
+ <div class="fd-playground-app__content">
79
+ <PlaygroundStudio
80
+ {workflowId}
81
+ {workflow}
82
+ {mode}
83
+ {endpointConfig}
84
+ {config}
85
+ {initialSessionId}
86
+ {initialPipelineOpen}
87
+ {minChatWidth}
88
+ {initialPipelineWidth}
89
+ {onClose}
90
+ {onSessionNavigate}
91
+ />
92
+ </div>
93
+ </div>
94
+
95
+ <style>
96
+ .fd-playground-app {
97
+ display: flex;
98
+ flex-direction: column;
99
+ height: 100%;
100
+ width: 100%;
101
+ overflow: hidden;
102
+ background: var(--fd-background);
103
+ }
104
+
105
+ .fd-playground-app__content {
106
+ flex: 1;
107
+ min-height: 0;
108
+ overflow: hidden;
109
+ }
110
+ </style>
@@ -0,0 +1,28 @@
1
+ import type { Workflow } from '../../types/index.js';
2
+ import type { EndpointConfig } from '../../config/endpoints.js';
3
+ import type { PlaygroundConfig } from '../../types/playground.js';
4
+ import type { SettingsCategory } from '../../types/settings.js';
5
+ import type { NavbarAction } from '../../types/navbar.js';
6
+ interface Props {
7
+ workflowId: string;
8
+ workflow?: Workflow;
9
+ mode?: 'standalone' | 'embedded';
10
+ endpointConfig?: EndpointConfig;
11
+ config?: PlaygroundConfig;
12
+ showNavbar?: boolean;
13
+ navbarTitle?: string;
14
+ primaryActions?: NavbarAction[];
15
+ showSettings?: boolean;
16
+ settingsCategories?: SettingsCategory[];
17
+ showSettingsSyncButton?: boolean;
18
+ showSettingsResetButton?: boolean;
19
+ initialSessionId?: string;
20
+ initialPipelineOpen?: boolean;
21
+ minChatWidth?: number;
22
+ initialPipelineWidth?: number;
23
+ onClose?: () => void;
24
+ onSessionNavigate?: (sessionId: string) => void;
25
+ }
26
+ declare const PlaygroundApp: import("svelte").Component<Props, {}, "">;
27
+ type PlaygroundApp = ReturnType<typeof PlaygroundApp>;
28
+ export default PlaygroundApp;
@@ -112,6 +112,7 @@
112
112
  */
113
113
  export { default as Playground } from '../components/playground/Playground.svelte';
114
114
  export { default as PlaygroundStudio } from '../components/playground/PlaygroundStudio.svelte';
115
+ export { default as PlaygroundApp } from '../components/playground/PlaygroundApp.svelte';
115
116
  export { default as PlaygroundModal } from '../components/playground/PlaygroundModal.svelte';
116
117
  export { default as ChatPanel } from '../components/playground/ChatPanel.svelte';
117
118
  export { default as PipelinePanel } from '../components/playground/PipelinePanel.svelte';
@@ -131,5 +132,5 @@ export type { InterruptType, InterruptStatus, Interrupt, InterruptChoice, Interr
131
132
  export { isInterruptMetadata, extractInterruptMetadata, metadataToInterrupt, defaultInterruptPollingConfig } from '../types/interrupt.js';
132
133
  export { getInterruptsMap, getPendingInterruptIds, getPendingInterrupts, getPendingInterruptCount, getResolvedInterrupts, getIsAnySubmitting, interruptActions, getInterrupt, isInterruptPending, isInterruptSubmitting, getInterruptError, getInterruptByMessageId, interruptHasError } from '../stores/interruptStore.svelte.js';
133
134
  export type { InterruptWithState } from '../stores/interruptStore.svelte.js';
134
- export { mountPlayground, unmountPlayground, mountPlaygroundStudio, type PlaygroundMountOptions, type PlaygroundStudioMountOptions, type MountedPlayground } from './mount.js';
135
+ export { mountPlayground, unmountPlayground, mountPlaygroundStudio, mountPlaygroundApp, type PlaygroundMountOptions, type PlaygroundStudioMountOptions, type PlaygroundAppMountOptions, type MountedPlayground } from './mount.js';
135
136
  export { createEndpointConfig, defaultEndpointConfig, buildEndpointUrl, type EndpointConfig } from '../config/endpoints.js';
@@ -115,6 +115,7 @@
115
115
  // ============================================================================
116
116
  export { default as Playground } from '../components/playground/Playground.svelte';
117
117
  export { default as PlaygroundStudio } from '../components/playground/PlaygroundStudio.svelte';
118
+ export { default as PlaygroundApp } from '../components/playground/PlaygroundApp.svelte';
118
119
  export { default as PlaygroundModal } from '../components/playground/PlaygroundModal.svelte';
119
120
  export { default as ChatPanel } from '../components/playground/ChatPanel.svelte';
120
121
  export { default as PipelinePanel } from '../components/playground/PipelinePanel.svelte';
@@ -174,7 +175,7 @@ getInterrupt, isInterruptPending, isInterruptSubmitting, getInterruptError, getI
174
175
  // ============================================================================
175
176
  // Playground Mount Functions (for vanilla JS / Drupal / IIFE integration)
176
177
  // ============================================================================
177
- export { mountPlayground, unmountPlayground, mountPlaygroundStudio } from './mount.js';
178
+ export { mountPlayground, unmountPlayground, mountPlaygroundStudio, mountPlaygroundApp } from './mount.js';
178
179
  // ============================================================================
179
180
  // Endpoint Configuration (re-exported for convenience)
180
181
  // ============================================================================
@@ -46,6 +46,8 @@
46
46
  import type { Workflow } from '../types/index.js';
47
47
  import type { EndpointConfig } from '../config/endpoints.js';
48
48
  import type { PlaygroundMode, PlaygroundConfig, PlaygroundSession, PlaygroundMessagesApiResponse, PlaygroundSessionStatus } from '../types/playground.js';
49
+ import type { PartialSettings, SettingsCategory } from '../types/settings.js';
50
+ import type { NavbarAction } from '../types/navbar.js';
49
51
  /**
50
52
  * Mount options for Playground component
51
53
  */
@@ -83,13 +85,15 @@ export interface PlaygroundMountOptions {
83
85
  */
84
86
  config?: PlaygroundConfig;
85
87
  /**
86
- * Container height (CSS value)
87
- * @default "100%"
88
+ * Container height (CSS value). If omitted, the library does NOT set an
89
+ * inline height — the host's own CSS owns sizing. Pass a definite value
90
+ * (e.g. `"100dvh"`, `"600px"`) only when mounting into an unsized
91
+ * container.
88
92
  */
89
93
  height?: string;
90
94
  /**
91
- * Container width (CSS value)
92
- * @default "100%"
95
+ * Container width (CSS value). If omitted, the library does NOT set an
96
+ * inline width. See `height` for rationale.
93
97
  */
94
98
  width?: string;
95
99
  /**
@@ -103,6 +107,12 @@ export interface PlaygroundMountOptions {
103
107
  * @param previousStatus - The previous session status
104
108
  */
105
109
  onSessionStatusChange?: (status: PlaygroundSessionStatus, previousStatus: PlaygroundSessionStatus) => void;
110
+ /**
111
+ * Optional setting overrides deep-merged over current settings before mount.
112
+ * Theme is re-initialized on every mount regardless. Mirrors mountFlowDropApp's
113
+ * `settings` option.
114
+ */
115
+ settings?: PartialSettings;
106
116
  }
107
117
  /**
108
118
  * Return type for mounted Playground instance
@@ -208,3 +218,45 @@ export interface PlaygroundStudioMountOptions extends PlaygroundMountOptions {
208
218
  onSessionNavigate?: (sessionId: string) => void;
209
219
  }
210
220
  export declare function mountPlaygroundStudio(container: HTMLElement, options: PlaygroundStudioMountOptions): Promise<MountedPlayground>;
221
+ export interface PlaygroundAppMountOptions extends Omit<PlaygroundStudioMountOptions, 'mode'> {
222
+ /**
223
+ * Display mode. Modal is unsupported — use mountPlayground() for that.
224
+ * @default "standalone"
225
+ */
226
+ mode?: 'standalone' | 'embedded';
227
+ /** Render the FlowDrop Navbar above the playground (default: true). */
228
+ showNavbar?: boolean;
229
+ /** Title shown in the navbar. Falls back to the workflow name, then "Playground". */
230
+ navbarTitle?: string;
231
+ /** Action buttons rendered in the navbar. Passed straight through to <Navbar primaryActions>. */
232
+ primaryActions?: NavbarAction[];
233
+ /** Show the settings gear icon in the navbar (default: true). */
234
+ showSettings?: boolean;
235
+ /** Restrict which settings categories are exposed in the settings modal. */
236
+ settingsCategories?: SettingsCategory[];
237
+ /** Show the "Sync to Cloud" button in the settings modal. */
238
+ showSettingsSyncButton?: boolean;
239
+ /** Show the reset buttons in the settings modal. */
240
+ showSettingsResetButton?: boolean;
241
+ }
242
+ /**
243
+ * Mount the full-page PlaygroundApp (Navbar + PlaygroundStudio) into a container.
244
+ *
245
+ * Use this when you want the same chrome as the FlowDrop editor — logo,
246
+ * branding, and settings modal — wrapped around the playground. For an
247
+ * embeddable split-pane without the navbar, use mountPlaygroundStudio().
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * const app = await mountPlaygroundApp(container, {
252
+ * workflowId: 'wf-123',
253
+ * endpointConfig: createEndpointConfig('/api/flowdrop'),
254
+ * navbarTitle: 'My Workflow',
255
+ * primaryActions: [
256
+ * { label: 'Edit', href: '/workflows/wf-123/edit', icon: 'mdi:pencil-outline', variant: 'secondary' },
257
+ * { label: 'Workflows', href: '/workflows', icon: 'mdi:arrow-left', variant: 'outline' }
258
+ * ]
259
+ * });
260
+ * ```
261
+ */
262
+ export declare function mountPlaygroundApp(container: HTMLElement, options: PlaygroundAppMountOptions): Promise<MountedPlayground>;
@@ -47,6 +47,8 @@ import { mount, unmount } from 'svelte';
47
47
  import Playground from '../components/playground/Playground.svelte';
48
48
  import PlaygroundModal from '../components/playground/PlaygroundModal.svelte';
49
49
  import PlaygroundStudio from '../components/playground/PlaygroundStudio.svelte';
50
+ import PlaygroundApp from '../components/playground/PlaygroundApp.svelte';
51
+ import { initializeSettings } from '../stores/settingsStore.svelte.js';
50
52
  import { setEndpointConfig } from '../services/api.js';
51
53
  import { playgroundService } from '../services/playgroundService.js';
52
54
  import { getCurrentSession, getSessions, getMessages, getIsExecuting, playgroundActions, applyServerResponse, subscribeToSessionStatus } from '../stores/playgroundStore.svelte.js';
@@ -62,6 +64,28 @@ async function resolveEndpointConfig(endpointConfig) {
62
64
  setEndpointConfig(resolved);
63
65
  return resolved;
64
66
  }
67
+ /**
68
+ * Shared prelude for the playground mount functions: resolves the endpoint
69
+ * config and initializes settings/theme. Each mount function still owns its
70
+ * own argument validation and container sizing (those vary per mode).
71
+ */
72
+ async function prepareMount(options) {
73
+ const finalEndpointConfig = await resolveEndpointConfig(options.endpointConfig);
74
+ await initializeSettings({ defaults: options.settings });
75
+ return finalEndpointConfig;
76
+ }
77
+ /**
78
+ * Apply caller-supplied height/width as inline styles. When omitted, the host
79
+ * CSS owns container sizing — this is deliberate: a default of `100%` does not
80
+ * resolve inside parents with only `min-height` set (a common pattern in
81
+ * Drupal admin chrome), collapsing the playground to zero.
82
+ */
83
+ function sizeContainer(container, height, width) {
84
+ if (height !== undefined)
85
+ container.style.height = height;
86
+ if (width !== undefined)
87
+ container.style.width = width;
88
+ }
65
89
  function buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange) {
66
90
  const pollingInterval = config.pollingInterval ?? 1500;
67
91
  const unsubscribeStatus = onSessionStatusChange
@@ -122,7 +146,7 @@ function buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusCh
122
146
  * ```
123
147
  */
124
148
  export async function mountPlayground(container, options) {
125
- const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height = '100%', width = '100%', onClose, onSessionStatusChange } = options;
149
+ const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, settings: initialSettings, onClose, onSessionStatusChange } = options;
126
150
  // Validate required parameters
127
151
  if (!workflowId) {
128
152
  throw new Error('workflowId is required for mountPlayground()');
@@ -134,14 +158,16 @@ export async function mountPlayground(container, options) {
134
158
  if (mode === 'modal' && !onClose) {
135
159
  throw new Error('onClose callback is required for modal mode');
136
160
  }
137
- const finalEndpointConfig = await resolveEndpointConfig(endpointConfig);
161
+ const finalEndpointConfig = await prepareMount({
162
+ endpointConfig,
163
+ settings: initialSettings
164
+ });
138
165
  let targetContainer = container;
139
166
  if (mode === 'modal') {
140
167
  targetContainer = document.body;
141
168
  }
142
169
  else {
143
- container.style.height = height;
144
- container.style.width = width;
170
+ sizeContainer(container, height, width);
145
171
  }
146
172
  let svelteApp;
147
173
  if (mode === 'modal') {
@@ -199,7 +225,7 @@ export function unmountPlayground(app) {
199
225
  }
200
226
  }
201
227
  export async function mountPlaygroundStudio(container, options) {
202
- const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height = '100%', width = '100%', initialPipelineOpen, minChatWidth, initialPipelineWidth, onClose, onSessionNavigate, onSessionStatusChange } = options;
228
+ const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, initialPipelineOpen, minChatWidth, initialPipelineWidth, settings: initialSettings, onClose, onSessionNavigate, onSessionStatusChange } = options;
203
229
  if (!workflowId) {
204
230
  throw new Error('workflowId is required for mountPlaygroundStudio()');
205
231
  }
@@ -209,9 +235,11 @@ export async function mountPlaygroundStudio(container, options) {
209
235
  if (mode === 'modal') {
210
236
  throw new Error('modal mode is not supported by mountPlaygroundStudio() — use mountPlayground() instead');
211
237
  }
212
- const finalEndpointConfig = await resolveEndpointConfig(endpointConfig);
213
- container.style.height = height;
214
- container.style.width = width;
238
+ const finalEndpointConfig = await prepareMount({
239
+ endpointConfig,
240
+ settings: initialSettings
241
+ });
242
+ sizeContainer(container, height, width);
215
243
  const svelteApp = mount(PlaygroundStudio, {
216
244
  target: container,
217
245
  props: {
@@ -230,3 +258,67 @@ export async function mountPlaygroundStudio(container, options) {
230
258
  });
231
259
  return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
232
260
  }
261
+ /**
262
+ * Mount the full-page PlaygroundApp (Navbar + PlaygroundStudio) into a container.
263
+ *
264
+ * Use this when you want the same chrome as the FlowDrop editor — logo,
265
+ * branding, and settings modal — wrapped around the playground. For an
266
+ * embeddable split-pane without the navbar, use mountPlaygroundStudio().
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const app = await mountPlaygroundApp(container, {
271
+ * workflowId: 'wf-123',
272
+ * endpointConfig: createEndpointConfig('/api/flowdrop'),
273
+ * navbarTitle: 'My Workflow',
274
+ * primaryActions: [
275
+ * { label: 'Edit', href: '/workflows/wf-123/edit', icon: 'mdi:pencil-outline', variant: 'secondary' },
276
+ * { label: 'Workflows', href: '/workflows', icon: 'mdi:arrow-left', variant: 'outline' }
277
+ * ]
278
+ * });
279
+ * ```
280
+ */
281
+ export async function mountPlaygroundApp(container, options) {
282
+ const { workflowId, workflow, mode = 'standalone', initialSessionId, endpointConfig, config = {}, height, width, showNavbar = true, navbarTitle, primaryActions, showSettings = true, settingsCategories, showSettingsSyncButton, showSettingsResetButton, initialPipelineOpen, minChatWidth, initialPipelineWidth, settings: initialSettings, onClose, onSessionNavigate, onSessionStatusChange } = options;
283
+ if (!workflowId) {
284
+ throw new Error('workflowId is required for mountPlaygroundApp()');
285
+ }
286
+ if (!container) {
287
+ throw new Error('container element is required for mountPlaygroundApp()');
288
+ }
289
+ // Positive narrowing (not `=== 'modal'`) because PlaygroundAppMountOptions
290
+ // Omit-narrows `mode`, so the type lies for JS callers — check the values
291
+ // we actually accept instead of the one we don't.
292
+ if (mode !== 'standalone' && mode !== 'embedded') {
293
+ throw new Error(`mountPlaygroundApp(): mode must be 'standalone' or 'embedded', got ${String(mode)}`);
294
+ }
295
+ const finalEndpointConfig = await prepareMount({
296
+ endpointConfig,
297
+ settings: initialSettings
298
+ });
299
+ sizeContainer(container, height, width);
300
+ const svelteApp = mount(PlaygroundApp, {
301
+ target: container,
302
+ props: {
303
+ workflowId,
304
+ workflow,
305
+ mode,
306
+ initialSessionId,
307
+ endpointConfig: finalEndpointConfig,
308
+ config,
309
+ showNavbar,
310
+ navbarTitle,
311
+ primaryActions,
312
+ showSettings,
313
+ settingsCategories,
314
+ showSettingsSyncButton,
315
+ showSettingsResetButton,
316
+ initialPipelineOpen,
317
+ minChatWidth,
318
+ initialPipelineWidth,
319
+ onClose,
320
+ onSessionNavigate
321
+ }
322
+ });
323
+ return buildMountedPlayground(svelteApp, workflowId, config, onSessionStatusChange);
324
+ }
@@ -14,16 +14,8 @@ import type { FlowDropTheme, FlowDropThemeName } from './types/theme.js';
14
14
  import type { WorkflowFormatAdapter } from './registry/workflowFormatRegistry.js';
15
15
  import './registry/builtinFormats.js';
16
16
  import type { PartialSettings, SettingsCategory } from './types/settings.js';
17
- /**
18
- * Navbar action configuration
19
- */
20
- export interface NavbarAction {
21
- label: string;
22
- href: string;
23
- icon?: string;
24
- variant?: 'primary' | 'secondary' | 'outline';
25
- onclick?: (event: Event) => void;
26
- }
17
+ import type { NavbarAction } from './types/navbar.js';
18
+ export type { NavbarAction };
27
19
  /**
28
20
  * Mount options for FlowDrop App
29
21
  */
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Navbar action button configuration.
3
+ *
4
+ * Rendered as a link by `<Navbar>` and the mount functions that wrap it.
5
+ */
6
+ export interface NavbarAction {
7
+ label: string;
8
+ href: string;
9
+ icon?: string;
10
+ variant?: 'primary' | 'secondary' | 'outline';
11
+ onclick?: (event: Event) => void;
12
+ /** If true, opens link in new tab with `rel="noopener noreferrer"`. */
13
+ external?: boolean;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A drop-in visual workflow editor for any web application. You own the backend. You own the data. You own the orchestration.",
4
4
  "license": "MIT",
5
5
  "private": false,
6
- "version": "1.10.0",
6
+ "version": "1.11.0",
7
7
  "author": "Shibin Das (D34dMan)",
8
8
  "bugs": {
9
9
  "url": "https://github.com/flowdrop-io/flowdrop/issues"