@kortix/sandbox 0.4.1

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 (246) hide show
  1. package/config/customize.sh +143 -0
  2. package/config/kortix-env-setup.sh +25 -0
  3. package/kortix-master/package.json +22 -0
  4. package/kortix-master/src/config.ts +22 -0
  5. package/kortix-master/src/index.ts +44 -0
  6. package/kortix-master/src/routes/env.ts +65 -0
  7. package/kortix-master/src/routes/proxy.ts +108 -0
  8. package/kortix-master/src/routes/update.ts +185 -0
  9. package/kortix-master/src/services/proxy.ts +43 -0
  10. package/kortix-master/src/services/secret-store.ts +156 -0
  11. package/kortix-master/tsconfig.json +14 -0
  12. package/opencode/agents/kortix-browser.md +142 -0
  13. package/opencode/agents/kortix-build.md +62 -0
  14. package/opencode/agents/kortix-explore.md +66 -0
  15. package/opencode/agents/kortix-image-gen.md +33 -0
  16. package/opencode/agents/kortix-main.md +450 -0
  17. package/opencode/agents/kortix-plan.md +100 -0
  18. package/opencode/agents/kortix-research.md +84 -0
  19. package/opencode/agents/kortix-sheets.md +61 -0
  20. package/opencode/agents/kortix-slides.md +64 -0
  21. package/opencode/agents/kortix-web-dev.md +572 -0
  22. package/opencode/commands/email.md +36 -0
  23. package/opencode/commands/init.md +43 -0
  24. package/opencode/commands/journal.md +44 -0
  25. package/opencode/commands/memory-init.md +81 -0
  26. package/opencode/commands/memory-search.md +50 -0
  27. package/opencode/commands/memory-status.md +56 -0
  28. package/opencode/commands/research.md +36 -0
  29. package/opencode/commands/search.md +38 -0
  30. package/opencode/commands/slides.md +32 -0
  31. package/opencode/commands/spreadsheet.md +30 -0
  32. package/opencode/memory.json +37 -0
  33. package/opencode/ocx.jsonc +10 -0
  34. package/opencode/opencode.jsonc +103 -0
  35. package/opencode/package.json +25 -0
  36. package/opencode/patches/apply.sh +19 -0
  37. package/opencode/patches/opencode-pty-spawn.txt +49 -0
  38. package/opencode/plugin/background-agents.ts.disabled +483 -0
  39. package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
  40. package/opencode/plugin/kdco-primitives/index.ts +26 -0
  41. package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
  42. package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
  43. package/opencode/plugin/kdco-primitives/shell.ts +138 -0
  44. package/opencode/plugin/kdco-primitives/temp.ts +36 -0
  45. package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
  46. package/opencode/plugin/kdco-primitives/types.ts +13 -0
  47. package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
  48. package/opencode/plugin/memory.ts +306 -0
  49. package/opencode/plugin/worktree/state.ts +412 -0
  50. package/opencode/plugin/worktree/terminal.ts +1002 -0
  51. package/opencode/plugin/worktree.ts +861 -0
  52. package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
  53. package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
  54. package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
  55. package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
  56. package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
  57. package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
  58. package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
  59. package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
  60. package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
  61. package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
  62. package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
  63. package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
  64. package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
  65. package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
  66. package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
  67. package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
  68. package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
  69. package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
  70. package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
  71. package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
  72. package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
  73. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
  74. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
  75. package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
  76. package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
  77. package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
  78. package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
  79. package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
  80. package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
  81. package/opencode/skills/KORTIX-email/SKILL.md +145 -0
  82. package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
  83. package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
  84. package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
  85. package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
  86. package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
  87. package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
  88. package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
  89. package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
  90. package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
  91. package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
  92. package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
  93. package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
  94. package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
  95. package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
  96. package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
  97. package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
  98. package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
  99. package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
  100. package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
  101. package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
  102. package/opencode/skills/KORTIX-pdf/forms.md +36 -0
  103. package/opencode/skills/KORTIX-pdf/reference.md +105 -0
  104. package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
  105. package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
  106. package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
  107. package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
  108. package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
  109. package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
  110. package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
  111. package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  112. package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
  113. package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
  114. package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
  115. package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
  116. package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
  117. package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
  118. package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
  119. package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
  120. package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
  121. package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
  122. package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
  123. package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
  124. package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
  125. package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
  126. package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
  127. package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
  128. package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
  129. package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
  130. package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
  131. package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
  132. package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
  133. package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
  134. package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
  135. package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
  136. package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
  137. package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
  138. package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
  139. package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
  140. package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
  141. package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
  142. package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
  143. package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
  144. package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
  145. package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
  146. package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
  147. package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
  148. package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
  149. package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
  150. package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
  151. package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
  152. package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
  153. package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
  154. package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
  155. package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
  156. package/opencode/skills/KORTIX-session-search/Untitled +1 -0
  157. package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
  158. package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
  159. package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
  160. package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
  161. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
  162. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
  163. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  164. package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
  165. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  166. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  167. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  168. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  169. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  170. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  171. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  172. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  173. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  174. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  175. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  176. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  177. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  178. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  179. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  180. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  181. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  182. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  183. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  184. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  185. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  186. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  187. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  188. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  189. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  190. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  191. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  192. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  193. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  194. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  195. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  196. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  197. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  198. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  199. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  200. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  201. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  202. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  203. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  204. package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
  205. package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
  206. package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
  207. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
  208. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
  209. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
  210. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
  211. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
  212. package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
  213. package/opencode/tools/image-gen.ts +342 -0
  214. package/opencode/tools/image-search.ts +190 -0
  215. package/opencode/tools/memory-get.ts +168 -0
  216. package/opencode/tools/memory-search.ts +247 -0
  217. package/opencode/tools/presentation-gen.ts +723 -0
  218. package/opencode/tools/scrape-webpage.ts +115 -0
  219. package/opencode/tools/scripts/.python-version +1 -0
  220. package/opencode/tools/scripts/convert_pdf.py +184 -0
  221. package/opencode/tools/scripts/convert_pptx.py +562 -0
  222. package/opencode/tools/scripts/pyproject.toml +11 -0
  223. package/opencode/tools/scripts/uv.lock +287 -0
  224. package/opencode/tools/scripts/validate_slide.py +74 -0
  225. package/opencode/tools/show-user.ts +217 -0
  226. package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
  227. package/opencode/tools/tests/image-gen.test.ts +215 -0
  228. package/opencode/tools/tests/image-search.test.ts +125 -0
  229. package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
  230. package/opencode/tools/tests/presentation-gen.test.ts +389 -0
  231. package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
  232. package/opencode/tools/tests/show-user.test.ts +241 -0
  233. package/opencode/tools/tests/video-gen.test.ts +110 -0
  234. package/opencode/tools/tests/web-search.test.ts +106 -0
  235. package/opencode/tools/video-gen.ts +200 -0
  236. package/opencode/tools/web-search.ts +153 -0
  237. package/opencode/tsconfig.json +29 -0
  238. package/package.json +36 -0
  239. package/patch-agent-browser.js +100 -0
  240. package/postinstall.sh +88 -0
  241. package/services/KORTIX-presentation-viewer/run +37 -0
  242. package/services/agent-browser-viewer/run +48 -0
  243. package/services/kortix-master/run +16 -0
  244. package/services/lss-sync/run +22 -0
  245. package/services/opencode-serve/run +25 -0
  246. package/services/opencode-web/run +21 -0
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env python3
2
+ """eCFR and Federal Register API client for statute/regulation lookup.
3
+
4
+ Look up federal regulations from the Code of Federal Regulations (eCFR)
5
+ and federal statutes. No API key required.
6
+
7
+ Usage:
8
+ # Search eCFR for regulations
9
+ python3 ecfr_lookup.py search "data privacy" --title 16
10
+ python3 ecfr_lookup.py search "employment discrimination"
11
+
12
+ # Get a specific CFR section
13
+ python3 ecfr_lookup.py section 16 444 # 16 C.F.R. § 444
14
+ python3 ecfr_lookup.py section 47 73.609 # 47 C.F.R. § 73.609
15
+
16
+ # Get CFR title structure (table of contents)
17
+ python3 ecfr_lookup.py toc 16 # Title 16 structure
18
+
19
+ # Search Federal Register (proposed rules, final rules)
20
+ python3 ecfr_lookup.py fedreg "artificial intelligence"
21
+
22
+ APIs used:
23
+ eCFR: https://www.ecfr.gov/api/versioner/v1/
24
+ Federal Register: https://www.federalregister.gov/api/v1/
25
+
26
+ No authentication required for either API.
27
+ """
28
+
29
+ import sys
30
+ import json
31
+ import re as _re
32
+ import urllib.request
33
+ import urllib.parse
34
+ import urllib.error
35
+ from datetime import date
36
+
37
+ ECFR_SEARCH_BASE = "https://www.ecfr.gov/api/search/v1"
38
+ ECFR_VERSIONER_BASE = "https://www.ecfr.gov/api/versioner/v1"
39
+ FEDREG_BASE = "https://www.federalregister.gov/api/v1"
40
+
41
+ # Cache for latest available date per title
42
+ _title_dates = {}
43
+
44
+
45
+ def api_get(url, params=None, accept="application/json"):
46
+ """Make a GET request."""
47
+ if params:
48
+ url += "?" + urllib.parse.urlencode(params)
49
+
50
+ req = urllib.request.Request(url, headers={"Accept": accept})
51
+ try:
52
+ with urllib.request.urlopen(req) as resp:
53
+ body = resp.read()
54
+ content_type = resp.headers.get("Content-Type", "")
55
+ if "json" in content_type or accept == "application/json":
56
+ try:
57
+ return json.loads(body)
58
+ except json.JSONDecodeError:
59
+ return {"raw": body.decode("utf-8", errors="replace")[:5000]}
60
+ return {"raw": body.decode("utf-8", errors="replace")[:5000]}
61
+ except urllib.error.HTTPError as e:
62
+ error_body = e.read().decode() if e.fp else ""
63
+ print(json.dumps({
64
+ "error": f"HTTP {e.code}: {e.reason}",
65
+ "detail": error_body[:500],
66
+ "url": url
67
+ }), file=sys.stderr)
68
+ sys.exit(1)
69
+ except urllib.error.URLError as e:
70
+ print(json.dumps({"error": str(e)}), file=sys.stderr)
71
+ sys.exit(1)
72
+
73
+
74
+ def get_latest_date(title_num):
75
+ """Get the latest available date for a CFR title from the versioner API.
76
+
77
+ The eCFR versioner API requires a date <= the title's up_to_date_as_of.
78
+ Returns the up_to_date_as_of date string (YYYY-MM-DD).
79
+ """
80
+ title_num = str(title_num)
81
+ if title_num in _title_dates:
82
+ return _title_dates[title_num]
83
+
84
+ data = api_get(f"{ECFR_VERSIONER_BASE}/titles.json")
85
+ titles = data.get("titles", data) if isinstance(data, dict) else data
86
+ for t in titles:
87
+ num = str(t.get("number", ""))
88
+ d = t.get("up_to_date_as_of", "")
89
+ if d:
90
+ _title_dates[num] = d
91
+
92
+ return _title_dates.get(title_num, date.today().isoformat())
93
+
94
+
95
+ def search_ecfr(query, title=None):
96
+ """Search the eCFR for regulations matching a query.
97
+
98
+ The eCFR search API uses full-text search across all CFR titles.
99
+ """
100
+ url = f"{ECFR_SEARCH_BASE}/results"
101
+ params = {
102
+ "query": query,
103
+ "per_page": 10,
104
+ "page": 1,
105
+ }
106
+ if title:
107
+ params["hierarchy[title]"] = title
108
+
109
+ data = api_get(url, params)
110
+
111
+ results = []
112
+ for r in data.get("results", []):
113
+ hierarchy = r.get("hierarchy", {})
114
+ headings = r.get("headings", {})
115
+ # Strip HTML tags from excerpts
116
+ snippet = _re.sub(r'<[^>]+>', '', r.get("full_text_excerpt", "") or "")
117
+ heading_text = _re.sub(r'<[^>]+>', '', headings.get("section", "") or headings.get("part", "") or "")
118
+ title_num = hierarchy.get("title", "")
119
+ section_num = hierarchy.get("section", "")
120
+ results.append({
121
+ "title": title_num,
122
+ "part": hierarchy.get("part", ""),
123
+ "section": section_num,
124
+ "heading": heading_text,
125
+ "snippet": snippet[:400],
126
+ "cfr_citation": f"{title_num} C.F.R. § {section_num}" if section_num else "",
127
+ "hierarchy": hierarchy,
128
+ })
129
+
130
+ return {
131
+ "count": data.get("meta", {}).get("total_count", len(results)),
132
+ "query": query,
133
+ "results": results,
134
+ }
135
+
136
+
137
+ def get_section(title, section):
138
+ """Get a specific CFR section's full text.
139
+
140
+ Args:
141
+ title: CFR title number (e.g., 16)
142
+ section: Section number (e.g., "444.1" or "73.609")
143
+ """
144
+ as_of = get_latest_date(title)
145
+ # Parse section into part and section
146
+ parts = str(section).split(".")
147
+ part = parts[0]
148
+
149
+ # The full endpoint returns XML; request it and extract text
150
+ url = f"{ECFR_VERSIONER_BASE}/full/{as_of}/title-{title}.xml"
151
+ params = {"part": part}
152
+ if len(parts) > 1:
153
+ params["section"] = section
154
+
155
+ data = api_get(url, params, accept="application/xml")
156
+
157
+ # Extract text from XML/raw response
158
+ raw = data.get("raw", "") if isinstance(data, dict) else str(data)
159
+ # Strip XML tags for readable output
160
+ text = _re.sub(r'<[^>]+>', ' ', raw)
161
+ text = _re.sub(r'\s+', ' ', text).strip()
162
+
163
+ return {
164
+ "citation": f"{title} C.F.R. § {section}",
165
+ "title_number": title,
166
+ "section_number": section,
167
+ "as_of_date": as_of,
168
+ "content": text[:3000],
169
+ }
170
+
171
+
172
+ def get_toc(title):
173
+ """Get table of contents for a CFR title."""
174
+ as_of = get_latest_date(title)
175
+ url = f"{ECFR_VERSIONER_BASE}/structure/{as_of}/title-{title}.json"
176
+ data = api_get(url)
177
+
178
+ # Flatten structure for readability
179
+ def flatten(node, depth=0):
180
+ results = []
181
+ label = node.get("label", "") or node.get("identifier", "")
182
+ label_desc = node.get("label_description", "") or node.get("reserved", "")
183
+ if label:
184
+ prefix = " " * depth
185
+ results.append(f"{prefix}{label}: {label_desc}")
186
+ for child in node.get("children", []):
187
+ results.extend(flatten(child, depth + 1))
188
+ return results
189
+
190
+ lines = flatten(data)
191
+ return {
192
+ "title": title,
193
+ "structure": lines[:100], # Limit output
194
+ "truncated": len(lines) > 100,
195
+ }
196
+
197
+
198
+ def search_federal_register(query, doc_type=None):
199
+ """Search the Federal Register for rules, proposed rules, and notices.
200
+
201
+ Args:
202
+ query: Search terms
203
+ doc_type: "RULE" | "PRORULE" | "NOTICE" | "PRESDOCU" (optional)
204
+ """
205
+ url = f"{FEDREG_BASE}/documents.json"
206
+ params = {
207
+ "conditions[term]": query,
208
+ "per_page": 10,
209
+ "order": "relevance",
210
+ }
211
+ if doc_type:
212
+ params["conditions[type][]"] = doc_type
213
+
214
+ data = api_get(url, params)
215
+
216
+ results = []
217
+ for r in data.get("results", []):
218
+ results.append({
219
+ "title": r.get("title", ""),
220
+ "type": r.get("type", ""),
221
+ "document_number": r.get("document_number", ""),
222
+ "publication_date": r.get("publication_date", ""),
223
+ "agencies": [a.get("name", "") for a in r.get("agencies", [])],
224
+ "abstract": (r.get("abstract", "") or "")[:300],
225
+ "citation": r.get("citation", ""),
226
+ "pdf_url": r.get("pdf_url", ""),
227
+ "html_url": r.get("html_url", ""),
228
+ "fr_citation": f"{r.get('volume', '')} Fed. Reg. {r.get('start_page', '')} ({r.get('publication_date', '')})"
229
+ if r.get("volume") else "",
230
+ })
231
+
232
+ return {
233
+ "count": data.get("count", len(results)),
234
+ "query": query,
235
+ "results": results,
236
+ }
237
+
238
+
239
+ # ─── CLI ────────────────────────────────────────────────────────────────────
240
+
241
+ def main():
242
+ if len(sys.argv) < 2:
243
+ print("Usage:", file=sys.stderr)
244
+ print(" ecfr_lookup.py search <query> [--title <n>]", file=sys.stderr)
245
+ print(" ecfr_lookup.py section <title> <section>", file=sys.stderr)
246
+ print(" ecfr_lookup.py toc <title>", file=sys.stderr)
247
+ print(" ecfr_lookup.py fedreg <query> [--type RULE|PRORULE|NOTICE]", file=sys.stderr)
248
+ sys.exit(1)
249
+
250
+ command = sys.argv[1]
251
+
252
+ if command == "search":
253
+ if len(sys.argv) < 3:
254
+ print("Usage: ecfr_lookup.py search <query> [--title <n>]", file=sys.stderr)
255
+ sys.exit(1)
256
+ query = sys.argv[2]
257
+ title = None
258
+ if "--title" in sys.argv:
259
+ idx = sys.argv.index("--title")
260
+ title = int(sys.argv[idx + 1])
261
+ result = search_ecfr(query, title=title)
262
+ print(json.dumps(result, indent=2))
263
+
264
+ elif command == "section":
265
+ if len(sys.argv) < 4:
266
+ print("Usage: ecfr_lookup.py section <title> <section>", file=sys.stderr)
267
+ sys.exit(1)
268
+ title = sys.argv[2]
269
+ section = sys.argv[3]
270
+ result = get_section(title, section)
271
+ print(json.dumps(result, indent=2))
272
+
273
+ elif command == "toc":
274
+ if len(sys.argv) < 3:
275
+ print("Usage: ecfr_lookup.py toc <title>", file=sys.stderr)
276
+ sys.exit(1)
277
+ title = sys.argv[2]
278
+ result = get_toc(title)
279
+ print(json.dumps(result, indent=2))
280
+
281
+ elif command == "fedreg":
282
+ if len(sys.argv) < 3:
283
+ print("Usage: ecfr_lookup.py fedreg <query>", file=sys.stderr)
284
+ sys.exit(1)
285
+ query = sys.argv[2]
286
+ doc_type = None
287
+ if "--type" in sys.argv:
288
+ idx = sys.argv.index("--type")
289
+ doc_type = sys.argv[idx + 1]
290
+ result = search_federal_register(query, doc_type=doc_type)
291
+ print(json.dumps(result, indent=2))
292
+
293
+ else:
294
+ print(f"Unknown command: {command}", file=sys.stderr)
295
+ sys.exit(1)
296
+
297
+
298
+ if __name__ == "__main__":
299
+ main()