mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
chore: post-2.0 QA hygiene across api hardening and qa metadata
This commit is contained in:
@@ -238,7 +238,9 @@ app.setErrorHandler((error: Error & { statusCode?: number }, request, reply) =>
|
|||||||
await app.register(cors, {
|
await app.register(cors, {
|
||||||
origin: env.CORS_ORIGIN
|
origin: env.CORS_ORIGIN
|
||||||
? env.CORS_ORIGIN.split(",").map((s) => s.trim())
|
? env.CORS_ORIGIN.split(",").map((s) => s.trim())
|
||||||
: process.env.NODE_ENV !== "production",
|
: process.env.NODE_ENV === "production"
|
||||||
|
? false
|
||||||
|
: [/^http:\/\/localhost:\d+$/],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Security headers -- applied in all environments. HSTS is ignored over plain
|
// Security headers -- applied in all environments. HSTS is ignored over plain
|
||||||
@@ -286,6 +288,7 @@ await app.register(rateLimit, {
|
|||||||
// Block TRACE method (returns 401 instead of 405 without this)
|
// Block TRACE method (returns 401 instead of 405 without this)
|
||||||
app.addHook("onRequest", async (request, reply) => {
|
app.addHook("onRequest", async (request, reply) => {
|
||||||
if (request.method === "TRACE") {
|
if (request.method === "TRACE") {
|
||||||
|
reply.header("Allow", "GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD");
|
||||||
return reply.status(405).send({ error: "Method not allowed" });
|
return reply.status(405).send({ error: "Method not allowed" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ export async function isToolAuditEnabled(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AUDIT_STRIP_RE = /[<>&"'\r\n\0\x85\u2028\u2029]/g;
|
||||||
|
|
||||||
export function sanitizeAuditInput(raw: string): string {
|
export function sanitizeAuditInput(raw: string): string {
|
||||||
return raw.replace(/[<>&"']/g, "").slice(0, MAX_AUDIT_INPUT_LENGTH) || "(empty)";
|
return raw.replace(AUDIT_STRIP_RE, "").slice(0, MAX_AUDIT_INPUT_LENGTH) || "(empty)";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { lookup } from "node:dns/promises";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
|
import { isIP } from "node:net";
|
||||||
import PQueue from "p-queue";
|
import PQueue from "p-queue";
|
||||||
import { type Browser, chromium } from "playwright";
|
import { type Browser, chromium, type Page } from "playwright";
|
||||||
|
import { isPrivateIp } from "./ssrf.js";
|
||||||
|
|
||||||
const MAX_PAGES = Math.max(1, parseInt(process.env.BROWSER_MAX_PAGES || "3", 10));
|
const MAX_PAGES = Math.max(1, parseInt(process.env.BROWSER_MAX_PAGES || "3", 10));
|
||||||
const CRASH_WINDOW_MS = 60_000;
|
const CRASH_WINDOW_MS = 60_000;
|
||||||
@@ -56,6 +59,59 @@ function recordCrash(): void {
|
|||||||
backoffUntil = now + delay;
|
backoffUntil = now + delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function isBlockedUrl(url: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
if (
|
||||||
|
parsed.protocol === "data:" ||
|
||||||
|
parsed.protocol === "blob:" ||
|
||||||
|
parsed.protocol === "about:"
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hostname = parsed.hostname.replace(/^\[|]$/g, "");
|
||||||
|
if (isIP(hostname)) {
|
||||||
|
return isPrivateIp(hostname);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await lookup(hostname, { all: true });
|
||||||
|
const entries = Array.isArray(result) ? result : [result];
|
||||||
|
return entries.some((e) => isPrivateIp(e.address));
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installNetworkGuard(page: Page): Promise<void> {
|
||||||
|
await page.route("**/*", async (route) => {
|
||||||
|
const url = route.request().url();
|
||||||
|
if (await isBlockedUrl(url)) {
|
||||||
|
await route.abort("blockedbyclient").catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await route.fetch();
|
||||||
|
const responseUrl = response.url();
|
||||||
|
if (responseUrl !== url && (await isBlockedUrl(responseUrl))) {
|
||||||
|
await route.abort("blockedbyclient").catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await route.fulfill({ response });
|
||||||
|
} catch {
|
||||||
|
await route.abort("failed").catch(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await page.routeWebSocket("**/*", (ws) => {
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function getBrowser(): Promise<Browser> {
|
async function getBrowser(): Promise<Browser> {
|
||||||
if (browserFailed) {
|
if (browserFailed) {
|
||||||
throw new Error("Browser service permanently disabled after repeated crashes");
|
throw new Error("Browser service permanently disabled after repeated crashes");
|
||||||
@@ -67,7 +123,12 @@ async function getBrowser(): Promise<Browser> {
|
|||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
browser = await chromium.launch({
|
browser = await chromium.launch({
|
||||||
args: ["--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"],
|
args: [
|
||||||
|
"--no-sandbox",
|
||||||
|
"--disable-gpu",
|
||||||
|
"--disable-dev-shm-usage",
|
||||||
|
"--disable-background-networking",
|
||||||
|
],
|
||||||
});
|
});
|
||||||
browser.on("disconnected", () => {
|
browser.on("disconnected", () => {
|
||||||
browser = null;
|
browser = null;
|
||||||
@@ -88,7 +149,9 @@ export async function capturePage(url: string, options: CaptureOptions): Promise
|
|||||||
const page = await b.newPage({
|
const page = await b.newPage({
|
||||||
viewport: { width: options.viewportWidth, height: options.viewportHeight },
|
viewport: { width: options.viewportWidth, height: options.viewportHeight },
|
||||||
isMobile: options.isMobile,
|
isMobile: options.isMobile,
|
||||||
|
serviceWorkers: "block",
|
||||||
});
|
});
|
||||||
|
await installNetworkGuard(page);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.goto(url, { waitUntil: "load", timeout: PAGE_LOAD_TIMEOUT });
|
await page.goto(url, { waitUntil: "load", timeout: PAGE_LOAD_TIMEOUT });
|
||||||
@@ -138,7 +201,9 @@ export async function captureHtml(html: string, options: CaptureOptions): Promis
|
|||||||
const page = await b.newPage({
|
const page = await b.newPage({
|
||||||
viewport: { width: options.viewportWidth, height: options.viewportHeight },
|
viewport: { width: options.viewportWidth, height: options.viewportHeight },
|
||||||
isMobile: options.isMobile,
|
isMobile: options.isMobile,
|
||||||
|
serviceWorkers: "block",
|
||||||
});
|
});
|
||||||
|
await installNetworkGuard(page);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.setContent(html, { waitUntil: "load", timeout: PAGE_LOAD_TIMEOUT });
|
await page.setContent(html, { waitUntil: "load", timeout: PAGE_LOAD_TIMEOUT });
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const envSchema = z
|
|||||||
MAX_WORKER_THREADS: z.coerce.number().default(0),
|
MAX_WORKER_THREADS: z.coerce.number().default(0),
|
||||||
PROCESSING_TIMEOUT_S: z.coerce.number().default(0),
|
PROCESSING_TIMEOUT_S: z.coerce.number().default(0),
|
||||||
MAX_PIPELINE_STEPS: z.coerce.number().default(20),
|
MAX_PIPELINE_STEPS: z.coerce.number().default(20),
|
||||||
|
MAX_PIPELINE_STEP_PIXELS: z.coerce.number().default(67_108_864),
|
||||||
MAX_CANVAS_PIXELS: z.coerce.number().default(0),
|
MAX_CANVAS_PIXELS: z.coerce.number().default(0),
|
||||||
MAX_SVG_SIZE_MB: z.coerce.number().default(50),
|
MAX_SVG_SIZE_MB: z.coerce.number().default(50),
|
||||||
MAX_SPLIT_GRID: z.coerce.number().default(100),
|
MAX_SPLIT_GRID: z.coerce.number().default(100),
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ function isPrivateIPv6(ip: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPrivateIp(ip: string): boolean {
|
||||||
|
return isPrivateIPv4(ip) || isPrivateIPv6(ip);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a hostname and validate all returned IPs are public.
|
* Resolve a hostname and validate all returned IPs are public.
|
||||||
* Returns the first valid resolved IP so callers can pin it for the actual
|
* Returns the first valid resolved IP so callers can pin it for the actual
|
||||||
@@ -58,7 +62,7 @@ function isPrivateIPv6(ip: string): boolean {
|
|||||||
async function resolveAndCheck(hostname: string): Promise<string> {
|
async function resolveAndCheck(hostname: string): Promise<string> {
|
||||||
const bare = hostname.replace(/^\[|]$/g, "");
|
const bare = hostname.replace(/^\[|]$/g, "");
|
||||||
if (isIP(bare)) {
|
if (isIP(bare)) {
|
||||||
if (isPrivateIPv4(bare) || isPrivateIPv6(bare)) {
|
if (isPrivateIp(bare)) {
|
||||||
throw new Error("URL resolves to a private or reserved IP address");
|
throw new Error("URL resolves to a private or reserved IP address");
|
||||||
}
|
}
|
||||||
return bare;
|
return bare;
|
||||||
@@ -67,8 +71,7 @@ async function resolveAndCheck(hostname: string): Promise<string> {
|
|||||||
const result = await lookup(hostname, { all: true });
|
const result = await lookup(hostname, { all: true });
|
||||||
const addresses = Array.isArray(result) ? result : [result];
|
const addresses = Array.isArray(result) ? result : [result];
|
||||||
for (const entry of addresses) {
|
for (const entry of addresses) {
|
||||||
const addr = entry.address;
|
if (isPrivateIp(entry.address)) {
|
||||||
if (isPrivateIPv4(addr) || isPrivateIPv6(addr)) {
|
|
||||||
throw new Error("URL resolves to a private or reserved IP address");
|
throw new Error("URL resolves to a private or reserved IP address");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ function decodeNumericEntities(input: string): string {
|
|||||||
* Sanitize an SVG buffer to prevent XXE, SSRF, and script injection.
|
* Sanitize an SVG buffer to prevent XXE, SSRF, and script injection.
|
||||||
* Throws if the SVG exceeds the maximum allowed size.
|
* Throws if the SVG exceeds the maximum allowed size.
|
||||||
*/
|
*/
|
||||||
|
const MAX_SVG_ELEMENTS = 5_000;
|
||||||
|
|
||||||
export function sanitizeSvg(buffer: Buffer): Buffer {
|
export function sanitizeSvg(buffer: Buffer): Buffer {
|
||||||
const maxSvgSize = env.MAX_SVG_SIZE_MB > 0 ? env.MAX_SVG_SIZE_MB * 1024 * 1024 : Infinity;
|
const maxSvgSize = env.MAX_SVG_SIZE_MB > 0 ? env.MAX_SVG_SIZE_MB * 1024 * 1024 : Infinity;
|
||||||
if (buffer.length > maxSvgSize) {
|
if (buffer.length > maxSvgSize) {
|
||||||
@@ -25,6 +27,11 @@ export function sanitizeSvg(buffer: Buffer): Buffer {
|
|||||||
// ── Pre-processing: strip CDATA sections and decode numeric entities ──
|
// ── Pre-processing: strip CDATA sections and decode numeric entities ──
|
||||||
// CDATA sections can hide script content from regex-based checks.
|
// CDATA sections can hide script content from regex-based checks.
|
||||||
svg = svg.replace(/<!\[CDATA\[[\s\S]*?\]\]>/gi, "");
|
svg = svg.replace(/<!\[CDATA\[[\s\S]*?\]\]>/gi, "");
|
||||||
|
|
||||||
|
const elementCount = (svg.match(/<[a-zA-Z][^>]*\/?>/g) || []).length;
|
||||||
|
if (elementCount > MAX_SVG_ELEMENTS) {
|
||||||
|
throw new Error(`SVG exceeds maximum element count of ${MAX_SVG_ELEMENTS}`);
|
||||||
|
}
|
||||||
// Decode numeric entities so obfuscated URIs (e.g. javascript:) are visible.
|
// Decode numeric entities so obfuscated URIs (e.g. javascript:) are visible.
|
||||||
svg = decodeNumericEntities(svg);
|
svg = decodeNumericEntities(svg);
|
||||||
|
|
||||||
|
|||||||
@@ -5876,6 +5876,7 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
expiresAt:
|
expiresAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
@@ -5923,6 +5924,7 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
@@ -6739,19 +6741,26 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
actorId:
|
actorId:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
actorUsername:
|
actorUsername:
|
||||||
type: string
|
type: string
|
||||||
action:
|
action:
|
||||||
type: string
|
type: string
|
||||||
targetType:
|
targetType:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
targetId:
|
targetId:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
details:
|
details:
|
||||||
type: object
|
type: object
|
||||||
nullable: true
|
nullable: true
|
||||||
ipAddress:
|
ipAddress:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
|
requestId:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
|||||||
@@ -413,6 +413,19 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
// MFA plugin not loaded
|
// MFA plugin not loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cookieReply = reply as FastifyReply & {
|
||||||
|
setCookie?: (name: string, value: string, opts: Record<string, unknown>) => FastifyReply;
|
||||||
|
};
|
||||||
|
if (typeof cookieReply.setCookie === "function") {
|
||||||
|
cookieReply.setCookie("snapotter-session", token, {
|
||||||
|
path: "/",
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: env.EXTERNAL_URL.startsWith("https"),
|
||||||
|
maxAge: SESSION_DURATION_MS / 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return reply.send({
|
return reply.send({
|
||||||
token,
|
token,
|
||||||
user: {
|
user: {
|
||||||
|
|||||||
@@ -387,6 +387,17 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (env.MAX_PIPELINE_STEP_PIXELS > 0) {
|
||||||
|
const s = settingsResult.data as Record<string, unknown>;
|
||||||
|
const w = Number(s.width) || 0;
|
||||||
|
const h = Number(s.height) || 0;
|
||||||
|
if (w > 0 && h > 0 && w * h > env.MAX_PIPELINE_STEP_PIXELS) {
|
||||||
|
return reply.status(400).send({
|
||||||
|
error: `Step ${i + 1} (${step.toolId}): Output dimensions ${w}x${h} exceed per-step pixel limit`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parsedSteps.push({
|
parsedSteps.push({
|
||||||
toolId: step.toolId,
|
toolId: step.toolId,
|
||||||
resolvedToolId,
|
resolvedToolId,
|
||||||
@@ -787,6 +798,17 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (env.MAX_PIPELINE_STEP_PIXELS > 0) {
|
||||||
|
const s = settingsResult.data as Record<string, unknown>;
|
||||||
|
const w = Number(s.width) || 0;
|
||||||
|
const h = Number(s.height) || 0;
|
||||||
|
if (w > 0 && h > 0 && w * h > env.MAX_PIPELINE_STEP_PIXELS) {
|
||||||
|
return reply.status(400).send({
|
||||||
|
error: `Step ${i + 1} (${step.toolId}): Output dimensions ${w}x${h} exceed per-step pixel limit`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parsedSteps.push({
|
parsedSteps.push({
|
||||||
toolId: step.toolId,
|
toolId: step.toolId,
|
||||||
resolvedToolId,
|
resolvedToolId,
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ const SENSITIVE_KEYS = new Set([
|
|||||||
"siem_webhook_auth",
|
"siem_webhook_auth",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const REDACTED_KEYS = new Set(["cookie_secret", "oidc_client_secret", "siem_webhook_auth"]);
|
||||||
|
|
||||||
|
const READONLY_KEYS = new Set(["cookie_secret", "instance_id"]);
|
||||||
|
|
||||||
async function encryptIfSensitive(key: string, value: string): Promise<string> {
|
async function encryptIfSensitive(key: string, value: string): Promise<string> {
|
||||||
if (!env.DATA_ENCRYPTION_KEY || !SENSITIVE_KEYS.has(key)) return value;
|
if (!env.DATA_ENCRYPTION_KEY || !SENSITIVE_KEYS.has(key)) return value;
|
||||||
return encrypt(value, env.DATA_ENCRYPTION_KEY);
|
return encrypt(value, env.DATA_ENCRYPTION_KEY);
|
||||||
@@ -57,6 +61,10 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
const settings: Record<string, string> = {};
|
const settings: Record<string, string> = {};
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (!isAdmin && SENSITIVE_KEYS.has(row.key)) continue;
|
if (!isAdmin && SENSITIVE_KEYS.has(row.key)) continue;
|
||||||
|
if (REDACTED_KEYS.has(row.key)) {
|
||||||
|
settings[row.key] = "********";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
settings[row.key] = await decryptIfNeeded(row.value);
|
settings[row.key] = await decryptIfNeeded(row.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +100,13 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (READONLY_KEYS.has(key)) {
|
||||||
|
return reply.status(400).send({
|
||||||
|
error: `Setting "${key}" cannot be modified via the API`,
|
||||||
|
code: "READONLY_SETTING",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
entries.push({ key, strValue });
|
entries.push({ key, strValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +167,7 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
|
|
||||||
return reply.send({
|
return reply.send({
|
||||||
key: row.key,
|
key: row.key,
|
||||||
value: await decryptIfNeeded(row.value),
|
value: REDACTED_KEYS.has(row.key) ? "********" : await decryptIfNeeded(row.value),
|
||||||
updatedAt: row.updatedAt.toISOString(),
|
updatedAt: row.updatedAt.toISOString(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
+184
-49
@@ -858,7 +858,9 @@
|
|||||||
"id": "ocr-pdf",
|
"id": "ocr-pdf",
|
||||||
"name": "PDF OCR",
|
"name": "PDF OCR",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": true
|
"isAI": true
|
||||||
},
|
},
|
||||||
@@ -2620,7 +2622,10 @@
|
|||||||
"id": "gif-webp",
|
"id": "gif-webp",
|
||||||
"name": "GIF/WebP Converter",
|
"name": "GIF/WebP Converter",
|
||||||
"modality": "image",
|
"modality": "image",
|
||||||
"acceptedInputs": [".gif", ".webp"],
|
"acceptedInputs": [
|
||||||
|
".gif",
|
||||||
|
".webp"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -2763,7 +2768,10 @@
|
|||||||
"id": "svg-to-raster",
|
"id": "svg-to-raster",
|
||||||
"name": "SVG to Raster",
|
"name": "SVG to Raster",
|
||||||
"modality": "image",
|
"modality": "image",
|
||||||
"acceptedInputs": [".svg", ".svgz"],
|
"acceptedInputs": [
|
||||||
|
".svg",
|
||||||
|
".svgz"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -2861,7 +2869,9 @@
|
|||||||
"id": "pdf-to-image",
|
"id": "pdf-to-image",
|
||||||
"name": "PDF to Image",
|
"name": "PDF to Image",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3277,7 +3287,9 @@
|
|||||||
"id": "gif-to-video",
|
"id": "gif-to-video",
|
||||||
"name": "GIF to Video",
|
"name": "GIF to Video",
|
||||||
"modality": "video",
|
"modality": "video",
|
||||||
"acceptedInputs": [".gif"],
|
"acceptedInputs": [
|
||||||
|
".gif"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3883,7 +3895,9 @@
|
|||||||
"id": "merge-pdf",
|
"id": "merge-pdf",
|
||||||
"name": "Merge PDFs",
|
"name": "Merge PDFs",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3891,7 +3905,9 @@
|
|||||||
"id": "split-pdf",
|
"id": "split-pdf",
|
||||||
"name": "Split PDF",
|
"name": "Split PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3899,7 +3915,9 @@
|
|||||||
"id": "compress-pdf",
|
"id": "compress-pdf",
|
||||||
"name": "Compress PDF",
|
"name": "Compress PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3907,7 +3925,9 @@
|
|||||||
"id": "rotate-pdf",
|
"id": "rotate-pdf",
|
||||||
"name": "Rotate PDF",
|
"name": "Rotate PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3915,7 +3935,13 @@
|
|||||||
"id": "convert-document",
|
"id": "convert-document",
|
||||||
"name": "Convert Document",
|
"name": "Convert Document",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".docx", ".doc", ".odt", ".rtf", ".txt"],
|
"acceptedInputs": [
|
||||||
|
".docx",
|
||||||
|
".doc",
|
||||||
|
".odt",
|
||||||
|
".rtf",
|
||||||
|
".txt"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3923,7 +3949,11 @@
|
|||||||
"id": "convert-presentation",
|
"id": "convert-presentation",
|
||||||
"name": "Convert Presentation",
|
"name": "Convert Presentation",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pptx", ".ppt", ".odp"],
|
"acceptedInputs": [
|
||||||
|
".pptx",
|
||||||
|
".ppt",
|
||||||
|
".odp"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3931,7 +3961,12 @@
|
|||||||
"id": "convert-spreadsheet",
|
"id": "convert-spreadsheet",
|
||||||
"name": "Convert Spreadsheet",
|
"name": "Convert Spreadsheet",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".xlsx", ".xls", ".ods", ".csv"],
|
"acceptedInputs": [
|
||||||
|
".xlsx",
|
||||||
|
".xls",
|
||||||
|
".ods",
|
||||||
|
".csv"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3939,7 +3974,12 @@
|
|||||||
"id": "excel-to-pdf",
|
"id": "excel-to-pdf",
|
||||||
"name": "Excel to PDF",
|
"name": "Excel to PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".xlsx", ".xls", ".ods", ".csv"],
|
"acceptedInputs": [
|
||||||
|
".xlsx",
|
||||||
|
".xls",
|
||||||
|
".ods",
|
||||||
|
".csv"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3947,7 +3987,13 @@
|
|||||||
"id": "word-to-pdf",
|
"id": "word-to-pdf",
|
||||||
"name": "Word to PDF",
|
"name": "Word to PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".docx", ".doc", ".odt", ".rtf", ".txt"],
|
"acceptedInputs": [
|
||||||
|
".docx",
|
||||||
|
".doc",
|
||||||
|
".odt",
|
||||||
|
".rtf",
|
||||||
|
".txt"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3955,7 +4001,9 @@
|
|||||||
"id": "extract-pages",
|
"id": "extract-pages",
|
||||||
"name": "Extract Pages",
|
"name": "Extract Pages",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3963,7 +4011,9 @@
|
|||||||
"id": "remove-pages",
|
"id": "remove-pages",
|
||||||
"name": "Remove Pages",
|
"name": "Remove Pages",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3971,7 +4021,9 @@
|
|||||||
"id": "organize-pdf",
|
"id": "organize-pdf",
|
||||||
"name": "Organize PDF",
|
"name": "Organize PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3979,7 +4031,9 @@
|
|||||||
"id": "protect-pdf",
|
"id": "protect-pdf",
|
||||||
"name": "Protect PDF",
|
"name": "Protect PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3987,7 +4041,9 @@
|
|||||||
"id": "unlock-pdf",
|
"id": "unlock-pdf",
|
||||||
"name": "Unlock PDF",
|
"name": "Unlock PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -3995,7 +4051,9 @@
|
|||||||
"id": "repair-pdf",
|
"id": "repair-pdf",
|
||||||
"name": "Repair PDF",
|
"name": "Repair PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4003,7 +4061,9 @@
|
|||||||
"id": "linearize-pdf",
|
"id": "linearize-pdf",
|
||||||
"name": "Web-Optimize PDF",
|
"name": "Web-Optimize PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4011,7 +4071,9 @@
|
|||||||
"id": "grayscale-pdf",
|
"id": "grayscale-pdf",
|
||||||
"name": "Grayscale PDF",
|
"name": "Grayscale PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4019,7 +4081,9 @@
|
|||||||
"id": "pdfa-convert",
|
"id": "pdfa-convert",
|
||||||
"name": "PDF/A Convert",
|
"name": "PDF/A Convert",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4027,7 +4091,9 @@
|
|||||||
"id": "crop-pdf",
|
"id": "crop-pdf",
|
||||||
"name": "Crop PDF",
|
"name": "Crop PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4035,7 +4101,9 @@
|
|||||||
"id": "nup-pdf",
|
"id": "nup-pdf",
|
||||||
"name": "N-up PDF",
|
"name": "N-up PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4043,7 +4111,9 @@
|
|||||||
"id": "booklet-pdf",
|
"id": "booklet-pdf",
|
||||||
"name": "Booklet PDF",
|
"name": "Booklet PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4051,7 +4121,9 @@
|
|||||||
"id": "watermark-pdf",
|
"id": "watermark-pdf",
|
||||||
"name": "Watermark PDF",
|
"name": "Watermark PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4059,7 +4131,9 @@
|
|||||||
"id": "pdf-page-numbers",
|
"id": "pdf-page-numbers",
|
||||||
"name": "PDF Page Numbers",
|
"name": "PDF Page Numbers",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4067,7 +4141,9 @@
|
|||||||
"id": "flatten-pdf",
|
"id": "flatten-pdf",
|
||||||
"name": "Flatten PDF",
|
"name": "Flatten PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4075,7 +4151,9 @@
|
|||||||
"id": "redact-pdf",
|
"id": "redact-pdf",
|
||||||
"name": "Redact PDF",
|
"name": "Redact PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4083,7 +4161,9 @@
|
|||||||
"id": "pdf-to-text",
|
"id": "pdf-to-text",
|
||||||
"name": "PDF to Text",
|
"name": "PDF to Text",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4091,7 +4171,9 @@
|
|||||||
"id": "pdf-to-word",
|
"id": "pdf-to-word",
|
||||||
"name": "PDF to Word",
|
"name": "PDF to Word",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4099,7 +4181,9 @@
|
|||||||
"id": "pdf-metadata",
|
"id": "pdf-metadata",
|
||||||
"name": "PDF Metadata",
|
"name": "PDF Metadata",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pdf"],
|
"acceptedInputs": [
|
||||||
|
".pdf"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4107,7 +4191,11 @@
|
|||||||
"id": "powerpoint-to-pdf",
|
"id": "powerpoint-to-pdf",
|
||||||
"name": "PowerPoint to PDF",
|
"name": "PowerPoint to PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".pptx", ".ppt", ".odp"],
|
"acceptedInputs": [
|
||||||
|
".pptx",
|
||||||
|
".ppt",
|
||||||
|
".odp"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4115,7 +4203,10 @@
|
|||||||
"id": "html-to-pdf",
|
"id": "html-to-pdf",
|
||||||
"name": "HTML to PDF",
|
"name": "HTML to PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".html", ".htm"],
|
"acceptedInputs": [
|
||||||
|
".html",
|
||||||
|
".htm"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4123,7 +4214,10 @@
|
|||||||
"id": "markdown-to-docx",
|
"id": "markdown-to-docx",
|
||||||
"name": "Markdown to Word",
|
"name": "Markdown to Word",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".md", ".markdown"],
|
"acceptedInputs": [
|
||||||
|
".md",
|
||||||
|
".markdown"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4131,7 +4225,10 @@
|
|||||||
"id": "markdown-to-html",
|
"id": "markdown-to-html",
|
||||||
"name": "Markdown to HTML",
|
"name": "Markdown to HTML",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".md", ".markdown"],
|
"acceptedInputs": [
|
||||||
|
".md",
|
||||||
|
".markdown"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4139,7 +4236,10 @@
|
|||||||
"id": "markdown-to-pdf",
|
"id": "markdown-to-pdf",
|
||||||
"name": "Markdown to PDF",
|
"name": "Markdown to PDF",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".md", ".markdown"],
|
"acceptedInputs": [
|
||||||
|
".md",
|
||||||
|
".markdown"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4147,7 +4247,9 @@
|
|||||||
"id": "epub-convert",
|
"id": "epub-convert",
|
||||||
"name": "Convert EPUB",
|
"name": "Convert EPUB",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".epub"],
|
"acceptedInputs": [
|
||||||
|
".epub"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4155,7 +4257,12 @@
|
|||||||
"id": "to-epub",
|
"id": "to-epub",
|
||||||
"name": "Convert to EPUB",
|
"name": "Convert to EPUB",
|
||||||
"modality": "document",
|
"modality": "document",
|
||||||
"acceptedInputs": [".docx", ".md", ".html", ".txt"],
|
"acceptedInputs": [
|
||||||
|
".docx",
|
||||||
|
".md",
|
||||||
|
".html",
|
||||||
|
".txt"
|
||||||
|
],
|
||||||
"executionHint": "long",
|
"executionHint": "long",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4163,7 +4270,10 @@
|
|||||||
"id": "chart-maker",
|
"id": "chart-maker",
|
||||||
"name": "Chart Maker",
|
"name": "Chart Maker",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".csv", ".json"],
|
"acceptedInputs": [
|
||||||
|
".csv",
|
||||||
|
".json"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4171,7 +4281,11 @@
|
|||||||
"id": "csv-excel",
|
"id": "csv-excel",
|
||||||
"name": "CSV to Excel",
|
"name": "CSV to Excel",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".csv", ".tsv", ".xlsx"],
|
"acceptedInputs": [
|
||||||
|
".csv",
|
||||||
|
".tsv",
|
||||||
|
".xlsx"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4179,7 +4293,11 @@
|
|||||||
"id": "csv-json",
|
"id": "csv-json",
|
||||||
"name": "CSV to JSON",
|
"name": "CSV to JSON",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".csv", ".tsv", ".json"],
|
"acceptedInputs": [
|
||||||
|
".csv",
|
||||||
|
".tsv",
|
||||||
|
".json"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4187,7 +4305,10 @@
|
|||||||
"id": "json-xml",
|
"id": "json-xml",
|
||||||
"name": "JSON to XML",
|
"name": "JSON to XML",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".json", ".xml"],
|
"acceptedInputs": [
|
||||||
|
".json",
|
||||||
|
".xml"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4195,7 +4316,10 @@
|
|||||||
"id": "split-csv",
|
"id": "split-csv",
|
||||||
"name": "Split CSV",
|
"name": "Split CSV",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".csv", ".tsv"],
|
"acceptedInputs": [
|
||||||
|
".csv",
|
||||||
|
".tsv"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4203,7 +4327,10 @@
|
|||||||
"id": "merge-csvs",
|
"id": "merge-csvs",
|
||||||
"name": "Merge CSVs",
|
"name": "Merge CSVs",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".csv", ".tsv"],
|
"acceptedInputs": [
|
||||||
|
".csv",
|
||||||
|
".tsv"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4211,7 +4338,11 @@
|
|||||||
"id": "yaml-json",
|
"id": "yaml-json",
|
||||||
"name": "YAML / JSON",
|
"name": "YAML / JSON",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".yaml", ".yml", ".json"],
|
"acceptedInputs": [
|
||||||
|
".yaml",
|
||||||
|
".yml",
|
||||||
|
".json"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4219,7 +4350,9 @@
|
|||||||
"id": "xml-to-csv",
|
"id": "xml-to-csv",
|
||||||
"name": "XML to CSV",
|
"name": "XML to CSV",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".xml"],
|
"acceptedInputs": [
|
||||||
|
".xml"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
},
|
},
|
||||||
@@ -4235,7 +4368,9 @@
|
|||||||
"id": "extract-zip",
|
"id": "extract-zip",
|
||||||
"name": "Extract ZIP",
|
"name": "Extract ZIP",
|
||||||
"modality": "file",
|
"modality": "file",
|
||||||
"acceptedInputs": [".zip"],
|
"acceptedInputs": [
|
||||||
|
".zip"
|
||||||
|
],
|
||||||
"executionHint": "fast",
|
"executionHint": "fast",
|
||||||
"isAI": false
|
"isAI": false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user