loopable-cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) 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-BzSY7M8I.js +1 -0
  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-COuQN02a.mjs +512 -0
  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/{callback-D20cVD2w.mjs → access-DTttr563.mjs} +37 -20
  46. package/.output/server/_ssr/{agents-CrUmIQpi.mjs → agents-B_qo1gpL.mjs} +196 -7
  47. package/.output/server/_ssr/{agents-Dms_6TBF.mjs → agents-Bz10gz_m.mjs} +8 -8
  48. package/.output/server/_ssr/agents-CjbOUp3Z.mjs +206 -0
  49. package/.output/server/_ssr/{alert-DQvzE7bu.mjs → alert-C2WBWfzo.mjs} +2 -2
  50. package/.output/server/_ssr/card-r1cV-PCN.mjs +36 -0
  51. package/.output/server/_ssr/{client-BtVRVhQ2.mjs → client-Dk3SD8Il.mjs} +5 -1
  52. package/.output/server/_ssr/connection-status-BogAL-Fj.mjs +32 -0
  53. package/.output/server/_ssr/{connections-ypex3qUd.mjs → connections-Cqvb6Gw2.mjs} +1109 -263
  54. package/.output/server/_ssr/connector-icon-DrhVbhs6.mjs +190 -0
  55. package/.output/server/_ssr/connectors-BfS5tQKr.mjs +81 -0
  56. package/.output/server/_ssr/{connectors-BZly0yGF.mjs → connectors-DJI-rzPU.mjs} +21 -10
  57. package/.output/server/_ssr/{createServerRpc-DPDrnFdH.mjs → createServerRpc-DDrp2nLT.mjs} +2 -2
  58. package/.output/server/_ssr/{gaps-D2Zh8sEo.mjs → gaps-CcvHiLcj.mjs} +18 -10
  59. package/.output/server/_ssr/inbox-D1ef761v.mjs +178 -0
  60. package/.output/server/_ssr/loop-form-DWwOR05R.mjs +346 -0
  61. package/.output/server/_ssr/{loops-Og_G3KyZ.mjs → loops-6WhUe_lc.mjs} +228 -25
  62. package/.output/server/_ssr/loops-DHPjzG-t.mjs +217 -0
  63. package/.output/server/_ssr/new-ChKBdcXJ.mjs +245 -0
  64. package/.output/server/_ssr/page-DZVxKvdn.mjs +120 -0
  65. package/.output/server/_ssr/{router-BbrBz36P.mjs → router-DdwREiOf.mjs} +167 -489
  66. package/.output/server/_ssr/runners-CK9Cx9fS.mjs +217 -0
  67. package/.output/server/_ssr/runners-CrznFPHt.mjs +140 -0
  68. package/.output/server/_ssr/runners-W-pgo5yo.mjs +55 -0
  69. package/.output/server/_ssr/{secrets-CRnpwGBh.mjs → secrets-CV5scFlt.mjs} +2 -2
  70. package/.output/server/_ssr/select-BtndHhHb.mjs +76 -0
  71. package/.output/server/_ssr/{setting-fields-mpXfGftI.mjs → setting-fields-LNgywAW7.mjs} +25 -20
  72. package/.output/server/_ssr/ssr.mjs +49 -40
  73. package/.output/server/_ssr/{start-BgTj-h_C.mjs → start-C_j-e4vF.mjs} +10 -6
  74. package/.output/server/_ssr/{switch-1pnd_bPJ.mjs → switch-odxNO_S4.mjs} +3 -3
  75. package/.output/server/_ssr/task-list-Dnmq_WeE.mjs +150 -0
  76. package/.output/server/_ssr/{tasks-LIi-W4cy.mjs → tasks-D-kXLgVP.mjs} +13 -13
  77. package/.output/server/_ssr/{tasks-B_QmDFVs.mjs → tasks-D7aUvA33.mjs} +55 -17
  78. package/.output/server/_ssr/{textarea-CHJTKtsJ.mjs → textarea-D2c_vTIC.mjs} +2 -2
  79. package/.output/server/_ssr/time-Cy8TZP0Z.mjs +13 -0
  80. package/.output/server/_ssr/use-live-tasks-BzhTkEOL.mjs +40 -0
  81. package/.output/server/_tanstack-start-manifest_v-2HUdn-2E.mjs +166 -0
  82. package/.output/server/_taskId-D3KkNJW1.mjs +263 -0
  83. package/.output/server/index.mjs +204 -162
  84. package/README.md +54 -19
  85. package/dist/cli.mjs +185 -67
  86. package/dist/dispatcher.mjs +1443 -277
  87. package/dist/runner.mjs +333 -46
  88. package/drizzle/0025_prompt_tasks_done.sql +2 -0
  89. package/drizzle/0026_guidance_into_prompt.sql +3 -0
  90. package/drizzle/0027_relative_folders.sql +5 -0
  91. package/drizzle/0028_runner_version.sql +5 -0
  92. package/drizzle/meta/_journal.json +29 -1
  93. package/package.json +23 -17
  94. package/.output/public/assets/_connectorId-m67WbiJ8.js +0 -1
  95. package/.output/public/assets/_loopId-Bf4K5sdd.js +0 -1
  96. package/.output/public/assets/_taskId-eCKkcVOx.js +0 -1
  97. package/.output/public/assets/alert-BT2EVMJP.js +0 -1
  98. package/.output/public/assets/arrow-left-CWUZt6k2.js +0 -1
  99. package/.output/public/assets/arrow-right-dy_oOg_c.js +0 -1
  100. package/.output/public/assets/connection-status-DCqqmY_6.js +0 -1
  101. package/.output/public/assets/connector-icon-qGpNsPgH.js +0 -1
  102. package/.output/public/assets/connectors-DxXlc9zF.js +0 -1
  103. package/.output/public/assets/external-link-DC11orFA.js +0 -1
  104. package/.output/public/assets/gaps-CoTA-PzA.js +0 -1
  105. package/.output/public/assets/inbox-CLEXUXCI.js +0 -1
  106. package/.output/public/assets/index-Cndsw84p.js +0 -16
  107. package/.output/public/assets/loader-B4Epchm_.js +0 -1
  108. package/.output/public/assets/loop-form-BvizlZdR.js +0 -1
  109. package/.output/public/assets/loops-Dbe_mf5T.js +0 -1
  110. package/.output/public/assets/new-DPEaXKAY.js +0 -1
  111. package/.output/public/assets/play-DD4xQgvi.js +0 -1
  112. package/.output/public/assets/preload-helper-CfdAYKD7.js +0 -3
  113. package/.output/public/assets/rolldown-runtime-CbXtAM7H.js +0 -1
  114. package/.output/public/assets/runners-BVBggS5B.js +0 -1
  115. package/.output/public/assets/select-Cppu-hti.js +0 -1
  116. package/.output/public/assets/setting-fields-BT1naVJX.js +0 -3
  117. package/.output/public/assets/styles-D3sonCfX.css +0 -2
  118. package/.output/public/assets/switch-hRIbNGOj.js +0 -1
  119. package/.output/public/assets/task-list-CrptnI1N.js +0 -1
  120. package/.output/public/assets/textarea-Fyuzi6Vp.js +0 -1
  121. package/.output/public/assets/trash-2-BXZzBsoV.js +0 -1
  122. package/.output/public/assets/useValueChanged-VVyq4hb9.js +0 -1
  123. package/.output/server/_connectorId-DcSnoRjk.mjs +0 -421
  124. package/.output/server/_loopId-G_TAkYtS.mjs +0 -272
  125. package/.output/server/_ssr/access-B7TcOZ7F.mjs +0 -42
  126. package/.output/server/_ssr/connection-status-BZr3Y-IC.mjs +0 -31
  127. package/.output/server/_ssr/connector-icon-CFrrHjzR.mjs +0 -37
  128. package/.output/server/_ssr/connectors-4yzKb0KG.mjs +0 -106
  129. package/.output/server/_ssr/inbox-C2VBGLa5.mjs +0 -114
  130. package/.output/server/_ssr/loop-form-D-U-kU4k.mjs +0 -413
  131. package/.output/server/_ssr/loops-D0XJ8eBb.mjs +0 -222
  132. package/.output/server/_ssr/new-CajvgIog.mjs +0 -169
  133. package/.output/server/_ssr/runners-B_WuF8UJ.mjs +0 -32
  134. package/.output/server/_ssr/runners-D-0_lfiq.mjs +0 -106
  135. package/.output/server/_ssr/runners-zqF_zbEc.mjs +0 -116
  136. package/.output/server/_ssr/task-list-BnQJ94Dk.mjs +0 -187
  137. package/.output/server/_tanstack-start-manifest_v-BCHAazE8.mjs +0 -129
  138. package/.output/server/_taskId-CIv0ajIx.mjs +0 -256
@@ -0,0 +1,512 @@
1
+ import { i as __toESM } from "./_runtime.mjs";
2
+ import { K as require_jsx_runtime, c as AvatarFallback$1, l as AvatarImage$1, q as require_react, u as AvatarRoot } from "./_libs/@base-ui/react+[...].mjs";
3
+ import { f as Link, m as useRouter } from "./_libs/@tanstack/react-router+[...].mjs";
4
+ import { a as connectionNoun, d as destinationLabel, s as connectorManifest } from "./_ssr/connections-Cqvb6Gw2.mjs";
5
+ import { l as registrableCallbackUrl } from "./_ssr/access-DTttr563.mjs";
6
+ import { F as ChevronRight, N as CircleAlert, c as RefreshCw, g as Loader, l as Plus, s as RotateCcw } from "./_libs/lucide-react.mjs";
7
+ import { n as toast } from "./_libs/sonner.mjs";
8
+ import { A as continueQrLogin, M as saveConnectionSettings, N as verifyConnection, O as beginQrLogin, P as cn, _ as Badge, g as Input$1, j as disconnectConnection, k as connectWithToken, u as Route$14, v as Button$1 } from "./_ssr/router-DdwREiOf.mjs";
9
+ import { t as ConnectorIcon } from "./_ssr/connector-icon-DrhVbhs6.mjs";
10
+ import { a as List, o as PageHeader, r as Label, t as Empty } from "./_ssr/page-DZVxKvdn.mjs";
11
+ import { n as initialFieldValues, t as SettingFieldInputs } from "./_ssr/setting-fields-LNgywAW7.mjs";
12
+ import { t as ConnectionStatusBadge } from "./_ssr/connection-status-BogAL-Fj.mjs";
13
+ import { n as AlertDescription, r as AlertTitle, t as Alert } from "./_ssr/alert-C2WBWfzo.mjs";
14
+ import { n as CardContent, t as Card } from "./_ssr/card-r1cV-PCN.mjs";
15
+ import { t as ago } from "./_ssr/time-Cy8TZP0Z.mjs";
16
+ //#region node_modules/.nitro/vite/services/ssr/assets/_connectorId-COuQN02a.js
17
+ var import_react = /* @__PURE__ */ __toESM(require_react());
18
+ var import_jsx_runtime = require_jsx_runtime();
19
+ /** Renders a connector's settings from its manifest, so no page knows the fields. */
20
+ function ConnectionSettingsForm({ fields, connection }) {
21
+ const router = useRouter();
22
+ const [values, setValues] = (0, import_react.useState)(() => initialFieldValues(fields, connection.settings));
23
+ const [saving, setSaving] = (0, import_react.useState)(false);
24
+ if (fields.length === 0) return null;
25
+ const save = async () => {
26
+ setSaving(true);
27
+ try {
28
+ await saveConnectionSettings({ data: {
29
+ id: connection.id,
30
+ settings: values
31
+ } });
32
+ await router.invalidate();
33
+ toast.success("Settings saved");
34
+ } catch (error) {
35
+ toast.error(error instanceof Error ? error.message : String(error));
36
+ } finally {
37
+ setSaving(false);
38
+ }
39
+ };
40
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
41
+ className: "flex flex-col gap-5",
42
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingFieldInputs, {
43
+ fields,
44
+ values,
45
+ onChange: (key, value) => setValues((prev) => ({
46
+ ...prev,
47
+ [key]: value
48
+ }))
49
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
50
+ onClick: save,
51
+ disabled: saving,
52
+ children: saving ? "Saving..." : "Save settings"
53
+ }) })]
54
+ });
55
+ }
56
+ var LOOK_EVERY_MS = 2e3;
57
+ /**
58
+ * A login that finishes on a phone.
59
+ *
60
+ * The waiting is the whole component: a code lasts about two minutes, so it
61
+ * has to say plainly whether it is still good, and offer another the moment it
62
+ * is not. A dead square with no explanation is the failure to avoid.
63
+ */
64
+ function QrConnect({ connectorId, note, onDone }) {
65
+ const router = useRouter();
66
+ const [code, setCode] = (0, import_react.useState)(null);
67
+ const [hint, setHint] = (0, import_react.useState)(null);
68
+ const [expired, setExpired] = (0, import_react.useState)(null);
69
+ const [loading, setLoading] = (0, import_react.useState)(true);
70
+ /** Polling reads this, so a code replaced mid-flight is not polled again. */
71
+ const current = (0, import_react.useRef)(null);
72
+ const fetchCode = async () => {
73
+ setLoading(true);
74
+ setExpired(null);
75
+ setHint(null);
76
+ try {
77
+ const started = await beginQrLogin({ data: { connectorId } });
78
+ current.current = started.attempt;
79
+ setCode({
80
+ image: started.image,
81
+ attempt: started.attempt
82
+ });
83
+ } catch (error) {
84
+ setExpired(error instanceof Error ? error.message : String(error));
85
+ setCode(null);
86
+ } finally {
87
+ setLoading(false);
88
+ }
89
+ };
90
+ (0, import_react.useEffect)(() => {
91
+ fetchCode();
92
+ }, [connectorId]);
93
+ (0, import_react.useEffect)(() => {
94
+ if (!code || expired) return;
95
+ let stop = false;
96
+ const look = async () => {
97
+ while (!stop) {
98
+ await new Promise((resolve) => setTimeout(resolve, LOOK_EVERY_MS));
99
+ if (stop || current.current === null) return;
100
+ try {
101
+ const outcome = await continueQrLogin({ data: {
102
+ connectorId,
103
+ attempt: current.current
104
+ } });
105
+ if (stop) return;
106
+ if (outcome.state === "connected") {
107
+ toast.success(`Connected ${outcome.accountLabel ?? connectorId}`);
108
+ await router.invalidate();
109
+ onDone?.();
110
+ return;
111
+ }
112
+ if (outcome.state === "expired") {
113
+ setExpired(outcome.reason ?? "The code expired before it was scanned.");
114
+ return;
115
+ }
116
+ current.current = outcome.attempt;
117
+ setHint(outcome.hint ?? null);
118
+ } catch (error) {
119
+ if (stop) return;
120
+ setHint(error instanceof Error ? error.message : String(error));
121
+ }
122
+ }
123
+ };
124
+ look();
125
+ return () => {
126
+ stop = true;
127
+ };
128
+ }, [
129
+ code,
130
+ expired,
131
+ connectorId,
132
+ router,
133
+ onDone
134
+ ]);
135
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
136
+ className: "flex flex-col items-center gap-4 py-4",
137
+ children: [
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
139
+ className: "max-w-md text-center text-sm text-muted-foreground",
140
+ children: note
141
+ }),
142
+ expired ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, {
143
+ variant: "destructive",
144
+ className: "max-w-md",
145
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleAlert, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: expired })]
146
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
147
+ className: "flex size-[240px] items-center justify-center rounded-lg border bg-white",
148
+ children: loading || !code ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Loader, { className: "size-5 animate-spin text-muted-foreground" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
149
+ src: code.image,
150
+ alt: "Scan with WeChat to connect",
151
+ className: "size-[240px]"
152
+ })
153
+ }),
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
155
+ className: "text-sm text-muted-foreground",
156
+ children: expired ? "Ask for another and scan it this time." : hint ?? "Waiting for a scan..."
157
+ }),
158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
159
+ variant: expired ? "default" : "outline",
160
+ size: "sm",
161
+ onClick: fetchCode,
162
+ disabled: loading,
163
+ children: expired ? "New code" : "Start over"
164
+ })
165
+ ]
166
+ }) });
167
+ }
168
+ /**
169
+ * A secret someone already has, pasted once.
170
+ *
171
+ * The token reaches this form and is sent to the server; it is never written
172
+ * back. There is no consent screen and no account login.
173
+ */
174
+ function TokenConnect({ connectorId, fields, note, helpUrl, onDone }) {
175
+ const router = useRouter();
176
+ const [values, setValues] = (0, import_react.useState)(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
177
+ const [saving, setSaving] = (0, import_react.useState)(false);
178
+ const save = async () => {
179
+ setSaving(true);
180
+ try {
181
+ const result = await connectWithToken({ data: {
182
+ connectorId,
183
+ fields: values
184
+ } });
185
+ toast.success(`Connected ${result.accountLabel ?? connectorId}`);
186
+ await router.invalidate();
187
+ onDone?.();
188
+ } catch (error) {
189
+ toast.error(error instanceof Error ? error.message : String(error));
190
+ } finally {
191
+ setSaving(false);
192
+ }
193
+ };
194
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
195
+ className: "flex flex-col gap-4 py-2",
196
+ children: [
197
+ note ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
198
+ className: "text-xs text-muted-foreground",
199
+ children: [note, helpUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
200
+ href: helpUrl,
201
+ target: "_blank",
202
+ rel: "noreferrer",
203
+ className: "underline hover:text-foreground",
204
+ children: "Open the app dashboard"
205
+ })] }) : null]
206
+ }) : null,
207
+ fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
208
+ className: "flex flex-col gap-1.5",
209
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
210
+ htmlFor: `token-${field.key}`,
211
+ children: field.label
212
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$1, {
213
+ id: `token-${field.key}`,
214
+ type: field.secret ? "password" : "text",
215
+ autoComplete: "off",
216
+ spellCheck: false,
217
+ placeholder: field.placeholder,
218
+ value: values[field.key] ?? "",
219
+ onChange: (event) => setValues((prev) => ({
220
+ ...prev,
221
+ [field.key]: event.target.value
222
+ }))
223
+ })]
224
+ }, field.key)),
225
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
226
+ onClick: save,
227
+ disabled: saving,
228
+ children: saving ? "Connecting..." : "Connect"
229
+ }) })
230
+ ]
231
+ }) });
232
+ }
233
+ function Avatar$1({ className, size = "default", ...props }) {
234
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarRoot, {
235
+ "data-slot": "avatar",
236
+ "data-size": size,
237
+ className: cn("group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten", className),
238
+ ...props
239
+ });
240
+ }
241
+ function AvatarImage({ className, ...props }) {
242
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarImage$1, {
243
+ "data-slot": "avatar-image",
244
+ className: cn("aspect-square size-full rounded-full object-cover", className),
245
+ ...props
246
+ });
247
+ }
248
+ function AvatarFallback({ className, ...props }) {
249
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarFallback$1, {
250
+ "data-slot": "avatar-fallback",
251
+ className: cn("flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs", className),
252
+ ...props
253
+ });
254
+ }
255
+ function ConnectorDetailPage() {
256
+ const { connectorId } = Route$14.useParams();
257
+ const state = Route$14.useLoaderData();
258
+ const manifest = connectorManifest(connectorId);
259
+ const canAddAccount = manifest.allowsMultipleAccounts || state.connections.length === 0;
260
+ const scans = manifest.auth.kind === "qr_scan";
261
+ const tokens = manifest.auth.kind === "token";
262
+ const [scanning, setScanning] = (0, import_react.useState)(false);
263
+ const [pasting, setPasting] = (0, import_react.useState)(false);
264
+ const connectButton = state.readiness.ready && canAddAccount ? scans ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
265
+ onClick: () => setScanning((open) => !open),
266
+ children: scanning ? "Cancel" : "Connect"
267
+ }) : tokens ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
268
+ onClick: () => setPasting((open) => !open),
269
+ children: pasting ? "Cancel" : "Add bot"
270
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
271
+ nativeButton: false,
272
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: `/api/connectors/${connectorId}/authorize` }),
273
+ children: state.connections.length > 0 ? "Add account" : "Connect"
274
+ }) : null;
275
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
276
+ className: "flex flex-col gap-6",
277
+ children: [
278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageHeader, {
279
+ back: {
280
+ to: "/connectors",
281
+ label: "Connectors"
282
+ },
283
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
284
+ icon: manifest.icon,
285
+ accent: manifest.accent
286
+ }),
287
+ title: manifest.name,
288
+ description: manifest.tagline,
289
+ actions: connectButton
290
+ }),
291
+ !state.readiness.ready ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, {
292
+ variant: "destructive",
293
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Not available in this build" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDescription, {
294
+ className: "flex flex-col gap-1",
295
+ children: [
296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: state.readiness.reason }),
297
+ state.readiness.fixHint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: state.readiness.fixHint }) : null,
298
+ manifest.auth.kind === "oauth_redirect" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
299
+ className: "text-xs",
300
+ children: ["Callback URL: ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: registrableCallbackUrl(connectorId) })]
301
+ }) : null
302
+ ]
303
+ })]
304
+ }) : null,
305
+ scanning && manifest.auth.kind === "qr_scan" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QrConnect, {
306
+ connectorId,
307
+ note: manifest.auth.note,
308
+ onDone: () => setScanning(false)
309
+ }) : null,
310
+ pasting && manifest.auth.kind === "token" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TokenConnect, {
311
+ connectorId,
312
+ fields: manifest.auth.fields,
313
+ note: manifest.auth.note,
314
+ helpUrl: manifest.auth.helpUrl,
315
+ onDone: () => setPasting(false)
316
+ }) : null,
317
+ state.connections.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { children: state.connections.map((connection) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectionRow, {
318
+ connection,
319
+ manifest
320
+ }, connection.id)) }) : state.readiness.ready && !scanning && !pasting ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Empty, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
321
+ "No ",
322
+ connectionNoun(connectorId),
323
+ " connected yet."
324
+ ] }), manifest.auth.kind === "oauth_redirect" && manifest.auth.note ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
325
+ className: "max-w-md text-xs",
326
+ children: manifest.auth.note
327
+ }) : null] }) : null,
328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
329
+ className: "flex flex-col gap-2",
330
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
331
+ className: "flex items-center justify-between gap-3",
332
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
333
+ className: "text-sm font-medium",
334
+ children: "Loops"
335
+ }), manifest.triggers.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
336
+ variant: "ghost",
337
+ size: "sm",
338
+ nativeButton: false,
339
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/loops/new" }),
340
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {}), "New loop"]
341
+ }) : null]
342
+ }), state.loops.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
343
+ className: "text-sm text-muted-foreground",
344
+ children: [
345
+ "No loop uses ",
346
+ manifest.name,
347
+ " yet."
348
+ ]
349
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { children: state.loops.map((loop) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
350
+ to: "/loops/$loopId",
351
+ params: { loopId: loop.id },
352
+ className: "flex items-center gap-3 px-4 py-2.5 transition-colors hover:bg-muted/50",
353
+ children: [
354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
355
+ className: "min-w-0 flex-1",
356
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
357
+ className: "truncate text-sm font-medium",
358
+ children: loop.name
359
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
360
+ className: "truncate text-xs text-muted-foreground",
361
+ children: loop.connectorId === connectorId ? destinationLabel(loop.actionConnectorId, loop.actionId) : `Answers here, from ${connectorManifest(loop.connectorId)?.name ?? loop.connectorId}`
362
+ })]
363
+ }),
364
+ loop.enabled ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
365
+ variant: "secondary",
366
+ children: "Off"
367
+ }),
368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "size-4 text-muted-foreground" })
369
+ ]
370
+ }, loop.id)) })]
371
+ }),
372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
373
+ className: "grid gap-6 sm:grid-cols-3",
374
+ children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CapabilityList, {
376
+ title: "Starts when",
377
+ empty: "Nothing. This connector cannot start a loop.",
378
+ items: manifest.triggers.map((trigger) => ({
379
+ id: trigger.id,
380
+ name: trigger.when,
381
+ summary: `When ${trigger.trigger}.`
382
+ }))
383
+ }),
384
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CapabilityList, {
385
+ title: "Workflows",
386
+ empty: "None. Use a custom loop.",
387
+ items: manifest.workflows.map((workflow) => ({
388
+ id: workflow.id,
389
+ name: workflow.name,
390
+ summary: workflow.summary
391
+ }))
392
+ }),
393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CapabilityList, {
394
+ title: "Send to",
395
+ empty: "Nothing. This connector only reads.",
396
+ items: manifest.actions.map((action) => ({
397
+ id: action.id,
398
+ name: action.name,
399
+ summary: action.summary
400
+ }))
401
+ })
402
+ ]
403
+ })
404
+ ]
405
+ });
406
+ }
407
+ function CapabilityList({ title, empty, items }) {
408
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
409
+ className: "flex flex-col gap-2",
410
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
411
+ className: "text-sm font-medium",
412
+ children: title
413
+ }), items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
414
+ className: "text-sm text-muted-foreground",
415
+ children: empty
416
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
417
+ className: "flex flex-col gap-2",
418
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
419
+ className: "text-sm",
420
+ children: item.name
421
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
422
+ className: "text-xs text-muted-foreground",
423
+ children: item.summary
424
+ })] }, item.id))
425
+ })]
426
+ });
427
+ }
428
+ function ConnectionRow({ connection, manifest }) {
429
+ const router = useRouter();
430
+ const [busy, setBusy] = (0, import_react.useState)(null);
431
+ const broken = connection.status === "needs_reauth" || connection.status === "error";
432
+ const run = async (action) => {
433
+ setBusy(action);
434
+ try {
435
+ if (action === "verify") {
436
+ const updated = await verifyConnection({ data: { id: connection.id } });
437
+ toast[updated.status === "connected" ? "success" : "error"](updated.status === "connected" ? `${updated.accountLabel} is reachable` : updated.lastError ?? "This connection is not usable");
438
+ } else {
439
+ await disconnectConnection({ data: { id: connection.id } });
440
+ toast.success(`Disconnected ${connection.accountLabel}`);
441
+ }
442
+ await router.invalidate();
443
+ } catch (error) {
444
+ toast.error(error instanceof Error ? error.message : String(error));
445
+ } finally {
446
+ setBusy(null);
447
+ }
448
+ };
449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
450
+ className: "flex flex-col gap-4 px-4 py-3",
451
+ children: [
452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
453
+ className: "flex items-center gap-3",
454
+ children: [
455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Avatar$1, {
456
+ className: "size-8",
457
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarImage, {
458
+ src: connection.avatarUrl ?? void 0,
459
+ alt: connection.accountLabel
460
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarFallback, { children: connection.accountLabel.slice(0, 2).toUpperCase() })]
461
+ }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
463
+ className: "min-w-0 flex-1",
464
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
465
+ className: "flex items-center gap-2",
466
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
467
+ className: "truncate text-sm font-medium",
468
+ children: connection.accountLabel
469
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectionStatusBadge, { status: connection.status })]
470
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
471
+ suppressHydrationWarning: true,
472
+ className: "mt-0.5 truncate text-xs text-muted-foreground",
473
+ children: [connection.lastSyncedAt ? `Checked ${ago(connection.lastSyncedAt)}` : "Not checked yet", connection.scopes.length > 0 ? connection.scopes.join(", ") : null].filter(Boolean).join(" · ")
474
+ })]
475
+ }),
476
+ broken ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
477
+ size: "sm",
478
+ nativeButton: false,
479
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: `/api/connectors/${connection.connectorId}/authorize` }),
480
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCcw, {}), "Reconnect"]
481
+ }) : null,
482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
483
+ variant: "ghost",
484
+ size: "sm",
485
+ onClick: () => run("verify"),
486
+ disabled: busy !== null,
487
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCw, { className: busy === "verify" ? "animate-spin" : void 0 }), "Verify"]
488
+ }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
490
+ variant: "ghost",
491
+ size: "sm",
492
+ onClick: () => {
493
+ if (window.confirm(`Disconnect ${connection.accountLabel}?`)) run("disconnect");
494
+ },
495
+ disabled: busy !== null,
496
+ children: "Disconnect"
497
+ })
498
+ ]
499
+ }),
500
+ connection.lastError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
501
+ className: "text-xs text-destructive",
502
+ children: connection.lastError
503
+ }) : null,
504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectionSettingsForm, {
505
+ fields: manifest.settings,
506
+ connection
507
+ })
508
+ ]
509
+ });
510
+ }
511
+ //#endregion
512
+ export { ConnectorDetailPage as component };