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
@@ -1,169 +0,0 @@
1
- import { U 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 { c as connectorWorkflow, n as allWorkflows, o as connectorManifest } from "./connections-Df-aDpfJ.mjs";
4
- import { B as ArrowLeft, z as ArrowRight } from "../_libs/lucide-react.mjs";
5
- import { B as Card, R as Badge, V as CardContent, a as Route$9, z as Button$1 } from "./router-54IDWTVz.mjs";
6
- import { t as ConnectorIcon } from "./connector-icon-WvxwZ5TF.mjs";
7
- import { t as LoopForm } from "./loop-form-FqSaXiHl.mjs";
8
- //#region node_modules/.nitro/vite/services/ssr/assets/new-bBsVaWD6.js
9
- var import_jsx_runtime = require_jsx_runtime();
10
- /**
11
- * One of a connector's workflows, as a loop nobody has saved yet.
12
- *
13
- * Nothing here touches the database, which is the point: choosing a workflow
14
- * used to create the loop, so a look around the page left a live loop behind,
15
- * enabled and with none of its questions answered. Now choosing only fills a
16
- * form in, and the loop begins at Save.
17
- *
18
- * Deliberately client-safe, so the form can be filled in without a round trip
19
- * and without anything on the server having to remember a half-made loop.
20
- */
21
- function draftForWorkflow(connectorId, workflowId) {
22
- const workflow = connectorWorkflow(connectorId, workflowId);
23
- if (!workflow) throw new Error(`Unknown workflow: ${workflowId}`);
24
- return {
25
- id: null,
26
- name: workflow.name,
27
- connectorId,
28
- workflowId,
29
- prompt: workflow.prompt,
30
- guidance: null,
31
- agentId: null,
32
- settings: {},
33
- actionConnectorId: workflow.actionConnectorId ?? connectorId,
34
- actionId: workflow.actionId,
35
- actionTarget: workflow.actionTarget ?? {},
36
- pollEveryMs: workflow.pollEveryMs ?? null,
37
- enabled: true
38
- };
39
- }
40
- function NewLoopPage() {
41
- const { connector, workflow } = Route$9.useSearch();
42
- const chosen = connector && workflow && connectorWorkflow(connector, workflow) ? {
43
- connector,
44
- workflow
45
- } : null;
46
- return chosen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShapeLoop, {
47
- connectorId: chosen.connector,
48
- workflowId: chosen.workflow
49
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChooseWorkflow, {});
50
- }
51
- /**
52
- * The second step, and the one that makes a loop.
53
- *
54
- * Nothing has been written down yet: the draft is worked out here from the
55
- * workflow the URL names, and the loop begins when the form is saved. Picking
56
- * a workflow used to create it, which meant a look around this page left a
57
- * live loop behind, enabled and with none of its questions answered.
58
- */
59
- function ShapeLoop({ connectorId, workflowId }) {
60
- const workflow = connectorWorkflow(connectorId, workflowId);
61
- const connector = connectorManifest(connectorId);
62
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
63
- className: "flex flex-col gap-6",
64
- children: [
65
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
66
- to: "/loops/new",
67
- className: "flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground",
68
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, { className: "size-4" }), "New loop"]
69
- }),
70
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
71
- className: "flex items-start gap-4",
72
- children: [connector ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
73
- icon: connector.icon,
74
- accent: connector.accent
75
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
76
- className: "text-2xl font-semibold tracking-tight",
77
- children: workflow.name
78
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
79
- className: "mt-1 text-sm text-muted-foreground",
80
- children: ["Nothing is watched for until you add this. ", workflow.summary]
81
- })] })]
82
- }),
83
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopForm, { loop: draftForWorkflow(connectorId, workflowId) })
84
- ]
85
- });
86
- }
87
- /**
88
- * There is no blank loop to write any more. Every loop is one of the
89
- * workflows a connector offers, already knowing its own job, so choosing is
90
- * the first half of making one and the settings are the second.
91
- */
92
- function ChooseWorkflow() {
93
- const { loops } = Route$9.useLoaderData();
94
- const navigate = useNavigate();
95
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
96
- className: "flex flex-col gap-6",
97
- children: [
98
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
99
- to: "/loops",
100
- className: "flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground",
101
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowLeft, { className: "size-4" }), "Loops"]
102
- }),
103
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
104
- className: "text-2xl font-semibold tracking-tight",
105
- children: "New loop"
106
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
107
- className: "mt-1 text-sm text-muted-foreground",
108
- children: "Pick what you want done. You can narrow it down to certain repositories, and add anything particular to your team, on the next screen."
109
- })] }),
110
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
111
- className: "flex flex-col gap-3",
112
- children: allWorkflows().map(({ connector, workflow }) => {
113
- const existing = loops.filter((loop) => loop.workflowId === workflow.id).length;
114
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
115
- className: "flex items-start gap-4 pt-6",
116
- children: [
117
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
118
- icon: connector.icon,
119
- accent: connector.accent
120
- }),
121
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
122
- className: "flex min-w-0 flex-1 flex-col gap-1",
123
- children: [
124
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
125
- className: "flex flex-wrap items-center gap-2",
126
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
127
- className: "font-medium",
128
- children: workflow.name
129
- }), existing > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
130
- variant: "secondary",
131
- children: existing === 1 ? "Already have one" : `Already have ${existing}`
132
- }) : null]
133
- }),
134
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
135
- className: "text-sm text-muted-foreground",
136
- children: workflow.summary
137
- }),
138
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
139
- className: "mt-1 flex items-start gap-1.5 text-xs text-muted-foreground",
140
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, { className: "mt-0.5 size-3.5 shrink-0" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
141
- "When ",
142
- workflow.trigger,
143
- ", it writes ",
144
- workflow.writes,
145
- "."
146
- ] })]
147
- })
148
- ]
149
- }),
150
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
151
- variant: "outline",
152
- onClick: () => void navigate({
153
- to: "/loops/new",
154
- search: {
155
- connector: connector.id,
156
- workflow: workflow.id
157
- }
158
- }),
159
- children: "Set up"
160
- })
161
- ]
162
- }) }, workflow.id);
163
- })
164
- })
165
- ]
166
- });
167
- }
168
- //#endregion
169
- export { NewLoopPage as component };
@@ -1,106 +0,0 @@
1
- import { f as eq } from "../_libs/drizzle-orm.mjs";
2
- import { f as runners, g as tasks, s as db } from "./client-BtVRVhQ2.mjs";
3
- import { a as runnerTokenKey, o as secretStore, r as joinTokenKey } from "./secrets-CRnpwGBh.mjs";
4
- import { randomBytes, randomUUID } from "node:crypto";
5
- import { hostname } from "node:os";
6
- //#region node_modules/.nitro/vite/services/ssr/assets/runners-D2UAdLyj.js
7
- var ONLINE_MS = 45e3;
8
- function isRunnerOnline(row, now = Date.now()) {
9
- if (row.status !== "online") return false;
10
- if (!row.lastSeenAt) return false;
11
- return now - row.lastSeenAt.getTime() < ONLINE_MS;
12
- }
13
- function toView(row, now = Date.now()) {
14
- const online = isRunnerOnline(row, now);
15
- return {
16
- id: row.id,
17
- name: row.name,
18
- hostname: row.hostname,
19
- status: online ? "online" : "offline",
20
- inventory: row.inventory,
21
- lastSeenAt: row.lastSeenAt?.toISOString() ?? null
22
- };
23
- }
24
- async function listRunners() {
25
- await db().delete(runners).where(eq(runners.id, "host")).run();
26
- const now = Date.now();
27
- return (await db().select().from(runners).all()).map((row) => toView(row, now));
28
- }
29
- async function getRunner(id) {
30
- return await db().select().from(runners).where(eq(runners.id, id)).get();
31
- }
32
- async function onlineRunnerCount() {
33
- return (await listRunners()).filter((row) => row.status === "online").length;
34
- }
35
- /** Agent ids signed in on an online runner. */
36
- async function availableAgentIds(requiresHost = false) {
37
- const local = hostname();
38
- return [...new Set((await listRunners()).filter((row) => row.status === "online" && (!requiresHost || row.hostname === local)).flatMap((row) => row.inventory.filter((entry) => entry.installed && entry.signedIn).map((entry) => entry.agentId)))];
39
- }
40
- async function getJoinToken() {
41
- const existing = await secretStore().get(joinTokenKey());
42
- if (existing) return existing;
43
- const token = randomBytes(24).toString("base64url");
44
- await secretStore().set(joinTokenKey(), token);
45
- return token;
46
- }
47
- async function rotateJoinToken() {
48
- const token = randomBytes(24).toString("base64url");
49
- await secretStore().set(joinTokenKey(), token);
50
- return token;
51
- }
52
- async function heartbeatRunner(runnerId, inventory) {
53
- if (!await getRunner(runnerId)) throw new Error("Unknown runner.");
54
- await db().update(runners).set({
55
- status: "online",
56
- inventory,
57
- lastSeenAt: /* @__PURE__ */ new Date()
58
- }).where(eq(runners.id, runnerId)).run();
59
- return toView(await getRunner(runnerId));
60
- }
61
- async function joinRunner(input) {
62
- const expected = await getJoinToken();
63
- if (!input.joinToken || input.joinToken !== expected) throw new Error("That join token is not valid.");
64
- const existing = (await db().select().from(runners).all()).find((row) => row.hostname === input.hostname);
65
- const id = existing?.id ?? randomUUID();
66
- const name = existing?.name ?? input.hostname;
67
- const now = /* @__PURE__ */ new Date();
68
- if (existing) await db().update(runners).set({
69
- status: "online",
70
- inventory: input.inventory,
71
- lastSeenAt: now,
72
- hostname: input.hostname
73
- }).where(eq(runners.id, id)).run();
74
- else await db().insert(runners).values({
75
- id,
76
- name,
77
- hostname: input.hostname,
78
- status: "online",
79
- inventory: input.inventory,
80
- lastSeenAt: now
81
- }).run();
82
- const token = randomBytes(24).toString("base64url");
83
- await secretStore().set(runnerTokenKey(id), token);
84
- return {
85
- runnerId: id,
86
- runnerToken: token,
87
- name
88
- };
89
- }
90
- async function runnerIdForToken(token) {
91
- if (!token) return null;
92
- for (const row of await db().select().from(runners).all()) {
93
- const stored = await secretStore().get(runnerTokenKey(row.id)) ?? await secretStore().get(`runners.machine.${row.id}`);
94
- if (stored && stored === token) return row.id;
95
- }
96
- return null;
97
- }
98
- async function forgetRunner(id) {
99
- if (!await getRunner(id)) throw new Error("Unknown runner.");
100
- if ((await db().select().from(tasks).all()).some((task) => task.runnerId === id && task.state === "awaiting_agent")) throw new Error("This runner still has a run in flight.");
101
- await db().delete(runners).where(eq(runners.id, id)).run();
102
- await secretStore().delete(runnerTokenKey(id));
103
- await secretStore().delete(`runners.machine.${id}`);
104
- }
105
- //#endregion
106
- export { heartbeatRunner as a, listRunners as c, runnerIdForToken as d, getRunner as i, onlineRunnerCount as l, forgetRunner as n, isRunnerOnline as o, getJoinToken as r, joinRunner as s, availableAgentIds as t, rotateJoinToken as u };
@@ -1,116 +0,0 @@
1
- import { U as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
- import { m as useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
3
- import { s as Server } from "../_libs/lucide-react.mjs";
4
- import { n as toast } from "../_libs/sonner.mjs";
5
- import { B as Card, H as CardHeader, R as Badge, V as CardContent, f as useLiveRefresh, i as rotateRunnerJoinToken, n as Route$7, r as removeRunner, z as Button$1 } from "./router-54IDWTVz.mjs";
6
- import { n as AlertDescription, t as Alert } from "./alert-DQvzE7bu.mjs";
7
- //#region node_modules/.nitro/vite/services/ssr/assets/runners-DGWaVvWi.js
8
- var import_jsx_runtime = require_jsx_runtime();
9
- function inventoryLines(runner) {
10
- if (runner.inventory.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
11
- className: "mt-2 text-sm text-muted-foreground",
12
- children: "No coding agents found yet."
13
- });
14
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
15
- className: "mt-2 text-sm text-muted-foreground",
16
- children: runner.inventory.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [entry.agentId, entry.signedIn ? " · signed in" : entry.installed ? " · not signed in" : " · missing"] }, entry.agentId))
17
- });
18
- }
19
- function RunnersPage() {
20
- const { runners, joinToken, origin } = Route$7.useLoaderData();
21
- const router = useRouter();
22
- useLiveRefresh();
23
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
24
- className: "flex flex-col gap-8",
25
- children: [
26
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
27
- className: "text-2xl font-semibold tracking-tight",
28
- children: "Runners"
29
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
30
- className: "mt-1 text-sm text-muted-foreground",
31
- children: "A runner is a process that runs agents. Join one on every host that should do work."
32
- })] }),
33
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, {
34
- className: "text-base font-medium",
35
- children: "Join a runner"
36
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
37
- className: "flex flex-col gap-3 text-sm",
38
- children: [
39
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
40
- className: "text-muted-foreground",
41
- children: [
42
- "Install the CLI once (",
43
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
44
- className: "text-xs",
45
- children: "npm install -g loopable-cli"
46
- }),
47
- "), sign in a coding agent on that host, then start a runner. Use this LAN IP so other machines can reach the App."
48
- ]
49
- }),
50
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
51
- className: "overflow-x-auto rounded-md bg-muted p-3 text-xs",
52
- children: `loopable runner --url ${origin} --token ${joinToken}`
53
- }),
54
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
55
- variant: "outline",
56
- size: "sm",
57
- onClick: async () => {
58
- try {
59
- await rotateRunnerJoinToken();
60
- await router.invalidate();
61
- toast.success("Join token rotated. Existing runners keep working.");
62
- } catch (error) {
63
- toast.error(error instanceof Error ? error.message : String(error));
64
- }
65
- },
66
- children: "Rotate join token"
67
- }) })
68
- ]
69
- })] }),
70
- runners.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: "No runner has joined yet. Loops will wait until one is online." }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
71
- className: "flex flex-col gap-3",
72
- children: runners.map((runner) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
73
- className: "flex items-start gap-4 pt-6",
74
- children: [
75
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Server, { className: "mt-0.5 size-5 shrink-0 text-muted-foreground" }),
76
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
77
- className: "min-w-0 flex-1",
78
- children: [
79
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
80
- className: "flex flex-wrap items-center gap-2",
81
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
82
- className: "font-medium",
83
- children: runner.name
84
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
85
- variant: runner.status === "online" ? "default" : "outline",
86
- children: runner.status
87
- })]
88
- }),
89
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
90
- className: "mt-1 text-xs text-muted-foreground",
91
- children: runner.hostname
92
- }),
93
- inventoryLines(runner)
94
- ]
95
- }),
96
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
97
- variant: "ghost",
98
- size: "sm",
99
- onClick: async () => {
100
- try {
101
- await removeRunner({ data: { id: runner.id } });
102
- await router.invalidate();
103
- } catch (error) {
104
- toast.error(error instanceof Error ? error.message : String(error));
105
- }
106
- },
107
- children: "Forget"
108
- })
109
- ]
110
- }) }, runner.id))
111
- })
112
- ]
113
- });
114
- }
115
- //#endregion
116
- export { RunnersPage as component };
@@ -1,187 +0,0 @@
1
- import { U as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
- import { f as Link } from "../_libs/@tanstack/react-router+[...].mjs";
3
- import { o as connectorManifest } from "./connections-Df-aDpfJ.mjs";
4
- import { n as agentManifest } from "./agents-DG643yoJ.mjs";
5
- import { A as CircleMinus, D as CircleSlash, I as Bot, T as CircleX, b as FileText, d as Plug, g as Loader, j as CircleCheck, n as Workflow, w as Clock } from "../_libs/lucide-react.mjs";
6
- import { B as Card, R as Badge, V as CardContent, W as cn, p as useLiveTasks } from "./router-54IDWTVz.mjs";
7
- import { t as ConnectorIcon } from "./connector-icon-WvxwZ5TF.mjs";
8
- //#region node_modules/.nitro/vite/services/ssr/assets/task-list-_qgBS9LU.js
9
- var import_jsx_runtime = require_jsx_runtime();
10
- var STATES = {
11
- queued: {
12
- label: "Queued",
13
- icon: Clock,
14
- tone: "text-muted-foreground"
15
- },
16
- preparing: {
17
- label: "Working",
18
- icon: Loader,
19
- tone: "text-muted-foreground"
20
- },
21
- awaiting_agent: {
22
- label: "On a runner",
23
- icon: Bot,
24
- tone: "text-muted-foreground"
25
- },
26
- applying: {
27
- label: "Writing",
28
- icon: Loader,
29
- tone: "text-muted-foreground"
30
- },
31
- prepared: {
32
- label: "Prepared",
33
- icon: FileText,
34
- tone: "text-muted-foreground"
35
- },
36
- done: {
37
- label: "Written",
38
- icon: CircleCheck,
39
- tone: "text-emerald-600"
40
- },
41
- skipped: {
42
- label: "Nothing to say",
43
- icon: CircleMinus,
44
- tone: "text-muted-foreground"
45
- },
46
- failed: {
47
- label: "Failed",
48
- icon: CircleX,
49
- tone: "text-destructive"
50
- },
51
- cancelled: {
52
- label: "Stopped",
53
- icon: CircleSlash,
54
- tone: "text-muted-foreground"
55
- }
56
- };
57
- function TaskStateLabel({ state }) {
58
- const meta = STATES[state];
59
- const Icon = meta.icon;
60
- const spinning = state === "preparing" || state === "awaiting_agent" || state === "applying";
61
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
62
- className: cn("flex items-center gap-1.5 text-xs", meta.tone),
63
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: cn("size-3.5", spinning && "animate-spin") }), meta.label]
64
- });
65
- }
66
- /** A queued task has no title yet: the connector has not fetched it. */
67
- function taskTitle(task) {
68
- return task.sourceTitle ?? task.sourceRef;
69
- }
70
- /**
71
- * Kinds whose reference a person can read. A repository and a number say
72
- * where the work is; a mail or a message id is a handle for fetching it
73
- * again and means nothing on a page.
74
- */
75
- var NAMED_REF = /* @__PURE__ */ new Set(["pull_request", "issue"]);
76
- /** What to call the thing a task is about, where a sentence needs a noun. */
77
- var KIND_LABEL = {
78
- pull_request: "Pull request",
79
- issue: "Issue",
80
- message: "Message",
81
- email: "Email",
82
- occurrence: "Scheduled run",
83
- prompt: "Test run"
84
- };
85
- function when(iso) {
86
- const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1e3);
87
- if (seconds < 60) return "just now";
88
- const minutes = Math.round(seconds / 60);
89
- if (minutes < 60) return `${minutes}m ago`;
90
- const hours = Math.round(minutes / 60);
91
- if (hours < 24) return `${hours}h ago`;
92
- return new Date(iso).toLocaleDateString();
93
- }
94
- function TaskList({ tasks, showLoop = true, empty }) {
95
- useLiveTasks(tasks);
96
- if (tasks.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
97
- className: "py-8 text-center text-sm text-muted-foreground",
98
- children: empty
99
- }) });
100
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
101
- className: "flex flex-col gap-2",
102
- children: tasks.map((task) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
103
- to: "/inbox/$taskId",
104
- params: { taskId: task.id },
105
- className: "rounded-lg border bg-card p-4 transition-colors hover:bg-accent/40",
106
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
107
- className: "flex items-start gap-3",
108
- children: [
109
- task.sourceKind === "prompt" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Bot, { className: "mt-0.5 size-7 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
110
- icon: connectorManifest(task.connectorId)?.icon ?? "Plug",
111
- accent: connectorManifest(task.connectorId)?.accent ?? "bg-muted",
112
- className: "mt-0.5 size-7 shrink-0"
113
- }),
114
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
115
- className: "min-w-0 flex-1",
116
- children: [
117
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
118
- className: "truncate text-sm font-medium",
119
- children: taskTitle(task)
120
- }),
121
- NAMED_REF.has(task.sourceKind) && task.sourceTitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
122
- className: "mt-0.5 truncate text-xs text-muted-foreground",
123
- children: task.sourceRef
124
- }) : null,
125
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskMeta, {
126
- task,
127
- showLoop
128
- }),
129
- task.output ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
130
- className: "mt-2 line-clamp-2 text-xs text-muted-foreground",
131
- children: task.output
132
- }) : task.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
133
- className: "mt-2 line-clamp-2 text-xs text-destructive",
134
- children: task.error
135
- }) : null
136
- ]
137
- }),
138
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
139
- className: "flex shrink-0 items-center gap-2",
140
- children: [task.dryRun ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
141
- variant: "outline",
142
- children: "Dry run"
143
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TaskStateLabel, { state: task.state })]
144
- })
145
- ]
146
- })
147
- }, task.id))
148
- });
149
- }
150
- /**
151
- * What ran, and what it ran through. Which agent wrote the words matters when
152
- * two loops disagree, and it is only known once the dispatcher has picked the task
153
- * up, so it is absent rather than wrong while a task is queued.
154
- */
155
- function TaskMeta({ task, showLoop }) {
156
- const connector = connectorManifest(task.connectorId);
157
- const agent = task.agentId ? agentManifest(task.agentId) : void 0;
158
- const parts = [];
159
- if (showLoop && task.loopName) parts.push({
160
- icon: Workflow,
161
- label: task.loopName
162
- });
163
- if (connector) parts.push({
164
- icon: Plug,
165
- label: connector.name
166
- });
167
- if (agent) parts.push({
168
- icon: Bot,
169
- label: agent.name
170
- });
171
- parts.push({
172
- icon: Clock,
173
- label: when(task.createdAt)
174
- });
175
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
176
- className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
177
- children: parts.map((part) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
178
- className: "flex min-w-0 items-center gap-1",
179
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(part.icon, { className: "size-3 shrink-0" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
180
- className: "truncate",
181
- children: part.label
182
- })]
183
- }, part.label))
184
- });
185
- }
186
- //#endregion
187
- export { taskTitle as i, TaskList as n, TaskStateLabel as r, KIND_LABEL as t };