ro 4.2.0 → 4.4.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +29 -8
  3. data/LICENSE +1 -1
  4. data/README.md +274 -111
  5. data/Rakefile +2 -2
  6. data/lib/ro/_lib.rb +18 -6
  7. data/lib/ro/asset.rb +23 -13
  8. data/lib/ro/collection.rb +47 -4
  9. data/lib/ro/config.rb +4 -0
  10. data/lib/ro/error.rb +5 -2
  11. data/lib/ro/html.rb +23 -0
  12. data/lib/ro/html_safe.rb +143 -0
  13. data/lib/ro/methods.rb +95 -38
  14. data/lib/ro/node.rb +78 -35
  15. data/lib/ro/path.rb +4 -0
  16. data/lib/ro/template.rb +62 -22
  17. data/lib/ro/text.rb +120 -0
  18. data/lib/ro.rb +4 -0
  19. data/public/api/ro/index-1.json +997 -79
  20. data/public/api/ro/index.json +997 -79
  21. data/public/api/ro/nerd/fastest-possible-embeddings/index.json +90 -0
  22. data/public/api/ro/nerd/ima/index.json +49 -0
  23. data/public/api/ro/nerd/index/index.json +74 -0
  24. data/public/api/ro/nerd/index-1.json +204 -0
  25. data/public/api/ro/nerd/index.json +194 -0
  26. data/public/api/ro/pages/about/index.json +60 -0
  27. data/public/api/ro/pages/contact/index.json +50 -0
  28. data/public/api/ro/pages/cv/index.json +49 -0
  29. data/public/api/ro/pages/disco/index.json +117 -0
  30. data/public/api/ro/pages/index/index.json +30 -0
  31. data/public/api/ro/pages/index-1.json +366 -0
  32. data/public/api/ro/pages/index.json +356 -0
  33. data/public/api/ro/pages/jess/index.json +62 -0
  34. data/public/api/ro/pages/now/index.json +43 -0
  35. data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +265 -0
  36. data/public/api/ro/posts/facebook-and-global-extremism/index.json +90 -0
  37. data/public/api/ro/posts/index-1.json +461 -79
  38. data/public/api/ro/posts/index.json +461 -79
  39. data/public/api/ro/posts/lemmings-considered-harmful/index.json +49 -0
  40. data/public/api/ro/posts/lost-in-the-desert/index.json +49 -0
  41. data/public/api/ro/posts/mission/index.json +49 -0
  42. data/public/api/ro/posts/return-your-laptop/index.json +61 -0
  43. data/public/ro/nerd/fastest-possible-embeddings/assets/giraffe.jpeg +0 -0
  44. data/public/ro/nerd/fastest-possible-embeddings/assets/let-me-in.jpg +0 -0
  45. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.js +70 -0
  46. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.rs +68 -0
  47. data/public/ro/nerd/fastest-possible-embeddings/assets/terminal.jpg +0 -0
  48. data/public/ro/nerd/fastest-possible-embeddings/attributes.yml +7 -0
  49. data/public/ro/nerd/fastest-possible-embeddings/body.md +266 -0
  50. data/public/ro/nerd/ima/assets/og.jpeg +0 -0
  51. data/public/ro/nerd/ima/attributes.yml +8 -0
  52. data/public/ro/nerd/ima/body.md +22 -0
  53. data/public/ro/nerd/index/assets/giraffe.jpeg +0 -0
  54. data/public/ro/nerd/index/assets/let-me-in.jpg +0 -0
  55. data/public/ro/nerd/index/assets/terminal.jpg +0 -0
  56. data/public/ro/nerd/index/attributes.yml +7 -0
  57. data/public/ro/nerd/index/body.md +130 -0
  58. data/public/ro/pages/about/assets/og.jpeg +0 -0
  59. data/public/ro/pages/about/assets/speak-english-pulp-fiction.gif +0 -0
  60. data/public/ro/pages/about/body.md +40 -0
  61. data/public/ro/pages/contact/assets/giraffe.jpeg +0 -0
  62. data/public/ro/pages/contact/attributes.yml +7 -0
  63. data/public/ro/pages/contact/body.md +9 -0
  64. data/public/ro/pages/cv/assets/ara.jpg +0 -0
  65. data/public/ro/pages/cv/attributes.yml +6 -0
  66. data/public/ro/pages/cv/body.md +122 -0
  67. data/public/ro/pages/disco/assets/disco.jpg +0 -0
  68. data/public/ro/pages/disco/assets/disco.png +0 -0
  69. data/public/ro/pages/disco/assets/speak-english-pulp-fiction.gif +0 -0
  70. data/public/ro/pages/disco/assets/src/environment.md +2354 -0
  71. data/public/ro/pages/disco/assets/src/fortune-500.md +2518 -0
  72. data/public/ro/pages/disco/assets/src/greed.md +2703 -0
  73. data/public/ro/pages/disco/assets/src/up-at-night.md +2337 -0
  74. data/public/ro/pages/disco/attributes.yml +9 -0
  75. data/public/ro/pages/disco/body.md +99 -0
  76. data/public/ro/pages/disco/samples/environment.md +2354 -0
  77. data/public/ro/pages/disco/samples/fortune-500.md +2518 -0
  78. data/public/ro/pages/disco/samples/greed.md +2703 -0
  79. data/public/ro/pages/disco/samples/up-at-night.md +2337 -0
  80. data/public/ro/pages/index/attributes.yml +1 -0
  81. data/public/ro/pages/index/body.md +15 -0
  82. data/public/ro/pages/jess/assets/og.jpg +0 -0
  83. data/public/ro/pages/jess/assets/speak-english-pulp-fiction.gif +0 -0
  84. data/public/ro/pages/jess/attributes.yml +7 -0
  85. data/public/ro/pages/jess/body.md +3 -0
  86. data/public/ro/pages/now/assets/speak-english-pulp-fiction.gif +0 -0
  87. data/public/ro/pages/now/attributes.yml +1 -0
  88. data/public/ro/pages/now/body.md +24 -0
  89. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image1.png +0 -0
  90. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image10.png +0 -0
  91. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image11.png +0 -0
  92. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image12.png +0 -0
  93. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image13.png +0 -0
  94. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image14.png +0 -0
  95. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image15.png +0 -0
  96. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image2.png +0 -0
  97. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image3.png +0 -0
  98. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image4.png +0 -0
  99. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image5.png +0 -0
  100. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image6.png +0 -0
  101. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image7.png +0 -0
  102. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image8.png +0 -0
  103. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image9.png +0 -0
  104. data/public/ro/posts/almost-died-in-an-ice-cave/assets/josh-pointing.jpg +0 -0
  105. data/public/ro/posts/almost-died-in-an-ice-cave/assets/levi-rawr.png +0 -0
  106. data/public/ro/posts/almost-died-in-an-ice-cave/assets/og.jpg +0 -0
  107. data/public/ro/posts/almost-died-in-an-ice-cave/assets/purple-heart.jpg +0 -0
  108. data/public/ro/posts/almost-died-in-an-ice-cave/attributes.yml +6 -0
  109. data/public/ro/posts/almost-died-in-an-ice-cave/body.md +419 -0
  110. data/public/ro/posts/facebook-and-global-extremism/assets/background.html +125 -0
  111. data/public/ro/posts/facebook-and-global-extremism/assets/background.md +95 -0
  112. data/public/ro/posts/facebook-and-global-extremism/assets/og.jpg +0 -0
  113. data/public/ro/posts/facebook-and-global-extremism/assets/prompt.txt +122 -0
  114. data/public/ro/posts/facebook-and-global-extremism/assets/results.md +183 -0
  115. data/public/ro/posts/facebook-and-global-extremism/assets/survey.txt +190 -0
  116. data/public/ro/posts/facebook-and-global-extremism/attributes.yml +7 -0
  117. data/public/ro/posts/facebook-and-global-extremism/body.md +393 -0
  118. data/public/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg +0 -0
  119. data/public/ro/posts/lemmings-considered-harmful/attributes.yml +6 -0
  120. data/public/ro/posts/lemmings-considered-harmful/body.md +43 -0
  121. data/public/ro/posts/lost-in-the-desert/assets/og.jpg +0 -0
  122. data/public/ro/posts/lost-in-the-desert/attributes.yml +6 -0
  123. data/public/ro/posts/lost-in-the-desert/body.md +7 -0
  124. data/public/ro/posts/mission/assets/og.jpg +0 -0
  125. data/public/ro/posts/mission/attributes.yml +6 -0
  126. data/public/ro/posts/mission/body.md +4 -0
  127. data/public/ro/posts/return-your-laptop/assets/og.jpg +0 -0
  128. data/public/ro/posts/return-your-laptop/assets/return-your-laptop.png +0 -0
  129. data/public/ro/posts/return-your-laptop/attributes.yml +6 -0
  130. data/public/ro/posts/return-your-laptop/body.md +58 -0
  131. data/ro.gemspec +178 -49
  132. data/scripts/speedtest.rb +324 -0
  133. data/tmp/gem-details.oe +0 -0
  134. metadata +157 -33
  135. data/public/api/ro/posts/first_post/index.json +0 -52
  136. data/public/api/ro/posts/second_post/index.json +0 -51
  137. data/public/api/ro/posts/third_post/index.json +0 -51
  138. data/public/ro/posts/first_post/assets/foo/bar/baz.jpg +0 -0
  139. data/public/ro/posts/first_post/assets/foo.jpg +0 -0
  140. data/public/ro/posts/first_post/assets/src/foo/bar.rb +0 -3
  141. data/public/ro/posts/first_post/attributes.yml +0 -2
  142. data/public/ro/posts/first_post/blurb.erb.md +0 -7
  143. data/public/ro/posts/first_post/body.md +0 -16
  144. data/public/ro/posts/first_post/testing.txt +0 -3
  145. data/public/ro/posts/second_post/assets/foo/bar/baz.jpg +0 -0
  146. data/public/ro/posts/second_post/assets/foo.jpg +0 -0
  147. data/public/ro/posts/second_post/assets/src/foo/bar.rb +0 -3
  148. data/public/ro/posts/second_post/attributes.yml +0 -2
  149. data/public/ro/posts/second_post/blurb.erb.md +0 -5
  150. data/public/ro/posts/second_post/body.md +0 -16
  151. data/public/ro/posts/third_post/assets/foo/bar/baz.jpg +0 -0
  152. data/public/ro/posts/third_post/assets/foo.jpg +0 -0
  153. data/public/ro/posts/third_post/assets/src/foo/bar.rb +0 -3
  154. data/public/ro/posts/third_post/attributes.yml +0 -2
  155. data/public/ro/posts/third_post/blurb.erb.md +0 -5
  156. data/public/ro/posts/third_post/body.md +0 -16
@@ -0,0 +1,49 @@
1
+ {
2
+ "data": {
3
+ "posts/lemmings-considered-harmful": {
4
+ "og": {
5
+ "image": {
6
+ "url": "/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg"
7
+ },
8
+ "title": "lemmings considered harmful",
9
+ "description": "enlightened lemmings, known as the \"reformers,\" sought to address the issues of overpopulation, disease, and infighting."
10
+ },
11
+ "body": "<div class=\"ro markdown\">\n <h5 id=\"the-rise-and-fall-of-the-lemmings\">the rise and fall of the lemmings</h5>\n\n<p><img src='/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg' alt=\"\" /></p>\n\n<p>in the heart of the tundra, a forgotten corner of the world, a small colony of lemmings thrived. these furry creatures, with their insatiable appetite for grass and their unwavering determination to reproduce, had long been a part of the delicate ecosystem. however, a series of fortunate events, or perhaps unfortunate coincidences, led to an unprecedented boom in their population.</p>\n\n<p>the lemmings, once a modest presence, began to multiply at an alarming rate.\ntheir numbers swelled, their burrows multiplied, and their insatiable hunger\nfor grass depleted the once-abundant fields. the other creatures of the\ntundra, the foxes, the owls, and the hares, watched in growing alarm as\ntheir food sources dwindled. the foxes and owls, simply outnumbered, were\nunable to compete with the overwhelming numbers of lemmings and the hares\nsimply could not find enough food.</p>\n\n<p>the lemmings, oblivious to the plight of their fellow inhabitants, continued their relentless pursuit of reproduction. their population grew exponentially, their tunnels crisscrossing the landscape like a network of subterranean highways. the once-verdant tundra began to show signs of strain, the grass patches turning brown and barren.</p>\n\n<p>the hares, once the dominant herbivores of the tundra, were now forced to compete with the insatiable lemmings for the dwindling food supply. their numbers dwindled, their once-proud herds reduced to scattered individuals.</p>\n\n<p>the lemmings, however, remained unconcerned. their focus remained firmly on reproduction, their tunnels expanding further, their hunger growing more insatiable. the tundra, once a vibrant ecosystem, was now a desolate wasteland, a victim of the lemmings’ unchecked growth.</p>\n\n<p>as the food supply dwindled, the lemmings began to turn on each other. fights erupted over scraps of grass, burrows were invaded, and the once-united colony fractured into warring factions. disease, a consequence of overcrowding and malnutrition, spread through the weakened population.</p>\n\n<p>amidst the chaos, a small group of lemmings emerged, driven by a sense of compassion and a desire to save their kind.</p>\n\n<hr />\n\n<p><strong>these enlightened lemmings, known as the “reformers,” sought to address the issues of overpopulation, disease, and infighting.</strong></p>\n\n<hr />\n\n<p>the reformers, armed with a deep understanding of their society, implemented a series of reforms aimed at improving living conditions and promoting harmony. they established communal granaries to ensure fair distribution of food, constructed improved burrows to prevent disease outbreaks, and promoted peaceful coexistence among the different factions.</p>\n\n<p>the reformers’ efforts yielded positive results. the death rate decreased, the incidence of disease declined, and the overall quality of life for the lemmings improved. however, the underlying problems of overpopulation and resource depletion remained unresolved.</p>\n\n<p>the reformers, blinded by their focus on short-term gains, failed to recognize the long-term consequences of their actions. their interventions, while seemingly beneficial, only served to prolong the inevitable collapse of the lemming population.</p>\n\n<p>as the tundra’s carrying capacity was exceeded, the lemmings’ numbers continued to rise, their impact on the ecosystem intensifying. the once-vibrant ecosystem, now dominated by the lemmings, could no longer sustain life. the foxes, the owls, and the hares, unable to compete with the overwhelming numbers of lemmings, perished.</p>\n\n<p>the lemmings, once a vital part of the tundra’s ecosystem, had become its destroyers. their unchecked growth, fueled by a relentless pursuit of reproduction, had led to the demise of their own kind and the destruction of their habitat.</p>\n\n<blockquote>\n <p>frozen tundra lies<br />\nlemmings’ ghostly shadows dance <br />\nnature’s balance lost</p>\n</blockquote>\n\n</div>\n",
12
+ "assets": {
13
+ "assets/lemming.jpeg": {
14
+ "url": "/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg",
15
+ "path": "posts/lemmings-considered-harmful/assets/lemming.jpeg",
16
+ "size": 8,
17
+ "img": {
18
+ "format": "jpeg",
19
+ "width": 2048,
20
+ "height": 1152
21
+ },
22
+ "src": null
23
+ }
24
+ },
25
+ "_meta": {
26
+ "identifier": "posts/lemmings-considered-harmful",
27
+ "type": "posts",
28
+ "id": "lemmings-considered-harmful",
29
+ "urls": [
30
+ "/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg",
31
+ "/ro/posts/lemmings-considered-harmful/attributes.yml",
32
+ "/ro/posts/lemmings-considered-harmful/body.md"
33
+ ],
34
+ "created_at": "2025-04-23 04:38:29 +0000",
35
+ "updated_at": "2025-04-23 04:38:29 +0000",
36
+ "rel": {
37
+ "curr": "posts/lemmings-considered-harmful",
38
+ "prev": "posts/facebook-and-global-extremism",
39
+ "next": "posts/lost-in-the-desert"
40
+ }
41
+ }
42
+ }
43
+ },
44
+ "_meta": {
45
+ "url": "/ro",
46
+ "type": "posts",
47
+ "id": "lemmings-considered-harmful"
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "data": {
3
+ "posts/lost-in-the-desert": {
4
+ "og": {
5
+ "image": {
6
+ "url": "/media/og/io/og.jpeg"
7
+ },
8
+ "title": null,
9
+ "description": "# WRITEME"
10
+ },
11
+ "body": "<div class=\"ro markdown\">\n <p>following is an AI generated summary of this article, so you won’t have to read it!</p>\n\n<blockquote>\n <h3>”</h3>\n <h1 id=\"fixme\">FIXME</h1>\n <h3 id=\"-1\">”</h3>\n</blockquote>\n\n<h1 id=\"writeme\">WRITEME</h1>\n\n</div>\n",
12
+ "assets": {
13
+ "assets/og.jpg": {
14
+ "url": "/ro/posts/lost-in-the-desert/assets/og.jpg",
15
+ "path": "posts/lost-in-the-desert/assets/og.jpg",
16
+ "size": 8,
17
+ "img": {
18
+ "format": "jpeg",
19
+ "width": 2400,
20
+ "height": 1200
21
+ },
22
+ "src": null
23
+ }
24
+ },
25
+ "_meta": {
26
+ "identifier": "posts/lost-in-the-desert",
27
+ "type": "posts",
28
+ "id": "lost-in-the-desert",
29
+ "urls": [
30
+ "/ro/posts/lost-in-the-desert/assets/og.jpg",
31
+ "/ro/posts/lost-in-the-desert/attributes.yml",
32
+ "/ro/posts/lost-in-the-desert/body.md"
33
+ ],
34
+ "created_at": "2025-04-23 04:38:29 +0000",
35
+ "updated_at": "2025-04-23 04:38:29 +0000",
36
+ "rel": {
37
+ "curr": "posts/lost-in-the-desert",
38
+ "prev": "posts/lemmings-considered-harmful",
39
+ "next": "posts/mission"
40
+ }
41
+ }
42
+ }
43
+ },
44
+ "_meta": {
45
+ "url": "/ro",
46
+ "type": "posts",
47
+ "id": "lost-in-the-desert"
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "data": {
3
+ "posts/mission": {
4
+ "og": {
5
+ "image": {
6
+ "url": "/media/og/io/og.jpeg"
7
+ },
8
+ "title": null,
9
+ "description": "# WRITEME"
10
+ },
11
+ "body": "<div class=\"ro markdown\">\n <p>everyone should have a mission statement. here is mine.</p>\n\n<ul>\n <li><a href=\"/jess\">jess</a> and i wanna shower with tomatoes growing in it, and a dirt\nfloor. like this one.</li>\n</ul>\n\n</div>\n",
12
+ "assets": {
13
+ "assets/og.jpg": {
14
+ "url": "/ro/posts/mission/assets/og.jpg",
15
+ "path": "posts/mission/assets/og.jpg",
16
+ "size": 8,
17
+ "img": {
18
+ "format": "jpeg",
19
+ "width": 2400,
20
+ "height": 1200
21
+ },
22
+ "src": null
23
+ }
24
+ },
25
+ "_meta": {
26
+ "identifier": "posts/mission",
27
+ "type": "posts",
28
+ "id": "mission",
29
+ "urls": [
30
+ "/ro/posts/mission/assets/og.jpg",
31
+ "/ro/posts/mission/attributes.yml",
32
+ "/ro/posts/mission/body.md"
33
+ ],
34
+ "created_at": "2025-04-23 04:38:29 +0000",
35
+ "updated_at": "2025-04-23 04:38:29 +0000",
36
+ "rel": {
37
+ "curr": "posts/mission",
38
+ "prev": "posts/lost-in-the-desert",
39
+ "next": "posts/return-your-laptop"
40
+ }
41
+ }
42
+ }
43
+ },
44
+ "_meta": {
45
+ "url": "/ro",
46
+ "type": "posts",
47
+ "id": "mission"
48
+ }
49
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "data": {
3
+ "posts/return-your-laptop": {
4
+ "og": {
5
+ "image": {
6
+ "url": "/ro/posts/return-your-laptop/assets/og.jpg"
7
+ },
8
+ "title": "Free Laptops For The Very Sacked",
9
+ "description": "Gotten axed recently? Should you keep your laptop? #eat-the-rich"
10
+ },
11
+ "body": "<div class=\"ro markdown\">\n <p>following is an AI generated summary of this article, so you won’t have to read it!</p>\n\n<blockquote>\n <h3>”</h3>\n <p>A user on LinkedIn suggested that terminated employees should <strong>always</strong>\nreturn thier laptops, and that issue was cut and dried.</p>\n\n <p><a href=\"/disco\">disco</a>, a tool for bringing real human subjectivity to AI was then\nused to examine public sentiment about this statement.</p>\n\n <p>The responses varied, with some people feeling that employees should keep the\nlaptops as a small consolation or to help with job searching, while others felt\nthat keeping company property was unethical or a security risk.</p>\n\n <p>Some people suggested that companies offer a buyout option for the laptops,\nwhile others felt that a clean break was best.</p>\n\n <p>The responses highlight the complex and multifaceted nature of the issue, with\nno easy answers or universal agreement.</p>\n <h3 id=\"-1\">”</h3>\n</blockquote>\n\n<p>i recently saw this unsolicited and undirected “suggestion” on linked-in (home\nof the weak sauce), suggesting that the <strong>only</strong> ethical move is to return your\nlaptop when you’ve been fired.</p>\n\n<p><img src='/ro/posts/return-your-laptop/assets/return-your-laptop.png' alt=\"return your laptop\" /></p>\n\n<p>while i do not condone brazen theft or just being a jerk, i personally\nconsidered this post astonishingly tone deaf, in light of the fear so many who\nhave recently been laid off are feeling. for many, a computer, and the\ninternet, are their only lifelines to a potential future.</p>\n\n<p>this situation is one i have experienced personally. i’ve been laid off, a\nfew years back, and went through a very, very difficult period (still to some\ndegree) where getting a job was a strangely recursive scenario - that is to\nsay, i needed money to buy a computer capable enough, to keep up with the\ntimes, to stay sharp, to get a job. this is a real phenomenon experienced by\nmany. a nice laptop for a developer costs a cool $4k.</p>\n\n<p>dan seemed pretty sure of himself so, i decided to run his theory through my\nnew thing, which i am calling <a href=\"/disco\">disco</a>. as you can read for yourself,\ni think the issue is more nuanced than he suggests, and that, in this period\nof history, considering the massive ill will the general public is\nsnowballing, and getting ready to toss at corporations, it’s worth seeing what\nothers might think. fwiw, <a href=\"/disco\">disco</a> lets you ask any question, and then\nget back answers that are informed by real human beings, at scale.</p>\n\n<p>it’s my little attempt to use their weapons against them.</p>\n\n<p>take some time, to review <a href=\"https://gist.github.com/ahoward/9696f63c118f7b9f102b04e5b4196ca5\">these results</a></p>\n\n<p><a href=\"/contact\">what do you think?</a></p>\n\n<p>ps. in a future post, i’ll show you how you can do way, way, way better with\nyour development environment by ditching apple, microsoft, etc. and moving to\nlinux and a chromebook. my current dev box costs me $320 at best buy and i’m\nmore productive than ever.</p>\n\n<p><img src='/ro/posts/return-your-laptop/assets/og.jpg' alt=\"\" /></p>\n\n</div>\n",
12
+ "assets": {
13
+ "assets/og.jpg": {
14
+ "url": "/ro/posts/return-your-laptop/assets/og.jpg",
15
+ "path": "posts/return-your-laptop/assets/og.jpg",
16
+ "size": 8,
17
+ "img": {
18
+ "format": "jpeg",
19
+ "width": 2400,
20
+ "height": 1200
21
+ },
22
+ "src": null
23
+ },
24
+ "assets/return-your-laptop.png": {
25
+ "url": "/ro/posts/return-your-laptop/assets/return-your-laptop.png",
26
+ "path": "posts/return-your-laptop/assets/return-your-laptop.png",
27
+ "size": 8,
28
+ "img": {
29
+ "format": "png",
30
+ "width": 711,
31
+ "height": 1025
32
+ },
33
+ "src": null
34
+ }
35
+ },
36
+ "_meta": {
37
+ "identifier": "posts/return-your-laptop",
38
+ "type": "posts",
39
+ "id": "return-your-laptop",
40
+ "urls": [
41
+ "/ro/posts/return-your-laptop/assets/og.jpg",
42
+ "/ro/posts/return-your-laptop/assets/return-your-laptop.png",
43
+ "/ro/posts/return-your-laptop/attributes.yml",
44
+ "/ro/posts/return-your-laptop/body.md"
45
+ ],
46
+ "created_at": "2025-04-23 04:38:29 +0000",
47
+ "updated_at": "2025-04-23 04:38:29 +0000",
48
+ "rel": {
49
+ "curr": "posts/return-your-laptop",
50
+ "prev": "posts/mission",
51
+ "next": null
52
+ }
53
+ }
54
+ }
55
+ },
56
+ "_meta": {
57
+ "url": "/ro",
58
+ "type": "posts",
59
+ "id": "return-your-laptop"
60
+ }
61
+ }
@@ -0,0 +1,70 @@
1
+ #! /usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ //const { FastEmbed } = require('fastembed');
5
+ const { EmbeddingModel, FlagEmbedding } = require('fastembed');
6
+
7
+ // Function to read input file
8
+ function readInputFile(inputPath) {
9
+ if (inputPath === '-') {
10
+ return fs.readFileSync('/dev/stdin', 'utf8');
11
+ } else {
12
+ return fs.readFileSync(inputPath, 'utf8');
13
+ }
14
+ }
15
+
16
+ // Function to write output file
17
+ function writeOutputFile(outputPath, data) {
18
+ if (outputPath === '-') {
19
+ process.stdout.write(data);
20
+ } else {
21
+ fs.writeFileSync(outputPath, data, 'utf8');
22
+ }
23
+ }
24
+
25
+ // Main function
26
+ async function main() {
27
+ const args = process.argv.slice(2);
28
+
29
+ if (args.length !== 2) {
30
+ console.error('Usage: node program.js <input> <output>');
31
+ process.exit(42);
32
+ }
33
+
34
+ const [inputPath, outputPath] = args;
35
+
36
+ try {
37
+ const inputText = readInputFile(inputPath);
38
+ //const embedder = new FastEmbed();
39
+ const embedder = await FlagEmbedding.init({
40
+ model: EmbeddingModel.BGEBaseEN
41
+ });
42
+
43
+ const documents = [inputText];
44
+
45
+ const embeddings = await embedder.embed(documents);
46
+
47
+ for await (const batch of embeddings) {
48
+ // batch is list of Float32 embeddings(number[][]) with length 2
49
+ //console.dir(batch);
50
+ const embedding = batch[0];
51
+ const embeddingString = "[\n" + embedding.join(",\n") + "\n]\n";
52
+ //const embeddingString = JSON.stringify(embedding);
53
+ writeOutputFile(outputPath, embeddingString);
54
+ process.exit(0);
55
+ };
56
+
57
+ //debugger;
58
+ //console.dir(embedding);
59
+ //const embeddingString = embedding.join(',');
60
+ //writeOutputFile(outputPath, embeddingString);
61
+
62
+ //process.exit(0);
63
+ } catch (error) {
64
+ console.error('Error:', error.message);
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ main();
70
+
@@ -0,0 +1,68 @@
1
+ use fastembed::{TextEmbedding, InitOptions, EmbeddingModel};
2
+ use serde::Serialize;
3
+ use std::env;
4
+ use std::fs;
5
+
6
+ #[derive(Serialize)]
7
+ struct EmbeddingOutput {
8
+ embedding: String,
9
+ }
10
+
11
+ fn main() {
12
+ let args: Vec<String> = env::args().collect();
13
+ if args.len() != 3 {
14
+ eprintln!("Usage: {} <input_file> <output_file>", args[0]);
15
+ std::process::exit(1);
16
+ }
17
+
18
+ let input_file = &args[1];
19
+ let output_file = &args[2];
20
+
21
+ let input_text = match fs::read_to_string(input_file) {
22
+ Ok(content) => content,
23
+ Err(e) => {
24
+ eprintln!("Error reading input file: {}", e);
25
+ std::process::exit(1);
26
+ }
27
+ };
28
+
29
+ let model = TextEmbedding::try_new(
30
+ InitOptions::new(EmbeddingModel::AllMiniLML6V2).with_show_download_progress(true),
31
+ ).expect("Failed to initialize the embedding model");
32
+
33
+ let documents = vec![input_text];
34
+
35
+ let embeddings = match model.embed(documents, None) {
36
+ Ok(embeddings) => embeddings,
37
+ Err(e) => {
38
+ eprintln!("Error generating embedding: {}", e);
39
+ std::process::exit(1);
40
+ }
41
+ };
42
+
43
+ let _embedding = embeddings[0][0];
44
+
45
+ let _embedding_str = embeddings[0]
46
+ .iter()
47
+ .map(|f| f.to_string())
48
+ .collect::<Vec<_>>()
49
+ .join(",");
50
+
51
+ let embedding_str = format!("{}{}{}", "[", _embedding_str, "]");
52
+
53
+ let json = serde_json::to_string(&EmbeddingOutput {
54
+ embedding: embedding_str,
55
+ }).unwrap();
56
+
57
+ //match fs::write(output_file, json) {
58
+ //Ok(_) | Err(_) => todo!(),
59
+ //Ok(_) => eprintln!("Embedding written to {}", output_file),
60
+ //Err(e) => eprintln!("Error writing to output file: {}", e),
61
+ //}
62
+
63
+ if let Err(e) = fs::write(output_file, json) {
64
+ eprintln!("Error writing to output file: {}", e);
65
+ std::process::exit(1);
66
+ }
67
+ }
68
+
@@ -0,0 +1,7 @@
1
+ og:
2
+ title: 'super fast embeddings with rust, javascript, ruby, ollama, and mistral.'
3
+ description: >-
4
+ T.I.L how to generate fast embeddings locally with open source tools
5
+ image:
6
+ url: ./assets/giraffe.jpeg
7
+ width: 2048
@@ -0,0 +1,266 @@
1
+ ### TL;DR;
2
+
3
+ > today i figured out the fasest possible way to generate embeddings locally with open source tools avaiable today
4
+
5
+
6
+ ---
7
+ **update 2025-02-24**
8
+
9
+ @kris (https://github.com/khasinski) helped me with this -> https://gist.github.com/ahoward/2a1d45499ac9e755d802dbcbaf401b71
10
+
11
+ it. is fast #af.
12
+
13
+
14
+ ---
15
+ i tried 4 approaches, based on *extensive* research and lots and lots of testing in my work from the past 8 mos.
16
+
17
+ i've been busy building 'socially informed AI'. essentially, a giant,
18
+ curated, enahanced, and distilled RAG database of over a million conversations
19
+ across a thousand subreddits, domains, etc. it's a vast database of
20
+ a million conversations that can be brought to bear for literally **ANY** task
21
+ one might do with **AI**.
22
+
23
+ i have a beta program now. ping me directly if you want an **API** key. it
24
+ will be developer-first in the immediate future. it's a sharp tool i don't
25
+ want used for the wrong purposes at this juncture. a self serve product is
26
+ in the works... more on that soon.
27
+
28
+ anyhow, let's say i need to generate embeddings for all these conversations.
29
+ what *is* the fastest way?
30
+
31
+ i tried 4 methods:
32
+
33
+ 1. ruby wrapper around https://mistral.ai/
34
+ 2. ruby wrapper on https://ollama.com/
35
+ 3. rust command line program https://github.com/Anush008/fastembed-rs
36
+ 4. node command line programm using https://github.com/Anush008/fastembed-js
37
+
38
+ ##### here is the ruby code to generate embeddings with mistral. very short. very easy. i expected this, to be the slowest, although it took me only 5 minutes to write.
39
+
40
+ ```ruby
41
+ #! /usr/bin/env ruby
42
+
43
+ require 'mistral-ai'
44
+ require 'json'
45
+
46
+ input =
47
+ ARGV.shift
48
+
49
+ prompt =
50
+ input == '-' ? STDIN.read : IO.binread(file)
51
+
52
+ mistral =
53
+ Mistral.new(
54
+ credentials: { api_key: ENV.fetch('MISTRAL_API_KEY') },
55
+ options: {
56
+ connection: { request: { timeout: 420 } },
57
+ }
58
+ )
59
+
60
+ model =
61
+ 'mistral-embed'
62
+
63
+ result =
64
+ mistral.embeddings(
65
+ { model: model,
66
+ input: inputs,
67
+ }
68
+ )
69
+
70
+ embedding =
71
+ result.fetch(:data).fetch(0).fetch(:embedding)
72
+
73
+ json =
74
+ JSON.generate(embedding: embedding)
75
+
76
+ puts json
77
+ ```
78
+ <br>
79
+
80
+ ##### next, i installed https://ollama.com/ and wrote this silly ruby wrapper to use it. bout 3 minutes to write.
81
+
82
+ ```ruby
83
+ #! /usr/bin/env ruby
84
+ require 'json'
85
+
86
+ input =
87
+ ARGV.shift
88
+
89
+ prompt =
90
+ input == '-' ? STDIN.read : IO.binread(file)
91
+
92
+ model =
93
+ "mxbai-embed-large"
94
+
95
+ data =
96
+ {prompt:, model:,}
97
+
98
+ cmd =
99
+ "curl -s 'http://localhost:11434/api/embeddings' -d '#{ data.to_json }'"
100
+
101
+ json =
102
+ IO.popen(cmd).read
103
+
104
+ puts json
105
+ ```
106
+ <br>
107
+
108
+ ##### finally, i wrote a rust and node/javascript version (see below) which were much longer and took a little time to produce, even **with** ai codebots, vs-code, and a pile of shit to install.
109
+
110
+ the numbers. roughly, for a 4 page document, on my linux lappy, which is pretty dang beefy but **not a GPU**
111
+
112
+ ```code
113
+ drawohara@drawohara.dev:disco[main] #=> cat /proc/cpuinfo | mistral-ai
114
+
115
+ The information you provided is the output from a command like `lscpu` or `cat /proc/cpuinfo` on a Linux system, detailing the specifications of each processor core in a multi-core AMD EPYC 7713 64-Core Processor. Here's a breakdown of the key details:
116
+
117
+ ### General Processor Information
118
+ - **Vendor ID**: AuthenticAMD
119
+ - **Model Name**: AMD EPYC 7713 64-Core Processor
120
+ - **CPU Family**: 25
121
+ - **Model**: 1
122
+ - **Stepping**: 1
123
+ - **Microcode**: 0xa0011d1
124
+ - **CPU MHz**: 1999.999 MHz (2 GHz base frequency)
125
+ - **Cache Size**: 512 KB (L2 cache size per core)
126
+ - **Physical ID**: 0 (Indicates it is part of the same physical package)
127
+ - **Siblings**: 4 (Indicates the number of logical processors per core, due to hyper-threading)
128
+ - **CPU Cores**: 4 (Number of cores reported by this logical processor)
129
+
130
+ ### Core-Specific Information
131
+ - **Core ID**: Ranges from 0 to 3 (Indicates the specific core within the physical package)
132
+ - **APIC ID**: Ranges from 0 to 3 (Advanced Programmable Interrupt Controller ID)
133
+ - **Initial APIC ID**: Ranges from 0 to 3
134
+
135
+ ### CPU Features
136
+ - **Flags**:
137
+ - Standard features: `fpu`, `vme`, `de`, `pse`, `tsc`, `msr`, `pae`, `mce`, `cx8`, `apic`, `sep`, `mtrr`, `pge`, `mca`, `cmov`, `pat`, `pse36`, `clflush`, `mmx`, `fxsr`, `sse`, `sse2`, `ss`, `ht`, `syscall`, `nx`, `pdpe1gb`, `rdtscp`, `lm`, `constant_tsc`, `rep_good`, `nopl`, `xtopology`, `nonstop_tsc`, `aperf`, `eagerfpu`, `pni`, `pclmulqdq`, `dtes64`, `monitor`, `ds_cpl`, `vmx`, `est`, `tm2`, `ssse3`, `cx16`, `xtpr`, `pdcm`, `pcid`, `sse4_1`, `sse4_2`, `x2apic`, `movbe`, `popcnt`, `tsc_deadline_timer`, `aes`, `xsave`, `avx`, `f16c`, `rdrand`, `lahf_lm`, `cmp_legacy`, `cr8_legacy`, `abm`, `sse4a`, `misalignsse`, `3dnowprefetch`, `osvw`, `skinit`, `wdt`, `nodeid_msr`, `topoext`, `perfctr_core`, `perfctr_nb`, `bpext`, `ptwrite`, `mwaitx`, `cat_l3`, `cat_l2`, `arat`, `pln`, `mce_am`, `powercnt`, `ssbd`, `ibrs`, `ibpb`, `stibp`, `vmmcall`, `fsgsbase`, `bmi1`, `hle`, `avx2`, `smep`, `bmi2`, `erms`, `invpcid`, `rtm`, `cqm`, `mpx`, `rdseed`, `adx`, `smap`, `clflushopt`, `clwb`, `sha_ni`, `xsaveopt`, `xsavec`, `xgetbv1`, `xsaves`, `avx512f`, `avx512dq`, `avx512pf`, `avx512er`, `avx512cd`, `avx512bw`, `avx512vl`, `prefetchwt1`, `clzero`, `avx512ifma`, `avx512vbmi`, `umip`, `pku`, `ospke`, `waitpkg`, `avx512_vnni`, `avx512bitalg`, `rdpid`, `fsrm`, `vaes`, `vpclmulqdq`, `arch_capabilities`
138
+ - **Bugs**: `sysret_ss_attrs`, `null_seg`, `spectre_v1`, `spectre_v2`, `spec_store_bypass`, `srso`
139
+
140
+ ### Performance and Cache Information
141
+ - **BogoMIPS**: 3999.99 (A relative measure of processor speed)
142
+ - **TLB Size**: 1024 4K pages
143
+ - **Clflush Size**: 64 bytes
144
+ - **Cache Alignment**: 64 bytes
145
+ - **Address Sizes**: 48 bits physical, 48 bits virtual
146
+
147
+ ### Power Management
148
+ - The output does not provide specific details on power management features, but modern CPUs like the EPYC 7713 will have various power management capabilities.
149
+
150
+ ### Summary
151
+ The AMD EPYC 7713 is a high-performance server processor with 64 cores and advanced features such as SMT (Simultaneous Multi-Threading), large cache sizes, and support for a wide range of instruction sets and security features. The output shows that each physical core has 4 logical processors due to hyper-threading, and all cores are part of the same physical package. The processor operates at a base frequency of 2 GHz and includes numerous features and capabilities suitable for enterprise-level computations.
152
+ ```
153
+ <br>
154
+
155
+ ##### the results really surprised me
156
+
157
+ 1. ruby + remote mistral api call = ~ 1.75 seconds per embedding
158
+ 2. ruby + local ollama api call = ~ 1.5 seconds per embedding
159
+ 2. javascript + fastembed-js = ~ 0.7 seconds per embedding
160
+ 2. rust + fastembed-rs = ~ 0.3 seconds per embedding
161
+
162
+ so, here is my math:
163
+
164
+
165
+ ```ruby
166
+
167
+ NUMBER_CONVERSATIONS_IN_DISCO =
168
+ 1_000_000
169
+
170
+ SECONDS_PER_CONVERSATION =
171
+ (0.3 .. 1.7).to_a.sample
172
+
173
+ TOTAL_SECONDS =
174
+ NUMBER_CONVERSATIONS_IN_DISCO * SECONDS_PER_CONVERSATION
175
+
176
+ TOTAL_MINUTES =
177
+ TOTAL_SECONDS / 60
178
+
179
+ TOTAL_HOURS =
180
+ TOTAL_MINUTES / 60
181
+
182
+ TOTAL_DAYS =
183
+ TOTAL_HOURS / 24
184
+
185
+ ```
186
+ <br>
187
+
188
+ basically, with rust + fastembed-js (the fastest): **about 4 days run-time to do all the embeddings**
189
+
190
+ with ruby + mistral-ai (the slowest): **about 20 days run-time to do all the embeddings**
191
+
192
+ so, rust is a clear winner yes? despite a whole tool chain of depenencies needed, compilation steps, integration into ci, etc.?
193
+
194
+ > ##### NOT SO FAST
195
+
196
+ remember, what i showed about my machine. for each of the approaches that run
197
+ locally, include ollama, rust, and javascript, i am
198
+
199
+ **limited by how many cpus i have**
200
+
201
+ through testing, i have found that running about 4 at a time gives the fastest throughput. ymmv.
202
+
203
+ whereas, with mistral, i can run using the `parallel` gem and process **20 at a time with mistral**, due to API limits. and this is *stunningly* easy:
204
+
205
+ ```ruby
206
+
207
+ prompts =
208
+ []
209
+
210
+ rps =
211
+ 20
212
+
213
+ embeddings =
214
+ Parallel.map(prompts, in_processes: rps) do |prompt|
215
+ embedding_for(prompt)
216
+ end
217
+
218
+ ```
219
+ <br>
220
+
221
+ yes, i **do actually have a rate_limter** but, since they take > 1 second each that simple code actually never hits the limit ;-)
222
+
223
+ finally, here is **the main thing**
224
+
225
+ > 4 days / 4
226
+
227
+ **==**
228
+
229
+ > 20 days / 20
230
+
231
+ because math.
232
+
233
+ and, actually, with batching i can make the mistral version quite a bit faster, about 10x.
234
+
235
+ so the moral is this
236
+
237
+ > the simplest and easiest solution was the best.
238
+
239
+ and
240
+
241
+ > premature optimization is the root of all evil.
242
+
243
+ someone [very clever once said that^^^^^!](https://wiki.c2.com/?PrematureOptimization)
244
+
245
+ in nearly 30 years doing both big, and i mean [really big](/nerd) data and science with ruby, i have yet to meet a better [VHLL](https://en.wikipedia.org/wiki/Very_high-level_programming_language) to model my abstractions in, and to get shit done **FAST**
246
+
247
+ for sure, i occasionally will need to drop into rust or c but, those cases, are more rare than you might think and, often, reaching for the 'big guns' too fast just wastes time.
248
+
249
+ certainly, anything in-between a VHLL and a compiled lang is a waste of time,
250
+ money, and effort. here's looking at you javascript and pythong (ssss 🐍)! haha ;-)
251
+
252
+ ps. because i can hear you thinking
253
+
254
+ > i can call APIs in pythong (🐍🐍🐍🐍sssss) and js too
255
+
256
+ and, of course, this is true. especially if you enjoy worst-in-history
257
+ dependency management, security issues, version churn, and really really like
258
+ writing 10x the LOC to accomplish the same damn thing that still blow up with
259
+ horrible stack traces of parallism in production your dev-ops team (fire them)
260
+ still can't figure out no matter how many parameters they tune.
261
+
262
+ next article is about dependency **hell** in pythong. the time i worked at a
263
+ "really great firm" that was "expert in python" (ssssss 🐍🐍🐍🐍🐍) and showed
264
+ them that, depsite 4 months of trying by thier principals, they could not
265
+ manage to have fewer than 4 versions of python running in CI and **no one
266
+ even knew**. but i will save that rant, for another day.
Binary file
@@ -0,0 +1,8 @@
1
+ og:
2
+ title: 'ima /code/fixme && /write/it'
3
+ description: >-
4
+ `ima` is a universal unix filter, prompt library, coding helper, and
5
+ wrtiting assistant that uses mistral and groq AI for hella fast, cheap,
6
+ and simple day-to-day tasks
7
+ image:
8
+ url: ./assets/og.jpeg