loopable-cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/_connectorId-CDCuSgpN.js +1 -0
  3. package/.output/public/assets/_loopId-DOnIsVTW.js +1 -0
  4. package/.output/public/assets/_taskId-BO5MZnlZ.js +1 -0
  5. package/.output/public/assets/agents-CBozi7oi.js +1 -0
  6. package/.output/public/assets/alert-Bn_wjgTX.js +1 -0
  7. package/.output/public/assets/button-K3ypIhAZ.js +1 -0
  8. package/.output/public/assets/card-DiVnaTIE.js +1 -0
  9. package/.output/public/assets/chevron-right-BrctLtFK.js +1 -0
  10. package/.output/public/assets/connection-status-DKWelvLO.js +1 -0
  11. package/.output/public/assets/connector-icon-CEbdE71G.js +1 -0
  12. package/.output/public/assets/connectors-JyX3IzJj.js +1 -0
  13. package/.output/public/assets/gaps-PjmcbaWo.js +1 -0
  14. package/.output/public/assets/inbox-CujRR0Cz.js +1 -0
  15. package/.output/public/assets/index-C9SQ_Baq.js +16 -0
  16. package/.output/public/assets/loop-form-Dw3cuTzw.js +1 -0
  17. package/.output/public/assets/loops-DcEfZa2_.js +1 -0
  18. package/.output/public/assets/manifests-yJpevyye.js +1 -0
  19. package/.output/public/assets/new-CwsnNuRW.js +1 -0
  20. package/.output/public/assets/page-bvnfRMum.js +1 -0
  21. package/.output/public/assets/play-yHLs4DjK.js +1 -0
  22. package/.output/public/assets/plus-Ber25lrU.js +1 -0
  23. package/.output/public/assets/preload-helper-BoU_7EQC.js +3 -0
  24. package/.output/public/assets/{refresh-cw-C3xBnKfy.js → refresh-cw-BzSY7M8I.js} +1 -1
  25. package/.output/public/assets/rotate-ccw-DLv9EfCX.js +1 -0
  26. package/.output/public/assets/runners-DzX-RU3i.js +2 -0
  27. package/.output/public/assets/select-C9grIFwG.js +1 -0
  28. package/.output/public/assets/setting-fields-DUc3npgh.js +3 -0
  29. package/.output/public/assets/styles-B9B98zLk.css +2 -0
  30. package/.output/public/assets/switch-DwABVI6d.js +1 -0
  31. package/.output/public/assets/task-list-Ddk8-qt7.js +1 -0
  32. package/.output/public/assets/textarea-Ds-gIYUQ.js +1 -0
  33. package/.output/public/assets/time-goBWd-5J.js +1 -0
  34. package/.output/public/assets/use-live-tasks-DZNNuECy.js +1 -0
  35. package/.output/public/assets/useCompositeListItem-P4Nmmn7j.js +1 -0
  36. package/.output/public/assets/usePositioner-B7i9Ejzh.js +1 -0
  37. package/.output/server/_chunks/ssr-renderer.mjs +1 -1
  38. package/.output/server/{_connectorId-C9yydJrC.mjs → _connectorId-COuQN02a.mjs} +198 -188
  39. package/.output/server/_libs/@base-ui/react+[...].mjs +1410 -804
  40. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  41. package/.output/server/_libs/lucide-react.mjs +93 -84
  42. package/.output/server/_libs/next-themes.mjs +1 -1
  43. package/.output/server/_libs/sonner.mjs +1 -1
  44. package/.output/server/_loopId-CZh6_Yhr.mjs +310 -0
  45. package/.output/server/_ssr/{agents-DG643yoJ.mjs → agents-B_qo1gpL.mjs} +195 -6
  46. package/.output/server/_ssr/{agents-C7B6RJ6-.mjs → agents-Bz10gz_m.mjs} +8 -8
  47. package/.output/server/_ssr/agents-CjbOUp3Z.mjs +206 -0
  48. package/.output/server/_ssr/{alert-DQvzE7bu.mjs → alert-C2WBWfzo.mjs} +2 -2
  49. package/.output/server/_ssr/card-r1cV-PCN.mjs +36 -0
  50. package/.output/server/_ssr/{client-BtVRVhQ2.mjs → client-Dk3SD8Il.mjs} +5 -1
  51. package/.output/server/_ssr/connection-status-BogAL-Fj.mjs +32 -0
  52. package/.output/server/_ssr/{connections-Df-aDpfJ.mjs → connections-Cqvb6Gw2.mjs} +372 -325
  53. package/.output/server/_ssr/connector-icon-DrhVbhs6.mjs +190 -0
  54. package/.output/server/_ssr/connectors-BfS5tQKr.mjs +81 -0
  55. package/.output/server/_ssr/{connectors-e0mf2pMs.mjs → connectors-DJI-rzPU.mjs} +10 -10
  56. package/.output/server/_ssr/{createServerRpc-DPDrnFdH.mjs → createServerRpc-DDrp2nLT.mjs} +2 -2
  57. package/.output/server/_ssr/{gaps-Cz76ZicM.mjs → gaps-CcvHiLcj.mjs} +15 -7
  58. package/.output/server/_ssr/inbox-D1ef761v.mjs +178 -0
  59. package/.output/server/_ssr/loop-form-DWwOR05R.mjs +346 -0
  60. package/.output/server/_ssr/{loops-D8WuK7Ke.mjs → loops-6WhUe_lc.mjs} +37 -27
  61. package/.output/server/_ssr/loops-DHPjzG-t.mjs +217 -0
  62. package/.output/server/_ssr/new-ChKBdcXJ.mjs +245 -0
  63. package/.output/server/_ssr/page-DZVxKvdn.mjs +120 -0
  64. package/.output/server/_ssr/{router-54IDWTVz.mjs → router-DdwREiOf.mjs} +162 -489
  65. package/.output/server/_ssr/runners-CK9Cx9fS.mjs +217 -0
  66. package/.output/server/_ssr/runners-CrznFPHt.mjs +140 -0
  67. package/.output/server/_ssr/{runners-Cj6z9Mqh.mjs → runners-W-pgo5yo.mjs} +5 -5
  68. package/.output/server/_ssr/{secrets-CRnpwGBh.mjs → secrets-CV5scFlt.mjs} +2 -2
  69. package/.output/server/_ssr/select-BtndHhHb.mjs +76 -0
  70. package/.output/server/_ssr/{setting-fields-DzBlslan.mjs → setting-fields-LNgywAW7.mjs} +25 -20
  71. package/.output/server/_ssr/ssr.mjs +43 -42
  72. package/.output/server/_ssr/{start-Vq3Oan3v.mjs → start-C_j-e4vF.mjs} +9 -4
  73. package/.output/server/_ssr/{switch-1pnd_bPJ.mjs → switch-odxNO_S4.mjs} +3 -3
  74. package/.output/server/_ssr/task-list-Dnmq_WeE.mjs +150 -0
  75. package/.output/server/_ssr/{tasks-Qtml5TlK.mjs → tasks-D-kXLgVP.mjs} +13 -13
  76. package/.output/server/_ssr/{tasks-BDqznm57.mjs → tasks-D7aUvA33.mjs} +44 -17
  77. package/.output/server/_ssr/{textarea-CHJTKtsJ.mjs → textarea-D2c_vTIC.mjs} +2 -2
  78. package/.output/server/_ssr/time-Cy8TZP0Z.mjs +13 -0
  79. package/.output/server/_ssr/use-live-tasks-BzhTkEOL.mjs +40 -0
  80. package/.output/server/_tanstack-start-manifest_v-2HUdn-2E.mjs +166 -0
  81. package/.output/server/_taskId-D3KkNJW1.mjs +263 -0
  82. package/.output/server/index.mjs +199 -164
  83. package/README.md +36 -6
  84. package/dist/cli.mjs +161 -8
  85. package/dist/dispatcher.mjs +671 -285
  86. package/dist/runner.mjs +333 -46
  87. package/drizzle/0025_prompt_tasks_done.sql +2 -0
  88. package/drizzle/0026_guidance_into_prompt.sql +3 -0
  89. package/drizzle/0027_relative_folders.sql +5 -0
  90. package/drizzle/0028_runner_version.sql +5 -0
  91. package/drizzle/meta/_journal.json +29 -1
  92. package/package.json +4 -2
  93. package/.output/public/assets/_connectorId-CkygNLny.js +0 -1
  94. package/.output/public/assets/_loopId-Cshlc5Ja.js +0 -1
  95. package/.output/public/assets/_taskId-BKDIJ_Fg.js +0 -1
  96. package/.output/public/assets/alert-BT2EVMJP.js +0 -1
  97. package/.output/public/assets/arrow-left-CWUZt6k2.js +0 -1
  98. package/.output/public/assets/arrow-right-dy_oOg_c.js +0 -1
  99. package/.output/public/assets/connection-status-DCqqmY_6.js +0 -1
  100. package/.output/public/assets/connector-icon-BUoK6Cru.js +0 -1
  101. package/.output/public/assets/connectors-CeLBtEtg.js +0 -1
  102. package/.output/public/assets/external-link-DC11orFA.js +0 -1
  103. package/.output/public/assets/gaps-BtFoD7mF.js +0 -1
  104. package/.output/public/assets/inbox-BYNjkNDJ.js +0 -1
  105. package/.output/public/assets/index-0yP3Jn_G.js +0 -18
  106. package/.output/public/assets/loader-B4Epchm_.js +0 -1
  107. package/.output/public/assets/loop-form-DnSJNc1k.js +0 -1
  108. package/.output/public/assets/loops-1CvbqO6m.js +0 -1
  109. package/.output/public/assets/new-m5NlqYQT.js +0 -1
  110. package/.output/public/assets/play-DD4xQgvi.js +0 -1
  111. package/.output/public/assets/preload-helper-CfdAYKD7.js +0 -3
  112. package/.output/public/assets/rolldown-runtime-CbXtAM7H.js +0 -1
  113. package/.output/public/assets/runners-D8QVl-Y6.js +0 -1
  114. package/.output/public/assets/select-Cppu-hti.js +0 -1
  115. package/.output/public/assets/setting-fields-bJ_gTG7Q.js +0 -3
  116. package/.output/public/assets/styles-ClTGaXU0.css +0 -2
  117. package/.output/public/assets/switch-hRIbNGOj.js +0 -1
  118. package/.output/public/assets/task-list-BAxjcVgr.js +0 -1
  119. package/.output/public/assets/textarea-Fyuzi6Vp.js +0 -1
  120. package/.output/public/assets/trash-2-BXZzBsoV.js +0 -1
  121. package/.output/public/assets/useValueChanged-VVyq4hb9.js +0 -1
  122. package/.output/server/_loopId-B0VG-eBC.mjs +0 -301
  123. package/.output/server/_ssr/connection-status-BZr3Y-IC.mjs +0 -31
  124. package/.output/server/_ssr/connector-icon-WvxwZ5TF.mjs +0 -56
  125. package/.output/server/_ssr/connectors-BU0IiRge.mjs +0 -107
  126. package/.output/server/_ssr/inbox-CAUBa1I3.mjs +0 -114
  127. package/.output/server/_ssr/loop-form-FqSaXiHl.mjs +0 -413
  128. package/.output/server/_ssr/loops-Dqoebe8q.mjs +0 -222
  129. package/.output/server/_ssr/new-bBsVaWD6.mjs +0 -169
  130. package/.output/server/_ssr/runners-D2UAdLyj.mjs +0 -106
  131. package/.output/server/_ssr/runners-DGWaVvWi.mjs +0 -116
  132. package/.output/server/_ssr/task-list-_qgBS9LU.mjs +0 -187
  133. package/.output/server/_tanstack-start-manifest_v-CvheE7ZF.mjs +0 -131
  134. package/.output/server/_taskId-V4QLBO2N.mjs +0 -256
@@ -0,0 +1,217 @@
1
+ import { i as __toESM } from "../_runtime.mjs";
2
+ import { K as require_jsx_runtime, a as MenuPortal, i as MenuPositioner, n as MenuTrigger, o as MenuPopup, q as require_react, r as MenuRoot, s as MenuItem, t as Separator } from "../_libs/@base-ui/react+[...].mjs";
3
+ import { f as Link, m as useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
4
+ import { d as destinationLabel, l as connectorWorkflow, s as connectorManifest } from "./connections-Cqvb6Gw2.mjs";
5
+ import { n as agentManifest } from "./agents-B_qo1gpL.mjs";
6
+ import { B as ArrowUp, C as Ellipsis, N as CircleAlert, V as ArrowDown, i as Trash2, l as Plus } from "../_libs/lucide-react.mjs";
7
+ import { n as toast } from "../_libs/sonner.mjs";
8
+ import { P as cn, _ as Badge, b as removeLoop, s as Route$11, v as Button$1, w as toggleLoop, x as reorderLoop } from "./router-DdwREiOf.mjs";
9
+ import { t as ConnectorIcon } from "./connector-icon-DrhVbhs6.mjs";
10
+ import { a as List, o as PageHeader, t as Empty } from "./page-DZVxKvdn.mjs";
11
+ import { t as Switch$1 } from "./switch-odxNO_S4.mjs";
12
+ import { n as AlertDescription, r as AlertTitle, t as Alert } from "./alert-C2WBWfzo.mjs";
13
+ import { n as useLiveRefresh } from "./use-live-tasks-BzhTkEOL.mjs";
14
+ import { n as issuesFor, t as gapsFor } from "./gaps-CcvHiLcj.mjs";
15
+ //#region node_modules/.nitro/vite/services/ssr/assets/loops-DHPjzG-t.js
16
+ var import_react = /* @__PURE__ */ __toESM(require_react());
17
+ var import_jsx_runtime = require_jsx_runtime();
18
+ function DropdownMenu({ ...props }) {
19
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRoot, {
20
+ "data-slot": "dropdown-menu",
21
+ ...props
22
+ });
23
+ }
24
+ function DropdownMenuTrigger({ ...props }) {
25
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuTrigger, {
26
+ "data-slot": "dropdown-menu-trigger",
27
+ ...props
28
+ });
29
+ }
30
+ function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4, className, ...props }) {
31
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPositioner, {
32
+ className: "isolate z-50 outline-none",
33
+ align,
34
+ alignOffset,
35
+ side,
36
+ sideOffset,
37
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPopup, {
38
+ "data-slot": "dropdown-menu-content",
39
+ className: cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className),
40
+ ...props
41
+ })
42
+ }) });
43
+ }
44
+ function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
45
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
46
+ "data-slot": "dropdown-menu-item",
47
+ "data-inset": inset,
48
+ "data-variant": variant,
49
+ className: cn("group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive", className),
50
+ ...props
51
+ });
52
+ }
53
+ function DropdownMenuSeparator({ className, ...props }) {
54
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator, {
55
+ "data-slot": "dropdown-menu-separator",
56
+ className: cn("-mx-1 my-1 h-px bg-border", className),
57
+ ...props
58
+ });
59
+ }
60
+ function LoopsPage() {
61
+ const { loops, readiness } = Route$11.useLoaderData();
62
+ useLiveRefresh();
63
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
64
+ className: "flex flex-col gap-6",
65
+ children: [
66
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageHeader, {
67
+ title: "Loops",
68
+ description: "When two loops match the same thing, the higher one runs.",
69
+ actions: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
70
+ nativeButton: false,
71
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/loops/new" }),
72
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {}), "New loop"]
73
+ })
74
+ }),
75
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Gaps, {
76
+ readiness,
77
+ loops
78
+ }),
79
+ loops.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
80
+ action: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
81
+ variant: "outline",
82
+ nativeButton: false,
83
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/loops/new" }),
84
+ children: "New loop"
85
+ }),
86
+ children: "No loops yet."
87
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { children: loops.map((loop, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopRow, {
88
+ loop,
89
+ readiness,
90
+ position: index + 1,
91
+ first: index === 0,
92
+ last: index === loops.length - 1
93
+ }, loop.id)) })
94
+ ]
95
+ });
96
+ }
97
+ /** What still stands between these loops and running. */
98
+ function Gaps({ readiness, loops }) {
99
+ const missing = gapsFor(readiness, loops);
100
+ if (missing.length === 0) return null;
101
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, { children: [
102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleAlert, {}),
103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Nothing will run yet" }),
104
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, {
105
+ className: "flex flex-col gap-0.5",
106
+ children: missing.map((line) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: line }, line))
107
+ })
108
+ ] });
109
+ }
110
+ /** Where this loop answers and with what, in a few words. */
111
+ function summaryOf(loop) {
112
+ if (!connectorWorkflow(loop.connectorId, loop.workflowId)) return "Workflow no longer offered";
113
+ const agent = loop.agentId ? agentManifest(loop.agentId) : void 0;
114
+ return [destinationLabel(loop.actionConnectorId, loop.actionId), agent?.name ?? "Default agent"].join(" · ");
115
+ }
116
+ function LoopRow({ loop, readiness, position, first, last }) {
117
+ const router = useRouter();
118
+ const manifest = connectorManifest(loop.connectorId);
119
+ const issues = issuesFor(readiness, loop);
120
+ const [busy, setBusy] = (0, import_react.useState)(false);
121
+ const run = async (work, message) => {
122
+ setBusy(true);
123
+ try {
124
+ await work();
125
+ await router.invalidate();
126
+ if (message) toast.success(message);
127
+ } catch (error) {
128
+ toast.error(error instanceof Error ? error.message : String(error));
129
+ } finally {
130
+ setBusy(false);
131
+ }
132
+ };
133
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
134
+ className: "flex items-center gap-3 px-4 py-3",
135
+ children: [
136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
137
+ className: "w-4 shrink-0 text-center text-xs text-muted-foreground tabular-nums",
138
+ children: position
139
+ }),
140
+ manifest ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
141
+ icon: manifest.icon,
142
+ accent: manifest.accent,
143
+ className: loop.enabled ? "size-8" : "size-8 opacity-50"
144
+ }) : null,
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
146
+ to: "/loops/$loopId",
147
+ params: { loopId: loop.id },
148
+ className: "group min-w-0 flex-1",
149
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
150
+ className: "flex items-center gap-2",
151
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
152
+ className: "truncate text-sm font-medium group-hover:underline",
153
+ children: loop.name
154
+ }), loop.enabled ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
155
+ variant: "secondary",
156
+ children: "Off"
157
+ })]
158
+ }), issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
159
+ className: "mt-0.5 flex items-center gap-1 truncate text-xs text-amber-700 dark:text-amber-500",
160
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleAlert, { className: "size-3 shrink-0" }), issues[0]]
161
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
162
+ className: "mt-0.5 truncate text-xs text-muted-foreground",
163
+ children: summaryOf(loop)
164
+ })]
165
+ }),
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
167
+ checked: loop.enabled,
168
+ "aria-label": loop.enabled ? "Turn off" : "Turn on",
169
+ disabled: busy,
170
+ onCheckedChange: (enabled) => run(() => toggleLoop({ data: {
171
+ id: loop.id,
172
+ enabled
173
+ } }), enabled ? `${loop.name} is on` : `${loop.name} is off`)
174
+ }),
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuTrigger, {
176
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
177
+ variant: "ghost",
178
+ size: "icon-sm",
179
+ "aria-label": "More",
180
+ disabled: busy
181
+ }),
182
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ellipsis, {})
183
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuContent, {
184
+ align: "end",
185
+ children: [
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
187
+ disabled: first,
188
+ onClick: () => run(() => reorderLoop({ data: {
189
+ id: loop.id,
190
+ direction: "up"
191
+ } })),
192
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowUp, {}), "Move up"]
193
+ }),
194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
195
+ disabled: last,
196
+ onClick: () => run(() => reorderLoop({ data: {
197
+ id: loop.id,
198
+ direction: "down"
199
+ } })),
200
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowDown, {}), "Move down"]
201
+ }),
202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuSeparator, {}),
203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
204
+ variant: "destructive",
205
+ onClick: () => {
206
+ if (!window.confirm(`Delete ${loop.name}? Its runs are deleted with it.`)) return;
207
+ run(() => removeLoop({ data: { id: loop.id } }), `Deleted ${loop.name}`);
208
+ },
209
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, {}), "Delete"]
210
+ })
211
+ ]
212
+ })] })
213
+ ]
214
+ });
215
+ }
216
+ //#endregion
217
+ export { LoopsPage as component };
@@ -0,0 +1,245 @@
1
+ import { K as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
+ import { f as Link, p as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
3
+ import { l as connectorWorkflow, n as allTriggers, r as allWorkflows, s as connectorManifest } from "./connections-Cqvb6Gw2.mjs";
4
+ import { f as PencilLine } from "../_libs/lucide-react.mjs";
5
+ import { a as Route$9 } from "./router-DdwREiOf.mjs";
6
+ import { t as ConnectorIcon } from "./connector-icon-DrhVbhs6.mjs";
7
+ import { n as Field, o as PageHeader } from "./page-DZVxKvdn.mjs";
8
+ import { a as SelectValue, i as SelectTrigger, n as SelectContent, r as SelectItem, t as Select$1 } from "./select-BtndHhHb.mjs";
9
+ import { t as LoopForm } from "./loop-form-DWwOR05R.mjs";
10
+ //#region node_modules/.nitro/vite/services/ssr/assets/new-ChKBdcXJ.js
11
+ var import_jsx_runtime = require_jsx_runtime();
12
+ /**
13
+ * One of a connector's workflows, as a loop nobody has saved yet.
14
+ *
15
+ * Nothing here touches the database, which is the point: choosing a workflow
16
+ * used to create the loop, so a look around the page left a live loop behind,
17
+ * enabled and with none of its questions answered. Now choosing only fills a
18
+ * form in, and the loop begins at Save.
19
+ *
20
+ * Deliberately client-safe, so the form can be filled in without a round trip
21
+ * and without anything on the server having to remember a half-made loop.
22
+ */
23
+ function draftForWorkflow(connectorId, workflowId) {
24
+ const workflow = connectorWorkflow(connectorId, workflowId);
25
+ if (!workflow) throw new Error(`Unknown workflow: ${workflowId}`);
26
+ return {
27
+ id: null,
28
+ name: workflow.name,
29
+ connectorId,
30
+ workflowId,
31
+ prompt: workflow.prompt,
32
+ guidance: null,
33
+ agentId: null,
34
+ settings: {},
35
+ actionConnectorId: workflow.actionConnectorId ?? connectorId,
36
+ actionId: workflow.actionId,
37
+ actionTarget: workflow.actionTarget ?? {},
38
+ pollEveryMs: workflow.pollEveryMs ?? null,
39
+ enabled: true
40
+ };
41
+ }
42
+ /** The trigger a custom loop starts on before anything is picked. */
43
+ var CUSTOM_DEFAULT = {
44
+ connector: "slack",
45
+ workflow: "slack.ask"
46
+ };
47
+ function NewLoopPage() {
48
+ const { connector, workflow, custom } = Route$9.useSearch();
49
+ const chosen = connector && workflow && connectorWorkflow(connector, workflow) ? {
50
+ connector,
51
+ workflow
52
+ } : null;
53
+ if (custom) {
54
+ const start = chosen ?? CUSTOM_DEFAULT;
55
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CustomLoop, {
56
+ connectorId: start.connector,
57
+ workflowId: start.workflow
58
+ });
59
+ }
60
+ return chosen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShapeLoop, {
61
+ connectorId: chosen.connector,
62
+ workflowId: chosen.workflow
63
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChooseWorkflow, {});
64
+ }
65
+ /**
66
+ * The second step, and the one that makes a loop. Nothing is written down
67
+ * until the form is saved.
68
+ */
69
+ function ShapeLoop({ connectorId, workflowId }) {
70
+ const workflow = connectorWorkflow(connectorId, workflowId);
71
+ const connector = connectorManifest(connectorId);
72
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73
+ className: "flex flex-col gap-8",
74
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageHeader, {
75
+ back: {
76
+ to: "/loops/new",
77
+ label: "New loop"
78
+ },
79
+ icon: connector ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
80
+ icon: connector.icon,
81
+ accent: connector.accent
82
+ }) : null,
83
+ title: workflow.name,
84
+ description: workflow.summary
85
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopForm, { loop: draftForWorkflow(connectorId, workflowId) })]
86
+ });
87
+ }
88
+ /**
89
+ * A loop with your own prompt. Underneath it is still one of the workflows,
90
+ * since a workflow is what knows how to watch for something and read the
91
+ * answer, but it starts with an empty prompt and a trigger you pick.
92
+ */
93
+ function CustomLoop({ connectorId, workflowId }) {
94
+ const navigate = useNavigate();
95
+ const draft = {
96
+ ...draftForWorkflow(connectorId, workflowId),
97
+ name: "Custom loop",
98
+ prompt: ""
99
+ };
100
+ const picker = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, {
101
+ label: "Starts when",
102
+ htmlFor: "custom-trigger",
103
+ className: "sm:max-w-sm",
104
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
105
+ value: `${connectorId}::${workflowId}`,
106
+ onValueChange: (value) => {
107
+ if (!value) return;
108
+ const [connector, workflow] = value.split("::");
109
+ navigate({
110
+ to: "/loops/new",
111
+ search: {
112
+ custom: true,
113
+ connector,
114
+ workflow
115
+ },
116
+ replace: true
117
+ });
118
+ },
119
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
120
+ id: "custom-trigger",
121
+ className: "w-full",
122
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { children: () => {
123
+ return connectorWorkflow(connectorId, workflowId)?.when ?? workflowId;
124
+ } })
125
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: allTriggers().map(({ connector, trigger }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
126
+ value: `${connector.id}::${trigger.id}`,
127
+ children: trigger.when
128
+ }, `${connector.id}::${trigger.id}`)) })]
129
+ })
130
+ });
131
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
132
+ className: "flex flex-col gap-8",
133
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageHeader, {
134
+ back: {
135
+ to: "/loops/new",
136
+ label: "New loop"
137
+ },
138
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CustomIcon, {}),
139
+ title: "Custom loop",
140
+ description: "Pick what starts it, then say what the agent should do."
141
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopForm, {
142
+ loop: draft,
143
+ trigger: picker
144
+ }, workflowId)]
145
+ });
146
+ }
147
+ function CustomIcon() {
148
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
149
+ className: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground",
150
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PencilLine, { className: "size-5" })
151
+ });
152
+ }
153
+ /** How many loops already use a workflow, said as a sentence rather than a count. */
154
+ function inUse(loops, workflowId) {
155
+ const count = loops.filter((loop) => loop.workflowId === workflowId).length;
156
+ if (count === 0) return null;
157
+ return count === 1 ? "You have 1 loop like this" : `You have ${count} loops like this`;
158
+ }
159
+ function ChoiceCard({ icon, title, summary, note, ...link }) {
160
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
161
+ to: "/loops/new",
162
+ search: link.search,
163
+ className: "flex items-start gap-3 rounded-xl bg-card p-4 ring-1 ring-foreground/10 transition-colors hover:bg-muted/50",
164
+ children: [icon, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
165
+ className: "min-w-0 flex-1",
166
+ children: [
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
168
+ className: "text-sm font-medium",
169
+ children: title
170
+ }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
172
+ className: "mt-1 text-xs text-muted-foreground",
173
+ children: summary
174
+ }),
175
+ note ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
176
+ className: "mt-2 text-xs text-muted-foreground",
177
+ children: note
178
+ }) : null
179
+ ]
180
+ })]
181
+ });
182
+ }
183
+ function workflowCard(connector, workflow, loops) {
184
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChoiceCard, {
185
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
186
+ icon: connector.icon,
187
+ accent: connector.accent,
188
+ className: "size-8"
189
+ }),
190
+ title: workflow.name,
191
+ summary: workflow.summary,
192
+ note: inUse(loops, workflow.id),
193
+ search: {
194
+ connector: connector.id,
195
+ workflow: workflow.id
196
+ }
197
+ }, workflow.id);
198
+ }
199
+ /**
200
+ * Every loop is one of the workflows a connector offers, or a custom one
201
+ * built on a workflow's trigger. Custom and Schedule come first: they are the
202
+ * two not tied to something arriving from a service.
203
+ */
204
+ function ChooseWorkflow() {
205
+ const { loops } = Route$9.useLoaderData();
206
+ const all = allWorkflows();
207
+ const scheduled = all.filter(({ connector }) => connector.id === "schedule");
208
+ const rest = all.filter(({ connector }) => connector.id !== "schedule");
209
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
210
+ className: "flex flex-col gap-6",
211
+ children: [
212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageHeader, {
213
+ back: {
214
+ to: "/loops",
215
+ label: "Loops"
216
+ },
217
+ title: "New loop"
218
+ }),
219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
220
+ className: "grid gap-3 sm:grid-cols-2",
221
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChoiceCard, {
222
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
223
+ className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted",
224
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PencilLine, { className: "size-4" })
225
+ }),
226
+ title: "Custom loop",
227
+ summary: "Pick a trigger and write your own prompt.",
228
+ search: { custom: true }
229
+ }), scheduled.map(({ connector, workflow }) => workflowCard(connector, workflow, loops))]
230
+ }),
231
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
232
+ className: "flex flex-col gap-3",
233
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
234
+ className: "text-sm font-medium text-muted-foreground",
235
+ children: "From a connector"
236
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
237
+ className: "grid gap-3 sm:grid-cols-2",
238
+ children: rest.map(({ connector, workflow }) => workflowCard(connector, workflow, loops))
239
+ })]
240
+ })
241
+ ]
242
+ });
243
+ }
244
+ //#endregion
245
+ export { NewLoopPage as component };
@@ -0,0 +1,120 @@
1
+ import "../_runtime.mjs";
2
+ import { K as require_jsx_runtime, q as require_react } from "../_libs/@base-ui/react+[...].mjs";
3
+ import { f as Link } from "../_libs/@tanstack/react-router+[...].mjs";
4
+ import { I as ChevronLeft } from "../_libs/lucide-react.mjs";
5
+ import { P as cn } from "./router-DdwREiOf.mjs";
6
+ require_react();
7
+ var import_jsx_runtime = require_jsx_runtime();
8
+ function Label({ className, ...props }) {
9
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
10
+ "data-slot": "label",
11
+ className: cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
12
+ ...props
13
+ });
14
+ }
15
+ /**
16
+ * The top of every page: where it sits, what it is called, and what can be
17
+ * done from here. One shape everywhere, so pages differ in what they show
18
+ * rather than in how far the title is from the edge.
19
+ */
20
+ function PageHeader({ title, description, back, icon, actions, children }) {
21
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
22
+ className: "flex flex-col gap-3",
23
+ children: [back ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
24
+ to: back.to,
25
+ params: back.params,
26
+ search: back.search,
27
+ className: "-ml-1 flex w-fit items-center gap-0.5 text-sm text-muted-foreground hover:text-foreground",
28
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronLeft, { className: "size-4" }), back.label]
29
+ }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
30
+ className: "flex flex-wrap items-start gap-3",
31
+ children: [
32
+ icon,
33
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
34
+ className: "min-w-0 flex-1 basis-56",
35
+ children: [
36
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
37
+ className: "text-xl font-semibold tracking-tight",
38
+ children: title
39
+ }),
40
+ description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
41
+ className: "mt-1 text-sm text-muted-foreground",
42
+ children: description
43
+ }) : null,
44
+ children
45
+ ]
46
+ }),
47
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
48
+ className: "flex shrink-0 items-center gap-2",
49
+ children: actions
50
+ }) : null
51
+ ]
52
+ })]
53
+ });
54
+ }
55
+ /** A labelled control, with at most one short line of help under it. */
56
+ function Field({ label, htmlFor, hint, className, children }) {
57
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
58
+ className: cn("flex flex-col gap-1.5", className),
59
+ children: [
60
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
61
+ htmlFor,
62
+ children: label
63
+ }),
64
+ children,
65
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
66
+ className: "text-xs text-muted-foreground",
67
+ children: hint
68
+ }) : null
69
+ ]
70
+ });
71
+ }
72
+ /**
73
+ * One titled block of a form or a detail page. Title on the left and fields
74
+ * on the right on a wide screen, stacked on a narrow one.
75
+ */
76
+ function Section({ title, description, children, className }) {
77
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
78
+ className: cn("grid gap-4 border-t py-6 first:border-t-0 first:pt-0 md:grid-cols-[11rem_1fr] md:gap-8", className),
79
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
80
+ className: "text-sm font-medium",
81
+ children: title
82
+ }), description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
83
+ className: "mt-1 text-xs text-muted-foreground",
84
+ children: description
85
+ }) : null] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
86
+ className: "flex min-w-0 flex-col gap-4",
87
+ children
88
+ })]
89
+ });
90
+ }
91
+ /** A list of rows inside one bordered box, rather than a stack of cards. */
92
+ function List({ className, children }) {
93
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
94
+ className: cn("divide-y overflow-hidden rounded-xl bg-card ring-1 ring-foreground/10", className),
95
+ children
96
+ });
97
+ }
98
+ /** What a list says when it has nothing in it. */
99
+ function Empty({ children, action }) {
100
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
101
+ className: "flex flex-col items-center gap-3 rounded-xl border border-dashed px-6 py-10 text-center text-sm text-muted-foreground",
102
+ children: [children, action]
103
+ });
104
+ }
105
+ /** Tabs that are links, so a tab can be bookmarked and shared. */
106
+ function LinkTabs({ items }) {
107
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
108
+ className: "flex gap-4 border-b",
109
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
110
+ to: item.to,
111
+ params: item.params,
112
+ search: item.search,
113
+ replace: true,
114
+ className: cn("-mb-px flex items-center gap-1.5 border-b-2 pb-2 text-sm transition-colors", item.active ? "border-foreground font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"),
115
+ children: item.label
116
+ }, index))
117
+ });
118
+ }
119
+ //#endregion
120
+ export { List as a, LinkTabs as i, Field as n, PageHeader as o, Label as r, Section as s, Empty as t };