{"id":13628548,"url":"https://github.com/ir33k/gmi100","last_synced_at":"2025-04-10T05:11:15.353Z","repository":{"id":180380603,"uuid":"665048203","full_name":"ir33k/gmi100","owner":"ir33k","description":"Gemini CLI protocol client written in 100 lines of ANSI C","archived":false,"fork":false,"pushed_at":"2024-10-29T08:30:06.000Z","size":12166,"stargazers_count":66,"open_issues_count":1,"forks_count":2,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-10-29T09:50:48.501Z","etag":null,"topics":["c","cli","gemini-client","gemini-protocol","hacker-news"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ir33k.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-11T10:16:21.000Z","updated_at":"2024-10-29T08:30:10.000Z","dependencies_parsed_at":"2024-11-08T19:43:06.450Z","dependency_job_id":null,"html_url":"https://github.com/ir33k/gmi100","commit_stats":null,"previous_names":["ir33k/gmi100"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ir33k%2Fgmi100","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ir33k%2Fgmi100/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ir33k%2Fgmi100/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ir33k%2Fgmi100/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ir33k","download_url":"https://codeload.github.com/ir33k/gmi100/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161276,"owners_count":21057555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","cli","gemini-client","gemini-protocol","hacker-news"],"created_at":"2024-08-01T22:00:53.832Z","updated_at":"2025-04-10T05:11:15.310Z","avatar_url":"https://github.com/ir33k.png","language":"C","funding_links":[],"categories":["C","Clients"],"sub_categories":["Terminal"],"readme":"gmi100\n======\n\nGemini CLI protocol client written in 100 lines of ANSI C.\n\n![demo.gif](demo.gif)\n\nOther similar Gemini client projects written in few lines of code\nsuccessfully shows how simple Gemini protocol is.  This code is far\nfrom straight forward.  But I had a different goal in mind.\n\nI tried to pack as much as possible in 100 lines of ANSI C.  Initially\nI struggled to fit simple TLS connection in such small space but\neventually I ended up with CLI client capable of efficient navigation\nbetween capsules of Gemini space 🚀\n\n[Discussion on Hacker News][3]\n\nBuild, run and usage\n--------------------\n\nRun `build` script or use any C compiler and link with [OpenSSL][0].\n\n\t$ ./build               # Compile on Linux\n\t$ ./gmi100              # Run with default \"less -XI\" pager\n\t$ ./gmi100 more         # Run using \"more\" pager\n\t$ ./gmi100 cat          # Run using \"cat\" as pager\n\n\tgmi100\u003e gemini.circumlunar.space\n\nIn `gmi100\u003e` prompt you can take few actions:\n\n1. Type Gemini URL to visit specific site.\n2. Type a number of link on current capsule, for example: `12`.\n3. Type `q` to quit.\n4. Type `r` to refresh current capsule.\n5. Type `u` to go \"up\" in URL directory path.\n6. Type `b` to go back in browsing history.\n7. Type `c` to print current capsule URI.\n8. Type `?` to search, geminispace.info/search is used by default.\n9. Type shell command prefixed with `!` to run it on current capsule.\n\nEach time you navigate to `text` document the pager program will be\nrun with that file.  By default `less -XI` is used but you can provide\nany other in first program argument.  If your pager is interactive\nlike less the you have to exit from that pager in order to go back to\ngmi100 prompt and navigate to other capsule.\n\nWhen non `text` file is visited, like an image or music then nothing\nwill be displayed but temporary file will be created.  Then you can\nuse any shell command to do something with it.  For example you can\nvisit capsule with video and open it with `mpv`:\n\n\tgmi100\u003e gemini://tilde.team/~konomo/noocat.webm\n\tgmi100\u003e !mpv\n\nOr similar example with image and music.  For example you can use\n`xdg-open` or `open` command to open file with default program for\ngiven MIME type.\n\n\tgmi100\u003e gemini://158.nu/images/full/158/2022-03-13-0013_v1.jpg\n\tgmi100\u003e !xdg-open\n\t\nYou also can use any program on reqular text capsules.  For example\nyou decided that your defauly pager is `cat` but for some capsules you\nwant to use `less`.  Or you want to edit given page in text editor.\nIn summary, you can open currently loaded capsule as file in any\nprogram as long as you don't navigate to other URI.\n\n\tgmi100\u003e gemini.circumlunar.space\n\tgmi100\u003e !less\n\tgmi100\u003e !emacs\n\tgmi100\u003e !firefox\n\tgmi100\u003e !xdg-open\n\n\nHow browsing history works\n--------------------------\n\nBrowsing history in gmi100 works differently than regular \"stack\" way\nthat is commonly used in browsers and other regular modern software.\nIt is inspired by how Emacs handles undo history.  That means with the\nsingle \"back\" button you can go back and forward in browsing history.\nAlso with that you will never loose any page you visited from history\nfile and I was able to write this implementation in only few lines.\n\nAfter you run the program it will open or create history .gmi100 file.\nThen every page you visits that is not a redirection to other page and\ndoesn't ask you for input will be appended at the end of history file.\nFile is never cleaned up by program itself to make history persistent\nbetween sessions but that means cleaning up browsing history is your\nresponsibility.  But this also gives you an control over history file\ncontent.  You can for example append some links that you want to visit\nin next session to have easier access to them just by running program\nand pressing \"b\" which will navigate to last link from history file.\n\nDuring browsing session typing \"b\" in program prompt for the first\ntime will result in navigation to last link in history file.  Then if\nyou type \"b\" again it will open second to last link from history.  But\nit will also append that link at the end.  You can input \"b\" multiple\ntimes and it will always go back by one link in history and append it\nat then end of history file at the same time.  Only if you decide to\nnavigate to other page by typing URL or choosing link number you will\nbreak that cycle.  Then history \"pointer\" will go back to the very\nbottom of the history file.  Example:\n\n\tgmi100 session      pos  .gmi100 history file content\n\t==================  ===  ===============================\n\t\n\tgmi100\u003e                  \u003cEMPTY HISTORY FILE\u003e\n\t\n\tgmi100\u003e tilde.pink  \u003e\u003e\u003e  tilde.pink\n\t\n\tgmi100\u003e 2                tilde.pink\n\t                    \u003e\u003e\u003e  tilde.pink/documentation.gmi\n\t\n\tgmi100\u003e 2                tilde.pink\n\t                         tilde.pink/documentation.gmi\n\t                    \u003e\u003e\u003e  tilde.pink/docs/gemini.gmi\n\t\n\tgmi100\u003e b                tilde.pink\n\t                    \u003e\u003e\u003e  tilde.pink/documentation.gmi\n\t                         tilde.pink/docs/gemini.gmi\n\t                         tilde.pink/documentation.gmi\n\t\n\tgmi100\u003e b           \u003e\u003e\u003e  tilde.pink\n\t                         tilde.pink/documentation.gmi\n\t                         tilde.pink/docs/gemini.gmi\n\t                         tilde.pink/documentation.gmi\n\t                         tilde.pink\n\t\n\tgmi100\u003e 3                tilde.pink\n\t                         tilde.pink/documentation.gmi\n\t                         tilde.pink/docs/gemini.gmi\n\t                         tilde.pink/documentation.gmi\n\t                         tilde.pink\n\t                    \u003e\u003e\u003e  gemini.circumlunar.space/\n\n\nDevlog\n------\n\n### 2023.07.11 Initial motivation and thoughts\n\nAuthors of Gemini protocol claims that it should be possible to write\nGemini client in modern language [in less than 100 lines of code][1].\nThere are few projects that do that in programming languages with\ngarbage collectors, build in dynamic data structures and useful std\nlibraries for string manipulation, parsing URLs etc.\n\nIntuition suggest that such achievement is not possible in plain C.\nEven tho I decided to start this silly project and see how far I can\ngo with just ANSI C, std libraries and one dependency - OpenSSL.\n\nIt took me around 3 weeks of lazy slow programming to get to this\npoint but results exceeded my expectations.  It turned out that it's\nnot only achievable but also it's possible to include many convenient\nfeatures like persistent browsing history, links formatting, wrapping\nof lines, pagination and some error handling.\n\nMy goal was to write in c89 standard avoiding any dirty tricks that\ncould buy me more lines like defining imports and constant values in\ncompiler command or writing multiple things in single line separated\nwith semicolon.  I think that final result can be called a normal C\ncode but OFC it is very dense, hard to read and uses practices that\nare normally not recommended.  Even tho I call it a success.\n\nI was not able to make better line wrapping work.  Ideally lines\nshould wrap at last whitespace that fits within defined boundary and\nrespects wide characters.  The best I could do in given constrains was\nto do a hard line wrap after defined number of bytes.  Yes - bytes, so\nit is possible to split wide character in half at the end of the line.\nIt can ruin ASCII art that uses non ASCII characters and sites written\nmainly without ASCII characters.  This is the only thing that bothers\nme.  Line wrapping itself is very necessary to make pagination and\npagination is necessary to make this program usable on terminals that\ndoes not support scrolling.  Maybe it would be better to somehow\nintegrate gmi100 with pager like \"less\".  Then I don't have to\nimplement pagination and line wrapping at all.  That would be great.\n\nI'm very happy that I was able to make browsing history work using\nexternal file and not and array like in most small implementation I\nhave read.  With that this program is actually usable for me.  I'm\nvery happy about how the history works which is out of the ordinary\nbut I allows to have back and forward navigation with single logic.\nWith that I could fit 2 functionalities in single implementation.\n\nI'm also very happy about links formatting.  Without this small\nadjustment of output text I would not like to use this program for\nactual browsing of Gemini space.\n\nI thought about adding \"default site\" being the Gemini capsule that\nopens by default when you run the program.  But that can be easily\ndone with small shell script or alias so I'm not going to do it.\n\n```sh\necho \"some.default.page.com\" | gmi100\n```\n\nI's amazing how much can fit in 100 lines of C.\n\n### 2023.07.12 - v2.0 the pager\n\nRemoving manual line wrapping and pagination in favor of pager program\nthat can be changed at any time was a great idea.  I love to navigate\nGemini holes with `cat` as pager when I'm in Emacs and with `less -X`\nwhen in terminal.\n\n### 2023.07.12 Wed 19:48 - v2.1 SSL issues and other changes\n\nAfter using gmi100 for some time I noticed that often you stumble upon\na capsule by navigating directly to some distant path pointing at some\ngemlog entry.  But then you want to visit home page of this author.\nWith current setup you would had to type URL by hand if visited page\ndid not provided handy \"Go home\" link.  Then I recalled that many GUI\nbrowsers include \"Up\" and \"Go home\" buttons because you are able to\neasily modify current URI to achieve such navigation.  This was\ntrivial to add in gmi100.  Required only single line that appends\n`../` to current URI.  I added only \"Up\" functionality as navigation\nto \"Home\" can be achieved by using \"Up\" few times in row and I don't\nwant to loose precious lines of code.\n\nMore than that, I changed default pager to `less` as it provides the\nbest experience in terminal and this is what people will use most of\nthe time including me.  For special cases in Emacs I can change pager\nto `cat` with ease anyway.\n\nBack to the main topic.  I had troubles opening many pages from\nspecific domains.  All of those probably run on the same server.  Some\nkind o SSL error, not very specific.  I was able to open those pages\nwith this simple line of code:\n\n```sh\n$ openssl s_client -crlf -ign_eof -quiet -connect senders.io:1965 \u003c\u003c\u003c \"gemini://senders.io:1965/gemlog/\"\n```\n\nWhich means that servers work fine and there is something wrong in my\ncode.  I'm probably missing some SSL setting.\n\n### 2023.07.13 Thu 04:56 - `SSL_ERROR_SSL` error fixed\n\nI finally found it.  I had to use `SSL_set_tlsext_host_name` before\nestablishing connection.  I would not be able to figured it out by\nmyself.  All thanks to source code of project [gplaces][2].  And yes,\nit's 5 am.\n\n### 2023.07.18 Tue 16:42 - v3.0 I am complete! \\m/\n\nIn v3 I completely redesigned core memory handling by switching to\nfiles only.  With that program is now able to handle non text capsules\nthat contains images, music, videos and other.\n\nIn simpler words, server response body is always stored as temporary\nfile.  This file is then passed to pager program if MIME type is of\ntext type.  Else nothing happens but you can invoke any command on\nthis file so you can use `mpv` for media files or PDF viewer for\ndocuments etc.  This also opens a lot of other possibilities.  For\nexample you can easily open currently loaded capsule in different\npager than default or in text editor or you can just use your system\ndefault program with `xdg-open`.  And as log as you don't navigate to\nother capsule you can keep using different commands on that file.\n\nI also added few small useful commands like easy searching with `?`.\nI was trying really hard to also implement handling for local files\nwith `file://` prefix.  But I would have to make links parser somehow\ngeneric.  Right now it depends on SSL functions.  I don't see how to\nfit that in current code structure.  I'm not planning any further\ndevelopment.  I already achieved much more than I initially wanted.\n\nI'm calling this project complete.\n\n\u003e I am complete!  \n\u003e Ha-aaaack  \n\u003e Yes, you are hacked  \n\u003e Overflow stack  \n\u003e Now I'm complete  \n\u003e And my log you debug  \n\u003e This code will be mine  \n\u003e #include in first line  \n\u003e \u003cyou_brought_me_the_lib.h\u003e  \n\u003e And now your shell compile  \n\n### 2023.07.24 Mon 17:53 - Feedback from interwebs\n\nDuring [discussion on Hacker News][3] one user pointed out critical\nbugs and potential errors in code.  Corrections are committed.\nEveryone should be safe now from buffer underflow and memory leak so\nplease disperse as there is nothing to see here and please don't tell\nRust community about it.\n\n### 2023.09.24 Sun 04:11 - Joining the big boys\n\nThanks to [Solderpunk][4] ([web][5]) at 21st of September project\n`gmi100` was added to list of Gemini software on official protocol\n[capsule][6] (and [website][7]).  I'm so proud of my little boy.\n\n\n[0]: https://www.openssl.org/\n[1]: https://gemini.circumlunar.space/docs/faq.gmi\n[2]: https://github.com/dimkr/gplaces/blob/gemini/gplaces.c#L841\n[3]: https://news.ycombinator.com/item?id=36786239\n[4]: gemini://zaibatsu.circumlunar.space/~solderpunk/\n[5]: https://zaibatsu.circumlunar.space/~solderpunk/\n[6]: gemini://geminiprotocol.net/software/\n[7]: https://geminiprotocol.net/software/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fir33k%2Fgmi100","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fir33k%2Fgmi100","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fir33k%2Fgmi100/lists"}