create-omniflow-plugin 0.1.0 → 0.3.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.
- package/README.md +68 -14
- package/dist/index.js +236 -66
- 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
|
|
47
|
-
│
|
|
48
|
-
|
|
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`
|
|
@@ -152,14 +184,36 @@ create-omniflow-plugin
|
|
|
152
184
|
|
|
153
185
|
## Publish to npm
|
|
154
186
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
3. Publish:
|
|
187
|
+
### One-time setup
|
|
188
|
+
|
|
189
|
+
1. Create an npm account at [npmjs.com/signup](https://www.npmjs.com/signup) if you don't have one.
|
|
190
|
+
2. Log in from your terminal:
|
|
161
191
|
```sh
|
|
162
|
-
npm
|
|
192
|
+
npm login
|
|
163
193
|
```
|
|
164
194
|
|
|
195
|
+
### Publish a release
|
|
196
|
+
|
|
197
|
+
```sh
|
|
198
|
+
# Build
|
|
199
|
+
npm run build
|
|
200
|
+
|
|
201
|
+
# Publish (first time — public by default for unscoped packages)
|
|
202
|
+
npm publish
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The package will be live at [npmjs.com/package/create-omniflow-plugin](https://www.npmjs.com/package/create-omniflow-plugin) and users can run `npx create-omniflow-plugin`.
|
|
206
|
+
|
|
165
207
|
The `files` field in `package.json` ensures only `dist/` is included in the published package.
|
|
208
|
+
|
|
209
|
+
### Future releases
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
# Bump version (patch/minor/major)
|
|
213
|
+
npm version patch # 0.3.0 → 0.3.1
|
|
214
|
+
npm version minor # 0.3.0 → 0.4.0
|
|
215
|
+
npm version major # 0.3.0 → 1.0.0
|
|
216
|
+
|
|
217
|
+
# Build and publish
|
|
218
|
+
npm run build && npm publish
|
|
219
|
+
```
|
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
|
-
|
|
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.
|
|
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'
|
|
115
|
-
'Plugin-Version': project.version,
|
|
116
|
-
'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
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
158
|
-
|
|
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<
|
|
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
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
318
|
-
next: "16.2.
|
|
319
|
-
react: "19.2.
|
|
320
|
-
"react-dom": "19.2.
|
|
321
|
-
swr: "
|
|
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": "
|
|
479
|
+
"@types/node": "22.19.19",
|
|
325
480
|
"@types/react": "19.2.14",
|
|
326
481
|
"@types/react-dom": "19.2.3",
|
|
327
|
-
autoprefixer: "
|
|
328
|
-
eslint: "
|
|
329
|
-
"eslint-config-next": "
|
|
330
|
-
postcss: "
|
|
331
|
-
tailwindcss: "
|
|
332
|
-
typescript: "
|
|
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
|
|
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
|
|
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
|
-
<
|
|
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
|
|
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: "
|
|
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.
|
|
3
|
+
"version": "0.3.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": "
|
|
18
|
-
"picocolors": "
|
|
17
|
+
"@clack/prompts": "1.5.0",
|
|
18
|
+
"picocolors": "1.1.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@types/node": "
|
|
22
|
-
"tsup": "
|
|
23
|
-
"typescript": "
|
|
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"
|