create-omniflow-plugin 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +39 -7
  2. package/dist/index.js +236 -66
  3. package/package.json +6 -6
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Interactive CLI scaffolder for [OmniFlow](https://github.com/agnistack/omniflow) plugins.
4
4
 
5
- Generates a ready-to-build Gradle project with a Java ingestor, optional Next.js micro UI, and all the wiring needed to upload the plugin as a JAR to a running OmniFlow backend.
5
+ Generates a ready-to-build Gradle project with a Java ingestor, optional AI tools (exposed to OmniFlow AI chat), optional webhook action, optional Next.js micro UI, and all the wiring needed to upload the plugin as a JAR to a running OmniFlow backend.
6
6
 
7
7
  ## Usage
8
8
 
@@ -30,9 +30,11 @@ The CLI will ask a few questions and scaffold a new directory named after your p
30
30
  | Description | Short description of what the plugin ingests |
31
31
  | Author | Your name or organisation |
32
32
  | Java base package | Root Java package (e.g. `io.github.acme.plugins.myingestor`) |
33
- | Ingestor type key | Used in API paths — `/api/ingest/{type}` |
33
+ | Ingestor type key | Used in API paths — `/api/v1/ingest/{type}` |
34
+ | Include AI tool? | Scaffold a `PluginTool` that exposes data to OmniFlow AI chat |
35
+ | Include webhook action? | Scaffold a `PluginAction` dispatched from OmniFlow scripts |
34
36
  | Include Next.js UI? | Whether to scaffold a micro frontend |
35
- | OmniFlow plugin-api version | Version of `omniflow-plugin-api` to depend on |
37
+ | OmniFlow plugin-api version | Version of `omniflow-plugin-api` to depend on (default: `0.1`) |
36
38
  | OmniFlow API base URL | Backend URL for local UI dev (e.g. `http://localhost:8080`) |
37
39
 
38
40
  ## What gets generated
@@ -43,9 +45,14 @@ The CLI will ask a few questions and scaffold a new directory named after your p
43
45
  ├── settings.gradle
44
46
  ├── gradlew / gradlew.bat # Wrapper stubs (run `gradle wrapper` for the real ones)
45
47
  ├── src/main/java/…/
46
- │ ├── <Name>Plugin.java # OmniflowPlugin implementation
47
- └── <Name>Ingestor.java # PluginIngestor implementation
48
- └── ui/ # Only when "Include Next.js UI?" = yes
48
+ │ ├── <Name>Plugin.java # OmniflowPlugin implementation
49
+ ├── <Name>Ingestor.java # PluginIngestor implementation
50
+ │ ├── <Name>WebhookAction.java # Only when "Include webhook action?" = yes
51
+ │ └── tools/
52
+ │ └── <Name>QueryTool.java # Only when "Include AI tool?" = yes
53
+ ├── src/main/resources/
54
+ │ └── META-INF/services/… # ServiceLoader descriptor (auto-generated)
55
+ └── ui/ # Only when "Include Next.js UI?" = yes
49
56
  ├── package.json
50
57
  ├── tsconfig.json
51
58
  ├── next.config.ts
@@ -89,7 +96,7 @@ To skip the UI build during development:
89
96
  ## Upload to OmniFlow
90
97
 
91
98
  ```sh
92
- curl -X POST http://localhost:8080/api/plugins/upload \
99
+ curl -X POST http://localhost:8080/api/v1/plugins/upload \
93
100
  -b "JSESSIONID=<your-session>" \
94
101
  -F "file=@build/libs/<plugin-id>-1.0.0.jar"
95
102
  ```
@@ -108,6 +115,31 @@ npm run dev # starts on http://localhost:3000
108
115
 
109
116
  The `NEXT_PUBLIC_API_URL` variable in `.env.local` controls which OmniFlow backend the UI talks to.
110
117
 
118
+ ## AI tools
119
+
120
+ When you include an AI tool, the scaffolder generates a `PluginTool` implementation that the OmniFlow AI agent can invoke. Tool names are automatically namespaced as `{pluginId}__{name}` to prevent clashes.
121
+
122
+ After installing the plugin, users can ask the AI agent questions about the ingested data — the agent calls your tool behind the scenes:
123
+
124
+ ```
125
+ User: "Show me the most recent records from my-plugin"
126
+ Agent: [calls my-plugin__query-records with limit=5]
127
+ ```
128
+
129
+ The generated tool uses `PluginContext.queryRecords()` to fetch data from the host database — no in-memory state required. Customize the `getInputSchema()` and `execute()` methods to add filters, aggregations, or any query logic specific to your data.
130
+
131
+ See the [deploy-tracker example plugin](https://github.com/ash-thakur-rh/omniflow/tree/main/example-plugins/deploy-tracker) for a full example with multiple AI tools.
132
+
133
+ ## Webhook action
134
+
135
+ When you include a webhook action, the scaffolder generates a `PluginAction` that posts notifications to a configurable webhook URL. Set the environment variable (e.g. `MY_PLUGIN_WEBHOOK_URL`) to your Slack, Teams, or custom endpoint.
136
+
137
+ Trigger it from an OmniFlow script:
138
+
139
+ ```
140
+ context.dispatch("my-plugin-notify", "Deploy Complete", "v2.3.1 is live", metadata)
141
+ ```
142
+
111
143
  ---
112
144
 
113
145
  ## Contributing to `create-omniflow-plugin`
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ function toPascalCase(id) {
15
15
  function javaTemplates(a) {
16
16
  const className = toPascalCase(a.pluginId);
17
17
  const pkgPath = a.javaPackage.replace(/\./g, "/");
18
- return {
18
+ const files = {
19
19
  // ── Gradle wrapper stub (real wrapper needs `gradle wrapper` to generate) ──
20
20
  "gradlew": gradlew(),
21
21
  "gradlew.bat": gradlewBat(),
@@ -24,10 +24,20 @@ function javaTemplates(a) {
24
24
  // ── Java sources ──────────────────────────────────────────────────────────
25
25
  [`src/main/java/${pkgPath}/${className}Plugin.java`]: pluginClass(a, className),
26
26
  [`src/main/java/${pkgPath}/${className}Ingestor.java`]: ingestorClass(a, className),
27
+ // ── ServiceLoader descriptor (required for plugin discovery) ──────────────
28
+ "src/main/resources/META-INF/services/io.github.agnistack.omniflow.pluginapi.OmniflowPlugin": `${a.javaPackage}.${className}Plugin
29
+ `,
27
30
  // ── CI / ingestor scripts ─────────────────────────────────────────────────
28
31
  "scripts/ingest.sh": ingestScript(a),
29
32
  "scripts/upload-plugin.sh": uploadPluginScript(a)
30
33
  };
34
+ if (a.hasTools) {
35
+ files[`src/main/java/${pkgPath}/tools/${className}QueryTool.java`] = toolClass(a, className);
36
+ }
37
+ if (a.hasAction) {
38
+ files[`src/main/java/${pkgPath}/${className}WebhookAction.java`] = actionClass(a, className);
39
+ }
40
+ return files;
31
41
  }
32
42
  function settingsGradle(a) {
33
43
  return `rootProject.name = '${a.pluginId}'
@@ -100,7 +110,7 @@ repositories {
100
110
 
101
111
  dependencies {
102
112
  compileOnly 'io.github.agnistack:omniflow-plugin-api:${a.omniflowVersion}'
103
- implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2'
113
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.21.2'
104
114
  }
105
115
  ${uiBlock}
106
116
  jar {
@@ -111,58 +121,73 @@ jar {
111
121
  duplicatesStrategy = DuplicatesStrategy.EXCLUDE
112
122
  manifest {
113
123
  attributes(
114
- 'Plugin-Id' : '${a.pluginId}',
115
- 'Plugin-Version': project.version,
116
- 'Plugin-Class' : '${a.javaPackage}.${className}Plugin'
124
+ 'Plugin-Id' : '${a.pluginId}',
125
+ 'Plugin-Version' : project.version,
126
+ 'Plugin-Api-Version' : '${a.omniflowVersion}',
127
+ 'Plugin-Class' : '${a.javaPackage}.${className}Plugin',
128
+ 'Implementation-Version': project.version
117
129
  )
118
130
  }
119
131
  }
120
132
  `;
121
133
  }
122
134
  function pluginClass(a, className) {
135
+ const toolImport = a.hasTools ? `
136
+ import ${a.javaPackage}.tools.${className}QueryTool;` : "";
137
+ const toolsMethod = a.hasTools ? `
138
+ private PluginContext ctx;
139
+
140
+ @Override
141
+ public List<PluginTool> tools() {
142
+ return List.of(new ${className}QueryTool(ctx));
143
+ }
144
+ ` : "";
145
+ const actionsMethod = a.hasAction ? `
146
+ @Override
147
+ public List<PluginAction> actions() {
148
+ return List.of(new ${className}WebhookAction());
149
+ }
150
+ ` : "";
151
+ const onLoadBody = a.hasTools ? ` this.ctx = ctx;
152
+ ctx.log("${a.pluginName} plugin loaded -> ingestor type: ${a.ingestorType}");` : ` ctx.log("${a.pluginName} plugin loaded -> ingestor type: ${a.ingestorType}");`;
153
+ const onUnload = a.hasTools ? `
154
+ @Override
155
+ public void onUnload() {
156
+ this.ctx = null;
157
+ }
158
+ ` : "";
123
159
  return `package ${a.javaPackage};
124
160
 
125
161
  import io.github.agnistack.omniflow.pluginapi.*;
126
- import java.util.List;
162
+ import java.util.List;${toolImport}
127
163
 
128
164
  public class ${className}Plugin implements OmniflowPlugin {
129
-
165
+ ${toolsMethod}
130
166
  @Override
131
167
  public PluginMetadata metadata() {
132
- return PluginMetadata.builder()
133
- .id("${a.pluginId}")
134
- .name("${a.pluginName}")
135
- .version("1.0.0")
136
- .description("${a.description}")
137
- .author("${a.author}")
138
- .build();
168
+ return new PluginMetadata(
169
+ "${a.pluginId}",
170
+ "${a.pluginName}",
171
+ PluginMetadata.resolveVersion(${className}Plugin.class, "1.0.0"),
172
+ "${a.description}",
173
+ "${a.author}");
139
174
  }
140
175
 
141
176
  @Override
142
177
  public List<PluginIngestor<?>> ingestors() {
143
178
  return List.of(new ${className}Ingestor());
144
179
  }
145
-
146
- @Override
147
- public List<PluginAction> actions() {
148
- return List.of();
149
- }
150
-
180
+ ${actionsMethod}
151
181
  @Override
152
182
  public boolean hasUi() {
153
183
  return ${a.hasUi};
154
184
  }
155
185
 
156
186
  @Override
157
- public void onLoad(PluginContext context) {
158
- // Called when the plugin is loaded \u2014 register schemas, etc.
159
- }
160
-
161
- @Override
162
- public void onUnload() {
163
- // Called when the plugin is unloaded \u2014 release resources.
187
+ public void onLoad(PluginContext ctx) {
188
+ ${onLoadBody}
164
189
  }
165
- }
190
+ ${onUnload}}
166
191
  `;
167
192
  }
168
193
  function ingestorClass(a, className) {
@@ -171,11 +196,9 @@ function ingestorClass(a, className) {
171
196
  import com.fasterxml.jackson.databind.ObjectMapper;
172
197
  import io.github.agnistack.omniflow.pluginapi.*;
173
198
  import java.io.InputStream;
174
- import java.time.Instant;
175
199
  import java.util.Map;
176
- import java.util.UUID;
177
200
 
178
- public class ${className}Ingestor implements PluginIngestor<PluginDataRecord> {
201
+ public class ${className}Ingestor implements PluginIngestor<SimplePluginDataRecord> {
179
202
 
180
203
  private static final ObjectMapper MAPPER = new ObjectMapper();
181
204
 
@@ -185,17 +208,149 @@ public class ${className}Ingestor implements PluginIngestor<PluginDataRecord> {
185
208
  }
186
209
 
187
210
  @Override
188
- public PluginDataRecord ingest(InputStream data) throws Exception {
211
+ public SimplePluginDataRecord ingest(InputStream data) throws Exception {
189
212
  // TODO: parse your data format here
190
213
  @SuppressWarnings("unchecked")
191
214
  Map<String, Object> parsed = MAPPER.readValue(data, Map.class);
192
215
 
193
- return PluginDataRecord.builder()
194
- .id(UUID.randomUUID().toString())
195
- .type(getType())
196
- .timestamp(Instant.now())
197
- .fields(parsed)
216
+ return SimplePluginDataRecord.of(getType(), parsed);
217
+ }
218
+ }
219
+ `;
220
+ }
221
+ function toolClass(a, className) {
222
+ return `package ${a.javaPackage}.tools;
223
+
224
+ import io.github.agnistack.omniflow.pluginapi.PluginContext;
225
+ import io.github.agnistack.omniflow.pluginapi.PluginTool;
226
+ import java.time.Instant;
227
+ import java.time.temporal.ChronoUnit;
228
+ import java.util.List;
229
+ import java.util.Map;
230
+ import java.util.stream.Collectors;
231
+
232
+ /**
233
+ * AI tool: {@code query-records}
234
+ * Namespaced by the host as {@code ${a.pluginId}__query-records}.
235
+ *
236
+ * Returns recently ingested records, optionally filtered by a field value.
237
+ * The OmniFlow AI agent can call this tool to answer user questions about
238
+ * the data ingested by this plugin.
239
+ */
240
+ public class ${className}QueryTool implements PluginTool {
241
+
242
+ private final PluginContext ctx;
243
+
244
+ public ${className}QueryTool(PluginContext ctx) {
245
+ this.ctx = ctx;
246
+ }
247
+
248
+ @Override
249
+ public String getName() {
250
+ return "query-records";
251
+ }
252
+
253
+ @Override
254
+ public String getDescription() {
255
+ return "Query recently ingested ${a.pluginName} records. "
256
+ + "Returns the most recent records, optionally limited by count.";
257
+ }
258
+
259
+ @Override
260
+ public String getInputSchema() {
261
+ return """
262
+ {
263
+ "type": "object",
264
+ "properties": {
265
+ "limit": {
266
+ "type": "integer",
267
+ "description": "Maximum number of results to return (1-50, default 10)"
268
+ }
269
+ }
270
+ }""";
271
+ }
272
+
273
+ @Override
274
+ public String execute(Map<String, Object> args) throws Exception {
275
+ int limit = args.get("limit") instanceof Number n ? n.intValue() : 10;
276
+ limit = Math.max(1, Math.min(limit, 50));
277
+
278
+ Instant since = Instant.now().minus(90, ChronoUnit.DAYS);
279
+ List<Map<String, Object>> records = ctx.queryRecords("${a.ingestorType}", limit, since);
280
+
281
+ if (records.isEmpty()) {
282
+ return "No ${a.pluginName} records found.";
283
+ }
284
+
285
+ StringBuilder sb = new StringBuilder();
286
+ sb.append("Found ").append(records.size()).append(" record(s):\\n\\n");
287
+
288
+ for (Map<String, Object> rec : records) {
289
+ sb.append("- id=").append(rec.get("id"))
290
+ .append(" timestamp=").append(rec.get("timestamp"))
291
+ .append(" fields=").append(rec)
292
+ .append("\\n");
293
+ }
294
+
295
+ return sb.toString();
296
+ }
297
+ }
298
+ `;
299
+ }
300
+ function actionClass(a, className) {
301
+ return `package ${a.javaPackage};
302
+
303
+ import io.github.agnistack.omniflow.pluginapi.PluginAction;
304
+ import java.net.URI;
305
+ import java.net.http.HttpClient;
306
+ import java.net.http.HttpRequest;
307
+ import java.net.http.HttpResponse;
308
+ import java.util.Map;
309
+
310
+ /**
311
+ * Webhook action: {@code ${a.pluginId}-notify}
312
+ *
313
+ * Posts a JSON payload to a webhook URL when dispatched from an OmniFlow script.
314
+ * Set the {@code ${a.pluginId.toUpperCase().replace(/-/g, "_")}_WEBHOOK_URL} environment variable
315
+ * to your Slack / Teams / custom webhook endpoint.
316
+ *
317
+ * Usage from an OmniFlow script:
318
+ * <pre>
319
+ * context.dispatch("${a.pluginId}-notify", "Title", "Message", metadata)
320
+ * </pre>
321
+ */
322
+ public class ${className}WebhookAction implements PluginAction {
323
+
324
+ private static final String ENV_KEY = "${a.pluginId.toUpperCase().replace(/-/g, "_")}_WEBHOOK_URL";
325
+
326
+ @Override
327
+ public String getType() {
328
+ return "${a.pluginId}-notify";
329
+ }
330
+
331
+ @Override
332
+ public boolean isConfigured() {
333
+ return System.getenv(ENV_KEY) != null;
334
+ }
335
+
336
+ @Override
337
+ public void execute(String title, String message, Map<String, String> metadata) throws Exception {
338
+ String url = System.getenv(ENV_KEY);
339
+ if (url == null || url.isBlank()) {
340
+ throw new IllegalStateException(ENV_KEY + " is not set");
341
+ }
342
+
343
+ String payload = String.format(
344
+ "{\\"text\\": \\"*%s*\\\\n%s\\"}", title.replace("\\"", "\\\\\\""), message.replace("\\"", "\\\\\\""));
345
+
346
+ HttpRequest request = HttpRequest.newBuilder()
347
+ .uri(URI.create(url))
348
+ .header("Content-Type", "application/json")
349
+ .POST(HttpRequest.BodyPublishers.ofString(payload))
198
350
  .build();
351
+
352
+ HttpClient.newHttpClient()
353
+ .send(request, HttpResponse.BodyHandlers.ofString());
199
354
  }
200
355
  }
201
356
  `;
@@ -232,7 +387,7 @@ API_KEY=\${OMNIFLOW_API_KEY:?Set OMNIFLOW_API_KEY environment variable}
232
387
  echo "Ingesting \${FILE} as type '${a.ingestorType}'..."
233
388
 
234
389
  HTTP_STATUS=$(curl -s -o /tmp/ingest_response.json -w "%{http_code}" \\
235
- -X POST "\${API_URL}/api/ingest/${a.ingestorType}" \\
390
+ -X POST "\${API_URL}/api/v1/ingest/${a.ingestorType}" \\
236
391
  -H "X-Api-Key: \${API_KEY}" \\
237
392
  -H "Content-Type: application/json" \\
238
393
  --data-binary "@\${FILE}")
@@ -269,7 +424,7 @@ echo "Building JAR..."
269
424
  echo "Uploading \${JAR} to \${API_URL}..."
270
425
 
271
426
  HTTP_STATUS=$(curl -s -o /tmp/upload_response.json -w "%{http_code}" \\
272
- -X POST "\${API_URL}/api/plugins/upload" \\
427
+ -X POST "\${API_URL}/api/v1/plugins/upload" \\
273
428
  -H "X-Api-Key: \${API_KEY}" \\
274
429
  -F "file=@\${JAR}")
275
430
 
@@ -314,26 +469,28 @@ function uiPackageJson(a) {
314
469
  lint: "next lint"
315
470
  },
316
471
  dependencies: {
317
- "@omniflow/ui": "latest",
318
- next: "16.2.1",
319
- react: "19.2.4",
320
- "react-dom": "19.2.4",
321
- swr: "^2.3.3"
472
+ "@agnistack/omniflow-ui": "0.1.4",
473
+ next: "16.2.6",
474
+ react: "19.2.6",
475
+ "react-dom": "19.2.6",
476
+ swr: "2.4.1"
322
477
  },
323
478
  devDependencies: {
324
- "@types/node": "^22",
479
+ "@types/node": "22.19.19",
325
480
  "@types/react": "19.2.14",
326
481
  "@types/react-dom": "19.2.3",
327
- autoprefixer: "^10.4.21",
328
- eslint: "^9.39.4",
329
- "eslint-config-next": "^16.2.1",
330
- postcss: "^8.5.3",
331
- tailwindcss: "^3.4.17",
332
- typescript: "^5"
482
+ autoprefixer: "10.5.0",
483
+ eslint: "9.39.4",
484
+ "eslint-config-next": "16.2.6",
485
+ postcss: "8.5.14",
486
+ tailwindcss: "3.4.19",
487
+ typescript: "5.9.3"
333
488
  },
334
489
  overrides: {
335
490
  "@types/react": "19.2.14",
336
- "@types/react-dom": "19.2.3"
491
+ "@types/react-dom": "19.2.3",
492
+ next: "$next",
493
+ postcss: "8.5.14"
337
494
  }
338
495
  },
339
496
  null,
@@ -373,11 +530,11 @@ function uiNextConfig(a) {
373
530
  const forJar = process.env.NEXT_BUILD_FOR_JAR === '1';
374
531
 
375
532
  const config: NextConfig = {
376
- transpilePackages: ['@omniflow/ui'],
533
+ transpilePackages: ['@agnistack/omniflow-ui'],
377
534
  output: 'export',
378
- // basePath and assetPrefix match the host's /api/plugins/{id}/ui/** route.
379
- basePath: forJar ? '/api/plugins/${a.pluginId}/ui' : '',
380
- assetPrefix: forJar ? '/api/plugins/${a.pluginId}/ui' : '',
535
+ // basePath and assetPrefix match the host's /api/v1/plugins/{id}/ui/** route.
536
+ basePath: forJar ? '/api/v1/plugins/${a.pluginId}/ui' : '',
537
+ assetPrefix: forJar ? '/api/v1/plugins/${a.pluginId}/ui' : '',
381
538
  images: { unoptimized: true },
382
539
  trailingSlash: false,
383
540
  };
@@ -393,7 +550,7 @@ const config: Config = {
393
550
  './app/**/*.{ts,tsx}',
394
551
  './components/**/*.{ts,tsx}',
395
552
  './lib/**/*.{ts,tsx}',
396
- './node_modules/@omniflow/ui/src/**/*.{ts,tsx}',
553
+ './node_modules/@agnistack/omniflow-ui/src/**/*.{ts,tsx}',
397
554
  ],
398
555
  darkMode: 'class',
399
556
  theme: { extend: {} },
@@ -448,8 +605,10 @@ body {
448
605
  }
449
606
  function uiLayout(a) {
450
607
  return `import type { Metadata } from 'next';
608
+ import { Suspense } from 'react';
451
609
  import './globals.css';
452
610
  import ThemeSync from '@/components/ThemeSync';
611
+ import { FilterProvider, FilterBar } from '@agnistack/omniflow-ui';
453
612
 
454
613
  export const metadata: Metadata = { title: '${a.pluginName} \u2014 OmniFlow' };
455
614
 
@@ -465,7 +624,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
465
624
  <span className="text-sm font-bold text-gray-900 dark:text-slate-100">${a.pluginName}</span>
466
625
  <span className="text-xs bg-purple-100 dark:bg-purple-900/50 text-purple-700 dark:text-purple-300 border border-purple-200 dark:border-purple-900 px-2 py-0.5 rounded-full">OmniFlow Plugin</span>
467
626
  </header>
468
- <main className="flex-1 p-6">{children}</main>
627
+ <FilterProvider defaultLimit="50">
628
+ <Suspense><FilterBar apiType="${a.ingestorType}" defaultLimit="50" limitOptions={[{ value: '20', label: 'Last 20' }, { value: '50', label: 'Last 50' }, { value: '100', label: 'Last 100' }]} statusOptions={[{ value: 'SUCCESS', label: 'Success' }, { value: 'FAILED', label: 'Failed' }]} /></Suspense>
629
+ <main className="flex-1 p-6">{children}</main>
630
+ </FilterProvider>
469
631
  </body>
470
632
  </html>
471
633
  );
@@ -476,7 +638,7 @@ function uiPage(a) {
476
638
  return `'use client';
477
639
 
478
640
  import useSWR from 'swr';
479
- import { EmptyState, cls } from '@omniflow/ui';
641
+ import { EmptyState, cls } from '@agnistack/omniflow-ui';
480
642
  import { fetchRecords, type PluginRecord } from '@/lib/api';
481
643
 
482
644
  export default function HomePage() {
@@ -490,7 +652,7 @@ export default function HomePage() {
490
652
  if (!records?.length) return (
491
653
  <EmptyState
492
654
  title="No ${a.pluginName} data ingested yet."
493
- description="POST data to /api/ingest/${a.ingestorType} to get started."
655
+ description="POST data to /api/v1/ingest/${a.ingestorType} to get started."
494
656
  />
495
657
  );
496
658
 
@@ -546,7 +708,7 @@ export default function ThemeSync() {
546
708
  }
547
709
  function uiApi(a) {
548
710
  return `// The OmniFlow backend URL. Defaults to same origin in production (assets
549
- // are served by the OmniFlow host at /api/plugins/${a.pluginId}/ui).
711
+ // are served by the OmniFlow host at /api/v1/plugins/${a.pluginId}/ui).
550
712
  const BASE = process.env.NEXT_PUBLIC_API_URL ?? '';
551
713
 
552
714
  async function get<T>(path: string): Promise<T> {
@@ -572,7 +734,7 @@ export interface PluginRecord {
572
734
  // \u2500\u2500 API calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
573
735
 
574
736
  export const fetchRecords = (limit = 50) =>
575
- get<PluginRecord[]>(\`/api/analytics/builds?type=${a.ingestorType}&limit=\${limit}\`);
737
+ get<PluginRecord[]>(\`/api/v1/analytics/builds?type=${a.ingestorType}&limit=\${limit}\`);
576
738
  `;
577
739
  }
578
740
 
@@ -607,7 +769,7 @@ async function main() {
607
769
  pluginId: () => p.text({
608
770
  message: "Plugin ID",
609
771
  placeholder: "my-ingestor",
610
- validate: (v) => /^[a-z][a-z0-9-]*$/.test(v) ? void 0 : "Use lowercase letters, numbers and hyphens only"
772
+ validate: (v = "") => /^[a-z][a-z0-9-]*$/.test(v) ? void 0 : "Use lowercase letters, numbers and hyphens only"
611
773
  }),
612
774
  pluginName: ({ results }) => p.text({
613
775
  message: "Plugin display name",
@@ -629,13 +791,21 @@ async function main() {
629
791
  message: "Ingestor type key (used in API paths, e.g. /api/ingest/{type})",
630
792
  initialValue: results.pluginId
631
793
  }),
794
+ hasTools: () => p.confirm({
795
+ message: "Include an AI tool? (exposes data to OmniFlow AI chat)",
796
+ initialValue: false
797
+ }),
798
+ hasAction: () => p.confirm({
799
+ message: "Include a webhook action? (dispatched from OmniFlow scripts)",
800
+ initialValue: false
801
+ }),
632
802
  hasUi: () => p.confirm({
633
803
  message: "Include a Next.js micro UI?",
634
804
  initialValue: true
635
805
  }),
636
806
  omniflowVersion: () => p.text({
637
807
  message: "OmniFlow plugin-api version to depend on",
638
- initialValue: "1.0.0"
808
+ initialValue: "0.1"
639
809
  }),
640
810
  apiUrl: () => p.text({
641
811
  message: "OmniFlow API base URL (for local dev)",
@@ -662,12 +832,12 @@ async function main() {
662
832
  `./gradlew jar`,
663
833
  "",
664
834
  "# Upload to a running OmniFlow backend:",
665
- `curl -X POST ${answers.apiUrl}/api/plugins/upload \\`,
835
+ `curl -X POST ${answers.apiUrl}/api/v1/plugins/upload \\`,
666
836
  ` -H "X-Api-Key: <your-api-key>" \\`,
667
837
  ` -F "file=@build/libs/${answers.pluginId}-1.0.0.jar"`,
668
838
  "",
669
839
  "# Ingest data (see scripts/ingest.sh for a ready-made script):",
670
- `curl -X POST ${answers.apiUrl}/api/ingest/${answers.ingestorType} \\`,
840
+ `curl -X POST ${answers.apiUrl}/api/v1/ingest/${answers.ingestorType} \\`,
671
841
  ` -H "X-Api-Key: <your-api-key>" \\`,
672
842
  ` -H "Content-Type: application/json" \\`,
673
843
  ` -d @data.json`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-omniflow-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffold a new OmniFlow plugin — Java ingestor/action + optional Next.js micro UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,13 +14,13 @@
14
14
  "start": "node dist/index.js"
15
15
  },
16
16
  "dependencies": {
17
- "@clack/prompts": "^0.9.0",
18
- "picocolors": "^1.1.1"
17
+ "@clack/prompts": "1.4.0",
18
+ "picocolors": "1.1.1"
19
19
  },
20
20
  "devDependencies": {
21
- "@types/node": "^22",
22
- "tsup": "^8",
23
- "typescript": "^5"
21
+ "@types/node": "22.19.15",
22
+ "tsup": "8.5.1",
23
+ "typescript": "5.9.3"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=18"