loopable-cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/_connectorId-CDCuSgpN.js +1 -0
  3. package/.output/public/assets/_loopId-DOnIsVTW.js +1 -0
  4. package/.output/public/assets/_taskId-BO5MZnlZ.js +1 -0
  5. package/.output/public/assets/agents-CBozi7oi.js +1 -0
  6. package/.output/public/assets/alert-Bn_wjgTX.js +1 -0
  7. package/.output/public/assets/button-K3ypIhAZ.js +1 -0
  8. package/.output/public/assets/card-DiVnaTIE.js +1 -0
  9. package/.output/public/assets/chevron-right-BrctLtFK.js +1 -0
  10. package/.output/public/assets/connection-status-DKWelvLO.js +1 -0
  11. package/.output/public/assets/connector-icon-CEbdE71G.js +1 -0
  12. package/.output/public/assets/connectors-JyX3IzJj.js +1 -0
  13. package/.output/public/assets/gaps-PjmcbaWo.js +1 -0
  14. package/.output/public/assets/inbox-CujRR0Cz.js +1 -0
  15. package/.output/public/assets/index-C9SQ_Baq.js +16 -0
  16. package/.output/public/assets/loop-form-Dw3cuTzw.js +1 -0
  17. package/.output/public/assets/loops-DcEfZa2_.js +1 -0
  18. package/.output/public/assets/manifests-yJpevyye.js +1 -0
  19. package/.output/public/assets/new-CwsnNuRW.js +1 -0
  20. package/.output/public/assets/page-bvnfRMum.js +1 -0
  21. package/.output/public/assets/play-yHLs4DjK.js +1 -0
  22. package/.output/public/assets/plus-Ber25lrU.js +1 -0
  23. package/.output/public/assets/preload-helper-BoU_7EQC.js +3 -0
  24. package/.output/public/assets/refresh-cw-BzSY7M8I.js +1 -0
  25. package/.output/public/assets/rotate-ccw-DLv9EfCX.js +1 -0
  26. package/.output/public/assets/runners-DzX-RU3i.js +2 -0
  27. package/.output/public/assets/select-C9grIFwG.js +1 -0
  28. package/.output/public/assets/setting-fields-DUc3npgh.js +3 -0
  29. package/.output/public/assets/styles-B9B98zLk.css +2 -0
  30. package/.output/public/assets/switch-DwABVI6d.js +1 -0
  31. package/.output/public/assets/task-list-Ddk8-qt7.js +1 -0
  32. package/.output/public/assets/textarea-Ds-gIYUQ.js +1 -0
  33. package/.output/public/assets/time-goBWd-5J.js +1 -0
  34. package/.output/public/assets/use-live-tasks-DZNNuECy.js +1 -0
  35. package/.output/public/assets/useCompositeListItem-P4Nmmn7j.js +1 -0
  36. package/.output/public/assets/usePositioner-B7i9Ejzh.js +1 -0
  37. package/.output/server/_chunks/ssr-renderer.mjs +1 -1
  38. package/.output/server/_connectorId-COuQN02a.mjs +512 -0
  39. package/.output/server/_libs/@base-ui/react+[...].mjs +1410 -804
  40. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  41. package/.output/server/_libs/lucide-react.mjs +93 -84
  42. package/.output/server/_libs/next-themes.mjs +1 -1
  43. package/.output/server/_libs/sonner.mjs +1 -1
  44. package/.output/server/_loopId-CZh6_Yhr.mjs +310 -0
  45. package/.output/server/_ssr/{callback-D20cVD2w.mjs → access-DTttr563.mjs} +37 -20
  46. package/.output/server/_ssr/{agents-CrUmIQpi.mjs → agents-B_qo1gpL.mjs} +196 -7
  47. package/.output/server/_ssr/{agents-Dms_6TBF.mjs → agents-Bz10gz_m.mjs} +8 -8
  48. package/.output/server/_ssr/agents-CjbOUp3Z.mjs +206 -0
  49. package/.output/server/_ssr/{alert-DQvzE7bu.mjs → alert-C2WBWfzo.mjs} +2 -2
  50. package/.output/server/_ssr/card-r1cV-PCN.mjs +36 -0
  51. package/.output/server/_ssr/{client-BtVRVhQ2.mjs → client-Dk3SD8Il.mjs} +5 -1
  52. package/.output/server/_ssr/connection-status-BogAL-Fj.mjs +32 -0
  53. package/.output/server/_ssr/{connections-ypex3qUd.mjs → connections-Cqvb6Gw2.mjs} +1109 -263
  54. package/.output/server/_ssr/connector-icon-DrhVbhs6.mjs +190 -0
  55. package/.output/server/_ssr/connectors-BfS5tQKr.mjs +81 -0
  56. package/.output/server/_ssr/{connectors-BZly0yGF.mjs → connectors-DJI-rzPU.mjs} +21 -10
  57. package/.output/server/_ssr/{createServerRpc-DPDrnFdH.mjs → createServerRpc-DDrp2nLT.mjs} +2 -2
  58. package/.output/server/_ssr/{gaps-D2Zh8sEo.mjs → gaps-CcvHiLcj.mjs} +18 -10
  59. package/.output/server/_ssr/inbox-D1ef761v.mjs +178 -0
  60. package/.output/server/_ssr/loop-form-DWwOR05R.mjs +346 -0
  61. package/.output/server/_ssr/{loops-Og_G3KyZ.mjs → loops-6WhUe_lc.mjs} +228 -25
  62. package/.output/server/_ssr/loops-DHPjzG-t.mjs +217 -0
  63. package/.output/server/_ssr/new-ChKBdcXJ.mjs +245 -0
  64. package/.output/server/_ssr/page-DZVxKvdn.mjs +120 -0
  65. package/.output/server/_ssr/{router-BbrBz36P.mjs → router-DdwREiOf.mjs} +167 -489
  66. package/.output/server/_ssr/runners-CK9Cx9fS.mjs +217 -0
  67. package/.output/server/_ssr/runners-CrznFPHt.mjs +140 -0
  68. package/.output/server/_ssr/runners-W-pgo5yo.mjs +55 -0
  69. package/.output/server/_ssr/{secrets-CRnpwGBh.mjs → secrets-CV5scFlt.mjs} +2 -2
  70. package/.output/server/_ssr/select-BtndHhHb.mjs +76 -0
  71. package/.output/server/_ssr/{setting-fields-mpXfGftI.mjs → setting-fields-LNgywAW7.mjs} +25 -20
  72. package/.output/server/_ssr/ssr.mjs +49 -40
  73. package/.output/server/_ssr/{start-BgTj-h_C.mjs → start-C_j-e4vF.mjs} +10 -6
  74. package/.output/server/_ssr/{switch-1pnd_bPJ.mjs → switch-odxNO_S4.mjs} +3 -3
  75. package/.output/server/_ssr/task-list-Dnmq_WeE.mjs +150 -0
  76. package/.output/server/_ssr/{tasks-LIi-W4cy.mjs → tasks-D-kXLgVP.mjs} +13 -13
  77. package/.output/server/_ssr/{tasks-B_QmDFVs.mjs → tasks-D7aUvA33.mjs} +55 -17
  78. package/.output/server/_ssr/{textarea-CHJTKtsJ.mjs → textarea-D2c_vTIC.mjs} +2 -2
  79. package/.output/server/_ssr/time-Cy8TZP0Z.mjs +13 -0
  80. package/.output/server/_ssr/use-live-tasks-BzhTkEOL.mjs +40 -0
  81. package/.output/server/_tanstack-start-manifest_v-2HUdn-2E.mjs +166 -0
  82. package/.output/server/_taskId-D3KkNJW1.mjs +263 -0
  83. package/.output/server/index.mjs +204 -162
  84. package/README.md +54 -19
  85. package/dist/cli.mjs +185 -67
  86. package/dist/dispatcher.mjs +1443 -277
  87. package/dist/runner.mjs +333 -46
  88. package/drizzle/0025_prompt_tasks_done.sql +2 -0
  89. package/drizzle/0026_guidance_into_prompt.sql +3 -0
  90. package/drizzle/0027_relative_folders.sql +5 -0
  91. package/drizzle/0028_runner_version.sql +5 -0
  92. package/drizzle/meta/_journal.json +29 -1
  93. package/package.json +23 -17
  94. package/.output/public/assets/_connectorId-m67WbiJ8.js +0 -1
  95. package/.output/public/assets/_loopId-Bf4K5sdd.js +0 -1
  96. package/.output/public/assets/_taskId-eCKkcVOx.js +0 -1
  97. package/.output/public/assets/alert-BT2EVMJP.js +0 -1
  98. package/.output/public/assets/arrow-left-CWUZt6k2.js +0 -1
  99. package/.output/public/assets/arrow-right-dy_oOg_c.js +0 -1
  100. package/.output/public/assets/connection-status-DCqqmY_6.js +0 -1
  101. package/.output/public/assets/connector-icon-qGpNsPgH.js +0 -1
  102. package/.output/public/assets/connectors-DxXlc9zF.js +0 -1
  103. package/.output/public/assets/external-link-DC11orFA.js +0 -1
  104. package/.output/public/assets/gaps-CoTA-PzA.js +0 -1
  105. package/.output/public/assets/inbox-CLEXUXCI.js +0 -1
  106. package/.output/public/assets/index-Cndsw84p.js +0 -16
  107. package/.output/public/assets/loader-B4Epchm_.js +0 -1
  108. package/.output/public/assets/loop-form-BvizlZdR.js +0 -1
  109. package/.output/public/assets/loops-Dbe_mf5T.js +0 -1
  110. package/.output/public/assets/new-DPEaXKAY.js +0 -1
  111. package/.output/public/assets/play-DD4xQgvi.js +0 -1
  112. package/.output/public/assets/preload-helper-CfdAYKD7.js +0 -3
  113. package/.output/public/assets/rolldown-runtime-CbXtAM7H.js +0 -1
  114. package/.output/public/assets/runners-BVBggS5B.js +0 -1
  115. package/.output/public/assets/select-Cppu-hti.js +0 -1
  116. package/.output/public/assets/setting-fields-BT1naVJX.js +0 -3
  117. package/.output/public/assets/styles-D3sonCfX.css +0 -2
  118. package/.output/public/assets/switch-hRIbNGOj.js +0 -1
  119. package/.output/public/assets/task-list-CrptnI1N.js +0 -1
  120. package/.output/public/assets/textarea-Fyuzi6Vp.js +0 -1
  121. package/.output/public/assets/trash-2-BXZzBsoV.js +0 -1
  122. package/.output/public/assets/useValueChanged-VVyq4hb9.js +0 -1
  123. package/.output/server/_connectorId-DcSnoRjk.mjs +0 -421
  124. package/.output/server/_loopId-G_TAkYtS.mjs +0 -272
  125. package/.output/server/_ssr/access-B7TcOZ7F.mjs +0 -42
  126. package/.output/server/_ssr/connection-status-BZr3Y-IC.mjs +0 -31
  127. package/.output/server/_ssr/connector-icon-CFrrHjzR.mjs +0 -37
  128. package/.output/server/_ssr/connectors-4yzKb0KG.mjs +0 -106
  129. package/.output/server/_ssr/inbox-C2VBGLa5.mjs +0 -114
  130. package/.output/server/_ssr/loop-form-D-U-kU4k.mjs +0 -413
  131. package/.output/server/_ssr/loops-D0XJ8eBb.mjs +0 -222
  132. package/.output/server/_ssr/new-CajvgIog.mjs +0 -169
  133. package/.output/server/_ssr/runners-B_WuF8UJ.mjs +0 -32
  134. package/.output/server/_ssr/runners-D-0_lfiq.mjs +0 -106
  135. package/.output/server/_ssr/runners-zqF_zbEc.mjs +0 -116
  136. package/.output/server/_ssr/task-list-BnQJ94Dk.mjs +0 -187
  137. package/.output/server/_tanstack-start-manifest_v-BCHAazE8.mjs +0 -129
  138. package/.output/server/_taskId-CIv0ajIx.mjs +0 -256
package/dist/cli.mjs CHANGED
@@ -14,68 +14,17 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
- // src/lib/callback.ts
18
- var callback_exports = {};
19
- __export(callback_exports, {
20
- appOrigin: () => appOrigin,
21
- callbackUrl: () => callbackUrl,
22
- isLoopbackHost: () => isLoopbackHost,
17
+ // src/lib/lan.ts
18
+ var lan_exports = {};
19
+ __export(lan_exports, {
23
20
  lanIPv4: () => lanIPv4,
24
- loopableOrigin: () => loopableOrigin,
25
- registrableCallbackUrl: () => registrableCallbackUrl
21
+ loopableOrigin: () => loopableOrigin
26
22
  });
27
23
  import { networkInterfaces } from "node:os";
28
24
  function configuredOrigin() {
29
25
  const base = process.env.LOOPABLE_BASE_URL?.replace(/\/$/, "");
30
26
  return base || null;
31
27
  }
32
- function isLoopbackHost(hostname) {
33
- return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
34
- }
35
- function isPrivateHostname(hostname) {
36
- if (isLoopbackHost(hostname)) return true;
37
- const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
38
- if (v4) {
39
- const a = Number(v4[1]);
40
- const b = Number(v4[2]);
41
- if (a === 10) return true;
42
- if (a === 127) return true;
43
- if (a === 192 && b === 168) return true;
44
- if (a === 172 && b >= 16 && b <= 31) return true;
45
- if (a === 169 && b === 254) return true;
46
- return false;
47
- }
48
- const host = hostname.toLowerCase();
49
- if (host.includes(":")) {
50
- if (host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd")) return true;
51
- }
52
- return false;
53
- }
54
- function publicConfiguredOrigin() {
55
- const base = configuredOrigin();
56
- if (!base) return null;
57
- try {
58
- const url = new URL(base);
59
- if (isPrivateHostname(url.hostname)) return null;
60
- return base;
61
- } catch {
62
- return null;
63
- }
64
- }
65
- function appOrigin(requestUrl) {
66
- const hosted = publicConfiguredOrigin();
67
- if (hosted) return hosted;
68
- const url = new URL(requestUrl);
69
- return `http://127.0.0.1${url.port ? `:${url.port}` : ""}`;
70
- }
71
- function callbackUrl(requestUrl, connectorId) {
72
- return `${appOrigin(requestUrl)}/api/connectors/${connectorId}/callback`;
73
- }
74
- function registrableCallbackUrl(connectorId) {
75
- const hosted = publicConfiguredOrigin();
76
- if (hosted) return `${hosted}/api/connectors/${connectorId}/callback`;
77
- return `http://127.0.0.1/api/connectors/${connectorId}/callback`;
78
- }
79
28
  function lanIPv4(nics = networkInterfaces()) {
80
29
  const found = [];
81
30
  for (const addrs of Object.values(nics)) {
@@ -96,8 +45,8 @@ function loopableOrigin(nics = networkInterfaces()) {
96
45
  const port = process.env.PORT ?? process.env.NITRO_PORT ?? "4321";
97
46
  return `http://${lanIPv4(nics) ?? "127.0.0.1"}:${port}`;
98
47
  }
99
- var init_callback = __esm({
100
- "src/lib/callback.ts"() {
48
+ var init_lan = __esm({
49
+ "src/lib/lan.ts"() {
101
50
  "use strict";
102
51
  }
103
52
  });
@@ -2240,6 +2189,130 @@ var init_drizzle_orm = __esm({
2240
2189
  }
2241
2190
  });
2242
2191
 
2192
+ // package.json
2193
+ var package_default;
2194
+ var init_package = __esm({
2195
+ "package.json"() {
2196
+ package_default = {
2197
+ name: "loopable-cli",
2198
+ version: "0.2.1",
2199
+ description: "CLI to run Loopable: App, Dispatcher, and Runner.",
2200
+ license: "MIT",
2201
+ type: "module",
2202
+ bin: {
2203
+ loopable: "./dist/cli.mjs"
2204
+ },
2205
+ files: [
2206
+ ".output",
2207
+ "dist",
2208
+ "drizzle"
2209
+ ],
2210
+ engines: {
2211
+ node: ">=22"
2212
+ },
2213
+ packageManager: "pnpm@11.22.0",
2214
+ repository: {
2215
+ type: "git",
2216
+ url: "git+https://github.com/imsobear/loopable.git"
2217
+ },
2218
+ bugs: {
2219
+ url: "https://github.com/imsobear/loopable/issues"
2220
+ },
2221
+ homepage: "https://github.com/imsobear/loopable#readme",
2222
+ publishConfig: {
2223
+ access: "public"
2224
+ },
2225
+ imports: {
2226
+ "#/*": "./src/*"
2227
+ },
2228
+ scripts: {
2229
+ dev: "node --experimental-strip-types scripts/dev.ts",
2230
+ start: "node dist/cli.mjs start",
2231
+ dispatcher: "LOOPABLE_DEV=1 node --experimental-strip-types src/cli.ts dispatcher",
2232
+ runner: "LOOPABLE_DEV=1 node --experimental-strip-types src/cli.ts runner",
2233
+ "generate-routes": "tsr generate",
2234
+ build: "vite build && node scripts/build-cli.mjs",
2235
+ preview: "vite preview",
2236
+ test: "vitest run",
2237
+ "test:watch": "vitest",
2238
+ "db:generate": "drizzle-kit generate",
2239
+ "db:studio": "LOOPABLE_DEV=1 drizzle-kit studio",
2240
+ typecheck: "tsc --noEmit",
2241
+ release: "node scripts/release.mjs",
2242
+ deploy: "LOOPABLE_CF=1 vite build && wrangler deploy",
2243
+ "site:dev": "pnpm --filter loopable-site dev",
2244
+ "site:build": "pnpm --filter loopable-site build",
2245
+ "site:deploy": "pnpm --filter loopable-site deploy",
2246
+ prepublishOnly: "pnpm typecheck && pnpm test && pnpm build"
2247
+ },
2248
+ dependencies: {
2249
+ "@libsql/client": "^0.18.0"
2250
+ },
2251
+ devDependencies: {
2252
+ "@base-ui/react": "^1.7.0",
2253
+ "@cloudflare/containers": "^0.3.7",
2254
+ "@cloudflare/vite-plugin": "^1.54.10",
2255
+ "@fontsource-variable/geist": "^5.3.0",
2256
+ "@tailwindcss/vite": "4.1.18",
2257
+ "@tanstack/devtools-vite": "0.8.5",
2258
+ "@tanstack/react-devtools": "0.10.12",
2259
+ "@tanstack/react-router": "1.170.32",
2260
+ "@tanstack/react-router-devtools": "1.167.1",
2261
+ "@tanstack/react-start": "1.168.49",
2262
+ "@tanstack/router-cli": "1.167.33",
2263
+ "@types/node": "^22.10.2",
2264
+ "@types/qrcode": "^1.5.6",
2265
+ "@types/react": "^19.2.0",
2266
+ "@types/react-dom": "^19.2.0",
2267
+ "@vitejs/plugin-react": "^6.0.1",
2268
+ "class-variance-authority": "^0.7.1",
2269
+ clsx: "^2.1.1",
2270
+ "drizzle-kit": "^0.31.10",
2271
+ "drizzle-orm": "^0.45.2",
2272
+ esbuild: "^0.28.2",
2273
+ "lucide-react": "^1.39.0",
2274
+ "next-themes": "^0.4.6",
2275
+ nitro: "3.0.260610-beta",
2276
+ qrcode: "^1.5.4",
2277
+ react: "19.2.0",
2278
+ "react-dom": "19.2.0",
2279
+ shadcn: "^4.19.1",
2280
+ sonner: "^2.0.8",
2281
+ "tailwind-merge": "^3.6.0",
2282
+ tailwindcss: "4.1.18",
2283
+ "tw-animate-css": "^1.4.0",
2284
+ typescript: "^6.0.2",
2285
+ vite: "^8.0.0",
2286
+ vitest: "^4.1.11",
2287
+ wrangler: "^4.132.0"
2288
+ }
2289
+ };
2290
+ }
2291
+ });
2292
+
2293
+ // src/lib/version.ts
2294
+ function olderThan(a, b) {
2295
+ if (!a) return true;
2296
+ const pa = a.split(".").map((part) => Number.parseInt(part, 10));
2297
+ const pb = b.split(".").map((part) => Number.parseInt(part, 10));
2298
+ for (let i = 0; i < 3; i++) {
2299
+ const x = pa[i] ?? 0;
2300
+ const y = pb[i] ?? 0;
2301
+ if (Number.isNaN(x)) return true;
2302
+ if (x !== y) return x < y;
2303
+ }
2304
+ return false;
2305
+ }
2306
+ var VERSION, RUNNER_PROTOCOL, LEGACY_RUNNER_PROTOCOL;
2307
+ var init_version2 = __esm({
2308
+ "src/lib/version.ts"() {
2309
+ init_package();
2310
+ VERSION = package_default.version;
2311
+ RUNNER_PROTOCOL = 2;
2312
+ LEGACY_RUNNER_PROTOCOL = 1;
2313
+ }
2314
+ });
2315
+
2243
2316
  // node_modules/.pnpm/drizzle-orm@0.45.2_@libsql+client@0.18.0_@types+better-sqlite3@9.6.0_better-sqlite3@13.0.3/node_modules/drizzle-orm/selection-proxy.js
2244
2317
  var SelectionProxyHandler;
2245
2318
  var init_selection_proxy = __esm({
@@ -6756,6 +6829,10 @@ var init_schema = __esm({
6756
6829
  status: text("status").$type().notNull().default("offline"),
6757
6830
  inventory: text("inventory", { mode: "json" }).$type().notNull().default([]),
6758
6831
  lastSeenAt: integer("last_seen_at", { mode: "timestamp_ms" }),
6832
+ /** The runner's loopable-cli version. Null for runners before 0.3. */
6833
+ version: text("version"),
6834
+ /** What jobs it understands; see RUNNER_PROTOCOL. Null means the first. */
6835
+ protocol: integer("protocol"),
6759
6836
  createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
6760
6837
  });
6761
6838
  }
@@ -6963,11 +7040,21 @@ __export(runners_exports, {
6963
7040
  listRunners: () => listRunners,
6964
7041
  onlineRunnerCount: () => onlineRunnerCount,
6965
7042
  pickRunner: () => pickRunner,
7043
+ protocolOf: () => protocolOf,
6966
7044
  rotateJoinToken: () => rotateJoinToken,
6967
7045
  runnerIdForToken: () => runnerIdForToken
6968
7046
  });
6969
7047
  import { randomBytes, randomUUID } from "node:crypto";
6970
7048
  import { hostname as osHostname } from "node:os";
7049
+ function protocolOf(row) {
7050
+ return row.protocol ?? LEGACY_RUNNER_PROTOCOL;
7051
+ }
7052
+ function buildFields(build) {
7053
+ return {
7054
+ version: typeof build.version === "string" && build.version ? build.version : null,
7055
+ protocol: typeof build.protocol === "number" ? build.protocol : null
7056
+ };
7057
+ }
6971
7058
  function isRunnerOnline(row, now = Date.now()) {
6972
7059
  if (row.status !== "online") return false;
6973
7060
  if (!row.lastSeenAt) return false;
@@ -6981,7 +7068,10 @@ function toView(row, now = Date.now()) {
6981
7068
  hostname: row.hostname,
6982
7069
  status: online ? "online" : "offline",
6983
7070
  inventory: row.inventory,
6984
- lastSeenAt: row.lastSeenAt?.toISOString() ?? null
7071
+ lastSeenAt: row.lastSeenAt?.toISOString() ?? null,
7072
+ version: row.version,
7073
+ protocol: protocolOf(row),
7074
+ outdated: protocolOf(row) < RUNNER_PROTOCOL || olderThan(row.version, VERSION)
6985
7075
  };
6986
7076
  }
6987
7077
  async function listRunners() {
@@ -7011,11 +7101,13 @@ async function busyRunnerIds() {
7011
7101
  )
7012
7102
  );
7013
7103
  }
7014
- async function availableAgentIds(requiresHost = false) {
7104
+ async function availableAgentIds(requiresHost = false, minProtocol = 0) {
7015
7105
  const local = osHostname();
7016
7106
  return [
7017
7107
  ...new Set(
7018
- (await listRunners()).filter((row) => row.status === "online" && (!requiresHost || row.hostname === local)).flatMap(
7108
+ (await listRunners()).filter(
7109
+ (row) => row.status === "online" && (!requiresHost || row.hostname === local) && row.protocol >= minProtocol
7110
+ ).flatMap(
7019
7111
  (row) => row.inventory.filter((entry) => entry.installed && entry.signedIn).map((entry) => entry.agentId)
7020
7112
  )
7021
7113
  )
@@ -7024,9 +7116,11 @@ async function availableAgentIds(requiresHost = false) {
7024
7116
  async function pickRunner(input) {
7025
7117
  const now = Date.now();
7026
7118
  const local = osHostname();
7027
- let candidates = (await db().select().from(runners).all()).filter(
7119
+ const minProtocol = input.minProtocol ?? 0;
7120
+ const capable = (await db().select().from(runners).all()).filter(
7028
7121
  (row) => isRunnerOnline(row, now) && runnerCanRun(row, input.agentId)
7029
7122
  );
7123
+ let candidates = capable.filter((row) => protocolOf(row) >= minProtocol);
7030
7124
  if (input.requiresHost) {
7031
7125
  candidates = candidates.filter((row) => row.hostname === local);
7032
7126
  }
@@ -7037,6 +7131,11 @@ async function pickRunner(input) {
7037
7131
  if (input.requiresHost) {
7038
7132
  throw new Error("This job needs a runner on the same host as Loopable.");
7039
7133
  }
7134
+ if (capable.length > 0) {
7135
+ throw new Error(
7136
+ "Every runner with this agent is too old for this job. Upgrade them with npm install -g loopable-cli."
7137
+ );
7138
+ }
7040
7139
  throw new Error("No runner is online that can run this agent.");
7041
7140
  }
7042
7141
  async function getJoinToken() {
@@ -7051,10 +7150,10 @@ async function rotateJoinToken() {
7051
7150
  await secretStore().set(joinTokenKey(), token);
7052
7151
  return token;
7053
7152
  }
7054
- async function heartbeatRunner(runnerId, inventory) {
7153
+ async function heartbeatRunner(runnerId, inventory, build = {}) {
7055
7154
  const row = await getRunner(runnerId);
7056
7155
  if (!row) throw new Error("Unknown runner.");
7057
- await db().update(runners).set({ status: "online", inventory, lastSeenAt: /* @__PURE__ */ new Date() }).where(eq(runners.id, runnerId)).run();
7156
+ await db().update(runners).set({ status: "online", inventory, lastSeenAt: /* @__PURE__ */ new Date(), ...buildFields(build) }).where(eq(runners.id, runnerId)).run();
7058
7157
  return toView(await getRunner(runnerId));
7059
7158
  }
7060
7159
  async function joinRunner(input) {
@@ -7073,7 +7172,8 @@ async function joinRunner(input) {
7073
7172
  status: "online",
7074
7173
  inventory: input.inventory,
7075
7174
  lastSeenAt: now,
7076
- hostname: input.hostname
7175
+ hostname: input.hostname,
7176
+ ...buildFields(input)
7077
7177
  }).where(eq(runners.id, id)).run();
7078
7178
  } else {
7079
7179
  await db().insert(runners).values({
@@ -7082,7 +7182,8 @@ async function joinRunner(input) {
7082
7182
  hostname: input.hostname,
7083
7183
  status: "online",
7084
7184
  inventory: input.inventory,
7085
- lastSeenAt: now
7185
+ lastSeenAt: now,
7186
+ ...buildFields(input)
7086
7187
  }).run();
7087
7188
  }
7088
7189
  const token = randomBytes(24).toString("base64url");
@@ -7113,6 +7214,7 @@ var init_runners = __esm({
7113
7214
  "src/server/runners.ts"() {
7114
7215
  "use strict";
7115
7216
  init_drizzle_orm();
7217
+ init_version2();
7116
7218
  init_client();
7117
7219
  init_schema();
7118
7220
  init_secrets();
@@ -7122,7 +7224,7 @@ var init_runners = __esm({
7122
7224
 
7123
7225
  // src/cli.ts
7124
7226
  import { spawn } from "node:child_process";
7125
- import { existsSync as existsSync2 } from "node:fs";
7227
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
7126
7228
  import { dirname, join as join3 } from "node:path";
7127
7229
  import { createInterface } from "node:readline";
7128
7230
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -7141,8 +7243,18 @@ function usage() {
7141
7243
 
7142
7244
  loopable dispatcher
7143
7245
  Dispatcher only.
7246
+
7247
+ loopable --version
7144
7248
  `;
7145
7249
  }
7250
+ function cliVersion() {
7251
+ try {
7252
+ const pkg = JSON.parse(readFileSync2(join3(root, "package.json"), "utf8"));
7253
+ return pkg.version;
7254
+ } catch {
7255
+ return "unknown";
7256
+ }
7257
+ }
7146
7258
  function fail(message) {
7147
7259
  process.stderr.write(`${message}
7148
7260
  `);
@@ -7162,6 +7274,7 @@ function parse(argv) {
7162
7274
  return value;
7163
7275
  };
7164
7276
  if (arg === "-h" || arg === "--help") return { command: "help", flags };
7277
+ if (arg === "-v" || arg === "--version") return { command: "version", flags };
7165
7278
  if (arg === "--host") flags.host = next();
7166
7279
  else if (arg === "--port") flags.port = next();
7167
7280
  else if (arg === "--url") flags.url = next();
@@ -7248,7 +7361,7 @@ async function waitForApp(port) {
7248
7361
  }
7249
7362
  async function printRunnerHint(flags) {
7250
7363
  await waitForApp(flags.port);
7251
- const { lanIPv4: lanIPv42 } = await Promise.resolve().then(() => (init_callback(), callback_exports));
7364
+ const { lanIPv4: lanIPv42 } = await Promise.resolve().then(() => (init_lan(), lan_exports));
7252
7365
  const { getJoinToken: getJoinToken2 } = await Promise.resolve().then(() => (init_runners(), runners_exports));
7253
7366
  const ip = lanIPv42() ?? "127.0.0.1";
7254
7367
  const url = `http://${ip}:${flags.port}`;
@@ -7279,6 +7392,11 @@ async function main() {
7279
7392
  process.stdout.write(usage());
7280
7393
  return;
7281
7394
  }
7395
+ if (command === "version") {
7396
+ process.stdout.write(`${cliVersion()}
7397
+ `);
7398
+ return;
7399
+ }
7282
7400
  if (command === "runner") {
7283
7401
  if (flags.url) process.env.LOOPABLE_URL = flags.url;
7284
7402
  if (flags.token) process.env.LOOPABLE_RUNNER_TOKEN = flags.token;