@edspencer/paddock 0.0.1 → 0.57.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 (296) hide show
  1. package/README.md +69 -0
  2. package/install-notice.mjs +29 -0
  3. package/package.json +58 -5
  4. package/packages/server/dist/adoptable.d.ts +127 -0
  5. package/packages/server/dist/adoptable.js +315 -0
  6. package/packages/server/dist/app.d.ts +67 -0
  7. package/packages/server/dist/app.js +325 -0
  8. package/packages/server/dist/archive.d.ts +42 -0
  9. package/packages/server/dist/archive.js +125 -0
  10. package/packages/server/dist/attachments-config.d.ts +114 -0
  11. package/packages/server/dist/attachments-config.js +165 -0
  12. package/packages/server/dist/attachments-hint.d.ts +53 -0
  13. package/packages/server/dist/attachments-hint.js +140 -0
  14. package/packages/server/dist/attachments.d.ts +33 -0
  15. package/packages/server/dist/attachments.js +117 -0
  16. package/packages/server/dist/auth.d.ts +84 -0
  17. package/packages/server/dist/auth.js +247 -0
  18. package/packages/server/dist/background.d.ts +36 -0
  19. package/packages/server/dist/background.js +137 -0
  20. package/packages/server/dist/bind-safety.d.ts +56 -0
  21. package/packages/server/dist/bind-safety.js +51 -0
  22. package/packages/server/dist/brand.d.ts +44 -0
  23. package/packages/server/dist/brand.js +88 -0
  24. package/packages/server/dist/chat-dto.d.ts +210 -0
  25. package/packages/server/dist/chat-dto.js +308 -0
  26. package/packages/server/dist/cli/import-chats.d.ts +21 -0
  27. package/packages/server/dist/cli/import-chats.js +197 -0
  28. package/packages/server/dist/cli/paddock.d.ts +2 -0
  29. package/packages/server/dist/cli/paddock.js +247 -0
  30. package/packages/server/dist/concurrency.d.ts +57 -0
  31. package/packages/server/dist/concurrency.js +71 -0
  32. package/packages/server/dist/config.d.ts +538 -0
  33. package/packages/server/dist/config.js +625 -0
  34. package/packages/server/dist/curation-config.d.ts +55 -0
  35. package/packages/server/dist/curation-config.js +64 -0
  36. package/packages/server/dist/environment-prompt.d.ts +52 -0
  37. package/packages/server/dist/environment-prompt.js +61 -0
  38. package/packages/server/dist/event-bus.d.ts +87 -0
  39. package/packages/server/dist/event-bus.js +48 -0
  40. package/packages/server/dist/git.d.ts +128 -0
  41. package/packages/server/dist/git.js +446 -0
  42. package/packages/server/dist/github-auth.d.ts +44 -0
  43. package/packages/server/dist/github-auth.js +169 -0
  44. package/packages/server/dist/herdctl-agent-config.d.ts +98 -0
  45. package/packages/server/dist/herdctl-agent-config.js +331 -0
  46. package/packages/server/dist/herdctl-agent-names.d.ts +172 -0
  47. package/packages/server/dist/herdctl-agent-names.js +228 -0
  48. package/packages/server/dist/herdctl-jobs-index.d.ts +65 -0
  49. package/packages/server/dist/herdctl-jobs-index.js +225 -0
  50. package/packages/server/dist/herdctl-jobs.d.ts +98 -0
  51. package/packages/server/dist/herdctl-jobs.js +225 -0
  52. package/packages/server/dist/herdctl.d.ts +623 -0
  53. package/packages/server/dist/herdctl.js +1279 -0
  54. package/packages/server/dist/hook-config.d.ts +217 -0
  55. package/packages/server/dist/hook-config.js +224 -0
  56. package/packages/server/dist/http-bytes.d.ts +24 -0
  57. package/packages/server/dist/http-bytes.js +52 -0
  58. package/packages/server/dist/index.d.ts +1 -0
  59. package/packages/server/dist/index.js +14 -0
  60. package/packages/server/dist/instance-config.d.ts +143 -0
  61. package/packages/server/dist/instance-config.js +385 -0
  62. package/packages/server/dist/localcommand.d.ts +23 -0
  63. package/packages/server/dist/localcommand.js +140 -0
  64. package/packages/server/dist/management-auth.d.ts +99 -0
  65. package/packages/server/dist/management-auth.js +165 -0
  66. package/packages/server/dist/management-config.d.ts +164 -0
  67. package/packages/server/dist/management-config.js +286 -0
  68. package/packages/server/dist/management-mcp-server.d.ts +20 -0
  69. package/packages/server/dist/management-mcp-server.js +95 -0
  70. package/packages/server/dist/management-metadata.d.ts +67 -0
  71. package/packages/server/dist/management-metadata.js +56 -0
  72. package/packages/server/dist/management-ops.d.ts +110 -0
  73. package/packages/server/dist/management-ops.js +449 -0
  74. package/packages/server/dist/management-policy.d.ts +172 -0
  75. package/packages/server/dist/management-policy.js +259 -0
  76. package/packages/server/dist/message-provenance.d.ts +112 -0
  77. package/packages/server/dist/message-provenance.js +260 -0
  78. package/packages/server/dist/models.d.ts +164 -0
  79. package/packages/server/dist/models.js +208 -0
  80. package/packages/server/dist/openapi.d.ts +66 -0
  81. package/packages/server/dist/openapi.js +183 -0
  82. package/packages/server/dist/parent-detach.d.ts +30 -0
  83. package/packages/server/dist/parent-detach.js +109 -0
  84. package/packages/server/dist/preload.d.ts +43 -0
  85. package/packages/server/dist/preload.js +55 -0
  86. package/packages/server/dist/project-files.d.ts +76 -0
  87. package/packages/server/dist/project-files.js +191 -0
  88. package/packages/server/dist/project-mime.d.ts +35 -0
  89. package/packages/server/dist/project-mime.js +55 -0
  90. package/packages/server/dist/project-paths.d.ts +88 -0
  91. package/packages/server/dist/project-paths.js +126 -0
  92. package/packages/server/dist/project-types.d.ts +316 -0
  93. package/packages/server/dist/project-types.js +48 -0
  94. package/packages/server/dist/projects.d.ts +202 -0
  95. package/packages/server/dist/projects.js +894 -0
  96. package/packages/server/dist/queued-message.d.ts +35 -0
  97. package/packages/server/dist/queued-message.js +116 -0
  98. package/packages/server/dist/read-state.d.ts +46 -0
  99. package/packages/server/dist/read-state.js +139 -0
  100. package/packages/server/dist/recovery-config.d.ts +111 -0
  101. package/packages/server/dist/recovery-config.js +105 -0
  102. package/packages/server/dist/recovery.d.ts +262 -0
  103. package/packages/server/dist/recovery.js +490 -0
  104. package/packages/server/dist/resume-drain.d.ts +122 -0
  105. package/packages/server/dist/resume-drain.js +158 -0
  106. package/packages/server/dist/route-context.d.ts +168 -0
  107. package/packages/server/dist/route-context.js +39 -0
  108. package/packages/server/dist/route-errors.d.ts +8 -0
  109. package/packages/server/dist/route-errors.js +13 -0
  110. package/packages/server/dist/routes/chats.d.ts +12 -0
  111. package/packages/server/dist/routes/chats.js +1406 -0
  112. package/packages/server/dist/routes/git.d.ts +13 -0
  113. package/packages/server/dist/routes/git.js +236 -0
  114. package/packages/server/dist/routes/mcp.d.ts +83 -0
  115. package/packages/server/dist/routes/mcp.js +250 -0
  116. package/packages/server/dist/routes/meta.d.ts +15 -0
  117. package/packages/server/dist/routes/meta.js +447 -0
  118. package/packages/server/dist/routes/projects.d.ts +25 -0
  119. package/packages/server/dist/routes/projects.js +736 -0
  120. package/packages/server/dist/routes/triggers.d.ts +21 -0
  121. package/packages/server/dist/routes/triggers.js +324 -0
  122. package/packages/server/dist/routes/workspace-mount.d.ts +48 -0
  123. package/packages/server/dist/routes/workspace-mount.js +32 -0
  124. package/packages/server/dist/routes.d.ts +26 -0
  125. package/packages/server/dist/routes.js +29 -0
  126. package/packages/server/dist/run-provenance.d.ts +109 -0
  127. package/packages/server/dist/run-provenance.js +206 -0
  128. package/packages/server/dist/runs.d.ts +92 -0
  129. package/packages/server/dist/runs.js +72 -0
  130. package/packages/server/dist/schedule-config.d.ts +68 -0
  131. package/packages/server/dist/schedule-config.js +75 -0
  132. package/packages/server/dist/schedule-session.d.ts +34 -0
  133. package/packages/server/dist/schedule-session.js +120 -0
  134. package/packages/server/dist/self-mcp-descriptions.d.ts +20 -0
  135. package/packages/server/dist/self-mcp-descriptions.js +145 -0
  136. package/packages/server/dist/self-mcp-read.d.ts +15 -0
  137. package/packages/server/dist/self-mcp-read.js +97 -0
  138. package/packages/server/dist/self-mcp-triggers.d.ts +30 -0
  139. package/packages/server/dist/self-mcp-triggers.js +144 -0
  140. package/packages/server/dist/self-mcp-types.d.ts +248 -0
  141. package/packages/server/dist/self-mcp-types.js +10 -0
  142. package/packages/server/dist/self-mcp-util.d.ts +73 -0
  143. package/packages/server/dist/self-mcp-util.js +157 -0
  144. package/packages/server/dist/self-mcp-write.d.ts +35 -0
  145. package/packages/server/dist/self-mcp-write.js +215 -0
  146. package/packages/server/dist/self-mcp.d.ts +95 -0
  147. package/packages/server/dist/self-mcp.js +448 -0
  148. package/packages/server/dist/send-file-mcp.d.ts +46 -0
  149. package/packages/server/dist/send-file-mcp.js +281 -0
  150. package/packages/server/dist/session-hub.d.ts +186 -0
  151. package/packages/server/dist/session-hub.js +291 -0
  152. package/packages/server/dist/spawn-capability.d.ts +99 -0
  153. package/packages/server/dist/spawn-capability.js +106 -0
  154. package/packages/server/dist/star.d.ts +26 -0
  155. package/packages/server/dist/star.js +94 -0
  156. package/packages/server/dist/start.d.ts +1 -0
  157. package/packages/server/dist/start.js +21 -0
  158. package/packages/server/dist/subagents.d.ts +337 -0
  159. package/packages/server/dist/subagents.js +626 -0
  160. package/packages/server/dist/sweep.d.ts +122 -0
  161. package/packages/server/dist/sweep.js +696 -0
  162. package/packages/server/dist/tooldetails.d.ts +33 -0
  163. package/packages/server/dist/tooldetails.js +466 -0
  164. package/packages/server/dist/transcribe.d.ts +69 -0
  165. package/packages/server/dist/transcribe.js +119 -0
  166. package/packages/server/dist/transcripts.d.ts +42 -0
  167. package/packages/server/dist/transcripts.js +164 -0
  168. package/packages/server/dist/trigger-config.d.ts +278 -0
  169. package/packages/server/dist/trigger-config.js +417 -0
  170. package/packages/server/dist/trigger-runtime.d.ts +94 -0
  171. package/packages/server/dist/trigger-runtime.js +82 -0
  172. package/packages/server/dist/trigger-session.d.ts +34 -0
  173. package/packages/server/dist/trigger-session.js +122 -0
  174. package/packages/server/dist/triggers.d.ts +83 -0
  175. package/packages/server/dist/triggers.js +124 -0
  176. package/packages/server/dist/trusted-proxy.d.ts +57 -0
  177. package/packages/server/dist/trusted-proxy.js +157 -0
  178. package/packages/server/dist/turn-notice.d.ts +154 -0
  179. package/packages/server/dist/turn-notice.js +271 -0
  180. package/packages/server/dist/unread.d.ts +36 -0
  181. package/packages/server/dist/unread.js +123 -0
  182. package/packages/server/dist/usage.d.ts +66 -0
  183. package/packages/server/dist/usage.js +243 -0
  184. package/packages/server/dist/wake-injection.d.ts +156 -0
  185. package/packages/server/dist/wake-injection.js +136 -0
  186. package/packages/server/dist/whisper-local.d.ts +9 -0
  187. package/packages/server/dist/whisper-local.js +122 -0
  188. package/packages/server/dist/ws-context.d.ts +158 -0
  189. package/packages/server/dist/ws-context.js +1 -0
  190. package/packages/server/dist/ws-protocol.d.ts +423 -0
  191. package/packages/server/dist/ws-protocol.js +251 -0
  192. package/packages/server/dist/ws-self-mcp.d.ts +52 -0
  193. package/packages/server/dist/ws-self-mcp.js +28 -0
  194. package/packages/server/dist/ws-triggers.d.ts +29 -0
  195. package/packages/server/dist/ws-triggers.js +261 -0
  196. package/packages/server/dist/ws-turn.d.ts +86 -0
  197. package/packages/server/dist/ws-turn.js +751 -0
  198. package/packages/server/dist/ws.d.ts +62 -0
  199. package/packages/server/dist/ws.js +1024 -0
  200. package/packages/server/package.json +6 -0
  201. package/packages/web/dist/assets/InstanceConfigPage-Bypd_quL.js +2 -0
  202. package/packages/web/dist/assets/MarkdownRenderer-Co8w9iuX.js +30 -0
  203. package/packages/web/dist/assets/ProjectRedirect-DXrA24Cy.js +2 -0
  204. package/packages/web/dist/assets/ProjectView-B9opP7Sv.js +13 -0
  205. package/packages/web/dist/assets/ProjectsGrid-D2cwb2jY.js +2 -0
  206. package/packages/web/dist/assets/arc-B--FIRB8.js +2 -0
  207. package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-B6dRWZZ6.js +37 -0
  208. package/packages/web/dist/assets/bash-I8pq0VWm.js +2 -0
  209. package/packages/web/dist/assets/blockDiagram-GPEHLZMM-DLxFAvvf.js +133 -0
  210. package/packages/web/dist/assets/c-D2F1sQUL.js +2 -0
  211. package/packages/web/dist/assets/c4Diagram-AAUBKEIU-CNee5ks4.js +11 -0
  212. package/packages/web/dist/assets/channel-Bw0A52CB.js +2 -0
  213. package/packages/web/dist/assets/chunk-2J33WTMH-DObw03Xl.js +2 -0
  214. package/packages/web/dist/assets/chunk-4BX2VUAB-CH2SR6nN.js +2 -0
  215. package/packages/web/dist/assets/chunk-55IACEB6-WlVpegT3.js +2 -0
  216. package/packages/web/dist/assets/chunk-727SXJPM-g8GT9Rgd.js +207 -0
  217. package/packages/web/dist/assets/chunk-AQP2D5EJ-DbN4PsDF.js +232 -0
  218. package/packages/web/dist/assets/chunk-FMBD7UC4-DSN3BjFG.js +16 -0
  219. package/packages/web/dist/assets/chunk-ND2GUHAM-D9R4le0z.js +2 -0
  220. package/packages/web/dist/assets/chunk-QZHKN3VN-D4YgRaKH.js +2 -0
  221. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-itf_kt-q.js +2 -0
  222. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-itf_kt-q.js +2 -0
  223. package/packages/web/dist/assets/core-CysJ2gLj.js +4 -0
  224. package/packages/web/dist/assets/cose-bilkent-S5V4N54A-D1PB0KNu.js +2 -0
  225. package/packages/web/dist/assets/cpp-Bm3ii6kY.js +2 -0
  226. package/packages/web/dist/assets/csharp-BNpuYZMG.js +2 -0
  227. package/packages/web/dist/assets/css-DazXZka4.js +2 -0
  228. package/packages/web/dist/assets/cytoscape.esm-CUqq0XTU.js +332 -0
  229. package/packages/web/dist/assets/dagre-BM42HDAG-C6Uuq-dq.js +5 -0
  230. package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
  231. package/packages/web/dist/assets/diagram-2AECGRRQ-C9JvQ425.js +44 -0
  232. package/packages/web/dist/assets/diagram-5GNKFQAL-Cu6pdh2d.js +11 -0
  233. package/packages/web/dist/assets/diagram-KO2AKTUF-PmXcUQhY.js +4 -0
  234. package/packages/web/dist/assets/diagram-LMA3HP47-9ww6_9Iq.js +25 -0
  235. package/packages/web/dist/assets/diagram-OG6HWLK6-DWQVjv_n.js +25 -0
  236. package/packages/web/dist/assets/erDiagram-TEJ5UH35-6F7xTuff.js +86 -0
  237. package/packages/web/dist/assets/flowDiagram-I6XJVG4X-SCiGu7m6.js +163 -0
  238. package/packages/web/dist/assets/format-yf7Vf9kR.js +2 -0
  239. package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BNpPwxkL.js +293 -0
  240. package/packages/web/dist/assets/gitGraphDiagram-PVQCEYII-C0gyAJjW.js +107 -0
  241. package/packages/web/dist/assets/go-BPx_y7j8.js +2 -0
  242. package/packages/web/dist/assets/graph-BlwPajkw.js +2 -0
  243. package/packages/web/dist/assets/index-B-YuzkF6.js +71 -0
  244. package/packages/web/dist/assets/index-CvGiQddO.css +1 -0
  245. package/packages/web/dist/assets/infoDiagram-5YYISTIA-CJ2CFPfy.js +3 -0
  246. package/packages/web/dist/assets/ini-B3BVgEsG.js +2 -0
  247. package/packages/web/dist/assets/init-Gi6I4Gst.js +2 -0
  248. package/packages/web/dist/assets/ishikawaDiagram-YF4QCWOH-Cs0D32Ry.js +71 -0
  249. package/packages/web/dist/assets/java-B5VKqtH2.js +2 -0
  250. package/packages/web/dist/assets/javascript-BKRaQes9.js +2 -0
  251. package/packages/web/dist/assets/journeyDiagram-JHISSGLW-Bc9fuI2i.js +140 -0
  252. package/packages/web/dist/assets/json-DIYVocXf.js +2 -0
  253. package/packages/web/dist/assets/kanban-definition-UN3LZRKU-D_tC7QfC.js +90 -0
  254. package/packages/web/dist/assets/katex-C5jXJg4s.js +258 -0
  255. package/packages/web/dist/assets/kotlin-Cw-eF_bm.js +3 -0
  256. package/packages/web/dist/assets/lastTab-CU1zsF0j.js +2 -0
  257. package/packages/web/dist/assets/layout-Boy8vhSP.js +2 -0
  258. package/packages/web/dist/assets/linear-BzGHkF9K.js +2 -0
  259. package/packages/web/dist/assets/mermaid.core-DlbsYzE-.js +310 -0
  260. package/packages/web/dist/assets/mindmap-definition-RKZ34NQL-Bxeh-ToD.js +97 -0
  261. package/packages/web/dist/assets/ordinal-Cboi1Yqb.js +2 -0
  262. package/packages/web/dist/assets/php-CRdd4dfS.js +3 -0
  263. package/packages/web/dist/assets/pieDiagram-4H26LBE5-CDmilo8S.js +31 -0
  264. package/packages/web/dist/assets/python-kSlZHhJb.js +2 -0
  265. package/packages/web/dist/assets/quadrantDiagram-W4KKPZXB-D14-JyE9.js +8 -0
  266. package/packages/web/dist/assets/queued-LamEU0GH.js +2 -0
  267. package/packages/web/dist/assets/requirementDiagram-4Y6WPE33-CJ1PUGu-.js +85 -0
  268. package/packages/web/dist/assets/ruby-7T4ktxyv.js +2 -0
  269. package/packages/web/dist/assets/rust-HC3MBUhY.js +2 -0
  270. package/packages/web/dist/assets/sankeyDiagram-5OEKKPKP-BkUK73W0.js +41 -0
  271. package/packages/web/dist/assets/scss-DZLA7YXz.js +2 -0
  272. package/packages/web/dist/assets/sequenceDiagram-3UESZ5HK-sbcvsuLp.js +163 -0
  273. package/packages/web/dist/assets/sql-BC88IN-V.js +2 -0
  274. package/packages/web/dist/assets/stateDiagram-AJRCARHV-DV-7BdHK.js +2 -0
  275. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BlkGdXKW.js +2 -0
  276. package/packages/web/dist/assets/swift-Dn8kKO_1.js +2 -0
  277. package/packages/web/dist/assets/timeline-definition-PNZ67QCA-C5su21G_.js +121 -0
  278. package/packages/web/dist/assets/typescript-C2FFdlUC.js +2 -0
  279. package/packages/web/dist/assets/vennDiagram-CIIHVFJN-JNkIJAaQ.js +35 -0
  280. package/packages/web/dist/assets/wardley-L42UT6IY-qcz6t2vR.js +174 -0
  281. package/packages/web/dist/assets/wardleyDiagram-YWT4CUSO-CMrOIZ-c.js +79 -0
  282. package/packages/web/dist/assets/xml-BXBhIUeX.js +2 -0
  283. package/packages/web/dist/assets/xychartDiagram-2RQKCTM6-C9q6Oy9u.js +8 -0
  284. package/packages/web/dist/assets/yaml-drsDnIKT.js +2 -0
  285. package/packages/web/dist/favicon.ico +0 -0
  286. package/packages/web/dist/fonts/inter-latin.woff2 +0 -0
  287. package/packages/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
  288. package/packages/web/dist/icons/apple-touch-icon.png +0 -0
  289. package/packages/web/dist/icons/favicon-16.png +0 -0
  290. package/packages/web/dist/icons/favicon-32.png +0 -0
  291. package/packages/web/dist/icons/icon-192.png +0 -0
  292. package/packages/web/dist/icons/icon-512.png +0 -0
  293. package/packages/web/dist/icons/maskable-512.png +0 -0
  294. package/packages/web/dist/index.html +56 -0
  295. package/packages/web/dist/manifest.webmanifest +32 -0
  296. package/packages/web/dist/sw.js +139 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * One-shot importer for native Claude Code chats (#588) — the headless twin of
3
+ * `POST …/adopt-chats`.
4
+ *
5
+ * ## Why it lives here and not in `scripts/`
6
+ *
7
+ * It needs `loadPaddockConfig`, `ProjectStore` and `HerdctlService`, which are
8
+ * TypeScript modules in this package. `scripts/` is pinned to CommonJS by its
9
+ * own `package.json` (for the extensionless `pm` wrapper) and holds only plain
10
+ * `.mjs`, so a script there could not import them without a build step or a
11
+ * duplicated config parser — and a duplicated parser is exactly how the CLI and
12
+ * the server would come to disagree about which data dir or Claude home they are
13
+ * working on. Here it inherits this package's `"type": "module"`, its tsconfig
14
+ * and its `tsx`, and runs the SAME code the server runs.
15
+ *
16
+ * ## Deployment note
17
+ *
18
+ * The import needs READ access to the user's transcripts. In a container that
19
+ * usually means running this on the HOST against the data dir (`--data-dir`),
20
+ * or mounting `~/.claude` in and pointing `CLAUDE_HOME` at the mount. Detection
21
+ * silently finds nothing if the transcripts are not visible — which is why
22
+ * `--dry-run` prints the sources it found rather than only a count.
23
+ *
24
+ * ## Usage
25
+ *
26
+ * npm run import-chats -w @paddock/server -- --project <slug> [options]
27
+ *
28
+ * --project <slug> Workspace to import into. Required. Use "" (or --root)
29
+ * for the ROOT workspace, whose key IS the empty string.
30
+ * --root Sugar for `--project ""`.
31
+ * --from <dir> Import only this source working directory. Default: every
32
+ * source detection offers.
33
+ * --move Move transcripts instead of copying them. DESTRUCTIVE:
34
+ * the sessions disappear from the user's own history.
35
+ * --dry-run Report what would happen and write nothing at all.
36
+ * --data-dir <dir> Paddock data dir (same meaning as PADDOCK_DATA_DIR).
37
+ * Applied to the environment BEFORE the config is read.
38
+ * --json Emit the raw result as JSON instead of prose.
39
+ */
40
+ import { loadPaddockConfig } from "../config.js";
41
+ import { ProjectStore, ROOT_KEY } from "../projects.js";
42
+ import { HerdctlService } from "../herdctl.js";
43
+ import { RunProvenanceStore, ADOPTED_ROOT } from "../run-provenance.js";
44
+ /**
45
+ * Parse argv. Hand-rolled because this is the repo's FIRST script taking flags
46
+ * (`pm` parses its own, in CommonJS) — there is no convention to follow and no
47
+ * argument parser in the dependency tree, and adding one for six flags would be
48
+ * the larger change.
49
+ *
50
+ * `--project ""` must survive: the root workspace's key is the empty string, so
51
+ * a "missing value" check written as truthiness would reject the one workspace
52
+ * that always exists.
53
+ */
54
+ export function parseArgs(argv) {
55
+ const args = { move: false, dryRun: false, json: false, help: false };
56
+ for (let i = 0; i < argv.length; i++) {
57
+ const arg = argv[i];
58
+ const value = () => {
59
+ const next = argv[i + 1];
60
+ if (next === undefined || (next.startsWith("--") && next.length > 2)) {
61
+ throw new Error(`${arg} needs a value`);
62
+ }
63
+ i++;
64
+ return next;
65
+ };
66
+ switch (arg) {
67
+ case "--project":
68
+ args.project = value();
69
+ break;
70
+ case "--root":
71
+ args.project = ROOT_KEY;
72
+ break;
73
+ case "--from":
74
+ args.from = value();
75
+ break;
76
+ case "--move":
77
+ args.move = true;
78
+ break;
79
+ case "--dry-run":
80
+ args.dryRun = true;
81
+ break;
82
+ case "--data-dir":
83
+ args.dataDir = value();
84
+ break;
85
+ case "--json":
86
+ args.json = true;
87
+ break;
88
+ case "--help":
89
+ case "-h":
90
+ args.help = true;
91
+ break;
92
+ default:
93
+ throw new Error(`Unknown argument: ${arg}`);
94
+ }
95
+ }
96
+ return args;
97
+ }
98
+ const USAGE = `Import existing Claude Code CLI chats into a Paddock workspace.
99
+
100
+ npm run import-chats -w @paddock/server -- --project <slug> [options]
101
+
102
+ --project <slug> Workspace to import into (required). "" = the root workspace.
103
+ --root Sugar for --project ""
104
+ --from <dir> Import only this source working directory
105
+ --move Move transcripts instead of copying (DESTRUCTIVE)
106
+ --dry-run Report what would happen; write nothing
107
+ --data-dir <dir> Paddock data dir (same as PADDOCK_DATA_DIR)
108
+ --json Emit raw JSON
109
+ `;
110
+ async function main() {
111
+ const args = parseArgs(process.argv.slice(2));
112
+ if (args.help) {
113
+ process.stdout.write(USAGE);
114
+ return 0;
115
+ }
116
+ // `=== undefined`, not falsiness: "" is the root workspace, a real target.
117
+ if (args.project === undefined) {
118
+ process.stderr.write("--project is required (use --root for the root workspace)\n\n" + USAGE);
119
+ return 2;
120
+ }
121
+ // Must land in the environment BEFORE the config is read — every other path
122
+ // (projects root, state dir, herdctl config) is derived from it.
123
+ if (args.dataDir !== undefined)
124
+ process.env.PADDOCK_DATA_DIR = args.dataDir;
125
+ const cfg = loadPaddockConfig();
126
+ const projects = new ProjectStore(cfg.projectsRoot);
127
+ const herdctl = new HerdctlService(cfg);
128
+ const project = await projects.get(args.project);
129
+ // Register the keepers so the agent this imports INTO exists and its `.chats/`
130
+ // symlink is in place. `init` is the same call the server makes at boot; the
131
+ // scheduler is never started, so nothing fires while we run.
132
+ const rootWorkspace = await projects.get(ROOT_KEY);
133
+ await herdctl.init([...(await projects.list()), rootWorkspace]);
134
+ try {
135
+ const summary = await herdctl.listAdoptable(project);
136
+ if (summary.count === 0 && summary.filtered.length === 0) {
137
+ process.stdout.write(args.json
138
+ ? JSON.stringify({ adopted: [], skipped: [] }) + "\n"
139
+ : `Nothing to import for "${project.slug || "(root)"}".\n` +
140
+ `Working directory: ${project.workingDir}\n` +
141
+ `Claude home: ${cfg.claudeHome}\n` +
142
+ `If you expected chats here, check that this process can READ the transcripts\n` +
143
+ `(a container only sees what is mounted) and that CLAUDE_HOME points at them.\n`);
144
+ return 0;
145
+ }
146
+ const result = await herdctl.adoptChats(project, {
147
+ sourceCwd: args.from,
148
+ mode: args.move ? "move" : "copy",
149
+ dryRun: args.dryRun,
150
+ });
151
+ // Provenance is a server-side sidecar, so the CLI must stamp it too or a
152
+ // chat imported headlessly would badge differently from one imported in the
153
+ // UI. Skipped under --dry-run: it is a write.
154
+ if (!args.dryRun) {
155
+ const runProvenance = new RunProvenanceStore(cfg.dataDir);
156
+ for (const sessionId of result.adopted) {
157
+ await runProvenance.stampIfAbsent(sessionId, ADOPTED_ROOT).catch(() => undefined);
158
+ }
159
+ }
160
+ if (args.json) {
161
+ process.stdout.write(JSON.stringify({ ...result, sources: summary.sources }, null, 2) + "\n");
162
+ return 0;
163
+ }
164
+ const verb = args.dryRun ? "Would import" : "Imported";
165
+ const lines = [];
166
+ lines.push(`${verb} ${result.adopted.length} chat(s) into "${project.slug || "(root)"}".`);
167
+ for (const source of summary.sources) {
168
+ lines.push(` from ${source.sourceCwd} (${source.sessionIds.length} offered)`);
169
+ }
170
+ if (summary.filtered.length > 0) {
171
+ lines.push(` withheld as noise: ${summary.filtered.length}`);
172
+ for (const f of summary.filtered)
173
+ lines.push(` ${f.sessionId} ${f.reason}`);
174
+ }
175
+ if (result.skipped.length > 0) {
176
+ lines.push(` skipped: ${result.skipped.length}`);
177
+ for (const s of result.skipped)
178
+ lines.push(` ${s.sessionId} ${s.reason}`);
179
+ }
180
+ if (args.dryRun)
181
+ lines.push("Dry run — nothing was written.");
182
+ process.stdout.write(lines.join("\n") + "\n");
183
+ return 0;
184
+ }
185
+ finally {
186
+ await herdctl.stop().catch(() => undefined);
187
+ }
188
+ }
189
+ // Only run when invoked directly, so the arg parser stays unit-testable.
190
+ if (process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`) {
191
+ main()
192
+ .then((code) => process.exit(code))
193
+ .catch((err) => {
194
+ process.stderr.write(`${err.message ?? err}\n`);
195
+ process.exit(1);
196
+ });
197
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `paddock` — the npx / global-install entrypoint (#TBD).
4
+ *
5
+ * PROTOTYPE. This is the user-facing front door for `npx @edspencer/paddock`:
6
+ * someone who has never seen this repo runs one command and gets a working
7
+ * instance on localhost. Everything here is preflight and defaults — the server
8
+ * itself is unchanged, and `start()` is the same lifecycle `node dist/index.js`
9
+ * uses.
10
+ *
11
+ * The design constraint that shapes this file: the server is already
12
+ * *tolerant* — every setting has a default, an empty data dir self-initializes,
13
+ * and auth defaults to `none` on a loopback bind. So a bare `start()` would in
14
+ * fact boot. What it would NOT do is tell a first-time user why their chats
15
+ * fail, or where their data went. Hence:
16
+ *
17
+ * 1. Node version — checked explicitly, because the `engines` field only
18
+ * warns and the eventual failure is an opaque syntax error.
19
+ * 2. Data dir — defaults to `~/.paddock`, NOT the server's `./data`, which is
20
+ * relative to cwd and would litter whatever directory you happened to run
21
+ * npx from (and silently give you a different instance next time).
22
+ * 3. `claude` on PATH — prepended from our own node_modules/.bin. Chats do not
23
+ * need it (the SDK runtime resolves its own bundled binary), but the
24
+ * sweeper and triggers shell out to `claude` by name.
25
+ * 4. Credentials — a warning, never a hard failure: we cannot reliably detect
26
+ * a macOS Keychain login, so refusing to start would lock out valid users.
27
+ */
28
+ import fs from "node:fs";
29
+ import os from "node:os";
30
+ import path from "node:path";
31
+ import { fileURLToPath } from "node:url";
32
+ const MIN_NODE_MAJOR = 22;
33
+ /** This module's own directory: `<…>/packages/server/dist/cli`. */
34
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
35
+ /**
36
+ * Walk up from this module looking for `rel`, returning the containing dir.
37
+ *
38
+ * Two layouts have to work and they nest differently:
39
+ *
40
+ * repo `<repo>/packages/server/dist/cli/paddock.js`
41
+ * deps at `<repo>/packages/server/node_modules` AND `<repo>/node_modules`
42
+ * published `<pkg>/packages/server/dist/cli/paddock.js`
43
+ * deps at `<pkg>/node_modules` only — two levels FURTHER up
44
+ *
45
+ * A fixed `../..` hop is right for the repo and wrong for the published tarball,
46
+ * where it lands on `packages/server` and finds no `node_modules` at all. Walking
47
+ * up is correct for both without branching on which one we are in.
48
+ */
49
+ function findUp(rel) {
50
+ let dir = moduleDir;
51
+ for (;;) {
52
+ if (fs.existsSync(path.join(dir, rel)))
53
+ return dir;
54
+ const parent = path.dirname(dir);
55
+ if (parent === dir)
56
+ return undefined;
57
+ dir = parent;
58
+ }
59
+ }
60
+ /** Nearest ancestor holding a `package.json` — `packages/server` in both layouts. */
61
+ const packageRoot = findUp("package.json") ?? path.resolve(moduleDir, "../..");
62
+ function parseArgs(argv) {
63
+ const opts = { help: false, version: false };
64
+ for (let i = 0; i < argv.length; i++) {
65
+ const arg = argv[i];
66
+ const next = () => {
67
+ const v = argv[++i];
68
+ if (v === undefined)
69
+ fail(`${arg} needs a value`);
70
+ return v;
71
+ };
72
+ switch (arg) {
73
+ case "-p":
74
+ case "--port":
75
+ opts.port = next();
76
+ break;
77
+ case "--host":
78
+ opts.host = next();
79
+ break;
80
+ case "-d":
81
+ case "--data-dir":
82
+ opts.dataDir = next();
83
+ break;
84
+ case "-h":
85
+ case "--help":
86
+ opts.help = true;
87
+ break;
88
+ case "-v":
89
+ case "--version":
90
+ opts.version = true;
91
+ break;
92
+ default:
93
+ fail(`unknown option: ${arg}\nRun \`paddock --help\` for usage.`);
94
+ }
95
+ }
96
+ return opts;
97
+ }
98
+ function fail(message) {
99
+ console.error(`paddock: ${message}`);
100
+ process.exit(1);
101
+ }
102
+ function readVersion() {
103
+ try {
104
+ const raw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf8");
105
+ return JSON.parse(raw).version ?? "unknown";
106
+ }
107
+ catch {
108
+ return "unknown";
109
+ }
110
+ }
111
+ const USAGE = `paddock — run a Paddock instance locally
112
+
113
+ Usage
114
+ npx @edspencer/paddock [options]
115
+
116
+ Options
117
+ -p, --port <port> HTTP/WS port (default 4000, or $PORT)
118
+ --host <host> Bind address (default 127.0.0.1)
119
+ -d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)
120
+ -v, --version Print the Paddock version and exit
121
+ -h, --help Show this help
122
+
123
+ Credentials
124
+ Paddock drives Claude Code, so it needs Claude credentials in the
125
+ environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API
126
+ billing). If you already use Claude Code on this machine, an existing login
127
+ under ~/.claude is picked up automatically. Otherwise run \`claude setup-token\`.
128
+
129
+ Notes
130
+ Binds loopback with authentication disabled, which is safe for a laptop. To
131
+ expose it on a network, set PADDOCK_AUTH_MODE first — Paddock refuses to bind
132
+ a routable interface wide open. See AUTH.md.
133
+
134
+ Docs: https://github.com/edspencer/paddock`;
135
+ /** Node's own version gate. `engines` only warns by default, and the failure it
136
+ * eventually produces is an unexplained syntax error deep in a dependency. */
137
+ function checkNodeVersion() {
138
+ const major = Number(process.versions.node.split(".")[0]);
139
+ if (Number.isFinite(major) && major < MIN_NODE_MAJOR) {
140
+ fail(`Node ${process.versions.node} is too old — Paddock needs Node ${MIN_NODE_MAJOR}+.\n` +
141
+ `Upgrade Node, then re-run. (nodejs.org, or \`nvm install ${MIN_NODE_MAJOR}\`)`);
142
+ }
143
+ }
144
+ /**
145
+ * Put our bundled `claude` on PATH for child processes.
146
+ *
147
+ * Chat turns do NOT need this: they run herdctl's SDK runtime, which resolves
148
+ * the binary from the Claude Agent SDK's own platform package via
149
+ * `require.resolve` and never consults PATH. But the post-turn sweeper and all
150
+ * triggers are one-shot `trigger()` calls on the CLI runtime, which does
151
+ * `execa("claude", …)` — by name, without execa's `preferLocal`, so a local
152
+ * install is invisible to it unless PATH says otherwise.
153
+ *
154
+ * Without this, chats work and every sweep/trigger fails (logged, non-fatal).
155
+ */
156
+ function addBundledBinsToPath() {
157
+ const owner = findUp(path.join("node_modules", ".bin"));
158
+ if (owner === undefined)
159
+ return;
160
+ const binDir = path.join(owner, "node_modules", ".bin");
161
+ const sep = process.platform === "win32" ? ";" : ":";
162
+ const current = process.env.PATH ?? "";
163
+ if (current.split(sep).includes(binDir))
164
+ return;
165
+ process.env.PATH = current ? `${binDir}${sep}${current}` : binDir;
166
+ }
167
+ /**
168
+ * Best-effort credential check — WARNS, never blocks.
169
+ *
170
+ * Claude Code stores credentials differently per platform (a file under
171
+ * ~/.claude on Linux, the Keychain on macOS), so absence of a file does not
172
+ * prove absence of a login. Refusing to start on a false negative would be
173
+ * worse than a chat that fails with the runtime's own error, so this only
174
+ * prints guidance.
175
+ */
176
+ function warnIfNoCredentials(claudeHome) {
177
+ if (process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY)
178
+ return;
179
+ const looksLoggedIn = fs.existsSync(path.join(claudeHome, ".credentials.json")) ||
180
+ fs.existsSync(path.join(os.homedir(), ".claude.json"));
181
+ if (looksLoggedIn)
182
+ return;
183
+ console.warn([
184
+ "",
185
+ " ⚠ No Claude credentials found.",
186
+ "",
187
+ " Paddock will start, but chats will fail until it has credentials.",
188
+ " Fix with either:",
189
+ "",
190
+ " claude setup-token # Claude Max/Pro",
191
+ " export ANTHROPIC_API_KEY=sk-ant-… # API billing",
192
+ "",
193
+ ].join("\n"));
194
+ }
195
+ /**
196
+ * Report where the data lives, the first time we create it.
197
+ *
198
+ * `npx` is stateless enough that people reasonably assume the whole thing is
199
+ * ephemeral. It is not — projects, chat transcripts, and settings all persist
200
+ * here across runs, so say so once, at the moment the directory appears.
201
+ */
202
+ function noteFirstRun(dataDir) {
203
+ if (fs.existsSync(dataDir))
204
+ return;
205
+ console.log([
206
+ "",
207
+ ` Welcome to Paddock. Creating a new instance in ${dataDir}`,
208
+ " Projects, chats and settings persist there between runs.",
209
+ "",
210
+ ].join("\n"));
211
+ }
212
+ async function main() {
213
+ const opts = parseArgs(process.argv.slice(2));
214
+ if (opts.help) {
215
+ console.log(USAGE);
216
+ return;
217
+ }
218
+ if (opts.version) {
219
+ console.log(readVersion());
220
+ return;
221
+ }
222
+ checkNodeVersion();
223
+ addBundledBinsToPath();
224
+ // Apply CLI defaults as env vars. This is the whole integration surface: the
225
+ // server resolves config inside `buildApp()`, so anything set before the
226
+ // dynamic import below is picked up with no special-casing in config.ts.
227
+ // Precedence is explicit flag > existing env > our default.
228
+ const dataDir = path.resolve(opts.dataDir ?? process.env.PADDOCK_DATA_DIR ?? path.join(os.homedir(), ".paddock"));
229
+ noteFirstRun(dataDir);
230
+ process.env.PADDOCK_DATA_DIR = dataDir;
231
+ if (opts.port)
232
+ process.env.PORT = opts.port;
233
+ if (opts.host)
234
+ process.env.HOST = opts.host;
235
+ warnIfNoCredentials(process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude"));
236
+ const port = process.env.PORT ?? "4000";
237
+ const host = process.env.HOST ?? "127.0.0.1";
238
+ // Imported dynamically, AFTER the env above is set: a static import would
239
+ // pull in app.ts -> config.ts and resolve config against the wrong data dir.
240
+ const { start } = await import("../start.js");
241
+ await start();
242
+ console.log(`\n Paddock is running at http://${host}:${port}\n Press Ctrl-C to stop.\n`);
243
+ }
244
+ main().catch((err) => {
245
+ console.error("fatal:", err);
246
+ process.exit(1);
247
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * concurrency — bounded fan-out for the routes that touch one file per chat.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * Several endpoints map over every session in a project and open a transcript
7
+ * per session. Written as `Promise.all(sessions.map(...))` that starts EVERY
8
+ * read at once: on a real instance that is 1,515 concurrent read streams, each
9
+ * buffering a transcript that averages ~350 KB. The work is identical either
10
+ * way — the reads just all land in memory at the same moment instead of a few
11
+ * at a time.
12
+ *
13
+ * Measured on that corpus (`GET /chats/usage`, issue #544):
14
+ *
15
+ * | concurrency | wall time | peak RSS |
16
+ * |---|---|---|
17
+ * | unbounded (1,515) | 4,510 ms | 706 MB |
18
+ * | 64 | 4,477 ms | 280 MB |
19
+ * | 16 | 4,329 ms | 178 MB |
20
+ *
21
+ * Bounding it is not a latency trade: the bounded runs were marginally FASTER,
22
+ * because 1,515 interleaved streams cost more in scheduling and GC pressure than
23
+ * they buy in overlap. The disk is not the bottleneck at this size — the parse
24
+ * is, and the parse is single-threaded regardless.
25
+ *
26
+ * ## Why a local copy
27
+ *
28
+ * `@herdctl/core` has this function twice (`state/job-index.ts` and
29
+ * `state/utils/concurrency.ts`) and exports NEITHER from the package root, so it
30
+ * is not reachable from here today. Consolidating and exporting the survivor is
31
+ * filed upstream as **herdctl#421**; when that lands, this module should be
32
+ * deleted and the import repointed at core. The clamping below matches the
33
+ * `state/utils` variant deliberately — the `job-index` copy silently spawns zero
34
+ * workers (and so resolves having done nothing) when `limit <= 0`.
35
+ */
36
+ /**
37
+ * Default bound for per-chat file fan-out.
38
+ *
39
+ * 16 measured *faster* than unbounded on a 1,515-chat project while cutting peak
40
+ * RSS ~4× (706 MB → 178 MB), so there is headroom on both sides of this number;
41
+ * it is not a knife-edge. Kept as a named constant because the next caller — the
42
+ * boot-warm sweep — must use the same bound to get the same guarantee.
43
+ */
44
+ export declare const FILE_FANOUT_CONCURRENCY = 16;
45
+ /**
46
+ * Map over `items` with at most `limit` calls to `fn` in flight, preserving
47
+ * input order in the result.
48
+ *
49
+ * Order-preserving is load-bearing: callers index the result positionally
50
+ * against the input array, so this must behave exactly like `Promise.all(
51
+ * items.map(fn))` in everything except how many calls are live at once.
52
+ *
53
+ * `limit` is clamped to at least 1, so a mis-computed or zero bound degrades to
54
+ * serial execution rather than silently completing without running `fn` at all.
55
+ * Rejections propagate as they would from `Promise.all`.
56
+ */
57
+ export declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * concurrency — bounded fan-out for the routes that touch one file per chat.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * Several endpoints map over every session in a project and open a transcript
7
+ * per session. Written as `Promise.all(sessions.map(...))` that starts EVERY
8
+ * read at once: on a real instance that is 1,515 concurrent read streams, each
9
+ * buffering a transcript that averages ~350 KB. The work is identical either
10
+ * way — the reads just all land in memory at the same moment instead of a few
11
+ * at a time.
12
+ *
13
+ * Measured on that corpus (`GET /chats/usage`, issue #544):
14
+ *
15
+ * | concurrency | wall time | peak RSS |
16
+ * |---|---|---|
17
+ * | unbounded (1,515) | 4,510 ms | 706 MB |
18
+ * | 64 | 4,477 ms | 280 MB |
19
+ * | 16 | 4,329 ms | 178 MB |
20
+ *
21
+ * Bounding it is not a latency trade: the bounded runs were marginally FASTER,
22
+ * because 1,515 interleaved streams cost more in scheduling and GC pressure than
23
+ * they buy in overlap. The disk is not the bottleneck at this size — the parse
24
+ * is, and the parse is single-threaded regardless.
25
+ *
26
+ * ## Why a local copy
27
+ *
28
+ * `@herdctl/core` has this function twice (`state/job-index.ts` and
29
+ * `state/utils/concurrency.ts`) and exports NEITHER from the package root, so it
30
+ * is not reachable from here today. Consolidating and exporting the survivor is
31
+ * filed upstream as **herdctl#421**; when that lands, this module should be
32
+ * deleted and the import repointed at core. The clamping below matches the
33
+ * `state/utils` variant deliberately — the `job-index` copy silently spawns zero
34
+ * workers (and so resolves having done nothing) when `limit <= 0`.
35
+ */
36
+ /**
37
+ * Default bound for per-chat file fan-out.
38
+ *
39
+ * 16 measured *faster* than unbounded on a 1,515-chat project while cutting peak
40
+ * RSS ~4× (706 MB → 178 MB), so there is headroom on both sides of this number;
41
+ * it is not a knife-edge. Kept as a named constant because the next caller — the
42
+ * boot-warm sweep — must use the same bound to get the same guarantee.
43
+ */
44
+ export const FILE_FANOUT_CONCURRENCY = 16;
45
+ /**
46
+ * Map over `items` with at most `limit` calls to `fn` in flight, preserving
47
+ * input order in the result.
48
+ *
49
+ * Order-preserving is load-bearing: callers index the result positionally
50
+ * against the input array, so this must behave exactly like `Promise.all(
51
+ * items.map(fn))` in everything except how many calls are live at once.
52
+ *
53
+ * `limit` is clamped to at least 1, so a mis-computed or zero bound degrades to
54
+ * serial execution rather than silently completing without running `fn` at all.
55
+ * Rejections propagate as they would from `Promise.all`.
56
+ */
57
+ export async function mapWithConcurrency(items, limit, fn) {
58
+ const results = new Array(items.length);
59
+ const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
60
+ let next = 0;
61
+ const worker = async () => {
62
+ for (;;) {
63
+ const index = next++;
64
+ if (index >= items.length)
65
+ return;
66
+ results[index] = await fn(items[index], index);
67
+ }
68
+ };
69
+ await Promise.all(Array.from({ length: workers }, worker));
70
+ return results;
71
+ }