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,76 @@
1
+ import { n as createServerFn } from "./ssr.mjs";
2
+ import { a as setDefaultAgent, i as saveAgentSettings, r as listAgents, s as testAgent } from "./agents-Btql102J.mjs";
3
+ import { c as listRunners, t as availableAgentIds } from "./runners-DtXs4Q8a.mjs";
4
+ import { t as createServerRpc } from "./createServerRpc-DPDrnFdH.mjs";
5
+ //#region node_modules/.nitro/vite/services/ssr/assets/agents-DAOZPgO2.js
6
+ async function withRunners(agents) {
7
+ const runners = await listRunners();
8
+ const available = await availableAgentIds();
9
+ const stored = agents.find((agent) => agent.isDefault)?.agentId ?? null;
10
+ const implicit = !stored && available.length === 1 ? available[0] : null;
11
+ return agents.map((agent) => {
12
+ const onRunners = runners.flatMap((runner) => {
13
+ const entry = runner.inventory.find((item) => item.agentId === agent.agentId);
14
+ if (!entry?.installed) return [];
15
+ return [{
16
+ runnerId: runner.id,
17
+ name: runner.name,
18
+ status: runner.status,
19
+ signedIn: entry.signedIn,
20
+ version: entry.version
21
+ }];
22
+ });
23
+ const isDefault = agent.agentId === stored || agent.agentId === implicit;
24
+ return {
25
+ ...agent,
26
+ onRunners,
27
+ isDefault,
28
+ defaultIsImplicit: isDefault && agent.agentId === implicit
29
+ };
30
+ });
31
+ }
32
+ var getAgentsPage_createServerFn_handler = createServerRpc({
33
+ id: "bde588e9b0b81a4ee619a63872d6b8b76e22db006c7a3c4351bcd56e5f76bbed",
34
+ name: "getAgentsPage",
35
+ filename: "src/server/functions/agents.ts"
36
+ }, (opts) => getAgentsPage.__executeServer(opts));
37
+ var getAgentsPage = createServerFn({ method: "GET" }).handler(getAgentsPage_createServerFn_handler, async () => ({ agents: await withRunners(await listAgents()) }));
38
+ var saveAgent_createServerFn_handler = createServerRpc({
39
+ id: "80883b912a5a0aba2aa583b505e4e289d765a3f7fbb793a97692f439e7f960ba",
40
+ name: "saveAgent",
41
+ filename: "src/server/functions/agents.ts"
42
+ }, (opts) => saveAgent.__executeServer(opts));
43
+ var saveAgent = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(saveAgent_createServerFn_handler, async ({ data }) => {
44
+ await saveAgentSettings(data.agentId, {
45
+ permissionMode: data.permissionMode,
46
+ model: data.model,
47
+ timeoutMs: data.timeoutMs
48
+ });
49
+ return await withRunners(await listAgents());
50
+ });
51
+ var chooseDefaultAgent_createServerFn_handler = createServerRpc({
52
+ id: "143ea51f7421d9eebec9ad31c2df8eed5aa2e5d8035473dbfad39913fd7d54c0",
53
+ name: "chooseDefaultAgent",
54
+ filename: "src/server/functions/agents.ts"
55
+ }, (opts) => chooseDefaultAgent.__executeServer(opts));
56
+ var chooseDefaultAgent = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(chooseDefaultAgent_createServerFn_handler, async ({ data }) => {
57
+ await setDefaultAgent(data.agentId);
58
+ return await withRunners(await listAgents());
59
+ });
60
+ var runAgentTest_createServerFn_handler = createServerRpc({
61
+ id: "b0878f841b35f46b5f2dc9cbd71c5be5895ae1b8903b3b24c24dfcf86291d3e4",
62
+ name: "runAgentTest",
63
+ filename: "src/server/functions/agents.ts"
64
+ }, (opts) => runAgentTest.__executeServer(opts));
65
+ var runAgentTest = createServerFn({ method: "POST" }).inputValidator((data) => data).handler(runAgentTest_createServerFn_handler, async ({ data }) => {
66
+ const result = await testAgent(data.agentId);
67
+ return {
68
+ passed: result.passed,
69
+ output: result.output.slice(0, 2e3),
70
+ detail: result.detail ?? null,
71
+ durationMs: result.durationMs,
72
+ command: result.command
73
+ };
74
+ });
75
+ //#endregion
76
+ export { chooseDefaultAgent_createServerFn_handler, getAgentsPage_createServerFn_handler, runAgentTest_createServerFn_handler, saveAgent_createServerFn_handler };
@@ -0,0 +1,37 @@
1
+ import "../_runtime.mjs";
2
+ import { U as require_jsx_runtime, W as require_react } from "../_libs/@base-ui/react+[...].mjs";
3
+ import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
4
+ import { V as cn } from "./router-3WmpE5b_.mjs";
5
+ require_react();
6
+ var import_jsx_runtime = require_jsx_runtime();
7
+ var alertVariants = cva("group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", {
8
+ variants: { variant: {
9
+ default: "bg-card text-card-foreground",
10
+ destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current"
11
+ } },
12
+ defaultVariants: { variant: "default" }
13
+ });
14
+ function Alert({ className, variant, ...props }) {
15
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
16
+ "data-slot": "alert",
17
+ role: "alert",
18
+ className: cn(alertVariants({ variant }), className),
19
+ ...props
20
+ });
21
+ }
22
+ function AlertTitle({ className, ...props }) {
23
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
24
+ "data-slot": "alert-title",
25
+ className: cn("font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground", className),
26
+ ...props
27
+ });
28
+ }
29
+ function AlertDescription({ className, ...props }) {
30
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
31
+ "data-slot": "alert-description",
32
+ className: cn("text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4", className),
33
+ ...props
34
+ });
35
+ }
36
+ //#endregion
37
+ export { AlertDescription as n, AlertTitle as r, Alert as t };
@@ -0,0 +1,62 @@
1
+ //#region node_modules/.nitro/vite/services/ssr/assets/callback-CUINvl7M.js
2
+ function configuredOrigin() {
3
+ return process.env.LOOPABLE_BASE_URL?.replace(/\/$/, "") || null;
4
+ }
5
+ function isLoopbackHost(hostname) {
6
+ return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
7
+ }
8
+ /**
9
+ * RFC1918, link-local, and loopback. A LAN App can use these as the join URL;
10
+ * GitHub and Google will not take them as an OAuth redirect.
11
+ */
12
+ function isPrivateHostname(hostname) {
13
+ if (isLoopbackHost(hostname)) return true;
14
+ const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
15
+ if (v4) {
16
+ const a = Number(v4[1]);
17
+ const b = Number(v4[2]);
18
+ if (a === 10) return true;
19
+ if (a === 127) return true;
20
+ if (a === 192 && b === 168) return true;
21
+ if (a === 172 && b >= 16 && b <= 31) return true;
22
+ if (a === 169 && b === 254) return true;
23
+ return false;
24
+ }
25
+ const host = hostname.toLowerCase();
26
+ if (host.includes(":")) {
27
+ if (host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd")) return true;
28
+ }
29
+ return false;
30
+ }
31
+ /** Public https origin for cloud. LAN and loopback BASE_URL do not count. */
32
+ function publicConfiguredOrigin() {
33
+ const base = configuredOrigin();
34
+ if (!base) return null;
35
+ try {
36
+ if (isPrivateHostname(new URL(base).hostname)) return null;
37
+ return base;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ /** Where GitHub / Gmail send the browser back. Local is always loopback. */
43
+ function appOrigin(requestUrl) {
44
+ const hosted = publicConfiguredOrigin();
45
+ if (hosted) return hosted;
46
+ const url = new URL(requestUrl);
47
+ return `http://127.0.0.1${url.port ? `:${url.port}` : ""}`;
48
+ }
49
+ function callbackUrl(requestUrl, connectorId) {
50
+ return `${appOrigin(requestUrl)}/api/connectors/${connectorId}/callback`;
51
+ }
52
+ function registrableCallbackUrl(connectorId) {
53
+ const hosted = publicConfiguredOrigin();
54
+ if (hosted) return `${hosted}/api/connectors/${connectorId}/callback`;
55
+ return `http://127.0.0.1/api/connectors/${connectorId}/callback`;
56
+ }
57
+ /** Where runners and people should address this Loopable. May be a LAN IP. */
58
+ function loopableOrigin() {
59
+ return configuredOrigin() ?? "http://127.0.0.1:4321";
60
+ }
61
+ //#endregion
62
+ export { registrableCallbackUrl as a, loopableOrigin as i, callbackUrl as n, isLoopbackHost as r, appOrigin as t };
@@ -0,0 +1,335 @@
1
+ import { a as uniqueIndex, c as integer, i as index, n as drizzle, o as sqliteTable, r as primaryKey, s as text, t as migrate, v as sql } from "../_libs/drizzle-orm.mjs";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { createClient } from "@libsql/client";
7
+ //#region node_modules/.nitro/vite/services/ssr/assets/client-CKkOfh-s.js
8
+ var __defProp = Object.defineProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ function isDevProfile() {
19
+ return process.env.LOOPABLE_DEV === "1";
20
+ }
21
+ function dataDir() {
22
+ const dir = process.env.LOOPABLE_HOME ?? join(homedir(), isDevProfile() ? ".loopable-dev" : ".loopable");
23
+ mkdirSync(dir, { recursive: true });
24
+ return dir;
25
+ }
26
+ function dbPath() {
27
+ return process.env.LOOPABLE_DB ?? join(dataDir(), "loopable.sqlite");
28
+ }
29
+ /** Keychain service prefix. Dev must not read or write the deploy install's secrets. */
30
+ function secretNamespace() {
31
+ return process.env.LOOPABLE_SECRET_PREFIX ?? (isDevProfile() ? "loopable-dev" : "loopable");
32
+ }
33
+ /**
34
+ * Where a run keeps its scratch files and its log. Not the system temp
35
+ * directory: the dispatcher writes these and the app reads them, they are the
36
+ * first thing to look at when a run goes wrong, and temp gets swept.
37
+ */
38
+ function runDir(taskId) {
39
+ const dir = join(dataDir(), "runs", taskId);
40
+ mkdirSync(dir, { recursive: true });
41
+ return dir;
42
+ }
43
+ var schema_exports = /* @__PURE__ */ __exportAll({
44
+ SIGNAL_OUTCOME: () => SIGNAL_OUTCOME,
45
+ agentSettings: () => agentSettings,
46
+ appSettings: () => appSettings,
47
+ authAttempts: () => authAttempts,
48
+ connections: () => connections,
49
+ loops: () => loops,
50
+ runners: () => runners,
51
+ secrets: () => secrets,
52
+ signals: () => signals,
53
+ tasks: () => tasks
54
+ });
55
+ /**
56
+ * One row per connected account, so a connector can be connected several times
57
+ * (two GitHub accounts, or github.com alongside an Enterprise host). Credentials
58
+ * live in the keychain under the row id, never in this table.
59
+ */
60
+ var connections = sqliteTable("connections", {
61
+ id: text("id").primaryKey(),
62
+ connectorId: text("connector_id").notNull(),
63
+ status: text("status").$type().notNull().default("connected"),
64
+ accountId: text("account_id").notNull(),
65
+ accountLabel: text("account_label").notNull(),
66
+ accountUrl: text("account_url"),
67
+ avatarUrl: text("avatar_url"),
68
+ scopes: text("scopes"),
69
+ settings: text("settings", { mode: "json" }).$type().notNull().default({}),
70
+ cursor: text("cursor", { mode: "json" }).$type(),
71
+ lastSyncedAt: integer("last_synced_at", { mode: "timestamp_ms" }),
72
+ lastError: text("last_error"),
73
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
74
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
75
+ }, (table) => [uniqueIndex("connections_account_unique").on(table.connectorId, table.accountId), index("connections_connector_idx").on(table.connectorId)]);
76
+ /**
77
+ * Short-lived state for an authorization that is mid-flight: the CSRF state and
78
+ * the PKCE verifier that the callback must present. Rows are consumed on
79
+ * callback and swept after they expire.
80
+ */
81
+ var authAttempts = sqliteTable("auth_attempts", {
82
+ state: text("state").primaryKey(),
83
+ connectorId: text("connector_id").notNull(),
84
+ verifier: text("verifier").notNull(),
85
+ redirectUri: text("redirect_uri").notNull(),
86
+ returnTo: text("return_to"),
87
+ expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
88
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
89
+ });
90
+ /**
91
+ * One run of one loop against one thing. Kept whether it wrote anything or
92
+ * not, because a loop that runs on its own is only trustworthy if you can see
93
+ * afterwards what it did.
94
+ */
95
+ var tasks = sqliteTable("tasks", {
96
+ id: text("id").primaryKey(),
97
+ loopId: text("loop_id").notNull().references(() => loops.id, { onDelete: "cascade" }),
98
+ connectorId: text("connector_id").notNull(),
99
+ state: text("state").$type().notNull().default("queued"),
100
+ /** What the task is about, resolved once so the record survives the source. */
101
+ sourceUrl: text("source_url").notNull(),
102
+ sourceKind: text("source_kind").$type().notNull(),
103
+ /** Names the thing inside its own connector. See `WorkItemRef`. */
104
+ sourceRef: text("source_ref").notNull(),
105
+ /** What the connector kept, when the source cannot be read twice. */
106
+ sourcePayload: text("source_payload", { mode: "json" }).$type(),
107
+ /**
108
+ * Identifying a link needs no network, but the title does, so it arrives
109
+ * when the dispatcher fetches rather than when the task is queued.
110
+ */
111
+ sourceTitle: text("source_title"),
112
+ /** Set when a run was asked to stop before writing anything. */
113
+ dryRun: integer("dry_run", { mode: "boolean" }).notNull().default(false),
114
+ agentId: text("agent_id"),
115
+ runnerId: text("runner_id"),
116
+ agentCommand: text("agent_command"),
117
+ /** What the agent produced, which is also what gets written back. */
118
+ output: text("output"),
119
+ /**
120
+ * Findings that were checked against the diff and can be attached to a
121
+ * line. Kept because a write that failed on a bad connection has to be
122
+ * repeatable without paying for the review a second time.
123
+ */
124
+ comments: text("comments", { mode: "json" }).$type(),
125
+ /**
126
+ * Which action is going to write this and where, taken from the loop when
127
+ * the task was queued. Kept here rather than read back off the loop so
128
+ * that changing a loop cannot change what a task already in the queue is
129
+ * about to do.
130
+ */
131
+ actionConnectorId: text("action_connector_id").notNull(),
132
+ actionId: text("action_id").notNull(),
133
+ actionTarget: text("action_target", { mode: "json" }).$type().notNull().default({}),
134
+ /** Where the write landed, once it has. */
135
+ resultUrl: text("result_url"),
136
+ error: text("error"),
137
+ /** Everything the agent printed, streamed to disk while it runs. */
138
+ logPath: text("log_path"),
139
+ /** Same log, kept in the database so the App Worker can read it. */
140
+ logText: text("log_text").notNull().default(""),
141
+ attempts: integer("attempts").notNull().default(0),
142
+ /** Not claimable before this: how a retry backs off. */
143
+ runAfter: integer("run_after", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
144
+ /**
145
+ * A claim that expires. Whoever holds it renews it while working, so a
146
+ * task whose dispatcher died can be told apart from one still being worked on.
147
+ */
148
+ leaseUntil: integer("lease_until", { mode: "timestamp_ms" }),
149
+ cancelRequested: integer("cancel_requested", { mode: "boolean" }).notNull().default(false),
150
+ /**
151
+ * Set when the task came from a signal rather than from a pasted link.
152
+ * The signals table already prevents a loop acting twice; this is the
153
+ * second lock, held by the database, in case two polls overlap.
154
+ */
155
+ dedupeKey: text("dedupe_key"),
156
+ durationMs: integer("duration_ms"),
157
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
158
+ startedAt: integer("started_at", { mode: "timestamp_ms" }),
159
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
160
+ }, (table) => [
161
+ index("tasks_loop_idx").on(table.loopId),
162
+ index("tasks_created_idx").on(table.createdAt),
163
+ index("tasks_claim_idx").on(table.state, table.runAfter),
164
+ uniqueIndex("tasks_dedupe_idx").on(table.dedupeKey)
165
+ ]);
166
+ /**
167
+ * One instance of one of a connector's workflows. The workflow decides what to
168
+ * watch for, what to ask, and where to write; a loop only carries the answers
169
+ * to the few questions the workflow asks. Settings are a JSON blob because
170
+ * only the connector knows what can be narrowed.
171
+ */
172
+ var loops = sqliteTable("loops", {
173
+ id: text("id").primaryKey(),
174
+ name: text("name").notNull(),
175
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
176
+ /** Lowest first. The first matching loop wins, so order is meaningful. */
177
+ priority: integer("priority").notNull(),
178
+ connectorId: text("connector_id").notNull(),
179
+ workflowId: text("workflow_id").notNull(),
180
+ settings: text("settings", { mode: "json" }).$type().notNull().default({}),
181
+ /**
182
+ * What the agent is asked to do. Copied from the workflow when the loop is
183
+ * created, and the loop's own words from then on.
184
+ *
185
+ * Copied rather than referenced because this is the part a person has
186
+ * opinions about, and an opinion that a later release can overwrite is not
187
+ * worth having. It is only prose, so nothing else has to agree with it.
188
+ */
189
+ prompt: text("prompt").notNull(),
190
+ /** Added after the prompt, for a note that is not worth editing it over. */
191
+ guidance: text("guidance"),
192
+ /** Null means whichever agent is currently the default. */
193
+ agentId: text("agent_id"),
194
+ /**
195
+ * Which action carries the answer back. Copied from the workflow when the
196
+ * loop is created and the loop's own from then on, so a workflow that
197
+ * changes its mind about where to write does not quietly move somebody's
198
+ * loop with it.
199
+ *
200
+ * The connector is stored apart from the one the loop watches because it
201
+ * need not be the same one. Being asked for a review on GitHub and
202
+ * answering in a chat is two connectors, and this column is what makes it
203
+ * one loop.
204
+ */
205
+ actionConnectorId: text("action_connector_id").notNull(),
206
+ actionId: text("action_id").notNull(),
207
+ /** Answers to the action's own `target` fields. Empty means: back to the source. */
208
+ actionTarget: text("action_target", { mode: "json" }).$type().notNull().default({}),
209
+ /**
210
+ * Null until the loop has been looked at once. That first look is what
211
+ * separates the backlog that predates the loop from everything after it,
212
+ * so it is worth being able to tell the two apart.
213
+ */
214
+ polledAt: integer("polled_at", { mode: "timestamp_ms" }),
215
+ /**
216
+ * How long to leave between looks, or null to look every time the dispatcher
217
+ * does.
218
+ *
219
+ * Worth having because looking often and acting cheaply pull in opposite
220
+ * directions. A loop that decides what is worth doing by weighing a batch
221
+ * has nothing to weigh when it is handed one item at a time, so for mail,
222
+ * where nothing turns on the next two minutes, waiting is what makes the
223
+ * weighing possible at all.
224
+ */
225
+ pollEveryMs: integer("poll_every_ms"),
226
+ /** Why the last look failed, if it did. Cleared by a look that works. */
227
+ pollError: text("poll_error"),
228
+ /** Where a stream got to. The connector's own business. See `poll`. */
229
+ pollCursor: text("poll_cursor", { mode: "json" }).$type(),
230
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`),
231
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
232
+ }, (table) => [index("loops_priority_idx").on(table.priority)]);
233
+ /**
234
+ * What a loop has already noticed. A loop acts once per key and never again,
235
+ * which is the whole of not reviewing the same commit twice.
236
+ */
237
+ var SIGNAL_OUTCOME = [
238
+ "queued",
239
+ "backlog",
240
+ "held",
241
+ "superseded"
242
+ ];
243
+ var signals = sqliteTable("signals", {
244
+ loopId: text("loop_id").notNull().references(() => loops.id, { onDelete: "cascade" }),
245
+ /** The connector's own idea of identity: changing it means act again. */
246
+ key: text("key").notNull(),
247
+ outcome: text("outcome").$type().notNull(),
248
+ /** Enough to show and to act on later, without asking the service again. */
249
+ sourceKind: text("source_kind").$type().notNull(),
250
+ sourceRef: text("source_ref").notNull(),
251
+ sourcePayload: text("source_payload", { mode: "json" }).$type(),
252
+ sourceTitle: text("source_title").notNull(),
253
+ sourceUrl: text("source_url").notNull(),
254
+ /** In the words shown to whoever has to decide whether to run it anyway. */
255
+ hold: text("hold"),
256
+ taskId: text("task_id"),
257
+ seenAt: integer("seen_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
258
+ }, (table) => [primaryKey({ columns: [table.loopId, table.key] }), index("signals_outcome_idx").on(table.loopId, table.outcome)]);
259
+ /**
260
+ * Agents are discovered on each runner, so nothing about their presence is
261
+ * stored here. Only the choices a person makes live in this table.
262
+ */
263
+ var agentSettings = sqliteTable("agent_settings", {
264
+ agentId: text("agent_id").primaryKey(),
265
+ permissionMode: text("permission_mode").$type().notNull(),
266
+ model: text("model"),
267
+ timeoutMs: integer("timeout_ms").notNull(),
268
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
269
+ });
270
+ /** Small key/value store for single settings such as the default agent. */
271
+ var appSettings = sqliteTable("app_settings", {
272
+ key: text("key").primaryKey(),
273
+ value: text("value", { mode: "json" }).$type().notNull(),
274
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
275
+ });
276
+ /**
277
+ * A joined runner that can run agents.
278
+ */
279
+ /**
280
+ * Credentials when App and Dispatcher do not share a keychain. Encrypted with
281
+ * LOOPABLE_MASTER_KEY. Local laptop installs keep using the OS store.
282
+ */
283
+ var secrets = sqliteTable("secrets", {
284
+ key: text("key").primaryKey(),
285
+ value: text("value").notNull(),
286
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
287
+ });
288
+ var runners = sqliteTable("runners", {
289
+ id: text("id").primaryKey(),
290
+ name: text("name").notNull(),
291
+ hostname: text("hostname").notNull(),
292
+ status: text("status").$type().notNull().default("offline"),
293
+ inventory: text("inventory", { mode: "json" }).$type().notNull().default([]),
294
+ lastSeenAt: integer("last_seen_at", { mode: "timestamp_ms" }),
295
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
296
+ });
297
+ function databaseUrl() {
298
+ if (process.env.LOOPABLE_DATABASE_URL) return process.env.LOOPABLE_DATABASE_URL;
299
+ return pathToFileURL(dbPath()).href;
300
+ }
301
+ function create() {
302
+ const client = createClient({
303
+ url: databaseUrl(),
304
+ authToken: process.env.LOOPABLE_DATABASE_AUTH_TOKEN
305
+ });
306
+ return drizzle(client, { schema: schema_exports });
307
+ }
308
+ function migrationsFolder() {
309
+ const here = fileURLToPath(new URL(".", import.meta.url));
310
+ return [
311
+ join(here, "../drizzle"),
312
+ join(here, "../../../drizzle"),
313
+ join(process.cwd(), "drizzle")
314
+ ].find((folder) => existsSync(folder)) ?? join(process.cwd(), "drizzle");
315
+ }
316
+ /**
317
+ * The dispatcher applies migrations. The App Worker has no drizzle folder on disk
318
+ * in production, and must not race the dispatcher on a fresh database.
319
+ */
320
+ async function migrateIfNeeded() {
321
+ if (process.env.LOOPABLE_SKIP_MIGRATE === "1") return;
322
+ const folder = migrationsFolder();
323
+ if (!existsSync(folder)) return;
324
+ await migrate(db(), { migrationsFolder: folder });
325
+ }
326
+ function rowsChanged(result) {
327
+ return Number(result.rowsAffected ?? result.changes ?? 0);
328
+ }
329
+ var cache = globalThis;
330
+ function db() {
331
+ cache.__loopableDb ??= create();
332
+ return cache.__loopableDb;
333
+ }
334
+ //#endregion
335
+ export { connections as a, loops as c, runDir as d, runners as f, tasks as g, signals as h, authAttempts as i, migrateIfNeeded as l, secrets as m, agentSettings as n, dataDir as o, secretNamespace as p, appSettings as r, db as s, __exportAll as t, rowsChanged as u };
@@ -0,0 +1,31 @@
1
+ import { U as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
+ import { I as Badge } from "./router-3WmpE5b_.mjs";
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/connection-status-Bi5peUAk.js
4
+ var import_jsx_runtime = require_jsx_runtime();
5
+ var LABELS = {
6
+ connected: {
7
+ text: "Connected",
8
+ variant: "default"
9
+ },
10
+ needs_reauth: {
11
+ text: "Needs reconnecting",
12
+ variant: "destructive"
13
+ },
14
+ error: {
15
+ text: "Error",
16
+ variant: "destructive"
17
+ },
18
+ paused: {
19
+ text: "Paused",
20
+ variant: "secondary"
21
+ }
22
+ };
23
+ function ConnectionStatusBadge({ status }) {
24
+ const label = LABELS[status] ?? LABELS.error;
25
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
26
+ variant: label.variant,
27
+ children: label.text
28
+ });
29
+ }
30
+ //#endregion
31
+ export { ConnectionStatusBadge as t };