loopable-cli 0.1.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 (133) hide show
  1. package/.output/nitro.json +17 -0
  2. package/.output/public/apple-touch-icon.png +0 -0
  3. package/.output/public/assets/_connectorId-BWjYn5tm.js +1 -0
  4. package/.output/public/assets/_loopId-Ds4RkyEc.js +1 -0
  5. package/.output/public/assets/_taskId-BOzICXDf.js +1 -0
  6. package/.output/public/assets/alert-Bn9-Zm0l.js +1 -0
  7. package/.output/public/assets/arrow-left-BF9juV9T.js +1 -0
  8. package/.output/public/assets/arrow-right-CzqvnOY1.js +1 -0
  9. package/.output/public/assets/button-HCY6DCY-.js +1 -0
  10. package/.output/public/assets/connection-status-CYZB8kLy.js +1 -0
  11. package/.output/public/assets/connector-icon-BZ_MUJV_.js +1 -0
  12. package/.output/public/assets/connectors-BvzttfSu.js +1 -0
  13. package/.output/public/assets/connectors-DdJNwRN_.js +1 -0
  14. package/.output/public/assets/external-link-D46X-bXa.js +1 -0
  15. package/.output/public/assets/gaps-D-I6xiDc.js +1 -0
  16. package/.output/public/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  17. package/.output/public/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  18. package/.output/public/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  19. package/.output/public/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  20. package/.output/public/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  21. package/.output/public/assets/inbox-BUUu9xxE.js +1 -0
  22. package/.output/public/assets/index-ztzex0FN.js +10 -0
  23. package/.output/public/assets/loader-CcBl2u8m.js +1 -0
  24. package/.output/public/assets/loop-form-dB3CLcZb.js +1 -0
  25. package/.output/public/assets/loops-DRunQrxL.js +1 -0
  26. package/.output/public/assets/loops-oSdqMd29.js +1 -0
  27. package/.output/public/assets/manifests-DNMqf7Vi.js +7 -0
  28. package/.output/public/assets/new-uyFxVjuB.js +1 -0
  29. package/.output/public/assets/preload-helper-ORbuV86A.js +3 -0
  30. package/.output/public/assets/runners-DSU3r5Ww.js +1 -0
  31. package/.output/public/assets/setting-fields-ixkpdBb-.js +3 -0
  32. package/.output/public/assets/styles-DZL5V1o7.css +2 -0
  33. package/.output/public/assets/switch-d5ummmHY.js +1 -0
  34. package/.output/public/assets/task-list-BpOStt9v.js +1 -0
  35. package/.output/public/assets/tasks-B8MsKgic.js +1 -0
  36. package/.output/public/assets/trash-2-D6u6NpzO.js +1 -0
  37. package/.output/public/favicon.svg +9 -0
  38. package/.output/server/_chunks/ssr-renderer.mjs +26 -0
  39. package/.output/server/_connectorId-8nb5yOzG.mjs +421 -0
  40. package/.output/server/_libs/@base-ui/react+[...].mjs +12841 -0
  41. package/.output/server/_libs/@tanstack/react-router+[...].mjs +14349 -0
  42. package/.output/server/_libs/class-variance-authority+clsx.mjs +69 -0
  43. package/.output/server/_libs/dijkstrajs.mjs +118 -0
  44. package/.output/server/_libs/drizzle-orm.mjs +4349 -0
  45. package/.output/server/_libs/h3+rou3+srvx.mjs +1361 -0
  46. package/.output/server/_libs/lucide-react.mjs +911 -0
  47. package/.output/server/_libs/next-themes.mjs +53 -0
  48. package/.output/server/_libs/pngjs.mjs +1680 -0
  49. package/.output/server/_libs/qrcode.mjs +2583 -0
  50. package/.output/server/_libs/sonner.mjs +955 -0
  51. package/.output/server/_libs/tailwind-merge.mjs +3380 -0
  52. package/.output/server/_libs/tanstack__history.mjs +341 -0
  53. package/.output/server/_libs/ufo.mjs +64 -0
  54. package/.output/server/_loopId-BsUL1SeL.mjs +272 -0
  55. package/.output/server/_runtime.mjs +35 -0
  56. package/.output/server/_ssr/access-zIl15o8y.mjs +42 -0
  57. package/.output/server/_ssr/agents-Btql102J.mjs +657 -0
  58. package/.output/server/_ssr/agents-DAOZPgO2.mjs +76 -0
  59. package/.output/server/_ssr/alert-NEiOCTOl.mjs +37 -0
  60. package/.output/server/_ssr/callback-CUINvl7M.mjs +62 -0
  61. package/.output/server/_ssr/client-CKkOfh-s.mjs +335 -0
  62. package/.output/server/_ssr/connection-status-Bi5peUAk.mjs +31 -0
  63. package/.output/server/_ssr/connections-qnw2GVlP.mjs +2542 -0
  64. package/.output/server/_ssr/connector-icon-DZXQLLFo.mjs +37 -0
  65. package/.output/server/_ssr/connectors-BHNwuIMj.mjs +106 -0
  66. package/.output/server/_ssr/connectors-C28g0i4c.mjs +83 -0
  67. package/.output/server/_ssr/createServerRpc-DPDrnFdH.mjs +12 -0
  68. package/.output/server/_ssr/empty-plugin-adapters-BJ4m9L8g.mjs +5 -0
  69. package/.output/server/_ssr/gaps-RAFSXLZf.mjs +53 -0
  70. package/.output/server/_ssr/inbox-k5zHergU.mjs +35 -0
  71. package/.output/server/_ssr/loop-form-DlObSewZ.mjs +412 -0
  72. package/.output/server/_ssr/loops-APFcP0y6.mjs +311 -0
  73. package/.output/server/_ssr/loops-xt2Bn7_q.mjs +222 -0
  74. package/.output/server/_ssr/new-DfKyd2Wa.mjs +169 -0
  75. package/.output/server/_ssr/router-3WmpE5b_.mjs +1553 -0
  76. package/.output/server/_ssr/runners-By-mwc3F.mjs +32 -0
  77. package/.output/server/_ssr/runners-DI_NxpHC.mjs +116 -0
  78. package/.output/server/_ssr/runners-DtXs4Q8a.mjs +106 -0
  79. package/.output/server/_ssr/secrets-Hqti-niS.mjs +199 -0
  80. package/.output/server/_ssr/setting-fields-8vKFj4df.mjs +92 -0
  81. package/.output/server/_ssr/ssr.mjs +6605 -0
  82. package/.output/server/_ssr/start-Bdc2GJUI.mjs +51 -0
  83. package/.output/server/_ssr/switch-ReNglgH6.mjs +18 -0
  84. package/.output/server/_ssr/task-list-CUITIqkb.mjs +186 -0
  85. package/.output/server/_ssr/tasks-SAjNovX6.mjs +110 -0
  86. package/.output/server/_ssr/tasks-vcoodp_D.mjs +533 -0
  87. package/.output/server/_tanstack-start-manifest_v-C0cP5F0M.mjs +125 -0
  88. package/.output/server/_taskId-CXS3lod0.mjs +249 -0
  89. package/.output/server/index.mjs +541 -0
  90. package/README.md +81 -0
  91. package/dist/cli.mjs +165 -0
  92. package/dist/dispatcher.mjs +9871 -0
  93. package/dist/runner.mjs +657 -0
  94. package/drizzle/0000_cold_demogoblin.sql +29 -0
  95. package/drizzle/0001_fancy_blonde_phantom.sql +13 -0
  96. package/drizzle/0002_spooky_smiling_tiger.sql +16 -0
  97. package/drizzle/0003_small_golden_guardian.sql +25 -0
  98. package/drizzle/0004_wonderful_thunderbolts.sql +39 -0
  99. package/drizzle/0005_named_workflows.sql +7 -0
  100. package/drizzle/0006_signals.sql +18 -0
  101. package/drizzle/0007_review_comments.sql +1 -0
  102. package/drizzle/0008_held_signals.sql +1 -0
  103. package/drizzle/0009_work_item_ref.sql +58 -0
  104. package/drizzle/0010_signal_payload.sql +2 -0
  105. package/drizzle/0011_rule_poll_cursor.sql +1 -0
  106. package/drizzle/0012_loops.sql +63 -0
  107. package/drizzle/0013_loop_owns_action_and_prompt.sql +154 -0
  108. package/drizzle/0014_loop_poll_every.sql +4 -0
  109. package/drizzle/0015_machines.sql +10 -0
  110. package/drizzle/0016_task_machine.sql +1 -0
  111. package/drizzle/0017_loop_machine.sql +1 -0
  112. package/drizzle/0018_runners.sql +1 -0
  113. package/drizzle/0019_task_runner.sql +1 -0
  114. package/drizzle/0020_loop_runner.sql +1 -0
  115. package/drizzle/0021_drop_runner_kind.sql +1 -0
  116. package/drizzle/0022_drop_loop_runner.sql +1 -0
  117. package/drizzle/0023_shared_host.sql +6 -0
  118. package/drizzle/meta/0000_snapshot.json +206 -0
  119. package/drizzle/meta/0001_snapshot.json +284 -0
  120. package/drizzle/meta/0002_snapshot.json +390 -0
  121. package/drizzle/meta/0003_snapshot.json +566 -0
  122. package/drizzle/meta/0004_snapshot.json +633 -0
  123. package/drizzle/meta/0005_snapshot.json +626 -0
  124. package/drizzle/meta/0006_snapshot.json +752 -0
  125. package/drizzle/meta/0007_snapshot.json +759 -0
  126. package/drizzle/meta/0008_snapshot.json +766 -0
  127. package/drizzle/meta/0009_snapshot.json +752 -0
  128. package/drizzle/meta/0010_snapshot.json +766 -0
  129. package/drizzle/meta/0011_snapshot.json +773 -0
  130. package/drizzle/meta/0012_snapshot.json +773 -0
  131. package/drizzle/meta/0013_snapshot.json +817 -0
  132. package/drizzle/meta/_journal.json +174 -0
  133. package/package.json +88 -0
@@ -0,0 +1,32 @@
1
+ import { n as createServerFn } from "./ssr.mjs";
2
+ import { i as loopableOrigin } from "./callback-CUINvl7M.mjs";
3
+ import { c as listRunners, n as forgetRunner, r as getJoinToken, u as rotateJoinToken } from "./runners-DtXs4Q8a.mjs";
4
+ import { t as createServerRpc } from "./createServerRpc-DPDrnFdH.mjs";
5
+ //#region node_modules/.nitro/vite/services/ssr/assets/runners-By-mwc3F.js
6
+ var getRunnersPage_createServerFn_handler = createServerRpc({
7
+ id: "45dbbb612763589268984d236edcd17f5868ecf716df9625bfa46e7fec691f4e",
8
+ name: "getRunnersPage",
9
+ filename: "src/server/functions/runners.ts"
10
+ }, (opts) => getRunnersPage.__executeServer(opts));
11
+ var getRunnersPage = createServerFn({ method: "GET" }).handler(getRunnersPage_createServerFn_handler, async () => ({
12
+ runners: await listRunners(),
13
+ joinToken: await getJoinToken(),
14
+ origin: loopableOrigin()
15
+ }));
16
+ var rotateRunnerJoinToken_createServerFn_handler = createServerRpc({
17
+ id: "b4f7a66e1bb69cc14ab84d8f5dc7f85119c9a9b1d065a6b4e51dd0ba84c3f60c",
18
+ name: "rotateRunnerJoinToken",
19
+ filename: "src/server/functions/runners.ts"
20
+ }, (opts) => rotateRunnerJoinToken.__executeServer(opts));
21
+ var rotateRunnerJoinToken = createServerFn({ method: "POST" }).handler(rotateRunnerJoinToken_createServerFn_handler, async () => await rotateJoinToken());
22
+ var removeRunner_createServerFn_handler = createServerRpc({
23
+ id: "611491b32d9583ccaae637ddf9fb2f842ff75994f50513ecd8273e305e889eb8",
24
+ name: "removeRunner",
25
+ filename: "src/server/functions/runners.ts"
26
+ }, (opts) => removeRunner.__executeServer(opts));
27
+ var removeRunner = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(removeRunner_createServerFn_handler, async ({ data }) => {
28
+ await forgetRunner(data.id);
29
+ return await listRunners();
30
+ });
31
+ //#endregion
32
+ export { getRunnersPage_createServerFn_handler, removeRunner_createServerFn_handler, rotateRunnerJoinToken_createServerFn_handler };
@@ -0,0 +1,116 @@
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 { C as Button$1, I as Badge, L as Card, R as CardContent, f as useLiveRefresh, i as rotateRunnerJoinToken, n as Route$7, r as removeRunner, z as CardHeader } from "./router-3WmpE5b_.mjs";
6
+ import { n as AlertDescription, t as Alert } from "./alert-NEiOCTOl.mjs";
7
+ //#region node_modules/.nitro/vite/services/ssr/assets/runners-DI_NxpHC.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 with this token."
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 };
@@ -0,0 +1,106 @@
1
+ import { f as eq } from "../_libs/drizzle-orm.mjs";
2
+ import { f as runners, g as tasks, s as db } from "./client-CKkOfh-s.mjs";
3
+ import { a as runnerTokenKey, o as secretStore, r as joinTokenKey } from "./secrets-Hqti-niS.mjs";
4
+ import { randomBytes, randomUUID } from "node:crypto";
5
+ import { hostname } from "node:os";
6
+ //#region node_modules/.nitro/vite/services/ssr/assets/runners-DtXs4Q8a.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 };
@@ -0,0 +1,199 @@
1
+ import { f as eq } from "../_libs/drizzle-orm.mjs";
2
+ import { m as secrets, o as dataDir, p as secretNamespace, s as db } from "./client-CKkOfh-s.mjs";
3
+ import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { execFileSync } from "node:child_process";
6
+ //#region node_modules/.nitro/vite/services/ssr/assets/secrets-Hqti-niS.js
7
+ /**
8
+ * Hosted Loopable shares a network database between the App Worker and the
9
+ * Dispatcher. A local laptop still uses a file, and needs none of this.
10
+ */
11
+ function isHosted() {
12
+ const url = process.env.LOOPABLE_DATABASE_URL ?? "";
13
+ return url.startsWith("libsql:") || url.startsWith("https:") || url.startsWith("wss:");
14
+ }
15
+ function account() {
16
+ return process.env.USER ?? "loopable";
17
+ }
18
+ function service(key) {
19
+ return `${secretNamespace()}.${key}`;
20
+ }
21
+ var keychainStore = {
22
+ async get(key) {
23
+ try {
24
+ return execFileSync("security", [
25
+ "find-generic-password",
26
+ "-a",
27
+ account(),
28
+ "-s",
29
+ service(key),
30
+ "-w"
31
+ ], {
32
+ encoding: "utf8",
33
+ stdio: [
34
+ "ignore",
35
+ "pipe",
36
+ "ignore"
37
+ ]
38
+ }).trim() || null;
39
+ } catch {
40
+ return null;
41
+ }
42
+ },
43
+ async set(key, value) {
44
+ try {
45
+ execFileSync("security", [
46
+ "delete-generic-password",
47
+ "-a",
48
+ account(),
49
+ "-s",
50
+ service(key)
51
+ ], { stdio: "ignore" });
52
+ } catch {}
53
+ execFileSync("security", [
54
+ "add-generic-password",
55
+ "-a",
56
+ account(),
57
+ "-s",
58
+ service(key),
59
+ "-w",
60
+ value
61
+ ], { stdio: "ignore" });
62
+ },
63
+ async delete(key) {
64
+ try {
65
+ execFileSync("security", [
66
+ "delete-generic-password",
67
+ "-a",
68
+ account(),
69
+ "-s",
70
+ service(key)
71
+ ], { stdio: "ignore" });
72
+ } catch {}
73
+ }
74
+ };
75
+ function secretFile(key) {
76
+ return join(dataDir(), `secret-${key.replace(/[^a-zA-Z0-9._-]/g, "_")}.json`);
77
+ }
78
+ var fileStore = {
79
+ async get(key) {
80
+ const path = secretFile(key);
81
+ if (!existsSync(path)) return null;
82
+ try {
83
+ return readFileSync(path, "utf8");
84
+ } catch {
85
+ return null;
86
+ }
87
+ },
88
+ async set(key, value) {
89
+ const path = secretFile(key);
90
+ writeFileSync(path, value, {
91
+ encoding: "utf8",
92
+ mode: 384
93
+ });
94
+ chmodSync(path, 384);
95
+ },
96
+ async delete(key) {
97
+ const path = secretFile(key);
98
+ if (existsSync(path)) rmSync(path);
99
+ }
100
+ };
101
+ function masterKey() {
102
+ const key = process.env.LOOPABLE_MASTER_KEY?.trim();
103
+ if (!key) throw new Error("Set LOOPABLE_MASTER_KEY so App and Dispatcher can share credentials.");
104
+ return key;
105
+ }
106
+ async function aesKey() {
107
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(masterKey()));
108
+ return crypto.subtle.importKey("raw", hash, "AES-GCM", false, ["encrypt", "decrypt"]);
109
+ }
110
+ async function encrypt(plain) {
111
+ const key = await aesKey();
112
+ const iv = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(12));
113
+ const sealed = await crypto.subtle.encrypt({
114
+ name: "AES-GCM",
115
+ iv
116
+ }, key, new TextEncoder().encode(plain));
117
+ const packed = new Uint8Array(iv.length + sealed.byteLength);
118
+ packed.set(iv);
119
+ packed.set(new Uint8Array(sealed), iv.length);
120
+ return bytesToBase64(packed);
121
+ }
122
+ async function decrypt(blob) {
123
+ const key = await aesKey();
124
+ const packed = base64ToBytes(blob);
125
+ const iv = packed.slice(0, 12);
126
+ const data = packed.slice(12);
127
+ const opened = await crypto.subtle.decrypt({
128
+ name: "AES-GCM",
129
+ iv
130
+ }, key, data);
131
+ return new TextDecoder().decode(opened);
132
+ }
133
+ function bytesToBase64(bytes) {
134
+ let binary = "";
135
+ for (const byte of bytes) binary += String.fromCharCode(byte);
136
+ return btoa(binary);
137
+ }
138
+ function base64ToBytes(blob) {
139
+ const binary = atob(blob);
140
+ const bytes = new Uint8Array(binary.length);
141
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
142
+ return bytes;
143
+ }
144
+ var tableStore = {
145
+ async get(key) {
146
+ const row = await db().select().from(secrets).where(eq(secrets.key, key)).get();
147
+ if (!row) return null;
148
+ return decrypt(row.value);
149
+ },
150
+ async set(key, value) {
151
+ const stored = await encrypt(value);
152
+ const now = /* @__PURE__ */ new Date();
153
+ if (await db().select().from(secrets).where(eq(secrets.key, key)).get()) {
154
+ await db().update(secrets).set({
155
+ value: stored,
156
+ updatedAt: now
157
+ }).where(eq(secrets.key, key)).run();
158
+ return;
159
+ }
160
+ await db().insert(secrets).values({
161
+ key,
162
+ value: stored,
163
+ updatedAt: now
164
+ }).run();
165
+ },
166
+ async delete(key) {
167
+ await db().delete(secrets).where(eq(secrets.key, key)).run();
168
+ }
169
+ };
170
+ function secretStore() {
171
+ if (isHosted() || process.env.LOOPABLE_MASTER_KEY) return tableStore;
172
+ return process.platform === "darwin" && process.env.LOOPABLE_KEYCHAIN !== "0" ? keychainStore : fileStore;
173
+ }
174
+ function connectionSecretKey(connectionId) {
175
+ return `connection.${connectionId}`;
176
+ }
177
+ async function readCredential(connectionId) {
178
+ const raw = await secretStore().get(connectionSecretKey(connectionId));
179
+ if (!raw) return null;
180
+ try {
181
+ return JSON.parse(raw);
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+ async function writeCredential(connectionId, credential) {
187
+ await secretStore().set(connectionSecretKey(connectionId), JSON.stringify(credential));
188
+ }
189
+ async function deleteCredential(connectionId) {
190
+ await secretStore().delete(connectionSecretKey(connectionId));
191
+ }
192
+ function joinTokenKey() {
193
+ return "runners.join";
194
+ }
195
+ function runnerTokenKey(runnerId) {
196
+ return `runners.token.${runnerId}`;
197
+ }
198
+ //#endregion
199
+ export { runnerTokenKey as a, readCredential as i, isHosted as n, secretStore as o, joinTokenKey as r, writeCredential as s, deleteCredential as t };
@@ -0,0 +1,92 @@
1
+ import "../_runtime.mjs";
2
+ import { U as require_jsx_runtime, W as require_react } from "../_libs/@base-ui/react+[...].mjs";
3
+ import { S as Input$1, V as cn, _ as SelectItem, b as Label, g as SelectContent, h as Select$1, v as SelectTrigger, y as SelectValue } from "./router-3WmpE5b_.mjs";
4
+ import { t as Switch$1 } from "./switch-ReNglgH6.mjs";
5
+ require_react();
6
+ var import_jsx_runtime = require_jsx_runtime();
7
+ function Textarea({ className, ...props }) {
8
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
9
+ "data-slot": "textarea",
10
+ className: cn("flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
11
+ ...props
12
+ });
13
+ }
14
+ /**
15
+ * One renderer for anything a connector declares as a field, so connection
16
+ * settings and loop conditions can never drift apart visually.
17
+ */
18
+ function initialFieldValues(fields, stored) {
19
+ const values = {};
20
+ for (const field of fields) {
21
+ const current = stored[field.key];
22
+ switch (field.kind) {
23
+ case "boolean":
24
+ values[field.key] = typeof current === "boolean" ? current : field.default;
25
+ break;
26
+ case "string_list":
27
+ values[field.key] = Array.isArray(current) ? current : [];
28
+ break;
29
+ case "select":
30
+ values[field.key] = typeof current === "string" ? current : field.default;
31
+ break;
32
+ default: values[field.key] = typeof current === "string" ? current : "";
33
+ }
34
+ }
35
+ return values;
36
+ }
37
+ function SettingFieldInputs({ fields, values, onChange, idPrefix = "" }) {
38
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fields.map((field) => {
39
+ const id = `${idPrefix}${field.key}`;
40
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
41
+ className: "flex flex-col gap-2",
42
+ children: field.kind === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
43
+ className: "flex items-center justify-between gap-4",
44
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
45
+ htmlFor: id,
46
+ children: field.label
47
+ }), field.help ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
48
+ className: "mt-1 text-xs text-muted-foreground",
49
+ children: field.help
50
+ }) : null] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
51
+ id,
52
+ checked: Boolean(values[field.key]),
53
+ onCheckedChange: (checked) => onChange(field.key, checked)
54
+ })]
55
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
56
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
57
+ htmlFor: id,
58
+ children: field.label
59
+ }),
60
+ field.help ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
61
+ className: "text-xs text-muted-foreground",
62
+ children: field.help
63
+ }) : null,
64
+ field.kind === "string_list" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
65
+ id,
66
+ rows: 4,
67
+ placeholder: field.placeholder,
68
+ value: (values[field.key] ?? []).join("\n"),
69
+ onChange: (event) => onChange(field.key, event.target.value.split("\n").map((line) => line.trim()).filter(Boolean))
70
+ }) : field.kind === "select" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
71
+ value: values[field.key],
72
+ onValueChange: (value) => onChange(field.key, value),
73
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
74
+ id,
75
+ className: "w-full",
76
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { children: (value) => field.options.find((option) => option.value === value)?.label ?? value })
77
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectContent, { children: field.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
78
+ value: option.value,
79
+ children: option.label
80
+ }, option.value)) })]
81
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$1, {
82
+ id,
83
+ placeholder: field.placeholder,
84
+ value: values[field.key],
85
+ onChange: (event) => onChange(field.key, event.target.value)
86
+ })
87
+ ] })
88
+ }, field.key);
89
+ }) });
90
+ }
91
+ //#endregion
92
+ export { Textarea as n, initialFieldValues as r, SettingFieldInputs as t };