@cloud-cli/on 1.18.0 → 1.19.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/dist/on.js CHANGED
@@ -356,7 +356,7 @@ var ye = class {
356
356
  type: "image/svg+xml",
357
357
  purpose: "any maskable"
358
358
  }]
359
- }), xe = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\">\n <rect width=\"512\" height=\"512\" rx=\"112\" fill=\"#030712\"/>\n <path d=\"M128 144h256v224H128z\" fill=\"#111827\" stroke=\"#6366f1\" stroke-width=\"24\"/>\n <path d=\"m174 218 62 38-62 38M260 300h78\" fill=\"none\" stroke=\"#f9fafb\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"24\"/>\n <circle cx=\"365\" cy=\"147\" r=\"47\" fill=\"#10b981\" stroke=\"#030712\" stroke-width=\"18\"/>\n</svg>", Se = "self.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()));\nself.addEventListener('notificationclick', (event) => {\n event.notification.close();\n const target = event.notification.data?.url || '/runs';\n event.waitUntil((async () => {\n const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });\n const existing = windows.find((client) => new URL(client.url).pathname === target);\n if (existing) return existing.focus();\n return self.clients.openWindow(target);\n })());\n});", Ce = "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"theme-color\" content=\"#030712\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n <link rel=\"icon\" href=\"/app-icon.svg\" type=\"image/svg+xml\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <title>Workflow Run</title>\n <script type=\"importmap\">\n {\n \"imports\": {\n \"@li3/\": \"https://cdn.li3.dev/@li3/\",\n \"ansi_up\": \"https://esm.sh/ansi_up@6.0.6\"\n }\n }\n <\/script>\n </head>\n <body class=\"bg-gray-950 text-gray-100 min-h-screen p-4 md:p-6 font-sans\">\n <template app>\n <div class=\"max-w-5xl mx-auto space-y-4\">\n <header class=\"flex flex-col md:flex-row md:items-center justify-between border-b border-gray-800 pb-6 gap-4\">\n <div class=\"min-w-0\">\n <a href=\"/runs\" class=\"text-xs text-indigo-400 hover:underline mb-1 inline-block\">Back to Dashboard</a>\n <h1 class=\"text-2xl font-bold text-white flex flex-wrap items-center gap-3\">\n {{ report.workflowName }}\n <span class=\"text-sm font-mono text-gray-500\">#{{ report.jobId }}</span>\n <template if=\"report.parentId\">\n <a attr-href=\"'/runs/' + report.parentId\" class=\"text-sm font-mono text-indigo-400 hover:underline\">\n from #{{ report.parentId }}\n </a>\n </template>\n </h1>\n <p class=\"text-xs text-gray-400 mt-1\">Started {{ report.startedAt }} · {{ timing }}</p>\n </div>\n <div class=\"flex items-center gap-2 shrink-0\">\n <span\n class=\"px-4 py-1.5 rounded-full text-sm font-semibold border\"\n bind-class=\"{\n 'bg-emerald-500/10.text-emerald-400.border-emerald-500/20': report.status === 'success',\n 'bg-rose-500/10.text-rose-400.border-rose-500/20': report.status === 'failed',\n 'bg-indigo-500/10.text-indigo-400.border-indigo-500/20': report.status === 'running',\n 'bg-amber-500/10.text-amber-400.border-amber-500/20': !['success', 'failed', 'running'].includes(report.status)\n }\"\n >\n {{ upper(report.status) }}\n </span>\n <template if=\"report.status === 'running'\">\n <button\n class=\"text-sm text-rose-300 px-4 py-1.5 border border-rose-500/50 hover:border-rose-400 rounded-full disabled:opacity-50\"\n on-click=\"cancelJob()\"\n attr-disabled=\"cancelling\"\n >\n {{ cancelling ? 'Cancelling...' : 'Cancel' }}\n </button>\n </template>\n <template if=\"report.status !== 'running'\">\n <button\n class=\"text-sm text-white px-4 py-1.5 border border-gray-600 hover:border-gray-400 rounded-full\"\n on-click=\"restartJob()\"\n >\n Restart\n </button>\n </template>\n </div>\n </header>\n\n <section class=\"rounded overflow-hidden border-b border-gray-800\">\n <h2 class=\"sr-only\">Execution Steps</h2>\n <template for=\"step of report.steps\">\n <details class=\"border border-b-0 border-gray-800 bg-gray-900/50 text-sm\">\n <summary\n class=\"flex flex-col md:flex-row md:items-center justify-between gap-2 p-3 bg-gray-800/40 border-b border-gray-800 cursor-pointer\"\n >\n <div class=\"flex flex-wrap items-center gap-3 min-w-0\">\n <lucide-icon\n class=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-gray-700/70\"\n bind-class=\"{\n 'text-emerald-400': step.status === 'success',\n 'text-rose-400': step.status === 'failed',\n 'text-indigo-400': step.status === 'running',\n 'text-gray-400': !['success', 'failed', 'running'].includes(step.status)\n }\"\n bind-icon=\"\n step.status === 'success'\n ? 'circle-check'\n : step.status === 'failed'\n ? 'circle-x'\n : step.status === 'running'\n ? 'loader-circle'\n : 'circle'\n \"\n size=\"15\"\n ></lucide-icon>\n <template if=\"step.exitCode !== undefined && step.exitCode !== 0\">\n <span class=\"font-mono text-gray-500\">({{ step.exitCode }})</span>\n </template>\n <h3 class=\"font-semibold text-gray-200 break-words\">{{ step.name }}</h3>\n <span\n class=\"px-2.5 py-0.5 rounded-full font-medium\"\n bind-class=\"{\n 'text-emerald-400.bg-emerald-500/10': step.status === 'success',\n 'text-rose-400.bg-rose-500/10': step.status === 'failed',\n 'text-indigo-400.bg-indigo-500/10': step.status === 'running',\n 'text-gray-400.bg-gray-500/10': !['success', 'failed', 'running'].includes(step.status)\n }\"\n >\n {{ upper(step.status) }}\n </span>\n </div>\n <div class=\"font-mono text-gray-400 shrink-0\">{{ step.durationMs }}ms</div>\n </summary>\n <template if=\"report.canViewLogs\">\n <div class=\"p-4 bg-gray-950 font-mono text-gray-300 leading-relaxed overflow-auto w-full\">\n <pre class=\"whitespace-pre-wrap\" bind-innerhtml=\"stepLog(step)\"></pre>\n </div>\n </template>\n <template if=\"!report.canViewLogs\">\n <div class=\"p-4 bg-gray-950 text-sm text-gray-500\">Logs are available to authenticated users.</div>\n </template>\n </details>\n </template>\n </section>\n\n <details class=\"bg-gray-900 border border-gray-800 rounded-xl p-4 overflow-auto max-h-[400px]\">\n <summary class=\"cursor-pointer\">\n <h2 class=\"inline text-xs font-semibold text-gray-400 uppercase tracking-wider\">Trigger Inputs</h2>\n </summary>\n <pre class=\"font-mono text-xs text-gray-200 bg-gray-950 p-3 mt-3 rounded-lg overflow-x-auto\">\n{{ inputsJson }}</pre\n >\n </details>\n </div>\n\n <script setup>\n import { computed, onDestroy, onInit, ref } from '@li3/web';\n import { AnsiUp } from 'ansi_up';\n\n export default function setup() {\n const report = ref({});\n const now = ref(Date.now());\n const ansiUp = new AnsiUp();\n ansiUp.use_classes = false;\n let eventSource;\n let refreshTimer;\n let clockTimer;\n let refreshing = false;\n let refreshPending = false;\n const cancelling = ref(false);\n\n const active = computed(() => ['pending', 'running'].includes(report.value.status));\n const inputsJson = computed(() => JSON.stringify(report.value.inputs || {}, null, 2));\n const timing = computed(() => {\n if (report.value.status === 'pending') return 'Waiting for a worker';\n if (report.value.status === 'running') {\n return `Running for ${Math.max(0, now.value - Date.parse(report.value.startedAt))}ms`;\n }\n return `Finished in ${report.value.durationMs}ms`;\n });\n\n const upper = (value) => String(value || '').toUpperCase();\n const stepLog = (step) => {\n if (step.status === 'skipped') return '';\n if (step.status === 'running')\n return '<span class=\"text-indigo-400\">Step is running. Logs will appear after it finishes.</span>';\n if (step.status === 'pending') return '<span class=\"text-gray-500\">Waiting to run.</span>';\n if (step.logContent) return ansiUp.ansi_to_html(step.logContent);\n return '<span class=\"text-gray-500\">(No terminal log output recorded for this step)</span>';\n };\n const restartJob = async () => {\n const response = await fetch(`/restart/${report.value.jobId}`, { method: 'POST' });\n if (response.ok) {\n const { id } = await response.json();\n location.href = `/runs/${id}`;\n }\n };\n const cancelJob = async () => {\n if (cancelling.value || report.value.status !== 'running') return;\n cancelling.value = true;\n try {\n const response = await fetch(`/api/jobs/${report.value.jobId}/cancel`, { method: 'POST' });\n if (!response.ok) throw new Error(`Cancel failed: ${response.status}`);\n await refreshRun();\n } catch (error) {\n console.error(error);\n } finally {\n cancelling.value = false;\n }\n };\n const refreshRun = async () => {\n if (refreshing) {\n refreshPending = true;\n return;\n }\n refreshing = true;\n\n try {\n const response = await fetch(`/api/runs/${report.value.jobId}`, {\n headers: { accept: 'application/json' },\n });\n if (!response.ok) throw new Error(`Run refresh failed: ${response.status}`);\n const previousStatus = report.value.status;\n const nextReport = await response.json();\n report.value = nextReport;\n if (\n ['pending', 'running'].includes(previousStatus) &&\n ['success', 'failed'].includes(nextReport.status) &&\n 'Notification' in window &&\n 'serviceWorker' in navigator &&\n Notification.permission === 'granted' &&\n localStorage.getItem('runner-notifications') === 'enabled'\n ) {\n const registration = await navigator.serviceWorker.ready;\n await registration.showNotification(\n nextReport.status === 'success'\n ? `Job #${nextReport.jobId} succeeded`\n : `Job #${nextReport.jobId} failed`,\n {\n body: `${nextReport.workflowName} finished with status ${nextReport.status}.`,\n icon: '/app-icon.svg',\n badge: '/app-icon.svg',\n tag: `runner-job-${nextReport.jobId}`,\n renotify: true,\n data: { url: `/runs/${nextReport.jobId}` },\n },\n );\n }\n } catch (error) {\n console.error(error);\n } finally {\n refreshing = false;\n if (refreshPending) {\n refreshPending = false;\n void refreshRun();\n }\n }\n };\n const handleJobChange = (event) => {\n const data = JSON.parse(event.data || '{}');\n if (!data.jobId || String(data.jobId) === String(report.value.jobId)) void refreshRun();\n };\n\n onInit(() => {\n document.title = `Run #${report.value.jobId} - ${report.value.workflowName}`;\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.register('/service-worker.js').catch((error) => {\n console.error('Service worker registration failed', error);\n });\n }\n eventSource = new EventSource('/api/events');\n eventSource.addEventListener('jobs.changed', handleJobChange);\n refreshTimer = setInterval(() => {\n if (active.value) void refreshRun();\n }, 60000);\n clockTimer = setInterval(() => {\n now.value = Date.now();\n }, 1000);\n });\n onDestroy(() => {\n eventSource?.close();\n clearInterval(refreshTimer);\n clearInterval(clockTimer);\n });\n\n return { report, inputsJson, timing, upper, stepLog, restartJob, cancelJob, cancelling };\n }\n <\/script>\n <script state>\n __REPORT_STATE__\n <\/script>\n </template>\n\n <script type=\"module\">\n import '@li3/web';\n <\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n", we = /(?:^|[_-])auth(?:entication)?(?:$|[_-])|access[_-]?key|api[_-]?key|authorization|cookie|credential|passphrase|password|private[_-]?key|secret|session(?:id)?|signing[_-]?key|token/i;
359
+ }), xe = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\">\n <rect width=\"512\" height=\"512\" rx=\"112\" fill=\"#030712\"/>\n <path d=\"M128 144h256v224H128z\" fill=\"#111827\" stroke=\"#6366f1\" stroke-width=\"24\"/>\n <path d=\"m174 218 62 38-62 38M260 300h78\" fill=\"none\" stroke=\"#f9fafb\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"24\"/>\n <circle cx=\"365\" cy=\"147\" r=\"47\" fill=\"#10b981\" stroke=\"#030712\" stroke-width=\"18\"/>\n</svg>", Se = "self.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()));\nself.addEventListener('notificationclick', (event) => {\n event.notification.close();\n const target = event.notification.data?.url || '/runs';\n event.waitUntil((async () => {\n const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });\n const existing = windows.find((client) => new URL(client.url).pathname === target);\n if (existing) return existing.focus();\n return self.clients.openWindow(target);\n })());\n});", Ce = "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"theme-color\" content=\"#030712\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n <link rel=\"icon\" href=\"/app-icon.svg\" type=\"image/svg+xml\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n <title>Workflow Run</title>\n <script type=\"importmap\">\n {\n \"imports\": {\n \"@li3/\": \"https://cdn.li3.dev/@li3/\",\n \"ansi_up\": \"https://esm.sh/ansi_up@6.0.6\"\n }\n }\n <\/script>\n </head>\n <body class=\"bg-gray-950 text-gray-100 min-h-screen p-4 md:p-6 font-sans\">\n <template app>\n <div class=\"max-w-5xl mx-auto space-y-4\">\n <header class=\"flex flex-col md:flex-row md:items-center justify-between border-b border-gray-800 pb-6 gap-4\">\n <div class=\"min-w-0\">\n <a href=\"/runs\" class=\"text-xs text-indigo-400 hover:underline mb-1 inline-block\">Back to Dashboard</a>\n <h1 class=\"text-2xl font-bold text-white flex flex-wrap items-center gap-3\">\n {{ report.workflowName }}\n <span class=\"text-sm font-mono text-gray-500\">#{{ report.jobId }}</span>\n <template if=\"report.parentId\">\n <a attr-href=\"'/runs/' + report.parentId\" class=\"text-sm font-mono text-indigo-400 hover:underline\">\n from #{{ report.parentId }}\n </a>\n </template>\n </h1>\n <p class=\"text-xs text-gray-400 mt-1\">Started {{ report.startedAt }} · {{ timing }}</p>\n </div>\n <div class=\"flex items-center gap-2 shrink-0\">\n <span\n class=\"px-4 py-1.5 rounded-full text-sm font-semibold border\"\n bind-class=\"{\n 'bg-emerald-500/10.text-emerald-400.border-emerald-500/20': report.status === 'success',\n 'bg-rose-500/10.text-rose-400.border-rose-500/20': report.status === 'failed',\n 'bg-indigo-500/10.text-indigo-400.border-indigo-500/20': report.status === 'running',\n 'bg-amber-500/10.text-amber-400.border-amber-500/20': !['success', 'failed', 'running'].includes(report.status)\n }\"\n >\n {{ upper(report.status) }}\n </span>\n <template if=\"report.status === 'running'\">\n <button\n class=\"text-sm text-rose-300 px-4 py-1.5 border border-rose-500/50 hover:border-rose-400 rounded-full disabled:opacity-50\"\n on-click=\"cancelJob()\"\n attr-disabled=\"cancelling\"\n >\n {{ cancelling ? 'Cancelling...' : 'Cancel' }}\n </button>\n </template>\n <template if=\"report.status !== 'running'\">\n <button\n class=\"text-sm text-white px-4 py-1.5 border border-gray-600 hover:border-gray-400 rounded-full\"\n on-click=\"restartJob()\"\n >\n Restart\n </button>\n </template>\n </div>\n </header>\n\n <section class=\"rounded overflow-hidden border-b border-gray-800\">\n <h2 class=\"sr-only\">Execution Steps</h2>\n <template for=\"step of report.steps\">\n <details class=\"border border-b-0 border-gray-800 bg-gray-900/50 text-sm\">\n <summary\n class=\"flex flex-col md:flex-row md:items-center justify-between gap-2 p-3 bg-gray-800/40 border-b border-gray-800 cursor-pointer\"\n >\n <div class=\"flex items-center gap-3 min-w-0\">\n <lucide-icon\n class=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-gray-700/70\"\n bind-class=\"{\n 'text-emerald-400': step.status === 'success',\n 'text-rose-400': step.status === 'failed',\n 'text-indigo-400': step.status === 'running',\n 'text-gray-400': !['success', 'failed', 'running'].includes(step.status)\n }\"\n bind-icon=\"\n step.status === 'success'\n ? 'circle-check'\n : step.status === 'failed'\n ? 'circle-x'\n : step.status === 'running'\n ? 'loader-circle'\n : 'circle'\n \"\n size=\"15\"\n ></lucide-icon>\n <template if=\"step.exitCode !== undefined && step.exitCode !== 0\">\n <span class=\"font-mono text-gray-500\">({{ step.exitCode }})</span>\n </template>\n <h3 class=\"font-semibold text-gray-200 truncate min-w-0 flex-1\">{{ step.name }}</h3>\n <div class=\"font-mono text-gray-400 shrink-0\">{{ step.durationMs }}ms</div>\n <span\n class=\"sr-only px-2.5 py-0.5 rounded-full font-medium\"\n bind-class=\"{\n 'text-emerald-400.bg-emerald-500/10': step.status === 'success',\n 'text-rose-400.bg-rose-500/10': step.status === 'failed',\n 'text-indigo-400.bg-indigo-500/10': step.status === 'running',\n 'text-gray-400.bg-gray-500/10': !['success', 'failed', 'running'].includes(step.status)\n }\"\n >\n {{ upper(step.status) }}\n </span>\n </div>\n </summary>\n <template if=\"report.canViewLogs\">\n <div class=\"p-4 bg-gray-950 font-mono text-gray-300 leading-relaxed overflow-auto w-full\">\n <pre class=\"whitespace-pre-wrap\" bind-innerhtml=\"stepLog(step)\"></pre>\n </div>\n </template>\n <template if=\"!report.canViewLogs\">\n <div class=\"p-4 bg-gray-950 text-sm text-gray-500\">Logs are available to authenticated users.</div>\n </template>\n </details>\n </template>\n </section>\n\n <details class=\"bg-gray-900 border border-gray-800 rounded-xl p-4 overflow-auto max-h-[400px]\">\n <summary class=\"cursor-pointer\">\n <h2 class=\"inline text-xs font-semibold text-gray-400 uppercase tracking-wider\">Trigger Inputs</h2>\n </summary>\n <pre class=\"font-mono text-xs text-gray-200 bg-gray-950 p-3 mt-3 rounded-lg overflow-x-auto\">\n{{ inputsJson }}</pre\n >\n </details>\n </div>\n\n <script setup>\n import { computed, onDestroy, onInit, ref } from '@li3/web';\n import { AnsiUp } from 'ansi_up';\n\n export default function setup() {\n const report = ref({});\n const now = ref(Date.now());\n const ansiUp = new AnsiUp();\n ansiUp.use_classes = false;\n let eventSource;\n let refreshTimer;\n let clockTimer;\n let refreshing = false;\n let refreshPending = false;\n const cancelling = ref(false);\n\n const active = computed(() => ['pending', 'running'].includes(report.value.status));\n const inputsJson = computed(() => JSON.stringify(report.value.inputs || {}, null, 2));\n const timing = computed(() => {\n if (report.value.status === 'pending') return 'Waiting for a worker';\n if (report.value.status === 'running') {\n return `Running for ${Math.max(0, now.value - Date.parse(report.value.startedAt))}ms`;\n }\n return `Finished in ${report.value.durationMs}ms`;\n });\n\n const upper = (value) => String(value || '').toUpperCase();\n const stepLog = (step) => {\n if (step.status === 'skipped') return '';\n if (step.status === 'running')\n return '<span class=\"text-indigo-400\">Step is running. Logs will appear after it finishes.</span>';\n if (step.status === 'pending') return '<span class=\"text-gray-500\">Waiting to run.</span>';\n if (step.logContent) return ansiUp.ansi_to_html(step.logContent);\n return '<span class=\"text-gray-500\">(No terminal log output recorded for this step)</span>';\n };\n const restartJob = async () => {\n const response = await fetch(`/restart/${report.value.jobId}`, { method: 'POST' });\n if (response.ok) {\n const { id } = await response.json();\n location.href = `/runs/${id}`;\n }\n };\n const cancelJob = async () => {\n if (cancelling.value || report.value.status !== 'running') return;\n cancelling.value = true;\n try {\n const response = await fetch(`/api/jobs/${report.value.jobId}/cancel`, { method: 'POST' });\n if (!response.ok) throw new Error(`Cancel failed: ${response.status}`);\n await refreshRun();\n } catch (error) {\n console.error(error);\n } finally {\n cancelling.value = false;\n }\n };\n const refreshRun = async () => {\n if (refreshing) {\n refreshPending = true;\n return;\n }\n refreshing = true;\n\n try {\n const response = await fetch(`/api/runs/${report.value.jobId}`, {\n headers: { accept: 'application/json' },\n });\n if (!response.ok) throw new Error(`Run refresh failed: ${response.status}`);\n const previousStatus = report.value.status;\n const nextReport = await response.json();\n report.value = nextReport;\n if (\n ['pending', 'running'].includes(previousStatus) &&\n ['success', 'failed'].includes(nextReport.status) &&\n 'Notification' in window &&\n 'serviceWorker' in navigator &&\n Notification.permission === 'granted' &&\n localStorage.getItem('runner-notifications') === 'enabled'\n ) {\n const registration = await navigator.serviceWorker.ready;\n await registration.showNotification(\n nextReport.status === 'success'\n ? `Job #${nextReport.jobId} succeeded`\n : `Job #${nextReport.jobId} failed`,\n {\n body: `${nextReport.workflowName} finished with status ${nextReport.status}.`,\n icon: '/app-icon.svg',\n badge: '/app-icon.svg',\n tag: `runner-job-${nextReport.jobId}`,\n renotify: true,\n data: { url: `/runs/${nextReport.jobId}` },\n },\n );\n }\n } catch (error) {\n console.error(error);\n } finally {\n refreshing = false;\n if (refreshPending) {\n refreshPending = false;\n void refreshRun();\n }\n }\n };\n const handleJobChange = (event) => {\n const data = JSON.parse(event.data || '{}');\n if (!data.jobId || String(data.jobId) === String(report.value.jobId)) void refreshRun();\n };\n\n onInit(() => {\n document.title = `Run #${report.value.jobId} - ${report.value.workflowName}`;\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.register('/service-worker.js').catch((error) => {\n console.error('Service worker registration failed', error);\n });\n }\n eventSource = new EventSource('/api/events');\n eventSource.addEventListener('jobs.changed', handleJobChange);\n refreshTimer = setInterval(() => {\n if (active.value) void refreshRun();\n }, 60000);\n clockTimer = setInterval(() => {\n now.value = Date.now();\n }, 1000);\n });\n onDestroy(() => {\n eventSource?.close();\n clearInterval(refreshTimer);\n clearInterval(clockTimer);\n });\n\n return { report, inputsJson, timing, upper, stepLog, restartJob, cancelJob, cancelling };\n }\n <\/script>\n <script state>\n __REPORT_STATE__\n <\/script>\n </template>\n\n <script type=\"module\">\n import '@li3/web';\n <\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n", we = /(?:^|[_-])auth(?:entication)?(?:$|[_-])|access[_-]?key|api[_-]?key|authorization|cookie|credential|passphrase|password|private[_-]?key|secret|session(?:id)?|signing[_-]?key|token/i;
360
360
  function Te(e, t, n, r = [], i = !0) {
361
361
  let a = JSON.parse(e.payload), o = e.report ? JSON.parse(e.report) : De(e, a, r), s = e.status;
362
362
  return {
@@ -3857,7 +3857,7 @@ var qn = class {
3857
3857
  let t = Buffer.from(e, "base64"), n = c.createDecipheriv("aes-256-gcm", this.encryptionKey(), t.subarray(0, 12));
3858
3858
  return n.setAuthTag(t.subarray(12, 28)), Buffer.concat([n.update(t.subarray(28)), n.final()]).toString("utf8");
3859
3859
  }
3860
- }, Jn = "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"theme-color\" content=\"#030712\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n <link rel=\"icon\" href=\"/app-icon.svg\" type=\"image/svg+xml\" />\n <title>Runner Workflow Management</title>\n <script type=\"importmap\">\n { \"imports\": { \"@li3/\": \"https://cdn.li3.dev/@li3/\" } }\n <\/script>\n <style>\n body[data-page='list'] .editor-panel {\n display: none;\n }\n body[data-page='editor'] .workflow-list,\n body[data-page='editor'] .secrets-panel {\n display: none;\n }\n </style>\n </head>\n <body data-page=\"__WORKFLOW_PAGE__\" class=\"min-h-screen bg-gray-950 p-4 font-sans text-gray-100 md:p-6\">\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/code-editor.html\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5\">\n <header class=\"flex flex-col gap-4 border-b border-gray-800 pb-5 sm:flex-row sm:items-end sm:justify-between\">\n <div class=\"flex items-start gap-3\">\n <a\n href=\"/runs\"\n aria-label=\"Back to dashboard\"\n class=\"mt-1 rounded-lg border border-gray-800 bg-gray-900 p-2 text-gray-400 transition hover:border-indigo-500/60 hover:text-indigo-300\"\n >\n <lucide-icon icon=\"arrow-left\" size=\"17\"></lucide-icon>\n </a>\n <div>\n <p class=\"text-xs font-semibold uppercase tracking-[0.18em] text-indigo-400\">Runner engine</p>\n <h1 class=\"mt-1 text-2xl font-bold tracking-tight text-white\">Workflow control room</h1>\n <p class=\"mt-1 text-sm text-gray-400\">Draft, validate, and publish revisioned YAML definitions.</p>\n </div>\n </div>\n <a\n href=\"/workflows/new\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg bg-indigo-500 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-950/40 transition hover:bg-indigo-400\"\n >\n <lucide-icon icon=\"plus\" size=\"16\"></lucide-icon>\n New workflow\n </a>\n </header>\n\n <template if=\"notice\">\n <div\n role=\"status\"\n class=\"flex items-center gap-2 rounded-lg border px-3 py-2.5 text-sm\"\n bind-class=\"noticeError ? 'border-rose-500/40.bg-rose-500/10.text-rose-100' : 'border-emerald-500/40.bg-emerald-500/10.text-emerald-100'\"\n >\n <lucide-icon bind-icon=\"noticeError ? 'circle-alert' : 'circle-check'\" size=\"17\"></lucide-icon>\n <span>{{ notice }}</span>\n </div>\n </template>\n\n <section id=\"workflow-layout\" class=\"space-y-5\">\n <aside class=\"workflow-list rounded-xl border border-gray-800 bg-gray-900/80 p-3 shadow-xl shadow-black/10\">\n <div class=\"mb-3 flex items-center justify-between px-2\">\n <h2 class=\"text-xs font-semibold uppercase tracking-wider text-gray-400\">Workflows</h2>\n <span class=\"rounded-full bg-gray-800 px-2 py-0.5 text-xs font-mono text-gray-400\"\n >{{ workflows.length }}</span\n >\n </div>\n <div class=\"space-y-1 flex flex-col w-full\">\n <template for=\"workflow of workflows\">\n <button\n on-click=\"visitWorkflow(workflow.id)\"\n class=\"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-left text-sm transition hover:bg-gray-800\"\n bind-class=\"workflow.id === selectedId ? 'bg-indigo-500/15.text-indigo-100' : 'text-gray-300'\"\n >\n <span class=\"truncate font-medium\">{{ workflow.name }}</span>\n <span\n class=\"ml-3 rounded-full border border-gray-700 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-gray-400\"\n >{{ workflow.status }}</span\n >\n </button>\n </template>\n <p class=\"px-3 py-5 text-center text-sm text-gray-500\" class-hidden=\"workflows.length > 0\">\n No workflows yet.\n </p>\n </div>\n </aside>\n\n <section\n class=\"editor-panel overflow-hidden rounded-xl border border-gray-800 bg-gray-900/80 shadow-xl shadow-black/10\"\n >\n <div class=\"border-b border-gray-800 bg-gray-900 px-4 py-4 sm:px-5\">\n <div class=\"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\n <label class=\"block max-w-xl flex-1 text-xs font-semibold uppercase tracking-wider text-gray-400\"\n >Workflow ID\n <input\n bind-value=\"workflowId\"\n on-input=\"setWorkflowId($event)\"\n class=\"mt-2 w-full rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm normal-case tracking-normal text-white outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"derived-from-workflow-name\"\n />\n </label>\n <div class=\"flex flex-wrap gap-2\">\n <button\n on-click=\"validate()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2.5 text-sm font-medium text-gray-200 transition hover:border-gray-500 hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"badge-check\" size=\"16\"></lucide-icon>\n Validate\n </button>\n <button\n on-click=\"save()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg bg-indigo-500 px-3 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-400 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"save\" size=\"16\"></lucide-icon>\n Save draft\n </button>\n <button\n on-click=\"publish()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-emerald-500/50 px-3 py-2.5 text-sm font-medium text-emerald-300 transition hover:bg-emerald-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"rocket\" size=\"16\"></lucide-icon>\n Publish\n </button>\n <button\n on-click=\"remove()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-rose-500/50 px-3 py-2.5 text-sm font-medium text-rose-300 transition hover:bg-rose-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"trash-2\" size=\"16\"></lucide-icon>\n Delete\n </button>\n </div>\n </div>\n </div>\n <div class=\"p-3 sm:p-5\">\n <div class=\"mb-2 flex items-center justify-between px-1 text-xs text-gray-500\">\n <span class=\"font-medium\">YAML definition</span>\n <span class=\"font-mono\">{{ source.length }} characters</span>\n </div>\n <code-editor\n id=\"source-yaml\"\n language=\"yaml\"\n theme=\"atom-one-dark\"\n nostatus=\"true\"\n class=\"block h-[calc(100vh-23rem)] min-h-[28rem] overflow-hidden rounded-lg border border-gray-700 bg-gray-950\"\n bind-value=\"source\"\n on-change=\"setSource($event.detail)\"\n ></code-editor>\n </div>\n </section>\n </section>\n\n <section\n class=\"secrets-panel rounded-xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10 sm:p-5\"\n >\n <div class=\"mb-4 flex items-start gap-3\">\n <span class=\"rounded-lg bg-indigo-500/10 p-2 text-indigo-300\"\n ><lucide-icon icon=\"key-round\" size=\"18\"></lucide-icon\n ></span>\n <div>\n <h2 class=\"text-base font-semibold text-white\">Secrets</h2>\n <p class=\"mt-0.5 text-sm text-gray-400\">\n Values are write-only and encrypted. Re-saving a name replaces its value.\n </p>\n </div>\n </div>\n <div class=\"grid gap-3 md:grid-cols-[14rem_1fr_auto_auto]\">\n <input\n bind-value=\"secretName\"\n on-input=\"setSecretName($event)\"\n class=\"rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm uppercase outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"NPM_TOKEN\"\n />\n <input\n ref=\"secretValueInput\"\n bind-value=\"secretValue\"\n on-input=\"setSecretValue($event)\"\n type=\"password\"\n class=\"rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"New secret value\"\n />\n <button\n on-click=\"saveSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg border border-indigo-500/60 px-3 py-2.5 text-sm font-medium text-indigo-200 transition hover:bg-indigo-500/10 disabled:opacity-50\"\n >\n <lucide-icon icon=\"lock-keyhole\" size=\"16\"></lucide-icon>Save value\n </button>\n <button\n on-click=\"removeSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg border border-rose-500/50 px-3 py-2.5 text-sm font-medium text-rose-300 transition hover:bg-rose-500/10 disabled:opacity-50\"\n >\n <lucide-icon icon=\"trash-2\" size=\"16\"></lucide-icon>Delete\n </button>\n </div>\n <div class=\"mt-4 flex flex-col space-y-1\">\n <template for=\"name of secrets\"\n ><button\n on-click=\"selectSecret(name)\"\n class=\"rounded-md border border-gray-700 px-2.5 py-1.5 font-mono text-xs text-gray-300 transition hover:border-indigo-500 hover:text-indigo-200 w-full\"\n >\n {{ name }}\n </button></template\n >\n </div>\n </section>\n </main>\n\n <script setup>\n import { onInit, ref, templateRef } from '@li3/web';\n\n export default function () {\n const page = document.body.dataset.page;\n const initialId = __WORKFLOW_ID__;\n const workflows = ref([]);\n const secrets = ref([]);\n const source = ref('');\n const workflowId = ref('');\n const selectedId = ref('');\n const secretName = ref('');\n const secretValue = ref('');\n const notice = ref('');\n const noticeError = ref(false);\n const busy = ref(false);\n const secretValueInput = templateRef('secretValueInput');\n\n const showNotice = (message, error = false) => {\n notice.value = message;\n noticeError.value = error;\n };\n const api = async (url, options = {}) => {\n const response = await fetch(url, {\n headers: { accept: 'application/json', ...(options.body ? { 'content-type': 'application/json' } : {}) },\n ...options,\n });\n const body = response.status === 204 ? null : await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(body.error || `Request failed: ${response.status}`);\n return body;\n };\n const derivedId = () =>\n source.value\n .match(/^\\s*(?:id:\\s*([^\\n]+)|name:\\s*([^\\n]+))/m)\n ?.slice(1)\n .find(Boolean)\n ?.trim()\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '-')\n .replace(/(^-|-$)/g, '') || '';\n const run = async (action) => {\n if (busy.value) return;\n busy.value = true;\n try {\n await action();\n } catch (error) {\n showNotice(error.message, true);\n } finally {\n busy.value = false;\n }\n };\n const loadWorkflows = async () => {\n workflows.value = (await api('/api/workflows')).workflows;\n };\n const loadSecrets = async () => {\n secrets.value = (await api('/api/secrets')).secrets;\n };\n const openWorkflow = async (id) => {\n const workflow = await api(`/api/workflows/${id}`);\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n source.value = workflow.sourceYaml;\n };\n const validate = () =>\n run(async () => {\n const result = await api('/api/workflows/validate', {\n method: 'POST',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n showNotice(`Valid: ${result.workflows.length} workflow definition(s).`);\n });\n const save = () =>\n run(async () => {\n const id = workflowId.value.trim() || derivedId();\n if (!id) throw new Error('Set an ID or add a workflow name first.');\n const workflow = await api(`/api/workflows/${id}`, {\n method: 'PUT',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n showNotice(`Saved ${workflow.id} as draft revision ${workflow.revision}.`);\n if (!initialId) history.replaceState(null, '', `/workflows/${workflow.id}`);\n });\n const publish = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id) throw new Error('Save a workflow before publishing it.');\n const workflow = await api(`/api/workflows/${id}/publish`, { method: 'POST' });\n selectedId.value = workflow.id;\n showNotice(`Published ${workflow.id} revision ${workflow.revision}.`);\n });\n const remove = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id || !confirm(`Delete ${id} and all of its revisions?`)) return;\n await api(`/api/workflows/${id}`, { method: 'DELETE' });\n window.location.href = '/workflows';\n });\n const saveSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || !secretValue.value) throw new Error('Set a secret name and value first.');\n await api(`/api/secrets/${name}`, { method: 'PUT', body: JSON.stringify({ value: secretValue.value }) });\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Saved ${name}.`);\n });\n const removeSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || !confirm(`Delete ${name}?`)) return;\n await api(`/api/secrets/${name}`, { method: 'DELETE' });\n secretName.value = '';\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Deleted ${name}.`);\n });\n const setSource = (value) => {\n source.value = value;\n };\n const setWorkflowId = (event) => {\n workflowId.value = event.target.value;\n };\n const setSecretName = (event) => {\n secretName.value = event.target.value;\n };\n const setSecretValue = (event) => {\n secretValue.value = event.target.value;\n };\n const selectSecret = (name) => {\n secretName.value = name;\n secretValueInput.value?.focus();\n };\n const visitWorkflow = (id) => {\n window.location.href = `/workflows/${id}`;\n };\n\n onInit(() => {\n const loadPage =\n page === 'editor'\n ? initialId\n ? openWorkflow(initialId)\n : Promise.resolve()\n : Promise.all([loadWorkflows(), loadSecrets()]);\n loadPage.catch((error) => showNotice(error.message, true));\n });\n return {\n workflows,\n secrets,\n source,\n workflowId,\n selectedId,\n secretName,\n secretValue,\n notice,\n noticeError,\n busy,\n validate,\n save,\n publish,\n remove,\n saveSecret,\n removeSecret,\n setSource,\n setWorkflowId,\n setSecretName,\n setSecretValue,\n selectSecret,\n visitWorkflow,\n };\n }\n <\/script>\n </template>\n\n <script type=\"module\">\n import '@li3/web';\n <\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n";
3860
+ }, Jn = "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"theme-color\" content=\"#030712\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n <link rel=\"icon\" href=\"/app-icon.svg\" type=\"image/svg+xml\" />\n <title>Runner Workflow Management</title>\n <script type=\"importmap\">\n { \"imports\": { \"@li3/\": \"https://cdn.li3.dev/@li3/\" } }\n <\/script>\n <style>\n body[data-page='list'] .editor-panel {\n display: none;\n }\n body[data-page='editor'] .workflow-list,\n body[data-page='editor'] .secrets-panel {\n display: none;\n }\n </style>\n </head>\n <body data-page=\"__WORKFLOW_PAGE__\" class=\"min-h-screen bg-gray-950 p-4 font-sans text-gray-100 md:p-6\">\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/code-editor.html\" />\n <link rel=\"component\" href=\"https://sodium.static.apphor.de/lucide-icon.html\" />\n\n <template app>\n <main class=\"mx-auto max-w-6xl space-y-5\">\n <header class=\"flex flex-col gap-4 border-b border-gray-800 pb-5 sm:flex-row sm:items-end sm:justify-between\">\n <div class=\"flex items-start gap-3\">\n <a\n href=\"/runs\"\n aria-label=\"Back to dashboard\"\n class=\"mt-1 rounded-lg border border-gray-800 bg-gray-900 p-2 text-gray-400 transition hover:border-indigo-500/60 hover:text-indigo-300\"\n >\n <lucide-icon icon=\"arrow-left\" size=\"17\"></lucide-icon>\n </a>\n <div>\n <p class=\"text-xs font-semibold uppercase tracking-[0.18em] text-indigo-400\">Runner engine</p>\n <h1 class=\"mt-1 text-2xl font-bold tracking-tight text-white\">Workflow control room</h1>\n <p class=\"mt-1 text-sm text-gray-400\">Draft, validate, and publish revisioned YAML definitions.</p>\n </div>\n </div>\n <a\n href=\"/workflows/new\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg bg-indigo-500 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-950/40 transition hover:bg-indigo-400\"\n >\n <lucide-icon icon=\"plus\" size=\"16\"></lucide-icon>\n New workflow\n </a>\n </header>\n\n <template if=\"notice\">\n <div\n role=\"status\"\n class=\"flex items-center gap-2 rounded-lg border px-3 py-2.5 text-sm\"\n bind-class=\"noticeError ? 'border-rose-500/40.bg-rose-500/10.text-rose-100' : 'border-emerald-500/40.bg-emerald-500/10.text-emerald-100'\"\n >\n <lucide-icon bind-icon=\"noticeError ? 'circle-alert' : 'circle-check'\" size=\"17\"></lucide-icon>\n <span>{{ notice }}</span>\n </div>\n </template>\n\n <section id=\"workflow-layout\" class=\"space-y-5\">\n <aside class=\"workflow-list rounded-xl border border-gray-800 bg-gray-900/80 p-3 shadow-xl shadow-black/10\">\n <div class=\"mb-3 flex items-center justify-between px-2\">\n <h2 class=\"text-xs font-semibold uppercase tracking-wider text-gray-400\">Workflows</h2>\n <span class=\"rounded-full bg-gray-800 px-2 py-0.5 text-xs font-mono text-gray-400\"\n >{{ workflows.length }}</span\n >\n </div>\n <div class=\"space-y-1 flex flex-col w-full\">\n <template for=\"workflow of workflows\">\n <button\n on-click=\"visitWorkflow(workflow.id)\"\n class=\"flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-left text-sm transition hover:bg-gray-800\"\n bind-class=\"workflow.id === selectedId ? 'bg-indigo-500/15.text-indigo-100' : 'text-gray-300'\"\n >\n <span class=\"truncate font-medium\">{{ workflow.name }}</span>\n <span\n class=\"ml-3 rounded-full border border-gray-700 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-gray-400\"\n >{{ workflow.status }}</span\n >\n </button>\n </template>\n <p class=\"px-3 py-5 text-center text-sm text-gray-500\" class-hidden=\"workflows.length > 0\">\n No workflows yet.\n </p>\n </div>\n </aside>\n\n <section\n class=\"editor-panel overflow-hidden rounded-xl border border-gray-800 bg-gray-900/80 shadow-xl shadow-black/10\"\n >\n <div class=\"border-b border-gray-800 bg-gray-900 px-4 py-4 sm:px-5\">\n <div class=\"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\n <label class=\"block max-w-xl flex-1 text-xs font-semibold uppercase tracking-wider text-gray-400\"\n >Workflow ID\n <input\n bind-value=\"workflowId\"\n on-input=\"setWorkflowId($event)\"\n class=\"mt-2 w-full rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm normal-case tracking-normal text-white outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"derived-from-workflow-name\"\n />\n </label>\n <label class=\"inline-flex items-center gap-2 text-sm text-gray-300\">\n <input\n type=\"checkbox\"\n bind-checked=\"enabled\"\n on-change=\"setEnabled($event)\"\n class=\"h-4 w-4 rounded border-gray-700 bg-gray-950 text-indigo-500 focus:ring-2 focus:ring-indigo-500/30\"\n />\n Enabled\n </label>\n <div class=\"flex flex-wrap gap-2\">\n <button\n on-click=\"validate()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-gray-700 px-3 py-2.5 text-sm font-medium text-gray-200 transition hover:border-gray-500 hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"badge-check\" size=\"16\"></lucide-icon>\n Validate\n </button>\n <button\n on-click=\"save()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg bg-indigo-500 px-3 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-400 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"save\" size=\"16\"></lucide-icon>\n Save draft\n </button>\n <button\n on-click=\"publish()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-emerald-500/50 px-3 py-2.5 text-sm font-medium text-emerald-300 transition hover:bg-emerald-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"rocket\" size=\"16\"></lucide-icon>\n Publish\n </button>\n <button\n on-click=\"remove()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center gap-2 rounded-lg border border-rose-500/50 px-3 py-2.5 text-sm font-medium text-rose-300 transition hover:bg-rose-500/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <lucide-icon icon=\"trash-2\" size=\"16\"></lucide-icon>\n Delete\n </button>\n </div>\n </div>\n </div>\n <div class=\"p-3 sm:p-5\">\n <div class=\"mb-2 flex items-center justify-between px-1 text-xs text-gray-500\">\n <span class=\"font-medium\">YAML definition</span>\n <span class=\"font-mono\">{{ source.length }} characters</span>\n </div>\n <code-editor\n id=\"source-yaml\"\n language=\"yaml\"\n theme=\"atom-one-dark\"\n nostatus=\"true\"\n class=\"block h-[calc(100vh-23rem)] min-h-[28rem] overflow-hidden rounded-lg border border-gray-700 bg-gray-950\"\n bind-value=\"source\"\n on-change=\"setSource($event.detail)\"\n ></code-editor>\n </div>\n </section>\n </section>\n\n <section\n class=\"secrets-panel rounded-xl border border-gray-800 bg-gray-900/80 p-4 shadow-xl shadow-black/10 sm:p-5\"\n >\n <div class=\"mb-4 flex items-start gap-3\">\n <span class=\"rounded-lg bg-indigo-500/10 p-2 text-indigo-300\"\n ><lucide-icon icon=\"key-round\" size=\"18\"></lucide-icon\n ></span>\n <div>\n <h2 class=\"text-base font-semibold text-white\">Secrets</h2>\n <p class=\"mt-0.5 text-sm text-gray-400\">\n Values are write-only and encrypted. Re-saving a name replaces its value.\n </p>\n </div>\n </div>\n <div class=\"grid gap-3 md:grid-cols-[14rem_1fr_auto_auto]\">\n <input\n bind-value=\"secretName\"\n on-input=\"setSecretName($event)\"\n class=\"rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm uppercase outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"NPM_TOKEN\"\n />\n <input\n ref=\"secretValueInput\"\n bind-value=\"secretValue\"\n on-input=\"setSecretValue($event)\"\n type=\"password\"\n class=\"rounded-lg border border-gray-700 bg-gray-950 px-3 py-2.5 font-mono text-sm outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20\"\n placeholder=\"New secret value\"\n />\n <button\n on-click=\"saveSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg border border-indigo-500/60 px-3 py-2.5 text-sm font-medium text-indigo-200 transition hover:bg-indigo-500/10 disabled:opacity-50\"\n >\n <lucide-icon icon=\"lock-keyhole\" size=\"16\"></lucide-icon>Save value\n </button>\n <button\n on-click=\"removeSecret()\"\n bind-disabled=\"busy\"\n class=\"inline-flex items-center justify-center gap-2 rounded-lg border border-rose-500/50 px-3 py-2.5 text-sm font-medium text-rose-300 transition hover:bg-rose-500/10 disabled:opacity-50\"\n >\n <lucide-icon icon=\"trash-2\" size=\"16\"></lucide-icon>Delete\n </button>\n </div>\n <div class=\"mt-4 flex flex-col space-y-1\">\n <template for=\"name of secrets\"\n ><button\n on-click=\"selectSecret(name)\"\n class=\"rounded-md border border-gray-700 px-2.5 py-1.5 font-mono text-xs text-gray-300 transition hover:border-indigo-500 hover:text-indigo-200 w-full\"\n >\n {{ name }}\n </button></template\n >\n </div>\n </section>\n </main>\n\n <script setup>\n import { onInit, ref, templateRef } from '@li3/web';\n\n export default function () {\n const page = document.body.dataset.page;\n const initialId = __WORKFLOW_ID__;\n const workflows = ref([]);\n const secrets = ref([]);\n const source = ref('');\n const workflowId = ref('');\n const enabled = ref(true);\n const selectedId = ref('');\n const secretName = ref('');\n const secretValue = ref('');\n const notice = ref('');\n const noticeError = ref(false);\n const busy = ref(false);\n const secretValueInput = templateRef('secretValueInput');\n\n const showNotice = (message, error = false) => {\n notice.value = message;\n noticeError.value = error;\n };\n const api = async (url, options = {}) => {\n const response = await fetch(url, {\n headers: { accept: 'application/json', ...(options.body ? { 'content-type': 'application/json' } : {}) },\n ...options,\n });\n const body = response.status === 204 ? null : await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(body.error || `Request failed: ${response.status}`);\n return body;\n };\n const derivedId = () =>\n source.value\n .match(/^\\s*(?:id:\\s*([^\\n]+)|name:\\s*([^\\n]+))/m)\n ?.slice(1)\n .find(Boolean)\n ?.trim()\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '-')\n .replace(/(^-|-$)/g, '') || '';\n const run = async (action) => {\n if (busy.value) return;\n busy.value = true;\n try {\n await action();\n } catch (error) {\n showNotice(error.message, true);\n } finally {\n busy.value = false;\n }\n };\n const loadWorkflows = async () => {\n workflows.value = (await api('/api/workflows')).workflows;\n };\n const loadSecrets = async () => {\n secrets.value = (await api('/api/secrets')).secrets;\n };\n const openWorkflow = async (id) => {\n const workflow = await api(`/api/workflows/${id}`);\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n source.value = workflow.sourceYaml;\n enabled.value = workflow.enabled;\n };\n const validate = () =>\n run(async () => {\n const result = await api('/api/workflows/validate', {\n method: 'POST',\n body: JSON.stringify({ sourceYaml: source.value }),\n });\n showNotice(`Valid: ${result.workflows.length} workflow definition(s).`);\n });\n const save = () =>\n run(async () => {\n const id = workflowId.value.trim() || derivedId();\n if (!id) throw new Error('Set an ID or add a workflow name first.');\n const workflow = await api(`/api/workflows/${id}`, {\n method: 'PUT',\n body: JSON.stringify({ sourceYaml: source.value, enabled: enabled.value }),\n });\n selectedId.value = workflow.id;\n workflowId.value = workflow.id;\n showNotice(`Saved ${workflow.id} as draft revision ${workflow.revision}.`);\n if (!initialId) history.replaceState(null, '', `/workflows/${workflow.id}`);\n });\n const publish = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id) throw new Error('Save a workflow before publishing it.');\n const workflow = await api(`/api/workflows/${id}/publish`, { method: 'POST' });\n selectedId.value = workflow.id;\n showNotice(`Published ${workflow.id} revision ${workflow.revision}.`);\n });\n const remove = () =>\n run(async () => {\n const id = workflowId.value.trim() || selectedId.value;\n if (!id || !confirm(`Delete ${id} and all of its revisions?`)) return;\n await api(`/api/workflows/${id}`, { method: 'DELETE' });\n window.location.href = '/workflows';\n });\n const saveSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || !secretValue.value) throw new Error('Set a secret name and value first.');\n await api(`/api/secrets/${name}`, { method: 'PUT', body: JSON.stringify({ value: secretValue.value }) });\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Saved ${name}.`);\n });\n const removeSecret = () =>\n run(async () => {\n const name = secretName.value.trim();\n if (!name || !confirm(`Delete ${name}?`)) return;\n await api(`/api/secrets/${name}`, { method: 'DELETE' });\n secretName.value = '';\n secretValue.value = '';\n await loadSecrets();\n showNotice(`Deleted ${name}.`);\n });\n const setSource = (value) => {\n source.value = value;\n };\n const setWorkflowId = (event) => {\n workflowId.value = event.target.value;\n };\n const setEnabled = (event) => {\n enabled.value = event.target.checked;\n };\n const setSecretName = (event) => {\n secretName.value = event.target.value;\n };\n const setSecretValue = (event) => {\n secretValue.value = event.target.value;\n };\n const selectSecret = (name) => {\n secretName.value = name;\n secretValueInput.value?.focus();\n };\n const visitWorkflow = (id) => {\n window.location.href = `/workflows/${id}`;\n };\n\n onInit(() => {\n const loadPage =\n page === 'editor'\n ? initialId\n ? openWorkflow(initialId)\n : Promise.resolve()\n : Promise.all([loadWorkflows(), loadSecrets()]);\n loadPage.catch((error) => showNotice(error.message, true));\n });\n return {\n workflows,\n secrets,\n source,\n workflowId,\n enabled,\n selectedId,\n secretName,\n secretValue,\n notice,\n noticeError,\n busy,\n validate,\n save,\n publish,\n remove,\n saveSecret,\n removeSecret,\n setSource,\n setWorkflowId,\n setEnabled,\n setSecretName,\n setSecretValue,\n selectSecret,\n visitWorkflow,\n };\n }\n <\/script>\n </template>\n\n <script type=\"module\">\n import '@li3/web';\n <\/script>\n <script src=\"https://cdn.tailwindcss.com\"><\/script>\n </body>\n</html>\n";
3861
3861
  //#endregion
3862
3862
  //#region src/workflows-ui.ts
3863
3863
  function Yn(e, t = "") {
@@ -8200,7 +8200,7 @@ function Xo(e) {
8200
8200
  }
8201
8201
  var Zo = class {
8202
8202
  async init() {
8203
- await S.exec("\n CREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY, name TEXT NOT NULL, source_yaml TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'draft', active_revision INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n CREATE TABLE IF NOT EXISTS workflow_revisions (\n workflow_id TEXT NOT NULL, revision INTEGER NOT NULL, source_yaml TEXT NOT NULL,\n normalized_json TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, revision)\n );\n CREATE TABLE IF NOT EXISTS scheduled_runs (\n workflow_id TEXT NOT NULL, trigger_id TEXT NOT NULL, scheduled_for TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, trigger_id, scheduled_for)\n );\n ");
8203
+ await S.exec("\n CREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY, name TEXT NOT NULL, source_yaml TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'draft', active_revision INTEGER, enabled INTEGER NOT NULL DEFAULT 1,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n CREATE TABLE IF NOT EXISTS workflow_revisions (\n workflow_id TEXT NOT NULL, revision INTEGER NOT NULL, source_yaml TEXT NOT NULL,\n normalized_json TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, revision)\n );\n CREATE TABLE IF NOT EXISTS scheduled_runs (\n workflow_id TEXT NOT NULL, trigger_id TEXT NOT NULL, scheduled_for TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (workflow_id, trigger_id, scheduled_for)\n );\n "), (await S.all("PRAGMA table_info(workflows)")).some((e) => e.name === "enabled") || await S.run("ALTER TABLE workflows ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1");
8204
8204
  }
8205
8205
  validate(e) {
8206
8206
  let t = Xo(e);
@@ -8210,25 +8210,27 @@ var Zo = class {
8210
8210
  }
8211
8211
  return t;
8212
8212
  }
8213
- async saveDraft(e, t) {
8214
- let n = this.validate(t);
8215
- if (n.length !== 1 || n[0].id !== e) throw Error("Workflow id must match the YAML name or id");
8216
- let r = await S.get("SELECT COALESCE(MAX(revision), 0) AS revision FROM workflow_revisions WHERE workflow_id = ?", [e]), i = Number(r?.revision || 0) + 1;
8217
- return await S.run("INSERT INTO workflows (id, name, source_yaml, status) VALUES (?, ?, ?, 'draft')\n ON CONFLICT(id) DO UPDATE SET name = excluded.name, source_yaml = excluded.source_yaml, status = 'draft', updated_at = CURRENT_TIMESTAMP", [
8213
+ async saveDraft(e, t, n = !0) {
8214
+ let r = this.validate(t);
8215
+ if (r.length !== 1 || r[0].id !== e) throw Error("Workflow id must match the YAML name or id");
8216
+ let i = await S.get("SELECT COALESCE(MAX(revision), 0) AS revision FROM workflow_revisions WHERE workflow_id = ?", [e]), a = Number(i?.revision || 0) + 1;
8217
+ return await S.run("INSERT INTO workflows (id, name, source_yaml, status, enabled) VALUES (?, ?, ?, 'draft', ?)\n ON CONFLICT(id) DO UPDATE SET name = excluded.name, source_yaml = excluded.source_yaml, status = 'draft', enabled = excluded.enabled, updated_at = CURRENT_TIMESTAMP", [
8218
8218
  e,
8219
- n[0].name,
8220
- t
8219
+ r[0].name,
8220
+ t,
8221
+ +!!n
8221
8222
  ]), await S.run("INSERT INTO workflow_revisions (workflow_id, revision, source_yaml, normalized_json) VALUES (?, ?, ?, ?)", [
8222
8223
  e,
8223
- i,
8224
+ a,
8224
8225
  t,
8225
- JSON.stringify(n[0])
8226
+ JSON.stringify(r[0])
8226
8227
  ]), {
8227
8228
  id: e,
8228
- name: n[0].name,
8229
+ name: r[0].name,
8229
8230
  sourceYaml: t,
8230
- revision: i,
8231
- status: "draft"
8231
+ revision: a,
8232
+ status: "draft",
8233
+ enabled: n
8232
8234
  };
8233
8235
  }
8234
8236
  async publish(e) {
@@ -8241,33 +8243,36 @@ var Zo = class {
8241
8243
  name: t.name,
8242
8244
  sourceYaml: t.source_yaml,
8243
8245
  revision: Number(n.revision),
8244
- status: "published"
8246
+ status: "published",
8247
+ enabled: !!t.enabled
8245
8248
  };
8246
8249
  }
8247
8250
  async list() {
8248
- return (await S.all("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status FROM workflows ORDER BY name")).map((e) => ({
8251
+ return (await S.all("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status, enabled FROM workflows ORDER BY name")).map((e) => ({
8249
8252
  id: e.id,
8250
8253
  name: e.name,
8251
8254
  sourceYaml: e.source_yaml,
8252
8255
  revision: Number(e.revision),
8253
- status: e.status
8256
+ status: e.status,
8257
+ enabled: !!e.enabled
8254
8258
  }));
8255
8259
  }
8256
8260
  async get(e) {
8257
- let t = await S.get("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status FROM workflows WHERE id = ?", [e]);
8261
+ let t = await S.get("SELECT id, name, source_yaml, COALESCE(active_revision, 0) AS revision, status, enabled FROM workflows WHERE id = ?", [e]);
8258
8262
  return t ? {
8259
8263
  id: t.id,
8260
8264
  name: t.name,
8261
8265
  sourceYaml: t.source_yaml,
8262
8266
  revision: Number(t.revision),
8263
- status: t.status
8267
+ status: t.status,
8268
+ enabled: !!t.enabled
8264
8269
  } : null;
8265
8270
  }
8266
8271
  async delete(e) {
8267
8272
  return await this.get(e) ? (await S.run("DELETE FROM scheduled_runs WHERE workflow_id = ?", [e]), await S.run("DELETE FROM workflow_revisions WHERE workflow_id = ?", [e]), await S.run("DELETE FROM workflows WHERE id = ?", [e]), !0) : !1;
8268
8273
  }
8269
8274
  async published() {
8270
- return (await S.all("SELECT w.id AS workflow_id, r.revision, r.normalized_json FROM workflows w JOIN workflow_revisions r\n ON r.workflow_id = w.id AND r.revision = w.active_revision WHERE w.status = 'published'")).map((e) => ({
8275
+ return (await S.all("SELECT w.id AS workflow_id, r.revision, r.normalized_json FROM workflows w JOIN workflow_revisions r\n ON r.workflow_id = w.id AND r.revision = w.active_revision WHERE w.status = 'published' AND w.enabled = 1")).map((e) => ({
8271
8276
  workflowId: e.workflow_id,
8272
8277
  revision: Number(e.revision),
8273
8278
  definition: JSON.parse(e.normalized_json)
@@ -8526,7 +8531,7 @@ var Zo = class {
8526
8531
  return;
8527
8532
  }
8528
8533
  try {
8529
- let e = await this.workflows.saveDraft(n, r.sourceYaml);
8534
+ let e = await this.workflows.saveDraft(n, r.sourceYaml, r.enabled !== !1);
8530
8535
  t.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify(e));
8531
8536
  } catch (e) {
8532
8537
  t.writeHead(422, { "Content-Type": "application/json; charset=utf-8" }), t.end(JSON.stringify({ error: e.message }));
@@ -5,13 +5,14 @@ export interface StoredWorkflow {
5
5
  sourceYaml: string;
6
6
  revision: number;
7
7
  status: 'draft' | 'published' | 'archived';
8
+ enabled: boolean;
8
9
  }
9
10
  /** Parses the portable, DB-stored YAML format. Includes are deliberately unsupported. */
10
11
  export declare function parseWorkflow(sourceYaml: string): WorkflowDefinition[];
11
12
  export declare class WorkflowRepository {
12
13
  init(): Promise<void>;
13
14
  validate(sourceYaml: string): WorkflowDefinition[];
14
- saveDraft(id: string, sourceYaml: string): Promise<StoredWorkflow>;
15
+ saveDraft(id: string, sourceYaml: string, enabled?: boolean): Promise<StoredWorkflow>;
15
16
  publish(id: string): Promise<StoredWorkflow | null>;
16
17
  list(): Promise<StoredWorkflow[]>;
17
18
  get(id: string): Promise<StoredWorkflow | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC;CAC5C;AAQD,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAuBtE;AAED,qBAAa,kBAAkB;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAa5C,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAWlE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IASnD,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAKjC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAK/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASpC,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAMxC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAKrF,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKvG"}
1
+ {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC;IAC3C,OAAO,EAAE,OAAO,CAAC;CAClB;AAQD,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAuBtE;AAED,qBAAa,kBAAkB;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAa5C,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAWlF,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IASnD,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAKjC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAK/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASpC,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAMxC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAKrF,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKvG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-cli/on",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "CLI entry point for the on plugin ecosystem.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.3",